Merge branch 'master' into lj/refs
authorJunio C Hamano <junkio@cox.net>
Mon, 2 Oct 2006 18:49:59 +0000 (11:49 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 2 Oct 2006 18:49:59 +0000 (11:49 -0700)
* master: (99 commits)
lock_ref_sha1_basic does not remove empty directories on BSD
git-push: .git/remotes/ file does not require SP after colon
git-mv: invalidate the removed path properly in cache-tree
Makefile: install and clean merge-recur, still.
GIT 1.4.3-rc1
gitweb: tree view: hash_base and hash are now context sensitive
git-diff -B output fix.
fetch: Reset remote refs list each time fetch_main is called
Remove -fPIC which was only needed for Git.xs
Fix approxidate() to understand 12:34 AM/PM are 00:34 and 12:34
git-diff -B output fix.
Make cvsexportcommit remove files.
diff --stat: ensure at least one '-' for deletions, and one '+' for additions
diff --stat=width[,name-width]: allow custom diffstat output width.
gitweb: History: blob and tree are first, then commitdiff, etc
gitweb: Remove redundant "commit" from history
http/ftp: optionally ask curl to not use EPSV command
gitweb: Don't use quotemeta on internally generated strings
gitweb: Add snapshot to shortlog
gitweb: Factor out gitweb_have_snapshot()
...

37 files changed:
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-daemon.txt
Documentation/git-repo-config.txt
GIT-VERSION-GEN
INSTALL
Makefile
builtin-log.c
builtin-mv.c
builtin-push.c
builtin-repo-config.c
compat/inet_pton.c [new file with mode: 0644]
config.mak.in
configure.ac
contrib/completion/git-completion.bash [new file with mode: 0755]
daemon.c
date.c
diff.c
diff.h
git-clone.sh
git-cvsexportcommit.perl
git-fetch.sh
git-ls-remote.sh
git-send-email.perl
git-svnimport.perl
git.spec.in
gitweb/gitweb.perl
http.c
interpolate.c
interpolate.h
perl/.gitignore [new file with mode: 0644]
perl/Git.pm [new file with mode: 0644]
perl/Makefile.PL [new file with mode: 0644]
perl/private-Error.pm [new file with mode: 0644]
refs.c
t/t7001-mv.sh
t/test-lib.sh
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 b5d9763594b3fcb4dcc273c0118f7edbf236380d..7b7b9e8ce92db7fbb2072b75a613b073b0c5a4e6 100644 (file)
 --patch-with-raw::
        Synonym for "-p --raw".
 
---stat::
-       Generate a diffstat.
+--stat[=width[,name-width]]::
+       Generate a diffstat.  You can override the default
+       output width for 80-column terminal by "--stat=width".
+       The width of the filename part can be controlled by
+       giving another width to it separated by a comma.
 
 --summary::
        Output a condensed summary of extended header information
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 b03d66f61ca4b234e721d671cc2521182a8e12b1..8a1ab61e943aba92aed6734c949b196850914a3a 100644 (file)
@@ -54,7 +54,8 @@ OPTIONS
 
 --get::
        Get the value for a given key (optionally filtered by a regex
-       matching the value).
+       matching the value). Returns error code 1 if the key was not
+       found and error code 2 if multiple key values were found.
 
 --get-all::
        Like get, but does not fail if the number of values for the key
index 14923c973bdcb87aaacb27f98eecc6215d5293b6..0cacac38fcfb8a98e70ac186cf5d783d925b3556 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.2.GIT
+DEF_VER=v1.4.3.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index fa9bf74a208697bd4c6cd5c91f3335b2f5475a49..fce6bc39d562271417fe4caeaf2d5dd9691deaf8 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -38,6 +38,19 @@ Issues of note:
    has been actively developed since 1997, and people have moved over to
    graphical file managers.
 
+ - You can use git after building but without installing if you
+   wanted to.  Various git commands need to find other git
+   commands and scripts to do their work, so you would need to
+   arrange a few environment variables to tell them that their
+   friends will be found in your built source area instead of at
+   their standard installation area.  Something like this works
+   for me:
+
+       GIT_EXEC_PATH=`pwd`
+       PATH=`pwd`:$PATH
+       GITPERLLIB=`pwd`/perl/blib/lib
+       export GIT_EXEC_PATH PATH GITPERLLIB
+
  - Git is reasonably self-sufficient, but does depend on a few external
    programs and libraries:
 
index 65fb8284f3c9ef210c08711b5ce4796593e72e75..e826247ccaa5ce2412f3cc10e2e46f7454ae8523 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,6 @@
 # The default target of this Makefile is...
 all:
 
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies MOZILLA_SHA1.
 #
@@ -60,6 +55,11 @@ all:
 # Define ARM_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for ARM.
 #
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
 # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
 #
 # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
@@ -84,13 +84,13 @@ all:
 # 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.
-
+#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
 # randomly break unless your underlying filesystem supports those sub-second
 # times (my ext3 doesn't).
-
+#
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
 
@@ -149,6 +149,12 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 
 ### --- END CONFIGURATION SECTION ---
 
+# Those must not be GNU-specific; they are shared with perl/ which may
+# be built by a different compiler. (Note that this is an artifact now
+# but it still might be nice to keep that distinction.)
+BASIC_CFLAGS =
+BASIC_LDFLAGS =
+
 SCRIPT_SH = \
        git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
@@ -209,7 +215,8 @@ BUILT_INS = \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \
+       git-merge-recur$X
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -305,7 +312,7 @@ BUILTIN_OBJS = \
        builtin-pack-refs.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-LIBS = $(GITLIBS) -lz
+EXTLIBS = -lz
 
 #
 # Platform specific tweaks
@@ -327,14 +334,14 @@ ifeq ($(uname_S),Darwin)
        NO_STRLCPY = YesPlease
        ifndef NO_FINK
                ifeq ($(shell test -d /sw/lib && echo y),y)
-                       ALL_CFLAGS += -I/sw/include
-                       ALL_LDFLAGS += -L/sw/lib
+                       BASIC_CFLAGS += -I/sw/include
+                       BASIC_LDFLAGS += -L/sw/lib
                endif
        endif
        ifndef NO_DARWIN_PORTS
                ifeq ($(shell test -d /opt/local/lib && echo y),y)
-                       ALL_CFLAGS += -I/opt/local/include
-                       ALL_LDFLAGS += -L/opt/local/lib
+                       BASIC_CFLAGS += -I/opt/local/include
+                       BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
 endif
@@ -356,7 +363,7 @@ ifeq ($(uname_S),SunOS)
        endif
        INSTALL = ginstall
        TAR = gtar
-       ALL_CFLAGS += -D__EXTENSIONS__
+       BASIC_CFLAGS += -D__EXTENSIONS__
 endif
 ifeq ($(uname_O),Cygwin)
        NO_D_TYPE_IN_DIRENT = YesPlease
@@ -374,21 +381,22 @@ ifeq ($(uname_O),Cygwin)
 endif
 ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
-       ALL_CFLAGS += -I/usr/local/include
-       ALL_LDFLAGS += -L/usr/local/lib
+       BASIC_CFLAGS += -I/usr/local/include
+       BASIC_LDFLAGS += -L/usr/local/lib
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        NEEDS_LIBICONV = YesPlease
-       ALL_CFLAGS += -I/usr/local/include
-       ALL_LDFLAGS += -L/usr/local/lib
+       BASIC_CFLAGS += -I/usr/local/include
+       BASIC_LDFLAGS += -L/usr/local/lib
 endif
 ifeq ($(uname_S),NetBSD)
        ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
                NEEDS_LIBICONV = YesPlease
        endif
-       ALL_CFLAGS += -I/usr/pkg/include
-       ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+       BASIC_CFLAGS += -I/usr/pkg/include
+       BASIC_LDFLAGS += -L/usr/pkg/lib
+       ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
 endif
 ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
@@ -402,9 +410,9 @@ ifeq ($(uname_S),IRIX64)
        NO_STRLCPY = YesPlease
        NO_SOCKADDR_STORAGE=YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
-       ALL_CFLAGS += -DPATH_MAX=1024
+       BASIC_CFLAGS += -DPATH_MAX=1024
        # for now, build 32-bit version
-       ALL_LDFLAGS += -L/usr/lib32
+       BASIC_LDFLAGS += -L/usr/lib32
 endif
 ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
@@ -426,7 +434,7 @@ endif
 ifndef NO_CURL
        ifdef CURLDIR
                # This is still problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(CURLDIR)/include
+               BASIC_CFLAGS += -I$(CURLDIR)/include
                CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
        else
                CURL_LIBCURL = -lcurl
@@ -447,13 +455,13 @@ ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                # Again this may be problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(OPENSSLDIR)/include
+               BASIC_CFLAGS += -I$(OPENSSLDIR)/include
                OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
        else
                OPENSSL_LINK =
        endif
 else
-       ALL_CFLAGS += -DNO_OPENSSL
+       BASIC_CFLAGS += -DNO_OPENSSL
        MOZILLA_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
@@ -465,32 +473,32 @@ endif
 ifdef NEEDS_LIBICONV
        ifdef ICONVDIR
                # Again this may be problematic -- gcc does not always want -R.
-               ALL_CFLAGS += -I$(ICONVDIR)/include
+               BASIC_CFLAGS += -I$(ICONVDIR)/include
                ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
        else
                ICONV_LINK =
        endif
-       LIBS += $(ICONV_LINK) -liconv
+       EXTLIBS += $(ICONV_LINK) -liconv
 endif
 ifdef NEEDS_SOCKET
-       LIBS += -lsocket
+       EXTLIBS += -lsocket
        SIMPLE_LIB += -lsocket
 endif
 ifdef NEEDS_NSL
-       LIBS += -lnsl
+       EXTLIBS += -lnsl
        SIMPLE_LIB += -lnsl
 endif
 ifdef NO_D_TYPE_IN_DIRENT
-       ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT
+       BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
 endif
 ifdef NO_D_INO_IN_DIRENT
-       ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
+       BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
 endif
 ifdef NO_C99_FORMAT
        ALL_CFLAGS += -DNO_C99_FORMAT
 endif
 ifdef NO_SYMLINK_HEAD
-       ALL_CFLAGS += -DNO_SYMLINK_HEAD
+       BASIC_CFLAGS += -DNO_SYMLINK_HEAD
 endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
@@ -513,21 +521,24 @@ ifdef NO_MMAP
        COMPAT_OBJS += compat/mmap.o
 endif
 ifdef NO_IPV6
-       ALL_CFLAGS += -DNO_IPV6
+       BASIC_CFLAGS += -DNO_IPV6
 endif
 ifdef NO_SOCKADDR_STORAGE
 ifdef NO_IPV6
-       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in
+       BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in
 else
-       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6
+       BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6
 endif
 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
+       BASIC_CFLAGS += -DNO_ICONV
 endif
 
 ifdef PPC_SHA1
@@ -543,12 +554,12 @@ ifdef MOZILLA_SHA1
        LIB_OBJS += mozilla-sha1/sha1.o
 else
        SHA1_HEADER = <openssl/sha.h>
-       LIBS += $(LIB_4_CRYPTO)
+       EXTLIBS += $(LIB_4_CRYPTO)
 endif
 endif
 endif
 ifdef NO_ACCURATE_DIFF
-       ALL_CFLAGS += -DNO_ACCURATE_DIFF
+       BASIC_CFLAGS += -DNO_ACCURATE_DIFF
 endif
 
 # Shell quote (do not use $(call) to accommodate ancient setups);
@@ -566,15 +577,23 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
 
-ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+LIBS = $(GITLIBS) $(EXTLIBS)
+
+BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
+
+ALL_CFLAGS += $(BASIC_CFLAGS)
+ALL_LDFLAGS += $(BASIC_LDFLAGS)
+
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+
+
 ### Build rules
 
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
-       git-merge-recur$X
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
 
-all:
+all: perl/Makefile
+       $(MAKE) -C perl
        $(MAKE) -C templates
 
 strip: $(PROGRAMS) git$X
@@ -608,9 +627,18 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        chmod +x $@+
        mv $@+ $@
 
-$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        rm -f $@ $@+
-       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+       INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
+       sed -e '1{' \
+           -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+           -e '        h' \
+           -e '        s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+           -e '        H' \
+           -e '        x' \
+           -e '}' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@+
        chmod +x $@+
@@ -740,6 +768,10 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
        rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
+perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
+       (cd perl && $(PERL_PATH) Makefile.PL \
+               PREFIX='$(prefix_SQ)')
+
 doc:
        $(MAKE) -C Documentation all
 
@@ -802,6 +834,7 @@ install: all
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+       $(MAKE) -C perl install
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
        $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@ -872,7 +905,9 @@ clean:
        rm -f $(htmldocs).tar.gz $(manpages).tar.gz
        rm -f gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
-       $(MAKE) -C templates clean
+       [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
+       rm -f perl/ppport.h perl/Makefile.old
+       $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
        rm -f GIT-VERSION-FILE GIT-CFLAGS
 
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?");
index 4d21d88412cac52cbd5d127de439abc3e172b352..54dd3bfe8ac787fda32f3557faac00c65a61de96 100644 (file)
@@ -278,6 +278,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                for (i = 0; i < deleted.nr; i++) {
                        const char *path = deleted.items[i].path;
                        remove_file_from_cache(path);
+                       cache_tree_invalidate_path(active_cache_tree, path);
                }
 
                if (active_cache_changed) {
index 581c44ba8e404b80e6a1c2010b501d9bb0642f0b..5f7eccf14b272fc108bcd49aece911ebde460b4f 100644 (file)
@@ -78,12 +78,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
                int is_refspec;
                char *s, *p;
 
-               if (!strncmp("URL: ", buffer, 5)) {
+               if (!strncmp("URL:", buffer, 4)) {
                        is_refspec = 0;
-                       s = buffer + 5;
-               } else if (!strncmp("Push: ", buffer, 6)) {
+                       s = buffer + 4;
+               } else if (!strncmp("Push:", buffer, 5)) {
                        is_refspec = 1;
-                       s = buffer + 6;
+                       s = buffer + 5;
                } else
                        continue;
 
index 9cf12d32e5bc0be8034b16551336afee1e7d3a00..f60cee1dc54497d8ba00069a56e14b431f6a0852 100644 (file)
@@ -119,7 +119,7 @@ static int get_value(const char* key_, const char* regex_)
        if (do_all)
                ret = !seen;
        else
-               ret =  (seen == 1) ? 0 : 1;
+               ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
 
 free_strings:
        free(repo_config);
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 6d20673b24552c505664c8d6983eb9ceb1b106d8..1cafa19ed4e4b9ecb683e4bc6c4aaa3ac3322b74 100644 (file)
@@ -2,6 +2,7 @@
 # @configure_input@
 
 CC = @CC@
+CFLAGS = @CFLAGS@
 AR = @AR@
 TAR = @TAR@
 #INSTALL = @INSTALL@           # needs install-sh or install.sh in sources
index b1a5833b40b905ae07b27c414833394ef63b0c99..cff5722eb9927ec652e3db731fc033508bc44ef2 100644 (file)
@@ -94,7 +94,7 @@ AC_SUBST(PYTHON_PATH)
 ## Checks for programs.
 AC_MSG_NOTICE([CHECKS for programs])
 #
-AC_PROG_CC
+AC_PROG_CC([cc gcc])
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOL(AR, ar, :)
 AC_CHECK_PROGS(TAR, [gtar tar])
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 2464238ccd6c94a53a0a73de476e33f7cc935ab9..fb8243261cb3cc9165dbe990586d3865fad4ee61 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -208,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);
@@ -635,21 +635,76 @@ static void diffstat_consume(void *priv, char *line, unsigned long len)
                x->deleted++;
 }
 
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
 const char mime_boundary_leader[] = "------------";
 
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+       /*
+        * make sure that at least one '-' is printed if there were deletions,
+        * and likewise for '+'.
+        */
+       if (max_change < 2)
+               return it;
+       return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+                     const char *reset, const char *set)
+{
+       printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+       if (cnt <= 0)
+               return;
+       printf("%s", set);
+       while (cnt--)
+               putchar(ch);
+       printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
 {
        int i, len, add, del, total, adds = 0, dels = 0;
-       int max, max_change = 0, max_len = 0;
+       int max_change = 0, max_len = 0;
        int total_files = data->nr;
+       int width, name_width;
+       const char *reset, *set, *add_c, *del_c;
 
        if (data->nr == 0)
                return;
 
+       width = options->stat_width ? options->stat_width : 80;
+       name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+       /* Sanity: give at least 5 columns to the graph,
+        * but leave at least 10 columns for the name.
+        */
+       if (width < name_width + 15) {
+               if (name_width <= 25)
+                       width = name_width + 15;
+               else
+                       name_width = width - 15;
+       }
+
+       /* Find the longest filename and max number of changes */
+       reset = diff_get_color(options->color_diff, DIFF_RESET);
+       set = diff_get_color(options->color_diff, DIFF_PLAIN);
+       add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+       del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
+               int change = file->added + file->deleted;
+
+               len = quote_c_style(file->name, NULL, NULL, 0);
+               if (len) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(file->name, qname, NULL, 0);
+                       free(file->name);
+                       file->name = qname;
+               }
 
                len = strlen(file->name);
                if (max_len < len)
@@ -657,54 +712,53 @@ static void show_stats(struct diffstat_t* data)
 
                if (file->is_binary || file->is_unmerged)
                        continue;
-               if (max_change < file->added + file->deleted)
-                       max_change = file->added + file->deleted;
+               if (max_change < change)
+                       max_change = change;
        }
 
+       /* Compute the width of the graph part;
+        * 10 is for one blank at the beginning of the line plus
+        * " | count " between the name and the graph.
+        *
+        * From here on, name_width is the width of the name area,
+        * and width is the width of the graph area.
+        */
+       name_width = (name_width < max_len) ? name_width : max_len;
+       if (width < (name_width + 10) + max_change)
+               width = width - (name_width + 10);
+       else
+               width = max_change;
+
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->name;
                int added = data->files[i]->added;
                int deleted = data->files[i]->deleted;
-
-               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
-                       char *qname = xmalloc(len + 1);
-                       quote_c_style(name, qname, NULL, 0);
-                       free(name);
-                       data->files[i]->name = name = qname;
-               }
+               int name_len;
 
                /*
                 * "scale" the filename
                 */
-               len = strlen(name);
-               max = max_len;
-               if (max > 50)
-                       max = 50;
-               if (len > max) {
+               len = name_width;
+               name_len = strlen(name);
+               if (name_width < name_len) {
                        char *slash;
                        prefix = "...";
-                       max -= 3;
-                       name += len - max;
+                       len -= 3;
+                       name += name_len - len;
                        slash = strchr(name, '/');
                        if (slash)
                                name = slash;
                }
-               len = max;
-
-               /*
-                * scale the add/delete
-                */
-               max = max_change;
-               if (max + len > 70)
-                       max = 70 - len;
 
                if (data->files[i]->is_binary) {
-                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Bin\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
-                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Unmerged\n");
                        goto free_diffstat_file;
                }
                else if (!data->files[i]->is_renamed &&
@@ -713,27 +767,32 @@ static void show_stats(struct diffstat_t* data)
                        goto free_diffstat_file;
                }
 
+               /*
+                * scale the add/delete
+                */
                add = added;
                del = deleted;
                total = add + del;
                adds += add;
                dels += del;
 
-               if (max_change > 0) {
-                       total = (total * max + max_change / 2) / max_change;
-                       add = (add * max + max_change / 2) / max_change;
-                       del = total - add;
+               if (width <= max_change) {
+                       add = scale_linear(add, width, max_change);
+                       del = scale_linear(del, width, max_change);
+                       total = add + del;
                }
-               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
-                               len, name, added + deleted,
-                               add, pluses, del, minuses);
+               show_name(prefix, name, len, reset, set);
+               printf("%5d ", added + deleted);
+               show_graph('+', add, add_c, reset);
+               show_graph('-', del, del_c, reset);
+               putchar('\n');
        free_diffstat_file:
                free(data->files[i]->name);
                free(data->files[i]);
        }
        free(data->files);
-       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
-                       total_files, adds, dels);
+       printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+              set, total_files, adds, dels, reset);
 }
 
 struct checkdiff_t {
@@ -1769,8 +1828,33 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
-       else if (!strcmp(arg, "--stat"))
+       else if (!strncmp(arg, "--stat", 6)) {
+               char *end;
+               int width = options->stat_width;
+               int name_width = options->stat_name_width;
+               arg += 6;
+               end = (char *)arg;
+
+               switch (*arg) {
+               case '-':
+                       if (!strncmp(arg, "-width=", 7))
+                               width = strtoul(arg + 7, &end, 10);
+                       else if (!strncmp(arg, "-name-width=", 12))
+                               name_width = strtoul(arg + 12, &end, 10);
+                       break;
+               case '=':
+                       width = strtoul(arg+1, &end, 10);
+                       if (*end == ',')
+                               name_width = strtoul(end+1, &end, 10);
+               }
+
+               /* Important! This checks all the error cases! */
+               if (*end)
+                       return 0;
                options->output_format |= DIFF_FORMAT_DIFFSTAT;
+               options->stat_name_width = name_width;
+               options->stat_width = width;
+       }
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@ -2528,7 +2612,7 @@ void diff_flush(struct diff_options *options)
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(&diffstat);
+               show_stats(&diffstat, options);
                separator++;
        }
 
diff --git a/diff.h b/diff.h
index 3435fe7b3f868ff1cefd5a20a3409993ce0a413b..b48c9914e7e3802d17870bbc0fd68c454fded61c 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -69,6 +69,9 @@ struct diff_options {
        const char *stat_sep;
        long xdl_opts;
 
+       int stat_width;
+       int stat_name_width;
+
        int nr_paths;
        const char **paths;
        int *pathlens;
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"
        ;;
index 746c525079317491e35827f331922e17e5fbd089..4a20310841b69280a21ac4c79d95ce9ce3ea7df4 100755 (executable)
@@ -21,6 +21,7 @@
 use Term::ReadLine;
 use Getopt::Long;
 use Data::Dumper;
+use Git;
 
 package FakeTerm;
 sub new {
@@ -92,6 +93,7 @@ sub format_2822_time {
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
 
+my $repo = Git->repository();
 my $term = eval {
        new Term::ReadLine 'git-send-email';
 };
@@ -132,33 +134,12 @@ sub format_2822_time {
 
 # Now, let's fill any that aren't set in with defaults:
 
-sub gitvar {
-    my ($var) = @_;
-    my $fh;
-    my $pid = open($fh, '-|');
-    die "$!" unless defined $pid;
-    if (!$pid) {
-       exec('git-var', $var) or die "$!";
-    }
-    my ($val) = <$fh>;
-    close $fh or die "$!";
-    chomp($val);
-    return $val;
-}
-
-sub gitvar_ident {
-    my ($name) = @_;
-    my $val = gitvar($name);
-    my @field = split(/\s+/, $val);
-    return join(' ', @field[0...(@field-3)]);
-}
-
-my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
-my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
+my ($author) = $repo->ident_person('author');
+my ($committer) = $repo->ident_person('committer');
 
 my %aliases;
-chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
-chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my @alias_files = $repo->config('sendemail.aliasesfile');
+my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
 my %parse_alias = (
        # multiline formats can be supported in the future
        mutt => sub { my $fh = shift; while (<$fh>) {
@@ -183,7 +164,7 @@ sub gitvar_ident {
                }}}
 );
 
-if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
        foreach my $file (@alias_files) {
                open my $fh, '<', $file or die "opening $file: $!\n";
                $parse_alias{$aliasfiletype}->($fh);
@@ -425,10 +406,7 @@ sub send_message
        my $date = format_2822_time($time++);
        my $gitversion = '@@GIT_VERSION@@';
        if ($gitversion =~ m/..GIT_VERSION../) {
-           $gitversion = `git --version`;
-           chomp $gitversion;
-           # keep only what's after the last space
-           $gitversion =~ s/^.* //;
+           $gitversion = Git::version();
        }
 
        my $header = "From: $from
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 8ccd2564e728efe13966c2c1fd3e1cb93ea583a6..6d900342e35e4948888dd3a8ac2045a504435259 100644 (file)
@@ -9,7 +9,7 @@ URL:            http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk
+Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, perl-Git
 
 %description
 This is a stupid (but extremely fast) directory content manager.  It
@@ -70,6 +70,16 @@ Requires:       git-core = %{version}-%{release}, tk >= 8.4
 %description -n gitk
 Git revision tree visualiser ('gitk')
 
+%package -n perl-Git
+Summary:        Perl interface to Git
+Group:          Development/Libraries
+Requires:       git-core = %{version}-%{release}
+Requires:       perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+BuildRequires:  perl(Error)
+
+%description -n perl-Git
+Perl interface to Git
+
 %prep
 %setup -q
 
@@ -80,12 +90,18 @@ make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
 %install
 rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
-     prefix=%{_prefix} mandir=%{_mandir} \
+     prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \
      install %{!?_without_docs: install-doc}
+find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 
 (find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{perl_vendorarch} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
 %if %{!?_without_docs:1}0
 (find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+%else
+rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
 
 %clean
@@ -129,6 +145,9 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
 %{!?_without_docs: %doc Documentation/*gitk*.html }
 
+%files -n perl-Git -f perl-files
+%defattr(-,root,root)
+
 %files core -f bin-man-doc-files
 %defattr(-,root,root)
 %{_datadir}/git-core/
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/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);
diff --git a/perl/.gitignore b/perl/.gitignore
new file mode 100644 (file)
index 0000000..e990cae
--- /dev/null
@@ -0,0 +1,4 @@
+Makefile
+blib
+blibdirs
+pm_to_blib
diff --git a/perl/Git.pm b/perl/Git.pm
new file mode 100644 (file)
index 0000000..2b26b65
--- /dev/null
@@ -0,0 +1,837 @@
+=head1 NAME
+
+Git - Perl interface to the Git version control system
+
+=cut
+
+
+package Git;
+
+use strict;
+
+
+BEGIN {
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
+
+# Totally unstable API.
+$VERSION = '0.01';
+
+
+=head1 SYNOPSIS
+
+  use Git;
+
+  my $version = Git::command_oneline('version');
+
+  git_cmd_try { Git::command_noisy('update-server-info') }
+              '%s failed w/ code %d';
+
+  my $repo = Git->repository (Directory => '/srv/git/cogito.git');
+
+
+  my @revs = $repo->command('rev-list', '--since=last monday', '--all');
+
+  my ($fh, $c) = $repo->command_output_pipe('rev-list', '--since=last monday', '--all');
+  my $lastrev = <$fh>; chomp $lastrev;
+  $repo->command_close_pipe($fh, $c);
+
+  my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],
+                                        STDERR => 0 );
+
+=cut
+
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+@EXPORT = qw(git_cmd_try);
+
+# Methods which can be called as standalone functions as well:
+@EXPORT_OK = qw(command command_oneline command_noisy
+                command_output_pipe command_input_pipe command_close_pipe
+                version exec_path hash_object git_cmd_try);
+
+
+=head1 DESCRIPTION
+
+This module provides Perl scripts easy way to interface the Git version control
+system. The modules have an easy and well-tested way to call arbitrary Git
+commands; in the future, the interface will also provide specialized methods
+for doing easily operations which are not totally trivial to do over
+the generic command interface.
+
+While some commands can be executed outside of any context (e.g. 'version'
+or 'init-db'), most operations require a repository context, which in practice
+means getting an instance of the Git object using the repository() constructor.
+(In the future, we will also get a new_repository() constructor.) All commands
+called as methods of the object are then executed in the context of the
+repository.
+
+Part of the "repository state" is also information about path to the attached
+working copy (unless you work with a bare repository). You can also navigate
+inside of the working copy using the C<wc_chdir()> method. (Note that
+the repository object is self-contained and will not change working directory
+of your process.)
+
+TODO: In the future, we might also do
+
+       my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master');
+       $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/');
+       my @refs = $remoterepo->refs();
+
+Currently, the module merely wraps calls to external Git tools. In the future,
+it will provide a much faster way to interact with Git by linking directly
+to libgit. This should be completely opaque to the user, though (performance
+increate nonwithstanding).
+
+=cut
+
+
+use Carp qw(carp croak); # but croak is bad - throw instead
+use Error qw(:try);
+use Cwd qw(abs_path);
+
+}
+
+
+=head1 CONSTRUCTORS
+
+=over 4
+
+=item repository ( OPTIONS )
+
+=item repository ( DIRECTORY )
+
+=item repository ()
+
+Construct a new repository object.
+C<OPTIONS> are passed in a hash like fashion, using key and value pairs.
+Possible options are:
+
+B<Repository> - Path to the Git repository.
+
+B<WorkingCopy> - Path to the associated working copy; not strictly required
+as many commands will happily crunch on a bare repository.
+
+B<WorkingSubdir> - Subdirectory in the working copy to work inside.
+Just left undefined if you do not want to limit the scope of operations.
+
+B<Directory> - Path to the Git working directory in its usual setup.
+The C<.git> directory is searched in the directory and all the parent
+directories; if found, C<WorkingCopy> is set to the directory containing
+it and C<Repository> to the C<.git> directory itself. If no C<.git>
+directory was found, the C<Directory> is assumed to be a bare repository,
+C<Repository> is set to point at it and C<WorkingCopy> is left undefined.
+If the C<$GIT_DIR> environment variable is set, things behave as expected
+as well.
+
+You should not use both C<Directory> and either of C<Repository> and
+C<WorkingCopy> - the results of that are undefined.
+
+Alternatively, a directory path may be passed as a single scalar argument
+to the constructor; it is equivalent to setting only the C<Directory> option
+field.
+
+Calling the constructor with no options whatsoever is equivalent to
+calling it with C<< Directory => '.' >>. In general, if you are building
+a standard porcelain command, simply doing C<< Git->repository() >> should
+do the right thing and setup the object to reflect exactly where the user
+is right now.
+
+=cut
+
+sub repository {
+       my $class = shift;
+       my @args = @_;
+       my %opts = ();
+       my $self;
+
+       if (defined $args[0]) {
+               if ($#args % 2 != 1) {
+                       # Not a hash.
+                       $#args == 0 or throw Error::Simple("bad usage");
+                       %opts = ( Directory => $args[0] );
+               } else {
+                       %opts = @args;
+               }
+       }
+
+       if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) {
+               $opts{Directory} ||= '.';
+       }
+
+       if ($opts{Directory}) {
+               -d $opts{Directory} or throw Error::Simple("Directory not found: $!");
+
+               my $search = Git->repository(WorkingCopy => $opts{Directory});
+               my $dir;
+               try {
+                       $dir = $search->command_oneline(['rev-parse', '--git-dir'],
+                                                       STDERR => 0);
+               } catch Git::Error::Command with {
+                       $dir = undef;
+               };
+
+               if ($dir) {
+                       $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir;
+                       $opts{Repository} = $dir;
+
+                       # If --git-dir went ok, this shouldn't die either.
+                       my $prefix = $search->command_oneline('rev-parse', '--show-prefix');
+                       $dir = abs_path($opts{Directory}) . '/';
+                       if ($prefix) {
+                               if (substr($dir, -length($prefix)) ne $prefix) {
+                                       throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix");
+                               }
+                               substr($dir, -length($prefix)) = '';
+                       }
+                       $opts{WorkingCopy} = $dir;
+                       $opts{WorkingSubdir} = $prefix;
+
+               } else {
+                       # A bare repository? Let's see...
+                       $dir = $opts{Directory};
+
+                       unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") {
+                               # Mimick git-rev-parse --git-dir error message:
+                               throw Error::Simple('fatal: Not a git repository');
+                       }
+                       my $search = Git->repository(Repository => $dir);
+                       try {
+                               $search->command('symbolic-ref', 'HEAD');
+                       } catch Git::Error::Command with {
+                               # Mimick git-rev-parse --git-dir error message:
+                               throw Error::Simple('fatal: Not a git repository');
+                       }
+
+                       $opts{Repository} = abs_path($dir);
+               }
+
+               delete $opts{Directory};
+       }
+
+       $self = { opts => \%opts };
+       bless $self, $class;
+}
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item command ( COMMAND [, ARGUMENTS... ] )
+
+=item command ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given Git C<COMMAND> (specify it without the 'git-'
+prefix), optionally with the specified extra C<ARGUMENTS>.
+
+The second more elaborate form can be used if you want to further adjust
+the command execution. Currently, only one option is supported:
+
+B<STDERR> - How to deal with the command's error output. By default (C<undef>)
+it is delivered to the caller's C<STDERR>. A false value (0 or '') will cause
+it to be thrown away. If you want to process it, you can get it in a filehandle
+you specify, but you must be extremely careful; if the error output is not
+very short and you want to read it in the same process as where you called
+C<command()>, you are set up for a nice deadlock!
+
+The method can be called without any instance or on a specified Git repository
+(in that case the command will be run in the repository context).
+
+In scalar context, it returns all the command output in a single string
+(verbatim).
+
+In array context, it returns an array containing lines printed to the
+command's stdout (without trailing newlines).
+
+In both cases, the command's stdin and stderr are the same as the caller's.
+
+=cut
+
+sub command {
+       my ($fh, $ctx) = command_output_pipe(@_);
+
+       if (not defined wantarray) {
+               # Nothing to pepper the possible exception with.
+               _cmd_close($fh, $ctx);
+
+       } elsif (not wantarray) {
+               local $/;
+               my $text = <$fh>;
+               try {
+                       _cmd_close($fh, $ctx);
+               } catch Git::Error::Command with {
+                       # Pepper with the output:
+                       my $E = shift;
+                       $E->{'-outputref'} = \$text;
+                       throw $E;
+               };
+               return $text;
+
+       } else {
+               my @lines = <$fh>;
+               chomp @lines;
+               try {
+                       _cmd_close($fh, $ctx);
+               } catch Git::Error::Command with {
+                       my $E = shift;
+                       $E->{'-outputref'} = \@lines;
+                       throw $E;
+               };
+               return @lines;
+       }
+}
+
+
+=item command_oneline ( COMMAND [, ARGUMENTS... ] )
+
+=item command_oneline ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but always return a scalar string containing the first line
+of the command's standard output.
+
+=cut
+
+sub command_oneline {
+       my ($fh, $ctx) = command_output_pipe(@_);
+
+       my $line = <$fh>;
+       defined $line and chomp $line;
+       try {
+               _cmd_close($fh, $ctx);
+       } catch Git::Error::Command with {
+               # Pepper with the output:
+               my $E = shift;
+               $E->{'-outputref'} = \$line;
+               throw $E;
+       };
+       return $line;
+}
+
+
+=item command_output_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_output_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but return a pipe filehandle from which the command output can be
+read.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_output_pipe {
+       _command_common_pipe('-|', @_);
+}
+
+
+=item command_input_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_input_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command_output_pipe()
+does but return an input pipe filehandle instead; the command output
+is not captured.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_input_pipe {
+       _command_common_pipe('|-', @_);
+}
+
+
+=item command_close_pipe ( PIPE [, CTX ] )
+
+Close the C<PIPE> as returned from C<command_*_pipe()>, checking
+whether the command finished successfuly. The optional C<CTX> argument
+is required if you want to see the command name in the error message,
+and it is the second value returned by C<command_*_pipe()> when
+called in array context. The call idiom is:
+
+       my ($fh, $ctx) = $r->command_output_pipe('status');
+       while (<$fh>) { ... }
+       $r->command_close_pipe($fh, $ctx);
+
+Note that you should not rely on whatever actually is in C<CTX>;
+currently it is simply the command name but in future the context might
+have more complicated structure.
+
+=cut
+
+sub command_close_pipe {
+       my ($self, $fh, $ctx) = _maybe_self(@_);
+       $ctx ||= '<unknown>';
+       _cmd_close($fh, $ctx);
+}
+
+
+=item command_noisy ( COMMAND [, ARGUMENTS... ] )
+
+Execute the given C<COMMAND> in the same way as command() does but do not
+capture the command output - the standard output is not redirected and goes
+to the standard output of the caller application.
+
+While the method is called command_noisy(), you might want to as well use
+it for the most silent Git commands which you know will never pollute your
+stdout but you want to avoid the overhead of the pipe setup when calling them.
+
+The function returns only after the command has finished running.
+
+=cut
+
+sub command_noisy {
+       my ($self, $cmd, @args) = _maybe_self(@_);
+       _check_valid_cmd($cmd);
+
+       my $pid = fork;
+       if (not defined $pid) {
+               throw Error::Simple("fork failed: $!");
+       } elsif ($pid == 0) {
+               _cmd_exec($self, $cmd, @args);
+       }
+       if (waitpid($pid, 0) > 0 and $?>>8 != 0) {
+               throw Git::Error::Command(join(' ', $cmd, @args), $? >> 8);
+       }
+}
+
+
+=item version ()
+
+Return the Git version in use.
+
+=cut
+
+sub version {
+       my $verstr = command_oneline('--version');
+       $verstr =~ s/^git version //;
+       $verstr;
+}
+
+
+=item exec_path ()
+
+Return path to the Git sub-command executables (the same as
+C<git --exec-path>). Useful mostly only internally.
+
+=cut
+
+sub exec_path { command_oneline('--exec-path') }
+
+
+=item repo_path ()
+
+Return path to the git repository. Must be called on a repository instance.
+
+=cut
+
+sub repo_path { $_[0]->{opts}->{Repository} }
+
+
+=item wc_path ()
+
+Return path to the working copy. Must be called on a repository instance.
+
+=cut
+
+sub wc_path { $_[0]->{opts}->{WorkingCopy} }
+
+
+=item wc_subdir ()
+
+Return path to the subdirectory inside of a working copy. Must be called
+on a repository instance.
+
+=cut
+
+sub wc_subdir { $_[0]->{opts}->{WorkingSubdir} ||= '' }
+
+
+=item wc_chdir ( SUBDIR )
+
+Change the working copy subdirectory to work within. The C<SUBDIR> is
+relative to the working copy root directory (not the current subdirectory).
+Must be called on a repository instance attached to a working copy
+and the directory must exist.
+
+=cut
+
+sub wc_chdir {
+       my ($self, $subdir) = @_;
+       $self->wc_path()
+               or throw Error::Simple("bare repository");
+
+       -d $self->wc_path().'/'.$subdir
+               or throw Error::Simple("subdir not found: $!");
+       # Of course we will not "hold" the subdirectory so anyone
+       # can delete it now and we will never know. But at least we tried.
+
+       $self->{opts}->{WorkingSubdir} = $subdir;
+}
+
+
+=item config ( VARIABLE )
+
+Retrieve the configuration C<VARIABLE> in the same manner as C<repo-config>
+does. In scalar context requires the variable to be set only one time
+(exception is thrown otherwise), in array context returns allows the
+variable to be set multiple times and returns all the values.
+
+Must be called on a repository instance.
+
+This currently wraps command('repo-config') so it is not so fast.
+
+=cut
+
+sub config {
+       my ($self, $var) = @_;
+       $self->repo_path()
+               or throw Error::Simple("not a repository");
+
+       try {
+               if (wantarray) {
+                       return $self->command('repo-config', '--get-all', $var);
+               } else {
+                       return $self->command_oneline('repo-config', '--get', $var);
+               }
+       } catch Git::Error::Command with {
+               my $E = shift;
+               if ($E->value() == 1) {
+                       # Key not found.
+                       return undef;
+               } else {
+                       throw $E;
+               }
+       };
+}
+
+
+=item ident ( TYPE | IDENTSTR )
+
+=item ident_person ( TYPE | IDENTSTR | IDENTARRAY )
+
+This suite of functions retrieves and parses ident information, as stored
+in the commit and tag objects or produced by C<var GIT_type_IDENT> (thus
+C<TYPE> can be either I<author> or I<committer>; case is insignificant).
+
+The C<ident> method retrieves the ident information from C<git-var>
+and either returns it as a scalar string or as an array with the fields parsed.
+Alternatively, it can take a prepared ident string (e.g. from the commit
+object) and just parse it.
+
+C<ident_person> returns the person part of the ident - name and email;
+it can take the same arguments as C<ident> or the array returned by C<ident>.
+
+The synopsis is like:
+
+       my ($name, $email, $time_tz) = ident('author');
+       "$name <$email>" eq ident_person('author');
+       "$name <$email>" eq ident_person($name);
+       $time_tz =~ /^\d+ [+-]\d{4}$/;
+
+Both methods must be called on a repository instance.
+
+=cut
+
+sub ident {
+       my ($self, $type) = @_;
+       my $identstr;
+       if (lc $type eq lc 'committer' or lc $type eq lc 'author') {
+               $identstr = $self->command_oneline('var', 'GIT_'.uc($type).'_IDENT');
+       } else {
+               $identstr = $type;
+       }
+       if (wantarray) {
+               return $identstr =~ /^(.*) <(.*)> (\d+ [+-]\d{4})$/;
+       } else {
+               return $identstr;
+       }
+}
+
+sub ident_person {
+       my ($self, @ident) = @_;
+       $#ident == 0 and @ident = $self->ident($ident[0]);
+       return "$ident[0] <$ident[1]>";
+}
+
+
+=item hash_object ( TYPE, FILENAME )
+
+Compute the SHA1 object id of the given C<FILENAME> (or data waiting in
+C<FILEHANDLE>) considering it is of the C<TYPE> object type (C<blob>,
+C<commit>, C<tree>).
+
+The method can be called without any instance or on a specified Git repository,
+it makes zero difference.
+
+The function returns the SHA1 hash.
+
+=cut
+
+# TODO: Support for passing FILEHANDLE instead of FILENAME
+sub hash_object {
+       my ($self, $type, $file) = _maybe_self(@_);
+       command_oneline('hash-object', '-t', $type, $file);
+}
+
+
+
+=back
+
+=head1 ERROR HANDLING
+
+All functions are supposed to throw Perl exceptions in case of errors.
+See the L<Error> module on how to catch those. Most exceptions are mere
+L<Error::Simple> instances.
+
+However, the C<command()>, C<command_oneline()> and C<command_noisy()>
+functions suite can throw C<Git::Error::Command> exceptions as well: those are
+thrown when the external command returns an error code and contain the error
+code as well as access to the captured command's output. The exception class
+provides the usual C<stringify> and C<value> (command's exit code) methods and
+in addition also a C<cmd_output> method that returns either an array or a
+string with the captured command output (depending on the original function
+call context; C<command_noisy()> returns C<undef>) and $<cmdline> which
+returns the command and its arguments (but without proper quoting).
+
+Note that the C<command_*_pipe()> functions cannot throw this exception since
+it has no idea whether the command failed or not. You will only find out
+at the time you C<close> the pipe; if you want to have that automated,
+use C<command_close_pipe()>, which can throw the exception.
+
+=cut
+
+{
+       package Git::Error::Command;
+
+       @Git::Error::Command::ISA = qw(Error);
+
+       sub new {
+               my $self = shift;
+               my $cmdline = '' . shift;
+               my $value = 0 + shift;
+               my $outputref = shift;
+               my(@args) = ();
+
+               local $Error::Depth = $Error::Depth + 1;
+
+               push(@args, '-cmdline', $cmdline);
+               push(@args, '-value', $value);
+               push(@args, '-outputref', $outputref);
+
+               $self->SUPER::new(-text => 'command returned error', @args);
+       }
+
+       sub stringify {
+               my $self = shift;
+               my $text = $self->SUPER::stringify;
+               $self->cmdline() . ': ' . $text . ': ' . $self->value() . "\n";
+       }
+
+       sub cmdline {
+               my $self = shift;
+               $self->{'-cmdline'};
+       }
+
+       sub cmd_output {
+               my $self = shift;
+               my $ref = $self->{'-outputref'};
+               defined $ref or undef;
+               if (ref $ref eq 'ARRAY') {
+                       return @$ref;
+               } else { # SCALAR
+                       return $$ref;
+               }
+       }
+}
+
+=over 4
+
+=item git_cmd_try { CODE } ERRMSG
+
+This magical statement will automatically catch any C<Git::Error::Command>
+exceptions thrown by C<CODE> and make your program die with C<ERRMSG>
+on its lips; the message will have %s substituted for the command line
+and %d for the exit status. This statement is useful mostly for producing
+more user-friendly error messages.
+
+In case of no exception caught the statement returns C<CODE>'s return value.
+
+Note that this is the only auto-exported function.
+
+=cut
+
+sub git_cmd_try(&$) {
+       my ($code, $errmsg) = @_;
+       my @result;
+       my $err;
+       my $array = wantarray;
+       try {
+               if ($array) {
+                       @result = &$code;
+               } else {
+                       $result[0] = &$code;
+               }
+       } catch Git::Error::Command with {
+               my $E = shift;
+               $err = $errmsg;
+               $err =~ s/\%s/$E->cmdline()/ge;
+               $err =~ s/\%d/$E->value()/ge;
+               # We can't croak here since Error.pm would mangle
+               # that to Error::Simple.
+       };
+       $err and croak $err;
+       return $array ? @result : $result[0];
+}
+
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright 2006 by Petr Baudis E<lt>pasky@suse.czE<gt>.
+
+This module is free software; it may be used, copied, modified
+and distributed under the terms of the GNU General Public Licence,
+either version 2, or (at your option) any later version.
+
+=cut
+
+
+# Take raw method argument list and return ($obj, @args) in case
+# the method was called upon an instance and (undef, @args) if
+# it was called directly.
+sub _maybe_self {
+       # This breaks inheritance. Oh well.
+       ref $_[0] eq 'Git' ? @_ : (undef, @_);
+}
+
+# Check if the command id is something reasonable.
+sub _check_valid_cmd {
+       my ($cmd) = @_;
+       $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd");
+}
+
+# Common backend for the pipe creators.
+sub _command_common_pipe {
+       my $direction = shift;
+       my ($self, @p) = _maybe_self(@_);
+       my (%opts, $cmd, @args);
+       if (ref $p[0]) {
+               ($cmd, @args) = @{shift @p};
+               %opts = ref $p[0] ? %{$p[0]} : @p;
+       } else {
+               ($cmd, @args) = @p;
+       }
+       _check_valid_cmd($cmd);
+
+       my $fh;
+       if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
+               # ActiveState Perl
+               #defined $opts{STDERR} and
+               #       warn 'ignoring STDERR option - running w/ ActiveState';
+               $direction eq '-|' or
+                       die 'input pipe for ActiveState not implemented';
+               tie ($fh, 'Git::activestate_pipe', $cmd, @args);
+
+       } else {
+               my $pid = open($fh, $direction);
+               if (not defined $pid) {
+                       throw Error::Simple("open failed: $!");
+               } elsif ($pid == 0) {
+                       if (defined $opts{STDERR}) {
+                               close STDERR;
+                       }
+                       if ($opts{STDERR}) {
+                               open (STDERR, '>&', $opts{STDERR})
+                                       or die "dup failed: $!";
+                       }
+                       _cmd_exec($self, $cmd, @args);
+               }
+       }
+       return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh;
+}
+
+# When already in the subprocess, set up the appropriate state
+# for the given repository and execute the git command.
+sub _cmd_exec {
+       my ($self, @args) = @_;
+       if ($self) {
+               $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
+               $self->wc_path() and chdir($self->wc_path());
+               $self->wc_subdir() and chdir($self->wc_subdir());
+       }
+       _execv_git_cmd(@args);
+       die "exec failed: $!";
+}
+
+# Execute the given Git command ($_[0]) with arguments ($_[1..])
+# by searching for it at proper places.
+sub _execv_git_cmd { exec('git', @_); }
+
+# Close pipe to a subprocess.
+sub _cmd_close {
+       my ($fh, $ctx) = @_;
+       if (not close $fh) {
+               if ($!) {
+                       # It's just close, no point in fatalities
+                       carp "error closing pipe: $!";
+               } elsif ($? >> 8) {
+                       # The caller should pepper this.
+                       throw Git::Error::Command($ctx, $? >> 8);
+               }
+               # else we might e.g. closed a live stream; the command
+               # dying of SIGPIPE would drive us here.
+       }
+}
+
+
+sub DESTROY { }
+
+
+# Pipe implementation for ActiveState Perl.
+
+package Git::activestate_pipe;
+use strict;
+
+sub TIEHANDLE {
+       my ($class, @params) = @_;
+       # FIXME: This is probably horrible idea and the thing will explode
+       # at the moment you give it arguments that require some quoting,
+       # but I have no ActiveState clue... --pasky
+       my $cmdline = join " ", @params;
+       my @data = qx{$cmdline};
+       bless { i => 0, data => \@data }, $class;
+}
+
+sub READLINE {
+       my $self = shift;
+       if ($self->{i} >= scalar @{$self->{data}}) {
+               return undef;
+       }
+       return $self->{'data'}->[ $self->{i}++ ];
+}
+
+sub CLOSE {
+       my $self = shift;
+       delete $self->{data};
+       delete $self->{i};
+}
+
+sub EOF {
+       my $self = shift;
+       return ($self->{i} >= scalar @{$self->{data}});
+}
+
+
+1; # Famous last words
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
new file mode 100644 (file)
index 0000000..de73235
--- /dev/null
@@ -0,0 +1,28 @@
+use ExtUtils::MakeMaker;
+
+sub MY::postamble {
+       return <<'MAKE_FRAG';
+instlibdir:
+       @echo '$(INSTALLSITELIB)'
+
+MAKE_FRAG
+}
+
+my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+
+# We come with our own bundled Error.pm. It's not in the set of default
+# Perl modules so install it if it's not available on the system yet.
+eval { require Error };
+if ($@) {
+       $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
+}
+
+my %extra;
+$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR};
+
+WriteMakefile(
+       NAME            => 'Git',
+       VERSION_FROM    => 'Git.pm',
+       PM              => \%pm,
+       %extra
+);
diff --git a/perl/private-Error.pm b/perl/private-Error.pm
new file mode 100644 (file)
index 0000000..8fff866
--- /dev/null
@@ -0,0 +1,827 @@
+# Error.pm
+#
+# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
+# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
+#
+# but modified ***significantly***
+
+package Error;
+
+use strict;
+use vars qw($VERSION);
+use 5.004;
+
+$VERSION = "0.15009";
+
+use overload (
+       '""'       =>   'stringify',
+       '0+'       =>   'value',
+       'bool'     =>   sub { return 1; },
+       'fallback' =>   1
+);
+
+$Error::Depth = 0;     # Depth to pass to caller()
+$Error::Debug = 0;     # Generate verbose stack traces
+@Error::STACK = ();    # Clause stack for try
+$Error::THROWN = undef;        # last error thrown, a workaround until die $ref works
+
+my $LAST;              # Last error created
+my %ERROR;             # Last error associated with package
+
+sub throw_Error_Simple
+{
+    my $args = shift;
+    return Error::Simple->new($args->{'text'});
+}
+
+$Error::ObjectifyCallback = \&throw_Error_Simple;
+
+
+# Exported subs are defined in Error::subs
+
+sub import {
+    shift;
+    local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
+    Error::subs->import(@_);
+}
+
+# I really want to use last for the name of this method, but it is a keyword
+# which prevent the syntax  last Error
+
+sub prior {
+    shift; # ignore
+
+    return $LAST unless @_;
+
+    my $pkg = shift;
+    return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
+       unless ref($pkg);
+
+    my $obj = $pkg;
+    my $err = undef;
+    if($obj->isa('HASH')) {
+       $err = $obj->{'__Error__'}
+           if exists $obj->{'__Error__'};
+    }
+    elsif($obj->isa('GLOB')) {
+       $err = ${*$obj}{'__Error__'}
+           if exists ${*$obj}{'__Error__'};
+    }
+
+    $err;
+}
+
+sub flush {
+    shift; #ignore
+
+    unless (@_) {
+       $LAST = undef;
+       return;
+    }
+
+    my $pkg = shift;
+    return unless ref($pkg);
+
+    undef $ERROR{$pkg} if defined $ERROR{$pkg};
+}
+
+# Return as much information as possible about where the error
+# happened. The -stacktrace element only exists if $Error::DEBUG
+# was set when the error was created
+
+sub stacktrace {
+    my $self = shift;
+
+    return $self->{'-stacktrace'}
+       if exists $self->{'-stacktrace'};
+
+    my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
+
+    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+       unless($text =~ /\n$/s);
+
+    $text;
+}
+
+# Allow error propagation, ie
+#
+# $ber->encode(...) or
+#    return Error->prior($ber)->associate($ldap);
+
+sub associate {
+    my $err = shift;
+    my $obj = shift;
+
+    return unless ref($obj);
+
+    if($obj->isa('HASH')) {
+       $obj->{'__Error__'} = $err;
+    }
+    elsif($obj->isa('GLOB')) {
+       ${*$obj}{'__Error__'} = $err;
+    }
+    $obj = ref($obj);
+    $ERROR{ ref($obj) } = $err;
+
+    return;
+}
+
+sub new {
+    my $self = shift;
+    my($pkg,$file,$line) = caller($Error::Depth);
+
+    my $err = bless {
+       '-package' => $pkg,
+       '-file'    => $file,
+       '-line'    => $line,
+       @_
+    }, $self;
+
+    $err->associate($err->{'-object'})
+       if(exists $err->{'-object'});
+
+    # To always create a stacktrace would be very inefficient, so
+    # we only do it if $Error::Debug is set
+
+    if($Error::Debug) {
+       require Carp;
+       local $Carp::CarpLevel = $Error::Depth;
+       my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
+       my $trace = Carp::longmess($text);
+       # Remove try calls from the trace
+       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+       $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+       $err->{'-stacktrace'} = $trace
+    }
+
+    $@ = $LAST = $ERROR{$pkg} = $err;
+}
+
+# Throw an error. this contains some very gory code.
+
+sub throw {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    # if we are not rethrow-ing then create the object to throw
+    $self = $self->new(@_) unless ref($self);
+
+    die $Error::THROWN = $self;
+}
+
+# syntactic sugar for
+#
+#    die with Error( ... );
+
+sub with {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    $self->new(@_);
+}
+
+# syntactic sugar for
+#
+#    record Error( ... ) and return;
+
+sub record {
+    my $self = shift;
+    local $Error::Depth = $Error::Depth + 1;
+
+    $self->new(@_);
+}
+
+# catch clause for
+#
+# try { ... } catch CLASS with { ... }
+
+sub catch {
+    my $pkg = shift;
+    my $code = shift;
+    my $clauses = shift || {};
+    my $catch = $clauses->{'catch'} ||= [];
+
+    unshift @$catch,  $pkg, $code;
+
+    $clauses;
+}
+
+# Object query methods
+
+sub object {
+    my $self = shift;
+    exists $self->{'-object'} ? $self->{'-object'} : undef;
+}
+
+sub file {
+    my $self = shift;
+    exists $self->{'-file'} ? $self->{'-file'} : undef;
+}
+
+sub line {
+    my $self = shift;
+    exists $self->{'-line'} ? $self->{'-line'} : undef;
+}
+
+sub text {
+    my $self = shift;
+    exists $self->{'-text'} ? $self->{'-text'} : undef;
+}
+
+# overload methods
+
+sub stringify {
+    my $self = shift;
+    defined $self->{'-text'} ? $self->{'-text'} : "Died";
+}
+
+sub value {
+    my $self = shift;
+    exists $self->{'-value'} ? $self->{'-value'} : undef;
+}
+
+package Error::Simple;
+
+@Error::Simple::ISA = qw(Error);
+
+sub new {
+    my $self  = shift;
+    my $text  = "" . shift;
+    my $value = shift;
+    my(@args) = ();
+
+    local $Error::Depth = $Error::Depth + 1;
+
+    @args = ( -file => $1, -line => $2)
+       if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
+    push(@args, '-value', 0 + $value)
+       if defined($value);
+
+    $self->SUPER::new(-text => $text, @args);
+}
+
+sub stringify {
+    my $self = shift;
+    my $text = $self->SUPER::stringify;
+    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+       unless($text =~ /\n$/s);
+    $text;
+}
+
+##########################################################################
+##########################################################################
+
+# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
+# Peter Seibel <peter@weblogic.com>
+
+package Error::subs;
+
+use Exporter ();
+use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
+
+@EXPORT_OK   = qw(try with finally except otherwise);
+%EXPORT_TAGS = (try => \@EXPORT_OK);
+
+@ISA = qw(Exporter);
+
+
+sub blessed {
+       my $item = shift;
+       local $@; # don't kill an outer $@
+       ref $item and eval { $item->can('can') };
+}
+
+
+sub run_clauses ($$$\@) {
+    my($clauses,$err,$wantarray,$result) = @_;
+    my $code = undef;
+
+    $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
+
+    CATCH: {
+
+       # catch
+       my $catch;
+       if(defined($catch = $clauses->{'catch'})) {
+           my $i = 0;
+
+           CATCHLOOP:
+           for( ; $i < @$catch ; $i += 2) {
+               my $pkg = $catch->[$i];
+               unless(defined $pkg) {
+                   #except
+                   splice(@$catch,$i,2,$catch->[$i+1]->());
+                   $i -= 2;
+                   next CATCHLOOP;
+               }
+               elsif(blessed($err) && $err->isa($pkg)) {
+                   $code = $catch->[$i+1];
+                   while(1) {
+                       my $more = 0;
+                       local($Error::THROWN);
+                       my $ok = eval {
+                           if($wantarray) {
+                               @{$result} = $code->($err,\$more);
+                           }
+                           elsif(defined($wantarray)) {
+                               @{$result} = ();
+                               $result->[0] = $code->($err,\$more);
+                           }
+                           else {
+                               $code->($err,\$more);
+                           }
+                           1;
+                       };
+                       if( $ok ) {
+                           next CATCHLOOP if $more;
+                           undef $err;
+                       }
+                       else {
+                           $err = defined($Error::THROWN)
+                                   ? $Error::THROWN : $@;
+                $err = $Error::ObjectifyCallback->({'text' =>$err})
+                    unless ref($err);
+                       }
+                       last CATCH;
+                   };
+               }
+           }
+       }
+
+       # otherwise
+       my $owise;
+       if(defined($owise = $clauses->{'otherwise'})) {
+           my $code = $clauses->{'otherwise'};
+           my $more = 0;
+           my $ok = eval {
+               if($wantarray) {
+                   @{$result} = $code->($err,\$more);
+               }
+               elsif(defined($wantarray)) {
+                   @{$result} = ();
+                   $result->[0] = $code->($err,\$more);
+               }
+               else {
+                   $code->($err,\$more);
+               }
+               1;
+           };
+           if( $ok ) {
+               undef $err;
+           }
+           else {
+               $err = defined($Error::THROWN)
+                       ? $Error::THROWN : $@;
+
+        $err = $Error::ObjectifyCallback->({'text' =>$err})
+            unless ref($err);
+           }
+       }
+    }
+    $err;
+}
+
+sub try (&;$) {
+    my $try = shift;
+    my $clauses = @_ ? shift : {};
+    my $ok = 0;
+    my $err = undef;
+    my @result = ();
+
+    unshift @Error::STACK, $clauses;
+
+    my $wantarray = wantarray();
+
+    do {
+       local $Error::THROWN = undef;
+    local $@ = undef;
+
+       $ok = eval {
+           if($wantarray) {
+               @result = $try->();
+           }
+           elsif(defined $wantarray) {
+               $result[0] = $try->();
+           }
+           else {
+               $try->();
+           }
+           1;
+       };
+
+       $err = defined($Error::THROWN) ? $Error::THROWN : $@
+           unless $ok;
+    };
+
+    shift @Error::STACK;
+
+    $err = run_clauses($clauses,$err,wantarray,@result)
+       unless($ok);
+
+    $clauses->{'finally'}->()
+       if(defined($clauses->{'finally'}));
+
+    if (defined($err))
+    {
+        if (blessed($err) && $err->can('throw'))
+        {
+            throw $err;
+        }
+        else
+        {
+            die $err;
+        }
+    }
+
+    wantarray ? @result : $result[0];
+}
+
+# Each clause adds a sub to the list of clauses. The finally clause is
+# always the last, and the otherwise clause is always added just before
+# the finally clause.
+#
+# All clauses, except the finally clause, add a sub which takes one argument
+# this argument will be the error being thrown. The sub will return a code ref
+# if that clause can handle that error, otherwise undef is returned.
+#
+# The otherwise clause adds a sub which unconditionally returns the users
+# code reference, this is why it is forced to be last.
+#
+# The catch clause is defined in Error.pm, as the syntax causes it to
+# be called as a method
+
+sub with (&;$) {
+    @_
+}
+
+sub finally (&) {
+    my $code = shift;
+    my $clauses = { 'finally' => $code };
+    $clauses;
+}
+
+# The except clause is a block which returns a hashref or a list of
+# key-value pairs, where the keys are the classes and the values are subs.
+
+sub except (&;$) {
+    my $code = shift;
+    my $clauses = shift || {};
+    my $catch = $clauses->{'catch'} ||= [];
+
+    my $sub = sub {
+       my $ref;
+       my(@array) = $code->($_[0]);
+       if(@array == 1 && ref($array[0])) {
+           $ref = $array[0];
+           $ref = [ %$ref ]
+               if(UNIVERSAL::isa($ref,'HASH'));
+       }
+       else {
+           $ref = \@array;
+       }
+       @$ref
+    };
+
+    unshift @{$catch}, undef, $sub;
+
+    $clauses;
+}
+
+sub otherwise (&;$) {
+    my $code = shift;
+    my $clauses = shift || {};
+
+    if(exists $clauses->{'otherwise'}) {
+       require Carp;
+       Carp::croak("Multiple otherwise clauses");
+    }
+
+    $clauses->{'otherwise'} = $code;
+
+    $clauses;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Error - Error/exception handling in an OO-ish way
+
+=head1 SYNOPSIS
+
+    use Error qw(:try);
+
+    throw Error::Simple( "A simple error");
+
+    sub xyz {
+        ...
+       record Error::Simple("A simple error")
+           and return;
+    }
+
+    unlink($file) or throw Error::Simple("$file: $!",$!);
+
+    try {
+       do_some_stuff();
+       die "error!" if $condition;
+       throw Error::Simple -text => "Oops!" if $other_condition;
+    }
+    catch Error::IO with {
+       my $E = shift;
+       print STDERR "File ", $E->{'-file'}, " had a problem\n";
+    }
+    except {
+       my $E = shift;
+       my $general_handler=sub {send_message $E->{-description}};
+       return {
+           UserException1 => $general_handler,
+           UserException2 => $general_handler
+       };
+    }
+    otherwise {
+       print STDERR "Well I don't know what to say\n";
+    }
+    finally {
+       close_the_garage_door_already(); # Should be reliable
+    }; # Don't forget the trailing ; or you might be surprised
+
+=head1 DESCRIPTION
+
+The C<Error> package provides two interfaces. Firstly C<Error> provides
+a procedural interface to exception handling. Secondly C<Error> is a
+base class for errors/exceptions that can either be thrown, for
+subsequent catch, or can simply be recorded.
+
+Errors in the class C<Error> should not be thrown directly, but the
+user should throw errors from a sub-class of C<Error>.
+
+=head1 PROCEDURAL INTERFACE
+
+C<Error> exports subroutines to perform exception handling. These will
+be exported if the C<:try> tag is used in the C<use> line.
+
+=over 4
+
+=item try BLOCK CLAUSES
+
+C<try> is the main subroutine called by the user. All other subroutines
+exported are clauses to the try subroutine.
+
+The BLOCK will be evaluated and, if no error is throw, try will return
+the result of the block.
+
+C<CLAUSES> are the subroutines below, which describe what to do in the
+event of an error being thrown within BLOCK.
+
+=item catch CLASS with BLOCK
+
+This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
+to be caught and handled by evaluating C<BLOCK>.
+
+C<BLOCK> will be passed two arguments. The first will be the error
+being thrown. The second is a reference to a scalar variable. If this
+variable is set by the catch block then, on return from the catch
+block, try will continue processing as if the catch block was never
+found.
+
+To propagate the error the catch block may call C<$err-E<gt>throw>
+
+If the scalar reference by the second argument is not set, and the
+error is not thrown. Then the current try block will return with the
+result from the catch block.
+
+=item except BLOCK
+
+When C<try> is looking for a handler, if an except clause is found
+C<BLOCK> is evaluated. The return value from this block should be a
+HASHREF or a list of key-value pairs, where the keys are class names
+and the values are CODE references for the handler of errors of that
+type.
+
+=item otherwise BLOCK
+
+Catch any error by executing the code in C<BLOCK>
+
+When evaluated C<BLOCK> will be passed one argument, which will be the
+error being processed.
+
+Only one otherwise block may be specified per try block
+
+=item finally BLOCK
+
+Execute the code in C<BLOCK> either after the code in the try block has
+successfully completed, or if the try block throws an error then
+C<BLOCK> will be executed after the handler has completed.
+
+If the handler throws an error then the error will be caught, the
+finally block will be executed and the error will be re-thrown.
+
+Only one finally block may be specified per try block
+
+=back
+
+=head1 CLASS INTERFACE
+
+=head2 CONSTRUCTORS
+
+The C<Error> object is implemented as a HASH. This HASH is initialized
+with the arguments that are passed to it's constructor. The elements
+that are used by, or are retrievable by the C<Error> class are listed
+below, other classes may add to these.
+
+       -file
+       -line
+       -text
+       -value
+       -object
+
+If C<-file> or C<-line> are not specified in the constructor arguments
+then these will be initialized with the file name and line number where
+the constructor was called from.
+
+If the error is associated with an object then the object should be
+passed as the C<-object> argument. This will allow the C<Error> package
+to associate the error with the object.
+
+The C<Error> package remembers the last error created, and also the
+last error associated with a package. This could either be the last
+error created by a sub in that package, or the last error which passed
+an object blessed into that package as the C<-object> argument.
+
+=over 4
+
+=item throw ( [ ARGS ] )
+
+Create a new C<Error> object and throw an error, which will be caught
+by a surrounding C<try> block, if there is one. Otherwise it will cause
+the program to exit.
+
+C<throw> may also be called on an existing error to re-throw it.
+
+=item with ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+    die with Some::Error ( ... );
+
+=item record ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+    record Some::Error ( ... )
+       and return;
+
+=back
+
+=head2 STATIC METHODS
+
+=over 4
+
+=item prior ( [ PACKAGE ] )
+
+Return the last error created, or the last error associated with
+C<PACKAGE>
+
+=item flush ( [ PACKAGE ] )
+
+Flush the last error created, or the last error associated with
+C<PACKAGE>.It is necessary to clear the error stack before exiting the
+package or uncaught errors generated using C<record> will be reported.
+
+     $Error->flush;
+
+=cut
+
+=back
+
+=head2 OBJECT METHODS
+
+=over 4
+
+=item stacktrace
+
+If the variable C<$Error::Debug> was non-zero when the error was
+created, then C<stacktrace> returns a string created by calling
+C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
+the text of the error appended with the filename and line number of
+where the error was created, providing the text does not end with a
+newline.
+
+=item object
+
+The object this error was associated with
+
+=item file
+
+The file where the constructor of this error was called from
+
+=item line
+
+The line where the constructor of this error was called from
+
+=item text
+
+The text of the error
+
+=back
+
+=head2 OVERLOAD METHODS
+
+=over 4
+
+=item stringify
+
+A method that converts the object into a string. This method may simply
+return the same as the C<text> method, or it may append more
+information. For example the file name and line number.
+
+By default this method returns the C<-text> argument that was passed to
+the constructor, or the string C<"Died"> if none was given.
+
+=item value
+
+A method that will return a value that can be associated with the
+error. For example if an error was created due to the failure of a
+system call, then this may return the numeric value of C<$!> at the
+time.
+
+By default this method returns the C<-value> argument that was passed
+to the constructor.
+
+=back
+
+=head1 PRE-DEFINED ERROR CLASSES
+
+=over 4
+
+=item Error::Simple
+
+This class can be used to hold simple error strings and values. It's
+constructor takes two arguments. The first is a text value, the second
+is a numeric value. These values are what will be returned by the
+overload methods.
+
+If the text value ends with C<at file line 1> as $@ strings do, then
+this infomation will be used to set the C<-file> and C<-line> arguments
+of the error object.
+
+This class is used internally if an eval'd block die's with an error
+that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
+
+=back
+
+=head1 $Error::ObjectifyCallback
+
+This variable holds a reference to a subroutine that converts errors that
+are plain strings to objects. It is used by Error.pm to convert textual
+errors to objects, and can be overrided by the user.
+
+It accepts a single argument which is a hash reference to named parameters.
+Currently the only named parameter passed is C<'text'> which is the text
+of the error, but others may be available in the future.
+
+For example the following code will cause Error.pm to throw objects of the
+class MyError::Bar by default:
+
+    sub throw_MyError_Bar
+    {
+        my $args = shift;
+        my $err = MyError::Bar->new();
+        $err->{'MyBarText'} = $args->{'text'};
+        return $err;
+    }
+
+    {
+        local $Error::ObjectifyCallback = \&throw_MyError_Bar;
+
+        # Error handling here.
+    }
+
+=head1 KNOWN BUGS
+
+None, but that does not mean there are not any.
+
+=head1 AUTHORS
+
+Graham Barr <gbarr@pobox.com>
+
+The code that inspired me to write this was originally written by
+Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
+<jglick@sig.bsh.com>.
+
+=head1 MAINTAINER
+
+Shlomi Fish <shlomif@iglu.org.il>
+
+=head1 PAST MAINTAINERS
+
+Arun Kumar U <u_arunkumar@yahoo.com>
+
+=cut
diff --git a/refs.c b/refs.c
index aa4c4e0b94585835298ad1785eee77462760e2d6..305c1a92cca8e546a91b578df227212acc96279c 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -234,6 +234,12 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        }
                }
 
+               /* Is it a directory? */
+               if (S_ISDIR(st.st_mode)) {
+                       errno = EISDIR;
+                       return NULL;
+               }
+
                /*
                 * Anything else, just open it and try to use it as
                 * a ref
index b7fcdb390c588545f2419ab3c825b924fbf96dd2..23a1eff3bba4505d449bd5fdffb3daf7f9b23ddf 100755 (executable)
@@ -86,4 +86,23 @@ test_expect_success \
     'move into "."' \
     'git-mv path1/path2/ .'
 
+test_expect_success "Michael Cassar's test case" '
+       rm -fr .git papers partA &&
+       git init-db &&
+       mkdir -p papers/unsorted papers/all-papers partA &&
+       echo a > papers/unsorted/Thesis.pdf &&
+       echo b > partA/outline.txt &&
+       echo c > papers/unsorted/_another &&
+       git add papers partA &&
+       T1=`git write-tree` &&
+
+       git mv papers/unsorted/Thesis.pdf papers/all-papers/moo-blah.pdf &&
+
+       T=`git write-tree` &&
+       git ls-tree -r $T | grep partA/outline.txt || {
+               git ls-tree -r $T
+               (exit 1)
+       }
+'
+
 test_done
index 0fe2718845fa8ad66dfa850ac2921e33b10b51c1..b523fef339da2ac21d7edd750cc6d5431a70fe52 100755 (executable)
@@ -218,6 +218,8 @@ PYTHON=`sed -e '1{
        PYTHONPATH=$(pwd)/../compat
        export PYTHONPATH
 }
+GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
+export GITPERLLIB
 test -d ../templates/blt || {
        error "You haven't built things yet, have you?"
 }