Fix git-svn tests for SVN 1.7.5.

21 messages, 4 authors, 2016-06-15 · open the first message on its own page

Fix git-svn tests for SVN 1.7.5.

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

Hi,

I've fixed the git-svn tests for SVN 1.7 and tested with SVN 1.7.5.  SVN 1.7
changed its expectations of path and URL formats and git-svn did not comply
with them.  The new code uses SVN's own canonicalization routines where
available.  This has been reported in several places...
https://bugs.gentoo.org/show_bug.cgi?id=418431
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=678764
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661094
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=678137
https://trac.macports.org/ticket/32753

It also split the internal classes out of git-svn.perl and into their own
modules in perl/Git/ to make them easier to work on.  They compile alone, but
remain heavily intertwined with each other and git-svn.  I didn't want to go
very far down that rabbit hole.

This makes the tests pass, but I'm pretty sure plenty of canonicalization
problems remain untested.  Hopefully by attacking the problem at the root (ie.
in the Git::SVN and Git::SVN::Ra accessors) it will wipe out a range of problems.

t9100-git-svn-basic.sh tests 11-13 continue to fail for what look like
unrelated reasons to do with SVN and symlinks.

There's a lot of work in this change, so I felt it better to submit the
patches as a link to a git repository rather than attach a pile of patches.
Here is my repository, the work is in the fix-canonical branch.
https://github.com/schwern/git

Here's a summary of what was done.

* Changed git-svn's main canonicalization routines to use SVN's API.

* Replaced other ad-hoc canonicalization routines with git-svn's
  single routine.

* Moved all the Git:: classes inside git-svn into their own .pm files
  in perl/Git.  They compile, but don't do much more than that alone.
  They're still heavily dependent on git-svn.  It's a start.

* Added Git::SVN->url, Git::SVN->path and Git::SVN::Ra->url to replace
  code grabbing at hash keys.

* Made the above automatically canonicalize their path or url.

* Found some key locations which were not canonicalizing.

* Made the process of adding a new Perl module easier by having the
  Makefile.PL scan for .pm files.


-- 
Alligator sandwich, and make it snappy!

Re: Fix git-svn tests for SVN 1.7.5.

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:54:16

Hi!

Michael G Schwern wrote:
I've fixed the git-svn tests for SVN 1.7 and tested with SVN 1.7.5.
Thanks.  git-svn is not maintained by Junio but by Eric and others on
the list.  I'm cc-ing Eric and Ben Walton so they can benefit from
your work.
                                                                     SVN 1.7
changed its expectations of path and URL formats and git-svn did not comply
with them.  The new code uses SVN's own canonicalization routines where
available.  This has been reported in several places...
https://bugs.gentoo.org/show_bug.cgi?id=418431
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=678764
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661094
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=678137
https://trac.macports.org/ticket/32753

It also split the internal classes out of git-svn.perl and into their own
modules in perl/Git/ to make them easier to work on.  They compile alone, but
remain heavily intertwined with each other and git-svn.  I didn't want to go
very far down that rabbit hole.

This makes the tests pass, but I'm pretty sure plenty of canonicalization
problems remain untested.  Hopefully by attacking the problem at the root (ie.
in the Git::SVN and Git::SVN::Ra accessors) it will wipe out a range of problems.

t9100-git-svn-basic.sh tests 11-13 continue to fail for what look like
unrelated reasons to do with SVN and symlinks.

There's a lot of work in this change, so I felt it better to submit the
patches as a link to a git repository rather than attach a pile of patches.
Here is my repository, the work is in the fix-canonical branch.
https://github.com/schwern/git
It is indeed quite the intimidating pile of patches, so I do not think
we will be able to apply it all in one chunk as-is. :(

My advice would be to send five or so of the patches that you would
like to be reviewed first, inline, one per message, in reply to this
message so we can start to work on that.  Presumably the patches do
not regress git-svn's behavior but only make it saner, so even if this
is not a complete fix it should allow us to get started.  See
Documentation/SubmittingPatches for more hints.

Thanks and hope that helps,
Jonathan

Re: Fix git-svn tests for SVN 1.7.5.

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

On 2012.7.17 10:44 AM, Jonathan Nieder wrote:
Michael G Schwern wrote:
quoted
I've fixed the git-svn tests for SVN 1.7 and tested with SVN 1.7.5.
Thanks.  git-svn is not maintained by Junio but by Eric and others on
the list.  I'm cc-ing Eric and Ben Walton so they can benefit from
your work.
Thanks.

quoted
There's a lot of work in this change, so I felt it better to submit the
patches as a link to a git repository rather than attach a pile of patches.
Here is my repository, the work is in the fix-canonical branch.
https://github.com/schwern/git
It is indeed quite the intimidating pile of patches, so I do not think
we will be able to apply it all in one chunk as-is. :(

My advice would be to send five or so of the patches that you would
like to be reviewed first, inline, one per message, in reply to this
message so we can start to work on that.  Presumably the patches do
not regress git-svn's behavior but only make it saner, so even if this
is not a complete fix it should allow us to get started.  See
Documentation/SubmittingPatches for more hints.
Yes, the refactorings are all as rote as I could make them and only lightly
touch the code enough to make the canonicalization possible... with a bit more
work than was strictly necessary around the Perl build system.

Let me do a bit of rebase work to make things work better as a series of
submissions and I'll get back to you.

I'm new here, and I'll play nice, but let me go on record to state that Git
asking for individual emails with inline patches feels like Sendmail Corp
asking to be faxed an email thread.  I was kinda hoping SubmittingPatches
wasn't serious about that and it was some sort of policy artifact that was
never updated. :-/


-- 
151. The proper way to report to my Commander is "Specialist Schwarz,
     reporting as ordered, Sir" not "You can't prove a thing!"
    -- The 213 Things Skippy Is No Longer Allowed To Do In The U.S. Army
           http://skippyslist.com/list/

Find .pm files automatically (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

On 2012.7.17 10:44 AM, Jonathan Nieder wrote:
My advice would be to send five or so of the patches that you would
like to be reviewed first, inline, one per message, in reply to this
message so we can start to work on that.  Presumably the patches do
not regress git-svn's behavior but only make it saner, so even if this
is not a complete fix it should allow us to get started.  See
Documentation/SubmittingPatches for more hints.
Ok, here goes.

First patch overhauls perl/Makefile.PL to make it easier to add .pm files,
which I'm going to be doing a lot of.  Instead of having to manually add to
the %pm hash, it scans for .pm files.

It also moves Error.pm into a bundle directory.  This both makes it just
another directory to scan (or not scan), but it also makes it possible to
bundle additional modules in the future.  ExtUtils::MakeMaker uses this
technique itself.

You still have to remember to add them to the other Makefile.

This is available as a branch.
https://github.com/schwern/git/tree/git-svn/easier_modules

From 47a723a860cded6b16a716ea74c5bc029ee5b0ac Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 00:05:38 -0700
Subject: [PATCH 01/11] Make the process of adding a module less blecherous.

* Scan for .pm files and build %pms rather than having to do it by hand.
* Move the bundled Error into its own directory so we can bundle other modules.

In addition...
* Add all the .pm files to the all dependency in the alternative Makefile
---
 perl/Makefile                                     |  6 ++--
 perl/Makefile.PL                                  | 42 +++++++++++++----------
 perl/{private-Error.pm => bundles/Error/Error.pm} |  0
 perl/bundles/README                               | 10 ++++++
 4 files changed, 36 insertions(+), 22 deletions(-)
 rename perl/{private-Error.pm => bundles/Error/Error.pm} (100%)
 create mode 100644 perl/bundles/README
diff --git a/perl/Makefile b/perl/Makefile
index 6ca7d47..4f25930 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -33,7 +33,7 @@ modules += Git/SVN/Prompt
 modules += Git/SVN/Ra

 $(makfile): ../GIT-CFLAGS Makefile
-	echo all: private-Error.pm Git.pm Git/I18N.pm > $@
+	echo all: bundles/Error/Error.pm $(modules) > $@
 	set -e; \
 	for i in $(modules); \
 	do \
@@ -49,7 +49,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	done
 	echo '	$(RM) blib/lib/Error.pm' >> $@
 	'$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
-	echo '	cp private-Error.pm blib/lib/Error.pm' >> $@
+	echo '	cp bundles/Error/Error.pm blib/lib/Error.pm' >> $@
 	echo install: >> $@
 	set -e; \
 	for i in $(modules); \
@@ -66,7 +66,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	done
 	echo '	$(RM) "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
 	'$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
-	echo '	cp private-Error.pm "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
+	echo '	cp bundles/Error/Error.pm "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
 	echo instlibdir: >> $@
 	echo '	echo $(instdir_SQ)' >> $@
 else
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index b54b04a..000d370 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -2,11 +2,16 @@ use strict;
 use warnings;
 use ExtUtils::MakeMaker;
 use Getopt::Long;
+use File::Find;
+
+# Don't forget to update the perl/Makefile, too.
+# Don't forget to test with NO_PERL_MAKEMAKER=YesPlease

 # Sanity: die at first unknown option
 Getopt::Long::Configure qw/ pass_through /;

-GetOptions("localedir=s" => \my $localedir);
+my $localedir = '';
+GetOptions("localedir=s" => \$localedir);

 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -24,27 +29,25 @@ endif
 MAKE_FRAG
 }

-# XXX. When editing this list:
-#
-# * Please update perl/Makefile, too.
-# * Don't forget to test with NO_PERL_MAKEMAKER=YesPlease
-my %pm = (
-	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
-	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
-	'Git/SVN/Memoize/YAML.pm' => '$(INST_LIBDIR)/Git/SVN/Memoize/YAML.pm',
-	'Git/SVN/Fetcher.pm' => '$(INST_LIBDIR)/Git/SVN/Fetcher.pm',
-	'Git/SVN/Editor.pm' => '$(INST_LIBDIR)/Git/SVN/Editor.pm',
-	'Git/SVN/Prompt.pm' => '$(INST_LIBDIR)/Git/SVN/Prompt.pm',
-	'Git/SVN/Ra.pm' => '$(INST_LIBDIR)/Git/SVN/Ra.pm',
-);
-
+my @pmlibdirs = ("Git");
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
-eval { require Error };
-if ($@ || $Error::VERSION < 0.15009) {
-	$pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
+if ( !eval { require Error } || $Error::VERSION < 0.15009) {
+    push @pmlibdirs, "bundles/Error";
 }

+
+# Find all the .pm files in @pmlibdirs which includes our bundled modules.
+my %pms;
+find sub {
+    return unless /\.pm$/;
+
+    my $inst = $File::Find::name;
+    $inst =~ s{bundles/[^/]+/?}{};
+    $pms{$File::Find::name} = '$(INST_LIBDIR)/'.$inst;
+}, @pmlibdirs;
+
+
 # redirect stdout, otherwise the message "Writing perl.mak for Git"
 # disrupts the output for the target 'instlibdir'
 open STDOUT, ">&STDERR";
@@ -52,8 +55,9 @@ open STDOUT, ">&STDERR";
 WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
-	PM		=> \%pm,
+	PM              => \%pms,
 	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
+
diff --git a/perl/private-Error.pm b/perl/bundles/Error/Error.pm
similarity index 100%
rename from perl/private-Error.pm
rename to perl/bundles/Error/Error.pm
diff --git a/perl/bundles/README b/perl/bundles/README
new file mode 100644
index 0000000..8a9ce39
--- /dev/null
+++ b/perl/bundles/README
@@ -0,0 +1,10 @@
+This is any Perl modules we might want to bundle because Perl doesn't (or didn't)
+ship with them.  Each directory is a distribution containing all the PM files.
+
+For example, if you wanted to bundle URI...
+1) mkdir bundles/URI/
+
+2) build URI & cp -r blib/lib/* into bundles/URI
+
+3) add bundles/URI to @pmlibdirs in the Makefile.PL with the
+   appropriate check for existance and high enough version
-- 
1.7.11.1




-- 
Alligator sandwich, and make it snappy!

Extract Git classes from git-svn (2/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 683a230e439f1d5ac2727ce4c2a74e93804fc72b Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Wed, 11 Jul 2012 22:16:01 -0700
Subject: [PATCH 03/11] Fix Git::SVN so it can at least compile alone.

It's still very intertwined with git-svn, but that's a lot of work.  This
gets things working and tests passing again (as well as they were).

This required some parallel refactorings...

* fatal() moved out of git-svn into a new Git::SVN::Utils

* The $can_compress lexical moved into Git::SVN::Utils::can_compress()

* The $_prefix variable which stores the --prefix option is wrapped
  in a function (rather than made global) so access to it can be
  controlled.  Git::SVN does not rely on this function being
  available so it can work without git-svn loaded.  In general,
  the options should be put back together into a hash and accessed
  via an options() function.

* A new tree of unit tests for the Git::SVN modules has been created.
  It doesn't work with the existing Makefile, that can be worried
  about later.

* Move initialization of Git::SVN globals into Git::SVN

* Have Git::SVN load the Git command* functions on its own
---
 git-svn.perl                   | 33 ++++++++++++++++++---------------
 perl/Git/SVN.pm                | 29 ++++++++++++++++++++---------
 perl/Git/SVN/Utils.pm          | 19 +++++++++++++++++++
 perl/Makefile                  |  2 ++
 t/Git-SVN/00compile.t          |  9 +++++++++
 t/Git-SVN/Utils/can_compress.t | 11 +++++++++++
 t/Git-SVN/Utils/fatal.t        | 34 ++++++++++++++++++++++++++++++++++
 7 files changed, 113 insertions(+), 24 deletions(-)
 create mode 100644 perl/Git/SVN/Utils.pm
 create mode 100644 t/Git-SVN/00compile.t
 create mode 100644 t/Git-SVN/Utils/can_compress.t
 create mode 100644 t/Git-SVN/Utils/fatal.t
diff --git a/git-svn.perl b/git-svn.perl
index 59db0a4..8a02d1c 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -10,6 +10,9 @@ use vars qw/	$AUTHOR $VERSION
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';

+use Git::SVN;
+use Git::SVN::Utils qw(fatal can_compress);
+
 # From which subdir have we been invoked?
 my $cmd_dir_prefix = eval {
 	command_oneline([qw/rev-parse --show-prefix/], STDERR => 0)
@@ -17,10 +20,8 @@ my $cmd_dir_prefix = eval {

 my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
 $ENV{GIT_DIR} ||= '.git';
-$Git::SVN::default_repo_id = 'svn';
-$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
+
 $Git::SVN::Ra::_log_window_size = 100;
-$Git::SVN::_minimize_url = 'unset';

 if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
 	$ENV{SVN_SSH} = $ENV{GIT_SSH};
@@ -35,8 +36,6 @@ $Git::SVN::Log::TZ = $ENV{TZ};
 $ENV{TZ} = 'UTC';
 $| = 1; # unbuffer STDOUT

-sub fatal (@) { print STDERR "@_\n"; exit 1 }
-
 # All SVN commands do it.  Otherwise we may die on SIGPIPE when the remote
 # repository decides to close the connection which we expect to be kept alive.
 $SIG{PIPE} = 'IGNORE';
@@ -66,7 +65,7 @@ sub _req_svn {
 		fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
 	}
 }
-my $can_compress = eval { require Compress::Zlib; 1};
+
 use Carp qw/croak/;
 use Digest::MD5;
 use IO::File qw//;
@@ -89,7 +88,7 @@ BEGIN {
 	foreach (qw/command command_oneline command_noisy command_output_pipe
 	            command_input_pipe command_close_pipe
 	            command_bidi_pipe command_close_bidi_pipe/) {
-		for my $package ( qw(Git::SVN::Migration Git::SVN::Log Git::SVN),
+		for my $package ( qw(Git::SVN::Migration Git::SVN::Log),
 			__PACKAGE__) {
 			*{"${package}::$_"} = \&{"Git::$_"};
 		}
@@ -109,7 +108,10 @@ my ($_stdin, $_help, $_edit,
 	$_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
 	$_prefix, $_no_checkout, $_url, $_verbose,
 	$_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
-$Git::SVN::_follow_parent = 1;
+
+# This is a refactoring artifact so Git::SVN can get at this variable.
+sub opt_prefix { return $_prefix || '' }
+
 $Git::SVN::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
@@ -1578,7 +1580,7 @@ sub cmd_reset {
 }

 sub cmd_gc {
-	if (!$can_compress) {
+	if (!can_compress()) {
 		warn "Compress::Zlib could not be found; unhandled.log " .
 		     "files will not be compressed.\n";
 	}
@@ -2020,7 +2022,7 @@ sub md5sum {
 }

 sub gc_directory {
-	if ($can_compress && -f $_ && basename($_) eq "unhandled.log") {
+	if (can_compress() && -f $_ && basename($_) eq "unhandled.log") {
 		my $out_filename = $_ . ".gz";
 		open my $in_fh, "<", $_ or die "Unable to open $_: $!\n";
 		binmode $in_fh;
@@ -2042,6 +2044,7 @@ sub gc_directory {
 package Git::SVN::Log;
 use strict;
 use warnings;
+use Git::SVN::Utils qw(fatal);
 use POSIX qw/strftime/;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
@@ -2140,15 +2143,15 @@ sub config_pager {
 sub run_pager {
 	return unless defined $pager;
 	pipe my ($rfd, $wfd) or return;
-	defined(my $pid = fork) or ::fatal "Can't fork: $!";
+	defined(my $pid = fork) or fatal "Can't fork: $!";
 	if (!$pid) {
 		open STDOUT, '>&', $wfd or
-		                     ::fatal "Can't redirect to stdout: $!";
+		                     fatal "Can't redirect to stdout: $!";
 		return;
 	}
-	open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!";
+	open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!";
 	$ENV{LESS} ||= 'FRSX';
-	exec $pager or ::fatal "Can't run pager: $! ($pager)";
+	exec $pager or fatal "Can't run pager: $! ($pager)";
 }

 sub format_svn_date {
@@ -2297,7 +2300,7 @@ sub cmd_show_log {
 		} elsif ($::_revision =~ /^\d+$/) {
 			$r_min = $r_max = $::_revision;
 		} else {
-			::fatal "-r$::_revision is not supported, use ",
+			fatal "-r$::_revision is not supported, use ",
 				"standard 'git log' arguments instead";
 		}
 	}
diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm
index 533f1da..02983d6 100644
--- a/perl/Git/SVN.pm
+++ b/perl/Git/SVN.pm
@@ -1,11 +1,13 @@
 package Git::SVN;
+
 use strict;
 use warnings;
+
 use Fcntl qw/:DEFAULT :seek/;
 use constant rev_map_fmt => 'NH40';
-use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
+use vars qw/$_no_metadata
             $_repack $_repack_flags $_use_svm_props $_head
-            $_use_svnsync_props $no_reuse_existing $_minimize_url
+            $_use_svnsync_props $no_reuse_existing
 	    $_use_log_author $_add_author_from $_localtime/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
@@ -20,6 +22,14 @@ BEGIN {
 	$can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1};
 }

+use Git qw(command command_oneline command_noisy command_output_pipe
command_close_pipe);
+use Git::SVN::Utils qw(fatal can_compress);
+
+our $_follow_parent  = 1;
+our $_minimize_url   = 'unset';
+our $default_repo_id = 'svn';
+our $default_ref_id  = $ENV{GIT_SVN_ID} || 'git-svn';
+
 my ($_gc_nr, $_gc_period);

 # properties that we do not log:
@@ -840,8 +850,8 @@ sub assert_index_clean {
 		command_noisy('read-tree', $treeish);
 		$x = command_oneline('write-tree');
 		if ($y ne $x) {
-			::fatal "trees ($treeish) $y != $x\n",
-			        "Something is seriously wrong...";
+			fatal "trees ($treeish) $y != $x\n",
+			      "Something is seriously wrong...";
 		}
 	});
 }
@@ -1196,7 +1206,7 @@ sub mkemptydirs {
 	my %empty_dirs = ();
 	my $gz_file = "$self->{dir}/unhandled.log.gz";
 	if (-f $gz_file) {
-		if (!$can_compress) {
+		if (!can_compress()) {
 			warn "Compress::Zlib could not be found; ",
 			     "empty directories in $gz_file will not be read\n";
 		} else {
@@ -1879,7 +1889,7 @@ sub set_tree {
 	my ($self, $tree) = (shift, shift);
 	my $log_entry = ::get_commit_entry($tree);
 	unless ($self->{last_rev}) {
-		::fatal("Must have an existing revision to commit");
+		fatal("Must have an existing revision to commit");
 	}
 	my %ed_opts = ( r => $self->{last_rev},
 	                log => $log_entry->{log},
@@ -2237,12 +2247,13 @@ sub find_rev_after {
 sub _new {
 	my ($class, $repo_id, $ref_id, $path) = @_;
 	unless (defined $repo_id && length $repo_id) {
-		$repo_id = $Git::SVN::default_repo_id;
+		$repo_id = $default_repo_id;
 	}
 	unless (defined $ref_id && length $ref_id) {
-		$_prefix = '' unless defined($_prefix);
+		# Access the prefix option from the git-svn main program if it's loaded.
+		my $prefix = defined &::opt_prefix ? ::opt_prefix() : "";
 		$_[2] = $ref_id =
-		             "refs/remotes/$_prefix$Git::SVN::default_ref_id";
+		             "refs/remotes/$prefix$default_ref_id";
 	}
 	$_[1] = $repo_id;
 	my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
diff --git a/perl/Git/SVN/Utils.pm b/perl/Git/SVN/Utils.pm
new file mode 100644
index 0000000..d8f63e6
--- /dev/null
+++ b/perl/Git/SVN/Utils.pm
@@ -0,0 +1,19 @@
+package Git::SVN::Utils;
+
+use strict;
+use warnings;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(fatal can_compress);
+
+sub fatal (@) { print STDERR "@_\n"; exit 1 }
+
+my $can_compress;
+sub can_compress {
+    return $can_compress if defined $can_compress;
+
+    return $can_compress = eval { require Compress::Zlib; } ? 1 : 0;
+}
+
+1;
diff --git a/perl/Makefile b/perl/Makefile
index 4f25930..d0a0c5c 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -26,11 +26,13 @@ instdir_SQ = $(subst ','\'',$(prefix)/lib)

 modules += Git
 modules += Git/I18N
+modules += Git/SVN
 modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
 modules += Git/SVN/Editor
 modules += Git/SVN/Prompt
 modules += Git/SVN/Ra
+modules += Git/SVN/Utils

 $(makfile): ../GIT-CFLAGS Makefile
 	echo all: bundles/Error/Error.pm $(modules) > $@
diff --git a/t/Git-SVN/00compile.t b/t/Git-SVN/00compile.t
new file mode 100644
index 0000000..c32ee4b
--- /dev/null
+++ b/t/Git-SVN/00compile.t
@@ -0,0 +1,9 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+require_ok 'Git::SVN';
+require_ok 'Git::SVN::Utils';
diff --git a/t/Git-SVN/Utils/can_compress.t b/t/Git-SVN/Utils/can_compress.t
new file mode 100644
index 0000000..d7b49b8
--- /dev/null
+++ b/t/Git-SVN/Utils/can_compress.t
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils qw(can_compress);
+
+# !! is the "convert this to boolean" operator.
+is !!can_compress(), !!eval { require Compress::Zlib };
diff --git a/t/Git-SVN/Utils/fatal.t b/t/Git-SVN/Utils/fatal.t
new file mode 100644
index 0000000..b90746c
--- /dev/null
+++ b/t/Git-SVN/Utils/fatal.t
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+BEGIN {
+    # Override exit at BEGIN time before Git::SVN::Utils is loaded
+    # so it will see our local exit later.
+    *CORE::GLOBAL::exit = sub(;$) {
+        return @_ ? CORE::exit($_[0]) : CORE::exit();
+    };
+}
+
+use Git::SVN::Utils qw(fatal);
+
+# fatal()
+{
+    # Capture the exit code and prevent exit.
+    my $exit_status;
+    no warnings 'redefine';
+    local *CORE::GLOBAL::exit = sub { $exit_status = $_[0] || 0 };
+
+    # Trap fatal's message to STDERR
+    my $stderr;
+    close STDERR;
+    ok open STDERR, ">", \$stderr;
+
+    fatal "Some", "Stuff", "Happened";
+
+    is $stderr, "Some Stuff Happened\n";
+    is $exit_status, 1;
+}
-- 
1.7.11.1

Extract Git classes from git-svn (3/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 5f0b609e9b0a70c86c46b48f0b180c96c3355a14 Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Tue, 17 Jul 2012 15:40:03 -0700
Subject: [PATCH 04/11] Extract Git::SVN::Log from git-svn.

This is a straight cut & paste.  Next commit will make it work.  This will
make it easier to see the differences in Git::SVN::Log.
---
 git-svn.perl        | 387 ---------------------------------------------------
 perl/Git/SVN/Log.pm | 388 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 388 insertions(+), 387 deletions(-)
 create mode 100644 perl/Git/SVN/Log.pm
diff --git a/git-svn.perl b/git-svn.perl
index 8a02d1c..5e6e3b5 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -2041,393 +2041,6 @@ sub gc_directory {
 }


-package Git::SVN::Log;
-use strict;
-use warnings;
-use Git::SVN::Utils qw(fatal);
-use POSIX qw/strftime/;
-use constant commit_log_separator => ('-' x 72) . "\n";
-use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
-            %rusers $show_commit $incremental/;
-my $l_fmt;
-
-sub cmt_showable {
-	my ($c) = @_;
-	return 1 if defined $c->{r};
-
-	# big commit message got truncated by the 16k pretty buffer in rev-list
-	if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
-				$c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-		@{$c->{l}} = ();
-		my @log = command(qw/cat-file commit/, $c->{c});
-
-		# shift off the headers
-		shift @log while ($log[0] ne '');
-		shift @log;
-
-		# TODO: make $c->{l} not have a trailing newline in the future
-		@{$c->{l}} = map { "$_\n" } grep !/^git-svn-id: /, @log;
-
-		(undef, $c->{r}, undef) = ::extract_metadata(
-				(grep(/^git-svn-id: /, @log))[-1]);
-	}
-	return defined $c->{r};
-}
-
-sub log_use_color {
-	return $color || Git->repository->get_colorbool('color.diff');
-}
-
-sub git_svn_log_cmd {
-	my ($r_min, $r_max, @args) = @_;
-	my $head = 'HEAD';
-	my (@files, @log_opts);
-	foreach my $x (@args) {
-		if ($x eq '--' || @files) {
-			push @files, $x;
-		} else {
-			if (::verify_ref("$x^0")) {
-				$head = $x;
-			} else {
-				push @log_opts, $x;
-			}
-		}
-	}
-
-	my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
-	$gs ||= Git::SVN->_new;
-	my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
-	           $gs->refname);
-	push @cmd, '-r' unless $non_recursive;
-	push @cmd, qw/--raw --name-status/ if $verbose;
-	push @cmd, '--color' if log_use_color();
-	push @cmd, @log_opts;
-	if (defined $r_max && $r_max == $r_min) {
-		push @cmd, '--max-count=1';
-		if (my $c = $gs->rev_map_get($r_max)) {
-			push @cmd, $c;
-		}
-	} elsif (defined $r_max) {
-		if ($r_max < $r_min) {
-			($r_min, $r_max) = ($r_max, $r_min);
-		}
-		my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min);
-		my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max);
-		# If there are no commits in the range, both $c_max and $c_min
-		# will be undefined.  If there is at least 1 commit in the
-		# range, both will be defined.
-		return () if !defined $c_min || !defined $c_max;
-		if ($c_min eq $c_max) {
-			push @cmd, '--max-count=1', $c_min;
-		} else {
-			push @cmd, '--boundary', "$c_min..$c_max";
-		}
-	}
-	return (@cmd, @files);
-}
-
-# adapted from pager.c
-sub config_pager {
-	if (! -t *STDOUT) {
-		$ENV{GIT_PAGER_IN_USE} = 'false';
-		$pager = undef;
-		return;
-	}
-	chomp($pager = command_oneline(qw(var GIT_PAGER)));
-	if ($pager eq 'cat') {
-		$pager = undef;
-	}
-	$ENV{GIT_PAGER_IN_USE} = defined($pager);
-}
-
-sub run_pager {
-	return unless defined $pager;
-	pipe my ($rfd, $wfd) or return;
-	defined(my $pid = fork) or fatal "Can't fork: $!";
-	if (!$pid) {
-		open STDOUT, '>&', $wfd or
-		                     fatal "Can't redirect to stdout: $!";
-		return;
-	}
-	open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!";
-	$ENV{LESS} ||= 'FRSX';
-	exec $pager or fatal "Can't run pager: $! ($pager)";
-}
-
-sub format_svn_date {
-	my $t = shift || time;
-	my $gmoff = Git::SVN::get_tz($t);
-	return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
-}
-
-sub parse_git_date {
-	my ($t, $tz) = @_;
-	# Date::Parse isn't in the standard Perl distro :(
-	if ($tz =~ s/^\+//) {
-		$t += tz_to_s_offset($tz);
-	} elsif ($tz =~ s/^\-//) {
-		$t -= tz_to_s_offset($tz);
-	}
-	return $t;
-}
-
-sub set_local_timezone {
-	if (defined $TZ) {
-		$ENV{TZ} = $TZ;
-	} else {
-		delete $ENV{TZ};
-	}
-}
-
-sub tz_to_s_offset {
-	my ($tz) = @_;
-	$tz =~ s/(\d\d)$//;
-	return ($1 * 60) + ($tz * 3600);
-}
-
-sub get_author_info {
-	my ($dest, $author, $t, $tz) = @_;
-	$author =~ s/(?:^\s*|\s*$)//g;
-	$dest->{a_raw} = $author;
-	my $au;
-	if ($::_authors) {
-		$au = $rusers{$author} || undef;
-	}
-	if (!$au) {
-		($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
-	}
-	$dest->{t} = $t;
-	$dest->{tz} = $tz;
-	$dest->{a} = $au;
-	$dest->{t_utc} = parse_git_date($t, $tz);
-}
-
-sub process_commit {
-	my ($c, $r_min, $r_max, $defer) = @_;
-	if (defined $r_min && defined $r_max) {
-		if ($r_min == $c->{r} && $r_min == $r_max) {
-			show_commit($c);
-			return 0;
-		}
-		return 1 if $r_min == $r_max;
-		if ($r_min < $r_max) {
-			# we need to reverse the print order
-			return 0 if (defined $limit && --$limit < 0);
-			push @$defer, $c;
-			return 1;
-		}
-		if ($r_min != $r_max) {
-			return 1 if ($r_min < $c->{r});
-			return 1 if ($r_max > $c->{r});
-		}
-	}
-	return 0 if (defined $limit && --$limit < 0);
-	show_commit($c);
-	return 1;
-}
-
-sub show_commit {
-	my $c = shift;
-	if ($oneline) {
-		my $x = "\n";
-		if (my $l = $c->{l}) {
-			while ($l->[0] =~ /^\s*$/) { shift @$l }
-			$x = $l->[0];
-		}
-		$l_fmt ||= 'A' . length($c->{r});
-		print 'r',pack($l_fmt, $c->{r}),' | ';
-		print "$c->{c} | " if $show_commit;
-		print $x;
-	} else {
-		show_commit_normal($c);
-	}
-}
-
-sub show_commit_changed_paths {
-	my ($c) = @_;
-	return unless $c->{changed};
-	print "Changed paths:\n", @{$c->{changed}};
-}
-
-sub show_commit_normal {
-	my ($c) = @_;
-	print commit_log_separator, "r$c->{r} | ";
-	print "$c->{c} | " if $show_commit;
-	print "$c->{a} | ", format_svn_date($c->{t_utc}), ' | ';
-	my $nr_line = 0;
-
-	if (my $l = $c->{l}) {
-		while ($l->[$#$l] eq "\n" && $#$l > 0
-		                          && $l->[($#$l - 1)] eq "\n") {
-			pop @$l;
-		}
-		$nr_line = scalar @$l;
-		if (!$nr_line) {
-			print "1 line\n\n\n";
-		} else {
-			if ($nr_line == 1) {
-				$nr_line = '1 line';
-			} else {
-				$nr_line .= ' lines';
-			}
-			print $nr_line, "\n";
-			show_commit_changed_paths($c);
-			print "\n";
-			print $_ foreach @$l;
-		}
-	} else {
-		print "1 line\n";
-		show_commit_changed_paths($c);
-		print "\n";
-
-	}
-	foreach my $x (qw/raw stat diff/) {
-		if ($c->{$x}) {
-			print "\n";
-			print $_ foreach @{$c->{$x}}
-		}
-	}
-}
-
-sub cmd_show_log {
-	my (@args) = @_;
-	my ($r_min, $r_max);
-	my $r_last = -1; # prevent dupes
-	set_local_timezone();
-	if (defined $::_revision) {
-		if ($::_revision =~ /^(\d+):(\d+)$/) {
-			($r_min, $r_max) = ($1, $2);
-		} elsif ($::_revision =~ /^\d+$/) {
-			$r_min = $r_max = $::_revision;
-		} else {
-			fatal "-r$::_revision is not supported, use ",
-				"standard 'git log' arguments instead";
-		}
-	}
-
-	config_pager();
-	@args = git_svn_log_cmd($r_min, $r_max, @args);
-	if (!@args) {
-		print commit_log_separator unless $incremental || $oneline;
-		return;
-	}
-	my $log = command_output_pipe(@args);
-	run_pager();
-	my (@k, $c, $d, $stat);
-	my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
-	while (<$log>) {
-		if (/^${esc_color}commit (?:- )?($::sha1_short)/o) {
-			my $cmt = $1;
-			if ($c && cmt_showable($c) && $c->{r} != $r_last) {
-				$r_last = $c->{r};
-				process_commit($c, $r_min, $r_max, \@k) or
-								goto out;
-			}
-			$d = undef;
-			$c = { c => $cmt };
-		} elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
-			get_author_info($c, $1, $2, $3);
-		} elsif (/^${esc_color}(?:tree|parent|committer) /o) {
-			# ignore
-		} elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
-			push @{$c->{raw}}, $_;
-		} elsif (/^${esc_color}[ACRMDT]\t/) {
-			# we could add $SVN->{svn_path} here, but that requires
-			# remote access at the moment (repo_path_split)...
-			s#^(${esc_color})([ACRMDT])\t#$1   $2 #o;
-			push @{$c->{changed}}, $_;
-		} elsif (/^${esc_color}diff /o) {
-			$d = 1;
-			push @{$c->{diff}}, $_;
-		} elsif ($d) {
-			push @{$c->{diff}}, $_;
-		} elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]*
-		          $esc_color*[\+\-]*$esc_color$/x) {
-			$stat = 1;
-			push @{$c->{stat}}, $_;
-		} elsif ($stat && /^ \d+ files changed, \d+ insertions/) {
-			push @{$c->{stat}}, $_;
-			$stat = undef;
-		} elsif (/^${esc_color}    (git-svn-id:.+)$/o) {
-			($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
-		} elsif (s/^${esc_color}    //o) {
-			push @{$c->{l}}, $_;
-		}
-	}
-	if ($c && defined $c->{r} && $c->{r} != $r_last) {
-		$r_last = $c->{r};
-		process_commit($c, $r_min, $r_max, \@k);
-	}
-	if (@k) {
-		($r_min, $r_max) = ($r_max, $r_min);
-		process_commit($_, $r_min, $r_max) foreach reverse @k;
-	}
-out:
-	close $log;
-	print commit_log_separator unless $incremental || $oneline;
-}
-
-sub cmd_blame {
-	my $path = pop;
-
-	config_pager();
-	run_pager();
-
-	my ($fh, $ctx, $rev);
-
-	if ($_git_format) {
-		($fh, $ctx) = command_output_pipe('blame', @_, $path);
-		while (my $line = <$fh>) {
-			if ($line =~ /^\^?([[:xdigit:]]+)\s/) {
-				# Uncommitted edits show up as a rev ID of
-				# all zeros, which we can't look up with
-				# cmt_metadata
-				if ($1 !~ /^0+$/) {
-					(undef, $rev, undef) =
-						::cmt_metadata($1);
-					$rev = '0' if (!$rev);
-				} else {
-					$rev = '0';
-				}
-				$rev = sprintf('%-10s', $rev);
-				$line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/;
-			}
-			print $line;
-		}
-	} else {
-		($fh, $ctx) = command_output_pipe('blame', '-p', @_, 'HEAD',
-						  '--', $path);
-		my ($sha1);
-		my %authors;
-		my @buffer;
-		my %dsha; #distinct sha keys
-
-		while (my $line = <$fh>) {
-			push @buffer, $line;
-			if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
-				$dsha{$1} = 1;
-			}
-		}
-
-		my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
-
-		foreach my $line (@buffer) {
-			if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
-				$rev = $s2r->{$1};
-				$rev = '0' if (!$rev)
-			}
-			elsif ($line =~ /^author (.*)/) {
-				$authors{$rev} = $1;
-				$authors{$rev} =~ s/\s/_/g;
-			}
-			elsif ($line =~ /^\t(.*)$/) {
-				printf("%6s %10s %s\n", $rev, $authors{$rev}, $1);
-			}
-		}
-	}
-	command_close_pipe($fh, $ctx);
-}
-
 package Git::SVN::Migration;
 # these version numbers do NOT correspond to actual version numbers
 # of git nor git-svn.  They are just relative.
diff --git a/perl/Git/SVN/Log.pm b/perl/Git/SVN/Log.pm
new file mode 100644
index 0000000..bbec3b0
--- /dev/null
+++ b/perl/Git/SVN/Log.pm
@@ -0,0 +1,388 @@
+package Git::SVN::Log;
+use strict;
+use warnings;
+use Git::SVN::Utils qw(fatal);
+use POSIX qw/strftime/;
+use constant commit_log_separator => ('-' x 72) . "\n";
+use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
+            %rusers $show_commit $incremental/;
+my $l_fmt;
+
+sub cmt_showable {
+	my ($c) = @_;
+	return 1 if defined $c->{r};
+
+	# big commit message got truncated by the 16k pretty buffer in rev-list
+	if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+				$c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+		@{$c->{l}} = ();
+		my @log = command(qw/cat-file commit/, $c->{c});
+
+		# shift off the headers
+		shift @log while ($log[0] ne '');
+		shift @log;
+
+		# TODO: make $c->{l} not have a trailing newline in the future
+		@{$c->{l}} = map { "$_\n" } grep !/^git-svn-id: /, @log;
+
+		(undef, $c->{r}, undef) = ::extract_metadata(
+				(grep(/^git-svn-id: /, @log))[-1]);
+	}
+	return defined $c->{r};
+}
+
+sub log_use_color {
+	return $color || Git->repository->get_colorbool('color.diff');
+}
+
+sub git_svn_log_cmd {
+	my ($r_min, $r_max, @args) = @_;
+	my $head = 'HEAD';
+	my (@files, @log_opts);
+	foreach my $x (@args) {
+		if ($x eq '--' || @files) {
+			push @files, $x;
+		} else {
+			if (::verify_ref("$x^0")) {
+				$head = $x;
+			} else {
+				push @log_opts, $x;
+			}
+		}
+	}
+
+	my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
+	$gs ||= Git::SVN->_new;
+	my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
+	           $gs->refname);
+	push @cmd, '-r' unless $non_recursive;
+	push @cmd, qw/--raw --name-status/ if $verbose;
+	push @cmd, '--color' if log_use_color();
+	push @cmd, @log_opts;
+	if (defined $r_max && $r_max == $r_min) {
+		push @cmd, '--max-count=1';
+		if (my $c = $gs->rev_map_get($r_max)) {
+			push @cmd, $c;
+		}
+	} elsif (defined $r_max) {
+		if ($r_max < $r_min) {
+			($r_min, $r_max) = ($r_max, $r_min);
+		}
+		my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min);
+		my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max);
+		# If there are no commits in the range, both $c_max and $c_min
+		# will be undefined.  If there is at least 1 commit in the
+		# range, both will be defined.
+		return () if !defined $c_min || !defined $c_max;
+		if ($c_min eq $c_max) {
+			push @cmd, '--max-count=1', $c_min;
+		} else {
+			push @cmd, '--boundary', "$c_min..$c_max";
+		}
+	}
+	return (@cmd, @files);
+}
+
+# adapted from pager.c
+sub config_pager {
+	if (! -t *STDOUT) {
+		$ENV{GIT_PAGER_IN_USE} = 'false';
+		$pager = undef;
+		return;
+	}
+	chomp($pager = command_oneline(qw(var GIT_PAGER)));
+	if ($pager eq 'cat') {
+		$pager = undef;
+	}
+	$ENV{GIT_PAGER_IN_USE} = defined($pager);
+}
+
+sub run_pager {
+	return unless defined $pager;
+	pipe my ($rfd, $wfd) or return;
+	defined(my $pid = fork) or fatal "Can't fork: $!";
+	if (!$pid) {
+		open STDOUT, '>&', $wfd or
+		                     fatal "Can't redirect to stdout: $!";
+		return;
+	}
+	open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!";
+	$ENV{LESS} ||= 'FRSX';
+	exec $pager or fatal "Can't run pager: $! ($pager)";
+}
+
+sub format_svn_date {
+	my $t = shift || time;
+	my $gmoff = Git::SVN::get_tz($t);
+	return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
+}
+
+sub parse_git_date {
+	my ($t, $tz) = @_;
+	# Date::Parse isn't in the standard Perl distro :(
+	if ($tz =~ s/^\+//) {
+		$t += tz_to_s_offset($tz);
+	} elsif ($tz =~ s/^\-//) {
+		$t -= tz_to_s_offset($tz);
+	}
+	return $t;
+}
+
+sub set_local_timezone {
+	if (defined $TZ) {
+		$ENV{TZ} = $TZ;
+	} else {
+		delete $ENV{TZ};
+	}
+}
+
+sub tz_to_s_offset {
+	my ($tz) = @_;
+	$tz =~ s/(\d\d)$//;
+	return ($1 * 60) + ($tz * 3600);
+}
+
+sub get_author_info {
+	my ($dest, $author, $t, $tz) = @_;
+	$author =~ s/(?:^\s*|\s*$)//g;
+	$dest->{a_raw} = $author;
+	my $au;
+	if ($::_authors) {
+		$au = $rusers{$author} || undef;
+	}
+	if (!$au) {
+		($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
+	}
+	$dest->{t} = $t;
+	$dest->{tz} = $tz;
+	$dest->{a} = $au;
+	$dest->{t_utc} = parse_git_date($t, $tz);
+}
+
+sub process_commit {
+	my ($c, $r_min, $r_max, $defer) = @_;
+	if (defined $r_min && defined $r_max) {
+		if ($r_min == $c->{r} && $r_min == $r_max) {
+			show_commit($c);
+			return 0;
+		}
+		return 1 if $r_min == $r_max;
+		if ($r_min < $r_max) {
+			# we need to reverse the print order
+			return 0 if (defined $limit && --$limit < 0);
+			push @$defer, $c;
+			return 1;
+		}
+		if ($r_min != $r_max) {
+			return 1 if ($r_min < $c->{r});
+			return 1 if ($r_max > $c->{r});
+		}
+	}
+	return 0 if (defined $limit && --$limit < 0);
+	show_commit($c);
+	return 1;
+}
+
+sub show_commit {
+	my $c = shift;
+	if ($oneline) {
+		my $x = "\n";
+		if (my $l = $c->{l}) {
+			while ($l->[0] =~ /^\s*$/) { shift @$l }
+			$x = $l->[0];
+		}
+		$l_fmt ||= 'A' . length($c->{r});
+		print 'r',pack($l_fmt, $c->{r}),' | ';
+		print "$c->{c} | " if $show_commit;
+		print $x;
+	} else {
+		show_commit_normal($c);
+	}
+}
+
+sub show_commit_changed_paths {
+	my ($c) = @_;
+	return unless $c->{changed};
+	print "Changed paths:\n", @{$c->{changed}};
+}
+
+sub show_commit_normal {
+	my ($c) = @_;
+	print commit_log_separator, "r$c->{r} | ";
+	print "$c->{c} | " if $show_commit;
+	print "$c->{a} | ", format_svn_date($c->{t_utc}), ' | ';
+	my $nr_line = 0;
+
+	if (my $l = $c->{l}) {
+		while ($l->[$#$l] eq "\n" && $#$l > 0
+		                          && $l->[($#$l - 1)] eq "\n") {
+			pop @$l;
+		}
+		$nr_line = scalar @$l;
+		if (!$nr_line) {
+			print "1 line\n\n\n";
+		} else {
+			if ($nr_line == 1) {
+				$nr_line = '1 line';
+			} else {
+				$nr_line .= ' lines';
+			}
+			print $nr_line, "\n";
+			show_commit_changed_paths($c);
+			print "\n";
+			print $_ foreach @$l;
+		}
+	} else {
+		print "1 line\n";
+		show_commit_changed_paths($c);
+		print "\n";
+
+	}
+	foreach my $x (qw/raw stat diff/) {
+		if ($c->{$x}) {
+			print "\n";
+			print $_ foreach @{$c->{$x}}
+		}
+	}
+}
+
+sub cmd_show_log {
+	my (@args) = @_;
+	my ($r_min, $r_max);
+	my $r_last = -1; # prevent dupes
+	set_local_timezone();
+	if (defined $::_revision) {
+		if ($::_revision =~ /^(\d+):(\d+)$/) {
+			($r_min, $r_max) = ($1, $2);
+		} elsif ($::_revision =~ /^\d+$/) {
+			$r_min = $r_max = $::_revision;
+		} else {
+			fatal "-r$::_revision is not supported, use ",
+				"standard 'git log' arguments instead";
+		}
+	}
+
+	config_pager();
+	@args = git_svn_log_cmd($r_min, $r_max, @args);
+	if (!@args) {
+		print commit_log_separator unless $incremental || $oneline;
+		return;
+	}
+	my $log = command_output_pipe(@args);
+	run_pager();
+	my (@k, $c, $d, $stat);
+	my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
+	while (<$log>) {
+		if (/^${esc_color}commit (?:- )?($::sha1_short)/o) {
+			my $cmt = $1;
+			if ($c && cmt_showable($c) && $c->{r} != $r_last) {
+				$r_last = $c->{r};
+				process_commit($c, $r_min, $r_max, \@k) or
+								goto out;
+			}
+			$d = undef;
+			$c = { c => $cmt };
+		} elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
+			get_author_info($c, $1, $2, $3);
+		} elsif (/^${esc_color}(?:tree|parent|committer) /o) {
+			# ignore
+		} elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
+			push @{$c->{raw}}, $_;
+		} elsif (/^${esc_color}[ACRMDT]\t/) {
+			# we could add $SVN->{svn_path} here, but that requires
+			# remote access at the moment (repo_path_split)...
+			s#^(${esc_color})([ACRMDT])\t#$1   $2 #o;
+			push @{$c->{changed}}, $_;
+		} elsif (/^${esc_color}diff /o) {
+			$d = 1;
+			push @{$c->{diff}}, $_;
+		} elsif ($d) {
+			push @{$c->{diff}}, $_;
+		} elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]*
+		          $esc_color*[\+\-]*$esc_color$/x) {
+			$stat = 1;
+			push @{$c->{stat}}, $_;
+		} elsif ($stat && /^ \d+ files changed, \d+ insertions/) {
+			push @{$c->{stat}}, $_;
+			$stat = undef;
+		} elsif (/^${esc_color}    (git-svn-id:.+)$/o) {
+			($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
+		} elsif (s/^${esc_color}    //o) {
+			push @{$c->{l}}, $_;
+		}
+	}
+	if ($c && defined $c->{r} && $c->{r} != $r_last) {
+		$r_last = $c->{r};
+		process_commit($c, $r_min, $r_max, \@k);
+	}
+	if (@k) {
+		($r_min, $r_max) = ($r_max, $r_min);
+		process_commit($_, $r_min, $r_max) foreach reverse @k;
+	}
+out:
+	close $log;
+	print commit_log_separator unless $incremental || $oneline;
+}
+
+sub cmd_blame {
+	my $path = pop;
+
+	config_pager();
+	run_pager();
+
+	my ($fh, $ctx, $rev);
+
+	if ($_git_format) {
+		($fh, $ctx) = command_output_pipe('blame', @_, $path);
+		while (my $line = <$fh>) {
+			if ($line =~ /^\^?([[:xdigit:]]+)\s/) {
+				# Uncommitted edits show up as a rev ID of
+				# all zeros, which we can't look up with
+				# cmt_metadata
+				if ($1 !~ /^0+$/) {
+					(undef, $rev, undef) =
+						::cmt_metadata($1);
+					$rev = '0' if (!$rev);
+				} else {
+					$rev = '0';
+				}
+				$rev = sprintf('%-10s', $rev);
+				$line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/;
+			}
+			print $line;
+		}
+	} else {
+		($fh, $ctx) = command_output_pipe('blame', '-p', @_, 'HEAD',
+						  '--', $path);
+		my ($sha1);
+		my %authors;
+		my @buffer;
+		my %dsha; #distinct sha keys
+
+		while (my $line = <$fh>) {
+			push @buffer, $line;
+			if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
+				$dsha{$1} = 1;
+			}
+		}
+
+		my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
+
+		foreach my $line (@buffer) {
+			if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
+				$rev = $s2r->{$1};
+				$rev = '0' if (!$rev)
+			}
+			elsif ($line =~ /^author (.*)/) {
+				$authors{$rev} = $1;
+				$authors{$rev} =~ s/\s/_/g;
+			}
+			elsif ($line =~ /^\t(.*)$/) {
+				printf("%6s %10s %s\n", $rev, $authors{$rev}, $1);
+			}
+		}
+	}
+	command_close_pipe($fh, $ctx);
+}
+
+1;
-- 
1.7.11.1

Extract Git classes from git-svn (4/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 8f70be0424a770c299b6a0c5bf99e4030e5e4d92 Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 16:58:53 -0700
Subject: [PATCH 05/11] Make Git::SVN::Log work.

Changes to Git::SVN::Log to make it compile....
* Change the $_git_format lexical only used by Git::SVN::Log into a
Git::SVN::Log global
* Have it load the Git command functions itself
---
 git-svn.perl          |  8 +++++---
 perl/Git/SVN/Log.pm   | 10 +++++++++-
 perl/Makefile         |  1 +
 t/Git-SVN/00compile.t |  4 +++-
 4 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/git-svn.perl b/git-svn.perl
index 5e6e3b5..7c8ca49 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -11,6 +11,8 @@ $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';

 use Git::SVN;
+use Git::SVN::Log;
+
 use Git::SVN::Utils qw(fatal can_compress);

 # From which subdir have we been invoked?
@@ -88,7 +90,7 @@ BEGIN {
 	foreach (qw/command command_oneline command_noisy command_output_pipe
 	            command_input_pipe command_close_pipe
 	            command_bidi_pipe command_close_bidi_pipe/) {
-		for my $package ( qw(Git::SVN::Migration Git::SVN::Log),
+		for my $package ( qw(Git::SVN::Migration),
 			__PACKAGE__) {
 			*{"${package}::$_"} = \&{"Git::$_"};
 		}
@@ -107,7 +109,7 @@ my ($_stdin, $_help, $_edit,
 	$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
 	$_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
 	$_prefix, $_no_checkout, $_url, $_verbose,
-	$_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
+	$_commit_url, $_tag, $_merge_info, $_interactive);

 # This is a refactoring artifact so Git::SVN can get at this variable.
 sub opt_prefix { return $_prefix || '' }
@@ -271,7 +273,7 @@ my %cmd = (
 		    { 'url' => \$_url, } ],
 	'blame' => [ \&Git::SVN::Log::cmd_blame,
 	            "Show what revision and author last modified each line of a file",
-		    { 'git-format' => \$_git_format } ],
+		    { 'git-format' => \$Git::SVN::Log::_git_format } ],
 	'reset' => [ \&cmd_reset,
 		     "Undo fetches back to the specified SVN revision",
 		     { 'revision|r=s' => \$_revision,
diff --git a/perl/Git/SVN/Log.pm b/perl/Git/SVN/Log.pm
index bbec3b0..7f3cb87 100644
--- a/perl/Git/SVN/Log.pm
+++ b/perl/Git/SVN/Log.pm
@@ -1,12 +1,17 @@
 package Git::SVN::Log;
+
 use strict;
 use warnings;
+
+use Git qw(command command_oneline command_output_pipe command_close_pipe);
 use Git::SVN::Utils qw(fatal);
 use POSIX qw/strftime/;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
-my $l_fmt;
+
+# Options set in git-svn
+our $_git_format;

 sub cmt_showable {
 	my ($c) = @_;
@@ -52,6 +57,7 @@ sub git_svn_log_cmd {
 	}

 	my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
+	require Git::SVN;
 	$gs ||= Git::SVN->_new;
 	my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
 	           $gs->refname);
@@ -113,6 +119,7 @@ sub run_pager {

 sub format_svn_date {
 	my $t = shift || time;
+	require Git::SVN;
 	my $gmoff = Git::SVN::get_tz($t);
 	return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
 }
@@ -183,6 +190,7 @@ sub process_commit {
 	return 1;
 }

+my $l_fmt;
 sub show_commit {
 	my $c = shift;
 	if ($oneline) {
diff --git a/perl/Makefile b/perl/Makefile
index d0a0c5c..2a4ca57 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -30,6 +30,7 @@ modules += Git/SVN
 modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
 modules += Git/SVN/Editor
+modules += Git/SVN/Log
 modules += Git/SVN/Prompt
 modules += Git/SVN/Ra
 modules += Git/SVN/Utils
diff --git a/t/Git-SVN/00compile.t b/t/Git-SVN/00compile.t
index c32ee4b..37626f4 100644
--- a/t/Git-SVN/00compile.t
+++ b/t/Git-SVN/00compile.t
@@ -3,7 +3,9 @@
 use strict;
 use warnings;

-use Test::More tests => 2;
+use Test::More tests => 4;

 require_ok 'Git::SVN';
 require_ok 'Git::SVN::Utils';
+require_ok 'Git::SVN::Ra';
+require_ok 'Git::SVN::Log';
-- 
1.7.11.1

Extract Git classes from git-svn (5/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From ab67ab421dbfd248b9a198b8cc1cd9944ba6178d Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Tue, 17 Jul 2012 15:46:44 -0700
Subject: [PATCH 06/11] Move Git::SVN::Migration into its own file.

Just a straight cut & paste, the fixes come next commit.
---
 git-svn.perl              | 246 ---------------------------------------------
 perl/Git/SVN/Migration.pm | 247 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 247 insertions(+), 246 deletions(-)
 create mode 100644 perl/Git/SVN/Migration.pm
diff --git a/git-svn.perl b/git-svn.perl
index 7c8ca49..f2bf759 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -2043,252 +2043,6 @@ sub gc_directory {
 }


-package Git::SVN::Migration;
-# these version numbers do NOT correspond to actual version numbers
-# of git nor git-svn.  They are just relative.
-#
-# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
-#
-# v1 layout: .git/$id/info/url, refs/remotes/$id
-#
-# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
-#
-# v3 layout: .git/svn/$id, refs/remotes/$id
-#            - info/url may remain for backwards compatibility
-#            - this is what we migrate up to this layout automatically,
-#            - this will be used by git svn init on single branches
-# v3.1 layout (auto migrated):
-#            - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
-#              for backwards compatibility
-#
-# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
-#            - this is only created for newly multi-init-ed
-#              repositories.  Similar in spirit to the
-#              --use-separate-remotes option in git-clone (now default)
-#            - we do not automatically migrate to this (following
-#              the example set by core git)
-#
-# v5 layout: .rev_db.$UUID => .rev_map.$UUID
-#            - newer, more-efficient format that uses 24-bytes per record
-#              with no filler space.
-#            - use xxd -c24 < .rev_map.$UUID to view and debug
-#            - This is a one-way migration, repositories updated to the
-#              new format will not be able to use old git-svn without
-#              rebuilding the .rev_db.  Rebuilding the rev_db is not
-#              possible if noMetadata or useSvmProps are set; but should
-#              be no problem for users that use the (sensible) defaults.
-use strict;
-use warnings;
-use Carp qw/croak/;
-use File::Path qw/mkpath/;
-use File::Basename qw/dirname basename/;
-use vars qw/$_minimize/;
-
-sub migrate_from_v0 {
-	my $git_dir = $ENV{GIT_DIR};
-	return undef unless -d $git_dir;
-	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
-	my $migrated = 0;
-	while (<$fh>) {
-		chomp;
-		my ($id, $orig_ref) = ($_, $_);
-		next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
-		next unless -f "$git_dir/$id/info/url";
-		my $new_ref = "refs/remotes/$id";
-		if (::verify_ref("$new_ref^0")) {
-			print STDERR "W: $orig_ref is probably an old ",
-			             "branch used by an ancient version of ",
-				     "git-svn.\n",
-				     "However, $new_ref also exists.\n",
-				     "We will not be able ",
-				     "to use this branch until this ",
-				     "ambiguity is resolved.\n";
-			next;
-		}
-		print STDERR "Migrating from v0 layout...\n" if !$migrated;
-		print STDERR "Renaming ref: $orig_ref => $new_ref\n";
-		command_noisy('update-ref', $new_ref, $orig_ref);
-		command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
-		$migrated++;
-	}
-	command_close_pipe($fh, $ctx);
-	print STDERR "Done migrating from v0 layout...\n" if $migrated;
-	$migrated;
-}
-
-sub migrate_from_v1 {
-	my $git_dir = $ENV{GIT_DIR};
-	my $migrated = 0;
-	return $migrated unless -d $git_dir;
-	my $svn_dir = "$git_dir/svn";
-
-	# just in case somebody used 'svn' as their $id at some point...
-	return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
-
-	print STDERR "Migrating from a git-svn v1 layout...\n";
-	mkpath([$svn_dir]);
-	print STDERR "Data from a previous version of git-svn exists, but\n\t",
-	             "$svn_dir\n\t(required for this version ",
-	             "($::VERSION) of git-svn) does not exist.\n";
-	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
-	while (<$fh>) {
-		my $x = $_;
-		next unless $x =~ s#^refs/remotes/##;
-		chomp $x;
-		next unless -f "$git_dir/$x/info/url";
-		my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
-		next unless $u;
-		my $dn = dirname("$git_dir/svn/$x");
-		mkpath([$dn]) unless -d $dn;
-		if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
-			mkpath(["$git_dir/svn/svn"]);
-			print STDERR " - $git_dir/$x/info => ",
-			                "$git_dir/svn/$x/info\n";
-			rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
-			       croak "$!: $x";
-			# don't worry too much about these, they probably
-			# don't exist with repos this old (save for index,
-			# and we can easily regenerate that)
-			foreach my $f (qw/unhandled.log index .rev_db/) {
-				rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
-			}
-		} else {
-			print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
-			rename "$git_dir/$x", "$git_dir/svn/$x" or
-			       croak "$!: $x";
-		}
-		$migrated++;
-	}
-	command_close_pipe($fh, $ctx);
-	print STDERR "Done migrating from a git-svn v1 layout\n";
-	$migrated;
-}
-
-sub read_old_urls {
-	my ($l_map, $pfx, $path) = @_;
-	my @dir;
-	foreach (<$path/*>) {
-		if (-r "$_/info/url") {
-			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
-			my $ref_id = $pfx . basename $_;
-			my $url = ::file_to_s("$_/info/url");
-			$l_map->{$ref_id} = $url;
-		} elsif (-d $_) {
-			push @dir, $_;
-		}
-	}
-	foreach (@dir) {
-		my $x = $_;
-		$x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
-		read_old_urls($l_map, $x, $_);
-	}
-}
-
-sub migrate_from_v2 {
-	my @cfg = command(qw/config -l/);
-	return if grep /^svn-remote\..+\.url=/, @cfg;
-	my %l_map;
-	read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
-	my $migrated = 0;
-
-	foreach my $ref_id (sort keys %l_map) {
-		eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
-		if ($@) {
-			Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
-		}
-		$migrated++;
-	}
-	$migrated;
-}
-
-sub minimize_connections {
-	my $r = Git::SVN::read_all_remotes();
-	my $new_urls = {};
-	my $root_repos = {};
-	foreach my $repo_id (keys %$r) {
-		my $url = $r->{$repo_id}->{url} or next;
-		my $fetch = $r->{$repo_id}->{fetch} or next;
-		my $ra = Git::SVN::Ra->new($url);
-
-		# skip existing cases where we already connect to the root
-		if (($ra->{url} eq $ra->{repos_root}) ||
-		    ($ra->{repos_root} eq $repo_id)) {
-			$root_repos->{$ra->{url}} = $repo_id;
-			next;
-		}
-
-		my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
-		my $root_path = $ra->{url};
-		$root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
-		foreach my $path (keys %$fetch) {
-			my $ref_id = $fetch->{$path};
-			my $gs = Git::SVN->new($ref_id, $repo_id, $path);
-
-			# make sure we can read when connecting to
-			# a higher level of a repository
-			my ($last_rev, undef) = $gs->last_rev_commit;
-			if (!defined $last_rev) {
-				$last_rev = eval {
-					$root_ra->get_latest_revnum;
-				};
-				next if $@;
-			}
-			my $new = $root_path;
-			$new .= length $path ? "/$path" : '';
-			eval {
-				$root_ra->get_log([$new], $last_rev, $last_rev,
-			                          0, 0, 1, sub { });
-			};
-			next if $@;
-			$new_urls->{$ra->{repos_root}}->{$new} =
-			        { ref_id => $ref_id,
-				  old_repo_id => $repo_id,
-				  old_path => $path };
-		}
-	}
-
-	my @emptied;
-	foreach my $url (keys %$new_urls) {
-		# see if we can re-use an existing [svn-remote "repo_id"]
-		# instead of creating a(n ugly) new section:
-		my $repo_id = $root_repos->{$url} || $url;
-
-		my $fetch = $new_urls->{$url};
-		foreach my $path (keys %$fetch) {
-			my $x = $fetch->{$path};
-			Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
-			my $pfx = "svn-remote.$x->{old_repo_id}";
-
-			my $old_fetch = quotemeta("$x->{old_path}:".
-			                          "$x->{ref_id}");
-			command_noisy(qw/config --unset/,
-			              "$pfx.fetch", '^'. $old_fetch . '$');
-			delete $r->{$x->{old_repo_id}}->
-			       {fetch}->{$x->{old_path}};
-			if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
-				command_noisy(qw/config --unset/,
-				              "$pfx.url");
-				push @emptied, $x->{old_repo_id}
-			}
-		}
-	}
-	if (@emptied) {
-		my $file = $ENV{GIT_CONFIG} || "$ENV{GIT_DIR}/config";
-		print STDERR <<EOF;
-The following [svn-remote] sections in your config file ($file) are empty
-and can be safely removed:
-EOF
-		print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
-	}
-}
-
-sub migration_check {
-	migrate_from_v0();
-	migrate_from_v1();
-	migrate_from_v2();
-	minimize_connections() if $_minimize;
-}
-
 package Git::IndexInfo;
 use strict;
 use warnings;
diff --git a/perl/Git/SVN/Migration.pm b/perl/Git/SVN/Migration.pm
new file mode 100644
index 0000000..082a788
--- /dev/null
+++ b/perl/Git/SVN/Migration.pm
@@ -0,0 +1,247 @@
+package Git::SVN::Migration;
+# these version numbers do NOT correspond to actual version numbers
+# of git nor git-svn.  They are just relative.
+#
+# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
+#
+# v1 layout: .git/$id/info/url, refs/remotes/$id
+#
+# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
+#
+# v3 layout: .git/svn/$id, refs/remotes/$id
+#            - info/url may remain for backwards compatibility
+#            - this is what we migrate up to this layout automatically,
+#            - this will be used by git svn init on single branches
+# v3.1 layout (auto migrated):
+#            - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
+#              for backwards compatibility
+#
+# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
+#            - this is only created for newly multi-init-ed
+#              repositories.  Similar in spirit to the
+#              --use-separate-remotes option in git-clone (now default)
+#            - we do not automatically migrate to this (following
+#              the example set by core git)
+#
+# v5 layout: .rev_db.$UUID => .rev_map.$UUID
+#            - newer, more-efficient format that uses 24-bytes per record
+#              with no filler space.
+#            - use xxd -c24 < .rev_map.$UUID to view and debug
+#            - This is a one-way migration, repositories updated to the
+#              new format will not be able to use old git-svn without
+#              rebuilding the .rev_db.  Rebuilding the rev_db is not
+#              possible if noMetadata or useSvmProps are set; but should
+#              be no problem for users that use the (sensible) defaults.
+use strict;
+use warnings;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Basename qw/dirname basename/;
+use vars qw/$_minimize/;
+
+sub migrate_from_v0 {
+	my $git_dir = $ENV{GIT_DIR};
+	return undef unless -d $git_dir;
+	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+	my $migrated = 0;
+	while (<$fh>) {
+		chomp;
+		my ($id, $orig_ref) = ($_, $_);
+		next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
+		next unless -f "$git_dir/$id/info/url";
+		my $new_ref = "refs/remotes/$id";
+		if (::verify_ref("$new_ref^0")) {
+			print STDERR "W: $orig_ref is probably an old ",
+			             "branch used by an ancient version of ",
+				     "git-svn.\n",
+				     "However, $new_ref also exists.\n",
+				     "We will not be able ",
+				     "to use this branch until this ",
+				     "ambiguity is resolved.\n";
+			next;
+		}
+		print STDERR "Migrating from v0 layout...\n" if !$migrated;
+		print STDERR "Renaming ref: $orig_ref => $new_ref\n";
+		command_noisy('update-ref', $new_ref, $orig_ref);
+		command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
+		$migrated++;
+	}
+	command_close_pipe($fh, $ctx);
+	print STDERR "Done migrating from v0 layout...\n" if $migrated;
+	$migrated;
+}
+
+sub migrate_from_v1 {
+	my $git_dir = $ENV{GIT_DIR};
+	my $migrated = 0;
+	return $migrated unless -d $git_dir;
+	my $svn_dir = "$git_dir/svn";
+
+	# just in case somebody used 'svn' as their $id at some point...
+	return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
+
+	print STDERR "Migrating from a git-svn v1 layout...\n";
+	mkpath([$svn_dir]);
+	print STDERR "Data from a previous version of git-svn exists, but\n\t",
+	             "$svn_dir\n\t(required for this version ",
+	             "($::VERSION) of git-svn) does not exist.\n";
+	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+	while (<$fh>) {
+		my $x = $_;
+		next unless $x =~ s#^refs/remotes/##;
+		chomp $x;
+		next unless -f "$git_dir/$x/info/url";
+		my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
+		next unless $u;
+		my $dn = dirname("$git_dir/svn/$x");
+		mkpath([$dn]) unless -d $dn;
+		if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
+			mkpath(["$git_dir/svn/svn"]);
+			print STDERR " - $git_dir/$x/info => ",
+			                "$git_dir/svn/$x/info\n";
+			rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
+			       croak "$!: $x";
+			# don't worry too much about these, they probably
+			# don't exist with repos this old (save for index,
+			# and we can easily regenerate that)
+			foreach my $f (qw/unhandled.log index .rev_db/) {
+				rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
+			}
+		} else {
+			print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
+			rename "$git_dir/$x", "$git_dir/svn/$x" or
+			       croak "$!: $x";
+		}
+		$migrated++;
+	}
+	command_close_pipe($fh, $ctx);
+	print STDERR "Done migrating from a git-svn v1 layout\n";
+	$migrated;
+}
+
+sub read_old_urls {
+	my ($l_map, $pfx, $path) = @_;
+	my @dir;
+	foreach (<$path/*>) {
+		if (-r "$_/info/url") {
+			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
+			my $ref_id = $pfx . basename $_;
+			my $url = ::file_to_s("$_/info/url");
+			$l_map->{$ref_id} = $url;
+		} elsif (-d $_) {
+			push @dir, $_;
+		}
+	}
+	foreach (@dir) {
+		my $x = $_;
+		$x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
+		read_old_urls($l_map, $x, $_);
+	}
+}
+
+sub migrate_from_v2 {
+	my @cfg = command(qw/config -l/);
+	return if grep /^svn-remote\..+\.url=/, @cfg;
+	my %l_map;
+	read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
+	my $migrated = 0;
+
+	foreach my $ref_id (sort keys %l_map) {
+		eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
+		if ($@) {
+			Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
+		}
+		$migrated++;
+	}
+	$migrated;
+}
+
+sub minimize_connections {
+	my $r = Git::SVN::read_all_remotes();
+	my $new_urls = {};
+	my $root_repos = {};
+	foreach my $repo_id (keys %$r) {
+		my $url = $r->{$repo_id}->{url} or next;
+		my $fetch = $r->{$repo_id}->{fetch} or next;
+		my $ra = Git::SVN::Ra->new($url);
+
+		# skip existing cases where we already connect to the root
+		if (($ra->{url} eq $ra->{repos_root}) ||
+		    ($ra->{repos_root} eq $repo_id)) {
+			$root_repos->{$ra->{url}} = $repo_id;
+			next;
+		}
+
+		my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
+		my $root_path = $ra->{url};
+		$root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
+		foreach my $path (keys %$fetch) {
+			my $ref_id = $fetch->{$path};
+			my $gs = Git::SVN->new($ref_id, $repo_id, $path);
+
+			# make sure we can read when connecting to
+			# a higher level of a repository
+			my ($last_rev, undef) = $gs->last_rev_commit;
+			if (!defined $last_rev) {
+				$last_rev = eval {
+					$root_ra->get_latest_revnum;
+				};
+				next if $@;
+			}
+			my $new = $root_path;
+			$new .= length $path ? "/$path" : '';
+			eval {
+				$root_ra->get_log([$new], $last_rev, $last_rev,
+			                          0, 0, 1, sub { });
+			};
+			next if $@;
+			$new_urls->{$ra->{repos_root}}->{$new} =
+			        { ref_id => $ref_id,
+				  old_repo_id => $repo_id,
+				  old_path => $path };
+		}
+	}
+
+	my @emptied;
+	foreach my $url (keys %$new_urls) {
+		# see if we can re-use an existing [svn-remote "repo_id"]
+		# instead of creating a(n ugly) new section:
+		my $repo_id = $root_repos->{$url} || $url;
+
+		my $fetch = $new_urls->{$url};
+		foreach my $path (keys %$fetch) {
+			my $x = $fetch->{$path};
+			Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
+			my $pfx = "svn-remote.$x->{old_repo_id}";
+
+			my $old_fetch = quotemeta("$x->{old_path}:".
+			                          "$x->{ref_id}");
+			command_noisy(qw/config --unset/,
+			              "$pfx.fetch", '^'. $old_fetch . '$');
+			delete $r->{$x->{old_repo_id}}->
+			       {fetch}->{$x->{old_path}};
+			if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
+				command_noisy(qw/config --unset/,
+				              "$pfx.url");
+				push @emptied, $x->{old_repo_id}
+			}
+		}
+	}
+	if (@emptied) {
+		my $file = $ENV{GIT_CONFIG} || "$ENV{GIT_DIR}/config";
+		print STDERR <<EOF;
+The following [svn-remote] sections in your config file ($file) are empty
+and can be safely removed:
+EOF
+		print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
+	}
+}
+
+sub migration_check {
+	migrate_from_v0();
+	migrate_from_v1();
+	migrate_from_v2();
+	minimize_connections() if $_minimize;
+}
+
+1;
-- 
1.7.11.1

Extract Git classes from git-svn (6/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From cb1a73929da15e87fa3dcc41c4cfa9ca592081fa Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 17:14:24 -0700
Subject: [PATCH 07/11] Fix Git::SVN::Migration after its move.

Also...
* eliminate the big "import all the Git command functions" loop, nothing needs it
  any more
* only load Git::SVN::Migration if we need it
---
 git-svn.perl              | 28 +++++++++++++++-------------
 perl/Git/SVN/Migration.pm | 16 +++++++++++++++-
 perl/Makefile             |  1 +
 t/Git-SVN/00compile.t     |  3 ++-
 4 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/git-svn.perl b/git-svn.perl
index f2bf759..8b8607d 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -11,7 +11,6 @@ $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';

 use Git::SVN;
-use Git::SVN::Log;

 use Git::SVN::Utils qw(fatal can_compress);
@@ -77,24 +76,26 @@ use File::Spec;
 use File::Find;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
 use IPC::Open3;
-use Git;
+
+use Git qw(
+    git_cmd_try
+    command
+    command_oneline
+    command_noisy
+    command_output_pipe
+    command_close_pipe
+    command_bidi_pipe
+    command_close_bidi_pipe
+);
+
 use Git::SVN::Editor qw//;
 use Git::SVN::Fetcher qw//;
-use Git::SVN::Ra qw//;
+use Git::SVN::Log;
 use Git::SVN::Prompt qw//;
+use Git::SVN::Ra qw//;
 use Memoize;  # core since 5.8.0, Jul 2002

 BEGIN {
-	# import functions from Git into our packages, en masse
-	no strict 'refs';
-	foreach (qw/command command_oneline command_noisy command_output_pipe
-	            command_input_pipe command_close_pipe
-	            command_bidi_pipe command_close_bidi_pipe/) {
-		for my $package ( qw(Git::SVN::Migration),
-			__PACKAGE__) {
-			*{"${package}::$_"} = \&{"Git::$_"};
-		}
-	}
 	Memoize::memoize 'Git::config';
 	Memoize::memoize 'Git::config_bool';
 }
@@ -365,6 +366,7 @@ if (defined $_authors_prog) {
 }

 unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
+	require Git::SVN::Migration;
 	Git::SVN::Migration::migration_check();
 }
 Git::SVN::init_vars();
diff --git a/perl/Git/SVN/Migration.pm b/perl/Git/SVN/Migration.pm
index 082a788..b17fe00 100644
--- a/perl/Git/SVN/Migration.pm
+++ b/perl/Git/SVN/Migration.pm
@@ -32,12 +32,22 @@ package Git::SVN::Migration;
 #              rebuilding the .rev_db.  Rebuilding the rev_db is not
 #              possible if noMetadata or useSvmProps are set; but should
 #              be no problem for users that use the (sensible) defaults.
+
 use strict;
 use warnings;
+
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Basename qw/dirname basename/;
-use vars qw/$_minimize/;
+
+our $_minimize;
+
+use Git qw(
+	command
+	command_noisy
+	command_output_pipe
+	command_close_pipe
+);

 sub migrate_from_v0 {
 	my $git_dir = $ENV{GIT_DIR};
@@ -146,6 +156,7 @@ sub migrate_from_v2 {
 	read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
 	my $migrated = 0;

+	require Git::SVN;
 	foreach my $ref_id (sort keys %l_map) {
 		eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
 		if ($@) {
@@ -157,6 +168,9 @@ sub migrate_from_v2 {
 }

 sub minimize_connections {
+	require Git::SVN;
+	require Git::SVN::Ra;
+
 	my $r = Git::SVN::read_all_remotes();
 	my $new_urls = {};
 	my $root_repos = {};
diff --git a/perl/Makefile b/perl/Makefile
index 2a4ca57..d6a0e84 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -31,6 +31,7 @@ modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
 modules += Git/SVN/Editor
 modules += Git/SVN/Log
+modules += Git/SVN/Migration
 modules += Git/SVN/Prompt
 modules += Git/SVN/Ra
 modules += Git/SVN/Utils
diff --git a/t/Git-SVN/00compile.t b/t/Git-SVN/00compile.t
index 37626f4..1307b65 100644
--- a/t/Git-SVN/00compile.t
+++ b/t/Git-SVN/00compile.t
@@ -3,9 +3,10 @@
 use strict;
 use warnings;

-use Test::More tests => 4;
+use Test::More tests => 5;

 require_ok 'Git::SVN';
 require_ok 'Git::SVN::Utils';
 require_ok 'Git::SVN::Ra';
 require_ok 'Git::SVN::Log';
+require_ok 'Git::SVN::Migration';
-- 
1.7.11.1

Extract Git classes from git-svn (7/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 9ff49d9e91c9741d501620ac47f78d8ff8ef9983 Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Tue, 17 Jul 2012 15:51:53 -0700
Subject: [PATCH 08/11] Cut & paste Git::IndexInfo into its own file.

No other changes, those are next commit so they can be seen in the diff.
---
 git-svn.perl          | 32 --------------------------------
 perl/Git/IndexInfo.pm | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+), 32 deletions(-)
 create mode 100644 perl/Git/IndexInfo.pm
diff --git a/git-svn.perl b/git-svn.perl
index 8b8607d..6632cfb 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -2045,38 +2045,6 @@ sub gc_directory {
 }


-package Git::IndexInfo;
-use strict;
-use warnings;
-use Git qw/command_input_pipe command_close_pipe/;
-
-sub new {
-	my ($class) = @_;
-	my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
-	bless { gui => $gui, ctx => $ctx, nr => 0}, $class;
-}
-
-sub remove {
-	my ($self, $path) = @_;
-	if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") {
-		return ++$self->{nr};
-	}
-	undef;
-}
-
-sub update {
-	my ($self, $mode, $hash, $path) = @_;
-	if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") {
-		return ++$self->{nr};
-	}
-	undef;
-}
-
-sub DESTROY {
-	my ($self) = @_;
-	command_close_pipe($self->{gui}, $self->{ctx});
-}
-
 package Git::SVN::GlobSpec;
 use strict;
 use warnings;
diff --git a/perl/Git/IndexInfo.pm b/perl/Git/IndexInfo.pm
new file mode 100644
index 0000000..a43108c
--- /dev/null
+++ b/perl/Git/IndexInfo.pm
@@ -0,0 +1,33 @@
+package Git::IndexInfo;
+use strict;
+use warnings;
+use Git qw/command_input_pipe command_close_pipe/;
+
+sub new {
+	my ($class) = @_;
+	my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
+	bless { gui => $gui, ctx => $ctx, nr => 0}, $class;
+}
+
+sub remove {
+	my ($self, $path) = @_;
+	if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") {
+		return ++$self->{nr};
+	}
+	undef;
+}
+
+sub update {
+	my ($self, $mode, $hash, $path) = @_;
+	if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") {
+		return ++$self->{nr};
+	}
+	undef;
+}
+
+sub DESTROY {
+	my ($self) = @_;
+	command_close_pipe($self->{gui}, $self->{ctx});
+}
+
+1;
-- 
1.7.11.1

Extract Git classes from git-svn (8/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 4fd7b8574b32753dcf22ec0a592f13586b938689 Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 17:20:02 -0700
Subject: [PATCH 09/11] Fix Git::IndexInfo so it compiles.

Only used by Git::SVN::Fetcher.
---
 perl/Git/IndexInfo.pm   | 2 ++
 perl/Git/SVN/Fetcher.pm | 2 ++
 perl/Makefile           | 1 +
 t/Git-SVN/00compile.t   | 3 ++-
 4 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/perl/Git/IndexInfo.pm b/perl/Git/IndexInfo.pm
index a43108c..9e454be 100644
--- a/perl/Git/IndexInfo.pm
+++ b/perl/Git/IndexInfo.pm
@@ -1,6 +1,8 @@
 package Git::IndexInfo;
+
 use strict;
 use warnings;
+
 use Git qw/command_input_pipe command_close_pipe/;

 sub new {
diff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm
index ef8e9ed..69486ef 100644
--- a/perl/Git/SVN/Fetcher.pm
+++ b/perl/Git/SVN/Fetcher.pm
@@ -10,6 +10,8 @@ use IO::File qw//;
 use Git qw/command command_oneline command_noisy command_output_pipe
            command_input_pipe command_close_pipe
            command_bidi_pipe command_close_bidi_pipe/;
+use Git::IndexInfo;
+
 BEGIN {
 	@ISA = qw(SVN::Delta::Editor);
 }
diff --git a/perl/Makefile b/perl/Makefile
index d6a0e84..6c32918 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -26,6 +26,7 @@ instdir_SQ = $(subst ','\'',$(prefix)/lib)

 modules += Git
 modules += Git/I18N
+modules += Git/IndexInfo
 modules += Git/SVN
 modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
diff --git a/t/Git-SVN/00compile.t b/t/Git-SVN/00compile.t
index 1307b65..5419438 100644
--- a/t/Git-SVN/00compile.t
+++ b/t/Git-SVN/00compile.t
@@ -3,10 +3,11 @@
 use strict;
 use warnings;

-use Test::More tests => 5;
+use Test::More tests => 6;

 require_ok 'Git::SVN';
 require_ok 'Git::SVN::Utils';
 require_ok 'Git::SVN::Ra';
 require_ok 'Git::SVN::Log';
 require_ok 'Git::SVN::Migration';
+require_ok 'Git::IndexInfo';
-- 
1.7.11.1

Extract Git classes from git-svn (9/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 368d6c7883080d85f82b1eae1815834e3d59ef5e Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Tue, 17 Jul 2012 15:54:33 -0700
Subject: [PATCH 10/11] Cut & paste Git::SVN::GlobSpec into its own file.

Fixes to make it work come next commit.
---
 git-svn.perl             | 58 -----------------------------------------------
 perl/Git/SVN/GlobSpec.pm | 59 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 58 deletions(-)
 create mode 100644 perl/Git/SVN/GlobSpec.pm
diff --git a/git-svn.perl b/git-svn.perl
index 6632cfb..7b54f43 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -2045,64 +2045,6 @@ sub gc_directory {
 }


-package Git::SVN::GlobSpec;
-use strict;
-use warnings;
-
-sub new {
-	my ($class, $glob, $pattern_ok) = @_;
-	my $re = $glob;
-	$re =~ s!/+$!!g; # no need for trailing slashes
-	my (@left, @right, @patterns);
-	my $state = "left";
-	my $die_msg = "Only one set of wildcard directories " .
-				"(e.g. '*' or '*/*/*') is supported: '$glob'\n";
-	for my $part (split(m|/|, $glob)) {
-		if ($part =~ /\*/ && $part ne "*") {
-			die "Invalid pattern in '$glob': $part\n";
-		} elsif ($pattern_ok && $part =~ /[{}]/ &&
-			 $part !~ /^\{[^{}]+\}/) {
-			die "Invalid pattern in '$glob': $part\n";
-		}
-		if ($part eq "*") {
-			die $die_msg if $state eq "right";
-			$state = "pattern";
-			push(@patterns, "[^/]*");
-		} elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
-			die $die_msg if $state eq "right";
-			$state = "pattern";
-			my $p = quotemeta($1);
-			$p =~ s/\\,/|/g;
-			push(@patterns, "(?:$p)");
-		} else {
-			if ($state eq "left") {
-				push(@left, $part);
-			} else {
-				push(@right, $part);
-				$state = "right";
-			}
-		}
-	}
-	my $depth = @patterns;
-	if ($depth == 0) {
-		die "One '*' is needed in glob: '$glob'\n";
-	}
-	my $left = join('/', @left);
-	my $right = join('/', @right);
-	$re = join('/', @patterns);
-	$re = join('\/',
-		   grep(length, quotemeta($left), "($re)", quotemeta($right)));
-	my $left_re = qr/^\/\Q$left\E(\/|$)/;
-	bless { left => $left, right => $right, left_regex => $left_re,
-	        regex => qr/$re/, glob => $glob, depth => $depth }, $class;
-}
-
-sub full_path {
-	my ($self, $path) = @_;
-	return (length $self->{left} ? "$self->{left}/" : '') .
-	       $path . (length $self->{right} ? "/$self->{right}" : '');
-}
-
 __END__

 Data structures:
diff --git a/perl/Git/SVN/GlobSpec.pm b/perl/Git/SVN/GlobSpec.pm
new file mode 100644
index 0000000..96cfd98
--- /dev/null
+++ b/perl/Git/SVN/GlobSpec.pm
@@ -0,0 +1,59 @@
+package Git::SVN::GlobSpec;
+use strict;
+use warnings;
+
+sub new {
+	my ($class, $glob, $pattern_ok) = @_;
+	my $re = $glob;
+	$re =~ s!/+$!!g; # no need for trailing slashes
+	my (@left, @right, @patterns);
+	my $state = "left";
+	my $die_msg = "Only one set of wildcard directories " .
+				"(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+	for my $part (split(m|/|, $glob)) {
+		if ($part =~ /\*/ && $part ne "*") {
+			die "Invalid pattern in '$glob': $part\n";
+		} elsif ($pattern_ok && $part =~ /[{}]/ &&
+			 $part !~ /^\{[^{}]+\}/) {
+			die "Invalid pattern in '$glob': $part\n";
+		}
+		if ($part eq "*") {
+			die $die_msg if $state eq "right";
+			$state = "pattern";
+			push(@patterns, "[^/]*");
+		} elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
+			die $die_msg if $state eq "right";
+			$state = "pattern";
+			my $p = quotemeta($1);
+			$p =~ s/\\,/|/g;
+			push(@patterns, "(?:$p)");
+		} else {
+			if ($state eq "left") {
+				push(@left, $part);
+			} else {
+				push(@right, $part);
+				$state = "right";
+			}
+		}
+	}
+	my $depth = @patterns;
+	if ($depth == 0) {
+		die "One '*' is needed in glob: '$glob'\n";
+	}
+	my $left = join('/', @left);
+	my $right = join('/', @right);
+	$re = join('/', @patterns);
+	$re = join('\/',
+		   grep(length, quotemeta($left), "($re)", quotemeta($right)));
+	my $left_re = qr/^\/\Q$left\E(\/|$)/;
+	bless { left => $left, right => $right, left_regex => $left_re,
+	        regex => qr/$re/, glob => $glob, depth => $depth }, $class;
+}
+
+sub full_path {
+	my ($self, $path) = @_;
+	return (length $self->{left} ? "$self->{left}/" : '') .
+	       $path . (length $self->{right} ? "/$self->{right}" : '');
+}
+
+1;
-- 
1.7.11.1

Extract Git classes from git-svn (10/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

From 5152b76800f076ba0bd528664f62d3c67966fa4e Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 17:25:25 -0700
Subject: [PATCH 11/11] Fix Git::SVN::GlobSpec so it works.

Only used in one place in Git::SVN, load it on demand.

That should be all the Git classes out of git-svn.
---
 perl/Git/SVN.pm          | 5 ++++-
 perl/Git/SVN/GlobSpec.pm | 1 +
 perl/Makefile            | 1 +
 t/Git-SVN/00compile.t    | 3 ++-
 4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm
index 02983d6..247ee1d 100644
--- a/perl/Git/SVN.pm
+++ b/perl/Git/SVN.pm
@@ -202,11 +202,14 @@ sub read_all_remotes {
 			    . "must start with 'refs/'\n")
 				unless $remote_ref =~ m{^refs/};
 			$local_ref = uri_decode($local_ref);
+
+			require Git::SVN::GlobSpec;
 			my $rs = {
 			    t => $t,
 			    remote => $remote,
 			    path => Git::SVN::GlobSpec->new($local_ref, 1),
-			    ref => Git::SVN::GlobSpec->new($remote_ref, 0) };
+			    ref => Git::SVN::GlobSpec->new($remote_ref, 0)
+			};
 			if (length($rs->{ref}->{right}) != 0) {
 				die "The '*' glob character must be the last ",
 				    "character of '$remote_ref'\n";
diff --git a/perl/Git/SVN/GlobSpec.pm b/perl/Git/SVN/GlobSpec.pm
index 96cfd98..fede3af 100644
--- a/perl/Git/SVN/GlobSpec.pm
+++ b/perl/Git/SVN/GlobSpec.pm
@@ -1,4 +1,5 @@
 package Git::SVN::GlobSpec;
+
 use strict;
 use warnings;
diff --git a/perl/Makefile b/perl/Makefile
index 6c32918..aa4a28b 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -31,6 +31,7 @@ modules += Git/SVN
 modules += Git/SVN/Memoize/YAML
 modules += Git/SVN/Fetcher
 modules += Git/SVN/Editor
+modules += Git/SVN/GlobSpec
 modules += Git/SVN/Log
 modules += Git/SVN/Migration
 modules += Git/SVN/Prompt
diff --git a/t/Git-SVN/00compile.t b/t/Git-SVN/00compile.t
index 5419438..c92fee4 100644
--- a/t/Git-SVN/00compile.t
+++ b/t/Git-SVN/00compile.t
@@ -3,7 +3,7 @@
 use strict;
 use warnings;

-use Test::More tests => 6;
+use Test::More tests => 7;

 require_ok 'Git::SVN';
 require_ok 'Git::SVN::Utils';
@@ -11,3 +11,4 @@ require_ok 'Git::SVN::Ra';
 require_ok 'Git::SVN::Log';
 require_ok 'Git::SVN::Migration';
 require_ok 'Git::IndexInfo';
+require_ok 'Git::SVN::GlobSpec';
-- 
1.7.11.1

Re: Find .pm files automatically (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:54:16

Hi,

Michael G Schwern wrote:
Ok, here goes.

First patch overhauls perl/Makefile.PL to make it easier to add .pm files,
which I'm going to be doing a lot of.  Instead of having to manually add to
the %pm hash, it scans for .pm files.
An excellent goal.
It also moves Error.pm into a bundle directory.  This both makes it just
another directory to scan (or not scan), but it also makes it possible to
bundle additional modules in the future.  ExtUtils::MakeMaker uses this
technique itself.
This is not so much "also" as "as an example to demonstrate the
technique", no?  I guess I'd prefer it to be in a separate patch, but
this way's fine, too.

[...]
From 47a723a860cded6b16a716ea74c5bc029ee5b0ac Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Thu, 12 Jul 2012 00:05:38 -0700
Subject: [PATCH 01/11] Make the process of adding a module less blecherous.

* Scan for .pm files and build %pms rather than having to do it by hand.
* Move the bundled Error into its own directory so we can bundle other modules.

In addition...
* Add all the .pm files to the all dependency in the alternative Makefile
---
You'll probably hate this.  Because we have a bunch of patches to
incorporate, I think it's worth spending the time to make that go as
smoothly as possible for later patches.

 - the "From 47a723..." line is for your mailer.  Please do not
   include it in the body of your message.

 - likewise for the From: and Date: lines which are redundant next to
   the corresponding fields in the mail header

 - comments that are useful for posterity, like 

	This patch overhauls perl/Makefile.PL to make it easier to add .pm files,
	which I'm going to be doing a lot of.  Instead of having to manually add to
	the %pm hash, it scans for .pm files.

   should go above the triple-dash marker, while comments that are
   less useful, like "Hi", go after the triple-dash.

 - using a different subject line for each patch makes the reader's
   life much easier, so please do use the subject lines from your
   commits in the mail header.

The git-format-patch(1) manpage has some instructions for using
Thunderbird to send patches, which should take care of all this
automatically.

If everything is right, then maintainers will love you because they
can save a bunch of your patches that look ready into a single mbox
and apply them all at once with "git am".

As Documentation/SubmittingPatches explains under "MUA specific
hints", you can test that a patch is being sent correctly by emailing
it to yourself, saving as an mbox, and trying to apply it with "git am
<path to mbox file>".  If the resulting commit is as expected, then
you've succeeded.
 perl/Makefile                                     |  6 ++--
 perl/Makefile.PL                                  | 42 +++++++++++++----------
 perl/{private-Error.pm => bundles/Error/Error.pm} |  0
 perl/bundles/README                               | 10 ++++++
 4 files changed, 36 insertions(+), 22 deletions(-)
 rename perl/{private-Error.pm => bundles/Error/Error.pm} (100%)
 create mode 100644 perl/bundles/README
Ok, on to the patch proper.
quoted hunk
diff --git a/perl/Makefile b/perl/Makefile
index 6ca7d47..4f25930 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -33,7 +33,7 @@ modules += Git/SVN/Prompt
 modules += Git/SVN/Ra

 $(makfile): ../GIT-CFLAGS Makefile
-	echo all: private-Error.pm Git.pm Git/I18N.pm > $@
+	echo all: bundles/Error/Error.pm $(modules) > $@
The word "bundles/" left me a little nervous, because I (ignorantly)
imagined that this might be some specialized facility like Python eggs
or Ruby gems.  Is the intent that this directory contains CPAN modules
we want to be able to depend on?  Is there really any intention of
having more of them than Error.pm?

Before this patch, in the default case (with MakeMaker), "make
install" wrote a manpage in <mandir>/man3/private-Error.3pm.  Does it
still do so after the patch?  Will people who have installation
scripts that expected that manpage have to change them, and if so, is
the new behavior better to make up for that effort?

[...]
quoted hunk
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -2,11 +2,16 @@ use strict;
 use warnings;
 use ExtUtils::MakeMaker;
 use Getopt::Long;
+use File::Find;
+
+# Don't forget to update the perl/Makefile, too.
+# Don't forget to test with NO_PERL_MAKEMAKER=YesPlease
Now the reader will have no reason to be looking at this file, so
these comments are pretty much useless.  In an ideal world, "make
test" in the MakeMaker build would automatically "grep perl/Makefile"
to catch modules that are not listed there, but that can wait, I
imagine.

Alternatively, maybe there could be a perl/modules.list that both
makefiles read?  That way, if I drop in an unrelated .pm file for
reference while coding the build system would not be confused by
it, and since both build systems would use the same module list
there would be no risk of it falling out of date.

Thanks for some food for thought, and hope that helps,
Jonathan

Re: Extract Git classes from git-svn (2/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:54:16

Hi,

Michael G Schwern wrote:
From 683a230e439f1d5ac2727ce4c2a74e93804fc72b Mon Sep 17 00:00:00 2001
From: "Michael G. Schwern" <redacted>
Date: Wed, 11 Jul 2012 22:16:01 -0700
Just like with patch 1, the mail body should lose the above.
Subject: [PATCH 03/11] Fix Git::SVN so it can at least compile alone.
Did I miss patch 2?
It's still very intertwined with git-svn, but that's a lot of work.  This
gets things working and tests passing again (as well as they were).

This required some parallel refactorings...

* fatal() moved out of git-svn into a new Git::SVN::Utils
[...]
 git-svn.perl                   | 33 ++++++++++++++++++---------------
 perl/Git/SVN.pm                | 29 ++++++++++++++++++++---------
 perl/Git/SVN/Utils.pm          | 19 +++++++++++++++++++
 perl/Makefile                  |  2 ++
 t/Git-SVN/00compile.t          |  9 +++++++++
 t/Git-SVN/Utils/can_compress.t | 11 +++++++++++
 t/Git-SVN/Utils/fatal.t        | 34 ++++++++++++++++++++++++++++++++++
 7 files changed, 113 insertions(+), 24 deletions(-)
 create mode 100644 perl/Git/SVN/Utils.pm
 create mode 100644 t/Git-SVN/00compile.t
 create mode 100644 t/Git-SVN/Utils/can_compress.t
 create mode 100644 t/Git-SVN/Utils/fatal.t
It seems like a lot is going on in the one patch.  Probably most of
the changes are good, but if this causes a regression we would have no
choice but to revert the whole thing, which would be unfeasible
because of later patches building on it.

So in other words, a patch like this that makes a lot of changes at
once would make life very hard for the maintainer, I imagine.

What is the motivation behind these changes?  Can they be untangled
from each other and applied one at a time, in such a way that each
incremental change looks obviously correct?

Since I'm missing the patch that created Git/SVN.pm in the first
place, I can't tell --- did that patch break the build and this one
fixes it?  In that case, the order of the two patches should be
swapped to ensure "git bisect" is still usable.

Sorry, I wish I had better news to mix in with all this.  I am
thrilled to see this is making the internal APIs saner and adding
tests so I hope we can get it in in a way that makes regressions
unlikely.

Thanks,
Jonathan

Re: Extract Git classes from git-svn (3/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:54:16

Michael G Schwern wrote:
This is a straight cut & paste.  Next commit will make it work.
As mentioned in reply to patch 3, we can't take this, but by swapping
the order of the two patches it should be possible to make it work.

Jonathan

Re: Find .pm files automatically (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:16

On 2012.7.17 5:01 PM, Jonathan Nieder wrote:
quoted
It also moves Error.pm into a bundle directory.  This both makes it just
another directory to scan (or not scan), but it also makes it possible to
bundle additional modules in the future.  ExtUtils::MakeMaker uses this
technique itself.
This is not so much "also" as "as an example to demonstrate the
technique", no?  I guess I'd prefer it to be in a separate patch, but
this way's fine, too.
I wrote the MakeMaker system so I was just cribbing off that.  It made more
sense to build a list of directories to scan and then scan them than to add
individual file exceptions later.  I could put it in a separate patch, but it
would require some bending.

You'll probably hate this.  Because we have a bunch of patches to
incorporate, I think it's worth spending the time to make that go as
smoothly as possible for later patches.
Sorry.  I have lots of experience with git but very little with the email
submission tools.  I've always either just done everything via repositories or
used Github.

It sounds like I should figure out the git-send-email tool and do this very
slowly.

The word "bundles/" left me a little nervous, because I (ignorantly)
imagined that this might be some specialized facility like Python eggs
or Ruby gems.
Nope, just copy .pm files in.

Is the intent that this directory contains CPAN modules
we want to be able to depend on?
Yes.

Is there really any intention of having more of them than Error.pm?
No idea, this is my first look at the code, but now it's possible.  In my
experience, if there's a barrier to using CPAN modules then people won't use
them.  They'll rewrite the functionality poorly.

Before this patch, in the default case (with MakeMaker), "make
install" wrote a manpage in <mandir>/man3/private-Error.3pm.  Does it
still do so after the patch?  Will people who have installation
scripts that expected that manpage have to change them, and if so, is
the new behavior better to make up for that effort?
The man page is now man3/bundles::Error::Error.3 which is equally as incorrect
as man3/private-Error.3.  It is possible to correct that so it's man3/Error.3,
but that's going to require some effort.  Basically its in the same boat as
PM.  Once you have to change one you have to change them all.

Why do install scripts have specific code to look for that man page?

If it's going to be trouble I can put Error.pm back.  It's just something I
did in passing.

quoted
+# Don't forget to update the perl/Makefile, too.
+# Don't forget to test with NO_PERL_MAKEMAKER=YesPlease
Now the reader will have no reason to be looking at this file, so
these comments are pretty much useless.  In an ideal world, "make
test" in the MakeMaker build would automatically "grep perl/Makefile"
to catch modules that are not listed there, but that can wait, I
imagine.

Alternatively, maybe there could be a perl/modules.list that both
makefiles read?  That way, if I drop in an unrelated .pm file for
reference while coding the build system would not be confused by
it, and since both build systems would use the same module list
there would be no risk of it falling out of date.
Ideally, that second Makefile would go away.  Parallel build systems are extra
work and generate bugs.

The log suggests it might have something to do with people wanting to build
with an ActiveState Perl on Cygwin or something?  MakeMaker builds different
Makefiles depending on the OS, so it may be as simple as telling Makefile.PL
what flavor of make you're using.


-- 
emacs -- THAT'S NO EDITOR... IT'S AN OPERATING SYSTEM!

Re: Find .pm files automatically (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:54:16

Thanks for the reply.  Quick clarifications:

Michael G Schwern wrote:
The man page is now man3/bundles::Error::Error.3 which is equally as incorrect
as man3/private-Error.3.  It is possible to correct that so it's man3/Error.3,
but that's going to require some effort.  Basically its in the same boat as
PM.  Once you have to change one you have to change them all.

Why do install scripts have specific code to look for that man page?
To delete it. :)

[...]
Ideally, that second Makefile would go away.  Parallel build systems are extra
work and generate bugs.
Agreed --- I'd love to see the NO_PERL_MAKEMAKER option go away.
The log suggests it might have something to do with people wanting to build
with an ActiveState Perl on Cygwin or something?  MakeMaker builds different
Makefiles depending on the OS, so it may be as simple as telling Makefile.PL
what flavor of make you're using.
I think the main user is the ordinary Git for Windows build (which
uses perl 5.8.8, from mingw or msys I imagine).  If you have the
Windows expertise to help them or know someone who could, I'm sure
they'd be happy to switch their git build to use MakeMaker.

Website: http://msysgit.github.com/

Thanks,
Jonathan

Re: Extract Git classes from git-svn (2/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Eric Wong <hidden>
Date: 2016-06-15 22:54:17

Michael G Schwern [off-list ref] wrote:

Hi Michael, thanks for taking your time to help with this.

I agree with everything Jonathan said (and thank him for taking
the time to point you in the right direction).

I too (very strongly) prefer email for code review.  I doubt I would've
ever gotten involved if git were run differently.  I'm actually
disappointed the mailing list culture that built git hasn't rubbed off
to other projects that adopt git.
quoted hunk
+++ b/t/Git-SVN/00compile.t
+use Test::More tests => 2;
I prefer not declaring test counts and using done_testing() instead.
done_testing() is favorable to me in at least 2 ways:

* done_testing() closely matches the behavior of the existing
  sh-based test suite in git (which calls test_done)

* maintaining test counts leads to unnecessary merge conflicts

Skipping the tests on old versions of Test::More (< 0.88) is acceptable
to me (especially since integration tests provide the real coverage
already).
quoted hunk
+++ b/t/Git-SVN/Utils/can_compress.t
+use Test::More 'no_plan';
no_plan is the worst way to use Test::More, I think.

Re: Extract Git classes from git-svn (2/10) (was Re: Fix git-svn tests for SVN 1.7.5.)

From: Michael G Schwern <hidden>
Date: 2016-06-15 22:54:17

On 2012.7.18 3:58 AM, Eric Wong wrote:
I agree with everything Jonathan said (and thank him for taking
the time to point you in the right direction).
Thanks, you guys have been very nice to my flailing and failing.  I'm going to
back off and send out a sort of overview email so we can figure out how best
to chunk this up.

quoted
+++ b/t/Git-SVN/00compile.t
quoted
+use Test::More tests => 2;
I prefer not declaring test counts and using done_testing() instead.
done_testing() is favorable to me in at least 2 ways:

* done_testing() closely matches the behavior of the existing
  sh-based test suite in git (which calls test_done)

* maintaining test counts leads to unnecessary merge conflicts
Yes, I concur 100%.  So much that I went back in my time machine and added
done_testing() to Test::More!  Also I killed Hitler, so now WWII ends in 1945.
 Things seem to have turned out for the better.

I love it when people advocate my features back to me. :)  I didn't use
done_testing because I didn't know your stance on using non-5.8 core versions
of modules.

Skipping the tests on old versions of Test::More (< 0.88) is acceptable
to me (especially since integration tests provide the real coverage
already).
It is very easy to bundle an uninstalled copy of Test::More, probably easier
than putting in the code necessary to check for it and skip it.  A lot of Perl
modules do it.  The usual thing is to put it into t/lib/ and add "use lib
't/lib'" to the tests.  I don't see any reason why that basic technique
wouldn't work here, with some minor changes to match the Git test suite.

I can help you with that, but I'd like to get through this SVN 1.7 fix first.


-- 
Being faith-based doesn't trump reality.
	-- Bruce Sterling

Re: Fix git-svn tests for SVN 1.7.5.

From: Ben Walton <hidden>
Date: 2016-06-15 22:54:18

Hi Michael,
quoted
I've fixed the git-svn tests for SVN 1.7 and tested with SVN 1.7.5.
Thanks.  git-svn is not maintained by Junio but by Eric and others on
the list.  I'm cc-ing Eric and Ben Walton so they can benefit from
your work.
This is fantastic.  It's been on my todo list but not a priority for
me.  I'm glad you've taken the time to scratch this itch though.

I'll try to run through this series in the next few days and I can
also do some test builds on solaris to see how it plays there.

Thanks
-Ben
--
Ben Walton
Systems Programmer - CHASS
University of Toronto
C:416.407.5610 | W:416.978.4302
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help