summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-repo-config.txt3
-rw-r--r--INSTALL13
-rw-r--r--Makefile116
-rw-r--r--builtin-repo-config.c2
-rw-r--r--config.mak.in1
-rw-r--r--configure.ac2
-rwxr-xr-xgit-send-email.perl38
-rw-r--r--git.spec.in23
-rw-r--r--perl/.gitignore4
-rw-r--r--perl/Git.pm837
-rw-r--r--perl/Makefile.PL28
-rw-r--r--perl/private-Error.pm827
-rwxr-xr-xt/test-lib.sh2
13 files changed, 1819 insertions, 77 deletions
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index b03d66f61c..8a1ab61e94 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -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
diff --git a/INSTALL b/INSTALL
index fa9bf74a20..fce6bc39d5 100644
--- a/INSTALL
+++ b/INSTALL
@@ -38,6 +38,19 @@ Issues of note:
has been actively developed since 1997, and people have moved over to
graphical file managers.
+ - You can use git after building but without installing if you
+ wanted to. Various git commands need to find other git
+ commands and scripts to do their work, so you would need to
+ arrange a few environment variables to tell them that their
+ friends will be found in your built source area instead of at
+ their standard installation area. Something like this works
+ for me:
+
+ GIT_EXEC_PATH=`pwd`
+ PATH=`pwd`:$PATH
+ GITPERLLIB=`pwd`/perl/blib/lib
+ export GIT_EXEC_PATH PATH GITPERLLIB
+
- Git is reasonably self-sufficient, but does depend on a few external
programs and libraries:
diff --git a/Makefile b/Makefile
index e649d72f60..dd33158529 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,6 @@
# The default target of this Makefile is...
all:
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies MOZILLA_SHA1.
#
@@ -60,6 +55,11 @@ all:
# Define ARM_SHA1 environment variable when running make to make use of
# a bundled SHA1 routine optimized for ARM.
#
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
#
# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
@@ -84,13 +84,13 @@ all:
# Define COLLISION_CHECK below if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
# sufficient guarantee that no collisions between objects will ever happen.
-
+#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
# randomly break unless your underlying filesystem supports those sub-second
# times (my ext3 doesn't).
-
+#
# Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-cache perspective.
@@ -149,6 +149,12 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
### --- END CONFIGURATION SECTION ---
+# Those must not be GNU-specific; they are shared with perl/ which may
+# be built by a different compiler. (Note that this is an artifact now
+# but it still might be nice to keep that distinction.)
+BASIC_CFLAGS =
+BASIC_LDFLAGS =
+
SCRIPT_SH = \
git-bisect.sh git-branch.sh git-checkout.sh \
git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
@@ -302,7 +308,7 @@ BUILTIN_OBJS = \
builtin-write-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-LIBS = $(GITLIBS) -lz
+EXTLIBS = -lz
#
# Platform specific tweaks
@@ -324,14 +330,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
@@ -353,7 +359,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
@@ -371,21 +377,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
@@ -399,9 +406,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
@@ -423,7 +430,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
@@ -444,13 +451,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
@@ -462,32 +469,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
@@ -510,13 +517,13 @@ 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
@@ -527,7 +534,7 @@ ifdef NO_INET_PTON
endif
ifdef NO_ICONV
- ALL_CFLAGS += -DNO_ICONV
+ BASIC_CFLAGS += -DNO_ICONV
endif
ifdef PPC_SHA1
@@ -543,12 +550,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 +573,24 @@ 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: perl/Makefile
+ $(MAKE) -C perl
$(MAKE) -C templates
strip: $(PROGRAMS) git$X
@@ -608,9 +624,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 +765,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 +831,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 +902,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
diff --git a/builtin-repo-config.c b/builtin-repo-config.c
index 9cf12d32e5..f60cee1dc5 100644
--- a/builtin-repo-config.c
+++ b/builtin-repo-config.c
@@ -119,7 +119,7 @@ static int get_value(const char* key_, const char* regex_)
if (do_all)
ret = !seen;
else
- ret = (seen == 1) ? 0 : 1;
+ ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
free_strings:
free(repo_config);
diff --git a/config.mak.in b/config.mak.in
index 6d20673b24..1cafa19ed4 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -2,6 +2,7 @@
# @configure_input@
CC = @CC@
+CFLAGS = @CFLAGS@
AR = @AR@
TAR = @TAR@
#INSTALL = @INSTALL@ # needs install-sh or install.sh in sources
diff --git a/configure.ac b/configure.ac
index b1a5833b40..cff5722eb9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -94,7 +94,7 @@ AC_SUBST(PYTHON_PATH)
## Checks for programs.
AC_MSG_NOTICE([CHECKS for programs])
#
-AC_PROG_CC
+AC_PROG_CC([cc gcc])
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
diff --git a/git-send-email.perl b/git-send-email.perl
index 746c525079..4a20310841 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -21,6 +21,7 @@ use warnings;
use Term::ReadLine;
use Getopt::Long;
use Data::Dumper;
+use Git;
package FakeTerm;
sub new {
@@ -92,6 +93,7 @@ my $smtp_server;
# 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 @@ foreach my $entry (@bcclist) {
# 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 @@ my %parse_alias = (
}}}
);
-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
diff --git a/git.spec.in b/git.spec.in
index 8ccd2564e7..6d900342e3 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -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/
diff --git a/perl/.gitignore b/perl/.gitignore
new file mode 100644
index 0000000000..e990caeea7
--- /dev/null
+++ b/perl/.gitignore
@@ -0,0 +1,4 @@
+Makefile
+blib
+blibdirs
+pm_to_blib
diff --git a/perl/Git.pm b/perl/Git.pm
new file mode 100644
index 0000000000..2b26b65bfb
--- /dev/null
+++ b/perl/Git.pm
@@ -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");
+