rebase: factor out reference parsing
[gitweb.git] / git-rebase.sh
index cbb0ea90ed410d5af68ce814b7fd93a90829f3ee..0f5f5fb206e74d996eb096aea7833708bdccb71c 100755 (executable)
@@ -36,17 +36,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
+merge_dir="$GIT_DIR"/rebase-merge
+apply_dir="$GIT_DIR"/rebase-apply
 prec=4
 verbose=
 diffstat=
@@ -55,33 +56,56 @@ 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
+
+read_state () {
+       if test "$type" = merge
+       then
+               onto_name=$(cat "$state_dir"/onto_name) &&
+               end=$(cat "$state_dir"/end) &&
+               msgnum=$(cat "$state_dir"/msgnum)
+       fi &&
+       head_name=$(cat "$state_dir"/head-name) &&
+       onto=$(cat "$state_dir"/onto) &&
+       orig_head=$(cat "$state_dir"/orig-head) &&
+       GIT_QUIET=$(cat "$state_dir"/quiet)
+}
 
 continue_merge () {
-       test -n "$prev_head" || die "prev_head must be defined"
-       test -d "$dotest" || die "$dotest directory does not exist"
+       test -d "$merge_dir" || die "$merge_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"
+               die "$resolvemsg"
        fi
 
-       cmt=`cat "$dotest/current"`
+       cmt=`cat "$merge_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"
+                       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"
+               echo "$cmt $(git rev-parse HEAD^0)" >> "$merge_dir/rewritten"
        else
                if test -z "$GIT_QUIET"
                then
@@ -91,29 +115,25 @@ continue_merge () {
        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"
+       echo "$msgnum" >"$merge_dir/msgnum"
 }
 
 call_merge () {
-       cmt="$(cat "$dotest/cmt.$1")"
-       echo "$cmt" > "$dotest/current"
+       cmt="$(cat "$merge_dir/cmt.$1")"
+       echo "$cmt" > "$merge_dir/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")
+       msgnum=$(cat "$merge_dir/msgnum")
        eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
-       eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
+       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
@@ -123,11 +143,11 @@ call_merge () {
                ;;
        1)
                git rerere $allow_rerere_autoupdate
-               die "$RESOLVEMSG"
+               die "$resolvemsg"
                ;;
        2)
                echo "Strategy: $rv $strategy failed, try another" 1>&2
-               die "$RESOLVEMSG"
+               die "$resolvemsg"
                ;;
        *)
                die "Unknown exit code ($rv) from command:" \
@@ -137,10 +157,6 @@ call_merge () {
 }
 
 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"
@@ -154,40 +170,25 @@ move_to_original_branch () {
 
 finish_rb_merge () {
        move_to_original_branch
-       git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+       git notes copy --for-rewrite=rebase < "$merge_dir"/rewritten
        if test -x "$GIT_DIR"/hooks/post-rewrite &&
-               test -s "$dotest"/rewritten; then
-               "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+               test -s "$merge_dir"/rewritten; then
+               "$GIT_DIR"/hooks/post-rewrite rebase < "$merge_dir"/rewritten
        fi
-       rm -r "$dotest"
+       rm -r "$merge_dir"
        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_interactive_rebase () {
        if [ "$interactive_rebase" = implied ]; then
                GIT_EDITOR=:
                export GIT_EDITOR
        fi
-
-       test -n "$interactive_rebase" || test -f "$dotest"/interactive
+       . git-rebase--interactive "$@"
 }
 
 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,108 +196,58 @@ 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 1 || usage
+               action=${1##--}
                ;;
        --onto)
                test 2 -le "$#" || usage
-               newbase="$2"
+               onto="$2"
                shift
                ;;
+       -i|--interactive)
+               interactive_rebase=explicit
+               ;;
+       -p|--preserve-merges)
+               preserve_merges=t
+               test -z "$interactive_rebase" && interactive_rebase=implied
+               ;;
+       --autosquash)
+               autosquash=t
+               ;;
+       --no-autosquash)
+               autosquash=
+               ;;
        -M|-m|--m|--me|--mer|--merg|--merge)
                do_merge=t
                ;;
@@ -316,6 +267,7 @@ do
                esac
                strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$newopt")"
                do_merge=t
+               test -z "$strategy" && strategy=recursive
                ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
@@ -386,38 +338,98 @@ 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?"
+       test "$type" = interactive && run_interactive_rebase
 fi
 
-# Make sure we do not have $GIT_DIR/rebase-apply
-if test -z "$do_merge"
-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.'
+case "$action" in
+continue)
+       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_state
+       if test -d "$merge_dir"
+       then
+               continue_merge
+               while test "$msgnum" -le "$end"
+               do
+                       call_merge "$msgnum"
+                       continue_merge
+               done
+               finish_rb_merge
+               exit
        fi
-else
-       if test -d "$dotest"
+       git am --resolved --3way --resolvemsg="$resolvemsg" &&
+       move_to_original_branch
+       exit
+       ;;
+skip)
+       git reset --hard HEAD || exit $?
+       read_state
+       if test -d "$merge_dir"
        then
-               die "previous rebase directory $dotest still exists." \
-                       'Try git rebase (--continue | --abort | --skip)'
+               git rerere clear
+               msgnum=$(($msgnum + 1))
+               while test "$msgnum" -le "$end"
+               do
+                       call_merge "$msgnum"
+                       continue_merge
+               done
+               finish_rb_merge
+               exit
        fi
+       git am -3 --skip --resolvemsg="$resolvemsg" &&
+       move_to_original_branch
+       exit
+       ;;
+abort)
+       git rerere clear
+       read_state
+       case "$head_name" in
+       refs/*)
+               git symbolic-ref HEAD $head_name ||
+               die "Could not move back to $head_name"
+               ;;
+       esac
+       git reset --hard $orig_head
+       rm -r "$state_dir"
+       exit
+       ;;
+esac
+
+# Make sure no rebase is in progress
+if test -n "$in_progress"
+then
+       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.'
 fi
 
-require_clean_work_tree "rebase" "Please commit or stash them."
+test $# -eq 0 && test -z "$rebase_root" && usage
+
+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
@@ -429,7 +441,7 @@ then
        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"
@@ -437,7 +449,7 @@ else
 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 +468,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
@@ -501,6 +511,10 @@ case "$#" in
 esac
 orig_head=$branch
 
+test "$type" = interactive && run_interactive_rebase "$@"
+
+require_clean_work_tree "rebase" "Please commit or stash them."
+
 # Now we are rebasing commits $upstream..$branch (or with --root,
 # everything leading up to $branch) on top of $onto
 
@@ -522,6 +536,9 @@ then
        fi
 fi
 
+# If a hook exists, give it a chance to interrupt
+run_pre_rebase_hook "$upstream_arg" "$@"
+
 # 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"
@@ -558,38 +575,36 @@ 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" &&
+       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
+       test 0 != $ret -a -d "$apply_dir" &&
+               echo $head_name > "$apply_dir/head-name" &&
+               echo $onto > "$apply_dir/onto" &&
+               echo $orig_head > "$apply_dir/orig-head" &&
+               echo "$GIT_QUIET" > "$apply_dir/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"
+mkdir -p "$merge_dir"
+echo "$onto_name" > "$merge_dir/onto_name"
+echo "$head_name" > "$merge_dir/head-name"
+echo "$onto" > "$merge_dir/onto"
+echo "$orig_head" > "$merge_dir/orig-head"
+echo "$GIT_QUIET" > "$merge_dir/quiet"
 
 msgnum=0
 for cmt in `git rev-list --reverse --no-merges "$revisions"`
 do
        msgnum=$(($msgnum + 1))
-       echo "$cmt" > "$dotest/cmt.$msgnum"
+       echo "$cmt" > "$merge_dir/cmt.$msgnum"
 done
 
-echo 1 >"$dotest/msgnum"
-echo $msgnum >"$dotest/end"
+echo 1 >"$merge_dir/msgnum"
+echo $msgnum >"$merge_dir/end"
 
 end=$msgnum
 msgnum=1