diff options
Diffstat (limited to 'git-rebase.sh')
-rwxr-xr-x | git-rebase.sh | 404 |
1 files changed, 154 insertions, 250 deletions
diff --git a/git-rebase.sh b/git-rebase.sh index 5e3c42cb11..03f73da887 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -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> @@ -13,7 +13,7 @@ It then attempts to create a new commit for each commit from the original 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 +that caused the merge failure with git rebase --skip. To check out the original <branch> and remove the .git/rebase-apply working files, use the command git rebase --abort instead. @@ -22,16 +22,48 @@ currently checked out branch is used. Example: git-rebase master~1 topic - A---B---C topic A'\''--B'\''--C'\'' 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_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 +abort! abort and check out the original branch +skip! skip current patch and continue +" . git-sh-setup set_reflog_action rebase -require_work_tree +require_work_tree_exists cd_to_toplevel LF=' @@ -40,7 +72,7 @@ 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\". +To check out the original branch and stop rebasing run \"git rebase --abort\". " unset onto strategy= @@ -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 } @@ -162,29 +153,20 @@ move_to_original_branch () { 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 || + git symbolic-ref \ + -m "rebase finished: returning to $head_name" \ + HEAD $head_name || die "Could not move back to $head_name" ;; 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 +209,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 +217,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 +230,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 +277,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 +302,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 || + git symbolic-ref -m "rebase: aborting" 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 +358,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 +373,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 <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,15 +434,14 @@ 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 - echo >&2 "fatal: no such branch: $1" - usage + die "fatal: no such branch: $1" fi ;; *) @@ -506,24 +454,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 +486,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 +496,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 +519,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 |