upload-pack: provide a hook for running pack-objects
[gitweb.git] / perl / Git.pm
index 377f7bafb779aeb7228419048d3f746179eda841..ce7e4e8da3947bb2c527c49d6d09e1e49b0392c3 100644 (file)
@@ -59,8 +59,9 @@ =head1 SYNOPSIS
                 command_bidi_pipe command_close_bidi_pipe
                 version exec_path html_path hash_object git_cmd_try
                 remote_refs prompt
+                get_tz_offset
                 credential credential_read credential_write
-                temp_acquire temp_release temp_reset temp_path);
+                temp_acquire temp_is_locked temp_release temp_reset temp_path);
 
 
 =head1 DESCRIPTION
@@ -103,6 +104,7 @@ =head1 DESCRIPTION
 use Cwd qw(abs_path cwd);
 use IPC::Open2 qw(open2);
 use Fcntl qw(SEEK_SET SEEK_CUR);
+use Time::Local qw(timegm);
 }
 
 
@@ -186,7 +188,8 @@ sub repository {
                };
 
                if ($dir) {
-                       $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir;
+                       _verify_require();
+                       File::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;
                        $opts{Repository} = abs_path($dir);
 
                        # If --git-dir went ok, this shouldn't die either.
@@ -390,7 +393,7 @@ sub command_close_pipe {
 Execute the given C<COMMAND> in the same way as command_output_pipe()
 does but return both an input pipe filehandle and an output pipe filehandle.
 
-The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>.
+The function will return C<($pid, $pipe_in, $pipe_out, $ctx)>.
 See C<command_close_bidi_pipe()> for details.
 
 =cut
@@ -515,6 +518,27 @@ sub version {
 
 sub html_path { command_oneline('--html-path') }
 
+
+=item get_tz_offset ( TIME )
+
+Return the time zone offset from GMT in the form +/-HHMM where HH is
+the number of hours from GMT and MM is the number of minutes.  This is
+the equivalent of what strftime("%z", ...) would provide on a GNU
+platform.
+
+If TIME is not supplied, the current local time is used.
+
+=cut
+
+sub get_tz_offset {
+       # some systmes don't handle or mishandle %z, so be creative.
+       my $t = shift || time;
+       my $gm = timegm(localtime($t));
+       my $sign = qw( + + - )[ $gm <=> $t ];
+       return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+}
+
+
 =item prompt ( PROMPT , ISPASSWORD  )
 
 Query user C<PROMPT> and return answer from user.
@@ -672,7 +696,7 @@ sub config_path {
 is simple decimal number.  An optional value suffix of 'k', 'm',
 or 'g' in the config file will cause the value to be multiplied
 by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output.
-It would return C<undef> if configuration variable is not defined,
+It would return C<undef> if configuration variable is not defined.
 
 =cut
 
@@ -681,7 +705,7 @@ sub config_int {
 }
 
 # Common subroutine to implement bulk of what the config* family of methods
-# do. This curently wraps command('config') so it is not so fast.
+# do. This currently wraps command('config') so it is not so fast.
 sub _config_common {
        my ($opts) = shift @_;
        my ($self, $var) = _maybe_self(@_);
@@ -841,6 +865,73 @@ sub ident_person {
        return "$ident[0] <$ident[1]>";
 }
 
+=item parse_mailboxes
+
+Return an array of mailboxes extracted from a string.
+
+=cut
+
+sub parse_mailboxes {
+       my $re_comment = qr/\((?:[^)]*)\)/;
+       my $re_quote = qr/"(?:[^\"\\]|\\.)*"/;
+       my $re_word = qr/(?:[^]["\s()<>:;@\\,.]|\\.)+/;
+
+       # divide the string in tokens of the above form
+       my $re_token = qr/(?:$re_quote|$re_word|$re_comment|\S)/;
+       my @tokens = map { $_ =~ /\s*($re_token)\s*/g } @_;
+
+       # add a delimiter to simplify treatment for the last mailbox
+       push @tokens, ",";
+
+       my (@addr_list, @phrase, @address, @comment, @buffer) = ();
+       foreach my $token (@tokens) {
+               if ($token =~ /^[,;]$/) {
+                       # if buffer still contains undeterminated strings
+                       # append it at the end of @address or @phrase
+                       if (@address) {
+                               push @address, @buffer;
+                       } else {
+                               push @phrase, @buffer;
+                       }
+
+                       my $str_phrase = join ' ', @phrase;
+                       my $str_address = join '', @address;
+                       my $str_comment = join ' ', @comment;
+
+                       # quote are necessary if phrase contains
+                       # special characters
+                       if ($str_phrase =~ /[][()<>:;@\\,.\000-\037\177]/) {
+                               $str_phrase =~ s/(^|[^\\])"/$1/g;
+                               $str_phrase = qq["$str_phrase"];
+                       }
+
+                       # add "<>" around the address if necessary
+                       if ($str_address ne "" && $str_phrase ne "") {
+                               $str_address = qq[<$str_address>];
+                       }
+
+                       my $str_mailbox = "$str_phrase $str_address $str_comment";
+                       $str_mailbox =~ s/^\s*|\s*$//g;
+                       push @addr_list, $str_mailbox if ($str_mailbox);
+
+                       @phrase = @address = @comment = @buffer = ();
+               } elsif ($token =~ /^\(/) {
+                       push @comment, $token;
+               } elsif ($token eq "<") {
+                       push @phrase, (splice @address), (splice @buffer);
+               } elsif ($token eq ">") {
+                       push @address, (splice @buffer);
+               } elsif ($token eq "@") {
+                       push @address, (splice @buffer), "@";
+               } elsif ($token eq ".") {
+                       push @address, (splice @buffer), ".";
+               } else {
+                       push @buffer, $token;
+               }
+       }
+
+       return @addr_list;
+}
 
 =item hash_object ( TYPE, FILENAME )
 
@@ -946,20 +1037,22 @@ sub cat_blob {
        my $size = $1;
 
        my $blob;
-       my $bytesRead = 0;
+       my $bytesLeft = $size;
 
        while (1) {
-               my $bytesLeft = $size - $bytesRead;
                last unless $bytesLeft;
 
                my $bytesToRead = $bytesLeft < 1024 ? $bytesLeft : 1024;
-               my $read = read($in, $blob, $bytesToRead, $bytesRead);
+               my $read = read($in, $blob, $bytesToRead);
                unless (defined($read)) {
                        $self->_close_cat_blob();
                        throw Error::Simple("in pipe went bad");
                }
-
-               $bytesRead += $read;
+               unless (print $fh $blob) {
+                       $self->_close_cat_blob();
+                       throw Error::Simple("couldn't write to passed in filehandle");
+               }
+               $bytesLeft -= $read;
        }
 
        # Skip past the trailing newline.
@@ -974,11 +1067,6 @@ sub cat_blob {
                throw Error::Simple("didn't find newline after blob");
        }
 
-       unless (print $fh $blob) {
-               $self->_close_cat_blob();
-               throw Error::Simple("couldn't write to passed in filehandle");
-       }
-
        return $size;
 }
 
@@ -1160,7 +1248,7 @@ sub credential {
 
 =item temp_acquire ( NAME )
 
-Attempts to retreive the temporary file mapped to the string C<NAME>. If an
+Attempts to retrieve the temporary file mapped to the string C<NAME>. If an
 associated temp file has not been created this session or was closed, it is
 created, cached, and set for autoflush and binmode.
 
@@ -1186,6 +1274,35 @@ sub temp_acquire {
        $temp_fd;
 }
 
+=item temp_is_locked ( NAME )
+
+Returns true if the internal lock created by a previous C<temp_acquire()>
+call with C<NAME> is still in effect.
+
+When temp_acquire is called on a C<NAME>, it internally locks the temporary
+file mapped to C<NAME>.  That lock will not be released until C<temp_release()>
+is called with either the original C<NAME> or the L<File::Handle> that was
+returned from the original call to temp_acquire.
+
+Subsequent attempts to call C<temp_acquire()> with the same C<NAME> will fail
+unless there has been an intervening C<temp_release()> call for that C<NAME>
+(or its corresponding L<File::Handle> that was returned by the original
+C<temp_acquire()> call).
+
+If true is returned by C<temp_is_locked()> for a C<NAME>, an attempt to
+C<temp_acquire()> the same C<NAME> will cause an error unless
+C<temp_release> is first called on that C<NAME> (or its corresponding
+L<File::Handle> that was returned by the original C<temp_acquire()> 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 )
@@ -1245,8 +1362,11 @@ sub _temp_cache {
                        $tmpdir = $self->repo_path();
                }
 
-               ($$temp_fd, $fname) = File::Temp->tempfile(
-                       'Git_XXXXXX', UNLINK => 1, DIR => $tmpdir,
+               my $n = $name;
+               $n =~ s/\W/_/g; # no strange chars
+
+               ($$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;
@@ -1469,12 +1589,12 @@ sub _command_common_pipe {
                if (not defined $pid) {
                        throw Error::Simple("open failed: $!");
                } elsif ($pid == 0) {
-                       if (defined $opts{STDERR}) {
-                               close STDERR;
-                       }
                        if ($opts{STDERR}) {
                                open (STDERR, '>&', $opts{STDERR})
                                        or die "dup failed: $!";
+                       } elsif (defined $opts{STDERR}) {
+                               open (STDERR, '>', '/dev/null')
+                                       or die "opening /dev/null failed: $!";
                        }
                        _cmd_exec($self, $cmd, @args);
                }