Skip to content

Commit

Permalink
contrib: add 'git difftool' for launching common merge tools
Browse files Browse the repository at this point in the history
'git difftool' is a git command that allows you to compare and edit files
between revisions using common merge tools.  'git difftool' does what
'git mergetool' does but its use is for non-merge situations such as
when preparing commits or comparing changes against the index.
It uses the same configuration variables as 'git mergetool' and
provides the same command-line interface as 'git diff'.

Signed-off-by: David Aguilar <davvid@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
David Aguilar authored and Junio C Hamano committed Jan 18, 2009
1 parent 3d27986 commit 5c38ea3
Show file tree
Hide file tree
Showing 3 changed files with 418 additions and 0 deletions.
74 changes: 74 additions & 0 deletions contrib/difftool/git-difftool
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env perl
# Copyright (c) 2009 David Aguilar
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool-helper script. This script exports
# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and
# GIT_NO_PROMPT and GIT_MERGE_TOOL for use by git-difftool-helper.
# Any arguments that are unknown to this script are forwarded to 'git diff'.

use strict;
use warnings;
use Cwd qw(abs_path);
use File::Basename qw(dirname);

my $DIR = abs_path(dirname($0));


sub usage
{
print << 'USAGE';
usage: git difftool [--no-prompt] [--tool=tool] ["git diff" options]
USAGE
exit 1;
}

sub setup_environment
{
$ENV{PATH} = "$DIR:$ENV{PATH}";
$ENV{GIT_PAGER} = '';
$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper';
}

sub exe
{
my $exe = shift;
return defined $ENV{COMSPEC} ? "$exe.exe" : $exe;
}

sub generate_command
{
my @command = (exe('git'), 'diff');
my $skip_next = 0;
my $idx = -1;
for my $arg (@ARGV) {
$idx++;
if ($skip_next) {
$skip_next = 0;
next;
}
if ($arg eq '-t' or $arg eq '--tool') {
usage() if $#ARGV <= $idx;
$ENV{GIT_MERGE_TOOL} = $ARGV[$idx + 1];
$skip_next = 1;
next;
}
if ($arg =~ /^--tool=/) {
$ENV{GIT_MERGE_TOOL} = substr($arg, 7);
next;
}
if ($arg eq '--no-prompt') {
$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
next;
}
if ($arg eq '-h' or $arg eq '--help') {
usage();
}
push @command, $arg;
}
return @command
}

setup_environment();
exec(generate_command());
240 changes: 240 additions & 0 deletions contrib/difftool/git-difftool-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
#!/bin/sh
# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# It supports kdiff3, tkdiff, xxdiff, meld, opendiff, emerge, ecmerge,
# vimdiff, gvimdiff, and custom user-configurable tools.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009 David Aguilar

# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
should_prompt () {
! test -n "$GIT_DIFFTOOL_NO_PROMPT"
}

# Should we keep the backup .orig file?
keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
keep_backup () {
test "$keep_backup_mode" = "true"
}

# This function manages the backup .orig file.
# A backup $MERGED.orig file is created if changes are detected.
cleanup_temp_files () {
if test -n "$MERGED"; then
if keep_backup && test "$MERGED" -nt "$BACKUP"; then
test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
else
rm -f -- "$BACKUP"
fi
fi
}

# This is called when users Ctrl-C out of git-difftool-helper
sigint_handler () {
echo
cleanup_temp_files
exit 1
}

# This function prepares temporary files and launches the appropriate
# merge tool.
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
# Remote is the contents of b/filename
# Custom merge tool commands might use $BASE so we provide it
MERGED="$1"
LOCAL="$2"
REMOTE="$3"
BASE="$1"
ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
BACKUP="$MERGED.BACKUP.$ext"

# Create and ensure that we clean up $BACKUP
test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
trap sigint_handler SIGINT

# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt; then
printf "\nViewing: '$MERGED'\n"
printf "Hit return to launch '%s': " "$merge_tool"
read ans
fi

# Run the appropriate merge tool command
case "$merge_tool" in
kdiff3)
basename=$(basename "$MERGED")
"$merge_tool_path" --auto \
--L1 "$basename (A)" \
--L2 "$basename (B)" \
-o "$MERGED" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1
;;

tkdiff)
"$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
;;

meld|vimdiff)
"$merge_tool_path" "$LOCAL" "$REMOTE"
;;

gvimdiff)
"$merge_tool_path" -f "$LOCAL" "$REMOTE"
;;

xxdiff)
"$merge_tool_path" \
-X \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$LOCAL" "$REMOTE"
;;

opendiff)
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-merge "$MERGED" | cat
;;

ecmerge)
"$merge_tool_path" "$LOCAL" "$REMOTE" \
--default --mode=merge2 --to="$MERGED"
;;

emerge)
"$merge_tool_path" -f emerge-files-command \
"$LOCAL" "$REMOTE" "$(basename "$MERGED")"
;;

*)
if test -n "$merge_tool_cmd"; then
( eval $merge_tool_cmd )
fi
;;
esac

cleanup_temp_files
}

# Verifies that mergetool.<tool>.cmd exists
valid_custom_tool() {
merge_tool_cmd="$(git config mergetool.$1.cmd)"
test -n "$merge_tool_cmd"
}

# Verifies that the chosen merge tool is properly setup.
# Built-in merge tools are always valid.
valid_tool() {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
;; # happy
*)
if ! valid_custom_tool "$1"
then
return 1
fi
;;
esac
}

# Sets up the merge_tool_path variable.
# This handles the mergetool.<tool>.path configuration.
init_merge_tool_path() {
merge_tool_path=$(git config mergetool."$1".path)
if test -z "$merge_tool_path"; then
case "$1" in
emerge)
merge_tool_path=emacs
;;
*)
merge_tool_path="$1"
;;
esac
fi
}

# Allow the GIT_MERGE_TOOL variable to provide a default value
test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"

# If not merge tool was specified then use the merge.tool
# configuration variable. If that's invalid then reset merge_tool.
if test -z "$merge_tool"; then
merge_tool=$(git config merge.tool)
if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
unset merge_tool
fi
fi

# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"; then

# We have a $DISPLAY so try some common UNIX merge tools
if test -n "$DISPLAY"; then
merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
# If gnome then prefer meld
if test -n "$GNOME_DESKTOP_SESSION_ID"; then
merge_tool_candidates="meld $merge_tool_candidates"
fi
# If KDE then prefer kdiff3
if test "$KDE_FULL_SESSION" = "true"; then
merge_tool_candidates="kdiff3 $merge_tool_candidates"
fi
fi

# $EDITOR is emacs so add emerge as a candidate
if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates emerge"
fi

# $EDITOR is vim so add vimdiff as a candidate
if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates vimdiff"
fi

merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
echo "merge tool candidates: $merge_tool_candidates"

# Loop over each candidate and stop when a valid merge tool is found.
for i in $merge_tool_candidates
do
init_merge_tool_path $i
if type "$merge_tool_path" > /dev/null 2>&1; then
merge_tool=$i
break
fi
done

if test -z "$merge_tool" ; then
echo "No known merge resolution program available."
exit 1
fi

else
# A merge tool has been set, so verify that it's valid.
if ! valid_tool "$merge_tool"; then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi

init_merge_tool_path "$merge_tool"

if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
exit 1
fi
fi


# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
launch_merge_tool "$1" "$2" "$5"
shift 7
done
Loading

0 comments on commit 5c38ea3

Please sign in to comment.