diff options
-rw-r--r-- | Documentation/git-repo-config.txt | 3 | ||||
-rw-r--r-- | INSTALL | 13 | ||||
-rw-r--r-- | Makefile | 116 | ||||
-rw-r--r-- | builtin-repo-config.c | 2 | ||||
-rw-r--r-- | config.mak.in | 1 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rwxr-xr-x | git-send-email.perl | 38 | ||||
-rw-r--r-- | git.spec.in | 23 | ||||
-rw-r--r-- | perl/.gitignore | 4 | ||||
-rw-r--r-- | perl/Git.pm | 837 | ||||
-rw-r--r-- | perl/Makefile.PL | 28 | ||||
-rw-r--r-- | perl/private-Error.pm | 827 | ||||
-rwxr-xr-x | t/test-lib.sh | 2 |
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 @@ -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: @@ -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"); + |