2021-10-14 21:21:30 +00:00
#!/usr/bin/env bash
2020-09-27 20:17:29 +00:00
set -eou pipefail
help( ) {
2026-04-24 11:27:36 +02:00
cat <<EOT
$0 : Prepare a pull request that syncs a branch with upstream
2020-09-27 20:17:29 +00:00
2026-04-24 11:27:36 +02:00
Usage:
2026-05-22 15:35:12 +02:00
$0 [ --switch] <base-branch> <upstream-ref>
2026-03-09 18:36:11 +02:00
2026-04-24 11:27:36 +02:00
This script creates a sync local branch pointing to <upstream-ref>. Moreover, it
generates a helper script for opening a pull request ( PR) merging the created
local branch into <base-branch>.
2020-09-27 20:17:29 +00:00
2026-04-24 11:27:36 +02:00
The synced upstream PRs are listed in the title and the description of the PR.
( This relies on upstream merging PRs using merge commits with titles of the form
"Merge <repo>#<prnum>: ..." .)
2020-09-27 20:17:29 +00:00
2026-04-24 11:27:36 +02:00
Arguments:
2026-05-22 15:35:12 +02:00
--switch: Try to switch to the created sync branch
2026-04-24 11:27:36 +02:00
<base-branch>: The branch to sync with upstream
<upstream-ref>: The upstream ref to merge into <base-branch>
Usage examples:
2026-05-22 15:35:12 +02:00
$0 --switch master upstream/master
2026-04-24 11:27:36 +02:00
$0 master abc1234
To find candidate merge commits from <upstream-ref> ( oldest first) , use:
git log --oneline --topo-order --reverse --merges \$ ( git merge-base <upstream-ref> <base-branch>) ..<upstream-ref>
EOT
}
2023-07-17 13:29:59 +00:00
2026-04-24 11:27:36 +02:00
### Parse arguments
2026-05-22 15:35:12 +02:00
SWITCH = false
if [ " $# " -ge 1 ] && [ " $1 " = "--switch" ] ; then
SWITCH = true
shift
fi
2026-04-24 11:27:36 +02:00
if [ " $# " -ne 2 ] ; then
2026-03-09 18:36:11 +02:00
help
exit 1
fi
2026-04-24 11:27:36 +02:00
BASE_BRANCH = " $1 "
UPSTREAM_REF = " $2 "
2023-07-17 13:29:59 +00:00
2026-04-24 11:27:36 +02:00
### Create PR metadata
2021-03-10 15:07:07 +01:00
TITLE = "Upstream PRs"
2026-04-24 11:27:36 +02:00
RANGESTART_COMMIT = $( git merge-base " $UPSTREAM_REF " " $BASE_BRANCH " )
RANGEEND_COMMIT = $( git rev-parse " $UPSTREAM_REF " )
COMMITS = $( git --no-pager log --pretty= format:%H --topo-order --reverse --merges " $RANGESTART_COMMIT " .." $RANGEEND_COMMIT " )
# If there are no commits, exit successfully
if [ -z " $COMMITS " ] ; then
echo " No merge commits in range ${ RANGESTART_COMMIT } .. ${ RANGEEND_COMMIT } " >& 2
exit 0
fi
BODY = " ${ GITHUB_ACTIONS +*Note : This PR has been created by a GitHub Actions workflow without human involvement.*
} "
BODY += "This PR syncs the following upstream PRs:"
for COMMIT in $COMMITS ; do
PRNUM = $( git log -1 " $COMMIT " --pretty= format:%s | sed s/'Merge .*#\([0-9]*\):.*' /'\1' /)
2021-03-10 15:07:07 +01:00
TITLE = " $TITLE $PRNUM , "
2026-04-24 11:27:36 +02:00
BODY = $( printf "%s\n * %s" " $BODY " " $( git log -1 " $COMMIT " --pretty= format:%s | sed s/'Merge ' //) " )
2020-09-27 20:17:29 +00:00
done
2021-03-10 15:07:07 +01:00
# Remove trailing ","
TITLE = ${ TITLE %? }
2026-04-24 11:27:36 +02:00
BODY += $( cat <<'EOF'
2026-02-05 15:50:58 +01:00
2026-04-24 11:27:36 +02:00
Usage hints:
* If this PR has merge conflicts, resolve these by switching to the PR branch and merging the base branch into it using ` git merge <base-branch>` .
* To show the conflict resolution diff from an existing merge commit, use ` git show --remerge-diff <merge-commit>` .
* In case you are recreating the PR branch locally, you can ( during the conflict resolution state) replay this conflict resolution diff using ` git read-tree --reset -u <merge-commit>` .
2026-02-05 15:50:58 +01:00
Be aware that this may discard your index as well as the uncommitted changes and untracked files in your worktree.
EOF
)
2020-09-27 20:17:29 +00:00
2026-04-24 11:27:36 +02:00
### Create a sync branch locally.
SYNC_BRANCH = " sync- $( git rev-parse --short " $UPSTREAM_REF " ) "
# This will error out if the branch already exists, which is what we want.
git branch --no-track " $SYNC_BRANCH " " $UPSTREAM_REF "
### Print the PR metadata
2020-09-27 20:17:29 +00:00
echo "-----------------------------------"
echo " $TITLE "
echo "-----------------------------------"
echo " $BODY "
echo "-----------------------------------"
2026-04-24 11:27:36 +02:00
### Generate the helper script for creating the PR
FNAME = "gh-pr-create.sh"
# Escape single quote ' -> '\''
2021-09-15 20:09:35 +00:00
quote( ) {
local quoted = ${ 1 // \' / \' \\ \' \' }
printf "%s" " $quoted "
}
TITLE = $( quote " $TITLE " )
BODY = $( quote " $BODY " )
2020-09-27 20:17:29 +00:00
cat <<EOT > "$FNAME "
#!/bin/sh
2026-04-24 11:27:36 +02:00
TITLE = '$TITLE'
BODY = '$BODY'
SYNC_BRANCH = '$SYNC_BRANCH'
BASE_BRANCH = '$BASE_BRANCH'
gh pr create --base "\$BASE_BRANCH" --head "\$SYNC_BRANCH" --title "\$TITLE" --body "\$BODY" "\$@"
2020-09-27 20:17:29 +00:00
EOT
chmod +x " $FNAME "
2026-04-24 11:27:36 +02:00
echo " Successfully created local sync branch $SYNC_BRANCH starting at $UPSTREAM_REF . "
echo
echo "You can now:"
echo " 1. Optionally resolve merge conflicts by merging $BASE_BRANCH into $SYNC_BRANCH . "
echo " 2. Push $SYNC_BRANCH to some GitHub remote. "
echo " 3. Run ./ $FNAME to create a pull request. (Tip: Pass --dry-run first.) "
2026-05-22 15:35:12 +02:00
if [ " ${ SWITCH :- false } " = true ] ; then
echo
echo "Trying to switch to the sync branch..."
echo
git switch " $SYNC_BRANCH "
fi