"Create a new backport branch with prefix `ai-backport-` by cherry-picking all commits from a GitHub PR onto a target release branch or commit. Use when the user wants to backport a PR, create a backport branch, cherry-pick PR commits to a release branch, or mentions backporting changes from dev/main to a version branch (e.g. v25.1.x, v25.2.x). Requires two arguments: TARGET (the release branch or commit hash to backport onto) and PR_NUM (the GitHub PR number to backport)."
Cherry-pick all commits from a GitHub PR onto a target release branch, resolving conflicts along the way.
Arguments:
$0 is target — a release branch (e.g. v25.1.x) or a commit hash (e.g. 81d9e1af33)$1 is pr-number (e.g. 12345)date +%s
backport-branch-name format: ai-backport-pr-${pr_number}-${target_branch}-${unix_timestamp}git checkout -b $backport_branch_name $target_branch
Get the list of commit SHAs from the PR:
gh api "repos/redpanda-data/redpanda/pulls/$pr_number/commits" --paginate --jq '[.[].sha[0:10]]|join(" ")'
Save the full list of commits as git_commits and report how many commits were found.
Run the cherry-pick for all commits at once:
git cherry-pick -x $git_commits
Track these counters as you go:
If the cherry-pick succeeds cleanly, check if any generated files were touched (see step 4a), then skip to step 6.
After each successful cherry-pick (clean or after conflict resolution), check if any generated files were touched. Use the same generated file patterns from step 5.3. For clean cherry-picks, check the files changed in the commit(s) just applied:
git diff --name-only HEAD~1 HEAD
Match the output against the generated file patterns. Add any matches to generated_files_touched.
If the cherry-pick fails with merge conflicts, attempt resolution. For each conflicted state:
Identify the commit that caused the conflict (the one currently being cherry-picked).
List conflicted files: git diff --name-only --diff-filter=U
Categorize each file:
*.pb.go, *.pb.h, *.pb.cc, *.pb.rs, *_pb2.py, go.sum, *.lock, MODULE.bazel.lock, package-lock.json, Cargo.lock, MODULE.bazel
git checkout --theirs -- $FILE && git add -- $FILEgenerated_files_touched list<<<<<<< / ======= / >>>>>>> markers. Attempt resolution.For each content conflict:
a. Read the full conflicted file
b. Run git log -p -1 $FAILING_COMMIT -- $FILE to understand what the cherry-picked commit intended
c. Understand both sides:
<<<<<<< to =======): what the target release branch has======= to >>>>>>>): what the cherry-picked commit brings
d. Read surrounding code if needed to understand context (imports, types, function signatures)
e. Determine the correct resolution:git add -- $FILEVerify the resolution is clean:
grep -rn '<<<<<<< \|=======$\|>>>>>>> ' -- $RESOLVED_FILES
git diff --name-only --diff-filter=U
Both commands should produce no output. If unmerged paths remain, something was missed.
Continue the cherry-pick:
git cherry-pick --continue --no-edit
If --continue reports "The previous cherry-pick is now empty", the commit's changes are already on the target branch. Run git cherry-pick --skip to move on, and increment commits_skipped.
Increment conflicts_resolved counter.
If the next commit also conflicts, repeat from step 5.1.
A cherry-pick can also be empty without any conflict — git will report "The previous cherry-pick is now empty" immediately. This happens when the commit's changes already exist on the target branch (e.g., from a prior backport or parallel fix). Run git cherry-pick --skip to move on and increment commits_skipped. This is normal and not an error.
Abort procedure — If at any point you are uncertain how to resolve a conflict (ambiguous intent, modify/delete conflicts, architectural changes you don't understand, or anything that feels risky):
git cherry-pick --abortgit cherry-pick -x ... command that was being attemptedAfter all commits are cherry-picked successfully, report:
Backport of PR https://github.com/redpanda-data/redpanda/pull/${pr_number}
- Command: git cherry-pick -x ${git_commits}
- Commits backported: ${total_commits}
- Conflicts resolved: ${conflicts_resolved}
- Commits skipped (already on target): ${commits_skipped}
- Backport branch: ${backport_branch_name}
Conflict details:
- ${commit_sha} (${file}): one-line explanation of what conflicted and how it was resolved
For each conflict resolved, include a one-line explanation covering what conflicted and how you resolved it. This helps PR reviewers understand the backport without reading every diff.
If generated_files_touched is non-empty, append a warning section:
⚠️ Generated files were cherry-picked and may need regeneration:
- ${file_1}
- ${file_2}
These files were accepted as-is from the source branch. Before merging,
regenerate them on the target branch to ensure they're correct. For example:
- MODULE.bazel.lock: run `bazel mod deps --lockfile_mode=update`
- *.pb.go / *.pb.cc / *.pb.h: rebuild protobuf targets
- go.sum: run `go mod tidy`