{"thread":{"id":"28184","subject":"[PATCH] git-svn: Destroy the cache when we fail to read it","startedAt":"2011-08-22T02:17:02Z","lastAt":"2012-06-13T01:34:46Z","messageCount":26,"participants":["Jason Gross","Jonathan Nieder","Eric Wong","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"173988","messageId":"1313979422-21286-1-git-send-email-jgross@mit.edu","threadId":"28184","inReplyTo":null,"subject":"[PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Jason Gross","fromEmail":"jgross@mit.edu","sentAt":"2011-08-22T02:17:02Z","receivedAt":"2011-08-22T02:17:02Z","isPatch":true,"sender":{"key":"jgross@mit.edu","avatar":"https://gravatar.com/avatar/f8521a4779102ad1ef15083aadfd102bbb4751d35174b22e9c0a0f434d9e5403?d=mp&s=160"},"body":"Previously, we would fail fatally when trying to fetch changes with\nmergeinfo on a 32 bit machine, when the repository previously had\nfetched changes with mergeinfo on a 64 bit machine.\n\nThis fixes bug 618875 (which is also 587650, 635097).  Much of the code\nwas written by Jonathan Nieder <jrnieder@gmail.com> with suggestions\nfrom Steffen Mueller <smueller@cpan.org> (see\nhttp://lists.debian.org/debian-perl/2011/05/msg00023.html and\nhttp://lists.debian.org/debian-perl/2011/05/msg00026.html).\n\nSigned-off-by: Jason Gross <jgross@mit.edu>\nCc: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl |   59 +++++++++++++++++++++++++++++++++++----------------------\n 1 files changed, 36 insertions(+), 23 deletions(-)\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex 89f83fd..78ccdc8 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -1680,7 +1680,7 @@ use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent\n             $_use_svnsync_props $no_reuse_existing $_minimize_url\n \t    $_use_log_author $_add_author_from $_localtime/;\n use Carp qw/croak/;\n-use File::Path qw/mkpath/;\n+use File::Path qw/mkpath rmtree/;\n use File::Copy qw/copy/;\n use IPC::Open3;\n use Memoize;  # core since 5.8.0, Jul 2002\n@@ -3198,28 +3198,41 @@ sub has_no_changes {\n \t\t$memoized = 1;\n \n \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n-\t\tmkpath([$cache_path]) unless -d $cache_path;\n-\n-\t\ttie my %lookup_svn_merge_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/lookup_svn_merge.db\", 'nstore';\n-\t\tmemoize 'lookup_svn_merge',\n-\t\t\tSCALAR_CACHE => 'FAULT',\n-\t\t\tLIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n-\t\t;\n-\n-\t\ttie my %check_cherry_pick_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/check_cherry_pick.db\", 'nstore';\n-\t\tmemoize 'check_cherry_pick',\n-\t\t\tSCALAR_CACHE => 'FAULT',\n-\t\t\tLIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n-\t\t;\n-\n-\t\ttie my %has_no_changes_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/has_no_changes.db\", 'nstore';\n-\t\tmemoize 'has_no_changes',\n-\t\t\tSCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n-\t\t\tLIST_CACHE => 'FAULT',\n-\t\t;\n+\t\tmy $do_memoization = sub {\n+\t\t\tmkpath([$cache_path]) unless -d $cache_path;\n+\n+\t\t\ttie my %lookup_svn_merge_cache => 'Memoize::Storable',\n+\t\t\t    \"$cache_path/lookup_svn_merge.db\", 'nstore';\n+\t\t\tmemoize 'lookup_svn_merge',\n+\t\t\t\tSCALAR_CACHE => 'FAULT',\n+\t\t\t\tLIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n+\t\t\t;\n+\n+\t\t\ttie my %check_cherry_pick_cache => 'Memoize::Storable',\n+\t\t\t    \"$cache_path/check_cherry_pick.db\", 'nstore';\n+\t\t\tmemoize 'check_cherry_pick',\n+\t\t\t\tSCALAR_CACHE => 'FAULT',\n+\t\t\t\tLIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n+\t\t\t;\n+\n+\t\t\ttie my %has_no_changes_cache => 'Memoize::Storable',\n+\t\t\t    \"$cache_path/has_no_changes.db\", 'nstore';\n+\t\t\tmemoize 'has_no_changes',\n+\t\t\t\tSCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n+\t\t\t\tLIST_CACHE => 'FAULT',\n+\t\t\t;\n+\t\t};\n+\n+\t\tif (not eval {\n+\t\t\t$do_memoization->();\n+\t\t\t1;\n+\t\t}) {\n+\t\t\tmy $err = $@ || \"Zombie error\"; # \"Zombie error\" to catch clobbered $@ in buggy destructors\n+\t\t\tdie $err unless -d $cache_path;\n+\t\t\tprint STDERR \"Discarding cache and trying again ($@)\\n\";\n+\t\t\trmtree([$cache_path]);\n+\t\t\t$do_memoization->();\n+\t\t}\n \t}\n \n \tsub unmemoize_svn_mergeinfo_functions {\n-- \n1.7.2.3\n"},{"id":"173993","messageId":"CAKObCao5KwTptvJG_2F8SkM2hA=GrhC2HC0hFrDSDw3_PVb-PA@mail.gmail.com","threadId":"28184","inReplyTo":"1313979422-21286-1-git-send-email-jgross@mit.edu","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Jason Gross","fromEmail":"jgross@mit.edu","sentAt":"2011-08-22T04:04:09Z","receivedAt":"2011-08-22T04:04:09Z","isPatch":true,"sender":{"key":"jgross@mit.edu","avatar":"https://gravatar.com/avatar/f8521a4779102ad1ef15083aadfd102bbb4751d35174b22e9c0a0f434d9e5403?d=mp&s=160"},"body":"Oops, the bug numbers I gave were debian bug ids, not git bug ids.\nThe \"This fixes bug ...\" sentence should either be removed from the\ncommit message before this commit is merged, or the numbers should be\nreplaced with the appropriate git bug numbers.\n\n-Jason\n\nOn Sun, Aug 21, 2011 at 10:17 PM, Jason Gross <jgross@mit.edu> wrote:\n>\n> Previously, we would fail fatally when trying to fetch changes with\n> mergeinfo on a 32 bit machine, when the repository previously had\n> fetched changes with mergeinfo on a 64 bit machine.\n>\n> This fixes bug 618875 (which is also 587650, 635097).  Much of the code\n> was written by Jonathan Nieder <jrnieder@gmail.com> with suggestions\n> from Steffen Mueller <smueller@cpan.org> (see\n> http://lists.debian.org/debian-perl/2011/05/msg00023.html and\n> http://lists.debian.org/debian-perl/2011/05/msg00026.html).\n>\n> Signed-off-by: Jason Gross <jgross@mit.edu>\n> Cc: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n>  git-svn.perl |   59 +++++++++++++++++++++++++++++++++++----------------------\n>  1 files changed, 36 insertions(+), 23 deletions(-)\n>\n> diff --git a/git-svn.perl b/git-svn.perl\n> index 89f83fd..78ccdc8 100755\n> --- a/git-svn.perl\n> +++ b/git-svn.perl\n> @@ -1680,7 +1680,7 @@ use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent\n>             $_use_svnsync_props $no_reuse_existing $_minimize_url\n>            $_use_log_author $_add_author_from $_localtime/;\n>  use Carp qw/croak/;\n> -use File::Path qw/mkpath/;\n> +use File::Path qw/mkpath rmtree/;\n>  use File::Copy qw/copy/;\n>  use IPC::Open3;\n>  use Memoize;  # core since 5.8.0, Jul 2002\n> @@ -3198,28 +3198,41 @@ sub has_no_changes {\n>                $memoized = 1;\n>\n>                my $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> -               mkpath([$cache_path]) unless -d $cache_path;\n> -\n> -               tie my %lookup_svn_merge_cache => 'Memoize::Storable',\n> -                   \"$cache_path/lookup_svn_merge.db\", 'nstore';\n> -               memoize 'lookup_svn_merge',\n> -                       SCALAR_CACHE => 'FAULT',\n> -                       LIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n> -               ;\n> -\n> -               tie my %check_cherry_pick_cache => 'Memoize::Storable',\n> -                   \"$cache_path/check_cherry_pick.db\", 'nstore';\n> -               memoize 'check_cherry_pick',\n> -                       SCALAR_CACHE => 'FAULT',\n> -                       LIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n> -               ;\n> -\n> -               tie my %has_no_changes_cache => 'Memoize::Storable',\n> -                   \"$cache_path/has_no_changes.db\", 'nstore';\n> -               memoize 'has_no_changes',\n> -                       SCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n> -                       LIST_CACHE => 'FAULT',\n> -               ;\n> +               my $do_memoization = sub {\n> +                       mkpath([$cache_path]) unless -d $cache_path;\n> +\n> +                       tie my %lookup_svn_merge_cache => 'Memoize::Storable',\n> +                           \"$cache_path/lookup_svn_merge.db\", 'nstore';\n> +                       memoize 'lookup_svn_merge',\n> +                               SCALAR_CACHE => 'FAULT',\n> +                               LIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n> +                       ;\n> +\n> +                       tie my %check_cherry_pick_cache => 'Memoize::Storable',\n> +                           \"$cache_path/check_cherry_pick.db\", 'nstore';\n> +                       memoize 'check_cherry_pick',\n> +                               SCALAR_CACHE => 'FAULT',\n> +                               LIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n> +                       ;\n> +\n> +                       tie my %has_no_changes_cache => 'Memoize::Storable',\n> +                           \"$cache_path/has_no_changes.db\", 'nstore';\n> +                       memoize 'has_no_changes',\n> +                               SCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n> +                               LIST_CACHE => 'FAULT',\n> +                       ;\n> +               };\n> +\n> +               if (not eval {\n> +                       $do_memoization->();\n> +                       1;\n> +               }) {\n> +                       my $err = $@ || \"Zombie error\"; # \"Zombie error\" to catch clobbered $@ in buggy destructors\n> +                       die $err unless -d $cache_path;\n> +                       print STDERR \"Discarding cache and trying again ($@)\\n\";\n> +                       rmtree([$cache_path]);\n> +                       $do_memoization->();\n> +               }\n>        }\n>\n>        sub unmemoize_svn_mergeinfo_functions {\n> --\n> 1.7.2.3\n>\n"},{"id":"173994","messageId":"1313986210-6536-1-git-send-email-jgross@mit.edu","threadId":"28184","inReplyTo":"1313979422-21286-1-git-send-email-jgross@mit.edu","subject":"[PATCH] Add tests for handling corrupted caches","fromName":"Jason Gross","fromEmail":"jgross@mit.edu","sentAt":"2011-08-22T04:10:10Z","receivedAt":"2011-08-22T04:10:10Z","isPatch":true,"sender":{"key":"jgross@mit.edu","avatar":"https://gravatar.com/avatar/f8521a4779102ad1ef15083aadfd102bbb4751d35174b22e9c0a0f434d9e5403?d=mp&s=160"},"body":"There might be a more straightforward way to generate an svn repository\nwith mergeinfo, but I'm not very familiar with svn.\n\nSigned-off-by: Jason Gross <jgross@mit.edu>\n---\n t/t9160-git-svn-caches.sh |   59 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 59 insertions(+), 0 deletions(-)\n create mode 100755 t/t9160-git-svn-caches.sh\n\ndiff --git a/t/t9160-git-svn-caches.sh b/t/t9160-git-svn-caches.sh\nnew file mode 100755\nindex 0000000..bba437a\n--- /dev/null\n+++ b/t/t9160-git-svn-caches.sh\n@@ -0,0 +1,59 @@\n+#!/bin/sh\n+\n+test_description='git svn handling of .git/svn/.caches'\n+\n+. ./lib-git-svn.sh\n+\n+say 'define NO_SVN_TESTS to skip git svn tests'\n+\n+test_expect_success 'initialize source svn repo' '\n+\tsvn_cmd mkdir -m x \"$svnrepo\"/trunk \"$svnrepo\"/branches &&\n+\tsvn_cmd co \"$svnrepo\" \"$SVN_TREE\"\n+'\n+\n+test_expect_success 'make a change that gives mergeinfo' '\n+\t(\n+\t\tcd \"$SVN_TREE\" &&\n+\t\ttouch trunk/foo &&\n+\t\tsvn_cmd add trunk/foo &&\n+\t\tsvn_cmd commit -m \"foo\" &&\n+\t\tsvn_cmd up &&\n+\t\tsvn_cmd cp trunk branches/1\n+\t\tsvn_cmd commit -m \"make branch\" &&\n+\t\tsvn_cmd up &&\n+\t\ttouch branches/1/bar &&\n+\t\tsvn_cmd add branches/1/bar &&\n+\t\tsvn_cmd commit -m bar &&\n+\t\tsvn_cmd up &&\n+\t\tcd trunk &&\n+\t\tsvn_cmd merge ../branches/1 &&\n+\t\tsvn_cmd commit -m merge &&\n+\t\tsvn_cmd up\n+\t)\n+'\n+\n+test_expect_success 'clone svn repo and corrupt .caches' '\n+\tgit svn init \"$svnrepo\" -T trunk -b branches &&\n+\tgit svn fetch &&\n+\techo corrupted > .git/svn/.caches/lookup_svn_merge.db\n+'\n+\n+test_expect_success 'make another change that generates mergeinfo' '\n+\t(\n+\t\tcd \"$SVN_TREE\" &&\n+\t\ttouch branches/1/baz &&\n+\t\tsvn_cmd commit -m baz &&\n+\t\tsvn_cmd up &&\n+\t\tcd trunk &&\n+\t\tsvn_cmd merge ../branches/1 &&\n+\t\tsvn_cmd commit -m merge2 &&\n+\t\tsvn_cmd up\n+\t) &&\n+\trm -rf \"$SVN_TREE\"\n+'\n+\n+test_expect_success 'fetch change with mergeinfo with a corrupted cache' '\n+\tgit svn fetch\n+'\n+\n+test_done\n-- \n1.7.6.558.g4b2f\n"},{"id":"174067","messageId":"20110823022717.GA4623@elie.gateway.2wire.net","threadId":"28184","inReplyTo":"1313979422-21286-1-git-send-email-jgross@mit.edu","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-08-23T02:27:17Z","receivedAt":"2011-08-23T02:27:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"(+cc: Eric Wong)\nHi Jason,\n\nJason Gross wrote:\n\n[This patch teaches \"git svn\" to invalidate caches when they\n fail to load, for example because the endianness or size of\n some type changed, which is common in the perl 5.6 -> 5.8\n upgrade.]\n\n> http://lists.debian.org/debian-perl/2011/05/msg00023.html and\n> http://lists.debian.org/debian-perl/2011/05/msg00026.html).\n[...]\n> --- a/git-svn.perl\n> +++ b/git-svn.perl\n> @@ -1680,7 +1680,7 @@ use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent\n>              $_use_svnsync_props $no_reuse_existing $_minimize_url\n>  \t    $_use_log_author $_add_author_from $_localtime/;\n>  use Carp qw/croak/;\n> -use File::Path qw/mkpath/;\n> +use File::Path qw/mkpath rmtree/;\n>  use File::Copy qw/copy/;\n>  use IPC::Open3;\n>  use Memoize;  # core since 5.8.0, Jul 2002\n> @@ -3198,28 +3198,41 @@ sub has_no_changes {\n>  \t\t$memoized = 1;\n>  \n>  \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> -\t\tmkpath([$cache_path]) unless -d $cache_path;\n> -\n> -\t\ttie my %lookup_svn_merge_cache => 'Memoize::Storable',\n[...]\n> -\t\t;\n> +\t\tmy $do_memoization = sub {\n> +\t\t\tmkpath([$cache_path]) unless -d $cache_path;\n[...]\n> +\t\t\t;\n> +\t\t};\n> +\n> +\t\tif (not eval {\n> +\t\t\t$do_memoization->();\n> +\t\t\t1;\n> +\t\t}) {\n> +\t\t\tmy $err = $@ || \"Zombie error\"; # \"Zombie error\" to catch clobbered $@ in buggy destructors\n> +\t\t\tdie $err unless -d $cache_path;\n> +\t\t\tprint STDERR \"Discarding cache and trying again ($@)\\n\";\n> +\t\t\trmtree([$cache_path]);\n> +\t\t\t$do_memoization->();\n> +\t\t}\n\nThank you thank you thank you.\n\nOkay, time for nitpicks:\n\n 1) Would it be possible to lift this do_memoization() to a toplevel sub?\n    I suspect that could make the code a little easier to read.\n\n 2) Is it important to discard the cache for _all_ errors, instead\n    of just corruption and \"is not compatible\" errors?  Rebuilding the\n    cache is not cheap, and I am afraid of effects like repeatedly\n    discarding the cache only to rebuild it again due to a typo in\n    git-svn.perl or an out-of-memory condition.\n\n 3) The line with \"Zombie error\" is very long --- I guess putting the\n    comment on the line before would help.\n\n 4) The series would be clearer imho as a single patch that includes\n    both the fix and tests.\n\nEric, what do you think?\n\nThanks again, :)\nJonathan\n"},{"id":"174068","messageId":"20110823023637.GB4623@elie.gateway.2wire.net","threadId":"28184","inReplyTo":"20110823022717.GA4623@elie.gateway.2wire.net","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-08-23T02:36:37Z","receivedAt":"2011-08-23T02:36:37Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> [This patch teaches \"git svn\" to invalidate caches when they\n>  fail to load, for example because the endianness or size of\n>  some type changed, which is common in the perl 5.6 -> 5.8\n>  upgrade.]\n\nErm, actually it showed up in the perl 5.10 -> 5.12 upgrade in Debian,\ndue to use of -Duse64bitint when compiling the latter and not the\nformer[1].\n\nTo summarize:\n\n - \"$Storable::interwork_56_64bit = 1;\" can be used to work around\n   that particular brand of breakage;\n\n - The interwork_* option does not take care of similar cases in which\n   a git-svn repository is generated on one machine and read by\n   another[2], though.\n\nHence my happiness at the arrival of Jason's patch.\n\n[1] http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=32;bug=618875\n[2] http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=20;bug=587650\n"},{"id":"174075","messageId":"CAKObCaoj+jwkCW3DOA=N-EtOmn-Ckwevi3fHPqans+_cd8ZC8A@mail.gmail.com","threadId":"28184","inReplyTo":"20110823022717.GA4623@elie.gateway.2wire.net","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Jason Gross","fromEmail":"jgross@mit.edu","sentAt":"2011-08-23T05:51:50Z","receivedAt":"2011-08-23T05:51:50Z","isPatch":true,"sender":{"key":"jgross@mit.edu","avatar":"https://gravatar.com/avatar/f8521a4779102ad1ef15083aadfd102bbb4751d35174b22e9c0a0f434d9e5403?d=mp&s=160"},"body":"1) Sure.  I'm not sure whether I should make $cache_path toplevel, or\na parameter of do_memoization.  I'm thinking the latter, just in case\n$ENV{GIT_DIR} changes between calls to\nmemoize_svn_mergeinfo_functions?\n\n2) I'm not sure if there's a clean/robust way to detect cache\ncorruption; if it's completely corrupted, Storable complains about not\nfinding the right magic number, but I could imagine things being\ncorrupted in more subtle ways that give rise to other Storable errors.\nAs for your worry: I don't know the code well enough to have git-svn\nrecover from some other error in memoization; if there's an error,\nthat means that it can't read the cache correctly (either in tieing\nthe hashes, or in memoizing the functions).  The other alternative\nthat I see is to die.  Is this preferable to repeatedly discarding the\ncache?\n\n3) Will fix.\n\n4) Will do.\n\nI'll send a new patch in once I hear back from you about 1 and 2.\n\n-Jason\n\nOn Mon, Aug 22, 2011 at 10:27 PM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> (+cc: Eric Wong)\n> Hi Jason,\n>\n> Jason Gross wrote:\n>\n> [This patch teaches \"git svn\" to invalidate caches when they\n>  fail to load, for example because the endianness or size of\n>  some type changed, which is common in the perl 5.6 -> 5.8\n>  upgrade.]\n>\n> > http://lists.debian.org/debian-perl/2011/05/msg00023.html and\n> > http://lists.debian.org/debian-perl/2011/05/msg00026.html).\n> [...]\n> > --- a/git-svn.perl\n> > +++ b/git-svn.perl\n> > @@ -1680,7 +1680,7 @@ use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent\n> >              $_use_svnsync_props $no_reuse_existing $_minimize_url\n> >           $_use_log_author $_add_author_from $_localtime/;\n> >  use Carp qw/croak/;\n> > -use File::Path qw/mkpath/;\n> > +use File::Path qw/mkpath rmtree/;\n> >  use File::Copy qw/copy/;\n> >  use IPC::Open3;\n> >  use Memoize;  # core since 5.8.0, Jul 2002\n> > @@ -3198,28 +3198,41 @@ sub has_no_changes {\n> >               $memoized = 1;\n> >\n> >               my $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> > -             mkpath([$cache_path]) unless -d $cache_path;\n> > -\n> > -             tie my %lookup_svn_merge_cache => 'Memoize::Storable',\n> [...]\n> > -             ;\n> > +             my $do_memoization = sub {\n> > +                     mkpath([$cache_path]) unless -d $cache_path;\n> [...]\n> > +                     ;\n> > +             };\n> > +\n> > +             if (not eval {\n> > +                     $do_memoization->();\n> > +                     1;\n> > +             }) {\n> > +                     my $err = $@ || \"Zombie error\"; # \"Zombie error\" to catch clobbered $@ in buggy destructors\n> > +                     die $err unless -d $cache_path;\n> > +                     print STDERR \"Discarding cache and trying again ($@)\\n\";\n> > +                     rmtree([$cache_path]);\n> > +                     $do_memoization->();\n> > +             }\n>\n> Thank you thank you thank you.\n>\n> Okay, time for nitpicks:\n>\n>  1) Would it be possible to lift this do_memoization() to a toplevel sub?\n>    I suspect that could make the code a little easier to read.\n>\n>  2) Is it important to discard the cache for _all_ errors, instead\n>    of just corruption and \"is not compatible\" errors?  Rebuilding the\n>    cache is not cheap, and I am afraid of effects like repeatedly\n>    discarding the cache only to rebuild it again due to a typo in\n>    git-svn.perl or an out-of-memory condition.\n>\n>  3) The line with \"Zombie error\" is very long --- I guess putting the\n>    comment on the line before would help.\n>\n>  4) The series would be clearer imho as a single patch that includes\n>    both the fix and tests.\n>\n> Eric, what do you think?\n>\n> Thanks again, :)\n> Jonathan\n"},{"id":"174080","messageId":"20110823081546.GA28091@dcvr.yhbt.net","threadId":"28184","inReplyTo":"1313979422-21286-1-git-send-email-jgross@mit.edu","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2011-08-23T08:15:46Z","receivedAt":"2011-08-23T08:15:46Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Jason Gross <jgross@MIT.EDU> wrote:\n>  \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n\nCan we make the caches sharable by encoding variables like\n$Config{use64bitint} and $Storable::VERSION into $cache_path?\n\nSomething like this (untested):\n\n\tuse Config;\n\n\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n\t$cache_path .= \"$Config{use64bitint}.$Storable::VERSION/\";\n\nWe'll blow everybody's cache away once during the git-svn upgrade, but\nin the future people will be able to share the same working tree between\ndifferent versions of perl/Storable/whatnot without needing extra code\nto detect croaks, different build options, and nuking each other's\ncaches.\n\n-- \nEric Wong\n"},{"id":"174101","messageId":"7vobzgrrbg.fsf@alter.siamese.dyndns.org","threadId":"28184","inReplyTo":"20110823081546.GA28091@dcvr.yhbt.net","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-23T17:05:55Z","receivedAt":"2011-08-23T17:05:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Wong <normalperson@yhbt.net> writes:\n\n> Jason Gross <jgross@MIT.EDU> wrote:\n>>  \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n>\n> Can we make the caches sharable by encoding variables like\n> $Config{use64bitint} and $Storable::VERSION into $cache_path?\n>\n> Something like this (untested):\n>\n> \tuse Config;\n>\n> \tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> \t$cache_path .= \"$Config{use64bitint}.$Storable::VERSION/\";\n>\n> We'll blow everybody's cache away once during the git-svn upgrade, but\n> in the future people will be able to share the same working tree between\n> different versions of perl/Storable/whatnot without needing extra code\n> to detect croaks, different build options, and nuking each other's\n> caches.\n\nMeaning multiple directories and people share with those with systems with\nsimilar characteristics?  It certainly is better than silently getting\nconfused or barfing when reading 32LE data on 64BE box, and presumably it\nwould be easy to implement, but is that the best we can do with memoize?\n\nI am wondering if memoize can be told to use a platform independent\nserializer backend that is reasonably efficient.\n"},{"id":"174116","messageId":"20110823195837.GA10935@dcvr.yhbt.net","threadId":"28184","inReplyTo":"7vobzgrrbg.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] git-svn: Destroy the cache when we fail to read it","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2011-08-23T19:58:37Z","receivedAt":"2011-08-23T19:58:37Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Wong <normalperson@yhbt.net> writes:\n> > Jason Gross <jgross@MIT.EDU> wrote:\n> >>  \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> >\n> > Can we make the caches sharable by encoding variables like\n> > $Config{use64bitint} and $Storable::VERSION into $cache_path?\n> >\n> > Something like this (untested):\n> >\n> > \tuse Config;\n> >\n> > \tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n> > \t$cache_path .= \"$Config{use64bitint}.$Storable::VERSION/\";\n> >\n> > We'll blow everybody's cache away once during the git-svn upgrade, but\n> > in the future people will be able to share the same working tree between\n> > different versions of perl/Storable/whatnot without needing extra code\n> > to detect croaks, different build options, and nuking each other's\n> > caches.\n> \n> Meaning multiple directories and people share with those with systems with\n> similar characteristics?\n\nYes.\n\n> It certainly is better than silently getting confused or barfing when\n> reading 32LE data on 64BE box, and presumably it would be easy to\n> implement, but is that the best we can do with memoize?\n> \n> I am wondering if memoize can be told to use a platform independent\n> serializer backend that is reasonably efficient.\n\nStorable nstore only guarantees endian-neutrality.  JSON/YAML/XML are\nall platform-neutral options but (AFAIK) none are standard parts of all\nPerl versions we support.  There's Data::Dumper + eval which should\nwork, but a Memoize::* interface needs to be implemented/imported.\n\n-- \nEric Wong\n"},{"id":"192296","messageId":"20120527192541.GA29490@burratino","threadId":"28184","inReplyTo":"7vobzgrrbg.fsf@alter.siamese.dyndns.org","subject":"[RFC/PATCH] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-27T19:25:41Z","receivedAt":"2012-05-27T19:25:41Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"In August, 2011, Junio C Hamano wrote:\n> Eric Wong <normalperson@yhbt.net> writes:\n\n>> Something like this (untested):\n>>\n>> \tuse Config;\n>>\n>> \tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n>> \t$cache_path .= \"$Config{use64bitint}.$Storable::VERSION/\";\n[...]\n> I am wondering if memoize can be told to use a platform independent\n> serializer backend that is reasonably efficient.\n\nHere is a rough sketch.  It uses YAML::Any.\n\nOther choices were considered:\n\n - thawing data from Data::Dumper involves \"eval\".  Doing that\n   without creating a security risk is fussy\n\n - the JSON API only works on scalars in memory and doesn't provide\n   a standard way to serialize straight to disk\n\nYAML::Any is reasonably fast and has a pleasant API.  In most backends,\nLoadFile() seems to read the entire file into a scalar anyway and\nconvert it as a second step, but having an interface that allows the\ndeserialization to happen on the fly without a temporary is still a\ncomfort.\n\nYAML::Any is not a core perl module.  This patch uses it when and only\nwhen it is available.  Installations without that module fall back to\nusing Storable with all its quirks, keeping their cache files in\n\n\t.git/svn/.caches/*.db\n\nInstallations with YAML peacefully coexist by keeping a new set of\ncache files in\n\n\t.git/svn/.caches/*.yaml.\n\nIn most cases, switching between is a one-time thing, so it doesn't\nseem worth the effort to copy over the existing caches.\n\nMy personal motivation is the ability to upgrade or downgrade perl or\nchange perl's use64bitint compile-time parameter without having to\ninvalidate caches or (worse, what users unaware of .git/svn/.caches\nwould probably do) reclone repositories.  The Storable format is not\nstable enough for forward-compatibility.\n\nSeems to work.  I haven't run any benchmarks.  \n\nThoughts?  Improvements?\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl |  126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 120 insertions(+), 6 deletions(-)\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex c84842ff..96b6046a 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -2039,6 +2039,101 @@ sub gc_directory {\n \t}\n }\n \n+package Git::SVN::Memoize::YAML;\n+use warnings;\n+use strict;\n+use YAML::Any ();\n+\n+# based on Memoize::Storable.\n+\n+sub TIEHASH {\n+\tmy $package = shift;\n+\tmy $filename = shift;\n+\tmy $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};\n+\tmy $self = {FILENAME => $filename, H => $truehash};\n+\tbless $self => $package;\n+}\n+\n+sub STORE {\n+\tmy $self = shift;\n+\t$self->{H}{$_[0]} = $_[1];\n+}\n+\n+sub FETCH {\n+\tmy $self = shift;\n+\t$self->{H}{$_[0]};\n+}\n+\n+sub EXISTS {\n+\tmy $self = shift;\n+\texists $self->{H}{$_[0]};\n+}\n+\n+sub DESTROY {\n+\tmy $self = shift;\n+\tYAML::Any::DumpFile($self->{FILENAME}, $self->{H});\n+}\n+\n+sub SCALAR {\n+\tmy $self = shift;\n+\tscalar(%{$self->{H}});\n+}\n+\n+sub FIRSTKEY {\n+\t'Fake hash from Memoize::YAML';\n+}\n+\n+sub NEXTKEY {\n+\tundef;\n+}\n+\n+1;\n+\n+=head1 NAME\n+\n+Git::SVN::Memoize::YAML - store Memoized data in YAML format\n+\n+=head1 SYNOPSIS\n+\n+    use Memoize;\n+    use Git::SVN::Memoize::YAML;\n+\n+    tie my %cache => 'Memoize::YAML', $filename;\n+    memoize('slow_function', SCALAR_CACHE => [HASH => \\%cache]);\n+    slow_function(arguments);\n+\n+=head1 DESCRIPTION\n+\n+This module provides a class that can be used to tie a hash to a\n+YAML file.  The file is read when the hash is initialized and\n+rewritten when the hash is destroyed.\n+\n+The intent is to allow L<Memoize> to back its cache with a file in\n+YAML format, just like L<Memoize::Storable> allows L<Memoize> to\n+back its cache with a file in Storable format.  Unlike the Storable\n+format, the YAML format is platform-independent and fairly stable.\n+\n+=head1 DIAGNOSTICS\n+\n+See L<YAML::Any>.\n+\n+=head1 DEPENDENCIES\n+\n+L<YAML::Any> from CPAN.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+The entire cache is read into a Perl hash when loading the file,\n+so this is not very scalable.\n+\n+Error handling is awkward.\n+=cut\n+\n+\n package Git::SVN;\n use strict;\n use warnings;\n@@ -2056,6 +2151,10 @@ use Time::Local;\n use Memoize;  # core since 5.8.0, Jul 2002\n use Memoize::Storable;\n use POSIX qw(:signal_h);\n+my $can_use_yaml;\n+BEGIN {\n+\t$can_use_yaml = eval { require YAML::Any; 1};\n+}\n \n my ($_gc_nr, $_gc_period);\n \n@@ -3578,6 +3677,17 @@ sub has_no_changes {\n \t\tcommand_oneline(\"rev-parse\", \"$commit~1^{tree}\"));\n }\n \n+sub tie_for_persistent_memoization {\n+\tmy $hash = shift;\n+\tmy $path = shift;\n+\n+\tif ($can_use_yaml) {\n+\t\ttie %$hash => 'Git::SVN::Memoize::YAML', \"$path.yaml\";\n+\t} else {\n+\t\ttie %$hash => 'Memoize::Storable', \"$path.db\", 'nstore';\n+\t}\n+}\n+\n # The GIT_DIR environment variable is not always set until after the command\n # line arguments are processed, so we can't memoize in a BEGIN block.\n {\n@@ -3590,22 +3700,26 @@ sub has_no_changes {\n \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n \t\tmkpath([$cache_path]) unless -d $cache_path;\n \n-\t\ttie my %lookup_svn_merge_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/lookup_svn_merge.db\", 'nstore';\n+\t\tmy %lookup_svn_merge_cache;\n+\t\tmy %check_cherry_pick_cache;\n+\t\tmy %has_no_changes_cache;\n+\n+\t\ttie_for_persistent_memoization(\\%lookup_svn_merge_cache,\n+\t\t    \"$cache_path/lookup_svn_merge\");\n \t\tmemoize 'lookup_svn_merge',\n \t\t\tSCALAR_CACHE => 'FAULT',\n \t\t\tLIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n \t\t;\n \n-\t\ttie my %check_cherry_pick_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/check_cherry_pick.db\", 'nstore';\n+\t\ttie_for_persistent_memoization(\\%check_cherry_pick_cache,\n+\t\t    \"$cache_path/check_cherry_pick\");\n \t\tmemoize 'check_cherry_pick',\n \t\t\tSCALAR_CACHE => 'FAULT',\n \t\t\tLIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n \t\t;\n \n-\t\ttie my %has_no_changes_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/has_no_changes.db\", 'nstore';\n+\t\ttie_for_persistent_memoization(\\%has_no_changes_cache,\n+\t\t    \"$cache_path/has_no_changes\");\n \t\tmemoize 'has_no_changes',\n \t\t\tSCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n \t\t\tLIST_CACHE => 'FAULT',\n-- \n1.7.10\n"},{"id":"192297","messageId":"20120527194822.GA31460@burratino","threadId":"28184","inReplyTo":"20120527192541.GA29490@burratino","subject":"[RFC/PATCH 2/1] fixup! git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-27T19:48:22Z","receivedAt":"2012-05-27T19:48:22Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> YAML::Any is not a core perl module.  This patch uses it when and only\n> when it is available.\n\nAnd here's a patch for squashing in that makes that true.  Sorry for\nthe confusion.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl |   12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git c/git-svn.perl i/git-svn.perl\nindex 96b6046a..1b4ef68f 100755\n--- c/git-svn.perl\n+++ i/git-svn.perl\n@@ -2042,7 +2042,11 @@ sub gc_directory {\n package Git::SVN::Memoize::YAML;\n use warnings;\n use strict;\n-use YAML::Any ();\n+my $usable;\n+BEGIN {\n+\t$Git::SVN::Memoize::YAML::usable = eval { require YAML::Any; 1 };\n+\trequire YAML::Any if $usable;\n+}\n \n # based on Memoize::Storable.\n \n@@ -2151,10 +2155,6 @@ use Time::Local;\n use Memoize;  # core since 5.8.0, Jul 2002\n use Memoize::Storable;\n use POSIX qw(:signal_h);\n-my $can_use_yaml;\n-BEGIN {\n-\t$can_use_yaml = eval { require YAML::Any; 1};\n-}\n \n my ($_gc_nr, $_gc_period);\n \n@@ -3681,7 +3681,7 @@ sub tie_for_persistent_memoization {\n \tmy $hash = shift;\n \tmy $path = shift;\n \n-\tif ($can_use_yaml) {\n+\tif ($Git::SVN::Memoize::YAML::usable) {\n \t\ttie %$hash => 'Git::SVN::Memoize::YAML', \"$path.yaml\";\n \t} else {\n \t\ttie %$hash => 'Memoize::Storable', \"$path.db\", 'nstore';\n"},{"id":"192298","messageId":"20120527201450.GA3630@dcvr.yhbt.net","threadId":"28184","inReplyTo":"20120527192541.GA29490@burratino","subject":"Re: [RFC/PATCH] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2012-05-27T20:14:50Z","receivedAt":"2012-05-27T20:14:50Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Jonathan Nieder <jrnieder@gmail.com> wrote:\n> My personal motivation is the ability to upgrade or downgrade perl or\n> change perl's use64bitint compile-time parameter without having to\n> invalidate caches or (worse, what users unaware of .git/svn/.caches\n> would probably do) reclone repositories.  The Storable format is not\n> stable enough for forward-compatibility.\n> \n> Seems to work.  I haven't run any benchmarks.  \n> \n> Thoughts?  Improvements?\n> \n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n\nSeems reasonable, a few comments below.\n\n>  git-svn.perl |  126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---\n\n> +package Git::SVN::Memoize::YAML;\n\nCan we use this as an opportunity to start splitting git-svn.perl into\nmultiple .pm files?  Thanks.\n\n> +\tmy $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};\n\n> +\tYAML::Any::DumpFile($self->{FILENAME}, $self->{H});\n\nThese should die on errors, right?\n\n> +=head1 BUGS\n\n> +Error handling is awkward.\n\nHow so?\n"},{"id":"192300","messageId":"20120528003901.GA11103@burratino","threadId":"28184","inReplyTo":"20120527201450.GA3630@dcvr.yhbt.net","subject":"Re: [RFC/PATCH] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-28T00:39:01Z","receivedAt":"2012-05-28T00:39:01Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric Wong wrote:\n> Jonathan Nieder <jrnieder@gmail.com> wrote:\n\n>> +package Git::SVN::Memoize::YAML;\n>\n> Can we use this as an opportunity to start splitting git-svn.perl into\n> multiple .pm files?\n\nNot a bad idea.  I've included an example patch to sanity-check the\napproach below.\n\n>> +\tmy $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};\n>\n>> +\tYAML::Any::DumpFile($self->{FILENAME}, $self->{H});\n>\n> These should die on errors, right?\n\nAt least in YAML::Old, they use Carp::croak.  Maybe something like\n\n\tlocal @CARP_NOT = qw(YAML::Any);\n\nto blame the caller for the error would bring sanity.\n\n>> +=head1 BUGS\n>\n>> +Error handling is awkward.\n>\n> How so?\n\nI mostly meant that it's not obvious what the state of %hash is at the\npoint marked with (*) below:\n\n\tif (not eval {\n\t\ttie my %hash => 'Foo::Bar', @params;\n\t\t1;\n\t}) {\n\t\tmy $err = $@ ||\n\t\t\t# a destructor might have clobbered $@\n\t\t\t\"Zombie error\";\n\t\tdie $err if worth_dying($err);\n\n\t\t(*) ... try to recover ...\n\t}\n\nThat's not specific to Memoize::YAML, though.  It probably is not\nawkward for wizards who know the details. :)\n\n-- >8 --\nSubject: git-svn: move Git::SVN::Prompt into its own file\n\ngit-svn.perl is very long (around 6500 lines) and although it is\nnicely split into modules, some new readers do not even notice --- it\nis too distracting to see all this functionality collected in a single\nfile.\n\nSplitting it into multiple files would make it easier for people\nto read individual modules straight through and to experiment with\ncomponents separately.\n\nLet's start with Git::SVN::Prompt.  For simplicity, we install this as\na module in the standard search path, just like the existing Git and\nGit::I18N modules.  In the process, add a manpage explaining its\ninterface and that it is not likely to be useful for other projects to\navoid confusion.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nThanks for a thoughtful review.\nJonathan\n\n git-svn.perl           |  145 +---------------------------------\n perl/Git/SVN/Prompt.pm |  202 ++++++++++++++++++++++++++++++++++++++++++++++++\n perl/Makefile.PL       |    1 +\n 3 files changed, 204 insertions(+), 144 deletions(-)\n create mode 100644 perl/Git/SVN/Prompt.pm\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex 1b4ef68f..ef60b874 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -80,6 +80,7 @@ use File::Find;\n use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;\n use IPC::Open3;\n use Git;\n+use Git::SVN::Prompt qw//;\n use Memoize;  # core since 5.8.0, Jul 2002\n \n BEGIN {\n@@ -4441,150 +4442,6 @@ sub remove_username {\n \t$_[0] =~ s{^([^:]*://)[^@]+@}{$1};\n }\n \n-package Git::SVN::Prompt;\n-use strict;\n-use warnings;\n-require SVN::Core;\n-use vars qw/$_no_auth_cache $_username/;\n-\n-sub simple {\n-\tmy ($cred, $realm, $default_username, $may_save, $pool) = @_;\n-\t$may_save = undef if $_no_auth_cache;\n-\t$default_username = $_username if defined $_username;\n-\tif (defined $default_username && length $default_username) {\n-\t\tif (defined $realm && length $realm) {\n-\t\t\tprint STDERR \"Authentication realm: $realm\\n\";\n-\t\t\tSTDERR->flush;\n-\t\t}\n-\t\t$cred->username($default_username);\n-\t} else {\n-\t\tusername($cred, $realm, $may_save, $pool);\n-\t}\n-\t$cred->password(_read_password(\"Password for '\" .\n-\t                               $cred->username . \"': \", $realm));\n-\t$cred->may_save($may_save);\n-\t$SVN::_Core::SVN_NO_ERROR;\n-}\n-\n-sub ssl_server_trust {\n-\tmy ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;\n-\t$may_save = undef if $_no_auth_cache;\n-\tprint STDERR \"Error validating server certificate for '$realm':\\n\";\n-\t{\n-\t\tno warnings 'once';\n-\t\t# All variables SVN::Auth::SSL::* are used only once,\n-\t\t# so we're shutting up Perl warnings about this.\n-\t\tif ($failures & $SVN::Auth::SSL::UNKNOWNCA) {\n-\t\t\tprint STDERR \" - The certificate is not issued \",\n-\t\t\t    \"by a trusted authority. Use the\\n\",\n-\t\t\t    \"   fingerprint to validate \",\n-\t\t\t    \"the certificate manually!\\n\";\n-\t\t}\n-\t\tif ($failures & $SVN::Auth::SSL::CNMISMATCH) {\n-\t\t\tprint STDERR \" - The certificate hostname \",\n-\t\t\t    \"does not match.\\n\";\n-\t\t}\n-\t\tif ($failures & $SVN::Auth::SSL::NOTYETVALID) {\n-\t\t\tprint STDERR \" - The certificate is not yet valid.\\n\";\n-\t\t}\n-\t\tif ($failures & $SVN::Auth::SSL::EXPIRED) {\n-\t\t\tprint STDERR \" - The certificate has expired.\\n\";\n-\t\t}\n-\t\tif ($failures & $SVN::Auth::SSL::OTHER) {\n-\t\t\tprint STDERR \" - The certificate has \",\n-\t\t\t    \"an unknown error.\\n\";\n-\t\t}\n-\t} # no warnings 'once'\n-\tprintf STDERR\n-\t        \"Certificate information:\\n\".\n-\t        \" - Hostname: %s\\n\".\n-\t        \" - Valid: from %s until %s\\n\".\n-\t        \" - Issuer: %s\\n\".\n-\t        \" - Fingerprint: %s\\n\",\n-\t        map $cert_info->$_, qw(hostname valid_from valid_until\n-\t                               issuer_dname fingerprint);\n-\tmy $choice;\n-prompt:\n-\tprint STDERR $may_save ?\n-\t      \"(R)eject, accept (t)emporarily or accept (p)ermanently? \" :\n-\t      \"(R)eject or accept (t)emporarily? \";\n-\tSTDERR->flush;\n-\t$choice = lc(substr(<STDIN> || 'R', 0, 1));\n-\tif ($choice =~ /^t$/i) {\n-\t\t$cred->may_save(undef);\n-\t} elsif ($choice =~ /^r$/i) {\n-\t\treturn -1;\n-\t} elsif ($may_save && $choice =~ /^p$/i) {\n-\t\t$cred->may_save($may_save);\n-\t} else {\n-\t\tgoto prompt;\n-\t}\n-\t$cred->accepted_failures($failures);\n-\t$SVN::_Core::SVN_NO_ERROR;\n-}\n-\n-sub ssl_client_cert {\n-\tmy ($cred, $realm, $may_save, $pool) = @_;\n-\t$may_save = undef if $_no_auth_cache;\n-\tprint STDERR \"Client certificate filename: \";\n-\tSTDERR->flush;\n-\tchomp(my $filename = <STDIN>);\n-\t$cred->cert_file($filename);\n-\t$cred->may_save($may_save);\n-\t$SVN::_Core::SVN_NO_ERROR;\n-}\n-\n-sub ssl_client_cert_pw {\n-\tmy ($cred, $realm, $may_save, $pool) = @_;\n-\t$may_save = undef if $_no_auth_cache;\n-\t$cred->password(_read_password(\"Password: \", $realm));\n-\t$cred->may_save($may_save);\n-\t$SVN::_Core::SVN_NO_ERROR;\n-}\n-\n-sub username {\n-\tmy ($cred, $realm, $may_save, $pool) = @_;\n-\t$may_save = undef if $_no_auth_cache;\n-\tif (defined $realm && length $realm) {\n-\t\tprint STDERR \"Authentication realm: $realm\\n\";\n-\t}\n-\tmy $username;\n-\tif (defined $_username) {\n-\t\t$username = $_username;\n-\t} else {\n-\t\tprint STDERR \"Username: \";\n-\t\tSTDERR->flush;\n-\t\tchomp($username = <STDIN>);\n-\t}\n-\t$cred->username($username);\n-\t$cred->may_save($may_save);\n-\t$SVN::_Core::SVN_NO_ERROR;\n-}\n-\n-sub _read_password {\n-\tmy ($prompt, $realm) = @_;\n-\tmy $password = '';\n-\tif (exists $ENV{GIT_ASKPASS}) {\n-\t\topen(PH, \"-|\", $ENV{GIT_ASKPASS}, $prompt);\n-\t\t$password = <PH>;\n-\t\t$password =~ s/[\\012\\015]//; # \\n\\r\n-\t\tclose(PH);\n-\t} else {\n-\t\tprint STDERR $prompt;\n-\t\tSTDERR->flush;\n-\t\trequire Term::ReadKey;\n-\t\tTerm::ReadKey::ReadMode('noecho');\n-\t\twhile (defined(my $key = Term::ReadKey::ReadKey(0))) {\n-\t\t\tlast if $key =~ /[\\012\\015]/; # \\n\\r\n-\t\t\t$password .= $key;\n-\t\t}\n-\t\tTerm::ReadKey::ReadMode('restore');\n-\t\tprint STDERR \"\\n\";\n-\t\tSTDERR->flush;\n-\t}\n-\t$password;\n-}\n-\n package SVN::Git::Fetcher;\n use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename\n             @deleted_gpath %added_placeholder $repo_id/;\ndiff --git a/perl/Git/SVN/Prompt.pm b/perl/Git/SVN/Prompt.pm\nnew file mode 100644\nindex 00000000..3a6f8af0\n--- /dev/null\n+++ b/perl/Git/SVN/Prompt.pm\n@@ -0,0 +1,202 @@\n+package Git::SVN::Prompt;\n+use strict;\n+use warnings;\n+require SVN::Core;\n+use vars qw/$_no_auth_cache $_username/;\n+\n+sub simple {\n+\tmy ($cred, $realm, $default_username, $may_save, $pool) = @_;\n+\t$may_save = undef if $_no_auth_cache;\n+\t$default_username = $_username if defined $_username;\n+\tif (defined $default_username && length $default_username) {\n+\t\tif (defined $realm && length $realm) {\n+\t\t\tprint STDERR \"Authentication realm: $realm\\n\";\n+\t\t\tSTDERR->flush;\n+\t\t}\n+\t\t$cred->username($default_username);\n+\t} else {\n+\t\tusername($cred, $realm, $may_save, $pool);\n+\t}\n+\t$cred->password(_read_password(\"Password for '\" .\n+\t                               $cred->username . \"': \", $realm));\n+\t$cred->may_save($may_save);\n+\t$SVN::_Core::SVN_NO_ERROR;\n+}\n+\n+sub ssl_server_trust {\n+\tmy ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;\n+\t$may_save = undef if $_no_auth_cache;\n+\tprint STDERR \"Error validating server certificate for '$realm':\\n\";\n+\t{\n+\t\tno warnings 'once';\n+\t\t# All variables SVN::Auth::SSL::* are used only once,\n+\t\t# so we're shutting up Perl warnings about this.\n+\t\tif ($failures & $SVN::Auth::SSL::UNKNOWNCA) {\n+\t\t\tprint STDERR \" - The certificate is not issued \",\n+\t\t\t    \"by a trusted authority. Use the\\n\",\n+\t\t\t    \"   fingerprint to validate \",\n+\t\t\t    \"the certificate manually!\\n\";\n+\t\t}\n+\t\tif ($failures & $SVN::Auth::SSL::CNMISMATCH) {\n+\t\t\tprint STDERR \" - The certificate hostname \",\n+\t\t\t    \"does not match.\\n\";\n+\t\t}\n+\t\tif ($failures & $SVN::Auth::SSL::NOTYETVALID) {\n+\t\t\tprint STDERR \" - The certificate is not yet valid.\\n\";\n+\t\t}\n+\t\tif ($failures & $SVN::Auth::SSL::EXPIRED) {\n+\t\t\tprint STDERR \" - The certificate has expired.\\n\";\n+\t\t}\n+\t\tif ($failures & $SVN::Auth::SSL::OTHER) {\n+\t\t\tprint STDERR \" - The certificate has \",\n+\t\t\t    \"an unknown error.\\n\";\n+\t\t}\n+\t} # no warnings 'once'\n+\tprintf STDERR\n+\t        \"Certificate information:\\n\".\n+\t        \" - Hostname: %s\\n\".\n+\t        \" - Valid: from %s until %s\\n\".\n+\t        \" - Issuer: %s\\n\".\n+\t        \" - Fingerprint: %s\\n\",\n+\t        map $cert_info->$_, qw(hostname valid_from valid_until\n+\t                               issuer_dname fingerprint);\n+\tmy $choice;\n+prompt:\n+\tprint STDERR $may_save ?\n+\t      \"(R)eject, accept (t)emporarily or accept (p)ermanently? \" :\n+\t      \"(R)eject or accept (t)emporarily? \";\n+\tSTDERR->flush;\n+\t$choice = lc(substr(<STDIN> || 'R', 0, 1));\n+\tif ($choice =~ /^t$/i) {\n+\t\t$cred->may_save(undef);\n+\t} elsif ($choice =~ /^r$/i) {\n+\t\treturn -1;\n+\t} elsif ($may_save && $choice =~ /^p$/i) {\n+\t\t$cred->may_save($may_save);\n+\t} else {\n+\t\tgoto prompt;\n+\t}\n+\t$cred->accepted_failures($failures);\n+\t$SVN::_Core::SVN_NO_ERROR;\n+}\n+\n+sub ssl_client_cert {\n+\tmy ($cred, $realm, $may_save, $pool) = @_;\n+\t$may_save = undef if $_no_auth_cache;\n+\tprint STDERR \"Client certificate filename: \";\n+\tSTDERR->flush;\n+\tchomp(my $filename = <STDIN>);\n+\t$cred->cert_file($filename);\n+\t$cred->may_save($may_save);\n+\t$SVN::_Core::SVN_NO_ERROR;\n+}\n+\n+sub ssl_client_cert_pw {\n+\tmy ($cred, $realm, $may_save, $pool) = @_;\n+\t$may_save = undef if $_no_auth_cache;\n+\t$cred->password(_read_password(\"Password: \", $realm));\n+\t$cred->may_save($may_save);\n+\t$SVN::_Core::SVN_NO_ERROR;\n+}\n+\n+sub username {\n+\tmy ($cred, $realm, $may_save, $pool) = @_;\n+\t$may_save = undef if $_no_auth_cache;\n+\tif (defined $realm && length $realm) {\n+\t\tprint STDERR \"Authentication realm: $realm\\n\";\n+\t}\n+\tmy $username;\n+\tif (defined $_username) {\n+\t\t$username = $_username;\n+\t} else {\n+\t\tprint STDERR \"Username: \";\n+\t\tSTDERR->flush;\n+\t\tchomp($username = <STDIN>);\n+\t}\n+\t$cred->username($username);\n+\t$cred->may_save($may_save);\n+\t$SVN::_Core::SVN_NO_ERROR;\n+}\n+\n+sub _read_password {\n+\tmy ($prompt, $realm) = @_;\n+\tmy $password = '';\n+\tif (exists $ENV{GIT_ASKPASS}) {\n+\t\topen(PH, \"-|\", $ENV{GIT_ASKPASS}, $prompt);\n+\t\t$password = <PH>;\n+\t\t$password =~ s/[\\012\\015]//; # \\n\\r\n+\t\tclose(PH);\n+\t} else {\n+\t\tprint STDERR $prompt;\n+\t\tSTDERR->flush;\n+\t\trequire Term::ReadKey;\n+\t\tTerm::ReadKey::ReadMode('noecho');\n+\t\twhile (defined(my $key = Term::ReadKey::ReadKey(0))) {\n+\t\t\tlast if $key =~ /[\\012\\015]/; # \\n\\r\n+\t\t\t$password .= $key;\n+\t\t}\n+\t\tTerm::ReadKey::ReadMode('restore');\n+\t\tprint STDERR \"\\n\";\n+\t\tSTDERR->flush;\n+\t}\n+\t$password;\n+}\n+\n+1;\n+__END__\n+\n+Git::SVN::Prompt - authentication callbacks for git-svn\n+\n+=head1 SYNOPSIS\n+\n+    use Git::SVN::Prompt qw(simple ssl_client_cert ssl_client_cert_pw\n+                            ssl_server_trust username);\n+    use SVN::Client ();\n+\n+    my $cached_simple = SVN::Client::get_simple_provider();\n+    my $git_simple = SVN::Client::get_simple_prompt_provider(\\&simple, 2);\n+    my $cached_ssl = SVN::Client::get_ssl_server_trust_file_provider();\n+    my $git_ssl = SVN::Client::get_ssl_server_trust_prompt_provider(\n+        \\&ssl_server_trust);\n+    my $cached_cert = SVN::Client::get_ssl_client_cert_file_provider();\n+    my $git_cert = SVN::Client::get_ssl_client_cert_prompt_provider(\n+        \\&ssl_client_cert, 2);\n+    my $cached_cert_pw = SVN::Client::get_ssl_client_cert_pw_file_provider();\n+    my $git_cert_pw = SVN::Client::get_ssl_client_cert_pw_prompt_provider(\n+        \\&ssl_client_cert_pw, 2);\n+    my $cached_username = SVN::Client::get_username_provider();\n+    my $git_username = SVN::Client::get_username_prompt_provider(\n+        \\&username, 2);\n+\n+    my $ctx = new SVN::Client(\n+        auth => [\n+            $cached_simple, $git_simple,\n+            $cached_ssl, $git_ssl,\n+            $cached_cert, $git_cert,\n+            $cached_cert_pw, $git_cert_pw,\n+            $cached_username, $git_username\n+        ]);\n+\n+=head1 DESCRIPTION\n+\n+This module is an implementation detail of the \"git svn\" command.\n+It implements git-svn's authentication policy.  Do not use it unless\n+you are developing git-svn.\n+\n+The interface will change as git-svn evolves.\n+\n+=head1 DEPENDENCIES\n+\n+L<SVN::Core>.\n+\n+=head1 SEE ALSO\n+\n+L<SVN::Client>.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+None.\ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 456d45bf..4d8e31d2 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -27,6 +27,7 @@ MAKE_FRAG\n my %pm = (\n \t'Git.pm' => '$(INST_LIBDIR)/Git.pm',\n \t'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',\n+\t'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',\n );\n \n # We come with our own bundled Error.pm. It's not in the set of default\n-- \n1.7.10\n"},{"id":"192308","messageId":"20120528065723.GC10976@burratino","threadId":"28184","inReplyTo":"20120528003901.GA11103@burratino","subject":"[RFC/PATCH 0/2] git-svn: give SVN::Git::Fetcher its own file","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-28T06:57:23Z","receivedAt":"2012-05-28T06:57:23Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n> Eric Wong wrote:\n\n>> Can we use this as an opportunity to start splitting git-svn.perl into\n>> multiple .pm files?\n>\n> Not a bad idea.  I've included an example patch to sanity-check the\n> approach below.\n[...]\n> Let's start with Git::SVN::Prompt.\n\nAnd here's a more interesting one: SVN::Git::Fetcher.\n\nJonathan Nieder (2):\n  git-svn: rename SVN::Git::* packages to Git::SVN::*\n  git-svn: make Git::SVN::Fetcher a separate file\n\n git-svn.perl            |  551 ++-----------------------------------------\n perl/Git/SVN/Fetcher.pm |  602 +++++++++++++++++++++++++++++++++++++++++++++++\n perl/Makefile.PL        |    1 +\n 3 files changed, 627 insertions(+), 527 deletions(-)\n create mode 100644 perl/Git/SVN/Fetcher.pm\n"},{"id":"192309","messageId":"20120528070046.GD10976@burratino","threadId":"28184","inReplyTo":"20120528065723.GC10976@burratino","subject":"[PATCH 1/2] git-svn: rename SVN::Git::* packages to Git::SVN::*","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-28T07:00:46Z","receivedAt":"2012-05-28T07:00:46Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Using names in the Git:: namespace means these cannot conflict with a\nhypothetical binding teaching Subversion to interact with git\nrepositories.\n\nCurrently the packages are private to git-svn.perl so the choice of\nname isn't likely to make much difference.  This change is mainly\nmeant as preparation for splitting out the packages in question as\nmodules on the public search path.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl |   50 +++++++++++++++++++++++++-------------------------\n 1 file changed, 25 insertions(+), 25 deletions(-)\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex ef60b874..d0bcf3f1 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -68,8 +68,8 @@ sub _req_svn {\n }\n my $can_compress = eval { require Compress::Zlib; 1};\n push @Git::SVN::Ra::ISA, 'SVN::Ra';\n-push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';\n-push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';\n+push @Git::SVN::Editor::ISA, 'SVN::Delta::Editor';\n+push @Git::SVN::Fetcher::ISA, 'SVN::Delta::Editor';\n use Carp qw/croak/;\n use Digest::MD5;\n use IO::File qw//;\n@@ -89,7 +89,7 @@ BEGIN {\n \tforeach (qw/command command_oneline command_noisy command_output_pipe\n \t            command_input_pipe command_close_pipe\n \t            command_bidi_pipe command_close_bidi_pipe/) {\n-\t\tfor my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher\n+\t\tfor my $package ( qw(Git::SVN::Editor Git::SVN::Fetcher\n \t\t\tGit::SVN::Migration Git::SVN::Log Git::SVN),\n \t\t\t__PACKAGE__) {\n \t\t\t*{\"${package}::$_\"} = \\&{\"Git::$_\"};\n@@ -111,12 +111,12 @@ my ($_stdin, $_help, $_edit,\n \t$_prefix, $_no_checkout, $_url, $_verbose,\n \t$_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);\n $Git::SVN::_follow_parent = 1;\n-$SVN::Git::Fetcher::_placeholder_filename = \".gitignore\";\n+$Git::SVN::Fetcher::_placeholder_filename = \".gitignore\";\n $_q ||= 0;\n my %remote_opts = ( 'username=s' => \\$Git::SVN::Prompt::_username,\n                     'config-dir=s' => \\$Git::SVN::Ra::config_dir,\n                     'no-auth-cache' => \\$Git::SVN::Prompt::_no_auth_cache,\n-                    'ignore-paths=s' => \\$SVN::Git::Fetcher::_ignore_regex,\n+                    'ignore-paths=s' => \\$Git::SVN::Fetcher::_ignore_regex,\n                     'ignore-refs=s' => \\$Git::SVN::Ra::_ignore_refs_regex );\n my %fc_opts = ( 'follow-parent|follow!' => \\$Git::SVN::_follow_parent,\n \t\t'authors-file|A=s' => \\$_authors,\n@@ -149,10 +149,10 @@ my %init_opts = ( 'template=s' => \\$_template, 'shared:s' => \\$_shared,\n \t\t  'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },\n                   %remote_opts );\n my %cmt_opts = ( 'edit|e' => \\$_edit,\n-\t\t'rmdir' => \\$SVN::Git::Editor::_rmdir,\n-\t\t'find-copies-harder' => \\$SVN::Git::Editor::_find_copies_harder,\n-\t\t'l=i' => \\$SVN::Git::Editor::_rename_limit,\n-\t\t'copy-similarity|C=i'=> \\$SVN::Git::Editor::_cp_similarity\n+\t\t'rmdir' => \\$Git::SVN::Editor::_rmdir,\n+\t\t'find-copies-harder' => \\$Git::SVN::Editor::_find_copies_harder,\n+\t\t'l=i' => \\$Git::SVN::Editor::_rename_limit,\n+\t\t'copy-similarity|C=i'=> \\$Git::SVN::Editor::_cp_similarity\n );\n \n my %cmd = (\n@@ -164,9 +164,9 @@ my %cmd = (\n \tclone => [ \\&cmd_clone, \"Initialize and fetch revisions\",\n \t\t\t{ 'revision|r=s' => \\$_revision,\n \t\t\t  'preserve-empty-dirs' =>\n-\t\t\t\t\\$SVN::Git::Fetcher::_preserve_empty_dirs,\n+\t\t\t\t\\$Git::SVN::Fetcher::_preserve_empty_dirs,\n \t\t\t  'placeholder-filename=s' =>\n-\t\t\t\t\\$SVN::Git::Fetcher::_placeholder_filename,\n+\t\t\t\t\\$Git::SVN::Fetcher::_placeholder_filename,\n \t\t\t   %fc_opts, %init_opts } ],\n \tinit => [ \\&cmd_init, \"Initialize a repo for tracking\" .\n \t\t\t  \" (requires URL argument)\",\n@@ -464,15 +464,15 @@ sub do_git_init_db {\n \t\tcommand_noisy('config', \"$pfx.$i\", $icv{$i});\n \t\t$set = $i;\n \t}\n-\tmy $ignore_paths_regex = \\$SVN::Git::Fetcher::_ignore_regex;\n+\tmy $ignore_paths_regex = \\$Git::SVN::Fetcher::_ignore_regex;\n \tcommand_noisy('config', \"$pfx.ignore-paths\", $$ignore_paths_regex)\n \t\tif defined $$ignore_paths_regex;\n \tmy $ignore_refs_regex = \\$Git::SVN::Ra::_ignore_refs_regex;\n \tcommand_noisy('config', \"$pfx.ignore-refs\", $$ignore_refs_regex)\n \t\tif defined $$ignore_refs_regex;\n \n-\tif (defined $SVN::Git::Fetcher::_preserve_empty_dirs) {\n-\t\tmy $fname = \\$SVN::Git::Fetcher::_placeholder_filename;\n+\tif (defined $Git::SVN::Fetcher::_preserve_empty_dirs) {\n+\t\tmy $fname = \\$Git::SVN::Fetcher::_placeholder_filename;\n \t\tcommand_noisy('config', \"$pfx.preserve-empty-dirs\", 'true');\n \t\tcommand_noisy('config', \"$pfx.placeholder-filename\", $$fname);\n \t}\n@@ -942,7 +942,7 @@ sub cmd_dcommit {\n \t\t\t                },\n \t\t\t\t\tmergeinfo => $_merge_info,\n \t\t\t                svn_path => '');\n-\t\t\tif (!SVN::Git::Editor->new(\\%ed_opts)->apply_diff) {\n+\t\t\tif (!Git::SVN::Editor->new(\\%ed_opts)->apply_diff) {\n \t\t\t\tprint \"No changes\\n$d~1 == $d\\n\";\n \t\t\t} elsif ($parents->{$d} && @{$parents->{$d}}) {\n \t\t\t\t$gs->{inject_parents_dcommit}->{$cmt_rev} =\n@@ -1066,8 +1066,8 @@ sub cmd_branch {\n \t\t            \" with the --destination argument.\\n\";\n \t\t}\n \t\tforeach my $g (@{$allglobs}) {\n-\t\t\t# SVN::Git::Editor could probably be moved to Git.pm..\n-\t\t\tmy $re = SVN::Git::Editor::glob2pat($g->{path}->{left});\n+\t\t\t# Git::SVN::Editor could probably be moved to Git.pm..\n+\t\t\tmy $re = Git::SVN::Editor::glob2pat($g->{path}->{left});\n \t\t\tif ($_branch_dest =~ /$re/) {\n \t\t\t\t$glob = $g;\n \t\t\t\tlast;\n@@ -1425,7 +1425,7 @@ sub cmd_commit_diff {\n \t                tree_b => $tb,\n \t                editor_cb => sub { print \"Committed r$_[0]\\n\" },\n \t                svn_path => $svn_path );\n-\tif (!SVN::Git::Editor->new(\\%ed_opts)->apply_diff) {\n+\tif (!Git::SVN::Editor->new(\\%ed_opts)->apply_diff) {\n \t\tprint \"No changes\\n$ta == $tb\\n\";\n \t}\n }\n@@ -3256,7 +3256,7 @@ sub find_parent_branch {\n \t\t\t# at the moment), so we can't rely on it\n \t\t\t$self->{last_rev} = $r0;\n \t\t\t$self->{last_commit} = $parent;\n-\t\t\t$ed = SVN::Git::Fetcher->new($self, $gs->{path});\n+\t\t\t$ed = Git::SVN::Fetcher->new($self, $gs->{path});\n \t\t\t$gs->ra->gs_do_switch($r0, $rev, $gs,\n \t\t\t\t\t      $self->full_url, $ed)\n \t\t\t  or die \"SVN connection failed somewhere...\\n\";\n@@ -3274,7 +3274,7 @@ sub find_parent_branch {\n \t\t} else {\n \t\t\tprint STDERR \"Following parent with do_update\\n\"\n \t\t\t             unless $::_q > 1;\n-\t\t\t$ed = SVN::Git::Fetcher->new($self);\n+\t\t\t$ed = Git::SVN::Fetcher->new($self);\n \t\t\t$self->ra->gs_do_update($rev, $rev, $self, $ed)\n \t\t\t  or die \"SVN connection failed somewhere...\\n\";\n \t\t}\n@@ -3297,7 +3297,7 @@ sub do_fetch {\n \t\t\tpush @{$log_entry->{parents}}, $lc;\n \t\t\treturn $log_entry;\n \t\t}\n-\t\t$ed = SVN::Git::Fetcher->new($self);\n+\t\t$ed = Git::SVN::Fetcher->new($self);\n \t\t$last_rev = $self->{last_rev};\n \t\t$ed->{c} = $lc;\n \t\t@parents = ($lc);\n@@ -3306,7 +3306,7 @@ sub do_fetch {\n \t\tif (my $log_entry = $self->find_parent_branch($paths, $rev)) {\n \t\t\treturn $log_entry;\n \t\t}\n-\t\t$ed = SVN::Git::Fetcher->new($self);\n+\t\t$ed = Git::SVN::Fetcher->new($self);\n \t}\n \tunless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {\n \t\tdie \"SVN connection failed somewhere...\\n\";\n@@ -4026,7 +4026,7 @@ sub set_tree {\n \t                editor_cb => sub {\n \t\t\t       $self->set_tree_cb($log_entry, $tree, @_) },\n \t                svn_path => $self->{path} );\n-\tif (!SVN::Git::Editor->new(\\%ed_opts)->apply_diff) {\n+\tif (!Git::SVN::Editor->new(\\%ed_opts)->apply_diff) {\n \t\tprint \"No changes\\nr$self->{last_rev} = $tree\\n\";\n \t}\n }\n@@ -4442,7 +4442,7 @@ sub remove_username {\n \t$_[0] =~ s{^([^:]*://)[^@]+@}{$1};\n }\n \n-package SVN::Git::Fetcher;\n+package Git::SVN::Fetcher;\n use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename\n             @deleted_gpath %added_placeholder $repo_id/;\n use strict;\n@@ -4945,7 +4945,7 @@ sub stash_placeholder_list {\n \t}\n }\n \n-package SVN::Git::Editor;\n+package Git::SVN::Editor;\n use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;\n use strict;\n use warnings;\n-- \n1.7.10\n"},{"id":"192310","messageId":"20120528070358.GE10976@burratino","threadId":"28184","inReplyTo":"20120528065723.GC10976@burratino","subject":"[PATCH 2/2] git-svn: make Git::SVN::Fetcher a separate file","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-05-28T07:03:58Z","receivedAt":"2012-05-28T07:03:58Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"This patch removes a chunk of code (the Git::SVN::Fetcher consumer of\nlibsvn's tree delta protocol) from git-svn.perl and documents its\ninterface so the hurried reader does not have to read that code right\naway.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl            |  507 +--------------------------------------\n perl/Git/SVN/Fetcher.pm |  602 +++++++++++++++++++++++++++++++++++++++++++++++\n perl/Makefile.PL        |    1 +\n 3 files changed, 605 insertions(+), 505 deletions(-)\n create mode 100644 perl/Git/SVN/Fetcher.pm\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex d0bcf3f1..35073dea 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -69,7 +69,6 @@ sub _req_svn {\n my $can_compress = eval { require Compress::Zlib; 1};\n push @Git::SVN::Ra::ISA, 'SVN::Ra';\n push @Git::SVN::Editor::ISA, 'SVN::Delta::Editor';\n-push @Git::SVN::Fetcher::ISA, 'SVN::Delta::Editor';\n use Carp qw/croak/;\n use Digest::MD5;\n use IO::File qw//;\n@@ -80,6 +79,7 @@ use File::Find;\n use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;\n use IPC::Open3;\n use Git;\n+use Git::SVN::Fetcher qw//;\n use Git::SVN::Prompt qw//;\n use Memoize;  # core since 5.8.0, Jul 2002\n \n@@ -89,7 +89,7 @@ BEGIN {\n \tforeach (qw/command command_oneline command_noisy command_output_pipe\n \t            command_input_pipe command_close_pipe\n \t            command_bidi_pipe command_close_bidi_pipe/) {\n-\t\tfor my $package ( qw(Git::SVN::Editor Git::SVN::Fetcher\n+\t\tfor my $package ( qw(Git::SVN::Editor\n \t\t\tGit::SVN::Migration Git::SVN::Log Git::SVN),\n \t\t\t__PACKAGE__) {\n \t\t\t*{\"${package}::$_\"} = \\&{\"Git::$_\"};\n@@ -4442,509 +4442,6 @@ sub remove_username {\n \t$_[0] =~ s{^([^:]*://)[^@]+@}{$1};\n }\n \n-package Git::SVN::Fetcher;\n-use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename\n-            @deleted_gpath %added_placeholder $repo_id/;\n-use strict;\n-use warnings;\n-use Carp qw/croak/;\n-use File::Basename qw/dirname/;\n-use IO::File qw//;\n-\n-# file baton members: path, mode_a, mode_b, pool, fh, blob, base\n-sub new {\n-\tmy ($class, $git_svn, $switch_path) = @_;\n-\tmy $self = SVN::Delta::Editor->new;\n-\tbless $self, $class;\n-\tif (exists $git_svn->{last_commit}) {\n-\t\t$self->{c} = $git_svn->{last_commit};\n-\t\t$self->{empty_symlinks} =\n-\t\t                  _mark_empty_symlinks($git_svn, $switch_path);\n-\t}\n-\n-\t# some options are read globally, but can be overridden locally\n-\t# per [svn-remote \"...\"] section.  Command-line options will *NOT*\n-\t# override options set in an [svn-remote \"...\"] section\n-\t$repo_id = $git_svn->{repo_id};\n-\tmy $k = \"svn-remote.$repo_id.ignore-paths\";\n-\tmy $v = eval { command_oneline('config', '--get', $k) };\n-\t$self->{ignore_regex} = $v;\n-\n-\t$k = \"svn-remote.$repo_id.preserve-empty-dirs\";\n-\t$v = eval { command_oneline('config', '--get', '--bool', $k) };\n-\tif ($v && $v eq 'true') {\n-\t\t$_preserve_empty_dirs = 1;\n-\t\t$k = \"svn-remote.$repo_id.placeholder-filename\";\n-\t\t$v = eval { command_oneline('config', '--get', $k) };\n-\t\t$_placeholder_filename = $v;\n-\t}\n-\n-\t# Load the list of placeholder files added during previous invocations.\n-\t$k = \"svn-remote.$repo_id.added-placeholder\";\n-\t$v = eval { command_oneline('config', '--get-all', $k) };\n-\tif ($_preserve_empty_dirs && $v) {\n-\t\t# command() prints errors to stderr, so we only call it if\n-\t\t# command_oneline() succeeded.\n-\t\tmy @v = command('config', '--get-all', $k);\n-\t\t$added_placeholder{ dirname($_) } = $_ foreach @v;\n-\t}\n-\n-\t$self->{empty} = {};\n-\t$self->{dir_prop} = {};\n-\t$self->{file_prop} = {};\n-\t$self->{absent_dir} = {};\n-\t$self->{absent_file} = {};\n-\t$self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });\n-\t$self->{pathnameencoding} = Git::config('svn.pathnameencoding');\n-\t$self;\n-}\n-\n-# this uses the Ra object, so it must be called before do_{switch,update},\n-# not inside them (when the Git::SVN::Fetcher object is passed) to\n-# do_{switch,update}\n-sub _mark_empty_symlinks {\n-\tmy ($git_svn, $switch_path) = @_;\n-\tmy $bool = Git::config_bool('svn.brokenSymlinkWorkaround');\n-\treturn {} if (!defined($bool)) || (defined($bool) && ! $bool);\n-\n-\tmy %ret;\n-\tmy ($rev, $cmt) = $git_svn->last_rev_commit;\n-\treturn {} unless ($rev && $cmt);\n-\n-\t# allow the warning to be printed for each revision we fetch to\n-\t# ensure the user sees it.  The user can also disable the workaround\n-\t# on the repository even while git svn is running and the next\n-\t# revision fetched will skip this expensive function.\n-\tmy $printed_warning;\n-\tchomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);\n-\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);\n-\tlocal $/ = \"\\0\";\n-\tmy $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};\n-\t$pfx .= '/' if length($pfx);\n-\twhile (<$ls>) {\n-\t\tchomp;\n-\t\ts/\\A100644 blob $empty_blob\\t//o or next;\n-\t\tunless ($printed_warning) {\n-\t\t\tprint STDERR \"Scanning for empty symlinks, \",\n-\t\t\t             \"this may take a while if you have \",\n-\t\t\t\t     \"many empty files\\n\",\n-\t\t\t\t     \"You may disable this with `\",\n-\t\t\t\t     \"git config svn.brokenSymlinkWorkaround \",\n-\t\t\t\t     \"false'.\\n\",\n-\t\t\t\t     \"This may be done in a different \",\n-\t\t\t\t     \"terminal without restarting \",\n-\t\t\t\t     \"git svn\\n\";\n-\t\t\t$printed_warning = 1;\n-\t\t}\n-\t\tmy $path = $_;\n-\t\tmy (undef, $props) =\n-\t\t               $git_svn->ra->get_file($pfx.$path, $rev, undef);\n-\t\tif ($props->{'svn:special'}) {\n-\t\t\t$ret{$path} = 1;\n-\t\t}\n-\t}\n-\tcommand_close_pipe($ls, $ctx);\n-\t\\%ret;\n-}\n-\n-# returns true if a given path is inside a \".git\" directory\n-sub in_dot_git {\n-\t$_[0] =~ m{(?:^|/)\\.git(?:/|$)};\n-}\n-\n-# return value: 0 -- don't ignore, 1 -- ignore\n-sub is_path_ignored {\n-\tmy ($self, $path) = @_;\n-\treturn 1 if in_dot_git($path);\n-\treturn 1 if defined($self->{ignore_regex}) &&\n-\t            $path =~ m!$self->{ignore_regex}!;\n-\treturn 0 unless defined($_ignore_regex);\n-\treturn 1 if $path =~ m!$_ignore_regex!o;\n-\treturn 0;\n-}\n-\n-sub set_path_strip {\n-\tmy ($self, $path) = @_;\n-\t$self->{path_strip} = qr/^\\Q$path\\E(\\/|$)/ if length $path;\n-}\n-\n-sub open_root {\n-\t{ path => '' };\n-}\n-\n-sub open_directory {\n-\tmy ($self, $path, $pb, $rev) = @_;\n-\t{ path => $path };\n-}\n-\n-sub git_path {\n-\tmy ($self, $path) = @_;\n-\tif (my $enc = $self->{pathnameencoding}) {\n-\t\trequire Encode;\n-\t\tEncode::from_to($path, 'UTF-8', $enc);\n-\t}\n-\tif ($self->{path_strip}) {\n-\t\t$path =~ s!$self->{path_strip}!! or\n-\t\t  die \"Failed to strip path '$path' ($self->{path_strip})\\n\";\n-\t}\n-\t$path;\n-}\n-\n-sub delete_entry {\n-\tmy ($self, $path, $rev, $pb) = @_;\n-\treturn undef if $self->is_path_ignored($path);\n-\n-\tmy $gpath = $self->git_path($path);\n-\treturn undef if ($gpath eq '');\n-\n-\t# remove entire directories.\n-\tmy ($tree) = (command('ls-tree', '-z', $self->{c}, \"./$gpath\")\n-\t                 =~ /\\A040000 tree ([a-f\\d]{40})\\t\\Q$gpath\\E\\0/);\n-\tif ($tree) {\n-\t\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree\n-\t\t                                     -r --name-only -z/,\n-\t\t\t\t                     $tree);\n-\t\tlocal $/ = \"\\0\";\n-\t\twhile (<$ls>) {\n-\t\t\tchomp;\n-\t\t\tmy $rmpath = \"$gpath/$_\";\n-\t\t\t$self->{gii}->remove($rmpath);\n-\t\t\tprint \"\\tD\\t$rmpath\\n\" unless $::_q;\n-\t\t}\n-\t\tprint \"\\tD\\t$gpath/\\n\" unless $::_q;\n-\t\tcommand_close_pipe($ls, $ctx);\n-\t} else {\n-\t\t$self->{gii}->remove($gpath);\n-\t\tprint \"\\tD\\t$gpath\\n\" unless $::_q;\n-\t}\n-\t# Don't add to @deleted_gpath if we're deleting a placeholder file.\n-\tpush @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};\n-\t$self->{empty}->{$path} = 0;\n-\tundef;\n-}\n-\n-sub open_file {\n-\tmy ($self, $path, $pb, $rev) = @_;\n-\tmy ($mode, $blob);\n-\n-\tgoto out if $self->is_path_ignored($path);\n-\n-\tmy $gpath = $self->git_path($path);\n-\t($mode, $blob) = (command('ls-tree', '-z', $self->{c}, \"./$gpath\")\n-\t                     =~ /\\A(\\d{6}) blob ([a-f\\d]{40})\\t\\Q$gpath\\E\\0/);\n-\tunless (defined $mode && defined $blob) {\n-\t\tdie \"$path was not found in commit $self->{c} (r$rev)\\n\";\n-\t}\n-\tif ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {\n-\t\t$mode = '120000';\n-\t}\n-out:\n-\t{ path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,\n-\t  pool => SVN::Pool->new, action => 'M' };\n-}\n-\n-sub add_file {\n-\tmy ($self, $path, $pb, $cp_path, $cp_rev) = @_;\n-\tmy $mode;\n-\n-\tif (!$self->is_path_ignored($path)) {\n-\t\tmy ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);\n-\t\tdelete $self->{empty}->{$dir};\n-\t\t$mode = '100644';\n-\n-\t\tif ($added_placeholder{$dir}) {\n-\t\t\t# Remove our placeholder file, if we created one.\n-\t\t\tdelete_entry($self, $added_placeholder{$dir})\n-\t\t\t\tunless $path eq $added_placeholder{$dir};\n-\t\t\tdelete $added_placeholder{$dir}\n-\t\t}\n-\t}\n-\n-\t{ path => $path, mode_a => $mode, mode_b => $mode,\n-\t  pool => SVN::Pool->new, action => 'A' };\n-}\n-\n-sub add_directory {\n-\tmy ($self, $path, $cp_path, $cp_rev) = @_;\n-\tgoto out if $self->is_path_ignored($path);\n-\tmy $gpath = $self->git_path($path);\n-\tif ($gpath eq '') {\n-\t\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree\n-\t\t                                     -r --name-only -z/,\n-\t\t\t\t                     $self->{c});\n-\t\tlocal $/ = \"\\0\";\n-\t\twhile (<$ls>) {\n-\t\t\tchomp;\n-\t\t\t$self->{gii}->remove($_);\n-\t\t\tprint \"\\tD\\t$_\\n\" unless $::_q;\n-\t\t\tpush @deleted_gpath, $gpath;\n-\t\t}\n-\t\tcommand_close_pipe($ls, $ctx);\n-\t\t$self->{empty}->{$path} = 0;\n-\t}\n-\tmy ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);\n-\tdelete $self->{empty}->{$dir};\n-\t$self->{empty}->{$path} = 1;\n-\n-\tif ($added_placeholder{$dir}) {\n-\t\t# Remove our placeholder file, if we created one.\n-\t\tdelete_entry($self, $added_placeholder{$dir});\n-\t\tdelete $added_placeholder{$dir}\n-\t}\n-\n-out:\n-\t{ path => $path };\n-}\n-\n-sub change_dir_prop {\n-\tmy ($self, $db, $prop, $value) = @_;\n-\treturn undef if $self->is_path_ignored($db->{path});\n-\t$self->{dir_prop}->{$db->{path}} ||= {};\n-\t$self->{dir_prop}->{$db->{path}}->{$prop} = $value;\n-\tundef;\n-}\n-\n-sub absent_directory {\n-\tmy ($self, $path, $pb) = @_;\n-\treturn undef if $self->is_path_ignored($path);\n-\t$self->{absent_dir}->{$pb->{path}} ||= [];\n-\tpush @{$self->{absent_dir}->{$pb->{path}}}, $path;\n-\tundef;\n-}\n-\n-sub absent_file {\n-\tmy ($self, $path, $pb) = @_;\n-\treturn undef if $self->is_path_ignored($path);\n-\t$self->{absent_file}->{$pb->{path}} ||= [];\n-\tpush @{$self->{absent_file}->{$pb->{path}}}, $path;\n-\tundef;\n-}\n-\n-sub change_file_prop {\n-\tmy ($self, $fb, $prop, $value) = @_;\n-\treturn undef if $self->is_path_ignored($fb->{path});\n-\tif ($prop eq 'svn:executable') {\n-\t\tif ($fb->{mode_b} != 120000) {\n-\t\t\t$fb->{mode_b} = defined $value ? 100755 : 100644;\n-\t\t}\n-\t} elsif ($prop eq 'svn:special') {\n-\t\t$fb->{mode_b} = defined $value ? 120000 : 100644;\n-\t} else {\n-\t\t$self->{file_prop}->{$fb->{path}} ||= {};\n-\t\t$self->{file_prop}->{$fb->{path}}->{$prop} = $value;\n-\t}\n-\tundef;\n-}\n-\n-sub apply_textdelta {\n-\tmy ($self, $fb, $exp) = @_;\n-\treturn undef if $self->is_path_ignored($fb->{path});\n-\tmy $fh = $::_repository->temp_acquire('svn_delta');\n-\t# $fh gets auto-closed() by SVN::TxDelta::apply(),\n-\t# (but $base does not,) so dup() it for reading in close_file\n-\topen my $dup, '<&', $fh or croak $!;\n-\tmy $base = $::_repository->temp_acquire('git_blob');\n-\n-\tif ($fb->{blob}) {\n-\t\tmy ($base_is_link, $size);\n-\n-\t\tif ($fb->{mode_a} eq '120000' &&\n-\t\t    ! $self->{empty_symlinks}->{$fb->{path}}) {\n-\t\t\tprint $base 'link ' or die \"print $!\\n\";\n-\t\t\t$base_is_link = 1;\n-\t\t}\n-\tretry:\n-\t\t$size = $::_repository->cat_blob($fb->{blob}, $base);\n-\t\tdie \"Failed to read object $fb->{blob}\" if ($size < 0);\n-\n-\t\tif (defined $exp) {\n-\t\t\tseek $base, 0, 0 or croak $!;\n-\t\t\tmy $got = ::md5sum($base);\n-\t\t\tif ($got ne $exp) {\n-\t\t\t\tmy $err = \"Checksum mismatch: \".\n-\t\t\t\t       \"$fb->{path} $fb->{blob}\\n\" .\n-\t\t\t\t       \"expected: $exp\\n\" .\n-\t\t\t\t       \"     got: $got\\n\";\n-\t\t\t\tif ($base_is_link) {\n-\t\t\t\t\twarn $err,\n-\t\t\t\t\t     \"Retrying... (possibly \",\n-\t\t\t\t\t     \"a bad symlink from SVN)\\n\";\n-\t\t\t\t\t$::_repository->temp_reset($base);\n-\t\t\t\t\t$base_is_link = 0;\n-\t\t\t\t\tgoto retry;\n-\t\t\t\t}\n-\t\t\t\tdie $err;\n-\t\t\t}\n-\t\t}\n-\t}\n-\tseek $base, 0, 0 or croak $!;\n-\t$fb->{fh} = $fh;\n-\t$fb->{base} = $base;\n-\t[ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];\n-}\n-\n-sub close_file {\n-\tmy ($self, $fb, $exp) = @_;\n-\treturn undef if $self->is_path_ignored($fb->{path});\n-\n-\tmy $hash;\n-\tmy $path = $self->git_path($fb->{path});\n-\tif (my $fh = $fb->{fh}) {\n-\t\tif (defined $exp) {\n-\t\t\tseek($fh, 0, 0) or croak $!;\n-\t\t\tmy $got = ::md5sum($fh);\n-\t\t\tif ($got ne $exp) {\n-\t\t\t\tdie \"Checksum mismatch: $path\\n\",\n-\t\t\t\t    \"expected: $exp\\n    got: $got\\n\";\n-\t\t\t}\n-\t\t}\n-\t\tif ($fb->{mode_b} == 120000) {\n-\t\t\tsysseek($fh, 0, 0) or croak $!;\n-\t\t\tmy $rd = sysread($fh, my $buf, 5);\n-\n-\t\t\tif (!defined $rd) {\n-\t\t\t\tcroak \"sysread: $!\\n\";\n-\t\t\t} elsif ($rd == 0) {\n-\t\t\t\twarn \"$path has mode 120000\",\n-\t\t\t\t     \" but it points to nothing\\n\",\n-\t\t\t\t     \"converting to an empty file with mode\",\n-\t\t\t\t     \" 100644\\n\";\n-\t\t\t\t$fb->{mode_b} = '100644';\n-\t\t\t} elsif ($buf ne 'link ') {\n-\t\t\t\twarn \"$path has mode 120000\",\n-\t\t\t\t     \" but is not a link\\n\";\n-\t\t\t} else {\n-\t\t\t\tmy $tmp_fh = $::_repository->temp_acquire(\n-\t\t\t\t\t'svn_hash');\n-\t\t\t\tmy $res;\n-\t\t\t\twhile ($res = sysread($fh, my $str, 1024)) {\n-\t\t\t\t\tmy $out = syswrite($tmp_fh, $str, $res);\n-\t\t\t\t\tdefined($out) && $out == $res\n-\t\t\t\t\t\tor croak(\"write \",\n-\t\t\t\t\t\t\tGit::temp_path($tmp_fh),\n-\t\t\t\t\t\t\t\": $!\\n\");\n-\t\t\t\t}\n-\t\t\t\tdefined $res or croak $!;\n-\n-\t\t\t\t($fh, $tmp_fh) = ($tmp_fh, $fh);\n-\t\t\t\tGit::temp_release($tmp_fh, 1);\n-\t\t\t}\n-\t\t}\n-\n-\t\t$hash = $::_repository->hash_and_insert_object(\n-\t\t\t\tGit::temp_path($fh));\n-\t\t$hash =~ /^[a-f\\d]{40}$/ or die \"not a sha1: $hash\\n\";\n-\n-\t\tGit::temp_release($fb->{base}, 1);\n-\t\tGit::temp_release($fh, 1);\n-\t} else {\n-\t\t$hash = $fb->{blob} or die \"no blob information\\n\";\n-\t}\n-\t$fb->{pool}->clear;\n-\t$self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;\n-\tprint \"\\t$fb->{action}\\t$path\\n\" if $fb->{action} && ! $::_q;\n-\tundef;\n-}\n-\n-sub abort_edit {\n-\tmy $self = shift;\n-\t$self->{nr} = $self->{gii}->{nr};\n-\tdelete $self->{gii};\n-\t$self->SUPER::abort_edit(@_);\n-}\n-\n-sub close_edit {\n-\tmy $self = shift;\n-\n-\tif ($_preserve_empty_dirs) {\n-\t\tmy @empty_dirs;\n-\n-\t\t# Any entry flagged as empty that also has an associated\n-\t\t# dir_prop represents a newly created empty directory.\n-\t\tforeach my $i (keys %{$self->{empty}}) {\n-\t\t\tpush @empty_dirs, $i if exists $self->{dir_prop}->{$i};\n-\t\t}\n-\n-\t\t# Search for directories that have become empty due subsequent\n-\t\t# file deletes.\n-\t\tpush @empty_dirs, $self->find_empty_directories();\n-\n-\t\t# Finally, add a placeholder file to each empty directory.\n-\t\t$self->add_placeholder_file($_) foreach (@empty_dirs);\n-\n-\t\t$self->stash_placeholder_list();\n-\t}\n-\n-\t$self->{git_commit_ok} = 1;\n-\t$self->{nr} = $self->{gii}->{nr};\n-\tdelete $self->{gii};\n-\t$self->SUPER::close_edit(@_);\n-}\n-\n-sub find_empty_directories {\n-\tmy ($self) = @_;\n-\tmy @empty_dirs;\n-\tmy %dirs = map { dirname($_) => 1 } @deleted_gpath;\n-\n-\tforeach my $dir (sort keys %dirs) {\n-\t\tnext if $dir eq \".\";\n-\n-\t\t# If there have been any additions to this directory, there is\n-\t\t# no reason to check if it is empty.\n-\t\tmy $skip_added = 0;\n-\t\tforeach my $t (qw/dir_prop file_prop/) {\n-\t\t\tforeach my $path (keys %{ $self->{$t} }) {\n-\t\t\t\tif (exists $self->{$t}->{dirname($path)}) {\n-\t\t\t\t\t$skip_added = 1;\n-\t\t\t\t\tlast;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tlast if $skip_added;\n-\t\t}\n-\t\tnext if $skip_added;\n-\n-\t\t# Use `git ls-tree` to get the filenames of this directory\n-\t\t# that existed prior to this particular commit.\n-\t\tmy $ls = command('ls-tree', '-z', '--name-only',\n-\t\t\t\t $self->{c}, \"$dir/\");\n-\t\tmy %files = map { $_ => 1 } split(/\\0/, $ls);\n-\n-\t\t# Remove the filenames that were deleted during this commit.\n-\t\tdelete $files{$_} foreach (@deleted_gpath);\n-\n-\t\t# Report the directory if there are no filenames left.\n-\t\tpush @empty_dirs, $dir unless (scalar %files);\n-\t}\n-\t@empty_dirs;\n-}\n-\n-sub add_placeholder_file {\n-\tmy ($self, $dir) = @_;\n-\tmy $path = \"$dir/$_placeholder_filename\";\n-\tmy $gpath = $self->git_path($path);\n-\n-\tmy $fh = $::_repository->temp_acquire($gpath);\n-\tmy $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));\n-\tGit::temp_release($fh, 1);\n-\t$self->{gii}->update('100644', $hash, $gpath) or croak $!;\n-\n-\t# The directory should no longer be considered empty.\n-\tdelete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};\n-\n-\t# Keep track of any placeholder files we create.\n-\t$added_placeholder{$dir} = $path;\n-}\n-\n-sub stash_placeholder_list {\n-\tmy ($self) = @_;\n-\tmy $k = \"svn-remote.$repo_id.added-placeholder\";\n-\tmy $v = eval { command_oneline('config', '--get-all', $k) };\n-\tcommand_noisy('config', '--unset-all', $k) if $v;\n-\tforeach (values %added_placeholder) {\n-\t\tcommand_noisy('config', '--add', $k, $_);\n-\t}\n-}\n-\n package Git::SVN::Editor;\n use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;\n use strict;\ndiff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm\nnew file mode 100644\nindex 00000000..4e9c77d7\n--- /dev/null\n+++ b/perl/Git/SVN/Fetcher.pm\n@@ -0,0 +1,602 @@\n+package Git::SVN::Fetcher;\n+use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename\n+            @deleted_gpath %added_placeholder $repo_id/;\n+use strict;\n+use warnings;\n+use SVN::Delta;\n+use Carp qw/croak/;\n+use File::Basename qw/dirname/;\n+use IO::File qw//;\n+use Git qw/command command_oneline command_noisy command_output_pipe\n+           command_input_pipe command_close_pipe\n+           command_bidi_pipe command_close_bidi_pipe/;\n+BEGIN {\n+\t@ISA = qw(SVN::Delta::Editor);\n+}\n+\n+# file baton members: path, mode_a, mode_b, pool, fh, blob, base\n+sub new {\n+\tmy ($class, $git_svn, $switch_path) = @_;\n+\tmy $self = SVN::Delta::Editor->new;\n+\tbless $self, $class;\n+\tif (exists $git_svn->{last_commit}) {\n+\t\t$self->{c} = $git_svn->{last_commit};\n+\t\t$self->{empty_symlinks} =\n+\t\t                  _mark_empty_symlinks($git_svn, $switch_path);\n+\t}\n+\n+\t# some options are read globally, but can be overridden locally\n+\t# per [svn-remote \"...\"] section.  Command-line options will *NOT*\n+\t# override options set in an [svn-remote \"...\"] section\n+\t$repo_id = $git_svn->{repo_id};\n+\tmy $k = \"svn-remote.$repo_id.ignore-paths\";\n+\tmy $v = eval { command_oneline('config', '--get', $k) };\n+\t$self->{ignore_regex} = $v;\n+\n+\t$k = \"svn-remote.$repo_id.preserve-empty-dirs\";\n+\t$v = eval { command_oneline('config', '--get', '--bool', $k) };\n+\tif ($v && $v eq 'true') {\n+\t\t$_preserve_empty_dirs = 1;\n+\t\t$k = \"svn-remote.$repo_id.placeholder-filename\";\n+\t\t$v = eval { command_oneline('config', '--get', $k) };\n+\t\t$_placeholder_filename = $v;\n+\t}\n+\n+\t# Load the list of placeholder files added during previous invocations.\n+\t$k = \"svn-remote.$repo_id.added-placeholder\";\n+\t$v = eval { command_oneline('config', '--get-all', $k) };\n+\tif ($_preserve_empty_dirs && $v) {\n+\t\t# command() prints errors to stderr, so we only call it if\n+\t\t# command_oneline() succeeded.\n+\t\tmy @v = command('config', '--get-all', $k);\n+\t\t$added_placeholder{ dirname($_) } = $_ foreach @v;\n+\t}\n+\n+\t$self->{empty} = {};\n+\t$self->{dir_prop} = {};\n+\t$self->{file_prop} = {};\n+\t$self->{absent_dir} = {};\n+\t$self->{absent_file} = {};\n+\t$self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });\n+\t$self->{pathnameencoding} = Git::config('svn.pathnameencoding');\n+\t$self;\n+}\n+\n+# this uses the Ra object, so it must be called before do_{switch,update},\n+# not inside them (when the Git::SVN::Fetcher object is passed) to\n+# do_{switch,update}\n+sub _mark_empty_symlinks {\n+\tmy ($git_svn, $switch_path) = @_;\n+\tmy $bool = Git::config_bool('svn.brokenSymlinkWorkaround');\n+\treturn {} if (!defined($bool)) || (defined($bool) && ! $bool);\n+\n+\tmy %ret;\n+\tmy ($rev, $cmt) = $git_svn->last_rev_commit;\n+\treturn {} unless ($rev && $cmt);\n+\n+\t# allow the warning to be printed for each revision we fetch to\n+\t# ensure the user sees it.  The user can also disable the workaround\n+\t# on the repository even while git svn is running and the next\n+\t# revision fetched will skip this expensive function.\n+\tmy $printed_warning;\n+\tchomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);\n+\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);\n+\tlocal $/ = \"\\0\";\n+\tmy $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};\n+\t$pfx .= '/' if length($pfx);\n+\twhile (<$ls>) {\n+\t\tchomp;\n+\t\ts/\\A100644 blob $empty_blob\\t//o or next;\n+\t\tunless ($printed_warning) {\n+\t\t\tprint STDERR \"Scanning for empty symlinks, \",\n+\t\t\t             \"this may take a while if you have \",\n+\t\t\t\t     \"many empty files\\n\",\n+\t\t\t\t     \"You may disable this with `\",\n+\t\t\t\t     \"git config svn.brokenSymlinkWorkaround \",\n+\t\t\t\t     \"false'.\\n\",\n+\t\t\t\t     \"This may be done in a different \",\n+\t\t\t\t     \"terminal without restarting \",\n+\t\t\t\t     \"git svn\\n\";\n+\t\t\t$printed_warning = 1;\n+\t\t}\n+\t\tmy $path = $_;\n+\t\tmy (undef, $props) =\n+\t\t               $git_svn->ra->get_file($pfx.$path, $rev, undef);\n+\t\tif ($props->{'svn:special'}) {\n+\t\t\t$ret{$path} = 1;\n+\t\t}\n+\t}\n+\tcommand_close_pipe($ls, $ctx);\n+\t\\%ret;\n+}\n+\n+# returns true if a given path is inside a \".git\" directory\n+sub in_dot_git {\n+\t$_[0] =~ m{(?:^|/)\\.git(?:/|$)};\n+}\n+\n+# return value: 0 -- don't ignore, 1 -- ignore\n+sub is_path_ignored {\n+\tmy ($self, $path) = @_;\n+\treturn 1 if in_dot_git($path);\n+\treturn 1 if defined($self->{ignore_regex}) &&\n+\t            $path =~ m!$self->{ignore_regex}!;\n+\treturn 0 unless defined($_ignore_regex);\n+\treturn 1 if $path =~ m!$_ignore_regex!o;\n+\treturn 0;\n+}\n+\n+sub set_path_strip {\n+\tmy ($self, $path) = @_;\n+\t$self->{path_strip} = qr/^\\Q$path\\E(\\/|$)/ if length $path;\n+}\n+\n+sub open_root {\n+\t{ path => '' };\n+}\n+\n+sub open_directory {\n+\tmy ($self, $path, $pb, $rev) = @_;\n+\t{ path => $path };\n+}\n+\n+sub git_path {\n+\tmy ($self, $path) = @_;\n+\tif (my $enc = $self->{pathnameencoding}) {\n+\t\trequire Encode;\n+\t\tEncode::from_to($path, 'UTF-8', $enc);\n+\t}\n+\tif ($self->{path_strip}) {\n+\t\t$path =~ s!$self->{path_strip}!! or\n+\t\t  die \"Failed to strip path '$path' ($self->{path_strip})\\n\";\n+\t}\n+\t$path;\n+}\n+\n+sub delete_entry {\n+\tmy ($self, $path, $rev, $pb) = @_;\n+\treturn undef if $self->is_path_ignored($path);\n+\n+\tmy $gpath = $self->git_path($path);\n+\treturn undef if ($gpath eq '');\n+\n+\t# remove entire directories.\n+\tmy ($tree) = (command('ls-tree', '-z', $self->{c}, \"./$gpath\")\n+\t                 =~ /\\A040000 tree ([a-f\\d]{40})\\t\\Q$gpath\\E\\0/);\n+\tif ($tree) {\n+\t\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree\n+\t\t                                     -r --name-only -z/,\n+\t\t\t\t                     $tree);\n+\t\tlocal $/ = \"\\0\";\n+\t\twhile (<$ls>) {\n+\t\t\tchomp;\n+\t\t\tmy $rmpath = \"$gpath/$_\";\n+\t\t\t$self->{gii}->remove($rmpath);\n+\t\t\tprint \"\\tD\\t$rmpath\\n\" unless $::_q;\n+\t\t}\n+\t\tprint \"\\tD\\t$gpath/\\n\" unless $::_q;\n+\t\tcommand_close_pipe($ls, $ctx);\n+\t} else {\n+\t\t$self->{gii}->remove($gpath);\n+\t\tprint \"\\tD\\t$gpath\\n\" unless $::_q;\n+\t}\n+\t# Don't add to @deleted_gpath if we're deleting a placeholder file.\n+\tpush @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};\n+\t$self->{empty}->{$path} = 0;\n+\tundef;\n+}\n+\n+sub open_file {\n+\tmy ($self, $path, $pb, $rev) = @_;\n+\tmy ($mode, $blob);\n+\n+\tgoto out if $self->is_path_ignored($path);\n+\n+\tmy $gpath = $self->git_path($path);\n+\t($mode, $blob) = (command('ls-tree', '-z', $self->{c}, \"./$gpath\")\n+\t                     =~ /\\A(\\d{6}) blob ([a-f\\d]{40})\\t\\Q$gpath\\E\\0/);\n+\tunless (defined $mode && defined $blob) {\n+\t\tdie \"$path was not found in commit $self->{c} (r$rev)\\n\";\n+\t}\n+\tif ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {\n+\t\t$mode = '120000';\n+\t}\n+out:\n+\t{ path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,\n+\t  pool => SVN::Pool->new, action => 'M' };\n+}\n+\n+sub add_file {\n+\tmy ($self, $path, $pb, $cp_path, $cp_rev) = @_;\n+\tmy $mode;\n+\n+\tif (!$self->is_path_ignored($path)) {\n+\t\tmy ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);\n+\t\tdelete $self->{empty}->{$dir};\n+\t\t$mode = '100644';\n+\n+\t\tif ($added_placeholder{$dir}) {\n+\t\t\t# Remove our placeholder file, if we created one.\n+\t\t\tdelete_entry($self, $added_placeholder{$dir})\n+\t\t\t\tunless $path eq $added_placeholder{$dir};\n+\t\t\tdelete $added_placeholder{$dir}\n+\t\t}\n+\t}\n+\n+\t{ path => $path, mode_a => $mode, mode_b => $mode,\n+\t  pool => SVN::Pool->new, action => 'A' };\n+}\n+\n+sub add_directory {\n+\tmy ($self, $path, $cp_path, $cp_rev) = @_;\n+\tgoto out if $self->is_path_ignored($path);\n+\tmy $gpath = $self->git_path($path);\n+\tif ($gpath eq '') {\n+\t\tmy ($ls, $ctx) = command_output_pipe(qw/ls-tree\n+\t\t                                     -r --name-only -z/,\n+\t\t\t\t                     $self->{c});\n+\t\tlocal $/ = \"\\0\";\n+\t\twhile (<$ls>) {\n+\t\t\tchomp;\n+\t\t\t$self->{gii}->remove($_);\n+\t\t\tprint \"\\tD\\t$_\\n\" unless $::_q;\n+\t\t\tpush @deleted_gpath, $gpath;\n+\t\t}\n+\t\tcommand_close_pipe($ls, $ctx);\n+\t\t$self->{empty}->{$path} = 0;\n+\t}\n+\tmy ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);\n+\tdelete $self->{empty}->{$dir};\n+\t$self->{empty}->{$path} = 1;\n+\n+\tif ($added_placeholder{$dir}) {\n+\t\t# Remove our placeholder file, if we created one.\n+\t\tdelete_entry($self, $added_placeholder{$dir});\n+\t\tdelete $added_placeholder{$dir}\n+\t}\n+\n+out:\n+\t{ path => $path };\n+}\n+\n+sub change_dir_prop {\n+\tmy ($self, $db, $prop, $value) = @_;\n+\treturn undef if $self->is_path_ignored($db->{path});\n+\t$self->{dir_prop}->{$db->{path}} ||= {};\n+\t$self->{dir_prop}->{$db->{path}}->{$prop} = $value;\n+\tundef;\n+}\n+\n+sub absent_directory {\n+\tmy ($self, $path, $pb) = @_;\n+\treturn undef if $self->is_path_ignored($path);\n+\t$self->{absent_dir}->{$pb->{path}} ||= [];\n+\tpush @{$self->{absent_dir}->{$pb->{path}}}, $path;\n+\tundef;\n+}\n+\n+sub absent_file {\n+\tmy ($self, $path, $pb) = @_;\n+\treturn undef if $self->is_path_ignored($path);\n+\t$self->{absent_file}->{$pb->{path}} ||= [];\n+\tpush @{$self->{absent_file}->{$pb->{path}}}, $path;\n+\tundef;\n+}\n+\n+sub change_file_prop {\n+\tmy ($self, $fb, $prop, $value) = @_;\n+\treturn undef if $self->is_path_ignored($fb->{path});\n+\tif ($prop eq 'svn:executable') {\n+\t\tif ($fb->{mode_b} != 120000) {\n+\t\t\t$fb->{mode_b} = defined $value ? 100755 : 100644;\n+\t\t}\n+\t} elsif ($prop eq 'svn:special') {\n+\t\t$fb->{mode_b} = defined $value ? 120000 : 100644;\n+\t} else {\n+\t\t$self->{file_prop}->{$fb->{path}} ||= {};\n+\t\t$self->{file_prop}->{$fb->{path}}->{$prop} = $value;\n+\t}\n+\tundef;\n+}\n+\n+sub apply_textdelta {\n+\tmy ($self, $fb, $exp) = @_;\n+\treturn undef if $self->is_path_ignored($fb->{path});\n+\tmy $fh = $::_repository->temp_acquire('svn_delta');\n+\t# $fh gets auto-closed() by SVN::TxDelta::apply(),\n+\t# (but $base does not,) so dup() it for reading in close_file\n+\topen my $dup, '<&', $fh or croak $!;\n+\tmy $base = $::_repository->temp_acquire('git_blob');\n+\n+\tif ($fb->{blob}) {\n+\t\tmy ($base_is_link, $size);\n+\n+\t\tif ($fb->{mode_a} eq '120000' &&\n+\t\t    ! $self->{empty_symlinks}->{$fb->{path}}) {\n+\t\t\tprint $base 'link ' or die \"print $!\\n\";\n+\t\t\t$base_is_link = 1;\n+\t\t}\n+\tretry:\n+\t\t$size = $::_repository->cat_blob($fb->{blob}, $base);\n+\t\tdie \"Failed to read object $fb->{blob}\" if ($size < 0);\n+\n+\t\tif (defined $exp) {\n+\t\t\tseek $base, 0, 0 or croak $!;\n+\t\t\tmy $got = ::md5sum($base);\n+\t\t\tif ($got ne $exp) {\n+\t\t\t\tmy $err = \"Checksum mismatch: \".\n+\t\t\t\t       \"$fb->{path} $fb->{blob}\\n\" .\n+\t\t\t\t       \"expected: $exp\\n\" .\n+\t\t\t\t       \"     got: $got\\n\";\n+\t\t\t\tif ($base_is_link) {\n+\t\t\t\t\twarn $err,\n+\t\t\t\t\t     \"Retrying... (possibly \",\n+\t\t\t\t\t     \"a bad symlink from SVN)\\n\";\n+\t\t\t\t\t$::_repository->temp_reset($base);\n+\t\t\t\t\t$base_is_link = 0;\n+\t\t\t\t\tgoto retry;\n+\t\t\t\t}\n+\t\t\t\tdie $err;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tseek $base, 0, 0 or croak $!;\n+\t$fb->{fh} = $fh;\n+\t$fb->{base} = $base;\n+\t[ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];\n+}\n+\n+sub close_file {\n+\tmy ($self, $fb, $exp) = @_;\n+\treturn undef if $self->is_path_ignored($fb->{path});\n+\n+\tmy $hash;\n+\tmy $path = $self->git_path($fb->{path});\n+\tif (my $fh = $fb->{fh}) {\n+\t\tif (defined $exp) {\n+\t\t\tseek($fh, 0, 0) or croak $!;\n+\t\t\tmy $got = ::md5sum($fh);\n+\t\t\tif ($got ne $exp) {\n+\t\t\t\tdie \"Checksum mismatch: $path\\n\",\n+\t\t\t\t    \"expected: $exp\\n    got: $got\\n\";\n+\t\t\t}\n+\t\t}\n+\t\tif ($fb->{mode_b} == 120000) {\n+\t\t\tsysseek($fh, 0, 0) or croak $!;\n+\t\t\tmy $rd = sysread($fh, my $buf, 5);\n+\n+\t\t\tif (!defined $rd) {\n+\t\t\t\tcroak \"sysread: $!\\n\";\n+\t\t\t} elsif ($rd == 0) {\n+\t\t\t\twarn \"$path has mode 120000\",\n+\t\t\t\t     \" but it points to nothing\\n\",\n+\t\t\t\t     \"converting to an empty file with mode\",\n+\t\t\t\t     \" 100644\\n\";\n+\t\t\t\t$fb->{mode_b} = '100644';\n+\t\t\t} elsif ($buf ne 'link ') {\n+\t\t\t\twarn \"$path has mode 120000\",\n+\t\t\t\t     \" but is not a link\\n\";\n+\t\t\t} else {\n+\t\t\t\tmy $tmp_fh = $::_repository->temp_acquire(\n+\t\t\t\t\t'svn_hash');\n+\t\t\t\tmy $res;\n+\t\t\t\twhile ($res = sysread($fh, my $str, 1024)) {\n+\t\t\t\t\tmy $out = syswrite($tmp_fh, $str, $res);\n+\t\t\t\t\tdefined($out) && $out == $res\n+\t\t\t\t\t\tor croak(\"write \",\n+\t\t\t\t\t\t\tGit::temp_path($tmp_fh),\n+\t\t\t\t\t\t\t\": $!\\n\");\n+\t\t\t\t}\n+\t\t\t\tdefined $res or croak $!;\n+\n+\t\t\t\t($fh, $tmp_fh) = ($tmp_fh, $fh);\n+\t\t\t\tGit::temp_release($tmp_fh, 1);\n+\t\t\t}\n+\t\t}\n+\n+\t\t$hash = $::_repository->hash_and_insert_object(\n+\t\t\t\tGit::temp_path($fh));\n+\t\t$hash =~ /^[a-f\\d]{40}$/ or die \"not a sha1: $hash\\n\";\n+\n+\t\tGit::temp_release($fb->{base}, 1);\n+\t\tGit::temp_release($fh, 1);\n+\t} else {\n+\t\t$hash = $fb->{blob} or die \"no blob information\\n\";\n+\t}\n+\t$fb->{pool}->clear;\n+\t$self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;\n+\tprint \"\\t$fb->{action}\\t$path\\n\" if $fb->{action} && ! $::_q;\n+\tundef;\n+}\n+\n+sub abort_edit {\n+\tmy $self = shift;\n+\t$self->{nr} = $self->{gii}->{nr};\n+\tdelete $self->{gii};\n+\t$self->SUPER::abort_edit(@_);\n+}\n+\n+sub close_edit {\n+\tmy $self = shift;\n+\n+\tif ($_preserve_empty_dirs) {\n+\t\tmy @empty_dirs;\n+\n+\t\t# Any entry flagged as empty that also has an associated\n+\t\t# dir_prop represents a newly created empty directory.\n+\t\tforeach my $i (keys %{$self->{empty}}) {\n+\t\t\tpush @empty_dirs, $i if exists $self->{dir_prop}->{$i};\n+\t\t}\n+\n+\t\t# Search for directories that have become empty due subsequent\n+\t\t# file deletes.\n+\t\tpush @empty_dirs, $self->find_empty_directories();\n+\n+\t\t# Finally, add a placeholder file to each empty directory.\n+\t\t$self->add_placeholder_file($_) foreach (@empty_dirs);\n+\n+\t\t$self->stash_placeholder_list();\n+\t}\n+\n+\t$self->{git_commit_ok} = 1;\n+\t$self->{nr} = $self->{gii}->{nr};\n+\tdelete $self->{gii};\n+\t$self->SUPER::close_edit(@_);\n+}\n+\n+sub find_empty_directories {\n+\tmy ($self) = @_;\n+\tmy @empty_dirs;\n+\tmy %dirs = map { dirname($_) => 1 } @deleted_gpath;\n+\n+\tforeach my $dir (sort keys %dirs) {\n+\t\tnext if $dir eq \".\";\n+\n+\t\t# If there have been any additions to this directory, there is\n+\t\t# no reason to check if it is empty.\n+\t\tmy $skip_added = 0;\n+\t\tforeach my $t (qw/dir_prop file_prop/) {\n+\t\t\tforeach my $path (keys %{ $self->{$t} }) {\n+\t\t\t\tif (exists $self->{$t}->{dirname($path)}) {\n+\t\t\t\t\t$skip_added = 1;\n+\t\t\t\t\tlast;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tlast if $skip_added;\n+\t\t}\n+\t\tnext if $skip_added;\n+\n+\t\t# Use `git ls-tree` to get the filenames of this directory\n+\t\t# that existed prior to this particular commit.\n+\t\tmy $ls = command('ls-tree', '-z', '--name-only',\n+\t\t\t\t $self->{c}, \"$dir/\");\n+\t\tmy %files = map { $_ => 1 } split(/\\0/, $ls);\n+\n+\t\t# Remove the filenames that were deleted during this commit.\n+\t\tdelete $files{$_} foreach (@deleted_gpath);\n+\n+\t\t# Report the directory if there are no filenames left.\n+\t\tpush @empty_dirs, $dir unless (scalar %files);\n+\t}\n+\t@empty_dirs;\n+}\n+\n+sub add_placeholder_file {\n+\tmy ($self, $dir) = @_;\n+\tmy $path = \"$dir/$_placeholder_filename\";\n+\tmy $gpath = $self->git_path($path);\n+\n+\tmy $fh = $::_repository->temp_acquire($gpath);\n+\tmy $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));\n+\tGit::temp_release($fh, 1);\n+\t$self->{gii}->update('100644', $hash, $gpath) or croak $!;\n+\n+\t# The directory should no longer be considered empty.\n+\tdelete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};\n+\n+\t# Keep track of any placeholder files we create.\n+\t$added_placeholder{$dir} = $path;\n+}\n+\n+sub stash_placeholder_list {\n+\tmy ($self) = @_;\n+\tmy $k = \"svn-remote.$repo_id.added-placeholder\";\n+\tmy $v = eval { command_oneline('config', '--get-all', $k) };\n+\tcommand_noisy('config', '--unset-all', $k) if $v;\n+\tforeach (values %added_placeholder) {\n+\t\tcommand_noisy('config', '--add', $k, $_);\n+\t}\n+}\n+\n+1;\n+__END__\n+\n+Git::SVN::Fetcher - tree delta consumer for \"git svn fetch\"\n+\n+=head1 SYNOPSIS\n+\n+    use SVN::Core;\n+    use SVN::Ra;\n+    use Git::SVN;\n+    use Git::SVN::Fetcher;\n+    use Git;\n+\n+    my $gs = Git::SVN->find_by_url($url);\n+    my $ra = SVN::Ra->new(url => $url);\n+    my $editor = Git::SVN::Fetcher->new($gs);\n+    my $reporter = $ra->do_update($SVN::Core::INVALID_REVNUM, '',\n+                                  1, $editor);\n+    $reporter->set_path('', $old_rev, 0);\n+    $reporter->finish_report;\n+    my $tree = $gs->tmp_index_do(sub { command_oneline('write-tree') });\n+\n+    foreach my $path (keys %{$editor->{dir_prop}) {\n+        my $props = $editor->{dir_prop}{$path};\n+        foreach my $prop (keys %$props) {\n+            print \"property $prop at $path changed to $props->{$prop}\\n\";\n+        }\n+    }\n+    foreach my $path (keys %{$editor->{empty}) {\n+        my $action = $editor->{empty}{$path} ? 'added' : 'removed';\n+        print \"empty directory $path $action\\n\";\n+    }\n+    foreach my $path (keys %{$editor->{file_prop}) { ... }\n+    foreach my $parent (keys %{$editor->{absent_dir}}) {\n+        my @children = @{$editor->{abstent_dir}{$parent}};\n+        print \"cannot fetch directory $parent/$_: not authorized?\\n\"\n+            foreach @children;\n+    }\n+    foreach my $parent (keys %{$editor->{absent_file}) { ... }\n+\n+=head1 DESCRIPTION\n+\n+This is a subclass of C<SVN::Delta::Editor>, which means it implements\n+callbacks to act as a consumer of Subversion tree deltas.  This\n+particular implementation of those callbacks is meant to store\n+information about the resulting content which B<git svn fetch> could\n+use to populate new commits and new entries for F<unhandled.log>.\n+More specifically:\n+\n+=over\n+\n+=item * Additions, removals, and modifications of files are propagated\n+to git-svn's index file F<$GIT_DIR/svn/$refname/index> using\n+B<git update-index>.\n+\n+=item * Changes in Subversion path properties are recorded in the\n+C<dir_prop> and C<file_prop> fields (which are hashes).\n+\n+=item * Addition and removal of empty directories are indicated by\n+entries with value 1 and 0 respectively in the C<empty> hash.\n+\n+=item * Paths that are present but cannot be conveyed (presumably due\n+to permissions) are recorded in the C<absent_file> and\n+C<absent_dirs> hashes.  For each key, the corresponding value is\n+a list of paths under that directory that were present but\n+could not be conveyed.\n+\n+=back\n+\n+The interface is unstable.  Do not use this module unless you are\n+developing git-svn.\n+\n+=head1 DEPENDENCIES\n+\n+L<SVN::Delta> from the Subversion perl bindings,\n+the core L<Carp>, L<File::Basename>, and L<IO::File> modules,\n+and git's L<Git> helper module.\n+\n+C<Git::SVN::Fetcher> has not been tested using callers other than\n+B<git-svn> itself.\n+\n+=head1 SEE ALSO\n+\n+L<SVN::Delta>.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+None.\ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 4d8e31d2..424890a1 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -27,6 +27,7 @@ MAKE_FRAG\n my %pm = (\n \t'Git.pm' => '$(INST_LIBDIR)/Git.pm',\n \t'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',\n+\t'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',\n \t'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',\n );\n \n-- \n1.7.10\n"},{"id":"192406","messageId":"20120529002537.GA4575@dcvr.yhbt.net","threadId":"28184","inReplyTo":"20120528003901.GA11103@burratino","subject":"Re: [RFC/PATCH] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2012-05-29T00:25:37Z","receivedAt":"2012-05-29T00:25:37Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Eric Wong wrote:\n> > Jonathan Nieder <jrnieder@gmail.com> wrote:\n> \n> >> +package Git::SVN::Memoize::YAML;\n> >\n> > Can we use this as an opportunity to start splitting git-svn.perl into\n> > multiple .pm files?\n> \n> Not a bad idea.  I've included an example patch to sanity-check the\n> approach below.\n\nThank you.  I've signed-off and pushed that and your other two out to\n\"master\" on git://bogomips.org/git-svn\n\n> >> +\tmy $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};\n> >\n> >> +\tYAML::Any::DumpFile($self->{FILENAME}, $self->{H});\n> >\n> > These should die on errors, right?\n> \n> At least in YAML::Old, they use Carp::croak.  Maybe something like\n> \n> \tlocal @CARP_NOT = qw(YAML::Any);\n> \n> to blame the caller for the error would bring sanity.\n\nInteresting, didn't know about @CARP_NOT.  It's barely documented in\non my Debian stable install(s), but it seems to be available since 5.8.\nWe should be able to use it without breaking compatibility.\n\n> >> +=head1 BUGS\n> >\n> >> +Error handling is awkward.\n> >\n> > How so?\n> \n> I mostly meant that it's not obvious what the state of %hash is at the\n> point marked with (*) below:\n> \n> \tif (not eval {\n> \t\ttie my %hash => 'Foo::Bar', @params;\n> \t\t1;\n> \t}) {\n> \t\tmy $err = $@ ||\n> \t\t\t# a destructor might have clobbered $@\n> \t\t\t\"Zombie error\";\n> \t\tdie $err if worth_dying($err);\n> \n> \t\t(*) ... try to recover ...\n> \t}\n> \n> That's not specific to Memoize::YAML, though.  It probably is not\n> awkward for wizards who know the details. :)\n\nOK, so we're no better or worse off than we were before :)\n"},{"id":"192437","messageId":"7vk3zu3e8i.fsf@alter.siamese.dyndns.org","threadId":"28184","inReplyTo":"20120529002537.GA4575@dcvr.yhbt.net","subject":"Re: [RFC/PATCH] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-05-29T20:48:45Z","receivedAt":"2012-05-29T20:48:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Wong <normalperson@yhbt.net> writes:\n\n> Jonathan Nieder <jrnieder@gmail.com> wrote:\n>> Eric Wong wrote:\n>> > Jonathan Nieder <jrnieder@gmail.com> wrote:\n>> \n>> >> +package Git::SVN::Memoize::YAML;\n>> >\n>> > Can we use this as an opportunity to start splitting git-svn.perl into\n>> > multiple .pm files?\n>> \n>> Not a bad idea.  I've included an example patch to sanity-check the\n>> approach below.\n>\n> Thank you.  I've signed-off and pushed that and your other two out to\n> \"master\" on git://bogomips.org/git-svn\n\nOK, I'll pull it before tagging -rc1.\n\nThanks.\n"},{"id":"193219","messageId":"20120609222039.GD28412@burratino","threadId":"28184","inReplyTo":"20120527201450.GA3630@dcvr.yhbt.net","subject":"[PATCH v2 0/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-09T22:20:39Z","receivedAt":"2012-06-09T22:20:39Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric Wong wrote:\n\n> Seems reasonable, a few comments below.\n\nThanks again.\n\n[...]\n> These should die on errors, right?\n\nYep, they do.  I didn't bother doing the CARP_NOT thing --- let's wait\nfor a bug report or rainy day and then handle errors properly (by\ninvalidating the cache when appropriate).\n\nPatches 1-2 are independent from patch 3.  I'm sending them this way\nfor no particular reason.\n\nThoughts of all kinds welcome, as usual.\n\nJonathan Nieder (3):\n  git-svn: make Git::SVN::Editor a separate file\n  git-svn: make Git::SVN::RA a separate file\n  git-svn: use YAML format for mergeinfo cache when possible\n\n git-svn.perl                 | 1115 ++----------------------------------------\n perl/Git/SVN/Editor.pm       |  536 ++++++++++++++++++++\n perl/Git/SVN/Fetcher.pm      |    3 +-\n perl/Git/SVN/Memoize/YAML.pm |   93 ++++\n perl/Git/SVN/Ra.pm           |  658 +++++++++++++++++++++++++\n perl/Makefile.PL             |    3 +\n 6 files changed, 1320 insertions(+), 1088 deletions(-)\n create mode 100644 perl/Git/SVN/Editor.pm\n create mode 100644 perl/Git/SVN/Memoize/YAML.pm\n create mode 100644 perl/Git/SVN/Ra.pm\n"},{"id":"193220","messageId":"20120609222556.GE28412@burratino","threadId":"28184","inReplyTo":"20120609222039.GD28412@burratino","subject":"[PATCH 1/3] git-svn: make Git::SVN::Editor a separate file","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-09T22:25:56Z","receivedAt":"2012-06-09T22:25:56Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"This makes the git-svn script shorter and less scary for beginners to\nread through for the first time.  Take the opportunity to explain the\npurpose and basic interface of the Git::SVN::Editor class while at it.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl            |  472 +----------------------------------------\n perl/Git/SVN/Editor.pm  |  536 +++++++++++++++++++++++++++++++++++++++++++++++\n perl/Git/SVN/Fetcher.pm |    3 +-\n perl/Makefile.PL        |    1 +\n 4 files changed, 541 insertions(+), 471 deletions(-)\n create mode 100644 perl/Git/SVN/Editor.pm\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex 7870cc15..7cec4416 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -68,7 +68,6 @@ sub _req_svn {\n }\n my $can_compress = eval { require Compress::Zlib; 1};\n push @Git::SVN::Ra::ISA, 'SVN::Ra';\n-push @Git::SVN::Editor::ISA, 'SVN::Delta::Editor';\n use Carp qw/croak/;\n use Digest::MD5;\n use IO::File qw//;\n@@ -79,6 +78,7 @@ use File::Find;\n use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;\n use IPC::Open3;\n use Git;\n+use Git::SVN::Editor qw//;\n use Git::SVN::Fetcher qw//;\n use Git::SVN::Prompt qw//;\n use Memoize;  # core since 5.8.0, Jul 2002\n@@ -89,8 +89,7 @@ BEGIN {\n \tforeach (qw/command command_oneline command_noisy command_output_pipe\n \t            command_input_pipe command_close_pipe\n \t            command_bidi_pipe command_close_bidi_pipe/) {\n-\t\tfor my $package ( qw(Git::SVN::Editor\n-\t\t\tGit::SVN::Migration Git::SVN::Log Git::SVN),\n+\t\tfor my $package ( qw(Git::SVN::Migration Git::SVN::Log Git::SVN),\n \t\t\t__PACKAGE__) {\n \t\t\t*{\"${package}::$_\"} = \\&{\"Git::$_\"};\n \t\t}\n@@ -1066,7 +1065,6 @@ sub cmd_branch {\n \t\t            \" with the --destination argument.\\n\";\n \t\t}\n \t\tforeach my $g (@{$allglobs}) {\n-\t\t\t# Git::SVN::Editor could probably be moved to Git.pm..\n \t\t\tmy $re = Git::SVN::Editor::glob2pat($g->{path}->{left});\n \t\t\tif ($_branch_dest =~ /$re/) {\n \t\t\t\t$glob = $g;\n@@ -4328,472 +4326,6 @@ sub remove_username {\n \t$_[0] =~ s{^([^:]*://)[^@]+@}{$1};\n }\n \n-package Git::SVN::Editor;\n-use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;\n-use strict;\n-use warnings;\n-use Carp qw/croak/;\n-use IO::File;\n-\n-sub new {\n-\tmy ($class, $opts) = @_;\n-\tforeach (qw/svn_path r ra tree_a tree_b log editor_cb/) {\n-\t\tdie \"$_ required!\\n\" unless (defined $opts->{$_});\n-\t}\n-\n-\tmy $pool = SVN::Pool->new;\n-\tmy $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});\n-\tmy $types = check_diff_paths($opts->{ra}, $opts->{svn_path},\n-\t                             $opts->{r}, $mods);\n-\n-\t# $opts->{ra} functions should not be used after this:\n-\tmy @ce  = $opts->{ra}->get_commit_editor($opts->{log},\n-\t                                        $opts->{editor_cb}, $pool);\n-\tmy $self = SVN::Delta::Editor->new(@ce, $pool);\n-\tbless $self, $class;\n-\tforeach (qw/svn_path r tree_a tree_b/) {\n-\t\t$self->{$_} = $opts->{$_};\n-\t}\n-\t$self->{url} = $opts->{ra}->{url};\n-\t$self->{mods} = $mods;\n-\t$self->{types} = $types;\n-\t$self->{pool} = $pool;\n-\t$self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };\n-\t$self->{rm} = { };\n-\t$self->{path_prefix} = length $self->{svn_path} ?\n-\t                       \"$self->{svn_path}/\" : '';\n-\t$self->{config} = $opts->{config};\n-\t$self->{mergeinfo} = $opts->{mergeinfo};\n-\treturn $self;\n-}\n-\n-sub generate_diff {\n-\tmy ($tree_a, $tree_b) = @_;\n-\tmy @diff_tree = qw(diff-tree -z -r);\n-\tif ($_cp_similarity) {\n-\t\tpush @diff_tree, \"-C$_cp_similarity\";\n-\t} else {\n-\t\tpush @diff_tree, '-C';\n-\t}\n-\tpush @diff_tree, '--find-copies-harder' if $_find_copies_harder;\n-\tpush @diff_tree, \"-l$_rename_limit\" if defined $_rename_limit;\n-\tpush @diff_tree, $tree_a, $tree_b;\n-\tmy ($diff_fh, $ctx) = command_output_pipe(@diff_tree);\n-\tlocal $/ = \"\\0\";\n-\tmy $state = 'meta';\n-\tmy @mods;\n-\twhile (<$diff_fh>) {\n-\t\tchomp $_; # this gets rid of the trailing \"\\0\"\n-\t\tif ($state eq 'meta' && /^:(\\d{6})\\s(\\d{6})\\s\n-\t\t\t\t\t($::sha1)\\s($::sha1)\\s\n-\t\t\t\t\t([MTCRAD])\\d*$/xo) {\n-\t\t\tpush @mods, {\tmode_a => $1, mode_b => $2,\n-\t\t\t\t\tsha1_a => $3, sha1_b => $4,\n-\t\t\t\t\tchg => $5 };\n-\t\t\tif ($5 =~ /^(?:C|R)$/) {\n-\t\t\t\t$state = 'file_a';\n-\t\t\t} else {\n-\t\t\t\t$state = 'file_b';\n-\t\t\t}\n-\t\t} elsif ($state eq 'file_a') {\n-\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n-\t\t\tif ($x->{chg} !~ /^(?:C|R)$/) {\n-\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n-\t\t\t}\n-\t\t\t$x->{file_a} = $_;\n-\t\t\t$state = 'file_b';\n-\t\t} elsif ($state eq 'file_b') {\n-\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n-\t\t\tif (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {\n-\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n-\t\t\t}\n-\t\t\tif (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {\n-\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n-\t\t\t}\n-\t\t\t$x->{file_b} = $_;\n-\t\t\t$state = 'meta';\n-\t\t} else {\n-\t\t\tcroak \"Error parsing $_\\n\";\n-\t\t}\n-\t}\n-\tcommand_close_pipe($diff_fh, $ctx);\n-\t\\@mods;\n-}\n-\n-sub check_diff_paths {\n-\tmy ($ra, $pfx, $rev, $mods) = @_;\n-\tmy %types;\n-\t$pfx .= '/' if length $pfx;\n-\n-\tsub type_diff_paths {\n-\t\tmy ($ra, $types, $path, $rev) = @_;\n-\t\tmy @p = split m#/+#, $path;\n-\t\tmy $c = shift @p;\n-\t\tunless (defined $types->{$c}) {\n-\t\t\t$types->{$c} = $ra->check_path($c, $rev);\n-\t\t}\n-\t\twhile (@p) {\n-\t\t\t$c .= '/' . shift @p;\n-\t\t\tnext if defined $types->{$c};\n-\t\t\t$types->{$c} = $ra->check_path($c, $rev);\n-\t\t}\n-\t}\n-\n-\tforeach my $m (@$mods) {\n-\t\tforeach my $f (qw/file_a file_b/) {\n-\t\t\tnext unless defined $m->{$f};\n-\t\t\tmy ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);\n-\t\t\tif (length $pfx.$dir && ! defined $types{$dir}) {\n-\t\t\t\ttype_diff_paths($ra, \\%types, $pfx.$dir, $rev);\n-\t\t\t}\n-\t\t}\n-\t}\n-\t\\%types;\n-}\n-\n-sub split_path {\n-\treturn ($_[0] =~ m#^(.*?)/?([^/]+)$#);\n-}\n-\n-sub repo_path {\n-\tmy ($self, $path) = @_;\n-\tif (my $enc = $self->{pathnameencoding}) {\n-\t\trequire Encode;\n-\t\tEncode::from_to($path, $enc, 'UTF-8');\n-\t}\n-\t$self->{path_prefix}.(defined $path ? $path : '');\n-}\n-\n-sub url_path {\n-\tmy ($self, $path) = @_;\n-\tif ($self->{url} =~ m#^https?://#) {\n-\t\t$path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf(\"%%%02x\",ord($1))!eg;\n-\t}\n-\t$self->{url} . '/' . $self->repo_path($path);\n-}\n-\n-sub rmdirs {\n-\tmy ($self) = @_;\n-\tmy $rm = $self->{rm};\n-\tdelete $rm->{''}; # we never delete the url we're tracking\n-\treturn unless %$rm;\n-\n-\tforeach (keys %$rm) {\n-\t\tmy @d = split m#/#, $_;\n-\t\tmy $c = shift @d;\n-\t\t$rm->{$c} = 1;\n-\t\twhile (@d) {\n-\t\t\t$c .= '/' . shift @d;\n-\t\t\t$rm->{$c} = 1;\n-\t\t}\n-\t}\n-\tdelete $rm->{$self->{svn_path}};\n-\tdelete $rm->{''}; # we never delete the url we're tracking\n-\treturn unless %$rm;\n-\n-\tmy ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,\n-\t                                     $self->{tree_b});\n-\tlocal $/ = \"\\0\";\n-\twhile (<$fh>) {\n-\t\tchomp;\n-\t\tmy @dn = split m#/#, $_;\n-\t\twhile (pop @dn) {\n-\t\t\tdelete $rm->{join '/', @dn};\n-\t\t}\n-\t\tunless (%$rm) {\n-\t\t\tclose $fh;\n-\t\t\treturn;\n-\t\t}\n-\t}\n-\tcommand_close_pipe($fh, $ctx);\n-\n-\tmy ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});\n-\tforeach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {\n-\t\t$self->close_directory($bat->{$d}, $p);\n-\t\tmy ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);\n-\t\tprint \"\\tD+\\t$d/\\n\" unless $::_q;\n-\t\t$self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);\n-\t\tdelete $bat->{$d};\n-\t}\n-}\n-\n-sub open_or_add_dir {\n-\tmy ($self, $full_path, $baton, $deletions) = @_;\n-\tmy $t = $self->{types}->{$full_path};\n-\tif (!defined $t) {\n-\t\tdie \"$full_path not known in r$self->{r} or we have a bug!\\n\";\n-\t}\n-\t{\n-\t\tno warnings 'once';\n-\t\t# SVN::Node::none and SVN::Node::file are used only once,\n-\t\t# so we're shutting up Perl's warnings about them.\n-\t\tif ($t == $SVN::Node::none || defined($deletions->{$full_path})) {\n-\t\t\treturn $self->add_directory($full_path, $baton,\n-\t\t\t    undef, -1, $self->{pool});\n-\t\t} elsif ($t == $SVN::Node::dir) {\n-\t\t\treturn $self->open_directory($full_path, $baton,\n-\t\t\t    $self->{r}, $self->{pool});\n-\t\t} # no warnings 'once'\n-\t\tprint STDERR \"$full_path already exists in repository at \",\n-\t\t    \"r$self->{r} and it is not a directory (\",\n-\t\t    ($t == $SVN::Node::file ? 'file' : 'unknown'),\"/$t)\\n\";\n-\t} # no warnings 'once'\n-\texit 1;\n-}\n-\n-sub ensure_path {\n-\tmy ($self, $path, $deletions) = @_;\n-\tmy $bat = $self->{bat};\n-\tmy $repo_path = $self->repo_path($path);\n-\treturn $bat->{''} unless (length $repo_path);\n-\n-\tmy @p = split m#/+#, $repo_path;\n-\tmy $c = shift @p;\n-\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}, $deletions);\n-\twhile (@p) {\n-\t\tmy $c0 = $c;\n-\t\t$c .= '/' . shift @p;\n-\t\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0}, $deletions);\n-\t}\n-\treturn $bat->{$c};\n-}\n-\n-# Subroutine to convert a globbing pattern to a regular expression.\n-# From perl cookbook.\n-sub glob2pat {\n-\tmy $globstr = shift;\n-\tmy %patmap = ('*' => '.*', '?' => '.', '[' => '[', ']' => ']');\n-\t$globstr =~ s{(.)} { $patmap{$1} || \"\\Q$1\" }ge;\n-\treturn '^' . $globstr . '$';\n-}\n-\n-sub check_autoprop {\n-\tmy ($self, $pattern, $properties, $file, $fbat) = @_;\n-\t# Convert the globbing pattern to a regular expression.\n-\tmy $regex = glob2pat($pattern);\n-\t# Check if the pattern matches the file name.\n-\tif($file =~ m/($regex)/) {\n-\t\t# Parse the list of properties to set.\n-\t\tmy @props = split(/;/, $properties);\n-\t\tforeach my $prop (@props) {\n-\t\t\t# Parse 'name=value' syntax and set the property.\n-\t\t\tif ($prop =~ /([^=]+)=(.*)/) {\n-\t\t\t\tmy ($n,$v) = ($1,$2);\n-\t\t\t\tfor ($n, $v) {\n-\t\t\t\t\ts/^\\s+//; s/\\s+$//;\n-\t\t\t\t}\n-\t\t\t\t$self->change_file_prop($fbat, $n, $v);\n-\t\t\t}\n-\t\t}\n-\t}\n-}\n-\n-sub apply_autoprops {\n-\tmy ($self, $file, $fbat) = @_;\n-\tmy $conf_t = ${$self->{config}}{'config'};\n-\tno warnings 'once';\n-\t# Check [miscellany]/enable-auto-props in svn configuration.\n-\tif (SVN::_Core::svn_config_get_bool(\n-\t\t$conf_t,\n-\t\t$SVN::_Core::SVN_CONFIG_SECTION_MISCELLANY,\n-\t\t$SVN::_Core::SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS,\n-\t\t0)) {\n-\t\t# Auto-props are enabled.  Enumerate them to look for matches.\n-\t\tmy $callback = sub {\n-\t\t\t$self->check_autoprop($_[0], $_[1], $file, $fbat);\n-\t\t};\n-\t\tSVN::_Core::svn_config_enumerate(\n-\t\t\t$conf_t,\n-\t\t\t$SVN::_Core::SVN_CONFIG_SECTION_AUTO_PROPS,\n-\t\t\t$callback);\n-\t}\n-}\n-\n-sub A {\n-\tmy ($self, $m, $deletions) = @_;\n-\tmy ($dir, $file) = split_path($m->{file_b});\n-\tmy $pbat = $self->ensure_path($dir, $deletions);\n-\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n-\t\t\t\t\tundef, -1);\n-\tprint \"\\tA\\t$m->{file_b}\\n\" unless $::_q;\n-\t$self->apply_autoprops($file, $fbat);\n-\t$self->chg_file($fbat, $m);\n-\t$self->close_file($fbat,undef,$self->{pool});\n-}\n-\n-sub C {\n-\tmy ($self, $m, $deletions) = @_;\n-\tmy ($dir, $file) = split_path($m->{file_b});\n-\tmy $pbat = $self->ensure_path($dir, $deletions);\n-\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n-\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n-\tprint \"\\tC\\t$m->{file_a} => $m->{file_b}\\n\" unless $::_q;\n-\t$self->chg_file($fbat, $m);\n-\t$self->close_file($fbat,undef,$self->{pool});\n-}\n-\n-sub delete_entry {\n-\tmy ($self, $path, $pbat) = @_;\n-\tmy $rpath = $self->repo_path($path);\n-\tmy ($dir, $file) = split_path($rpath);\n-\t$self->{rm}->{$dir} = 1;\n-\t$self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});\n-}\n-\n-sub R {\n-\tmy ($self, $m, $deletions) = @_;\n-\tmy ($dir, $file) = split_path($m->{file_b});\n-\tmy $pbat = $self->ensure_path($dir, $deletions);\n-\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n-\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n-\tprint \"\\tR\\t$m->{file_a} => $m->{file_b}\\n\" unless $::_q;\n-\t$self->apply_autoprops($file, $fbat);\n-\t$self->chg_file($fbat, $m);\n-\t$self->close_file($fbat,undef,$self->{pool});\n-\n-\t($dir, $file) = split_path($m->{file_a});\n-\t$pbat = $self->ensure_path($dir, $deletions);\n-\t$self->delete_entry($m->{file_a}, $pbat);\n-}\n-\n-sub M {\n-\tmy ($self, $m, $deletions) = @_;\n-\tmy ($dir, $file) = split_path($m->{file_b});\n-\tmy $pbat = $self->ensure_path($dir, $deletions);\n-\tmy $fbat = $self->open_file($self->repo_path($m->{file_b}),\n-\t\t\t\t$pbat,$self->{r},$self->{pool});\n-\tprint \"\\t$m->{chg}\\t$m->{file_b}\\n\" unless $::_q;\n-\t$self->chg_file($fbat, $m);\n-\t$self->close_file($fbat,undef,$self->{pool});\n-}\n-\n-sub T { shift->M(@_) }\n-\n-sub change_file_prop {\n-\tmy ($self, $fbat, $pname, $pval) = @_;\n-\t$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});\n-}\n-\n-sub change_dir_prop {\n-\tmy ($self, $pbat, $pname, $pval) = @_;\n-\t$self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});\n-}\n-\n-sub _chg_file_get_blob ($$$$) {\n-\tmy ($self, $fbat, $m, $which) = @_;\n-\tmy $fh = $::_repository->temp_acquire(\"git_blob_$which\");\n-\tif ($m->{\"mode_$which\"} =~ /^120/) {\n-\t\tprint $fh 'link ' or croak $!;\n-\t\t$self->change_file_prop($fbat,'svn:special','*');\n-\t} elsif ($m->{mode_a} =~ /^120/ && $m->{\"mode_$which\"} !~ /^120/) {\n-\t\t$self->change_file_prop($fbat,'svn:special',undef);\n-\t}\n-\tmy $blob = $m->{\"sha1_$which\"};\n-\treturn ($fh,) if ($blob =~ /^0{40}$/);\n-\tmy $size = $::_repository->cat_blob($blob, $fh);\n-\tcroak \"Failed to read object $blob\" if ($size < 0);\n-\t$fh->flush == 0 or croak $!;\n-\tseek $fh, 0, 0 or croak $!;\n-\n-\tmy $exp = ::md5sum($fh);\n-\tseek $fh, 0, 0 or croak $!;\n-\treturn ($fh, $exp);\n-}\n-\n-sub chg_file {\n-\tmy ($self, $fbat, $m) = @_;\n-\tif ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {\n-\t\t$self->change_file_prop($fbat,'svn:executable','*');\n-\t} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {\n-\t\t$self->change_file_prop($fbat,'svn:executable',undef);\n-\t}\n-\tmy ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';\n-\tmy ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';\n-\tmy $pool = SVN::Pool->new;\n-\tmy $atd = $self->apply_textdelta($fbat, $exp_a, $pool);\n-\tif (-s $fh_a) {\n-\t\tmy $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);\n-\t\tmy $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);\n-\t\tif (defined $res) {\n-\t\t\tdie \"Unexpected result from send_txstream: $res\\n\",\n-\t\t\t    \"(SVN::Core::VERSION: $SVN::Core::VERSION)\\n\";\n-\t\t}\n-\t} else {\n-\t\tmy $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);\n-\t\tdie \"Checksum mismatch\\nexpected: $exp_b\\ngot: $got\\n\"\n-\t\t    if ($got ne $exp_b);\n-\t}\n-\tGit::temp_release($fh_b, 1);\n-\tGit::temp_release($fh_a, 1);\n-\t$pool->clear;\n-}\n-\n-sub D {\n-\tmy ($self, $m, $deletions) = @_;\n-\tmy ($dir, $file) = split_path($m->{file_b});\n-\tmy $pbat = $self->ensure_path($dir, $deletions);\n-\tprint \"\\tD\\t$m->{file_b}\\n\" unless $::_q;\n-\t$self->delete_entry($m->{file_b}, $pbat);\n-}\n-\n-sub close_edit {\n-\tmy ($self) = @_;\n-\tmy ($p,$bat) = ($self->{pool}, $self->{bat});\n-\tforeach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {\n-\t\tnext if $_ eq '';\n-\t\t$self->close_directory($bat->{$_}, $p);\n-\t}\n-\t$self->close_directory($bat->{''}, $p);\n-\t$self->SUPER::close_edit($p);\n-\t$p->clear;\n-}\n-\n-sub abort_edit {\n-\tmy ($self) = @_;\n-\t$self->SUPER::abort_edit($self->{pool});\n-}\n-\n-sub DESTROY {\n-\tmy $self = shift;\n-\t$self->SUPER::DESTROY(@_);\n-\t$self->{pool}->clear;\n-}\n-\n-# this drives the editor\n-sub apply_diff {\n-\tmy ($self) = @_;\n-\tmy $mods = $self->{mods};\n-\tmy %o = ( D => 0, C => 1, R => 2, A => 3, M => 4, T => 5 );\n-\tmy %deletions;\n-\n-\tforeach my $m (@$mods) {\n-\t\tif ($m->{chg} eq \"D\") {\n-\t\t\t$deletions{$m->{file_b}} = 1;\n-\t\t}\n-\t}\n-\n-\tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n-\t\tmy $f = $m->{chg};\n-\t\tif (defined $o{$f}) {\n-\t\t\t$self->$f($m, \\%deletions);\n-\t\t} else {\n-\t\t\tfatal(\"Invalid change type: $f\");\n-\t\t}\n-\t}\n-\n-\tif (defined($self->{mergeinfo})) {\n-\t\t$self->change_dir_prop($self->{bat}{''}, \"svn:mergeinfo\",\n-\t\t\t               $self->{mergeinfo});\n-\t}\n-\t$self->rmdirs if $_rmdir;\n-\tif (@$mods == 0 && !defined($self->{mergeinfo})) {\n-\t\t$self->abort_edit;\n-\t} else {\n-\t\t$self->close_edit;\n-\t}\n-\treturn scalar @$mods;\n-}\n-\n package Git::SVN::Ra;\n use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;\n use strict;\ndiff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm\nnew file mode 100644\nindex 00000000..755092fd\n--- /dev/null\n+++ b/perl/Git/SVN/Editor.pm\n@@ -0,0 +1,536 @@\n+package Git::SVN::Editor;\n+use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;\n+use strict;\n+use warnings;\n+use SVN::Core;\n+use SVN::Delta;\n+use Carp qw/croak/;\n+use IO::File;\n+use Git qw/command command_oneline command_noisy command_output_pipe\n+           command_input_pipe command_close_pipe\n+           command_bidi_pipe command_close_bidi_pipe/;\n+BEGIN {\n+\t@ISA = qw(SVN::Delta::Editor);\n+}\n+\n+sub new {\n+\tmy ($class, $opts) = @_;\n+\tforeach (qw/svn_path r ra tree_a tree_b log editor_cb/) {\n+\t\tdie \"$_ required!\\n\" unless (defined $opts->{$_});\n+\t}\n+\n+\tmy $pool = SVN::Pool->new;\n+\tmy $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});\n+\tmy $types = check_diff_paths($opts->{ra}, $opts->{svn_path},\n+\t                             $opts->{r}, $mods);\n+\n+\t# $opts->{ra} functions should not be used after this:\n+\tmy @ce  = $opts->{ra}->get_commit_editor($opts->{log},\n+\t                                        $opts->{editor_cb}, $pool);\n+\tmy $self = SVN::Delta::Editor->new(@ce, $pool);\n+\tbless $self, $class;\n+\tforeach (qw/svn_path r tree_a tree_b/) {\n+\t\t$self->{$_} = $opts->{$_};\n+\t}\n+\t$self->{url} = $opts->{ra}->{url};\n+\t$self->{mods} = $mods;\n+\t$self->{types} = $types;\n+\t$self->{pool} = $pool;\n+\t$self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };\n+\t$self->{rm} = { };\n+\t$self->{path_prefix} = length $self->{svn_path} ?\n+\t                       \"$self->{svn_path}/\" : '';\n+\t$self->{config} = $opts->{config};\n+\t$self->{mergeinfo} = $opts->{mergeinfo};\n+\treturn $self;\n+}\n+\n+sub generate_diff {\n+\tmy ($tree_a, $tree_b) = @_;\n+\tmy @diff_tree = qw(diff-tree -z -r);\n+\tif ($_cp_similarity) {\n+\t\tpush @diff_tree, \"-C$_cp_similarity\";\n+\t} else {\n+\t\tpush @diff_tree, '-C';\n+\t}\n+\tpush @diff_tree, '--find-copies-harder' if $_find_copies_harder;\n+\tpush @diff_tree, \"-l$_rename_limit\" if defined $_rename_limit;\n+\tpush @diff_tree, $tree_a, $tree_b;\n+\tmy ($diff_fh, $ctx) = command_output_pipe(@diff_tree);\n+\tlocal $/ = \"\\0\";\n+\tmy $state = 'meta';\n+\tmy @mods;\n+\twhile (<$diff_fh>) {\n+\t\tchomp $_; # this gets rid of the trailing \"\\0\"\n+\t\tif ($state eq 'meta' && /^:(\\d{6})\\s(\\d{6})\\s\n+\t\t\t\t\t($::sha1)\\s($::sha1)\\s\n+\t\t\t\t\t([MTCRAD])\\d*$/xo) {\n+\t\t\tpush @mods, {\tmode_a => $1, mode_b => $2,\n+\t\t\t\t\tsha1_a => $3, sha1_b => $4,\n+\t\t\t\t\tchg => $5 };\n+\t\t\tif ($5 =~ /^(?:C|R)$/) {\n+\t\t\t\t$state = 'file_a';\n+\t\t\t} else {\n+\t\t\t\t$state = 'file_b';\n+\t\t\t}\n+\t\t} elsif ($state eq 'file_a') {\n+\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n+\t\t\tif ($x->{chg} !~ /^(?:C|R)$/) {\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n+\t\t\t}\n+\t\t\t$x->{file_a} = $_;\n+\t\t\t$state = 'file_b';\n+\t\t} elsif ($state eq 'file_b') {\n+\t\t\tmy $x = $mods[$#mods] or croak \"Empty array\\n\";\n+\t\t\tif (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n+\t\t\t}\n+\t\t\tif (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {\n+\t\t\t\tcroak \"Error parsing $_, $x->{chg}\\n\";\n+\t\t\t}\n+\t\t\t$x->{file_b} = $_;\n+\t\t\t$state = 'meta';\n+\t\t} else {\n+\t\t\tcroak \"Error parsing $_\\n\";\n+\t\t}\n+\t}\n+\tcommand_close_pipe($diff_fh, $ctx);\n+\t\\@mods;\n+}\n+\n+sub check_diff_paths {\n+\tmy ($ra, $pfx, $rev, $mods) = @_;\n+\tmy %types;\n+\t$pfx .= '/' if length $pfx;\n+\n+\tsub type_diff_paths {\n+\t\tmy ($ra, $types, $path, $rev) = @_;\n+\t\tmy @p = split m#/+#, $path;\n+\t\tmy $c = shift @p;\n+\t\tunless (defined $types->{$c}) {\n+\t\t\t$types->{$c} = $ra->check_path($c, $rev);\n+\t\t}\n+\t\twhile (@p) {\n+\t\t\t$c .= '/' . shift @p;\n+\t\t\tnext if defined $types->{$c};\n+\t\t\t$types->{$c} = $ra->check_path($c, $rev);\n+\t\t}\n+\t}\n+\n+\tforeach my $m (@$mods) {\n+\t\tforeach my $f (qw/file_a file_b/) {\n+\t\t\tnext unless defined $m->{$f};\n+\t\t\tmy ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);\n+\t\t\tif (length $pfx.$dir && ! defined $types{$dir}) {\n+\t\t\t\ttype_diff_paths($ra, \\%types, $pfx.$dir, $rev);\n+\t\t\t}\n+\t\t}\n+\t}\n+\t\\%types;\n+}\n+\n+sub split_path {\n+\treturn ($_[0] =~ m#^(.*?)/?([^/]+)$#);\n+}\n+\n+sub repo_path {\n+\tmy ($self, $path) = @_;\n+\tif (my $enc = $self->{pathnameencoding}) {\n+\t\trequire Encode;\n+\t\tEncode::from_to($path, $enc, 'UTF-8');\n+\t}\n+\t$self->{path_prefix}.(defined $path ? $path : '');\n+}\n+\n+sub url_path {\n+\tmy ($self, $path) = @_;\n+\tif ($self->{url} =~ m#^https?://#) {\n+\t\t$path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf(\"%%%02x\",ord($1))!eg;\n+\t}\n+\t$self->{url} . '/' . $self->repo_path($path);\n+}\n+\n+sub rmdirs {\n+\tmy ($self) = @_;\n+\tmy $rm = $self->{rm};\n+\tdelete $rm->{''}; # we never delete the url we're tracking\n+\treturn unless %$rm;\n+\n+\tforeach (keys %$rm) {\n+\t\tmy @d = split m#/#, $_;\n+\t\tmy $c = shift @d;\n+\t\t$rm->{$c} = 1;\n+\t\twhile (@d) {\n+\t\t\t$c .= '/' . shift @d;\n+\t\t\t$rm->{$c} = 1;\n+\t\t}\n+\t}\n+\tdelete $rm->{$self->{svn_path}};\n+\tdelete $rm->{''}; # we never delete the url we're tracking\n+\treturn unless %$rm;\n+\n+\tmy ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,\n+\t                                     $self->{tree_b});\n+\tlocal $/ = \"\\0\";\n+\twhile (<$fh>) {\n+\t\tchomp;\n+\t\tmy @dn = split m#/#, $_;\n+\t\twhile (pop @dn) {\n+\t\t\tdelete $rm->{join '/', @dn};\n+\t\t}\n+\t\tunless (%$rm) {\n+\t\t\tclose $fh;\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\tcommand_close_pipe($fh, $ctx);\n+\n+\tmy ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});\n+\tforeach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {\n+\t\t$self->close_directory($bat->{$d}, $p);\n+\t\tmy ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);\n+\t\tprint \"\\tD+\\t$d/\\n\" unless $::_q;\n+\t\t$self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);\n+\t\tdelete $bat->{$d};\n+\t}\n+}\n+\n+sub open_or_add_dir {\n+\tmy ($self, $full_path, $baton, $deletions) = @_;\n+\tmy $t = $self->{types}->{$full_path};\n+\tif (!defined $t) {\n+\t\tdie \"$full_path not known in r$self->{r} or we have a bug!\\n\";\n+\t}\n+\t{\n+\t\tno warnings 'once';\n+\t\t# SVN::Node::none and SVN::Node::file are used only once,\n+\t\t# so we're shutting up Perl's warnings about them.\n+\t\tif ($t == $SVN::Node::none || defined($deletions->{$full_path})) {\n+\t\t\treturn $self->add_directory($full_path, $baton,\n+\t\t\t    undef, -1, $self->{pool});\n+\t\t} elsif ($t == $SVN::Node::dir) {\n+\t\t\treturn $self->open_directory($full_path, $baton,\n+\t\t\t    $self->{r}, $self->{pool});\n+\t\t} # no warnings 'once'\n+\t\tprint STDERR \"$full_path already exists in repository at \",\n+\t\t    \"r$self->{r} and it is not a directory (\",\n+\t\t    ($t == $SVN::Node::file ? 'file' : 'unknown'),\"/$t)\\n\";\n+\t} # no warnings 'once'\n+\texit 1;\n+}\n+\n+sub ensure_path {\n+\tmy ($self, $path, $deletions) = @_;\n+\tmy $bat = $self->{bat};\n+\tmy $repo_path = $self->repo_path($path);\n+\treturn $bat->{''} unless (length $repo_path);\n+\n+\tmy @p = split m#/+#, $repo_path;\n+\tmy $c = shift @p;\n+\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}, $deletions);\n+\twhile (@p) {\n+\t\tmy $c0 = $c;\n+\t\t$c .= '/' . shift @p;\n+\t\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0}, $deletions);\n+\t}\n+\treturn $bat->{$c};\n+}\n+\n+# Subroutine to convert a globbing pattern to a regular expression.\n+# From perl cookbook.\n+sub glob2pat {\n+\tmy $globstr = shift;\n+\tmy %patmap = ('*' => '.*', '?' => '.', '[' => '[', ']' => ']');\n+\t$globstr =~ s{(.)} { $patmap{$1} || \"\\Q$1\" }ge;\n+\treturn '^' . $globstr . '$';\n+}\n+\n+sub check_autoprop {\n+\tmy ($self, $pattern, $properties, $file, $fbat) = @_;\n+\t# Convert the globbing pattern to a regular expression.\n+\tmy $regex = glob2pat($pattern);\n+\t# Check if the pattern matches the file name.\n+\tif($file =~ m/($regex)/) {\n+\t\t# Parse the list of properties to set.\n+\t\tmy @props = split(/;/, $properties);\n+\t\tforeach my $prop (@props) {\n+\t\t\t# Parse 'name=value' syntax and set the property.\n+\t\t\tif ($prop =~ /([^=]+)=(.*)/) {\n+\t\t\t\tmy ($n,$v) = ($1,$2);\n+\t\t\t\tfor ($n, $v) {\n+\t\t\t\t\ts/^\\s+//; s/\\s+$//;\n+\t\t\t\t}\n+\t\t\t\t$self->change_file_prop($fbat, $n, $v);\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n+sub apply_autoprops {\n+\tmy ($self, $file, $fbat) = @_;\n+\tmy $conf_t = ${$self->{config}}{'config'};\n+\tno warnings 'once';\n+\t# Check [miscellany]/enable-auto-props in svn configuration.\n+\tif (SVN::_Core::svn_config_get_bool(\n+\t\t$conf_t,\n+\t\t$SVN::_Core::SVN_CONFIG_SECTION_MISCELLANY,\n+\t\t$SVN::_Core::SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS,\n+\t\t0)) {\n+\t\t# Auto-props are enabled.  Enumerate them to look for matches.\n+\t\tmy $callback = sub {\n+\t\t\t$self->check_autoprop($_[0], $_[1], $file, $fbat);\n+\t\t};\n+\t\tSVN::_Core::svn_config_enumerate(\n+\t\t\t$conf_t,\n+\t\t\t$SVN::_Core::SVN_CONFIG_SECTION_AUTO_PROPS,\n+\t\t\t$callback);\n+\t}\n+}\n+\n+sub A {\n+\tmy ($self, $m, $deletions) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir, $deletions);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t\tundef, -1);\n+\tprint \"\\tA\\t$m->{file_b}\\n\" unless $::_q;\n+\t$self->apply_autoprops($file, $fbat);\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub C {\n+\tmy ($self, $m, $deletions) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir, $deletions);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n+\tprint \"\\tC\\t$m->{file_a} => $m->{file_b}\\n\" unless $::_q;\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub delete_entry {\n+\tmy ($self, $path, $pbat) = @_;\n+\tmy $rpath = $self->repo_path($path);\n+\tmy ($dir, $file) = split_path($rpath);\n+\t$self->{rm}->{$dir} = 1;\n+\t$self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});\n+}\n+\n+sub R {\n+\tmy ($self, $m, $deletions) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir, $deletions);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n+\tprint \"\\tR\\t$m->{file_a} => $m->{file_b}\\n\" unless $::_q;\n+\t$self->apply_autoprops($file, $fbat);\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+\n+\t($dir, $file) = split_path($m->{file_a});\n+\t$pbat = $self->ensure_path($dir, $deletions);\n+\t$self->delete_entry($m->{file_a}, $pbat);\n+}\n+\n+sub M {\n+\tmy ($self, $m, $deletions) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir, $deletions);\n+\tmy $fbat = $self->open_file($self->repo_path($m->{file_b}),\n+\t\t\t\t$pbat,$self->{r},$self->{pool});\n+\tprint \"\\t$m->{chg}\\t$m->{file_b}\\n\" unless $::_q;\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub T { shift->M(@_) }\n+\n+sub change_file_prop {\n+\tmy ($self, $fbat, $pname, $pval) = @_;\n+\t$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});\n+}\n+\n+sub change_dir_prop {\n+\tmy ($self, $pbat, $pname, $pval) = @_;\n+\t$self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});\n+}\n+\n+sub _chg_file_get_blob ($$$$) {\n+\tmy ($self, $fbat, $m, $which) = @_;\n+\tmy $fh = $::_repository->temp_acquire(\"git_blob_$which\");\n+\tif ($m->{\"mode_$which\"} =~ /^120/) {\n+\t\tprint $fh 'link ' or croak $!;\n+\t\t$self->change_file_prop($fbat,'svn:special','*');\n+\t} elsif ($m->{mode_a} =~ /^120/ && $m->{\"mode_$which\"} !~ /^120/) {\n+\t\t$self->change_file_prop($fbat,'svn:special',undef);\n+\t}\n+\tmy $blob = $m->{\"sha1_$which\"};\n+\treturn ($fh,) if ($blob =~ /^0{40}$/);\n+\tmy $size = $::_repository->cat_blob($blob, $fh);\n+\tcroak \"Failed to read object $blob\" if ($size < 0);\n+\t$fh->flush == 0 or croak $!;\n+\tseek $fh, 0, 0 or croak $!;\n+\n+\tmy $exp = ::md5sum($fh);\n+\tseek $fh, 0, 0 or croak $!;\n+\treturn ($fh, $exp);\n+}\n+\n+sub chg_file {\n+\tmy ($self, $fbat, $m) = @_;\n+\tif ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {\n+\t\t$self->change_file_prop($fbat,'svn:executable','*');\n+\t} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {\n+\t\t$self->change_file_prop($fbat,'svn:executable',undef);\n+\t}\n+\tmy ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';\n+\tmy ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';\n+\tmy $pool = SVN::Pool->new;\n+\tmy $atd = $self->apply_textdelta($fbat, $exp_a, $pool);\n+\tif (-s $fh_a) {\n+\t\tmy $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);\n+\t\tmy $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);\n+\t\tif (defined $res) {\n+\t\t\tdie \"Unexpected result from send_txstream: $res\\n\",\n+\t\t\t    \"(SVN::Core::VERSION: $SVN::Core::VERSION)\\n\";\n+\t\t}\n+\t} else {\n+\t\tmy $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);\n+\t\tdie \"Checksum mismatch\\nexpected: $exp_b\\ngot: $got\\n\"\n+\t\t    if ($got ne $exp_b);\n+\t}\n+\tGit::temp_release($fh_b, 1);\n+\tGit::temp_release($fh_a, 1);\n+\t$pool->clear;\n+}\n+\n+sub D {\n+\tmy ($self, $m, $deletions) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir, $deletions);\n+\tprint \"\\tD\\t$m->{file_b}\\n\" unless $::_q;\n+\t$self->delete_entry($m->{file_b}, $pbat);\n+}\n+\n+sub close_edit {\n+\tmy ($self) = @_;\n+\tmy ($p,$bat) = ($self->{pool}, $self->{bat});\n+\tforeach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {\n+\t\tnext if $_ eq '';\n+\t\t$self->close_directory($bat->{$_}, $p);\n+\t}\n+\t$self->close_directory($bat->{''}, $p);\n+\t$self->SUPER::close_edit($p);\n+\t$p->clear;\n+}\n+\n+sub abort_edit {\n+\tmy ($self) = @_;\n+\t$self->SUPER::abort_edit($self->{pool});\n+}\n+\n+sub DESTROY {\n+\tmy $self = shift;\n+\t$self->SUPER::DESTROY(@_);\n+\t$self->{pool}->clear;\n+}\n+\n+# this drives the editor\n+sub apply_diff {\n+\tmy ($self) = @_;\n+\tmy $mods = $self->{mods};\n+\tmy %o = ( D => 0, C => 1, R => 2, A => 3, M => 4, T => 5 );\n+\tmy %deletions;\n+\n+\tforeach my $m (@$mods) {\n+\t\tif ($m->{chg} eq \"D\") {\n+\t\t\t$deletions{$m->{file_b}} = 1;\n+\t\t}\n+\t}\n+\n+\tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n+\t\tmy $f = $m->{chg};\n+\t\tif (defined $o{$f}) {\n+\t\t\t$self->$f($m, \\%deletions);\n+\t\t} else {\n+\t\t\tfatal(\"Invalid change type: $f\");\n+\t\t}\n+\t}\n+\n+\tif (defined($self->{mergeinfo})) {\n+\t\t$self->change_dir_prop($self->{bat}{''}, \"svn:mergeinfo\",\n+\t\t\t               $self->{mergeinfo});\n+\t}\n+\t$self->rmdirs if $_rmdir;\n+\tif (@$mods == 0 && !defined($self->{mergeinfo})) {\n+\t\t$self->abort_edit;\n+\t} else {\n+\t\t$self->close_edit;\n+\t}\n+\treturn scalar @$mods;\n+}\n+\n+1;\n+__END__\n+\n+Git::SVN::Editor - commit driver for \"git svn set-tree\" and dcommit\n+\n+=head1 SYNOPSIS\n+\n+\tuse Git::SVN::Editor;\n+\tuse Git::SVN::Ra;\n+\n+\tmy $ra = Git::SVN::Ra->new($url);\n+\tmy %opts = (\n+\t\tr => 19,\n+\t\tlog => \"log message\",\n+\t\tra => $ra,\n+\t\tconfig => SVN::Core::config_get_config($svn_config_dir),\n+\t\ttree_a => \"$commit^\",\n+\t\ttree_b => \"$commit\",\n+\t\teditor_cb => sub { print \"Committed r$_[0]\\n\"; },\n+\t\tmergeinfo => \"/branches/foo:1-10\",\n+\t\tsvn_path => \"trunk\"\n+\t);\n+\tGit::SVN::Editor->new(\\%opts)->apply_diff or print \"No changes\\n\";\n+\n+\tmy $re = Git::SVN::Editor::glob2pat(\"trunk/*\");\n+\tif ($branchname =~ /$re/) {\n+\t\tprint \"matched!\\n\";\n+\t}\n+\n+=head1 DESCRIPTION\n+\n+This module is an implementation detail of the \"git svn\" command.\n+Do not use it unless you are developing git-svn.\n+\n+This module adapts the C<SVN::Delta::Editor> object returned by\n+C<SVN::Delta::get_commit_editor> and drives it to convey the\n+difference between two git tree objects to a remote Subversion\n+repository.\n+\n+The interface will change as git-svn evolves.\n+\n+=head1 DEPENDENCIES\n+\n+Subversion perl bindings,\n+the core L<Carp> and L<IO::File> modules,\n+and git's L<Git> helper module.\n+\n+C<Git::SVN::Editor> has not been tested using callers other than\n+B<git-svn> itself.\n+\n+=head1 SEE ALSO\n+\n+L<SVN::Delta>,\n+L<Git::SVN::Fetcher>.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+None.\ndiff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm\nindex 4e9c77d7..ef8e9ed2 100644\n--- a/perl/Git/SVN/Fetcher.pm\n+++ b/perl/Git/SVN/Fetcher.pm\n@@ -591,7 +591,8 @@ B<git-svn> itself.\n \n =head1 SEE ALSO\n \n-L<SVN::Delta>.\n+L<SVN::Delta>,\n+L<Git::SVN::Editor>.\n \n =head1 INCOMPATIBILITIES\n \ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 424890a1..32caf214 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -28,6 +28,7 @@ my %pm = (\n \t'Git.pm' => '$(INST_LIBDIR)/Git.pm',\n \t'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',\n \t'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',\n+\t'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',\n \t'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',\n );\n \n-- \n1.7.10\n"},{"id":"193221","messageId":"20120609222810.GF28412@burratino","threadId":"28184","inReplyTo":"20120609222039.GD28412@burratino","subject":"[PATCH 2/3] git-svn: make Git::SVN::RA a separate file","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-09T22:28:10Z","receivedAt":"2012-06-09T22:28:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"This slices off another 600 or so lines from the frighteningly long\ngit-svn.perl script.\n\nThe Git::SVN::Ra interface is similar enough to SVN::Ra that it is\nprobably safe to ignore most of its implementation on first reading.\n(Documenting or moving functions that do not fit that pattern is left\nas an exercise to the interested reader.)\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n git-svn.perl       |  612 +-----------------------------------------------\n perl/Git/SVN/Ra.pm |  658 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n perl/Makefile.PL   |    1 +\n 3 files changed, 660 insertions(+), 611 deletions(-)\n create mode 100644 perl/Git/SVN/Ra.pm\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex 7cec4416..24c0af2e 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -67,7 +67,6 @@ sub _req_svn {\n \t}\n }\n my $can_compress = eval { require Compress::Zlib; 1};\n-push @Git::SVN::Ra::ISA, 'SVN::Ra';\n use Carp qw/croak/;\n use Digest::MD5;\n use IO::File qw//;\n@@ -80,6 +79,7 @@ use IPC::Open3;\n use Git;\n use Git::SVN::Editor qw//;\n use Git::SVN::Fetcher qw//;\n+use Git::SVN::Ra qw//;\n use Git::SVN::Prompt qw//;\n use Memoize;  # core since 5.8.0, Jul 2002\n \n@@ -4326,616 +4326,6 @@ sub remove_username {\n \t$_[0] =~ s{^([^:]*://)[^@]+@}{$1};\n }\n \n-package Git::SVN::Ra;\n-use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;\n-use strict;\n-use warnings;\n-my ($ra_invalid, $can_do_switch, %ignored_err, $RA);\n-\n-BEGIN {\n-\t# enforce temporary pool usage for some simple functions\n-\tno strict 'refs';\n-\tfor my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root\n-\t              get_file/) {\n-\t\tmy $SUPER = \"SUPER::$f\";\n-\t\t*$f = sub {\n-\t\t\tmy $self = shift;\n-\t\t\tmy $pool = SVN::Pool->new;\n-\t\t\tmy @ret = $self->$SUPER(@_,$pool);\n-\t\t\t$pool->clear;\n-\t\t\twantarray ? @ret : $ret[0];\n-\t\t};\n-\t}\n-}\n-\n-sub _auth_providers () {\n-\tmy @rv = (\n-\t  SVN::Client::get_simple_provider(),\n-\t  SVN::Client::get_ssl_server_trust_file_provider(),\n-\t  SVN::Client::get_simple_prompt_provider(\n-\t    \\&Git::SVN::Prompt::simple, 2),\n-\t  SVN::Client::get_ssl_client_cert_file_provider(),\n-\t  SVN::Client::get_ssl_client_cert_prompt_provider(\n-\t    \\&Git::SVN::Prompt::ssl_client_cert, 2),\n-\t  SVN::Client::get_ssl_client_cert_pw_file_provider(),\n-\t  SVN::Client::get_ssl_client_cert_pw_prompt_provider(\n-\t    \\&Git::SVN::Prompt::ssl_client_cert_pw, 2),\n-\t  SVN::Client::get_username_provider(),\n-\t  SVN::Client::get_ssl_server_trust_prompt_provider(\n-\t    \\&Git::SVN::Prompt::ssl_server_trust),\n-\t  SVN::Client::get_username_prompt_provider(\n-\t    \\&Git::SVN::Prompt::username, 2)\n-\t);\n-\n-\t# earlier 1.6.x versions would segfault, and <= 1.5.x didn't have\n-\t# this function\n-\tif (::compare_svn_version('1.6.12') > 0) {\n-\t\tmy $config = SVN::Core::config_get_config($config_dir);\n-\t\tmy ($p, @a);\n-\t\t# config_get_config returns all config files from\n-\t\t# ~/.subversion, auth_get_platform_specific_client_providers\n-\t\t# just wants the config \"file\".\n-\t\t@a = ($config->{'config'}, undef);\n-\t\t$p = SVN::Core::auth_get_platform_specific_client_providers(@a);\n-\t\t# Insert the return value from\n-\t\t# auth_get_platform_specific_providers\n-\t\tunshift @rv, @$p;\n-\t}\n-\t\\@rv;\n-}\n-\n-sub escape_uri_only {\n-\tmy ($uri) = @_;\n-\tmy @tmp;\n-\tforeach (split m{/}, $uri) {\n-\t\ts/([^~\\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf(\"%%%02X\",ord($1))/eg;\n-\t\tpush @tmp, $_;\n-\t}\n-\tjoin('/', @tmp);\n-}\n-\n-sub escape_url {\n-\tmy ($url) = @_;\n-\tif ($url =~ m#^(https?)://([^/]+)(.*)$#) {\n-\t\tmy ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));\n-\t\t$url = \"$scheme://$domain$uri\";\n-\t}\n-\t$url;\n-}\n-\n-sub new {\n-\tmy ($class, $url) = @_;\n-\t$url =~ s!/+$!!;\n-\treturn $RA if ($RA && $RA->{url} eq $url);\n-\n-\t::_req_svn();\n-\n-\tSVN::_Core::svn_config_ensure($config_dir, undef);\n-\tmy ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);\n-\tmy $config = SVN::Core::config_get_config($config_dir);\n-\t$RA = undef;\n-\tmy $dont_store_passwords = 1;\n-\tmy $conf_t = ${$config}{'config'};\n-\t{\n-\t\tno warnings 'once';\n-\t\t# The usage of $SVN::_Core::SVN_CONFIG_* variables\n-\t\t# produces warnings that variables are used only once.\n-\t\t# I had not found the better way to shut them up, so\n-\t\t# the warnings of type 'once' are disabled in this block.\n-\t\tif (SVN::_Core::svn_config_get_bool($conf_t,\n-\t\t    $SVN::_Core::SVN_CONFIG_SECTION_AUTH,\n-\t\t    $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,\n-\t\t    1) == 0) {\n-\t\t\tSVN::_Core::svn_auth_set_parameter($baton,\n-\t\t\t    $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,\n-\t\t\t    bless (\\$dont_store_passwords, \"_p_void\"));\n-\t\t}\n-\t\tif (SVN::_Core::svn_config_get_bool($conf_t,\n-\t\t    $SVN::_Core::SVN_CONFIG_SECTION_AUTH,\n-\t\t    $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,\n-\t\t    1) == 0) {\n-\t\t\t$Git::SVN::Prompt::_no_auth_cache = 1;\n-\t\t}\n-\t} # no warnings 'once'\n-\tmy $self = SVN::Ra->new(url => escape_url($url), auth => $baton,\n-\t                      config => $config,\n-\t\t\t      pool => SVN::Pool->new,\n-\t                      auth_provider_callbacks => $callbacks);\n-\t$self->{url} = $url;\n-\t$self->{svn_path} = $url;\n-\t$self->{repos_root} = $self->get_repos_root;\n-\t$self->{svn_path} =~ s#^\\Q$self->{repos_root}\\E(/|$)##;\n-\t$self->{cache} = { check_path => { r => 0, data => {} },\n-\t                   get_dir => { r => 0, data => {} } };\n-\t$RA = bless $self, $class;\n-}\n-\n-sub check_path {\n-\tmy ($self, $path, $r) = @_;\n-\tmy $cache = $self->{cache}->{check_path};\n-\tif ($r == $cache->{r} && exists $cache->{data}->{$path}) {\n-\t\treturn $cache->{data}->{$path};\n-\t}\n-\tmy $pool = SVN::Pool->new;\n-\tmy $t = $self->SUPER::check_path($path, $r, $pool);\n-\t$pool->clear;\n-\tif ($r != $cache->{r}) {\n-\t\t%{$cache->{data}} = ();\n-\t\t$cache->{r} = $r;\n-\t}\n-\t$cache->{data}->{$path} = $t;\n-}\n-\n-sub get_dir {\n-\tmy ($self, $dir, $r) = @_;\n-\tmy $cache = $self->{cache}->{get_dir};\n-\tif ($r == $cache->{r}) {\n-\t\tif (my $x = $cache->{data}->{$dir}) {\n-\t\t\treturn wantarray ? @$x : $x->[0];\n-\t\t}\n-\t}\n-\tmy $pool = SVN::Pool->new;\n-\tmy ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);\n-\tmy %dirents = map { $_ => { kind => $d->{$_}->kind } } keys %$d;\n-\t$pool->clear;\n-\tif ($r != $cache->{r}) {\n-\t\t%{$cache->{data}} = ();\n-\t\t$cache->{r} = $r;\n-\t}\n-\t$cache->{data}->{$dir} = [ \\%dirents, $r, $props ];\n-\twantarray ? (\\%dirents, $r, $props) : \\%dirents;\n-}\n-\n-sub DESTROY {\n-\t# do not call the real DESTROY since we store ourselves in $RA\n-}\n-\n-# get_log(paths, start, end, limit,\n-#         discover_changed_paths, strict_node_history, receiver)\n-sub get_log {\n-\tmy ($self, @args) = @_;\n-\tmy $pool = SVN::Pool->new;\n-\n-\t# svn_log_changed_path_t objects passed to get_log are likely to be\n-\t# overwritten even if only the refs are copied to an external variable,\n-\t# so we should dup the structures in their entirety.  Using an\n-\t# externally passed pool (instead of our temporary and quickly cleared\n-\t# pool in Git::SVN::Ra) does not help matters at all...\n-\tmy $receiver = pop @args;\n-\tmy $prefix = \"/\".$self->{svn_path};\n-\t$prefix =~ s#/+($)##;\n-\tmy $prefix_regex = qr#^\\Q$prefix\\E#;\n-\tpush(@args, sub {\n-\t\tmy ($paths) = $_[0];\n-\t\treturn &$receiver(@_) unless $paths;\n-\t\t$_[0] = ();\n-\t\tforeach my $p (keys %$paths) {\n-\t\t\tmy $i = $paths->{$p};\n-\t\t\t# Make path relative to our url, not repos_root\n-\t\t\t$p =~ s/$prefix_regex//;\n-\t\t\tmy %s = map { $_ => $i->$_; }\n-\t\t\t\tqw/copyfrom_path copyfrom_rev action/;\n-\t\t\tif ($s{'copyfrom_path'}) {\n-\t\t\t\t$s{'copyfrom_path'} =~ s/$prefix_regex//;\n-\t\t\t}\n-\t\t\t$_[0]{$p} = \\%s;\n-\t\t}\n-\t\t&$receiver(@_);\n-\t});\n-\n-\n-\t# the limit parameter was not supported in SVN 1.1.x, so we\n-\t# drop it.  Therefore, the receiver callback passed to it\n-\t# is made aware of this limitation by being wrapped if\n-\t# the limit passed to is being wrapped.\n-\tif (::compare_svn_version('1.2.0') <= 0) {\n-\t\tmy $limit = splice(@args, 3, 1);\n-\t\tif ($limit > 0) {\n-\t\t\tmy $receiver = pop @args;\n-\t\t\tpush(@args, sub { &$receiver(@_) if (--$limit >= 0) });\n-\t\t}\n-\t}\n-\tmy $ret = $self->SUPER::get_log(@args, $pool);\n-\t$pool->clear;\n-\t$ret;\n-}\n-\n-sub trees_match {\n-\tmy ($self, $url1, $rev1, $url2, $rev2) = @_;\n-\tmy $ctx = SVN::Client->new(auth => _auth_providers);\n-\tmy $out = IO::File->new_tmpfile;\n-\n-\t# older SVN (1.1.x) doesn't take $pool as the last parameter for\n-\t# $ctx->diff(), so we'll create a default one\n-\tmy $pool = SVN::Pool->new_default_sub;\n-\n-\t$ra_invalid = 1; # this will open a new SVN::Ra connection to $url1\n-\t$ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out);\n-\t$out->flush;\n-\tmy $ret = (($out->stat)[7] == 0);\n-\tclose $out or croak $!;\n-\n-\t$ret;\n-}\n-\n-sub get_commit_editor {\n-\tmy ($self, $log, $cb, $pool) = @_;\n-\n-\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();\n-\t$self->SUPER::get_commit_editor($log, $cb, @lock, $pool);\n-}\n-\n-sub gs_do_update {\n-\tmy ($self, $rev_a, $rev_b, $gs, $editor) = @_;\n-\tmy $new = ($rev_a == $rev_b);\n-\tmy $path = $gs->{path};\n-\n-\tif ($new && -e $gs->{index}) {\n-\t\tunlink $gs->{index} or die\n-\t\t  \"Couldn't unlink index: $gs->{index}: $!\\n\";\n-\t}\n-\tmy $pool = SVN::Pool->new;\n-\t$editor->set_path_strip($path);\n-\tmy (@pc) = split m#/#, $path;\n-\tmy $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),\n-\t                                1, $editor, $pool);\n-\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();\n-\n-\t# Since we can't rely on svn_ra_reparent being available, we'll\n-\t# just have to do some magic with set_path to make it so\n-\t# we only want a partial path.\n-\tmy $sp = '';\n-\tmy $final = join('/', @pc);\n-\twhile (@pc) {\n-\t\t$reporter->set_path($sp, $rev_b, 0, @lock, $pool);\n-\t\t$sp .= '/' if length $sp;\n-\t\t$sp .= shift @pc;\n-\t}\n-\tdie \"BUG: '$sp' != '$final'\\n\" if ($sp ne $final);\n-\n-\t$reporter->set_path($sp, $rev_a, $new, @lock, $pool);\n-\n-\t$reporter->finish_report($pool);\n-\t$pool->clear;\n-\t$editor->{git_commit_ok};\n-}\n-\n-# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and\n-# svn_ra_reparent didn't work before 1.4)\n-sub gs_do_switch {\n-\tmy ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;\n-\tmy $path = $gs->{path};\n-\tmy $pool = SVN::Pool->new;\n-\n-\tmy $full_url = $self->{url};\n-\tmy $old_url = $full_url;\n-\t$full_url .= '/' . $path if length $path;\n-\tmy ($ra, $reparented);\n-\n-\tif ($old_url =~ m#^svn(\\+ssh)?://# ||\n-\t    ($full_url =~ m#^https?://# &&\n-\t     escape_url($full_url) ne $full_url)) {\n-\t\t$_[0] = undef;\n-\t\t$self = undef;\n-\t\t$RA = undef;\n-\t\t$ra = Git::SVN::Ra->new($full_url);\n-\t\t$ra_invalid = 1;\n-\t} elsif ($old_url ne $full_url) {\n-\t\tSVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);\n-\t\t$self->{url} = $full_url;\n-\t\t$reparented = 1;\n-\t}\n-\n-\t$ra ||= $self;\n-\t$url_b = escape_url($url_b);\n-\tmy $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);\n-\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();\n-\t$reporter->set_path('', $rev_a, 0, @lock, $pool);\n-\t$reporter->finish_report($pool);\n-\n-\tif ($reparented) {\n-\t\tSVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);\n-\t\t$self->{url} = $old_url;\n-\t}\n-\n-\t$pool->clear;\n-\t$editor->{git_commit_ok};\n-}\n-\n-sub longest_common_path {\n-\tmy ($gsv, $globs) = @_;\n-\tmy %common;\n-\tmy $common_max = scalar @$gsv;\n-\n-\tforeach my $gs (@$gsv) {\n-\t\tmy @tmp = split m#/#, $gs->{path};\n-\t\tmy $p = '';\n-\t\tforeach (@tmp) {\n-\t\t\t$p .= length($p) ? \"/$_\" : $_;\n-\t\t\t$common{$p} ||= 0;\n-\t\t\t$common{$p}++;\n-\t\t}\n-\t}\n-\t$globs ||= [];\n-\t$common_max += scalar @$globs;\n-\tforeach my $glob (@$globs) {\n-\t\tmy @tmp = split m#/#, $glob->{path}->{left};\n-\t\tmy $p = '';\n-\t\tforeach (@tmp) {\n-\t\t\t$p .= length($p) ? \"/$_\" : $_;\n-\t\t\t$common{$p} ||= 0;\n-\t\t\t$common{$p}++;\n-\t\t}\n-\t}\n-\n-\tmy $longest_path = '';\n-\tforeach (sort {length $b <=> length $a} keys %common) {\n-\t\tif ($common{$_} == $common_max) {\n-\t\t\t$longest_path = $_;\n-\t\t\tlast;\n-\t\t}\n-\t}\n-\t$longest_path;\n-}\n-\n-sub gs_fetch_loop_common {\n-\tmy ($self, $base, $head, $gsv, $globs) = @_;\n-\treturn if ($base > $head);\n-\tmy $inc = $_log_window_size;\n-\tmy ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);\n-\tmy $longest_path = longest_common_path($gsv, $globs);\n-\tmy $ra_url = $self->{url};\n-\tmy $find_trailing_edge;\n-\twhile (1) {\n-\t\tmy %revs;\n-\t\tmy $err;\n-\t\tmy $err_handler = $SVN::Error::handler;\n-\t\t$SVN::Error::handler = sub {\n-\t\t\t($err) = @_;\n-\t\t\tskip_unknown_revs($err);\n-\t\t};\n-\t\tsub _cb {\n-\t\t\tmy ($paths, $r, $author, $date, $log) = @_;\n-\t\t\t[ $paths,\n-\t\t\t  { author => $author, date => $date, log => $log } ];\n-\t\t}\n-\t\t$self->get_log([$longest_path], $min, $max, 0, 1, 1,\n-\t\t               sub { $revs{$_[1]} = _cb(@_) });\n-\t\tif ($err) {\n-\t\t\tprint \"Checked through r$max\\r\";\n-\t\t} else {\n-\t\t\t$find_trailing_edge = 1;\n-\t\t}\n-\t\tif ($err and $find_trailing_edge) {\n-\t\t\tprint STDERR \"Path '$longest_path' \",\n-\t\t\t\t     \"was probably deleted:\\n\",\n-\t\t\t\t     $err->expanded_message,\n-\t\t\t\t     \"\\nWill attempt to follow \",\n-\t\t\t\t     \"revisions r$min .. r$max \",\n-\t\t\t\t     \"committed before the deletion\\n\";\n-\t\t\tmy $hi = $max;\n-\t\t\twhile (--$hi >= $min) {\n-\t\t\t\tmy $ok;\n-\t\t\t\t$self->get_log([$longest_path], $min, $hi,\n-\t\t\t\t               0, 1, 1, sub {\n-\t\t\t\t               $ok = $_[1];\n-\t\t\t\t               $revs{$_[1]} = _cb(@_) });\n-\t\t\t\tif ($ok) {\n-\t\t\t\t\tprint STDERR \"r$min .. r$ok OK\\n\";\n-\t\t\t\t\tlast;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\t$find_trailing_edge = 0;\n-\t\t}\n-\t\t$SVN::Error::handler = $err_handler;\n-\n-\t\tmy %exists = map { $_->{path} => $_ } @$gsv;\n-\t\tforeach my $r (sort {$a <=> $b} keys %revs) {\n-\t\t\tmy ($paths, $logged) = @{$revs{$r}};\n-\n-\t\t\tforeach my $gs ($self->match_globs(\\%exists, $paths,\n-\t\t\t                                   $globs, $r)) {\n-\t\t\t\tif ($gs->rev_map_max >= $r) {\n-\t\t\t\t\tnext;\n-\t\t\t\t}\n-\t\t\t\tnext unless $gs->match_paths($paths, $r);\n-\t\t\t\t$gs->{logged_rev_props} = $logged;\n-\t\t\t\tif (my $last_commit = $gs->last_commit) {\n-\t\t\t\t\t$gs->assert_index_clean($last_commit);\n-\t\t\t\t}\n-\t\t\t\tmy $log_entry = $gs->do_fetch($paths, $r);\n-\t\t\t\tif ($log_entry) {\n-\t\t\t\t\t$gs->do_git_commit($log_entry);\n-\t\t\t\t}\n-\t\t\t\t$INDEX_FILES{$gs->{index}} = 1;\n-\t\t\t}\n-\t\t\tforeach my $g (@$globs) {\n-\t\t\t\tmy $k = \"svn-remote.$g->{remote}.\" .\n-\t\t\t\t        \"$g->{t}-maxRev\";\n-\t\t\t\tGit::SVN::tmp_config($k, $r);\n-\t\t\t}\n-\t\t\tif ($ra_invalid) {\n-\t\t\t\t$_[0] = undef;\n-\t\t\t\t$self = undef;\n-\t\t\t\t$RA = undef;\n-\t\t\t\t$self = Git::SVN::Ra->new($ra_url);\n-\t\t\t\t$ra_invalid = undef;\n-\t\t\t}\n-\t\t}\n-\t\t# pre-fill the .rev_db since it'll eventually get filled in\n-\t\t# with '0' x40 if something new gets committed\n-\t\tforeach my $gs (@$gsv) {\n-\t\t\tnext if $gs->rev_map_max >= $max;\n-\t\t\tnext if defined $gs->rev_map_get($max);\n-\t\t\t$gs->rev_map_set($max, 0 x40);\n-\t\t}\n-\t\tforeach my $g (@$globs) {\n-\t\t\tmy $k = \"svn-remote.$g->{remote}.$g->{t}-maxRev\";\n-\t\t\tGit::SVN::tmp_config($k, $max);\n-\t\t}\n-\t\tlast if $max >= $head;\n-\t\t$min = $max + 1;\n-\t\t$max += $inc;\n-\t\t$max = $head if ($max > $head);\n-\t}\n-\tGit::SVN::gc();\n-}\n-\n-sub get_dir_globbed {\n-\tmy ($self, $left, $depth, $r) = @_;\n-\n-\tmy @x = eval { $self->get_dir($left, $r) };\n-\treturn unless scalar @x == 3;\n-\tmy $dirents = $x[0];\n-\tmy @finalents;\n-\tforeach my $de (keys %$dirents) {\n-\t\tnext if $dirents->{$de}->{kind} != $SVN::Node::dir;\n-\t\tif ($depth > 1) {\n-\t\t\tmy @args = (\"$left/$de\", $depth - 1, $r);\n-\t\t\tforeach my $dir ($self->get_dir_globbed(@args)) {\n-\t\t\t\tpush @finalents, \"$de/$dir\";\n-\t\t\t}\n-\t\t} else {\n-\t\t\tpush @finalents, $de;\n-\t\t}\n-\t}\n-\t@finalents;\n-}\n-\n-# return value: 0 -- don't ignore, 1 -- ignore\n-sub is_ref_ignored {\n-\tmy ($g, $p) = @_;\n-\tmy $refname = $g->{ref}->full_path($p);\n-\treturn 1 if defined($g->{ignore_refs_regex}) &&\n-\t            $refname =~ m!$g->{ignore_refs_regex}!;\n-\treturn 0 unless defined($_ignore_refs_regex);\n-\treturn 1 if $refname =~ m!$_ignore_refs_regex!o;\n-\treturn 0;\n-}\n-\n-sub match_globs {\n-\tmy ($self, $exists, $paths, $globs, $r) = @_;\n-\n-\tsub get_dir_check {\n-\t\tmy ($self, $exists, $g, $r) = @_;\n-\n-\t\tmy @dirs = $self->get_dir_globbed($g->{path}->{left},\n-\t\t                                  $g->{path}->{depth},\n-\t\t                                  $r);\n-\n-\t\tforeach my $de (@dirs) {\n-\t\t\tmy $p = $g->{path}->full_path($de);\n-\t\t\tnext if $exists->{$p};\n-\t\t\tnext if (length $g->{path}->{right} &&\n-\t\t\t\t ($self->check_path($p, $r) !=\n-\t\t\t\t  $SVN::Node::dir));\n-\t\t\tnext unless $p =~ /$g->{path}->{regex}/;\n-\t\t\t$exists->{$p} = Git::SVN->init($self->{url}, $p, undef,\n-\t\t\t\t\t $g->{ref}->full_path($de), 1);\n-\t\t}\n-\t}\n-\tforeach my $g (@$globs) {\n-\t\tif (my $path = $paths->{\"/$g->{path}->{left}\"}) {\n-\t\t\tif ($path->{action} =~ /^[AR]$/) {\n-\t\t\t\tget_dir_check($self, $exists, $g, $r);\n-\t\t\t}\n-\t\t}\n-\t\tforeach (keys %$paths) {\n-\t\t\tif (/$g->{path}->{left_regex}/ &&\n-\t\t\t    !/$g->{path}->{regex}/) {\n-\t\t\t\tnext if $paths->{$_}->{action} !~ /^[AR]$/;\n-\t\t\t\tget_dir_check($self, $exists, $g, $r);\n-\t\t\t}\n-\t\t\tnext unless /$g->{path}->{regex}/;\n-\t\t\tmy $p = $1;\n-\t\t\tmy $pathname = $g->{path}->full_path($p);\n-\t\t\tnext if is_ref_ignored($g, $p);\n-\t\t\tnext if $exists->{$pathname};\n-\t\t\tnext if ($self->check_path($pathname, $r) !=\n-\t\t\t         $SVN::Node::dir);\n-\t\t\t$exists->{$pathname} = Git::SVN->init(\n-\t\t\t                      $self->{url}, $pathname, undef,\n-\t\t\t                      $g->{ref}->full_path($p), 1);\n-\t\t}\n-\t\tmy $c = '';\n-\t\tforeach (split m#/#, $g->{path}->{left}) {\n-\t\t\t$c .= \"/$_\";\n-\t\t\tnext unless ($paths->{$c} &&\n-\t\t\t             ($paths->{$c}->{action} =~ /^[AR]$/));\n-\t\t\tget_dir_check($self, $exists, $g, $r);\n-\t\t}\n-\t}\n-\tvalues %$exists;\n-}\n-\n-sub minimize_url {\n-\tmy ($self) = @_;\n-\treturn $self->{url} if ($self->{url} eq $self->{repos_root});\n-\tmy $url = $self->{repos_root};\n-\tmy @components = split(m!/!, $self->{svn_path});\n-\tmy $c = '';\n-\tdo {\n-\t\t$url .= \"/$c\" if length $c;\n-\t\teval {\n-\t\t\tmy $ra = (ref $self)->new($url);\n-\t\t\tmy $latest = $ra->get_latest_revnum;\n-\t\t\t$ra->get_log(\"\", $latest, 0, 1, 0, 1, sub {});\n-\t\t};\n-\t} while ($@ && ($c = shift @components));\n-\t$url;\n-}\n-\n-sub can_do_switch {\n-\tmy $self = shift;\n-\tunless (defined $can_do_switch) {\n-\t\tmy $pool = SVN::Pool->new;\n-\t\tmy $rep = eval {\n-\t\t\t$self->do_switch(1, '', 0, $self->{url},\n-\t\t\t                 SVN::Delta::Editor->new, $pool);\n-\t\t};\n-\t\tif ($@) {\n-\t\t\t$can_do_switch = 0;\n-\t\t} else {\n-\t\t\t$rep->abort_report($pool);\n-\t\t\t$can_do_switch = 1;\n-\t\t}\n-\t\t$pool->clear;\n-\t}\n-\t$can_do_switch;\n-}\n-\n-sub skip_unknown_revs {\n-\tmy ($err) = @_;\n-\tmy $errno = $err->apr_err();\n-\t# Maybe the branch we're tracking didn't\n-\t# exist when the repo started, so it's\n-\t# not an error if it doesn't, just continue\n-\t#\n-\t# Wonderfully consistent library, eh?\n-\t# 160013 - svn:// and file://\n-\t# 175002 - http(s)://\n-\t# 175007 - http(s):// (this repo required authorization, too...)\n-\t#   More codes may be discovered later...\n-\tif ($errno == 175007 || $errno == 175002 || $errno == 160013) {\n-\t\tmy $err_key = $err->expanded_message;\n-\t\t# revision numbers change every time, filter them out\n-\t\t$err_key =~ s/\\d+/\\0/g;\n-\t\t$err_key = \"$errno\\0$err_key\";\n-\t\tunless ($ignored_err{$err_key}) {\n-\t\t\twarn \"W: Ignoring error from SVN, path probably \",\n-\t\t\t     \"does not exist: ($errno): \",\n-\t\t\t     $err->expanded_message,\"\\n\";\n-\t\t\twarn \"W: Do not be alarmed at the above message \",\n-\t\t\t     \"git-svn is just searching aggressively for \",\n-\t\t\t     \"old history.\\n\",\n-\t\t\t     \"This may take a while on large repositories\\n\";\n-\t\t\t$ignored_err{$err_key} = 1;\n-\t\t}\n-\t\treturn;\n-\t}\n-\tdie \"Error from SVN, ($errno): \", $err->expanded_message,\"\\n\";\n-}\n-\n package Git::SVN::Log;\n use strict;\n use warnings;\ndiff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm\nnew file mode 100644\nindex 00000000..e88b4e79\n--- /dev/null\n+++ b/perl/Git/SVN/Ra.pm\n@@ -0,0 +1,658 @@\n+package Git::SVN::Ra;\n+use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;\n+use strict;\n+use warnings;\n+use SVN::Client;\n+use SVN::Ra;\n+BEGIN {\n+\t@ISA = qw(SVN::Ra);\n+}\n+\n+my ($ra_invalid, $can_do_switch, %ignored_err, $RA);\n+\n+BEGIN {\n+\t# enforce temporary pool usage for some simple functions\n+\tno strict 'refs';\n+\tfor my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root\n+\t              get_file/) {\n+\t\tmy $SUPER = \"SUPER::$f\";\n+\t\t*$f = sub {\n+\t\t\tmy $self = shift;\n+\t\t\tmy $pool = SVN::Pool->new;\n+\t\t\tmy @ret = $self->$SUPER(@_,$pool);\n+\t\t\t$pool->clear;\n+\t\t\twantarray ? @ret : $ret[0];\n+\t\t};\n+\t}\n+}\n+\n+sub _auth_providers () {\n+\tmy @rv = (\n+\t  SVN::Client::get_simple_provider(),\n+\t  SVN::Client::get_ssl_server_trust_file_provider(),\n+\t  SVN::Client::get_simple_prompt_provider(\n+\t    \\&Git::SVN::Prompt::simple, 2),\n+\t  SVN::Client::get_ssl_client_cert_file_provider(),\n+\t  SVN::Client::get_ssl_client_cert_prompt_provider(\n+\t    \\&Git::SVN::Prompt::ssl_client_cert, 2),\n+\t  SVN::Client::get_ssl_client_cert_pw_file_provider(),\n+\t  SVN::Client::get_ssl_client_cert_pw_prompt_provider(\n+\t    \\&Git::SVN::Prompt::ssl_client_cert_pw, 2),\n+\t  SVN::Client::get_username_provider(),\n+\t  SVN::Client::get_ssl_server_trust_prompt_provider(\n+\t    \\&Git::SVN::Prompt::ssl_server_trust),\n+\t  SVN::Client::get_username_prompt_provider(\n+\t    \\&Git::SVN::Prompt::username, 2)\n+\t);\n+\n+\t# earlier 1.6.x versions would segfault, and <= 1.5.x didn't have\n+\t# this function\n+\tif (::compare_svn_version('1.6.12') > 0) {\n+\t\tmy $config = SVN::Core::config_get_config($config_dir);\n+\t\tmy ($p, @a);\n+\t\t# config_get_config returns all config files from\n+\t\t# ~/.subversion, auth_get_platform_specific_client_providers\n+\t\t# just wants the config \"file\".\n+\t\t@a = ($config->{'config'}, undef);\n+\t\t$p = SVN::Core::auth_get_platform_specific_client_providers(@a);\n+\t\t# Insert the return value from\n+\t\t# auth_get_platform_specific_providers\n+\t\tunshift @rv, @$p;\n+\t}\n+\t\\@rv;\n+}\n+\n+sub escape_uri_only {\n+\tmy ($uri) = @_;\n+\tmy @tmp;\n+\tforeach (split m{/}, $uri) {\n+\t\ts/([^~\\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf(\"%%%02X\",ord($1))/eg;\n+\t\tpush @tmp, $_;\n+\t}\n+\tjoin('/', @tmp);\n+}\n+\n+sub escape_url {\n+\tmy ($url) = @_;\n+\tif ($url =~ m#^(https?)://([^/]+)(.*)$#) {\n+\t\tmy ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));\n+\t\t$url = \"$scheme://$domain$uri\";\n+\t}\n+\t$url;\n+}\n+\n+sub new {\n+\tmy ($class, $url) = @_;\n+\t$url =~ s!/+$!!;\n+\treturn $RA if ($RA && $RA->{url} eq $url);\n+\n+\t::_req_svn();\n+\n+\tSVN::_Core::svn_config_ensure($config_dir, undef);\n+\tmy ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);\n+\tmy $config = SVN::Core::config_get_config($config_dir);\n+\t$RA = undef;\n+\tmy $dont_store_passwords = 1;\n+\tmy $conf_t = ${$config}{'config'};\n+\t{\n+\t\tno warnings 'once';\n+\t\t# The usage of $SVN::_Core::SVN_CONFIG_* variables\n+\t\t# produces warnings that variables are used only once.\n+\t\t# I had not found the better way to shut them up, so\n+\t\t# the warnings of type 'once' are disabled in this block.\n+\t\tif (SVN::_Core::svn_config_get_bool($conf_t,\n+\t\t    $SVN::_Core::SVN_CONFIG_SECTION_AUTH,\n+\t\t    $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,\n+\t\t    1) == 0) {\n+\t\t\tSVN::_Core::svn_auth_set_parameter($baton,\n+\t\t\t    $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,\n+\t\t\t    bless (\\$dont_store_passwords, \"_p_void\"));\n+\t\t}\n+\t\tif (SVN::_Core::svn_config_get_bool($conf_t,\n+\t\t    $SVN::_Core::SVN_CONFIG_SECTION_AUTH,\n+\t\t    $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,\n+\t\t    1) == 0) {\n+\t\t\t$Git::SVN::Prompt::_no_auth_cache = 1;\n+\t\t}\n+\t} # no warnings 'once'\n+\tmy $self = SVN::Ra->new(url => escape_url($url), auth => $baton,\n+\t                      config => $config,\n+\t\t\t      pool => SVN::Pool->new,\n+\t                      auth_provider_callbacks => $callbacks);\n+\t$self->{url} = $url;\n+\t$self->{svn_path} = $url;\n+\t$self->{repos_root} = $self->get_repos_root;\n+\t$self->{svn_path} =~ s#^\\Q$self->{repos_root}\\E(/|$)##;\n+\t$self->{cache} = { check_path => { r => 0, data => {} },\n+\t                   get_dir => { r => 0, data => {} } };\n+\t$RA = bless $self, $class;\n+}\n+\n+sub check_path {\n+\tmy ($self, $path, $r) = @_;\n+\tmy $cache = $self->{cache}->{check_path};\n+\tif ($r == $cache->{r} && exists $cache->{data}->{$path}) {\n+\t\treturn $cache->{data}->{$path};\n+\t}\n+\tmy $pool = SVN::Pool->new;\n+\tmy $t = $self->SUPER::check_path($path, $r, $pool);\n+\t$pool->clear;\n+\tif ($r != $cache->{r}) {\n+\t\t%{$cache->{data}} = ();\n+\t\t$cache->{r} = $r;\n+\t}\n+\t$cache->{data}->{$path} = $t;\n+}\n+\n+sub get_dir {\n+\tmy ($self, $dir, $r) = @_;\n+\tmy $cache = $self->{cache}->{get_dir};\n+\tif ($r == $cache->{r}) {\n+\t\tif (my $x = $cache->{data}->{$dir}) {\n+\t\t\treturn wantarray ? @$x : $x->[0];\n+\t\t}\n+\t}\n+\tmy $pool = SVN::Pool->new;\n+\tmy ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);\n+\tmy %dirents = map { $_ => { kind => $d->{$_}->kind } } keys %$d;\n+\t$pool->clear;\n+\tif ($r != $cache->{r}) {\n+\t\t%{$cache->{data}} = ();\n+\t\t$cache->{r} = $r;\n+\t}\n+\t$cache->{data}->{$dir} = [ \\%dirents, $r, $props ];\n+\twantarray ? (\\%dirents, $r, $props) : \\%dirents;\n+}\n+\n+sub DESTROY {\n+\t# do not call the real DESTROY since we store ourselves in $RA\n+}\n+\n+# get_log(paths, start, end, limit,\n+#         discover_changed_paths, strict_node_history, receiver)\n+sub get_log {\n+\tmy ($self, @args) = @_;\n+\tmy $pool = SVN::Pool->new;\n+\n+\t# svn_log_changed_path_t objects passed to get_log are likely to be\n+\t# overwritten even if only the refs are copied to an external variable,\n+\t# so we should dup the structures in their entirety.  Using an\n+\t# externally passed pool (instead of our temporary and quickly cleared\n+\t# pool in Git::SVN::Ra) does not help matters at all...\n+\tmy $receiver = pop @args;\n+\tmy $prefix = \"/\".$self->{svn_path};\n+\t$prefix =~ s#/+($)##;\n+\tmy $prefix_regex = qr#^\\Q$prefix\\E#;\n+\tpush(@args, sub {\n+\t\tmy ($paths) = $_[0];\n+\t\treturn &$receiver(@_) unless $paths;\n+\t\t$_[0] = ();\n+\t\tforeach my $p (keys %$paths) {\n+\t\t\tmy $i = $paths->{$p};\n+\t\t\t# Make path relative to our url, not repos_root\n+\t\t\t$p =~ s/$prefix_regex//;\n+\t\t\tmy %s = map { $_ => $i->$_; }\n+\t\t\t\tqw/copyfrom_path copyfrom_rev action/;\n+\t\t\tif ($s{'copyfrom_path'}) {\n+\t\t\t\t$s{'copyfrom_path'} =~ s/$prefix_regex//;\n+\t\t\t}\n+\t\t\t$_[0]{$p} = \\%s;\n+\t\t}\n+\t\t&$receiver(@_);\n+\t});\n+\n+\n+\t# the limit parameter was not supported in SVN 1.1.x, so we\n+\t# drop it.  Therefore, the receiver callback passed to it\n+\t# is made aware of this limitation by being wrapped if\n+\t# the limit passed to is being wrapped.\n+\tif (::compare_svn_version('1.2.0') <= 0) {\n+\t\tmy $limit = splice(@args, 3, 1);\n+\t\tif ($limit > 0) {\n+\t\t\tmy $receiver = pop @args;\n+\t\t\tpush(@args, sub { &$receiver(@_) if (--$limit >= 0) });\n+\t\t}\n+\t}\n+\tmy $ret = $self->SUPER::get_log(@args, $pool);\n+\t$pool->clear;\n+\t$ret;\n+}\n+\n+sub trees_match {\n+\tmy ($self, $url1, $rev1, $url2, $rev2) = @_;\n+\tmy $ctx = SVN::Client->new(auth => _auth_providers);\n+\tmy $out = IO::File->new_tmpfile;\n+\n+\t# older SVN (1.1.x) doesn't take $pool as the last parameter for\n+\t# $ctx->diff(), so we'll create a default one\n+\tmy $pool = SVN::Pool->new_default_sub;\n+\n+\t$ra_invalid = 1; # this will open a new SVN::Ra connection to $url1\n+\t$ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out);\n+\t$out->flush;\n+\tmy $ret = (($out->stat)[7] == 0);\n+\tclose $out or croak $!;\n+\n+\t$ret;\n+}\n+\n+sub get_commit_editor {\n+\tmy ($self, $log, $cb, $pool) = @_;\n+\n+\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();\n+\t$self->SUPER::get_commit_editor($log, $cb, @lock, $pool);\n+}\n+\n+sub gs_do_update {\n+\tmy ($self, $rev_a, $rev_b, $gs, $editor) = @_;\n+\tmy $new = ($rev_a == $rev_b);\n+\tmy $path = $gs->{path};\n+\n+\tif ($new && -e $gs->{index}) {\n+\t\tunlink $gs->{index} or die\n+\t\t  \"Couldn't unlink index: $gs->{index}: $!\\n\";\n+\t}\n+\tmy $pool = SVN::Pool->new;\n+\t$editor->set_path_strip($path);\n+\tmy (@pc) = split m#/#, $path;\n+\tmy $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),\n+\t                                1, $editor, $pool);\n+\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();\n+\n+\t# Since we can't rely on svn_ra_reparent being available, we'll\n+\t# just have to do some magic with set_path to make it so\n+\t# we only want a partial path.\n+\tmy $sp = '';\n+\tmy $final = join('/', @pc);\n+\twhile (@pc) {\n+\t\t$reporter->set_path($sp, $rev_b, 0, @lock, $pool);\n+\t\t$sp .= '/' if length $sp;\n+\t\t$sp .= shift @pc;\n+\t}\n+\tdie \"BUG: '$sp' != '$final'\\n\" if ($sp ne $final);\n+\n+\t$reporter->set_path($sp, $rev_a, $new, @lock, $pool);\n+\n+\t$reporter->finish_report($pool);\n+\t$pool->clear;\n+\t$editor->{git_commit_ok};\n+}\n+\n+# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and\n+# svn_ra_reparent didn't work before 1.4)\n+sub gs_do_switch {\n+\tmy ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;\n+\tmy $path = $gs->{path};\n+\tmy $pool = SVN::Pool->new;\n+\n+\tmy $full_url = $self->{url};\n+\tmy $old_url = $full_url;\n+\t$full_url .= '/' . $path if length $path;\n+\tmy ($ra, $reparented);\n+\n+\tif ($old_url =~ m#^svn(\\+ssh)?://# ||\n+\t    ($full_url =~ m#^https?://# &&\n+\t     escape_url($full_url) ne $full_url)) {\n+\t\t$_[0] = undef;\n+\t\t$self = undef;\n+\t\t$RA = undef;\n+\t\t$ra = Git::SVN::Ra->new($full_url);\n+\t\t$ra_invalid = 1;\n+\t} elsif ($old_url ne $full_url) {\n+\t\tSVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);\n+\t\t$self->{url} = $full_url;\n+\t\t$reparented = 1;\n+\t}\n+\n+\t$ra ||= $self;\n+\t$url_b = escape_url($url_b);\n+\tmy $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);\n+\tmy @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();\n+\t$reporter->set_path('', $rev_a, 0, @lock, $pool);\n+\t$reporter->finish_report($pool);\n+\n+\tif ($reparented) {\n+\t\tSVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);\n+\t\t$self->{url} = $old_url;\n+\t}\n+\n+\t$pool->clear;\n+\t$editor->{git_commit_ok};\n+}\n+\n+sub longest_common_path {\n+\tmy ($gsv, $globs) = @_;\n+\tmy %common;\n+\tmy $common_max = scalar @$gsv;\n+\n+\tforeach my $gs (@$gsv) {\n+\t\tmy @tmp = split m#/#, $gs->{path};\n+\t\tmy $p = '';\n+\t\tforeach (@tmp) {\n+\t\t\t$p .= length($p) ? \"/$_\" : $_;\n+\t\t\t$common{$p} ||= 0;\n+\t\t\t$common{$p}++;\n+\t\t}\n+\t}\n+\t$globs ||= [];\n+\t$common_max += scalar @$globs;\n+\tforeach my $glob (@$globs) {\n+\t\tmy @tmp = split m#/#, $glob->{path}->{left};\n+\t\tmy $p = '';\n+\t\tforeach (@tmp) {\n+\t\t\t$p .= length($p) ? \"/$_\" : $_;\n+\t\t\t$common{$p} ||= 0;\n+\t\t\t$common{$p}++;\n+\t\t}\n+\t}\n+\n+\tmy $longest_path = '';\n+\tforeach (sort {length $b <=> length $a} keys %common) {\n+\t\tif ($common{$_} == $common_max) {\n+\t\t\t$longest_path = $_;\n+\t\t\tlast;\n+\t\t}\n+\t}\n+\t$longest_path;\n+}\n+\n+sub gs_fetch_loop_common {\n+\tmy ($self, $base, $head, $gsv, $globs) = @_;\n+\treturn if ($base > $head);\n+\tmy $inc = $_log_window_size;\n+\tmy ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);\n+\tmy $longest_path = longest_common_path($gsv, $globs);\n+\tmy $ra_url = $self->{url};\n+\tmy $find_trailing_edge;\n+\twhile (1) {\n+\t\tmy %revs;\n+\t\tmy $err;\n+\t\tmy $err_handler = $SVN::Error::handler;\n+\t\t$SVN::Error::handler = sub {\n+\t\t\t($err) = @_;\n+\t\t\tskip_unknown_revs($err);\n+\t\t};\n+\t\tsub _cb {\n+\t\t\tmy ($paths, $r, $author, $date, $log) = @_;\n+\t\t\t[ $paths,\n+\t\t\t  { author => $author, date => $date, log => $log } ];\n+\t\t}\n+\t\t$self->get_log([$longest_path], $min, $max, 0, 1, 1,\n+\t\t               sub { $revs{$_[1]} = _cb(@_) });\n+\t\tif ($err) {\n+\t\t\tprint \"Checked through r$max\\r\";\n+\t\t} else {\n+\t\t\t$find_trailing_edge = 1;\n+\t\t}\n+\t\tif ($err and $find_trailing_edge) {\n+\t\t\tprint STDERR \"Path '$longest_path' \",\n+\t\t\t\t     \"was probably deleted:\\n\",\n+\t\t\t\t     $err->expanded_message,\n+\t\t\t\t     \"\\nWill attempt to follow \",\n+\t\t\t\t     \"revisions r$min .. r$max \",\n+\t\t\t\t     \"committed before the deletion\\n\";\n+\t\t\tmy $hi = $max;\n+\t\t\twhile (--$hi >= $min) {\n+\t\t\t\tmy $ok;\n+\t\t\t\t$self->get_log([$longest_path], $min, $hi,\n+\t\t\t\t               0, 1, 1, sub {\n+\t\t\t\t               $ok = $_[1];\n+\t\t\t\t               $revs{$_[1]} = _cb(@_) });\n+\t\t\t\tif ($ok) {\n+\t\t\t\t\tprint STDERR \"r$min .. r$ok OK\\n\";\n+\t\t\t\t\tlast;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t$find_trailing_edge = 0;\n+\t\t}\n+\t\t$SVN::Error::handler = $err_handler;\n+\n+\t\tmy %exists = map { $_->{path} => $_ } @$gsv;\n+\t\tforeach my $r (sort {$a <=> $b} keys %revs) {\n+\t\t\tmy ($paths, $logged) = @{$revs{$r}};\n+\n+\t\t\tforeach my $gs ($self->match_globs(\\%exists, $paths,\n+\t\t\t                                   $globs, $r)) {\n+\t\t\t\tif ($gs->rev_map_max >= $r) {\n+\t\t\t\t\tnext;\n+\t\t\t\t}\n+\t\t\t\tnext unless $gs->match_paths($paths, $r);\n+\t\t\t\t$gs->{logged_rev_props} = $logged;\n+\t\t\t\tif (my $last_commit = $gs->last_commit) {\n+\t\t\t\t\t$gs->assert_index_clean($last_commit);\n+\t\t\t\t}\n+\t\t\t\tmy $log_entry = $gs->do_fetch($paths, $r);\n+\t\t\t\tif ($log_entry) {\n+\t\t\t\t\t$gs->do_git_commit($log_entry);\n+\t\t\t\t}\n+\t\t\t\t$Git::SVN::INDEX_FILES{$gs->{index}} = 1;\n+\t\t\t}\n+\t\t\tforeach my $g (@$globs) {\n+\t\t\t\tmy $k = \"svn-remote.$g->{remote}.\" .\n+\t\t\t\t        \"$g->{t}-maxRev\";\n+\t\t\t\tGit::SVN::tmp_config($k, $r);\n+\t\t\t}\n+\t\t\tif ($ra_invalid) {\n+\t\t\t\t$_[0] = undef;\n+\t\t\t\t$self = undef;\n+\t\t\t\t$RA = undef;\n+\t\t\t\t$self = Git::SVN::Ra->new($ra_url);\n+\t\t\t\t$ra_invalid = undef;\n+\t\t\t}\n+\t\t}\n+\t\t# pre-fill the .rev_db since it'll eventually get filled in\n+\t\t# with '0' x40 if something new gets committed\n+\t\tforeach my $gs (@$gsv) {\n+\t\t\tnext if $gs->rev_map_max >= $max;\n+\t\t\tnext if defined $gs->rev_map_get($max);\n+\t\t\t$gs->rev_map_set($max, 0 x40);\n+\t\t}\n+\t\tforeach my $g (@$globs) {\n+\t\t\tmy $k = \"svn-remote.$g->{remote}.$g->{t}-maxRev\";\n+\t\t\tGit::SVN::tmp_config($k, $max);\n+\t\t}\n+\t\tlast if $max >= $head;\n+\t\t$min = $max + 1;\n+\t\t$max += $inc;\n+\t\t$max = $head if ($max > $head);\n+\t}\n+\tGit::SVN::gc();\n+}\n+\n+sub get_dir_globbed {\n+\tmy ($self, $left, $depth, $r) = @_;\n+\n+\tmy @x = eval { $self->get_dir($left, $r) };\n+\treturn unless scalar @x == 3;\n+\tmy $dirents = $x[0];\n+\tmy @finalents;\n+\tforeach my $de (keys %$dirents) {\n+\t\tnext if $dirents->{$de}->{kind} != $SVN::Node::dir;\n+\t\tif ($depth > 1) {\n+\t\t\tmy @args = (\"$left/$de\", $depth - 1, $r);\n+\t\t\tforeach my $dir ($self->get_dir_globbed(@args)) {\n+\t\t\t\tpush @finalents, \"$de/$dir\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\tpush @finalents, $de;\n+\t\t}\n+\t}\n+\t@finalents;\n+}\n+\n+# return value: 0 -- don't ignore, 1 -- ignore\n+sub is_ref_ignored {\n+\tmy ($g, $p) = @_;\n+\tmy $refname = $g->{ref}->full_path($p);\n+\treturn 1 if defined($g->{ignore_refs_regex}) &&\n+\t            $refname =~ m!$g->{ignore_refs_regex}!;\n+\treturn 0 unless defined($_ignore_refs_regex);\n+\treturn 1 if $refname =~ m!$_ignore_refs_regex!o;\n+\treturn 0;\n+}\n+\n+sub match_globs {\n+\tmy ($self, $exists, $paths, $globs, $r) = @_;\n+\n+\tsub get_dir_check {\n+\t\tmy ($self, $exists, $g, $r) = @_;\n+\n+\t\tmy @dirs = $self->get_dir_globbed($g->{path}->{left},\n+\t\t                                  $g->{path}->{depth},\n+\t\t                                  $r);\n+\n+\t\tforeach my $de (@dirs) {\n+\t\t\tmy $p = $g->{path}->full_path($de);\n+\t\t\tnext if $exists->{$p};\n+\t\t\tnext if (length $g->{path}->{right} &&\n+\t\t\t\t ($self->check_path($p, $r) !=\n+\t\t\t\t  $SVN::Node::dir));\n+\t\t\tnext unless $p =~ /$g->{path}->{regex}/;\n+\t\t\t$exists->{$p} = Git::SVN->init($self->{url}, $p, undef,\n+\t\t\t\t\t $g->{ref}->full_path($de), 1);\n+\t\t}\n+\t}\n+\tforeach my $g (@$globs) {\n+\t\tif (my $path = $paths->{\"/$g->{path}->{left}\"}) {\n+\t\t\tif ($path->{action} =~ /^[AR]$/) {\n+\t\t\t\tget_dir_check($self, $exists, $g, $r);\n+\t\t\t}\n+\t\t}\n+\t\tforeach (keys %$paths) {\n+\t\t\tif (/$g->{path}->{left_regex}/ &&\n+\t\t\t    !/$g->{path}->{regex}/) {\n+\t\t\t\tnext if $paths->{$_}->{action} !~ /^[AR]$/;\n+\t\t\t\tget_dir_check($self, $exists, $g, $r);\n+\t\t\t}\n+\t\t\tnext unless /$g->{path}->{regex}/;\n+\t\t\tmy $p = $1;\n+\t\t\tmy $pathname = $g->{path}->full_path($p);\n+\t\t\tnext if is_ref_ignored($g, $p);\n+\t\t\tnext if $exists->{$pathname};\n+\t\t\tnext if ($self->check_path($pathname, $r) !=\n+\t\t\t         $SVN::Node::dir);\n+\t\t\t$exists->{$pathname} = Git::SVN->init(\n+\t\t\t                      $self->{url}, $pathname, undef,\n+\t\t\t                      $g->{ref}->full_path($p), 1);\n+\t\t}\n+\t\tmy $c = '';\n+\t\tforeach (split m#/#, $g->{path}->{left}) {\n+\t\t\t$c .= \"/$_\";\n+\t\t\tnext unless ($paths->{$c} &&\n+\t\t\t             ($paths->{$c}->{action} =~ /^[AR]$/));\n+\t\t\tget_dir_check($self, $exists, $g, $r);\n+\t\t}\n+\t}\n+\tvalues %$exists;\n+}\n+\n+sub minimize_url {\n+\tmy ($self) = @_;\n+\treturn $self->{url} if ($self->{url} eq $self->{repos_root});\n+\tmy $url = $self->{repos_root};\n+\tmy @components = split(m!/!, $self->{svn_path});\n+\tmy $c = '';\n+\tdo {\n+\t\t$url .= \"/$c\" if length $c;\n+\t\teval {\n+\t\t\tmy $ra = (ref $self)->new($url);\n+\t\t\tmy $latest = $ra->get_latest_revnum;\n+\t\t\t$ra->get_log(\"\", $latest, 0, 1, 0, 1, sub {});\n+\t\t};\n+\t} while ($@ && ($c = shift @components));\n+\t$url;\n+}\n+\n+sub can_do_switch {\n+\tmy $self = shift;\n+\tunless (defined $can_do_switch) {\n+\t\tmy $pool = SVN::Pool->new;\n+\t\tmy $rep = eval {\n+\t\t\t$self->do_switch(1, '', 0, $self->{url},\n+\t\t\t                 SVN::Delta::Editor->new, $pool);\n+\t\t};\n+\t\tif ($@) {\n+\t\t\t$can_do_switch = 0;\n+\t\t} else {\n+\t\t\t$rep->abort_report($pool);\n+\t\t\t$can_do_switch = 1;\n+\t\t}\n+\t\t$pool->clear;\n+\t}\n+\t$can_do_switch;\n+}\n+\n+sub skip_unknown_revs {\n+\tmy ($err) = @_;\n+\tmy $errno = $err->apr_err();\n+\t# Maybe the branch we're tracking didn't\n+\t# exist when the repo started, so it's\n+\t# not an error if it doesn't, just continue\n+\t#\n+\t# Wonderfully consistent library, eh?\n+\t# 160013 - svn:// and file://\n+\t# 175002 - http(s)://\n+\t# 175007 - http(s):// (this repo required authorization, too...)\n+\t#   More codes may be discovered later...\n+\tif ($errno == 175007 || $errno == 175002 || $errno == 160013) {\n+\t\tmy $err_key = $err->expanded_message;\n+\t\t# revision numbers change every time, filter them out\n+\t\t$err_key =~ s/\\d+/\\0/g;\n+\t\t$err_key = \"$errno\\0$err_key\";\n+\t\tunless ($ignored_err{$err_key}) {\n+\t\t\twarn \"W: Ignoring error from SVN, path probably \",\n+\t\t\t     \"does not exist: ($errno): \",\n+\t\t\t     $err->expanded_message,\"\\n\";\n+\t\t\twarn \"W: Do not be alarmed at the above message \",\n+\t\t\t     \"git-svn is just searching aggressively for \",\n+\t\t\t     \"old history.\\n\",\n+\t\t\t     \"This may take a while on large repositories\\n\";\n+\t\t\t$ignored_err{$err_key} = 1;\n+\t\t}\n+\t\treturn;\n+\t}\n+\tdie \"Error from SVN, ($errno): \", $err->expanded_message,\"\\n\";\n+}\n+\n+1;\n+__END__\n+\n+Git::SVN::Ra - Subversion remote access functions for git-svn\n+\n+=head1 SYNOPSIS\n+\n+    use Git::SVN::Ra;\n+\n+    my $ra = Git::SVN::Ra->new($branchurl);\n+    my ($dirents, $fetched_revnum, $props) =\n+        $ra->get_dir('.', $SVN::Core::INVALID_REVNUM);\n+\n+=head1 DESCRIPTION\n+\n+This is a wrapper around the L<SVN::Ra> module for use by B<git-svn>.\n+It fills in some default parameters (such as the authentication\n+scheme), smooths over incompatibilities between libsvn versions, adds\n+caching, and implements some functions specific to B<git-svn>.\n+\n+Do not use it unless you are developing git-svn.  The interface will\n+change as git-svn evolves.\n+\n+=head1 DEPENDENCIES\n+\n+Subversion perl bindings,\n+L<Git::SVN>.\n+\n+C<Git::SVN::Ra> has not been tested using callers other than\n+B<git-svn> itself.\n+\n+=head1 SEE ALSO\n+\n+L<SVN::Ra>.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+None.\ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 32caf214..08afdcb2 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -30,6 +30,7 @@ my %pm = (\n \t'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',\n \t'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',\n \t'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',\n+\t'Git/SVN/Ra.pm' => '$(INST_LIBDIR)/Git/SVN/Ra.pm',\n );\n \n # We come with our own bundled Error.pm. It's not in the set of default\n-- \n1.7.10\n"},{"id":"193222","messageId":"20120609223535.GG28412@burratino","threadId":"28184","inReplyTo":"20120609222039.GD28412@burratino","subject":"[PATCH 3/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-09T22:35:35Z","receivedAt":"2012-06-09T22:35:35Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Since v1.7.0-rc2~11 (git-svn: persistent memoization, 2010-01-30),\ngit-svn has maintained some private per-repository caches in\n.git/svn/.caches to avoid refetching and recalculating some\nmergeinfo-related information with every \"git svn fetch\".\n\nThese caches use the 'nstore' format from the perl core module\nStorable, which can be read and written quickly and was designed for\ntransfer over the wire (the 'n' stands for 'network').  This format is\nendianness-independent and independent of floating-point\nrepresentation.\n\nUnfortunately the format is *not* independent of the perl version ---\nnew perl versions will write files that very old perl cannot read.\nWorse, the format is not independent of the size of a perl integer.\nSo if you toggle perl's use64bitint compile-time option, then using\n'git svn fetch' on your old repositories produces errors like this:\n\n\tByte order is not compatible at ../../lib/Storable.pm (autosplit\n\tinto ../../lib/auto/Storable/_retrieve.al) line 380, at\n\t/usr/share/perl/5.12/Memoize/Storable.pm line 21\n\nThat is, upgrading perl to a version that uses use64bitint for the\nfirst time makes git-svn suddenly refuse to fetch in existing\nrepositories.  Removing .git/svn/.caches lets git-svn recover.\n\nIt's time to switch to a platform independent serializer backend with\nbetter compatibility guarantees.  This patch uses YAML::Any.\n\nOther choices were considered:\n\n - thawing data from Data::Dumper involves \"eval\".  Doing that without\n   creating a security risk is fussy.\n\n - the JSON API works on scalars in memory and doesn't provide a\n   standard way to serialize straight to disk.\n\nYAML::Any is reasonably fast and has a pleasant API.  In most\nbackends, LoadFile() reads the entire file into a scalar anyway and\nconverts it as a second step, but having an interface that allows the\ndeserialization to happen on the fly without a temporary is still a\ncomfort.\n\nYAML::Any is not a core perl module, so we take care to use it when\nand only when it is available.  Installations without that module\nshould fall back to using Storable with all its quirks, keeping their\ncache files in\n\n\t.git/svn/.caches/*.db\n\nInstallations with YAML peacefully coexist by keeping a separate set\nof cache files in\n\n\t.git/svn/.caches/*.yaml.\n\nIn most cases, switching between is a one-time thing, so it doesn't\nseem worth the complication to migrate existing caches.\n\nThe upshot: after this patch, as long as YAML::Any is installed you\ncan move your git repository between machines with different perl\ninstallations and \"git svn fetch\" will work fine.  If you do not have\nYAML::Any, the behavior is unchanged (and in particular does not get\nany worse).\n\nReported-by: Sandro Weiser <sandro.weiser@informatik.tu-chemnitz.de>\nReported-by: Bdale Garbee <bdale@gag.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nThanks for reading.\n\n git-svn.perl                 |   31 +++++++++++---\n perl/Git/SVN/Memoize/YAML.pm |   93 ++++++++++++++++++++++++++++++++++++++++++\n perl/Makefile.PL             |    1 +\n 3 files changed, 119 insertions(+), 6 deletions(-)\n create mode 100644 perl/Git/SVN/Memoize/YAML.pm\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex 24c0af2e..0b074c4c 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -2055,6 +2055,10 @@ use Time::Local;\n use Memoize;  # core since 5.8.0, Jul 2002\n use Memoize::Storable;\n use POSIX qw(:signal_h);\n+my $can_use_yaml;\n+BEGIN {\n+\t$can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1};\n+}\n \n my ($_gc_nr, $_gc_period);\n \n@@ -3577,6 +3581,17 @@ sub has_no_changes {\n \t\tcommand_oneline(\"rev-parse\", \"$commit~1^{tree}\"));\n }\n \n+sub tie_for_persistent_memoization {\n+\tmy $hash = shift;\n+\tmy $path = shift;\n+\n+\tif ($can_use_yaml) {\n+\t\ttie %$hash => 'Git::SVN::Memoize::YAML', \"$path.yaml\";\n+\t} else {\n+\t\ttie %$hash => 'Memoize::Storable', \"$path.db\", 'nstore';\n+\t}\n+}\n+\n # The GIT_DIR environment variable is not always set until after the command\n # line arguments are processed, so we can't memoize in a BEGIN block.\n {\n@@ -3589,22 +3604,26 @@ sub has_no_changes {\n \t\tmy $cache_path = \"$ENV{GIT_DIR}/svn/.caches/\";\n \t\tmkpath([$cache_path]) unless -d $cache_path;\n \n-\t\ttie my %lookup_svn_merge_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/lookup_svn_merge.db\", 'nstore';\n+\t\tmy %lookup_svn_merge_cache;\n+\t\tmy %check_cherry_pick_cache;\n+\t\tmy %has_no_changes_cache;\n+\n+\t\ttie_for_persistent_memoization(\\%lookup_svn_merge_cache,\n+\t\t    \"$cache_path/lookup_svn_merge\");\n \t\tmemoize 'lookup_svn_merge',\n \t\t\tSCALAR_CACHE => 'FAULT',\n \t\t\tLIST_CACHE => ['HASH' => \\%lookup_svn_merge_cache],\n \t\t;\n \n-\t\ttie my %check_cherry_pick_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/check_cherry_pick.db\", 'nstore';\n+\t\ttie_for_persistent_memoization(\\%check_cherry_pick_cache,\n+\t\t    \"$cache_path/check_cherry_pick\");\n \t\tmemoize 'check_cherry_pick',\n \t\t\tSCALAR_CACHE => 'FAULT',\n \t\t\tLIST_CACHE => ['HASH' => \\%check_cherry_pick_cache],\n \t\t;\n \n-\t\ttie my %has_no_changes_cache => 'Memoize::Storable',\n-\t\t    \"$cache_path/has_no_changes.db\", 'nstore';\n+\t\ttie_for_persistent_memoization(\\%has_no_changes_cache,\n+\t\t    \"$cache_path/has_no_changes\");\n \t\tmemoize 'has_no_changes',\n \t\t\tSCALAR_CACHE => ['HASH' => \\%has_no_changes_cache],\n \t\t\tLIST_CACHE => 'FAULT',\ndiff --git a/perl/Git/SVN/Memoize/YAML.pm b/perl/Git/SVN/Memoize/YAML.pm\nnew file mode 100644\nindex 00000000..9676b8f2\n--- /dev/null\n+++ b/perl/Git/SVN/Memoize/YAML.pm\n@@ -0,0 +1,93 @@\n+package Git::SVN::Memoize::YAML;\n+use warnings;\n+use strict;\n+use YAML::Any ();\n+\n+# based on Memoize::Storable.\n+\n+sub TIEHASH {\n+\tmy $package = shift;\n+\tmy $filename = shift;\n+\tmy $truehash = (-e $filename) ? YAML::Any::LoadFile($filename) : {};\n+\tmy $self = {FILENAME => $filename, H => $truehash};\n+\tbless $self => $package;\n+}\n+\n+sub STORE {\n+\tmy $self = shift;\n+\t$self->{H}{$_[0]} = $_[1];\n+}\n+\n+sub FETCH {\n+\tmy $self = shift;\n+\t$self->{H}{$_[0]};\n+}\n+\n+sub EXISTS {\n+\tmy $self = shift;\n+\texists $self->{H}{$_[0]};\n+}\n+\n+sub DESTROY {\n+\tmy $self = shift;\n+\tYAML::Any::DumpFile($self->{FILENAME}, $self->{H});\n+}\n+\n+sub SCALAR {\n+\tmy $self = shift;\n+\tscalar(%{$self->{H}});\n+}\n+\n+sub FIRSTKEY {\n+\t'Fake hash from Git::SVN::Memoize::YAML';\n+}\n+\n+sub NEXTKEY {\n+\tundef;\n+}\n+\n+1;\n+__END__\n+\n+=head1 NAME\n+\n+Git::SVN::Memoize::YAML - store Memoized data in YAML format\n+\n+=head1 SYNOPSIS\n+\n+    use Memoize;\n+    use Git::SVN::Memoize::YAML;\n+\n+    tie my %cache => 'Git::SVN::Memoize::YAML', $filename;\n+    memoize('slow_function', SCALAR_CACHE => [HASH => \\%cache]);\n+    slow_function(arguments);\n+\n+=head1 DESCRIPTION\n+\n+This module provides a class that can be used to tie a hash to a\n+YAML file.  The file is read when the hash is initialized and\n+rewritten when the hash is destroyed.\n+\n+The intent is to allow L<Memoize> to back its cache with a file in\n+YAML format, just like L<Memoize::Storable> allows L<Memoize> to\n+back its cache with a file in Storable format.  Unlike the Storable\n+format, the YAML format is platform-independent and fairly stable.\n+\n+Carps on error.\n+\n+=head1 DIAGNOSTICS\n+\n+See L<YAML::Any>.\n+\n+=head1 DEPENDENCIES\n+\n+L<YAML::Any> from CPAN.\n+\n+=head1 INCOMPATIBILITIES\n+\n+None reported.\n+\n+=head1 BUGS\n+\n+The entire cache is read into a Perl hash when loading the file,\n+so this is not very scalable.\ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 08afdcb2..2c20290f 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -27,6 +27,7 @@ MAKE_FRAG\n my %pm = (\n \t'Git.pm' => '$(INST_LIBDIR)/Git.pm',\n \t'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',\n+\t'Git/SVN/Memoize/YAML.pm' => '$(INST_LIBDIR)/Git/SVN/Memoize/YAML.pm',\n \t'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',\n \t'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',\n \t'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',\n-- \n1.7.10\n"},{"id":"193235","messageId":"20120610090039.GA12868@dcvr.yhbt.net","threadId":"28184","inReplyTo":"20120609222039.GD28412@burratino","subject":"Re: [PATCH v2 0/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2012-06-10T09:00:39Z","receivedAt":"2012-06-10T09:00:39Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Eric Wong wrote:\n> > These should die on errors, right?\n> \n> Yep, they do.  I didn't bother doing the CARP_NOT thing --- let's wait\n> for a bug report or rainy day and then handle errors properly (by\n> invalidating the cache when appropriate).\n\nOK.\n\n> Patches 1-2 are independent from patch 3.  I'm sending them this way\n> for no particular reason.\n> \n> Thoughts of all kinds welcome, as usual.\n\nThanks!  I only had a trivial conflict with 2/3, but fixed it,\nsigned-off and pushed to master of git://bogomips.org/git-svn\n\n> Jonathan Nieder (3):\n>   git-svn: make Git::SVN::Editor a separate file\n>   git-svn: make Git::SVN::RA a separate file\n\n2/3 had a conflict with commit c26ddce86d7215b4d9687bd4c6b5dd43a3fabf31\nwhich I resolved by hand\n(git-svn: platform auth providers are working only on 1.6.15 or newer)\n\n--- a.mbox\t2012-06-10 08:58:19.000000000 +0000\n+++ b.mbox\t2012-06-10 08:58:23.000000000 +0000\n@@ -64,7 +64,7 @@\n -\n -\t# earlier 1.6.x versions would segfault, and <= 1.5.x didn't have\n -\t# this function\n--\tif (::compare_svn_version('1.6.12') > 0) {\n+-\tif (::compare_svn_version('1.6.15') >= 0) {\n -\t\tmy $config = SVN::Core::config_get_config($config_dir);\n -\t\tmy ($p, @a);\n -\t\t# config_get_config returns all config files from\n@@ -688,7 +688,7 @@\n +\n +\t# earlier 1.6.x versions would segfault, and <= 1.5.x didn't have\n +\t# this function\n-+\tif (::compare_svn_version('1.6.12') > 0) {\n++\tif (::compare_svn_version('1.6.15') >= 0) {\n +\t\tmy $config = SVN::Core::config_get_config($config_dir);\n +\t\tmy ($p, @a);\n +\t\t# config_get_config returns all config files from\n"},{"id":"193240","messageId":"20120610100447.GE30151@burratino","threadId":"28184","inReplyTo":"20120610090039.GA12868@dcvr.yhbt.net","subject":"Re: [PATCH v2 0/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-10T10:04:47Z","receivedAt":"2012-06-10T10:04:47Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric Wong wrote:\n\n> 2/3 had a conflict with commit c26ddce86d7215b4d9687bd4c6b5dd43a3fabf31\n> which I resolved by hand\n\nThat was fast. :)  Checked by glancing through the output of\n\n\tgit diff 9f7ad147^:git-svn.perl 9f7ad147:perl/Git/SVN/Ra.pm\n\nLooks good.\n\nGood night,\nJonathan\n"},{"id":"193309","messageId":"7vy5nt99d0.fsf@alter.siamese.dyndns.org","threadId":"28184","inReplyTo":"20120610100447.GE30151@burratino","subject":"Re: [PATCH v2 0/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-06-11T15:06:03Z","receivedAt":"2012-06-11T15:06:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Eric Wong wrote:\n>\n>> 2/3 had a conflict with commit c26ddce86d7215b4d9687bd4c6b5dd43a3fabf31\n>> which I resolved by hand\n>\n> That was fast. :)  Checked by glancing through the output of\n>\n> \tgit diff 9f7ad147^:git-svn.perl 9f7ad147:perl/Git/SVN/Ra.pm\n>\n> Looks good.\n>\n> Good night,\n> Jonathan\n\nThanks, both.  Pulled.\n"},{"id":"193521","messageId":"20120613013446.GA3759@burratino","threadId":"28184","inReplyTo":"20120609223535.GG28412@burratino","subject":"Re: [PATCH 3/3] git-svn: use YAML format for mergeinfo cache when possible","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-06-13T01:34:46Z","receivedAt":"2012-06-13T01:34:46Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> Unfortunately the format is *not* independent of the perl version ---\n> new perl versions will write files that very old perl cannot read.\n\nTrue and documented.\n\n> Worse, the format is not independent of the size of a perl integer.\n> So if you toggle perl's use64bitint compile-time option, then using\n> 'git svn fetch' on your old repositories produces errors like this:\n>\n>\tByte order is not compatible at ../../lib/Storable.pm (autosplit\n\nWeird and contrary to documentation.  First, why byte order?  Wouldn't\nthe problem be word size, if anything?  nstore implies network-endian,\nafter all.\n\nExamining the cache file explains it:\n\n\t$ xxd <.git/svn/.caches/lookup_svn_merge.db | head -1\n\t0000000: 7073 7430 0408 0831 3233 3435 3637 3804  pst0...12345678.\n\nThe byte order is \"12345678\", which reflects both endianness and word\nsize.\n\nNotice that the format is not network-endian.  That is because\nMemoize::Storable ignores the 'nstore' option, for silly reasons:\n\n\thttps://rt.cpan.org/Public/Bug/Display.html?id=77790\n\nAh.  (Thanks to Tim Retout for noticing.)\n\nAvoiding this arcane and fragile facility when possible still seems to\nme like a reasonable behavior for git-svn, but it's also nice to see\nthat the problem will eventually fix itself.  Sorry for the confusion.\n\nAnd until then, using a renamed cache with a format (YAML) known to be\nplatform-agnostic means git-svn can keep working on repositories that\nhave caches written by unfixed versions of perl.\n\nCiao,\nJonathan\n"}]}