#!/usr/bin/env bash # # Make a diff between two GIT trees. # Copyright (c) Petr Baudis, 2005 # # Outputs a diff for converting the first tree to the second one. # By default compares the current working tree to the state at the # last commit. The output will automatically be displayed in a pager # unless it is piped to a program. # # OPTIONS # ------- # -c:: Colorize # Colorize the diff output # # -p:: Use ID parent # Show diff to the parent of the current commit (or the commit # specified by the -r parameter). # # -s:: Summarize and show diff stat # Summarize the diff by showing a histogram for removed and added # lines (similar to the output of diffstat(1)) and information # about added and renamed files and mode changes. # # -r FROM_ID[..TO_ID]:: Limit to revision range # Specify the revisions to diff using either '-r rev1..rev2' or # '-r rev1 -r rev2'. If no revision is specified, the current # working tree is implied. Note that no revision is different from # empty revision which means '-r rev..' compares between 'rev' and # 'HEAD', while '-r rev' compares between 'rev' and working tree. # # -m:: Base the diff at the merge base # Base the diff at the merge base of the -r arguments (defaulting # to HEAD and origin). # # 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` flag to allow displaying of colorized output. # # 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-diff [-c] [-m] [-s] [-p] [-r FROM_ID[..TO_ID]] [FILE]..." . "${COGITO_LIB}"cg-Xlib || exit 1 # TODO: Make cg-log use this too. setup_colors() { local C="diffhdr=1;36" C="$C:diffhdradd=1;32:diffadd=32" C="$C:diffhdrmod=1;35:diffmod=35" C="$C:diffhdrrem=1;31:diffrem=31" C="$C:diffhunk=36:diffctx=34" C="$C:default=0" [ -n "$COGITO_COLORS" ] && C="$C:$COGITO_COLORS" C=${C//=/=\'$'\e'[} C=col${C//:/m\'; col}m\' #coldefault=$(tput op) eval "$C" } colorize() { if [ "$opt_summary" ]; then git-apply --summary --stat elif [ "$opt_color" ]; then gawk ' { if (/^(diff --git) /) print "'$coldiffhdr'" $0 "'$coldefault'" else if (/^\+\+\+/) print "'$coldiffhdradd'" $0 "'$coldefault'" else if (/^---/) print "'$coldiffhdrrem'" $0 "'$coldefault'" else if (/^(\+|new( file)? mode )/) print "'$coldiffadd'" $0 "'$coldefault'" else if (/^(-|(deleted file|old) mode )/) print "'$coldiffrem'" $0 "'$coldefault'" else if (/^@@ \-[0-9]+(,[0-9]+)? \+[0-9]+(,[0-9]+)? @@/) print gensub(/^(@@[^@]*@@)([ \t]*)(.*)/, "'$coldiffhunk'" "\\1" "'$coldefault'" \ "\\2" \ "'$coldiffctx'" "\\3" "'$coldefault'", "") else print }' else cat fi } id1=" " id2=" " parent= opt_color= mergebase= opt_summary= while optparse; do if optparse -c; then opt_color=1 setup_colors elif optparse -p; then parent=1 elif optparse -s; then opt_summary=1 elif optparse -r=; then if echo "$OPTARG" | fgrep -q '..'; then id2="${OPTARG#*..}" [ "$id2" ] || log_end="HEAD" id1="${OPTARG%..*}" elif echo "$OPTARG" | grep -q ':'; then id2="${OPTARG#*:}" [ "$id2" ] || log_end="HEAD" id1="${OPTARG%:*}" elif [ "$id1" = " " ]; then id1="$OPTARG" else id2="$OPTARG" fi elif optparse -m; then mergebase=1 else optfail fi done if [ "$parent" ]; then id2="$id1" id="$id2"; [ "$id" = " " ] && id="" ids="$(cg-object-id -p "$id")" [ "$(echo "$ids" | wc -l)" -gt 1 ] && \ warn "choosing the first parent of a merge commit. This may not be what you want." id1="$(echo "$ids" | head -n 1)" || exit 1 [ "$id1" ] || exit 1 fi if [ "$mergebase" ]; then [ "$id1" != " " ] || id1="HEAD" [ "$id2" != " " ] || id2="origin" id1="$(cg-object-id -c "$id1")" || exit 1 id2="$(cg-object-id -c "$id2")" || exit 1 conservative_merge_base "$id1" "$id2" || exit 1 [ "$_cg_base_conservative" ] && warn -b "multiple merge bases, picking the most conservative one" id1="$_cg_baselist" fi filter="$(mktemp -t gitdiff.XXXXXX)" [ "$_git_relpath" -a ! "$ARGS" ] && echo "$_git_relpath" >>"$filter" for file in "${ARGS[@]}"; do echo "${_git_relpath}$file" >>"$filter" done if [ "$id2" = " " ]; then if [ "$id1" != " " ]; then tree="$(cg-object-id -t "$id1")" || exit 1 else tree="$(cg-object-id -t)" || exit 1 fi # Ensure to only diff modified files git-update-index --refresh >/dev/null # FIXME: Update ret based on what did we match. And take "$@" # to account after all. #ret= # xargs on FreeBSD is hardcoded to --no-run-if-empty :/ if [ -s "$filter" ]; then cat "$filter" | path_xargs git-diff-index -m -r -p "$tree" | colorize | pager else git-diff-index -m -r -p "$tree" | colorize | pager fi rm "$filter" #[ "$ret" ] && die "no files matched" #exit $ret exit 0 fi id1="$(cg-object-id -t "$id1")" || exit 1 id2="$(cg-object-id -t "$id2")" || exit 1 [ "$id1" = "$id2" ] && exit 0 # xargs on FreeBSD is hardcoded to --no-run-if-empty :/ if [ -s "$filter" ]; then cat "$filter" | path_xargs git-diff-tree -r -p "$id1" "$id2" | colorize | pager else git-diff-tree -r -p "$id1" "$id2" | colorize | pager fi rm "$filter" exit 0