#!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0-only # $0 from-branch to-branch # # applies all commits that from-branch has over to-branch, # based on a common ancestor and gerrit meta-data from=$1 to=$2 # match string: this is the git commit line that is used to # identify commits that were already copied over. # # Must not contain spaces except for leading and trailing. # # The first pick was Change-Id, but it was lost too often, # so go for Reviewed-on instead. It's also unique because it # contains the gerrit instance's host name and the change's number # on that system. match_string='[-A-Za-z]*[Rr]eviewed-on:' # Custom root: allow a certain CL (identified by matching either side's # match_string) to be the new root, instead of using git's common history only. # This allows cutting down on commits that are re-evaluated on every run. # # Use: # To the commit message of a commit on the "to" side, add # $custom_root: match_string (the part coming after $match_string) # # For a $match_string of ~ "Reviewed-on: " this might # be "$custom_root: https://example.com/12345" # # On traversal, the commit with "$match_string: https://example.com/12345" # is then considered a base commit. custom_root='^Gerrit-Rebase-Ignore-CLs-Before:' # fetch common ancestor common_base=$(git merge-base ${from} ${to} 2>/dev/null) if [ -z "${common_base}" ]; then echo \"${from}\" or \"${to}\" is not a valid branch name. exit 1 fi from_base=$common_base # fetch custom root ID croot_marker=$(git log --pretty=%b -1 --grep "${custom_root}" \ ${common_base}..${to} | \ grep "${custom_root}" | cut -d: -f2-) if [ -n "${croot_marker}" ]; then from_base=$( ( \ git log --pretty=%H -1 \ --grep "^${match_string}${croot_marker}" \ ${from_base}..${from}; echo ${from_base} )| head -1) fi # collect matches that are present on the target side to_matches="$(git log ${common_base}..${to} | \ grep "^ ${match_string}" | \ cut -d: -f2-)" # start rebase process, but fail immediately by enforcing an invalid todo GIT_SEQUENCE_EDITOR="echo 'Ignore this error, it works around a git-rebase limitation'>" \ git rebase -i --onto ${to} ${from} ${to} 2>/dev/null # write new rebase todo # the appended "commit" line triggers handling of the last log entry commit="" (git log --reverse ${from_base}..${from} | \ grep -E "(^commit [0-9a-f]{40}\$|^ ${match_string})"; \ echo "commit") | \ while read key value; do if [ "${key}" = "commit" ]; then if [ -n "${commit}" ]; then git log -n 1 --pretty="pick %h %s" ${commit} fi commit="${value}" else # if value was already found on the "to" side, skip this # commit if [[ ${to_matches} == *"${value}"* ]]; then commit="" fi fi done | GIT_SEQUENCE_EDITOR="cat >" git rebase --edit-todo # allow user to edit todo git rebase --edit-todo # start processing todo to mimick git rebase -i behavior git rebase --continue