Skip to content

Commit 68f532f

Browse files
jrnEric Wong
authored andcommitted
git-svn: use YAML format for mergeinfo cache when possible
Since v1.7.0-rc2~11 (git-svn: persistent memoization, 2010-01-30), git-svn has maintained some private per-repository caches in .git/svn/.caches to avoid refetching and recalculating some mergeinfo-related information with every "git svn fetch". These caches use the 'nstore' format from the perl core module Storable, which can be read and written quickly and was designed for transfer over the wire (the 'n' stands for 'network'). This format is endianness-independent and independent of floating-point representation. Unfortunately the format is *not* independent of the perl version --- new perl versions will write files that very old perl cannot read. Worse, the format is not independent of the size of a perl integer. So if you toggle perl's use64bitint compile-time option, then using 'git svn fetch' on your old repositories produces errors like this: Byte order is not compatible at ../../lib/Storable.pm (autosplit into ../../lib/auto/Storable/_retrieve.al) line 380, at /usr/share/perl/5.12/Memoize/Storable.pm line 21 That is, upgrading perl to a version that uses use64bitint for the first time makes git-svn suddenly refuse to fetch in existing repositories. Removing .git/svn/.caches lets git-svn recover. It's time to switch to a platform independent serializer backend with better compatibility guarantees. This patch uses YAML::Any. Other choices were considered: - thawing data from Data::Dumper involves "eval". Doing that without creating a security risk is fussy. - the JSON API works on scalars in memory and doesn't provide a standard way to serialize straight to disk. YAML::Any is reasonably fast and has a pleasant API. In most backends, LoadFile() reads the entire file into a scalar anyway and converts it as a second step, but having an interface that allows the deserialization to happen on the fly without a temporary is still a comfort. YAML::Any is not a core perl module, so we take care to use it when and only when it is available. Installations without that module should fall back to using Storable with all its quirks, keeping their cache files in .git/svn/.caches/*.db Installations with YAML peacefully coexist by keeping a separate set of cache files in .git/svn/.caches/*.yaml. In most cases, switching between is a one-time thing, so it doesn't seem worth the complication to migrate existing caches. The upshot: after this patch, as long as YAML::Any is installed you can move your git repository between machines with different perl installations and "git svn fetch" will work fine. If you do not have YAML::Any, the behavior is unchanged (and in particular does not get any worse). Reported-by: Sandro Weiser <[email protected]> Reported-by: Bdale Garbee <[email protected]> Signed-off-by: Jonathan Nieder <[email protected]> Signed-off-by: Eric Wong <[email protected]>
1 parent 9f7ad14 commit 68f532f

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

git-svn.perl

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,6 +2055,10 @@ package Git::SVN;
20552055
use Memoize; # core since 5.8.0, Jul 2002
20562056
use Memoize::Storable;
20572057
use POSIX qw(:signal_h);
2058+
my $can_use_yaml;
2059+
BEGIN {
2060+
$can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1};
2061+
}
20582062

20592063
my ($_gc_nr, $_gc_period);
20602064

@@ -3577,6 +3581,17 @@ sub has_no_changes {
35773581
command_oneline("rev-parse", "$commit~1^{tree}"));
35783582
}
35793583

3584+
sub tie_for_persistent_memoization {
3585+
my $hash = shift;
3586+
my $path = shift;
3587+
3588+
if ($can_use_yaml) {
3589+
tie %$hash => 'Git::SVN::Memoize::YAML', "$path.yaml";
3590+
} else {
3591+
tie %$hash => 'Memoize::Storable', "$path.db", 'nstore';
3592+
}
3593+
}
3594+
35803595
# The GIT_DIR environment variable is not always set until after the command
35813596
# line arguments are processed, so we can't memoize in a BEGIN block.
35823597
{
@@ -3589,22 +3604,26 @@ sub has_no_changes {
35893604
my $cache_path = "$ENV{GIT_DIR}/svn/.caches/";
35903605
mkpath([$cache_path]) unless -d $cache_path;
35913606

3592-
tie my %lookup_svn_merge_cache => 'Memoize::Storable',
3593-
"$cache_path/lookup_svn_merge.db", 'nstore';
3607+
my %lookup_svn_merge_cache;
3608+
my %check_cherry_pick_cache;
3609+
my %has_no_changes_cache;
3610+
3611+
tie_for_persistent_memoization(\%lookup_svn_merge_cache,
3612+
"$cache_path/lookup_svn_merge");
35943613
memoize 'lookup_svn_merge',
35953614
SCALAR_CACHE => 'FAULT',
35963615
LIST_CACHE => ['HASH' => \%lookup_svn_merge_cache],
35973616
;
35983617

3599-
tie my %check_cherry_pick_cache => 'Memoize::Storable',
3600-
"$cache_path/check_cherry_pick.db", 'nstore';
3618+
tie_for_persistent_memoization(\%check_cherry_pick_cache,
3619+
"$cache_path/check_cherry_pick");
36013620
memoize 'check_cherry_pick',
36023621
SCALAR_CACHE => 'FAULT',
36033622
LIST_CACHE => ['HASH' => \%check_cherry_pick_cache],
36043623
;
36053624

3606-
tie my %has_no_changes_cache => 'Memoize::Storable',
3607-
"$cache_path/has_no_changes.db", 'nstore';
3625+
tie_for_persistent_memoization(\%has_no_changes_cache,
3626+
"$cache_path/has_no_changes");
36083627
memoize 'has_no_changes',
36093628
SCALAR_CACHE => ['HASH' => \%has_no_changes_cache],
36103629
LIST_CACHE => 'FAULT',

perl/Git/SVN/Memoize/YAML.pm

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package Git::SVN::Memoize::YAML;
2+
use warnings;
3+
use strict;
4+
use YAML::Any ();
5+
6+
# based on Memoize::Storable.
7+
8+
sub TIEHASH {
9+
my $package = shift;
10+
my $filename = shift;
11+
my $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};
12+
my $self = {FILENAME => $filename, H => $truehash};
13+
bless $self => $package;
14+
}
15+
16+
sub STORE {
17+
my $self = shift;
18+
$self->{H}{$_[0]} = $_[1];
19+
}
20+
21+
sub FETCH {
22+
my $self = shift;
23+
$self->{H}{$_[0]};
24+
}
25+
26+
sub EXISTS {
27+
my $self = shift;
28+
exists $self->{H}{$_[0]};
29+
}
30+
31+
sub DESTROY {
32+
my $self = shift;
33+
YAML::Any::DumpFile($self->{FILENAME}, $self->{H});
34+
}
35+
36+
sub SCALAR {
37+
my $self = shift;
38+
scalar(%{$self->{H}});
39+
}
40+
41+
sub FIRSTKEY {
42+
'Fake hash from Git::SVN::Memoize::YAML';
43+
}
44+
45+
sub NEXTKEY {
46+
undef;
47+
}
48+
49+
1;
50+
__END__
51+
52+
=head1 NAME
53+
54+
Git::SVN::Memoize::YAML - store Memoized data in YAML format
55+
56+
=head1 SYNOPSIS
57+
58+
use Memoize;
59+
use Git::SVN::Memoize::YAML;
60+
61+
tie my %cache => 'Git::SVN::Memoize::YAML', $filename;
62+
memoize('slow_function', SCALAR_CACHE => [HASH => \%cache]);
63+
slow_function(arguments);
64+
65+
=head1 DESCRIPTION
66+
67+
This module provides a class that can be used to tie a hash to a
68+
YAML file. The file is read when the hash is initialized and
69+
rewritten when the hash is destroyed.
70+
71+
The intent is to allow L<Memoize> to back its cache with a file in
72+
YAML format, just like L<Memoize::Storable> allows L<Memoize> to
73+
back its cache with a file in Storable format. Unlike the Storable
74+
format, the YAML format is platform-independent and fairly stable.
75+
76+
Carps on error.
77+
78+
=head1 DIAGNOSTICS
79+
80+
See L<YAML::Any>.
81+
82+
=head1 DEPENDENCIES
83+
84+
L<YAML::Any> from CPAN.
85+
86+
=head1 INCOMPATIBILITIES
87+
88+
None reported.
89+
90+
=head1 BUGS
91+
92+
The entire cache is read into a Perl hash when loading the file,
93+
so this is not very scalable.

perl/Makefile.PL

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ MAKE_FRAG
2727
my %pm = (
2828
'Git.pm' => '$(INST_LIBDIR)/Git.pm',
2929
'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
30+
'Git/SVN/Memoize/YAML.pm' => '$(INST_LIBDIR)/Git/SVN/Memoize/YAML.pm',
3031
'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',
3132
'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',
3233
'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',

0 commit comments

Comments
 (0)