rebase -i: learn to rebase root commit
Teach git-rebase -i a new option --root, which instructs it to rebase
the entire history leading up to <branch>. This is mainly for
symmetry with ordinary git-rebase; it cannot be used to edit the root
commit in-place (it requires --onto <newbase>). Commits that already
exist in <newbase> are skipped.
In the normal mode of operation, this is fairly straightforward. We
run cherry-pick in a loop, and cherry-pick has supported picking the
root commit since f95ebf7
(Allow cherry-picking root commits,
2008-07-04).
In --preserve-merges mode, we track the mapping from old to rewritten
commits and use it to update the parent list of each commit. In this
case, we define 'rebase -i -p --root --onto $onto $branch' to rewrite
the parent list of all root commit(s) on $branch to contain $onto
instead.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
190f53232d
commit
d911d1465d
@ -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,28 +571,36 @@ first and then run 'git rebase --continue' again."
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
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"
|
||||
|
||||
UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
|
||||
run_pre_rebase_hook ${1+"$@"}
|
||||
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
|
||||
|
||||
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?"
|
||||
@ -599,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
|
||||
@ -614,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"
|
||||
@ -627,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
|
||||
@ -648,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)" = ""
|
||||
@ -661,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
|
||||
|
@ -67,6 +67,100 @@ 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 &&
|
||||
@ -83,4 +177,11 @@ test_expect_success 'pre-rebase hook stops rebase' '
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user