needs to be a reference to a subroutine. The function will execute C to fill the provided credential hash, then call C with C as the sole argument. If C's return value is defined, the function will execute C (if return value yields true) or C (if return value is false). If the return value is undef, nothing at all is executed; this is useful, for example, if the credential could neither be verified nor rejected due to an unrelated network error. The return value is the same as what C returns. With this form, the usage might look as follows: if (Git::credential { 'protocol' => 'https', 'host' => 'example.com', 'username' => 'bob' }, sub { my $cred = shift; return !!try_to_authenticate($cred->{'username'}, $cred->{'password'}); }) { ... do more stuff ... } =cut sub credential { my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); if ('CODE' eq ref $op_or_code) { _credential_run $credential, 'fill'; my $ret = $op_or_code->($credential); if (defined $ret) { _credential_run $credential, $ret ? 'approve' : 'reject'; } return $ret; } else { _credential_run $credential, $op_or_code; } } { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); =item temp_acquire ( NAME ) Attempts to retrieve the temporary file mapped to the string C. If an associated temp file has not been created this session or was closed, it is created, cached, and set for autoflush and binmode. Internally locks the file mapped to C. This lock must be released with C when the temp file is no longer needed. Subsequent attempts to retrieve temporary files mapped to the same C while still locked will cause an error. This locking mechanism provides a weak guarantee and is not threadsafe. It does provide some error checking to help prevent temp file refs writing over one another. In general, the L returned should not be closed by consumers as it defeats the purpose of this caching mechanism. If you need to close the temp file handle, then you should use L or another temp file faculty directly. If a handle is closed and then requested again, then a warning will issue. =cut sub temp_acquire { my $temp_fd = _temp_cache(@_); $TEMP_FILES{$temp_fd}{locked} = 1; $temp_fd; } =item temp_is_locked ( NAME ) Returns true if the internal lock created by a previous C call with C is still in effect. When temp_acquire is called on a C, it internally locks the temporary file mapped to C. That lock will not be released until C is called with either the original C or the L that was returned from the original call to temp_acquire. Subsequent attempts to call C with the same C will fail unless there has been an intervening C call for that C (or its corresponding L that was returned by the original C call). If true is returned by C for a C, an attempt to C the same C will cause an error unless C is first called on that C (or its corresponding L that was returned by the original C call). =cut sub temp_is_locked { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; defined $$temp_fd && $$temp_fd->opened && $TEMP_FILES{$$temp_fd}{locked}; } =item temp_release ( NAME ) =item temp_release ( FILEHANDLE ) Releases a lock acquired through C. Can be called either with the C mapping used when acquiring the temp file or with the C referencing a locked temp file. Warns if an attempt is made to release a file that is not locked. The temp file will be truncated before being released. This can help to reduce disk I/O where the system is smart enough to detect the truncation while data is in the output buffers. Beware that after the temp file is released and truncated, any operations on that file may fail miserably until it is re-acquired. All contents are lost between each release and acquire mapped to the same string. =cut sub temp_release { my ($self, $temp_fd, $trunc) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILES{$temp_fd}; } unless ($TEMP_FILES{$temp_fd}{locked}) { carp "Attempt to release temp file '", $temp_fd, "' that has not been locked"; } temp_reset($temp_fd) if $trunc and $temp_fd->opened; $TEMP_FILES{$temp_fd}{locked} = 0; undef; } sub _temp_cache { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; if (defined $$temp_fd and $$temp_fd->opened) { if ($TEMP_FILES{$$temp_fd}{locked}) { throw Error::Simple("Temp file with moniker '" . $name . "' already in use"); } } else { if (defined $$temp_fd) { # then we're here because of a closed handle. carp "Temp file '", $name, "' was closed. Opening replacement."; } my $fname; my $tmpdir; if (defined $self) { $tmpdir = $self->repo_path(); } my $n = $name; $n =~ s/\W/_/g; # no strange chars require File::Temp; ($$temp_fd, $fname) = File::Temp::tempfile( "Git_${n}_XXXXXX", UNLINK => 1, DIR => $tmpdir, ) or throw Error::Simple("couldn't open new temp file"); $$temp_fd->autoflush; binmode $$temp_fd; $TEMP_FILES{$$temp_fd}{fname} = $fname; } $$temp_fd; } =item temp_reset ( FILEHANDLE ) Truncates and resets the position of the C. =cut sub temp_reset { my ($self, $temp_fd) = _maybe_self(@_); truncate $temp_fd, 0 or throw Error::Simple("couldn't truncate file"); sysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET()) or throw Error::Simple("couldn't seek to beginning of file"); sysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0 or throw Error::Simple("expected file position to be reset"); } =item temp_path ( NAME ) =item temp_path ( FILEHANDLE ) Returns the filename associated with the given tempfile. =cut sub temp_path { my ($self, $temp_fd) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILEMAP{$temp_fd}; } $TEMP_FILES{$temp_fd}{fname}; } sub END { unlink values %TEMP_FILEMAP if %TEMP_FILEMAP; } } # %TEMP_* Lexical Context =item prefix_lines ( PREFIX, STRING [, STRING... ]) Prefixes lines in C with C. =cut sub prefix_lines { my $prefix = shift; my $string = join("\n", @_); $string =~ s/^/$prefix/mg; return $string; } =item unquote_path ( PATH ) Unquote a quoted path containing c-escapes as returned by ls-files etc. when not using -z or when parsing the output of diff -u. =cut { my %cquote_map = ( "a" => chr(7), "b" => chr(8), "t" => chr(9), "n" => chr(10), "v" => chr(11), "f" => chr(12), "r" => chr(13), "\\" => "\\", "\042" => "\042", ); sub unquote_path { local ($_) = @_; my ($retval, $remainder); if (!/^\042(.*)\042$/) { return $_; } ($_, $retval) = ($1, ""); while (/^([^\\]*)\\(.*)$/) { $remainder = $2; $retval .= $1; for ($remainder) { if (/^([0-3][0-7][0-7])(.*)$/) { $retval .= chr(oct($1)); $_ = $2; last; } if (/^([\\\042abtnvfr])(.*)$/) { $retval .= $cquote_map{$1}; $_ = $2; last; } # This is malformed throw Error::Simple("invalid quoted path $_[0]"); } $_ = $remainder; } $retval .= $_; return $retval; } } =item get_comment_line_char ( ) Gets the core.commentchar configuration value. The value falls-back to '#' if core.commentchar is set to 'auto'. =cut sub get_comment_line_char { my $comment_line_char = config("core.commentchar") || '#'; $comment_line_char = '#' if ($comment_line_char eq 'auto'); $comment_line_char = '#' if (length($comment_line_char) != 1); return $comment_line_char; } =item comment_lines ( STRING [, STRING... ]) Comments lines following core.commentchar configuration. =cut sub comment_lines { my $comment_line_char = get_comment_line_char; return prefix_lines("$comment_line_char ", @_); } =back =head1 ERROR HANDLING All functions are supposed to throw Perl exceptions in case of errors. See the L module on how to catch those. Most exceptions are mere L instances. However, the C, C and C functions suite can throw C 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 and C (command's exit code) methods and in addition also a C method that returns either an array or a string with the captured command output (depending on the original function call context; C returns C) and $ which returns the command and its arguments (but without proper quoting). Note that the C 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 the pipe; if you want to have that automated, use C, 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 exceptions thrown by C and make your program die with C 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'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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
with C as the sole argument. If C's return value is defined, the function will execute C (if return value yields true) or C (if return value is false). If the return value is undef, nothing at all is executed; this is useful, for example, if the credential could neither be verified nor rejected due to an unrelated network error. The return value is the same as what C returns. With this form, the usage might look as follows: if (Git::credential { 'protocol' => 'https', 'host' => 'example.com', 'username' => 'bob' }, sub { my $cred = shift; return !!try_to_authenticate($cred->{'username'}, $cred->{'password'}); }) { ... do more stuff ... } =cut sub credential { my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); if ('CODE' eq ref $op_or_code) { _credential_run $credential, 'fill'; my $ret = $op_or_code->($credential); if (defined $ret) { _credential_run $credential, $ret ? 'approve' : 'reject'; } return $ret; } else { _credential_run $credential, $op_or_code; } } { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); =item temp_acquire ( NAME ) Attempts to retrieve the temporary file mapped to the string C. If an associated temp file has not been created this session or was closed, it is created, cached, and set for autoflush and binmode. Internally locks the file mapped to C. This lock must be released with C when the temp file is no longer needed. Subsequent attempts to retrieve temporary files mapped to the same C while still locked will cause an error. This locking mechanism provides a weak guarantee and is not threadsafe. It does provide some error checking to help prevent temp file refs writing over one another. In general, the L returned should not be closed by consumers as it defeats the purpose of this caching mechanism. If you need to close the temp file handle, then you should use L or another temp file faculty directly. If a handle is closed and then requested again, then a warning will issue. =cut sub temp_acquire { my $temp_fd = _temp_cache(@_); $TEMP_FILES{$temp_fd}{locked} = 1; $temp_fd; } =item temp_is_locked ( NAME ) Returns true if the internal lock created by a previous C call with C is still in effect. When temp_acquire is called on a C, it internally locks the temporary file mapped to C. That lock will not be released until C is called with either the original C or the L that was returned from the original call to temp_acquire. Subsequent attempts to call C with the same C will fail unless there has been an intervening C call for that C (or its corresponding L that was returned by the original C call). If true is returned by C for a C, an attempt to C the same C will cause an error unless C is first called on that C (or its corresponding L that was returned by the original C call). =cut sub temp_is_locked { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; defined $$temp_fd && $$temp_fd->opened && $TEMP_FILES{$$temp_fd}{locked}; } =item temp_release ( NAME ) =item temp_release ( FILEHANDLE ) Releases a lock acquired through C. Can be called either with the C mapping used when acquiring the temp file or with the C referencing a locked temp file. Warns if an attempt is made to release a file that is not locked. The temp file will be truncated before being released. This can help to reduce disk I/O where the system is smart enough to detect the truncation while data is in the output buffers. Beware that after the temp file is released and truncated, any operations on that file may fail miserably until it is re-acquired. All contents are lost between each release and acquire mapped to the same string. =cut sub temp_release { my ($self, $temp_fd, $trunc) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILES{$temp_fd}; } unless ($TEMP_FILES{$temp_fd}{locked}) { carp "Attempt to release temp file '", $temp_fd, "' that has not been locked"; } temp_reset($temp_fd) if $trunc and $temp_fd->opened; $TEMP_FILES{$temp_fd}{locked} = 0; undef; } sub _temp_cache { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; if (defined $$temp_fd and $$temp_fd->opened) { if ($TEMP_FILES{$$temp_fd}{locked}) { throw Error::Simple("Temp file with moniker '" . $name . "' already in use"); } } else { if (defined $$temp_fd) { # then we're here because of a closed handle. carp "Temp file '", $name, "' was closed. Opening replacement."; } my $fname; my $tmpdir; if (defined $self) { $tmpdir = $self->repo_path(); } my $n = $name; $n =~ s/\W/_/g; # no strange chars require File::Temp; ($$temp_fd, $fname) = File::Temp::tempfile( "Git_${n}_XXXXXX", UNLINK => 1, DIR => $tmpdir, ) or throw Error::Simple("couldn't open new temp file"); $$temp_fd->autoflush; binmode $$temp_fd; $TEMP_FILES{$$temp_fd}{fname} = $fname; } $$temp_fd; } =item temp_reset ( FILEHANDLE ) Truncates and resets the position of the C. =cut sub temp_reset { my ($self, $temp_fd) = _maybe_self(@_); truncate $temp_fd, 0 or throw Error::Simple("couldn't truncate file"); sysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET()) or throw Error::Simple("couldn't seek to beginning of file"); sysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0 or throw Error::Simple("expected file position to be reset"); } =item temp_path ( NAME ) =item temp_path ( FILEHANDLE ) Returns the filename associated with the given tempfile. =cut sub temp_path { my ($self, $temp_fd) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILEMAP{$temp_fd}; } $TEMP_FILES{$temp_fd}{fname}; } sub END { unlink values %TEMP_FILEMAP if %TEMP_FILEMAP; } } # %TEMP_* Lexical Context =item prefix_lines ( PREFIX, STRING [, STRING... ]) Prefixes lines in C with C. =cut sub prefix_lines { my $prefix = shift; my $string = join("\n", @_); $string =~ s/^/$prefix/mg; return $string; } =item unquote_path ( PATH ) Unquote a quoted path containing c-escapes as returned by ls-files etc. when not using -z or when parsing the output of diff -u. =cut { my %cquote_map = ( "a" => chr(7), "b" => chr(8), "t" => chr(9), "n" => chr(10), "v" => chr(11), "f" => chr(12), "r" => chr(13), "\\" => "\\", "\042" => "\042", ); sub unquote_path { local ($_) = @_; my ($retval, $remainder); if (!/^\042(.*)\042$/) { return $_; } ($_, $retval) = ($1, ""); while (/^([^\\]*)\\(.*)$/) { $remainder = $2; $retval .= $1; for ($remainder) { if (/^([0-3][0-7][0-7])(.*)$/) { $retval .= chr(oct($1)); $_ = $2; last; } if (/^([\\\042abtnvfr])(.*)$/) { $retval .= $cquote_map{$1}; $_ = $2; last; } # This is malformed throw Error::Simple("invalid quoted path $_[0]"); } $_ = $remainder; } $retval .= $_; return $retval; } } =item get_comment_line_char ( ) Gets the core.commentchar configuration value. The value falls-back to '#' if core.commentchar is set to 'auto'. =cut sub get_comment_line_char { my $comment_line_char = config("core.commentchar") || '#'; $comment_line_char = '#' if ($comment_line_char eq 'auto'); $comment_line_char = '#' if (length($comment_line_char) != 1); return $comment_line_char; } =item comment_lines ( STRING [, STRING... ]) Comments lines following core.commentchar configuration. =cut sub comment_lines { my $comment_line_char = get_comment_line_char; return prefix_lines("$comment_line_char ", @_); } =back =head1 ERROR HANDLING All functions are supposed to throw Perl exceptions in case of errors. See the L module on how to catch those. Most exceptions are mere L instances. However, the C, C and C functions suite can throw C 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 and C (command's exit code) methods and in addition also a C method that returns either an array or a string with the captured command output (depending on the original function call context; C returns C) and $ which returns the command and its arguments (but without proper quoting). Note that the C 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 the pipe; if you want to have that automated, use C, 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 exceptions thrown by C and make your program die with C 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'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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
's return value is defined, the function will execute C (if return value yields true) or C (if return value is false). If the return value is undef, nothing at all is executed; this is useful, for example, if the credential could neither be verified nor rejected due to an unrelated network error. The return value is the same as what C returns. With this form, the usage might look as follows: if (Git::credential { 'protocol' => 'https', 'host' => 'example.com', 'username' => 'bob' }, sub { my $cred = shift; return !!try_to_authenticate($cred->{'username'}, $cred->{'password'}); }) { ... do more stuff ... } =cut sub credential { my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); if ('CODE' eq ref $op_or_code) { _credential_run $credential, 'fill'; my $ret = $op_or_code->($credential); if (defined $ret) { _credential_run $credential, $ret ? 'approve' : 'reject'; } return $ret; } else { _credential_run $credential, $op_or_code; } } { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); =item temp_acquire ( NAME ) Attempts to retrieve the temporary file mapped to the string C. If an associated temp file has not been created this session or was closed, it is created, cached, and set for autoflush and binmode. Internally locks the file mapped to C. This lock must be released with C when the temp file is no longer needed. Subsequent attempts to retrieve temporary files mapped to the same C while still locked will cause an error. This locking mechanism provides a weak guarantee and is not threadsafe. It does provide some error checking to help prevent temp file refs writing over one another. In general, the L returned should not be closed by consumers as it defeats the purpose of this caching mechanism. If you need to close the temp file handle, then you should use L or another temp file faculty directly. If a handle is closed and then requested again, then a warning will issue. =cut sub temp_acquire { my $temp_fd = _temp_cache(@_); $TEMP_FILES{$temp_fd}{locked} = 1; $temp_fd; } =item temp_is_locked ( NAME ) Returns true if the internal lock created by a previous C call with C is still in effect. When temp_acquire is called on a C, it internally locks the temporary file mapped to C. That lock will not be released until C is called with either the original C or the L that was returned from the original call to temp_acquire. Subsequent attempts to call C with the same C will fail unless there has been an intervening C call for that C (or its corresponding L that was returned by the original C call). If true is returned by C for a C, an attempt to C the same C will cause an error unless C is first called on that C (or its corresponding L that was returned by the original C call). =cut sub temp_is_locked { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; defined $$temp_fd && $$temp_fd->opened && $TEMP_FILES{$$temp_fd}{locked}; } =item temp_release ( NAME ) =item temp_release ( FILEHANDLE ) Releases a lock acquired through C. Can be called either with the C mapping used when acquiring the temp file or with the C referencing a locked temp file. Warns if an attempt is made to release a file that is not locked. The temp file will be truncated before being released. This can help to reduce disk I/O where the system is smart enough to detect the truncation while data is in the output buffers. Beware that after the temp file is released and truncated, any operations on that file may fail miserably until it is re-acquired. All contents are lost between each release and acquire mapped to the same string. =cut sub temp_release { my ($self, $temp_fd, $trunc) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILES{$temp_fd}; } unless ($TEMP_FILES{$temp_fd}{locked}) { carp "Attempt to release temp file '", $temp_fd, "' that has not been locked"; } temp_reset($temp_fd) if $trunc and $temp_fd->opened; $TEMP_FILES{$temp_fd}{locked} = 0; undef; } sub _temp_cache { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; if (defined $$temp_fd and $$temp_fd->opened) { if ($TEMP_FILES{$$temp_fd}{locked}) { throw Error::Simple("Temp file with moniker '" . $name . "' already in use"); } } else { if (defined $$temp_fd) { # then we're here because of a closed handle. carp "Temp file '", $name, "' was closed. Opening replacement."; } my $fname; my $tmpdir; if (defined $self) { $tmpdir = $self->repo_path(); } my $n = $name; $n =~ s/\W/_/g; # no strange chars require File::Temp; ($$temp_fd, $fname) = File::Temp::tempfile( "Git_${n}_XXXXXX", UNLINK => 1, DIR => $tmpdir, ) or throw Error::Simple("couldn't open new temp file"); $$temp_fd->autoflush; binmode $$temp_fd; $TEMP_FILES{$$temp_fd}{fname} = $fname; } $$temp_fd; } =item temp_reset ( FILEHANDLE ) Truncates and resets the position of the C. =cut sub temp_reset { my ($self, $temp_fd) = _maybe_self(@_); truncate $temp_fd, 0 or throw Error::Simple("couldn't truncate file"); sysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET()) or throw Error::Simple("couldn't seek to beginning of file"); sysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0 or throw Error::Simple("expected file position to be reset"); } =item temp_path ( NAME ) =item temp_path ( FILEHANDLE ) Returns the filename associated with the given tempfile. =cut sub temp_path { my ($self, $temp_fd) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILEMAP{$temp_fd}; } $TEMP_FILES{$temp_fd}{fname}; } sub END { unlink values %TEMP_FILEMAP if %TEMP_FILEMAP; } } # %TEMP_* Lexical Context =item prefix_lines ( PREFIX, STRING [, STRING... ]) Prefixes lines in C with C. =cut sub prefix_lines { my $prefix = shift; my $string = join("\n", @_); $string =~ s/^/$prefix/mg; return $string; } =item unquote_path ( PATH ) Unquote a quoted path containing c-escapes as returned by ls-files etc. when not using -z or when parsing the output of diff -u. =cut { my %cquote_map = ( "a" => chr(7), "b" => chr(8), "t" => chr(9), "n" => chr(10), "v" => chr(11), "f" => chr(12), "r" => chr(13), "\\" => "\\", "\042" => "\042", ); sub unquote_path { local ($_) = @_; my ($retval, $remainder); if (!/^\042(.*)\042$/) { return $_; } ($_, $retval) = ($1, ""); while (/^([^\\]*)\\(.*)$/) { $remainder = $2; $retval .= $1; for ($remainder) { if (/^([0-3][0-7][0-7])(.*)$/) { $retval .= chr(oct($1)); $_ = $2; last; } if (/^([\\\042abtnvfr])(.*)$/) { $retval .= $cquote_map{$1}; $_ = $2; last; } # This is malformed throw Error::Simple("invalid quoted path $_[0]"); } $_ = $remainder; } $retval .= $_; return $retval; } } =item get_comment_line_char ( ) Gets the core.commentchar configuration value. The value falls-back to '#' if core.commentchar is set to 'auto'. =cut sub get_comment_line_char { my $comment_line_char = config("core.commentchar") || '#'; $comment_line_char = '#' if ($comment_line_char eq 'auto'); $comment_line_char = '#' if (length($comment_line_char) != 1); return $comment_line_char; } =item comment_lines ( STRING [, STRING... ]) Comments lines following core.commentchar configuration. =cut sub comment_lines { my $comment_line_char = get_comment_line_char; return prefix_lines("$comment_line_char ", @_); } =back =head1 ERROR HANDLING All functions are supposed to throw Perl exceptions in case of errors. See the L module on how to catch those. Most exceptions are mere L instances. However, the C, C and C functions suite can throw C 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 and C (command's exit code) methods and in addition also a C method that returns either an array or a string with the captured command output (depending on the original function call context; C returns C) and $ which returns the command and its arguments (but without proper quoting). Note that the C 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 the pipe; if you want to have that automated, use C, 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 exceptions thrown by C and make your program die with C 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'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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
returns. With this form, the usage might look as follows: if (Git::credential { 'protocol' => 'https', 'host' => 'example.com', 'username' => 'bob' }, sub { my $cred = shift; return !!try_to_authenticate($cred->{'username'}, $cred->{'password'}); }) { ... do more stuff ... } =cut sub credential { my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); if ('CODE' eq ref $op_or_code) { _credential_run $credential, 'fill'; my $ret = $op_or_code->($credential); if (defined $ret) { _credential_run $credential, $ret ? 'approve' : 'reject'; } return $ret; } else { _credential_run $credential, $op_or_code; } } { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); =item temp_acquire ( NAME ) Attempts to retrieve the temporary file mapped to the string C. If an associated temp file has not been created this session or was closed, it is created, cached, and set for autoflush and binmode. Internally locks the file mapped to C. This lock must be released with C when the temp file is no longer needed. Subsequent attempts to retrieve temporary files mapped to the same C while still locked will cause an error. This locking mechanism provides a weak guarantee and is not threadsafe. It does provide some error checking to help prevent temp file refs writing over one another. In general, the L returned should not be closed by consumers as it defeats the purpose of this caching mechanism. If you need to close the temp file handle, then you should use L or another temp file faculty directly. If a handle is closed and then requested again, then a warning will issue. =cut sub temp_acquire { my $temp_fd = _temp_cache(@_); $TEMP_FILES{$temp_fd}{locked} = 1; $temp_fd; } =item temp_is_locked ( NAME ) Returns true if the internal lock created by a previous C call with C is still in effect. When temp_acquire is called on a C, it internally locks the temporary file mapped to C. That lock will not be released until C is called with either the original C or the L that was returned from the original call to temp_acquire. Subsequent attempts to call C with the same C will fail unless there has been an intervening C call for that C (or its corresponding L that was returned by the original C call). If true is returned by C for a C, an attempt to C the same C will cause an error unless C is first called on that C (or its corresponding L that was returned by the original C call). =cut sub temp_is_locked { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; defined $$temp_fd && $$temp_fd->opened && $TEMP_FILES{$$temp_fd}{locked}; } =item temp_release ( NAME ) =item temp_release ( FILEHANDLE ) Releases a lock acquired through C. Can be called either with the C mapping used when acquiring the temp file or with the C referencing a locked temp file. Warns if an attempt is made to release a file that is not locked. The temp file will be truncated before being released. This can help to reduce disk I/O where the system is smart enough to detect the truncation while data is in the output buffers. Beware that after the temp file is released and truncated, any operations on that file may fail miserably until it is re-acquired. All contents are lost between each release and acquire mapped to the same string. =cut sub temp_release { my ($self, $temp_fd, $trunc) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILES{$temp_fd}; } unless ($TEMP_FILES{$temp_fd}{locked}) { carp "Attempt to release temp file '", $temp_fd, "' that has not been locked"; } temp_reset($temp_fd) if $trunc and $temp_fd->opened; $TEMP_FILES{$temp_fd}{locked} = 0; undef; } sub _temp_cache { my ($self, $name) = _maybe_self(@_); my $temp_fd = \$TEMP_FILEMAP{$name}; if (defined $$temp_fd and $$temp_fd->opened) { if ($TEMP_FILES{$$temp_fd}{locked}) { throw Error::Simple("Temp file with moniker '" . $name . "' already in use"); } } else { if (defined $$temp_fd) { # then we're here because of a closed handle. carp "Temp file '", $name, "' was closed. Opening replacement."; } my $fname; my $tmpdir; if (defined $self) { $tmpdir = $self->repo_path(); } my $n = $name; $n =~ s/\W/_/g; # no strange chars require File::Temp; ($$temp_fd, $fname) = File::Temp::tempfile( "Git_${n}_XXXXXX", UNLINK => 1, DIR => $tmpdir, ) or throw Error::Simple("couldn't open new temp file"); $$temp_fd->autoflush; binmode $$temp_fd; $TEMP_FILES{$$temp_fd}{fname} = $fname; } $$temp_fd; } =item temp_reset ( FILEHANDLE ) Truncates and resets the position of the C. =cut sub temp_reset { my ($self, $temp_fd) = _maybe_self(@_); truncate $temp_fd, 0 or throw Error::Simple("couldn't truncate file"); sysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET()) or throw Error::Simple("couldn't seek to beginning of file"); sysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0 or throw Error::Simple("expected file position to be reset"); } =item temp_path ( NAME ) =item temp_path ( FILEHANDLE ) Returns the filename associated with the given tempfile. =cut sub temp_path { my ($self, $temp_fd) = _maybe_self(@_); if (exists $TEMP_FILEMAP{$temp_fd}) { $temp_fd = $TEMP_FILEMAP{$temp_fd}; } $TEMP_FILES{$temp_fd}{fname}; } sub END { unlink values %TEMP_FILEMAP if %TEMP_FILEMAP; } } # %TEMP_* Lexical Context =item prefix_lines ( PREFIX, STRING [, STRING... ]) Prefixes lines in C with C. =cut sub prefix_lines { my $prefix = shift; my $string = join("\n", @_); $string =~ s/^/$prefix/mg; return $string; } =item unquote_path ( PATH ) Unquote a quoted path containing c-escapes as returned by ls-files etc. when not using -z or when parsing the output of diff -u. =cut { my %cquote_map = ( "a" => chr(7), "b" => chr(8), "t" => chr(9), "n" => chr(10), "v" => chr(11), "f" => chr(12), "r" => chr(13), "\\" => "\\", "\042" => "\042", ); sub unquote_path { local ($_) = @_; my ($retval, $remainder); if (!/^\042(.*)\042$/) { return $_; } ($_, $retval) = ($1, ""); while (/^([^\\]*)\\(.*)$/) { $remainder = $2; $retval .= $1; for ($remainder) { if (/^([0-3][0-7][0-7])(.*)$/) { $retval .= chr(oct($1)); $_ = $2; last; } if (/^([\\\042abtnvfr])(.*)$/) { $retval .= $cquote_map{$1}; $_ = $2; last; } # This is malformed throw Error::Simple("invalid quoted path $_[0]"); } $_ = $remainder; } $retval .= $_; return $retval; } } =item get_comment_line_char ( ) Gets the core.commentchar configuration value. The value falls-back to '#' if core.commentchar is set to 'auto'. =cut sub get_comment_line_char { my $comment_line_char = config("core.commentchar") || '#'; $comment_line_char = '#' if ($comment_line_char eq 'auto'); $comment_line_char = '#' if (length($comment_line_char) != 1); return $comment_line_char; } =item comment_lines ( STRING [, STRING... ]) Comments lines following core.commentchar configuration. =cut sub comment_lines { my $comment_line_char = get_comment_line_char; return prefix_lines("$comment_line_char ", @_); } =back =head1 ERROR HANDLING All functions are supposed to throw Perl exceptions in case of errors. See the L module on how to catch those. Most exceptions are mere L instances. However, the C, C and C functions suite can throw C 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 and C (command's exit code) methods and in addition also a C method that returns either an array or a string with the captured command output (depending on the original function call context; C returns C) and $ which returns the command and its arguments (but without proper quoting). Note that the C 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 the pipe; if you want to have that automated, use C, 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 exceptions thrown by C and make your program die with C 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'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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
and make your program die with C 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'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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
'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 Epasky@suse.czE. 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 { UNIVERSAL::isa($_[0], '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 'MSWin32') { # ActiveState Perl #defined $opts{STDERR} and # warn 'ignoring STDERR option - running w/ ActiveState'; $direction eq '-|' or die 'input pipe for ActiveState not implemented'; # the strange construction with *ACPIPE is just to # explain the tie below that we want to bind to # a handle class, not scalar. It is not known if # it is something specific to ActiveState Perl or # just a Perl quirk. tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args); $fh = *ACPIPE; } else { my $pid = open($fh, $direction); if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; } elsif (defined $opts{STDERR}) { open (STDERR, '>', '/dev/null') or die "opening /dev/null 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) = @_; _setup_git_cmd_env($self); _execv_git_cmd(@args); die qq[exec "@args" failed: $!]; } # set up the appropriate state for git command sub _setup_git_cmd_env { my $self = shift; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); $self->repo_path() and $self->wc_path() and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } } # Execute the given Git command ($_[0]) with arguments ($_[1..]) # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } sub _is_sig { my ($v, $n) = @_; # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot # Git.pm codepath. require POSIX; no strict 'refs'; $v == *{"POSIX::$n"}->(); } # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; foreach my $fh (@_) { if (close $fh) { # nop } elsif ($!) { # 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); } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { # we might e.g. closed a live stream; the command # dying of SIGPIPE would drive us here. } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } elsif ($? & 127) { die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', $?, $? & 127); } } } sub DESTROY { my ($self) = @_; $self->_close_hash_and_insert_object(); $self->_close_cat_blob(); } # Pipe implementation for ActiveState Perl. package Git::activestate_pipe; 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 # Let's just hope ActiveState Perl does at least the quoting # correctly. my @data = qx{git @params}; bless { i => 0, data => \@data }, $class; } sub READLINE { my $self = shift; if ($self->{i} >= scalar @{$self->{data}}) { return undef; } my $i = $self->{i}; if (wantarray) { $self->{i} = $#{$self->{'data'}} + 1; return splice(@{$self->{'data'}}, $i); } $self->{i} = $i + 1; return $self->{'data'}->[ $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