3 # Show the list of changes
4 # Copyright (c) Petr Baudis, 2005.
5 # Copyright (c) David Woodhouse, 2005.
7 # Display log of changes on a given branch, within a given range of commits,
8 # and/or concerning given set of files. The log can be further filtered to
9 # e.g. only changes touching a given string or done by a given user. Several
10 # output formats are available.
12 # The output will automatically be displayed in a pager unless it is piped to
17 # Arguments not interpreted as options will be interpreted as filenames;
18 # cg-log then displays only changes in those files.
21 # Colorize the output. You can customize the colors using the
22 # $CG_COLORS environment variable (see below).
24 # -d:: Show diffs against previous commits
25 # Accompany each commit with a diff against previous commit.
28 # --diffcore ARGS:: Diffcore arguments to pass the Git diff command
29 # Pass the given diffcore arguments the called Git diff command.
30 # See e.g. git-diff-tree(1) documentation for the list of possible
31 # arguments; '-R', '-B', and '-C' might be of particular interest
32 # ('-M' is sometimes passed automatically, but not always). This
33 # is mostly only relevant in conjunction with the '-d' option.
35 # -f:: List affected files
36 # List affected files. (No effect when passed along '-s'.)
39 # -r FROM_ID[..TO_ID]:: Limit to a set of revisions
40 # Limit the log information to a set of revisions using either
41 # '-r FROM_ID[..TO_ID]' or '-r FROM_ID -r TO_ID'. In both cases the
42 # option expects IDs which resolve to commits and will include the
43 # specified IDs. If 'TO_ID' is omitted all commits from 'FROM_ID'
44 # to the initial commit is shown. If no revisions is specified,
45 # the log information starting from 'HEAD' will be shown.
47 # -D DATE:: Limit to revisions newer than given DATE
48 # Limit the log information to revisions newer than given DATE,
49 # and on second time further restrain it to revisions older than
50 # given date. Therefore, '-D "2 days ago" -D "yesterday"' will
51 # show all the commits from the day before yesterday.
53 # -m:: End the log at the merge base of the revision set
54 # End the log listing at the merge base of the -r arguments
55 # to HEAD and 'origin' or the current branch's default remote
56 # branch, see `cg-fetch` for details).
58 # -M, --merges:: Show merge commits
59 # Display merge commits in the log.
61 # -R, --no-renames:: Do not follow renames
62 # This flag is currently no-op. `cg-log` will not follow file history
65 # -s:: Short output format of the log entries
66 # Show the log entries one per line. The entry summary contains
67 # information about the commit date, the author, the first line
68 # of the commit log and the commit ID. Long author names and commit
69 # IDs are trimmed and marked with an ending tilde (~).
71 # --summary:: Group commits by author
72 # Generate the changes summary, listing the commit titles grouped
73 # by their author. This is also known as a "shortlog", suitable
74 # e.g. for contribution summaries of announcements.
76 # -S, --pickaxe STRING:: Limit to changes touching STRING ("pick-axe")
77 # List only commits with changes concerning STRING (also known as
78 # pick-axe). In other words, only commits where the parent contains
79 # STRING and the child does not contain it at the same place in
80 # a file or vice versa are shown. The STRING may contain any
81 # special characters or even newlines (but you might need to quote
82 # it properly when calling `cg-log` from a shell). It is matched
85 # -u USERNAME:: Limit to commit where author/committer matches USERNAME
86 # List only commits where author or committer contains 'USERNAME'.
87 # The search for 'USERNAME' is case-insensitive.
89 # -v:: Verbose header listing
90 # By default, only the 'commit' and 'author' headers are shown. This
91 # makes `cg-log` show even the other commit headers - 'tree', 'parent',
94 # ENVIRONMENT VARIABLES
95 # ---------------------
97 # The pager to display log information in, defaults to `less`.
100 # Flags to pass to the pager.
103 # Colon-separated list of 'name=color' pairs, where name is
104 # one of logcommit, logheader, logauthor, logcommitter,
105 # logfilemod, logfileadd, logfiledel, logfileren, logsignoff,
106 # logsumauthor, logsumtrim, logsumcommit, logsumdate, default,
107 # and value is an ECMA-48 SGR sequence (see e.g. console_codes(4)).
108 # You can also customize the diff colors; see `cg-diff` documentation
109 # for the appropriate color names.
112 # Even if -c was passed or specified in ~/.cgrc, if this option
113 # is set, use colors only when the output is a terminal and it
117 # This is what the $LESS environment variable value will be set
118 # to before invoking $PAGER. It defaults to $LESS concatenated
119 # with the `R` and `S` flags to allow displaying of colorized output
120 # and to avoid long lines from wrapping when using `-s`.
122 # CONFIGURATION VARIABLES
123 # -----------------------
124 # The following GIT configuration file variables are recognized:
127 # If enabled, colorify the output like with -c if the output
132 # To show a log of changes between two releases tagged as 'releasetag-0.9'
133 # and 'releasetag-0.10' do:
135 # $ cg-log -r releasetag-0.9..releasetag-0.10
137 # Similarily, to see which commits are in branch A but not in branch B,
141 # (meaning "all the commits which newly appear along the way from B to A").
143 # If you see a dubious "if (current->uid = 0)" test in a file and wonder
144 # about its genesis, you can run
146 # $ cg-log -d -S "if (current->uid = 0)" filename
148 # to show the commits adding, removing or modifying that string, together
149 # with the relevant patches (you can obviously refrain from limiting
150 # the pick-axe to a particular file, but it will make it significantly
155 # The ':' is equivalent to '..' in revisions range specification (to make
156 # things more comfortable to SVN users). See cogito(7) for more details
157 # about revision specification.
161 USAGE
="cg-log [-D DATE] [-r FROM_ID[..TO_ID]] [-d] [-s | --summary] [OTHER_OPTIONS] [FILE]..."
164 .
"${COGITO_LIB}"cg-Xlib ||
exit 1
165 # Try to fix the annoying "Broken pipe" output. May not help, but apparently
166 # at least somewhere it does. Bash is broken.
173 local C
="logcommit=32:logheader=32"
174 C
="$C:logauthor=36:logcommitter=35:logsignoff=33"
175 C
="$C:logfilemod=34:logfileadd=36:logfiledel=31:logfileren=33"
176 C
="$C:logsumauthor=36:logsumtrim=35"
177 C
="$C:logsumcommit=34:logsumdate=32"
179 [ "$show_diffs" ] && C
="$C:logcommit=32;1"
180 # Remove bolds from diffcolors - they tended to overshadow cg-log's
181 # highlighting, making it less obvious where one commit ends and
183 colorify_setup
"$C:${colorify_diffcolors//1;/}"
184 collogfile_M0
="$collogfilemod"
185 collogfile_A0
="$collogfileadd"
186 collogfile_D0
="$collogfiledel"
187 collogfile_R0
="$collogfileren"
188 collogfile_R1
="$collogfileadd"
193 commit
="${commit%:*}"
194 author
="${author% <*}"
195 [ "${#author}" -gt 15 ] && author
="${author:0:14}$collogsumtrim~"
196 sumcommit
="${commit:0:12}$collogsumtrim~"
198 # We want wordsplitting in the $date here, to get
199 # TZ as separate argument.
200 date=(${committer#*> })
201 showdate
${date[*]} '+%F %H:%M'; date="$_showdate"
205 printf "$collogsumcommit%s $collogsumauthor%-15s $collogsumdate%s $coldefault%s\n" \
206 "$sumcommit" "$author" "$date" "${title:3}"
211 if [ ${#files[@]} -eq 0 ]; then
212 echo " * no changes:"
220 for (( i
=0; i
< ${#files[@]}; i
++ )); do
222 sep
="$collogfilemod, "
223 line
="$line$sep${files[$i]}"
224 if [ ${#line} -le 74 ]; then
225 echo -n "${filecols[$i]}${files[$i]}"
229 echo -n " ${filecols[$i]}${files[$i]}"
236 print_commit_contents
()
238 [ "$list_files" ] && list_commit_files
253 # The $state variable mostly describes what should happen on the next
258 process_commit_line
()
260 if [ "$key" = "%" ] ||
[ "$key" = "%$collogsignoff" ]; then
261 # The fast common case
262 [ "$state" = silent
] || msg
="$msg ${rest#?}
267 "commit"|
"diff-tree")
270 commit
="${rest:0:40}"
271 # If we've just seen a commit, we are seeing multiple
272 # instances of the same commit caused by git-diff-tree --stdin
273 # hitting a merge. We show only the diff against the first
274 # parent since heuristically, this is the interesting one.
275 # In some cases, this might not be true, but this is hopefully
276 # a good general strategy (always except in the change-here-
277 # -merge-there-fastforward-here case and doing anything else
278 # results in unusably huge file lists etc).
279 [ "$commit" = "$oldcommit" ] && state
=silent
285 parents
[${#parents[@]}]="$rest"
297 crest
="${crest%[0-9][0-9][0-9]}" # rename similarity
299 while [ x
"$rest" != x
"$orest" ]; do
300 local l
="collogfile_${crest:(-1):1}$i"
301 filecols
[${#files[@]}]="${!l}"
302 files
[${#files[@]}]="${rest%% *}"
303 # Multiple tab-separated filenames are present in case
311 if [ "$state" = silent
]; then
314 if [ "$state" = waitdiff
]; then
315 # We cannot hook this to ^: since the diff may be empty
316 [ "$show_diffs" ] && msg
="$msg
321 if [ "$state" = showcommit
]; then
322 print_commit_contents
323 [ "$show_diffs" ] && echo
327 if [ "$state" != printhdr
]; then
328 die
"internal error - state '$state'"
332 if ! [[ "$author" == *"$user"* ||
"$committer" == *"$user"* ]]; then
337 if [ "$oneline" ]; then
344 [ ! "$verbose" -a ${#parents[@]} -gt 1 ] && merge
=" (merge)"
345 echo "${collogcommit}Commit: ${commit%:*}$merge $coldefault"
347 if [ "$verbose" ]; then
348 echo "${collogheader}Tree: $tree $coldefault"
350 for parent
in "${parents[@]}"; do
351 echo "${collogheader}Parent: $parent $coldefault"
355 # We want wordsplitting in the $date here, to get
356 # TZ as separate argument.
358 showdate
${date[*]}; pdate
="$_showdate"
359 [ "$pdate" ] && author
="${author%> *}> $pdate"
360 echo "${collogauthor}Author: $author $coldefault"
362 if [ "$verbose" ]; then
363 date=(${committer#*> })
364 showdate
${date[*]}; pdate
="$_showdate"
365 [ "$pdate" ] && committer
="${committer%> *}> $pdate"
366 echo "${collogcommitter}Committer: $committer $coldefault"
370 if [ "$difffilter" ]; then
381 [ "$show_diffs" ] || colorify_diffsed
=
386 /^% *@[Ss]igned-[Oo]ff-[Bb]y:.*/ s/^% @\(.*\)/% @'$collogsignoff'\1'$coldefault'/
387 /^% *@[Aa][Cc][Kk]ed-[Bb]y:.*/ s/^% @\(.*\)/% @'$collogsignoff'\1'$coldefault'/
388 ' -e "$colorify_diffsed" |
{ while IFS
=$
'\n' read -r line
; do
390 if [ "$state" = "showcommit" -a "$show_diffs" -a -n "$line" ]; then
391 [ x
"${line#% @}" = x
"$line" ] || line
=" ${line#% @}" # undo sed damage
392 [ "$state" = silent
] || msg
="$msg$line
399 done; [ "$state" = "showcommit" ] && print_commit_contents
# the last commit
431 no_merges
=--no-merges
440 elif optparse
-d; then
445 elif optparse
-f; then
450 elif optparse
-u=; then
452 elif optparse
-r=; then
453 if echo "$OPTARG" | fgrep
-q '..'; then
456 # id2 was specified as empty commit, that is HEAD;
457 # but leaving it empty now would give the code below
459 [ "$id2" ] || id2
="HEAD"
460 elif echo "$OPTARG" |
grep -q ':'; then
463 [ "$id2" ] || id2
="HEAD"
464 elif [ -z "$id1" ]; then
466 elif [ -z "$id2" ]; then
469 die
"too many revisions"
471 elif optparse
-D=; then
472 if [ -z "$date_from" ]; then
473 date_from
="--max-age=$(date -d "$OPTARG" +%s)" ||
exit 1
475 date_to
="--min-age=$(date -d "$OPTARG" +%s)" ||
exit 1
477 elif optparse
-d=; then
478 die
"the -d option was renamed to -D"
479 elif optparse
-m; then
481 elif optparse
-M || optparse
--merges; then
484 elif optparse
-R || optparse
--no-renames; then
486 elif optparse
-s; then
488 elif optparse
-S= || optparse
--pickaxe=; then
490 pickaxe
=(-S"$OPTARG")
491 # The trouble with this is that less behaves really strange.
492 # It withholds the output until it reads all the input, for
493 # example. So this didn't work out very well so far. :-(
494 # pickaxe_less=$'+/\013\022'"${OPTARG}"
495 # pickaxe_less="${pickaxe_less%$'\n'*}" # stupid less!
497 elif optparse
--diffcore=; then
499 elif optparse
--summary; then
501 elif optparse
-v; then
509 # [ "$pickaxe_less" -a "$show_diffs" ] && _local_CG_LESS="$pickaxe_less"
510 colorify_detect
"$colors" log
&& setup_colors
511 if [ "$show_diffs" -a "${ARGS[*]}" ]; then
512 #warn "-d is buggy and cannot follow renames yet; implying --no-renames"
517 # Word splitting is ok here and we want to auto-drop empty dates.
518 revls
="$no_merges $date_from $date_to"
521 if [ "$mergebase" ]; then
522 [ "$id1" ] || id1
="HEAD"
523 [ "$id2" ] ||
{ id2
="$(choose_origin refs/heads "what to log against?
")" ||
exit 1; }
525 id1
="$(cg-object-id -c "$id1")" ||
exit 1
526 id2
="$(cg-object-id -c "$id2")" ||
exit 1
527 conservative_merge_base
"$id1" "$id2" ||
exit 1
528 [ "$_cg_base_conservative" ] &&
529 warn
-b "multiple merge bases, picking the most conservative one"
533 id1
="$(cg-object-id -c "$id1")" ||
exit 1
537 id2
="$(cg-object-id -c "$id2")" ||
exit 1
545 if [ "$shortlog" ]; then
552 if [ "${ARGS[*]}" ]; then
553 [ "$neverfollowrenames" ] || followrenames
=1
557 # Translate arguments to relpath:
558 if [ "$_git_relpath" ]; then
559 for (( i
=0; i
<${#ARGS[@]}; i
++ )); do
560 ARGS
[$i]="$_git_relpath${ARGS[$i]}"
564 [ "$followrenames" ] && difffilter
=followrenames
567 # A curious pipeline:
570 # XXX: Following renames is broken and turns out to be massive
572 # if [ "$followrenames" ]; then
573 # [ "${ARGS[*]}" ] || die "internal error: no files to follow renames on"
574 # # We ignore $fmt but that's no biggie, shortlog
575 # # will actually work anyway.
576 # "${COGITO_LIB}"cg-Xfollowrenames $revls -- \
577 # --root --pickaxe-all $diffmerges $diffpatches \
578 # $always $diffcore "${pickaxe[@]}" -- \
579 # $revlsstart -- "${ARGS[@]}"
581 if [ "$difffilter" ]; then
582 git-rev-list
$revls $revlsstart $sep "${ARGS[@]}" | \
583 git-diff-tree
-r --stdin --root --pickaxe-all \
584 $diffmerges $diffpatches $always $diffcore $fmt \
587 git-rev-list
$revls $revlsstart $fmt $sep "${ARGS[@]}"
593 if [ "$shortlog" ]; then
596 # LESS="S" will prevent less to wrap too long titles
597 # to multiple lines; you can scroll horizontally.
598 print_commit_log | _local_CG_LESS
="S $_local_CG_LESS" pager
602 rev_extract | rev_show