contrib / examples / git-commit.shon commit Merge branch 'tz/complete-branch-copy' (93bfe62)
   1#!/bin/sh
   2#
   3# Copyright (c) 2005 Linus Torvalds
   4# Copyright (c) 2006 Junio C Hamano
   5
   6USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
   7SUBDIRECTORY_OK=Yes
   8OPTIONS_SPEC=
   9. git-sh-setup
  10require_work_tree
  11
  12git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
  13
  14case "$0" in
  15*status)
  16        status_only=t
  17        ;;
  18*commit)
  19        status_only=
  20        ;;
  21esac
  22
  23refuse_partial () {
  24        echo >&2 "$1"
  25        echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
  26        exit 1
  27}
  28
  29TMP_INDEX=
  30THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}"
  31NEXT_INDEX="$GIT_DIR/next-index$$"
  32rm -f "$NEXT_INDEX"
  33save_index () {
  34        cp -p "$THIS_INDEX" "$NEXT_INDEX"
  35}
  36
  37run_status () {
  38        # If TMP_INDEX is defined, that means we are doing
  39        # "--only" partial commit, and that index file is used
  40        # to build the tree for the commit.  Otherwise, if
  41        # NEXT_INDEX exists, that is the index file used to
  42        # make the commit.  Otherwise we are using as-is commit
  43        # so the regular index file is what we use to compare.
  44        if test '' != "$TMP_INDEX"
  45        then
  46                GIT_INDEX_FILE="$TMP_INDEX"
  47                export GIT_INDEX_FILE
  48        elif test -f "$NEXT_INDEX"
  49        then
  50                GIT_INDEX_FILE="$NEXT_INDEX"
  51                export GIT_INDEX_FILE
  52        fi
  53
  54        if test "$status_only" = "t" || test "$use_status_color" = "t"; then
  55                color=
  56        else
  57                color=--nocolor
  58        fi
  59        git runstatus ${color} \
  60                ${verbose:+--verbose} \
  61                ${amend:+--amend} \
  62                ${untracked_files:+--untracked}
  63}
  64
  65trap '
  66        test -z "$TMP_INDEX" || {
  67                test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
  68        }
  69        rm -f "$NEXT_INDEX"
  70' 0
  71
  72################################################################
  73# Command line argument parsing and sanity checking
  74
  75all=
  76also=
  77allow_empty=f
  78interactive=
  79only=
  80logfile=
  81use_commit=
  82amend=
  83edit_flag=
  84no_edit=
  85log_given=
  86log_message=
  87verify=t
  88quiet=
  89verbose=
  90signoff=
  91force_author=
  92only_include_assumed=
  93untracked_files=
  94templatefile="$(git config commit.template)"
  95while test $# != 0
  96do
  97        case "$1" in
  98        -F|--F|-f|--f|--fi|--fil|--file)
  99                case "$#" in 1) usage ;; esac
 100                shift
 101                no_edit=t
 102                log_given=t$log_given
 103                logfile="$1"
 104                ;;
 105        -F*|-f*)
 106                no_edit=t
 107                log_given=t$log_given
 108                logfile="${1#-[Ff]}"
 109                ;;
 110        --F=*|--f=*|--fi=*|--fil=*|--file=*)
 111                no_edit=t
 112                log_given=t$log_given
 113                logfile="${1#*=}"
 114                ;;
 115        -a|--a|--al|--all)
 116                all=t
 117                ;;
 118        --allo|--allow|--allow-|--allow-e|--allow-em|--allow-emp|\
 119        --allow-empt|--allow-empty)
 120                allow_empty=t
 121                ;;
 122        --au=*|--aut=*|--auth=*|--autho=*|--author=*)
 123                force_author="${1#*=}"
 124                ;;
 125        --au|--aut|--auth|--autho|--author)
 126                case "$#" in 1) usage ;; esac
 127                shift
 128                force_author="$1"
 129                ;;
 130        -e|--e|--ed|--edi|--edit)
 131                edit_flag=t
 132                ;;
 133        -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
 134                also=t
 135                ;;
 136        --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
 137        --interactiv|--interactive)
 138                interactive=t
 139                ;;
 140        -o|--o|--on|--onl|--only)
 141                only=t
 142                ;;
 143        -m|--m|--me|--mes|--mess|--messa|--messag|--message)
 144                case "$#" in 1) usage ;; esac
 145                shift
 146                log_given=m$log_given
 147                log_message="${log_message:+${log_message}
 148
 149}$1"
 150                no_edit=t
 151                ;;
 152        -m*)
 153                log_given=m$log_given
 154                log_message="${log_message:+${log_message}
 155
 156}${1#-m}"
 157                no_edit=t
 158                ;;
 159        --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
 160                log_given=m$log_given
 161                log_message="${log_message:+${log_message}
 162
 163}${1#*=}"
 164                no_edit=t
 165                ;;
 166        -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
 167        --no-verify)
 168                verify=
 169                ;;
 170        --a|--am|--ame|--amen|--amend)
 171                amend=t
 172                use_commit=HEAD
 173                ;;
 174        -c)
 175                case "$#" in 1) usage ;; esac
 176                shift
 177                log_given=t$log_given
 178                use_commit="$1"
 179                no_edit=
 180                ;;
 181        --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
 182        --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
 183        --reedit-messag=*|--reedit-message=*)
 184                log_given=t$log_given
 185                use_commit="${1#*=}"
 186                no_edit=
 187                ;;
 188        --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
 189        --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
 190        --reedit-message)
 191                case "$#" in 1) usage ;; esac
 192                shift
 193                log_given=t$log_given
 194                use_commit="$1"
 195                no_edit=
 196                ;;
 197        -C)
 198                case "$#" in 1) usage ;; esac
 199                shift
 200                log_given=t$log_given
 201                use_commit="$1"
 202                no_edit=t
 203                ;;
 204        --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
 205        --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
 206        --reuse-message=*)
 207                log_given=t$log_given
 208                use_commit="${1#*=}"
 209                no_edit=t
 210                ;;
 211        --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
 212        --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
 213                case "$#" in 1) usage ;; esac
 214                shift
 215                log_given=t$log_given
 216                use_commit="$1"
 217                no_edit=t
 218                ;;
 219        -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
 220                signoff=t
 221                ;;
 222        -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
 223                case "$#" in 1) usage ;; esac
 224                shift
 225                templatefile="$1"
 226                no_edit=
 227                ;;
 228        -q|--q|--qu|--qui|--quie|--quiet)
 229                quiet=t
 230                ;;
 231        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
 232                verbose=t
 233                ;;
 234        -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
 235        --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
 236        --untracked-file|--untracked-files)
 237                untracked_files=t
 238                ;;
 239        --)
 240                shift
 241                break
 242                ;;
 243        -*)
 244                usage
 245                ;;
 246        *)
 247                break
 248                ;;
 249        esac
 250        shift
 251done
 252case "$edit_flag" in t) no_edit= ;; esac
 253
 254################################################################
 255# Sanity check options
 256
 257case "$amend,$initial_commit" in
 258t,t)
 259        die "You do not have anything to amend." ;;
 260t,)
 261        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
 262                die "You are in the middle of a merge -- cannot amend."
 263        fi ;;
 264esac
 265
 266case "$log_given" in
 267tt*)
 268        die "Only one of -c/-C/-F can be used." ;;
 269*tm*|*mt*)
 270        die "Option -m cannot be combined with -c/-C/-F." ;;
 271esac
 272
 273case "$#,$also,$only,$amend" in
 274*,t,t,*)
 275        die "Only one of --include/--only can be used." ;;
 2760,t,,* | 0,,t,)
 277        die "No paths with --include/--only does not make sense." ;;
 2780,,t,t)
 279        only_include_assumed="# Clever... amending the last one with dirty index." ;;
 2800,,,*)
 281        ;;
 282*,,,*)
 283        only_include_assumed="# Explicit paths specified without -i or -o; assuming --only paths..."
 284        also=
 285        ;;
 286esac
 287unset only
 288case "$all,$interactive,$also,$#" in
 289*t,*t,*)
 290        die "Cannot use -a, --interactive or -i at the same time." ;;
 291t,,,[1-9]*)
 292        die "Paths with -a does not make sense." ;;
 293,t,,[1-9]*)
 294        die "Paths with --interactive does not make sense." ;;
 295,,t,0)
 296        die "No paths with -i does not make sense." ;;
 297esac
 298
 299if test ! -z "$templatefile" && test -z "$log_given"
 300then
 301        if test ! -f "$templatefile"
 302        then
 303                die "Commit template file does not exist."
 304        fi
 305fi
 306
 307################################################################
 308# Prepare index to have a tree to be committed
 309
 310case "$all,$also" in
 311t,)
 312        if test ! -f "$THIS_INDEX"
 313        then
 314                die 'nothing to commit (use "git add file1 file2" to include for commit)'
 315        fi
 316        save_index &&
 317        (
 318                cd_to_toplevel &&
 319                GIT_INDEX_FILE="$NEXT_INDEX" &&
 320                export GIT_INDEX_FILE &&
 321                git diff-files --name-only -z |
 322                git update-index --remove -z --stdin
 323        ) || exit
 324        ;;
 325,t)
 326        save_index &&
 327        git ls-files --error-unmatch -- "$@" >/dev/null || exit
 328
 329        git diff-files --name-only -z -- "$@"  |
 330        (
 331                cd_to_toplevel &&
 332                GIT_INDEX_FILE="$NEXT_INDEX" &&
 333                export GIT_INDEX_FILE &&
 334                git update-index --remove -z --stdin
 335        ) || exit
 336        ;;
 337,)
 338        if test "$interactive" = t; then
 339                git add --interactive || exit
 340        fi
 341        case "$#" in
 342        0)
 343                ;; # commit as-is
 344        *)
 345                if test -f "$GIT_DIR/MERGE_HEAD"
 346                then
 347                        refuse_partial "Cannot do a partial commit during a merge."
 348                fi
 349
 350                TMP_INDEX="$GIT_DIR/tmp-index$$"
 351                W=
 352                test -z "$initial_commit" && W=--with-tree=HEAD
 353                commit_only=$(git ls-files --error-unmatch $W -- "$@") || exit
 354
 355                # Build a temporary index and update the real index
 356                # the same way.
 357                if test -z "$initial_commit"
 358                then
 359                        GIT_INDEX_FILE="$THIS_INDEX" \
 360                        git read-tree --index-output="$TMP_INDEX" -i -m HEAD
 361                else
 362                        rm -f "$TMP_INDEX"
 363                fi || exit
 364
 365                printf '%s\n' "$commit_only" |
 366                GIT_INDEX_FILE="$TMP_INDEX" \
 367                git update-index --add --remove --stdin &&
 368
 369                save_index &&
 370                printf '%s\n' "$commit_only" |
 371                (
 372                        GIT_INDEX_FILE="$NEXT_INDEX"
 373                        export GIT_INDEX_FILE
 374                        git update-index --add --remove --stdin
 375                ) || exit
 376                ;;
 377        esac
 378        ;;
 379esac
 380
 381################################################################
 382# If we do as-is commit, the index file will be THIS_INDEX,
 383# otherwise NEXT_INDEX after we make this commit.  We leave
 384# the index as is if we abort.
 385
 386if test -f "$NEXT_INDEX"
 387then
 388        USE_INDEX="$NEXT_INDEX"
 389else
 390        USE_INDEX="$THIS_INDEX"
 391fi
 392
 393case "$status_only" in
 394t)
 395        # This will silently fail in a read-only repository, which is
 396        # what we want.
 397        GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
 398        run_status
 399        exit $?
 400        ;;
 401'')
 402        GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
 403        ;;
 404esac
 405
 406################################################################
 407# Grab commit message, write out tree and make commit.
 408
 409if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
 410then
 411    GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
 412    || exit
 413fi
 414
 415if test "$log_message" != ''
 416then
 417        printf '%s\n' "$log_message"
 418elif test "$logfile" != ""
 419then
 420        if test "$logfile" = -
 421        then
 422                test -t 0 &&
 423                echo >&2 "(reading log message from standard input)"
 424                cat
 425        else
 426                cat <"$logfile"
 427        fi
 428elif test "$use_commit" != ""
 429then
 430        encoding=$(git config i18n.commitencoding || echo UTF-8)
 431        git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
 432        sed -e '1,/^$/d' -e 's/^    //'
 433elif test -f "$GIT_DIR/MERGE_MSG"
 434then
 435        cat "$GIT_DIR/MERGE_MSG"
 436elif test -f "$GIT_DIR/SQUASH_MSG"
 437then
 438        cat "$GIT_DIR/SQUASH_MSG"
 439elif test "$templatefile" != ""
 440then
 441        cat "$templatefile"
 442fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 443
 444case "$signoff" in
 445t)
 446        sign=$(git var GIT_COMMITTER_IDENT | sed -e '
 447                s/>.*/>/
 448                s/^/Signed-off-by: /
 449                ')
 450        blank_before_signoff=
 451        tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
 452        grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
 453'
 454        tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
 455        grep "$sign"$ >/dev/null ||
 456        printf '%s%s\n' "$blank_before_signoff" "$sign" \
 457                >>"$GIT_DIR"/COMMIT_EDITMSG
 458        ;;
 459esac
 460
 461if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
 462        echo "#"
 463        echo "# It looks like you may be committing a MERGE."
 464        echo "# If this is not correct, please remove the file"
 465        printf '%s\n' "#        $GIT_DIR/MERGE_HEAD"
 466        echo "# and try again"
 467        echo "#"
 468fi >>"$GIT_DIR"/COMMIT_EDITMSG
 469
 470# Author
 471if test '' != "$use_commit"
 472then
 473        eval "$(get_author_ident_from_commit "$use_commit")"
 474        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 475fi
 476if test '' != "$force_author"
 477then
 478        GIT_AUTHOR_NAME=$(expr "z$force_author" : 'z\(.*[^ ]\) *<.*') &&
 479        GIT_AUTHOR_EMAIL=$(expr "z$force_author" : '.*\(<.*\)') &&
 480        test '' != "$GIT_AUTHOR_NAME" &&
 481        test '' != "$GIT_AUTHOR_EMAIL" ||
 482        die "malformed --author parameter"
 483        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
 484fi
 485
 486PARENTS="-p HEAD"
 487if test -z "$initial_commit"
 488then
 489        rloga='commit'
 490        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
 491                rloga='commit (merge)'
 492                PARENTS="-p HEAD "$(sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD")
 493        elif test -n "$amend"; then
 494                rloga='commit (amend)'
 495                PARENTS=$(git cat-file commit HEAD |
 496                        sed -n -e '/^$/q' -e 's/^parent /-p /p')
 497        fi
 498        current="$(git rev-parse --verify HEAD)"
 499else
 500        if [ -z "$(git ls-files)" ]; then
 501                echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
 502                exit 1
 503        fi
 504        PARENTS=""
 505        rloga='commit (initial)'
 506        current=''
 507fi
 508set_reflog_action "$rloga"
 509
 510if test -z "$no_edit"
 511then
 512        {
 513                echo ""
 514                echo "# Please enter the commit message for your changes."
 515                echo "# (Comment lines starting with '#' will not be included)"
 516                test -z "$only_include_assumed" || echo "$only_include_assumed"
 517                run_status
 518        } >>"$GIT_DIR"/COMMIT_EDITMSG
 519else
 520        # we need to check if there is anything to commit
 521        run_status >/dev/null
 522fi
 523case "$allow_empty,$?,$PARENTS" in
 524t,* | ?,0,* | ?,*,-p' '?*-p' '?*)
 525        # an explicit --allow-empty, or a merge commit can record the
 526        # same tree as its parent.  Otherwise having commitable paths
 527        # is required.
 528        ;;
 529*)
 530        rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
 531        use_status_color=t
 532        run_status
 533        exit 1
 534esac
 535
 536case "$no_edit" in
 537'')
 538        git var GIT_AUTHOR_IDENT > /dev/null  || die
 539        git var GIT_COMMITTER_IDENT > /dev/null  || die
 540        git_editor "$GIT_DIR/COMMIT_EDITMSG"
 541        ;;
 542esac
 543
 544case "$verify" in
 545t)
 546        if test -x "$GIT_DIR"/hooks/commit-msg
 547        then
 548                "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
 549        fi
 550esac
 551
 552if test -z "$no_edit"
 553then
 554    sed -e '
 555        /^diff --git a\/.*/{
 556            s///
 557            q
 558        }
 559        /^#/d
 560    ' "$GIT_DIR"/COMMIT_EDITMSG
 561else
 562    cat "$GIT_DIR"/COMMIT_EDITMSG
 563fi |
 564git stripspace >"$GIT_DIR"/COMMIT_MSG
 565
 566# Test whether the commit message has any content we didn't supply.
 567have_commitmsg=
 568grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
 569        git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
 570
 571# Is the commit message totally empty?
 572if test -s "$GIT_DIR"/COMMIT_BAREMSG
 573then
 574        if test "$templatefile" != ""
 575        then
 576                # Test whether this is just the unaltered template.
 577                if cnt=$(sed -e '/^#/d' < "$templatefile" |
 578                        git stripspace |
 579                        diff "$GIT_DIR"/COMMIT_BAREMSG - |
 580                        wc -l) &&
 581                   test 0 -lt $cnt
 582                then
 583                        have_commitmsg=t
 584                fi
 585        else
 586                # No template, so the content in the commit message must
 587                # have come from the user.
 588                have_commitmsg=t
 589        fi
 590fi
 591
 592rm -f "$GIT_DIR"/COMMIT_BAREMSG
 593
 594if test "$have_commitmsg" = "t"
 595then
 596        if test -z "$TMP_INDEX"
 597        then
 598                tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
 599        else
 600                tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
 601                rm -f "$TMP_INDEX"
 602        fi &&
 603        commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
 604        rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
 605        git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
 606        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
 607        if test -f "$NEXT_INDEX"
 608        then
 609                mv "$NEXT_INDEX" "$THIS_INDEX"
 610        else
 611                : ;# happy
 612        fi
 613else
 614        echo >&2 "* no commit message?  aborting commit."
 615        false
 616fi
 617ret="$?"
 618rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
 619
 620cd_to_toplevel
 621
 622git rerere
 623
 624if test "$ret" = 0
 625then
 626        git gc --auto
 627        if test -x "$GIT_DIR"/hooks/post-commit
 628        then
 629                "$GIT_DIR"/hooks/post-commit
 630        fi
 631        if test -z "$quiet"
 632        then
 633                commit=$(git diff-tree --always --shortstat --pretty="format:%h: %s"\
 634                       --abbrev --summary --root HEAD --)
 635                echo "Created${initial_commit:+ initial} commit $commit"
 636        fi
 637fi
 638
 639exit "$ret"