| #!/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 |