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:
Junio C Hamano 2008-08-12 21:41:29 -07:00
commit c67a9e2682
2 changed files with 156 additions and 36 deletions

View File

@ -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 $!;
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 {

View File

@ -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