Merge git://git.bogomips.org/git-svn
* git://git.bogomips.org/git-svn: git-svn: Reduce temp file usage when dealing with non-links git-svn: Make it incrementally faster by minimizing temp files Git.pm: Add faculties to allow temp files to be cached
This commit is contained in:
commit
c67a9e2682
65
git-svn.perl
65
git-svn.perl
@ -1265,7 +1265,7 @@ sub md5sum {
|
||||
my $arg = shift;
|
||||
my $ref = ref $arg;
|
||||
my $md5 = Digest::MD5->new();
|
||||
if ($ref eq 'GLOB' || $ref eq 'IO::File') {
|
||||
if ($ref eq 'GLOB' || $ref eq 'IO::File' || $ref eq 'File::Temp') {
|
||||
$md5->addfile($arg) or croak $!;
|
||||
} elsif ($ref eq 'SCALAR') {
|
||||
$md5->add($$arg) or croak $!;
|
||||
@ -1328,6 +1328,7 @@ BEGIN {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my (%LOCKFILES, %INDEX_FILES);
|
||||
END {
|
||||
unlink keys %LOCKFILES if %LOCKFILES;
|
||||
@ -3230,13 +3231,11 @@ sub change_file_prop {
|
||||
|
||||
sub apply_textdelta {
|
||||
my ($self, $fb, $exp) = @_;
|
||||
my $fh = IO::File->new_tmpfile;
|
||||
$fh->autoflush(1);
|
||||
my $fh = Git::temp_acquire('svn_delta');
|
||||
# $fh gets auto-closed() by SVN::TxDelta::apply(),
|
||||
# (but $base does not,) so dup() it for reading in close_file
|
||||
open my $dup, '<&', $fh or croak $!;
|
||||
my $base = IO::File->new_tmpfile;
|
||||
$base->autoflush(1);
|
||||
my $base = Git::temp_acquire('git_blob');
|
||||
if ($fb->{blob}) {
|
||||
print $base 'link ' if ($fb->{mode_a} == 120000);
|
||||
my $size = $::_repository->cat_blob($fb->{blob}, $base);
|
||||
@ -3251,9 +3250,9 @@ sub apply_textdelta {
|
||||
}
|
||||
}
|
||||
seek $base, 0, 0 or croak $!;
|
||||
$fb->{fh} = $dup;
|
||||
$fb->{fh} = $fh;
|
||||
$fb->{base} = $base;
|
||||
[ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
|
||||
[ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];
|
||||
}
|
||||
|
||||
sub close_file {
|
||||
@ -3269,35 +3268,36 @@ sub close_file {
|
||||
"expected: $exp\n got: $got\n";
|
||||
}
|
||||
}
|
||||
sysseek($fh, 0, 0) or croak $!;
|
||||
if ($fb->{mode_b} == 120000) {
|
||||
eval {
|
||||
sysread($fh, my $buf, 5) == 5 or croak $!;
|
||||
$buf eq 'link ' or die "$path has mode 120000",
|
||||
" but is not a link";
|
||||
};
|
||||
if ($@) {
|
||||
warn "$@\n";
|
||||
sysseek($fh, 0, 0) or croak $!;
|
||||
sysread($fh, my $buf, 5) == 5 or croak $!;
|
||||
|
||||
unless ($buf eq 'link ') {
|
||||
warn "$path has mode 120000",
|
||||
" but is not a link\n";
|
||||
} else {
|
||||
my $tmp_fh = Git::temp_acquire('svn_hash');
|
||||
my $res;
|
||||
while ($res = sysread($fh, my $str, 1024)) {
|
||||
my $out = syswrite($tmp_fh, $str, $res);
|
||||
defined($out) && $out == $res
|
||||
or croak("write ",
|
||||
$tmp_fh->filename,
|
||||
": $!\n");
|
||||
}
|
||||
defined $res or croak $!;
|
||||
|
||||
($fh, $tmp_fh) = ($tmp_fh, $fh);
|
||||
Git::temp_release($tmp_fh, 1);
|
||||
}
|
||||
}
|
||||
|
||||
my ($tmp_fh, $tmp_filename) = File::Temp::tempfile(UNLINK => 1);
|
||||
my $result;
|
||||
while ($result = sysread($fh, my $string, 1024)) {
|
||||
my $wrote = syswrite($tmp_fh, $string, $result);
|
||||
defined($wrote) && $wrote == $result
|
||||
or croak("write $tmp_filename: $!\n");
|
||||
}
|
||||
defined $result or croak $!;
|
||||
close $tmp_fh or croak $!;
|
||||
|
||||
close $fh or croak $!;
|
||||
|
||||
$hash = $::_repository->hash_and_insert_object($tmp_filename);
|
||||
unlink($tmp_filename);
|
||||
$hash = $::_repository->hash_and_insert_object(
|
||||
$fh->filename);
|
||||
$hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
|
||||
close $fb->{base} or croak $!;
|
||||
|
||||
Git::temp_release($fb->{base}, 1);
|
||||
Git::temp_release($fh, 1);
|
||||
} else {
|
||||
$hash = $fb->{blob} or die "no blob information\n";
|
||||
}
|
||||
@ -3667,7 +3667,7 @@ sub chg_file {
|
||||
} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
|
||||
$self->change_file_prop($fbat,'svn:executable',undef);
|
||||
}
|
||||
my $fh = IO::File->new_tmpfile or croak $!;
|
||||
my $fh = Git::temp_acquire('git_blob');
|
||||
if ($m->{mode_b} =~ /^120/) {
|
||||
print $fh 'link ' or croak $!;
|
||||
$self->change_file_prop($fbat,'svn:special','*');
|
||||
@ -3686,9 +3686,8 @@ sub chg_file {
|
||||
my $atd = $self->apply_textdelta($fbat, undef, $pool);
|
||||
my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
|
||||
die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
|
||||
Git::temp_release($fh, 1);
|
||||
$pool->clear;
|
||||
|
||||
close $fh or croak $!;
|
||||
}
|
||||
|
||||
sub D {
|
||||
|
125
perl/Git.pm
125
perl/Git.pm
@ -57,7 +57,8 @@ require Exporter;
|
||||
command_output_pipe command_input_pipe command_close_pipe
|
||||
command_bidi_pipe command_close_bidi_pipe
|
||||
version exec_path hash_object git_cmd_try
|
||||
remote_refs);
|
||||
remote_refs
|
||||
temp_acquire temp_release temp_reset);
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
@ -99,7 +100,9 @@ use Carp qw(carp croak); # but croak is bad - throw instead
|
||||
use Error qw(:try);
|
||||
use Cwd qw(abs_path);
|
||||
use IPC::Open2 qw(open2);
|
||||
|
||||
use File::Temp ();
|
||||
require File::Spec;
|
||||
use Fcntl qw(SEEK_SET SEEK_CUR);
|
||||
}
|
||||
|
||||
|
||||
@ -933,6 +936,124 @@ sub _close_cat_blob {
|
||||
delete @$self{@vars};
|
||||
}
|
||||
|
||||
|
||||
{ # %TEMP_* Lexical Context
|
||||
|
||||
my (%TEMP_LOCKS, %TEMP_FILES);
|
||||
|
||||
=item temp_acquire ( NAME )
|
||||
|
||||
Attempts to retreive 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.
|
||||
|
||||
Internally locks the file mapped to C<NAME>. This lock must be released with
|
||||
C<temp_release()> when the temp file is no longer needed. Subsequent attempts
|
||||
to retrieve temporary files mapped to the same C<NAME> 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<File::Handle> 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<File::Temp> 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 ($self, $name) = _maybe_self(@_);
|
||||
|
||||
my $temp_fd = _temp_cache($name);
|
||||
|
||||
$TEMP_LOCKS{$temp_fd} = 1;
|
||||
$temp_fd;
|
||||
}
|
||||
|
||||
=item temp_release ( NAME )
|
||||
|
||||
=item temp_release ( FILEHANDLE )
|
||||
|
||||
Releases a lock acquired through C<temp_acquire()>. Can be called either with
|
||||
the C<NAME> mapping used when acquiring the temp file or with the C<FILEHANDLE>
|
||||
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 (ref($temp_fd) ne 'File::Temp') {
|
||||
$temp_fd = $TEMP_FILES{$temp_fd};
|
||||
}
|
||||
unless ($TEMP_LOCKS{$temp_fd}) {
|
||||
carp "Attempt to release temp file '",
|
||||
$temp_fd, "' that has not been locked";
|
||||
}
|
||||
temp_reset($temp_fd) if $trunc and $temp_fd->opened;
|
||||
|
||||
$TEMP_LOCKS{$temp_fd} = 0;
|
||||
undef;
|
||||
}
|
||||
|
||||
sub _temp_cache {
|
||||
my ($name) = @_;
|
||||
|
||||
my $temp_fd = \$TEMP_FILES{$name};
|
||||
if (defined $$temp_fd and $$temp_fd->opened) {
|
||||
if ($TEMP_LOCKS{$$temp_fd}) {
|
||||
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.";
|
||||
}
|
||||
$$temp_fd = File::Temp->new(
|
||||
TEMPLATE => 'Git_XXXXXX',
|
||||
DIR => File::Spec->tmpdir
|
||||
) or throw Error::Simple("couldn't open new temp file");
|
||||
$$temp_fd->autoflush;
|
||||
binmode $$temp_fd;
|
||||
}
|
||||
$$temp_fd;
|
||||
}
|
||||
|
||||
=item temp_reset ( FILEHANDLE )
|
||||
|
||||
Truncates and resets the position of the C<FILEHANDLE>.
|
||||
|
||||
=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, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)
|
||||
or throw Error::Simple("couldn't seek to beginning of file");
|
||||
sysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0
|
||||
or throw Error::Simple("expected file position to be reset");
|
||||
}
|
||||
|
||||
sub END {
|
||||
unlink values %TEMP_FILES if %TEMP_FILES;
|
||||
}
|
||||
|
||||
} # %TEMP_* Lexical Context
|
||||
|
||||
=back
|
||||
|
||||
=head1 ERROR HANDLING
|
||||
|
Loading…
Reference in New Issue
Block a user