git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [GIT PULL] Please pull mergetool.git
@ 2007-03-14  1:15 Theodore Ts'o
  2007-03-14  5:59 ` Shawn O. Pearce
  2007-03-14  9:45 ` Junio C Hamano
  0 siblings, 2 replies; 6+ messages in thread
From: Theodore Ts'o @ 2007-03-14  1:15 UTC (permalink / raw
  To: Junio C Hamano; +Cc: git

Hi Junio,

	Per our previous email conversation, I've cleaned done my final
cleanups before being ready to submit mergetool for inclusion, and
rebased it to lastest changeset the git master branch.

	In order to make this easier for me to send future features and
cleanups, I've set up a repository on repo.or.cz.  So please pull from:

	git://repo.or.cz/git/mergetool.git

The final patch that is the repository is attached.

						- Ted

From: Theodore Ts'o <tytso@mit.edu>
Date: Tue, 6 Mar 2007 00:05:16 -0500
Subject: [PATCH] Add git-mergetool to run an appropriate merge conflict resolution program

The git-mergetool program can be used to automatically run an appropriate
merge resolution program to resolve merge conflicts.  It will automatically
run one of kdiff3, tkdiff, meld, xxdiff, or emacs emerge programs.

Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
---
 .gitignore                      |    1 +
 Documentation/config.txt        |    5 +
 Documentation/git-mergetool.txt |   46 +++++
 Makefile                        |    2 +-
 git-mergetool.sh                |  352 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 405 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/git-mergetool.txt
 create mode 100755 git-mergetool.sh

diff --git a/.gitignore b/.gitignore
index 0eaba0a..27797d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ git-merge-ours
 git-merge-recursive
 git-merge-resolve
 git-merge-stupid
+git-mergetool
 git-mktag
 git-mktree
 git-name-rev
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5408dd6..aaae9ac 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -453,6 +453,11 @@ merge.summary::
 	Whether to include summaries of merged commits in newly created
 	merge commit messages. False by default.
 
+merge.tool::
+	Controls which merge resolution program is used by
+	gitlink:git-mergetool[l].  Valid values are: "kdiff3", "tkdiff",
+	"meld", "xxdiff", "emerge"
+
 merge.verbosity::
 	Controls the amount of output shown by the recursive merge
 	strategy.  Level 0 outputs nothing except a final error
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
new file mode 100644
index 0000000..ae69a0e
--- /dev/null
+++ b/Documentation/git-mergetool.txt
@@ -0,0 +1,46 @@
+git-mergetool(1)
+================
+
+NAME
+----
+git-mergetool - Run merge conflict resolution tools to resolve merge conflicts
+
+SYNOPSIS
+--------
+'git-mergetool' [--tool=<tool>] [<file>]...
+
+DESCRIPTION
+-----------
+
+Use 'git mergetool' to run one of several merge utilities to resolve
+merge conflicts.  It is typically run after gitlink:git-merge[1].
+
+If one or more <file> parameters are given, the merge tool program will
+be run to resolve differences on each file.  If no <file> names are
+specified, 'git mergetool' will run the merge tool program on every file
+with merge conflicts.
+
+OPTIONS
+-------
+-t or --tool=<tool>::
+	Use the merge resolution program specified by <tool>.
+	Valid merge tools are:
+	kdiff3, tkdiff, meld, xxdiff, and emerge.
+
+	If a merge resolution program is not specified, 'git mergetool'
+	will use the configuration variable merge.tool.  If the
+	configuration variable merge.tool is not set, 'git mergetool'
+	will pick a suitable default.
+
+Author
+------
+Written by Theodore Y Ts'o <tytso@mit.edu>
+
+Documentation
+--------------
+Documentation by Theodore Y Ts'o.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Makefile b/Makefile
index 1a51f84..45fe366 100644
--- a/Makefile
+++ b/Makefile
@@ -179,7 +179,7 @@ SCRIPT_SH = \
 	git-clean.sh git-clone.sh git-commit.sh \
 	git-fetch.sh git-gc.sh \
 	git-ls-remote.sh \
-	git-merge-one-file.sh git-parse-remote.sh \
+	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
 	git-pull.sh git-rebase.sh \
 	git-repack.sh git-request-pull.sh git-reset.sh \
 	git-sh-setup.sh \
diff --git a/git-mergetool.sh b/git-mergetool.sh
new file mode 100755
index 0000000..52386a5
--- /dev/null
+++ b/git-mergetool.sh
@@ -0,0 +1,352 @@
+#!/bin/sh
+#
+# This program resolves merge conflicts in git
+#
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Junio C Hammano.
+#
+
+USAGE='[--tool=tool] [file to merge] ...'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+
+# Returns true if the mode reflects a symlink
+function is_symlink () {
+    test "$1" = 120000
+}
+
+function local_present () {
+    test -n "$local_mode"
+}
+
+function remote_present () {
+    test -n "$remote_mode"
+}
+
+function base_present () {
+    test -n "$base_mode"
+}
+
+cleanup_temp_files () {
+    if test "$1" = --save-backup ; then
+	mv -- "$BACKUP" "$path.orig"
+	rm -f -- "$LOCAL" "$REMOTE" "$BASE"
+    else
+	rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
+    fi
+}
+
+function describe_file () {
+    mode="$1"
+    branch="$2"
+    file="$3"
+
+    echo -n "    "
+    if test -z "$mode"; then
+	echo -n "'$path' was deleted"
+    elif is_symlink "$mode" ; then
+	echo -n "'$path' is a symlink containing '"
+	cat "$file"
+	echo -n "'"
+    else
+	if base_present; then
+	    echo -n "'$path' was created"
+	else
+	    echo -n "'$path' was modified"
+	fi
+    fi
+    echo " in the $branch branch"
+}
+
+
+resolve_symlink_merge () {
+    while /bin/true; do
+	echo -n "Use (r)emote or (l)ocal, or (a)bort? "
+	read ans
+	case "$ans" in
+	    [lL]*)
+		git-checkout-index -f --stage=2 -- "$path"
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	   [rR]*)
+		git-checkout-index -f --stage=3 -- "$path"
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	    [qQ]*)
+		exit 1
+		;;
+	    esac
+	done
+}
+
+resolve_deleted_merge () {
+    while /bin/true; do
+	echo -n "Use (m)odified or (d)eleted file, or (a)bort? "
+	read ans
+	case "$ans" in
+	    [mM]*)
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	   [dD]*)
+		git-rm -- "$path"
+		cleanup_temp_files
+		return
+		;;
+	    [qQ]*)
+		exit 1
+		;;
+	    esac
+	done
+}
+
+merge_file () {
+    path="$1"
+
+    if test ! -f "$path" ; then
+	echo "$path: file not found"
+	exit 1
+    fi
+
+    f=`git-ls-files -u -- "$path"`
+    if test -z "$f" ; then
+	echo "$path: file does not need merging"
+	exit 1
+    fi
+
+    BACKUP="$path.BACKUP.$$"
+    LOCAL="$path.LOCAL.$$"
+    REMOTE="$path.REMOTE.$$"
+    BASE="$path.BASE.$$"
+
+    mv -- "$path" "$BACKUP"
+    cp -- "$BACKUP" "$path"
+
+    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
+    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
+    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
+
+    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
+    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
+    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null
+
+    if test -z "$local_mode" -o -z "$remote_mode"; then
+	echo "Deleted merge conflict for $path:"
+	describe_file "$local_mode" "local" "$LOCAL"
+	describe_file "$remote_mode" "remote" "$REMOTE"
+	resolve_deleted_merge
+	return
+    fi
+
+    if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
+	echo "Symlink merge conflict for $path:"
+	describe_file "$local_mode" "local" "$LOCAL"
+	describe_file "$remote_mode" "remote" "$REMOTE"
+	resolve_symlink_merge
+	return
+    fi
+
+    echo "Normal merge conflict for $path:"
+    describe_file "$local_mode" "local" "$LOCAL"
+    describe_file "$remote_mode" "remote" "$REMOTE"
+    echo -n "Hit return to start merge resolution tool ($merge_tool): "
+    read ans
+
+    case "$merge_tool" in
+	kdiff3)
+	    if base_present ; then
+		(kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \
+		    -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+	    else
+		(kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \
+		    -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+	    fi
+	    status=$?
+	    if test "$status" -eq 0; then
+		rm "$BACKUP"
+	    fi
+	    ;;
+	tkdiff)
+	    if base_present ; then
+		tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
+	    else
+		tkdiff -o "$path" -- "$LOCAL" "$REMOTE"
+	    fi
+	    status=$?
+	    if test "$status" -eq 0; then
+		mv -- "$BACKUP" "$path.orig"
+	    fi
+	    ;;
+	meld)
+	    touch "$BACKUP"
+	    meld -- "$LOCAL" "$path" "$REMOTE"
+	    if test "$path" -nt "$BACKUP" ; then
+		status=0;
+	    else
+		while true; do
+		    echo "$path seems unchanged."
+		    echo -n "Was the merge successful? [y/n] "
+		    read answer < /dev/tty
+		    case "$answer" in
+			y*|Y*) status=0; break ;;
+			n*|N*) status=1; break ;;
+		    esac
+		done
+	    fi
+	    if test "$status" -eq 0; then
+		mv -- "$BACKUP" "$path.orig"
+	    fi
+	    ;;
+	xxdiff)
+	    touch "$BACKUP"
+	    if base_present ; then
+		xxdiff -X --show-merged-pane \
+		    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+		    -R 'Accel.Search: "Ctrl+F"' \
+		    -R 'Accel.SearchForward: "Ctrl-G"' \
+		    --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
+	    else
+		xxdiff -X --show-merged-pane \
+		    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+		    -R 'Accel.Search: "Ctrl+F"' \
+		    -R 'Accel.SearchForward: "Ctrl-G"' \
+		    --merged-file "$path" -- "$LOCAL" "$REMOTE"
+	    fi
+	    if test "$path" -nt "$BACKUP" ; then
+		status=0;
+	    else
+		while true; do
+		    echo "$path seems unchanged."
+		    echo -n "Was the merge successful? [y/n] "
+		    read answer < /dev/tty
+		    case "$answer" in
+			y*|Y*) status=0; break ;;
+			n*|N*) status=1; break ;;
+		    esac
+		done
+	    fi
+	    if test "$status" -eq 0; then
+		mv -- "$BACKUP" "$path.orig"
+	    fi
+	    ;;
+	emerge)
+	    if base_present ; then
+		emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path"
+	    else
+		emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path"
+	    fi
+	    status=$?
+	    if test "$status" -eq 0; then
+		mv -- "$BACKUP" "$path.orig"
+	    fi
+	    ;;
+    esac
+    if test "$status" -ne 0; then
+	echo "merge of $path failed" 1>&2
+	mv -- "$BACKUP" "$path"
+	exit 1
+    fi
+    git add -- "$path"
+    cleanup_temp_files
+}
+
+while case $# in 0) break ;; esac
+do
+    case "$1" in
+	-t|--tool*)
+	    case "$#,$1" in
+		*,*=*)
+		    merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		    ;;
+		1,*)
+		    usage ;;
+		*)
+		    merge_tool="$2"
+		    shift ;;
+	    esac
+	    ;;
+	--)
+	    break
+	    ;;
+	-*)
+	    usage
+	    ;;
+	*)
+	    break
+	    ;;
+    esac
+    shift
+done
+
+if test -z "$merge_tool"; then
+    merge_tool=`git-config merge.tool`
+    if test $merge_tool = kdiff3 -o $merge_tool = tkdiff -o \
+	$merge_tool = xxdiff -o $merge_tool = meld ; then
+	unset merge_tool
+    fi
+fi
+
+if test -z "$merge_tool" ; then
+    if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool="kdiff3";
+    elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=tkdiff
+    elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=xxdiff
+    elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=meld
+    elif type emacs >/dev/null 2>&1; then
+	merge_tool=emerge
+    else
+	echo "No available merge resolution programs available."
+	exit 1
+    fi
+fi
+
+case "$merge_tool" in
+    kdiff3|tkdiff|meld|xxdiff)
+	if ! type "$merge_tool" > /dev/null 2>&1; then
+	    echo "The merge tool $merge_tool is not available"
+	    exit 1
+	fi
+	;;
+    emerge)
+	if ! type "emacs" > /dev/null 2>&1; then
+	    echo "Emacs is not available"
+	    exit 1
+	fi
+	;;
+    *)
+	echo "Unknown merge tool: $merge_tool"
+	exit 1
+	;;
+esac
+
+if test $# -eq 0 ; then
+	files=`git ls-files -u | sed -e 's/^[^	]*	//' | sort -u`
+	if test -z "$files" ; then
+		echo "No files need merging"
+		exit 0
+	fi
+	echo Merging the files: $files
+	git ls-files -u | sed -e 's/^[^	]*	//' | sort -u | while read i
+	do
+		echo ""
+		merge_file "$i" < /dev/tty > /dev/tty
+	done
+else
+	while test $# -gt 0; do
+		echo ""
+		merge_file "$1"
+		shift
+	done
+fi
+exit 0
-- 
1.5.0.3.272.gb515-dirty

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [GIT PULL] Please pull mergetool.git
  2007-03-14  1:15 [GIT PULL] Please pull mergetool.git Theodore Ts'o
@ 2007-03-14  5:59 ` Shawn O. Pearce
  2007-03-14  6:17   ` Theodore Tso
  2007-03-14  7:03   ` Junio C Hamano
  2007-03-14  9:45 ` Junio C Hamano
  1 sibling, 2 replies; 6+ messages in thread
From: Shawn O. Pearce @ 2007-03-14  5:59 UTC (permalink / raw
  To: Theodore Ts'o; +Cc: Junio C Hamano, git

Theodore Ts'o <tytso@mit.edu> wrote:
> +    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
> +    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
> +    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
> +
> +    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
> +    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
> +    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null

Why not use `git checkout-index --stage=all "$path"` ?
E.g.:

	git checkout-index --stage=all "$path" |
	read base_temp local_temp remote_temp path

I'm not trying to nitpick, I'm just curious about why this particular
feature of checkout-index was not useful here.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GIT PULL] Please pull mergetool.git
  2007-03-14  5:59 ` Shawn O. Pearce
@ 2007-03-14  6:17   ` Theodore Tso
  2007-03-14  7:03   ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Theodore Tso @ 2007-03-14  6:17 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Junio C Hamano, git

On Wed, Mar 14, 2007 at 01:59:23AM -0400, Shawn O. Pearce wrote:
> Theodore Ts'o <tytso@mit.edu> wrote:
> > +    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
> > +    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
> > +    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
> > +
> > +    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
> > +    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
> > +    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null
> 
> Why not use `git checkout-index --stage=all "$path"` ?
> E.g.:
> 
> 	git checkout-index --stage=all "$path" |
> 	read base_temp local_temp remote_temp path
> 
> I'm not trying to nitpick, I'm just curious about why this particular
> feature of checkout-index was not useful here.

1)  I didn't know about it.

2) If I used it would I have to have renamed the files to
<path>.LOCAL, <path>.BASE, et.al, because with most of the graphical
merge tools, the filename is the only thing which gets displayed to
tell the user which file came from the local branch or the remote
branch or the base revision --- since file names such as
.merge_file_QBaxrn and .merge_file_prSEqs don't have a lot of human
meaning.....

So I don't know that it would havce saved much in the script.  You
replace three invocations to git-cat-file with one invocation to
git-checkout-index plus three invocations to mv.

Regards,

						- Ted


			

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GIT PULL] Please pull mergetool.git
  2007-03-14  5:59 ` Shawn O. Pearce
  2007-03-14  6:17   ` Theodore Tso
@ 2007-03-14  7:03   ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2007-03-14  7:03 UTC (permalink / raw
  To: Shawn O. Pearce; +Cc: Theodore Ts'o, git

"Shawn O. Pearce" <spearce@spearce.org> writes:

> Why not use `git checkout-index --stage=all "$path"` ?
> E.g.:
>
> 	git checkout-index --stage=all "$path" |
> 	read base_temp local_temp remote_temp path

$ echo a b c d | read a b c d; echo "<$a> <$b> <$c> <$d>"
<> <> <> <>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GIT PULL] Please pull mergetool.git
  2007-03-14  1:15 [GIT PULL] Please pull mergetool.git Theodore Ts'o
  2007-03-14  5:59 ` Shawn O. Pearce
@ 2007-03-14  9:45 ` Junio C Hamano
  2007-03-14  9:55   ` Alex Riesen
  1 sibling, 1 reply; 6+ messages in thread
From: Junio C Hamano @ 2007-03-14  9:45 UTC (permalink / raw
  To: Theodore Ts'o; +Cc: git

"Theodore Ts'o" <tytso@mit.edu> writes:

> In order to make this easier for me to send future features and
> cleanups, I've set up a repository on repo.or.cz.  So please pull from:
>
> 	git://repo.or.cz/git/mergetool.git
>
> The final patch that is the repository is attached.

Thanks, pulled.

There should be a checklist for adding a new user-level command.

I've been reluctant to write this up, partly because some people
seem to be disturbed by seeing too many git-blah commands in
their distro's /usr/bin/ and I've been avoiding introducing new
commands (I even rejected "git-diff2", which I liked what it
did), but that is a lame excuse.

So here it is.

 - An .gitignore entry to ignore the build product.

 - An Makefile entry to build and clean.

   - For scripts, add git-foo.sh to SCRIPT_SH (or
     git-foo.perl to SCRIPT_PERL).

   - For a new built-in 'git-foo' that is produced from
     builtin-foo.c, add builtin-foo.o to BUILTIN_OBJS.

   - For a new standalone 'git-foo' whose main is in foo.c, add
     git-foo$X to PROGRAMS.

   - (these are not limited to a new program) If you are adding
     a library object 'foo.o', add it to LIB_OBJS; add new
     headers to to LIB_H.  For the latter, sorry but our
     dependencies are a bit too eager to recompile.

 - Asciidoc manual in Documentation/git-foo.txt to be formatted
   to both HTML and man.

 - An entry in Documentation/cmd-list.perl (the list at the end
   of the script).  This is used to link the command from the
   main documentation git(7) and git.html.

The last one you missed but that is not a big deal.  I'll add an
extra commit to add one entry to classify this as an ancillary
manipulator.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [GIT PULL] Please pull mergetool.git
  2007-03-14  9:45 ` Junio C Hamano
@ 2007-03-14  9:55   ` Alex Riesen
  0 siblings, 0 replies; 6+ messages in thread
From: Alex Riesen @ 2007-03-14  9:55 UTC (permalink / raw
  To: Junio C Hamano; +Cc: Theodore Ts'o, git

On 3/14/07, Junio C Hamano <junkio@cox.net> wrote:
> So here it is.
>
>  - An .gitignore entry to ignore the build product.
>
>  - An Makefile entry to build and clean.
>

- If the "product" has own Makefile: QUIET support (aka "V=x")

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2007-03-14  9:56 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-03-14  1:15 [GIT PULL] Please pull mergetool.git Theodore Ts'o
2007-03-14  5:59 ` Shawn O. Pearce
2007-03-14  6:17   ` Theodore Tso
2007-03-14  7:03   ` Junio C Hamano
2007-03-14  9:45 ` Junio C Hamano
2007-03-14  9:55   ` Alex Riesen

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).