chore: Add script to cherry-pick new commits from develop to a release

This commit is contained in:
Ed Hennis
2026-05-04 19:56:08 -04:00
parent d050073842
commit 97059761a6

159
bin/git/sync-to-release.sh Executable file
View File

@@ -0,0 +1,159 @@
#!/bin/bash -e
set -o pipefail
_usage()
{
self=$( basename -- $( echo $0 ) )
cat << USAGE
Usage: $self release_branch develop_branch working_branch [new_version]
This script is intended to be used to synchronize changes between two
branches where the "release_branch" has a few insignificant changes (e.g.
version numbers) compared to the "develop_branch", which has active
ongoing development. It has not (yet) been tested in scenarios where the
branches have significantly diverged, or where one has been merged to
the other.
release_branch: Usually a pending release branch, this is the low
activity branch which needs to be updated from develop_branch.
develop_branch: The active development branch.
working_branch: This branch will be created from the release_branch to
have the new commits applied. If it exists, it will be assumed to have
been created by an earlier invocation, and will be used for
comparisons instead of release_branch. When the script is done, this
branch will NOT be pushed. The user must manually push the branch and
create a pull request.
new_version: OPTIONAL. If provided, will update the version string in
BuildInfo.cpp to this value, and create a version_setting commit.
USAGE
if [[ $# -ne 0 ]]; then
echo "Error: ${@}"
echo
fi
exit 1
}
if [[ $# -lt 3 || $# -gt 4 ]]; then
_usage
fi
release_branch="$1"
shift
develop_branch="$1"
shift
working_branch="$1"
shift
if [[ $# -ne 0 ]]; then
new_version="$1"
shift
fi
_run()
{
echo
echo "\$ ${@}"
"${@}"
}
if git rev-parse --quiet --verify --end-of-options "refs/heads/${working_branch}" > /dev/null; then
release_branch="${working_branch}"
_run git checkout "${working_branch}"
else
_run git checkout --no-track -B "${working_branch}" "${release_branch}"
fi
# Fetch if it hasn't been done in a while
if find "$( git rev-parse --git-dir )/FETCH_HEAD" -type f -mmin +10 | grep -q "FETCH"; then
_run git fetch --all
fi
temp=$( mktemp )
# The "word-diff" option doesn't seem to have any meaningful effect on the output.
# This may make this script fragile across git versions with differing output formats.
_run git range-diff --word-diff=porcelain --no-patch --right-only \
"${release_branch}"..."${develop_branch}" | tee "${temp}"
first_commit=$( cat "${temp}" | \
awk '( $1 == "-:" && $2 ~ /-+/ && $3 == ">" ) { print $5; exit; }' )
if [[ "${first_commit}" == "" && "${new_version}" == "" ]]; then
_usage No divergence found
exit 1
fi
if [[ "${first_commit}" == "" ]]; then
echo -e "\nNo divergence found. Continue to update version number"
else
echo -e "\nFound first unique commit in ${develop_branch}: ${first_commit}"
first_commit=$( git rev-parse --quiet "${first_commit}" )
echo "Full commit ID is: ${first_commit}"
merge_base=$( git merge-base --octopus "${first_commit}" "${develop_branch}" )
if [[ "${first_commit}" != "${merge_base}" ]]; then
echo "Bad first commit: ${first_commit} is not an ancestor of ${develop_branch}"
exit 1
fi
_run git diff "${first_commit}~" "${release_branch}"
read -n 1 -s -p "Does the diff look reasonable? (y to continue / any other key to abort)" continue
echo
if [[ ! ( "${continue}" =~ [yY] ) ]]; then
echo Abort
exit 1
fi
# Dilemma: if the cherry-pick is interrupted, for example, because of a conflict, the script
# will abort, and can not easily be resumed. This will prevent the final step(s) from running.
if ! _run git cherry-pick --empty=drop "${first_commit}~..${develop_branch}"; then
# Solution: if the cherry-pick is interrupted, run the command again.
if [[ "${new_version}" != "" ]]; then
echo
echo "Cherry pick failed or was interrupted. Resolve issues, finish the"
echo " cherry pick, then run this script again to with the same parameters"
echo " to update the version number."
fi
exit 1
fi
if [[ "${new_version}" == "" ]]; then
# If there is no version number change to make, we're done.
exit 0
fi
fi
# There should only be one matching file, but loop in case not.
buildfiles=( $( git ls-files | grep "BuildInfo.cpp" ) )
for build in "${buildfiles[@]}"; do
sed 's/\(^.*versionString =\).*$/\1 "'${new_version}'"/' ${build} > version.cpp && \
_run diff "${build}" version.cpp && exit 1 || \
mv -vi version.cpp ${build}
git add ${build}
done
_run git commit -S -m "Set version to ${new_version}"
_run git log --oneline --first-parent ${release_branch}^..
cat << PUSH
-------------------------------------------------------------------
This script will not push to remote. Verify everything is correct,
then push, and create a PR as described in CONTRIBUTING.md.
-------------------------------------------------------------------
PUSH