Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

231: The merge bot cannot create merge conflict PRs #361

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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