Merge branch 'mz/rebase'

* mz/rebase: (34 commits)
  rebase: define options in OPTIONS_SPEC
  Makefile: do not install sourced rebase scripts
  rebase: use @{upstream} if no upstream specified
  rebase -i: remove unnecessary state rebase-root
  rebase -i: don't read unused variable preserve_merges
  git-rebase--am: remove unnecessary --3way option
  rebase -m: don't print exit code 2 when merge fails
  rebase -m: remember allow_rerere_autoupdate option
  rebase: remember strategy and strategy options
  rebase: remember verbose option
  rebase: extract code for writing basic state
  rebase: factor out sub command handling
  rebase: make -v a tiny bit more verbose
  rebase -i: align variable names
  rebase: show consistent conflict resolution hint
  rebase: extract am code to new source file
  rebase: extract merge code to new source file
  rebase: remove $branch as synonym for $orig_head
  rebase -i: support --stat
  rebase: factor out call to pre-rebase hook
  ...
This commit is contained in:
Junio C Hamano 2011-04-28 14:11:39 -07:00
commit 78c6e0f3fa
14 changed files with 847 additions and 871 deletions

2
.gitignore vendored
View File

@ -101,7 +101,9 @@
/git-quiltimport
/git-read-tree
/git-rebase
/git-rebase--am
/git-rebase--interactive
/git-rebase--merge
/git-receive-pack
/git-reflog
/git-relink

View File

@ -641,7 +641,7 @@ branch.<name>.remote::
branch.<name>.merge::
Defines, together with branch.<name>.remote, the upstream branch
for the given branch. It tells 'git fetch'/'git pull' which
for the given branch. It tells 'git fetch'/'git pull'/'git rebase' which
branch to merge and can also affect 'git push' (see push.default).
When in branch <name>, it tells 'git fetch' the default
refspec to be marked for merging in FETCH_HEAD. The value is

View File

@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git rebase' [-i | --interactive] [options] [--onto <newbase>]
<upstream> [<branch>]
[<upstream>] [<branch>]
'git rebase' [-i | --interactive] [options] --onto <newbase>
--root [<branch>]
@ -21,6 +21,12 @@ If <branch> is specified, 'git rebase' will perform an automatic
`git checkout <branch>` before doing anything else. Otherwise
it remains on the current branch.
If <upstream> is not specified, the upstream configured in
branch.<name>.remote and branch.<name>.merge options will be used; see
linkgit:git-config[1] for details. If you are currently not on any
branch or if the current branch does not have a configured upstream,
the rebase will abort.
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` (or
@ -217,7 +223,8 @@ leave out at most one of A and B, in which case it defaults to HEAD.
<upstream>::
Upstream branch to compare against. May be any valid commit,
not just an existing branch name.
not just an existing branch name. Defaults to the configured
upstream for the current branch.
<branch>::
Working branch; defaults to HEAD.

View File

@ -368,7 +368,6 @@ SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase--interactive.sh
SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.sh
@ -378,6 +377,9 @@ SCRIPT_SH += git-web--browse.sh
SCRIPT_LIB += git-mergetool--lib
SCRIPT_LIB += git-parse-remote
SCRIPT_LIB += git-rebase--am
SCRIPT_LIB += git-rebase--interactive
SCRIPT_LIB += git-rebase--merge
SCRIPT_LIB += git-sh-setup
SCRIPT_PERL += git-add--interactive.perl

View File

@ -50,3 +50,41 @@ get_remote_merge_branch () {
esac
esac
}
error_on_missing_default_upstream () {
cmd="$1"
op_type="$2"
op_prep="$3"
example="$4"
branch_name=$(git symbolic-ref -q HEAD)
if test -z "$branch_name"
then
echo "You are not currently on a branch, so I cannot use any
'branch.<branchname>.merge' in your configuration file.
Please specify which branch you want to $op_type $op_prep on the command
line and try again (e.g. '$example').
See git-${cmd}(1) for details."
else
echo "You asked me to $cmd without telling me which branch you
want to $op_type $op_prep, and 'branch.${branch_name#refs/heads/}.merge' in
your configuration file does not tell me, either. Please
specify which branch you want to use on the command line and
try again (e.g. '$example').
See git-${cmd}(1) for details.
If you often $op_type $op_prep the same branch, you may want to
use something like the following in your configuration file:
[branch \"${branch_name#refs/heads/}\"]
remote = <nickname>
merge = <remote-ref>"
test rebase = "$op_type" &&
echo " rebase = true"
echo "
[remote \"<nickname>\"]
url = <url>
fetch = <refspec>
See git-config(1) for details."
fi
exit 1
}

View File

@ -168,34 +168,10 @@ error_on_no_merge_candidates () {
echo "You asked to pull from the remote '$1', but did not specify"
echo "a branch. Because this is not the default configured remote"
echo "for your current branch, you must specify a branch on the command line."
elif [ -z "$curr_branch" ]; then
echo "You are not currently on a branch, so I cannot use any"
echo "'branch.<branchname>.merge' in your configuration file."
echo "Please specify which remote branch you want to use on the command"
echo "line and try again (e.g. 'git pull <repository> <refspec>')."
echo "See git-pull(1) for details."
elif [ -z "$upstream" ]; then
echo "You asked me to pull without telling me which branch you"
echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
echo "your configuration file does not tell me, either. Please"
echo "specify which branch you want to use on the command line and"
echo "try again (e.g. 'git pull <repository> <refspec>')."
echo "See git-pull(1) for details."
echo
echo "If you often $op_type $op_prep the same branch, you may want to"
echo "use something like the following in your configuration file:"
echo
echo " [branch \"${curr_branch}\"]"
echo " remote = <nickname>"
echo " merge = <remote-ref>"
test rebase = "$op_type" &&
echo " rebase = true"
echo
echo " [remote \"<nickname>\"]"
echo " url = <url>"
echo " fetch = <refspec>"
echo
echo "See git-config(1) for details."
elif [ -z "$curr_branch" -o -z "$upstream" ]; then
. git-parse-remote
error_on_missing_default_upstream "pull" $op_type $op_prep \
"git pull <repository> <refspec>"
else
echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
echo "from the remote, but no such ref was fetched."

30
git-rebase--am.sh Normal file
View File

@ -0,0 +1,30 @@
#!/bin/sh
#
# Copyright (c) 2010 Junio C Hamano.
#
. git-sh-setup
case "$action" in
continue)
git am --resolved --resolvemsg="$resolvemsg" &&
move_to_original_branch
exit
;;
skip)
git am --skip --resolvemsg="$resolvemsg" &&
move_to_original_branch
exit
;;
esac
test -n "$rebase_root" && root_flag=--root
git format-patch -k --stdout --full-index --ignore-if-in-upstream \
--src-prefix=a/ --dst-prefix=b/ \
--no-renames $root_flag "$revisions" |
git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
move_to_original_branch
ret=$?
test 0 != $ret -a -d "$state_dir" && write_basic_state
exit $ret

753
git-rebase--interactive.sh Executable file → Normal file

File diff suppressed because it is too large Load Diff

151
git-rebase--merge.sh Normal file
View File

@ -0,0 +1,151 @@
#!/bin/sh
#
# Copyright (c) 2010 Junio C Hamano.
#
. git-sh-setup
prec=4
read_state () {
onto_name=$(cat "$state_dir"/onto_name) &&
end=$(cat "$state_dir"/end) &&
msgnum=$(cat "$state_dir"/msgnum)
}
continue_merge () {
test -d "$state_dir" || die "$state_dir 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 to use git add?"
die "$resolvemsg"
fi
cmt=`cat "$state_dir/current"`
if ! git diff-index --quiet --ignore-submodules HEAD --
then
if ! git commit --no-verify -C "$cmt"
then
echo "Commit failed, please do not call \"git commit\""
echo "directly, but instead do one of the following: "
die "$resolvemsg"
fi
if test -z "$GIT_QUIET"
then
printf "Committed: %0${prec}d " $msgnum
fi
echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten"
else
if test -z "$GIT_QUIET"
then
printf "Already applied: %0${prec}d " $msgnum
fi
fi
test -z "$GIT_QUIET" &&
GIT_PAGER='' git log --format=%s -1 "$cmt"
# onto the next patch:
msgnum=$(($msgnum + 1))
echo "$msgnum" >"$state_dir/msgnum"
}
call_merge () {
cmt="$(cat "$state_dir/cmt.$1")"
echo "$cmt" > "$state_dir/current"
hd=$(git rev-parse --verify HEAD)
cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
msgnum=$(cat "$state_dir/msgnum")
eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
eval GITHEAD_$hd='$onto_name'
export GITHEAD_$cmt GITHEAD_$hd
if test -n "$GIT_QUIET"
then
GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
fi
test -z "$strategy" && strategy=recursive
eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"'
rv=$?
case "$rv" in
0)
unset GITHEAD_$cmt GITHEAD_$hd
return
;;
1)
git rerere $allow_rerere_autoupdate
die "$resolvemsg"
;;
2)
echo "Strategy: $strategy failed, try another" 1>&2
die "$resolvemsg"
;;
*)
die "Unknown exit code ($rv) from command:" \
"git-merge-$strategy $cmt^ -- HEAD $cmt"
;;
esac
}
finish_rb_merge () {
move_to_original_branch
git notes copy --for-rewrite=rebase < "$state_dir"/rewritten
if test -x "$GIT_DIR"/hooks/post-rewrite &&
test -s "$state_dir"/rewritten; then
"$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten
fi
rm -r "$state_dir"
say All done.
}
case "$action" in
continue)
read_state
continue_merge
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge
exit
;;
skip)
read_state
git rerere clear
msgnum=$(($msgnum + 1))
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge
exit
;;
esac
mkdir -p "$state_dir"
echo "$onto_name" > "$state_dir/onto_name"
write_basic_state
msgnum=0
for cmt in `git rev-list --reverse --no-merges "$revisions"`
do
msgnum=$(($msgnum + 1))
echo "$cmt" > "$state_dir/cmt.$msgnum"
done
echo 1 >"$state_dir/msgnum"
echo $msgnum >"$state_dir/end"
end=$msgnum
msgnum=1
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge

View File

@ -3,7 +3,7 @@
# Copyright (c) 2005 Junio C Hamano.
#
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]'
USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
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>
@ -28,7 +28,39 @@ Example: git-rebase master~1 topic
'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
git rebase [-i] [options] --onto <newbase> --root [<branch>]
git-rebase [-i] --continue | --abort | --skip
--
Available options are
v,verbose! display a diffstat of what changed upstream
q,quiet! be quiet. implies --no-stat
onto=! rebase onto given branch instead of upstream
p,preserve-merges! try to recreate merges instead of ignoring them
s,strategy=! use the given merge strategy
no-ff! cherry-pick all commits, even if unchanged
m,merge! use merging strategies to rebase
i,interactive! let the user edit the list of commits to rebase
f,force-rebase! force rebase even if branch is up to date
X,strategy-option=! pass the argument through to the merge strategy
stat! display a diffstat of what changed upstream
n,no-stat! do not show diffstat of what changed upstream
verify allow pre-rebase hook to run
rerere-autoupdate allow rerere to update index with resolved conflicts
root! rebase all reachable commits up to the root(s)
autosquash move commits that begin with squash!/fixup! under -i
committer-date-is-author-date! passed to 'git am'
ignore-date! passed to 'git am'
whitespace=! passed to 'git apply'
ignore-whitespace! passed to 'git apply'
C=! passed to 'git apply'
Actions:
continue! continue rebasing process
abort! abort rebasing process and restore original branch
skip! skip current patch and continue rebasing process
"
. git-sh-setup
set_reflog_action rebase
require_work_tree
@ -36,18 +68,18 @@ cd_to_toplevel
LF='
'
OK_TO_SKIP_PRE_REBASE=
RESOLVEMSG="
ok_to_skip_pre_rebase=
resolvemsg="
When you have resolved this problem run \"git rebase --continue\".
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\".
"
unset newbase
strategy=recursive
unset onto
strategy=
strategy_opts=
do_merge=
dotest="$GIT_DIR"/rebase-merge
prec=4
merge_dir="$GIT_DIR"/rebase-merge
apply_dir="$GIT_DIR"/rebase-apply
verbose=
diffstat=
test "$(git config --bool rebase.stat)" = true && diffstat=t
@ -55,92 +87,67 @@ git_am_opt=
rebase_root=
force_rebase=
allow_rerere_autoupdate=
# Non-empty if a rebase was in progress when 'git rebase' was invoked
in_progress=
# One of {am, merge, interactive}
type=
# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge}
state_dir=
# One of {'', continue, skip, abort}, as parsed from command line
action=
preserve_merges=
autosquash=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
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"
read_basic_state () {
head_name=$(cat "$state_dir"/head-name) &&
onto=$(cat "$state_dir"/onto) &&
# We always write to orig-head, but interactive rebase used to write to
# head. Fall back to reading from head to cover for the case that the
# user upgraded git with an ongoing interactive rebase.
if test -f "$state_dir"/orig-head
then
echo "You still have unmerged paths in your index"
echo "did you forget to use git add?"
die "$RESOLVEMSG"
fi
cmt=`cat "$dotest/current"`
if ! git diff-index --quiet --ignore-submodules HEAD --
then
if ! git commit --no-verify -C "$cmt"
then
echo "Commit failed, please do not call \"git commit\""
echo "directly, but instead do one of the following: "
die "$RESOLVEMSG"
fi
if test -z "$GIT_QUIET"
then
printf "Committed: %0${prec}d " $msgnum
fi
echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
orig_head=$(cat "$state_dir"/orig-head)
else
if test -z "$GIT_QUIET"
then
printf "Already applied: %0${prec}d " $msgnum
fi
fi
test -z "$GIT_QUIET" &&
GIT_PAGER='' git log --format=%s -1 "$cmt"
prev_head=`git rev-parse HEAD^0`
# save the resulting commit so we can read-tree on it later
echo "$prev_head" > "$dotest/prev_head"
# onto the next patch:
msgnum=$(($msgnum + 1))
echo "$msgnum" >"$dotest/msgnum"
orig_head=$(cat "$state_dir"/head)
fi &&
GIT_QUIET=$(cat "$state_dir"/quiet) &&
test -f "$state_dir"/verbose && verbose=t
test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
test -f "$state_dir"/strategy_opts &&
strategy_opts="$(cat "$state_dir"/strategy_opts)"
test -f "$state_dir"/allow_rerere_autoupdate &&
allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)"
}
call_merge () {
cmt="$(cat "$dotest/cmt.$1")"
echo "$cmt" > "$dotest/current"
hd=$(git rev-parse --verify HEAD)
cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
msgnum=$(cat "$dotest/msgnum")
end=$(cat "$dotest/end")
eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
export GITHEAD_$cmt GITHEAD_$hd
if test -n "$GIT_QUIET"
then
GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
fi
eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"'
rv=$?
case "$rv" in
0)
unset GITHEAD_$cmt GITHEAD_$hd
return
;;
1)
git rerere $allow_rerere_autoupdate
die "$RESOLVEMSG"
;;
2)
echo "Strategy: $rv $strategy failed, try another" 1>&2
die "$RESOLVEMSG"
write_basic_state () {
echo "$head_name" > "$state_dir"/head-name &&
echo "$onto" > "$state_dir"/onto &&
echo "$orig_head" > "$state_dir"/orig-head &&
echo "$GIT_QUIET" > "$state_dir"/quiet &&
test t = "$verbose" && : > "$state_dir"/verbose
test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
test -n "$strategy_opts" && echo "$strategy_opts" > \
"$state_dir"/strategy_opts
test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
"$state_dir"/allow_rerere_autoupdate
}
output () {
case "$verbose" in
'')
output=$("$@" 2>&1 )
status=$?
test $status != 0 && printf "%s\n" "$output"
return $status
;;
*)
die "Unknown exit code ($rv) from command:" \
"git-merge-$strategy $cmt^ -- HEAD $cmt"
"$@"
;;
esac
}
move_to_original_branch () {
test -z "$head_name" &&
head_name="$(cat "$dotest"/head-name)" &&
onto="$(cat "$dotest"/onto)" &&
orig_head="$(cat "$dotest"/orig-head)"
case "$head_name" in
refs/*)
message="rebase finished: $head_name onto $onto"
@ -152,42 +159,16 @@ move_to_original_branch () {
esac
}
finish_rb_merge () {
move_to_original_branch
git notes copy --for-rewrite=rebase < "$dotest"/rewritten
if test -x "$GIT_DIR"/hooks/post-rewrite &&
test -s "$dotest"/rewritten; then
"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
fi
rm -r "$dotest"
say All done.
}
is_interactive () {
while test $# != 0
do
case "$1" in
-i|--interactive)
interactive_rebase=explicit
break
;;
-p|--preserve-merges)
interactive_rebase=implied
;;
esac
shift
done
run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
export GIT_EDITOR
fi
test -n "$interactive_rebase" || test -f "$dotest"/interactive
. git-rebase--$type
}
run_pre_rebase_hook () {
if test -z "$OK_TO_SKIP_PRE_REBASE" &&
if test -z "$ok_to_skip_pre_rebase" &&
test -x "$GIT_DIR/hooks/pre-rebase"
then
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
@ -195,163 +176,94 @@ run_pre_rebase_hook () {
fi
}
test -f "$GIT_DIR"/rebase-apply/applying &&
test -f "$apply_dir"/applying &&
die 'It looks like git-am is in progress. Cannot rebase.'
is_interactive "$@" && exec git-rebase--interactive "$@"
if test -d "$apply_dir"
then
type=am
state_dir="$apply_dir"
elif test -d "$merge_dir"
then
if test -f "$merge_dir"/interactive
then
type=interactive
interactive_rebase=explicit
else
type=merge
fi
state_dir="$merge_dir"
fi
test -n "$type" && in_progress=t
total_argc=$#
while test $# != 0
do
case "$1" in
--no-verify)
OK_TO_SKIP_PRE_REBASE=yes
ok_to_skip_pre_rebase=yes
;;
--verify)
OK_TO_SKIP_PRE_REBASE=
ok_to_skip_pre_rebase=
;;
--continue)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
die "No rebase in progress?"
git update-index --ignore-submodules --refresh &&
git diff-files --quiet --ignore-submodules || {
echo "You must edit all merge conflicts and then"
echo "mark them as resolved using git add"
exit 1
}
if test -d "$dotest"
then
prev_head=$(cat "$dotest/prev_head")
end=$(cat "$dotest/end")
msgnum=$(cat "$dotest/msgnum")
onto=$(cat "$dotest/onto")
GIT_QUIET=$(cat "$dotest/quiet")
continue_merge
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge
exit
fi
head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--skip)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
die "No rebase in progress?"
git reset --hard HEAD || exit $?
if test -d "$dotest"
then
git rerere clear
prev_head=$(cat "$dotest/prev_head")
end=$(cat "$dotest/end")
msgnum=$(cat "$dotest/msgnum")
msgnum=$(($msgnum + 1))
onto=$(cat "$dotest/onto")
GIT_QUIET=$(cat "$dotest/quiet")
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge
exit
fi
head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--abort)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
die "No rebase in progress?"
git rerere clear
test -d "$dotest" || dotest="$GIT_DIR"/rebase-apply
head_name="$(cat "$dotest"/head-name)" &&
case "$head_name" in
refs/*)
git symbolic-ref HEAD $head_name ||
die "Could not move back to $head_name"
;;
esac
git reset --hard $(cat "$dotest/orig-head")
rm -r "$dotest"
exit
--continue|--skip|--abort)
test $total_argc -eq 2 || usage
action=${1##--}
;;
--onto)
test 2 -le "$#" || usage
newbase="$2"
onto="$2"
shift
;;
-M|-m|--m|--me|--mer|--merg|--merge)
-i)
interactive_rebase=explicit
;;
-p)
preserve_merges=t
test -z "$interactive_rebase" && interactive_rebase=implied
;;
--autosquash)
autosquash=t
;;
--no-autosquash)
autosquash=
;;
-M|-m)
do_merge=t
;;
-X*|--strategy-option*)
case "$#,$1" in
1,-X|1,--strategy-option)
usage ;;
*,-X|*,--strategy-option)
newopt="$2"
shift ;;
*,--strategy-option=*)
newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;;
*,-X*)
newopt="$(expr " $1" : ' -X\(.*\)')" ;;
1,*)
usage ;;
esac
strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")"
-X)
shift
strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")"
do_merge=t
test -z "$strategy" && strategy=recursive
;;
-s)
shift
strategy="$1"
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 "z$1" : 'z-[^=]*=\(.*\)'` ;;
1,*)
usage ;;
*)
strategy="$2"
shift ;;
esac
do_merge=t
;;
-n|--no-stat)
-n)
diffstat=
;;
--stat)
diffstat=t
;;
-v|--verbose)
-v)
verbose=t
diffstat=t
GIT_QUIET=
;;
-q|--quiet)
-q)
GIT_QUIET=t
git_am_opt="$git_am_opt -q"
verbose=
diffstat=
;;
--whitespace=*)
git_am_opt="$git_am_opt $1"
--whitespace)
shift
git_am_opt="$git_am_opt --whitespace=$1"
case "$1" in
--whitespace=fix|--whitespace=strip)
fix|strip)
force_rebase=t
;;
esac
@ -363,22 +275,21 @@ do
git_am_opt="$git_am_opt $1"
force_rebase=t
;;
-C*)
git_am_opt="$git_am_opt $1"
-C)
shift
git_am_opt="$git_am_opt -C$1"
;;
--root)
rebase_root=t
;;
-f|--f|--fo|--for|--forc|--force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
-f|--no-ff)
force_rebase=t
;;
--rerere-autoupdate|--no-rerere-autoupdate)
allow_rerere_autoupdate="$1"
;;
-*)
usage
;;
*)
--)
shift
break
;;
esac
@ -386,58 +297,106 @@ do
done
test $# -gt 2 && usage
if test $# -eq 0 && test -z "$rebase_root"
if test -n "$action"
then
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
die 'A rebase is in progress, try --continue, --skip or --abort.'
test -z "$in_progress" && die "No rebase in progress?"
# Only interactive rebase uses detailed reflog messages
if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase
then
GIT_REFLOG_ACTION="rebase -i ($action)"
export GIT_REFLOG_ACTION
fi
fi
# Make sure we do not have $GIT_DIR/rebase-apply
if test -z "$do_merge"
case "$action" in
continue)
# Sanity check
git rev-parse --verify HEAD >/dev/null ||
die "Cannot read HEAD"
git update-index --ignore-submodules --refresh &&
git diff-files --quiet --ignore-submodules || {
echo "You must edit all merge conflicts and then"
echo "mark them as resolved using git add"
exit 1
}
read_basic_state
run_specific_rebase
;;
skip)
output git reset --hard HEAD || exit $?
read_basic_state
run_specific_rebase
;;
abort)
git rerere clear
read_basic_state
case "$head_name" in
refs/*)
git symbolic-ref HEAD $head_name ||
die "Could not move back to $head_name"
;;
esac
output git reset --hard $orig_head
rm -r "$state_dir"
exit
;;
esac
# Make sure no rebase is in progress
if test -n "$in_progress"
then
if mkdir "$GIT_DIR"/rebase-apply 2>/dev/null
then
rmdir "$GIT_DIR"/rebase-apply
else
echo >&2 '
It seems that I cannot create a rebase-apply 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 '"$GIT_DIR"'/rebase-apply
die '
It seems that there is already a '"${state_dir##*/}"' directory, and
I wonder if you are in the middle of another rebase. If that is the
case, please try
git rebase (--continue | --abort | --skip)
If that is not the case, please
rm -fr '"$state_dir"'
and run me again. I am stopping in case you still have something
valuable there.'
exit 1
fi
else
if test -d "$dotest"
then
die "previous rebase directory $dotest still exists." \
'Try git rebase (--continue | --abort | --skip)'
fi
fi
require_clean_work_tree "rebase" "Please commit or stash them."
if test -n "$interactive_rebase"
then
type=interactive
state_dir="$merge_dir"
elif test -n "$do_merge"
then
type=merge
state_dir="$merge_dir"
else
type=am
state_dir="$apply_dir"
fi
if test -z "$rebase_root"
then
# The upstream head must be given. Make sure it is valid.
upstream_name="$1"
shift
case "$#" in
0)
if ! upstream_name=$(git rev-parse --symbolic-full-name \
--verify -q @{upstream} 2>/dev/null)
then
. git-parse-remote
error_on_missing_default_upstream "rebase" "rebase" \
"against" "git rebase <upstream branch>"
fi
;;
*) upstream_name="$1"
shift
;;
esac
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"
test -z "$onto" && die "You must specify --onto when using --root"
unset upstream_name
unset upstream
root_flag="--root"
upstream_arg="$root_flag"
upstream_arg=--root
fi
# Make sure the branch to rebase onto is valid.
onto_name=${newbase-"$upstream_name"}
onto_name=${onto-"$upstream_name"}
case "$onto_name" in
*...*)
if left=${onto_name%...*} right=${onto_name#*...} &&
@ -456,13 +415,11 @@ case "$onto_name" in
fi
;;
*)
onto=$(git rev-parse --verify "${onto_name}^0") || exit
onto=$(git rev-parse --verify "${onto_name}^0") ||
die "Does not point to a valid commit: $1"
;;
esac
# If a hook exists, give it a chance to interrupt
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
@ -475,10 +432,10 @@ case "$#" in
switch_to="$1"
if git show-ref --verify --quiet -- "refs/heads/$1" &&
branch=$(git rev-parse -q --verify "refs/heads/$1")
orig_head=$(git rev-parse -q --verify "refs/heads/$1")
then
head_name="refs/heads/$1"
elif branch=$(git rev-parse -q --verify "$1")
elif orig_head=$(git rev-parse -q --verify "$1")
then
head_name="detached HEAD"
else
@ -496,20 +453,23 @@ case "$#" in
head_name="detached HEAD"
branch_name=HEAD ;# detached
fi
branch=$(git rev-parse --verify "${branch_name}^0") || exit
orig_head=$(git rev-parse --verify "${branch_name}^0") || exit
;;
esac
orig_head=$branch
# Now we are rebasing commits $upstream..$branch (or with --root,
# everything leading up to $branch) on top of $onto
require_clean_work_tree "rebase" "Please commit or stash them."
# Now we are rebasing commits $upstream..$orig_head (or with --root,
# everything leading up to $orig_head) 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.
mb=$(git merge-base "$onto" "$branch")
if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
# but this should be done only when upstream and onto are the same
# and if this is not an interactive rebase.
mb=$(git merge-base "$onto" "$orig_head")
if test "$type" != interactive && test "$upstream" = "$onto" &&
test "$mb" = "$onto" &&
# linear history?
! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null
! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
then
if test -z "$force_rebase"
then
@ -522,10 +482,8 @@ then
fi
fi
# Detach HEAD and reset the tree
say "First, rewinding head to replay your work on top of it..."
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $branch
# If a hook exists, give it a chance to interrupt
run_pre_rebase_hook "$upstream_arg" "$@"
if test -n "$diffstat"
then
@ -537,9 +495,16 @@ then
GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
test "$type" = interactive && run_specific_rebase
# Detach HEAD and reset the tree
say "First, rewinding head to replay your work on top of it..."
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast-forwarded.
if test "$mb" = "$branch"
if test "$mb" = "$orig_head"
then
say "Fast-forwarded $branch_name to $onto_name."
move_to_original_branch
@ -553,51 +518,4 @@ else
revisions="$upstream..$orig_head"
fi
if test -z "$do_merge"
then
git format-patch -k --stdout --full-index --ignore-if-in-upstream \
--src-prefix=a/ --dst-prefix=b/ \
--no-renames $root_flag "$revisions" |
git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
ret=$?
test 0 != $ret -a -d "$GIT_DIR"/rebase-apply &&
echo $head_name > "$GIT_DIR"/rebase-apply/head-name &&
echo $onto > "$GIT_DIR"/rebase-apply/onto &&
echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head &&
echo "$GIT_QUIET" > "$GIT_DIR"/rebase-apply/quiet
exit $ret
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"
echo "$onto_name" > "$dotest/onto_name"
prev_head=$orig_head
echo "$prev_head" > "$dotest/prev_head"
echo "$orig_head" > "$dotest/orig-head"
echo "$head_name" > "$dotest/head-name"
echo "$GIT_QUIET" > "$dotest/quiet"
msgnum=0
for cmt in `git rev-list --reverse --no-merges "$revisions"`
do
msgnum=$(($msgnum + 1))
echo "$cmt" > "$dotest/cmt.$msgnum"
done
echo 1 >"$dotest/msgnum"
echo $msgnum >"$dotest/end"
end=$msgnum
msgnum=1
while test "$msgnum" -le "$end"
do
call_merge "$msgnum"
continue_merge
done
finish_rb_merge
run_specific_rebase

View File

@ -158,15 +158,24 @@ test_expect_success 'Show verbose error when HEAD could not be detached' '
'
rm -f B
test_expect_success 'dump usage when upstream arg is missing' '
git checkout -b usage topic &&
test_must_fail git rebase 2>error1 &&
grep "[Uu]sage" error1 &&
test_must_fail git rebase --abort 2>error2 &&
grep "No rebase in progress" error2 &&
test_must_fail git rebase --onto master 2>error3 &&
grep "[Uu]sage" error3 &&
! grep "can.t shift" error3
test_expect_success 'fail when upstream arg is missing and not on branch' '
git checkout topic &&
test_must_fail git rebase >output.out &&
grep "You are not currently on a branch" output.out
'
test_expect_success 'fail when upstream arg is missing and not configured' '
git checkout -b no-config topic &&
test_must_fail git rebase >output.out &&
grep "branch.no-config.merge" output.out
'
test_expect_success 'default to @{upstream} when upstream arg is missing' '
git checkout -b default topic &&
git config branch.default.remote .
git config branch.default.merge refs/heads/master
git rebase &&
test "$(git rev-parse default~1)" = "$(git rev-parse master)"
'
test_expect_success 'rebase -q is quiet' '

View File

@ -35,6 +35,11 @@ test_expect_success 'rebase with git am -3 (default)' '
test_must_fail git rebase master
'
test_expect_success 'rebase --skip can not be used with other options' '
test_must_fail git rebase -v --skip &&
test_must_fail git rebase --skip -v
'
test_expect_success 'rebase --skip with am -3' '
git rebase --skip
'

View File

@ -84,6 +84,16 @@ testrebase() {
test_cmp reflog_before reflog_after &&
rm reflog_before reflog_after
'
test_expect_success 'rebase --abort can not be used with other options' '
cd "$work_dir" &&
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type master &&
test_must_fail git rebase -v --abort &&
test_must_fail git rebase --abort -v &&
git rebase --abort
'
}
testrebase "" .git/rebase-apply

View File

@ -40,4 +40,59 @@ test_expect_success 'non-interactive rebase --continue works with touched file'
git rebase --continue
'
test_expect_success 'rebase --continue can not be used with other options' '
test_must_fail git rebase -v --continue &&
test_must_fail git rebase --continue -v
'
test_expect_success 'rebase --continue remembers merge strategy and options' '
rm -fr .git/rebase-* &&
git reset --hard commit-new-file-F2-on-topic-branch &&
test_commit "commit-new-file-F3-on-topic-branch" F3 32 &&
test_when_finished "rm -fr test-bin funny.was.run" &&
mkdir test-bin &&
cat >test-bin/git-merge-funny <<-EOF
#!$SHELL_PATH
case "\$1" in --opt) ;; *) exit 2 ;; esac
shift &&
>funny.was.run &&
exec git merge-recursive "\$@"
EOF
chmod +x test-bin/git-merge-funny &&
(
PATH=./test-bin:$PATH
test_must_fail git rebase -s funny -Xopt master topic
) &&
test -f funny.was.run &&
rm funny.was.run &&
echo "Resolved" >F2 &&
git add F2 &&
(
PATH=./test-bin:$PATH
git rebase --continue
) &&
test -f funny.was.run
'
test_expect_success 'rebase --continue remembers --rerere-autoupdate' '
rm -fr .git/rebase-* &&
git reset --hard commit-new-file-F3-on-topic-branch &&
git checkout master
test_commit "commit-new-file-F3" F3 3 &&
git config rerere.enabled true &&
test_must_fail git rebase -m master topic &&
echo "Resolved" >F2 &&
git add F2 &&
test_must_fail git rebase --continue &&
echo "Resolved" >F3 &&
git add F3 &&
git rebase --continue &&
git reset --hard topic@{1} &&
test_must_fail git rebase -m --rerere-autoupdate master &&
test "$(cat F2)" = "Resolved" &&
test_must_fail git rebase --continue &&
test "$(cat F3)" = "Resolved" &&
git rebase --continue
'
test_done