updates tool folder (#2905)
* updates tool folder * impliments #2874 changes
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 773 B |
@@ -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;}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 571 B |
Binary file not shown.
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{BA95D3D9-1940-4183-8563-BE617D752D0B}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>CreditsTool</RootNamespace>
|
||||
<AssemblyName>CreditsTool</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
Binary file not shown.
@@ -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 <repo owner> <repo> [authToken (helps with github rate limiter)]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Usage: ./CreditsTool.exe <repo owner> <repo> [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<string, string>();
|
||||
//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<string, string> LoadConfig()
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
if (File.Exists(ConfigPath))
|
||||
foreach (var I in File.ReadAllLines(ConfigPath))
|
||||
if (!String.IsNullOrWhiteSpace(I) && I[0] != '#')
|
||||
{
|
||||
var splits = new List<string>(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<IDictionary<string, object>> 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<IEnumerable<IDictionary<string, object>>>(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<IEnumerable<IDictionary<string, object>>>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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']).': <a href="'.$payload['pull_request']['html_url'].'">'.htmlSpecialChars('#'.$payload['pull_request']['number'].' '.$payload['pull_request']['user']['login'].' - '.$payload['pull_request']['title']).'</a>';
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -57,7 +57,12 @@ validPrefixes = [
|
||||
'spellcheck',
|
||||
'experiment',
|
||||
'tgs',
|
||||
'balance'
|
||||
'balance',
|
||||
'code_imp',
|
||||
'refactor',
|
||||
'config',
|
||||
'admin',
|
||||
'server'
|
||||
]
|
||||
|
||||
def dictToTuples(inp):
|
||||
|
||||
Reference in New Issue
Block a user