d706a6dee32839007f8fcfb32794b51a1e59765a
   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# mktemp is not available on all platforms (missing from msysgit)
  49# Use a hard-coded tmp dir if it is not available
  50tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || {
  51        tmp=/tmp/git-diffall-tmp.$$
  52        mkdir "$tmp" || exit 1
  53}
  54
  55trap 'rm -rf "$tmp" 2>/dev/null' EXIT
  56
  57left=
  58right=
  59paths=
  60dashdash_seen=
  61compare_staged=
  62merge_base=
  63left_dir=
  64right_dir=
  65diff_tool=
  66copy_back=
  67
  68while test $# != 0
  69do
  70        case "$1" in
  71        -h|--h|--he|--hel|--help)
  72                usage
  73                ;;
  74        --cached)
  75                compare_staged=1
  76                ;;
  77        --copy-back)
  78                copy_back=1
  79                ;;
  80        -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
  81                if test $# = 1
  82                then
  83                        echo You must specify the tool for use with --extcmd
  84                        usage
  85                else
  86                        diff_tool=$2
  87                        shift
  88                fi
  89                ;;
  90        --)
  91                dashdash_seen=1
  92                ;;
  93        -*)
  94                echo Invalid option: "$1"
  95                usage
  96                ;;
  97        *)
  98                # could be commit, commit range or path limiter
  99                case "$1" in
 100                *...*)
 101                        left=${1%...*}
 102                        right=${1#*...}
 103                        merge_base=1
 104                        ;;
 105                *..*)
 106                        left=${1%..*}
 107                        right=${1#*..}
 108                        ;;
 109                *)
 110                        if test -n "$dashdash_seen"
 111                        then
 112                                paths="$paths$1 "
 113                        elif test -z "$left"
 114                        then
 115                                left=$1
 116                        elif test -z "$right"
 117                        then
 118                                right=$1
 119                        else
 120                                paths="$paths$1 "
 121                        fi
 122                        ;;
 123                esac
 124                ;;
 125        esac
 126        shift
 127done
 128
 129# Determine the set of files which changed
 130if test -n "$left" && test -n "$right"
 131then
 132        left_dir="cmt-$(git rev-parse --short $left)"
 133        right_dir="cmt-$(git rev-parse --short $right)"
 134
 135        if test -n "$compare_staged"
 136        then
 137                usage
 138        elif test -n "$merge_base"
 139        then
 140                git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
 141        else
 142                git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
 143        fi
 144elif test -n "$left"
 145then
 146        left_dir="cmt-$(git rev-parse --short $left)"
 147
 148        if test -n "$compare_staged"
 149        then
 150                right_dir="staged"
 151                git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
 152        else
 153                right_dir="working_tree"
 154                git diff --name-only "$left" -- $paths >"$tmp/filelist"
 155        fi
 156else
 157        left_dir="HEAD"
 158
 159        if test -n "$compare_staged"
 160        then
 161                right_dir="staged"
 162                git diff --name-only --cached -- $paths >"$tmp/filelist"
 163        else
 164                right_dir="working_tree"
 165                git diff --name-only -- $paths >"$tmp/filelist"
 166        fi
 167fi
 168
 169# Exit immediately if there are no diffs
 170if test ! -s "$tmp/filelist"
 171then
 172        exit 0
 173fi
 174
 175if test -n "$copy_back" && test "$right_dir" != "working_tree"
 176then
 177        echo "--copy-back is only valid when diff includes the working tree."
 178        exit 1
 179fi
 180
 181# Create the named tmp directories that will hold the files to be compared
 182mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
 183
 184# Populate the tmp/right_dir directory with the files to be compared
 185if test -n "$right"
 186then
 187        while read name
 188        do
 189                ls_list=$(git ls-tree $right "$name")
 190                if test -n "$ls_list"
 191                then
 192                        mkdir -p "$tmp/$right_dir/$(dirname "$name")"
 193                        git show "$right":"$name" >"$tmp/$right_dir/$name" || true
 194                fi
 195        done < "$tmp/filelist"
 196elif test -n "$compare_staged"
 197then
 198        while read name
 199        do
 200                ls_list=$(git ls-files -- "$name")
 201                if test -n "$ls_list"
 202                then
 203                        mkdir -p "$tmp/$right_dir/$(dirname "$name")"
 204                        git show :"$name" >"$tmp/$right_dir/$name"
 205                fi
 206        done < "$tmp/filelist"
 207else
 208        # Mac users have gnutar rather than tar
 209        (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
 210                gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
 211        }
 212fi
 213
 214# Populate the tmp/left_dir directory with the files to be compared
 215while read name
 216do
 217        if test -n "$left"
 218        then
 219                ls_list=$(git ls-tree $left "$name")
 220                if test -n "$ls_list"
 221                then
 222                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 223                        git show "$left":"$name" >"$tmp/$left_dir/$name" || true
 224                fi
 225        else
 226                if test -n "$compare_staged"
 227                then
 228                        ls_list=$(git ls-tree HEAD "$name")
 229                        if test -n "$ls_list"
 230                        then
 231                                mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 232                                git show HEAD:"$name" >"$tmp/$left_dir/$name"
 233                        fi
 234                else
 235                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 236                        git show :"$name" >"$tmp/$left_dir/$name"
 237                fi
 238        fi
 239done < "$tmp/filelist"
 240
 241cd "$tmp"
 242LOCAL="$left_dir"
 243REMOTE="$right_dir"
 244
 245if test -n "$diff_tool"
 246then
 247        export BASE
 248        eval $diff_tool '"$LOCAL"' '"$REMOTE"'
 249else
 250        run_merge_tool "$merge_tool" false
 251fi
 252
 253# Copy files back to the working dir, if requested
 254if test -n "$copy_back" && test "$right_dir" = "working_tree"
 255then
 256        cd "$start_dir"
 257        git_top_dir=$(git rev-parse --show-toplevel)
 258        find "$tmp/$right_dir" -type f |
 259        while read file
 260        do
 261                cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
 262        done
 263fi