|  | #!/bin/sh | 
|  | # Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com> | 
|  | # | 
|  | # 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=<command>] <commit>{0,2} [-- <path>*] | 
|  |  | 
|  | --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=<command> | 
|  | --extcmd=<command>  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 | 
|  | } | 
|  |  | 
|  | # set up temp dir | 
|  | tmp=$(perl -e 'use File::Temp qw(tempdir); | 
|  | $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1); | 
|  | print $t') || exit 1 | 
|  | trap 'rm -rf "$tmp"' 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 | 
|  | while read name | 
|  | do | 
|  | if test -n "$right" | 
|  | then | 
|  | 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 | 
|  | elif test -n "$compare_staged" | 
|  | then | 
|  | 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 | 
|  | else | 
|  | if test -e "$name" | 
|  | then | 
|  | mkdir -p "$tmp/$right_dir/$(dirname "$name")" | 
|  | cp "$name" "$tmp/$right_dir/$name" | 
|  | fi | 
|  | fi | 
|  | done < "$tmp/filelist" | 
|  |  | 
|  | # 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" | 
|  |  | 
|  | LOCAL="$tmp/$left_dir" | 
|  | REMOTE="$tmp/$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 |