提交 1f0a9a45 编写于 作者: A Andy Gocke 提交者: GitHub

Merge pull request #17555 from agocke/new-gh-merge-tool

Add a new Azure Function-based merge tool

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26206.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GithubMergeTool", "GithubMergeTool\GithubMergeTool.csproj", "{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|x64.ActiveCfg = Debug|x64
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|x64.Build.0 = Debug|x64
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|x86.ActiveCfg = Debug|x86
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Debug|x86.Build.0 = Debug|x86
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|Any CPU.Build.0 = Release|Any CPU
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|x64.ActiveCfg = Release|x64
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|x64.Build.0 = Release|x64
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|x86.ActiveCfg = Release|x86
{EE2E9B45-BCB6-4CFB-A8CD-93B9F3535F86}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace GithubMergeTool
{
public class GithubMergeTool
{
private static readonly Uri GithubBaseUri = new Uri("https://api.github.com/");
private readonly HttpClient _client;
public GithubMergeTool(
string username,
string password)
{
var client = new HttpClient();
client.BaseAddress = GithubBaseUri;
var authArray = Encoding.ASCII.GetBytes($"{username}:{password}");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(authArray));
client.DefaultRequestHeaders.Add(
"user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2;)");
_client = client;
}
/// <summary>
/// Create a merge PR.
/// </summary>
/// <returns>
/// null if the merge PR was completed without error. Otherwise,
/// the response which had an error is returned. Note that a response
/// with an <see cref="HttpStatusCode" /> value 422 is returned if a PR
/// cannot be created because the <paramref name="srcBranch"/> creates
/// all the commits in the <paramref name="destBranch"/>.
/// </returns>
public async Task<HttpResponseMessage> CreateMergePr(
string repoOwner,
string repoName,
string srcBranch,
string destBranch)
{
// Get the SHA for the source branch
var response = await _client.GetAsync($"repos/{repoOwner}/{repoName}/git/refs/heads/{srcBranch}");
if (response.StatusCode != HttpStatusCode.OK)
{
return response;
}
var jsonBody = JObject.Parse(await response.Content.ReadAsStringAsync());
if (jsonBody.Type == JTokenType.Array)
{
// Branch doesn't exist
return response;
}
var srcSha = ((JValue)jsonBody["object"]["sha"]).ToObject<string>();
// Generate a new branch name for the merge branch
var prBranchName = $"merges/{srcBranch}-to-{destBranch}-{DateTime.UtcNow.ToString("yyyMMdd-HHmmss")}";
// Create a branch on the repo
var body = $@"
{{
""ref"": ""refs/heads/{prBranchName}"",
""sha"": ""{srcSha}""
}}";
response = await _client.PostAsyncAsJson($"repos/{repoOwner}/{repoName}/git/refs", body);
if (response.StatusCode != HttpStatusCode.Created)
{
return response;
}
const string newLine = @"
";
var prMessage = $@"
This is an automatically generated pull request from {srcBranch} into {destBranch}.
``` bash
git fetch --all
git checkout {prBranchName}
git reset --hard upstream/{destBranch}
git merge upstream/{srcBranch}
# Fix merge conflicts
git commit
git push {prBranchName} --force
```
Once all conflicts are resolved and all the tests pass, you are free to merge the pull request.";
prMessage = prMessage.Replace(newLine, "\\n");
// Create a PR from the new branch to the dest
body = $@"
{{
""title"": ""Merge {srcBranch} to {destBranch}"",
""body"": ""{prMessage}"",
""head"": ""{prBranchName}"",
""base"": ""{destBranch}""
}}";
response = await _client.PostAsyncAsJson($"repos/{repoOwner}/{repoName}/pulls", body);
jsonBody = JObject.Parse(await response.Content.ReadAsStringAsync());
// 422 (Unprocessable Entity) indicates there were no commits to merge
if (response.StatusCode == (HttpStatusCode)422)
{
return response;
}
return null;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net46</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace GithubMergeTool
{
internal static class HttpClientExtensions
{
public static Task<HttpResponseMessage> PostAsyncAsJson(this HttpClient client, string requestUri, string body)
=> client.PostAsync(requestUri, new StringContent(body, Encoding.UTF8, "application/json"));
}
}
{
"profiles": {
"GithubMergeTool": {
"commandName": "Executable",
"executablePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\Roslyn\\csi.exe",
"commandLineArgs": "MakeMergePRs.csx",
"workingDirectory": "bin/Debug/net46"
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#r "System.Web"
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Web.Configuration;
using static System.Environment;
private const string KeyVaultUrl = "https://roslyninfra.vault.azure.net:443";
public static async Task<string> GetSecret(string secretName)
{
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));
var secret = await kv.GetSecretAsync(KeyVaultUrl, secretName);
return secret.Value;
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var ctx = new AuthenticationContext(authority);
var clientId = WebConfigurationManager.AppSettings["ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["ClientSecret"];
var clientCred = new ClientCredential(clientId, clientSecret);
var authResult = await ctx.AcquireTokenAsync(resource, clientCred);
return authResult.AccessToken;
}
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#r "GithubMergeTool.dll"
#load "auth.csx"
using System;
using System.Net;
using System.Threading.Tasks;
private static string VslsnapGithubAuthToken = null;
private static TraceWriter Log = null;
private static async Task MakeGithubPr(string repoOwner, string repoName, string srcBranch, string destBranch)
{
var gh = new GithubMergeTool.GithubMergeTool("vslsnap@users.noreply.github.com", VslsnapGithubAuthToken);
Log.Info($"Merging from {srcBranch} to {destBranch}");
var result = await gh.CreateMergePr(repoOwner, repoName, srcBranch, destBranch);
if (result != null)
{
if (result.StatusCode == (HttpStatusCode)422)
{
Log.Info("PR not created -- all commits are present in base branch");
}
else
{
Log.Error($"Error creating PR. GH response code: {result.StatusCode}");
}
}
else
{
Log.Info("PR created successfully");
}
}
private static Task MakeRoslynPr(string srcBranch, string destBranch)
=> MakeGithubPr("dotnet", "roslyn", srcBranch, destBranch);
private static async Task RunAsync()
{
VslsnapGithubAuthToken = await GetSecret("vslsnap-github-auth-token");
// Roslyn branches
await MakeRoslynPr("dev15.0.x", "dev15.1.x");
await MakeRoslynPr("dev15.1.x", "master");
await MakeRoslynPr("master", "dev16");
}
public static void Run(TimerInfo myTimer, TraceWriter log)
{
Log = log;
log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
RunAsync().GetAwaiter().GetResult();
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册