Skip to content

Commit

Permalink
Merge branch 'tr/rebase-root'
Browse files Browse the repository at this point in the history
* tr/rebase-root:
  rebase: update documentation for --root
  rebase -i: learn to rebase root commit
  rebase: learn to rebase root commit
  rebase -i: execute hook only after argument checking
  • Loading branch information
Junio C Hamano committed Jan 18, 2009
2 parents 6fc2a19 + be49662 commit 90abc19
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 52 deletions.
21 changes: 16 additions & 5 deletions Documentation/git-rebase.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
110 changes: 79 additions & 31 deletions git-rebase--interactive.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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" : ' \([^ ]*\)')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -547,34 +561,46 @@ first and then run 'git rebase --continue' again."
-i)
# yeah, we know
;;
--root)
REBASE_ROOT=t
;;
--onto)
shift
ONTO=$(git rev-parse --verify "$1") ||
die "Does not point to a valid commit: $1"
;;
--)
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?"
Expand All @@ -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
Expand All @@ -613,27 +646,41 @@ 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"
then
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
Expand All @@ -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)" = ""
Expand All @@ -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
Expand Down
56 changes: 40 additions & 16 deletions git-rebase.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -297,6 +298,9 @@ do
-C*)
git_am_opt="$git_am_opt $1"
;;
--root)
rebase_root=t
;;
-*)
usage
;;
Expand Down Expand Up @@ -344,34 +348,46 @@ 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)
# $orig_head -- commit object name of tip of the branch before rebasing
# $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
Expand All @@ -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.
Expand Down Expand Up @@ -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=$?
Expand All @@ -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"
Expand Down
Loading

0 comments on commit 90abc19

Please sign in to comment.