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-quiltimport
/git-read-tree /git-read-tree
/git-rebase /git-rebase
/git-rebase--am
/git-rebase--interactive /git-rebase--interactive
/git-rebase--merge
/git-receive-pack /git-receive-pack
/git-reflog /git-reflog
/git-relink /git-relink

View File

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

View File

@ -9,7 +9,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git rebase' [-i | --interactive] [options] [--onto <newbase>] 'git rebase' [-i | --interactive] [options] [--onto <newbase>]
<upstream> [<branch>] [<upstream>] [<branch>]
'git rebase' [-i | --interactive] [options] --onto <newbase> 'git rebase' [-i | --interactive] [options] --onto <newbase>
--root [<branch>] --root [<branch>]
@ -21,6 +21,12 @@ If <branch> is specified, 'git rebase' will perform an automatic
`git checkout <branch>` before doing anything else. Otherwise `git checkout <branch>` before doing anything else. Otherwise
it remains on the current branch. 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 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 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 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>::
Upstream branch to compare against. May be any valid commit, 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>:: <branch>::
Working branch; defaults to HEAD. 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-mergetool.sh
SCRIPT_SH += git-pull.sh SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase--interactive.sh
SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-repack.sh SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.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-mergetool--lib
SCRIPT_LIB += git-parse-remote 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_LIB += git-sh-setup
SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-add--interactive.perl

View File

@ -50,3 +50,41 @@ get_remote_merge_branch () {
esac esac
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 "You asked to pull from the remote '$1', but did not specify"
echo "a branch. Because this is not the default configured remote" 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." echo "for your current branch, you must specify a branch on the command line."
elif [ -z "$curr_branch" ]; then elif [ -z "$curr_branch" -o -z "$upstream" ]; then
echo "You are not currently on a branch, so I cannot use any" . git-parse-remote
echo "'branch.<branchname>.merge' in your configuration file." error_on_missing_default_upstream "pull" $op_type $op_prep \
echo "Please specify which remote branch you want to use on the command" "git pull <repository> <refspec>"
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."
else else
echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'" echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
echo "from the remote, but no such ref was fetched." 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. # 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 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts 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> 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 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 . git-sh-setup
set_reflog_action rebase set_reflog_action rebase
require_work_tree require_work_tree
@ -36,18 +68,18 @@ cd_to_toplevel
LF=' LF='
' '
OK_TO_SKIP_PRE_REBASE= ok_to_skip_pre_rebase=
RESOLVEMSG=" resolvemsg="
When you have resolved this problem run \"git rebase --continue\". When you have resolved this problem run \"git rebase --continue\".
If you would prefer to skip this patch, instead run \"git rebase --skip\". 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\". To restore the original branch and stop rebasing run \"git rebase --abort\".
" "
unset newbase unset onto
strategy=recursive strategy=
strategy_opts= strategy_opts=
do_merge= do_merge=
dotest="$GIT_DIR"/rebase-merge merge_dir="$GIT_DIR"/rebase-merge
prec=4 apply_dir="$GIT_DIR"/rebase-apply
verbose= verbose=
diffstat= diffstat=
test "$(git config --bool rebase.stat)" = true && diffstat=t test "$(git config --bool rebase.stat)" = true && diffstat=t
@ -55,92 +87,67 @@ git_am_opt=
rebase_root= rebase_root=
force_rebase= force_rebase=
allow_rerere_autoupdate= 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 () { read_basic_state () {
test -n "$prev_head" || die "prev_head must be defined" head_name=$(cat "$state_dir"/head-name) &&
test -d "$dotest" || die "$dotest directory does not exist" onto=$(cat "$state_dir"/onto) &&
# We always write to orig-head, but interactive rebase used to write to
unmerged=$(git ls-files -u) # head. Fall back to reading from head to cover for the case that the
if test -n "$unmerged" # user upgraded git with an ongoing interactive rebase.
if test -f "$state_dir"/orig-head
then then
echo "You still have unmerged paths in your index" orig_head=$(cat "$state_dir"/orig-head)
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"
else else
if test -z "$GIT_QUIET" orig_head=$(cat "$state_dir"/head)
then fi &&
printf "Already applied: %0${prec}d " $msgnum GIT_QUIET=$(cat "$state_dir"/quiet) &&
fi test -f "$state_dir"/verbose && verbose=t
fi test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
test -z "$GIT_QUIET" && test -f "$state_dir"/strategy_opts &&
GIT_PAGER='' git log --format=%s -1 "$cmt" strategy_opts="$(cat "$state_dir"/strategy_opts)"
test -f "$state_dir"/allow_rerere_autoupdate &&
prev_head=`git rev-parse HEAD^0` allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)"
# 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"
} }
call_merge () { write_basic_state () {
cmt="$(cat "$dotest/cmt.$1")" echo "$head_name" > "$state_dir"/head-name &&
echo "$cmt" > "$dotest/current" echo "$onto" > "$state_dir"/onto &&
hd=$(git rev-parse --verify HEAD) echo "$orig_head" > "$state_dir"/orig-head &&
cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) echo "$GIT_QUIET" > "$state_dir"/quiet &&
msgnum=$(cat "$dotest/msgnum") test t = "$verbose" && : > "$state_dir"/verbose
end=$(cat "$dotest/end") test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' test -n "$strategy_opts" && echo "$strategy_opts" > \
eval GITHEAD_$hd='$(cat "$dotest/onto_name")' "$state_dir"/strategy_opts
export GITHEAD_$cmt GITHEAD_$hd test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
if test -n "$GIT_QUIET" "$state_dir"/allow_rerere_autoupdate
then }
GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
fi output () {
eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' case "$verbose" in
rv=$? '')
case "$rv" in output=$("$@" 2>&1 )
0) status=$?
unset GITHEAD_$cmt GITHEAD_$hd test $status != 0 && printf "%s\n" "$output"
return return $status
;;
1)
git rerere $allow_rerere_autoupdate
die "$RESOLVEMSG"
;;
2)
echo "Strategy: $rv $strategy failed, try another" 1>&2
die "$RESOLVEMSG"
;; ;;
*) *)
die "Unknown exit code ($rv) from command:" \ "$@"
"git-merge-$strategy $cmt^ -- HEAD $cmt"
;; ;;
esac esac
} }
move_to_original_branch () { 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 case "$head_name" in
refs/*) refs/*)
message="rebase finished: $head_name onto $onto" message="rebase finished: $head_name onto $onto"
@ -152,42 +159,16 @@ move_to_original_branch () {
esac esac
} }
finish_rb_merge () { run_specific_rebase () {
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
if [ "$interactive_rebase" = implied ]; then if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=: GIT_EDITOR=:
export GIT_EDITOR export GIT_EDITOR
fi fi
. git-rebase--$type
test -n "$interactive_rebase" || test -f "$dotest"/interactive
} }
run_pre_rebase_hook () { 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" test -x "$GIT_DIR/hooks/pre-rebase"
then then
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
@ -195,163 +176,94 @@ run_pre_rebase_hook () {
fi fi
} }
test -f "$GIT_DIR"/rebase-apply/applying && test -f "$apply_dir"/applying &&
die 'It looks like git-am is in progress. Cannot rebase.' 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 while test $# != 0
do do
case "$1" in case "$1" in
--no-verify) --no-verify)
OK_TO_SKIP_PRE_REBASE=yes ok_to_skip_pre_rebase=yes
;; ;;
--verify) --verify)
OK_TO_SKIP_PRE_REBASE= ok_to_skip_pre_rebase=
;; ;;
--continue) --continue|--skip|--abort)
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || test $total_argc -eq 2 || usage
die "No rebase in progress?" action=${1##--}
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
;; ;;
--onto) --onto)
test 2 -le "$#" || usage test 2 -le "$#" || usage
newbase="$2" onto="$2"
shift 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 do_merge=t
;; ;;
-X*|--strategy-option*) -X)
case "$#,$1" in shift
1,-X|1,--strategy-option) strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")"
usage ;; do_merge=t
*,-X|*,--strategy-option) test -z "$strategy" && strategy=recursive
newopt="$2" ;;
shift ;; -s)
*,--strategy-option=*) shift
newopt="$(expr " $1" : ' --strategy-option=\(.*\)')" ;; strategy="$1"
*,-X*)
newopt="$(expr " $1" : ' -X\(.*\)')" ;;
1,*)
usage ;;
esac
strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")"
do_merge=t do_merge=t
;; ;;
-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ -n)
--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)
diffstat= diffstat=
;; ;;
--stat) --stat)
diffstat=t diffstat=t
;; ;;
-v|--verbose) -v)
verbose=t verbose=t
diffstat=t diffstat=t
GIT_QUIET= GIT_QUIET=
;; ;;
-q|--quiet) -q)
GIT_QUIET=t GIT_QUIET=t
git_am_opt="$git_am_opt -q" git_am_opt="$git_am_opt -q"
verbose= verbose=
diffstat= diffstat=
;; ;;
--whitespace=*) --whitespace)
git_am_opt="$git_am_opt $1" shift
git_am_opt="$git_am_opt --whitespace=$1"
case "$1" in case "$1" in
--whitespace=fix|--whitespace=strip) fix|strip)
force_rebase=t force_rebase=t
;; ;;
esac esac
@ -363,22 +275,21 @@ do
git_am_opt="$git_am_opt $1" git_am_opt="$git_am_opt $1"
force_rebase=t force_rebase=t
;; ;;
-C*) -C)
git_am_opt="$git_am_opt $1" shift
git_am_opt="$git_am_opt -C$1"
;; ;;
--root) --root)
rebase_root=t 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 force_rebase=t
;; ;;
--rerere-autoupdate|--no-rerere-autoupdate) --rerere-autoupdate|--no-rerere-autoupdate)
allow_rerere_autoupdate="$1" allow_rerere_autoupdate="$1"
;; ;;
-*) --)
usage shift
;;
*)
break break
;; ;;
esac esac
@ -386,58 +297,106 @@ do
done done
test $# -gt 2 && usage test $# -gt 2 && usage
if test $# -eq 0 && test -z "$rebase_root" if test -n "$action"
then then
test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage test -z "$in_progress" && die "No rebase in progress?"
test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing && # Only interactive rebase uses detailed reflog messages
die 'A rebase is in progress, try --continue, --skip or --abort.' if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase
then
GIT_REFLOG_ACTION="rebase -i ($action)"
export GIT_REFLOG_ACTION
fi
fi fi
# Make sure we do not have $GIT_DIR/rebase-apply case "$action" in
if test -z "$do_merge" 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 then
if mkdir "$GIT_DIR"/rebase-apply 2>/dev/null die '
then It seems that there is already a '"${state_dir##*/}"' directory, and
rmdir "$GIT_DIR"/rebase-apply I wonder if you are in the middle of another rebase. If that is the
else case, please try
echo >&2 ' git rebase (--continue | --abort | --skip)
It seems that I cannot create a rebase-apply directory, and If that is not the case, please
I wonder if you are in the middle of patch application or another rm -fr '"$state_dir"'
rebase. If that is not the case, please
rm -fr '"$GIT_DIR"'/rebase-apply
and run me again. I am stopping in case you still have something and run me again. I am stopping in case you still have something
valuable there.' 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 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" if test -z "$rebase_root"
then then
# The upstream head must be given. Make sure it is valid. case "$#" in
upstream_name="$1" 0)
shift 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"` || upstream=`git rev-parse --verify "${upstream_name}^0"` ||
die "invalid upstream $upstream_name" die "invalid upstream $upstream_name"
unset root_flag
upstream_arg="$upstream_name" upstream_arg="$upstream_name"
else 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_name
unset upstream unset upstream
root_flag="--root" upstream_arg=--root
upstream_arg="$root_flag"
fi fi
# Make sure the branch to rebase onto is valid. # Make sure the branch to rebase onto is valid.
onto_name=${newbase-"$upstream_name"} onto_name=${onto-"$upstream_name"}
case "$onto_name" in case "$onto_name" in
*...*) *...*)
if left=${onto_name%...*} right=${onto_name#*...} && if left=${onto_name%...*} right=${onto_name#*...} &&
@ -456,13 +415,11 @@ case "$onto_name" in
fi 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 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 # If the branch to rebase is given, that is the branch we will rebase
# $branch_name -- branch being rebased, or HEAD (already detached) # $branch_name -- branch being rebased, or HEAD (already detached)
# $orig_head -- commit object name of tip of the branch before rebasing # $orig_head -- commit object name of tip of the branch before rebasing
@ -475,10 +432,10 @@ case "$#" in
switch_to="$1" switch_to="$1"
if git show-ref --verify --quiet -- "refs/heads/$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 then
head_name="refs/heads/$1" head_name="refs/heads/$1"
elif branch=$(git rev-parse -q --verify "$1") elif orig_head=$(git rev-parse -q --verify "$1")
then then
head_name="detached HEAD" head_name="detached HEAD"
else else
@ -496,20 +453,23 @@ case "$#" in
head_name="detached HEAD" head_name="detached HEAD"
branch_name=HEAD ;# detached branch_name=HEAD ;# detached
fi fi
branch=$(git rev-parse --verify "${branch_name}^0") || exit orig_head=$(git rev-parse --verify "${branch_name}^0") || exit
;; ;;
esac esac
orig_head=$branch
# Now we are rebasing commits $upstream..$branch (or with --root, require_clean_work_tree "rebase" "Please commit or stash them."
# everything leading up to $branch) on top of $onto
# 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, # Check if we are already based on $onto with linear history,
# but this should be done only when upstream and onto are the same. # but this should be done only when upstream and onto are the same
mb=$(git merge-base "$onto" "$branch") # and if this is not an interactive rebase.
if test "$upstream" = "$onto" && test "$mb" = "$onto" && mb=$(git merge-base "$onto" "$orig_head")
if test "$type" != interactive && test "$upstream" = "$onto" &&
test "$mb" = "$onto" &&
# linear history? # linear history?
! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
then then
if test -z "$force_rebase" if test -z "$force_rebase"
then then
@ -522,10 +482,8 @@ then
fi fi
fi fi
# Detach HEAD and reset the tree # If a hook exists, give it a chance to interrupt
say "First, rewinding head to replay your work on top of it..." run_pre_rebase_hook "$upstream_arg" "$@"
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $branch
if test -n "$diffstat" if test -n "$diffstat"
then then
@ -537,9 +495,16 @@ then
GIT_PAGER='' git diff --stat --summary "$mb" "$onto" GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi 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 # If the $onto is a proper descendant of the tip of the branch, then
# we just fast-forwarded. # we just fast-forwarded.
if test "$mb" = "$branch" if test "$mb" = "$orig_head"
then then
say "Fast-forwarded $branch_name to $onto_name." say "Fast-forwarded $branch_name to $onto_name."
move_to_original_branch move_to_original_branch
@ -553,51 +518,4 @@ else
revisions="$upstream..$orig_head" revisions="$upstream..$orig_head"
fi fi
if test -z "$do_merge" run_specific_rebase
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

View File

@ -158,15 +158,24 @@ test_expect_success 'Show verbose error when HEAD could not be detached' '
' '
rm -f B rm -f B
test_expect_success 'dump usage when upstream arg is missing' ' test_expect_success 'fail when upstream arg is missing and not on branch' '
git checkout -b usage topic && git checkout topic &&
test_must_fail git rebase 2>error1 && test_must_fail git rebase >output.out &&
grep "[Uu]sage" error1 && grep "You are not currently on a branch" output.out
test_must_fail git rebase --abort 2>error2 && '
grep "No rebase in progress" error2 &&
test_must_fail git rebase --onto master 2>error3 && test_expect_success 'fail when upstream arg is missing and not configured' '
grep "[Uu]sage" error3 && git checkout -b no-config topic &&
! grep "can.t shift" error3 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' ' 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_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' ' test_expect_success 'rebase --skip with am -3' '
git rebase --skip git rebase --skip
' '

View File

@ -84,6 +84,16 @@ testrebase() {
test_cmp reflog_before reflog_after && test_cmp reflog_before reflog_after &&
rm 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 testrebase "" .git/rebase-apply

View File

@ -40,4 +40,59 @@ test_expect_success 'non-interactive rebase --continue works with touched file'
git rebase --continue 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 test_done