diff --git a/bots/pr/src/main/java/org/openjdk/skara/bots/pr/BackportCommand.java b/bots/pr/src/main/java/org/openjdk/skara/bots/pr/BackportCommand.java index 237b33b66..3683f2282 100644 --- a/bots/pr/src/main/java/org/openjdk/skara/bots/pr/BackportCommand.java +++ b/bots/pr/src/main/java/org/openjdk/skara/bots/pr/BackportCommand.java @@ -105,57 +105,64 @@ public void handle(PullRequestBot bot, HostedCommit commit, CensusInstance censu try { var hash = commit.hash(); var fork = bot.writeableForkOf(targetRepo); - var localRepoDir = scratchPath.resolve("backport-command") - .resolve(repoName) - .resolve("fork"); - var localRepo = bot.hostedRepositoryPool() - .orElseThrow(() -> new IllegalStateException("Missing repository pool for PR bot")) - .materialize(fork, localRepoDir); - var fetchHead = localRepo.fetch(bot.repo().url(), hash.hex(), false); - localRepo.checkout(targetBranch); - var head = localRepo.head(); - var backportBranch = localRepo.branch(head, "backport-" + hash.abbreviate()); - localRepo.checkout(backportBranch); - var didApply = localRepo.cherryPick(fetchHead); - if (!didApply) { - var lines = new ArrayList(); - lines.add("@" + username + " could **not** automatically backport `" + hash.abbreviate() + "` to " + - "[" + repoName + "](" + targetRepo.webUrl() + ") due to conflicts in the following files:"); - lines.add(""); - var unmerged = localRepo.status() - .stream() - .filter(e -> e.status().isUnmerged()) - .map(e -> e.target().path().orElseGet(() -> e.source().path().orElseThrow())) - .collect(Collectors.toList()); - for (var path : unmerged) { - lines.add("- " + path.toString()); + Hash backportHash = null; + var backportBranchName = "backport-" + hash.abbreviate(); + var hostedBackportBranch = fork.branches().stream().filter(b -> b.name().equals(backportBranchName)).findAny(); + if (hostedBackportBranch.isEmpty()) { + var localRepoDir = scratchPath.resolve("backport-command") + .resolve(repoName) + .resolve("fork"); + var localRepo = bot.hostedRepositoryPool() + .orElseThrow(() -> new IllegalStateException("Missing repository pool for PR bot")) + .materialize(fork, localRepoDir); + var fetchHead = localRepo.fetch(bot.repo().url(), hash.hex(), false); + localRepo.checkout(targetBranch); + var head = localRepo.head(); + var backportBranch = localRepo.branch(head, backportBranchName); + localRepo.checkout(backportBranch); + var didApply = localRepo.cherryPick(fetchHead); + if (!didApply) { + var lines = new ArrayList(); + lines.add("@" + username + " could **not** automatically backport `" + hash.abbreviate() + "` to " + + "[" + repoName + "](" + targetRepo.webUrl() + ") due to conflicts in the following files:"); + lines.add(""); + var unmerged = localRepo.status() + .stream() + .filter(e -> e.status().isUnmerged()) + .map(e -> e.target().path().orElseGet(() -> e.source().path().orElseThrow())) + .collect(Collectors.toList()); + for (var path : unmerged) { + lines.add("- " + path.toString()); + } + lines.add(""); + lines.add("To manually resolve these conflicts run the following commands in your personal fork of [" + repoName + "](" + targetRepo.webUrl() + "):"); + lines.add(""); + lines.add("```"); + lines.add("$ git checkout -b " + backportBranchName); + lines.add("$ git fetch --no-tags " + bot.repo().webUrl() + " " + hash.hex()); + lines.add("$ git cherry-pick --no-commit " + hash.hex()); + lines.add("$ # Resolve conflicts"); + lines.add("$ git add files/with/resolved/conflicts"); + lines.add("$ git commit -m 'Backport " + hash.hex() + "'"); + lines.add("```"); + lines.add(""); + lines.add("Once you have resolved the conflicts as explained above continue with creating a pull request towards the [" + repoName + "](" + targetRepo.webUrl() + ") with the title `Backport " + hash.hex() + "`."); + + reply.println(String.join("\n", lines)); + localRepo.reset(head, true); + return; } - lines.add(""); - lines.add("To manually resolve these conflicts run the following commands in your personal fork of [" + repoName + "](" + targetRepo.webUrl() + "):"); - lines.add(""); - lines.add("```"); - lines.add("$ git checkout -b " + backportBranch.name()); - lines.add("$ git fetch --no-tags " + bot.repo().webUrl() + " " + hash.hex()); - lines.add("$ git cherry-pick --no-commit " + hash.hex()); - lines.add("$ # Resolve conflicts"); - lines.add("$ git add files/with/resolved/conflicts"); - lines.add("$ git commit -m 'Backport " + hash.hex() + "'"); - lines.add("```"); - lines.add(""); - lines.add("Once you have resolved the conflicts as explained above continue with creating a pull request towards the [" + repoName + "](" + targetRepo.webUrl() + ") with the title `Backport " + hash.hex() + "`."); - - reply.println(String.join("\n", lines)); - localRepo.reset(head, true); - return; - } - var backportHash = localRepo.commit("Backport " + hash.hex(), "duke", "duke@openjdk.org"); - localRepo.push(backportHash, fork.url(), backportBranch.name(), true); + backportHash = localRepo.commit("Backport " + hash.hex(), "duke", "duke@openjdk.org"); + localRepo.push(backportHash, fork.url(), backportBranchName, false); + } else { + backportHash = hostedBackportBranch.get().hash(); + } if (!fork.canPush(command.user())) { fork.addCollaborator(command.user(), true); } - fork.restrictPushAccess(backportBranch, List.of(command.user())); + fork.restrictPushAccess(new Branch(backportBranchName), List.of(command.user())); var message = CommitMessageParsers.v1.parse(commit); var formatter = DateTimeFormatter.ofPattern("d MMM uuuu"); @@ -196,12 +203,12 @@ public void handle(PullRequestBot bot, HostedCommit commit, CensusInstance censu body.add("> "); body.add("> Thanks!"); - var createPrUrl = fork.createPullRequestUrl(targetRepo, targetBranch.name(), backportBranch.name()); + var createPrUrl = fork.createPullRequestUrl(targetRepo, targetBranch.name(), backportBranchName); var targetBranchWebUrl = targetRepo.webUrl(targetBranch); - var backportBranchWebUrl = fork.webUrl(backportBranch); + var backportBranchWebUrl = fork.webUrl(new Branch(backportBranchName)); var backportWebUrl = fork.webUrl(backportHash); reply.println("@" + command.user().username() + " the [backport](" + backportWebUrl + ")" + - " was successfully created on the branch [" + backportBranch.name() + "](" + + " was successfully created on the branch [" + backportBranchName + "](" + backportBranchWebUrl + ") in my [personal fork](" + fork.webUrl() + ") of [" + targetRepo.name() + "](" + targetRepo.webUrl() + "). To create a pull request " + "with this backport targeting [" + targetRepo.name() + ":" + targetBranch.name() + "](" + @@ -219,8 +226,8 @@ public void handle(PullRequestBot bot, HostedCommit commit, CensusInstance censu "[" + targetRepo.name() + "](" + targetRepo.webUrl() + "):\n" + "\n" + "```\n" + - "$ git fetch " + fork.webUrl() + " " + backportBranch.name() + ":" + backportBranch.name() + "\n" + - "$ git checkout " + backportBranch.name() + "\n" + + "$ git fetch " + fork.webUrl() + " " + backportBranchName + ":" + backportBranchName + "\n" + + "$ git checkout " + backportBranchName + "\n" + "# make changes\n" + "$ git add paths/to/changed/files\n" + "$ git commit --message 'Describe additional changes made'\n" + diff --git a/bots/pr/src/test/java/org/openjdk/skara/bots/pr/BackportCommitCommandTests.java b/bots/pr/src/test/java/org/openjdk/skara/bots/pr/BackportCommitCommandTests.java index 87dce5939..630c231d8 100644 --- a/bots/pr/src/test/java/org/openjdk/skara/bots/pr/BackportCommitCommandTests.java +++ b/bots/pr/src/test/java/org/openjdk/skara/bots/pr/BackportCommitCommandTests.java @@ -197,4 +197,58 @@ void backportDoesNotApply(TestInfo testInfo) throws IOException { assertEquals(List.of(), author.pullRequests()); } } + + @Test + void backportTwice(TestInfo testInfo) throws IOException { + try (var credentials = new HostCredentials(testInfo); + var tempFolder = new TemporaryDirectory()) { + var author = credentials.getHostedRepository(); + var reviewer = credentials.getHostedRepository(); + + var censusBuilder = credentials.getCensusBuilder() + .addAuthor(author.forge().currentUser().id()) + .addReviewer(reviewer.forge().currentUser().id()); + var seedFolder = tempFolder.path().resolve("seed"); + var bot = PullRequestBot.newBuilder() + .repo(author) + .censusRepo(censusBuilder.build()) + .censusLink("https://census.com/{{contributor}}-profile") + .seedStorage(seedFolder) + .forks(Map.of(author.name(), author)) + .build(); + + // Populate the projects repository + var localRepo = CheckableRepository.init(tempFolder.path(), author.repositoryType()); + var masterHash = CheckableRepository.appendAndCommit(localRepo); + localRepo.push(masterHash, author.url(), "master", true); + + // Make a change in another branch + var editHash = CheckableRepository.appendAndCommit(localRepo); + localRepo.push(editHash, author.url(), "edit"); + + // Add a backport command + author.addCommitComment(editHash, "/backport " + author.name()); + TestBotRunner.runPeriodicItems(bot); + + var recentCommitComments = author.recentCommitComments(); + assertEquals(2, recentCommitComments.size()); + var botReply = recentCommitComments.get(0); + assertTrue(botReply.body().contains("backport")); + assertTrue(botReply.body().contains("was successfully created")); + assertTrue(botReply.body().contains("To create a pull request")); + assertTrue(botReply.body().contains("with this backport")); + + // Add a backport command again + author.addCommitComment(editHash, "/backport " + author.name()); + TestBotRunner.runPeriodicItems(bot); + + recentCommitComments = author.recentCommitComments(); + assertEquals(4, recentCommitComments.size()); + botReply = recentCommitComments.get(0); + assertTrue(botReply.body().contains("backport")); + assertTrue(botReply.body().contains("was successfully created")); + assertTrue(botReply.body().contains("To create a pull request")); + assertTrue(botReply.body().contains("with this backport")); + } + } }