add tests for various blame formats
[gitweb.git] / git-rebase.sh
index 0f5f5fb206e74d996eb096aea7833708bdccb71c..7a54bfc6182a567860af023cb7720680d1806c72 100755 (executable)
@@ -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
@@ -48,7 +80,6 @@ strategy_opts=
 do_merge=
 merge_dir="$GIT_DIR"/rebase-merge
 apply_dir="$GIT_DIR"/rebase-apply
-prec=4
 verbose=
 diffstat=
 test "$(git config --bool rebase.stat)" = true && diffstat=t
@@ -68,90 +99,50 @@ 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 &&
+read_basic_state () {
        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 -d "$merge_dir" || die "$merge_dir directory does not exist"
-
-       unmerged=$(git ls-files -u)
-       if test -n "$unmerged"
+       # 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 "$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"
-               fi
-               if test -z "$GIT_QUIET"
-               then
-                       printf "Committed: %0${prec}d " $msgnum
-               fi
-               echo "$cmt $(git rev-parse HEAD^0)" >> "$merge_dir/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"
+               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)"
+}
 
-       # onto the next patch:
-       msgnum=$(($msgnum + 1))
-       echo "$msgnum" >"$merge_dir/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
 }
 
-call_merge () {
-       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 "$merge_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: $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
 }
@@ -168,23 +159,12 @@ move_to_original_branch () {
        esac
 }
 
-finish_rb_merge () {
-       move_to_original_branch
-       git notes copy --for-rewrite=rebase < "$merge_dir"/rewritten
-       if test -x "$GIT_DIR"/hooks/post-rewrite &&
-               test -s "$merge_dir"/rewritten; then
-               "$GIT_DIR"/hooks/post-rewrite rebase < "$merge_dir"/rewritten
-       fi
-       rm -r "$merge_dir"
-       say All done.
-}
-
-run_interactive_rebase () {
+run_specific_rebase () {
        if [ "$interactive_rebase" = implied ]; then
                GIT_EDITOR=:
                export GIT_EDITOR
        fi
-       . git-rebase--interactive "$@"
+       . git-rebase--$type
 }
 
 run_pre_rebase_hook () {
@@ -227,7 +207,7 @@ do
                ok_to_skip_pre_rebase=
                ;;
        --continue|--skip|--abort)
-               test $total_argc -eq 1 || usage
+               test $total_argc -eq 2 || usage
                action=${1##--}
                ;;
        --onto)
@@ -235,10 +215,10 @@ do
                onto="$2"
                shift
                ;;
-       -i|--interactive)
+       -i)
                interactive_rebase=explicit
                ;;
-       -p|--preserve-merges)
+       -p)
                preserve_merges=t
                test -z "$interactive_rebase" && interactive_rebase=implied
                ;;
@@ -248,62 +228,42 @@ do
        --no-autosquash)
                autosquash=
                ;;
-       -M|-m|--m|--me|--mer|--merg|--merge)
+       -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=*|--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
+       -s)
+               shift
+               strategy="$1"
                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
@@ -315,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
@@ -341,62 +300,43 @@ test $# -gt 2 && usage
 if test -n "$action"
 then
        test -z "$in_progress" && die "No rebase in progress?"
-       test "$type" = interactive && run_interactive_rebase
+       # 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
 
 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_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
-       git am --resolved --3way --resolvemsg="$resolvemsg" &&
-       move_to_original_branch
-       exit
+       read_basic_state
+       run_specific_rebase
        ;;
 skip)
-       git reset --hard HEAD || exit $?
-       read_state
-       if test -d "$merge_dir"
-       then
-               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
+       output git reset --hard HEAD || exit $?
+       read_basic_state
+       run_specific_rebase
        ;;
 abort)
        git rerere clear
-       read_state
+       read_basic_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
+       output git reset --hard $orig_head
        rm -r "$state_dir"
        exit
        ;;
@@ -416,8 +356,6 @@ and run me again.  I am stopping in case you still have something
 valuable there.'
 fi
 
-test $# -eq 0 && test -z "$rebase_root" && usage
-
 if test -n "$interactive_rebase"
 then
        type=interactive
@@ -433,19 +371,28 @@ 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 "$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.
@@ -485,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
@@ -506,24 +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
-
-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
+# 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
@@ -539,11 +485,6 @@ 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"
-git update-ref ORIG_HEAD $branch
-
 if test -n "$diffstat"
 then
        if test -n "$verbose"
@@ -554,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
@@ -570,49 +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 "$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 "$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" > "$merge_dir/cmt.$msgnum"
-done
-
-echo 1 >"$merge_dir/msgnum"
-echo $msgnum >"$merge_dir/end"
-
-end=$msgnum
-msgnum=1
-
-while test "$msgnum" -le "$end"
-do
-       call_merge "$msgnum"
-       continue_merge
-done
-
-finish_rb_merge
+run_specific_rebase