Skip to content

Commit 286ec63

Browse files
authored
Merge pull request #4 from openrails/feature/auto-merge
Automatic merge of open pull requests
2 parents 827dc4b + edc2c91 commit 286ec63

File tree

2 files changed

+243
-3
lines changed

2 files changed

+243
-3
lines changed

Git/Project.cs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace Open_Rails_Code_Bot.Git
9+
{
10+
public class Project
11+
{
12+
string GitPath;
13+
bool Verbose;
14+
15+
public Project(string gitPath, bool verbose)
16+
{
17+
GitPath = gitPath;
18+
Verbose = verbose;
19+
}
20+
21+
public void Init(string repository)
22+
{
23+
if (!Directory.Exists(GitPath))
24+
{
25+
Directory.CreateDirectory(GitPath);
26+
RunCommand($"clone --mirror {repository} .");
27+
}
28+
}
29+
30+
public void Fetch()
31+
{
32+
RunCommand("fetch --update-head-ok");
33+
}
34+
35+
public string ParseRef(string reference)
36+
{
37+
foreach (var line in GetCommandOutput($"rev-parse {reference}"))
38+
{
39+
return line;
40+
}
41+
throw new ApplicationException("Unable to find ref");
42+
}
43+
44+
public string GetAbbreviatedCommit(string reference)
45+
{
46+
foreach (var line in GetCommandOutput($"log --format=%h -1 {reference}"))
47+
{
48+
return line;
49+
}
50+
throw new ApplicationException("Unable to find ref");
51+
}
52+
53+
public string Describe(string options)
54+
{
55+
foreach (var line in GetCommandOutput($"describe {options}"))
56+
{
57+
return line;
58+
}
59+
throw new ApplicationException("Unable to describe commit");
60+
}
61+
62+
public string CommitTree(string treeRef, IEnumerable<string> parentRefs, string message)
63+
{
64+
var tempFile = Path.GetTempFileName();
65+
File.WriteAllText(tempFile, message);
66+
try {
67+
var parents = String.Join(" ", parentRefs.Select(parentRef => $"-p {parentRef}"));
68+
foreach (var line in GetCommandOutput($"commit-tree {treeRef} {parents} -F {tempFile}"))
69+
{
70+
return line;
71+
}
72+
throw new ApplicationException("Unable to describe commit");
73+
}
74+
finally
75+
{
76+
File.Delete(tempFile);
77+
}
78+
}
79+
80+
public DateTimeOffset GetCommitDate(string reference)
81+
{
82+
foreach (var line in GetCommandOutput($"cat-file -p {reference}"))
83+
{
84+
if (line.StartsWith("author "))
85+
{
86+
return DateTimeOffset.FromUnixTimeSeconds(long.Parse(line.Split("> ")[1].Split(" ")[0]));
87+
}
88+
}
89+
throw new ApplicationException("Unable to describe commit");
90+
}
91+
92+
public void Checkout(string reference)
93+
{
94+
RunCommand($"checkout --quiet {reference}");
95+
}
96+
97+
public void CheckoutDetached(string reference)
98+
{
99+
RunCommand($"checkout --quiet --detach {reference}");
100+
}
101+
102+
public void ResetHard()
103+
{
104+
RunCommand("reset --hard");
105+
}
106+
107+
public void Merge(string reference)
108+
{
109+
RunCommand($"merge --no-edit --no-ff {reference}");
110+
}
111+
112+
public void SetBranchRef(string branch, string reference)
113+
{
114+
RunCommand($"branch -f {branch} {reference}");
115+
}
116+
117+
void RunCommand(string command)
118+
{
119+
foreach (var line in GetCommandOutput(command))
120+
{
121+
}
122+
}
123+
124+
IEnumerable<string> GetCommandOutput(string command)
125+
{
126+
var args = $"--no-pager {command}";
127+
if (Verbose)
128+
{
129+
Console.WriteLine("```shell");
130+
Console.WriteLine($"{GitPath}> git {args}");
131+
}
132+
var git = Process.Start(new ProcessStartInfo()
133+
{
134+
WorkingDirectory = GitPath,
135+
FileName = "git",
136+
Arguments = args,
137+
StandardOutputEncoding = Encoding.UTF8,
138+
RedirectStandardOutput = true,
139+
});
140+
while (!git.StandardOutput.EndOfStream)
141+
{
142+
yield return git.StandardOutput.ReadLine();
143+
}
144+
git.WaitForExit();
145+
if (git.ExitCode != 0)
146+
{
147+
throw new ApplicationException($"git {command} failed: {git.ExitCode}");
148+
}
149+
if (Verbose)
150+
{
151+
Console.WriteLine("```");
152+
}
153+
}
154+
}
155+
}

Program.cs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ static async Task AsyncMain(IConfigurationRoot config)
4343
var gitHubConfig = config.GetSection("github");
4444
var query = new Query(gitHubConfig["token"]);
4545

46+
Console.WriteLine($"GitHub organisation: {gitHubConfig["organization"]}");
47+
Console.WriteLine($"GitHub team: {gitHubConfig["team"]}");
48+
Console.WriteLine($"GitHub repository: {gitHubConfig["repository"]}");
49+
Console.WriteLine($"GitHub base branch: {gitHubConfig["baseBranch"]}");
50+
Console.WriteLine($"GitHub merge branch: {gitHubConfig["mergeBranch"]}");
51+
4652
var members = await query.GetTeamMembers(gitHubConfig["organization"], gitHubConfig["team"]);
47-
Console.WriteLine($"Org '{gitHubConfig["organization"]}' team '{gitHubConfig["team"]}' members ({members.Count})");
53+
Console.WriteLine($"Team members ({members.Count}):");
4854
foreach (var member in members)
4955
{
5056
Console.WriteLine($" {member.Login}");
@@ -53,7 +59,7 @@ static async Task AsyncMain(IConfigurationRoot config)
5359

5460
var pullRequests = await query.GetOpenPullRequests(gitHubConfig["organization"], gitHubConfig["repository"]);
5561
var autoMergePullRequests = new List<GraphPullRequest>();
56-
Console.WriteLine($"Org '{gitHubConfig["organization"]}' repo '{gitHubConfig["repository"]}' open pull requests ({pullRequests.Count})");
62+
Console.WriteLine($"Open pull requests ({pullRequests.Count}):");
5763
foreach (var pullRequest in pullRequests)
5864
{
5965
var autoMerge = memberLogins.Contains(pullRequest.Author.Login)
@@ -69,11 +75,90 @@ static async Task AsyncMain(IConfigurationRoot config)
6975
}
7076
}
7177

72-
Console.WriteLine($"Org '{gitHubConfig["organization"]}' repo '{gitHubConfig["repository"]}' auto-merge pull requests ({autoMergePullRequests.Count})");
78+
Console.WriteLine($"Pull requests suitable for auto-merging ({autoMergePullRequests.Count}):");
7379
foreach (var pullRequest in autoMergePullRequests)
7480
{
7581
Console.WriteLine($" #{pullRequest.Number} {pullRequest.Title}");
7682
}
83+
84+
var git = new Git.Project(GetGitPath(), false);
85+
git.Init($"https://github.com/{gitHubConfig["organization"]}/{gitHubConfig["repository"]}.git");
86+
git.Fetch();
87+
git.ResetHard();
88+
var baseBranchCommit = git.ParseRef(gitHubConfig["baseBranch"]);
89+
var mergeBranchCommit = git.ParseRef(gitHubConfig["mergeBranch"]);
90+
var mergeBranchTree = git.ParseRef($"{mergeBranchCommit}^{{tree}}");
91+
git.CheckoutDetached(baseBranchCommit);
92+
var baseBranchVersion = String.Format(gitHubConfig["versionFormat"] ?? "{0}", git.Describe(gitHubConfig["versionDescribeOptions"] ?? ""));
93+
var mergeBranchParents = new List<string>();
94+
mergeBranchParents.Add(mergeBranchCommit);
95+
mergeBranchParents.Add(baseBranchCommit);
96+
var autoMergePullRequestsSuccess = new List<GraphPullRequest>();
97+
var autoMergePullRequestsFailure = new List<GraphPullRequest>();
98+
foreach (var pullRequest in autoMergePullRequests)
99+
{
100+
var mergeCommit = git.ParseRef($"pull/{pullRequest.Number}/head");
101+
try
102+
{
103+
git.Merge(mergeCommit);
104+
mergeBranchParents.Add(mergeCommit);
105+
autoMergePullRequestsSuccess.Add(pullRequest);
106+
}
107+
catch (ApplicationException)
108+
{
109+
autoMergePullRequestsFailure.Add(pullRequest);
110+
git.ResetHard();
111+
}
112+
}
113+
var autoMergeCommit = git.ParseRef("HEAD");
114+
var autoMergeTree = git.ParseRef($"{autoMergeCommit}^{{tree}}");
115+
116+
Console.WriteLine($"Pull requests successfully auto-merged ({autoMergePullRequestsSuccess.Count}):");
117+
foreach (var pullRequest in autoMergePullRequestsSuccess)
118+
{
119+
Console.WriteLine($" #{pullRequest.Number} {pullRequest.Title}");
120+
}
121+
122+
Console.WriteLine($"Pull requests not auto-merged ({autoMergePullRequestsFailure.Count}):");
123+
foreach (var pullRequest in autoMergePullRequestsFailure)
124+
{
125+
Console.WriteLine($" #{pullRequest.Number} {pullRequest.Title}");
126+
}
127+
128+
if (mergeBranchTree == autoMergeTree)
129+
{
130+
Console.WriteLine("No changes to push into merge branch");
131+
}
132+
else
133+
{
134+
var newMergeBranchMessage = String.Format(gitHubConfig["mergeMessageFormat"],
135+
baseBranchVersion,
136+
autoMergePullRequestsSuccess.Count,
137+
String.Join("", autoMergePullRequestsSuccess.Select(pr => String.Format(
138+
gitHubConfig["mergeMessagePRFormat"],
139+
pr.Number,
140+
pr.Title,
141+
git.GetAbbreviatedCommit($"pull/{pr.Number}/head")
142+
)))
143+
);
144+
var newMergeBranchCommit = git.CommitTree($"{autoMergeCommit}^{{tree}}", mergeBranchParents, newMergeBranchMessage);
145+
git.SetBranchRef(gitHubConfig["mergeBranch"], newMergeBranchCommit);
146+
git.Checkout(gitHubConfig["mergeBranch"]);
147+
var newMergeBranchVersion = String.Format(
148+
gitHubConfig["mergeVersionFormat"] ?? gitHubConfig["versionFormat"] ?? "{0}",
149+
git.Describe(gitHubConfig["mergeVersionDescribeOptions"] ?? gitHubConfig["versionDescribeOptions"] ?? ""),
150+
git.GetCommitDate(newMergeBranchCommit)
151+
);
152+
Console.WriteLine("Pushed changes into merge branch:");
153+
Console.WriteLine($" Version: {newMergeBranchVersion}");
154+
Console.WriteLine($" Message: {newMergeBranchMessage.Split("\n")[0]}");
155+
}
156+
}
157+
158+
static string GetGitPath()
159+
{
160+
var appFilePath = System.Reflection.Assembly.GetEntryAssembly().Location;
161+
return Path.Combine(Path.GetDirectoryName(appFilePath), "git");
77162
}
78163
}
79164
}

0 commit comments

Comments
 (0)