#!/bin/bash -eE # # Copyright (c) 2012-2013 Luke Shumaker # USAGE='[-h] [-v] [--svn] ...' LONG_USAGE='Like filter-branch, but can be used to update branches. This creates or updates from . If already exists, it only runs new commits through the filter. This way, this can be used to pull from a remote with a different layout than yours. This will append "git-rewrite-id" to all commit messages, similar to "git-svn-id" with `git-svn`. -h Show this text -v Be more verbose --svn Use an existing "git-svn-id" instead of "git-rewrite-id" (in-branch was generated by git-svn) is passed directly to `git filter-branch`, and therefore has the same format. ' SUBDIRECTORY_OP=Yes OPTIONS_SPEC= . git-sh-setup . git-sh-i18n require_work_tree_exists # when $gitmode is true, $ibranch's commits are used as IDs gitmode=true tag='git-rewrite-id' ibranch='' obranch='' wbranch='' panic() { echo 'panic: malformed call to internal function' exit 1 } verbose() { : } ################################################################################ id2commit() { [[ $# = 2 ]] || panic 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 ]] || panic 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 ]] || panic 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 ######################################### while true; do case "${1:-}" in --svn) gitmode=false tag='git-svn-id' shift ;; -h) usage return 0 ;; -v) verbose() { echo "$*"; } shift ;; *) break ;; esac done if [[ $# < 3 ]]; then usage return 1 fi ibranch=$1 obranch=$2 shift 2 local filters=(); if $gitmode; then filters=(--msg-filter "git-rewrite-branch--appendid '${tag}'") fi filters+=("$@") # Main ################################################################# wbranch="$obranch.tmp" local revlist local rebase 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 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 rebase=true else # obranch does not exist, create it echo 'Creating new rewritten branch...' revlist=$wbranch rebase=false fi git checkout "$ibranch" git branch -D "$wbranch" 2>/dev/null || true git checkout -b "$wbranch" git filter-branch -f "${filters[@]}" "$revlist" if $rebase; 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" else git branch -m "$wbranch" "$obranch" fi } main "$@"