diff --git a/html/ban.png b/html/ban.png
new file mode 100644
index 0000000000..54f68f4770
Binary files /dev/null and b/html/ban.png differ
diff --git a/html/changelog.css b/html/changelog.css
index b83a8d4f56..031c1e42c0 100644
--- a/html/changelog.css
+++ b/html/changelog.css
@@ -20,6 +20,11 @@ a img {border:none;}
.imagedel {background-image:url(image-minus.png)}
.spellcheck {background-image:url(spell-check.png)}
.experiment {background-image:url(burn-exclamation.png)}
+.refactor {background-image:url(burn-exclamation.png)}
+.code_imp {background-image:url(coding.png)}
+.config {background-image:url(chrome_wrench.png)}
+.admin {background-image:url(ban.png)}
+.server {background-image:url(hard-hat-exclamation.png)}
.balance {background-image:url(scales.png)}
.sansserif {font-family:Tahoma,sans-serif;font-size:12px;}
.commit {margin-bottom:20px;font-size:100%;font-weight:normal;}
diff --git a/html/chrome-wrench.png b/html/chrome-wrench.png
new file mode 100644
index 0000000000..a64f202074
Binary files /dev/null and b/html/chrome-wrench.png differ
diff --git a/html/coding.png b/html/coding.png
new file mode 100644
index 0000000000..887d6b5068
Binary files /dev/null and b/html/coding.png differ
diff --git a/tools/CreditsTool/CreditsTool.csproj b/tools/CreditsTool/CreditsTool.csproj
new file mode 100644
index 0000000000..68187a0815
--- /dev/null
+++ b/tools/CreditsTool/CreditsTool.csproj
@@ -0,0 +1,47 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BA95D3D9-1940-4183-8563-BE617D752D0B}
+ Exe
+ CreditsTool
+ CreditsTool
+ v4.6.1
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/CreditsTool/CreditsTool.exe b/tools/CreditsTool/CreditsTool.exe
new file mode 100644
index 0000000000..da0187aaf7
Binary files /dev/null and b/tools/CreditsTool/CreditsTool.exe differ
diff --git a/tools/CreditsTool/Program.cs b/tools/CreditsTool/Program.cs
new file mode 100644
index 0000000000..d5dd162412
--- /dev/null
+++ b/tools/CreditsTool/Program.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Net;
+using System.Web.Script.Serialization;
+
+namespace CreditsTool
+{
+ class Program
+ {
+ const string ConfigPath = "remappings.txt";
+ const string OutputLocation = "./credit_pngs";
+ const int world_icon_size = 32;
+ //this downloads all the user images of contributors of a passed github repository
+ //usage ./CreditsTool [authToken (helps with github rate limiter)]
+ static void Main(string[] args)
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Usage: ./CreditsTool.exe [authToken]");
+ return;
+ }
+
+ if (Directory.Exists(OutputLocation))
+ {
+ Console.WriteLine(String.Format("Aborted: {0} exists!", OutputLocation));
+ return;
+ }
+ Directory.CreateDirectory(OutputLocation);
+
+ string repoOwner = args[0],
+ repoName = args[1],
+ authToken = args.Length > 2 ? args[2] : null;
+
+ Console.WriteLine("Querying contributors API...");
+
+ var FirstResponse = GetPageResponse(repoOwner, repoName, authToken, 1);
+
+ Console.WriteLine("Collecting avatar URLs...");
+
+ var LoginAvatars = new Dictionary();
+ //now list the things we want: avatar urls and logins
+ foreach (var I in LoadPages(FirstResponse, repoOwner, repoName, authToken)) {
+ var avurl = (string)I["avatar_url"];
+ LoginAvatars.Add((string)I["login"], String.Format("{0}{1}s={2}", avurl, avurl.Contains("?") ? "&" : "?", world_icon_size));
+ }
+
+ Console.WriteLine(String.Format("Collected info for {0} contributors.", LoginAvatars.Count));
+
+ Console.WriteLine("Remapping github logins...");
+
+ var remaps = LoadConfig();
+
+ Console.WriteLine(String.Format("Downloading and converting avatars to {0} (this will take a while)...", OutputLocation));
+
+ using (var client = new WebClient())
+ {
+ var count = 0;
+ foreach (var I in LoginAvatars)
+ {
+ var writtenFilename = I.Key;
+ if (remaps.TryGetValue(writtenFilename, out string tmp))
+ {
+ if (tmp == "__REMOVE__")
+ continue;
+ writtenFilename = tmp;
+ }
+ using (var stream = new MemoryStream(client.DownloadData(I.Value)))
+ using (var originalBMP = new Bitmap(stream))
+ {
+ if (originalBMP.Width == world_icon_size && originalBMP.Height == world_icon_size) //no need to resize
+ SaveBMP(originalBMP, writtenFilename);
+ else
+ using (var resizedBMP = new Bitmap(originalBMP, new Size(world_icon_size, world_icon_size)))
+ SaveBMP(resizedBMP, writtenFilename);
+ }
+ Console.WriteLine(String.Format("Done {0}.png! {1}%", writtenFilename, (int)((((float)(count + 1)) / LoginAvatars.Count) * 100)));
+ ++count;
+ }
+ }
+ }
+
+ static void SaveBMP(Bitmap bmp, string writtenFilename)
+ {
+ bmp.Save(String.Format("{0}{1}{2}.png", OutputLocation, Path.DirectorySeparatorChar, writtenFilename), ImageFormat.Png);
+ }
+
+ static IDictionary LoadConfig()
+ {
+ var result = new Dictionary();
+ if (File.Exists(ConfigPath))
+ foreach (var I in File.ReadAllLines(ConfigPath))
+ if (!String.IsNullOrWhiteSpace(I) && I[0] != '#')
+ {
+ var splits = new List(I.Split(' '));
+ if (splits.Count >= 1 && !String.IsNullOrEmpty(splits[1]))
+ {
+ var key = splits[0];
+ splits.RemoveAt(0);
+ result.Add(key, String.Join(" ", splits));
+ }
+ }
+ return result;
+ }
+
+ static IEnumerable> LoadPages(WebResponse firstResponse, string repoOwner, string repoName, string authToken)
+ {
+ int numPages = GetNumPagesOfContributors(firstResponse);
+ Console.WriteLine(String.Format("Downloading {0} pages of contributor info...", numPages));
+ //load and combine json for all pages
+ var jss = new JavaScriptSerializer();
+ using (var sr = new StreamReader(firstResponse.GetResponseStream()))
+ foreach (var J in jss.Deserialize>>(sr.ReadToEnd()))
+ yield return J;
+
+ //skip the first
+ for (var I = 2; I <= numPages; ++I)
+ using (var sr = new StreamReader(GetPageResponse(repoOwner, repoName, authToken, I).GetResponseStream()))
+ foreach (var J in jss.Deserialize>>(sr.ReadToEnd()))
+ yield return J;
+ }
+
+ static int GetNumPagesOfContributors(WebResponse response)
+ {
+ var splits = response.Headers["Link"].Split(',');
+ foreach (var I in splits)
+ if (I.Contains("rel=\"last\"")) //our boy
+ {
+ var pagestrIndex = I.IndexOf("&page=") + 6;
+ var closingIndex = I.IndexOf('>', pagestrIndex + 1);
+ var thedroidswerelookingfor = I.Substring(pagestrIndex, closingIndex - pagestrIndex);
+ return Convert.ToInt32(thedroidswerelookingfor);
+ }
+ return 1;
+ }
+
+ static WebResponse GetPageResponse(string repoOwner, string repoName, string authToken, int pageNumber)
+ {
+ HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(String.Format("https://api.github.com/repos/{0}/{1}/contributors?per_page=100&page={2}", repoOwner, repoName, pageNumber));
+ httpWebRequest.Method = WebRequestMethods.Http.Get;
+ httpWebRequest.Accept = "application/json";
+ httpWebRequest.UserAgent = "tgstation-13-credits-tool";
+ if (authToken != null)
+ httpWebRequest.Headers.Add(String.Format("Authorization: token {0}", authToken));
+ return httpWebRequest.GetResponse();
+ }
+ }
+}
diff --git a/tools/CreditsTool/UpdateCreditsDMI.sh b/tools/CreditsTool/UpdateCreditsDMI.sh
new file mode 100644
index 0000000000..89d24e1e8f
--- /dev/null
+++ b/tools/CreditsTool/UpdateCreditsDMI.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+#If you hit github's rate limit, add a 3rd parameter here that is a github personal access token
+./CreditsTool tgstation tgstation
+
+rm ../../icons/credits.dmi
+
+for filename in credit_pngs/*.png; do
+ realname=$(basename "$filename")
+ java -jar ../dmitool/dmitool.jar import ../../icons/credits.dmi "${realname%.*}" "$filename"
+done
+
+rm -rf credit_pngs
diff --git a/tools/WebhookProcessor/github_webhook_processor.php b/tools/WebhookProcessor/github_webhook_processor.php
index dde21e4f43..4e20a7e902 100644
--- a/tools/WebhookProcessor/github_webhook_processor.php
+++ b/tools/WebhookProcessor/github_webhook_processor.php
@@ -15,10 +15,8 @@
*/
-//CONFIG START (all defaults are random examples, do change them)
-//Use single quotes for config options that are strings.
+//CONFIGS ARE IN SECRET.PHP, THESE ARE JUST DEFAULTS!
-//These are all default settings that are described in secret.php
$hookSecret = '08ajh0qj93209qj90jfq932j32r';
$apiKey = '209ab8d879c0f987d06a09b9d879c0f987d06a09b9d8787d0a089c';
$repoOwnerAndName = "tgstation/tgstation";
@@ -33,6 +31,7 @@ $maintainer_team_id = 133041;
$validation = "org";
$validation_count = 1;
$tracked_branch = 'master';
+$require_changelogs = false;
require_once 'secret.php';
@@ -43,6 +42,7 @@ set_error_handler(function($severity, $message, $file, $line) {
set_exception_handler(function($e) {
header('HTTP/1.1 500 Internal Server Error');
echo "Error on line {$e->getLine()}: " . htmlSpecialChars($e->getMessage());
+ file_put_contents('htwebhookerror.log', "Error on line {$e->getLine()}: " . $e->getMessage(), FILE_APPEND);
die();
});
$rawPost = NULL;
@@ -96,6 +96,16 @@ switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) {
case 'pull_request':
handle_pr($payload);
break;
+ case 'pull_request_review':
+ if($payload['action'] == 'submitted'){
+ $lower_state = strtolower($payload['review']['state']);
+ if(($lower_state == 'approved' || $lower_state == 'changes_requested') && is_maintainer($payload, $payload['review']['user']['login'])){
+ $lower_association = strtolower($payload['review']['author_association']);
+ if($lower_association == 'member' || $lower_association == 'contributor' || $lower_association == 'owner')
+ remove_ready_for_review($payload);
+ }
+ }
+ break;
default:
header('HTTP/1.0 404 Not Found');
echo "Event:$_SERVER[HTTP_X_GITHUB_EVENT] Payload:\n";
@@ -103,36 +113,87 @@ switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) {
die();
}
-//rip bs-12
-function tag_pr($payload, $opened) {
+function apisend($url, $method = 'GET', $content = NULL) {
global $apiKey;
-
- //We need to reget the pull_request part of the payload to actually see the mergeable field populated
- //http://stackoverflow.com/questions/30619549/why-does-github-api-return-an-unknown-mergeable-state-in-a-pull-request
+ if (is_array($content))
+ $content = json_encode($content);
+
$scontext = array('http' => array(
- 'method' => 'GET',
+ 'method' => $method,
'header' =>
"Content-type: application/json\r\n".
'Authorization: token ' . $apiKey,
'ignore_errors' => true,
'user_agent' => 'tgstation13.org-Github-Automation-Tools'
));
+ if ($content)
+ $scontext['http']['content'] = $content;
+
+ return file_get_contents($url, false, stream_context_create($scontext));
+}
+function validate_user($payload) {
+ global $validation, $validation_count;
+ $query = array();
+ if (empty($validation))
+ $validation = 'org';
+ switch (strtolower($validation)) {
+ case 'disable':
+ return TRUE;
+ case 'repo':
+ $query['repo'] = $payload['pull_request']['base']['repo']['full_name'];
+ break;
+ default:
+ $query['user'] = $payload['pull_request']['base']['repo']['owner']['login'];
+ break;
+ }
+ $query['author'] = $payload['pull_request']['user']['login'];
+ $query['is'] = 'merged';
+ $querystring = '';
+ foreach($query as $key => $value)
+ $querystring .= ($querystring == '' ? '' : '+') . urlencode($key) . ':' . urlencode($value);
+ $res = apisend('https://api.github.com/search/issues?q='.$querystring);
+ $res = json_decode($res, TRUE);
+ return $res['total_count'] >= (int)$validation_count;
+
+}
+function get_labels($payload){
+ $url = $payload['pull_request']['issue_url'] . '/labels';
+ $existing_labels = json_decode(apisend($url), true);
+ $existing = array();
+ foreach($existing_labels as $label)
+ $existing[] = $label['name'];
+ return $existing;
+}
+
+function check_tag_and_replace($payload, $title_tag, $label, &$array_to_add_label_to){
+ $title = $payload['pull_request']['title'];
+ if(stripos($title, $title_tag) !== FALSE){
+ $array_to_add_label_to[] = $label;
+ $title = trim(str_ireplace($title_tag, '', $title));
+ apisend($payload['pull_request']['url'], 'PATCH', array('title' => $title));
+ return true;
+ }
+ return false;
+}
+
+//rip bs-12
+function tag_pr($payload, $opened) {
+ //get the mergeable state
$url = $payload['pull_request']['url'];
- $payload['pull_request'] = json_decode(file_get_contents($url, false, stream_context_create($scontext)), true);
+ $payload['pull_request'] = json_decode(apisend($url), TRUE);
if($payload['pull_request']['mergeable'] == null) {
//STILL not ready. Give it a bit, then try one more time
sleep(10);
- $payload['pull_request'] = json_decode(file_get_contents($url, false, stream_context_create($scontext)), true);
+ $payload['pull_request'] = json_decode(apisend($url), TRUE);
}
$tags = array();
$title = $payload['pull_request']['title'];
if($opened) { //you only have one shot on these ones so as to not annoy maintainers
- $tags = checkchangelog($payload, true, false);
+ $tags = checkchangelog($payload, false);
- $lowertitle = strtolower($title);
- if(strpos($lowertitle, 'refactor') !== FALSE)
+ if(strpos(strtolower($title), 'refactor') !== FALSE)
$tags[] = 'Refactor';
if(strpos(strtolower($title), 'revert') !== FALSE || strpos(strtolower($title), 'removes') !== FALSE)
@@ -148,7 +209,7 @@ function tag_pr($payload, $opened) {
$tags[] = 'Merge Conflict';
$treetags = array('_maps' => 'Map Edit', 'tools' => 'Tools', 'SQL' => 'SQL');
- $addonlytags = array('icons' => 'Sprites', 'sounds' => 'Sound');
+ $addonlytags = array('icons' => 'Sprites', 'sounds' => 'Sound', 'config' => 'Config Update');
foreach($treetags as $tree => $tag)
if(has_tree_been_edited($payload, $tree))
$tags[] = $tag;
@@ -158,21 +219,14 @@ function tag_pr($payload, $opened) {
if(has_tree_been_edited($payload, $tree))
$tags[] = $tag;
- //only maintners should be able to remove these
- if(strpos($lowertitle, '[dnm]') !== FALSE)
- $tags[] = 'Do Not Merge';
-
- if(strpos($lowertitle, '[wip]') !== FALSE)
- $tags[] = 'Work In Progress';
+ check_tag_and_replace($payload, '[dnm]', 'Do Not Merge', $tags);
+ if(!check_tag_and_replace($payload, '[wip]', 'Work In Progress', $tags))
+ check_tag_and_replace($payload, '[ready]', 'Work In Progress', $remove);
$url = $payload['pull_request']['issue_url'] . '/labels';
- $existing_labels = file_get_contents($url, false, stream_context_create($scontext));
- $existing_labels = json_decode($existing_labels, true);
+ $existing = get_labels($payload);
- $existing = array();
- foreach($existing_labels as $label)
- $existing[] = $label['name'];
$tags = array_merge($tags, $existing);
$tags = array_unique($tags);
$tags = array_diff($tags, $remove);
@@ -181,27 +235,153 @@ function tag_pr($payload, $opened) {
foreach($tags as $t)
$final[] = $t;
- $scontext['http']['method'] = 'PUT';
- $scontext['http']['content'] = json_encode($final);
- echo file_get_contents($url, false, stream_context_create($scontext));
+ echo apisend($url, 'PUT', $final);
+
+ return $final;
+}
+
+function remove_ready_for_review($payload, $labels = null){
+ if($labels == null)
+ $labels = get_labels($payload);
+ $index = array_search('Ready for Review', $labels);
+ if($index !== FALSE)
+ unset($labels[$index]);
+ $url = $payload['pull_request']['issue_url'] . '/labels';
+ apisend($url, 'PUT', $labels);
+}
+
+function dismiss_review($payload, $id, $reason){
+ $content = array('message' => $reason);
+ apisend($payload['pull_request']['url'] . '/reviews/' . $id . '/dismissals', 'PUT', $content);
+}
+
+function get_reviews($payload){
+ return json_decode(apisend($payload['pull_request']['url'] . '/reviews'), true);
+}
+
+function check_ready_for_review($payload, $labels = null){
+ $r4rlabel = 'Ready for Review';
+ $labels_which_should_not_be_ready = array('Do Not Merge', 'Work In Progress', 'Merge Conflict');
+ $has_label_already = false;
+ $should_not_have_label = false;
+ if($labels == null)
+ $labels = get_labels($payload);
+ //if the label is already there we may need to remove it
+ foreach($labels as $L){
+ if(in_array($L, $labels_which_should_not_be_ready))
+ $should_not_have_label = true;
+ if($L == $r4rlabel)
+ $has_label_already = true;
+ }
+
+ if($has_label_already && $should_not_have_label){
+ remove_ready_for_review($payload, $labels, $r4rlabel);
+ return;
+ }
+
+ //find all reviews to see if changes were requested at some point
+ $reviews = get_reviews($payload);
+
+ $reviews_ids_with_changes_requested = array();
+ $dismissed_an_approved_review = false;
+
+ foreach($reviews as $R){
+ $lower_association = strtolower($R['author_association']);
+ if($lower_association == 'member' || $lower_association == 'contributor' || $lower_association == 'owner'){
+ $lower_state = strtolower($R['state']);
+ if($lower_state == 'changes_requested')
+ $reviews_ids_with_changes_requested[] = $R['id'];
+ else if ($lower_state == 'approved'){
+ dismiss_review($payload, $R['id'], 'Out of date review');
+ $dismissed_an_approved_review = true;
+ }
+ }
+ }
+
+ if(!$dismissed_an_approved_review && count($reviews_ids_with_changes_requested) == 0){
+ if($has_label_already)
+ remove_ready_for_review($payload, $labels);
+ return; //no need to be here
+ }
+
+ if(count($reviews_ids_with_changes_requested) > 0){
+ //now get the review comments for the offending reviews
+
+ $review_comments = json_decode(apisend($payload['pull_request']['review_comments_url']), true);
+
+ foreach($review_comments as $C){
+ //make sure they are part of an offending review
+ if(!in_array($C['pull_request_review_id'], $reviews_ids_with_changes_requested))
+ continue;
+
+ //review comments which are outdated have a null position
+ if($C['position'] !== null){
+ if($has_label_already)
+ remove_ready_for_review($payload, $labels);
+ return; //no need to tag
+ }
+ }
+ }
+
+ //finally, add it if necessary
+ if(!$has_label_already){
+ $labels[] = $r4rlabel;
+ $url = $payload['pull_request']['issue_url'] . '/labels';
+ apisend($url, 'PUT', $labels);
+ }
+}
+
+function check_dismiss_changelog_review($payload){
+ global $require_changelog;
+ global $no_changelog;
+
+ if(!$require_changelog)
+ return;
+
+ if(!$no_changelog)
+ checkchangelog($payload, false);
+
+ $review_message = 'Your changelog for this PR is either malformed or non-existent. Please create one to document your changes.';
+
+ $reviews = get_reviews($payload);
+ if($no_changelog){
+ //check and see if we've already have this review
+ foreach($reviews as $R)
+ if($R['body'] == $review_message && strtolower($R['state']) == 'changes_requested')
+ return;
+ //otherwise make it ourself
+ apisend($payload['pull_request']['url'] . '/reviews', 'POST', array('body' => $review_message, 'event' => 'REQUEST_CHANGES'));
+ }
+ else
+ //kill previous reviews
+ foreach($reviews as $R)
+ if($R['body'] == $review_message && strtolower($R['state']) == 'changes_requested')
+ dismiss_review($payload, $R['id'], 'Changelog added/fixed.');
}
function handle_pr($payload) {
+ global $no_changelog;
$action = 'opened';
+ $validated = validate_user($payload);
switch ($payload["action"]) {
case 'opened':
tag_pr($payload, true);
+ if($no_changelog)
+ check_dismiss_changelog_review($payload);
if(get_pr_code_friendliness($payload) < 0){
$balances = pr_balances();
$author = $payload['pull_request']['user']['login'];
- if(isset($balances[$author]) && $balances[$author] < 0)
+ if(isset($balances[$author]) && $balances[$author] < 0 && !is_maintainer($payload, $author))
create_comment($payload, 'You currently have a negative Fix/Feature pull request delta of ' . $balances[$author] . '. Maintainers may close this PR at will. Fixing issues or improving the codebase will improve this score.');
}
break;
case 'edited':
+ check_dismiss_changelog_review($payload);
case 'synchronize':
- tag_pr($payload, false);
+ $labels = tag_pr($payload, false);
+ if($payload['action'] == 'synchronize')
+ check_ready_for_review($payload, $labels);
return;
case 'reopened':
$action = $payload['action'];
@@ -213,7 +393,7 @@ function handle_pr($payload) {
else {
$action = 'merged';
auto_update($payload);
- checkchangelog($payload, true, true);
+ checkchangelog($payload, true);
update_pr_balance($payload);
$validated = TRUE; //pr merged events always get announced.
}
@@ -226,7 +406,11 @@ function handle_pr($payload) {
echo "PR Announcement Halted; Secret tag detected.\n";
return;
}
-
+ if (!$validated) {
+ echo "PR Announcement Halted; User not validated.\n";
+ return;
+ }
+
$msg = '['.$payload['pull_request']['base']['repo']['full_name'].'] Pull Request '.$action.' by '.htmlSpecialChars($payload['sender']['login']).': '.htmlSpecialChars('#'.$payload['pull_request']['number'].' '.$payload['pull_request']['user']['login'].' - '.$payload['pull_request']['title']).'';
sendtoallservers('?announce='.urlencode($msg), $payload);
}
@@ -306,7 +490,7 @@ function is_maintainer($payload, $author){
global $maintainer_team_id;
$repo_is_org = $payload['pull_request']['base']['repo']['owner']['type'] == 'Organization';
if($maintainer_team_id == null || !$repo_is_org) {
- $collaburl = $payload['pull_request']['base']['repo']['collaborators_url'] . '/' . $author . '/permissions';
+ $collaburl = str_replace('{/collaborator}', '/' . $author, $payload['pull_request']['base']['repo']['collaborators_url']) . '/permission';
$perms = json_decode(apisend($collaburl), true);
$permlevel = $perms['permission'];
return $permlevel == 'admin' || $permlevel == 'write';
@@ -314,7 +498,7 @@ function is_maintainer($payload, $author){
else {
$check_url = 'https://api.github.com/teams/' . $maintainer_team_id . '/memberships/' . $author;
$result = json_decode(apisend($check_url), true);
- return isset($result['state']); //this field won't be here if they aren't a member
+ return isset($result['state']) && $result['state'] == 'active';
}
}
@@ -325,17 +509,17 @@ function update_pr_balance($payload) {
if(!$trackPRBalance)
return;
$author = $payload['pull_request']['user']['login'];
- if(is_maintainer($payload, $author)) //immune
- return;
$balances = pr_balances();
if(!isset($balances[$author]))
$balances[$author] = $startingPRBalance;
$friendliness = get_pr_code_friendliness($payload, $balances[$author]);
$balances[$author] += $friendliness;
- if($balances[$author] < 0 && $friendliness < 0)
- create_comment($payload, 'Your Fix/Feature pull request delta is currently below zero (' . $balances[$author] . '). Maintainers may close future Feature/Tweak/Balance PRs. Fixing issues or helping to improve the codebase will raise this score.');
- else if($balances[$author] >= 0 && ($balances[$author] - $friendliness) < 0)
- create_comment($payload, 'Your Fix/Feature pull request delta is now above zero (' . $balances[$author] . '). Feel free to make Feature/Tweak/Balance PRs.');
+ if(!is_maintainer($payload, $author)){ //immune
+ if($balances[$author] < 0 && $friendliness < 0)
+ create_comment($payload, 'Your Fix/Feature pull request delta is currently below zero (' . $balances[$author] . '). Maintainers may close future Feature/Tweak/Balance PRs. Fixing issues or helping to improve the codebase will raise this score.');
+ else if($balances[$author] >= 0 && ($balances[$author] - $friendliness) < 0)
+ create_comment($payload, 'Your Fix/Feature pull request delta is now above zero (' . $balances[$author] . '). Feel free to make Feature/Tweak/Balance PRs.');
+ }
$balances_file = fopen(pr_balance_json_path(), 'w');
fwrite($balances_file, json_encode($balances));
fclose($balances_file);
@@ -372,10 +556,9 @@ function has_tree_been_edited($payload, $tree){
return $github_diff !== FALSE && strpos($github_diff, 'diff --git a/' . $tree) !== FALSE;
}
-function checkchangelog($payload, $merge = false, $compile = true) {
- global $apiKey;
- if (!$merge)
- return;
+$no_changelog = false;
+function checkchangelog($payload, $compile = true) {
+ global $no_changelog;
if (!isset($payload['pull_request']) || !isset($payload['pull_request']['body'])) {
return;
}
@@ -449,10 +632,6 @@ function checkchangelog($payload, $merge = false, $compile = true) {
$currentchangelogblock[] = array('type' => 'bugfix', 'body' => $item);
}
break;
- case 'wip':
- if($item != 'added a few works in progress')
- $currentchangelogblock[] = array('type' => 'wip', 'body' => $item);
- break;
case 'rsctweak':
case 'tweaks':
case 'tweak':
@@ -510,11 +689,6 @@ function checkchangelog($payload, $merge = false, $compile = true) {
$currentchangelogblock[] = array('type' => 'spellcheck', 'body' => $item);
}
break;
- case 'experimental':
- case 'experiment':
- if($item != 'added an experimental thingy')
- $currentchangelogblock[] = array('type' => 'experiment', 'body' => $item);
- break;
case 'balance':
case 'rebalance':
if($item != 'rebalanced something'){
@@ -525,6 +699,35 @@ function checkchangelog($payload, $merge = false, $compile = true) {
case 'tgs':
$currentchangelogblock[] = array('type' => 'tgs', 'body' => $item);
break;
+ case 'code_imp':
+ case 'code':
+ if($item != 'changed some code'){
+ $tags[] = 'Code Improvement';
+ $currentchangelogblock[] = array('type' => 'code_imp', 'body' => $item);
+ }
+ break;
+ case 'refactor':
+ if($item != 'refactored some code'){
+ $tags[] = 'Refactor';
+ $currentchangelogblock[] = array('type' => 'refactor', 'body' => $item);
+ }
+ break;
+ case 'config':
+ if($item != 'changed some config setting'){
+ $tags[] = 'Config Update';
+ $currentchangelogblock[] = array('type' => 'config', 'body' => $item);
+ }
+ break;
+ case 'admin':
+ if($item != 'messed with admin stuff'){
+ $tags[] = 'Administration';
+ $currentchangelogblock[] = array('type' => 'admin', 'body' => $item);
+ }
+ break;
+ case 'server':
+ if($item != 'something server ops should know')
+ $currentchangelogblock[] = array('type' => 'server', 'body' => $item);
+ break;
default:
//we add it to the last changelog entry as a separate line
if (count($currentchangelogblock) > 0)
@@ -533,7 +736,10 @@ function checkchangelog($payload, $merge = false, $compile = true) {
}
}
- if (!count($changelogbody) || !$compile)
+ if(!count($changelogbody))
+ $no_changelog = true;
+
+ if ($no_changelog || !$compile)
return $tags;
$file = 'author: "'.trim(str_replace(array("\\", '"'), array("\\\\", "\\\""), $username)).'"'."\n";
@@ -550,17 +756,9 @@ function checkchangelog($payload, $merge = false, $compile = true) {
'message' => 'Automatic changelog generation for PR #'.$payload['pull_request']['number'].' [ci skip]',
'content' => base64_encode($file)
);
- $scontext = array('http' => array(
- 'method' => 'PUT',
- 'header' =>
- "Content-type: application/json\r\n".
- 'Authorization: token ' . $apiKey,
- 'content' => json_encode($content),
- 'ignore_errors' => true,
- 'user_agent' => 'tgstation13.org-Github-Automation-Tools'
- ));
+
$filename = '/html/changelogs/AutoChangeLog-pr-'.$payload['pull_request']['number'].'.yml';
- echo file_get_contents($payload['pull_request']['base']['repo']['url'].'/contents'.$filename, false, stream_context_create($scontext));
+ echo apisend($payload['pull_request']['base']['repo']['url'].'/contents'.$filename, 'PUT', $content);
}
function sendtoallservers($str, $payload = null) {
diff --git a/tools/WebhookProcessor/secret.php b/tools/WebhookProcessor/secret.php
index abea10564f..93c9b318f6 100644
--- a/tools/WebhookProcessor/secret.php
+++ b/tools/WebhookProcessor/secret.php
@@ -8,6 +8,7 @@
$hookSecret = '08ajh0qj93209qj90jfq932j32r';
//Api key for pushing changelogs.
+//This requires the public_repo (or repo for private repositories) and read:org permissions
$apiKey = '209ab8d879c0f987d06a09b9d879c0f987d06a09b9d8787d0a089c';
//Used to prevent potential RCEs
@@ -47,3 +48,6 @@ $validation = "org";
//how many merged prs must they have under the rules above to have their pr announced to the game servers.
$validation_count = 1;
+
+//enforce changelogs on PRs
+$require_changelogs = false;
diff --git a/tools/ss13_genchangelog.py b/tools/ss13_genchangelog.py
index c4e22014df..c7a31325b1 100644
--- a/tools/ss13_genchangelog.py
+++ b/tools/ss13_genchangelog.py
@@ -57,7 +57,12 @@ validPrefixes = [
'spellcheck',
'experiment',
'tgs',
- 'balance'
+ 'balance',
+ 'code_imp',
+ 'refactor',
+ 'config',
+ 'admin',
+ 'server'
]
def dictToTuples(inp):