Skip to contentSkip to conversation

contrib/diffall: Add script to perform directory diff using external diff tool

PATCH New Tim Henigan 1 participant 1 message Jun 15, 2016

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 contrib/diffall: Add script to perform directory diff using external diff tool

The existing 'git difftool' command allows the user to view git diffs using an external diff tool. However, if multiple files contain differences, a separate instance of the diff tool is launched for each one.

This script launches a single instance of the external diff tool and performs a directory diff between the specified revisions. The before/after files are copied to a tmp directory to do this.

The 'diff.tool' configuration option must be set for this script to work.

The user interface matches the standard 'git diff' command.

---

This script is based on an example provided by Thomas Rast on the Git list [1].

I tested with:
- msysgit and kdiff3 (the platform where I first needed this script)
- cygwin git and kdiff3
- git and meld

[1] http://thread.gmane.org/gmane.comp.version-control.git/124807

 contrib/diffall/git-diffall |  159 +++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 159 insertions(+), 0 deletions(-)
 create mode 100755 contrib/diffall/git-diffall
contrib/diffall/git-diffall new file +159 -0
new file mode 100755
index 0000000..c24fba9
@@ -0,0 +1,159 @@
+#!/bin/sh -e
+# Copyright 2010, Tim Henigan <[email protected]>
+#
+# Perform a directory diff between commits in the repository using
+# the external diff tool specified in the 'diff.tool' configuration
+# option.
+
+USAGE='<options> <commit>{0,2} -- <path>*
+
+--cached  Compare to the index rather than the working tree
+commit    SHA1 of a commit
+path      Limit the diff to the specified paths
+'
+
+. git-sh-setup
+
+if [ -z $(git config --get diff.tool) ]; then
+    echo "Error: The 'diff.tool' configuration option must be set."
+    usage
+fi
+
+start_dir=$(pwd)
+cd_to_toplevel      # needed to access tar utility
+
+# mktemp is not available on all platforms (missing from msysgit)
+# Use a hard-coded tmp dir if it is not available
+if [ -z $(which mktemp) ]; then
+    tmp=/tmp/git-diffall-tmp
+else
+    tmp="$(mktemp -d)"
+fi
+mkdir -p "$tmp" "$tmp"/a "$tmp"/b
+
+left=
+right=
+paths=
+path_sep=
+compare_staged=
+common_anscestor=
+
+while test $# != 0; do
+	case "$1" in
+    -h|--h|--he|--hel|--help)
+        usage
+        ;;
+    --cached)
+        compare_staged=1
+        ;;
+    --)
+        path_sep=1
+        ;;
+    -*)
+        echo Invalid option: "$1"
+        usage
+        ;;
+    *)
+        # could be commit, commit range or path limiter
+        case "$1" in
+        *...*)
+            left=${1%...*}
+            right=${1#*...}
+            common_anscestor=1
+            ;;
+        *..*)
+            left=${1%..*}
+            right=${1#*..}
+            ;;
+        *)
+            if [ -n "$path_sep" ]; then
+                if [ -z "$paths" ]; then
+                    paths=$1
+                else
+                    paths="$paths $1"
+                fi
+            elif [ -z "$left" ]; then
+                left=$1
+            elif [ -z "$right" ]; then
+                right=$1
+            else
+                if [ -z "$paths" ]; then
+                    paths=$1
+                else
+                    paths="$paths $1"
+                fi
+            fi
+            ;;
+        esac
+        ;;
+    esac
+    shift
+done
+
+# Determine the set of files which changed
+if [ -n "$left" ] && [ -n "$right" ]; then
+    if [ -n "$compare_staged" ]; then
+        usage
+    elif [ -n "$common_anscestor" ]; then
+        git diff --name-only "$left"..."$right" -- "$paths" > "$tmp"/filelist
+    else
+        git diff --name-only "$left" "$right" -- "$paths" > "$tmp"/filelist
+    fi
+elif [ -n "$left" ]; then
+    if [ -n "$compare_staged" ]; then
+        git diff --name-only --cached "$left" -- "$paths" > "$tmp"/filelist
+    else
+        git diff --name-only "$left" -- "$paths" > "$tmp"/filelist
+    fi
+else
+    if [ -n "$compare_staged" ]; then
+        git diff --name-only --cached -- "$paths" > "$tmp"/filelist
+    else
+        git diff --name-only -- "$paths" > "$tmp"/filelist
+    fi
+fi
+
+# Exit immediately if there are no diffs
+if [ ! -s "$tmp"/filelist ]; then
+    exit 0
+fi
+
+# Populate the tmp/b directory with the files to be compared
+if [ -n "$right" ]; then
+	while read name; do
+		mkdir -p "$tmp"/b/"$(dirname "$name")"
+		git show "$right":"$name" > "$tmp"/b/"$name"
+	done < "$tmp"/filelist
+elif [ -n "$compare_staged" ]; then
+	while read name; do
+		mkdir -p "$tmp"/b/"$(dirname "$name")"
+		git show :"$name" > "$tmp"/b/"$name"
+	done < "$tmp"/filelist
+else
+    tar -c -T "$tmp"/filelist | (cd "$tmp"/b && tar -x)
+fi
+
+# Populate the tmp/a directory with the files to be compared
+while read name; do
+	mkdir -p "$tmp"/a/"$(dirname "$name")"
+    if [ -n "$left" ]; then
+	    git show "$left":"$name" > "$tmp"/a/"$name"
+    else
+        if [ -n "$compare_staged" ]; then
+            git show HEAD:"$name" > "$tmp"/a/"$name"
+        else
+            git show :"$name" > "$tmp"/a/"$name"
+        fi
+    fi
+done < "$tmp"/filelist
+
+cd "$tmp"
+$(git config --get diff.tool) a b
+
+# On exit, remove the tmp directory
+cleanup () {
+    cd "$start_dir"
+	rm -rf "$tmp"
+}
+
+trap cleanup EXIT
Signature · 1 line
1.7.0.3.291.g5e4f6.dirty