#!/usr/bin/env bash # # Make a log of changes in a GIT branch. # Copyright (c) Petr Baudis, 2005. # Copyright (c) David Woodhouse, 2005. # # Display log information for files or a range of commits. The output # will automatically be displayed in a pager unless it is piped to # a program. # # OPTIONS # ------- # Arguments not interpreted as options will be interpreted as filenames; # cg-log then displays only changes in those files. # # -c:: Colorize # Colorize the output. The used colors are listed below together # with information about which log output (summary, full or both) # they apply to: # - `author`: 'cyan' (both) # - `committer`: 'magenta' (full) # - `header`: 'green' (full) # - `files`: 'blue' (full) # - `signoff`: 'yellow' (full) # - `commit_id`: 'blue' (summary) # - `date`: 'green' (summary) # - `trim_mark`: 'magenta' (summary) # # -f:: List affected files # List affected files. (No effect when passed along `-s`.) # # -r FROM_ID[..TO_ID]:: Limit to a set of revisions # Limit the log information to a set of revisions using either # '-r FROM_ID[..TO_ID]' or '-r FROM_ID -r TO_ID'. In both cases the # option expects IDs which resolve to commits and will include the # specified IDs. If 'TO_ID' is omitted all commits from 'FROM_ID' # to the initial commit is shown. If no revisions is specified, # the log information starting from 'HEAD' will be shown. # # -D DATE:: Limit to revisions newer than given DATE # Limit the log information to revisions newer than given DATE, # and on second time further restrain it to revisions older than # given date. Therefore, '-D "2 days ago" -D "yesterday"' will # show all the commits from the day before yesterday. # # -m:: End the log at the merge base of the revision set # End the log listing at the merge base of the -r arguments # (defaulting to HEAD and origin). # # -M, --no-merges:: Ignore merge commits # Don't display merge commits in the log. # # -s:: Summarize the log entries # Show a one line summary for each log entry. The summary contains # information about the commit date, the author, the first line # of the commit log and the commit ID. Long author names and commit # IDs are trimmed and marked with an ending tilde (~). # # --summary:: Group commits by title # Generate the changes summary, listing the commit titles grouped # by their author. This is also known as a "shortlog", suitable # e.g. for contribution summaries of announcements. # # -uUSERNAME:: Limit to commit where author/committer matches USERNAME # List only commits where author or committer contains 'USERNAME'. # The search for 'USERNAME' is case-insensitive. # # ENVIRONMENT VARIABLES # --------------------- # PAGER:: # The pager to display log information in, defaults to `less`. # # PAGER_FLAGS:: # Flags to pass to the pager. # # CG_LESS:: # This is what the $LESS environment variable value will be set # to before invoking $PAGER. It defaults to $LESS concatenated # with the `R` and `S` flags to allow displaying of colorized output # and to avoid long lines from wrapping when using `-s`. # # EXAMPLE USAGE # ------------- # To show a log of changes between two releases tagged as 'releasetag-0.9' # and 'releasetag-0.10' do: # # $ cg-log -r releasetag-0.9..releasetag-0.10 # # Similarily, to see which commits are in branch A but not in branch B, # # $ cg-log -r B..A # # (meaning "all the commits which newly appear along the way from B to A"). # # NOTES # ----- # The ':' is equivalent to '..' in revisions range specification (to make # things more comfortable to SVN users). See cogito(7) for more details # about revision specification. USAGE="cg-log [-D DATE] [-r FROM_ID[..TO_ID]] [-s] [--summary] [OTHER_OPTIONS] FILE..." . "${COGITO_LIB}"cg-Xlib || exit 1 # Try to fix the annoying "Broken pipe" output. May not help, but apparently # at least somewhere it does. Bash is broken. trap exit SIGPIPE list_commit_files() { tree1="$1" tree2="$2" line= sep=" * $colfiles" # List all files for for the initial commit if [ -z $tree2 ]; then list_cmd="git-ls-tree -r $tree1" else list_cmd="git-diff-tree -r $tree1 $tree2" fi echo $list_cmd | cut -f 2- | while read -r file; do echo -n "$sep" sep=", " line="$line$sep$file" if [ ${#line} -le 74 ]; then echo -n "$file" else line=" $file" echo "$coldefault" echo -n " $colfiles$file" fi done echo "$coldefault:" } process_commit_line() { if [ "$key" = "%" ] || [ "$key" = "%$colsignoff" ]; then # The fast common case [ "$summary" ] || [ "$skip_commit" ] || echo " $rest" return fi case "$key" in "commit") [ "$summary" ] || [ "$skip_commit" ] || { [ "$commit" ] && echo; } commit="$rest" parents=() skip_commit= ;; "tree") tree="$rest" ;; "parent") parents[${#parents[@]}]="$rest" ;; "committer") committer="$rest" ;; "author") author="$rest" ;; "") if [ ! "$commit" ]; then # Next commit is coming [ "$summary" ] || echo return fi if [ "$user" ]; then if ! [[ "$author" == *"$user"* || "$committer" == *"$user"* ]]; then skip_commit=1 return fi fi if [ "$summary" ]; then # Print summary commit="${commit%:*}" author="${author% <*}" # We want wordsplitting in the $date here, to get # TZ as separate argument. date=(${committer#*> }) showdate ${date[*]} '+%F %H:%M'; date="$_showdate" read -r title if [ "${#author}" -gt 15 ]; then author="${author:0:14}$coltrim~" fi if [ "${COLUMNS:-0}" -le 90 ]; then commit="${commit:0:12}$coltrim~" fi printf "$colcommit%s $colauthor%-15s $coldate%s $coldefault%s\n" \ "${commit%:*}" "$author" "$date" "${title:2}" commit= return fi echo "${colheader}commit ${commit%:*} $coldefault" echo "${colheader}tree $tree $coldefault" for parent in "${parents[@]}"; do echo "${colheader}parent $parent $coldefault" done # We want wordsplitting in the $date here, to get # TZ as separate argument. date=(${author#*> }) showdate ${date[*]}; pdate="$_showdate" [ "$pdate" ] && author="${author%> *}> $pdate" echo "${colauthor}author $author $coldefault" date=(${committer#*> }) showdate ${date[*]}; pdate="$_showdate" [ "$pdate" ] && committer="${committer%> *}> $pdate" echo "${colcommitter}committer $committer $coldefault" if [ -n "$list_files" ]; then list_commit_files "$tree" "${parents[0]}" fi echo commit= ;; esac } print_commit_log() { commit= author= committer= tree= sed -e ' s/^ \(.*\)/% \1/ /^% *[Ss]igned-[Oo]ff-[Bb]y:.*/ s/^% \(.*\)/% '$colsignoff'\1'$coldefault'/ /^% *[Aa][Cc][Kk]ed-[Bb]y:.*/ s/^% \(.*\)/% '$colsignoff'\1'$coldefault'/ ' | while read -r key rest; do trap exit SIGPIPE process_commit_line done } [ "$COLUMNS" ] || COLUMNS="$(tput cols)" colheader= colauthor= colcommitter= colfiles= colsignoff= colcommit= coldate= coltrim= coldefault= list_files= log_start= log_end= summary= shortlog= user= mergebase= date_from= date_to= no_merges= while optparse; do if optparse -c; then # See terminfo(5), "Color Handling" colheader="$(tput setaf 2)" # Green colauthor="$(tput setaf 6)" # Cyan colcommitter="$(tput setaf 5)" # Magenta colfiles="$(tput setaf 4)" # Blue colsignoff="$(tput setaf 3)" # Yellow colcommit="$(tput setaf 4)" coldate="$(tput setaf 2)" coltrim="$(tput setaf 5)" coldefault="$(tput op)" # Restore default elif optparse -f; then list_files=1 elif optparse -u=; then user="$OPTARG" elif optparse -r=; then if echo "$OPTARG" | fgrep -q '..'; then log_end="${OPTARG#*..}" [ "$log_end" ] || log_end="HEAD" log_start="${OPTARG%..*}" elif echo "$OPTARG" | grep -q ':'; then log_end="${OPTARG#*:}" [ "$log_end" ] || log_end="HEAD" log_start="${OPTARG%:*}" elif [ -z "$log_start" ]; then log_start="$OPTARG" else log_end="$OPTARG" fi elif optparse -D=; then if [ -z "$date_from" ]; then date_from="--max-age=$(date -d "$OPTARG" +%s)" || exit 1 else date_to="--min-age=$(date -d "$OPTARG" +%s)" || exit 1 fi elif optparse -d=; then die "the -d option was renamed to -D" elif optparse -m; then mergebase=1 elif optparse -M || optparse --no-merges; then no_merges="--no-merges" elif optparse -s; then summary=1 elif optparse --summary; then shortlog=1 else optfail fi done if [ "$mergebase" ]; then [ "$log_start" ] || log_start="HEAD" [ "$log_end" ] || log_end="origin" id1="$(cg-object-id -c "$log_start")" || exit 1 id2="$(cg-object-id -c "$log_end")" || exit 1 conservative_merge_base "$id1" "$id2" || exit 1 [ "$_cg_base_conservative" ] && warn -b "multiple merge bases, picking the most conservative one" log_start="$_cg_baselist" fi if [ "$shortlog" ]; then revls="git-rev-list --pretty=short" else revls="git-rev-list --pretty=raw" fi # Word splitting is ok here and we want to auto-drop empty dates. revls="$revls $no_merges $date_from $date_to" id1="$(cg-object-id -c "$log_start")" || exit 1 if [ "$log_end" ]; then id2="$(cg-object-id -c "$log_end")" || exit 1 revls="$revls $id2 ^$id1" else revls="$revls $id1" fi # Later, we will want to use git-diff-tree --stdin for the filtering # instead of git-rev-list, so that we can do custom renames detection. # See also . sep= [ "${ARGS[*]}" ] && sep=-- # Translate arguments to relpath: if [ "$_git_relpath" ]; then for (( i=0; i<${#ARGS[@]}; i++ )); do ARGS[$i]="$_git_relpath${ARGS[$i]}" done fi if [ "$shortlog" ]; then # Special care here. $revls $sep "${ARGS[@]}" | git-shortlog | pager exit fi # LESS="S" will prevent less to wrap too long titles to multiple lines; # you can scroll horizontally. $revls $sep "${ARGS[@]}" | print_commit_log | _local_CG_LESS="S" pager exit 0