Gracefully handle existing remote branch and/or pull request by oliverpool · Pull Request #239 · kiegroup/git-backporting · GitHub
Skip to content

Gracefully handle existing remote branch and/or pull request - #239

Open
oliverpool wants to merge 5 commits into
kiegroup:mainfrom
oliverpool:force-with-lease
Open

oliverpool wants to merge 5 commits into
kiegroup:mainfrom
oliverpool:force-with-lease

Conversation

@oliverpool

@oliverpool oliverpool commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

fixes #234

Description

This changes the behavior introduced in #231 (aborting early if the branch exists on the remote) by @BtbN (tagging you to get your feedback :)

Instead of early-aborting if the same branch exists on the remote, this PR performs the cherry-pick and the compares the local and remote:

  • if the remote has some original work (someone else pushed a commit), abort
  • otherwise force-push with lease (equivalent of rebasing the remote branch, if some new commit occured on the target branch)
    Afterwards the PR creation is always attempted: if a 409 reply is received (a PR already exists), log the message and succeed (only for github/forgejo).

This would be valuable for Forgejo on codeberg, where sometimes the branch creation succeeds, but the PR creation fails: currently the branch is left there forever (needs a manual PR opening).
With this fix, a re-run of the job would update the branch and retry PR creation (nicer than a manual PR opening).

How Has This Been Tested?

Unit tests have been added at the git-cli level, as well as at the runner level.
I performed a manual test on a local forgejo instance:

  • setup a repo with main and lts branches
  • create and merge a PR targeting main
  • run git-backporting: backport PR is opened (unchanged)
  • push some commits to lts
  • re-run git-backporting: the existing branch will be rebased (force push) - PR will be detected as present
  • close the PR (but keep the branch)
  • re-run git-backporting: the existing branch will be left unchanged and a new PR created
  • push some commits to the backport branch
  • re-run git-backporting: failure with message Remote branch contains original work, aborting

Best reviewed commit-by-commit.

Checklist

  • Tests added if applicable.
  • Documentation updated if applicable.

Merge criteria:

  • The commits and have meaningful messages; the author will squash them after approval or will ask to merge with squash.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

Note: dist/cli/index.js and dist/gha/index.js are automatically generated by git hooks and gh workflows.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

@oliverpool oliverpool changed the title Handle existing remote branch or pull request Gracefully handle existing remote branch and/or pull request Oct 2, 2026
@lampajr

lampajr commented Oct 17, 2026

Copy link
Copy Markdown
Member

Thanks again @oliverpool , I really appreciate the effort you're putting in improving this tool ❤️

Overall the idea looks good but I'd like to take some time to ensure it is working as expected.

if the remote has some original work (someone else pushed a commit), abort
otherwise force-push with lease (equivalent of rebasing the remote branch, if some new commit occured on the target branch)

This is the tricky part: I haven't checked the code yet, but I just want to make sure we don't run the risk of doing a force push to a branch we shouldn't, since that could be irreversible.

Afterwards the PR creation is always attempted: if a 409 reply is received (a PR already exists), log the message and succeed (only for github/forgejo).

This is very good improvement, +100 🚀

@oliverpool oliverpool left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lampajr thanks for the kind words!

To ease your review, here are the key additions of this PR.
Let me know what you think, once you have got time to review them!

const output = await simpleGit().raw(["ls-remote", "--heads", this.remoteWithAuth(remote), branch]);
return output.trim().length > 0;
async _isUpstreamMissingWork(cwd: string, upstream: string, head: string): Promise<boolean> {
const stdout = await this.git(cwd).raw(["cherry", upstream, head]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -187,7 +227,15 @@ export default class GitCLIService {
if (force) {
options.push("--force-with-lease");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I was able to keep the force-with-lease (since we fetch the upstream branch, to check for original work)

Comment on lines +280 to +290
await git.gitCli.hasRemoteOriginalWork(configs.folder, backportPR.head, target_remote)
) {
logger.info("Remote branch contains original work, aborting");
// throw original error: we cannot push in non-fast-forwarding way
throw err.cause;
}
if (
await git.gitCli.hasLocalOriginalWork(configs.folder, backportPR.head, target_remote)
) {
logger.info("Force-pushing rebased branch");
await git.gitCli.push(configs.folder, backportPR.head, target_remote, true);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the core of the logic:

  • fetch the remote branch
  • if it contains original work, abort
  • otherwise if we have original work (ie rebase), force-push-with-lease

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this with original work and it works pretty well:

[INFO ] [prod] Remote branch contains original work, aborting
[ERROR] [prod] Something went wrong backporting to prod: Error: To https://github.com/lampajr/backporting-example.git
!	refs/heads/bp-prod-6353aff:refs/heads/bp-prod-6353aff	[rejected] (non-fast-forward)
Done
Pushing to https://github.com/lampajr/backporting-example.git
error: failed to push some refs to 'https://github.com/lampajr/backporting-example.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

[ERROR] Error: Failure occurred during one of the backports: [Error: To https://github.com/lampajr/backporting-example.git
!	refs/heads/bp-prod-6353aff:refs/heads/bp-prod-6353aff	[rejected] (non-fast-forward)
Done
Pushing to https://github.com/lampajr/backporting-example.git
error: failed to push some refs to 'https://github.com/lampajr/backporting-example.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
]
[INFO ] Process failed

We could probably strip some error msg here as it might be confusing but the important bit is [INFO ] [prod] Remote branch contains original work, aborting - can be improved later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit hacky: return an empty URL if a pull request already exists for this base+head.

Ideally we should return the URL of the existing PR, but Forgejo does not expose this in a structured way (yet). I haven't tested github.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a temporary solution might, even though I don't think it is working as expected with github:

POST /repos/lampajr/backporting-example/pulls - 422 with id 9BB4:1EB9F9:EF86DE:1631085:6ADBBFF9 in 750ms
[ERROR] [prod] Something went wrong backporting to prod: HttpError: Validation Failed: {"resource":"PullRequest","code":"custom","message":"A pull request already exists for lampajr:bp-prod-6353aff."} - https://docs.github.com/rest/pulls/pulls#create-a-pull-request
[ERROR] Error: Failure occurred during one of the backports: [HttpError: Validation Failed: {"resource":"PullRequest","code":"custom","message":"A pull request already exists for lampajr:bp-prod-6353aff."} - https://docs.github.com/rest/pulls/pulls#create-a-pull-request]
[INFO ] Process failed

The process still fails in case of existing PR

@lampajr lampajr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @oliverpool , I tested this out a little bit and it looks good to me!

There is only one small issue that I'd like to cover before merging, looks like Forgejo returns different http code compared to github in case of existing PR - 409 (forgejo) vs 422 (github). Do you see any problem in implementing this as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle when the branch already exists on the remote

2 participants