Skip to content
Permalink
Browse files
231: The merge bot cannot create merge conflict PRs
Reviewed-by: rwestberg
  • Loading branch information
edvbld committed Jan 17, 2020
1 parent eb31138 commit a667c285da2bc496b2e87826c13acd1efc17497d
Showing 10 changed files with 176 additions and 55 deletions.
@@ -43,14 +43,16 @@ class MergeBot implements Bot, WorkItem {
private final Branch fromBranch;
private final HostedRepository to;
private final Branch toBranch;
private final HostedRepository toFork;

MergeBot(Path storage, HostedRepository from, Branch fromBranch,
HostedRepository to, Branch toBranch) {
HostedRepository to, Branch toBranch, HostedRepository toFork) {
this.storage = storage;
this.from = from;
this.fromBranch = fromBranch;
this.to = to;
this.toBranch = toBranch;
this.toFork = toFork;
}

@Override
@@ -72,18 +74,26 @@ public void run(Path scratchPath) {
if (!Files.exists(dir)) {
log.info("Cloning " + to.name());
Files.createDirectories(dir);
repo = Repository.clone(to.url(), dir);
repo = Repository.clone(toFork.url(), dir);
} else {
log.info("Found existing scratch directory for " + to.name());
repo = Repository.get(dir).orElseThrow(() -> {
return new RuntimeException("Repository in " + dir + " has vanished");
});
}

repo.fetchAll();
var originToBranch = new Branch("origin/" + toBranch.name());
// Sync personal fork
var remoteBranches = repo.remoteBranches(to.url().toString());
for (var branch : remoteBranches) {
var fetchHead = repo.fetch(to.url(), branch.hash().hex());
repo.push(fetchHead, toFork.url(), branch.name());
}

// Checkout the branch to merge into
repo.pull(toFork.url().toString(), toBranch.name());
repo.checkout(toBranch, false);

// Check if pull request already created
// Check if merge conflict pull request is present
var title = "Cannot automatically merge " + from.name() + ":" + fromBranch.name();
var marker = "<!-- MERGE CONFLICTS -->";
for (var pr : to.pullRequests()) {
@@ -92,7 +102,7 @@ public void run(Path scratchPath) {
to.forge().currentUser().equals(pr.author())) {
var lines = pr.body().split("\n");
var head = new Hash(lines[1].substring(5, 45));
if (repo.contains(originToBranch, head)) {
if (repo.contains(toBranch, head)) {
log.info("Closing resolved merge conflict PR " + pr.id());
pr.addComment("Merge conflicts have been resolved, closing this PR");
pr.setState(PullRequest.State.CLOSED);
@@ -108,15 +118,14 @@ public void run(Path scratchPath) {
var head = repo.resolve(toBranch.name()).orElseThrow(() ->
new IOException("Could not resolve branch " + toBranch.name())
);
if (repo.contains(originToBranch, fetchHead)) {
if (repo.contains(toBranch, fetchHead)) {
log.info("Nothing to merge");
return;
}

var isAncestor = repo.isAncestor(head, fetchHead);

log.info("Trying to merge into " + toBranch.name());
repo.checkout(toBranch, false);
IOException error = null;
try {
repo.merge(fetchHead);
@@ -135,6 +144,10 @@ public void run(Path scratchPath) {
log.info("Aborting unsuccesful merge");
repo.abortMerge();

var fromRepoName = Path.of(from.webUrl().getPath()).getFileName();
var fromBranchDesc = fromRepoName + "/" + fromBranch.name();
repo.push(fetchHead, toFork.url(), fromBranchDesc, true);

log.info("Creating pull request to alert");
var mergeBase = repo.mergeBase(fetchHead, head);
var commits = repo.commits(mergeBase.hex() + ".." + fetchHead.hex(), true).asList();
@@ -164,15 +177,16 @@ public void run(Path scratchPath) {
message.add("$ git commit -m 'Merge'");
message.add("```");
message.add("");
message.add("Push the resulting merge conflict to your personal fork and " +
"create a pull request towards this repository. This pull request " +
"will be closed automatically once the pull request with the resolved " +
"conflicts has been integrated.");
var pr = from.createPullRequest(to,
toBranch.name(),
fromBranch.name(),
title,
message);
message.add("Push the resolved merge conflict to your personal fork and " +
"create a pull request towards this repository.");
message.add("");
message.add("This pull request will be closed automatically by a bot once " +
"the merge conflicts have been resolved.");
var pr = toFork.createPullRequest(to,
toBranch.name(),
fromBranchDesc,
title,
message);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
@@ -55,10 +55,11 @@ public List<Bot> create(BotConfiguration configuration) {

var toRepo = configuration.repository(repo.get("to").asString());
var toBranch = new Branch(configuration.repositoryRef(repo.get("to").asString()));
var toFork = configuration.repository(repo.get("fork").asString());

log.info("Setting up merging from " + fromRepo.name() + ":" + fromBranch.name() +
" to " + toRepo.name() + ":" + toBranch.name());
bots.add(new MergeBot(storage, fromRepo, fromBranch, toRepo, toBranch));
bots.add(new MergeBot(storage, fromRepo, fromBranch, toRepo, toBranch, toFork));
}
return bots;
}
@@ -41,7 +41,7 @@
class MergeBotTests {
@Test
void mergeMasterBranch(TestInfo testInfo) throws IOException {
try (var temp = new TemporaryDirectory()) {
try (var temp = new TemporaryDirectory(false)) {
var host = TestHost.createNew(List.of(new HostUser(0, "duke", "J. Duke")));

var fromDir = temp.path().resolve("from.git");
@@ -50,11 +50,18 @@ void mergeMasterBranch(TestInfo testInfo) throws IOException {

var toDir = temp.path().resolve("to.git");
var toLocalRepo = Repository.init(toDir, VCS.GIT);
var gitConfig = toDir.resolve(".git").resolve("config");
Files.write(gitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
var toGitConfig = toDir.resolve(".git").resolve("config");
Files.write(toGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toHostedRepo = new TestHostedRepository(host, "test-mirror", toLocalRepo);

var forkDir = temp.path().resolve("fork.git");
var forkLocalRepo = Repository.init(forkDir, VCS.GIT);
var forkGitConfig = forkDir.resolve(".git").resolve("config");
Files.write(forkGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toFork = new TestHostedRepository(host, "test-mirror-fork", forkLocalRepo);

var now = ZonedDateTime.now();
var fromFileA = fromDir.resolve("a.txt");
Files.writeString(fromFileA, "Hello A\n");
@@ -85,7 +92,7 @@ void mergeMasterBranch(TestInfo testInfo) throws IOException {

var storage = temp.path().resolve("storage");
var master = new Branch("master");
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master);
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master, toFork);
TestBotRunner.runPeriodicItems(bot);

toCommits = toLocalRepo.commits().asList();
@@ -108,7 +115,7 @@ void mergeMasterBranch(TestInfo testInfo) throws IOException {

@Test
void failingMergeTest(TestInfo testInfo) throws IOException {
try (var temp = new TemporaryDirectory()) {
try (var temp = new TemporaryDirectory(false)) {
var host = TestHost.createNew(List.of(new HostUser(0, "duke", "J. Duke")));

var fromDir = temp.path().resolve("from.git");
@@ -117,11 +124,18 @@ void failingMergeTest(TestInfo testInfo) throws IOException {

var toDir = temp.path().resolve("to.git");
var toLocalRepo = Repository.init(toDir, VCS.GIT);
var gitConfig = toDir.resolve(".git").resolve("config");
Files.write(gitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
var toGitConfig = toDir.resolve(".git").resolve("config");
Files.write(toGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toHostedRepo = new TestHostedRepository(host, "test-mirror", toLocalRepo);

var forkDir = temp.path().resolve("fork.git");
var forkLocalRepo = Repository.init(forkDir, VCS.GIT);
var forkGitConfig = forkDir.resolve(".git").resolve("config");
Files.write(forkGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toFork = new TestHostedRepository(host, "test-mirror-fork", forkLocalRepo);

var now = ZonedDateTime.now();
var fromFileA = fromDir.resolve("a.txt");
Files.writeString(fromFileA, "Hello A\n");
@@ -152,7 +166,7 @@ void failingMergeTest(TestInfo testInfo) throws IOException {

var storage = temp.path().resolve("storage");
var master = new Branch("master");
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master);
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master, toFork);
TestBotRunner.runPeriodicItems(bot);

toCommits = toLocalRepo.commits().asList();
@@ -170,7 +184,7 @@ void failingMergeTest(TestInfo testInfo) throws IOException {

@Test
void failingMergeShouldResultInOnlyOnePR(TestInfo testInfo) throws IOException {
try (var temp = new TemporaryDirectory()) {
try (var temp = new TemporaryDirectory(false)) {
var host = TestHost.createNew(List.of(new HostUser(0, "duke", "J. Duke")));

var fromDir = temp.path().resolve("from.git");
@@ -179,11 +193,18 @@ void failingMergeShouldResultInOnlyOnePR(TestInfo testInfo) throws IOException {

var toDir = temp.path().resolve("to.git");
var toLocalRepo = Repository.init(toDir, VCS.GIT);
var gitConfig = toDir.resolve(".git").resolve("config");
Files.write(gitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
var toGitConfig = toDir.resolve(".git").resolve("config");
Files.write(toGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toHostedRepo = new TestHostedRepository(host, "test-mirror", toLocalRepo);

var forkDir = temp.path().resolve("fork.git");
var forkLocalRepo = Repository.init(forkDir, VCS.GIT);
var forkGitConfig = forkDir.resolve(".git").resolve("config");
Files.write(forkGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toFork = new TestHostedRepository(host, "test-mirror-fork", forkLocalRepo);

var now = ZonedDateTime.now();
var fromFileA = fromDir.resolve("a.txt");
Files.writeString(fromFileA, "Hello A\n");
@@ -214,7 +235,7 @@ void failingMergeShouldResultInOnlyOnePR(TestInfo testInfo) throws IOException {

var storage = temp.path().resolve("storage");
var master = new Branch("master");
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master);
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master, toFork);
TestBotRunner.runPeriodicItems(bot);
TestBotRunner.runPeriodicItems(bot);

@@ -242,11 +263,18 @@ void resolvedMergeConflictShouldResultInClosedPR(TestInfo testInfo) throws IOExc

var toDir = temp.path().resolve("to.git");
var toLocalRepo = Repository.init(toDir, VCS.GIT);
var gitConfig = toDir.resolve(".git").resolve("config");
Files.write(gitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
var toGitConfig = toDir.resolve(".git").resolve("config");
Files.write(toGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toHostedRepo = new TestHostedRepository(host, "test-mirror", toLocalRepo);

var forkDir = temp.path().resolve("fork.git");
var forkLocalRepo = Repository.init(forkDir, VCS.GIT);
var forkGitConfig = forkDir.resolve(".git").resolve("config");
Files.write(forkGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toFork = new TestHostedRepository(host, "test-mirror-fork", forkLocalRepo);

var now = ZonedDateTime.now();
var fromFileA = fromDir.resolve("a.txt");
Files.writeString(fromFileA, "Hello A\n");
@@ -277,7 +305,7 @@ void resolvedMergeConflictShouldResultInClosedPR(TestInfo testInfo) throws IOExc

var storage = temp.path().resolve("storage");
var master = new Branch("master");
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master);
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master, toFork);
TestBotRunner.runPeriodicItems(bot);
TestBotRunner.runPeriodicItems(bot);

@@ -316,11 +344,18 @@ void resolvedMergeConflictAndThenNewConflict(TestInfo testInfo) throws IOExcepti

var toDir = temp.path().resolve("to.git");
var toLocalRepo = Repository.init(toDir, VCS.GIT);
var gitConfig = toDir.resolve(".git").resolve("config");
Files.write(gitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
var toGitConfig = toDir.resolve(".git").resolve("config");
Files.write(toGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toHostedRepo = new TestHostedRepository(host, "test-mirror", toLocalRepo);

var forkDir = temp.path().resolve("fork.git");
var forkLocalRepo = Repository.init(forkDir, VCS.GIT);
var forkGitConfig = forkDir.resolve(".git").resolve("config");
Files.write(forkGitConfig, List.of("[receive]", "denyCurrentBranch = ignore"),
StandardOpenOption.APPEND);
var toFork = new TestHostedRepository(host, "test-mirror-fork", forkLocalRepo);

var now = ZonedDateTime.now();
var fromFileA = fromDir.resolve("a.txt");
Files.writeString(fromFileA, "Hello A\n");
@@ -351,7 +386,7 @@ void resolvedMergeConflictAndThenNewConflict(TestInfo testInfo) throws IOExcepti

var storage = temp.path().resolve("storage");
var master = new Branch("master");
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master);
var bot = new MergeBot(storage, fromHostedRepo, master, toHostedRepo, master, toFork);
TestBotRunner.runPeriodicItems(bot);
TestBotRunner.runPeriodicItems(bot);

@@ -143,9 +143,9 @@ void close() {
}
}

TestPullRequest createPullRequest(TestHostedRepository repository, String targetRef, String sourceRef, String title, List<String> body, boolean draft) {
TestPullRequest createPullRequest(TestHostedRepository targetRepository, TestHostedRepository sourceRepository, String targetRef, String sourceRef, String title, List<String> body, boolean draft) {
var id = String.valueOf(data.pullRequests.size() + 1);
var pr = TestPullRequest.createNew(repository, id, targetRef, sourceRef, title, body, draft);
var pr = TestPullRequest.createNew(targetRepository, sourceRepository, id, targetRef, sourceRef, title, body, draft);
data.pullRequests.put(id, pr);
return pr;
}
@@ -59,7 +59,7 @@ public Optional<HostedRepository> parent() {

@Override
public PullRequest createPullRequest(HostedRepository target, String targetRef, String sourceRef, String title, List<String> body, boolean draft) {
return host.createPullRequest(this, targetRef, sourceRef, title, body, draft);
return host.createPullRequest((TestHostedRepository) target, this, targetRef, sourceRef, title, body, draft);
}

@Override

0 comments on commit a667c28

Please sign in to comment.