diff options
-rw-r--r-- | Documentation/git-rebase.txt | 21 | ||||
-rwxr-xr-x | git-rebase--interactive.sh | 110 | ||||
-rwxr-xr-x | git-rebase.sh | 56 | ||||
-rwxr-xr-x | t/t3412-rebase-root.sh | 187 |
4 files changed, 322 insertions, 52 deletions
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index c8ad86a56f..3d6d429e5e 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] - [-s <strategy> | --strategy=<strategy>] [--no-verify] - [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] - [--onto <newbase>] <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] [--onto <newbase>] + <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] --onto <newbase> + --root [<branch>] + 'git rebase' --continue | --skip | --abort DESCRIPTION @@ -22,7 +23,8 @@ it remains on the current branch. All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set -of commits that would be shown by `git log <upstream>..HEAD`. +of commits that would be shown by `git log <upstream>..HEAD` (or +`git log HEAD`, if --root is specified). The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as @@ -255,6 +257,15 @@ OPTIONS --preserve-merges:: Instead of ignoring merges, try to recreate them. +--root:: + Rebase all commits reachable from <branch>, instead of + limiting them with an <upstream>. This allows you to rebase + the root commit(s) on a branch. Must be used with --onto, and + will skip changes already contained in <newbase> (instead of + <upstream>). When used together with --preserve-merges, 'all' + root commits will be rewritten to have <newbase> as parent + instead. + include::merge-strategies.txt[] NOTES diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 8ed2244819..21ac20c305 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -27,6 +27,7 @@ continue continue rebasing process abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation +root rebase all reachable commmits up to the root(s) " . git-sh-setup @@ -44,6 +45,7 @@ STRATEGY= ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= +REBASE_ROOT= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -154,6 +156,11 @@ pick_one () { output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return + if test ! -z "$REBASE_ROOT" + then + output git cherry-pick "$@" + return + fi parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) @@ -197,7 +204,11 @@ pick_one_preserving_merges () { # rewrite parents; if none were rewritten, we can fast-forward. new_parents= - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)" + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" + if test "$pend" = " " + then + pend=" root" + fi while [ "$pend" != "" ] do p=$(expr "$pend" : ' \([^ ]*\)') @@ -227,7 +238,9 @@ pick_one_preserving_merges () { if test -f "$DROPPED"/$p then fast_forward=f - pend=" $(cat "$DROPPED"/$p)$pend" + replacement="$(cat "$DROPPED"/$p)" + test -z "$replacement" && replacement=root + pend=" $replacement$pend" else new_parents="$new_parents $p" fi @@ -443,6 +456,7 @@ get_saved_options () { test -d "$REWRITTEN" && PRESERVE_MERGES=t test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" test -f "$DOTEST"/verbose && VERBOSE=t + test ! -s "$DOTEST"/upstream && REBASE_ROOT=t } while test $# != 0 @@ -547,6 +561,9 @@ first and then run 'git rebase --continue' again." -i) # yeah, we know ;; + --root) + REBASE_ROOT=t + ;; --onto) shift ONTO=$(git rev-parse --verify "$1") || @@ -554,27 +571,36 @@ first and then run 'git rebase --continue' again." ;; --) shift - run_pre_rebase_hook ${1+"$@"} - test $# -eq 1 -o $# -eq 2 || usage + test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage test -d "$DOTEST" && die "Interactive rebase already started" git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first" + if test -z "$REBASE_ROOT" + then + UPSTREAM_ARG="$1" + UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + test -z "$ONTO" && ONTO=$UPSTREAM + shift + else + UPSTREAM_ARG=--root + test -z "$ONTO" && + die "You must specify --onto when using --root" + fi + run_pre_rebase_hook "$UPSTREAM_ARG" "$@" + comment_for_reflog start require_clean_work_tree - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" - test -z "$ONTO" && ONTO=$UPSTREAM - - if test ! -z "$2" + if test ! -z "$1" then - output git show-ref --verify --quiet "refs/heads/$2" || - die "Invalid branchname: $2" - output git checkout "$2" || - die "Could not checkout $2" + output git show-ref --verify --quiet "refs/heads/$1" || + die "Invalid branchname: $1" + output git checkout "$1" || + die "Could not checkout $1" fi HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" @@ -598,12 +624,19 @@ first and then run 'git rebase --continue' again." # This ensures that commits on merged, but otherwise # unrelated side branches are left alone. (Think "X" # in the man page's example.) - mkdir "$REWRITTEN" && - for c in $(git merge-base --all $HEAD $UPSTREAM) - do - echo $ONTO > "$REWRITTEN"/$c || + if test -z "$REBASE_ROOT" + then + mkdir "$REWRITTEN" && + for c in $(git merge-base --all $HEAD $UPSTREAM) + do + echo $ONTO > "$REWRITTEN"/$c || + die "Could not init rewritten commits" + done + else + mkdir "$REWRITTEN" && + echo $ONTO > "$REWRITTEN"/root || die "Could not init rewritten commits" - done + fi # No cherry-pick because our first pass is to determine # parents to rewrite and skipping dropped commits would # prematurely end our probe @@ -613,12 +646,21 @@ first and then run 'git rebase --continue' again." MERGES_OPTION="--no-merges --cherry-pick" fi - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) SHORTHEAD=$(git rev-parse --short $HEAD) SHORTONTO=$(git rev-parse --short $ONTO) + if test -z "$REBASE_ROOT" + # this is now equivalent to ! -z "$UPSTREAM" + then + SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) + REVISIONS=$UPSTREAM...$HEAD + SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD + else + REVISIONS=$ONTO...$HEAD + SHORTREVISIONS=$SHORTHEAD + fi git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ --abbrev=7 --reverse --left-right --topo-order \ - $UPSTREAM...$HEAD | \ + $REVISIONS | \ sed -n "s/^>//p" | while read shortsha1 rest do if test t != "$PRESERVE_MERGES" @@ -626,14 +668,19 @@ first and then run 'git rebase --continue' again." echo "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) - do - if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) - then - preserve=f - fi - done + if test -z "$REBASE_ROOT" + then + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) + do + if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) + then + preserve=f + fi + done + else + preserve=f + fi if test f = "$preserve" then touch "$REWRITTEN"/$sha1 @@ -647,11 +694,11 @@ first and then run 'git rebase --continue' again." then mkdir "$DROPPED" # Save all non-cherry-picked changes - git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \ + git rev-list $REVISIONS --left-right --cherry-pick | \ sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks # Now all commits and note which ones are missing in # not-cherry-picks and hence being dropped - git rev-list $UPSTREAM..$HEAD | + git rev-list $REVISIONS | while read rev do if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = "" @@ -660,17 +707,18 @@ first and then run 'git rebase --continue' again." # not worthwhile, we don't want to track its multiple heads, # just the history of its first-parent for others that will # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" rm "$REWRITTEN"/$rev fi done fi + test -s "$TODO" || echo noop >> "$TODO" cat >> "$TODO" << EOF -# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO +# Rebase $SHORTREVISIONS onto $SHORTONTO # # Commands: # p, pick = use commit diff --git a/git-rebase.sh b/git-rebase.sh index ebd4df3a0e..6d3eddbada 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]' +USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge prec=4 verbose= git_am_opt= +rebase_root= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -297,6 +298,9 @@ do -C*) git_am_opt="$git_am_opt $1" ;; + --root) + rebase_root=t + ;; -*) usage ;; @@ -344,17 +348,29 @@ case "$diff" in ;; esac -# The upstream head must be given. Make sure it is valid. -upstream_name="$1" -upstream=`git rev-parse --verify "${upstream_name}^0"` || - die "invalid upstream $upstream_name" +if test -z "$rebase_root" +then + # The upstream head must be given. Make sure it is valid. + upstream_name="$1" + shift + upstream=`git rev-parse --verify "${upstream_name}^0"` || + die "invalid upstream $upstream_name" + unset root_flag + upstream_arg="$upstream_name" +else + test -z "$newbase" && die "--root must be used with --onto" + unset upstream_name + unset upstream + root_flag="--root" + upstream_arg="$root_flag" +fi # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} onto=$(git rev-parse --verify "${onto_name}^0") || exit # If a hook exists, give it a chance to interrupt -run_pre_rebase_hook ${1+"$@"} +run_pre_rebase_hook "$upstream_arg" "$@" # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) @@ -362,16 +378,16 @@ run_pre_rebase_hook ${1+"$@"} # $head_name -- refs/heads/<that-branch> or "detached HEAD" switch_to= case "$#" in -2) +1) # Is it "rebase other $branchname" or "rebase other $commit"? - branch_name="$2" - switch_to="$2" + branch_name="$1" + switch_to="$1" - if git show-ref --verify --quiet -- "refs/heads/$2" && - branch=$(git rev-parse -q --verify "refs/heads/$2") + if git show-ref --verify --quiet -- "refs/heads/$1" && + branch=$(git rev-parse -q --verify "refs/heads/$1") then - head_name="refs/heads/$2" - elif branch=$(git rev-parse -q --verify "$2") + head_name="refs/heads/$1" + elif branch=$(git rev-parse -q --verify "$1") then head_name="detached HEAD" else @@ -393,7 +409,8 @@ case "$#" in esac orig_head=$branch -# Now we are rebasing commits $upstream..$branch on top of $onto +# Now we are rebasing commits $upstream..$branch (or with --root, +# everything leading up to $branch) on top of $onto # Check if we are already based on $onto with linear history, # but this should be done only when upstream and onto are the same. @@ -429,10 +446,17 @@ then exit 0 fi +if test -n "$rebase_root" +then + revisions="$onto..$orig_head" +else + revisions="$upstream..$orig_head" +fi + if test -z "$do_merge" then git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - "$upstream..$orig_head" | + $root_flag "$revisions" | git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? @@ -455,7 +479,7 @@ echo "$orig_head" > "$dotest/orig-head" echo "$head_name" > "$dotest/head-name" msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"` +for cmt in `git rev-list --reverse --no-merges "$revisions"` do msgnum=$(($msgnum + 1)) echo "$cmt" > "$dotest/cmt.$msgnum" diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh new file mode 100755 index 0000000000..6359580262 --- /dev/null +++ b/t/t3412-rebase-root.sh @@ -0,0 +1,187 @@ +#!/bin/sh + +test_description='git rebase --root + +Tests if git rebase --root --onto <newparent> can rebase the root commit. +' +. ./test-lib.sh + +test_expect_success 'prepare repository' ' + echo 1 > A && + git add A && + git commit -m 1 && + echo 2 > A && + git add A && + git commit -m 2 && + git symbolic-ref HEAD refs/heads/other && + rm .git/index && + echo 3 > B && + git add B && + git commit -m 3 && + echo 1 > A && + git add A && + git commit -m 1b && + echo 4 > B && + git add B && + git commit -m 4 +' + +test_expect_success 'rebase --root expects --onto' ' + test_must_fail git rebase --root +' + +test_expect_success 'setup pre-rebase hook' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +echo "\$1,\$2" >.git/PRE-REBASE-INPUT +EOF + chmod +x .git/hooks/pre-rebase +' +cat > expect <<EOF +4 +3 +2 +1 +EOF + +test_expect_success 'rebase --root --onto <newbase>' ' + git checkout -b work && + git rebase --root --onto master && + git log --pretty=tformat:"%s" > rebased && + test_cmp expect rebased +' + +test_expect_success 'pre-rebase got correct input (1)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase --root --onto <newbase> <branch>' ' + git branch work2 other && + git rebase --root --onto master work2 && + git log --pretty=tformat:"%s" > rebased2 && + test_cmp expect rebased2 +' + +test_expect_success 'pre-rebase got correct input (2)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2 +' + +test_expect_success 'rebase -i --root --onto <newbase>' ' + git checkout -b work3 other && + GIT_EDITOR=: git rebase -i --root --onto master && + git log --pretty=tformat:"%s" > rebased3 && + test_cmp expect rebased3 +' + +test_expect_success 'pre-rebase got correct input (3)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase -i --root --onto <newbase> <branch>' ' + git branch work4 other && + GIT_EDITOR=: git rebase -i --root --onto master work4 && + git log --pretty=tformat:"%s" > rebased4 && + test_cmp expect rebased4 +' + +test_expect_success 'pre-rebase got correct input (4)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4 +' + +test_expect_success 'rebase -i -p with linear history' ' + git checkout -b work5 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --pretty=tformat:"%s" > rebased5 && + test_cmp expect rebased5 +' + +test_expect_success 'pre-rebase got correct input (5)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'set up merge history' ' + git checkout other^ && + git checkout -b side && + echo 5 > C && + git add C && + git commit -m 5 && + git checkout other && + git merge side +' + +sed 's/#/ /g' > expect-side <<'EOF' +* Merge branch 'side' into other +|\## +| * 5 +* | 4 +|/## +* 3 +* 2 +* 1 +EOF + +test_expect_success 'rebase -i -p with merge' ' + git checkout -b work6 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --graph --topo-order --pretty=tformat:"%s" > rebased6 && + test_cmp expect-side rebased6 +' + +test_expect_success 'set up second root and merge' ' + git symbolic-ref HEAD refs/heads/third && + rm .git/index && + rm A B C && + echo 6 > D && + git add D && + git commit -m 6 && + git checkout other && + git merge third +' + +sed 's/#/ /g' > expect-third <<'EOF' +* Merge branch 'third' into other +|\## +| * 6 +* | Merge branch 'side' into other +|\ \## +| * | 5 +* | | 4 +|/ /## +* | 3 +|/## +* 2 +* 1 +EOF + +test_expect_success 'rebase -i -p with two roots' ' + git checkout -b work7 other && + GIT_EDITOR=: git rebase -i -p --root --onto master && + git log --graph --topo-order --pretty=tformat:"%s" > rebased7 && + test_cmp expect-third rebased7 +' + +test_expect_success 'setup pre-rebase hook that fails' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +false +EOF + chmod +x .git/hooks/pre-rebase +' + +test_expect_success 'pre-rebase hook stops rebase' ' + git checkout -b stops1 other && + GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1 + test 0 = $(git rev-list other...stops1 | wc -l) +' + +test_expect_success 'pre-rebase hook stops rebase -i' ' + git checkout -b stops2 other && + GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2 + test 0 = $(git rev-list other...stops2 | wc -l) +' + +test_done |