summaryrefslogtreecommitdiff
path: root/git-rebase.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-rebase.sh')
-rwxr-xr-xgit-rebase.sh825
1 files changed, 481 insertions, 344 deletions
diff --git a/git-rebase.sh b/git-rebase.sh
index 6d3eddbada..48d7c5ded4 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,374 +3,508 @@
# Copyright (c) 2005 Junio C Hamano.
#
-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>
-It then attempts to create a new commit for each commit from the original
-<branch> that does not exist in the <upstream> branch.
-
-It is possible that a merge failure will prevent this process from being
-completely automatic. You will have to resolve any such merge failure
-and run git rebase --continue. Another option is to bypass the commit
-that caused the merge failure with git rebase --skip. To restore the
-original <branch> and remove the .git/rebase-apply working files, use the
-command git rebase --abort instead.
-
-Note that if <branch> is not specified on the command line, the
-currently checked out branch is used.
-
-Example: git-rebase master~1 topic
-
- A---B---C topic A'\''--B'\''--C'\'' topic
- / --> /
- D---E---F---G master D---E---F---G master
-'
-
SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
+OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=t
+OPTIONS_SPEC="\
+git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
+git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
+git-rebase --continue | --abort | --skip | --edit-todo
+--
+ Available options are
+v,verbose! display a diffstat of what changed upstream
+q,quiet! be quiet. implies --no-stat
+autostash automatically stash/stash pop before and after
+fork-point use 'merge-base --fork-point' to refine upstream
+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
+x,exec=! add exec lines after each commit of the editable list
+k,keep-empty preserve empty commits during 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'
+S,gpg-sign? GPG-sign commits
+ Actions:
+continue! continue
+abort! abort and check out the original branch
+skip! skip current patch and continue
+edit-todo! edit the todo list during an interactive rebase
+quit! abort but keep HEAD where it is
+"
. git-sh-setup
set_reflog_action rebase
-require_work_tree
+require_work_tree_exists
cd_to_toplevel
-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\".
+LF='
+'
+ok_to_skip_pre_rebase=
+resolvemsg="
+$(gettext 'When you have resolved this problem, run "git rebase --continue".
+If you prefer to skip this patch, run "git rebase --skip" instead.
+To check out the original branch and stop rebasing, run "git rebase --abort".')
"
-unset newbase
-strategy=recursive
+unset onto
+unset restrict_revision
+cmd=
+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
+autostash="$(git config --bool rebase.autostash || echo false)"
+fork_point=auto
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=
+keep_empty=
+test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
+case "$(git config --bool commit.gpgsign)" in
+true) gpg_sign_opt=-S ;;
+*) gpg_sign_opt= ;;
+esac
-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"
- 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 --
+read_basic_state () {
+ test -f "$state_dir/head-name" &&
+ test -f "$state_dir/onto" &&
+ 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
- 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
- printf "Committed: %0${prec}d " $msgnum
+ orig_head=$(cat "$state_dir"/orig-head)
else
- printf "Already applied: %0${prec}d " $msgnum
- fi
- git rev-list --pretty=oneline -1 "$cmt" | sed -e 's/^[^ ]* //'
-
- 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"
+ 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)"
+ test -f "$state_dir"/gpg_sign_opt &&
+ gpg_sign_opt="$(cat "$state_dir"/gpg_sign_opt)"
+}
- # onto the next patch:
- msgnum=$(($msgnum + 1))
- echo "$msgnum" >"$dotest/msgnum"
+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
+ test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
}
-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
- git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
- rv=$?
- case "$rv" in
- 0)
- unset GITHEAD_$cmt GITHEAD_$hd
- return
- ;;
- 1)
- git rerere
- die "$RESOLVEMSG"
- ;;
- 2)
- echo "Strategy: $rv $strategy failed, try another" 1>&2
- die "$RESOLVEMSG"
+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"
git update-ref -m "$message" \
$head_name $(git rev-parse HEAD) $orig_head &&
- git symbolic-ref HEAD $head_name ||
- die "Could not move back to $head_name"
+ git symbolic-ref \
+ -m "rebase finished: returning to $head_name" \
+ HEAD $head_name ||
+ die "$(eval_gettext "Could not move back to \$head_name")"
;;
esac
}
-finish_rb_merge () {
- move_to_original_branch
- rm -r "$dotest"
- echo "All done."
+apply_autostash () {
+ if test -f "$state_dir/autostash"
+ then
+ stash_sha1=$(cat "$state_dir/autostash")
+ if git stash apply $stash_sha1 2>&1 >/dev/null
+ then
+ echo "$(gettext 'Applied autostash.')"
+ else
+ git stash store -m "autostash" -q $stash_sha1 ||
+ die "$(eval_gettext "Cannot store \$stash_sha1")"
+ gettext 'Applying autostash resulted in conflicts.
+Your changes are safe in the stash.
+You can run "git stash pop" or "git stash drop" at any time.
+'
+ fi
+ fi
}
-is_interactive () {
- while test $# != 0
- do
- case "$1" in
- -i|--interactive)
- interactive_rebase=explicit
- break
- ;;
- -p|--preserve-merges)
- interactive_rebase=implied
- ;;
- esac
- shift
- done
+finish_rebase () {
+ apply_autostash &&
+ { git gc --auto || true; } &&
+ rm -rf "$state_dir"
+}
+run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
export GIT_EDITOR
+ autosquash=
fi
-
- test -n "$interactive_rebase" || test -f "$dotest"/interactive
+ . git-rebase--$type
+ ret=$?
+ if test $ret -eq 0
+ then
+ finish_rebase
+ elif test $ret -eq 2 # special exit status for rebase -i
+ then
+ apply_autostash &&
+ rm -rf "$state_dir" &&
+ die "Nothing to do"
+ fi
+ exit $ret
}
run_pre_rebase_hook () {
- if test -z "$OK_TO_SKIP_PRE_REBASE" &&
- test -x "$GIT_DIR/hooks/pre-rebase"
+ if test -z "$ok_to_skip_pre_rebase" &&
+ test -x "$(git rev-parse --git-path hooks/pre-rebase)"
then
- "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
- echo >&2 "The pre-rebase hook refused to rebase."
- exit 1
- }
+ "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} ||
+ die "$(gettext "The pre-rebase hook refused to rebase.")"
fi
}
-test -f "$GIT_DIR"/rebase-apply/applying &&
- die 'It looks like git-am is in progress. Cannot rebase.'
-
-is_interactive "$@" && exec git-rebase--interactive "$@"
+test -f "$apply_dir"/applying &&
+ die "$(gettext "It looks like git-am is in progress. Cannot rebase.")"
-if test $# -eq 0
+if test -d "$apply_dir"
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.'
- die "No arguments given and $GIT_DIR/rebase-apply already exists."
+ 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
- ;;
- --continue)
- test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
- die "No rebase in progress?"
-
- 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")
- 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 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")
- 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 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
- if test -d "$dotest"
- then
- move_to_original_branch
- else
- dotest="$GIT_DIR"/rebase-apply
- move_to_original_branch
- fi
- git reset --hard $(cat "$dotest/orig-head")
- rm -r "$dotest"
- exit
+ ok_to_skip_pre_rebase=yes
;;
- --onto)
- test 2 -le "$#" || usage
- newbase="$2"
- shift
+ --verify)
+ ok_to_skip_pre_rebase=
+ ;;
+ --continue|--skip|--abort|--quit|--edit-todo)
+ test $total_argc -eq 2 || usage
+ action=${1##--}
+ ;;
+ --onto=*)
+ onto="${1#--onto=}"
+ ;;
+ --exec=*)
+ cmd="${cmd}exec ${1#--exec=}${LF}"
+ test -z "$interactive_rebase" && interactive_rebase=implied
+ ;;
+ --interactive)
+ interactive_rebase=explicit
+ ;;
+ --keep-empty)
+ keep_empty=yes
+ ;;
+ --preserve-merges)
+ preserve_merges=t
+ test -z "$interactive_rebase" && interactive_rebase=implied
;;
- -M|-m|--m|--me|--mer|--merg|--merge)
+ --autosquash)
+ autosquash=t
+ ;;
+ --no-autosquash)
+ autosquash=
+ ;;
+ --fork-point)
+ fork_point=t
+ ;;
+ --no-fork-point)
+ fork_point=
+ ;;
+ --merge)
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
+ --strategy-option=*)
+ strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}")"
do_merge=t
+ test -z "$strategy" && strategy=recursive
+ ;;
+ --strategy=*)
+ strategy="${1#--strategy=}"
+ do_merge=t
+ ;;
+ --no-stat)
+ diffstat=
+ ;;
+ --stat)
+ diffstat=t
+ ;;
+ --autostash)
+ autostash=true
;;
- -v|--verbose)
+ --no-autostash)
+ autostash=false
+ ;;
+ --verbose)
verbose=t
+ diffstat=t
+ GIT_QUIET=
+ ;;
+ --quiet)
+ GIT_QUIET=t
+ git_am_opt="$git_am_opt -q"
+ verbose=
+ diffstat=
;;
--whitespace=*)
+ git_am_opt="$git_am_opt --whitespace=${1#--whitespace=}"
+ case "${1#--whitespace=}" in
+ fix|strip)
+ force_rebase=t
+ ;;
+ esac
+ ;;
+ --ignore-whitespace)
git_am_opt="$git_am_opt $1"
;;
+ --committer-date-is-author-date|--ignore-date)
+ git_am_opt="$git_am_opt $1"
+ force_rebase=t
+ ;;
-C*)
git_am_opt="$git_am_opt $1"
;;
--root)
rebase_root=t
;;
- -*)
- usage
+ --force-rebase|--no-ff)
+ force_rebase=t
;;
- *)
+ --rerere-autoupdate|--no-rerere-autoupdate)
+ allow_rerere_autoupdate="$1"
+ ;;
+ --gpg-sign)
+ gpg_sign_opt=-S
+ ;;
+ --gpg-sign=*)
+ gpg_sign_opt="-S${1#--gpg-sign=}"
+ ;;
+ --)
+ shift
break
;;
esac
shift
done
+test $# -gt 2 && usage
-# Make sure we do not have $GIT_DIR/rebase-apply
-if test -z "$do_merge"
+if test -n "$action"
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
-and run me again. I am stopping in case you still have something
-valuable there.'
- exit 1
- fi
-else
- if test -d "$dotest"
+ test -z "$in_progress" && die "$(gettext "No rebase in progress?")"
+ # Only interactive rebase uses detailed reflog messages
+ if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase
then
- die "previous rebase directory $dotest still exists." \
- 'Try git rebase (--continue | --abort | --skip)'
+ GIT_REFLOG_ACTION="rebase -i ($action)"
+ export GIT_REFLOG_ACTION
fi
fi
-# The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh; then
- echo >&2 "cannot rebase: you have unstaged changes"
- exit 1
+if test "$action" = "edit-todo" && test "$type" != "interactive"
+then
+ die "$(gettext "The --edit-todo action can only be used during interactive rebase.")"
fi
-diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
-case "$diff" in
-?*) echo >&2 "cannot rebase: your index contains uncommitted changes"
- echo >&2 "$diff"
- exit 1
+
+case "$action" in
+continue)
+ # Sanity check
+ git rev-parse --verify HEAD >/dev/null ||
+ die "$(gettext "Cannot read HEAD")"
+ git update-index --ignore-submodules --refresh &&
+ git diff-files --quiet --ignore-submodules || {
+ echo "$(gettext "You must edit all merge conflicts and then
+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 -m "rebase: aborting" HEAD $head_name ||
+ die "$(eval_gettext "Could not move back to \$head_name")"
+ ;;
+ esac
+ output git reset --hard $orig_head
+ finish_rebase
+ exit
+ ;;
+quit)
+ exec rm -rf "$state_dir"
+ ;;
+edit-todo)
+ run_specific_rebase
;;
esac
+# Make sure no rebase is in progress
+if test -n "$in_progress"
+then
+ state_dir_base=${state_dir##*/}
+ cmd_live_rebase="git rebase (--continue | --abort | --skip)"
+ cmd_clear_stale_rebase="rm -fr \"$state_dir\""
+ die "
+$(eval_gettext 'It seems that there is already a $state_dir_base directory, and
+I wonder if you are in the middle of another rebase. If that is the
+case, please try
+ $cmd_live_rebase
+If that is not the case, please
+ $cmd_clear_stale_rebase
+and run me again. I am stopping in case you still have something
+valuable there.')"
+fi
+
+if test -n "$rebase_root" && test -z "$onto"
+then
+ test -z "$interactive_rebase" && interactive_rebase=implied
+fi
+
+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
- upstream=`git rev-parse --verify "${upstream_name}^0"` ||
- die "invalid upstream $upstream_name"
- unset root_flag
+ 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 $(gettext '<branch>')"
+ fi
+
+ test "$fork_point" = auto && fork_point=t
+ ;;
+ *) upstream_name="$1"
+ if test "$upstream_name" = "-"
+ then
+ upstream_name="@{-1}"
+ fi
+ shift
+ ;;
+ esac
+ upstream=$(peel_committish "${upstream_name}") ||
+ die "$(eval_gettext "invalid upstream \$upstream_name")"
upstream_arg="$upstream_name"
else
- test -z "$newbase" && die "--root must be used with --onto"
+ if test -z "$onto"
+ then
+ empty_tree=$(git hash-object -t tree /dev/null)
+ onto=$(git commit-tree $empty_tree </dev/null)
+ squash_onto="$onto"
+ fi
unset upstream_name
unset upstream
- root_flag="--root"
- upstream_arg="$root_flag"
+ test $# -gt 1 && usage
+ upstream_arg=--root
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 "$upstream_arg" "$@"
+onto_name=${onto-"$upstream_name"}
+case "$onto_name" in
+*...*)
+ if left=${onto_name%...*} right=${onto_name#*...} &&
+ onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+ then
+ case "$onto" in
+ ?*"$LF"?*)
+ die "$(eval_gettext "\$onto_name: there are more than one merge bases")"
+ ;;
+ '')
+ die "$(eval_gettext "\$onto_name: there is no merge base")"
+ ;;
+ esac
+ else
+ die "$(eval_gettext "\$onto_name: there is no merge base")"
+ fi
+ ;;
+*)
+ onto=$(peel_committish "$onto_name") ||
+ die "$(eval_gettext "Does not point to a valid commit: \$onto_name")"
+ ;;
+esac
# If the branch to rebase is given, that is the branch we will rebase
# $branch_name -- branch being rebased, or HEAD (already detached)
@@ -384,65 +518,112 @@ 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
- usage
+ die "$(eval_gettext "fatal: no such branch: \$branch_name")"
fi
;;
-*)
+0)
# Do not need to switch branches, we are already on it.
- if branch_name=`git symbolic-ref -q HEAD`
+ if branch_name=$(git symbolic-ref -q HEAD)
then
head_name=$branch_name
- branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+ branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)')
else
head_name="detached HEAD"
branch_name=HEAD ;# detached
fi
- branch=$(git rev-parse --verify "${branch_name}^0") || exit
+ orig_head=$(git rev-parse --verify HEAD) || exit
+ ;;
+*)
+ die "BUG: unexpected number of arguments left to parse"
;;
esac
-orig_head=$branch
-# Now we are rebasing commits $upstream..$branch (or with --root,
-# everything leading up to $branch) on top of $onto
+if test "$fork_point" = t
+then
+ new_upstream=$(git merge-base --fork-point "$upstream_name" \
+ "${switch_to:-HEAD}")
+ if test -n "$new_upstream"
+ then
+ restrict_revision=$new_upstream
+ fi
+fi
+
+if test "$autostash" = true && ! (require_clean_work_tree) 2>/dev/null
+then
+ stash_sha1=$(git stash create "autostash") ||
+ die "$(gettext 'Cannot autostash')"
+
+ mkdir -p "$state_dir" &&
+ echo $stash_sha1 >"$state_dir/autostash" &&
+ stash_abbrev=$(git rev-parse --short $stash_sha1) &&
+ echo "$(eval_gettext 'Created autostash: $stash_abbrev')" &&
+ git reset --hard
+fi
+
+require_clean_work_tree "rebase" "$(gettext "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" && test -z "$restrict_revision" &&
# linear history?
- ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null
+ ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
then
- # Lazily switch to the target branch if needed...
- test -z "$switch_to" || git checkout "$switch_to"
- echo >&2 "Current branch $branch_name is up to date."
- exit 0
+ if test -z "$force_rebase"
+ then
+ # Lazily switch to the target branch if needed...
+ test -z "$switch_to" ||
+ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \
+ git checkout -q "$switch_to" --
+ say "$(eval_gettext "Current branch \$branch_name is up to date.")"
+ finish_rebase
+ exit 0
+ else
+ say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")"
+ fi
fi
-if test -n "$verbose"
+# If a hook exists, give it a chance to interrupt
+run_pre_rebase_hook "$upstream_arg" "$@"
+
+if test -n "$diffstat"
then
- echo "Changes from $mb to $onto:"
+ if test -n "$verbose"
+ then
+ echo "$(eval_gettext "Changes from \$mb to \$onto:")"
+ fi
# We want color (if set), but no pager
GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
+test "$type" = interactive && run_specific_rebase
+
# Detach HEAD and reset the tree
-echo "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
+say "$(gettext "First, rewinding head to replay your work on top of it...")"
+
+GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \
+ 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"
+# we just fast-forwarded.
+if test "$mb" = "$orig_head"
then
- echo >&2 "Fast-forwarded $branch_name to $onto_name."
+ say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")"
move_to_original_branch
+ finish_rebase
exit 0
fi
@@ -450,51 +631,7 @@ 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 \
- $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
- exit $ret
+ revisions="${restrict_revision-$upstream}..$orig_head"
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"
-
-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