f981ac1bd373ac12f0f66f4ec41b30a5e85f689f
   1#!/bin/sh
   2# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
   3#
   4# Perform a directory diff between commits in the repository using
   5# the external diff or merge tool specified in the user's config.
   6
   7USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
   8
   9    --cached     Compare to the index rather than the working tree.
  10
  11    --copy-back  Copy files back to the working tree when the diff
  12                 tool exits (in case they were modified by the
  13                 user).  This option is only valid if the diff
  14                 compared with the working tree.
  15
  16    -x=<command>
  17    --extcmd=<command>  Specify a custom command for viewing diffs.
  18                 git-diffall ignores the configured defaults and
  19                 runs $command $LOCAL $REMOTE when this option is
  20                 specified. Additionally, $BASE is set in the
  21                 environment.
  22'
  23
  24SUBDIRECTORY_OK=1
  25. "$(git --exec-path)/git-sh-setup"
  26
  27TOOL_MODE=diff
  28. "$(git --exec-path)/git-mergetool--lib"
  29
  30merge_tool="$(get_merge_tool)"
  31if test -z "$merge_tool"
  32then
  33        echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
  34        usage
  35fi
  36
  37start_dir=$(pwd)
  38
  39# All the file paths returned by the diff command are relative to the root
  40# of the working copy. So if the script is called from a subdirectory, it
  41# must switch to the root of working copy before trying to use those paths.
  42cdup=$(git rev-parse --show-cdup) &&
  43cd "$cdup" || {
  44        echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
  45        exit 1
  46}
  47
  48# set up temp dir
  49tmp=$(perl -e 'use File::Temp qw(tempdir);
  50        $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1);
  51        print $t') || exit 1
  52trap 'rm -rf "$tmp" 2>/dev/null' EXIT
  53
  54left=
  55right=
  56paths=
  57dashdash_seen=
  58compare_staged=
  59merge_base=
  60left_dir=
  61right_dir=
  62diff_tool=
  63copy_back=
  64
  65while test $# != 0
  66do
  67        case "$1" in
  68        -h|--h|--he|--hel|--help)
  69                usage
  70                ;;
  71        --cached)
  72                compare_staged=1
  73                ;;
  74        --copy-back)
  75                copy_back=1
  76                ;;
  77        -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
  78                if test $# = 1
  79                then
  80                        echo You must specify the tool for use with --extcmd
  81                        usage
  82                else
  83                        diff_tool=$2
  84                        shift
  85                fi
  86                ;;
  87        --)
  88                dashdash_seen=1
  89                ;;
  90        -*)
  91                echo Invalid option: "$1"
  92                usage
  93                ;;
  94        *)
  95                # could be commit, commit range or path limiter
  96                case "$1" in
  97                *...*)
  98                        left=${1%...*}
  99                        right=${1#*...}
 100                        merge_base=1
 101                        ;;
 102                *..*)
 103                        left=${1%..*}
 104                        right=${1#*..}
 105                        ;;
 106                *)
 107                        if test -n "$dashdash_seen"
 108                        then
 109                                paths="$paths$1 "
 110                        elif test -z "$left"
 111                        then
 112                                left=$1
 113                        elif test -z "$right"
 114                        then
 115                                right=$1
 116                        else
 117                                paths="$paths$1 "
 118                        fi
 119                        ;;
 120                esac
 121                ;;
 122        esac
 123        shift
 124done
 125
 126# Determine the set of files which changed
 127if test -n "$left" && test -n "$right"
 128then
 129        left_dir="cmt-$(git rev-parse --short $left)"
 130        right_dir="cmt-$(git rev-parse --short $right)"
 131
 132        if test -n "$compare_staged"
 133        then
 134                usage
 135        elif test -n "$merge_base"
 136        then
 137                git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
 138        else
 139                git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
 140        fi
 141elif test -n "$left"
 142then
 143        left_dir="cmt-$(git rev-parse --short $left)"
 144
 145        if test -n "$compare_staged"
 146        then
 147                right_dir="staged"
 148                git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
 149        else
 150                right_dir="working_tree"
 151                git diff --name-only "$left" -- $paths >"$tmp/filelist"
 152        fi
 153else
 154        left_dir="HEAD"
 155
 156        if test -n "$compare_staged"
 157        then
 158                right_dir="staged"
 159                git diff --name-only --cached -- $paths >"$tmp/filelist"
 160        else
 161                right_dir="working_tree"
 162                git diff --name-only -- $paths >"$tmp/filelist"
 163        fi
 164fi
 165
 166# Exit immediately if there are no diffs
 167if test ! -s "$tmp/filelist"
 168then
 169        exit 0
 170fi
 171
 172if test -n "$copy_back" && test "$right_dir" != "working_tree"
 173then
 174        echo "--copy-back is only valid when diff includes the working tree."
 175        exit 1
 176fi
 177
 178# Create the named tmp directories that will hold the files to be compared
 179mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
 180
 181# Populate the tmp/right_dir directory with the files to be compared
 182if test -n "$right"
 183then
 184        while read name
 185        do
 186                ls_list=$(git ls-tree $right "$name")
 187                if test -n "$ls_list"
 188                then
 189                        mkdir -p "$tmp/$right_dir/$(dirname "$name")"
 190                        git show "$right":"$name" >"$tmp/$right_dir/$name" || true
 191                fi
 192        done < "$tmp/filelist"
 193elif test -n "$compare_staged"
 194then
 195        while read name
 196        do
 197                ls_list=$(git ls-files -- "$name")
 198                if test -n "$ls_list"
 199                then
 200                        mkdir -p "$tmp/$right_dir/$(dirname "$name")"
 201                        git show :"$name" >"$tmp/$right_dir/$name"
 202                fi
 203        done < "$tmp/filelist"
 204else
 205        while read name
 206        do
 207                if test -e "$name"
 208                then
 209                        mkdir -p "$tmp/$right_dir/$(dirname "$name")"
 210                        cp "$name" "$tmp/$right_dir/$name"
 211                fi
 212        done < "$tmp/filelist"
 213fi
 214
 215# Populate the tmp/left_dir directory with the files to be compared
 216while read name
 217do
 218        if test -n "$left"
 219        then
 220                ls_list=$(git ls-tree $left "$name")
 221                if test -n "$ls_list"
 222                then
 223                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 224                        git show "$left":"$name" >"$tmp/$left_dir/$name" || true
 225                fi
 226        else
 227                if test -n "$compare_staged"
 228                then
 229                        ls_list=$(git ls-tree HEAD "$name")
 230                        if test -n "$ls_list"
 231                        then
 232                                mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 233                                git show HEAD:"$name" >"$tmp/$left_dir/$name"
 234                        fi
 235                else
 236                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 237                        git show :"$name" >"$tmp/$left_dir/$name"
 238                fi
 239        fi
 240done < "$tmp/filelist"
 241
 242cd "$tmp"
 243LOCAL="$left_dir"
 244REMOTE="$right_dir"
 245
 246if test -n "$diff_tool"
 247then
 248        export BASE
 249        eval $diff_tool '"$LOCAL"' '"$REMOTE"'
 250else
 251        run_merge_tool "$merge_tool" false
 252fi
 253
 254# Copy files back to the working dir, if requested
 255if test -n "$copy_back" && test "$right_dir" = "working_tree"
 256then
 257        cd "$start_dir"
 258        git_top_dir=$(git rev-parse --show-toplevel)
 259        find "$tmp/$right_dir" -type f |
 260        while read file
 261        do
 262                cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
 263        done
 264fi