#!/bin/sh # Copyright 2010 - 2012, Tim Henigan # # Perform a directory diff between commits in the repository using # the external diff or merge tool specified in the user's config. USAGE='[--cached] [--copy-back] [-x|--extcmd=] {0,2} [-- *] --cached Compare to the index rather than the working tree. --copy-back Copy files back to the working tree when the diff tool exits (in case they were modified by the user). This option is only valid if the diff compared with the working tree. -x= --extcmd= Specify a custom command for viewing diffs. git-diffall ignores the configured defaults and runs $command $LOCAL $REMOTE when this option is specified. Additionally, $BASE is set in the environment. ' SUBDIRECTORY_OK=1 . "$(git --exec-path)/git-sh-setup" TOOL_MODE=diff . "$(git --exec-path)/git-mergetool--lib" merge_tool="$(get_merge_tool)" if test -z "$merge_tool" then echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set." usage fi start_dir=$(pwd) # All the file paths returned by the diff command are relative to the root # of the working copy. So if the script is called from a subdirectory, it # must switch to the root of working copy before trying to use those paths. cdup=$(git rev-parse --show-cdup) && cd "$cdup" || { echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" exit 1 } # mktemp is not available on all platforms (missing from msysgit) # Use a hard-coded tmp dir if it is not available tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || { tmp=/tmp/git-diffall-tmp.$$ mkdir "$tmp" || exit 1 } trap 'rm -rf "$tmp" 2>/dev/null' EXIT left= right= paths= dashdash_seen= compare_staged= merge_base= left_dir= right_dir= diff_tool= copy_back= while test $# != 0 do case "$1" in -h|--h|--he|--hel|--help) usage ;; --cached) compare_staged=1 ;; --copy-back) copy_back=1 ;; -x|--e|--ex|--ext|--extc|--extcm|--extcmd) if test $# = 1 then echo You must specify the tool for use with --extcmd usage else diff_tool=$2 shift fi ;; --) dashdash_seen=1 ;; -*) echo Invalid option: "$1" usage ;; *) # could be commit, commit range or path limiter case "$1" in *...*) left=${1%...*} right=${1#*...} merge_base=1 ;; *..*) left=${1%..*} right=${1#*..} ;; *) if test -n "$dashdash_seen" then paths="$paths$1 " elif test -z "$left" then left=$1 elif test -z "$right" then right=$1 else paths="$paths$1 " fi ;; esac ;; esac shift done # Determine the set of files which changed if test -n "$left" && test -n "$right" then left_dir="cmt-$(git rev-parse --short $left)" right_dir="cmt-$(git rev-parse --short $right)" if test -n "$compare_staged" then usage elif test -n "$merge_base" then git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist" else git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist" fi elif test -n "$left" then left_dir="cmt-$(git rev-parse --short $left)" if test -n "$compare_staged" then right_dir="staged" git diff --name-only --cached "$left" -- $paths >"$tmp/filelist" else right_dir="working_tree" git diff --name-only "$left" -- $paths >"$tmp/filelist" fi else left_dir="HEAD" if test -n "$compare_staged" then right_dir="staged" git diff --name-only --cached -- $paths >"$tmp/filelist" else right_dir="working_tree" git diff --name-only -- $paths >"$tmp/filelist" fi fi # Exit immediately if there are no diffs if test ! -s "$tmp/filelist" then exit 0 fi if test -n "$copy_back" && test "$right_dir" != "working_tree" then echo "--copy-back is only valid when diff includes the working tree." exit 1 fi # Create the named tmp directories that will hold the files to be compared mkdir -p "$tmp/$left_dir" "$tmp/$right_dir" # Populate the tmp/right_dir directory with the files to be compared if test -n "$right" then while read name do ls_list=$(git ls-tree $right "$name") if test -n "$ls_list" then mkdir -p "$tmp/$right_dir/$(dirname "$name")" git show "$right":"$name" >"$tmp/$right_dir/$name" || true fi done < "$tmp/filelist" elif test -n "$compare_staged" then while read name do ls_list=$(git ls-files -- "$name") if test -n "$ls_list" then mkdir -p "$tmp/$right_dir/$(dirname "$name")" git show :"$name" >"$tmp/$right_dir/$name" fi done < "$tmp/filelist" else # Mac users have gnutar rather than tar (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || { gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x) } fi # Populate the tmp/left_dir directory with the files to be compared while read name do if test -n "$left" then ls_list=$(git ls-tree $left "$name") if test -n "$ls_list" then mkdir -p "$tmp/$left_dir/$(dirname "$name")" git show "$left":"$name" >"$tmp/$left_dir/$name" || true fi else if test -n "$compare_staged" then ls_list=$(git ls-tree HEAD "$name") if test -n "$ls_list" then mkdir -p "$tmp/$left_dir/$(dirname "$name")" git show HEAD:"$name" >"$tmp/$left_dir/$name" fi else mkdir -p "$tmp/$left_dir/$(dirname "$name")" git show :"$name" >"$tmp/$left_dir/$name" fi fi done < "$tmp/filelist" cd "$tmp" LOCAL="$left_dir" REMOTE="$right_dir" if test -n "$diff_tool" then export BASE eval $diff_tool '"$LOCAL"' '"$REMOTE"' else run_merge_tool "$merge_tool" false fi # Copy files back to the working dir, if requested if test -n "$copy_back" && test "$right_dir" = "working_tree" then cd "$start_dir" git_top_dir=$(git rev-parse --show-toplevel) find "$tmp/$right_dir" -type f | while read file do cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}" done fi