{"thread":{"id":"7202","subject":"[PATCH,RFC] Add git-mergetool to run an appropriate merge conflict resolution program","startedAt":"2007-03-12T06:00:53Z","lastAt":"2007-03-12T13:16:05Z","messageCount":3,"participants":["Theodore Ts'o","Junio C Hamano","Theodore Tso"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"36888","messageId":"E1HQdaX-00025N-3s@candygram.thunk.org","threadId":"7202","inReplyTo":null,"subject":"[PATCH,RFC] Add git-mergetool to run an appropriate merge conflict resolution program","fromName":"Theodore Ts'o","fromEmail":"tytso@mit.edu","sentAt":"2007-03-12T06:00:53Z","receivedAt":"2007-03-12T06:00:53Z","isPatch":true,"sender":{"key":"tytso@mit.edu","avatar":"https://avatars.githubusercontent.com/u/51416?v=4"},"body":"\nOK, here's my second attempt at a git-mergetool subcommand.\n\nMajor differences from last time, besides addressing the comments made\nby Junio, is that the git-mergetool now handles various scnearios where\nthe conflicts are caused by files changing to/from symlinks, and where\nthe symlink was changed to two different destinations in the local and\nremote branches.\n\nI'm not sure the UI and feature set is completely right[1], but I think\nwe're getting to the point where I need people to try it out and give me\ncomments about what works and doesn't work for them.\n\n[1] Should I allow the user the opportunity to see the diff between the\nbase and local/remote revision?  If so, how do I expose that function to\nthe customer?\n\n---\nThe git-mergetool program can be used to automatically run an appropriate\nmerge resolution program to resolve merge conflicts.  It will automatically\nrun one of kdiff3, tkdiff, meld, xxdiff, or emacs emerge programs.\n\nSigned-off-by: \"Theodore Ts'o\" <tytso@mit.edu>\n---\n .gitignore                      |    1 +\n Documentation/config.txt        |    5 +\n Documentation/git-mergetool.txt |   46 ++++++\n Makefile                        |    2 +-\n git-mergetool.sh                |  317 +++++++++++++++++++++++++++++++++++++++\n 5 files changed, 370 insertions(+), 1 deletions(-)\n create mode 100644 Documentation/git-mergetool.txt\n create mode 100755 git-mergetool.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 0eaba0a..27797d1 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -75,6 +75,7 @@ git-merge-ours\n git-merge-recursive\n git-merge-resolve\n git-merge-stupid\n+git-mergetool\n git-mktag\n git-mktree\n git-name-rev\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 5408dd6..aaae9ac 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -453,6 +453,11 @@ merge.summary::\n \tWhether to include summaries of merged commits in newly created\n \tmerge commit messages. False by default.\n \n+merge.tool::\n+\tControls which merge resolution program is used by\n+\tgitlink:git-mergetool[l].  Valid values are: \"kdiff3\", \"tkdiff\",\n+\t\"meld\", \"xxdiff\", \"emerge\"\n+\n merge.verbosity::\n \tControls the amount of output shown by the recursive merge\n \tstrategy.  Level 0 outputs nothing except a final error\ndiff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt\nnew file mode 100644\nindex 0000000..ae69a0e\n--- /dev/null\n+++ b/Documentation/git-mergetool.txt\n@@ -0,0 +1,46 @@\n+git-mergetool(1)\n+================\n+\n+NAME\n+----\n+git-mergetool - Run merge conflict resolution tools to resolve merge conflicts\n+\n+SYNOPSIS\n+--------\n+'git-mergetool' [--tool=<tool>] [<file>]...\n+\n+DESCRIPTION\n+-----------\n+\n+Use 'git mergetool' to run one of several merge utilities to resolve\n+merge conflicts.  It is typically run after gitlink:git-merge[1].\n+\n+If one or more <file> parameters are given, the merge tool program will\n+be run to resolve differences on each file.  If no <file> names are\n+specified, 'git mergetool' will run the merge tool program on every file\n+with merge conflicts.\n+\n+OPTIONS\n+-------\n+-t or --tool=<tool>::\n+\tUse the merge resolution program specified by <tool>.\n+\tValid merge tools are:\n+\tkdiff3, tkdiff, meld, xxdiff, and emerge.\n+\n+\tIf a merge resolution program is not specified, 'git mergetool'\n+\twill use the configuration variable merge.tool.  If the\n+\tconfiguration variable merge.tool is not set, 'git mergetool'\n+\twill pick a suitable default.\n+\n+Author\n+------\n+Written by Theodore Y Ts'o <tytso@mit.edu>\n+\n+Documentation\n+--------------\n+Documentation by Theodore Y Ts'o.\n+\n+GIT\n+---\n+Part of the gitlink:git[7] suite\n+\ndiff --git a/Makefile b/Makefile\nindex 084b7fe..8fb5230 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -179,7 +179,7 @@ SCRIPT_SH = \\\n \tgit-clean.sh git-clone.sh git-commit.sh \\\n \tgit-fetch.sh git-gc.sh \\\n \tgit-ls-remote.sh \\\n-\tgit-merge-one-file.sh git-parse-remote.sh \\\n+\tgit-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \\\n \tgit-pull.sh git-rebase.sh \\\n \tgit-repack.sh git-request-pull.sh git-reset.sh \\\n \tgit-sh-setup.sh \\\ndiff --git a/git-mergetool.sh b/git-mergetool.sh\nnew file mode 100755\nindex 0000000..e75f934\n--- /dev/null\n+++ b/git-mergetool.sh\n@@ -0,0 +1,317 @@\n+#!/bin/sh\n+#\n+# This program resolves merge conflicts in git\n+#\n+# Copyright (c) 2006 Theodore Y. Ts'o\n+#\n+# This file is licensed under the GPL v2, or a later version\n+# at the discretion of Linus Torvalds.\n+#\n+\n+USAGE='[--tool=tool] [file to merge] ...'\n+SUBDIRECTORY_OK=Yes\n+. git-sh-setup\n+require_work_tree\n+\n+# Returns true if the mode reflects a symlink\n+function is_symlink () {\n+    test \"$1\" = 120000\n+}\n+\n+function describe_file () {\n+    mode=\"$1\"\n+    branch=\"$2\"\n+    file=\"$3\"\n+\n+    echo -n \"    \"\n+    if test -z \"$mode\"; then\n+\techo -n \"'$path' was deleted\"\n+    elif is_symlink \"$mode\" ; then\n+\techo -n \"'$path' is a symlink containing '\"\n+\tcat \"$file\"\n+\techo -n \"'\"\n+    else\n+\tif test -z \"$base_mode\"; then\n+\t    echo -n \"'$path' was created\"\n+\telse\n+\t    echo -n \"'$path' was modified\"\n+\tfi\n+    fi\n+    echo \" in the $branch branch\"\n+}\n+\n+\n+resolve_symlink_merge () {\n+    while /bin/true; do\n+\techo -n \"Use (r)emote or (l)ocal, or (a)bort? \"\n+\tread ans\n+\tcase \"$ans\" in\n+\t    [lL]*)\n+\t\tgit-checkout-index -f --stage=2 -- \"$path\"\n+\t\tgit-add -- \"$path\"\n+\t\trm -f -- \"$LOCAL\" \"$REMOTE\" \"$BASE\"\n+\t\tmv -- \"$BACKUP\" \"$path.orig\"\n+\t\treturn\n+\t\t;;\n+\t   [rR]*)\n+\t\tgit-checkout-index -f --stage=3 -- \"$path\"\n+\t\tgit-add -- \"$path\"\n+\t\trm -f -- \"$LOCAL\" \"$REMOTE\" \"$BASE\"\n+\t\tmv -- \"$BACKUP\" \"$path.orig\"\n+\t\treturn\n+\t\t;;\n+\t    [qQ]*)\n+\t\texit 1\n+\t\t;;\n+\t    esac\n+\tdone\n+}\n+\n+resolve_deleted_merge () {\n+    while /bin/true; do\n+\techo -n \"Use (m)odified or (d)eleted file, or (a)bort? \"\n+\tread ans\n+\tcase \"$ans\" in\n+\t    [mM]*)\n+\t\tgit-add -- \"$path\"\n+\t\trm -f -- \"$LOCAL\" \"$REMOTE\" \"$BASE\"\n+\t\tmv -- \"$BACKUP\" \"$path.orig\"\n+\t\treturn\n+\t\t;;\n+\t   [dD]*)\n+\t\tgit-rm -- \"$path\"\n+\t\trm -f -- \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$BACKUP\"\n+\t\treturn\n+\t\t;;\n+\t    [qQ]*)\n+\t\texit 1\n+\t\t;;\n+\t    esac\n+\tdone\n+}\n+\n+merge_file () {\n+    path=\"$1\"\n+\n+    if test ! -f \"$path\" ; then\n+\techo \"$path: file not found\"\n+\texit 1\n+    fi\n+\n+    f=`git-ls-files -u -- \"$path\"`\n+    if test -z \"$f\" ; then\n+\techo \"$path: file does not need merging\"\n+\texit 1\n+    fi\n+\n+    BACKUP=\"$path.BACKUP.$$\"\n+    LOCAL=\"$path.LOCAL.$$\"\n+    REMOTE=\"$path.REMOTE.$$\"\n+    BASE=\"$path.BASE.$$\"\n+\n+    mv -- \"$path\" \"$BACKUP\"\n+    cp -- \"$BACKUP\" \"$path\"\n+\n+    base_mode=`git ls-files -u -- \"$path\" | awk '{if ($3==1) print $1;}'`\n+    local_mode=`git ls-files -u -- \"$path\" | awk '{if ($3==2) print $1;}'`\n+    remote_mode=`git ls-files -u -- \"$path\" | awk '{if ($3==3) print $1;}'`\n+\n+#    echo \"base_mode is '$base_mode'\"\n+#    echo \"local_mode is '$local_mode'\"\n+#    echo \"remote_mode is '$remote_mode'\"\n+\n+    git cat-file blob \":1:$path\" > \"$BASE\" 2>/dev/null\n+    git cat-file blob \":2:$path\" > \"$LOCAL\" 2>/dev/null\n+    git cat-file blob \":3:$path\" > \"$REMOTE\" 2>/dev/null\n+\n+    if test -z \"$local_mode\" -o -z \"$remote_mode\"; then\n+\techo \"Deleted merge conflict for $path:\"\n+\tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n+\tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n+\tresolve_deleted_merge\n+\treturn\n+    fi\n+\n+    if is_symlink \"$local_mode\" || is_symlink \"$remote_mode\"; then\n+\techo \"Symlink merge conflict for $path:\"\n+\tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n+\tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n+\tresolve_symlink_merge\n+\treturn\n+    fi\n+\n+    echo \"Normal merge conflict for $path:\"\n+    describe_file \"$local_mode\" \"local\" \"$LOCAL\"\n+    describe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n+    echo -n \"Hit return to start merge resolution tool ($merge_tool): \"\n+    read ans\n+\n+    case \"$merge_tool\" in\n+\tkdiff3)\n+\t    (kdiff3 --auto --L1 \"$path (Base)\" -L2 \"$path (Local)\" --L3 \"$path (Remote)\" \\\n+\t\t\"$BASE\" \"$LOCAL\" \"$REMOTE\" -o \"$path\" > /dev/null 2>&1)\n+\t    status=$?\n+\t    if test \"$status\" -eq 0; then\n+\t\trm \"$BACKUP\"\n+\t    fi\n+\t    ;;\n+\ttkdiff)\n+\t    tkdiff \"$LOCAL\" \"$REMOTE\" -a \"$BASE\" -o \"$path\"\n+\t    status=$?\n+\t    if test \"$status\" -eq 0; then\n+\t\tmv \"$BACKUP\" \"$path.orig\"\n+\t    fi\n+\t    ;;\n+\tmeld)\n+\t    touch \"$BACKUP\"\n+\t    meld \"$LOCAL\" \"$path\" \"$REMOTE\"\n+\t    if test \"$path\" -nt \"$BACKUP\" ; then\n+\t\tstatus=0;\n+\t    else\n+\t\twhile true; do\n+\t\t    echo \"$path seems unchanged.\"\n+\t\t    echo -n \"Was the merge successful? [y/n] \"\n+\t\t    read answer < /dev/tty\n+\t\t    case \"$answer\" in\n+\t\t\ty*|Y*) status=0; break ;;\n+\t\t\tn*|N*) status=1; break ;;\n+\t\t    esac\n+\t\tdone\n+\t    fi\n+\t    if test \"$status\" -eq 0; then\n+\t\tmv \"$BACKUP\" \"$path.orig\"\n+\t    fi\n+\t    ;;\n+\txxdiff)\n+\t    touch \"$BACKUP\"\n+\t    xxdiff -X --show-merged-pane \\\n+\t\t-R 'Accel.SaveAsMerged: \"Ctrl-S\"' \\\n+\t\t-R 'Accel.Search: \"Ctrl+F\"' \\\n+\t\t-R 'Accel.SearchForward: \"Ctrl-G\"' \\\n+\t\t--merged-file \"$path\" \"$LOCAL\" \"$BASE\" \"$REMOTE\"\n+\t    if test \"$path\" -nt \"$BACKUP\" ; then\n+\t\tstatus=0;\n+\t    else\n+\t\twhile true; do\n+\t\t    echo \"$path seems unchanged.\"\n+\t\t    echo -n \"Was the merge successful? [y/n] \"\n+\t\t    read answer < /dev/tty\n+\t\t    case \"$answer\" in\n+\t\t\ty*|Y*) status=0; break ;;\n+\t\t\tn*|N*) status=1; break ;;\n+\t\t    esac\n+\t\tdone\n+\t    fi\n+\t    if test \"$status\" -eq 0; then\n+\t\tmv \"$BACKUP\" \"$path.orig\"\n+\t    fi\n+\t    ;;\n+\temerge)\n+\t    emacs -f emerge-files-with-ancestor-command \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$path\"\n+\t    status=$?\n+\t    if test \"$status\" -eq 0; then\n+\t\tmv -- \"$BACKUP\" \"$path.orig\"\n+\t    fi\n+\t    ;;\n+    esac\n+    rm -f \"$LOCAL\" \"$REMOTE\" \"$BASE\"\n+    if test \"$status\" -ne 0; then\n+\techo \"merge of $path failed\" 1>&2\n+\tmv -- \"$BACKUP\" \"$path\"\n+\texit 1\n+    fi\n+    git add -- \"$path\"\n+}\n+\n+while case $# in 0) break ;; esac\n+do\n+    case \"$1\" in\n+\t-t|--tool*)\n+\t    case \"$#,$1\" in\n+\t\t*,*=*)\n+\t\t    merge_tool=`expr \"z$1\" : 'z-[^=]*=\\(.*\\)'`\n+\t\t    ;;\n+\t\t1,*)\n+\t\t    usage ;;\n+\t\t*)\n+\t\t    merge_tool=\"$2\"\n+\t\t    shift ;;\n+\t    esac\n+\t    ;;\n+\t--)\n+\t    break\n+\t    ;;\n+\t-*)\n+\t    usage\n+\t    ;;\n+\t*)\n+\t    break\n+\t    ;;\n+    esac\n+    shift\n+done\n+\n+if test -z \"$merge_tool\"; then\n+    merge_tool=`git-config merge.tool`\n+    if test $merge_tool = kdiff3 -o $merge_tool = tkdiff -o \\\n+\t$merge_tool = xxdiff -o $merge_tool = meld ; then\n+\tunset merge_tool\n+    fi\n+fi\n+\n+if test -z \"$merge_tool\" ; then\n+    if type kdiff3 >/dev/null 2>&1 && test -n \"$DISPLAY\"; then\n+\tmerge_tool=\"kdiff3\";\n+    elif type tkdiff >/dev/null 2>&1 && test -n \"$DISPLAY\"; then\n+    \tmerge_tool=tkdiff\n+    elif type xxdiff >/dev/null 2>&1 && test -n \"$DISPLAY\"; then\n+    \tmerge_tool=xxdiff\n+    elif type meld >/dev/null 2>&1 && test -n \"$DISPLAY\"; then\n+        merge_tool=meld\n+    elif type emacs >/dev/null 2>&1; then\n+        merge_tool=emerge\n+    else\n+\techo \"No available merge tools available.\"\n+\texit 1\n+    fi\n+fi\n+\n+case \"$merge_tool\" in\n+    kdiff3|tkdiff|meld|xxdiff)\n+    \tif ! type \"$merge_tool\" > /dev/null 2>&1; then\n+\t    echo \"The merge tool $merge_tool is not available\"\n+\t    exit 1\n+\tfi\n+\t;;\n+    emerge)\n+    \tif ! type \"emacs\" > /dev/null 2>&1; then\n+\t    echo \"Emacs is not available\"\n+\t    exit 1\n+\tfi\n+\t;;\n+    *)\n+    \techo \"Unknown merge tool: $merge_tool\"\n+\texit 1\n+\t;;\n+esac\n+\n+if test $# -eq 0 ; then\n+\tfiles=`git ls-files -u | sed -e 's/^[^\t]*\t//' | sort -u`\n+\tif test -z \"$files\" ; then\n+\t\techo \"No files need merging\"\n+\t\texit 0\n+\tfi\n+\techo Merging the files: $files\n+\tgit ls-files -u | sed -e 's/^[^\t]*\t//' | sort -u | while read i\n+\tdo\n+\t\techo \"\"\n+\t\tmerge_file \"$i\" < /dev/tty > /dev/tty\n+\tdone\n+else\n+\twhile test $# -gt 0; do\n+\t\techo \"\"\n+\t\tmerge_file \"$1\"\n+\t\tshift\n+\tdone\n+fi\n+exit 0\n-- \n1.5.0.3.272.gb515-dirty\n"},{"id":"36890","messageId":"7vodmzt0ud.fsf@assigned-by-dhcp.cox.net","threadId":"7202","inReplyTo":"E1HQdaX-00025N-3s@candygram.thunk.org","subject":"Re: [PATCH,RFC] Add git-mergetool to run an appropriate merge conflict resolution program","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-12T06:47:22Z","receivedAt":"2007-03-12T06:47:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Theodore Ts'o\" <tytso@mit.edu> writes:\n\n> OK, here's my second attempt at a git-mergetool subcommand.\n>\n> Major differences from last time, besides addressing the comments made\n> by Junio, is that the git-mergetool now handles various scnearios where\n> the conflicts are caused by files changing to/from symlinks, and where\n> the symlink was changed to two different destinations in the local and\n> remote branches.\n\nThanks.  By the way, is it fashionable to misspell \"scenario\" in\nthe kernel circles? ;-)\n\n> +    git cat-file blob \":1:$path\" > \"$BASE\" 2>/dev/null\n> +    git cat-file blob \":2:$path\" > \"$LOCAL\" 2>/dev/null\n> +    git cat-file blob \":3:$path\" > \"$REMOTE\" 2>/dev/null\n> +\n> +    if test -z \"$local_mode\" -o -z \"$remote_mode\"; then\n> +\techo \"Deleted merge conflict for $path:\"\n> +\tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n> +\tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n> +\tresolve_deleted_merge\n> +\treturn\n> +    fi\n\nRunning cat-file even when you know it does not exist at that\nstage does not feel right here, although you are not checking\nthe exit status and discarding 2>/dev/null...\n\nOne situation that happens in the real life to cause \"we deleted\nwhile they modified\" is when in reality we moved then modified\nso much that the difference between our version and the common\nancestor version is too large to be considered a rename anymore.\n\nSuch a misidentified rename would appear as one path that is \"we\ndeleted while they modified\" (original path) and the other path\nthat is \"we created while they didn't do anything to the path\".\nThe latter does not conflict and is already resolved in the\nindex when you would run git-mergetool.\n\nSo if you have \"we deleted while they modified\" conflict, it may\nmake sense to give the paths the index adds (relative to HEAD),\nlet the user pick one of them and allow 3-way merge to update\nthe path we renamed to.  The original path which had conflicted\nwould be removed as the result.\n"},{"id":"36905","messageId":"20070312131605.GA4372@thunk.org","threadId":"7202","inReplyTo":"7vodmzt0ud.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH,RFC] Add git-mergetool to run an appropriate merge conflict resolution program","fromName":"Theodore Tso","fromEmail":"tytso@mit.edu","sentAt":"2007-03-12T13:16:05Z","receivedAt":"2007-03-12T13:16:05Z","isPatch":true,"sender":{"key":"tytso@mit.edu","avatar":"https://avatars.githubusercontent.com/u/51416?v=4"},"body":"On Sun, Mar 11, 2007 at 11:47:22PM -0700, Junio C Hamano wrote:\n> Thanks.  By the way, is it fashionable to misspell \"scenario\" in\n> the kernel circles? ;-)\n\nProbably not; it's just that many of us are probably fast typists, and\nthere's a race condition between the 'n' and the 'e on the right and\nleft hands, respectively.  :-)\n\n> \n> > +    git cat-file blob \":1:$path\" > \"$BASE\" 2>/dev/null\n> > +    git cat-file blob \":2:$path\" > \"$LOCAL\" 2>/dev/null\n> > +    git cat-file blob \":3:$path\" > \"$REMOTE\" 2>/dev/null\n> > +\n> > +    if test -z \"$local_mode\" -o -z \"$remote_mode\"; then\n> > +\techo \"Deleted merge conflict for $path:\"\n> > +\tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n> > +\tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n> > +\tresolve_deleted_merge\n> > +\treturn\n> > +    fi\n> \n> Running cat-file even when you know it does not exist at that\n> stage does not feel right here, although you are not checking\n> the exit status and discarding 2>/dev/null...\n\nOK, I'll fix that.  \n\n> One situation that happens in the real life to cause \"we deleted\n> while they modified\" is when in reality we moved then modified\n> so much that the difference between our version and the common\n> ancestor version is too large to be considered a rename anymore.\n> \n> Such a misidentified rename would appear as one path that is \"we\n> deleted while they modified\" (original path) and the other path\n> that is \"we created while they didn't do anything to the path\".\n> The latter does not conflict and is already resolved in the\n> index when you would run git-mergetool.\n> \n> So if you have \"we deleted while they modified\" conflict, it may\n> make sense to give the paths the index adds (relative to HEAD),\n> let the user pick one of them and allow 3-way merge to update\n> the path we renamed to.  The original path which had conflicted\n> would be removed as the result.\n\nI agree that would be useful, but it I'm going to have to restructure\nthe code a bit to support that.  Hmm, let me think about that a bit.\n\nIt occurs to me that I might also want to think about the order in\nwhich I present files to be merged to the user in the \"git mergetool\"\ncase.  If we force the user to resolve these cases first, probably in\nthis order:\n\n\t* 2/3-way modified symlink/symlink\n\t* The symlink/modified file case\n\t* 3-way modified file case\n\t* 2-way modified file case (no common ancestor)\n\nIt also occurs to me that listing all of the files and characterizing\nthem according to what needs to be done up-front might also be a good\nidea.  \n\nI can remember, in the pre-git, Bitkeeper days some *truly nasty* ACPI\nmerges where there were some 20-30 files getting renamed and merged at\nthe same time.  Blech.  Can't we just slap the developers hands when\nthey do something this evil?  (Oh right, we already gave the ACPI\ndevelopers octopus merges which are a bisect nightmare.\nSigh... sometimes I'm not sure why we encourage them like this; I\nguess it makes the kernel have some really test cases for git, which\nis the only reason why it exists, right?  :-)\n\n\nJunio, when and how do you feel about merging this?  It's clear the\ncode is going to have to go through some significant changes still to\nadd the above functionality, which would be an argument against\nmerging it, but on the other hand, it's quite useful in its current\nform and I'd love to get more testers.  Another argument in favor of\nmerging it is that we already have more functionality here than hg,\nmonotone, Bitkeeper (as of the last free version), et. al.  And hey, I\ncreated a man page so that proves I'm going to keep\nmaintaining/improving it.  :-)\n\n\t\t\t\t\t\t- Ted\n"}]}