Merge branch 'jc/diff'
authorJunio C Hamano <junkio@cox.net>
Wed, 26 Apr 2006 06:12:21 +0000 (23:12 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 26 Apr 2006 06:12:21 +0000 (23:12 -0700)
* jc/diff:
  Libified diff-index: backward compatibility fix.
  Libify diff-index.
  Libify diff-files.

12 files changed:
Documentation/config.txt [new file with mode: 0644]
Documentation/git-repo-config.txt
Documentation/git-var.txt
Makefile
config.c
contrib/colordiff/README [new file with mode: 0644]
contrib/colordiff/colordiff.perl [new file with mode: 0755]
daemon.c
gitk
rev-parse.c
revision.c
update-index.c

diff --git a/Documentation/config.txt b/Documentation/config.txt
new file mode 100644 (file)
index 0000000..b27b0d5
--- /dev/null
@@ -0,0 +1,181 @@
+CONFIGURATION FILE
+------------------
+
+The git configuration file contains a number of variables that affect
+the git commands behaviour. They can be used by both the git plumbing
+and the porcelains. The variables are divided to sections, where
+in the fully qualified variable name the variable itself is the last
+dot-separated segment and the section name is everything before the last
+dot. The variable names are case-insensitive and only alphanumeric
+characters are allowed. Some variables may appear multiple times.
+
+The syntax is fairly flexible and permissive; whitespaces are mostly
+ignored. The '#' and ';' characters begin commends to the end of line,
+blank lines are ignored, lines containing strings enclosed in square
+brackets start sections and all the other lines are recognized
+as setting variables, in the form 'name = value'. If there is no equal
+sign on the line, the entire line is taken as 'name' and the variable
+is recognized as boolean "true". String values may be entirely or partially
+enclosed in double quotes; some variables may require special value format.
+
+Example
+~~~~~~~
+
+       # Core variables
+       [core]
+               ; Don't trust file modes
+               filemode = false
+
+       # Our diff algorithm
+       [diff]
+               external = "/usr/local/bin/gnu-diff -u"
+               renames = true
+
+Variables
+~~~~~~~~~
+
+Note that this list is non-comprehensive and not necessarily complete.
+For command-specific variables, you will find more detailed description
+in the appropriate manual page. You will find description of non-core
+porcelain configuration variables in the respective porcelain documentation.
+
+core.fileMode::
+       If false, the executable bit differences between the index and
+       the working copy are ignored; useful on broken filesystems like FAT.
+       See gitlink:git-update-index[1]. True by default.
+
+core.gitProxy::
+       A "proxy command" to execute (as 'command host port') instead
+       of establishing direct connection to the remote server when
+       using the git protocol for fetching. If the variable value is
+       in the "COMMAND for DOMAIN" format, the command is applied only
+       on hostnames ending with the specified domain string. This variable
+       may be set multiple times and is matched in the given order;
+       the first match wins.
+
+       Can be overriden by the 'GIT_PROXY_COMMAND' environment variable
+       (which always applies universally, without the special "for"
+       handling).
+
+core.ignoreStat::
+       The working copy files are assumed to stay unchanged until you
+       mark them otherwise manually - Git will not detect the file changes
+       by lstat() calls. This is useful on systems where those are very
+       slow, such as Microsoft Windows.  See gitlink:git-update-index[1].
+       False by default.
+
+core.onlyUseSymrefs::
+       Always use the "symref" format instead of symbolic links for HEAD
+       and other symbolic reference files. True by default.
+
+core.repositoryFormatVersion::
+       Internal variable identifying the repository format and layout
+       version.
+
+core.sharedRepository::
+       If true, the repository is made shareable between several users
+       in a group (making sure all the files and objects are group-writable).
+       See gitlink:git-init-db[1]. False by default.
+
+core.warnAmbiguousRefs::
+       If true, git will warn you if the ref name you passed it is ambiguous
+       and might match multiple refs in the .git/refs/ tree. True by default.
+
+apply.whitespace::
+       Tells `git-apply` how to handle whitespaces, in the same way
+       as the '--whitespace' option. See gitlink:git-apply[1].
+
+diff.renameLimit::
+       The number of files to consider when performing the copy/rename
+       detection; equivalent to the git diff option '-l'.
+
+format.headers::
+       Additional email headers to include in a patch to be submitted
+       by mail.  See gitlink:git-format-patch[1].
+
+gitcvs.enabled::
+       Whether the cvs pserver interface is enabled for this repository.
+       See gitlink:git-cvsserver[1].
+
+gitcvs.logfile::
+       Path to a log file where the cvs pserver interface well... logs
+       various stuff. See gitlink:git-cvsserver[1].
+
+http.sslVerify::
+       Whether to verify the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_NO_VERIFY' environment
+       variable.
+
+http.sslCert::
+       File containing the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_CERT' environment
+       variable.
+
+http.sslKey::
+       File containing the SSL private key when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_KEY' environment
+       variable.
+
+http.sslCAInfo::
+       File containing the certificates to verify the peer with when
+       fetching or pushing over HTTPS. Can be overriden by the
+       'GIT_SSL_CAINFO' environment variable.
+
+http.sslCAPath::
+       Path containing files with the CA certificates to verify the peer
+       with when fetching or pushing over HTTPS. Can be overriden
+       by the 'GIT_SSL_CAPATH' environment variable.
+
+http.maxRequests::
+       How many HTTP requests to launch in parallel. Can be overriden
+       by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+
+http.lowSpeedLimit, http.lowSpeedTime::
+       If the HTTP transfer speed is less than 'http.lowSpeedLimit'
+       for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
+       Can be overriden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
+       'GIT_HTTP_LOW_SPEED_TIME' environment variables.
+
+i18n.commitEncoding::
+       Character encoding the commit messages are stored in; git itself
+       does not care per se, but this information is necessary e.g. when
+       importing commits from emails or in the gitk graphical history
+       browser (and possibly at other places in the future or in other
+       porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
+
+merge.summary::
+       Whether to include summaries of merged commits in newly created
+       merge commit messages. False by default.
+
+pull.octopus::
+       The default merge strategy to use when pulling multiple branches
+       at once.
+
+pull.twohead::
+       The default merge strategy to use when pulling a single branch.
+
+show.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-show[1].
+
+showbranch.default::
+       The default set of branches for gitlink:git-show-branch[1].
+       See gitlink:git-show-branch[1].
+
+user.email::
+       Your email address to be recorded in any newly created commits.
+       Can be overriden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+user.name::
+       Your full name to be recorded in any newly created commits.
+       Can be overriden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+whatchanged.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-whatchanged[1].
+
+imap::
+       The configuration variables in the 'imap' section are described
+       in gitlink:git-imap-send[1].
index 26759a8..71f96bd 100644 (file)
@@ -87,11 +87,11 @@ Given a .git/config like this:
                renames = true
 
        ; Proxy settings
-       [proxy]
-               command="ssh" for "ssh://kernel.org/"
-               command="proxy-command" for kernel.org
-               command="myprotocol-command" for "my://"
-               command=default-proxy ; for all the rest
+       [core]
+               gitproxy="ssh" for "ssh://kernel.org/"
+               gitproxy="proxy-command" for kernel.org
+               gitproxy="myprotocol-command" for "my://"
+               gitproxy=default-proxy ; for all the rest
 
 you can set the filemode to true with
 
@@ -104,7 +104,7 @@ to what URL they apply. Here is how to change the entry for kernel.org
 to "ssh".
 
 ------------
-% git repo-config proxy.command '"ssh" for kernel.org' 'for kernel.org$'
+% git repo-config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
 ------------
 
 This makes sure that only the key/value pair for kernel.org is replaced.
@@ -115,7 +115,7 @@ To delete the entry for renames, do
 % git repo-config --unset diff.renames
 ------------
 
-If you want to delete an entry for a multivar (like proxy.command above),
+If you want to delete an entry for a multivar (like core.gitproxy above),
 you have to provide a regex matching the value of exactly one line.
 
 To query the value for a given key, do
@@ -133,27 +133,27 @@ or
 or, to query a multivar:
 
 ------------
-% git repo-config --get proxy.command "for kernel.org$"
+% git repo-config --get core.gitproxy "for kernel.org$"
 ------------
 
 If you want to know all the values for a multivar, do:
 
 ------------
-% git repo-config --get-all proxy.command
+% git repo-config --get-all core.gitproxy
 ------------
 
-If you like to live dangerous, you can replace *all* proxy.commands by a
+If you like to live dangerous, you can replace *all* core.gitproxy by a
 new one with
 
 ------------
-% git repo-config --replace-all proxy.command ssh
+% git repo-config --replace-all core.gitproxy ssh
 ------------
 
 However, if you really only want to replace the line for the default proxy,
 i.e. the one without a "for ..." postfix, do something like this:
 
 ------------
-% git repo-config proxy.command ssh '! for '
+% git repo-config core.gitproxy ssh '! for '
 ------------
 
 To actually match only values with an exclamation mark, you have to
@@ -163,13 +163,16 @@ To actually match only values with an exclamation mark, you have to
 ------------
 
 
+include::config.txt[]
+
+
 Author
 ------
 Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
 
 Documentation
 --------------
-Documentation by Johannes Schindelin.
+Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
index 90cb157..379571e 100644 (file)
@@ -17,7 +17,9 @@ Prints a git logical variable.
 OPTIONS
 -------
 -l::
-       Cause the logical variables to be listed.
+       Cause the logical variables to be listed. In addition, all the
+       variables of the git configuration file .git/config are listed
+       as well.
 
 EXAMPLE
 --------
@@ -46,6 +48,7 @@ See Also
 --------
 gitlink:git-commit-tree[1]
 gitlink:git-tag[1]
+gitlink:git-repo-config[1]
 
 Author
 ------
index 0924f84..087ff27 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -575,7 +575,7 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(AR) rcs $@ $(LIB_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
 
index 7ea8a73..4e1f0c2 100644 (file)
--- a/config.c
+++ b/config.c
@@ -252,7 +252,7 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       /* Add other config variables here.. */
+       /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
 
diff --git a/contrib/colordiff/README b/contrib/colordiff/README
new file mode 100644 (file)
index 0000000..2678fdf
--- /dev/null
@@ -0,0 +1,2 @@
+This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
+Ewart <davee@sungate.co.uk>, modified specifically for git.
diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl
new file mode 100755 (executable)
index 0000000..5789cfb
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+#
+# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
+
+########################################################################
+#                                                                      #
+# ColorDiff - a wrapper/replacment for 'diff' producing                #
+#             colourful output                                         #
+#                                                                      #
+# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk)              #
+#                                                                      #
+########################################################################
+#                                                                      #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or    #
+# (at your option) any later version.                                  #
+#                                                                      #
+# This program is distributed in the hope that it will be useful,      #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
+# GNU General Public License for more details.                         #
+#                                                                      #
+# You should have received a copy of the GNU General Public License    #
+# along with this program; if not, write to the Free Software          #
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            #
+#                                                                      #
+########################################################################
+
+use strict;
+use Getopt::Long qw(:config pass_through);
+use IPC::Open2;
+
+my $app_name     = 'colordiff';
+my $version      = '1.0.4';
+my $author       = 'Dave Ewart';
+my $author_email = 'davee@sungate.co.uk';
+my $app_www      = 'http://colordiff.sourceforge.net/';
+my $copyright    = '(C)2002-2004';
+my $show_banner  = 1;
+
+# ANSI sequences for colours
+my %colour;
+$colour{white}       = "\033[1;37m";
+$colour{yellow}      = "\033[1;33m";
+$colour{green}       = "\033[1;32m";
+$colour{blue}        = "\033[1;34m";
+$colour{cyan}        = "\033[1;36m";
+$colour{red}         = "\033[1;31m";
+$colour{magenta}     = "\033[1;35m";
+$colour{black}       = "\033[1;30m";
+$colour{darkwhite}   = "\033[0;37m";
+$colour{darkyellow}  = "\033[0;33m";
+$colour{darkgreen}   = "\033[0;32m";
+$colour{darkblue}    = "\033[0;34m";
+$colour{darkcyan}    = "\033[0;36m";
+$colour{darkred}     = "\033[0;31m";
+$colour{darkmagenta} = "\033[0;35m";
+$colour{darkblack}   = "\033[0;30m";
+$colour{OFF}         = "\033[0;0m";
+
+# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
+my $plain_text = $colour{OFF};
+my $file_old   = $colour{red};
+my $file_new   = $colour{blue};
+my $diff_stuff = $colour{magenta};
+
+# Locations for personal and system-wide colour configurations
+my $HOME   = $ENV{HOME};
+my $etcdir = '/etc';
+
+my ($setting, $value);
+my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
+my $config_file;
+
+foreach $config_file (@config_files) {
+    if (open(COLORDIFFRC, "<$config_file")) {
+        while (<COLORDIFFRC>) {
+            chop;
+            next if (/^#/ || /^$/);
+            s/\s+//g;
+            ($setting, $value) = split ('=');
+            if ($setting eq 'banner') {
+                if ($value eq 'no') {
+                    $show_banner = 0;
+                }
+                next;
+            }
+            if (!defined $colour{$value}) {
+                print "Invalid colour specification ($value) in $config_file\n";
+                next;
+            }
+            if ($setting eq 'plain') {
+                $plain_text = $colour{$value};
+            }
+            elsif ($setting eq 'oldtext') {
+                $file_old = $colour{$value};
+            }
+            elsif ($setting eq 'newtext') {
+                $file_new = $colour{$value};
+            }
+            elsif ($setting eq 'diffstuff') {
+                $diff_stuff = $colour{$value};
+            }
+            else {
+                print "Unknown option in $etcdir/colordiffrc: $setting\n";
+            }
+        }
+        close COLORDIFFRC;
+    }
+}
+
+# colordiff specfic options here.  Need to pre-declare if using variables
+GetOptions(
+    "no-banner" => sub { $show_banner = 0 },
+    "plain-text=s" => \&set_color,
+    "file-old=s"   => \&set_color,
+    "file-new=s"   => \&set_color,
+    "diff-stuff=s" => \&set_color
+);
+
+if ($show_banner == 1) {
+    print STDERR "$app_name $version ($app_www)\n";
+    print STDERR "$copyright $author, $author_email\n\n";
+}
+
+if (defined $ARGV[0]) {
+    # More reliable way of pulling in arguments
+    open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
+}
+else {
+    *INPUTSTREAM = \*STDIN;
+}
+
+my $record;
+my $nrecs           = 0;
+my $inside_file_old = 1;
+my $nparents        = undef;
+
+while (<INPUTSTREAM>) {
+    $nrecs++;
+    if (/^(\@\@+) -[-+0-9, ]+ \1/) {
+           print "$diff_stuff";
+           $nparents = length($1) - 1;
+    }
+    elsif (/^diff -/ || /^index / ||
+          /^old mode / || /^new mode / ||
+          /^deleted file mode / || /^new file mode / ||
+          /^similarity index / || /^dissimilarity index / ||
+          /^copy from / || /^copy to / ||
+          /^rename from / || /^rename to /) {
+           $nparents = undef;
+           print "$diff_stuff";
+    }
+    elsif (defined $nparents) {
+           if ($nparents == 1) {
+                   if (/^\+/) {
+                           print $file_new;
+                   }
+                   elsif (/^-/) {
+                           print $file_old;
+                   }
+                   else {
+                           print $plain_text;
+                   }
+           }
+           elsif (/^ {$nparents}/) {
+                   print "$plain_text";
+           }
+           elsif (/^[+ ]{$nparents}/) {
+                   print "$file_new";
+           }
+           elsif (/^[- ]{$nparents}/) {
+                   print "$file_old";
+           }
+           else {
+                   print $plain_text;
+           }
+    }
+    elsif (/^--- / || /^\+\+\+ /) {
+           print $diff_stuff;
+    }
+    else {
+           print "$plain_text";
+    }
+    s/$/$colour{OFF}/;
+    print "$_";
+}
+close INPUTSTREAM;
+
+sub set_color {
+    my ($type, $color) = @_;
+
+    $type =~ s/-/_/;
+    eval "\$$type = \$colour{$color}";
+}
index a1ccda3..776749e 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -535,7 +535,7 @@ static int socksetup(int port, int **socklist_p)
 
                if (set_reuse_addr(sockfd)) {
                        close(sockfd);
-                       return 0;       /* not fatal */
+                       continue;
                }
 
                if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
diff --git a/gitk b/gitk
index 87e7162..5362b76 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -16,22 +16,6 @@ proc gitdir {} {
     }
 }
 
-proc parse_args {rargs} {
-    global parsed_args
-
-    if {[catch {
-       set parse_args [concat --default HEAD $rargs]
-       set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
-    }]} {
-       # if git-rev-parse failed for some reason...
-       if {$rargs == {}} {
-           set rargs HEAD
-       }
-       set parsed_args $rargs
-    }
-    return $parsed_args
-}
-
 proc start_rev_list {rlargs} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
@@ -46,7 +30,7 @@ proc start_rev_list {rlargs} {
     }
     if {[catch {
        set commfd [open [concat | git-rev-list --header $order \
-                             --parents --boundary $rlargs] r]
+                             --parents --boundary --default HEAD $rlargs] r]
     } err]} {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
@@ -65,7 +49,7 @@ proc getcommits {rargs} {
     global phase canv mainfont
 
     set phase getcommits
-    start_rev_list [parse_args $rargs]
+    start_rev_list $rargs
     $canv delete all
     $canv create text 3 3 -anchor nw -text "Reading commits..." \
        -font $mainfont -tags textitems
index e956cd5..7f66ae2 100644 (file)
@@ -160,6 +160,14 @@ static int show_file(const char *arg)
        return 0;
 }
 
+static void die_badfile(const char *arg)
+{
+       if (errno != ENOENT)
+               die("'%s': %s", arg, strerror(errno));
+       die("'%s' is ambiguous - revision name or file/directory name?\n"
+           "Please put '--' before the list of filenames.", arg);
+}
+
 int main(int argc, char **argv)
 {
        int i, as_is = 0, verify = 0;
@@ -176,7 +184,7 @@ int main(int argc, char **argv)
                if (as_is) {
                        if (show_file(arg) && as_is < 2)
                                if (lstat(arg, &st) < 0)
-                                       die("'%s': %s", arg, strerror(errno));
+                                       die_badfile(arg);
                        continue;
                }
                if (!strcmp(arg,"-n")) {
@@ -343,7 +351,7 @@ int main(int argc, char **argv)
                if (verify)
                        die("Needed a single revision");
                if (lstat(arg, &st) < 0)
-                       die("'%s': %s", arg, strerror(errno));
+                       die_badfile(arg);
        }
        show_default();
        if (verify && revs_count != 1)
index 113dd5a..f9c7d15 100644 (file)
@@ -789,7 +789,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        }
        if (revs->combine_merges) {
                revs->ignore_merges = 0;
-               if (revs->dense_combined_merges)
+               if (revs->dense_combined_merges &&
+                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
                        revs->diffopt.output_format = DIFF_FORMAT_PATCH;
        }
        revs->diffopt.abbrev = revs->abbrev;
index 1efac27..facec8d 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "tree-walk.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -328,7 +329,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        return 0;
 }
 
-static int chmod_path(int flip, const char *path)
+static void chmod_path(int flip, const char *path)
 {
        int pos;
        struct cache_entry *ce;
@@ -336,21 +337,24 @@ static int chmod_path(int flip, const char *path)
 
        pos = cache_name_pos(path, strlen(path));
        if (pos < 0)
-               return -1;
+               goto fail;
        ce = active_cache[pos];
        mode = ntohl(ce->ce_mode);
        if (!S_ISREG(mode))
-               return -1;
+               goto fail;
        switch (flip) {
        case '+':
                ce->ce_mode |= htonl(0111); break;
        case '-':
                ce->ce_mode &= htonl(~0111); break;
        default:
-               return -1;
+               goto fail;
        }
        active_cache_changed = 1;
-       return 0;
+       report("chmod %cx '%s'", flip, path);
+       return;
+ fail:
+       die("git-update-index: cannot chmod %cx '%s'", flip, path);
 }
 
 static struct cache_file cache_file;
@@ -471,6 +475,124 @@ static void read_index_info(int line_termination)
 static const char update_index_usage[] =
 "git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
 
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+                                       unsigned char *ent, const char *path,
+                                       int namelen, int stage)
+{
+       unsigned mode;
+       unsigned char sha1[20];
+       int size;
+       struct cache_entry *ce;
+
+       if (get_tree_entry(ent, path, sha1, &mode)) {
+               error("%s: not in %s branch.", path, which);
+               return NULL;
+       }
+       if (mode == S_IFDIR) {
+               error("%s: not a blob in %s branch.", path, which);
+               return NULL;
+       }
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = create_ce_flags(namelen, stage);
+       ce->ce_mode = create_ce_mode(mode);
+       return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+       int namelen = strlen(path);
+       int pos;
+       int ret = 0;
+       struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+       /* See if there is such entry in the index. */
+       pos = cache_name_pos(path, namelen);
+       if (pos < 0) {
+               /* If there isn't, either it is unmerged, or
+                * resolved as "removed" by mistake.  We do not
+                * want to do anything in the former case.
+                */
+               pos = -pos-1;
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen)) {
+                               fprintf(stderr,
+                                       "%s: skipping still unmerged path.\n",
+                                       path);
+                               goto free_return;
+                       }
+               }
+       }
+
+       /* Grab blobs from given path from HEAD and MERGE_HEAD,
+        * stuff HEAD version in stage #2,
+        * stuff MERGE_HEAD version in stage #3.
+        */
+       ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+       ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+       if (!ce_2 || !ce_3) {
+               ret = -1;
+               goto free_return;
+       }
+       if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+           ce_2->ce_mode == ce_3->ce_mode) {
+               fprintf(stderr, "%s: identical in both, skipping.\n",
+                       path);
+               goto free_return;
+       }
+
+       remove_file_from_cache(path);
+       if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
+               error("%s: cannot add our version to the index.", path);
+               ret = -1;
+               goto free_return;
+       }
+       if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+               return 0;
+       error("%s: cannot add their version to the index.", path);
+       ret = -1;
+ free_return:
+       free(ce_2);
+       free(ce_3);
+       return ret;
+}
+
+static void read_head_pointers(void)
+{
+       if (read_ref(git_path("HEAD"), head_sha1))
+               die("No HEAD -- no initial commit yet?\n");
+       if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+               fprintf(stderr, "Not in the middle of a merge.\n");
+               exit(0);
+       }
+}
+
+static int do_unresolve(int ac, const char **av)
+{
+       int i;
+       int err = 0;
+
+       /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+        * are not doing a merge, so exit with success status.
+        */
+       read_head_pointers();
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               err |= unresolve_one(arg);
+       }
+       return err;
+}
+
 int main(int argc, const char **argv)
 {
        int i, newfd, entries, has_errors = 0, line_termination = '\n';
@@ -478,6 +600,7 @@ int main(int argc, const char **argv)
        int read_from_stdin = 0;
        const char *prefix = setup_git_directory();
        int prefix_length = prefix ? strlen(prefix) : 0;
+       char set_executable_bit = 0;
 
        git_config(git_default_config);
 
@@ -544,8 +667,7 @@ int main(int argc, const char **argv)
                            !strcmp(path, "--chmod=+x")) {
                                if (argc <= i+1)
                                        die("git-update-index: %s <path>", path);
-                               if (chmod_path(path[8], argv[++i]))
-                                       die("git-update-index: %s cannot chmod %s", path, argv[i]);
+                               set_executable_bit = path[8];
                                continue;
                        }
                        if (!strcmp(path, "--assume-unchanged")) {
@@ -581,6 +703,12 @@ int main(int argc, const char **argv)
                                read_index_info(line_termination);
                                break;
                        }
+                       if (!strcmp(path, "--unresolve")) {
+                               has_errors = do_unresolve(argc - i, argv + i);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;
                                continue;
@@ -594,6 +722,8 @@ int main(int argc, const char **argv)
                        die("unknown option %s", path);
                }
                update_one(path, prefix, prefix_length);
+               if (set_executable_bit)
+                       chmod_path(set_executable_bit, path);
        }
        if (read_from_stdin) {
                struct strbuf buf;
@@ -608,10 +738,16 @@ int main(int argc, const char **argv)
                        else
                                path_name = buf.buf;
                        update_one(path_name, prefix, prefix_length);
+                       if (set_executable_bit) {
+                               const char *p = prefix_path(prefix, prefix_length, path_name);
+                               chmod_path(set_executable_bit, p);
+                       }
                        if (path_name != buf.buf)
                                free(path_name);
                }
        }
+
+ finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_index_file(&cache_file))