git-rebase--interactive.shon commit Merge branch 'master' of git://people.freedesktop.org/~hausmann/git-p4 (6fb73e4)
   1#!/bin/sh
   2#
   3# Copyright (c) 2006 Johannes E. Schindelin
   4
   5# SHORT DESCRIPTION
   6#
   7# This script makes it easy to fix up commits in the middle of a series,
   8# and rearrange commits.
   9#
  10# The original idea comes from Eric W. Biederman, in
  11# http://article.gmane.org/gmane.comp.version-control.git/22407
  12
  13USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
  14        [--onto <branch>] <upstream> [<branch>])'
  15
  16. git-sh-setup
  17require_work_tree
  18
  19DOTEST="$GIT_DIR/.dotest-merge"
  20TODO="$DOTEST"/todo
  21DONE="$DOTEST"/done
  22REWRITTEN="$DOTEST"/rewritten
  23PRESERVE_MERGES=
  24STRATEGY=
  25VERBOSE=
  26test -d "$REWRITTEN" && PRESERVE_MERGES=t
  27test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
  28test -f "$DOTEST"/verbose && VERBOSE=t
  29
  30warn () {
  31        echo "$*" >&2
  32}
  33
  34require_clean_work_tree () {
  35        # test if working tree is dirty
  36        git rev-parse --verify HEAD > /dev/null &&
  37        git update-index --refresh &&
  38        git diff-files --quiet &&
  39        git diff-index --cached --quiet HEAD ||
  40        die "Working tree is dirty"
  41}
  42
  43ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
  44
  45comment_for_reflog () {
  46        case "$ORIG_REFLOG_ACTION" in
  47        ''|rebase*)
  48                GIT_REFLOG_ACTION="rebase -i ($1)"
  49                export GIT_REFLOG_ACTION
  50        esac
  51}
  52
  53mark_action_done () {
  54        sed -e 1q < "$TODO" >> "$DONE"
  55        sed -e 1d < "$TODO" >> "$TODO".new
  56        mv -f "$TODO".new "$TODO"
  57}
  58
  59make_patch () {
  60        parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
  61        git diff "$parent_sha1".."$1" > "$DOTEST"/patch
  62}
  63
  64die_with_patch () {
  65        test -f "$DOTEST"/message ||
  66                git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message
  67        test -f "$DOTEST"/author-script ||
  68                get_author_ident_from_commit $sha1 > "$DOTEST"/author-script
  69        make_patch "$1"
  70        die "$2"
  71}
  72
  73die_abort () {
  74        rm -rf "$DOTEST"
  75        die "$1"
  76}
  77
  78pick_one () {
  79        case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
  80        git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
  81        test -d "$REWRITTEN" &&
  82                pick_one_preserving_merges "$@" && return
  83        parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
  84        current_sha1=$(git rev-parse --verify HEAD)
  85        if [ $current_sha1 = $parent_sha1 ]; then
  86                git reset --hard $sha1
  87                test "a$1" = a-n && git reset --soft $current_sha1
  88                sha1=$(git rev-parse --short $sha1)
  89                warn Fast forward to $sha1
  90        else
  91                git cherry-pick $STRATEGY "$@"
  92        fi
  93}
  94
  95pick_one_preserving_merges () {
  96        case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
  97        sha1=$(git rev-parse $sha1)
  98
  99        if [ -f "$DOTEST"/current-commit ]
 100        then
 101                current_commit=$(cat "$DOTEST"/current-commit) &&
 102                git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
 103                rm "$DOTEST"/current-commit ||
 104                die "Cannot write current commit's replacement sha1"
 105        fi
 106
 107        # rewrite parents; if none were rewritten, we can fast-forward.
 108        fast_forward=t
 109        preserve=t
 110        new_parents=
 111        for p in $(git rev-list --parents -1 $sha1 | cut -d\  -f2-)
 112        do
 113                if [ -f "$REWRITTEN"/$p ]
 114                then
 115                        preserve=f
 116                        new_p=$(cat "$REWRITTEN"/$p)
 117                        test $p != $new_p && fast_forward=f
 118                        case "$new_parents" in
 119                        *$new_p*)
 120                                ;; # do nothing; that parent is already there
 121                        *)
 122                                new_parents="$new_parents $new_p"
 123                        esac
 124                fi
 125        done
 126        case $fast_forward in
 127        t)
 128                echo "Fast forward to $sha1"
 129                test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
 130                ;;
 131        f)
 132                test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
 133
 134                first_parent=$(expr "$new_parents" : " \([^ ]*\)")
 135                # detach HEAD to current parent
 136                git checkout $first_parent 2> /dev/null ||
 137                        die "Cannot move HEAD to $first_parent"
 138
 139                echo $sha1 > "$DOTEST"/current-commit
 140                case "$new_parents" in
 141                \ *\ *)
 142                        # redo merge
 143                        author_script=$(get_author_ident_from_commit $sha1)
 144                        eval "$author_script"
 145                        msg="$(git cat-file commit $sha1 | \
 146                                sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
 147                        # NEEDSWORK: give rerere a chance
 148                        if ! git merge $STRATEGY -m "$msg" $new_parents
 149                        then
 150                                echo "$msg" > "$GIT_DIR"/MERGE_MSG
 151                                die Error redoing merge $sha1
 152                        fi
 153                        ;;
 154                *)
 155                        git cherry-pick $STRATEGY "$@" ||
 156                                die_with_patch $sha1 "Could not pick $sha1"
 157                esac
 158        esac
 159}
 160
 161do_next () {
 162        test -f "$DOTEST"/message && rm "$DOTEST"/message
 163        test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script
 164        read command sha1 rest < "$TODO"
 165        case "$command" in
 166        \#|'')
 167                mark_action_done
 168                ;;
 169        pick)
 170                comment_for_reflog pick
 171
 172                mark_action_done
 173                pick_one $sha1 ||
 174                        die_with_patch $sha1 "Could not apply $sha1... $rest"
 175                ;;
 176        edit)
 177                comment_for_reflog edit
 178
 179                mark_action_done
 180                pick_one $sha1 ||
 181                        die_with_patch $sha1 "Could not apply $sha1... $rest"
 182                make_patch $sha1
 183                warn
 184                warn "You can amend the commit now, with"
 185                warn
 186                warn "  git commit --amend"
 187                warn
 188                exit 0
 189                ;;
 190        squash)
 191                comment_for_reflog squash
 192
 193                test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
 194                        die "Cannot 'squash' without a previous commit"
 195
 196                mark_action_done
 197                MSG="$DOTEST"/message
 198                echo "# This is a combination of two commits." > "$MSG"
 199                echo "# The first commit's message is:" >> "$MSG"
 200                echo >> "$MSG"
 201                git cat-file commit HEAD | sed -e '1,/^$/d' >> "$MSG"
 202                echo >> "$MSG"
 203                failed=f
 204                pick_one -n $sha1 || failed=t
 205                echo "# And this is the 2nd commit message:" >> "$MSG"
 206                echo >> "$MSG"
 207                git cat-file commit $sha1 | sed -e '1,/^$/d' >> "$MSG"
 208                git reset --soft HEAD^
 209                author_script=$(get_author_ident_from_commit $sha1)
 210                echo "$author_script" > "$DOTEST"/author-script
 211                case $failed in
 212                f)
 213                        # This is like --amend, but with a different message
 214                        eval "$author_script"
 215                        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 216                        git commit -F "$MSG" -e
 217                        ;;
 218                t)
 219                        cp "$MSG" "$GIT_DIR"/MERGE_MSG
 220                        warn
 221                        warn "Could not apply $sha1... $rest"
 222                        die_with_patch $sha1 ""
 223                esac
 224                ;;
 225        *)
 226                warn "Unknown command: $command $sha1 $rest"
 227                die_with_patch $sha1 "Please fix this in the file $TODO."
 228        esac
 229        test -s "$TODO" && return
 230
 231        comment_for_reflog finish &&
 232        HEADNAME=$(cat "$DOTEST"/head-name) &&
 233        OLDHEAD=$(cat "$DOTEST"/head) &&
 234        SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
 235        if [ -d "$REWRITTEN" ]
 236        then
 237                test -f "$DOTEST"/current-commit &&
 238                        current_commit=$(cat "$DOTEST"/current-commit) &&
 239                        git rev-parse HEAD > "$REWRITTEN"/$current_commit
 240                NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
 241        else
 242                NEWHEAD=$(git rev-parse HEAD)
 243        fi &&
 244        message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
 245        git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
 246        git symbolic-ref HEAD $HEADNAME && {
 247                test ! -f "$DOTEST"/verbose ||
 248                        git diff --stat $(cat "$DOTEST"/head)..HEAD
 249        } &&
 250        rm -rf "$DOTEST" &&
 251        warn "Successfully rebased and updated $HEADNAME."
 252
 253        exit
 254}
 255
 256do_rest () {
 257        while :
 258        do
 259                do_next
 260        done
 261}
 262
 263while case $# in 0) break ;; esac
 264do
 265        case "$1" in
 266        --continue)
 267                comment_for_reflog continue
 268
 269                test -d "$DOTEST" || die "No interactive rebase running"
 270
 271                # commit if necessary
 272                git rev-parse --verify HEAD > /dev/null &&
 273                git update-index --refresh &&
 274                git diff-files --quiet &&
 275                ! git diff-index --cached --quiet HEAD &&
 276                . "$DOTEST"/author-script &&
 277                export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
 278                git commit -F "$DOTEST"/message -e
 279
 280                require_clean_work_tree
 281                do_rest
 282                ;;
 283        --abort)
 284                comment_for_reflog abort
 285
 286                test -d "$DOTEST" || die "No interactive rebase running"
 287
 288                HEADNAME=$(cat "$DOTEST"/head-name)
 289                HEAD=$(cat "$DOTEST"/head)
 290                git symbolic-ref HEAD $HEADNAME &&
 291                git reset --hard $HEAD &&
 292                rm -rf "$DOTEST"
 293                exit
 294                ;;
 295        --skip)
 296                comment_for_reflog skip
 297
 298                test -d "$DOTEST" || die "No interactive rebase running"
 299
 300                git reset --hard && do_rest
 301                ;;
 302        -s|--strategy)
 303                shift
 304                case "$#,$1" in
 305                *,*=*)
 306                        STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
 307                1,*)
 308                        usage ;;
 309                *)
 310                        STRATEGY="-s $2"
 311                        shift ;;
 312                esac
 313                ;;
 314        --merge)
 315                # we use merge anyway
 316                ;;
 317        -C*)
 318                die "Interactive rebase uses merge, so $1 does not make sense"
 319                ;;
 320        -v|--verbose)
 321                VERBOSE=t
 322                ;;
 323        -p|--preserve-merges)
 324                PRESERVE_MERGES=t
 325                ;;
 326        -i|--interactive)
 327                # yeah, we know
 328                ;;
 329        ''|-h)
 330                usage
 331                ;;
 332        *)
 333                test -d "$DOTEST" &&
 334                        die "Interactive rebase already started"
 335
 336                git var GIT_COMMITTER_IDENT >/dev/null ||
 337                        die "You need to set your committer info first"
 338
 339                comment_for_reflog start
 340
 341                ONTO=
 342                case "$1" in
 343                --onto)
 344                        ONTO=$(git rev-parse --verify "$2") ||
 345                                die "Does not point to a valid commit: $2"
 346                        shift; shift
 347                        ;;
 348                esac
 349
 350                require_clean_work_tree
 351
 352                if [ ! -z "$2"]
 353                then
 354                        git show-ref --verify --quiet "refs/heads/$2" ||
 355                                die "Invalid branchname: $2"
 356                        git checkout "$2" ||
 357                                die "Could not checkout $2"
 358                fi
 359
 360                HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
 361                UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 362
 363                test -z "$ONTO" && ONTO=$UPSTREAM
 364
 365                mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 366                : > "$DOTEST"/interactive || die "Could not mark as interactive"
 367                git symbolic-ref HEAD > "$DOTEST"/head-name ||
 368                        die "Could not get HEAD"
 369
 370                echo $HEAD > "$DOTEST"/head
 371                echo $UPSTREAM > "$DOTEST"/upstream
 372                echo $ONTO > "$DOTEST"/onto
 373                test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
 374                test t = "$VERBOSE" && : > "$DOTEST"/verbose
 375                if [ t = "$PRESERVE_MERGES" ]
 376                then
 377                        # $REWRITTEN contains files for each commit that is
 378                        # reachable by at least one merge base of $HEAD and
 379                        # $UPSTREAM. They are not necessarily rewritten, but
 380                        # their children might be.
 381                        # This ensures that commits on merged, but otherwise
 382                        # unrelated side branches are left alone. (Think "X"
 383                        # in the man page's example.)
 384                        mkdir "$REWRITTEN" &&
 385                        for c in $(git merge-base --all $HEAD $UPSTREAM)
 386                        do
 387                                echo $ONTO > "$REWRITTEN"/$c ||
 388                                        die "Could not init rewritten commits"
 389                        done
 390                        MERGES_OPTION=
 391                else
 392                        MERGES_OPTION=--no-merges
 393                fi
 394
 395                SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
 396                SHORTHEAD=$(git rev-parse --short $HEAD)
 397                SHORTONTO=$(git rev-parse --short $ONTO)
 398                cat > "$TODO" << EOF
 399# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
 400#
 401# Commands:
 402#  pick = use commit
 403#  edit = use commit, but stop for amending
 404#  squash = use commit, but meld into previous commit
 405#
 406# If you remove a line here THAT COMMIT WILL BE LOST.
 407#
 408EOF
 409                git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
 410                        --abbrev=7 --reverse $UPSTREAM..$HEAD | \
 411                        sed "s/^/pick /" >> "$TODO"
 412
 413                test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
 414                        die_abort "Nothing to do"
 415
 416                cp "$TODO" "$TODO".backup
 417                ${VISUAL:-${EDITOR:-vi}} "$TODO" ||
 418                        die "Could not execute editor"
 419
 420                test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
 421                        die_abort "Nothing to do"
 422
 423                git checkout $ONTO && do_rest
 424        esac
 425        shift
 426done