contrib / diffall / git-diffallon commit contrib/diffall: create tmp dirs without mktemp (c5770f7)
   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        # Mac users have gnutar rather than tar
 206        (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
 207                gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
 208        }
 209fi
 210
 211# Populate the tmp/left_dir directory with the files to be compared
 212while read name
 213do
 214        if test -n "$left"
 215        then
 216                ls_list=$(git ls-tree $left "$name")
 217                if test -n "$ls_list"
 218                then
 219                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 220                        git show "$left":"$name" >"$tmp/$left_dir/$name" || true
 221                fi
 222        else
 223                if test -n "$compare_staged"
 224                then
 225                        ls_list=$(git ls-tree HEAD "$name")
 226                        if test -n "$ls_list"
 227                        then
 228                                mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 229                                git show HEAD:"$name" >"$tmp/$left_dir/$name"
 230                        fi
 231                else
 232                        mkdir -p "$tmp/$left_dir/$(dirname "$name")"
 233                        git show :"$name" >"$tmp/$left_dir/$name"
 234                fi
 235        fi
 236done < "$tmp/filelist"
 237
 238cd "$tmp"
 239LOCAL="$left_dir"
 240REMOTE="$right_dir"
 241
 242if test -n "$diff_tool"
 243then
 244        export BASE
 245        eval $diff_tool '"$LOCAL"' '"$REMOTE"'
 246else
 247        run_merge_tool "$merge_tool" false
 248fi
 249
 250# Copy files back to the working dir, if requested
 251if test -n "$copy_back" && test "$right_dir" = "working_tree"
 252then
 253        cd "$start_dir"
 254        git_top_dir=$(git rev-parse --show-toplevel)
 255        find "$tmp/$right_dir" -type f |
 256        while read file
 257        do
 258                cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
 259        done
 260fi