Merge branch 'master' into lj/refs

* 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()
  ...
This commit is contained in:
Junio C Hamano 2006-10-02 11:49:59 -07:00
commit ff989b8d46
37 changed files with 3029 additions and 323 deletions

View 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

View file

@ -10,8 +10,11 @@
--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

View 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.
--port::
Listen on an alternative port.
--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=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

View 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

View file

@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
DEF_VER=v1.4.2.GIT
DEF_VER=v1.4.3.GIT
LF='
'

13
INSTALL
View file

@ -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:

125
Makefile
View file

@ -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

View 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?");

View 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) {

View 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;

View 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);

220
compat/inet_pton.c Normal file
View file

@ -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 */
}

View 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

View 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])

View file

@ -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

179
daemon.c
View file

@ -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);
}

63
date.c
View file

@ -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))

168
diff.c
View file

@ -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++;
}

3
diff.h
View file

@ -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;

View file

@ -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?"

View file

@ -135,7 +135,7 @@
if ($fields[4] eq 'M') {
push @mfiles, $fields[5];
}
if ($fields[4] eq 'R') {
if ($fields[4] eq 'D') {
push @dfiles, $fields[5];
}
}

View file

@ -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 ||

View file

@ -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"
;;

View file

@ -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

View file

@ -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;

View 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/

View file

@ -106,7 +106,7 @@
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');
our $file_parent = $cgi->param('fp');
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;
# 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;
}
# no null characters
if ($input =~ m!\0!) {
return undef;
}
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;
}
if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
return undef;
}
if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
# 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;

12
http.c
View file

@ -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

View 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,

View 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);

4
perl/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
Makefile
blib
blibdirs
pm_to_blib

837
perl/Git.pm Normal file
View file

@ -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

28
perl/Makefile.PL Normal file
View file

@ -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
);

827
perl/private-Error.pm Normal file
View file

@ -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

6
refs.c
View file

@ -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

View file

@ -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

View file

@ -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?"
}