X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=contrib%2Fgit-svn%2Fgit-svn.perl;h=da0ff9ad8a56f41c351946990ee17d1e80f533e8;hb=1a82e79315ed633f6b0b1fc4076054950c5380d3;hp=9618c8bab5263ca0552209f9d8591ccd3d58434d;hpb=a5e0cedc0a4d0018f3e7e4ba8ca54c91742dd859;p=git.git diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index 9618c8ba..da0ff9ad 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -6,9 +6,9 @@ use strict; use vars qw/ $AUTHOR $VERSION $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $REV_DIR $GIT_SVN_DIR/; + $GIT_DIR $GIT_SVN_DIR $REVDB/; $AUTHOR = 'Eric Wong '; -$VERSION = '1.1.0-pre'; +$VERSION = '1.1.1-broken'; use Cwd qw/abs_path/; $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); @@ -31,19 +31,23 @@ use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use File::Spec qw//; use POSIX qw/strftime/; +use IPC::Open3; +use Memoize; +memoize('revisions_eq'); my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); $_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB}; libsvn_load(); +my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_cp_similarity, + $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m); -my (@_branch_from, %tree_map, %users, %rusers); +my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -82,6 +86,7 @@ my %cmd = ( { 'revision|r=i' => \$_revision } ], rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", { 'no-ignore-externals' => \$_no_ignore_ext, + 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], 'graft-branches' => [ \&graft_branches, 'Detect merges/branches from already imported history', @@ -130,7 +135,7 @@ init_vars(); load_authors() if $_authors; load_all_refs() if $_branch_all_refs; svn_compat_check(); -migration_check() unless $cmd =~ /^(?:init|multi-init)$/; +migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -170,6 +175,9 @@ sub version { } sub rebuild { + if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) { + copy_remote_ref(); + } $SVN_URL = shift or undef; my $newest_rev = 0; if ($_upgrade) { @@ -201,7 +209,6 @@ sub rebuild { next if (defined $SVN_UUID && ($uuid ne $SVN_UUID)); next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); - print "r$rev = $c\n"; unless (defined $latest) { if (!$SVN_URL && !$url) { croak "SVN repository location required: $url\n"; @@ -211,8 +218,8 @@ sub rebuild { setup_git_svn(); $latest = $rev; } - assert_revision_eq_or_unknown($rev, $c); - sys('git-update-ref',"svn/$GIT_SVN/revs/$rev",$c); + revdb_set($REVDB, $rev, $c); + print "r$rev = $c\n"; $newest_rev = $rev if ($rev > $newest_rev); } close $rev_list or croak $?; @@ -280,7 +287,11 @@ sub fetch_cmd { my $svn_log = svn_log_raw(@log_args); my $base = next_log_entry($svn_log) or croak "No base revision!\n"; - my $last_commit = undef; + # don't need last_revision from grab_base_rev() because + # user could've specified a different revision to skip (they + # didn't want to import certain revisions into git for whatever + # reason, so trust $base->{revision} instead. + my (undef, $last_commit) = svn_grab_base_rev(); unless (-d $SVN_WC) { svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC); chdir $SVN_WC or croak $!; @@ -290,7 +301,6 @@ sub fetch_cmd { } else { chdir $SVN_WC or croak $!; read_uuid(); - eval { $last_commit = file_to_s("$REV_DIR/$base->{revision}") }; # looks like a user manually cp'd and svn switch'ed unless ($last_commit) { sys(qw/svn revert -R ./); @@ -303,7 +313,6 @@ sub fetch_cmd { push @svn_up, '--ignore-externals' unless $_no_ignore_ext; my $last = $base; while (my $log_msg = next_log_entry($svn_log)) { - assert_tree($last_commit); if ($last->{revision} >= $log_msg->{revision}) { croak "Out of order: last >= current: ", "$last->{revision} >= $log_msg->{revision}\n"; @@ -364,7 +373,6 @@ sub fetch_lib { defined(my $pid = fork) or croak $!; if (!$pid) { $SVN::Error::handler = \&libsvn_skip_unknown_revs; - print "Fetching revisions $min .. $max\n"; # Yes I'm perfectly aware that the fourth argument # below is the limit revisions number. Unfortunately @@ -387,7 +395,6 @@ sub fetch_lib { $log_msg, @parents); } }); - $SVN::Error::handler = sub { 'quiet warnings' }; exit 0; } waitpid $pid, 0; @@ -444,14 +451,14 @@ sub commit_cmd { } $info = svn_info('.'); read_uuid($info); - my $svn_current_rev = $info->{'Last Changed Rev'}; + my $last = $fetched; foreach my $c (@revs) { - my $mods = svn_checkout_tree($svn_current_rev, $c); + my $mods = svn_checkout_tree($last, $c); if (scalar @$mods == 0) { print "Skipping, no changes detected\n"; next; } - $svn_current_rev = svn_commit_tree($svn_current_rev, $c); + $last = svn_commit_tree($last, $c); } } @@ -459,7 +466,7 @@ sub commit_lib { my (@revs) = @_; my ($r_last, $cmt_last) = svn_grab_base_rev(); defined $r_last or die "Must have an existing revision to commit\n"; - my $fetched = fetch_lib(); + my $fetched = fetch(); if ($r_last != $fetched->{revision}) { print STDERR "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", @@ -500,7 +507,7 @@ sub commit_lib { }, @lock) ); - my $mods = libsvn_checkout_tree($r_last, $c, $ed); + my $mods = libsvn_checkout_tree($cmt_last, $c, $ed); if (@$mods == 0) { print "No changes\nr$r_last = $cmt_last\n"; $ed->abort_edit; @@ -519,7 +526,7 @@ sub commit_lib { $no = 1; } } - close $fh or croak $!; + close $fh or croak $?; if (! defined $r_new && ! defined $cmt_new) { unless ($no) { die "Failed to parse revision information\n"; @@ -629,17 +636,8 @@ sub multi_init { sub multi_fetch { # try to do trunk first, since branches/tags # may be descended from it. - if (-d "$GIT_DIR/svn/trunk") { - print "Fetching trunk\n"; - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; - init_vars(); - fetch(@_); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; + if (-e "$GIT_DIR/svn/trunk/info/url") { + fetch_child_id('trunk', @_); } rec_fetch('', "$GIT_DIR/svn", @_); } @@ -669,17 +667,15 @@ sub show_log { my $pid = open(my $log,'-|'); defined $pid or croak $!; if (!$pid) { - my @rl = (qw/git-log --abbrev-commit --pretty=raw - --default/, "remotes/$GIT_SVN"); - push @rl, '--raw' if $_verbose; - exec(@rl, @args) or croak $!; + exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!; } setup_pager(); my (@k, $c, $d); + while (<$log>) { if (/^commit ($sha1_short)/o) { my $cmt = $1; - if ($c && defined $c->{r} && $c->{r} != $r_last) { + if ($c && cmt_showable($c) && $c->{r} != $r_last) { $r_last = $c->{r}; process_commit($c, $r_min, $r_max, \@k) or goto out; @@ -698,8 +694,7 @@ sub show_log { } elsif ($d) { push @{$c->{diff}}, $_; } elsif (/^ (git-svn-id:.+)$/) { - my ($url, $rev, $uuid) = extract_metadata($1); - $c->{r} = $rev; + (undef, $c->{r}, undef) = extract_metadata($1); } elsif (s/^ //) { push @{$c->{l}}, $_; } @@ -721,6 +716,87 @@ out: ########################### utility functions ######################### +sub cmt_showable { + my ($c) = @_; + return 1 if defined $c->{r}; + if ($c->{l} && $c->{l}->[-1] eq "...\n" && + $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { + my @msg = safe_qx(qw/git-cat-file commit/, $c->{c}); + shift @msg while ($msg[0] ne "\n"); + shift @msg; + @{$c->{l}} = grep !/^git-svn-id: /, @msg; + + (undef, $c->{r}, undef) = extract_metadata( + (grep(/^git-svn-id: /, @msg))[-1]); + } + return defined $c->{r}; +} + +sub git_svn_log_cmd { + my ($r_min, $r_max) = @_; + my @cmd = (qw/git-log --abbrev-commit --pretty=raw + --default/, "refs/remotes/$GIT_SVN"); + push @cmd, '--summary' if $_verbose; + return @cmd unless defined $r_max; + if ($r_max == $r_min) { + push @cmd, '--max-count=1'; + if (my $c = revdb_get($REVDB, $r_max)) { + push @cmd, $c; + } + } else { + my ($c_min, $c_max); + $c_max = revdb_get($REVDB, $r_max); + $c_min = revdb_get($REVDB, $r_min); + if ($c_min && $c_max) { + if ($r_max > $r_max) { + push @cmd, "$c_min..$c_max"; + } else { + push @cmd, "$c_max..$c_min"; + } + } elsif ($r_max > $r_min) { + push @cmd, $c_max; + } else { + push @cmd, $c_min; + } + } + return @cmd; +} + +sub fetch_child_id { + my $id = shift; + print "Fetching $id\n"; + my $ref = "$GIT_DIR/refs/remotes/$id"; + my $ca = file_to_s($ref) if (-r $ref); + defined(my $pid = fork) or croak $!; + if (!$pid) { + $GIT_SVN = $ENV{GIT_SVN_ID} = $id; + init_vars(); + fetch(@_); + exit 0; + } + waitpid $pid, 0; + croak $? if $?; + return unless $_repack || -r $ref; + + my $cb = file_to_s($ref); + + defined($pid = open my $fh, '-|') or croak $!; + my $url = file_to_s("$GIT_DIR/svn/$id/info/url"); + $url = qr/\Q$url\E/; + if (!$pid) { + exec qw/git-rev-list --pretty=raw/, + $ca ? "$ca..$cb" : $cb or croak $!; + } + while (<$fh>) { + if (/^ git-svn-id: $url\@\d+ [a-f0-9\-]+$/) { + check_repack(); + } elsif (/^ git-svn-id: \S+\@\d+ [a-f0-9\-]+$/) { + last; + } + } + close $fh; +} + sub rec_fetch { my ($pfx, $p, @args) = @_; my @dir; @@ -729,16 +805,7 @@ sub rec_fetch { $pfx .= '/' if $pfx && $pfx !~ m!/$!; my $id = $pfx . basename $_; next if $id eq 'trunk'; - print "Fetching $id\n"; - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - fetch(@args); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; + fetch_child_id($id, @args); } elsif (-d $_) { push @dir, $_; } @@ -814,7 +881,7 @@ sub graft_file_copy_cmd { my ($grafts, $l_map, $u) = @_; my $paths = $l_map->{$u}; my $pfx = common_prefix([keys %$paths]); - + $SVN_URL ||= $u.$pfx; my $pid = open my $fh, '-|'; defined $pid or croak $!; unless ($pid) { @@ -851,6 +918,8 @@ sub graft_file_copy_lib { my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); + my $eh = $SVN::Error::handler; + $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { my $pool = SVN::Pool->new; $SVN_LOG->get_log( "/$path", $min, $max, 0, 1, 1, @@ -864,6 +933,7 @@ sub graft_file_copy_lib { $max += $inc; $max = $head if ($max > $head); } + $SVN::Error::handler = $eh; } sub process_merge_msg_matches { @@ -936,7 +1006,6 @@ sub read_uuid { $SVN_UUID = $info->{'Repository UUID'} or croak "Repository UUID unreadable\n"; } - s_to_file($SVN_UUID,"$GIT_SVN_DIR/info/uuid"); } sub quiet_run { @@ -994,15 +1063,10 @@ sub setup_git_svn { } mkpath([$GIT_SVN_DIR]); mkpath(["$GIT_SVN_DIR/info"]); - mkpath([$REV_DIR]); + open my $fh, '>>',$REVDB or croak $!; + close $fh; s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url"); - open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!; - print $fd '.svn',"\n"; - close $fd or croak $!; - my ($url, $path) = repo_path_split($SVN_URL); - s_to_file($url, "$GIT_SVN_DIR/info/repo_url"); - s_to_file($path, "$GIT_SVN_DIR/info/repo_path"); } sub assert_svn_wc_clean { @@ -1105,7 +1169,7 @@ sub parse_diff_tree { croak "Error parsing $_\n"; } } - close $diff_fh or croak $!; + close $diff_fh or croak $?; return \@mods; } @@ -1201,8 +1265,7 @@ sub precommit_check { sub get_diff { - my ($svn_rev, $treeish) = @_; - my $from = file_to_s("$REV_DIR/$svn_rev"); + my ($from, $treeish) = @_; assert_tree($from); print "diff-tree $from $treeish\n"; my $pid = open my $diff_fh, '-|'; @@ -1222,8 +1285,8 @@ sub get_diff { } sub svn_checkout_tree { - my ($svn_rev, $treeish) = @_; - my $mods = get_diff($svn_rev, $treeish); + my ($from, $treeish) = @_; + my $mods = get_diff($from->{commit}, $treeish); return $mods unless (scalar @$mods); my ($rm, $add) = precommit_check($mods); @@ -1247,12 +1310,12 @@ sub svn_checkout_tree { } elsif ($m->{chg} eq 'T') { sys(qw(svn rm --force),$m->{file_b}); apply_mod_line_blob($m); - sys(qw(svn add --force), $m->{file_b}); + sys(qw(svn add), $m->{file_b}); svn_check_prop_executable($m); } elsif ($m->{chg} eq 'A') { svn_ensure_parent_path( $m->{file_b} ); apply_mod_line_blob($m); - sys(qw(svn add --force), $m->{file_b}); + sys(qw(svn add), $m->{file_b}); svn_check_prop_executable($m); } else { croak "Invalid chg: $m->{chg}\n"; @@ -1268,8 +1331,8 @@ sub svn_checkout_tree { } sub libsvn_checkout_tree { - my ($svn_rev, $treeish, $ed) = @_; - my $mods = get_diff($svn_rev, $treeish); + my ($from, $treeish, $ed) = @_; + my $mods = get_diff($from, $treeish); return $mods unless (scalar @$mods); my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 ); foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { @@ -1347,7 +1410,7 @@ sub get_commit_message { print $msg $_ or croak $!; } } - close $msg_fh or croak $!; + close $msg_fh or croak $?; } close $msg or croak $!; @@ -1365,7 +1428,7 @@ sub get_commit_message { } sub svn_commit_tree { - my ($svn_rev, $commit) = @_; + my ($last, $commit) = @_; my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $log_msg = get_commit_message($commit, $commit_msg); my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/); @@ -1392,7 +1455,7 @@ sub svn_commit_tree { my @svn_up = qw(svn up); push @svn_up, '--ignore-externals' unless $_no_ignore_ext; - if ($committed == ($svn_rev + 1)) { + if ($_optimize_commits && ($committed == ($last->{revision} + 1))) { push @svn_up, "-r$committed"; sys(@svn_up); my $info = svn_info('.'); @@ -1408,14 +1471,14 @@ sub svn_commit_tree { $log_msg->{author} = $info->{'Last Changed Author'}; $log_msg->{revision} = $committed; $log_msg->{msg} .= "\n"; - my $parent = file_to_s("$REV_DIR/$svn_rev"); - git_commit($log_msg, $parent, $commit); - return $committed; + $log_msg->{parents} = [ $last->{commit} ]; + $log_msg->{commit} = git_commit($log_msg, $commit); + return $log_msg; } # resync immediately - push @svn_up, "-r$svn_rev"; + push @svn_up, "-r$last->{revision}"; sys(@svn_up); - return fetch("$committed=$commit")->{revision}; + return fetch("$committed=$commit"); } sub rev_list_raw { @@ -1561,7 +1624,7 @@ sub svn_info { push @{$ret->{-order}}, $1; } } - close $info_fh or croak $!; + close $info_fh or croak $?; return $ret; } @@ -1637,11 +1700,17 @@ sub do_update_index { } print $ui $x,"\0"; } - close $ui or croak $!; + close $ui or croak $?; } sub index_changes { return if $_use_lib; + + if (!-f "$GIT_SVN_DIR/info/exclude") { + open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!; + print $fd '.svn',"\n"; + close $fd or croak $!; + } my $no_text_base = shift; do_update_index([qw/git-diff-files --name-only -z/], 'remove', @@ -1671,10 +1740,9 @@ sub file_to_s { } sub assert_revision_unknown { - my $revno = shift; - if (-f "$REV_DIR/$revno") { - croak "$REV_DIR/$revno already exists! ", - "Why are we refetching it?"; + my $r = shift; + if (my $c = revdb_get($REVDB, $r)) { + croak "$r = $c already exists! Why are we refetching it?"; } } @@ -1690,18 +1758,6 @@ sub trees_eq { return 1; } -sub assert_revision_eq_or_unknown { - my ($revno, $commit) = @_; - if (-f "$REV_DIR/$revno") { - my $current = file_to_s("$REV_DIR/$revno"); - if (($commit ne $current) && !trees_eq($commit, $current)) { - croak "$REV_DIR/$revno already exists!\n", - "current: $current\nexpected: $commit\n"; - } - return; - } -} - sub git_commit { my ($log_msg, @parents) = @_; assert_revision_unknown($log_msg->{revision}); @@ -1763,26 +1819,23 @@ sub git_commit { } my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit); if (my $primary_parent = shift @exec_parents) { - $pid = fork; - defined $pid or croak $!; - if (!$pid) { - close STDERR; - close STDOUT; - exec 'git-rev-parse','--verify', - "refs/remotes/$GIT_SVN^0" or croak $!; - } - waitpid $pid, 0; + quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"); push @update_ref, $primary_parent unless $?; } sys(@update_ref); - sys('git-update-ref',"svn/$GIT_SVN/revs/$log_msg->{revision}",$commit); + revdb_set($REVDB, $log_msg->{revision}, $commit); + # this output is read via pipe, do not change: print "r$log_msg->{revision} = $commit\n"; + check_repack(); + return $commit; +} + +sub check_repack { if ($_repack && (--$_repack_nr == 0)) { $_repack_nr = $_repack; sys("git repack $_repack_flags"); } - return $commit; } sub set_commit_env { @@ -1890,6 +1943,11 @@ sub svn_cmd_checkout { } sub check_upgrade_needed { + if (!-r $REVDB) { + -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); + open my $fh, '>>',$REVDB or croak $!; + close $fh; + } my $old = eval { my $pid = open my $child, '-|'; defined $pid or croak $!; @@ -1990,7 +2048,30 @@ sub git_svn_each { } } +sub migrate_revdb { + git_svn_each(sub { + my $id = shift; + defined(my $pid = fork) or croak $!; + if (!$pid) { + $GIT_SVN = $ENV{GIT_SVN_ID} = $id; + init_vars(); + exit 0 if -r $REVDB; + print "Upgrading svn => git mapping...\n"; + -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); + open my $fh, '>>',$REVDB or croak $!; + close $fh; + rebuild(); + print "Done upgrading. You may now delete the ", + "deprecated $GIT_SVN_DIR/revs directory\n"; + exit 0; + } + waitpid $pid, 0; + croak $? if $?; + }); +} + sub migration_check { + migrate_revdb() unless (-e $REVDB); return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR); print "Upgrading repository...\n"; unless (-d "$GIT_DIR/svn") { @@ -2009,19 +2090,21 @@ sub migration_check { my $dn = dirname("$GIT_DIR/svn/$x"); mkpath([$dn]) unless -d $dn; rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x"; - my ($url, $path) = repo_path_split($u); - s_to_file($url, "$GIT_DIR/svn/$x/info/repo_url"); - s_to_file($path, "$GIT_DIR/svn/$x/info/repo_path"); } + migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB); print "Done upgrading.\n"; } sub find_rev_before { - my ($r, $git_svn_id) = @_; - my @revs = map { basename $_ } <$GIT_DIR/svn/$git_svn_id/revs/*>; - foreach my $r0 (sort { $b <=> $a } @revs) { - next if $r0 >= $r; - return ($r0, file_to_s("$GIT_DIR/svn/$git_svn_id/revs/$r0")); + my ($r, $id, $eq_ok) = @_; + my $f = "$GIT_DIR/svn/$id/.rev_db"; + return (undef,undef) unless -r $f; + --$r unless $eq_ok; + while ($r > 0) { + if (my $c = revdb_get($f, $r)) { + return ($r, $c); + } + --$r; } return (undef, undef); } @@ -2029,9 +2112,9 @@ sub find_rev_before { sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; + $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - $REV_DIR = "$GIT_SVN_DIR/revs"; $SVN_WC = "$GIT_SVN_DIR/tree"; } @@ -2062,7 +2145,7 @@ sub set_default_vals { if (defined $_repack) { $_repack = 1000 if ($_repack <= 0); $_repack_nr = $_repack; - $_repack_flags ||= ''; + $_repack_flags ||= '-d'; } } @@ -2125,15 +2208,8 @@ sub write_grafts { sub read_url_paths { my $l_map = {}; git_svn_each(sub { my $x = shift; - my $u = file_to_s("$GIT_DIR/svn/$x/info/repo_url"); - my $p = file_to_s("$GIT_DIR/svn/$x/info/repo_path"); - # we hate trailing slashes - if ($u =~ s#(?:^\/+|\/+$)##g) { - s_to_file($u,"$GIT_DIR/svn/$x/info/repo_url"); - } - if ($p =~ s#(?:^\/+|\/+$)##g) { - s_to_file($p,"$GIT_DIR/svn/$x/info/repo_path"); - } + my $url = file_to_s("$GIT_DIR/svn/$x/info/url"); + my ($u, $p) = repo_path_split($url); $l_map->{$u}->{$p} = $x; }); return $l_map; @@ -2179,6 +2255,7 @@ sub setup_pager { # translated to Perl from pager.c sub get_author_info { my ($dest, $author, $t, $tz) = @_; $author =~ s/(?:^\s*|\s*$)//g; + $dest->{a_raw} = $author; my $_a; if ($_authors) { $_a = $rusers{$author} || undef; @@ -2309,47 +2386,36 @@ sub libsvn_get_file { my $p = $f; return unless ($p =~ s#^\Q$SVN_PATH\E/?##); - my $fd = IO::File->new_tmpfile or croak $!; + my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; - my ($r, $props) = $SVN->get_file($f, $rev, $fd, $pool); + defined($pid = open3($in, $out, '>&STDERR', + qw/git-hash-object -w --stdin/)) or croak $!; + my ($r, $props) = $SVN->get_file($f, $rev, $in, $pool); + $in->flush == 0 or croak $!; + close $in or croak $!; $pool->clear; - $fd->flush == 0 or croak $!; - seek $fd, 0, 0 or croak $!; - if (my $es = $props->{'svn:eol-style'}) { - my $new_fd = IO::File->new_tmpfile or croak $!; - eol_cp_fd($fd, $new_fd, $es); - close $fd or croak $!; - $fd = $new_fd; - seek $fd, 0, 0 or croak $!; - $fd->flush == 0 or croak $!; - } - my $mode = '100644'; - if (exists $props->{'svn:executable'}) { - $mode = '100755'; - } + chomp($hash = do { local $/; <$out> }); + close $out or croak $!; + waitpid $pid, 0; + $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; + + my $mode = exists $props->{'svn:executable'} ? '100755' : '100644'; if (exists $props->{'svn:special'}) { $mode = '120000'; - local $/; - my $link = <$fd>; + my $link = `git-cat-file blob $hash`; $link =~ s/^link // or die "svn:special file with contents: <", $link, "> is not understood\n"; - seek $fd, 0, 0 or croak $!; - truncate $fd, 0 or croak $!; - print $fd $link or croak $!; - seek $fd, 0, 0 or croak $!; - $fd->flush == 0 or croak $!; - } - my $pid = open my $ho, '-|'; - defined $pid or croak $!; - if (!$pid) { - open STDIN, '<&', $fd or croak $!; - exec qw/git-hash-object -w --stdin/ or croak $!; + defined($pid = open3($in, $out, '>&STDERR', + qw/git-hash-object -w --stdin/)) or croak $!; + print $in $link; + $in->flush == 0 or croak $!; + close $in or croak $!; + chomp($hash = do { local $/; <$out> }); + close $out or croak $!; + waitpid $pid, 0; + $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } - chomp(my $hash = do { local $/; <$ho> }); - close $ho or croak $?; - $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!; - close $fd or croak $!; } sub libsvn_log_entry { @@ -2378,7 +2444,7 @@ sub process_rm { while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; } - close $ls or croak $!; + close $ls or croak $?; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; } @@ -2408,7 +2474,7 @@ sub libsvn_fetch { $pool->clear; } libsvn_get_file($gui, $_, $rev) foreach (@amr); - close $gui or croak $!; + close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); } @@ -2424,7 +2490,7 @@ sub svn_grab_base_rev { close $fh; if (defined $c && length $c) { my ($url, $rev, $uuid) = extract_metadata((grep(/^git-svn-id: /, - safe_qx(qw/git-cat-file commit/, $c)))[0]); + safe_qx(qw/git-cat-file commit/, $c)))[-1]); return ($rev, $c); } return (undef, undef); @@ -2491,43 +2557,63 @@ sub libsvn_traverse_ignore { $pool->clear; } -sub libsvn_new_tree { +sub revisions_eq { + my ($path, $r0, $r1) = @_; + return 1 if $r0 == $r1; + my $nr = 0; + if ($_use_lib) { + # should be OK to use Pool here (r1 - r0) should be small + my $pool = SVN::Pool->new; + $SVN->get_log("/$path", $r0, $r1, 0, 1, 1, sub {$nr++},$pool); + $pool->clear; + } else { + my ($url, undef) = repo_path_split($SVN_URL); + my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1"); + while (next_log_entry($svn_log)) { $nr++ } + close $svn_log->{fh}; + } + return 0 if ($nr > 1); + return 1; +} + +sub libsvn_find_parent_branch { my ($paths, $rev, $author, $date, $msg) = @_; my $svn_path = '/'.$SVN_PATH; # look for a parent from another branch: - foreach (keys %$paths) { - next if ($_ ne $svn_path); - my $i = $paths->{$_}; - my $branch_from = $i->copyfrom_path or next; - my $r = $i->copyfrom_rev; - print STDERR "Found possible branch point: ", - "$branch_from => $svn_path, $r\n"; - $branch_from =~ s#^/##; - my $l_map = read_url_paths(); - my $url = $SVN->{url}; - defined $l_map->{$url} or next; - my $id = $l_map->{$url}->{$branch_from} or next; - my $f = "$GIT_DIR/svn/$id/revs/$r"; - while ($r && !-r $f) { - $r--; - $f = "$GIT_DIR/svn/$id/revs/$r"; - } - if (-r $f) { - my $parent = file_to_s($f); - unlink $GIT_SVN_INDEX; - print STDERR "Found branch parent: $parent\n"; - sys(qw/git-read-tree/, $parent); - return libsvn_fetch($parent, $paths, $rev, - $author, $date, $msg); - } - print STDERR "Nope, branch point not imported or unknown\n"; + my $i = $paths->{$svn_path} or return; + my $branch_from = $i->copyfrom_path or return; + my $r = $i->copyfrom_rev; + print STDERR "Found possible branch point: ", + "$branch_from => $svn_path, $r\n"; + $branch_from =~ s#^/##; + my $l_map = read_url_paths(); + my $url = $SVN->{url}; + defined $l_map->{$url} or return; + my $id = $l_map->{$url}->{$branch_from} or return; + my ($r0, $parent) = find_rev_before($r,$id,1); + return unless (defined $r0 && defined $parent); + if (revisions_eq($branch_from, $r0, $r)) { + unlink $GIT_SVN_INDEX; + print STDERR "Found branch parent: $parent\n"; + sys(qw/git-read-tree/, $parent); + return libsvn_fetch($parent, $paths, $rev, + $author, $date, $msg); + } + print STDERR "Nope, branch point not imported or unknown\n"; + return undef; +} + +sub libsvn_new_tree { + if (my $log_entry = libsvn_find_parent_branch(@_)) { + return $log_entry; } + my ($paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my $pool = SVN::Pool->new; libsvn_traverse($gui, '', $SVN_PATH, $rev, $pool); $pool->clear; - close $gui or croak $!; + close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg); } @@ -2536,10 +2622,8 @@ sub find_graft_path_commit { foreach my $x (keys %$tree_paths) { next unless ($p1 =~ /^\Q$x\E/); my $i = $tree_paths->{$x}; - my $f = "$GIT_DIR/svn/$i/revs/$r1"; - - return file_to_s($f) if (-r $f); - + my ($r0, $parent) = find_rev_before($r1,$i,1); + return $parent if (defined $r0 && $r0 == $r1); print STDERR "r$r1 of $i not imported\n"; next; } @@ -2551,18 +2635,10 @@ sub find_graft_path_parents { foreach my $x (keys %$tree_paths) { next unless ($p0 =~ /^\Q$x\E/); my $i = $tree_paths->{$x}; - my $f = "$GIT_DIR/svn/$i/revs/$r0"; - while ($r0 && !-r $f) { - # could be an older revision, too... - $r0--; - $f = "$GIT_DIR/svn/$i/revs/$r0"; - } - unless (-r $f) { - print STDERR "r$r0 of $i not imported\n"; - next; + my ($r, $parent) = find_rev_before($r0, $i, 1); + if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) { + $grafts->{$c}->{$parent} = 1; } - my $parent = file_to_s($f); - $grafts->{$c}->{$parent} = 1; } } @@ -2600,8 +2676,7 @@ sub restore_index { sub libsvn_commit_cb { my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_; - if ($rev == ($r_last + 1)) { - # optimized (avoid fetch) + if ($_optimize_commits && $rev == ($r_last + 1)) { my $log = libsvn_log_entry($rev,$committer,$date,$msg); $log->{tree} = get_tree_from_treeish($c); my $cmt = git_commit($log, $cmt_last, $c); @@ -2612,7 +2687,7 @@ sub libsvn_commit_cb { exit 1; } } else { - fetch_lib("$rev=$c"); + fetch("$rev=$c"); } } @@ -2646,12 +2721,65 @@ sub libsvn_skip_unknown_revs { # 175002 - http(s):// # More codes may be discovered later... if ($errno == 175002 || $errno == 160013) { - print STDERR "directory non-existent\n"; return; } croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; }; +# Tie::File seems to be prone to offset errors if revisions get sparse, +# it's not that fast, either. Tie::File is also not in Perl 5.6. So +# one of my favorite modules is out :< Next up would be one of the DBM +# modules, but I'm not sure which is most portable... So I'll just +# go with something that's plain-text, but still capable of +# being randomly accessed. So here's my ultra-simple fixed-width +# database. All records are 40 characters + "\n", so it's easy to seek +# to a revision: (41 * rev) is the byte offset. +# A record of 40 0s denotes an empty revision. +# And yes, it's still pretty fast (faster than Tie::File). +sub revdb_set { + my ($file, $rev, $commit) = @_; + length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; + open my $fh, '+<', $file or croak $!; + my $offset = $rev * 41; + # assume that append is the common case: + seek $fh, 0, 2 or croak $!; + my $pos = tell $fh; + if ($pos < $offset) { + print $fh (('0' x 40),"\n") x (($offset - $pos) / 41); + } + seek $fh, $offset, 0 or croak $!; + print $fh $commit,"\n"; + close $fh or croak $!; +} + +sub revdb_get { + my ($file, $rev) = @_; + my $ret; + my $offset = $rev * 41; + open my $fh, '<', $file or croak $!; + seek $fh, $offset, 0; + if (tell $fh == $offset) { + $ret = readline $fh; + if (defined $ret) { + chomp $ret; + $ret = undef if ($ret =~ /^0{40}$/); + } + } + close $fh or croak $!; + return $ret; +} + +sub copy_remote_ref { + my $origin = $_cp_remote ? $_cp_remote : 'origin'; + my $ref = "refs/remotes/$GIT_SVN"; + if (safe_qx('git-ls-remote', $origin, $ref)) { + sys(qw/git fetch/, $origin, "$ref:$ref"); + } else { + die "Unable to find remote reference: ", + "refs/remotes/$GIT_SVN on $origin\n"; + } +} + package SVN::Git::Editor; use vars qw/@ISA/; use strict;