{"thread":{"id":"4499","subject":"[PATCH 0/13] git-svn: better branch support, SVN:: lib usage, feature additions","startedAt":"2006-06-13T18:02:02Z","lastAt":"2006-06-13T18:02:15Z","messageCount":14,"participants":["Eric Wong"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"21727","messageId":"11502217352245-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":null,"subject":"[PATCH 0/13] git-svn: better branch support, SVN:: lib usage, feature additions","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:02Z","receivedAt":"2006-06-13T18:02:02Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Most of these patches rely on the last series of patches I posted\n        (<11500094252972-git-send-email-normalperson@yhbt.net>).\n\n[PATCH 1/13] git-svn: support -C<num> passing to git-diff-tree\n[PATCH 2/13] git-svn: --branch-all-refs / -B support\n[PATCH 3/13] git-svn: optimize --branch and --branch-all-ref\n[PATCH 4/13] git-svn: support manually placed initial trees from fetch\n        These have been around since April or so, but I kept forgetting\n        about them since they were on a machine I rarely use.\n        All low impact changes.\n\n[PATCH 5/13] git-svn: Move all git-svn-related paths into $GIT_DIR/svn\n        Compatibility with 1.0.0 is broken here, in preparation for later\n        changes and stuff.\n\n[PATCH 6/13] git-svn: minor cleanups, extra error-checking\n\n[PATCH 7/13] git-svn: add --repack and --repack-flags= options\n[PATCH 8/13] git-svn: add --shared and --template= options to pass to init-db\n        Simple, low-impact changes.\n\n[PATCH 9/13] git-svn: add some functionality to better support branches in svn\n        Three new commands, graft-branches being the most interesting.\n        Large patches, but low impact.\n\n[PATCH 10/13] git-svn: add UTF-8 message test\n        Low impact change.\n\n[PATCH 11/13] git-svn: add 'log' command, a facsimile of basic `svn log'\n        A simple convenience command.  This should help ease communication\n        between git-svn and SVN users.  Large patch but low impact.\n\n\nThese last two are very high impact changes.  Some bugs were noticed\nand fixed, but more may have been introduced.\n\n[PATCH 12/13] git-svn: add support for Perl SVN::* libraries\n        We're faster, much faster in the common case.  Some hacks to get\n        around memory leaks, but we're faster, and that matters to\n        people.  There's a huge disk space reduction, too, since we\n        don't have to keep working copies around.  Of course, this is\n        optional, command-line svn will continue to be supported.\n\n[PATCH 13/13] git-svn: make the $GIT_DIR/svn/*/revs directory obsolete\n        Now that we're faster, we can import 100k revisions in a finite\n        amount of time.  Which means lots of 41-byte files in one\n        directory: gross.  Huge disk space reduction for large\n        histories.\n\n-- \nEric Wong\n"},{"id":"21728","messageId":"11502217372898-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 1/13] git-svn: support -C<num> passing to git-diff-tree","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:03Z","receivedAt":"2006-06-13T18:02:03Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"The repo-config key is 'svn.copysimilarity'\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   11 +++++++++--\n 1 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 72129de..089d597 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -33,7 +33,8 @@ use POSIX qw/strftime/;\n my $sha1 = qr/[a-f\\d]{40}/;\n my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n-\t$_find_copies_harder, $_l, $_version, $_upgrade, $_authors);\n+\t$_find_copies_harder, $_l, $_cp_similarity,\n+\t$_version, $_upgrade, $_authors);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n \n@@ -55,6 +56,7 @@ my %cmd = (\n \t\t\t\t'rmdir' => \\$_rmdir,\n \t\t\t\t'find-copies-harder' => \\$_find_copies_harder,\n \t\t\t\t'l=i' => \\$_l,\n+\t\t\t\t'copy-similarity|C=i'=> \\$_cp_similarity,\n \t\t\t\t%fc_opts,\n \t\t\t} ],\n \t'show-ignore' => [ \\&show_ignore, \"Show svn:ignore listings\", { } ],\n@@ -580,7 +582,12 @@ sub svn_checkout_tree {\n \tmy $pid = open my $diff_fh, '-|';\n \tdefined $pid or croak $!;\n \tif ($pid == 0) {\n-\t\tmy @diff_tree = qw(git-diff-tree -z -r -C);\n+\t\tmy @diff_tree = qw(git-diff-tree -z -r);\n+\t\tif ($_cp_similarity) {\n+\t\t\tpush @diff_tree, \"-C$_cp_similarity\";\n+\t\t} else {\n+\t\t\tpush @diff_tree, '-C';\n+\t\t}\n \t\tpush @diff_tree, '--find-copies-harder' if $_find_copies_harder;\n \t\tpush @diff_tree, \"-l$_l\" if defined $_l;\n \t\texec(@diff_tree, $from, $treeish) or croak $!;\n-- \n1.4.0\n"},{"id":"21729","messageId":"11502217381866-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 2/13] git-svn: --branch-all-refs / -B support","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:04Z","receivedAt":"2006-06-13T18:02:04Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"This should make life easier for all those who type:\n\n\t`git-rev-parse --symbolic --all | xargs -n1 echo -b`\n\nevery time they run git-svn fetch.\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   15 ++++++++++++++-\n 1 files changed, 14 insertions(+), 1 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 089d597..c91160d 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -34,12 +34,13 @@ my $sha1 = qr/[a-f\\d]{40}/;\n my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n \t$_find_copies_harder, $_l, $_cp_similarity,\n-\t$_version, $_upgrade, $_authors);\n+\t$_version, $_upgrade, $_authors, $_branch_all_refs);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n \n my %fc_opts = ( 'no-ignore-externals' => \\$_no_ignore_ext,\n \t\t'branch|b=s' => \\@_branch_from,\n+\t\t'branch-all-refs|B' => \\$_branch_all_refs,\n \t\t'authors-file|A=s' => \\$_authors );\n \n # yes, 'native' sets \"\\n\".  Patches to fix this for non-*nix systems welcome:\n@@ -108,6 +109,7 @@ usage(0) if $_help;\n version() if $_version;\n usage(1) unless defined $cmd;\n load_authors() if $_authors;\n+load_all_refs() if $_branch_all_refs;\n svn_compat_check();\n $cmd{$cmd}->[0]->(@ARGV);\n exit 0;\n@@ -1238,6 +1240,17 @@ sub map_tree_joins {\n \t}\n }\n \n+sub load_all_refs {\n+\tif (@_branch_from) {\n+\t\tprint STDERR '--branch|-b parameters are ignored when ',\n+\t\t\t\"--branch-all-refs|-B is passed\\n\";\n+\t}\n+\n+\t# don't worry about rev-list on non-commit objects/tags,\n+\t# it shouldn't blow up if a ref is a blob or tree...\n+\tchomp(@_branch_from = `git-rev-parse --symbolic --all`);\n+}\n+\n # '<svn username> = real-name <email address>' mapping based on git-svnimport:\n sub load_authors {\n \topen my $authors, '<', $_authors or die \"Can't open $_authors $!\\n\";\n-- \n1.4.0\n"},{"id":"21740","messageId":"11502217393714-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 3/13] git-svn: optimize --branch and --branch-all-ref","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:05Z","receivedAt":"2006-06-13T18:02:05Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"By breaking the pipe read once we've seen a commit twice.\n\nThis should make -B/--branch-all-ref faster and usable on a\nfrequent basis.\n\nWe use topological order now for calling git-rev-list, and any\ncommit we've seen before should imply that all parents have been\nseen (at least I hope that's the case for --topo-order).\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   11 +++++++++--\n 1 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex c91160d..d4b9323 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -1220,23 +1220,30 @@ sub check_upgrade_needed {\n # fills %tree_map with a reverse mapping of trees to commits.  Useful\n # for finding parents to commit on.\n sub map_tree_joins {\n+\tmy %seen;\n \tforeach my $br (@_branch_from) {\n \t\tmy $pid = open my $pipe, '-|';\n \t\tdefined $pid or croak $!;\n \t\tif ($pid == 0) {\n-\t\t\texec(qw(git-rev-list --pretty=raw), $br) or croak $?;\n+\t\t\texec(qw(git-rev-list --topo-order --pretty=raw), $br)\n+\t\t\t\t\t\t\t\tor croak $?;\n \t\t}\n \t\twhile (<$pipe>) {\n \t\t\tif (/^commit ($sha1)$/o) {\n \t\t\t\tmy $commit = $1;\n+\n+\t\t\t\t# if we've seen a commit,\n+\t\t\t\t# we've seen its parents\n+\t\t\t\tlast if $seen{$commit};\n \t\t\t\tmy ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);\n \t\t\t\tunless (defined $tree) {\n \t\t\t\t\tdie \"Failed to parse commit $commit\\n\";\n \t\t\t\t}\n \t\t\t\tpush @{$tree_map{$tree}}, $commit;\n+\t\t\t\t$seen{$commit} = 1;\n \t\t\t}\n \t\t}\n-\t\tclose $pipe or croak $?;\n+\t\tclose $pipe; # we could be breaking the pipe early\n \t}\n }\n \n-- \n1.4.0\n"},{"id":"21731","messageId":"11502217403634-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 4/13] git-svn: support manually placed initial trees from fetch","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:06Z","receivedAt":"2006-06-13T18:02:06Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Sometimes I don't feel like downloading an entire tree again when\nI actually decide a branch is worth tracking, so some users can\nget around it more easily with this.\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |    9 ++++++++-\n 1 files changed, 8 insertions(+), 1 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex d4b9323..54f3d63 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -262,7 +262,14 @@ sub fetch {\n \t} else {\n \t\tchdir $SVN_WC or croak $!;\n \t\tread_uuid();\n-\t\t$last_commit = file_to_s(\"$REV_DIR/$base->{revision}\");\n+\t\teval { $last_commit = file_to_s(\"$REV_DIR/$base->{revision}\") };\n+\t\t# looks like a user manually cp'd and svn switch'ed\n+\t\tunless ($last_commit) {\n+\t\t\tsys(qw/svn revert -R ./);\n+\t\t\tassert_svn_wc_clean($base->{revision});\n+\t\t\t$last_commit = git_commit($base, @parents);\n+\t\t\tassert_tree($last_commit);\n+\t\t}\n \t}\n \tmy @svn_up = qw(svn up);\n \tpush @svn_up, '--ignore-externals' unless $_no_ignore_ext;\n-- \n1.4.0\n"},{"id":"21737","messageId":"11502217421355-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 5/13] git-svn: Move all git-svn-related paths into $GIT_DIR/svn","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:07Z","receivedAt":"2006-06-13T18:02:07Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Since GIT_SVN_ID usage is probably going to become more\nwidespread <evil grin>, we won't run the chance of somebody\nhaving a GIT_SVN_ID name that conflicts with one of the default\ndirectories that already exist in $GIT_DIR (branches/tags).\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl     |   99 ++++++++++++++++++++++++++++++++------\n contrib/git-svn/t/lib-git-svn.sh |    2 -\n 2 files changed, 85 insertions(+), 16 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 54f3d63..2dce4e7 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -6,7 +6,7 @@ use strict;\n use vars qw/\t$AUTHOR $VERSION\n \t\t$SVN_URL $SVN_INFO $SVN_WC $SVN_UUID\n \t\t$GIT_SVN_INDEX $GIT_SVN\n-\t\t$GIT_DIR $REV_DIR/;\n+\t\t$GIT_DIR $REV_DIR $GIT_SVN_DIR/;\n $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';\n $VERSION = '1.1.0-pre';\n \n@@ -37,6 +37,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$\n \t$_version, $_upgrade, $_authors, $_branch_all_refs);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n+my @repo_path_split_cache;\n \n my %fc_opts = ( 'no-ignore-externals' => \\$_no_ignore_ext,\n \t\t'branch|b=s' => \\@_branch_from,\n@@ -100,10 +101,11 @@ GetOptions(%opts, 'help|H|h' => \\$_help,\n \t\t'id|i=s' => \\$GIT_SVN) or exit 1;\n \n $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';\n-$GIT_SVN_INDEX = \"$GIT_DIR/$GIT_SVN/index\";\n+$GIT_SVN_DIR = \"$GIT_DIR/svn/$GIT_SVN\";\n+$GIT_SVN_INDEX = \"$GIT_SVN_DIR/index\";\n $SVN_URL = undef;\n-$REV_DIR = \"$GIT_DIR/$GIT_SVN/revs\";\n-$SVN_WC = \"$GIT_DIR/$GIT_SVN/tree\";\n+$REV_DIR = \"$GIT_SVN_DIR/revs\";\n+$SVN_WC = \"$GIT_SVN_DIR/tree\";\n \n usage(0) if $_help;\n version() if $_version;\n@@ -111,6 +113,7 @@ usage(1) unless defined $cmd;\n load_authors() if $_authors;\n load_all_refs() if $_branch_all_refs;\n svn_compat_check();\n+migration_check() unless $cmd eq 'init';\n $cmd{$cmd}->[0]->(@ARGV);\n exit 0;\n \n@@ -200,7 +203,7 @@ sub rebuild {\n \t\t\t$latest = $rev;\n \t\t}\n \t\tassert_revision_eq_or_unknown($rev, $c);\n-\t\tsys('git-update-ref',\"$GIT_SVN/revs/$rev\",$c);\n+\t\tsys('git-update-ref',\"svn/$GIT_SVN/revs/$rev\",$c);\n \t\t$newest_rev = $rev if ($rev > $newest_rev);\n \t}\n \tclose $rev_list or croak $?;\n@@ -241,7 +244,7 @@ sub init {\n sub fetch {\n \tmy (@parents) = @_;\n \tcheck_upgrade_needed();\n-\t$SVN_URL ||= file_to_s(\"$GIT_DIR/$GIT_SVN/info/url\");\n+\t$SVN_URL ||= file_to_s(\"$GIT_SVN_DIR/info/url\");\n \tmy @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);\n \tunless ($_revision) {\n \t\t$_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';\n@@ -350,7 +353,7 @@ sub show_ignore {\n \tchomp(my @excludes = (<$fh>));\n \tclose $fh or croak $!;\n \n-\t$SVN_URL ||= file_to_s(\"$GIT_DIR/$GIT_SVN/info/url\");\n+\t$SVN_URL ||= file_to_s(\"$GIT_SVN_DIR/info/url\");\n \tchdir $SVN_WC or croak $!;\n \tmy %ign;\n \tFile::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d \"$_/.svn\"){\n@@ -374,7 +377,44 @@ sub read_uuid {\n \tmy $info = shift || svn_info('.');\n \t$SVN_UUID = $info->{'Repository UUID'} or\n \t\t\t\t\tcroak \"Repository UUID unreadable\\n\";\n-\ts_to_file($SVN_UUID,\"$GIT_DIR/$GIT_SVN/info/uuid\");\n+\ts_to_file($SVN_UUID,\"$GIT_SVN_DIR/info/uuid\");\n+}\n+\n+sub quiet_run {\n+\tmy $pid = fork;\n+\tdefined $pid or croak $!;\n+\tif (!$pid) {\n+\t\topen my $null, '>', '/dev/null' or croak $!;\n+\t\topen STDERR, '>&', $null or croak $!;\n+\t\topen STDOUT, '>&', $null or croak $!;\n+\t\texec @_ or croak $!;\n+\t}\n+\twaitpid $pid, 0;\n+\treturn $?;\n+}\n+\n+sub repo_path_split {\n+\tmy $full_url = shift;\n+\t$full_url =~ s#/+$##;\n+\n+\tforeach (@repo_path_split_cache) {\n+\t\tif ($full_url =~ s#$_##) {\n+\t\t\tmy $u = $1;\n+\t\t\t$full_url =~ s#^/+##;\n+\t\t\treturn ($u, $full_url);\n+\t\t}\n+\t}\n+\n+\tmy ($url, $path) = ($full_url =~ m!^([a-z\\+]+://[^/]*)(.*)$!i);\n+\t$path =~ s#^/+##;\n+\tmy @paths = split(m#/+#, $path);\n+\n+\twhile (quiet_run(qw/svn ls --non-interactive/, $url)) {\n+\t\tmy $n = shift @paths || last;\n+\t\t$url .= \"/$n\";\n+\t}\n+\tpush @repo_path_split_cache, qr/^(\\Q$url\\E)/;\n+\treturn ($url, $path);\n }\n \n sub setup_git_svn {\n@@ -382,14 +422,17 @@ sub setup_git_svn {\n \tunless (-d $GIT_DIR) {\n \t\tcroak \"GIT_DIR=$GIT_DIR does not exist!\\n\";\n \t}\n-\tmkpath([\"$GIT_DIR/$GIT_SVN\"]);\n-\tmkpath([\"$GIT_DIR/$GIT_SVN/info\"]);\n+\tmkpath([$GIT_SVN_DIR]);\n+\tmkpath([\"$GIT_SVN_DIR/info\"]);\n \tmkpath([$REV_DIR]);\n-\ts_to_file($SVN_URL,\"$GIT_DIR/$GIT_SVN/info/url\");\n+\ts_to_file($SVN_URL,\"$GIT_SVN_DIR/info/url\");\n \n-\topen my $fd, '>>', \"$GIT_DIR/$GIT_SVN/info/exclude\" or croak $!;\n+\topen my $fd, '>>', \"$GIT_SVN_DIR/info/exclude\" or croak $!;\n \tprint $fd '.svn',\"\\n\";\n \tclose $fd or croak $!;\n+\tmy ($url, $path) = repo_path_split($SVN_URL);\n+\ts_to_file($url, \"$GIT_SVN_DIR/info/repo_url\");\n+\ts_to_file($path, \"$GIT_SVN_DIR/info/repo_path\");\n }\n \n sub assert_svn_wc_clean {\n@@ -688,7 +731,7 @@ sub handle_rmdir {\n \n sub svn_commit_tree {\n \tmy ($svn_rev, $commit) = @_;\n-\tmy $commit_msg = \"$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$\";\n+\tmy $commit_msg = \"$GIT_SVN_DIR/.svn-commit.tmp.$$\";\n \tmy %log_msg = ( msg => '' );\n \topen my $msg, '>', $commit_msg or croak $!;\n \n@@ -965,7 +1008,7 @@ sub index_changes {\n \t\t\t'remove',\n \t\t\t$no_text_base);\n \tdo_update_index([qw/git-ls-files -z --others/,\n-\t\t\t      \"--exclude-from=$GIT_DIR/$GIT_SVN/info/exclude\"],\n+\t\t\t\t\"--exclude-from=$GIT_SVN_DIR/info/exclude\"],\n \t\t\t'add',\n \t\t\t$no_text_base);\n }\n@@ -1097,7 +1140,7 @@ sub git_commit {\n \t\tpush @update_ref, $primary_parent unless $?;\n \t}\n \tsys(@update_ref);\n-\tsys('git-update-ref',\"$GIT_SVN/revs/$log_msg->{revision}\",$commit);\n+\tsys('git-update-ref',\"svn/$GIT_SVN/revs/$log_msg->{revision}\",$commit);\n \tprint \"r$log_msg->{revision} = $commit\\n\";\n \treturn $commit;\n }\n@@ -1283,6 +1326,32 @@ sub svn_propget_base {\n \treturn safe_qx(qw/svn propget/, $p, $f);\n }\n \n+sub migration_check {\n+\treturn if (-d \"$GIT_DIR/svn\" || !-d $GIT_DIR);\n+\tprint \"Upgrading repository...\\n\";\n+\tunless (-d \"$GIT_DIR/svn\") {\n+\t\tmkdir \"$GIT_DIR/svn\" or croak $!;\n+\t}\n+\tprint \"Data from a previous version of git-svn exists, but\\n\\t\",\n+\t\t\t\t\"$GIT_SVN_DIR\\n\\t(required for this version \",\n+\t\t\t\t\"($VERSION) of git-svn) does not.\\n\";\n+\n+\tforeach my $x (`git-rev-parse --symbolic --all`) {\n+\t\tnext unless $x =~ s#^refs/remotes/##;\n+\t\tchomp $x;\n+\t\tnext unless -f \"$GIT_DIR/$x/info/url\";\n+\t\tmy $u = eval { file_to_s(\"$GIT_DIR/$x/info/url\") };\n+\t\tnext unless $u;\n+\t\tmy $dn = dirname(\"$GIT_DIR/svn/$x\");\n+\t\tmkpath([$dn]) unless -d $dn;\n+\t\trename \"$GIT_DIR/$x\", \"$GIT_DIR/svn/$x\" or croak \"$!: $x\";\n+\t\tmy ($url, $path) = repo_path_split($u);\n+\t\ts_to_file($url, \"$GIT_DIR/svn/$x/info/repo_url\");\n+\t\ts_to_file($path, \"$GIT_DIR/svn/$x/info/repo_path\");\n+\t}\n+\tprint \"Done upgrading.\\n\";\n+}\n+\n __END__\n \n Data structures:\ndiff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh\nindex a98e9d1..58408a6 100644\n--- a/contrib/git-svn/t/lib-git-svn.sh\n+++ b/contrib/git-svn/t/lib-git-svn.sh\n@@ -10,7 +10,7 @@ fi\n . ./test-lib.sh\n \n GIT_DIR=$PWD/.git\n-GIT_SVN_DIR=$GIT_DIR/git-svn\n+GIT_SVN_DIR=$GIT_DIR/svn/git-svn\n SVN_TREE=$GIT_SVN_DIR/tree\n \n svnadmin >/dev/null 2>&1\n-- \n1.4.0\n"},{"id":"21738","messageId":"11502217433396-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 6/13] git-svn: minor cleanups, extra error-checking","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:08Z","receivedAt":"2006-06-13T18:02:08Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"While we're at it, read_repo_config has been added and\nexpanded to handle case where command-line arguments are\noptional to Getopt::Long\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   82 ++++++++++++++++++++++++------------------\n 1 files changed, 46 insertions(+), 36 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 2dce4e7..a243060 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -77,39 +77,15 @@ for (my $i = 0; $i < @ARGV; $i++) {\n \n my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);\n \n-# convert GetOpt::Long specs for use by git-repo-config\n-foreach my $o (keys %opts) {\n-\tmy $v = $opts{$o};\n-\tmy ($key) = ($o =~ /^([a-z\\-]+)/);\n-\t$key =~ s/-//g;\n-\tmy $arg = 'git-repo-config';\n-\t$arg .= ' --int' if ($o =~ /=i$/);\n-\t$arg .= ' --bool' if ($o !~ /=[sfi]$/);\n-\tif (ref $v eq 'ARRAY') {\n-\t\tchomp(my @tmp = `$arg --get-all svn.$key`);\n-\t\t@$v = @tmp if @tmp;\n-\t} else {\n-\t\tchomp(my $tmp = `$arg --get svn.$key`);\n-\t\tif ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {\n-\t\t\t$$v = $tmp;\n-\t\t}\n-\t}\n-}\n-\n+read_repo_config(\\%opts);\n GetOptions(%opts, 'help|H|h' => \\$_help,\n \t\t'version|V' => \\$_version,\n \t\t'id|i=s' => \\$GIT_SVN) or exit 1;\n \n-$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';\n-$GIT_SVN_DIR = \"$GIT_DIR/svn/$GIT_SVN\";\n-$GIT_SVN_INDEX = \"$GIT_SVN_DIR/index\";\n-$SVN_URL = undef;\n-$REV_DIR = \"$GIT_SVN_DIR/revs\";\n-$SVN_WC = \"$GIT_SVN_DIR/tree\";\n-\n usage(0) if $_help;\n version() if $_version;\n usage(1) unless defined $cmd;\n+init_vars();\n load_authors() if $_authors;\n load_all_refs() if $_branch_all_refs;\n svn_compat_check();\n@@ -132,7 +108,7 @@ Usage: $0 <command> [options] [arguments\n \t\tprint $fd '  ',pack('A13',$_),$cmd{$_}->[1],\"\\n\";\n \t\tforeach (keys %{$cmd{$_}->[2]}) {\n \t\t\t# prints out arguments as they should be passed:\n-\t\t\tmy $x = s#=s$## ? '<arg>' : s#=i$## ? '<num>' : '';\n+\t\t\tmy $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';\n \t\t\tprint $fd ' ' x 17, join(', ', map { length $_ > 1 ?\n \t\t\t\t\t\t\t\"--$_\" : \"-$_\" }\n \t\t\t\t\t\tsplit /\\|/,$_),\" $x\\n\";\n@@ -220,9 +196,10 @@ sub rebuild {\n \t\tsys(@svn_up,\"-r$newest_rev\");\n \t\t$ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;\n \t\tindex_changes();\n-\t\texec('git-write-tree');\n+\t\texec('git-write-tree') or croak $!;\n \t}\n \twaitpid $pid, 0;\n+\tcroak $? if $?;\n \n \tif ($_upgrade) {\n \t\tprint STDERR <<\"\";\n@@ -295,6 +272,7 @@ sub fetch {\n \tunless (-e \"$GIT_DIR/refs/heads/master\") {\n \t\tsys(qw(git-update-ref refs/heads/master),$last_commit);\n \t}\n+\tclose $svn_log->{fh};\n \treturn $last;\n }\n \n@@ -830,7 +808,7 @@ sub svn_log_raw {\n \t\texec (qw(svn log), @log_args) or croak $!\n \t}\n \twaitpid $pid, 0;\n-\tcroak if $?;\n+\tcroak $? if $?;\n \tseek $log_fh, 0, 0 or croak $!;\n \treturn { state => 'sep', fh => $log_fh };\n }\n@@ -1090,7 +1068,7 @@ sub git_commit {\n \t\t$ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;\n \t\tindex_changes();\n \t\tchomp(my $tree = `git-write-tree`);\n-\t\tcroak if $?;\n+\t\tcroak $? if $?;\n \t\tif (exists $tree_map{$tree}) {\n \t\t\tmy %seen_parent = map { $_ => 1 } @exec_parents;\n \t\t\tforeach (@{$tree_map{$tree}}) {\n@@ -1118,7 +1096,7 @@ sub git_commit {\n \t\texec @exec or croak $!;\n \t}\n \twaitpid($pid,0);\n-\tcroak if $?;\n+\tcroak $? if $?;\n \n \t$out_fh->flush == 0 or croak $!;\n \tseek $out_fh, 0, 0 or croak $!;\n@@ -1134,7 +1112,7 @@ sub git_commit {\n \t\t\tclose STDERR;\n \t\t\tclose STDOUT;\n \t\t\texec 'git-rev-parse','--verify',\n-\t\t\t\t\t\t\"refs/remotes/$GIT_SVN^0\";\n+\t\t\t\t\t\"refs/remotes/$GIT_SVN^0\" or croak $!;\n \t\t}\n \t\twaitpid $pid, 0;\n \t\tpush @update_ref, $primary_parent unless $?;\n@@ -1190,7 +1168,7 @@ sub blob_to_file {\n \n \tif ($pid == 0) {\n \t\topen STDOUT, '>&', $blob_fh or croak $!;\n-\t\texec('git-cat-file','blob',$blob);\n+\t\texec('git-cat-file','blob',$blob) or croak $!;\n \t}\n \twaitpid $pid, 0;\n \tcroak $? if $?;\n@@ -1202,7 +1180,7 @@ sub safe_qx {\n \tmy $pid = open my $child, '-|';\n \tdefined $pid or croak $!;\n \tif ($pid == 0) {\n-\t\texec(@_) or croak $?;\n+\t\texec(@_) or croak $!;\n \t}\n \tmy @ret = (<$child>);\n \tclose $child or croak $?;\n@@ -1252,7 +1230,7 @@ sub check_upgrade_needed {\n \t\tdefined $pid or croak $!;\n \t\tif ($pid == 0) {\n \t\t\tclose STDERR;\n-\t\t\texec('git-rev-parse',\"$GIT_SVN-HEAD\") or croak $?;\n+\t\t\texec('git-rev-parse',\"$GIT_SVN-HEAD\") or croak $!;\n \t\t}\n \t\tmy @ret = (<$child>);\n \t\tclose $child or croak $?;\n@@ -1276,7 +1254,7 @@ sub map_tree_joins {\n \t\tdefined $pid or croak $!;\n \t\tif ($pid == 0) {\n \t\t\texec(qw(git-rev-list --topo-order --pretty=raw), $br)\n-\t\t\t\t\t\t\t\tor croak $?;\n+\t\t\t\t\t\t\t\tor croak $!;\n \t\t}\n \t\twhile (<$pipe>) {\n \t\t\tif (/^commit ($sha1)$/o) {\n@@ -1352,6 +1330,38 @@ sub migration_check {\n \tprint \"Done upgrading.\\n\";\n }\n \n+sub init_vars {\n+\t$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';\n+\t$GIT_SVN_DIR = \"$GIT_DIR/svn/$GIT_SVN\";\n+\t$GIT_SVN_INDEX = \"$GIT_SVN_DIR/index\";\n+\t$SVN_URL = undef;\n+\t$REV_DIR = \"$GIT_SVN_DIR/revs\";\n+\t$SVN_WC = \"$GIT_SVN_DIR/tree\";\n+}\n+\n+# convert GetOpt::Long specs for use by git-repo-config\n+sub read_repo_config {\n+\treturn unless -d $GIT_DIR;\n+\tmy $opts = shift;\n+\tforeach my $o (keys %$opts) {\n+\t\tmy $v = $opts->{$o};\n+\t\tmy ($key) = ($o =~ /^([a-z\\-]+)/);\n+\t\t$key =~ s/-//g;\n+\t\tmy $arg = 'git-repo-config';\n+\t\t$arg .= ' --int' if ($o =~ /[:=]i$/);\n+\t\t$arg .= ' --bool' if ($o !~ /[:=][sfi]$/);\n+\t\tif (ref $v eq 'ARRAY') {\n+\t\t\tchomp(my @tmp = `$arg --get-all svn.$key`);\n+\t\t\t@$v = @tmp if @tmp;\n+\t\t} else {\n+\t\t\tchomp(my $tmp = `$arg --get svn.$key`);\n+\t\t\tif ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {\n+\t\t\t\t$$v = $tmp;\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n __END__\n \n Data structures:\n-- \n1.4.0\n"},{"id":"21739","messageId":"1150221745225-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 7/13] git-svn: add --repack and --repack-flags= options","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:09Z","receivedAt":"2006-06-13T18:02:09Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"This should help keep disk usage sane for large imports.\n\n--repack takes an optional argument for the interval, it\ndefaults to 1000 if no argument is specified.\n\nArguments to --repack-flags are passed directly to git-repack.\nNo arguments are passed by default.\n\nIdea stolen from git-cvsimport :)\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   18 +++++++++++++++++-\n 1 files changed, 17 insertions(+), 1 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex a243060..a04cf1d 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -34,6 +34,7 @@ my $sha1 = qr/[a-f\\d]{40}/;\n my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n \t$_find_copies_harder, $_l, $_cp_similarity,\n+\t$_repack, $_repack_nr, $_repack_flags,\n \t$_version, $_upgrade, $_authors, $_branch_all_refs);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n@@ -42,7 +43,9 @@ my @repo_path_split_cache;\n my %fc_opts = ( 'no-ignore-externals' => \\$_no_ignore_ext,\n \t\t'branch|b=s' => \\@_branch_from,\n \t\t'branch-all-refs|B' => \\$_branch_all_refs,\n-\t\t'authors-file|A=s' => \\$_authors );\n+\t\t'authors-file|A=s' => \\$_authors,\n+\t\t'repack:i' => \\$_repack,\n+\t\t'repack-flags|repack-args|repack-opts=s' => \\$_repack_flags);\n \n # yes, 'native' sets \"\\n\".  Patches to fix this for non-*nix systems welcome:\n my %EOL = ( CR => \"\\015\", LF => \"\\012\", CRLF => \"\\015\\012\", native => \"\\012\" );\n@@ -82,6 +85,7 @@ GetOptions(%opts, 'help|H|h' => \\$_help,\n \t\t'version|V' => \\$_version,\n \t\t'id|i=s' => \\$GIT_SVN) or exit 1;\n \n+set_default_vals();\n usage(0) if $_help;\n version() if $_version;\n usage(1) unless defined $cmd;\n@@ -1120,6 +1124,10 @@ sub git_commit {\n \tsys(@update_ref);\n \tsys('git-update-ref',\"svn/$GIT_SVN/revs/$log_msg->{revision}\",$commit);\n \tprint \"r$log_msg->{revision} = $commit\\n\";\n+\tif ($_repack && (--$_repack_nr == 0)) {\n+\t\t$_repack_nr = $_repack;\n+\t\tsys(\"git repack $_repack_flags\");\n+\t}\n \treturn $commit;\n }\n \n@@ -1362,6 +1370,14 @@ sub read_repo_config {\n \t}\n }\n \n+sub set_default_vals {\n+\tif (defined $_repack) {\n+\t\t$_repack = 1000 if ($_repack <= 0);\n+\t\t$_repack_nr = $_repack;\n+\t\t$_repack_flags ||= '';\n+\t}\n+}\n+\n __END__\n \n Data structures:\n-- \n1.4.0\n"},{"id":"21730","messageId":"1150221746915-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 8/13] git-svn: add --shared and --template= options to pass to init-db","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:10Z","receivedAt":"2006-06-13T18:02:10Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Signed-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |   10 ++++++++--\n 1 files changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex a04cf1d..d8f103e 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -35,6 +35,7 @@ my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n \t$_find_copies_harder, $_l, $_cp_similarity,\n \t$_repack, $_repack_nr, $_repack_flags,\n+\t$_template, $_shared,\n \t$_version, $_upgrade, $_authors, $_branch_all_refs);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n@@ -54,7 +55,9 @@ my %cmd = (\n \tfetch => [ \\&fetch, \"Download new revisions from SVN\",\n \t\t\t{ 'revision|r=s' => \\$_revision, %fc_opts } ],\n \tinit => [ \\&init, \"Initialize a repo for tracking\" .\n-\t\t\t  \" (requires URL argument)\", { } ],\n+\t\t\t  \" (requires URL argument)\",\n+\t\t\t  { 'template=s' => \\$_template,\n+\t\t\t    'shared' => \\$_shared } ],\n \tcommit => [ \\&commit, \"Commit git revisions to SVN\",\n \t\t\t{\t'stdin|' => \\$_stdin,\n \t\t\t\t'edit|e' => \\$_edit,\n@@ -217,7 +220,10 @@ sub init {\n \t$SVN_URL = shift or die \"SVN repository location required \" .\n \t\t\t\t\"as a command-line argument\\n\";\n \tunless (-d $GIT_DIR) {\n-\t\tsys('git-init-db');\n+\t\tmy @init_db = ('git-init-db');\n+\t\tpush @init_db, \"--template=$_template\" if defined $_template;\n+\t\tpush @init_db, \"--shared\" if defined $_shared;\n+\t\tsys(@init_db);\n \t}\n \tsetup_git_svn();\n }\n-- \n1.4.0\n"},{"id":"21733","messageId":"11502217472672-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 9/13] git-svn: add some functionality to better support branches in svn","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:11Z","receivedAt":"2006-06-13T18:02:11Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"New commands:\n\ngraft-branches - The most interesting command of the bunch.  It\ndetects branches in SVN via various techniques (currently\nregexes and file copies).   It can be later extended to handle\nsvk and other properties people may use to track merges in svk.\nBasically, merge tracking is not standardized at all in the SVN\nworld, and git grafts are perfect for dealing with this\nsituation.\n\nExisting branch support (via tree matches) is only handled at\nfetch time.\n\nThe following tow were originally implemented as shell scripts\nseveral months ago, but I just decided to streamline things a\nbit and added them to the main script.\n\nmulti-init - supports git-svnimport-like command-line syntax for\nimporting repositories that are layed out as recommended by the\nSVN folks.  This is a bit more tolerant than the git-svnimport\ncommand-line syntax and doesn't require the user to figure out\nwhere the repository URL ends and where the repository path\nbegins.\n\nmulti-fetch - runs fetch on all known SVN branches we're\ntracking.  This will NOT discover new branches (unlike\ngit-svnimport), so multi-init will need to be re-run (it's\nidempotent).\n\nConsider these three to be auxilliary commands (like\nshow-ignore, and rebuild) so their behavior won't receive as\nmuch testing or scrutiny as the core commands (fetch and\ncommit).\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |  429 ++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 424 insertions(+), 5 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex d8f103e..d5c7e47 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -35,8 +35,8 @@ my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n \t$_find_copies_harder, $_l, $_cp_similarity,\n \t$_repack, $_repack_nr, $_repack_flags,\n-\t$_template, $_shared,\n-\t$_version, $_upgrade, $_authors, $_branch_all_refs);\n+\t$_template, $_shared, $_no_default_regex, $_no_graft_copy,\n+\t$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);\n my (@_branch_from, %tree_map, %users);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n my @repo_path_split_cache;\n@@ -48,6 +48,12 @@ my %fc_opts = ( 'no-ignore-externals' =>\n \t\t'repack:i' => \\$_repack,\n \t\t'repack-flags|repack-args|repack-opts=s' => \\$_repack_flags);\n \n+my ($_trunk, $_tags, $_branches);\n+my %multi_opts = ( 'trunk|T=s' => \\$_trunk,\n+\t\t'tags|t=s' => \\$_tags,\n+\t\t'branches|b=s' => \\$_branches );\n+my %init_opts = ( 'template=s' => \\$_template, 'shared' => \\$_shared );\n+\n # yes, 'native' sets \"\\n\".  Patches to fix this for non-*nix systems welcome:\n my %EOL = ( CR => \"\\015\", LF => \"\\012\", CRLF => \"\\015\\012\", native => \"\\012\" );\n \n@@ -56,8 +62,7 @@ my %cmd = (\n \t\t\t{ 'revision|r=s' => \\$_revision, %fc_opts } ],\n \tinit => [ \\&init, \"Initialize a repo for tracking\" .\n \t\t\t  \" (requires URL argument)\",\n-\t\t\t  { 'template=s' => \\$_template,\n-\t\t\t    'shared' => \\$_shared } ],\n+\t\t\t  \\%init_opts ],\n \tcommit => [ \\&commit, \"Commit git revisions to SVN\",\n \t\t\t{\t'stdin|' => \\$_stdin,\n \t\t\t\t'edit|e' => \\$_edit,\n@@ -71,7 +76,19 @@ my %cmd = (\n \trebuild => [ \\&rebuild, \"Rebuild git-svn metadata (after git clone)\",\n \t\t\t{ 'no-ignore-externals' => \\$_no_ignore_ext,\n \t\t\t  'upgrade' => \\$_upgrade } ],\n+\t'graft-branches' => [ \\&graft_branches,\n+\t\t\t'Detect merges/branches from already imported history',\n+\t\t\t{ 'merge-rx|m' => \\@_opt_m,\n+\t\t\t  'no-default-regex' => \\$_no_default_regex,\n+\t\t\t  'no-graft-copy' => \\$_no_graft_copy } ],\n+\t'multi-init' => [ \\&multi_init,\n+\t\t\t'Initialize multiple trees (like git-svnimport)',\n+\t\t\t{ %multi_opts, %fc_opts } ],\n+\t'multi-fetch' => [ \\&multi_fetch,\n+\t\t\t'Fetch multiple trees (like git-svnimport)',\n+\t\t\t\\%fc_opts ],\n );\n+\n my $cmd;\n for (my $i = 0; $i < @ARGV; $i++) {\n \tif (defined $cmd{$ARGV[$i]}) {\n@@ -96,7 +113,7 @@ init_vars();\n load_authors() if $_authors;\n load_all_refs() if $_branch_all_refs;\n svn_compat_check();\n-migration_check() unless $cmd eq 'init';\n+migration_check() unless $cmd =~ /^(?:init|multi-init)$/;\n $cmd{$cmd}->[0]->(@ARGV);\n exit 0;\n \n@@ -219,6 +236,7 @@ when you have upgraded your tools and ha\n sub init {\n \t$SVN_URL = shift or die \"SVN repository location required \" .\n \t\t\t\t\"as a command-line argument\\n\";\n+\t$SVN_URL =~ s!/+$!!; # strip trailing slash\n \tunless (-d $GIT_DIR) {\n \t\tmy @init_db = ('git-init-db');\n \t\tpush @init_db, \"--template=$_template\" if defined $_template;\n@@ -358,8 +376,283 @@ sub show_ignore {\n \t}\n }\n \n+sub graft_branches {\n+\tmy $gr_file = \"$GIT_DIR/info/grafts\";\n+\tmy ($grafts, $comments) = read_grafts($gr_file);\n+\tmy $gr_sha1;\n+\n+\tif (%$grafts) {\n+\t\t# temporarily disable our grafts file to make this idempotent\n+\t\tchomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));\n+\t\trename $gr_file, \"$gr_file~$gr_sha1\" or croak $!;\n+\t}\n+\n+\tmy $l_map = read_url_paths();\n+\tmy @re = map { qr/$_/is } @_opt_m if @_opt_m;\n+\tunless ($_no_default_regex) {\n+\t\tpush @re, (\tqr/\\b(?:merge|merging|merged)\\s+(\\S.+)/is,\n+\t\t\t\tqr/\\b(?:from|of)\\s+(\\S.+)/is );\n+\t}\n+\tforeach my $u (keys %$l_map) {\n+\t\tif (@re) {\n+\t\t\tforeach my $p (keys %{$l_map->{$u}}) {\n+\t\t\t\tgraft_merge_msg($grafts,$l_map,$u,$p);\n+\t\t\t}\n+\t\t}\n+\t\tgraft_file_copy($grafts,$l_map,$u) unless $_no_graft_copy;\n+\t}\n+\n+\twrite_grafts($grafts, $comments, $gr_file);\n+\tunlink \"$gr_file~$gr_sha1\" if $gr_sha1;\n+}\n+\n+sub multi_init {\n+\tmy $url = shift;\n+\t$_trunk ||= 'trunk';\n+\t$_trunk =~ s#/+$##;\n+\t$url =~ s#/+$## if $url;\n+\tif ($_trunk !~ m#^[a-z\\+]+://#) {\n+\t\t$_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);\n+\t\tunless ($url) {\n+\t\t\tprint STDERR \"E: '$_trunk' is not a complete URL \",\n+\t\t\t\t\"and a separate URL is not specified\\n\";\n+\t\t\texit 1;\n+\t\t}\n+\t\t$_trunk = $url . $_trunk;\n+\t}\n+\tif ($GIT_SVN eq 'git-svn') {\n+\t\tprint \"GIT_SVN_ID set to 'trunk' for $_trunk\\n\";\n+\t\t$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';\n+\t}\n+\tinit_vars();\n+\tinit($_trunk);\n+\tcomplete_url_ls_init($url, $_branches, '--branches/-b', '');\n+\tcomplete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');\n+}\n+\n+sub multi_fetch {\n+\t# try to do trunk first, since branches/tags\n+\t# may be descended from it.\n+\tif (-d \"$GIT_DIR/svn/trunk\") {\n+\t\tprint \"Fetching trunk\\n\";\n+\t\tdefined(my $pid = fork) or croak $!;\n+\t\tif (!$pid) {\n+\t\t\t$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';\n+\t\t\tinit_vars();\n+\t\t\tfetch(@_);\n+\t\t\texit 0;\n+\t\t}\n+\t\twaitpid $pid, 0;\n+\t\tcroak $? if $?;\n+\t}\n+\trec_fetch('', \"$GIT_DIR/svn\", @_);\n+}\n+\n ########################### utility functions #########################\n \n+sub rec_fetch {\n+\tmy ($pfx, $p, @args) = @_;\n+\tmy @dir;\n+\tforeach (sort <$p/*>) {\n+\t\tif (-r \"$_/info/url\") {\n+\t\t\t$pfx .= '/' if $pfx && $pfx !~ m!/$!;\n+\t\t\tmy $id = $pfx . basename $_;\n+\t\t\tnext if $id eq 'trunk';\n+\t\t\tprint \"Fetching $id\\n\";\n+\t\t\tdefined(my $pid = fork) or croak $!;\n+\t\t\tif (!$pid) {\n+\t\t\t\t$GIT_SVN = $ENV{GIT_SVN_ID} = $id;\n+\t\t\t\tinit_vars();\n+\t\t\t\tfetch(@args);\n+\t\t\t\texit 0;\n+\t\t\t}\n+\t\t\twaitpid $pid, 0;\n+\t\t\tcroak $? if $?;\n+\t\t} elsif (-d $_) {\n+\t\t\tpush @dir, $_;\n+\t\t}\n+\t}\n+\tforeach (@dir) {\n+\t\tmy $x = $_;\n+\t\t$x =~ s!^\\Q$GIT_DIR\\E/svn/!!;\n+\t\trec_fetch($x, $_);\n+\t}\n+}\n+\n+sub complete_url_ls_init {\n+\tmy ($url, $var, $switch, $pfx) = @_;\n+\tunless ($var) {\n+\t\tprint STDERR \"W: $switch not specified\\n\";\n+\t\treturn;\n+\t}\n+\t$var =~ s#/+$##;\n+\tif ($var !~ m#^[a-z\\+]+://#) {\n+\t\t$var = '/' . $var if ($var !~ m#^/#);\n+\t\tunless ($url) {\n+\t\t\tprint STDERR \"E: '$var' is not a complete URL \",\n+\t\t\t\t\"and a separate URL is not specified\\n\";\n+\t\t\texit 1;\n+\t\t}\n+\t\t$var = $url . $var;\n+\t}\n+\tchomp(my @ls = safe_qx(qw/svn ls --non-interactive/, $var));\n+\tmy $old = $GIT_SVN;\n+\tdefined(my $pid = fork) or croak $!;\n+\tif (!$pid) {\n+\t\tforeach my $u (map { \"$var/$_\" } (grep m!/$!, @ls)) {\n+\t\t\t$u =~ s#/+$##;\n+\t\t\tif ($u !~ m!\\Q$var\\E/(.+)$!) {\n+\t\t\t\tprint STDERR \"W: Unrecognized URL: $u\\n\";\n+\t\t\t\tdie \"This should never happen\\n\";\n+\t\t\t}\n+\t\t\tmy $id = $pfx.$1;\n+\t\t\tprint \"init $u => $id\\n\";\n+\t\t\t$GIT_SVN = $ENV{GIT_SVN_ID} = $id;\n+\t\t\tinit_vars();\n+\t\t\tinit($u);\n+\t\t}\n+\t\texit 0;\n+\t}\n+\twaitpid $pid, 0;\n+\tcroak $? if $?;\n+}\n+\n+sub common_prefix {\n+\tmy $paths = shift;\n+\tmy %common;\n+\tforeach (@$paths) {\n+\t\tmy @tmp = split m#/#, $_;\n+\t\tmy $p = '';\n+\t\twhile (my $x = shift @tmp) {\n+\t\t\t$p .= \"/$x\";\n+\t\t\t$common{$p} ||= 0;\n+\t\t\t$common{$p}++;\n+\t\t}\n+\t}\n+\tforeach (sort {length $b <=> length $a} keys %common) {\n+\t\tif ($common{$_} == @$paths) {\n+\t\t\treturn $_;\n+\t\t}\n+\t}\n+\treturn '';\n+}\n+\n+# this isn't funky-filename safe, but good enough for now...\n+sub graft_file_copy {\n+\tmy ($grafts, $l_map, $u) = @_;\n+\tmy $paths = $l_map->{$u};\n+\tmy $pfx = common_prefix([keys %$paths]);\n+\n+\tmy $pid = open my $fh, '-|';\n+\tdefined $pid or croak $!;\n+\tunless ($pid) {\n+\t\texec(qw/svn log -v/, $u.$pfx) or croak $!;\n+\t}\n+\tmy ($r, $mp) = (undef, undef);\n+\twhile (<$fh>) {\n+\t\tchomp;\n+\t\tif (/^\\-{72}$/) {\n+\t\t\t$mp = $r = undef;\n+\t\t} elsif (/^r(\\d+) \\| /) {\n+\t\t\t$r = $1 unless defined $r;\n+\t\t} elsif (/^Changed paths:/) {\n+\t\t\t$mp = 1;\n+\t\t} elsif ($mp && m#^   [AR] /(\\S.*?) \\(from /(\\S+?):(\\d+)\\)$#) {\n+\t\t\tmy $dbg = \"r$r | $_\";\n+\t\t\tmy ($p1, $p0, $r0) = ($1, $2, $3);\n+\t\t\tmy $c;\n+\t\t\tforeach my $x (keys %$paths) {\n+\t\t\t\tnext unless ($p1 =~ /^\\Q$x\\E/);\n+\t\t\t\tmy $i = $paths->{$x};\n+\t\t\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r\";\n+\t\t\t\tunless (-r $f) {\n+\t\t\t\t\tprint STDERR \"r$r of $i not imported,\",\n+\t\t\t\t\t\t\t\t\" $dbg\\n\";\n+\t\t\t\t\tnext;\n+\t\t\t\t}\n+\t\t\t\t$c = file_to_s($f);\n+\t\t\t}\n+\t\t\tnext unless $c;\n+\t\t\tforeach my $x (keys %$paths) {\n+\t\t\t\tnext unless ($p0 =~ /^\\Q$x\\E/);\n+\t\t\t\tmy $i = $paths->{$x};\n+\t\t\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r0\";\n+\t\t\t\twhile ($r0 && !-r $f) {\n+\t\t\t\t\t# could be an older revision, too...\n+\t\t\t\t\t$r0--;\n+\t\t\t\t\t$f = \"$GIT_DIR/svn/$i/revs/$r0\";\n+\t\t\t\t}\n+\t\t\t\tunless (-r $f) {\n+\t\t\t\t\tprint STDERR \"r$r0 of $i not imported,\",\n+\t\t\t\t\t\t\t\t\" $dbg\\n\";\n+\t\t\t\t\tnext;\n+\t\t\t\t}\n+\t\t\t\tmy $r1 = file_to_s($f);\n+\t\t\t\t$grafts->{$c}->{$r1} = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n+sub process_merge_msg_matches {\n+\tmy ($grafts, $l_map, $u, $p, $c, @matches) = @_;\n+\tmy (@strong, @weak);\n+\tforeach (@matches) {\n+\t\t# merging with ourselves is not interesting\n+\t\tnext if $_ eq $p;\n+\t\tif ($l_map->{$u}->{$_}) {\n+\t\t\tpush @strong, $_;\n+\t\t} else {\n+\t\t\tpush @weak, $_;\n+\t\t}\n+\t}\n+\tforeach my $w (@weak) {\n+\t\tlast if @strong;\n+\t\t# no exact match, use branch name as regexp.\n+\t\tmy $re = qr/\\Q$w\\E/i;\n+\t\tforeach (keys %{$l_map->{$u}}) {\n+\t\t\tif (/$re/) {\n+\t\t\t\tpush @strong, $_;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t\tlast if @strong;\n+\t\t$w = basename($w);\n+\t\t$re = qr/\\Q$w\\E/i;\n+\t\tforeach (keys %{$l_map->{$u}}) {\n+\t\t\tif (/$re/) {\n+\t\t\t\tpush @strong, $_;\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tmy ($rev) = ($c->{m} =~ /^git-svn-id:\\s(?:\\S+?)\\@(\\d+)\n+\t\t\t\t\t\\s(?:[a-f\\d\\-]+)$/xsm);\n+\tunless (defined $rev) {\n+\t\t($rev) = ($c->{m} =~/^git-svn-id:\\s(\\d+)\n+\t\t\t\t\t\\@(?:[a-f\\d\\-]+)/xsm);\n+\t\treturn unless defined $rev;\n+\t}\n+\tforeach my $m (@strong) {\n+\t\tmy ($r0, $s0) = find_rev_before($rev, $m);\n+\t\t$grafts->{$c->{c}}->{$s0} = 1 if defined $s0;\n+\t}\n+}\n+\n+sub graft_merge_msg {\n+\tmy ($grafts, $l_map, $u, $p, @re) = @_;\n+\n+\tmy $x = $l_map->{$u}->{$p};\n+\tmy $rl = rev_list_raw($x);\n+\twhile (my $c = next_rev_list_entry($rl)) {\n+\t\tforeach my $re (@re) {\n+\t\t\tmy (@br) = ($c->{m} =~ /$re/g);\n+\t\t\tnext unless @br;\n+\t\t\tprocess_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);\n+\t\t}\n+\t}\n+}\n+\n sub read_uuid {\n \treturn if $SVN_UUID;\n \tmy $info = shift || svn_info('.');\n@@ -402,6 +695,7 @@ sub repo_path_split {\n \t\t$url .= \"/$n\";\n \t}\n \tpush @repo_path_split_cache, qr/^(\\Q$url\\E)/;\n+\t$path = join('/',@paths);\n \treturn ($url, $path);\n }\n \n@@ -806,6 +1100,38 @@ sub svn_commit_tree {\n \treturn fetch(\"$committed=$commit\")->{revision};\n }\n \n+sub rev_list_raw {\n+\tmy (@args) = @_;\n+\tmy $pid = open my $fh, '-|';\n+\tdefined $pid or croak $!;\n+\tif (!$pid) {\n+\t\texec(qw/git-rev-list --pretty=raw/, @args) or croak $!;\n+\t}\n+\treturn { fh => $fh, t => { } };\n+}\n+\n+sub next_rev_list_entry {\n+\tmy $rl = shift;\n+\tmy $fh = $rl->{fh};\n+\tmy $x = $rl->{t};\n+\twhile (<$fh>) {\n+\t\tif (/^commit ($sha1)$/o) {\n+\t\t\tif ($x->{c}) {\n+\t\t\t\t$rl->{t} = { c => $1 };\n+\t\t\t\treturn $x;\n+\t\t\t} else {\n+\t\t\t\t$x->{c} = $1;\n+\t\t\t}\n+\t\t} elsif (/^parent ($sha1)$/o) {\n+\t\t\t$x->{p}->{$1} = 1;\n+\t\t} elsif (s/^    //) {\n+\t\t\t$x->{m} ||= '';\n+\t\t\t$x->{m} .= $_;\n+\t\t}\n+\t}\n+\treturn ($x != $rl->{t}) ? $x : undef;\n+}\n+\n # read the entire log into a temporary file (which is removed ASAP)\n # and store the file handle + parser state\n sub svn_log_raw {\n@@ -1318,6 +1644,16 @@ sub svn_propget_base {\n \treturn safe_qx(qw/svn propget/, $p, $f);\n }\n \n+sub git_svn_each {\n+\tmy $sub = shift;\n+\tforeach (`git-rev-parse --symbolic --all`) {\n+\t\tnext unless s#^refs/remotes/##;\n+\t\tchomp $_;\n+\t\tnext unless -f \"$GIT_DIR/svn/$_/info/url\";\n+\t\t&$sub($_);\n+\t}\n+}\n+\n sub migration_check {\n \treturn if (-d \"$GIT_DIR/svn\" || !-d $GIT_DIR);\n \tprint \"Upgrading repository...\\n\";\n@@ -1344,6 +1680,16 @@ sub migration_check {\n \tprint \"Done upgrading.\\n\";\n }\n \n+sub find_rev_before {\n+\tmy ($r, $git_svn_id) = @_;\n+\tmy @revs = map { basename $_ } <$GIT_DIR/svn/$git_svn_id/revs/*>;\n+\tforeach my $r0 (sort { $b <=> $a } @revs) {\n+\t\tnext if $r0 >= $r;\n+\t\treturn ($r0, file_to_s(\"$GIT_DIR/svn/$git_svn_id/revs/$r0\"));\n+\t}\n+\treturn (undef, undef);\n+}\n+\n sub init_vars {\n \t$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';\n \t$GIT_SVN_DIR = \"$GIT_DIR/svn/$GIT_SVN\";\n@@ -1384,6 +1730,79 @@ sub set_default_vals {\n \t}\n }\n \n+sub read_grafts {\n+\tmy $gr_file = shift;\n+\tmy ($grafts, $comments) = ({}, {});\n+\tif (open my $fh, '<', $gr_file) {\n+\t\tmy @tmp;\n+\t\twhile (<$fh>) {\n+\t\t\tif (/^($sha1)\\s+/) {\n+\t\t\t\tmy $c = $1;\n+\t\t\t\tif (@tmp) {\n+\t\t\t\t\t@{$comments->{$c}} = @tmp;\n+\t\t\t\t\t@tmp = ();\n+\t\t\t\t}\n+\t\t\t\tforeach my $p (split /\\s+/, $_) {\n+\t\t\t\t\t$grafts->{$c}->{$p} = 1;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tpush @tmp, $_;\n+\t\t\t}\n+\t\t}\n+\t\tclose $fh or croak $!;\n+\t\t@{$comments->{'END'}} = @tmp if @tmp;\n+\t}\n+\treturn ($grafts, $comments);\n+}\n+\n+sub write_grafts {\n+\tmy ($grafts, $comments, $gr_file) = @_;\n+\n+\topen my $fh, '>', $gr_file or croak $!;\n+\tforeach my $c (sort keys %$grafts) {\n+\t\tif ($comments->{$c}) {\n+\t\t\tprint $fh $_ foreach @{$comments->{$c}};\n+\t\t}\n+\t\tmy $p = $grafts->{$c};\n+\t\tdelete $p->{$c}; # commits are not self-reproducing...\n+\t\tmy $pid = open my $ch, '-|';\n+\t\tdefined $pid or croak $!;\n+\t\tif (!$pid) {\n+\t\t\texec(qw/git-cat-file commit/, $c) or croak $!;\n+\t\t}\n+\t\twhile (<$ch>) {\n+\t\t\tif (/^parent ([a-f\\d]{40})/) {\n+\t\t\t\t$p->{$1} = 1;\n+\t\t\t} else {\n+\t\t\t\tlast unless /^\\S/i;\n+\t\t\t}\n+\t\t}\n+\t\tclose $ch; # breaking the pipe\n+\t\tprint $fh $c, ' ', join(' ', sort keys %$p),\"\\n\";\n+\t}\n+\tif ($comments->{'END'}) {\n+\t\tprint $fh $_ foreach @{$comments->{'END'}};\n+\t}\n+\tclose $fh or croak $!;\n+}\n+\n+sub read_url_paths {\n+\tmy $l_map = {};\n+\tgit_svn_each(sub { my $x = shift;\n+\t\t\tmy $u = file_to_s(\"$GIT_DIR/svn/$x/info/repo_url\");\n+\t\t\tmy $p = file_to_s(\"$GIT_DIR/svn/$x/info/repo_path\");\n+\t\t\t# we hate trailing slashes\n+\t\t\tif ($u =~ s#(?:^\\/+|\\/+$)##g) {\n+\t\t\t\ts_to_file($u,\"$GIT_DIR/svn/$x/info/repo_url\");\n+\t\t\t}\n+\t\t\tif ($p =~ s#(?:^\\/+|\\/+$)##g) {\n+\t\t\t\ts_to_file($p,\"$GIT_DIR/svn/$x/info/repo_path\");\n+\t\t\t}\n+\t\t\t$l_map->{$u}->{$p} = $x;\n+\t\t\t});\n+\treturn $l_map;\n+}\n+\n __END__\n \n Data structures:\n-- \n1.4.0\n"},{"id":"21734","messageId":"11502217493878-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 10/13] git-svn: add UTF-8 message test","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:12Z","receivedAt":"2006-06-13T18:02:12Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Signed-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/t/t0000-contrib-git-svn.sh |   13 +++++++++++++\n 1 files changed, 13 insertions(+), 0 deletions(-)\n\ndiff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh\nindex a07fbad..0c6ff20 100644\n--- a/contrib/git-svn/t/t0000-contrib-git-svn.sh\n+++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh\n@@ -4,6 +4,7 @@ # Copyright (c) 2006 Eric Wong\n #\n \n test_description='git-svn tests'\n+GIT_SVN_LC_ALL=$LC_ALL\n . ./lib-git-svn.sh\n \n mkdir import\n@@ -163,6 +164,18 @@ test_expect_success \"$name\" \\\n      diff -u help $SVN_TREE/exec-2.sh\"\n \n \n+if test -n \"$GIT_SVN_LC_ALL\" && echo $GIT_SVN_LC_ALL | grep -q '\\.UTF-8$'\n+then\n+\tname=\"commit with UTF-8 message: locale: $GIT_SVN_LC_ALL\"\n+\techo '# hello' >> exec-2.sh\n+\tgit update-index exec-2.sh\n+\tgit commit -m 'Ã©Ã¯â'\n+\texport LC_ALL=\"$GIT_SVN_LC_ALL\"\n+\ttest_expect_success \"$name\" \"git-svn commit HEAD\"\n+\tunset LC_ALL\n+else\n+\techo \"UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)\"\n+fi\n \n name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'\n GIT_SVN_ID=alt\n-- \n1.4.0\n"},{"id":"21735","messageId":"11502217502740-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 11/13] git-svn: add 'log' command, a facsimile of basic `svn log'","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:13Z","receivedAt":"2006-06-13T18:02:13Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"This quick feature should make it easy to look up svn log\nmessages when svn users refer to -r/--revision numbers.\n\nThe following features from `svn log' are supported:\n\n  --revision=<n>[:<n>] - is supported, non-numeric args are not:\n\t\t\t HEAD, NEXT, BASE, PREV, etc ...\n  -v/--verbose         - just maps to --raw (in git log), so\n\t\t\t it's completely incompatible with\n\t\t\t the --verbose output in svn log\n  --limit=<n>          - is NOT the same as --max-count,\n\t\t\t doesn't count merged/excluded commits\n  --incremental        - supported (trivial :P)\n\nNew features:\n\n  --show-commit        - shows the git commit sha1, as well\n  --oneline            - our version of --pretty=oneline\n\nAny other arguments are passed directly to `git log'\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl |  260 +++++++++++++++++++++++++++++++++++++++---\n 1 files changed, 243 insertions(+), 17 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex d5c7e47..03416ae 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -15,6 +15,7 @@ use Cwd qw/abs_path/;\n $ENV{GIT_DIR} = $GIT_DIR;\n \n my $LC_ALL = $ENV{LC_ALL};\n+my $TZ = $ENV{TZ};\n # make sure the svn binary gives consistent output between locales and TZs:\n $ENV{TZ} = 'UTC';\n $ENV{LC_ALL} = 'C';\n@@ -27,7 +28,7 @@ use Carp qw/croak/;\n use IO::File qw//;\n use File::Basename qw/dirname basename/;\n use File::Path qw/mkpath/;\n-use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;\n+use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;\n use File::Spec qw//;\n use POSIX qw/strftime/;\n my $sha1 = qr/[a-f\\d]{40}/;\n@@ -36,8 +37,9 @@ my ($_revision,$_stdin,$_no_ignore_ext,$\n \t$_find_copies_harder, $_l, $_cp_similarity,\n \t$_repack, $_repack_nr, $_repack_flags,\n \t$_template, $_shared, $_no_default_regex, $_no_graft_copy,\n+\t$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,\n \t$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);\n-my (@_branch_from, %tree_map, %users);\n+my (@_branch_from, %tree_map, %users, %rusers);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n my @repo_path_split_cache;\n \n@@ -87,6 +89,15 @@ my %cmd = (\n \t'multi-fetch' => [ \\&multi_fetch,\n \t\t\t'Fetch multiple trees (like git-svnimport)',\n \t\t\t\\%fc_opts ],\n+\t'log' => [ \\&show_log, 'Show commit logs',\n+\t\t\t{ 'limit=i' => \\$_limit,\n+\t\t\t  'revision|r=s' => \\$_revision,\n+\t\t\t  'verbose|v' => \\$_verbose,\n+\t\t\t  'incremental' => \\$_incremental,\n+\t\t\t  'oneline' => \\$_oneline,\n+\t\t\t  'show-commit' => \\$_show_commit,\n+\t\t\t  'authors-file|A=s' => \\$_authors,\n+\t\t\t} ],\n );\n \n my $cmd;\n@@ -101,9 +112,10 @@ for (my $i = 0; $i < @ARGV; $i++) {\n my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);\n \n read_repo_config(\\%opts);\n-GetOptions(%opts, 'help|H|h' => \\$_help,\n-\t\t'version|V' => \\$_version,\n-\t\t'id|i=s' => \\$GIT_SVN) or exit 1;\n+my $rv = GetOptions(%opts, 'help|H|h' => \\$_help,\n+\t\t\t\t'version|V' => \\$_version,\n+\t\t\t\t'id|i=s' => \\$GIT_SVN);\n+exit 1 if (!$rv && $cmd ne 'log');\n \n set_default_vals();\n usage(0) if $_help;\n@@ -173,18 +185,10 @@ sub rebuild {\n \t\tcroak \"Non-SHA1: $c\\n\" unless $c =~ /^$sha1$/o;\n \t\tmy @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);\n \t\tnext if (!@commit); # skip merges\n-\t\tmy $id = $commit[$#commit];\n-\t\tmy ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\\s(\\S+?)\\@(\\d+)\n-\t\t\t\t\t\t\\s([a-f\\d\\-]+)$/x);\n-\t\tif (!$rev || !$uuid || !$url) {\n-\t\t\t# some of the original repositories I made had\n-\t\t\t# indentifiers like this:\n-\t\t\t($rev, $uuid) = ($id =~/^git-svn-id:\\s(\\d+)\n-\t\t\t\t\t\t\t\\@([a-f\\d\\-]+)/x);\n-\t\t\tif (!$rev || !$uuid) {\n-\t\t\t\tcroak \"Unable to extract revision or UUID from \",\n-\t\t\t\t\t\"$c, $id\\n\";\n-\t\t\t}\n+\t\tmy ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);\n+\t\tif (!$rev || !$uuid) {\n+\t\t\tcroak \"Unable to extract revision or UUID from \",\n+\t\t\t\t\"$c, $commit[$#commit]\\n\";\n \t\t}\n \n \t\t# if we merged or otherwise started elsewhere, this is\n@@ -448,6 +452,81 @@ sub multi_fetch {\n \trec_fetch('', \"$GIT_DIR/svn\", @_);\n }\n \n+sub show_log {\n+\tmy (@args) = @_;\n+\tmy ($r_min, $r_max);\n+\tmy $r_last = -1; # prevent dupes\n+\trload_authors() if $_authors;\n+\tif (defined $TZ) {\n+\t\t$ENV{TZ} = $TZ;\n+\t} else {\n+\t\tdelete $ENV{TZ};\n+\t}\n+\tif (defined $_revision) {\n+\t\tif ($_revision =~ /^(\\d+):(\\d+)$/) {\n+\t\t\t($r_min, $r_max) = ($1, $2);\n+\t\t} elsif ($_revision =~ /^\\d+$/) {\n+\t\t\t$r_min = $r_max = $_revision;\n+\t\t} else {\n+\t\t\tprint STDERR \"-r$_revision is not supported, use \",\n+\t\t\t\t\"standard \\'git log\\' arguments instead\\n\";\n+\t\t\texit 1;\n+\t\t}\n+\t}\n+\n+\tmy $pid = open(my $log,'-|');\n+\tdefined $pid or croak $!;\n+\tif (!$pid) {\n+\t\tmy @rl = (qw/git-log --abbrev-commit --pretty=raw\n+\t\t\t\t--default/, \"remotes/$GIT_SVN\");\n+\t\tpush @rl, '--raw' if $_verbose;\n+\t\texec(@rl, @args) or croak $!;\n+\t}\n+\tsetup_pager();\n+\tmy (@k, $c, $d);\n+\twhile (<$log>) {\n+\t\tif (/^commit ($sha1_short)/o) {\n+\t\t\tmy $cmt = $1;\n+\t\t\tif ($c && defined $c->{r} && $c->{r} != $r_last) {\n+\t\t\t\t$r_last = $c->{r};\n+\t\t\t\tprocess_commit($c, $r_min, $r_max, \\@k) or\n+\t\t\t\t\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\t$d = undef;\n+\t\t\t$c = { c => $cmt };\n+\t\t} elsif (/^author (.+) (\\d+) ([\\-\\+]?\\d+)$/) {\n+\t\t\tget_author_info($c, $1, $2, $3);\n+\t\t} elsif (/^(?:tree|parent|committer) /) {\n+\t\t\t# ignore\n+\t\t} elsif (/^:\\d{6} \\d{6} $sha1_short/o) {\n+\t\t\tpush @{$c->{raw}}, $_;\n+\t\t} elsif (/^diff /) {\n+\t\t\t$d = 1;\n+\t\t\tpush @{$c->{diff}}, $_;\n+\t\t} elsif ($d) {\n+\t\t\tpush @{$c->{diff}}, $_;\n+\t\t} elsif (/^    (git-svn-id:.+)$/) {\n+\t\t\tmy ($url, $rev, $uuid) = extract_metadata($1);\n+\t\t\t$c->{r} = $rev;\n+\t\t} elsif (s/^    //) {\n+\t\t\tpush @{$c->{l}}, $_;\n+\t\t}\n+\t}\n+\tif ($c && defined $c->{r} && $c->{r} != $r_last) {\n+\t\t$r_last = $c->{r};\n+\t\tprocess_commit($c, $r_min, $r_max, \\@k);\n+\t}\n+\tif (@k) {\n+\t\tmy $swap = $r_max;\n+\t\t$r_max = $r_min;\n+\t\t$r_min = $swap;\n+\t\tprocess_commit($_, $r_min, $r_max) foreach reverse @k;\n+\t}\n+out:\n+\tclose $log;\n+\tprint '-' x72,\"\\n\" unless $_incremental || $_oneline;\n+}\n+\n ########################### utility functions #########################\n \n sub rec_fetch {\n@@ -1638,6 +1717,17 @@ sub load_authors {\n \tclose $authors or croak $!;\n }\n \n+sub rload_authors {\n+\topen my $authors, '<', $_authors or die \"Can't open $_authors $!\\n\";\n+\twhile (<$authors>) {\n+\t\tchomp;\n+\t\tnext unless /^(\\S+?)\\s*=\\s*(.+?)\\s*<(.+)>\\s*$/;\n+\t\tmy ($user, $name, $email) = ($1, $2, $3);\n+\t\t$rusers{\"$name <$email>\"} = $user;\n+\t}\n+\tclose $authors or croak $!;\n+}\n+\n sub svn_propget_base {\n \tmy ($p, $f) = @_;\n \t$f .= '@BASE' if $_svn_pg_peg_revs;\n@@ -1803,6 +1893,142 @@ sub read_url_paths {\n \treturn $l_map;\n }\n \n+sub extract_metadata {\n+\tmy $id = shift;\n+\tmy ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\\s(\\S+?)\\@(\\d+)\n+\t\t\t\t\t\t\t\\s([a-f\\d\\-]+)$/x);\n+\tif (!$rev || !$uuid || !$url) {\n+\t\t# some of the original repositories I made had\n+\t\t# indentifiers like this:\n+\t\t($rev, $uuid) = ($id =~/^git-svn-id:\\s(\\d+)\\@([a-f\\d\\-]+)/);\n+\t}\n+\treturn ($url, $rev, $uuid);\n+}\n+\n+sub tz_to_s_offset {\n+\tmy ($tz) = @_;\n+\t$tz =~ s/(\\d\\d)$//;\n+\treturn ($1 * 60) + ($tz * 3600);\n+}\n+\n+sub setup_pager { # translated to Perl from pager.c\n+\treturn unless (-t *STDOUT);\n+\tmy $pager = $ENV{PAGER};\n+\tif (!defined $pager) {\n+\t\t$pager = 'less';\n+\t} elsif (length $pager == 0 || $pager eq 'cat') {\n+\t\treturn;\n+\t}\n+\tpipe my $rfd, my $wfd or return;\n+\tdefined(my $pid = fork) or croak $!;\n+\tif (!$pid) {\n+\t\topen STDOUT, '>&', $wfd or croak $!;\n+\t\treturn;\n+\t}\n+\topen STDIN, '<&', $rfd or croak $!;\n+\t$ENV{LESS} ||= '-S';\n+\texec $pager or croak \"Can't run pager: $!\\n\";;\n+}\n+\n+sub get_author_info {\n+\tmy ($dest, $author, $t, $tz) = @_;\n+\t$author =~ s/(?:^\\s*|\\s*$)//g;\n+\tmy $_a;\n+\tif ($_authors) {\n+\t\t$_a = $rusers{$author} || undef;\n+\t}\n+\tif (!$_a) {\n+\t\t($_a) = ($author =~ /<([^>]+)\\@[^>]+>$/);\n+\t}\n+\t$dest->{t} = $t;\n+\t$dest->{tz} = $tz;\n+\t$dest->{a} = $_a;\n+\t# Date::Parse isn't in the standard Perl distro :(\n+\tif ($tz =~ s/^\\+//) {\n+\t\t$t += tz_to_s_offset($tz);\n+\t} elsif ($tz =~ s/^\\-//) {\n+\t\t$t -= tz_to_s_offset($tz);\n+\t}\n+\t$dest->{t_utc} = $t;\n+}\n+\n+sub process_commit {\n+\tmy ($c, $r_min, $r_max, $defer) = @_;\n+\tif (defined $r_min && defined $r_max) {\n+\t\tif ($r_min == $c->{r} && $r_min == $r_max) {\n+\t\t\tshow_commit($c);\n+\t\t\treturn 0;\n+\t\t}\n+\t\treturn 1 if $r_min == $r_max;\n+\t\tif ($r_min < $r_max) {\n+\t\t\t# we need to reverse the print order\n+\t\t\treturn 0 if (defined $_limit && --$_limit < 0);\n+\t\t\tpush @$defer, $c;\n+\t\t\treturn 1;\n+\t\t}\n+\t\tif ($r_min != $r_max) {\n+\t\t\treturn 1 if ($r_min < $c->{r});\n+\t\t\treturn 1 if ($r_max > $c->{r});\n+\t\t}\n+\t}\n+\treturn 0 if (defined $_limit && --$_limit < 0);\n+\tshow_commit($c);\n+\treturn 1;\n+}\n+\n+sub show_commit {\n+\tmy $c = shift;\n+\tif ($_oneline) {\n+\t\tmy $x = \"\\n\";\n+\t\tif (my $l = $c->{l}) {\n+\t\t\twhile ($l->[0] =~ /^\\s*$/) { shift @$l }\n+\t\t\t$x = $l->[0];\n+\t\t}\n+\t\t$_l_fmt ||= 'A' . length($c->{r});\n+\t\tprint 'r',pack($_l_fmt, $c->{r}),' | ';\n+\t\tprint \"$c->{c} | \" if $_show_commit;\n+\t\tprint $x;\n+\t} else {\n+\t\tshow_commit_normal($c);\n+\t}\n+}\n+\n+sub show_commit_normal {\n+\tmy ($c) = @_;\n+\tprint '-' x72, \"\\nr$c->{r} | \";\n+\tprint \"$c->{c} | \" if $_show_commit;\n+\tprint \"$c->{a} | \", strftime(\"%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)\",\n+\t\t\t\t localtime($c->{t_utc})), ' | ';\n+\tmy $nr_line = 0;\n+\n+\tif (my $l = $c->{l}) {\n+\t\twhile ($l->[$#$l] eq \"\\n\" && $l->[($#$l - 1)] eq \"\\n\") {\n+\t\t\tpop @$l;\n+\t\t}\n+\t\t$nr_line = scalar @$l;\n+\t\tif (!$nr_line) {\n+\t\t\tprint \"1 line\\n\\n\\n\";\n+\t\t} else {\n+\t\t\tif ($nr_line == 1) {\n+\t\t\t\t$nr_line = '1 line';\n+\t\t\t} else {\n+\t\t\t\t$nr_line .= ' lines';\n+\t\t\t}\n+\t\t\tprint $nr_line, \"\\n\\n\";\n+\t\t\tprint $_ foreach @$l;\n+\t\t}\n+\t} else {\n+\t\tprint \"1 line\\n\\n\";\n+\n+\t}\n+\tforeach my $x (qw/raw diff/) {\n+\t\tif ($c->{$x}) {\n+\t\t\tprint \"\\n\";\n+\t\t\tprint $_ foreach @{$c->{$x}}\n+\t\t}\n+\t}\n+}\n+\n __END__\n \n Data structures:\n-- \n1.4.0\n"},{"id":"21732","messageId":"115022175180-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 12/13] git-svn: add support for Perl SVN::* libraries","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:14Z","receivedAt":"2006-06-13T18:02:14Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"This means we no longer have to deal with having bloated SVN\nworking copies around and we get a nice performance increase as\nwell because we don't have to exec the SVN binary and start a\nnew server connection each time.\n\nOf course we have to manually manage memory with SVN::Pool\nwhenever we can, and hack around cases where SVN just eats\nmemory despite pools (I blame Perl, too).  I would like to\nkeep memory usage as stable as possible during long fetch/commit\nprocesses since I still use computers with only 256-512M RAM.\n\ncommit should always be faster with the SVN library code.  The\nSVN::Delta interface is leaky (or I'm not using it with pools\ncorrectly), so I'm forking on every commit, but that doesn't\nseem to hurt performance too much (at least on normal Unix/Linux\nsystems where fork() is pretty cheap).\n\nfetch should be faster in most common cases, but probably not all.\nfetches will be faster where client/server delta generation is\nthe bottleneck and not bandwidth.  Of course, full-files are\ngenerated server-side via deltas, too.  Full files are always\ntransferred when they're updated, just like git-svnimport and\nunlike command-line svn.  I'm also hacking around memory leaks\n(see comments) here by using some more forks.\n\nI've tested fetch with http://, https://, file://, and svn://\nrepositories, so we should be reasonably covered in terms of\nerror handling for fetching.\n\nOf course, we'll keep plain command-line svn compatibility as a\nfallback for people running SVN 1.1 (I'm looking into library\nsupport for 1.1.x SVN, too).  If you want to force command-line\nSVN usage, set GIT_SVN_NO_LIB=1 in your environment.\n\nWe also require two simultaneous connections (just like\ngit-svnimport), but this shouldn't be a problem for most\nservers.\n\nLess important commands:\n\nshow-ignore is slower because it requires repository\naccess, but -r/--revision <num> can be specified.\n\ngraft-branches may use more memory, but it's a\nshort-term process and is funky-filename-safe.\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/git-svn.perl               | 1068 +++++++++++++++++++++++++---\n contrib/git-svn/t/lib-git-svn.sh           |    2 \n contrib/git-svn/t/t0000-contrib-git-svn.sh |   15 \n 3 files changed, 974 insertions(+), 111 deletions(-)\n\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 03416ae..9618c8b 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -31,6 +31,10 @@ use File::Path qw/mkpath/;\n use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;\n use File::Spec qw//;\n use POSIX qw/strftime/;\n+\n+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);\n+$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};\n+libsvn_load();\n my $sha1 = qr/[a-f\\d]{40}/;\n my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n@@ -74,7 +78,8 @@ my %cmd = (\n \t\t\t\t'copy-similarity|C=i'=> \\$_cp_similarity,\n \t\t\t\t%fc_opts,\n \t\t\t} ],\n-\t'show-ignore' => [ \\&show_ignore, \"Show svn:ignore listings\", { } ],\n+\t'show-ignore' => [ \\&show_ignore, \"Show svn:ignore listings\",\n+\t\t\t{ 'revision|r=i' => \\$_revision } ],\n \trebuild => [ \\&rebuild, \"Rebuild git-svn metadata (after git clone)\",\n \t\t\t{ 'no-ignore-externals' => \\$_no_ignore_ext,\n \t\t\t  'upgrade' => \\$_upgrade } ],\n@@ -211,6 +216,8 @@ sub rebuild {\n \t\t$newest_rev = $rev if ($rev > $newest_rev);\n \t}\n \tclose $rev_list or croak $?;\n+\n+\tgoto out if $_use_lib;\n \tif (!chdir $SVN_WC) {\n \t\tsvn_cmd_checkout($SVN_URL, $latest, $SVN_WC);\n \t\tchdir $SVN_WC or croak $!;\n@@ -228,7 +235,7 @@ sub rebuild {\n \t}\n \twaitpid $pid, 0;\n \tcroak $? if $?;\n-\n+out:\n \tif ($_upgrade) {\n \t\tprint STDERR <<\"\";\n Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it\n@@ -251,9 +258,18 @@ sub init {\n }\n \n sub fetch {\n-\tmy (@parents) = @_;\n \tcheck_upgrade_needed();\n \t$SVN_URL ||= file_to_s(\"$GIT_SVN_DIR/info/url\");\n+\tmy $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);\n+\tif ($ret->{commit} && quiet_run(qw(git-rev-parse --verify\n+\t\t\t\t\t\trefs/heads/master^0))) {\n+\t\tsys(qw(git-update-ref refs/heads/master),$ret->{commit});\n+\t}\n+\treturn $ret;\n+}\n+\n+sub fetch_cmd {\n+\tmy (@parents) = @_;\n \tmy @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);\n \tunless ($_revision) {\n \t\t$_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';\n@@ -301,13 +317,91 @@ sub fetch {\n \t\t$last_commit = git_commit($log_msg, $last_commit, @parents);\n \t\t$last = $log_msg;\n \t}\n-\tunless (-e \"$GIT_DIR/refs/heads/master\") {\n-\t\tsys(qw(git-update-ref refs/heads/master),$last_commit);\n-\t}\n \tclose $svn_log->{fh};\n+\t$last->{commit} = $last_commit;\n \treturn $last;\n }\n \n+sub fetch_lib {\n+\tmy (@parents) = @_;\n+\t$SVN_URL ||= file_to_s(\"$GIT_SVN_DIR/info/url\");\n+\tmy $repo;\n+\t($repo, $SVN_PATH) = repo_path_split($SVN_URL);\n+\t$SVN_LOG ||= libsvn_connect($repo);\n+\t$SVN ||= libsvn_connect($repo);\n+\tmy ($last_rev, $last_commit) = svn_grab_base_rev();\n+\tmy ($base, $head) = libsvn_parse_revision($last_rev);\n+\tif ($base > $head) {\n+\t\treturn { revision => $last_rev, commit => $last_commit }\n+\t}\n+\tmy $index = set_index($GIT_SVN_INDEX);\n+\n+\t# limit ourselves and also fork() since get_log won't release memory\n+\t# after processing a revision and SVN stuff seems to leak\n+\tmy $inc = 1000;\n+\tmy ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);\n+\tread_uuid();\n+\tif (defined $last_commit) {\n+\t\tunless (-e $GIT_SVN_INDEX) {\n+\t\t\tsys(qw/git-read-tree/, $last_commit);\n+\t\t}\n+\t\tchomp (my $x = `git-write-tree`);\n+\t\tmy ($y) = (`git-cat-file commit $last_commit`\n+\t\t\t\t\t\t\t=~ /^tree ($sha1)/m);\n+\t\tif ($y ne $x) {\n+\t\t\tunlink $GIT_SVN_INDEX or croak $!;\n+\t\t\tsys(qw/git-read-tree/, $last_commit);\n+\t\t}\n+\t\tchomp ($x = `git-write-tree`);\n+\t\tif ($y ne $x) {\n+\t\t\tprint STDERR \"trees ($last_commit) $y != $x\\n\",\n+\t\t\t\t \"Something is seriously wrong...\\n\";\n+\t\t}\n+\t}\n+\twhile (1) {\n+\t\t# fork, because using SVN::Pool with get_log() still doesn't\n+\t\t# seem to help enough to keep memory usage down.\n+\t\tdefined(my $pid = fork) or croak $!;\n+\t\tif (!$pid) {\n+\t\t\t$SVN::Error::handler = \\&libsvn_skip_unknown_revs;\n+\t\t\tprint \"Fetching revisions $min .. $max\\n\";\n+\n+\t\t\t# Yes I'm perfectly aware that the fourth argument\n+\t\t\t# below is the limit revisions number.  Unfortunately\n+\t\t\t# performance sucks with it enabled, so it's much\n+\t\t\t# faster to fetch revision ranges instead of relying\n+\t\t\t# on the limiter.\n+\t\t\t$SVN_LOG->get_log( '/'.$SVN_PATH, $min, $max, 0, 1, 1,\n+\t\t\t\tsub {\n+\t\t\t\t\tmy $log_msg;\n+\t\t\t\t\tif ($last_commit) {\n+\t\t\t\t\t\t$log_msg = libsvn_fetch(\n+\t\t\t\t\t\t\t$last_commit, @_);\n+\t\t\t\t\t\t$last_commit = git_commit(\n+\t\t\t\t\t\t\t$log_msg,\n+\t\t\t\t\t\t\t$last_commit,\n+\t\t\t\t\t\t\t@parents);\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\t$log_msg = libsvn_new_tree(@_);\n+\t\t\t\t\t\t$last_commit = git_commit(\n+\t\t\t\t\t\t\t$log_msg, @parents);\n+\t\t\t\t\t}\n+\t\t\t\t});\n+\t\t\t$SVN::Error::handler = sub { 'quiet warnings' };\n+\t\t\texit 0;\n+\t\t}\n+\t\twaitpid $pid, 0;\n+\t\tcroak $? if $?;\n+\t\t($last_rev, $last_commit) = svn_grab_base_rev();\n+\t\tlast if ($max >= $head);\n+\t\t$min = $max + 1;\n+\t\t$max += $inc;\n+\t\t$max = $head if ($max > $head);\n+\t}\n+\trestore_index($index);\n+\treturn { revision => $last_rev, commit => $last_commit };\n+}\n+\n sub commit {\n \tmy (@commits) = @_;\n \tcheck_upgrade_needed();\n@@ -332,6 +426,12 @@ sub commit {\n \t\t}\n \t}\n \tchomp @revs;\n+\t$_use_lib ? commit_lib(@revs) : commit_cmd(@revs);\n+\tprint \"Done committing \",scalar @revs,\" revisions to SVN\\n\";\n+}\n+\n+sub commit_cmd {\n+\tmy (@revs) = @_;\n \n \tchdir $SVN_WC or croak \"Unable to chdir $SVN_WC: $!\\n\";\n \tmy $info = svn_info('.');\n@@ -353,17 +453,95 @@ sub commit {\n \t\t}\n \t\t$svn_current_rev = svn_commit_tree($svn_current_rev, $c);\n \t}\n-\tprint \"Done committing \",scalar @revs,\" revisions to SVN\\n\";\n }\n \n-sub show_ignore {\n-\trequire File::Find or die $!;\n-\tmy $exclude_file = \"$GIT_DIR/info/exclude\";\n-\topen my $fh, '<', $exclude_file or croak $!;\n-\tchomp(my @excludes = (<$fh>));\n-\tclose $fh or croak $!;\n+sub commit_lib {\n+\tmy (@revs) = @_;\n+\tmy ($r_last, $cmt_last) = svn_grab_base_rev();\n+\tdefined $r_last or die \"Must have an existing revision to commit\\n\";\n+\tmy $fetched = fetch_lib();\n+\tif ($r_last != $fetched->{revision}) {\n+\t\tprint STDERR \"There are new revisions that were fetched \",\n+\t\t\t\t\"and need to be merged (or acknowledged) \",\n+\t\t\t\t\"before committing.\\n\",\n+\t\t\t\t\"last rev: $r_last\\n\",\n+\t\t\t\t\" current: $fetched->{revision}\\n\";\n+\t\texit 1;\n+\t}\n+\tread_uuid();\n+\tmy @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();\n+\tmy $commit_msg = \"$GIT_SVN_DIR/.svn-commit.tmp.$$\";\n+\n+\tforeach my $c (@revs) {\n+\t\t# fork for each commit because there's a memory leak I\n+\t\t# can't track down... (it's probably in the SVN code)\n+\t\tdefined(my $pid = open my $fh, '-|') or croak $!;\n+\t\tif (!$pid) {\n+\t\t\tif (defined $LC_ALL) {\n+\t\t\t\t$ENV{LC_ALL} = $LC_ALL;\n+\t\t\t} else {\n+\t\t\t\tdelete $ENV{LC_ALL};\n+\t\t\t}\n+\t\t\tmy $log_msg = get_commit_message($c, $commit_msg);\n+\t\t\tmy $ed = SVN::Git::Editor->new(\n+\t\t\t\t\t{\tr => $r_last,\n+\t\t\t\t\t\tra => $SVN,\n+\t\t\t\t\t\tc => $c,\n+\t\t\t\t\t\tsvn_path => $SVN_PATH\n+\t\t\t\t\t},\n+\t\t\t\t\t$SVN->get_commit_editor(\n+\t\t\t\t\t\t$log_msg->{msg},\n+\t\t\t\t\t\tsub {\n+\t\t\t\t\t\t\tlibsvn_commit_cb(\n+\t\t\t\t\t\t\t\t@_, $c,\n+\t\t\t\t\t\t\t\t$log_msg->{msg},\n+\t\t\t\t\t\t\t\t$r_last,\n+\t\t\t\t\t\t\t\t$cmt_last)\n+\t\t\t\t\t\t},\n+\t\t\t\t\t\t@lock)\n+\t\t\t\t\t);\n+\t\t\tmy $mods = libsvn_checkout_tree($r_last, $c, $ed);\n+\t\t\tif (@$mods == 0) {\n+\t\t\t\tprint \"No changes\\nr$r_last = $cmt_last\\n\";\n+\t\t\t\t$ed->abort_edit;\n+\t\t\t} else {\n+\t\t\t\t$ed->close_edit;\n+\t\t\t}\n+\t\t\texit 0;\n+\t\t}\n+\t\tmy ($r_new, $cmt_new, $no);\n+\t\twhile (<$fh>) {\n+\t\t\tprint $_;\n+\t\t\tchomp;\n+\t\t\tif (/^r(\\d+) = ($sha1)$/o) {\n+\t\t\t\t($r_new, $cmt_new) = ($1, $2);\n+\t\t\t} elsif ($_ eq 'No changes') {\n+\t\t\t\t$no = 1;\n+\t\t\t}\n+\t\t}\n+\t\tclose $fh or croak $!;\n+\t\tif (! defined $r_new && ! defined $cmt_new) {\n+\t\t\tunless ($no) {\n+\t\t\t\tdie \"Failed to parse revision information\\n\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\t($r_last, $cmt_last) = ($r_new, $cmt_new);\n+\t\t}\n+\t}\n+\tunlink $commit_msg;\n+}\n \n+sub show_ignore {\n \t$SVN_URL ||= file_to_s(\"$GIT_SVN_DIR/info/url\");\n+\t$_use_lib ? show_ignore_lib() : show_ignore_cmd();\n+}\n+\n+sub show_ignore_cmd {\n+\trequire File::Find or die $!;\n+\tif (defined $_revision) {\n+\t\tdie \"-r/--revision option doesn't work unless the Perl SVN \",\n+\t\t\t\"libraries are used\\n\";\n+\t}\n \tchdir $SVN_WC or croak $!;\n \tmy %ign;\n \tFile::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d \"$_/.svn\"){\n@@ -380,6 +558,14 @@ sub show_ignore {\n \t}\n }\n \n+sub show_ignore_lib {\n+\tmy $repo;\n+\t($repo, $SVN_PATH) = repo_path_split($SVN_URL);\n+\t$SVN ||= libsvn_connect($repo);\n+\tmy $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;\n+\tlibsvn_traverse_ignore(\\*STDOUT, $SVN_PATH, $r);\n+}\n+\n sub graft_branches {\n \tmy $gr_file = \"$GIT_DIR/info/grafts\";\n \tmy ($grafts, $comments) = read_grafts($gr_file);\n@@ -403,7 +589,13 @@ sub graft_branches {\n \t\t\t\tgraft_merge_msg($grafts,$l_map,$u,$p);\n \t\t\t}\n \t\t}\n-\t\tgraft_file_copy($grafts,$l_map,$u) unless $_no_graft_copy;\n+\t\tunless ($_no_graft_copy) {\n+\t\t\tif ($_use_lib) {\n+\t\t\t\tgraft_file_copy_lib($grafts,$l_map,$u);\n+\t\t\t} else {\n+\t\t\t\tgraft_file_copy_cmd($grafts,$l_map,$u);\n+\t\t\t}\n+\t\t}\n \t}\n \n \twrite_grafts($grafts, $comments, $gr_file);\n@@ -574,7 +766,8 @@ sub complete_url_ls_init {\n \t\t}\n \t\t$var = $url . $var;\n \t}\n-\tchomp(my @ls = safe_qx(qw/svn ls --non-interactive/, $var));\n+\tchomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)\n+\t\t\t\t: safe_qx(qw/svn ls --non-interactive/, $var));\n \tmy $old = $GIT_SVN;\n \tdefined(my $pid = fork) or croak $!;\n \tif (!$pid) {\n@@ -617,7 +810,7 @@ sub common_prefix {\n }\n \n # this isn't funky-filename safe, but good enough for now...\n-sub graft_file_copy {\n+sub graft_file_copy_cmd {\n \tmy ($grafts, $l_map, $u) = @_;\n \tmy $paths = $l_map->{$u};\n \tmy $pfx = common_prefix([keys %$paths]);\n@@ -625,7 +818,9 @@ sub graft_file_copy {\n \tmy $pid = open my $fh, '-|';\n \tdefined $pid or croak $!;\n \tunless ($pid) {\n-\t\texec(qw/svn log -v/, $u.$pfx) or croak $!;\n+\t\tmy @exec = qw/svn log -v/;\n+\t\tpush @exec, \"-r$_revision\" if defined $_revision;\n+\t\texec @exec, $u.$pfx or croak $!;\n \t}\n \tmy ($r, $mp) = (undef, undef);\n \twhile (<$fh>) {\n@@ -637,42 +832,40 @@ sub graft_file_copy {\n \t\t} elsif (/^Changed paths:/) {\n \t\t\t$mp = 1;\n \t\t} elsif ($mp && m#^   [AR] /(\\S.*?) \\(from /(\\S+?):(\\d+)\\)$#) {\n-\t\t\tmy $dbg = \"r$r | $_\";\n \t\t\tmy ($p1, $p0, $r0) = ($1, $2, $3);\n-\t\t\tmy $c;\n-\t\t\tforeach my $x (keys %$paths) {\n-\t\t\t\tnext unless ($p1 =~ /^\\Q$x\\E/);\n-\t\t\t\tmy $i = $paths->{$x};\n-\t\t\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r\";\n-\t\t\t\tunless (-r $f) {\n-\t\t\t\t\tprint STDERR \"r$r of $i not imported,\",\n-\t\t\t\t\t\t\t\t\" $dbg\\n\";\n-\t\t\t\t\tnext;\n-\t\t\t\t}\n-\t\t\t\t$c = file_to_s($f);\n-\t\t\t}\n+\t\t\tmy $c = find_graft_path_commit($paths, $p1, $r);\n \t\t\tnext unless $c;\n-\t\t\tforeach my $x (keys %$paths) {\n-\t\t\t\tnext unless ($p0 =~ /^\\Q$x\\E/);\n-\t\t\t\tmy $i = $paths->{$x};\n-\t\t\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r0\";\n-\t\t\t\twhile ($r0 && !-r $f) {\n-\t\t\t\t\t# could be an older revision, too...\n-\t\t\t\t\t$r0--;\n-\t\t\t\t\t$f = \"$GIT_DIR/svn/$i/revs/$r0\";\n-\t\t\t\t}\n-\t\t\t\tunless (-r $f) {\n-\t\t\t\t\tprint STDERR \"r$r0 of $i not imported,\",\n-\t\t\t\t\t\t\t\t\" $dbg\\n\";\n-\t\t\t\t\tnext;\n-\t\t\t\t}\n-\t\t\t\tmy $r1 = file_to_s($f);\n-\t\t\t\t$grafts->{$c}->{$r1} = 1;\n-\t\t\t}\n+\t\t\tfind_graft_path_parents($grafts, $paths, $c, $p0, $r0);\n \t\t}\n \t}\n }\n \n+sub graft_file_copy_lib {\n+\tmy ($grafts, $l_map, $u) = @_;\n+\tmy $tree_paths = $l_map->{$u};\n+\tmy $pfx = common_prefix([keys %$tree_paths]);\n+\tmy ($repo, $path) = repo_path_split($u.$pfx);\n+\t$SVN_LOG ||= libsvn_connect($repo);\n+\t$SVN ||= libsvn_connect($repo);\n+\n+\tmy ($base, $head) = libsvn_parse_revision();\n+\tmy $inc = 1000;\n+\tmy ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);\n+\twhile (1) {\n+\t\tmy $pool = SVN::Pool->new;\n+\t\t$SVN_LOG->get_log( \"/$path\", $min, $max, 0, 1, 1,\n+\t\t\tsub {\n+\t\t\t\tlibsvn_graft_file_copies($grafts, $tree_paths,\n+\t\t\t\t\t\t\t$path, @_);\n+\t\t\t}, $pool);\n+\t\t$pool->clear;\n+\t\tlast if ($max >= $head);\n+\t\t$min = $max + 1;\n+\t\t$max += $inc;\n+\t\t$max = $head if ($max > $head);\n+\t}\n+}\n+\n sub process_merge_msg_matches {\n \tmy ($grafts, $l_map, $u, $p, $c, @matches) = @_;\n \tmy (@strong, @weak);\n@@ -734,9 +927,15 @@ sub graft_merge_msg {\n \n sub read_uuid {\n \treturn if $SVN_UUID;\n-\tmy $info = shift || svn_info('.');\n-\t$SVN_UUID = $info->{'Repository UUID'} or\n+\tif ($_use_lib) {\n+\t\tmy $pool = SVN::Pool->new;\n+\t\t$SVN_UUID = $SVN->get_uuid($pool);\n+\t\t$pool->clear;\n+\t} else {\n+\t\tmy $info = shift || svn_info('.');\n+\t\t$SVN_UUID = $info->{'Repository UUID'} or\n \t\t\t\t\tcroak \"Repository UUID unreadable\\n\";\n+\t}\n \ts_to_file($SVN_UUID,\"$GIT_SVN_DIR/info/uuid\");\n }\n \n@@ -769,9 +968,19 @@ sub repo_path_split {\n \t$path =~ s#^/+##;\n \tmy @paths = split(m#/+#, $path);\n \n-\twhile (quiet_run(qw/svn ls --non-interactive/, $url)) {\n-\t\tmy $n = shift @paths || last;\n-\t\t$url .= \"/$n\";\n+\tif ($_use_lib) {\n+\t\twhile (1) {\n+\t\t\t$SVN = libsvn_connect($url);\n+\t\t\tlast if (defined $SVN &&\n+\t\t\t\tdefined eval { $SVN->get_latest_revnum });\n+\t\t\tmy $n = shift @paths || last;\n+\t\t\t$url .= \"/$n\";\n+\t\t}\n+\t} else {\n+\t\twhile (quiet_run(qw/svn ls --non-interactive/, $url)) {\n+\t\t\tmy $n = shift @paths || last;\n+\t\t\t$url .= \"/$n\";\n+\t\t}\n \t}\n \tpush @repo_path_split_cache, qr/^(\\Q$url\\E)/;\n \t$path = join('/',@paths);\n@@ -797,6 +1006,7 @@ sub setup_git_svn {\n }\n \n sub assert_svn_wc_clean {\n+\treturn if $_use_lib;\n \tmy ($svn_rev) = @_;\n \tcroak \"$svn_rev is not an integer!\\n\" unless ($svn_rev =~ /^\\d+$/);\n \tmy $lcr = svn_info('.')->{'Last Changed Rev'};\n@@ -819,7 +1029,7 @@ sub assert_svn_wc_clean {\n \t}\n }\n \n-sub assert_tree {\n+sub get_tree_from_treeish {\n \tmy ($treeish) = @_;\n \tcroak \"Not a sha1: $treeish\\n\" unless $treeish =~ /^$sha1$/o;\n \tchomp(my $type = `git-cat-file -t $treeish`);\n@@ -836,20 +1046,22 @@ sub assert_tree {\n \t} else {\n \t\tdie \"$treeish is a $type, expected tree, tag or commit\\n\";\n \t}\n+\treturn $expected;\n+}\n+\n+sub assert_tree {\n+\treturn if $_use_lib;\n+\tmy ($treeish) = @_;\n+\tmy $expected = get_tree_from_treeish($treeish);\n \n-\tmy $old_index = $ENV{GIT_INDEX_FILE};\n \tmy $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';\n \tif (-e $tmpindex) {\n \t\tunlink $tmpindex or croak $!;\n \t}\n-\t$ENV{GIT_INDEX_FILE} = $tmpindex;\n+\tmy $old_index = set_index($tmpindex);\n \tindex_changes(1);\n \tchomp(my $tree = `git-write-tree`);\n-\tif ($old_index) {\n-\t\t$ENV{GIT_INDEX_FILE} = $old_index;\n-\t} else {\n-\t\tdelete $ENV{GIT_INDEX_FILE};\n-\t}\n+\trestore_index($old_index);\n \tif ($tree ne $expected) {\n \t\tcroak \"Tree mismatch, Got: $tree, Expected: $expected\\n\";\n \t}\n@@ -987,7 +1199,8 @@ sub precommit_check {\n \t}\n }\n \n-sub svn_checkout_tree {\n+\n+sub get_diff {\n \tmy ($svn_rev, $treeish) = @_;\n \tmy $from = file_to_s(\"$REV_DIR/$svn_rev\");\n \tassert_tree($from);\n@@ -1005,11 +1218,13 @@ sub svn_checkout_tree {\n \t\tpush @diff_tree, \"-l$_l\" if defined $_l;\n \t\texec(@diff_tree, $from, $treeish) or croak $!;\n \t}\n-\tmy $mods = parse_diff_tree($diff_fh);\n-\tunless (@$mods) {\n-\t\t# git can do empty commits, but SVN doesn't allow it...\n-\t\treturn $mods;\n-\t}\n+\treturn parse_diff_tree($diff_fh);\n+}\n+\n+sub svn_checkout_tree {\n+\tmy ($svn_rev, $treeish) = @_;\n+\tmy $mods = get_diff($svn_rev, $treeish);\n+\treturn $mods unless (scalar @$mods);\n \tmy ($rm, $add) = precommit_check($mods);\n \n \tmy %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );\n@@ -1052,6 +1267,23 @@ sub svn_checkout_tree {\n \treturn $mods;\n }\n \n+sub libsvn_checkout_tree {\n+\tmy ($svn_rev, $treeish, $ed) = @_;\n+\tmy $mods = get_diff($svn_rev, $treeish);\n+\treturn $mods unless (scalar @$mods);\n+\tmy %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );\n+\tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n+\t\tmy $f = $m->{chg};\n+\t\tif (defined $o{$f}) {\n+\t\t\t$ed->$f($m);\n+\t\t} else {\n+\t\t\tcroak \"Invalid change type: $f\\n\";\n+\t\t}\n+\t}\n+\t$ed->rmdirs if $_rmdir;\n+\treturn $mods;\n+}\n+\n # svn ls doesn't work with respect to the current working tree, but what's\n # in the repository.  There's not even an option for it... *sigh*\n # (added files don't show up and removed files remain in the ls listing)\n@@ -1090,12 +1322,12 @@ sub handle_rmdir {\n \t}\n }\n \n-sub svn_commit_tree {\n-\tmy ($svn_rev, $commit) = @_;\n-\tmy $commit_msg = \"$GIT_SVN_DIR/.svn-commit.tmp.$$\";\n+sub get_commit_message {\n+\tmy ($commit, $commit_msg) = (@_);\n \tmy %log_msg = ( msg => '' );\n \topen my $msg, '>', $commit_msg or croak $!;\n \n+\tprint \"commit: $commit\\n\";\n \tchomp(my $type = `git-cat-file -t $commit`);\n \tif ($type eq 'commit') {\n \t\tmy $pid = open my $msg_fh, '-|';\n@@ -1129,7 +1361,14 @@ sub svn_commit_tree {\n \t{ local $/; chomp($log_msg{msg} = <$msg>); }\n \tclose $msg or croak $!;\n \n-\tmy ($oneline) = ($log_msg{msg} =~ /([^\\n\\r]+)/);\n+\treturn \\%log_msg;\n+}\n+\n+sub svn_commit_tree {\n+\tmy ($svn_rev, $commit) = @_;\n+\tmy $commit_msg = \"$GIT_SVN_DIR/.svn-commit.tmp.$$\";\n+\tmy $log_msg = get_commit_message($commit, $commit_msg);\n+\tmy ($oneline) = ($log_msg->{msg} =~ /([^\\n\\r]+)/);\n \tprint \"Committing $commit: $oneline\\n\";\n \n \tif (defined $LC_ALL) {\n@@ -1165,12 +1404,12 @@ sub svn_commit_tree {\n \t\t\t\t\t/(\\d{4})\\-(\\d\\d)\\-(\\d\\d)\\s\n \t\t\t\t\t (\\d\\d)\\:(\\d\\d)\\:(\\d\\d)\\s([\\-\\+]\\d+)/x)\n \t\t\t\t\t or croak \"Failed to parse date: $date\\n\";\n-\t\t$log_msg{date} = \"$tz $Y-$m-$d $H:$M:$S\";\n-\t\t$log_msg{author} = $info->{'Last Changed Author'};\n-\t\t$log_msg{revision} = $committed;\n-\t\t$log_msg{msg} .= \"\\n\";\n+\t\t$log_msg->{date} = \"$tz $Y-$m-$d $H:$M:$S\";\n+\t\t$log_msg->{author} = $info->{'Last Changed Author'};\n+\t\t$log_msg->{revision} = $committed;\n+\t\t$log_msg->{msg} .= \"\\n\";\n \t\tmy $parent = file_to_s(\"$REV_DIR/$svn_rev\");\n-\t\tgit_commit(\\%log_msg, $parent, $commit);\n+\t\tgit_commit($log_msg, $parent, $commit);\n \t\treturn $committed;\n \t}\n \t# resync immediately\n@@ -1335,8 +1574,14 @@ sub eol_cp {\n \tbinmode $rfd or croak $!;\n \topen my $wfd, '>', $to or croak $!;\n \tbinmode $wfd or croak $!;\n+\teol_cp_fd($rfd, $wfd, $es);\n+\tclose $rfd or croak $!;\n+\tclose $wfd or croak $!;\n+}\n \n-\tmy $eol = $EOL{$es} or undef;\n+sub eol_cp_fd {\n+\tmy ($rfd, $wfd, $es) = @_;\n+\tmy $eol = defined $es ? $EOL{$es} : undef;\n \tmy $buf;\n \tuse bytes;\n \twhile (1) {\n@@ -1396,6 +1641,7 @@ sub do_update_index {\n }\n \n sub index_changes {\n+\treturn if $_use_lib;\n \tmy $no_text_base = shift;\n \tdo_update_index([qw/git-diff-files --name-only -z/],\n \t\t\t'remove',\n@@ -1459,63 +1705,59 @@ sub assert_revision_eq_or_unknown {\n sub git_commit {\n \tmy ($log_msg, @parents) = @_;\n \tassert_revision_unknown($log_msg->{revision});\n-\tmy $out_fh = IO::File->new_tmpfile or croak $!;\n-\n \tmap_tree_joins() if (@_branch_from && !%tree_map);\n \n+\tmy (@tmp_parents, @exec_parents, %seen_parent);\n+\tif (my $lparents = $log_msg->{parents}) {\n+\t\t@tmp_parents = @$lparents\n+\t}\n \t# commit parents can be conditionally bound to a particular\n \t# svn revision via: \"svn_revno=commit_sha1\", filter them out here:\n-\tmy @exec_parents;\n \tforeach my $p (@parents) {\n \t\tnext unless defined $p;\n \t\tif ($p =~ /^(\\d+)=($sha1_short)$/o) {\n \t\t\tif ($1 == $log_msg->{revision}) {\n-\t\t\t\tpush @exec_parents, $2;\n+\t\t\t\tpush @tmp_parents, $2;\n \t\t\t}\n \t\t} else {\n-\t\t\tpush @exec_parents, $p if $p =~ /$sha1_short/o;\n+\t\t\tpush @tmp_parents, $p if $p =~ /$sha1_short/o;\n \t\t}\n \t}\n-\n-\tmy $pid = fork;\n-\tdefined $pid or croak $!;\n-\tif ($pid == 0) {\n-\t\t$ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;\n+\tmy $tree = $log_msg->{tree};\n+\tif (!defined $tree) {\n+\t\tmy $index = set_index($GIT_SVN_INDEX);\n \t\tindex_changes();\n-\t\tchomp(my $tree = `git-write-tree`);\n+\t\tchomp($tree = `git-write-tree`);\n \t\tcroak $? if $?;\n-\t\tif (exists $tree_map{$tree}) {\n-\t\t\tmy %seen_parent = map { $_ => 1 } @exec_parents;\n-\t\t\tforeach (@{$tree_map{$tree}}) {\n-\t\t\t\t# MAXPARENT is defined to 16 in commit-tree.c:\n-\t\t\t\tif ($seen_parent{$_} || @exec_parents > 16) {\n-\t\t\t\t\tnext;\n-\t\t\t\t}\n-\t\t\t\tpush @exec_parents, $_;\n-\t\t\t\t$seen_parent{$_} = 1;\n-\t\t\t}\n-\t\t}\n+\t\trestore_index($index);\n+\t}\n+\tif (exists $tree_map{$tree}) {\n+\t\tpush @tmp_parents, @{$tree_map{$tree}};\n+\t}\n+\tforeach (@tmp_parents) {\n+\t\tnext if $seen_parent{$_};\n+\t\t$seen_parent{$_} = 1;\n+\t\tpush @exec_parents, $_;\n+\t\t# MAXPARENT is defined to 16 in commit-tree.c:\n+\t\tlast if @exec_parents > 16;\n+\t}\n+\n+\tdefined(my $pid = open my $out_fh, '-|') or croak $!;\n+\tif ($pid == 0) {\n \t\tmy $msg_fh = IO::File->new_tmpfile or croak $!;\n \t\tprint $msg_fh $log_msg->{msg}, \"\\ngit-svn-id: \",\n \t\t\t\t\t\"$SVN_URL\\@$log_msg->{revision}\",\n \t\t\t\t\t\" $SVN_UUID\\n\" or croak $!;\n \t\t$msg_fh->flush == 0 or croak $!;\n \t\tseek $msg_fh, 0, 0 or croak $!;\n-\n \t\tset_commit_env($log_msg);\n-\n \t\tmy @exec = ('git-commit-tree',$tree);\n \t\tpush @exec, '-p', $_  foreach @exec_parents;\n \t\topen STDIN, '<&', $msg_fh or croak $!;\n-\t\topen STDOUT, '>&', $out_fh or croak $!;\n \t\texec @exec or croak $!;\n \t}\n-\twaitpid($pid,0);\n-\tcroak $? if $?;\n-\n-\t$out_fh->flush == 0 or croak $!;\n-\tseek $out_fh, 0, 0 or croak $!;\n \tchomp(my $commit = do { local $/; <$out_fh> });\n+\tclose $out_fh or croak $?;\n \tif ($commit !~ /^$sha1$/o) {\n \t\tcroak \"Failed to commit, invalid sha1: $commit\\n\";\n \t}\n@@ -1534,6 +1776,7 @@ sub git_commit {\n \t}\n \tsys(@update_ref);\n \tsys('git-update-ref',\"svn/$GIT_SVN/revs/$log_msg->{revision}\",$commit);\n+\t# this output is read via pipe, do not change:\n \tprint \"r$log_msg->{revision} = $commit\\n\";\n \tif ($_repack && (--$_repack_nr == 0)) {\n \t\t$_repack_nr = $_repack;\n@@ -1545,6 +1788,9 @@ sub git_commit {\n sub set_commit_env {\n \tmy ($log_msg) = @_;\n \tmy $author = $log_msg->{author};\n+\tif (!defined $author || length $author == 0) {\n+\t\t$author = '(no author)';\n+\t}\n \tmy ($name,$email) = defined $users{$author} ?  @{$users{$author}}\n \t\t\t\t: ($author,\"$author\\@$SVN_UUID\");\n \t$ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;\n@@ -2029,6 +2275,612 @@ sub show_commit_normal {\n \t}\n }\n \n+sub libsvn_load {\n+\treturn unless $_use_lib;\n+\t$_use_lib = eval {\n+\t\trequire SVN::Core;\n+\t\tif ($SVN::Core::VERSION lt '1.2.1') {\n+\t\t\tdie \"Need SVN::Core 1.2.1 or better \",\n+\t\t\t\t\t\"(got $SVN::Core::VERSION) \",\n+\t\t\t\t\t\"Falling back to command-line svn\\n\";\n+\t\t}\n+\t\trequire SVN::Ra;\n+\t\trequire SVN::Delta;\n+\t\tpush @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';\n+\t\tmy $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.\n+\t\t\t\t\t$SVN::Node::dir.$SVN::Node::unknown.\n+\t\t\t\t\t$SVN::Node::none.$SVN::Node::file.\n+\t\t\t\t\t$SVN::Node::dir.$SVN::Node::unknown;\n+\t\t1;\n+\t};\n+}\n+\n+sub libsvn_connect {\n+\tmy ($url) = @_;\n+\tmy $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),\n+\t\t\t  SVN::Client::get_ssl_server_trust_file_provider(),\n+\t\t\t  SVN::Client::get_username_provider()]);\n+\tmy $s = eval { SVN::Ra->new(url => $url, auth => $auth) };\n+\treturn $s;\n+}\n+\n+sub libsvn_get_file {\n+\tmy ($gui, $f, $rev) = @_;\n+\tmy $p = $f;\n+\treturn unless ($p =~ s#^\\Q$SVN_PATH\\E/?##);\n+\n+\tmy $fd = IO::File->new_tmpfile or croak $!;\n+\tmy $pool = SVN::Pool->new;\n+\tmy ($r, $props) = $SVN->get_file($f, $rev, $fd, $pool);\n+\t$pool->clear;\n+\t$fd->flush == 0 or croak $!;\n+\tseek $fd, 0, 0 or croak $!;\n+\tif (my $es = $props->{'svn:eol-style'}) {\n+\t\tmy $new_fd = IO::File->new_tmpfile or croak $!;\n+\t\teol_cp_fd($fd, $new_fd, $es);\n+\t\tclose $fd or croak $!;\n+\t\t$fd = $new_fd;\n+\t\tseek $fd, 0, 0 or croak $!;\n+\t\t$fd->flush == 0 or croak $!;\n+\t}\n+\tmy $mode = '100644';\n+\tif (exists $props->{'svn:executable'}) {\n+\t\t$mode = '100755';\n+\t}\n+\tif (exists $props->{'svn:special'}) {\n+\t\t$mode = '120000';\n+\t\tlocal $/;\n+\t\tmy $link = <$fd>;\n+\t\t$link =~ s/^link // or die \"svn:special file with contents: <\",\n+\t\t\t\t\t\t$link, \"> is not understood\\n\";\n+\t\tseek $fd, 0, 0 or croak $!;\n+\t\ttruncate $fd, 0 or croak $!;\n+\t\tprint $fd $link or croak $!;\n+\t\tseek $fd, 0, 0 or croak $!;\n+\t\t$fd->flush == 0 or croak $!;\n+\t}\n+\tmy $pid = open my $ho, '-|';\n+\tdefined $pid or croak $!;\n+\tif (!$pid) {\n+\t\topen STDIN, '<&', $fd or croak $!;\n+\t\texec qw/git-hash-object -w --stdin/ or croak $!;\n+\t}\n+\tchomp(my $hash = do { local $/; <$ho> });\n+\tclose $ho or croak $?;\n+\t$hash =~ /^$sha1$/o or die \"not a sha1: $hash\\n\";\n+\tprint $gui $mode,' ',$hash,\"\\t\",$p,\"\\0\" or croak $!;\n+\tclose $fd or croak $!;\n+}\n+\n+sub libsvn_log_entry {\n+\tmy ($rev, $author, $date, $msg, $parents) = @_;\n+\tmy ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\\d{4})\\-(\\d\\d)\\-(\\d\\d)T\n+\t\t\t\t\t (\\d\\d)\\:(\\d\\d)\\:(\\d\\d).\\d+Z$/x)\n+\t\t\t\tor die \"Unable to parse date: $date\\n\";\n+\tif (defined $_authors && ! defined $users{$author}) {\n+\t\tdie \"Author: $author not defined in $_authors file\\n\";\n+\t}\n+\treturn { revision => $rev, date => \"+0000 $Y-$m-$d $H:$M:$S\",\n+\t\tauthor => $author, msg => $msg.\"\\n\", parents => $parents || [] }\n+}\n+\n+sub process_rm {\n+\tmy ($gui, $last_commit, $f) = @_;\n+\t$f =~ s#^\\Q$SVN_PATH\\E/?## or return;\n+\t# remove entire directories.\n+\tif (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {\n+\t\tdefined(my $pid = open my $ls, '-|') or croak $!;\n+\t\tif (!$pid) {\n+\t\t\texec(qw/git-ls-tree -r --name-only -z/,\n+\t\t\t\t$last_commit,'--',$f) or croak $!;\n+\t\t}\n+\t\tlocal $/ = \"\\0\";\n+\t\twhile (<$ls>) {\n+\t\t\tprint $gui '0 ',0 x 40,\"\\t\",$_ or croak $!;\n+\t\t}\n+\t\tclose $ls or croak $!;\n+\t} else {\n+\t\tprint $gui '0 ',0 x 40,\"\\t\",$f,\"\\0\" or croak $!;\n+\t}\n+}\n+\n+sub libsvn_fetch {\n+\tmy ($last_commit, $paths, $rev, $author, $date, $msg) = @_;\n+\topen my $gui, '| git-update-index -z --index-info' or croak $!;\n+\tmy @amr;\n+\tforeach my $f (keys %$paths) {\n+\t\tmy $m = $paths->{$f}->action();\n+\t\t$f =~ s#^/+##;\n+\t\tif ($m =~ /^[DR]$/) {\n+\t\t\tprocess_rm($gui, $last_commit, $f);\n+\t\t\tnext if $m eq 'D';\n+\t\t\t# 'R' can be file replacements, too, right?\n+\t\t}\n+\t\tmy $pool = SVN::Pool->new;\n+\t\tmy $t = $SVN->check_path($f, $rev, $pool);\n+\t\tif ($t == $SVN::Node::file) {\n+\t\t\tif ($m =~ /^[AMR]$/) {\n+\t\t\t\tpush @amr, $f;\n+\t\t\t} else {\n+\t\t\t\tdie \"Unrecognized action: $m, ($f r$rev)\\n\";\n+\t\t\t}\n+\t\t}\n+\t\t$pool->clear;\n+\t}\n+\tlibsvn_get_file($gui, $_, $rev) foreach (@amr);\n+\tclose $gui or croak $!;\n+\treturn libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);\n+}\n+\n+sub svn_grab_base_rev {\n+\tdefined(my $pid = open my $fh, '-|') or croak $!;\n+\tif (!$pid) {\n+\t\topen my $null, '>', '/dev/null' or croak $!;\n+\t\topen STDERR, '>&', $null or croak $!;\n+\t\texec qw/git-rev-parse --verify/,\"refs/remotes/$GIT_SVN^0\"\n+\t\t\t\t\t\t\t\tor croak $!;\n+\t}\n+\tchomp(my $c = do { local $/; <$fh> });\n+\tclose $fh;\n+\tif (defined $c && length $c) {\n+\t\tmy ($url, $rev, $uuid) = extract_metadata((grep(/^git-svn-id: /,\n+\t\t\tsafe_qx(qw/git-cat-file commit/, $c)))[0]);\n+\t\treturn ($rev, $c);\n+\t}\n+\treturn (undef, undef);\n+}\n+\n+sub libsvn_parse_revision {\n+\tmy $base = shift;\n+\tmy $head = $SVN->get_latest_revnum();\n+\tif (!defined $_revision || $_revision eq 'BASE:HEAD') {\n+\t\treturn ($base + 1, $head) if (defined $base);\n+\t\treturn (0, $head);\n+\t}\n+\treturn ($1, $2) if ($_revision =~ /^(\\d+):(\\d+)$/);\n+\treturn ($_revision, $_revision) if ($_revision =~ /^\\d+$/);\n+\tif ($_revision =~ /^BASE:(\\d+)$/) {\n+\t\treturn ($base + 1, $1) if (defined $base);\n+\t\treturn (0, $head);\n+\t}\n+\treturn ($1, $head) if ($_revision =~ /^(\\d+):HEAD$/);\n+\tdie \"revision argument: $_revision not understood by git-svn\\n\",\n+\t\t\"Try using the command-line svn client instead\\n\";\n+}\n+\n+sub libsvn_traverse {\n+\tmy ($gui, $pfx, $path, $rev) = @_;\n+\tmy $cwd = \"$pfx/$path\";\n+\tmy $pool = SVN::Pool->new;\n+\t$cwd =~ s#^/+##g;\n+\tmy ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);\n+\tforeach my $d (keys %$dirent) {\n+\t\tmy $t = $dirent->{$d}->kind;\n+\t\tif ($t == $SVN::Node::dir) {\n+\t\t\tlibsvn_traverse($gui, $cwd, $d, $rev);\n+\t\t} elsif ($t == $SVN::Node::file) {\n+\t\t\tlibsvn_get_file($gui, \"$cwd/$d\", $rev);\n+\t\t}\n+\t}\n+\t$pool->clear;\n+}\n+\n+sub libsvn_traverse_ignore {\n+\tmy ($fh, $path, $r) = @_;\n+\t$path =~ s#^/+##g;\n+\tmy $pool = SVN::Pool->new;\n+\tmy ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);\n+\tmy $p = $path;\n+\t$p =~ s#^\\Q$SVN_PATH\\E/?##;\n+\tprint $fh length $p ? \"\\n# $p\\n\" : \"\\n# /\\n\";\n+\tif (my $s = $props->{'svn:ignore'}) {\n+\t\t$s =~ s/[\\r\\n]+/\\n/g;\n+\t\tchomp $s;\n+\t\tif (length $p == 0) {\n+\t\t\t$s =~ s#\\n#\\n/$p#g;\n+\t\t\tprint $fh \"/$s\\n\";\n+\t\t} else {\n+\t\t\t$s =~ s#\\n#\\n/$p/#g;\n+\t\t\tprint $fh \"/$p/$s\\n\";\n+\t\t}\n+\t}\n+\tforeach (sort keys %$dirent) {\n+\t\tnext if $dirent->{$_}->kind != $SVN::Node::dir;\n+\t\tlibsvn_traverse_ignore($fh, \"$path/$_\", $r);\n+\t}\n+\t$pool->clear;\n+}\n+\n+sub libsvn_new_tree {\n+\tmy ($paths, $rev, $author, $date, $msg) = @_;\n+\tmy $svn_path = '/'.$SVN_PATH;\n+\n+\t# look for a parent from another branch:\n+\tforeach (keys %$paths) {\n+\t\tnext if ($_ ne $svn_path);\n+\t\tmy $i = $paths->{$_};\n+\t\tmy $branch_from = $i->copyfrom_path or next;\n+\t\tmy $r = $i->copyfrom_rev;\n+\t\tprint STDERR  \"Found possible branch point: \",\n+\t\t\t\"$branch_from => $svn_path, $r\\n\";\n+\t\t$branch_from =~ s#^/##;\n+\t\tmy $l_map = read_url_paths();\n+\t\tmy $url = $SVN->{url};\n+\t\tdefined $l_map->{$url} or next;\n+\t\tmy $id  = $l_map->{$url}->{$branch_from} or next;\n+\t\tmy $f = \"$GIT_DIR/svn/$id/revs/$r\";\n+\t\twhile ($r && !-r $f) {\n+\t\t\t$r--;\n+\t\t\t$f = \"$GIT_DIR/svn/$id/revs/$r\";\n+\t\t}\n+\t\tif (-r $f) {\n+\t\t\tmy $parent = file_to_s($f);\n+\t\t\tunlink $GIT_SVN_INDEX;\n+\t\t\tprint STDERR \"Found branch parent: $parent\\n\";\n+\t\t\tsys(qw/git-read-tree/, $parent);\n+\t\t\treturn libsvn_fetch($parent, $paths, $rev,\n+\t\t\t\t\t\t$author, $date, $msg);\n+\t\t}\n+\t\tprint STDERR \"Nope, branch point not imported or unknown\\n\";\n+\t}\n+\topen my $gui, '| git-update-index -z --index-info' or croak $!;\n+\tmy $pool = SVN::Pool->new;\n+\tlibsvn_traverse($gui, '', $SVN_PATH, $rev, $pool);\n+\t$pool->clear;\n+\tclose $gui or croak $!;\n+\treturn libsvn_log_entry($rev, $author, $date, $msg);\n+}\n+\n+sub find_graft_path_commit {\n+\tmy ($tree_paths, $p1, $r1) = @_;\n+\tforeach my $x (keys %$tree_paths) {\n+\t\tnext unless ($p1 =~ /^\\Q$x\\E/);\n+\t\tmy $i = $tree_paths->{$x};\n+\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r1\";\n+\n+\t\treturn file_to_s($f) if (-r $f);\n+\n+\t\tprint STDERR \"r$r1 of $i not imported\\n\";\n+\t\tnext;\n+\t}\n+\treturn undef;\n+}\n+\n+sub find_graft_path_parents {\n+\tmy ($grafts, $tree_paths, $c, $p0, $r0) = @_;\n+\tforeach my $x (keys %$tree_paths) {\n+\t\tnext unless ($p0 =~ /^\\Q$x\\E/);\n+\t\tmy $i = $tree_paths->{$x};\n+\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r0\";\n+\t\twhile ($r0 && !-r $f) {\n+\t\t\t# could be an older revision, too...\n+\t\t\t$r0--;\n+\t\t\t$f = \"$GIT_DIR/svn/$i/revs/$r0\";\n+\t\t}\n+\t\tunless (-r $f) {\n+\t\t\tprint STDERR \"r$r0 of $i not imported\\n\";\n+\t\t\tnext;\n+\t\t}\n+\t\tmy $parent = file_to_s($f);\n+\t\t$grafts->{$c}->{$parent} = 1;\n+\t}\n+}\n+\n+sub libsvn_graft_file_copies {\n+\tmy ($grafts, $tree_paths, $path, $paths, $rev) = @_;\n+\tforeach (keys %$paths) {\n+\t\tmy $i = $paths->{$_};\n+\t\tmy ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,\n+\t\t\t\t\t$i->copyfrom_rev);\n+\t\tnext unless (defined $p0 && defined $r0);\n+\n+\t\tmy $p1 = $_;\n+\t\t$p1 =~ s#^/##;\n+\t\t$p0 =~ s#^/##;\n+\t\tmy $c = find_graft_path_commit($tree_paths, $p1, $rev);\n+\t\tnext unless $c;\n+\t\tfind_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);\n+\t}\n+}\n+\n+sub set_index {\n+\tmy $old = $ENV{GIT_INDEX_FILE};\n+\t$ENV{GIT_INDEX_FILE} = shift;\n+\treturn $old;\n+}\n+\n+sub restore_index {\n+\tmy ($old) = @_;\n+\tif (defined $old) {\n+\t\t$ENV{GIT_INDEX_FILE} = $old;\n+\t} else {\n+\t\tdelete $ENV{GIT_INDEX_FILE};\n+\t}\n+}\n+\n+sub libsvn_commit_cb {\n+\tmy ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;\n+\tif ($rev == ($r_last + 1)) {\n+\t\t# optimized (avoid fetch)\n+\t\tmy $log = libsvn_log_entry($rev,$committer,$date,$msg);\n+\t\t$log->{tree} = get_tree_from_treeish($c);\n+\t\tmy $cmt = git_commit($log, $cmt_last, $c);\n+\t\tmy @diff = safe_qx('git-diff-tree', $cmt, $c);\n+\t\tif (@diff) {\n+\t\t\tprint STDERR \"Trees differ: $cmt $c\\n\",\n+\t\t\t\t\tjoin('',@diff),\"\\n\";\n+\t\t\texit 1;\n+\t\t}\n+\t} else {\n+\t\tfetch_lib(\"$rev=$c\");\n+\t}\n+}\n+\n+sub libsvn_ls_fullurl {\n+\tmy $fullurl = shift;\n+\tmy ($repo, $path) = repo_path_split($fullurl);\n+\t$SVN ||= libsvn_connect($repo);\n+\tmy @ret;\n+\tmy $pool = SVN::Pool->new;\n+\tmy ($dirent, undef, undef) = $SVN->get_dir($path,\n+\t\t\t\t\t\t$SVN->get_latest_revnum, $pool);\n+\tforeach my $d (keys %$dirent) {\n+\t\tif ($dirent->{$d}->kind == $SVN::Node::dir) {\n+\t\t\tpush @ret, \"$d/\"; # add '/' for compat with cli svn\n+\t\t}\n+\t}\n+\t$pool->clear;\n+\treturn @ret;\n+}\n+\n+\n+sub libsvn_skip_unknown_revs {\n+\tmy $err = shift;\n+\tmy $errno = $err->apr_err();\n+\t# Maybe the branch we're tracking didn't\n+\t# exist when the repo started, so it's\n+\t# not an error if it doesn't, just continue\n+\t#\n+\t# Wonderfully consistent library, eh?\n+\t# 160013 - svn:// and file://\n+\t# 175002 - http(s)://\n+\t#   More codes may be discovered later...\n+\tif ($errno == 175002 || $errno == 160013) {\n+\t\tprint STDERR \"directory non-existent\\n\";\n+\t\treturn;\n+\t}\n+\tcroak \"Error from SVN, ($errno): \", $err->expanded_message,\"\\n\";\n+};\n+\n+package SVN::Git::Editor;\n+use vars qw/@ISA/;\n+use strict;\n+use warnings;\n+use Carp qw/croak/;\n+use IO::File;\n+\n+sub new {\n+\tmy $class = shift;\n+\tmy $git_svn = shift;\n+\tmy $self = SVN::Delta::Editor->new(@_);\n+\tbless $self, $class;\n+\tforeach (qw/svn_path c r ra /) {\n+\t\tdie \"$_ required!\\n\" unless (defined $git_svn->{$_});\n+\t\t$self->{$_} = $git_svn->{$_};\n+\t}\n+\t$self->{pool} = SVN::Pool->new;\n+\t$self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };\n+\t$self->{rm} = { };\n+\trequire Digest::MD5;\n+\treturn $self;\n+}\n+\n+sub split_path {\n+\treturn ($_[0] =~ m#^(.*?)/?([^/]+)$#);\n+}\n+\n+sub repo_path {\n+\t(defined $_[1] && length $_[1]) ? \"$_[0]->{svn_path}/$_[1]\"\n+\t\t\t\t\t: $_[0]->{svn_path}\n+}\n+\n+sub url_path {\n+\tmy ($self, $path) = @_;\n+\t$self->{ra}->{url} . '/' . $self->repo_path($path);\n+}\n+\n+sub rmdirs {\n+\tmy ($self) = @_;\n+\tmy $rm = $self->{rm};\n+\tdelete $rm->{''}; # we never delete the url we're tracking\n+\treturn unless %$rm;\n+\n+\tforeach (keys %$rm) {\n+\t\tmy @d = split m#/#, $_;\n+\t\tmy $c = shift @d;\n+\t\t$rm->{$c} = 1;\n+\t\twhile (@d) {\n+\t\t\t$c .= '/' . shift @d;\n+\t\t\t$rm->{$c} = 1;\n+\t\t}\n+\t}\n+\tdelete $rm->{$self->{svn_path}};\n+\tdelete $rm->{''}; # we never delete the url we're tracking\n+\treturn unless %$rm;\n+\n+\tdefined(my $pid = open my $fh,'-|') or croak $!;\n+\tif (!$pid) {\n+\t\texec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;\n+\t}\n+\tlocal $/ = \"\\0\";\n+\twhile (<$fh>) {\n+\t\tchomp;\n+\t\t$_ = $self->{svn_path} . '/' . $_;\n+\t\tmy ($dn) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#);\n+\t\tdelete $rm->{$dn};\n+\t\tlast unless %$rm;\n+\t}\n+\tmy ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});\n+\tforeach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {\n+\t\t$self->close_directory($bat->{$d}, $p);\n+\t\tmy ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);\n+\t\t$self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);\n+\t\tdelete $bat->{$d};\n+\t}\n+}\n+\n+sub open_or_add_dir {\n+\tmy ($self, $full_path, $baton) = @_;\n+\tmy $p = SVN::Pool->new;\n+\tmy $t = $self->{ra}->check_path($full_path, $self->{r}, $p);\n+\t$p->clear;\n+\tif ($t == $SVN::Node::none) {\n+\t\treturn $self->add_directory($full_path, $baton,\n+\t\t\t\t\t\tundef, -1, $self->{pool});\n+\t} elsif ($t == $SVN::Node::dir) {\n+\t\treturn $self->open_directory($full_path, $baton,\n+\t\t\t\t\t\t$self->{r}, $self->{pool});\n+\t}\n+\tprint STDERR \"$full_path already exists in repository at \",\n+\t\t\"r$self->{r} and it is not a directory (\",\n+\t\t($t == $SVN::Node::file ? 'file' : 'unknown'),\"/$t)\\n\";\n+\texit 1;\n+}\n+\n+sub ensure_path {\n+\tmy ($self, $path) = @_;\n+\tmy $bat = $self->{bat};\n+\t$path = $self->repo_path($path);\n+\treturn $bat->{''} unless (length $path);\n+\tmy @p = split m#/+#, $path;\n+\tmy $c = shift @p;\n+\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});\n+\twhile (@p) {\n+\t\tmy $c0 = $c;\n+\t\t$c .= '/' . shift @p;\n+\t\t$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});\n+\t}\n+\treturn $bat->{$c};\n+}\n+\n+sub A {\n+\tmy ($self, $m) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t\tundef, -1);\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub C {\n+\tmy ($self, $m) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub delete_entry {\n+\tmy ($self, $path, $pbat) = @_;\n+\tmy $rpath = $self->repo_path($path);\n+\tmy ($dir, $file) = split_path($rpath);\n+\t$self->{rm}->{$dir} = 1;\n+\t$self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});\n+}\n+\n+sub R {\n+\tmy ($self, $m) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir);\n+\tmy $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,\n+\t\t\t\t$self->url_path($m->{file_a}), $self->{r});\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+\n+\t($dir, $file) = split_path($m->{file_a});\n+\t$pbat = $self->ensure_path($dir);\n+\t$self->delete_entry($m->{file_a}, $pbat);\n+}\n+\n+sub M {\n+\tmy ($self, $m) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir);\n+\tmy $fbat = $self->open_file($self->repo_path($m->{file_b}),\n+\t\t\t\t$pbat,$self->{r},$self->{pool});\n+\t$self->chg_file($fbat, $m);\n+\t$self->close_file($fbat,undef,$self->{pool});\n+}\n+\n+sub T { shift->M(@_) }\n+\n+sub change_file_prop {\n+\tmy ($self, $fbat, $pname, $pval) = @_;\n+\t$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});\n+}\n+\n+sub chg_file {\n+\tmy ($self, $fbat, $m) = @_;\n+\tif ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {\n+\t\t$self->change_file_prop($fbat,'svn:executable','*');\n+\t} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {\n+\t\t$self->change_file_prop($fbat,'svn:executable',undef);\n+\t}\n+\tmy $fh = IO::File->new_tmpfile or croak $!;\n+\tif ($m->{mode_b} =~ /^120/) {\n+\t\tprint $fh 'link ' or croak $!;\n+\t\t$self->change_file_prop($fbat,'svn:special','*');\n+\t} elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {\n+\t\t$self->change_file_prop($fbat,'svn:special',undef);\n+\t}\n+\tdefined(my $pid = fork) or croak $!;\n+\tif (!$pid) {\n+\t\topen STDOUT, '>&', $fh or croak $!;\n+\t\texec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;\n+\t}\n+\twaitpid $pid, 0;\n+\tcroak $? if $?;\n+\t$fh->flush == 0 or croak $!;\n+\tseek $fh, 0, 0 or croak $!;\n+\n+\tmy $md5 = Digest::MD5->new;\n+\t$md5->addfile($fh) or croak $!;\n+\tseek $fh, 0, 0 or croak $!;\n+\n+\tmy $exp = $md5->hexdigest;\n+\tmy $atd = $self->apply_textdelta($fbat, undef, $self->{pool});\n+\tmy $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});\n+\tdie \"Checksum mismatch\\nexpected: $exp\\ngot: $got\\n\" if ($got ne $exp);\n+\n+\tclose $fh or croak $!;\n+}\n+\n+sub D {\n+\tmy ($self, $m) = @_;\n+\tmy ($dir, $file) = split_path($m->{file_b});\n+\tmy $pbat = $self->ensure_path($dir);\n+\t$self->delete_entry($m->{file_b}, $pbat);\n+}\n+\n+sub close_edit {\n+\tmy ($self) = @_;\n+\tmy ($p,$bat) = ($self->{pool}, $self->{bat});\n+\tforeach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {\n+\t\t$self->close_directory($bat->{$_}, $p);\n+\t}\n+\t$self->SUPER::close_edit($p);\n+\t$p->clear;\n+}\n+\n+sub abort_edit {\n+\tmy ($self) = @_;\n+\t$self->SUPER::abort_edit($self->{pool});\n+\t$self->{pool}->clear;\n+}\n+\n __END__\n \n Data structures:\n@@ -2062,3 +2914,7 @@ diff-index line ($m hash)\n \tfile_b => new/current file name of a file (any chg)\n }\n ;\n+\n+Notes:\n+\tI don't trust the each() function on unless I created %hash myself\n+\tbecause the internal iterator may not have started at base.\ndiff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh\nindex 58408a6..2843258 100644\n--- a/contrib/git-svn/t/lib-git-svn.sh\n+++ b/contrib/git-svn/t/lib-git-svn.sh\n@@ -11,7 +11,7 @@ fi\n \n GIT_DIR=$PWD/.git\n GIT_SVN_DIR=$GIT_DIR/svn/git-svn\n-SVN_TREE=$GIT_SVN_DIR/tree\n+SVN_TREE=$GIT_SVN_DIR/svn-tree\n \n svnadmin >/dev/null 2>&1\n if test $? != 1\ndiff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh\nindex 0c6ff20..c33b522 100644\n--- a/contrib/git-svn/t/t0000-contrib-git-svn.sh\n+++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh\n@@ -31,6 +31,7 @@ test_expect_success \\\n     'import an SVN revision into git' \\\n     'git-svn fetch'\n \n+test_expect_success \"checkout from svn\" \"svn co $svnrepo $SVN_TREE\"\n \n name='try a deep --rmdir with a commit'\n git checkout -f -b mybranch remotes/git-svn\n@@ -41,6 +42,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&\n+     svn up $SVN_TREE &&\n      test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a\"\n \n \n@@ -52,7 +54,7 @@ git update-index --remove dir/file\n git update-index --add dir/file/file\n git commit -m \"$name\"\n \n-test_expect_code 1 \"$name\" \\\n+test_expect_failure \"$name\" \\\n     'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \\\n     || true\n \n@@ -67,7 +69,7 @@ git update-index --remove -- bar/zzz\n git update-index --add -- bar\n git commit -m \"$name\"\n \n-test_expect_code 1 \"$name\" \\\n+test_expect_failure \"$name\" \\\n     'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \\\n     || true\n \n@@ -82,7 +84,7 @@ echo yyy > bar/zzz/yyy\n git-update-index --add bar/zzz/yyy\n git commit -m \"$name\"\n \n-test_expect_code 1 \"$name\" \\\n+test_expect_failure \"$name\" \\\n     'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \\\n     || true\n \n@@ -97,7 +99,7 @@ echo asdf > dir\n git update-index --add -- dir\n git commit -m \"$name\"\n \n-test_expect_code 1 \"$name\" \\\n+test_expect_failure \"$name\" \\\n     'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \\\n     || true\n \n@@ -111,6 +113,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&\n+     svn up $SVN_TREE &&\n      test ! -x $SVN_TREE/exec.sh\"\n \n \n@@ -121,6 +124,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&\n+     svn up $SVN_TREE &&\n      test -x $SVN_TREE/exec.sh\"\n \n \n@@ -133,6 +137,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&\n+     svn up $SVN_TREE &&\n      test -L $SVN_TREE/exec.sh\"\n \n \n@@ -145,6 +150,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&\n+     svn up $SVN_TREE &&\n      test -x $SVN_TREE/bar/zzz &&\n      test -L $SVN_TREE/exec-2.sh\"\n \n@@ -159,6 +165,7 @@ git commit -m \"$name\"\n \n test_expect_success \"$name\" \\\n     \"git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&\n+     svn up $SVN_TREE &&\n      test -f $SVN_TREE/exec-2.sh &&\n      test ! -L $SVN_TREE/exec-2.sh &&\n      diff -u help $SVN_TREE/exec-2.sh\"\n-- \n1.4.0\n"},{"id":"21736","messageId":"11502217531195-git-send-email-normalperson@yhbt.net","threadId":"4499","inReplyTo":"11502217352245-git-send-email-normalperson@yhbt.net","subject":"[PATCH 13/13] git-svn: make the $GIT_DIR/svn/*/revs directory obsolete","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2006-06-13T18:02:15Z","receivedAt":"2006-06-13T18:02:15Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"This is a very intrusive change, so I've beefed up the tests\nsignificantly.  Added 'full-test' a target to the Makefile,\nto test different possible configurations.  This is intended\nfor maintainers only.  Users should only be concerned with\n'test' succeeding.\n\nWe now have a very simple custom database format for handling\nmapping of svn revisions => git commits.  Of course, we're\nnot really using it yet, either.\n\nAlso disabled automatic branch-finding on new trees for now.\nIt's too easily broken.  revisions_eq() function should be\nhelpful for branch detection.\n\nAlso removed an extra assertion in fetch_cmd() that wasn't\ncorrectly done.  This bug was found by full-test.\n\nSigned-off-by: Eric Wong <normalperson@yhbt.net>\n---\n contrib/git-svn/Makefile                         |   12 +\n contrib/git-svn/git-svn.perl                     |  245 ++++++++++++++--------\n contrib/git-svn/t/t0000-contrib-git-svn.sh       |   13 +\n contrib/git-svn/t/t0001-contrib-git-svn-props.sh |   86 ++++----\n 4 files changed, 224 insertions(+), 132 deletions(-)\n\ndiff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile\nindex 48f60b3..d73aa56 100644\n--- a/contrib/git-svn/Makefile\n+++ b/contrib/git-svn/Makefile\n@@ -29,8 +29,16 @@ git-svn.html : git-svn.txt\n \tasciidoc -b xhtml11 -d manpage \\\n \t\t-f ../../Documentation/asciidoc.conf $<\n test: git-svn\n-\tcd t && $(SHELL) ./t0000-contrib-git-svn.sh\n-\tcd t && $(SHELL) ./t0001-contrib-git-svn-props.sh\n+\tcd t && $(SHELL) ./t0000-contrib-git-svn.sh $(TEST_FLAGS)\n+\tcd t && $(SHELL) ./t0001-contrib-git-svn-props.sh $(TEST_FLAGS)\n+\n+full-test:\n+\t$(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1\n+\t$(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1\n+\t$(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \\\n+\t\t\t\t\t\t\tLC_ALL=en_US.UTF-8\n+\t$(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \\\n+\t\t\t\t\t\t\tLC_ALL=en_US.UTF-8\n \n clean:\n \trm -f git-svn *.xml *.html *.1\ndiff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl\nindex 9618c8b..884969e 100755\n--- a/contrib/git-svn/git-svn.perl\n+++ b/contrib/git-svn/git-svn.perl\n@@ -6,9 +6,9 @@ use strict;\n use vars qw/\t$AUTHOR $VERSION\n \t\t$SVN_URL $SVN_INFO $SVN_WC $SVN_UUID\n \t\t$GIT_SVN_INDEX $GIT_SVN\n-\t\t$GIT_DIR $REV_DIR $GIT_SVN_DIR/;\n+\t\t$GIT_DIR $GIT_SVN_DIR $REVDB/;\n $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';\n-$VERSION = '1.1.0-pre';\n+$VERSION = '1.1.1-broken';\n \n use Cwd qw/abs_path/;\n $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');\n@@ -31,10 +31,13 @@ use File::Path qw/mkpath/;\n use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;\n use File::Spec qw//;\n use POSIX qw/strftime/;\n+use Memoize;\n+memoize('revisions_eq');\n \n my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);\n $_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};\n libsvn_load();\n+my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};\n my $sha1 = qr/[a-f\\d]{40}/;\n my $sha1_short = qr/[a-f\\d]{4,40}/;\n my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,\n@@ -43,7 +46,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$\n \t$_template, $_shared, $_no_default_regex, $_no_graft_copy,\n \t$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,\n \t$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);\n-my (@_branch_from, %tree_map, %users, %rusers);\n+my (@_branch_from, %tree_map, %users, %rusers, %equiv);\n my ($_svn_co_url_revs, $_svn_pg_peg_revs);\n my @repo_path_split_cache;\n \n@@ -201,7 +204,6 @@ sub rebuild {\n \t\tnext if (defined $SVN_UUID && ($uuid ne $SVN_UUID));\n \t\tnext if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));\n \n-\t\tprint \"r$rev = $c\\n\";\n \t\tunless (defined $latest) {\n \t\t\tif (!$SVN_URL && !$url) {\n \t\t\t\tcroak \"SVN repository location required: $url\\n\";\n@@ -211,8 +213,8 @@ sub rebuild {\n \t\t\tsetup_git_svn();\n \t\t\t$latest = $rev;\n \t\t}\n-\t\tassert_revision_eq_or_unknown($rev, $c);\n-\t\tsys('git-update-ref',\"svn/$GIT_SVN/revs/$rev\",$c);\n+\t\trevdb_set($REVDB, $rev, $c);\n+\t\tprint \"r$rev = $c\\n\";\n \t\t$newest_rev = $rev if ($rev > $newest_rev);\n \t}\n \tclose $rev_list or croak $?;\n@@ -280,7 +282,11 @@ sub fetch_cmd {\n \tmy $svn_log = svn_log_raw(@log_args);\n \n \tmy $base = next_log_entry($svn_log) or croak \"No base revision!\\n\";\n-\tmy $last_commit = undef;\n+\t# don't need last_revision from grab_base_rev() because\n+\t# user could've specified a different revision to skip (they\n+\t# didn't want to import certain revisions into git for whatever\n+\t# reason, so trust $base->{revision} instead.\n+\tmy (undef, $last_commit) = svn_grab_base_rev();\n \tunless (-d $SVN_WC) {\n \t\tsvn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);\n \t\tchdir $SVN_WC or croak $!;\n@@ -290,7 +296,6 @@ sub fetch_cmd {\n \t} else {\n \t\tchdir $SVN_WC or croak $!;\n \t\tread_uuid();\n-\t\teval { $last_commit = file_to_s(\"$REV_DIR/$base->{revision}\") };\n \t\t# looks like a user manually cp'd and svn switch'ed\n \t\tunless ($last_commit) {\n \t\t\tsys(qw/svn revert -R ./);\n@@ -303,7 +308,6 @@ sub fetch_cmd {\n \tpush @svn_up, '--ignore-externals' unless $_no_ignore_ext;\n \tmy $last = $base;\n \twhile (my $log_msg = next_log_entry($svn_log)) {\n-\t\tassert_tree($last_commit);\n \t\tif ($last->{revision} >= $log_msg->{revision}) {\n \t\t\tcroak \"Out of order: last >= current: \",\n \t\t\t\t\"$last->{revision} >= $log_msg->{revision}\\n\";\n@@ -444,14 +448,14 @@ sub commit_cmd {\n \t}\n \t$info = svn_info('.');\n \tread_uuid($info);\n-\tmy $svn_current_rev =  $info->{'Last Changed Rev'};\n+\tmy $last = $fetched;\n \tforeach my $c (@revs) {\n-\t\tmy $mods = svn_checkout_tree($svn_current_rev, $c);\n+\t\tmy $mods = svn_checkout_tree($last, $c);\n \t\tif (scalar @$mods == 0) {\n \t\t\tprint \"Skipping, no changes detected\\n\";\n \t\t\tnext;\n \t\t}\n-\t\t$svn_current_rev = svn_commit_tree($svn_current_rev, $c);\n+\t\t$last = svn_commit_tree($last, $c);\n \t}\n }\n \n@@ -500,7 +504,7 @@ sub commit_lib {\n \t\t\t\t\t\t},\n \t\t\t\t\t\t@lock)\n \t\t\t\t\t);\n-\t\t\tmy $mods = libsvn_checkout_tree($r_last, $c, $ed);\n+\t\t\tmy $mods = libsvn_checkout_tree($cmt_last, $c, $ed);\n \t\t\tif (@$mods == 0) {\n \t\t\t\tprint \"No changes\\nr$r_last = $cmt_last\\n\";\n \t\t\t\t$ed->abort_edit;\n@@ -814,7 +818,7 @@ sub graft_file_copy_cmd {\n \tmy ($grafts, $l_map, $u) = @_;\n \tmy $paths = $l_map->{$u};\n \tmy $pfx = common_prefix([keys %$paths]);\n-\n+\t$SVN_URL ||= $u.$pfx;\n \tmy $pid = open my $fh, '-|';\n \tdefined $pid or croak $!;\n \tunless ($pid) {\n@@ -851,6 +855,8 @@ sub graft_file_copy_lib {\n \tmy ($base, $head) = libsvn_parse_revision();\n \tmy $inc = 1000;\n \tmy ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);\n+\tmy $eh = $SVN::Error::handler;\n+\t$SVN::Error::handler = \\&libsvn_skip_unknown_revs;\n \twhile (1) {\n \t\tmy $pool = SVN::Pool->new;\n \t\t$SVN_LOG->get_log( \"/$path\", $min, $max, 0, 1, 1,\n@@ -864,6 +870,7 @@ sub graft_file_copy_lib {\n \t\t$max += $inc;\n \t\t$max = $head if ($max > $head);\n \t}\n+\t$SVN::Error::handler = $eh;\n }\n \n sub process_merge_msg_matches {\n@@ -994,7 +1001,8 @@ sub setup_git_svn {\n \t}\n \tmkpath([$GIT_SVN_DIR]);\n \tmkpath([\"$GIT_SVN_DIR/info\"]);\n-\tmkpath([$REV_DIR]);\n+\topen my $fh, '>>',$REVDB or croak $!;\n+\tclose $fh;\n \ts_to_file($SVN_URL,\"$GIT_SVN_DIR/info/url\");\n \n \topen my $fd, '>>', \"$GIT_SVN_DIR/info/exclude\" or croak $!;\n@@ -1201,8 +1209,7 @@ sub precommit_check {\n \n \n sub get_diff {\n-\tmy ($svn_rev, $treeish) = @_;\n-\tmy $from = file_to_s(\"$REV_DIR/$svn_rev\");\n+\tmy ($from, $treeish) = @_;\n \tassert_tree($from);\n \tprint \"diff-tree $from $treeish\\n\";\n \tmy $pid = open my $diff_fh, '-|';\n@@ -1222,8 +1229,8 @@ sub get_diff {\n }\n \n sub svn_checkout_tree {\n-\tmy ($svn_rev, $treeish) = @_;\n-\tmy $mods = get_diff($svn_rev, $treeish);\n+\tmy ($from, $treeish) = @_;\n+\tmy $mods = get_diff($from->{commit}, $treeish);\n \treturn $mods unless (scalar @$mods);\n \tmy ($rm, $add) = precommit_check($mods);\n \n@@ -1268,8 +1275,8 @@ sub svn_checkout_tree {\n }\n \n sub libsvn_checkout_tree {\n-\tmy ($svn_rev, $treeish, $ed) = @_;\n-\tmy $mods = get_diff($svn_rev, $treeish);\n+\tmy ($from, $treeish, $ed) = @_;\n+\tmy $mods = get_diff($from, $treeish);\n \treturn $mods unless (scalar @$mods);\n \tmy %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );\n \tforeach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {\n@@ -1365,7 +1372,7 @@ sub get_commit_message {\n }\n \n sub svn_commit_tree {\n-\tmy ($svn_rev, $commit) = @_;\n+\tmy ($last, $commit) = @_;\n \tmy $commit_msg = \"$GIT_SVN_DIR/.svn-commit.tmp.$$\";\n \tmy $log_msg = get_commit_message($commit, $commit_msg);\n \tmy ($oneline) = ($log_msg->{msg} =~ /([^\\n\\r]+)/);\n@@ -1392,7 +1399,7 @@ sub svn_commit_tree {\n \n \tmy @svn_up = qw(svn up);\n \tpush @svn_up, '--ignore-externals' unless $_no_ignore_ext;\n-\tif ($committed == ($svn_rev + 1)) {\n+\tif ($_optimize_commits && ($committed == ($last->{revision} + 1))) {\n \t\tpush @svn_up, \"-r$committed\";\n \t\tsys(@svn_up);\n \t\tmy $info = svn_info('.');\n@@ -1408,14 +1415,14 @@ sub svn_commit_tree {\n \t\t$log_msg->{author} = $info->{'Last Changed Author'};\n \t\t$log_msg->{revision} = $committed;\n \t\t$log_msg->{msg} .= \"\\n\";\n-\t\tmy $parent = file_to_s(\"$REV_DIR/$svn_rev\");\n-\t\tgit_commit($log_msg, $parent, $commit);\n-\t\treturn $committed;\n+\t\t$log_msg->{parents} = [ $last->{commit} ];\n+\t\t$log_msg->{commit} = git_commit($log_msg, $commit);\n+\t\treturn $log_msg;\n \t}\n \t# resync immediately\n-\tpush @svn_up, \"-r$svn_rev\";\n+\tpush @svn_up, \"-r$last->{revision}\";\n \tsys(@svn_up);\n-\treturn fetch(\"$committed=$commit\")->{revision};\n+\treturn fetch(\"$committed=$commit\");\n }\n \n sub rev_list_raw {\n@@ -1671,10 +1678,9 @@ sub file_to_s {\n }\n \n sub assert_revision_unknown {\n-\tmy $revno = shift;\n-\tif (-f \"$REV_DIR/$revno\") {\n-\t\tcroak \"$REV_DIR/$revno already exists! \",\n-\t\t\t\t\"Why are we refetching it?\";\n+\tmy $r = shift;\n+\tif (my $c = revdb_get($REVDB, $r)) {\n+\t\tcroak \"$r = $c already exists! Why are we refetching it?\";\n \t}\n }\n \n@@ -1690,18 +1696,6 @@ sub trees_eq {\n \treturn 1;\n }\n \n-sub assert_revision_eq_or_unknown {\n-\tmy ($revno, $commit) = @_;\n-\tif (-f \"$REV_DIR/$revno\") {\n-\t\tmy $current = file_to_s(\"$REV_DIR/$revno\");\n-\t\tif (($commit ne $current) && !trees_eq($commit, $current)) {\n-\t\t\tcroak \"$REV_DIR/$revno already exists!\\n\",\n-\t\t\t\t\"current: $current\\nexpected: $commit\\n\";\n-\t\t}\n-\t\treturn;\n-\t}\n-}\n-\n sub git_commit {\n \tmy ($log_msg, @parents) = @_;\n \tassert_revision_unknown($log_msg->{revision});\n@@ -1763,19 +1757,12 @@ sub git_commit {\n \t}\n \tmy @update_ref = ('git-update-ref',\"refs/remotes/$GIT_SVN\",$commit);\n \tif (my $primary_parent = shift @exec_parents) {\n-\t\t$pid = fork;\n-\t\tdefined $pid or croak $!;\n-\t\tif (!$pid) {\n-\t\t\tclose STDERR;\n-\t\t\tclose STDOUT;\n-\t\t\texec 'git-rev-parse','--verify',\n-\t\t\t\t\t\"refs/remotes/$GIT_SVN^0\" or croak $!;\n-\t\t}\n-\t\twaitpid $pid, 0;\n+\t\tquiet_run(qw/git-rev-parse --verify/,\"refs/remotes/$GIT_SVN^0\");\n \t\tpush @update_ref, $primary_parent unless $?;\n \t}\n \tsys(@update_ref);\n-\tsys('git-update-ref',\"svn/$GIT_SVN/revs/$log_msg->{revision}\",$commit);\n+\trevdb_set($REVDB, $log_msg->{revision}, $commit);\n+\n \t# this output is read via pipe, do not change:\n \tprint \"r$log_msg->{revision} = $commit\\n\";\n \tif ($_repack && (--$_repack_nr == 0)) {\n@@ -1990,7 +1977,29 @@ sub git_svn_each {\n \t}\n }\n \n+sub migrate_revdb {\n+\tgit_svn_each(sub {\n+\t\tmy $id = shift;\n+\t\tdefined(my $pid = fork) or croak $!;\n+\t\tif (!$pid) {\n+\t\t\t$GIT_SVN = $ENV{GIT_SVN_ID} = $id;\n+\t\t\tinit_vars();\n+\t\t\texit 0 if -r $REVDB;\n+\t\t\tprint \"Upgrading svn => git mapping...\\n\";\n+\t\t\topen my $fh, '>>',$REVDB or croak $!;\n+\t\t\tclose $fh;\n+\t\t\trebuild();\n+\t\t\tprint \"Done upgrading. You may now delete the \",\n+\t\t\t\t\"deprecated $GIT_SVN_DIR/revs directory\\n\";\n+\t\t\texit 0;\n+\t\t}\n+\t\twaitpid $pid, 0;\n+\t\tcroak $? if $?;\n+\t});\n+}\n+\n sub migration_check {\n+\tmigrate_revdb() unless (-e $REVDB);\n \treturn if (-d \"$GIT_DIR/svn\" || !-d $GIT_DIR);\n \tprint \"Upgrading repository...\\n\";\n \tunless (-d \"$GIT_DIR/svn\") {\n@@ -2013,15 +2022,19 @@ sub migration_check {\n \t\ts_to_file($url, \"$GIT_DIR/svn/$x/info/repo_url\");\n \t\ts_to_file($path, \"$GIT_DIR/svn/$x/info/repo_path\");\n \t}\n+\tmigrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);\n \tprint \"Done upgrading.\\n\";\n }\n \n sub find_rev_before {\n-\tmy ($r, $git_svn_id) = @_;\n-\tmy @revs = map { basename $_ } <$GIT_DIR/svn/$git_svn_id/revs/*>;\n-\tforeach my $r0 (sort { $b <=> $a } @revs) {\n-\t\tnext if $r0 >= $r;\n-\t\treturn ($r0, file_to_s(\"$GIT_DIR/svn/$git_svn_id/revs/$r0\"));\n+\tmy ($r, $id, $eq_ok) = @_;\n+\tmy $f = \"$GIT_DIR/svn/$id/.rev_db\";\n+\t# --$r unless $eq_ok;\n+\twhile ($r > 0) {\n+\t\tif (my $c = revdb_get($f, $r)) {\n+\t\t\treturn ($r, $c);\n+\t\t}\n+\t\t--$r;\n \t}\n \treturn (undef, undef);\n }\n@@ -2029,9 +2042,9 @@ sub find_rev_before {\n sub init_vars {\n \t$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';\n \t$GIT_SVN_DIR = \"$GIT_DIR/svn/$GIT_SVN\";\n+\t$REVDB = \"$GIT_SVN_DIR/.rev_db\";\n \t$GIT_SVN_INDEX = \"$GIT_SVN_DIR/index\";\n \t$SVN_URL = undef;\n-\t$REV_DIR = \"$GIT_SVN_DIR/revs\";\n \t$SVN_WC = \"$GIT_SVN_DIR/tree\";\n }\n \n@@ -2491,7 +2504,27 @@ sub libsvn_traverse_ignore {\n \t$pool->clear;\n }\n \n-sub libsvn_new_tree {\n+sub revisions_eq {\n+\tmy ($path, $r0, $r1) = @_;\n+\treturn 1 if $r0 == $r1;\n+\tmy $nr = 0;\n+\tif ($_use_lib) {\n+\t\t# should be OK to use Pool here (r1 - r0) should be small\n+\t\tmy $pool = SVN::Pool->new;\n+\t\t$SVN->get_log(\"/$path\", $r0, $r1, 0, 1, 1, sub {$nr++},$pool);\n+\t\t$pool->clear;\n+\t} else {\n+\t\tmy ($url, undef) = repo_path_split($SVN_URL);\n+\t\tmy $svn_log = svn_log_raw(\"$url/$path\",\"-r$r0:$r1\");\n+\t\twhile (next_log_entry($svn_log)) { $nr++ }\n+\t\tclose $svn_log->{fh};\n+\t}\n+\treturn 0 if ($nr > 1);\n+\treturn 1;\n+}\n+\n+sub libsvn_find_parent_branch {\n+\treturn undef; # XXX this function is disabled atm (not tested enough)\n \tmy ($paths, $rev, $author, $date, $msg) = @_;\n \tmy $svn_path = '/'.$SVN_PATH;\n \n@@ -2502,27 +2535,33 @@ sub libsvn_new_tree {\n \t\tmy $branch_from = $i->copyfrom_path or next;\n \t\tmy $r = $i->copyfrom_rev;\n \t\tprint STDERR  \"Found possible branch point: \",\n-\t\t\t\"$branch_from => $svn_path, $r\\n\";\n+\t\t\t\t\t\"$branch_from => $svn_path, $r\\n\";\n \t\t$branch_from =~ s#^/##;\n \t\tmy $l_map = read_url_paths();\n \t\tmy $url = $SVN->{url};\n \t\tdefined $l_map->{$url} or next;\n \t\tmy $id  = $l_map->{$url}->{$branch_from} or next;\n-\t\tmy $f = \"$GIT_DIR/svn/$id/revs/$r\";\n-\t\twhile ($r && !-r $f) {\n-\t\t\t$r--;\n-\t\t\t$f = \"$GIT_DIR/svn/$id/revs/$r\";\n-\t\t}\n-\t\tif (-r $f) {\n-\t\t\tmy $parent = file_to_s($f);\n+\t\tmy ($r0, $parent) = find_rev_before($r,$id,1);\n+\t\tif (defined $r0 && defined $parent &&\n+\t\t\t\t\trevisions_eq($branch_from, $r0, $r)) {\n \t\t\tunlink $GIT_SVN_INDEX;\n \t\t\tprint STDERR \"Found branch parent: $parent\\n\";\n \t\t\tsys(qw/git-read-tree/, $parent);\n \t\t\treturn libsvn_fetch($parent, $paths, $rev,\n \t\t\t\t\t\t$author, $date, $msg);\n+\t\t} else {\n+\t\t\tprint STDERR\n+\t\t\t\t\"Nope, branch point not imported or unknown\\n\";\n \t\t}\n-\t\tprint STDERR \"Nope, branch point not imported or unknown\\n\";\n \t}\n+\treturn undef;\n+}\n+\n+sub libsvn_new_tree {\n+\tif (my $log_entry = libsvn_find_parent_branch(@_)) {\n+\t\treturn $log_entry;\n+\t}\n+\tmy ($paths, $rev, $author, $date, $msg) = @_;\n \topen my $gui, '| git-update-index -z --index-info' or croak $!;\n \tmy $pool = SVN::Pool->new;\n \tlibsvn_traverse($gui, '', $SVN_PATH, $rev, $pool);\n@@ -2536,10 +2575,8 @@ sub find_graft_path_commit {\n \tforeach my $x (keys %$tree_paths) {\n \t\tnext unless ($p1 =~ /^\\Q$x\\E/);\n \t\tmy $i = $tree_paths->{$x};\n-\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r1\";\n-\n-\t\treturn file_to_s($f) if (-r $f);\n-\n+\t\tmy ($r0, $parent) = find_rev_before($r1,$i,1);\n+\t\treturn $parent if (defined $r0 && $r0 == $r1);\n \t\tprint STDERR \"r$r1 of $i not imported\\n\";\n \t\tnext;\n \t}\n@@ -2551,18 +2588,10 @@ sub find_graft_path_parents {\n \tforeach my $x (keys %$tree_paths) {\n \t\tnext unless ($p0 =~ /^\\Q$x\\E/);\n \t\tmy $i = $tree_paths->{$x};\n-\t\tmy $f = \"$GIT_DIR/svn/$i/revs/$r0\";\n-\t\twhile ($r0 && !-r $f) {\n-\t\t\t# could be an older revision, too...\n-\t\t\t$r0--;\n-\t\t\t$f = \"$GIT_DIR/svn/$i/revs/$r0\";\n-\t\t}\n-\t\tunless (-r $f) {\n-\t\t\tprint STDERR \"r$r0 of $i not imported\\n\";\n-\t\t\tnext;\n+\t\tmy ($r, $parent) = find_rev_before($r0, $i, 1);\n+\t\tif (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {\n+\t\t\t$grafts->{$c}->{$parent} = 1;\n \t\t}\n-\t\tmy $parent = file_to_s($f);\n-\t\t$grafts->{$c}->{$parent} = 1;\n \t}\n }\n \n@@ -2600,8 +2629,7 @@ sub restore_index {\n \n sub libsvn_commit_cb {\n \tmy ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;\n-\tif ($rev == ($r_last + 1)) {\n-\t\t# optimized (avoid fetch)\n+\tif ($_optimize_commits && $rev == ($r_last + 1)) {\n \t\tmy $log = libsvn_log_entry($rev,$committer,$date,$msg);\n \t\t$log->{tree} = get_tree_from_treeish($c);\n \t\tmy $cmt = git_commit($log, $cmt_last, $c);\n@@ -2652,6 +2680,49 @@ sub libsvn_skip_unknown_revs {\n \tcroak \"Error from SVN, ($errno): \", $err->expanded_message,\"\\n\";\n };\n \n+# Tie::File seems to be prone to offset errors if revisions get sparse,\n+# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So\n+# one of my favorite modules is out :<  Next up would be one of the DBM\n+# modules, but I'm not sure which is most portable...  So I'll just\n+# go with something that's plain-text, but still capable of\n+# being randomly accessed.  So here's my ultra-simple fixed-width\n+# database.  All records are 40 characters + \"\\n\", so it's easy to seek\n+# to a revision: (41 * rev) is the byte offset.\n+# A record of 40 0s denotes an empty revision.\n+# And yes, it's still pretty fast (faster than Tie::File).\n+sub revdb_set {\n+\tmy ($file, $rev, $commit) = @_;\n+\tlength $commit == 40 or croak \"arg3 must be a full SHA1 hexsum\\n\";\n+\topen my $fh, '+<', $file or croak $!;\n+\tmy $offset = $rev * 41;\n+\t# assume that append is the common case:\n+\tseek $fh, 0, 2 or croak $!;\n+\tmy $pos = tell $fh;\n+\tif ($pos < $offset) {\n+\t\tprint $fh (('0' x 40),\"\\n\") x (($offset - $pos) / 41);\n+\t}\n+\tseek $fh, $offset, 0 or croak $!;\n+\tprint $fh $commit,\"\\n\";\n+\tclose $fh or croak $!;\n+}\n+\n+sub revdb_get {\n+\tmy ($file, $rev) = @_;\n+\tmy $ret;\n+\tmy $offset = $rev * 41;\n+\topen my $fh, '<', $file or croak $!;\n+\tseek $fh, $offset, 0;\n+\tif (tell $fh == $offset) {\n+\t\t$ret = readline $fh;\n+\t\tif (defined $ret) {\n+\t\t\tchomp $ret;\n+\t\t\t$ret = undef if ($ret =~ /^0{40}$/);\n+\t\t}\n+\t}\n+\tclose $fh or croak $!;\n+\treturn $ret;\n+}\n+\n package SVN::Git::Editor;\n use vars qw/@ISA/;\n use strict;\ndiff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh\nindex c33b522..f896e2c 100644\n--- a/contrib/git-svn/t/t0000-contrib-git-svn.sh\n+++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh\n@@ -193,5 +193,18 @@ test_expect_success \"$name\" \\\n      git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&\n      diff -u a b\"\n \n+name='check imported tree checksums expected tree checksums'\n+cat > expected <<\\EOF\n+tree f735671b89a7eb30cab1d8597de35bd4271ab813\n+tree 4b9af72bb861eaed053854ec502cf7df72618f0f\n+tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1\n+tree 0b094cbff17168f24c302e297f55bfac65eb8bd3\n+tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e\n+tree 56a30b966619b863674f5978696f4a3594f2fca9\n+tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e\n+tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4\n+EOF\n+test_expect_success \"$name\" \"diff -u a expected\"\n+\n test_done\n \ndiff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh\nindex 23a5a2a..54e0ed7 100644\n--- a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh\n+++ b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh\n@@ -52,49 +52,49 @@ EOF\n cd ..\n \n rm -rf import\n-svn co \"$svnrepo\" test_wc\n+test_expect_success 'checkout working copy from svn' \"svn co $svnrepo test_wc\"\n+test_expect_success 'setup some commits to svn' \\\n+\t'cd test_wc &&\n+\t\techo Greetings >> kw.c &&\n+\t\tsvn commit -m \"Not yet an Id\" &&\n+\t\tsvn up &&\n+\t\techo Hello world >> kw.c &&\n+\t\tsvn commit -m \"Modified file, but still not yet an Id\" &&\n+\t\tsvn up &&\n+\t\tsvn propset svn:keywords Id kw.c &&\n+\t\tsvn commit -m \"Propset Id\" &&\n+\t\tsvn up &&\n+\tcd ..'\n+\n+test_expect_success 'initialize git-svn' \"git-svn init $svnrepo\"\n+test_expect_success 'fetch revisions from svn' 'git-svn fetch'\n \n-cd test_wc\n-\techo 'Greetings' >> kw.c\n-\tsvn commit -m 'Not yet an $Id$'\n-\tsvn up\n-\n-\techo 'Hello world' >> kw.c\n-\tsvn commit -m 'Modified file, but still not yet an $Id$'\n-\tsvn up\n-\n-\tsvn propset svn:keywords Id kw.c\n-\tsvn commit -m 'Propset $Id$'\n-\tsvn up\n-cd ..\n-\n-git-svn init \"$svnrepo\"\n-git-svn fetch\n-\n-git checkout -b mybranch remotes/git-svn\n-echo 'Hi again' >> kw.c\n name='test svn:keywords ignoring'\n-\n-git commit -a -m \"$name\"\n-git-svn commit remotes/git-svn..mybranch\n-git pull . remotes/git-svn\n+test_expect_success \"$name\" \\\n+\t'git checkout -b mybranch remotes/git-svn &&\n+\techo Hi again >> kw.c &&\n+\tgit commit -a -m \"test keywoards ignoring\" &&\n+\tgit-svn commit remotes/git-svn..mybranch &&\n+\tgit pull . remotes/git-svn'\n \n expect='/* $Id$ */'\n got=\"`sed -ne 2p kw.c`\"\n test_expect_success 'raw $Id$ found in kw.c' \"test '$expect' = '$got'\"\n \n-cd test_wc\n-\tsvn propset svn:eol-style CR empty\n-\tsvn propset svn:eol-style CR crlf\n-\tsvn propset svn:eol-style CR ne_crlf\n-\tsvn commit -m 'propset CR on crlf files'\n-\tsvn up\n-cd ..\n+test_expect_success \"propset CR on crlf files\" \\\n+\t'cd test_wc &&\n+\t\tsvn propset svn:eol-style CR empty &&\n+\t\tsvn propset svn:eol-style CR crlf &&\n+\t\tsvn propset svn:eol-style CR ne_crlf &&\n+\t\tsvn commit -m \"propset CR on crlf files\" &&\n+\t\tsvn up &&\n+\t cd ..'\n \n-git-svn fetch\n-git pull . remotes/git-svn\n+test_expect_success 'fetch and pull latest from svn and checkout a new wc' \\\n+\t\"git-svn fetch &&\n+\t git pull . remotes/git-svn &&\n+\t svn co $svnrepo new_wc\"\n \n-svn co \"$svnrepo\" new_wc\n for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf\n do\n \ttest_expect_success \"Comparing $i\" \"cmp $i new_wc/$i\"\n@@ -106,16 +106,16 @@ cd test_wc\n \tprintf '$Id$\\rHello\\rWorld' > ne_cr\n \ta_cr=`printf '$Id$\\r\\nHello\\r\\nWorld\\r\\n' | git-hash-object --stdin`\n \ta_ne_cr=`printf '$Id$\\r\\nHello\\r\\nWorld' | git-hash-object --stdin`\n-\tsvn propset svn:eol-style CRLF cr\n-\tsvn propset svn:eol-style CRLF ne_cr\n-\tsvn propset svn:keywords Id cr\n-\tsvn propset svn:keywords Id ne_cr\n-\tsvn commit -m 'propset CRLF on cr files'\n-\tsvn up\n+\ttest_expect_success 'Set CRLF on cr files' \\\n+\t'svn propset svn:eol-style CRLF cr &&\n+\t svn propset svn:eol-style CRLF ne_cr &&\n+\t svn propset svn:keywords Id cr &&\n+\t svn propset svn:keywords Id ne_cr &&\n+\t svn commit -m \"propset CRLF on cr files\" &&\n+\t svn up'\n cd ..\n-\n-git-svn fetch\n-git pull . remotes/git-svn\n+test_expect_success 'fetch and pull latest from svn' \\\n+\t'git-svn fetch && git pull . remotes/git-svn'\n \n b_cr=\"`git-hash-object cr`\"\n b_ne_cr=\"`git-hash-object ne_cr`\"\n-- \n1.4.0\n"}]}