X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=git-rebase--interactive.sh;h=c9681178f7e65ca2c6ed62dc24347f2dbdbab429;hb=72583e6c685a85b9354ee2310cec3d9240df3c0f;hp=a64d9d57ab5943ac4e065866ce0ccd7de5364a9a;hpb=5f54de5bd07481f6d88e8dbd5551f3356ecbf513;p=git.git diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a64d9d57a..c9681178f 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -10,25 +10,38 @@ # The original idea comes from Eric W. Biederman, in # http://article.gmane.org/gmane.comp.version-control.git/22407 -USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose] - [--onto ] [])' +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git-rebase [-i] [options] [--] [] +git-rebase [-i] (--continue | --abort | --skip) +-- + Available options are +v,verbose display a diffstat of what changed 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 +m,merge always used (no-op) +i,interactive always used (no-op) + Actions: +continue continue rebasing process +abort abort rebasing process and restore original branch +skip skip current patch and continue rebasing process +" -OPTIONS_SPEC= . git-sh-setup require_work_tree -DOTEST="$GIT_DIR/.dotest-merge" +DOTEST="$GIT_DIR/rebase-merge" TODO="$DOTEST"/git-rebase-todo DONE="$DOTEST"/done MSG="$DOTEST"/message SQUASH_MSG="$DOTEST"/message-squash REWRITTEN="$DOTEST"/rewritten +DROPPED="$DOTEST"/dropped PRESERVE_MERGES= STRATEGY= +ONTO= VERBOSE= -test -d "$REWRITTEN" && PRESERVE_MERGES=t -test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" -test -f "$DOTEST"/verbose && VERBOSE=t GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add ', and @@ -53,6 +66,16 @@ output () { esac } +run_pre_rebase_hook () { + if test -x "$GIT_DIR/hooks/pre-rebase" + then + "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || { + echo >&2 "The pre-rebase hook refused to rebase." + exit 1 + } + fi +} + require_clean_work_tree () { # test if working tree is dirty git rev-parse --verify HEAD > /dev/null && @@ -133,7 +156,16 @@ pick_one () { } pick_one_preserving_merges () { - case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac + fast_forward=t + case "$1" in + -n) + fast_forward=f + sha1=$2 + ;; + *) + sha1=$1 + ;; + esac sha1=$(git rev-parse $sha1) if test -f "$DOTEST"/current-commit @@ -144,15 +176,18 @@ pick_one_preserving_merges () { die "Cannot write current commit's replacement sha1" fi + echo $sha1 > "$DOTEST"/current-commit + # rewrite parents; if none were rewritten, we can fast-forward. - fast_forward=t - preserve=t new_parents= - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)" + while [ "$pend" != "" ] do + p=$(expr "$pend" : ' \([^ ]*\)') + pend="${pend# $p}" + if test -f "$REWRITTEN"/$p then - preserve=f new_p=$(cat "$REWRITTEN"/$p) test $p != $new_p && fast_forward=f case "$new_parents" in @@ -162,12 +197,21 @@ pick_one_preserving_merges () { new_parents="$new_parents $new_p" ;; esac + else + if test -f "$DROPPED"/$p + then + fast_forward=f + pend=" $(cat "$DROPPED"/$p)$pend" + else + new_parents="$new_parents $p" + fi fi done case $fast_forward in t) output warn "Fast forward to $sha1" - test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1 + output git reset --hard $sha1 || + die "Cannot fast forward to $sha1" ;; f) test "a$1" = a-n && die "Refusing to squash a merge: $sha1" @@ -177,7 +221,6 @@ pick_one_preserving_merges () { output git checkout $first_parent 2> /dev/null || die "Cannot move HEAD to $first_parent" - echo $sha1 > "$DOTEST"/current-commit case "$new_parents" in ' '*' '*) # redo merge @@ -245,7 +288,7 @@ do_next () { "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in - '#'*|'') + '#'*|''|noop) mark_action_done ;; pick|p) @@ -262,8 +305,8 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 - : > "$DOTEST"/amend - warn + git rev-parse --verify HEAD > "$DOTEST"/amend + warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn warn " git commit --amend" @@ -282,23 +325,28 @@ do_next () { mark_action_done make_squash_message $sha1 > "$MSG" + failed=f + author_script=$(get_author_ident_from_commit HEAD) + output git reset --soft HEAD^ + pick_one -n $sha1 || failed=t case "$(peek_next_command)" in squash|s) EDIT_COMMIT= USE_OUTPUT=output + MSG_OPT=-F + MSG_FILE="$MSG" cp "$MSG" "$SQUASH_MSG" ;; *) EDIT_COMMIT=-e USE_OUTPUT= + MSG_OPT= + MSG_FILE= rm -f "$SQUASH_MSG" || exit + cp "$MSG" "$GIT_DIR"/SQUASH_MSG + rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac - - failed=f - author_script=$(get_author_ident_from_commit HEAD) - output git reset --soft HEAD^ - pick_one -n $sha1 || failed=t echo "$author_script" > "$DOTEST"/author-script if test $failed = f then @@ -307,7 +355,7 @@ do_next () { GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify -F "$MSG" $EDIT_COMMIT || failed=t + $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t fi if test $failed = t then @@ -328,20 +376,7 @@ do_next () { HEADNAME=$(cat "$DOTEST"/head-name) && OLDHEAD=$(cat "$DOTEST"/head) && SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) && - if test -d "$REWRITTEN" - then - test -f "$DOTEST"/current-commit && - current_commit=$(cat "$DOTEST"/current-commit) && - git rev-parse HEAD > "$REWRITTEN"/$current_commit - if test -f "$REWRITTEN"/$OLDHEAD - then - NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD) - else - NEWHEAD=$OLDHEAD - fi - else - NEWHEAD=$(git rev-parse HEAD) - fi && + NEWHEAD=$(git rev-parse HEAD) && case $HEADNAME in refs/*) message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" && @@ -366,10 +401,27 @@ do_rest () { done } +# check if no other options are set +is_standalone () { + test $# -eq 2 -a "$2" = '--' && + test -z "$ONTO" && + test -z "$PRESERVE_MERGES" && + test -z "$STRATEGY" && + test -z "$VERBOSE" +} + +get_saved_options () { + test -d "$REWRITTEN" && PRESERVE_MERGES=t + test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" + test -f "$DOTEST"/verbose && VERBOSE=t +} + while test $# != 0 do case "$1" in --continue) + is_standalone "$@" || usage + get_saved_options comment_for_reflog continue test -d "$DOTEST" || die "No interactive rebase running" @@ -388,20 +440,30 @@ do else . "$DOTEST"/author-script || die "Cannot find the author identity" + amend= if test -f "$DOTEST"/amend then + amend=$(git rev-parse --verify HEAD) + test "$amend" = $(cat "$DOTEST"/amend) || + die "\ +You have uncommitted changes in your working tree. Please, commit them +first and then run 'git rebase --continue' again." git reset --soft HEAD^ || die "Cannot rewind the HEAD" fi export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$DOTEST"/message -e || - die "Could not commit staged changes." + git commit --no-verify -F "$DOTEST"/message -e || { + test -n "$amend" && git reset --soft $amend + die "Could not commit staged changes." + } fi require_clean_work_tree do_rest ;; --abort) + is_standalone "$@" || usage + get_saved_options comment_for_reflog abort git rerere clear @@ -419,6 +481,8 @@ do exit ;; --skip) + is_standalone "$@" || usage + get_saved_options comment_for_reflog skip git rerere clear @@ -426,7 +490,7 @@ do output git reset --hard && do_rest ;; - -s|--strategy) + -s) case "$#,$1" in *,*=*) STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; @@ -437,25 +501,27 @@ do shift ;; esac ;; - -m|--merge) + -m) # we use merge anyway ;; - -C*) - die "Interactive rebase uses merge, so $1 does not make sense" - ;; - -v|--verbose) + -v) VERBOSE=t ;; - -p|--preserve-merges) + -p) PRESERVE_MERGES=t ;; - -i|--interactive) + -i) # yeah, we know ;; - ''|-h) - usage + --onto) + shift + ONTO=$(git rev-parse --verify "$1") || + die "Does not point to a valid commit: $1" ;; - *) + --) + shift + run_pre_rebase_hook ${1+"$@"} + test $# -eq 1 -o $# -eq 2 || usage test -d "$DOTEST" && die "Interactive rebase already started" @@ -464,15 +530,6 @@ do comment_for_reflog start - ONTO= - case "$1" in - --onto) - ONTO=$(git rev-parse --verify "$2") || - die "Does not point to a valid commit: $2" - shift; shift - ;; - esac - require_clean_work_tree UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" @@ -525,6 +582,7 @@ do --abbrev=7 --reverse --left-right --cherry-pick \ $UPSTREAM...$HEAD | \ sed -n "s/^>/pick /p" > "$TODO" + test -s "$TODO" || echo noop >> "$TODO" cat >> "$TODO" << EOF # Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO @@ -539,6 +597,28 @@ do # EOF + # Watch for commits that been dropped by --cherry-pick + if test t = "$PRESERVE_MERGES" + then + mkdir "$DROPPED" + # drop the --cherry-pick parameter this time + git rev-list $MERGES_OPTION --abbrev-commit \ + --abbrev=7 $UPSTREAM...$HEAD --left-right | \ + sed -n "s/^>//p" | while read rev + do + grep --quiet "$rev" "$TODO" + if [ $? -ne 0 ] + then + # Use -f2 because if rev-list is telling this commit is not + # worthwhile, we don't want to track its multiple heads, + # just the history of its first-parent for others that will + # be rebasing on top of us + full=$(git rev-parse $rev) + git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$full + fi + done + fi + has_action "$TODO" || die_abort "Nothing to do" @@ -549,6 +629,7 @@ EOF has_action "$TODO" || die_abort "Nothing to do" + git update-ref ORIG_HEAD $HEAD output git checkout $ONTO && do_rest ;; esac