Code

Merge branch 'jc/diff-algo-cleanup'
[git.git] / contrib / examples / git-commit.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2005 Linus Torvalds
4 # Copyright (c) 2006 Junio C Hamano
6 USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
7 SUBDIRECTORY_OK=Yes
8 OPTIONS_SPEC=
9 . git-sh-setup
10 require_work_tree
12 git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
14 case "$0" in
15 *status)
16         status_only=t
17         ;;
18 *commit)
19         status_only=
20         ;;
21 esac
23 refuse_partial () {
24         echo >&2 "$1"
25         echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
26         exit 1
27 }
29 TMP_INDEX=
30 THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}"
31 NEXT_INDEX="$GIT_DIR/next-index$$"
32 rm -f "$NEXT_INDEX"
33 save_index () {
34         cp -p "$THIS_INDEX" "$NEXT_INDEX"
35 }
37 run_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
54         if test "$status_only" = "t" -o "$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 }
65 trap '
66         test -z "$TMP_INDEX" || {
67                 test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
68         }
69         rm -f "$NEXT_INDEX"
70 ' 0
72 ################################################################
73 # Command line argument parsing and sanity checking
75 all=
76 also=
77 allow_empty=f
78 interactive=
79 only=
80 logfile=
81 use_commit=
82 amend=
83 edit_flag=
84 no_edit=
85 log_given=
86 log_message=
87 verify=t
88 quiet=
89 verbose=
90 signoff=
91 force_author=
92 only_include_assumed=
93 untracked_files=
94 templatefile="`git config commit.template`"
95 while test $# != 0
96 do
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}
149 }$1"
150                 no_edit=t
151                 ;;
152         -m*)
153                 log_given=m$log_given
154                 log_message="${log_message:+${log_message}
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}
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
251 done
252 case "$edit_flag" in t) no_edit= ;; esac
254 ################################################################
255 # Sanity check options
257 case "$amend,$initial_commit" in
258 t,t)
259         die "You do not have anything to amend." ;;
260 t,)
261         if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
262                 die "You are in the middle of a merge -- cannot amend."
263         fi ;;
264 esac
266 case "$log_given" in
267 tt*)
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." ;;
271 esac
273 case "$#,$also,$only,$amend" in
274 *,t,t,*)
275         die "Only one of --include/--only can be used." ;;
276 0,t,,* | 0,,t,)
277         die "No paths with --include/--only does not make sense." ;;
278 0,,t,t)
279         only_include_assumed="# Clever... amending the last one with dirty index." ;;
280 0,,,*)
281         ;;
282 *,,,*)
283         only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
284         also=
285         ;;
286 esac
287 unset only
288 case "$all,$interactive,$also,$#" in
289 *t,*t,*)
290         die "Cannot use -a, --interactive or -i at the same time." ;;
291 t,,,[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." ;;
297 esac
299 if test ! -z "$templatefile" -a -z "$log_given"
300 then
301         if test ! -f "$templatefile"
302         then
303                 die "Commit template file does not exist."
304         fi
305 fi
307 ################################################################
308 # Prepare index to have a tree to be committed
310 case "$all,$also" in
311 t,)
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
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
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
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
365                 printf '%s\n' "$commit_only" |
366                 GIT_INDEX_FILE="$TMP_INDEX" \
367                 git update-index --add --remove --stdin &&
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         ;;
379 esac
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.
386 if test -f "$NEXT_INDEX"
387 then
388         USE_INDEX="$NEXT_INDEX"
389 else
390         USE_INDEX="$THIS_INDEX"
391 fi
393 case "$status_only" in
394 t)
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         ;;
404 esac
406 ################################################################
407 # Grab commit message, write out tree and make commit.
409 if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
410 then
411     GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
412     || exit
413 fi
415 if test "$log_message" != ''
416 then
417         printf '%s\n' "$log_message"
418 elif test "$logfile" != ""
419 then
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
428 elif test "$use_commit" != ""
429 then
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/^    //'
433 elif test -f "$GIT_DIR/MERGE_MSG"
434 then
435         cat "$GIT_DIR/MERGE_MSG"
436 elif test -f "$GIT_DIR/SQUASH_MSG"
437 then
438         cat "$GIT_DIR/SQUASH_MSG"
439 elif test "$templatefile" != ""
440 then
441         cat "$templatefile"
442 fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
444 case "$signoff" in
445 t)
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='
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         ;;
459 esac
461 if 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 "#"
468 fi >>"$GIT_DIR"/COMMIT_EDITMSG
470 # Author
471 if test '' != "$use_commit"
472 then
473         eval "$(get_author_ident_from_commit "$use_commit")"
474         export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
475 fi
476 if test '' != "$force_author"
477 then
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
484 fi
486 PARENTS="-p HEAD"
487 if test -z "$initial_commit"
488 then
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)"
499 else
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=''
507 fi
508 set_reflog_action "$rloga"
510 if test -z "$no_edit"
511 then
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
519 else
520         # we need to check if there is anything to commit
521         run_status >/dev/null
522 fi
523 case "$allow_empty,$?,$PARENTS" in
524 t,* | ?,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
534 esac
536 case "$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         ;;
542 esac
544 case "$verify" in
545 t)
546         if test -x "$GIT_DIR"/hooks/commit-msg
547         then
548                 "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
549         fi
550 esac
552 if test -z "$no_edit"
553 then
554     sed -e '
555         /^diff --git a\/.*/{
556             s///
557             q
558         }
559         /^#/d
560     ' "$GIT_DIR"/COMMIT_EDITMSG
561 else
562     cat "$GIT_DIR"/COMMIT_EDITMSG
563 fi |
564 git stripspace >"$GIT_DIR"/COMMIT_MSG
566 # Test whether the commit message has any content we didn't supply.
567 have_commitmsg=
568 grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
569         git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
571 # Is the commit message totally empty?
572 if test -s "$GIT_DIR"/COMMIT_BAREMSG
573 then
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
590 fi
592 rm -f "$GIT_DIR"/COMMIT_BAREMSG
594 if test "$have_commitmsg" = "t"
595 then
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
613 else
614         echo >&2 "* no commit message?  aborting commit."
615         false
616 fi
617 ret="$?"
618 rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
620 cd_to_toplevel
622 git rerere
624 if test "$ret" = 0
625 then
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
637 fi
639 exit "$ret"