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):