diff options
-rwxr-xr-x | git-rewrite-branch | 149 | ||||
-rwxr-xr-x | git-rewrite-branch--appendid | 4 |
2 files changed, 153 insertions, 0 deletions
diff --git a/git-rewrite-branch b/git-rewrite-branch new file mode 100755 index 0000000..a2b3c81 --- /dev/null +++ b/git-rewrite-branch @@ -0,0 +1,149 @@ +#!/bin/bash -euE + +# when $gitmode is true, $ibranch's commits are used as IDs +gitmode=true +tag='git-rewrite-id' + +ibranch='' +obranch='' +wbranch='' + +usage() { + echo 'malformed call to internal function' +} + +verbose() { + : "$*" + #echo "$*" +} + +################################################################################ + +id2commit() { + [[ $# = 2 ]] || { usage; return 1; } + local branch=$1 + local id=$2 + if [[ $branch == $ibranch ]] && $gitmode; then + printf '%s\n' "$id" + else + git log "$branch" --pretty=format:'%H' --grep "${tag}: ${id}" + fi +} + +commit2id() { + [[ $# = 2 ]] || { usage; return 1; } + local branch=$1 + local commit=$2 + if [[ $branch == $ibranch ]] && $gitmode; then + printf '%s\n' "$commit" + else + git log "$branch" -n1 --pretty=formtat:'%B' "$commit" | sed -n "s|^\s*${tag}: ||p" + fi +} + +# commit2commit +c2c() { + [[ $# = 3 ]] || { usage; return 1; } + local from=$1 + local to=$2 + local commit=$3 + if [[ "$from" == "$to" ]]; then + # optimization + # also, properly normalizes $ibranch when $gitmode=true + git log "$commit" -n1 --pretty=format:'%H' + else + id2commit "$to" "$(commit2id "$from" "$commit")" + fi +} + +################################################################################ + +main() { + # Parse command line arguments ######################################### + if [[ $1 = '--svn' ]]; then + gitmode=false + tag='git-svn-id' + shift + fi + + if [[ $# < 3 ]]; then + usage + exit 1 + fi + + ibranch=$1 + obranch=$2 + shift 2 + + local filters=(); + if $gitmode; then + filters=(--msg-filter "git-rewrite-branch--appendid '${tag}'") + fi + filters+=("$@") + + # Main ################################################################# + + if git checkout "$obranch" -- 2>/dev/null; then + # obranch exists, update it + echo 'Updating existing rewritten branch...' + + local icommit="$(c2c "$ibranch" "$ibranch" "$ibranch")" + local ocommit="$(c2c "$obranch" "$ibranch" "$obranch")" + if [[ "$icommit" == "$ocommit" ]]; then + echo "Nothing to do" + return 0 + fi + + # Just to be safe + git branch -D "$obranch.tmp" 2>/dev/null || true + + wbranch="$obranch.tmp" + local common="$(c2c "$obranch" "$ibranch" "$obranch")" + if c2c "$ibranch" "$ibranch" "${common}^" &>/dev/null; then + revlist="$(c2c "$obranch" "$ibranch" "$obranch")^..${wbranch}" + else + # There is only one commit from $ibranch in $obranch + revlist="$wbranch" + fi + else + # obranch does not exist, create it + echo 'Creating new rewritten branch...' + wbranch=$obranch + revlist=$wbranch + fi + + git checkout "$ibranch" + git checkout -b "$wbranch" + git filter-branch -f "${filters[@]}" "$revlist" + + if [[ "$obranch" != "$wbranch" ]]; then + # rebase the changes in wbranch onto obranch + echo 'Rebasing rewrites onto existing branch...' + local wcommit="$(c2c "$wbranch" "$ibranch" "$wbranch")" + if [[ "$wcommit" == "$ocommit" ]]; then + echo "Nothing to do" + return 0 + fi + + local commonish="$(c2c "$obranch" "$wbranch" "$obranch")" + cmd=(git rebase --onto "$obranch" "$commonish" "$wbranch") + + verbose + verbose " o---o---o $obranch" + verbose ' :' + verbose " o---o---o---o---o $ibranch" + verbose ' \ :' + verbose ' `-C---o---o '"$wbranch" + verbose + verbose " C = $commonish" + verbose + verbose " ${cmd[*]}" + verbose + + "${cmd[@]}" + git checkout "$obranch" + git branch -d "$wbranch" + fi +} + +main "$@" diff --git a/git-rewrite-branch--appendid b/git-rewrite-branch--appendid new file mode 100755 index 0000000..e44180a --- /dev/null +++ b/git-rewrite-branch--appendid @@ -0,0 +1,4 @@ +#!/bin/bash + +tag=$1 +sed '$a'"${tag}: ${GIT_COMMIT}" |