This solves the problem of rebasing local commits against an upstream that has renamed files. Signed-off-by: Eric Wong <normalperson@xxxxxxxx> --- Documentation/git-rebase.txt | 20 ++++ git-rebase.sh | 192 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 10 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 08ee4aa..c339c45 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a n SYNOPSIS -------- -'git-rebase' [--onto <newbase>] <upstream> [<branch>] +'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort @@ -106,6 +106,24 @@ OPTIONS --abort:: Restore the original branch and abort the rebase operation. +--skip:: + Restart the rebasing process by skipping the current patch. + This does not work with the --merge option. + +--merge:: + Use merging strategies to rebase. When the recursive (default) merge + strategy is used, this allows rebase to be aware of renames on the + upstream side. + +-s <strategy>, \--strategy=<strategy>:: + Use the given merge strategy; can be supplied more than + once to specify them in the order they should be tried. + If there is no `-s` option, a built-in list of strategies + is used instead (`git-merge-recursive` when merging a single + head, `git-merge-octopus` otherwise). This implies --merge. + +include::merge-strategies.txt[] + NOTES ----- When you rebase a branch, you are changing its history in a way that diff --git a/git-rebase.sh b/git-rebase.sh index e6b57b8..bce7bf8 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -34,7 +34,96 @@ When you have resolved this problem run If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " + +MRESOLVEMSG=" +When you have resolved this problem run \"git rebase --continue\". +To restore the original branch and stop rebasing run \"git rebase --abort\". +" unset newbase +strategy=recursive +do_merge= +dotest=$GIT_DIR/.dotest-merge +prec=4 + +continue_merge () { + test -n "$prev_head" || die "prev_head must be defined" + test -d "$dotest" || die "$dotest directory does not exist" + + unmerged=$(git-ls-files -u) + if test -n "$unmerged" + then + echo "You still have unmerged paths in your index" + echo "did you forget update-index?" + die "$MRESOLVEMSG" + fi + + if test -n "`git-diff-index HEAD`" + then + git-commit -C "`cat $dotest/current`" + else + echo "Previous merge succeeded automatically" + fi + + prev_head=`git-rev-parse HEAD^0` + + # save the resulting commit so we can read-tree on it later + echo "$prev_head" > "$dotest/`printf %0${prec}d $msgnum`.result" + echo "$prev_head" > "$dotest/prev_head" + + # onto the next patch: + msgnum=$(($msgnum + 1)) + printf "%0${prec}d" "$msgnum" > "$dotest/msgnum" +} + +call_merge () { + cmt="$(cat $dotest/`printf %0${prec}d $1`)" + echo "$cmt" > "$dotest/current" + git-merge-$strategy "$cmt^" -- HEAD "$cmt" + rv=$? + case "$rv" in + 0) + git-commit -C "$cmt" || die "commit failed: $MRESOLVEMSG" + ;; + 1) + test -d "$GIT_DIR/rr-cache" && git-rerere + die "$MRESOLVEMSG" + ;; + 2) + echo "Strategy: $rv $strategy failed, try another" 1>&2 + die "$MRESOLVEMSG" + ;; + *) + die "Unknown exit code ($rv) from command:" \ + "git-merge-$strategy $cmt^ -- HEAD $cmt" + ;; + esac +} + +finish_rb_merge () { + set -e + + msgnum=1 + echo "Finalizing rebased commits..." + git-reset --hard "`cat $dotest/onto`" + end="`cat $dotest/end`" + while test "$msgnum" -le "$end" + do + msgnum=`printf "%0${prec}d" "$msgnum"` + printf "%0${prec}d" "$msgnum" > "$dotest/msgnum" + + git-read-tree `cat "$dotest/$msgnum.result"` + git-checkout-index -q -f -u -a + git-commit -C "`cat $dotest/$msgnum`" + + echo "Committed $msgnum" + echo ' '`git-rev-list --pretty=oneline -1 HEAD | \ + sed 's/^[a-f0-9]\+ //'` + msgnum=$(($msgnum + 1)) + done + rm -r "$dotest" + echo "All done." +} + while case "$#" in 0) break ;; esac do case "$1" in @@ -46,17 +135,43 @@ do exit 1 ;; esac + if test -d "$dotest" + then + prev_head="`cat $dotest/prev_head`" + end="`cat $dotest/end`" + msgnum="`cat $dotest/msgnum`" + onto="`cat $dotest/onto`" + continue_merge + while test "$msgnum" -le "$end" + do + call_merge "$msgnum" + continue_merge + done + finish_rb_merge + exit + fi git am --resolved --3way --resolvemsg="$RESOLVEMSG" exit ;; --skip) + if test -d "$dotest" + then + die "--skip is not supported when using --merge" + fi git am -3 --skip --resolvemsg="$RESOLVEMSG" exit ;; --abort) - [ -d .dotest ] || die "No rebase in progress?" + if test -d "$dotest" + then + rm -r "$dotest" + elif test -d .dotest + then + rm -r .dotest + else + die "No rebase in progress?" + fi git reset --hard ORIG_HEAD - rm -r .dotest exit ;; --onto) @@ -64,6 +179,23 @@ do newbase="$2" shift ;; + -M|-m|--m|--me|--mer|--merg|--merge) + do_merge=t + ;; + -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ + --strateg=*|--strategy=*|\ + -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) + case "$#,$1" in + *,*=*) + strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;; + 1,*) + usage ;; + *) + strategy="$2" + shift ;; + esac + do_merge=t + ;; -*) usage ;; @@ -75,16 +207,25 @@ do done # Make sure we do not have .dotest -if mkdir .dotest +if test -z "$do_merge" then - rmdir .dotest -else - echo >&2 ' + if mkdir .dotest + then + rmdir .dotest + else + echo >&2 ' It seems that I cannot create a .dotest directory, and I wonder if you are in the middle of patch application or another rebase. If that is not the case, please rm -fr .dotest and run me again. I am stopping in case you still have something valuable there.' - exit 1 + exit 1 + fi +else + if test -d "$dotest" + then + die "previous dotest directory $dotest still exists." \ + 'try git-rebase < --continue | --abort >' + fi fi # The tree must be really really clean. @@ -152,6 +293,39 @@ then exit 0 fi -git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | -git am --binary -3 -k --resolvemsg="$RESOLVEMSG" +if test -z "$do_merge" +then + git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | + git am --binary -3 -k --resolvemsg="$RESOLVEMSG" + exit $? +fi + +# start doing a rebase with git-merge +# this is rename-aware if the recursive (default) strategy is used + +mkdir -p "$dotest" +echo "$onto" > "$dotest/onto" +prev_head=`git-rev-parse HEAD^0` +echo "$prev_head" > "$dotest/prev_head" + +msgnum=0 +for cmt in `git-rev-list --no-merges "$upstream"..ORIG_HEAD \ + | perl -e 'print reverse <>'` +do + msgnum=$(($msgnum + 1)) + echo "$cmt" > "$dotest/`printf "%0${prec}d" $msgnum`" +done + +printf "%0${prec}d" 1 > "$dotest/msgnum" +printf "%0${prec}d" "$msgnum" > "$dotest/end" + +end=$msgnum +msgnum=1 + +while test "$msgnum" -le "$end" +do + call_merge "$msgnum" + continue_merge +done +finish_rb_merge -- 1.4.0.g65f3 - : send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html