diff --git a/bin/git/sync-to-release.sh b/bin/git/sync-to-release.sh new file mode 100755 index 0000000000..f2c28659a5 --- /dev/null +++ b/bin/git/sync-to-release.sh @@ -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