Merge branch 'jc/diff-stat'
authorJunio C Hamano <junkio@cox.net>
Sun, 1 Oct 2006 04:29:18 +0000 (21:29 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 1 Oct 2006 04:29:18 +0000 (21:29 -0700)
* jc/diff-stat:
diff --stat: ensure at least one '-' for deletions, and one '+' for additions
diff --stat=width[,name-width]: allow custom diffstat output width.
diff --stat: color output.
diff --stat: allow custom diffstat output width.

38 files changed:
.gitignore
Documentation/config.txt
Documentation/git-daemon.txt
Makefile
builtin-apply.c
builtin-grep.c
builtin-log.c
compat/inet_pton.c [new file with mode: 0644]
configure.ac
contrib/completion/git-completion.bash [new file with mode: 0755]
daemon.c
date.c
diff.c
diff.h
dir.c
git-branch.sh
git-checkout.sh
git-clone.sh
git-cvsexportcommit.perl
git-fetch.sh
git-ls-remote.sh
git-merge-recursive-old.py [new file with mode: 0755]
git-merge-recursive.py [deleted file]
git-merge.sh
git-rebase.sh
git-repack.sh
git-svnimport.perl
gitweb/gitweb.perl
grep.c
grep.h
http.c
interpolate.c
interpolate.h
t/t3200-branch.sh
t/t3700-add.sh
t/t6001-rev-list-graft.sh [new file with mode: 0755]
t/t7201-co.sh
t/test-lib.sh
index 284db5dffc6605a44b08b21a677434758c63cbe5..25eb4637a6f971a4a290ab823a937997c700bd7f 100644 (file)
@@ -65,6 +65,7 @@ git-merge-one-file
 git-merge-ours
 git-merge-recur
 git-merge-recursive
+git-merge-recursive-old
 git-merge-resolve
 git-merge-stupid
 git-mktag
index 98c1f3e2e32e71047d6f0f6cf982e207117c64e2..84e38911eeecd3f4e5f195ada61cb9bc831a4627 100644 (file)
@@ -202,6 +202,12 @@ http.lowSpeedLimit, http.lowSpeedTime::
        Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
        'GIT_HTTP_LOW_SPEED_TIME' environment variables.
 
+http.noEPSV::
+       A boolean which disables using of EPSV ftp command by curl.
+       This can helpful with some "poor" ftp servers which doesn't
+       support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
+       environment variable. Default is false (curl will use EPSV).
+
 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
index 51d7c94d7df0c15a74855f5ef910def64045681e..d562232e522fba78721328b1c76ce08490b0ee3e 100644 (file)
@@ -8,14 +8,15 @@ git-daemon - A really simple server for git repositories
 SYNOPSIS
 --------
 [verse]
-'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+'git-daemon' [--verbose] [--syslog] [--export-all]
              [--timeout=n] [--init-timeout=n] [--strict-paths]
              [--base-path=path] [--user-path | --user-path=path]
              [--interpolated-path=pathtemplate]
+             [--reuseaddr] [--detach] [--pid-file=file]
              [--enable=service] [--disable=service]
             [--allow-override=service] [--forbid-override=service]
-             [--reuseaddr] [--detach] [--pid-file=file]
-             [--user=user [--group=group]] [directory...]
+            [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]]
+            [directory...]
 
 DESCRIPTION
 -----------
@@ -54,8 +55,12 @@ OPTIONS
 --interpolated-path=pathtemplate::
        To support virtual hosting, an interpolated path template can be
        used to dynamically construct alternate paths.  The template
-       supports %H for the target hostname as supplied by the client,
+       supports %H for the target hostname as supplied by the client but
+       converted to all lowercase, %CH for the canonical hostname,
+       %IP for the server's IP address, %P for the port number,
        and %D for the absolute path of the named repository.
+       After interpolation, the path is validated against the directory
+       whitelist.
 
 --export-all::
        Allow pulling from all directories that look like GIT repositories
@@ -64,9 +69,17 @@ OPTIONS
 
 --inetd::
        Have the server run as an inetd service. Implies --syslog.
+       Incompatible with --port, --listen, --user and --group options.
+
+--listen=host_or_ipaddr::
+       Listen on an a specific IP address or hostname.  IP addresses can
+       be either an IPv4 address or an IPV6 address if supported.  If IPv6
+       is not supported, then --listen=hostname is also not supported and
+       --listen must be given an IPv4 address.
+       Incompatible with '--inetd' option.
 
---port::
-       Listen on an alternative port.
+--port=n::
+       Listen on an alternative port.  Incompatible with '--inetd' option.
 
 --init-timeout::
        Timeout between the moment the connection is established and the
@@ -182,6 +195,24 @@ clients, a symlink from `/software` into the appropriate
 default repository could be made as well.
 
 
+git-daemon as regular daemon for virtual hosts::
+       To set up `git-daemon` as a regular, non-inetd service that
+       handles repositories for multiple virtual hosts based on
+       their IP addresses, start the daemon like this:
++
+------------------------------------------------
+       git-daemon --verbose --export-all
+               --interpolated-path=/pub/%IP/%D
+               /pub/192.168.1.200/software
+               /pub/10.10.220.23/software
+------------------------------------------------
++
+In this example, the root-level directory `/pub` will contain
+a subdirectory for each virtual host IP address supported.
+Repositories can still be accessed by hostname though, assuming
+they correspond to these IP addresses.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
index 28091d6be00666b0cbfd2faaa558770c836695cd..e649d72f605c4b8a7d310464319d728eba52440f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -81,8 +81,6 @@ all:
 # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
 # a missing newline at the end of the file.
 #
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
-#
 # Define COLLISION_CHECK below if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
 # sufficient guarantee that no collisions between objects will ever happen.
@@ -174,7 +172,7 @@ SCRIPT_PERL = \
        git-send-email.perl git-svn.perl
 
 SCRIPT_PYTHON = \
-       git-merge-recursive.py
+       git-merge-recursive-old.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@ -199,7 +197,7 @@ PROGRAMS = \
        git-upload-pack$X git-verify-pack$X \
        git-pack-redundant$X git-var$X \
        git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \
-       git-merge-recur$X \
+       git-merge-recursive$X \
        $(EXTRA_PROGRAMS)
 
 # Empty...
@@ -524,6 +522,9 @@ endif
 ifdef NO_INET_NTOP
        LIB_OBJS += compat/inet_ntop.o
 endif
+ifdef NO_INET_PTON
+       LIB_OBJS += compat/inet_pton.o
+endif
 
 ifdef NO_ICONV
        ALL_CFLAGS += -DNO_ICONV
@@ -570,7 +571,8 @@ LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
+       git-merge-recur$X
 
 all:
        $(MAKE) -C templates
@@ -585,6 +587,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
 
 help.o: common-cmds.h
 
+git-merge-recur$X: git-merge-recursive$X
+       rm -f $@ && ln git-merge-recursive$X $@
+
 $(BUILT_INS): git$X
        rm -f $@ && ln git$X $@
 
@@ -722,11 +727,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-merge-recursive.o path-list.o: path-list.h
-git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS)
-
 $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
 $(DIFF_OBJS): diffcore.h
@@ -887,6 +887,7 @@ check-docs::
                case "$$v" in \
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
                git-merge-resolve | git-merge-stupid | git-merge-recur | \
+               git-merge-recursive-old | \
                git-ssh-pull | git-ssh-push ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
index 25e90d8d29a8f55b20a98425edccfe49cad4c8e2..de5f855266f6fcf3274c9648b02ab960a267bb90 100644 (file)
@@ -854,6 +854,49 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
        return -1;
 }
 
+static void check_whitespace(const char *line, int len)
+{
+       const char *err = "Adds trailing whitespace";
+       int seen_space = 0;
+       int i;
+
+       /*
+        * We know len is at least two, since we have a '+' and we
+        * checked that the last character was a '\n' before calling
+        * this function.  That is, an addition of an empty line would
+        * check the '+' here.  Sneaky...
+        */
+       if (isspace(line[len-2]))
+               goto error;
+
+       /*
+        * Make sure that there is no space followed by a tab in
+        * indentation.
+        */
+       err = "Space in indent is followed by a tab";
+       for (i = 1; i < len; i++) {
+               if (line[i] == '\t') {
+                       if (seen_space)
+                               goto error;
+               }
+               else if (line[i] == ' ')
+                       seen_space = 1;
+               else
+                       break;
+       }
+       return;
+
+ error:
+       whitespace_error++;
+       if (squelch_whitespace_errors &&
+           squelch_whitespace_errors < whitespace_error)
+               ;
+       else
+               fprintf(stderr, "%s.\n%s:%d:%.*s\n",
+                       err, patch_input_file, linenr, len-2, line+1);
+}
+
+
 /*
  * Parse a unified diff. Note that this really needs to parse each
  * fragment separately, since the only way to know the difference
@@ -904,25 +947,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                        trailing = 0;
                        break;
                case '+':
-                       /*
-                        * We know len is at least two, since we have a '+' and
-                        * we checked that the last character was a '\n' above.
-                        * That is, an addition of an empty line would check
-                        * the '+' here.  Sneaky...
-                        */
-                       if ((new_whitespace != nowarn_whitespace) &&
-                           isspace(line[len-2])) {
-                               whitespace_error++;
-                               if (squelch_whitespace_errors &&
-                                   squelch_whitespace_errors <
-                                   whitespace_error)
-                                       ;
-                               else {
-                                       fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
-                                               patch_input_file,
-                                               linenr, len-2, line+1);
-                               }
-                       }
+                       if (new_whitespace != nowarn_whitespace)
+                               check_whitespace(line, len);
                        added++;
                        newlines--;
                        trailing = 0;
@@ -1494,22 +1520,68 @@ static int apply_line(char *output, const char *patch, int plen)
 {
        /* plen is number of bytes to be copied from patch,
         * starting at patch+1 (patch[0] is '+').  Typically
-        * patch[plen] is '\n'.
+        * patch[plen] is '\n', unless this is the incomplete
+        * last line.
         */
+       int i;
        int add_nl_to_tail = 0;
-       if ((new_whitespace == strip_whitespace) &&
-           1 < plen && isspace(patch[plen-1])) {
+       int fixed = 0;
+       int last_tab_in_indent = -1;
+       int last_space_in_indent = -1;
+       int need_fix_leading_space = 0;
+       char *buf;
+
+       if ((new_whitespace != strip_whitespace) || !whitespace_error) {
+               memcpy(output, patch + 1, plen);
+               return plen;
+       }
+
+       if (1 < plen && isspace(patch[plen-1])) {
                if (patch[plen] == '\n')
                        add_nl_to_tail = 1;
                plen--;
                while (0 < plen && isspace(patch[plen]))
                        plen--;
-               applied_after_stripping++;
+               fixed = 1;
        }
-       memcpy(output, patch + 1, plen);
+
+       for (i = 1; i < plen; i++) {
+               char ch = patch[i];
+               if (ch == '\t') {
+                       last_tab_in_indent = i;
+                       if (0 <= last_space_in_indent)
+                               need_fix_leading_space = 1;
+               }
+               else if (ch == ' ')
+                       last_space_in_indent = i;
+               else
+                       break;
+       }
+
+       buf = output;
+       if (need_fix_leading_space) {
+               /* between patch[1..last_tab_in_indent] strip the
+                * funny spaces, updating them to tab as needed.
+                */
+               for (i = 1; i < last_tab_in_indent; i++, plen--) {
+                       char ch = patch[i];
+                       if (ch != ' ')
+                               *output++ = ch;
+                       else if ((i % 8) == 0)
+                               *output++ = '\t';
+               }
+               fixed = 1;
+               i = last_tab_in_indent;
+       }
+       else
+               i = 1;
+
+       memcpy(output, patch + i, plen);
        if (add_nl_to_tail)
                output[plen++] = '\n';
-       return plen;
+       if (fixed)
+               applied_after_stripping++;
+       return output + plen - buf;
 }
 
 static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof)
index 671878817386c72f16fe64cc62b6fa8daa62f102..4205e5d38dea6dee2d815a7434f87eacd089e4a9 100644 (file)
@@ -325,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
                else
                        hit |= grep_file(opt, ce->name);
        }
+       free_grep_patterns(opt);
        return hit;
 }
 
@@ -694,5 +695,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                if (grep_object(&opt, paths, real_obj, list.objects[i].name))
                        hit = 1;
        }
+       free_grep_patterns(&opt);
        return !hit;
 }
index fbc58bbcab1a992efb6ee359171d7ed6f0c52ab9..9d1ceae44c6a449d2329e897b9705a5b457187f0 100644 (file)
@@ -348,6 +348,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        if (!rev.diffopt.output_format)
                rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
 
+       if (!output_directory)
+               output_directory = prefix;
+
        if (output_directory) {
                if (use_stdout)
                        die("standard output, or directory, which one?");
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
new file mode 100644 (file)
index 0000000..5704e0d
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 1996-2001  Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef NS_INT16SZ
+#define NS_INT16SZ       2
+#endif
+
+#ifndef NS_INADDRSZ
+#define NS_INADDRSZ      4
+#endif
+
+#ifndef NS_IN6ADDRSZ
+#define NS_IN6ADDRSZ    16
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4.  sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+static int inet_pton4(const char *src, unsigned char *dst);
+static int inet_pton6(const char *src, unsigned char *dst);
+
+/* int
+ * inet_pton4(src, dst)
+ *      like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ *      1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ *      does not touch `dst' unless it's returning 1.
+ * author:
+ *      Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, unsigned char *dst)
+{
+        static const char digits[] = "0123456789";
+        int saw_digit, octets, ch;
+        unsigned char tmp[NS_INADDRSZ], *tp;
+
+        saw_digit = 0;
+        octets = 0;
+        *(tp = tmp) = 0;
+        while ((ch = *src++) != '\0') {
+                const char *pch;
+
+                if ((pch = strchr(digits, ch)) != NULL) {
+                        unsigned int new = *tp * 10 + (pch - digits);
+
+                        if (new > 255)
+                                return (0);
+                        *tp = new;
+                        if (! saw_digit) {
+                                if (++octets > 4)
+                                        return (0);
+                                saw_digit = 1;
+                        }
+                } else if (ch == '.' && saw_digit) {
+                        if (octets == 4)
+                                return (0);
+                        *++tp = 0;
+                        saw_digit = 0;
+                } else
+                        return (0);
+        }
+        if (octets < 4)
+                return (0);
+        memcpy(dst, tmp, NS_INADDRSZ);
+        return (1);
+}
+
+/* int
+ * inet_pton6(src, dst)
+ *      convert presentation level address to network order binary form.
+ * return:
+ *      1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ *      (1) does not touch `dst' unless it's returning 1.
+ *      (2) :: in a full address is silently ignored.
+ * credit:
+ *      inspired by Mark Andrews.
+ * author:
+ *      Paul Vixie, 1996.
+ */
+
+#ifndef NO_IPV6
+static int
+inet_pton6(const char *src, unsigned char *dst)
+{
+        static const char xdigits_l[] = "0123456789abcdef",
+                          xdigits_u[] = "0123456789ABCDEF";
+        unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+        const char *xdigits, *curtok;
+        int ch, saw_xdigit;
+        unsigned int val;
+
+        memset((tp = tmp), '\0', NS_IN6ADDRSZ);
+        endp = tp + NS_IN6ADDRSZ;
+        colonp = NULL;
+        /* Leading :: requires some special handling. */
+        if (*src == ':')
+                if (*++src != ':')
+                        return (0);
+        curtok = src;
+        saw_xdigit = 0;
+        val = 0;
+        while ((ch = *src++) != '\0') {
+                const char *pch;
+
+                if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+                        pch = strchr((xdigits = xdigits_u), ch);
+                if (pch != NULL) {
+                        val <<= 4;
+                        val |= (pch - xdigits);
+                        if (val > 0xffff)
+                                return (0);
+                        saw_xdigit = 1;
+                        continue;
+                }
+                if (ch == ':') {
+                        curtok = src;
+                        if (!saw_xdigit) {
+                                if (colonp)
+                                        return (0);
+                                colonp = tp;
+                                continue;
+                        }
+                        if (tp + NS_INT16SZ > endp)
+                                return (0);
+                        *tp++ = (unsigned char) (val >> 8) & 0xff;
+                        *tp++ = (unsigned char) val & 0xff;
+                        saw_xdigit = 0;
+                        val = 0;
+                        continue;
+                }
+                if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
+                    inet_pton4(curtok, tp) > 0) {
+                        tp += NS_INADDRSZ;
+                        saw_xdigit = 0;
+                        break;  /* '\0' was seen by inet_pton4(). */
+                }
+                return (0);
+        }
+        if (saw_xdigit) {
+                if (tp + NS_INT16SZ > endp)
+                        return (0);
+                *tp++ = (unsigned char) (val >> 8) & 0xff;
+                *tp++ = (unsigned char) val & 0xff;
+        }
+        if (colonp != NULL) {
+                /*
+                 * Since some memmove()'s erroneously fail to handle
+                 * overlapping regions, we'll do the shift by hand.
+                 */
+                const int n = tp - colonp;
+                int i;
+
+                for (i = 1; i <= n; i++) {
+                        endp[- i] = colonp[n - i];
+                        colonp[n - i] = 0;
+                }
+                tp = endp;
+        }
+        if (tp != endp)
+                return (0);
+        memcpy(dst, tmp, NS_IN6ADDRSZ);
+        return (1);
+}
+#endif
+
+/* int
+ * isc_net_pton(af, src, dst)
+ *      convert from presentation format (which usually means ASCII printable)
+ *      to network format (which is usually some kind of binary format).
+ * return:
+ *      1 if the address was valid for the specified address family
+ *      0 if the address wasn't valid (`dst' is untouched in this case)
+ *      -1 if some other error occurred (`dst' is untouched in this case, too)
+ * author:
+ *      Paul Vixie, 1996.
+ */
+int
+inet_pton(int af, const char *src, void *dst)
+{
+        switch (af) {
+        case AF_INET:
+                return (inet_pton4(src, dst));
+#ifndef NO_IPV6
+        case AF_INET6:
+                return (inet_pton6(src, dst));
+#endif
+        default:
+                errno = EAFNOSUPPORT;
+                return (-1);
+        }
+        /* NOTREACHED */
+}
index 511cac93d6cec11f3717e810d0c80b27646a5713..b1a5833b40b905ae07b27c414833394ef63b0c99 100644 (file)
@@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
 # Define PYTHON_PATH to provide path to Python.
 AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
 AS_HELP_STRING([--without-python], [don't use python scripts])],
@@ -100,7 +99,6 @@ AC_PROG_CC
 AC_CHECK_TOOL(AR, ar, :)
 AC_CHECK_PROGS(TAR, [gtar tar])
 #
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
 # Define PYTHON_PATH to provide path to Python.
 if test -z "$NO_PYTHON"; then
        if test -z "$PYTHON_PATH"; then
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
new file mode 100755 (executable)
index 0000000..d9cb17d
--- /dev/null
@@ -0,0 +1,324 @@
+#
+# bash completion support for core Git.
+#
+# Copyright (C) 2006 Shawn Pearce
+# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
+#
+# The contained completion routines provide support for completing:
+#
+#    *) local and remote branch names
+#    *) local and remote tag names
+#    *) .git/remotes file names
+#    *) git 'subcommands'
+#    *) tree paths within 'ref:path/to/file' expressions
+#
+# To use these routines:
+#
+#    1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
+#    2) Added the following line to your .bashrc:
+#        source ~/.git-completion.sh
+#
+
+__git_refs ()
+{
+       local cmd i is_hash=y
+       if [ -d "$1" ]; then
+               cmd=git-peek-remote
+       else
+               cmd=git-ls-remote
+       fi
+       for i in $($cmd "$1" 2>/dev/null); do
+               case "$is_hash,$i" in
+               y,*) is_hash=n ;;
+               n,*^{}) is_hash=y ;;
+               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
+               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+               n,*) is_hash=y; echo "$i" ;;
+               esac
+       done
+}
+
+__git_refs2 ()
+{
+       local cmd i is_hash=y
+       if [ -d "$1" ]; then
+               cmd=git-peek-remote
+       else
+               cmd=git-ls-remote
+       fi
+       for i in $($cmd "$1" 2>/dev/null); do
+               case "$is_hash,$i" in
+               y,*) is_hash=n ;;
+               n,*^{}) is_hash=y ;;
+               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
+               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
+               n,*) is_hash=y; echo "$i:$i" ;;
+               esac
+       done
+}
+
+__git_remotes ()
+{
+       local i REVERTGLOB=$(shopt -p nullglob)
+       shopt -s nullglob
+       for i in .git/remotes/*; do
+               echo ${i#.git/remotes/}
+       done
+       $REVERTGLOB
+}
+
+__git_complete_file ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       ?*:*)
+               local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
+               cur="$(echo "$cur" | sed 's,^.*:,,')"
+               case "$cur" in
+               ?*/*)
+                       pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
+                       cur="$(echo "$cur" | sed 's,^.*/,,')"
+                       ls="$ref:$pfx"
+                       pfx="$pfx/"
+                       ;;
+               *)
+                       ls="$ref"
+                       ;;
+           esac
+               COMPREPLY=($(compgen -P "$pfx" \
+                       -W "$(git-ls-tree "$ls" \
+                               | sed '/^100... blob /s,^.*     ,,
+                                      /^040000 tree /{
+                                          s,^.*        ,,
+                                          s,$,/,
+                                      }
+                                      s/^.*    //')" \
+                       -- "$cur"))
+               ;;
+       *)
+               COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+               ;;
+       esac
+}
+
+_git_branch ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur"))
+}
+
+_git_cat_file ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "${COMP_WORDS[0]},$COMP_CWORD" in
+       git-cat-file*,1)
+               COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+               ;;
+       git,2)
+               COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+               ;;
+       *)
+               __git_complete_file
+               ;;
+       esac
+}
+
+_git_checkout ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur"))
+}
+
+_git_diff ()
+{
+       __git_complete_file
+}
+
+_git_diff_tree ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur"))
+}
+
+_git_fetch ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+
+       case "${COMP_WORDS[0]},$COMP_CWORD" in
+       git-fetch*,1)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       git,2)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       *)
+               case "$cur" in
+               *:*)
+               cur=$(echo "$cur" | sed 's/^.*://')
+                       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+                       ;;
+               *)
+                       local remote
+                       case "${COMP_WORDS[0]}" in
+                       git-fetch) remote="${COMP_WORDS[1]}" ;;
+                       git)       remote="${COMP_WORDS[2]}" ;;
+                       esac
+                       COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur"))
+                       ;;
+               esac
+               ;;
+       esac
+}
+
+_git_ls_remote ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+}
+
+_git_ls_tree ()
+{
+       __git_complete_file
+}
+
+_git_log ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       *..*)
+               local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
+               cur=$(echo "$cur" | sed 's/^.*\.\.//')
+               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+               ;;
+       *)
+               COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+               ;;
+       esac
+}
+
+_git_merge_base ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git_pull ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+
+       case "${COMP_WORDS[0]},$COMP_CWORD" in
+       git-pull*,1)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       git,2)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       *)
+               local remote
+               case "${COMP_WORDS[0]}" in
+               git-pull)  remote="${COMP_WORDS[1]}" ;;
+               git)       remote="${COMP_WORDS[2]}" ;;
+               esac
+               COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+               ;;
+       esac
+}
+
+_git_push ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+
+       case "${COMP_WORDS[0]},$COMP_CWORD" in
+       git-push*,1)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       git,2)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               ;;
+       *)
+               case "$cur" in
+               *:*)
+                       local remote
+                       case "${COMP_WORDS[0]}" in
+                       git-push)  remote="${COMP_WORDS[1]}" ;;
+                       git)       remote="${COMP_WORDS[2]}" ;;
+                       esac
+               cur=$(echo "$cur" | sed 's/^.*://')
+                       COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+                       ;;
+               *)
+                       COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+                       ;;
+               esac
+               ;;
+       esac
+}
+
+_git_show ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git ()
+{
+       if [ $COMP_CWORD = 1 ]; then
+               COMPREPLY=($(compgen \
+                       -W "--version $(git help -a|egrep '^ ')" \
+                       -- "${COMP_WORDS[COMP_CWORD]}"))
+       else
+               case "${COMP_WORDS[1]}" in
+               branch)      _git_branch ;;
+               cat-file)    _git_cat_file ;;
+               checkout)    _git_checkout ;;
+               diff)        _git_diff ;;
+               diff-tree)   _git_diff_tree ;;
+               fetch)       _git_fetch ;;
+               log)         _git_log ;;
+               ls-remote)   _git_ls_remote ;;
+               ls-tree)     _git_ls_tree ;;
+               pull)        _git_pull ;;
+               push)        _git_push ;;
+               show)        _git_show ;;
+               show-branch) _git_log ;;
+               whatchanged) _git_log ;;
+               *)           COMPREPLY=() ;;
+               esac
+       fi
+}
+
+_gitk ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur"))
+}
+
+complete -o default -o nospace -F _git git
+complete -o default            -F _gitk gitk
+complete -o default            -F _git_branch git-branch
+complete -o default -o nospace -F _git_cat_file git-cat-file
+complete -o default            -F _git_checkout git-checkout
+complete -o default -o nospace -F _git_diff git-diff
+complete -o default            -F _git_diff_tree git-diff-tree
+complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_log git-log
+complete -o default            -F _git_ls_remote git-ls-remote
+complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default            -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_pull git-pull
+complete -o default -o nospace -F _git_push git-push
+complete -o default            -F _git_show git-show
+complete -o default -o nospace -F _git_log git-whatchanged
+
+# The following are necessary only for Cygwin, and only are needed
+# when the user has tab-completed the executable name and consequently
+# included the '.exe' suffix.
+#
+complete -o default -o nospace -F _git_cat_file git-cat-file.exe
+complete -o default -o nospace -F _git_diff git-diff.exe
+complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_log git-log.exe
+complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
+complete -o default            -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_log git-whatchanged.exe
index eb4f3f1e9f08cd3a95f429a477495cf4151ddd86..ad8492873ea1db63d56c24bec6174c4682105433 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -9,23 +9,30 @@
 #include <syslog.h>
 #include <pwd.h>
 #include <grp.h>
+#include <limits.h>
 #include "pkt-line.h"
 #include "cache.h"
 #include "exec_cmd.h"
 #include "interpolate.h"
 
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
+
 static int log_syslog;
 static int verbose;
 static int reuseaddr;
 
 static const char daemon_usage[] =
-"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+"git-daemon [--verbose] [--syslog] [--export-all]\n"
 "           [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
 "           [--base-path=path] [--user-path | --user-path=path]\n"
 "           [--interpolated-path=path]\n"
 "           [--reuseaddr] [--detach] [--pid-file=file]\n"
 "           [--[enable|disable|allow-override|forbid-override]=service]\n"
-"           [--user=user [[--group=group]] [directory...]";
+"           [--inetd | [--listen=host_or_ipaddr] [--port=n]\n"
+"                      [--user=user [--group=group]]\n"
+"           [directory...]";
 
 /* List of acceptable pathname prefixes */
 static char **ok_paths;
@@ -56,13 +63,19 @@ static unsigned int init_timeout;
  * Feel free to make dynamic as needed.
  */
 #define INTERP_SLOT_HOST       (0)
-#define INTERP_SLOT_DIR                (1)
-#define INTERP_SLOT_PERCENT    (2)
+#define INTERP_SLOT_CANON_HOST (1)
+#define INTERP_SLOT_IP         (2)
+#define INTERP_SLOT_PORT       (3)
+#define INTERP_SLOT_DIR                (4)
+#define INTERP_SLOT_PERCENT    (5)
 
 static struct interp interp_table[] = {
        { "%H", 0},
+       { "%CH", 0},
+       { "%IP", 0},
+       { "%P", 0},
        { "%D", 0},
-       { "%%", "%"},
+       { "%%", 0},
 };
 
 
@@ -396,7 +409,11 @@ static void make_service_overridable(const char *name, int ena) {
        die("No such service %s", name);
 }
 
-static void parse_extra_args(char *extra_args, int buflen)
+/*
+ * Separate the "extra args" information as supplied by the client connection.
+ * Any resulting data is squirrelled away in the given interpolation table.
+ */
+static void parse_extra_args(struct interp *table, char *extra_args, int buflen)
 {
        char *val;
        int vallen;
@@ -408,16 +425,88 @@ static void parse_extra_args(char *extra_args, int buflen)
                        val = extra_args + 5;
                        vallen = strlen(val) + 1;
                        if (*val) {
-                               char *save = xmalloc(vallen);
-                               interp_table[INTERP_SLOT_HOST].value = save;
-                               strlcpy(save, val, vallen);
+                               /* Split <host>:<port> at colon. */
+                               char *host = val;
+                               char *port = strrchr(host, ':');
+                               if (port) {
+                                       *port = 0;
+                                       port++;
+                                       interp_set_entry(table, INTERP_SLOT_PORT, port);
+                               }
+                               interp_set_entry(table, INTERP_SLOT_HOST, host);
                        }
+
                        /* On to the next one */
                        extra_args = val + vallen;
                }
        }
 }
 
+void fill_in_extra_table_entries(struct interp *itable)
+{
+       char *hp;
+
+       /*
+        * Replace literal host with lowercase-ized hostname.
+        */
+       hp = interp_table[INTERP_SLOT_HOST].value;
+       for ( ; *hp; hp++)
+               *hp = tolower(*hp);
+
+       /*
+        * Locate canonical hostname and its IP address.
+        */
+#ifndef NO_IPV6
+       {
+               struct addrinfo hints;
+               struct addrinfo *ai, *ai0;
+               int gai;
+               static char addrbuf[HOST_NAME_MAX + 1];
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_flags = AI_CANONNAME;
+
+               gai = getaddrinfo(interp_table[INTERP_SLOT_HOST].value, 0, &hints, &ai0);
+               if (!gai) {
+                       for (ai = ai0; ai; ai = ai->ai_next) {
+                               struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
+
+                               inet_ntop(AF_INET, &sin_addr->sin_addr,
+                                         addrbuf, sizeof(addrbuf));
+                               interp_set_entry(interp_table,
+                                                INTERP_SLOT_CANON_HOST, ai->ai_canonname);
+                               interp_set_entry(interp_table,
+                                                INTERP_SLOT_IP, addrbuf);
+                               break;
+                       }
+                       freeaddrinfo(ai0);
+               }
+       }
+#else
+       {
+               struct hostent *hent;
+               struct sockaddr_in sa;
+               char **ap;
+               static char addrbuf[HOST_NAME_MAX + 1];
+
+               hent = gethostbyname(interp_table[INTERP_SLOT_HOST].value);
+
+               ap = hent->h_addr_list;
+               memset(&sa, 0, sizeof sa);
+               sa.sin_family = hent->h_addrtype;
+               sa.sin_port = htons(0);
+               memcpy(&sa.sin_addr, *ap, hent->h_length);
+
+               inet_ntop(hent->h_addrtype, &sa.sin_addr,
+                         addrbuf, sizeof(addrbuf));
+
+               interp_set_entry(interp_table, INTERP_SLOT_CANON_HOST, hent->h_name);
+               interp_set_entry(interp_table, INTERP_SLOT_IP, addrbuf);
+       }
+#endif
+}
+
+
 static int execute(struct sockaddr *addr)
 {
        static char line[1000];
@@ -458,8 +547,16 @@ static int execute(struct sockaddr *addr)
        if (len && line[len-1] == '\n')
                line[--len] = 0;
 
-       if (len != pktlen)
-           parse_extra_args(line + len + 1, pktlen - len - 1);
+       /*
+        * Initialize the path interpolation table for this connection.
+        */
+       interp_clear_table(interp_table, ARRAY_SIZE(interp_table));
+       interp_set_entry(interp_table, INTERP_SLOT_PERCENT, "%");
+
+       if (len != pktlen) {
+           parse_extra_args(interp_table, line + len + 1, pktlen - len - 1);
+           fill_in_extra_table_entries(interp_table);
+       }
 
        for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
                struct daemon_service *s = &(daemon_service[i]);
@@ -467,7 +564,12 @@ static int execute(struct sockaddr *addr)
                if (!strncmp("git-", line, 4) &&
                    !strncmp(s->name, line + 4, namelen) &&
                    line[namelen + 4] == ' ') {
-                       interp_table[INTERP_SLOT_DIR].value = line+namelen+5;
+                       /*
+                        * Note: The directory here is probably context sensitive,
+                        * and might depend on the actual service being performed.
+                        */
+                       interp_set_entry(interp_table,
+                                        INTERP_SLOT_DIR, line + namelen + 5);
                        return run_service(interp_table, s);
                }
        }
@@ -663,23 +765,22 @@ static int set_reuse_addr(int sockfd)
 
 #ifndef NO_IPV6
 
-static int socksetup(int port, int **socklist_p)
+static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
 {
        int socknum = 0, *socklist = NULL;
        int maxfd = -1;
        char pbuf[NI_MAXSERV];
-
        struct addrinfo hints, *ai0, *ai;
        int gai;
 
-       sprintf(pbuf, "%d", port);
+       sprintf(pbuf, "%d", listen_port);
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_PASSIVE;
 
-       gai = getaddrinfo(NULL, pbuf, &hints, &ai0);
+       gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
        if (gai)
                die("getaddrinfo() failed: %s\n", gai_strerror(gai));
 
@@ -733,20 +834,27 @@ static int socksetup(int port, int **socklist_p)
 
 #else /* NO_IPV6 */
 
-static int socksetup(int port, int **socklist_p)
+static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
 {
        struct sockaddr_in sin;
        int sockfd;
 
+       memset(&sin, 0, sizeof sin);
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(listen_port);
+
+       if (listen_addr) {
+               /* Well, host better be an IP address here. */
+               if (inet_pton(AF_INET, listen_addr, &sin.sin_addr.s_addr) <= 0)
+                       return 0;
+       } else {
+               sin.sin_addr.s_addr = htonl(INADDR_ANY);
+       }
+
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
                return 0;
 
-       memset(&sin, 0, sizeof sin);
-       sin.sin_family = AF_INET;
-       sin.sin_addr.s_addr = htonl(INADDR_ANY);
-       sin.sin_port = htons(port);
-
        if (set_reuse_addr(sockfd)) {
                close(sockfd);
                return 0;
@@ -855,13 +963,14 @@ static void store_pid(const char *path)
        fclose(f);
 }
 
-static int serve(int port, struct passwd *pass, gid_t gid)
+static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
 {
        int socknum, *socklist;
 
-       socknum = socksetup(port, &socklist);
+       socknum = socksetup(listen_addr, listen_port, &socklist);
        if (socknum == 0)
-               die("unable to allocate any listen sockets on port %u", port);
+               die("unable to allocate any listen sockets on host %s port %u",
+                   listen_addr, listen_port);
 
        if (pass && gid &&
            (initgroups(pass->pw_name, gid) || setgid (gid) ||
@@ -873,7 +982,8 @@ static int serve(int port, struct passwd *pass, gid_t gid)
 
 int main(int argc, char **argv)
 {
-       int port = DEFAULT_GIT_PORT;
+       int listen_port = 0;
+       char *listen_addr = NULL;
        int inetd_mode = 0;
        const char *pid_file = NULL, *user_name = NULL, *group_name = NULL;
        int detach = 0;
@@ -890,12 +1000,20 @@ int main(int argc, char **argv)
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
+               if (!strncmp(arg, "--listen=", 9)) {
+                   char *p = arg + 9;
+                   char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1);
+                   while (*p)
+                       *ph++ = tolower(*p++);
+                   *ph = 0;
+                   continue;
+               }
                if (!strncmp(arg, "--port=", 7)) {
                        char *end;
                        unsigned long n;
                        n = strtoul(arg+7, &end, 0);
                        if (arg[7] && !*end) {
-                               port = n;
+                               listen_port = n;
                                continue;
                        }
                }
@@ -995,6 +1113,11 @@ int main(int argc, char **argv)
        if (inetd_mode && (group_name || user_name))
                die("--user and --group are incompatible with --inetd");
 
+       if (inetd_mode && (listen_port || listen_addr))
+               die("--listen= and --port= are incompatible with --inetd");
+       else if (listen_port == 0)
+               listen_port = DEFAULT_GIT_PORT;
+
        if (group_name && !user_name)
                die("--group supplied without --user");
 
@@ -1043,5 +1166,5 @@ int main(int argc, char **argv)
        if (pid_file)
                store_pid(pid_file);
 
-       return serve(port, pass, gid);
+       return serve(listen_addr, listen_port, pass, gid);
 }
diff --git a/date.c b/date.c
index e387dcd3976d8e12afdce5d7600ca0022f4ca835..18259227321f47988386c2bd70888b3c967d8ab6 100644 (file)
--- a/date.c
+++ b/date.c
@@ -256,8 +256,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
        }
 
        if (match_string(date, "PM") == 2) {
-               if (tm->tm_hour > 0 && tm->tm_hour < 12)
-                       tm->tm_hour += 12;
+               tm->tm_hour = (tm->tm_hour % 12) + 12;
+               return 2;
+       }
+
+       if (match_string(date, "AM") == 2) {
+               tm->tm_hour = (tm->tm_hour % 12) + 0;
                return 2;
        }
 
@@ -598,6 +602,34 @@ static void date_tea(struct tm *tm, int *num)
        date_time(tm, 17);
 }
 
+static void date_pm(struct tm *tm, int *num)
+{
+       int hour, n = *num;
+       *num = 0;
+
+       hour = tm->tm_hour;
+       if (n) {
+               hour = n;
+               tm->tm_min = 0;
+               tm->tm_sec = 0;
+       }
+       tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, int *num)
+{
+       int hour, n = *num;
+       *num = 0;
+
+       hour = tm->tm_hour;
+       if (n) {
+               hour = n;
+               tm->tm_min = 0;
+               tm->tm_sec = 0;
+       }
+       tm->tm_hour = (hour % 12);
+}
+
 static const struct special {
        const char *name;
        void (*fn)(struct tm *, int *);
@@ -606,6 +638,8 @@ static const struct special {
        { "noon", date_noon },
        { "midnight", date_midnight },
        { "tea", date_tea },
+       { "PM", date_pm },
+       { "AM", date_am },
        { NULL }
 };
 
@@ -712,6 +746,27 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
        return end;
 }
 
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+       char *end;
+       unsigned long number = strtoul(date, &end, 10);
+
+       switch (*end) {
+       case ':':
+       case '.':
+       case '/':
+       case '-':
+               if (isdigit(end[1])) {
+                       int match = match_multi_number(number, *end, date, end, tm);
+                       if (match)
+                               return date + match;
+               }
+       }
+
+       *num = number;
+       return end;
+}
+
 unsigned long approxidate(const char *date)
 {
        int number = 0;
@@ -731,9 +786,7 @@ unsigned long approxidate(const char *date)
                        break;
                date++;
                if (isdigit(c)) {
-                       char *end;
-                       number = strtoul(date-1, &end, 10);
-                       date = end;
+                       date = approxidate_digit(date-1, &tm, &number);
                        continue;
                }
                if (isalpha(c))
diff --git a/diff.c b/diff.c
index 2df085f3c67afd96b5fcea5ade6f45c5e330198b..fb8243261cb3cc9165dbe990586d3865fad4ee61 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -20,12 +20,13 @@ static int diff_use_color_default;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
-       "",             /* normal */
-       "\033[1m",      /* bold */
-       "\033[36m",     /* cyan */
-       "\033[31m",     /* red */
-       "\033[32m",     /* green */
-       "\033[33m"      /* yellow */
+       "",             /* PLAIN (normal) */
+       "\033[1m",      /* METAINFO (bold) */
+       "\033[36m",     /* FRAGINFO (cyan) */
+       "\033[31m",     /* OLD (red) */
+       "\033[32m",     /* NEW (green) */
+       "\033[33m",     /* COMMIT (yellow) */
+       "\033[41m",     /* WHITESPACE (red background) */
 };
 
 static int parse_diff_color_slot(const char *var, int ofs)
@@ -42,6 +43,8 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_FILE_NEW;
        if (!strcasecmp(var+ofs, "commit"))
                return DIFF_COMMIT;
+       if (!strcasecmp(var+ofs, "whitespace"))
+               return DIFF_WHITESPACE;
        die("bad config variable '%s'", var);
 }
 
@@ -205,7 +208,7 @@ static void emit_rewrite_diff(const char *name_a,
        diff_populate_filespec(two, 0);
        lc_a = count_lines(one->data, one->size);
        lc_b = count_lines(two->data, two->size);
-       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+       printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
        print_line_count(lc_a);
        printf(" +");
        print_line_count(lc_b);
@@ -383,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
        return "";
 }
 
+static void emit_line(const char *set, const char *reset, const char *line, int len)
+{
+       if (len > 0 && line[len-1] == '\n')
+               len--;
+       fputs(set, stdout);
+       fwrite(line, len, 1, stdout);
+       puts(reset);
+}
+
+static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+{
+       int col0 = ecbdata->nparents;
+       int last_tab_in_indent = -1;
+       int last_space_in_indent = -1;
+       int i;
+       int tail = len;
+       int need_highlight_leading_space = 0;
+       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+       if (!*ws) {
+               emit_line(set, reset, line, len);
+               return;
+       }
+
+       /* The line is a newly added line.  Does it have funny leading
+        * whitespaces?  In indent, SP should never precede a TAB.
+        */
+       for (i = col0; i < len; i++) {
+               if (line[i] == '\t') {
+                       last_tab_in_indent = i;
+                       if (0 <= last_space_in_indent)
+                               need_highlight_leading_space = 1;
+               }
+               else if (line[i] == ' ')
+                       last_space_in_indent = i;
+               else
+                       break;
+       }
+       fputs(set, stdout);
+       fwrite(line, col0, 1, stdout);
+       fputs(reset, stdout);
+       if (((i == len) || line[i] == '\n') && i != col0) {
+               /* The whole line was indent */
+               emit_line(ws, reset, line + col0, len - col0);
+               return;
+       }
+       i = col0;
+       if (need_highlight_leading_space) {
+               while (i < last_tab_in_indent) {
+                       if (line[i] == ' ') {
+                               fputs(ws, stdout);
+                               putchar(' ');
+                               fputs(reset, stdout);
+                       }
+                       else
+                               putchar(line[i]);
+                       i++;
+               }
+       }
+       tail = len - 1;
+       if (line[tail] == '\n' && i < tail)
+               tail--;
+       while (i < tail) {
+               if (!isspace(line[tail]))
+                       break;
+               tail--;
+       }
+       if ((i < tail && line[tail + 1] != '\n')) {
+               /* This has whitespace between tail+1..len */
+               fputs(set, stdout);
+               fwrite(line + i, tail - i + 1, 1, stdout);
+               fputs(reset, stdout);
+               emit_line(ws, reset, line + tail + 1, len - tail - 1);
+       }
+       else
+               emit_line(set, reset, line + i, len - i);
+}
+
 static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        int i;
+       int color;
        struct emit_callback *ecbdata = priv;
        const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
@@ -403,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                ;
        if (2 <= i && i < len && line[i] == ' ') {
                ecbdata->nparents = i - 1;
-               set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+               emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
+                         reset, line, len);
+               return;
        }
-       else if (len < ecbdata->nparents)
+
+       if (len < ecbdata->nparents) {
                set = reset;
-       else {
-               int nparents = ecbdata->nparents;
-               int color = DIFF_PLAIN;
-               if (ecbdata->diff_words && nparents != 1)
-                       /* fall back to normal diff */
-                       free_diff_words_data(ecbdata);
-               if (ecbdata->diff_words) {
-                       if (line[0] == '-') {
-                               diff_words_append(line, len,
-                                               &ecbdata->diff_words->minus);
-                               return;
-                       } else if (line[0] == '+') {
-                               diff_words_append(line, len,
-                                               &ecbdata->diff_words->plus);
-                               return;
-                       }
-                       if (ecbdata->diff_words->minus.text.size ||
-                                       ecbdata->diff_words->plus.text.size)
-                               diff_words_show(ecbdata->diff_words);
-                       line++;
-                       len--;
-               } else
-                       for (i = 0; i < nparents && len; i++) {
-                               if (line[i] == '-')
-                                       color = DIFF_FILE_OLD;
-                               else if (line[i] == '+')
-                                       color = DIFF_FILE_NEW;
-                       }
-               set = diff_get_color(ecbdata->color_diff, color);
+               emit_line(reset, reset, line, len);
+               return;
        }
-       if (len > 0 && line[len-1] == '\n')
+
+       color = DIFF_PLAIN;
+       if (ecbdata->diff_words && ecbdata->nparents != 1)
+               /* fall back to normal diff */
+               free_diff_words_data(ecbdata);
+       if (ecbdata->diff_words) {
+               if (line[0] == '-') {
+                       diff_words_append(line, len,
+                                         &ecbdata->diff_words->minus);
+                       return;
+               } else if (line[0] == '+') {
+                       diff_words_append(line, len,
+                                         &ecbdata->diff_words->plus);
+                       return;
+               }
+               if (ecbdata->diff_words->minus.text.size ||
+                   ecbdata->diff_words->plus.text.size)
+                       diff_words_show(ecbdata->diff_words);
+               line++;
                len--;
-       fputs (set, stdout);
-       fwrite (line, len, 1, stdout);
-       puts (reset);
+               emit_line(set, reset, line, len);
+               return;
+       }
+       for (i = 0; i < ecbdata->nparents && len; i++) {
+               if (line[i] == '-')
+                       color = DIFF_FILE_OLD;
+               else if (line[i] == '+')
+                       color = DIFF_FILE_NEW;
+       }
+
+       if (color != DIFF_FILE_NEW) {
+               emit_line(diff_get_color(ecbdata->color_diff, color),
+                         reset, line, len);
+               return;
+       }
+       emit_add_line(reset, ecbdata, line, len);
 }
 
 static char *pprint_rename(const char *a, const char *b)
diff --git a/diff.h b/diff.h
index e06d0f41885fa6d224cb1904e6ee06dbc3324e12..b48c9914e7e3802d17870bbc0fd68c454fded61c 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -89,6 +89,7 @@ enum color_diff {
        DIFF_FILE_OLD = 4,
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
+       DIFF_WHITESPACE = 7,
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 
diff --git a/dir.c b/dir.c
index e2f472ba7f5d3e5146f110dd3eaae9e5494854e2..96389b32e66af24896220e4738cd93bf2ac75704 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -283,7 +283,7 @@ static int dir_exists(const char *dirname, int len)
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
-static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only)
 {
        DIR *fdir = opendir(path);
        int contents = 0;
@@ -314,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
 
                        switch (DTYPE(de)) {
                        struct stat st;
-                       int subdir, rewind_base;
                        default:
                                continue;
                        case DT_UNKNOWN:
@@ -328,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
                                len++;
-                               rewind_base = dir->nr;
-                               subdir = read_directory_recursive(dir, fullname, fullname,
-                                                       baselen + len);
                                if (dir->show_other_directories &&
-                                   (subdir || !dir->hide_empty_directories) &&
                                    !dir_exists(fullname, baselen + len)) {
-                                       /* Rewind the read subdirectory */
-                                       while (dir->nr > rewind_base)
-                                               free(dir->entries[--dir->nr]);
+                                       if (dir->hide_empty_directories &&
+                                           !read_directory_recursive(dir,
+                                                   fullname, fullname,
+                                                   baselen + len, 1))
+                                               continue;
                                        break;
                                }
-                               contents += subdir;
+
+                               contents += read_directory_recursive(dir,
+                                       fullname, fullname, baselen + len, 0);
                                continue;
                        case DT_REG:
                        case DT_LNK:
                                break;
                        }
-                       add_name(dir, fullname, baselen + len);
                        contents++;
+                       if (check_only)
+                               goto exit_early;
+                       else
+                               add_name(dir, fullname, baselen + len);
                }
+exit_early:
                closedir(fdir);
 
                pop_exclude_per_directory(dir, exclude_stk);
@@ -393,7 +396,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
                }
        }
 
-       read_directory_recursive(dir, path, base, baselen);
+       read_directory_recursive(dir, path, base, baselen, 0);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
index e0501ec23f5c40134af0e6211cf1ba28e69e1d5b..4f31903d63662db7d49d3d257e328004ca2958f7 100755 (executable)
@@ -112,6 +112,16 @@ rev=$(git-rev-parse --verify "$head") || exit
 git-check-ref-format "heads/$branchname" ||
        die "we do not like '$branchname' as a branch name."
 
+if [ -d "$GIT_DIR/refs/heads/$branchname" ]
+then
+       for refdir in `cd "$GIT_DIR" && \
+               find "refs/heads/$branchname" -type d | sort -r`
+       do
+               rmdir "$GIT_DIR/$refdir" || \
+                   die "Could not delete '$refdir', there may still be a ref there."
+       done
+fi
+
 if [ -e "$GIT_DIR/refs/heads/$branchname" ]
 then
        if test '' = "$force"
index 580a9e8a233f0a94c84792eba7c3dc653fccf3a0..dd477245fb3703402b950cbc1378b35ce379d631 100755 (executable)
@@ -4,8 +4,8 @@ USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
 SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
 
-old=$(git-rev-parse HEAD)
 old_name=HEAD
+old=$(git-rev-parse --verify $old_name 2>/dev/null)
 new=
 new_name=
 force=
@@ -139,6 +139,13 @@ fi
        die "git checkout: to checkout the requested commit you need to specify 
               a name for a new branch which is created and switched to"
 
+if [ "X$old" = X ]
+then
+       echo "warning: You do not appear to currently be on a branch." >&2
+       echo "warning: Forcing checkout of $new_name." >&2
+       force=1
+fi
+
 if [ "$force" ]
 then
     git-read-tree --reset -u $new
index e1b3bf382f7683ec429e99eae13a3abc3b80e2b1..3998c55cef3658eda18817d513a5bd5003c30e89 100755 (executable)
@@ -31,6 +31,10 @@ clone_dumb_http () {
        cd "$2" &&
        clone_tmp="$GIT_DIR/clone-tmp" &&
        mkdir -p "$clone_tmp" || exit 1
+       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git-repo-config --bool http.noEPSV`" = true ]; then
+               curl_extra_args="${curl_extra_args} --disable-epsv"
+       fi
        http_fetch "$1/info/refs" "$clone_tmp/refs" || {
                echo >&2 "Cannot get remote repository information.
 Perhaps git-update-server-info needs to be run there?"
index 99b3dc392afdd5ffb3e2aa8f6fe6b484c2e2f1fe..5e23851f8cf866112baf3e76973a8ca649d5c105 100755 (executable)
     if ($fields[4] eq 'M') {
        push @mfiles, $fields[5];
     }
-    if ($fields[4] eq 'R') {
+    if ($fields[4] eq 'D') {
        push @dfiles, $fields[5];
     }
 }
index 50ad101e89500af01edde00b047b91fc16fdcb81..f1522bd49a2fc1c3106c6b2ed0a38af0a9ff72de 100755 (executable)
@@ -257,6 +257,7 @@ fi
 fetch_main () {
   reflist="$1"
   refs=
+  rref=
 
   for ref in $reflist
   do
@@ -289,6 +290,10 @@ fetch_main () {
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
+         if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git-repo-config --bool http.noEPSV`" = true ]; then
+             noepsv_opt="--disable-epsv"
+         fi
          max_depth=5
          depth=0
          head="ref: $remote_name"
@@ -300,7 +305,7 @@ fetch_main () {
              $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
              print "$u";
          ' "$head")
-           head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+           head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
            depth=$( expr \( $depth + 1 \) )
          done
          expr "z$head" : "z$_x40\$" >/dev/null ||
index 2c0b52122f1c3ee7278912966b1584dadd8b5aea..0f88953f299c1fd1243c3d5d6ae1e71d56e842a8 100755 (executable)
@@ -53,6 +53,10 @@ http://* | https://* | ftp://* )
         if [ -n "$GIT_SSL_NO_VERIFY" ]; then
             curl_extra_args="-k"
         fi
+       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git-repo-config --bool http.noEPSV`" = true ]; then
+               curl_extra_args="${curl_extra_args} --disable-epsv"
+       fi
        curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
                echo "failed    slurping"
        ;;
diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py
new file mode 100755 (executable)
index 0000000..4039435
--- /dev/null
@@ -0,0 +1,944 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2005 Fredrik Kuivinen
+#
+
+import sys
+sys.path.append('''@@GIT_PYTHON_PATH@@''')
+
+import math, random, os, re, signal, tempfile, stat, errno, traceback
+from heapq import heappush, heappop
+from sets import Set
+
+from gitMergeCommon import *
+
+outputIndent = 0
+def output(*args):
+    sys.stdout.write('  '*outputIndent)
+    printList(args)
+
+originalIndexFile = os.environ.get('GIT_INDEX_FILE',
+                                   os.environ.get('GIT_DIR', '.git') + '/index')
+temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
+                     '/merge-recursive-tmp-index'
+def setupIndex(temporary):
+    try:
+        os.unlink(temporaryIndexFile)
+    except OSError:
+        pass
+    if temporary:
+        newIndex = temporaryIndexFile
+    else:
+        newIndex = originalIndexFile
+    os.environ['GIT_INDEX_FILE'] = newIndex
+
+# This is a global variable which is used in a number of places but
+# only written to in the 'merge' function.
+
+# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
+#                       don't update the working directory.
+#              False => Leave unmerged entries in the cache and update
+#                       the working directory.
+
+cacheOnly = False
+
+# The entry point to the merge code
+# ---------------------------------
+
+def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
+    '''Merge the commits h1 and h2, return the resulting virtual
+    commit object and a flag indicating the cleanness of the merge.'''
+    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
+
+    global outputIndent
+
+    output('Merging:')
+    output(h1)
+    output(h2)
+    sys.stdout.flush()
+
+    if ancestor:
+        ca = [ancestor]
+    else:
+        assert(isinstance(graph, Graph))
+        ca = getCommonAncestors(graph, h1, h2)
+    output('found', len(ca), 'common ancestor(s):')
+    for x in ca:
+        output(x)
+    sys.stdout.flush()
+
+    mergedCA = ca[0]
+    for h in ca[1:]:
+        outputIndent = callDepth+1
+        [mergedCA, dummy] = merge(mergedCA, h,
+                                  'Temporary merge branch 1',
+                                  'Temporary merge branch 2',
+                                  graph, callDepth+1)
+        outputIndent = callDepth
+        assert(isinstance(mergedCA, Commit))
+
+    global cacheOnly
+    if callDepth == 0:
+        setupIndex(False)
+        cacheOnly = False
+    else:
+        setupIndex(True)
+        runProgram(['git-read-tree', h1.tree()])
+        cacheOnly = True
+
+    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
+                                 branch1Name, branch2Name)
+
+    if graph and (clean or cacheOnly):
+        res = Commit(None, [h1, h2], tree=shaRes)
+        graph.addNode(res)
+    else:
+        res = None
+
+    return [res, clean]
+
+getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
+def getFilesAndDirs(tree):
+    files = Set()
+    dirs = Set()
+    out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
+    for l in out.split('\0'):
+        m = getFilesRE.match(l)
+        if m:
+            if m.group(2) == 'tree':
+                dirs.add(m.group(4))
+            elif m.group(2) == 'blob':
+                files.add(m.group(4))
+
+    return [files, dirs]
+
+# Those two global variables are used in a number of places but only
+# written to in 'mergeTrees' and 'uniquePath'. They keep track of
+# every file and directory in the two branches that are about to be
+# merged.
+currentFileSet = None
+currentDirectorySet = None
+
+def mergeTrees(head, merge, common, branch1Name, branch2Name):
+    '''Merge the trees 'head' and 'merge' with the common ancestor
+    'common'. The name of the head branch is 'branch1Name' and the name of
+    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
+    where tree is the resulting tree and cleanMerge is True iff the
+    merge was clean.'''
+    
+    assert(isSha(head) and isSha(merge) and isSha(common))
+
+    if common == merge:
+        output('Already uptodate!')
+        return [head, True]
+
+    if cacheOnly:
+        updateArg = '-i'
+    else:
+        updateArg = '-u'
+
+    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
+                                common, head, merge], returnCode = True)
+    if code != 0:
+        die('git-read-tree:', out)
+
+    [tree, code] = runProgram('git-write-tree', returnCode=True)
+    tree = tree.rstrip()
+    if code != 0:
+        global currentFileSet, currentDirectorySet
+        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
+        [filesM, dirsM] = getFilesAndDirs(merge)
+        currentFileSet.union_update(filesM)
+        currentDirectorySet.union_update(dirsM)
+
+        entries = unmergedCacheEntries()
+        renamesHead =  getRenames(head, common, head, merge, entries)
+        renamesMerge = getRenames(merge, common, head, merge, entries)
+
+        cleanMerge = processRenames(renamesHead, renamesMerge,
+                                    branch1Name, branch2Name)
+        for entry in entries:
+            if entry.processed:
+                continue
+            if not processEntry(entry, branch1Name, branch2Name):
+                cleanMerge = False
+                
+        if cleanMerge or cacheOnly:
+            tree = runProgram('git-write-tree').rstrip()
+        else:
+            tree = None
+    else:
+        cleanMerge = True
+
+    return [tree, cleanMerge]
+
+# Low level file merging, update and removal
+# ------------------------------------------
+
+def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
+              branch1Name, branch2Name):
+
+    merge = False
+    clean = True
+
+    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
+        clean = False
+        if stat.S_ISREG(aMode):
+            mode = aMode
+            sha = aSha
+        else:
+            mode = bMode
+            sha = bSha
+    else:
+        if aSha != oSha and bSha != oSha:
+            merge = True
+
+        if aMode == oMode:
+            mode = bMode
+        else:
+            mode = aMode
+
+        if aSha == oSha:
+            sha = bSha
+        elif bSha == oSha:
+            sha = aSha
+        elif stat.S_ISREG(aMode):
+            assert(stat.S_ISREG(bMode))
+
+            orig = runProgram(['git-unpack-file', oSha]).rstrip()
+            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+            try:
+                [out, code] = runProgram(['merge',
+                                          '-L', branch1Name + '/' + aPath,
+                                          '-L', 'orig/' + oPath,
+                                          '-L', branch2Name + '/' + bPath,
+                                          src1, orig, src2], returnCode=True)
+            except ProgramError, e:
+                print >>sys.stderr, e
+                die("Failed to execute 'merge'. merge(1) is used as the "
+                    "file-level merge tool. Is 'merge' in your path?")
+
+            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
+                              src1]).rstrip()
+
+            os.unlink(orig)
+            os.unlink(src1)
+            os.unlink(src2)
+
+            clean = (code == 0)
+        else:
+            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
+            sha = aSha
+
+            if aSha != bSha:
+                clean = False
+
+    return [sha, mode, clean, merge]
+
+def updateFile(clean, sha, mode, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    return updateFileExt(sha, mode, path, updateCache, updateWd)
+
+def updateFileExt(sha, mode, path, updateCache, updateWd):
+    if cacheOnly:
+        updateWd = False
+
+    if updateWd:
+        pathComponents = path.split('/')
+        for x in xrange(1, len(pathComponents)):
+            p = '/'.join(pathComponents[0:x])
+
+            try:
+                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
+            except OSError:
+                createDir = True
+            
+            if createDir:
+                try:
+                    os.mkdir(p)
+                except OSError, e:
+                    die("Couldn't create directory", p, e.strerror)
+
+        prog = ['git-cat-file', 'blob', sha]
+        if stat.S_ISREG(mode):
+            try:
+                os.unlink(path)
+            except OSError:
+                pass
+            if mode & 0100:
+                mode = 0777
+            else:
+                mode = 0666
+            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
+            proc = subprocess.Popen(prog, stdout=fd)
+            proc.wait()
+            os.close(fd)
+        elif stat.S_ISLNK(mode):
+            linkTarget = runProgram(prog)
+            os.symlink(linkTarget, path)
+        else:
+            assert(False)
+
+    if updateWd and updateCache:
+        runProgram(['git-update-index', '--add', '--', path])
+    elif updateCache:
+        runProgram(['git-update-index', '--add', '--cacheinfo',
+                    '0%o' % mode, sha, path])
+
+def setIndexStages(path,
+                   oSHA1, oMode,
+                   aSHA1, aMode,
+                   bSHA1, bMode,
+                   clear=True):
+    istring = []
+    if clear:
+        istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
+    if oMode:
+        istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
+    if aMode:
+        istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
+    if bMode:
+        istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
+
+    runProgram(['git-update-index', '-z', '--index-info'],
+               input="".join(istring))
+
+def removeFile(clean, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    if updateCache:
+        runProgram(['git-update-index', '--force-remove', '--', path])
+
+    if updateWd:
+        try:
+            os.unlink(path)
+        except OSError, e:
+            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
+                raise
+        try:
+            os.removedirs(os.path.dirname(path))
+        except OSError:
+            pass
+
+def uniquePath(path, branch):
+    def fileExists(path):
+        try:
+            os.lstat(path)
+            return True
+        except OSError, e:
+            if e.errno == errno.ENOENT:
+                return False
+            else:
+                raise
+
+    branch = branch.replace('/', '_')
+    newPath = path + '~' + branch
+    suffix = 0
+    while newPath in currentFileSet or \
+          newPath in currentDirectorySet  or \
+          fileExists(newPath):
+        suffix += 1
+        newPath = path + '~' + branch + '_' + str(suffix)
+    currentFileSet.add(newPath)
+    return newPath
+
+# Cache entry management
+# ----------------------
+
+class CacheEntry:
+    def __init__(self, path):
+        class Stage:
+            def __init__(self):
+                self.sha1 = None
+                self.mode = None
+
+            # Used for debugging only
+            def __str__(self):
+                if self.mode != None:
+                    m = '0%o' % self.mode
+                else:
+                    m = 'None'
+
+                if self.sha1:
+                    sha1 = self.sha1
+                else:
+                    sha1 = 'None'
+                return 'sha1: ' + sha1 + ' mode: ' + m
+        
+        self.stages = [Stage(), Stage(), Stage(), Stage()]
+        self.path = path
+        self.processed = False
+
+    def __str__(self):
+        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
+
+class CacheEntryContainer:
+    def __init__(self):
+        self.entries = {}
+
+    def add(self, entry):
+        self.entries[entry.path] = entry
+
+    def get(self, path):
+        return self.entries.get(path)
+
+    def __iter__(self):
+        return self.entries.itervalues()
+    
+unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
+def unmergedCacheEntries():
+    '''Create a dictionary mapping file names to CacheEntry
+    objects. The dictionary contains one entry for every path with a
+    non-zero stage entry.'''
+
+    lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
+    lines.pop()
+
+    res = CacheEntryContainer()
+    for l in lines:
+        m = unmergedRE.match(l)
+        if m:
+            mode = int(m.group(1), 8)
+            sha1 = m.group(2)
+            stage = int(m.group(3))
+            path = m.group(4)
+
+            e = res.get(path)
+            if not e:
+                e = CacheEntry(path)
+                res.add(e)
+
+            e.stages[stage].mode = mode
+            e.stages[stage].sha1 = sha1
+        else:
+            die('Error: Merge program failed: Unexpected output from',
+                'git-ls-files:', l)
+    return res
+
+lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
+def getCacheEntry(path, origTree, aTree, bTree):
+    '''Returns a CacheEntry object which doesn't have to correspond to
+    a real cache entry in Git's index.'''
+    
+    def parse(out):
+        if out == '':
+            return [None, None]
+        else:
+            m = lsTreeRE.match(out)
+            if not m:
+                die('Unexpected output from git-ls-tree:', out)
+            elif m.group(2) == 'blob':
+                return [m.group(3), int(m.group(1), 8)]
+            else:
+                return [None, None]
+
+    res = CacheEntry(path)
+
+    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
+    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
+    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
+
+    res.stages[1].sha1 = oSha
+    res.stages[1].mode = oMode
+    res.stages[2].sha1 = aSha
+    res.stages[2].mode = aMode
+    res.stages[3].sha1 = bSha
+    res.stages[3].mode = bMode
+
+    return res
+
+# Rename detection and handling
+# -----------------------------
+
+class RenameEntry:
+    def __init__(self,
+                 src, srcSha, srcMode, srcCacheEntry,
+                 dst, dstSha, dstMode, dstCacheEntry,
+                 score):
+        self.srcName = src
+        self.srcSha = srcSha
+        self.srcMode = srcMode
+        self.srcCacheEntry = srcCacheEntry
+        self.dstName = dst
+        self.dstSha = dstSha
+        self.dstMode = dstMode
+        self.dstCacheEntry = dstCacheEntry
+        self.score = score
+
+        self.processed = False
+
+class RenameEntryContainer:
+    def __init__(self):
+        self.entriesSrc = {}
+        self.entriesDst = {}
+
+    def add(self, entry):
+        self.entriesSrc[entry.srcName] = entry
+        self.entriesDst[entry.dstName] = entry
+
+    def getSrc(self, path):
+        return self.entriesSrc.get(path)
+
+    def getDst(self, path):
+        return self.entriesDst.get(path)
+
+    def __iter__(self):
+        return self.entriesSrc.itervalues()
+
+parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
+def getRenames(tree, oTree, aTree, bTree, cacheEntries):
+    '''Get information of all renames which occured between 'oTree' and
+    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
+    'bTree') to be able to associate the correct cache entries with
+    the rename information. 'tree' is always equal to either aTree or bTree.'''
+
+    assert(tree == aTree or tree == bTree)
+    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
+                      '-z', oTree, tree])
+
+    ret = RenameEntryContainer()
+    try:
+        recs = inp.split("\0")
+        recs.pop() # remove last entry (which is '')
+        it = recs.__iter__()
+        while True:
+            rec = it.next()
+            m = parseDiffRenamesRE.match(rec)
+
+            if not m:
+                die('Unexpected output from git-diff-tree:', rec)
+
+            srcMode = int(m.group(1), 8)
+            dstMode = int(m.group(2), 8)
+            srcSha = m.group(3)
+            dstSha = m.group(4)
+            score = m.group(5)
+            src = it.next()
+            dst = it.next()
+
+            srcCacheEntry = cacheEntries.get(src)
+            if not srcCacheEntry:
+                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
+                cacheEntries.add(srcCacheEntry)
+
+            dstCacheEntry = cacheEntries.get(dst)
+            if not dstCacheEntry:
+                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
+                cacheEntries.add(dstCacheEntry)
+
+            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
+                                dst, dstSha, dstMode, dstCacheEntry,
+                                score))
+    except StopIteration:
+        pass
+    return ret
+
+def fmtRename(src, dst):
+    srcPath = src.split('/')
+    dstPath = dst.split('/')
+    path = []
+    endIndex = min(len(srcPath), len(dstPath)) - 1
+    for x in range(0, endIndex):
+        if srcPath[x] == dstPath[x]:
+            path.append(srcPath[x])
+        else:
+            endIndex = x
+            break
+
+    if len(path) > 0:
+        return '/'.join(path) + \
+               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
+               '/'.join(dstPath[endIndex:]) + '}'
+    else:
+        return src + ' => ' + dst
+
+def processRenames(renamesA, renamesB, branchNameA, branchNameB):
+    srcNames = Set()
+    for x in renamesA:
+        srcNames.add(x.srcName)
+    for x in renamesB:
+        srcNames.add(x.srcName)
+
+    cleanMerge = True
+    for path in srcNames:
+        if renamesA.getSrc(path):
+            renames1 = renamesA
+            renames2 = renamesB
+            branchName1 = branchNameA
+            branchName2 = branchNameB
+        else:
+            renames1 = renamesB
+            renames2 = renamesA
+            branchName1 = branchNameB
+            branchName2 = branchNameA
+        
+        ren1 = renames1.getSrc(path)
+        ren2 = renames2.getSrc(path)
+
+        ren1.dstCacheEntry.processed = True
+        ren1.srcCacheEntry.processed = True
+
+        if ren1.processed:
+            continue
+
+        ren1.processed = True
+
+        if ren2:
+            # Renamed in 1 and renamed in 2
+            assert(ren1.srcName == ren2.srcName)
+            ren2.dstCacheEntry.processed = True
+            ren2.processed = True
+
+            if ren1.dstName != ren2.dstName:
+                output('CONFLICT (rename/rename): Rename',
+                       fmtRename(path, ren1.dstName), 'in branch', branchName1,
+                       'rename', fmtRename(path, ren2.dstName), 'in',
+                       branchName2)
+                cleanMerge = False
+
+                if ren1.dstName in currentDirectorySet:
+                    dstName1 = uniquePath(ren1.dstName, branchName1)
+                    output(ren1.dstName, 'is a directory in', branchName2,
+                           'adding as', dstName1, 'instead.')
+                    removeFile(False, ren1.dstName)
+                else:
+                    dstName1 = ren1.dstName
+
+                if ren2.dstName in currentDirectorySet:
+                    dstName2 = uniquePath(ren2.dstName, branchName2)
+                    output(ren2.dstName, 'is a directory in', branchName1,
+                           'adding as', dstName2, 'instead.')
+                    removeFile(False, ren2.dstName)
+                else:
+                    dstName2 = ren2.dstName
+                setIndexStages(dstName1,
+                               None, None,
+                               ren1.dstSha, ren1.dstMode,
+                              None, None)
+                setIndexStages(dstName2,
+                               None, None,
+                               None, None,
+                               ren2.dstSha, ren2.dstMode)
+
+            else:
+                removeFile(True, ren1.srcName)
+
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
+                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
+                                   branchName1, branchName2)
+
+                if merge or not clean:
+                    output('Renaming', fmtRename(path, ren1.dstName))
+
+                if merge:
+                    output('Auto-merging', ren1.dstName)
+
+                if not clean:
+                    output('CONFLICT (content): merge conflict in',
+                           ren1.dstName)
+                    cleanMerge = False
+
+                    if not cacheOnly:
+                        setIndexStages(ren1.dstName,
+                                       ren1.srcSha, ren1.srcMode,
+                                       ren1.dstSha, ren1.dstMode,
+                                       ren2.dstSha, ren2.dstMode)
+
+                updateFile(clean, resSha, resMode, ren1.dstName)
+        else:
+            removeFile(True, ren1.srcName)
+
+            # Renamed in 1, maybe changed in 2
+            if renamesA == renames1:
+                stage = 3
+            else:
+                stage = 2
+                
+            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
+            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
+
+            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
+            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
+
+            tryMerge = False
+            
+            if ren1.dstName in currentDirectorySet:
+                newPath = uniquePath(ren1.dstName, branchName1)
+                output('CONFLICT (rename/directory): Rename',
+                       fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
+                       'directory', ren1.dstName, 'added in', branchName2)
+                output('Renaming', ren1.srcName, 'to', newPath, 'instead')
+                cleanMerge = False
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
+            elif srcShaOtherBranch == None:
+                output('CONFLICT (rename/delete): Rename',
+                       fmtRename(ren1.srcName, ren1.dstName), 'in',
+                       branchName1, 'and deleted in', branchName2)
+                cleanMerge = False
+                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
+            elif dstShaOtherBranch:
+                newPath = uniquePath(ren1.dstName, branchName2)
+                output('CONFLICT (rename/add): Rename',
+                       fmtRename(ren1.srcName, ren1.dstName), 'in',
+                       branchName1 + '.', ren1.dstName, 'added in', branchName2)
+                output('Adding as', newPath, 'instead')
+                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
+                cleanMerge = False
+                tryMerge = True
+            elif renames2.getDst(ren1.dstName):
+                dst2 = renames2.getDst(ren1.dstName)
+                newPath1 = uniquePath(ren1.dstName, branchName1)
+                newPath2 = uniquePath(dst2.dstName, branchName2)
+                output('CONFLICT (rename/rename): Rename',
+                       fmtRename(ren1.srcName, ren1.dstName), 'in',
+                       branchName1+'. Rename',
+                       fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
+                output('Renaming', ren1.srcName, 'to', newPath1, 'and',
+                       dst2.srcName, 'to', newPath2, 'instead')
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
+                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
+                dst2.processed = True
+                cleanMerge = False
+            else:
+                tryMerge = True
+
+            if tryMerge:
+
+                oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
+                aName, bName = ren1.dstName, ren1.srcName
+                aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
+                aMode, bMode = ren1.dstMode, srcModeOtherBranch
+                aBranch, bBranch = branchName1, branchName2
+
+                if renamesA != renames1:
+                    aName, bName = bName, aName
+                    aSHA1, bSHA1 = bSHA1, aSHA1
+                    aMode, bMode = bMode, aMode
+                    aBranch, bBranch = bBranch, aBranch
+
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(oName, oSHA1, oMode,
+                                   aName, aSHA1, aMode,
+                                   bName, bSHA1, bMode,
+                                   aBranch, bBranch);
+
+                if merge or not clean:
+                    output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
+
+                if merge:
+                    output('Auto-merging', ren1.dstName)
+
+                if not clean:
+                    output('CONFLICT (rename/modify): Merge conflict in',
+                           ren1.dstName)
+                    cleanMerge = False
+
+                    if not cacheOnly:
+                        setIndexStages(ren1.dstName,
+                                       oSHA1, oMode,
+                                       aSHA1, aMode,
+                                       bSHA1, bMode)
+
+                updateFile(clean, resSha, resMode, ren1.dstName)
+
+    return cleanMerge
+
+# Per entry merge function
+# ------------------------
+
+def processEntry(entry, branch1Name, branch2Name):
+    '''Merge one cache entry.'''
+
+    debug('processing', entry.path, 'clean cache:', cacheOnly)
+
+    cleanMerge = True
+
+    path = entry.path
+    oSha = entry.stages[1].sha1
+    oMode = entry.stages[1].mode
+    aSha = entry.stages[2].sha1
+    aMode = entry.stages[2].mode
+    bSha = entry.stages[3].sha1
+    bMode = entry.stages[3].mode
+
+    assert(oSha == None or isSha(oSha))
+    assert(aSha == None or isSha(aSha))
+    assert(bSha == None or isSha(bSha))
+
+    assert(oMode == None or type(oMode) is int)
+    assert(aMode == None or type(aMode) is int)
+    assert(bMode == None or type(bMode) is int)
+
+    if (oSha and (not aSha or not bSha)):
+    #
+    # Case A: Deleted in one
+    #
+        if (not aSha     and not bSha) or \
+           (aSha == oSha and not bSha) or \
+           (not aSha     and bSha == oSha):
+    # Deleted in both or deleted in one and unchanged in the other
+            if aSha:
+                output('Removing', path)
+            removeFile(True, path)
+        else:
+    # Deleted in one and changed in the other
+            cleanMerge = False
+            if not aSha:
+                output('CONFLICT (delete/modify):', path, 'deleted in',
+                       branch1Name, 'and modified in', branch2Name + '.',
+                       'Version', branch2Name, 'of', path, 'left in tree.')
+                mode = bMode
+                sha = bSha
+            else:
+                output('CONFLICT (modify/delete):', path, 'deleted in',
+                       branch2Name, 'and modified in', branch1Name + '.',
+                       'Version', branch1Name, 'of', path, 'left in tree.')
+                mode = aMode
+                sha = aSha
+
+            updateFile(False, sha, mode, path)
+
+    elif (not oSha and aSha     and not bSha) or \
+         (not oSha and not aSha and bSha):
+    #
+    # Case B: Added in one.
+    #
+        if aSha:
+            addBranch = branch1Name
+            otherBranch = branch2Name
+            mode = aMode
+            sha = aSha
+            conf = 'file/directory'
+        else:
+            addBranch = branch2Name
+            otherBranch = branch1Name
+            mode = bMode
+            sha = bSha
+            conf = 'directory/file'
+    
+        if path in currentDirectorySet:
+            cleanMerge = False
+            newPath = uniquePath(path, addBranch)
+            output('CONFLICT (' + conf + '):',
+                   'There is a directory with name', path, 'in',
+                   otherBranch + '. Adding', path, 'as', newPath)
+
+            removeFile(False, path)
+            updateFile(False, sha, mode, newPath)
+        else:
+            output('Adding', path)
+            updateFile(True, sha, mode, path)
+    
+    elif not oSha and aSha and bSha:
+    #
+    # Case C: Added in both (check for same permissions).
+    #
+        if aSha == bSha:
+            if aMode != bMode:
+                cleanMerge = False
+                output('CONFLICT: File', path,
+                       'added identically in both branches, but permissions',
+                       'conflict', '0%o' % aMode, '->', '0%o' % bMode)
+                output('CONFLICT: adding with permission:', '0%o' % aMode)
+
+                updateFile(False, aSha, aMode, path)
+            else:
+                # This case is handled by git-read-tree
+                assert(False)
+        else:
+            cleanMerge = False
+            newPath1 = uniquePath(path, branch1Name)
+            newPath2 = uniquePath(path, branch2Name)
+            output('CONFLICT (add/add): File', path,
+                   'added non-identically in both branches. Adding as',
+                   newPath1, 'and', newPath2, 'instead.')
+            removeFile(False, path)
+            updateFile(False, aSha, aMode, newPath1)
+            updateFile(False, bSha, bMode, newPath2)
+
+    elif oSha and aSha and bSha:
+    #
+    # case D: Modified in both, but differently.
+    #
+        output('Auto-merging', path)
+        [sha, mode, clean, dummy] = \
+              mergeFile(path, oSha, oMode,
+                        path, aSha, aMode,
+                        path, bSha, bMode,
+                        branch1Name, branch2Name)
+        if clean:
+            updateFile(True, sha, mode, path)
+        else:
+            cleanMerge = False
+            output('CONFLICT (content): Merge conflict in', path)
+
+            if cacheOnly:
+                updateFile(False, sha, mode, path)
+            else:
+                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
+    else:
+        die("ERROR: Fatal merge failure, shouldn't happen.")
+
+    return cleanMerge
+
+def usage():
+    die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
+
+# main entry point as merge strategy module
+# The first parameters up to -- are merge bases, and the rest are heads.
+
+if len(sys.argv) < 4:
+    usage()
+
+bases = []
+for nextArg in xrange(1, len(sys.argv)):
+    if sys.argv[nextArg] == '--':
+        if len(sys.argv) != nextArg + 3:
+            die('Not handling anything other than two heads merge.')
+        try:
+            h1 = firstBranch = sys.argv[nextArg + 1]
+            h2 = secondBranch = sys.argv[nextArg + 2]
+        except IndexError:
+            usage()
+        break
+    else:
+        bases.append(sys.argv[nextArg])
+
+print 'Merging', h1, 'with', h2
+
+try:
+    h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
+    h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
+
+    if len(bases) == 1:
+        base = runProgram(['git-rev-parse', '--verify',
+                           bases[0] + '^0']).rstrip()
+        ancestor = Commit(base, None)
+        [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
+                               firstBranch, secondBranch, None, 0,
+                               ancestor)
+    else:
+        graph = buildGraph([h1, h2])
+        [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
+                               firstBranch, secondBranch, graph)
+
+    print ''
+except:
+    if isinstance(sys.exc_info()[1], SystemExit):
+        raise
+    else:
+        traceback.print_exc(None, sys.stderr)
+        sys.exit(2)
+
+if clean:
+    sys.exit(0)
+else:
+    sys.exit(1)
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
deleted file mode 100755 (executable)
index 4039435..0000000
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys
-sys.path.append('''@@GIT_PYTHON_PATH@@''')
-
-import math, random, os, re, signal, tempfile, stat, errno, traceback
-from heapq import heappush, heappop
-from sets import Set
-
-from gitMergeCommon import *
-
-outputIndent = 0
-def output(*args):
-    sys.stdout.write('  '*outputIndent)
-    printList(args)
-
-originalIndexFile = os.environ.get('GIT_INDEX_FILE',
-                                   os.environ.get('GIT_DIR', '.git') + '/index')
-temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
-                     '/merge-recursive-tmp-index'
-def setupIndex(temporary):
-    try:
-        os.unlink(temporaryIndexFile)
-    except OSError:
-        pass
-    if temporary:
-        newIndex = temporaryIndexFile
-    else:
-        newIndex = originalIndexFile
-    os.environ['GIT_INDEX_FILE'] = newIndex
-
-# This is a global variable which is used in a number of places but
-# only written to in the 'merge' function.
-
-# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
-#                       don't update the working directory.
-#              False => Leave unmerged entries in the cache and update
-#                       the working directory.
-
-cacheOnly = False
-
-# The entry point to the merge code
-# ---------------------------------
-
-def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
-    '''Merge the commits h1 and h2, return the resulting virtual
-    commit object and a flag indicating the cleanness of the merge.'''
-    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
-
-    global outputIndent
-
-    output('Merging:')
-    output(h1)
-    output(h2)
-    sys.stdout.flush()
-
-    if ancestor:
-        ca = [ancestor]
-    else:
-        assert(isinstance(graph, Graph))
-        ca = getCommonAncestors(graph, h1, h2)
-    output('found', len(ca), 'common ancestor(s):')
-    for x in ca:
-        output(x)
-    sys.stdout.flush()
-
-    mergedCA = ca[0]
-    for h in ca[1:]:
-        outputIndent = callDepth+1
-        [mergedCA, dummy] = merge(mergedCA, h,
-                                  'Temporary merge branch 1',
-                                  'Temporary merge branch 2',
-                                  graph, callDepth+1)
-        outputIndent = callDepth
-        assert(isinstance(mergedCA, Commit))
-
-    global cacheOnly
-    if callDepth == 0:
-        setupIndex(False)
-        cacheOnly = False
-    else:
-        setupIndex(True)
-        runProgram(['git-read-tree', h1.tree()])
-        cacheOnly = True
-
-    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
-                                 branch1Name, branch2Name)
-
-    if graph and (clean or cacheOnly):
-        res = Commit(None, [h1, h2], tree=shaRes)
-        graph.addNode(res)
-    else:
-        res = None
-
-    return [res, clean]
-
-getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
-def getFilesAndDirs(tree):
-    files = Set()
-    dirs = Set()
-    out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
-    for l in out.split('\0'):
-        m = getFilesRE.match(l)
-        if m:
-            if m.group(2) == 'tree':
-                dirs.add(m.group(4))
-            elif m.group(2) == 'blob':
-                files.add(m.group(4))
-
-    return [files, dirs]
-
-# Those two global variables are used in a number of places but only
-# written to in 'mergeTrees' and 'uniquePath'. They keep track of
-# every file and directory in the two branches that are about to be
-# merged.
-currentFileSet = None
-currentDirectorySet = None
-
-def mergeTrees(head, merge, common, branch1Name, branch2Name):
-    '''Merge the trees 'head' and 'merge' with the common ancestor
-    'common'. The name of the head branch is 'branch1Name' and the name of
-    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
-    where tree is the resulting tree and cleanMerge is True iff the
-    merge was clean.'''
-    
-    assert(isSha(head) and isSha(merge) and isSha(common))
-
-    if common == merge:
-        output('Already uptodate!')
-        return [head, True]
-
-    if cacheOnly:
-        updateArg = '-i'
-    else:
-        updateArg = '-u'
-
-    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
-                                common, head, merge], returnCode = True)
-    if code != 0:
-        die('git-read-tree:', out)
-
-    [tree, code] = runProgram('git-write-tree', returnCode=True)
-    tree = tree.rstrip()
-    if code != 0:
-        global currentFileSet, currentDirectorySet
-        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
-        [filesM, dirsM] = getFilesAndDirs(merge)
-        currentFileSet.union_update(filesM)
-        currentDirectorySet.union_update(dirsM)
-
-        entries = unmergedCacheEntries()
-        renamesHead =  getRenames(head, common, head, merge, entries)
-        renamesMerge = getRenames(merge, common, head, merge, entries)
-
-        cleanMerge = processRenames(renamesHead, renamesMerge,
-                                    branch1Name, branch2Name)
-        for entry in entries:
-            if entry.processed:
-                continue
-            if not processEntry(entry, branch1Name, branch2Name):
-                cleanMerge = False
-                
-        if cleanMerge or cacheOnly:
-            tree = runProgram('git-write-tree').rstrip()
-        else:
-            tree = None
-    else:
-        cleanMerge = True
-
-    return [tree, cleanMerge]
-
-# Low level file merging, update and removal
-# ------------------------------------------
-
-def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
-              branch1Name, branch2Name):
-
-    merge = False
-    clean = True
-
-    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
-        clean = False
-        if stat.S_ISREG(aMode):
-            mode = aMode
-            sha = aSha
-        else:
-            mode = bMode
-            sha = bSha
-    else:
-        if aSha != oSha and bSha != oSha:
-            merge = True
-
-        if aMode == oMode:
-            mode = bMode
-        else:
-            mode = aMode
-
-        if aSha == oSha:
-            sha = bSha
-        elif bSha == oSha:
-            sha = aSha
-        elif stat.S_ISREG(aMode):
-            assert(stat.S_ISREG(bMode))
-
-            orig = runProgram(['git-unpack-file', oSha]).rstrip()
-            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
-            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
-            try:
-                [out, code] = runProgram(['merge',
-                                          '-L', branch1Name + '/' + aPath,
-                                          '-L', 'orig/' + oPath,
-                                          '-L', branch2Name + '/' + bPath,
-                                          src1, orig, src2], returnCode=True)
-            except ProgramError, e:
-                print >>sys.stderr, e
-                die("Failed to execute 'merge'. merge(1) is used as the "
-                    "file-level merge tool. Is 'merge' in your path?")
-
-            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
-                              src1]).rstrip()
-
-            os.unlink(orig)
-            os.unlink(src1)
-            os.unlink(src2)
-
-            clean = (code == 0)
-        else:
-            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
-            sha = aSha
-
-            if aSha != bSha:
-                clean = False
-
-    return [sha, mode, clean, merge]
-
-def updateFile(clean, sha, mode, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    return updateFileExt(sha, mode, path, updateCache, updateWd)
-
-def updateFileExt(sha, mode, path, updateCache, updateWd):
-    if cacheOnly:
-        updateWd = False
-
-    if updateWd:
-        pathComponents = path.split('/')
-        for x in xrange(1, len(pathComponents)):
-            p = '/'.join(pathComponents[0:x])
-
-            try:
-                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except OSError:
-                createDir = True
-            
-            if createDir:
-                try:
-                    os.mkdir(p)
-                except OSError, e:
-                    die("Couldn't create directory", p, e.strerror)
-
-        prog = ['git-cat-file', 'blob', sha]
-        if stat.S_ISREG(mode):
-            try:
-                os.unlink(path)
-            except OSError:
-                pass
-            if mode & 0100:
-                mode = 0777
-            else:
-                mode = 0666
-            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
-            proc = subprocess.Popen(prog, stdout=fd)
-            proc.wait()
-            os.close(fd)
-        elif stat.S_ISLNK(mode):
-            linkTarget = runProgram(prog)
-            os.symlink(linkTarget, path)
-        else:
-            assert(False)
-
-    if updateWd and updateCache:
-        runProgram(['git-update-index', '--add', '--', path])
-    elif updateCache:
-        runProgram(['git-update-index', '--add', '--cacheinfo',
-                    '0%o' % mode, sha, path])
-
-def setIndexStages(path,
-                   oSHA1, oMode,
-                   aSHA1, aMode,
-                   bSHA1, bMode,
-                   clear=True):
-    istring = []
-    if clear:
-        istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
-    if oMode:
-        istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
-    if aMode:
-        istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
-    if bMode:
-        istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
-
-    runProgram(['git-update-index', '-z', '--index-info'],
-               input="".join(istring))
-
-def removeFile(clean, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    if updateCache:
-        runProgram(['git-update-index', '--force-remove', '--', path])
-
-    if updateWd:
-        try:
-            os.unlink(path)
-        except OSError, e:
-            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
-                raise
-        try:
-            os.removedirs(os.path.dirname(path))
-        except OSError:
-            pass
-
-def uniquePath(path, branch):
-    def fileExists(path):
-        try:
-            os.lstat(path)
-            return True
-        except OSError, e:
-            if e.errno == errno.ENOENT:
-                return False
-            else:
-                raise
-
-    branch = branch.replace('/', '_')
-    newPath = path + '~' + branch
-    suffix = 0
-    while newPath in currentFileSet or \
-          newPath in currentDirectorySet  or \
-          fileExists(newPath):
-        suffix += 1
-        newPath = path + '~' + branch + '_' + str(suffix)
-    currentFileSet.add(newPath)
-    return newPath
-
-# Cache entry management
-# ----------------------
-
-class CacheEntry:
-    def __init__(self, path):
-        class Stage:
-            def __init__(self):
-                self.sha1 = None
-                self.mode = None
-
-            # Used for debugging only
-            def __str__(self):
-                if self.mode != None:
-                    m = '0%o' % self.mode
-                else:
-                    m = 'None'
-
-                if self.sha1:
-                    sha1 = self.sha1
-                else:
-                    sha1 = 'None'
-                return 'sha1: ' + sha1 + ' mode: ' + m
-        
-        self.stages = [Stage(), Stage(), Stage(), Stage()]
-        self.path = path
-        self.processed = False
-
-    def __str__(self):
-        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
-
-class CacheEntryContainer:
-    def __init__(self):
-        self.entries = {}
-
-    def add(self, entry):
-        self.entries[entry.path] = entry
-
-    def get(self, path):
-        return self.entries.get(path)
-
-    def __iter__(self):
-        return self.entries.itervalues()
-    
-unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-def unmergedCacheEntries():
-    '''Create a dictionary mapping file names to CacheEntry
-    objects. The dictionary contains one entry for every path with a
-    non-zero stage entry.'''
-
-    lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
-    lines.pop()
-
-    res = CacheEntryContainer()
-    for l in lines:
-        m = unmergedRE.match(l)
-        if m:
-            mode = int(m.group(1), 8)
-            sha1 = m.group(2)
-            stage = int(m.group(3))
-            path = m.group(4)
-
-            e = res.get(path)
-            if not e:
-                e = CacheEntry(path)
-                res.add(e)
-
-            e.stages[stage].mode = mode
-            e.stages[stage].sha1 = sha1
-        else:
-            die('Error: Merge program failed: Unexpected output from',
-                'git-ls-files:', l)
-    return res
-
-lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
-def getCacheEntry(path, origTree, aTree, bTree):
-    '''Returns a CacheEntry object which doesn't have to correspond to
-    a real cache entry in Git's index.'''
-    
-    def parse(out):
-        if out == '':
-            return [None, None]
-        else:
-            m = lsTreeRE.match(out)
-            if not m:
-                die('Unexpected output from git-ls-tree:', out)
-            elif m.group(2) == 'blob':
-                return [m.group(3), int(m.group(1), 8)]
-            else:
-                return [None, None]
-
-    res = CacheEntry(path)
-
-    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
-    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
-    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
-
-    res.stages[1].sha1 = oSha
-    res.stages[1].mode = oMode
-    res.stages[2].sha1 = aSha
-    res.stages[2].mode = aMode
-    res.stages[3].sha1 = bSha
-    res.stages[3].mode = bMode
-
-    return res
-
-# Rename detection and handling
-# -----------------------------
-
-class RenameEntry:
-    def __init__(self,
-                 src, srcSha, srcMode, srcCacheEntry,
-                 dst, dstSha, dstMode, dstCacheEntry,
-                 score):
-        self.srcName = src
-        self.srcSha = srcSha
-        self.srcMode = srcMode
-        self.srcCacheEntry = srcCacheEntry
-        self.dstName = dst
-        self.dstSha = dstSha
-        self.dstMode = dstMode
-        self.dstCacheEntry = dstCacheEntry
-        self.score = score
-
-        self.processed = False
-
-class RenameEntryContainer:
-    def __init__(self):
-        self.entriesSrc = {}
-        self.entriesDst = {}
-
-    def add(self, entry):
-        self.entriesSrc[entry.srcName] = entry
-        self.entriesDst[entry.dstName] = entry
-
-    def getSrc(self, path):
-        return self.entriesSrc.get(path)
-
-    def getDst(self, path):
-        return self.entriesDst.get(path)
-
-    def __iter__(self):
-        return self.entriesSrc.itervalues()
-
-parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
-def getRenames(tree, oTree, aTree, bTree, cacheEntries):
-    '''Get information of all renames which occured between 'oTree' and
-    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
-    'bTree') to be able to associate the correct cache entries with
-    the rename information. 'tree' is always equal to either aTree or bTree.'''
-
-    assert(tree == aTree or tree == bTree)
-    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
-                      '-z', oTree, tree])
-
-    ret = RenameEntryContainer()
-    try:
-        recs = inp.split("\0")
-        recs.pop() # remove last entry (which is '')
-        it = recs.__iter__()
-        while True:
-            rec = it.next()
-            m = parseDiffRenamesRE.match(rec)
-
-            if not m:
-                die('Unexpected output from git-diff-tree:', rec)
-
-            srcMode = int(m.group(1), 8)
-            dstMode = int(m.group(2), 8)
-            srcSha = m.group(3)
-            dstSha = m.group(4)
-            score = m.group(5)
-            src = it.next()
-            dst = it.next()
-
-            srcCacheEntry = cacheEntries.get(src)
-            if not srcCacheEntry:
-                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
-                cacheEntries.add(srcCacheEntry)
-
-            dstCacheEntry = cacheEntries.get(dst)
-            if not dstCacheEntry:
-                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
-                cacheEntries.add(dstCacheEntry)
-
-            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
-                                dst, dstSha, dstMode, dstCacheEntry,
-                                score))
-    except StopIteration:
-        pass
-    return ret
-
-def fmtRename(src, dst):
-    srcPath = src.split('/')
-    dstPath = dst.split('/')
-    path = []
-    endIndex = min(len(srcPath), len(dstPath)) - 1
-    for x in range(0, endIndex):
-        if srcPath[x] == dstPath[x]:
-            path.append(srcPath[x])
-        else:
-            endIndex = x
-            break
-
-    if len(path) > 0:
-        return '/'.join(path) + \
-               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
-               '/'.join(dstPath[endIndex:]) + '}'
-    else:
-        return src + ' => ' + dst
-
-def processRenames(renamesA, renamesB, branchNameA, branchNameB):
-    srcNames = Set()
-    for x in renamesA:
-        srcNames.add(x.srcName)
-    for x in renamesB:
-        srcNames.add(x.srcName)
-
-    cleanMerge = True
-    for path in srcNames:
-        if renamesA.getSrc(path):
-            renames1 = renamesA
-            renames2 = renamesB
-            branchName1 = branchNameA
-            branchName2 = branchNameB
-        else:
-            renames1 = renamesB
-            renames2 = renamesA
-            branchName1 = branchNameB
-            branchName2 = branchNameA
-        
-        ren1 = renames1.getSrc(path)
-        ren2 = renames2.getSrc(path)
-
-        ren1.dstCacheEntry.processed = True
-        ren1.srcCacheEntry.processed = True
-
-        if ren1.processed:
-            continue
-
-        ren1.processed = True
-
-        if ren2:
-            # Renamed in 1 and renamed in 2
-            assert(ren1.srcName == ren2.srcName)
-            ren2.dstCacheEntry.processed = True
-            ren2.processed = True
-
-            if ren1.dstName != ren2.dstName:
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(path, ren1.dstName), 'in branch', branchName1,
-                       'rename', fmtRename(path, ren2.dstName), 'in',
-                       branchName2)
-                cleanMerge = False
-
-                if ren1.dstName in currentDirectorySet:
-                    dstName1 = uniquePath(ren1.dstName, branchName1)
-                    output(ren1.dstName, 'is a directory in', branchName2,
-                           'adding as', dstName1, 'instead.')
-                    removeFile(False, ren1.dstName)
-                else:
-                    dstName1 = ren1.dstName
-
-                if ren2.dstName in currentDirectorySet:
-                    dstName2 = uniquePath(ren2.dstName, branchName2)
-                    output(ren2.dstName, 'is a directory in', branchName1,
-                           'adding as', dstName2, 'instead.')
-                    removeFile(False, ren2.dstName)
-                else:
-                    dstName2 = ren2.dstName
-                setIndexStages(dstName1,
-                               None, None,
-                               ren1.dstSha, ren1.dstMode,
-                              None, None)
-                setIndexStages(dstName2,
-                               None, None,
-                               None, None,
-                               ren2.dstSha, ren2.dstMode)
-
-            else:
-                removeFile(True, ren1.srcName)
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
-                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
-                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
-                                   branchName1, branchName2)
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(path, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (content): merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       ren1.srcSha, ren1.srcMode,
-                                       ren1.dstSha, ren1.dstMode,
-                                       ren2.dstSha, ren2.dstMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-        else:
-            removeFile(True, ren1.srcName)
-
-            # Renamed in 1, maybe changed in 2
-            if renamesA == renames1:
-                stage = 3
-            else:
-                stage = 2
-                
-            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
-            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
-
-            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
-            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
-
-            tryMerge = False
-            
-            if ren1.dstName in currentDirectorySet:
-                newPath = uniquePath(ren1.dstName, branchName1)
-                output('CONFLICT (rename/directory): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
-                       'directory', ren1.dstName, 'added in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath, 'instead')
-                cleanMerge = False
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
-            elif srcShaOtherBranch == None:
-                output('CONFLICT (rename/delete): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1, 'and deleted in', branchName2)
-                cleanMerge = False
-                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
-            elif dstShaOtherBranch:
-                newPath = uniquePath(ren1.dstName, branchName2)
-                output('CONFLICT (rename/add): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1 + '.', ren1.dstName, 'added in', branchName2)
-                output('Adding as', newPath, 'instead')
-                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
-                cleanMerge = False
-                tryMerge = True
-            elif renames2.getDst(ren1.dstName):
-                dst2 = renames2.getDst(ren1.dstName)
-                newPath1 = uniquePath(ren1.dstName, branchName1)
-                newPath2 = uniquePath(dst2.dstName, branchName2)
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1+'. Rename',
-                       fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath1, 'and',
-                       dst2.srcName, 'to', newPath2, 'instead')
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
-                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
-                dst2.processed = True
-                cleanMerge = False
-            else:
-                tryMerge = True
-
-            if tryMerge:
-
-                oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
-                aName, bName = ren1.dstName, ren1.srcName
-                aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
-                aMode, bMode = ren1.dstMode, srcModeOtherBranch
-                aBranch, bBranch = branchName1, branchName2
-
-                if renamesA != renames1:
-                    aName, bName = bName, aName
-                    aSHA1, bSHA1 = bSHA1, aSHA1
-                    aMode, bMode = bMode, aMode
-                    aBranch, bBranch = bBranch, aBranch
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(oName, oSHA1, oMode,
-                                   aName, aSHA1, aMode,
-                                   bName, bSHA1, bMode,
-                                   aBranch, bBranch);
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (rename/modify): Merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       oSHA1, oMode,
-                                       aSHA1, aMode,
-                                       bSHA1, bMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-
-    return cleanMerge
-
-# Per entry merge function
-# ------------------------
-
-def processEntry(entry, branch1Name, branch2Name):
-    '''Merge one cache entry.'''
-
-    debug('processing', entry.path, 'clean cache:', cacheOnly)
-
-    cleanMerge = True
-
-    path = entry.path
-    oSha = entry.stages[1].sha1
-    oMode = entry.stages[1].mode
-    aSha = entry.stages[2].sha1
-    aMode = entry.stages[2].mode
-    bSha = entry.stages[3].sha1
-    bMode = entry.stages[3].mode
-
-    assert(oSha == None or isSha(oSha))
-    assert(aSha == None or isSha(aSha))
-    assert(bSha == None or isSha(bSha))
-
-    assert(oMode == None or type(oMode) is int)
-    assert(aMode == None or type(aMode) is int)
-    assert(bMode == None or type(bMode) is int)
-
-    if (oSha and (not aSha or not bSha)):
-    #
-    # Case A: Deleted in one
-    #
-        if (not aSha     and not bSha) or \
-           (aSha == oSha and not bSha) or \
-           (not aSha     and bSha == oSha):
-    # Deleted in both or deleted in one and unchanged in the other
-            if aSha:
-                output('Removing', path)
-            removeFile(True, path)
-        else:
-    # Deleted in one and changed in the other
-            cleanMerge = False
-            if not aSha:
-                output('CONFLICT (delete/modify):', path, 'deleted in',
-                       branch1Name, 'and modified in', branch2Name + '.',
-                       'Version', branch2Name, 'of', path, 'left in tree.')
-                mode = bMode
-                sha = bSha
-            else:
-                output('CONFLICT (modify/delete):', path, 'deleted in',
-                       branch2Name, 'and modified in', branch1Name + '.',
-                       'Version', branch1Name, 'of', path, 'left in tree.')
-                mode = aMode
-                sha = aSha
-
-            updateFile(False, sha, mode, path)
-
-    elif (not oSha and aSha     and not bSha) or \
-         (not oSha and not aSha and bSha):
-    #
-    # Case B: Added in one.
-    #
-        if aSha:
-            addBranch = branch1Name
-            otherBranch = branch2Name
-            mode = aMode
-            sha = aSha
-            conf = 'file/directory'
-        else:
-            addBranch = branch2Name
-            otherBranch = branch1Name
-            mode = bMode
-            sha = bSha
-            conf = 'directory/file'
-    
-        if path in currentDirectorySet:
-            cleanMerge = False
-            newPath = uniquePath(path, addBranch)
-            output('CONFLICT (' + conf + '):',
-                   'There is a directory with name', path, 'in',
-                   otherBranch + '. Adding', path, 'as', newPath)
-
-            removeFile(False, path)
-            updateFile(False, sha, mode, newPath)
-        else:
-            output('Adding', path)
-            updateFile(True, sha, mode, path)
-    
-    elif not oSha and aSha and bSha:
-    #
-    # Case C: Added in both (check for same permissions).
-    #
-        if aSha == bSha:
-            if aMode != bMode:
-                cleanMerge = False
-                output('CONFLICT: File', path,
-                       'added identically in both branches, but permissions',
-                       'conflict', '0%o' % aMode, '->', '0%o' % bMode)
-                output('CONFLICT: adding with permission:', '0%o' % aMode)
-
-                updateFile(False, aSha, aMode, path)
-            else:
-                # This case is handled by git-read-tree
-                assert(False)
-        else:
-            cleanMerge = False
-            newPath1 = uniquePath(path, branch1Name)
-            newPath2 = uniquePath(path, branch2Name)
-            output('CONFLICT (add/add): File', path,
-                   'added non-identically in both branches. Adding as',
-                   newPath1, 'and', newPath2, 'instead.')
-            removeFile(False, path)
-            updateFile(False, aSha, aMode, newPath1)
-            updateFile(False, bSha, bMode, newPath2)
-
-    elif oSha and aSha and bSha:
-    #
-    # case D: Modified in both, but differently.
-    #
-        output('Auto-merging', path)
-        [sha, mode, clean, dummy] = \
-              mergeFile(path, oSha, oMode,
-                        path, aSha, aMode,
-                        path, bSha, bMode,
-                        branch1Name, branch2Name)
-        if clean:
-            updateFile(True, sha, mode, path)
-        else:
-            cleanMerge = False
-            output('CONFLICT (content): Merge conflict in', path)
-
-            if cacheOnly:
-                updateFile(False, sha, mode, path)
-            else:
-                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
-    else:
-        die("ERROR: Fatal merge failure, shouldn't happen.")
-
-    return cleanMerge
-
-def usage():
-    die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
-
-# main entry point as merge strategy module
-# The first parameters up to -- are merge bases, and the rest are heads.
-
-if len(sys.argv) < 4:
-    usage()
-
-bases = []
-for nextArg in xrange(1, len(sys.argv)):
-    if sys.argv[nextArg] == '--':
-        if len(sys.argv) != nextArg + 3:
-            die('Not handling anything other than two heads merge.')
-        try:
-            h1 = firstBranch = sys.argv[nextArg + 1]
-            h2 = secondBranch = sys.argv[nextArg + 2]
-        except IndexError:
-            usage()
-        break
-    else:
-        bases.append(sys.argv[nextArg])
-
-print 'Merging', h1, 'with', h2
-
-try:
-    h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
-    h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
-
-    if len(bases) == 1:
-        base = runProgram(['git-rev-parse', '--verify',
-                           bases[0] + '^0']).rstrip()
-        ancestor = Commit(base, None)
-        [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
-                               firstBranch, secondBranch, None, 0,
-                               ancestor)
-    else:
-        graph = buildGraph([h1, h2])
-        [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
-                               firstBranch, secondBranch, graph)
-
-    print ''
-except:
-    if isinstance(sys.exc_info()[1], SystemExit):
-        raise
-    else:
-        traceback.print_exc(None, sys.stderr)
-        sys.exit(2)
-
-if clean:
-    sys.exit(0)
-else:
-    sys.exit(1)
index d049e164318a416941334b15699d52f87b00387f..5b34b4de99c33a99dfb841795baced8889f74a88 100755 (executable)
@@ -9,21 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <
 LF='
 '
 
-all_strategies='recursive recur octopus resolve stupid ours'
-case "${GIT_USE_RECUR_FOR_RECURSIVE}" in
-'')
-       default_twohead_strategies=recursive ;;
-?*)
-       default_twohead_strategies=recur ;;
-esac
+all_strategies='recur recursive recursive-old octopus resolve stupid ours'
+default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_trivial_merge_strategies='ours'
 use_strategies=
 
 index_merge=t
 if test "@@NO_PYTHON@@"; then
-       all_strategies='recur resolve octopus stupid ours'
-       default_twohead_strategies='resolve'
+       all_strategies='recur recursive resolve octopus stupid ours'
 fi
 
 dropsave() {
@@ -122,10 +116,6 @@ do
                        strategy="$2"
                        shift ;;
                esac
-               case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in
-               recursive,?*)
-                       strategy=recur ;;
-               esac
                case " $all_strategies " in
                *" $strategy "*)
                        use_strategies="$use_strategies$strategy " ;;
index 20f74d416732e681d3b44ea610e428529af49235..a7373c0532fad447263e7199d0b8ec2908c683c9 100755 (executable)
@@ -35,13 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\".
 To restore the original branch and stop rebasing run \"git rebase --abort\".
 "
 unset newbase
-case "${GIT_USE_RECUR_FOR_RECURSIVE}" in
-'')
-       strategy=recursive ;;
-?*)
-       strategy=recur ;;
-esac
-
+strategy=recursive
 do_merge=
 dotest=$GIT_DIR/.dotest-merge
 prec=4
@@ -206,11 +200,6 @@ do
        shift
 done
 
-case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in
-recursive,?*)
-       strategy=recur ;;
-esac
-
 # Make sure we do not have .dotest
 if test -z "$do_merge"
 then
@@ -303,11 +292,11 @@ then
        exit $?
 fi
 
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive"
+if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
 then
-       die 'The recursive merge strategy currently relies on Python,
+       die 'The recursive-old merge strategy is written in Python,
 which this installation of git was not configured with.  Please consider
-a different merge strategy (e.g. octopus, resolve, stupid, ours)
+a different merge strategy (e.g. recursive, resolve, or stupid)
 or install Python and git with Python support.'
 
 fi
index b525fc5dfd9053a6fb63a4d38517e25e75341199..f2c9071d1109e014832f0efd8a1fd67dca44c8af 100755 (executable)
@@ -4,6 +4,7 @@
 #
 
 USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
 no_update_info= all_into_one= remove_redundant=
@@ -32,12 +33,10 @@ trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15
 # There will be more repacking strategies to come...
 case ",$all_into_one," in
 ,,)
-       rev_list='--unpacked'
-       pack_objects='--incremental'
+       args='--unpacked --incremental'
        ;;
 ,t,)
-       rev_list=
-       pack_objects=
+       args=
 
        # Redundancy check in all-into-one case is trivial.
        existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \
@@ -45,11 +44,8 @@ case ",$all_into_one," in
        ;;
 esac
 
-pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
-name=$( { git-rev-list --objects --all $rev_list ||
-         echo "git-rev-list died with exit code $?"
-       } |
-       git-pack-objects --non-empty $pack_objects "$PACKTMP") ||
+args="$args $local $quiet $no_reuse_delta$extra"
+name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") ||
        exit 1
 if [ -z "$name" ]; then
        echo Nothing new to pack.
index ed628974d7d5ff356d05f0cc51a8cb42046a6c1b..988514e293fcde4c536cc18d5cd8c9fb77b890d3 100755 (executable)
@@ -31,7 +31,7 @@
 $ENV{'TZ'}="UTC";
 
 our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
-    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S);
+    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F);
 
 sub usage() {
        print STDERR <<END;
@@ -39,12 +39,12 @@ ()
        [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
        [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
-       [-m] [-M regex] [-A author_file] [-S] [SVN_URL]
+       [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL]
 END
        exit(1);
 }
 
-getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:Suv") or usage();
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage();
 usage if $opt_h;
 
 my $tag_name = $opt_t || "tags";
@@ -548,8 +548,12 @@ sub commit {
                $committer_name = $committer_email = $author;
        }
 
-       if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+       if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
                ($author_name, $author_email) = ($1, $2);
+               print "Author from From: $1 <$2>\n" if ($opt_v);;
+       } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+               ($author_name, $author_email) = ($1, $2);
+               print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
        } else {
                $author_name = $committer_name;
                $author_email = $committer_email;
index 597d29f22fc931cf13574a8d2b0fde9f6b2f9908..44991b1538316b971884bafa5f8f01f678ec9a71 100755 (executable)
 
 sub gitweb_check_feature {
        my ($name) = @_;
-       return undef unless exists $feature{$name};
+       return unless exists $feature{$name};
        my ($sub, $override, @defaults) = (
                $feature{$name}{'sub'},
                $feature{$name}{'override'},
@@ -155,6 +155,13 @@ sub feature_snapshot {
        return ($ctype, $suffix, $command);
 }
 
+sub gitweb_have_snapshot {
+       my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
+       my $have_snapshot = (defined $ctype && defined $suffix);
+
+       return $have_snapshot;
+}
+
 # To enable system wide have in $GITWEB_CONFIG
 # $feature{'pickaxe'}{'default'} = [1];
 # To have project specific config enable override in $GITWEB_CONFIG
@@ -200,9 +207,10 @@ sub feature_pickaxe {
        }
 }
 
+# parameters which are pathnames
 our $project = $cgi->param('p');
 if (defined $project) {
-       if (!validate_input($project) ||
+       if (!validate_pathname($project) ||
            !(-d "$projectroot/$project") ||
            !(-e "$projectroot/$project/HEAD") ||
            ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
@@ -212,38 +220,50 @@ sub feature_pickaxe {
        }
 }
 
-# We have to handle those containing any characters:
 our $file_name = $cgi->param('f');
+if (defined $file_name) {
+       if (!validate_pathname($file_name)) {
+               die_error(undef, "Invalid file parameter");
+       }
+}
+
 our $file_parent = $cgi->param('fp');
+if (defined $file_parent) {
+       if (!validate_pathname($file_parent)) {
+               die_error(undef, "Invalid file parent parameter");
+       }
+}
 
+# parameters which are refnames
 our $hash = $cgi->param('h');
 if (defined $hash) {
-       if (!validate_input($hash)) {
+       if (!validate_refname($hash)) {
                die_error(undef, "Invalid hash parameter");
        }
 }
 
 our $hash_parent = $cgi->param('hp');
 if (defined $hash_parent) {
-       if (!validate_input($hash_parent)) {
+       if (!validate_refname($hash_parent)) {
                die_error(undef, "Invalid hash parent parameter");
        }
 }
 
 our $hash_base = $cgi->param('hb');
 if (defined $hash_base) {
-       if (!validate_input($hash_base)) {
+       if (!validate_refname($hash_base)) {
                die_error(undef, "Invalid hash base parameter");
        }
 }
 
 our $hash_parent_base = $cgi->param('hpb');
 if (defined $hash_parent_base) {
-       if (!validate_input($hash_parent_base)) {
+       if (!validate_refname($hash_parent_base)) {
                die_error(undef, "Invalid hash parent base parameter");
        }
 }
 
+# other parameters
 our $page = $cgi->param('pg');
 if (defined $page) {
        if ($page =~ m/[^0-9]/) {
@@ -273,7 +293,7 @@ sub evaluate_path_info {
                $project =~ s,/*[^/]*$,,;
        }
        # validate project
-       $project = validate_input($project);
+       $project = validate_pathname($project);
        if (!$project ||
            ($export_ok && !-e "$projectroot/$project/$export_ok") ||
            ($strict_export && !project_in_list($project))) {
@@ -294,12 +314,12 @@ sub evaluate_path_info {
                } else {
                        $action  ||= "blob_plain";
                }
-               $hash_base ||= validate_input($refname);
-               $file_name ||= $pathname;
+               $hash_base ||= validate_refname($refname);
+               $file_name ||= validate_pathname($pathname);
        } elsif (defined $refname) {
                # we got "project.git/branch"
                $action ||= "shortlog";
-               $hash   ||= validate_input($refname);
+               $hash   ||= validate_refname($refname);
        }
 }
 evaluate_path_info();
@@ -387,16 +407,34 @@ (%)
 ## ======================================================================
 ## validation, quoting/unquoting and escaping
 
-sub validate_input {
-       my $input = shift;
+sub validate_pathname {
+       my $input = shift || return undef;
 
-       if ($input =~ m/^[0-9a-fA-F]{40}$/) {
-               return $input;
+       # no '.' or '..' as elements of path, i.e. no '.' nor '..'
+       # at the beginning, at the end, and between slashes.
+       # also this catches doubled slashes
+       if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
+               return undef;
        }
-       if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+       # no null characters
+       if ($input =~ m!\0!) {
                return undef;
        }
-       if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
+       return $input;
+}
+
+sub validate_refname {
+       my $input = shift || return undef;
+
+       # textual hashes are O.K.
+       if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+               return $input;
+       }
+       # it must be correct pathname
+       $input = validate_pathname($input)
+               or return undef;
+       # restrictions on ref name according to git-check-ref-format
+       if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
                return undef;
        }
        return $input;
@@ -412,6 +450,15 @@ sub esc_param {
        return $str;
 }
 
+# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+sub esc_url {
+       my $str = shift;
+       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
+       $str =~ s/\+/%2B/g;
+       $str =~ s/ /\+/g;
+       return $str;
+}
+
 # replace invalid utf8 character with SUBSTITUTION sequence
 sub esc_html {
        my $str = shift;
@@ -710,7 +757,7 @@ sub git_get_hash_by_path {
        my $path = shift || return undef;
        my $type = shift;
 
-       my $tree = $base;
+       $path =~ s,/+$,,;
 
        open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
                or die_error(undef, "Open git-ls-tree failed");
@@ -781,7 +828,7 @@ sub git_get_projects_list {
                # 'git%2Fgit.git Linus+Torvalds'
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-               open my ($fd), $projects_list or return undef;
+               open my ($fd), $projects_list or return;
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($path, $owner) = split ' ', $line;
@@ -1328,7 +1375,7 @@ sub git_header_html {
              "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
              "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
              "</a>\n";
-       print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / ";
+       print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
        if (defined $project) {
                print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
                if (defined $action) {
@@ -1600,48 +1647,45 @@ sub git_print_tree_entry {
        my %base_key = ();
        $base_key{hash_base} = $hash_base if defined $hash_base;
 
+       # The format of a table row is: mode list link.  Where mode is
+       # the mode of the entry, list is the name of the entry, an href,
+       # and link is the action links of the entry.
+
        print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
        if ($t->{'type'} eq "blob") {
                print "<td class=\"list\">" .
-                     $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key),
-                             -class => "list"}, esc_html($t->{'name'})) .
-                     "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "blob");
+                       $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+                                              file_name=>"$basedir$t->{'name'}", %base_key),
+                                -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+               print "<td class=\"link\">";
                if ($have_blame) {
-                       print " | " .
-                               $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
-                                                      file_name=>"$basedir$t->{'name'}", %base_key)},
-                                       "blame");
+                       print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+                                                    file_name=>"$basedir$t->{'name'}", %base_key)},
+                                     "blame");
                }
                if (defined $hash_base) {
-                       print " | " .
-                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       if ($have_blame) {
+                               print " | ";
+                       }
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
                print " | " .
-                     $cgi->a({-href => href(action=>"blob_plain",
-                                            hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
-                             "raw") .
-                     "</td>\n";
+                       $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
+                                              file_name=>"$basedir$t->{'name'}")},
+                               "raw");
+               print "</td>\n";
 
        } elsif ($t->{'type'} eq "tree") {
-               print "<td class=\"list\">" .
-                     $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+               print "<td class=\"list\">";
+               print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
                                             file_name=>"$basedir$t->{'name'}", %base_key)},
-                             esc_html($t->{'name'})) .
-                     "</td>\n" .
-                     "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "tree");
+                             esc_html($t->{'name'}));
+               print "</td>\n";
+               print "<td class=\"link\">";
                if (defined $hash_base) {
-                       print " | " .
-                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
@@ -1662,7 +1706,7 @@ sub git_difftree_body {
        print "</div>\n";
 
        print "<table class=\"diff_tree\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
                my %diff = parse_difftree_raw_line($line);
@@ -1695,47 +1739,42 @@ sub git_difftree_body {
                        my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
                        $mode_chng   .= " with mode: $to_mode_str" if $to_mode_str;
                        $mode_chng   .= "]</span>";
-                       print "<td>" .
-                             $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                       print "<td>";
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
                                                     hash_base=>$hash, file_name=>$diff{'file'}),
-                                     -class => "list"}, esc_html($diff{'file'})) .
-                             "</td>\n" .
-                             "<td>$mode_chng</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
-                                     "blob");
+                                      -class => "list"}, esc_html($diff{'file'}));
+                       print "</td>\n";
+                       print "<td>$mode_chng</td>\n";
+                       print "<td class=\"link\">";
                        if ($action eq 'commitdiff') {
                                # link to patch
                                $patchno++;
-                               print " | " .
-                                     $cgi->a({-href => "#patch$patchno"}, "patch");
+                               print $cgi->a({-href => "#patch$patchno"}, "patch");
                        }
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "D") { # deleted
                        my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
-                       print "<td>" .
-                             $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                       print "<td>";
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
                                                     hash_base=>$parent, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_html($diff{'file'})) .
-                             "</td>\n" .
-                             "<td>$mode_chng</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'file'})},
-                                     "blob") .
-                             " | ";
+                                      -class => "list"}, esc_html($diff{'file'}));
+                       print "</td>\n";
+                       print "<td>$mode_chng</td>\n";
+                       print "<td class=\"link\">";
                        if ($action eq 'commitdiff') {
                                # link to patch
                                $patchno++;
-                               print " | " .
-                                     $cgi->a({-href => "#patch$patchno"}, "patch");
+                               print $cgi->a({-href => "#patch$patchno"}, "patch");
+                               print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'file'})},
-                                     "history") .
-                             "</td>\n";
+                                                    file_name=>$diff{'file'})},
+                                     "history");
+                       print "</td>\n";
 
                } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
                        my $mode_chnge = "";
@@ -1754,42 +1793,32 @@ sub git_difftree_body {
                                $mode_chnge .= "]</span>\n";
                        }
                        print "<td>";
-                       if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
-                               print $cgi->a({-href => href(action=>"blobdiff",
-                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                            hash_base=>$hash, hash_parent_base=>$parent,
-                                                            file_name=>$diff{'file'}),
-                                             -class => "list"}, esc_html($diff{'file'}));
-                       } else { # only mode changed
-                               print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                            hash_base=>$hash, file_name=>$diff{'file'}),
-                                             -class => "list"}, esc_html($diff{'file'}));
-                       }
-                       print "</td>\n" .
-                             "<td>$mode_chnge</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
-                                     "blob");
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'}),
+                                      -class => "list"}, esc_html($diff{'file'}));
+                       print "</td>\n";
+                       print "<td>$mode_chnge</td>\n";
+                       print "<td class=\"link\">";
                        if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
                                if ($action eq 'commitdiff') {
                                        # link to patch
                                        $patchno++;
-                                       print " | " .
-                                               $cgi->a({-href => "#patch$patchno"}, "patch");
+                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
                                } else {
-                                       print " | " .
-                                               $cgi->a({-href => href(action=>"blobdiff",
-                                                                      hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                      hash_base=>$hash, hash_parent_base=>$parent,
-                                                                      file_name=>$diff{'file'})},
-                                                       "diff");
+                                       print $cgi->a({-href => href(action=>"blobdiff",
+                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                                    hash_base=>$hash, hash_parent_base=>$parent,
+                                                                    file_name=>$diff{'file'})},
+                                                     "diff");
                                }
+                               print " | ";
                        }
-                       print " | " .
-                               $cgi->a({-href => href(action=>"history",
-                                                      hash_base=>$hash, file_name=>$diff{'file'})},
-                                       "history");
+                       print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+                                                    file_name=>$diff{'file'})},
+                                     "blame") . " | ";
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+                                                    file_name=>$diff{'file'})},
+                                     "history");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
@@ -1809,25 +1838,27 @@ sub git_difftree_body {
                                                     hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
                                      -class => "list"}, esc_html($diff{'from_file'})) .
                              " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
-                                                    hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})},
-                                     "blob");
+                             "<td class=\"link\">";
                        if ($diff{'to_id'} ne $diff{'from_id'}) {
                                if ($action eq 'commitdiff') {
                                        # link to patch
                                        $patchno++;
-                                       print " | " .
-                                               $cgi->a({-href => "#patch$patchno"}, "patch");
+                                       print $cgi->a({-href => "#patch$patchno"}, "patch");
                                } else {
-                                       print " | " .
-                                               $cgi->a({-href => href(action=>"blobdiff",
-                                                                      hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
-                                                                      hash_base=>$hash, hash_parent_base=>$parent,
-                                                                      file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
-                                                       "diff");
+                                       print $cgi->a({-href => href(action=>"blobdiff",
+                                                                    hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                                    hash_base=>$hash, hash_parent_base=>$parent,
+                                                                    file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+                                                     "diff");
                                }
+                               print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+                                                    file_name=>$diff{'from_file'})},
+                                     "blame") . " | ";
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
+                                                    file_name=>$diff{'from_file'})},
+                                     "history");
                        print "</td>\n";
 
                } # we should not encounter Unmerged (U) or Unknown (X) status
@@ -1969,7 +2000,7 @@ sub git_shortlog_body {
        $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
 
        print "<table class=\"shortlog\" cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                my $commit = $revlist->[$i];
                #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
@@ -1989,9 +2020,9 @@ sub git_shortlog_body {
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
-                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
+                     $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " .
+                     $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
                print "</td>\n" .
                      "</tr>\n";
        }
@@ -2011,7 +2042,7 @@ sub git_history_body {
        $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
 
        print "<table class=\"history\" cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
                        next;
@@ -2040,9 +2071,8 @@ sub git_history_body {
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
-                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
-                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
-                     $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype);
+                     $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
+                     $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
 
                if ($ftype eq 'blob') {
                        my $blob_current = git_get_hash_by_path($hash_base, $file_name);
@@ -2075,7 +2105,7 @@ sub git_tags_body {
        $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
 
        print "<table class=\"tags\" cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $taglist->[$i];
                my %tag = %$entry;
@@ -2135,7 +2165,7 @@ sub git_heads_body {
        $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
 
        print "<table class=\"heads\" cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
                my $entry = $headlist->[$i];
                my %tag = %$entry;
@@ -2251,7 +2281,7 @@ sub git_project_list {
        }
        print "<th></th>\n" .
              "</tr>\n";
-       my $alternate = 0;
+       my $alternate = 1;
        foreach my $pr (@projects) {
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@ -2283,7 +2313,7 @@ sub git_project_index {
        print $cgi->header(
                -type => 'text/plain',
                -charset => 'utf-8',
-               -content_disposition => qq(inline; filename="index.aux"));
+               -content_disposition => 'inline; filename="index.aux"');
 
        foreach my $pr (@projects) {
                if (!exists $pr->{'owner'}) {
@@ -2629,7 +2659,7 @@ sub git_blob_plain {
        print $cgi->header(
                -type => "$type",
                -expires=>$expires,
-               -content_disposition => "inline; filename=\"$save_as\"");
+               -content_disposition => 'inline; filename="' . "$save_as" . '"');
        undef $/;
        binmode STDOUT, ':raw';
        print <$fd>;
@@ -2713,17 +2743,16 @@ sub git_blob {
 }
 
 sub git_tree {
-       my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
-       my $have_snapshot = (defined $ctype && defined $suffix);
+       my $have_snapshot = gitweb_have_snapshot();
 
+       if (!defined $hash_base) {
+               $hash_base = "HEAD";
+       }
        if (!defined $hash) {
-               $hash = git_get_head_hash($project);
                if (defined $file_name) {
-                       my $base = $hash_base || $hash;
-                       $hash = git_get_hash_by_path($base, $file_name, "tree");
-               }
-               if (!defined $hash_base) {
-                       $hash_base = $hash;
+                       $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
+               } else {
+                       $hash = $hash_base;
                }
        }
        $/ = "\0";
@@ -2769,7 +2798,7 @@ sub git_tree {
        git_print_page_path($file_name, 'tree', $hash_base);
        print "<div class=\"page_body\">\n";
        print "<table cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        foreach my $line (@entries) {
                my %t = parse_ls_tree_line($line, -z => 1);
 
@@ -2790,7 +2819,6 @@ sub git_tree {
 }
 
 sub git_snapshot {
-
        my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
        my $have_snapshot = (defined $ctype && defined $suffix);
        if (!$have_snapshot) {
@@ -2803,10 +2831,11 @@ sub git_snapshot {
 
        my $filename = basename($project) . "-$hash.tar.$suffix";
 
-       print $cgi->header(-type => 'application/x-tar',
-                          -content_encoding => $ctype,
-                          -content_disposition => "inline; filename=\"$filename\"",
-                          -status => '200 OK');
+       print $cgi->header(
+               -type => 'application/x-tar',
+               -content_encoding => $ctype,
+               -content_disposition => 'inline; filename="' . "$filename" . '"',
+               -status => '200 OK');
 
        my $git_command = git_cmd_str();
        open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or
@@ -2899,12 +2928,10 @@ sub git_commit {
        my $refs = git_get_references();
        my $ref = format_ref_marker($refs, $co{'id'});
 
-       my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
-       my $have_snapshot = (defined $ctype && defined $suffix);
+       my $have_snapshot = gitweb_have_snapshot();
 
        my @views_nav = ();
        if (defined $file_name && defined $co{'parent'}) {
-               my $parent = $co{'parent'};
                push @views_nav,
                        $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
                                "blame");
@@ -3116,7 +3143,7 @@ sub git_blobdiff {
                        -type => 'text/plain',
                        -charset => 'utf-8',
                        -expires => $expires,
-                       -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch"));
+                       -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
 
                print "X-Git-Url: " . $cgi->self_url() . "\n\n";
 
@@ -3219,7 +3246,7 @@ sub git_commitdiff {
                        -type => 'text/plain',
                        -charset => 'utf-8',
                        -expires => $expires,
-                       -content_disposition => qq(inline; filename="$filename"));
+                       -content_disposition => 'inline; filename="' . "$filename" . '"');
                my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
                print <<TEXT;
 From: $co{'author'}
@@ -3364,7 +3391,7 @@ sub git_search {
        git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
        print "<table cellspacing=\"0\">\n";
-       my $alternate = 0;
+       my $alternate = 1;
        if ($commit_search) {
                $/ = "\0";
                open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
diff --git a/grep.c b/grep.c
index cc8d6846a59563b6b902f576262ec97b73f01562..c411ddd4d52e2aa68edd979733dd0b15f51d5f32 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -138,16 +138,13 @@ void compile_grep_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
 
-       if (opt->fixed)
-               return;
-
-       /* First compile regexps */
        for (p = opt->pattern_list; p; p = p->next) {
                switch (p->token) {
                case GREP_PATTERN: /* atom */
                case GREP_PATTERN_HEAD:
                case GREP_PATTERN_BODY:
-                       compile_regexp(p, opt);
+                       if (!opt->fixed)
+                               compile_regexp(p, opt);
                        break;
                default:
                        opt->extended = 1;
@@ -167,6 +164,46 @@ void compile_grep_patterns(struct grep_opt *opt)
                die("incomplete pattern expression: %s", p->pattern);
 }
 
+static void free_pattern_expr(struct grep_expr *x)
+{
+       switch (x->node) {
+       case GREP_NODE_ATOM:
+               break;
+       case GREP_NODE_NOT:
+               free_pattern_expr(x->u.unary);
+               break;
+       case GREP_NODE_AND:
+       case GREP_NODE_OR:
+               free_pattern_expr(x->u.binary.left);
+               free_pattern_expr(x->u.binary.right);
+               break;
+       }
+       free(x);
+}
+
+void free_grep_patterns(struct grep_opt *opt)
+{
+       struct grep_pat *p, *n;
+
+       for (p = opt->pattern_list; p; p = n) {
+               n = p->next;
+               switch (p->token) {
+               case GREP_PATTERN: /* atom */
+               case GREP_PATTERN_HEAD:
+               case GREP_PATTERN_BODY:
+                       regfree(&p->regexp);
+                       break;
+               default:
+                       break;
+               }
+               free(p);
+       }
+
+       if (!opt->extended)
+               return;
+       free_pattern_expr(opt->pattern_expression);
+}
+
 static char *end_of_line(char *cp, unsigned long *left)
 {
        unsigned long l = *left;
@@ -439,6 +476,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
                lno++;
        }
 
+       free(prev);
+
        if (opt->status_only)
                return 0;
        if (opt->unmatch_name_only) {
diff --git a/grep.h b/grep.h
index 0b503ea6657692672c3365f3090151cc5e9e6681..af9098cfe8699680aeaa11790dad3e8d556ed395 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -73,6 +73,7 @@ struct grep_opt {
 
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
 extern void compile_grep_patterns(struct grep_opt *opt);
+extern void free_grep_patterns(struct grep_opt *opt);
 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
 
 #endif
diff --git a/http.c b/http.c
index 6c1937b676ecd584cededed1c82df30448046ebe..576740feff9ffa62b65f8cdf0a38f2354872c520 100644 (file)
--- a/http.c
+++ b/http.c
@@ -23,6 +23,7 @@ char *ssl_capath = NULL;
 char *ssl_cainfo = NULL;
 long curl_low_speed_limit = -1;
 long curl_low_speed_time = -1;
+int curl_ftp_no_epsv = 0;
 
 struct curl_slist *pragma_header;
 
@@ -155,6 +156,11 @@ static int http_options(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp("http.noepsv", var)) {
+               curl_ftp_no_epsv = git_config_bool(var, value);
+               return 0;
+       }
+
        /* Fall back on the default ones */
        return git_default_config(var, value);
 }
@@ -196,6 +202,9 @@ static CURL* get_curl_handle(void)
 
        curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
 
+       if (curl_ftp_no_epsv)
+               curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
+
        return result;
 }
 
@@ -251,6 +260,9 @@ void http_init(void)
                max_requests = DEFAULT_MAX_REQUESTS;
 #endif
 
+       if (getenv("GIT_CURL_FTP_NO_EPSV"))
+               curl_ftp_no_epsv = 1;
+
 #ifndef NO_CURL_EASY_DUPHANDLE
        curl_default = get_curl_handle();
 #endif
index 4570c123dce811bab8a89efe4c3b4a9a977e6c5a..5d9d1889f088c64131cd3c116e6a16876ce95db8 100644 (file)
@@ -4,9 +4,35 @@
 
 #include <string.h>
 
+#include "git-compat-util.h"
 #include "interpolate.h"
 
 
+void interp_set_entry(struct interp *table, int slot, const char *value)
+{
+       char *oldval = table[slot].value;
+       char *newval = NULL;
+
+       if (oldval)
+               free(oldval);
+
+       if (value)
+               newval = xstrdup(value);
+
+       table[slot].value = newval;
+}
+
+
+void interp_clear_table(struct interp *table, int ninterps)
+{
+       int i;
+
+       for (i = 0; i < ninterps; i++) {
+               interp_set_entry(table, i, NULL);
+       }
+}
+
+
 /*
  * Convert a NUL-terminated string in buffer orig
  * into the supplied buffer, result, whose length is reslen,
index d16f9244f3de25ec9e554c6d9f5bae07de7bf2a0..190a180b587aa565ae2ae71f9eb068ad75db168f 100644 (file)
@@ -16,6 +16,9 @@ struct interp {
        char *value;
 };
 
+extern void interp_set_entry(struct interp *table, int slot, const char *value);
+extern void interp_clear_table(struct interp *table, int ninterps);
+
 extern int interpolate(char *result, int reslen,
                       const char *orig,
                       const struct interp *interps, int ninterps);
index 5b04efc89d54c79df49495bb8fa44931729df682..6907cbcd295614bf895ef2a8d0be59e08e570f09 100755 (executable)
@@ -61,4 +61,16 @@ test_expect_success \
         test -f .git/logs/refs/heads/g/h/i &&
         diff expect .git/logs/refs/heads/g/h/i'
 
+test_expect_success \
+    'git branch j/k should work after branch j has been deleted' \
+       'git-branch j &&
+        git-branch -d j &&
+        git-branch j/k'
+
+test_expect_success \
+    'git branch l should work after branch l/m has been deleted' \
+       'git-branch l/m &&
+        git-branch -d l/m &&
+        git-branch l'
+
 test_done
index d36f22d7deb071d50d220eac76bae5ef6415e412..c20e4c29fcc864dff80e5ae73a8ad7ede1f794f0 100755 (executable)
@@ -35,7 +35,7 @@ test_expect_success \
        'git repo-config core.filemode 0 &&
         echo foo >xfoo2 &&
         chmod 755 xfoo2 &&
-        git-add xfoo2 &&
+        git-update-index --add xfoo2 &&
         case "`git-ls-files --stage xfoo2`" in
         100644" "*xfoo2) echo ok;;
         *) echo fail; git-ls-files --stage xfoo2; exit 1;;
diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh
new file mode 100755 (executable)
index 0000000..b2131cd
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+test_description='Revision traversal vs grafts and path limiter'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir subdir &&
+       echo >fileA fileA &&
+       echo >subdir/fileB fileB &&
+       git add fileA subdir/fileB &&
+       git commit -a -m "Initial in one history." &&
+       A0=`git rev-parse --verify HEAD` &&
+
+       echo >fileA fileA modified &&
+       git commit -a -m "Second in one history." &&
+       A1=`git rev-parse --verify HEAD` &&
+
+       echo >subdir/fileB fileB modified &&
+       git commit -a -m "Third in one history." &&
+       A2=`git rev-parse --verify HEAD` &&
+
+       rm -f .git/refs/heads/master .git/index &&
+
+       echo >fileA fileA again &&
+       echo >subdir/fileB fileB again &&
+       git add fileA subdir/fileB &&
+       git commit -a -m "Initial in alternate history." &&
+       B0=`git rev-parse --verify HEAD` &&
+
+       echo >fileA fileA modified in alternate history &&
+       git commit -a -m "Second in alternate history." &&
+       B1=`git rev-parse --verify HEAD` &&
+
+       echo >subdir/fileB fileB modified in alternate history &&
+       git commit -a -m "Third in alternate history." &&
+       B2=`git rev-parse --verify HEAD` &&
+       : done
+'
+
+check () {
+       type=$1
+       shift
+
+       arg=
+       which=arg
+       rm -f test.expect
+       for a
+       do
+               if test "z$a" = z--
+               then
+                       which=expect
+                       child=
+                       continue
+               fi
+               if test "$which" = arg
+               then
+                       arg="$arg$a "
+                       continue
+               fi
+               if test "$type" = basic
+               then
+                       echo "$a"
+               else
+                       if test "z$child" != z
+                       then
+                               echo "$child $a"
+                       fi
+                       child="$a"
+               fi
+       done >test.expect
+       if test "$type" != basic && test "z$child" != z
+       then
+               echo >>test.expect $child
+       fi
+       if test $type = basic
+       then
+               git rev-list $arg >test.actual
+       elif test $type = parents
+       then
+               git rev-list --parents $arg >test.actual
+       elif test $type = parents-raw
+       then
+               git rev-list --parents --pretty=raw $arg |
+               sed -n -e 's/^commit //p' >test.actual
+       fi
+       diff test.expect test.actual
+}
+
+for type in basic parents parents-raw
+do
+       test_expect_success 'without grafts' "
+               rm -f .git/info/grafts
+               check $type $B2 -- $B2 $B1 $B0
+       "
+
+       test_expect_success 'with grafts' "
+               echo '$B0 $A2' >.git/info/grafts
+               check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0
+       "
+
+       test_expect_success 'without grafts, with pathlimit' "
+               rm -f .git/info/grafts
+               check $type $B2 subdir -- $B2 $B0
+       "
+
+       test_expect_success 'with grafts, with pathlimit' "
+               echo '$B0 $A2' >.git/info/grafts
+               check $type $B2 subdir -- $B2 $B0 $A2 $A0
+       "
+
+done
+test_done
index b64e8b7d773f9503c7a5b0e80945be60f71e345f..085d4a096b90e076afef70a7e8b7746f58a49060 100755 (executable)
@@ -31,6 +31,15 @@ test_expect_success setup '
        git checkout master
 '
 
+test_expect_success "checkout from non-existing branch" '
+
+       git checkout -b delete-me master &&
+       rm .git/refs/heads/delete-me &&
+       test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+       git checkout master &&
+       test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
 test_expect_success "checkout with dirty tree without -m" '
 
        fill 0 1 2 3 4 5 >one &&
index e75ad5faace6e40e7f6f3f47e9be90b0e9c4691e..0fe2718845fa8ad66dfa850ac2921e33b10b51c1 100755 (executable)
@@ -211,7 +211,7 @@ export PATH GIT_EXEC_PATH
 PYTHON=`sed -e '1{
        s/^#!//
        q
-}' ../git-merge-recursive` || {
+}' ../git-merge-recursive-old` || {
        error "You haven't built things yet, have you?"
 }
 "$PYTHON" -c 'import subprocess' 2>/dev/null || {