From d976acfd89a7ea539cdaf3fc806308d426b8007a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 00:45:03 -0800 Subject: git-svn: move authentication prompts into their own namespace I'm going to be reorganizing some more code. Signed-off-by: Eric Wong --- git-svn.perl | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index d792a62d7c..cbe4ed2050 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -69,7 +69,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache, + $_config_dir, $_pager, $_color, $_prefix); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_can_do_switch); @@ -83,9 +83,9 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, - 'username=s' => \$_username, + 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$_config_dir, - 'no-auth-cache' => \$_no_auth_cache, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); @@ -131,9 +131,9 @@ my %cmd = ( 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, - 'username=s' => \$_username, + 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$_config_dir, - 'no-auth-cache' => \$_no_auth_cache, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&multi_fetch, @@ -1912,7 +1912,13 @@ sub show_commit_normal { } } -sub _simple_prompt { +package Git::SVN::Prompt; +use strict; +use warnings; +require SVN::Core; +use vars qw/$_no_auth_cache $_username/; + +sub simple { my ($cred, $realm, $default_username, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; $default_username = $_username if defined $_username; @@ -1923,7 +1929,7 @@ sub _simple_prompt { } $cred->username($default_username); } else { - _username_prompt($cred, $realm, $may_save, $pool); + username($cred, $realm, $may_save, $pool); } $cred->password(_read_password("Password for '" . $cred->username . "': ", $realm)); @@ -1931,7 +1937,7 @@ sub _simple_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_server_trust_prompt { +sub ssl_server_trust { my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Error validating server certificate for '$realm':\n"; @@ -1980,7 +1986,7 @@ prompt: $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_client_cert_prompt { +sub ssl_client_cert { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Client certificate filename: "; @@ -1991,7 +1997,7 @@ sub _ssl_client_cert_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_client_cert_pw_prompt { +sub ssl_client_cert_pw { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; $cred->password(_read_password("Password: ", $realm)); @@ -1999,7 +2005,7 @@ sub _ssl_client_cert_pw_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _username_prompt { +sub username { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; if (defined $realm && length $realm) { @@ -2035,6 +2041,8 @@ sub _read_password { $password; } +package main; + sub libsvn_connect { my ($url) = @_; SVN::_Core::svn_config_ensure($_config_dir, undef); @@ -2042,16 +2050,16 @@ sub libsvn_connect { SVN::Client::get_simple_provider(), SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_simple_prompt_provider( - \&_simple_prompt, 2), + \&Git::SVN::Prompt::simple, 2), SVN::Client::get_ssl_client_cert_prompt_provider( - \&_ssl_client_cert_prompt, 2), + \&Git::SVN::Prompt::ssl_client_cert, 2), SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&_ssl_client_cert_pw_prompt, 2), + \&Git::SVN::Prompt::ssl_client_cert_pw, 2), SVN::Client::get_username_provider(), SVN::Client::get_ssl_server_trust_prompt_provider( - \&_ssl_server_trust_prompt), + \&Git::SVN::Prompt::ssl_server_trust), SVN::Client::get_username_prompt_provider( - \&_username_prompt, 2), + \&Git::SVN::username, 2), ]); my $config = SVN::Core::config_get_config($_config_dir); my $ra = SVN::Ra->new(url => $url, auth => $baton, -- cgit v1.2.3 From 4a87db0e12df48408501610605bd7cde81c6d20e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 01:38:18 -0800 Subject: git-svn: cleanup: move process_rm around (it's only used in one function now) Signed-off-by: Eric Wong --- git-svn.perl | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index cbe4ed2050..fcef05c2ee 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -40,7 +40,6 @@ if ($SVN::Core::VERSION lt '1.1.0') { } push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; -*SVN::Git::Fetcher::process_rm = *process_rm; use Carp qw/croak/; use IO::File qw//; use File::Basename qw/dirname basename/; @@ -2181,28 +2180,6 @@ sub libsvn_log_entry { revprops => $rp } } -sub process_rm { - my ($gui, $last_commit, $f, $q) = @_; - # remove entire directories. - if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { - my ($ls, $ctx) = command_output_pipe(qw/ls-tree - -r --name-only -z/, - $last_commit,'--',$f); - local $/ = "\0"; - while (<$ls>) { - print $gui '0 ',0 x 40,"\t",$_ or croak $!; - print "\tD\t$_\n" unless $q; - } - print "\tD\t$f/\n" unless $q; - command_close_pipe($ls, $ctx); - return $SVN::Node::dir; - } else { - print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; - print "\tD\t$f\n" unless $q; - return $SVN::Node::file; - } -} - sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; my $pool = SVN::Pool->new; @@ -2634,8 +2611,25 @@ sub open_directory { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); - $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; + my $gui = $self->{gui}; + + # remove entire directories. + if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) { + my ($ls, $ctx) = command_output_pipe(qw/ls-tree + -r --name-only -z/, + $self->{c}, '--', $path); + local $/ = "\0"; + while (<$ls>) { + print $gui '0 ',0 x 40,"\t",$_ or croak $!; + print "\tD\t$_\n" unless $self->{q}; + } + print "\tD\t$path/\n" unless $self->{q}; + command_close_pipe($ls, $ctx); + $self->{empty}->{$path} = 0 + } else { + print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!; + print "\tD\t$path\n" unless $self->{q}; + } undef; } -- cgit v1.2.3 From d81bf827192f0af6b1cca64d2cdbaac9b5ca2020 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 10 Jan 2007 01:22:38 -0800 Subject: git-svn: cleanup: put SVN workarounds into their own namespace Force some svn_ra functions to use a temporary pool via wrapper This cleans up the code a bit by removing explicit instances of pool allocation and deallocation and providing wrapper functions that make use of temporary pools. I've also added an explicit pool usage when creating the commit editor for commit-diff where get_commit_editor can be called multiple times with the same pool previously. Signed-off-by: Eric Wong --- git-svn.perl | 322 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 173 insertions(+), 149 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index fcef05c2ee..5a3a877709 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -4,7 +4,7 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID + $SVN_URL $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB/; $AUTHOR = 'Eric Wong '; @@ -38,6 +38,7 @@ require SVN::Delta; if ($SVN::Core::VERSION lt '1.1.0') { fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n"; } +push @Git::SVN::Ra::ISA, 'SVN::Ra'; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; use Carp qw/croak/; @@ -63,16 +64,16 @@ my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, - $_message, $_file, $_follow_parent, $_no_metadata, + $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_config_dir, $_pager, $_color, $_prefix); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_can_do_switch); my @repo_path_split_cache; +use vars qw/$_follow_parent/; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, @@ -83,7 +84,7 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$_config_dir, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); @@ -131,7 +132,7 @@ my %cmd = ( { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$_config_dir, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], @@ -236,6 +237,7 @@ sub rebuild { my ($rev_list, $ctx) = command_output_pipe("rev-list", "refs/remotes/$GIT_SVN"); my $latest; + my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; @@ -251,7 +253,7 @@ sub rebuild { # if we merged or otherwise started elsewhere, this is # how we break out of it - next if (defined $SVN_UUID && ($uuid ne $SVN_UUID)); + next if (defined $svn_uuid && ($uuid ne $svn_uuid)); next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); unless (defined $latest) { @@ -259,7 +261,7 @@ sub rebuild { croak "SVN repository location required: $url\n"; } $SVN_URL ||= $url; - $SVN_UUID ||= $uuid; + $svn_uuid ||= $uuid; setup_git_svn(); $latest = $rev; } @@ -310,7 +312,7 @@ sub fetch { sub fetch_lib { my (@parents) = @_; $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); my ($last_rev, $last_commit) = svn_grab_base_rev(); my ($base, $head) = libsvn_parse_revision($last_rev); if ($base > $head) { @@ -322,7 +324,6 @@ sub fetch_lib { # after processing a revision and SVN stuff seems to leak my $inc = 1000; my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - read_uuid(); if (defined $last_commit) { unless (-e $GIT_SVN_INDEX) { command_noisy('read-tree', $last_commit); @@ -352,8 +353,7 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - libsvn_get_log(libsvn_dup_ra($SVN), [''], - $min, $max, 0, 1, 1, + $SVN->dup->get_log([''], $min, $max, 0, 1, 1, sub { my $log_msg; if ($last_commit) { @@ -378,7 +378,7 @@ sub fetch_lib { $min = $max + 1; $max += $inc; $max = $head if ($max > $head); - $SVN = libsvn_connect($SVN_URL); + $SVN = Git::SVN::Ra->new($SVN_URL); } restore_index($index); return { revision => $last_rev, commit => $last_commit }; @@ -424,8 +424,6 @@ sub commit_lib { " current: $fetched->{revision}\n"; exit 1; } - read_uuid(); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $repo; @@ -437,9 +435,10 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => libsvn_dup_ra($SVN), + ra => $SVN->dup, c => $c, svn_path => $SVN->{svn_path}, }, @@ -451,8 +450,7 @@ sub commit_lib { $log_msg->{msg}, $r_last, $cmt_last) - }, - @lock) + }, $pool) ); my $mods = libsvn_checkout_tree($cmt_last, $c, $ed); if (@$mods == 0) { @@ -461,6 +459,7 @@ sub commit_lib { } else { $ed->close_edit; } + $pool->clear; exit 0; } my ($r_new, $cmt_new, $no); @@ -534,7 +533,7 @@ sub dcommit { sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); my $repo; - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; libsvn_traverse_ignore(\*STDOUT, '', $r); } @@ -716,16 +715,16 @@ sub commit_diff { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; } - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); if ($r eq 'HEAD') { $r = $SVN->get_latest_revnum; } elsif ($r !~ /^\d+$/) { die "revision argument: $r not understood by git-svn\n"; } - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $rev_committed; + my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $r, - ra => libsvn_dup_ra($SVN), + ra => $SVN->dup, c => $tb, svn_path => $SVN->{svn_path} }, @@ -733,7 +732,8 @@ sub commit_diff { sub { $rev_committed = $_[0]; print "Committed $_[0]\n"; - }, @lock) + }, + $pool) ); eval { my $mods = libsvn_checkout_tree($ta, $tb, $ed); @@ -744,6 +744,7 @@ sub commit_diff { $ed->close_edit; } }; + $pool->clear; fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; @@ -1012,7 +1013,7 @@ sub graft_file_copy_lib { my $tree_paths = $l_map->{$u}; my $pfx = common_prefix([keys %$tree_paths]); my ($repo, $path) = repo_path_split($u.$pfx); - $SVN = libsvn_connect($repo); + $SVN = Git::SVN::Ra->new($repo); my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; @@ -1020,14 +1021,11 @@ sub graft_file_copy_lib { my $eh = $SVN::Error::handler; $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { - my $pool = SVN::Pool->new; - libsvn_get_log(libsvn_dup_ra($SVN), [$path], - $min, $max, 0, 2, 1, + $SVN->dup->get_log([$path], $min, $max, 0, 2, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); - }, $pool); - $pool->clear; + }); last if ($max >= $head); $min = $max + 1; $max += $inc; @@ -1095,13 +1093,6 @@ sub graft_merge_msg { } } -sub read_uuid { - return if $SVN_UUID; - my $pool = SVN::Pool->new; - $SVN_UUID = $SVN->get_uuid($pool); - $pool->clear; -} - sub verify_ref { my ($ref) = @_; eval { command_oneline([ 'rev-parse', '--verify', $ref ], @@ -1119,7 +1110,7 @@ sub repo_path_split { return ($u, $full_url); } } - my $tmp = libsvn_connect($full_url); + my $tmp = Git::SVN::Ra->new($full_url); return ($tmp->{repos_root}, $tmp->{svn_path}); } @@ -1371,10 +1362,10 @@ sub git_commit { } next if $skip; my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); - next if (($SVN_UUID eq $uuid_p) && + next if (($SVN->uuid eq $uuid_p) && ($log_msg->{revision} > $r_p)); next if (defined $url_p && defined $SVN_URL && - ($SVN_UUID eq $uuid_p) && + ($SVN->uuid eq $uuid_p) && ($url_p eq $SVN_URL)); push @tmp_parents, $p; } @@ -1394,8 +1385,8 @@ sub git_commit { or croak $!; print $msg_fh $log_msg->{msg} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}", - " $SVN_UUID\n" or croak $!; + print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision} ", + $SVN->uuid,"\n" or croak $!; } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; @@ -1429,7 +1420,7 @@ sub set_commit_env { $author = '(no author)'; } my ($name,$email) = defined $users{$author} ? @{$users{$author}} - : ($author,"$author\@$SVN_UUID"); + : ($author,$author . '@' . $SVN->uuid); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; @@ -1589,7 +1580,6 @@ sub init_vars { $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - $SVN_WC = "$GIT_SVN_DIR/tree"; %tree_map = (); } @@ -2042,60 +2032,6 @@ sub _read_password { package main; -sub libsvn_connect { - my ($url) = @_; - SVN::_Core::svn_config_ensure($_config_dir, undef); - my ($baton, $callbacks) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&Git::SVN::Prompt::simple, 2), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert_pw, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&Git::SVN::Prompt::ssl_server_trust), - SVN::Client::get_username_prompt_provider( - \&Git::SVN::username, 2), - ]); - my $config = SVN::Core::config_get_config($_config_dir); - my $ra = SVN::Ra->new(url => $url, auth => $baton, - config => $config, - pool => SVN::Pool->new, - auth_provider_callbacks => $callbacks); - $ra->{svn_path} = $url; - $ra->{repos_root} = $ra->get_repos_root; - $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; - push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/; - return $ra; -} - -sub libsvn_can_do_switch { - unless (defined $_svn_can_do_switch) { - my $pool = SVN::Pool->new; - my $rep = eval { - $SVN->do_switch(1, '', 0, $SVN->{url}, - SVN::Delta::Editor->new, $pool); - }; - if ($@) { - $_svn_can_do_switch = 0; - } else { - $rep->abort_report($pool); - $_svn_can_do_switch = 1; - } - $pool->clear; - } - $_svn_can_do_switch; -} - -sub libsvn_dup_ra { - my ($ra) = @_; - SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url - auth auth_provider_callbacks repos_root svn_path/); -} - sub uri_encode { my ($f) = @_; $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; @@ -2165,14 +2101,12 @@ sub libsvn_log_entry { } # revprops (make this optional? it's an extra network trip...) - my $pool = SVN::Pool->new; - my $rp = $SVN->rev_proplist($rev, $pool); + my $rp = $SVN->rev_proplist($rev); foreach (sort keys %$rp) { next if /^svn:(?:author|date|log)$/; print $un " rev_prop: ", uri_encode($_), ' ', uri_encode($rp->{$_}), "\n"; } - $pool->clear; close $un or croak $!; { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", @@ -2182,15 +2116,9 @@ sub libsvn_log_entry { sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; - my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); - my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my (undef, $last_rev, undef) = cmt_metadata($last_commit); - $reporter->set_path('', $last_rev, 0, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { + unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); @@ -2250,8 +2178,7 @@ sub libsvn_parse_revision { sub libsvn_traverse_ignore { my ($fh, $path, $r) = @_; $path =~ s#^/+##g; - my $pool = SVN::Pool->new; - my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); + my ($dirent, undef, $props) = $SVN->get_dir($path, $r); my $p = $path; $p =~ s#^\Q$SVN->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; @@ -2270,7 +2197,6 @@ sub libsvn_traverse_ignore { next if $dirent->{$_}->kind != $SVN::Node::dir; libsvn_traverse_ignore($fh, "$path/$_", $r); } - $pool->clear; } sub revisions_eq { @@ -2278,10 +2204,7 @@ sub revisions_eq { return 1 if $r0 == $r1; my $nr = 0; # should be OK to use Pool here (r1 - r0) should be small - my $pool = SVN::Pool->new; - libsvn_get_log($SVN, [$path], $r0, $r1, - 0, 0, 1, sub {$nr++}, $pool); - $pool->clear; + $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++}); return 0 if ($nr > 1); return 1; } @@ -2337,40 +2260,23 @@ sub libsvn_find_parent_branch { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; command_noisy('read-tree', $parent); - unless (libsvn_can_do_switch()) { + unless ($SVN->can_do_switch) { return _libsvn_new_tree($paths, $rev, $author, $date, $msg, [$parent]); } # do_switch works with svn/trunk >= r22312, but that is not # included with SVN 1.4.2 (the latest version at the moment), # so we can't rely on it. - my $ra = libsvn_connect("$url/$branch_from"); + my $ra = Git::SVN::Ra->new("$url/$branch_from"); my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); - my $pool = SVN::Pool->new; - my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url}, - $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path('', $r0, 0, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { - die "SVN connection failed somewhere...\n"; - } + $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or + die "SVN connection failed somewhere...\n"; return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; } -sub libsvn_get_log { - my ($ra, @args) = @_; - $args[4]-- if $args[4] && ! $_follow_parent; - if ($SVN::Core::VERSION le '1.2.0') { - splice(@args, 3, 1); - } - $ra->get_log(@args); -} - sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; @@ -2381,14 +2287,8 @@ sub libsvn_new_tree { sub _libsvn_new_tree { my ($paths, $rev, $author, $date, $msg, $parents) = @_; - my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({q => $_q}); - my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path('', $rev, 1, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { + unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed); @@ -2474,21 +2374,18 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - my $ra = libsvn_connect($fullurl); + my $ra = Git::SVN::Ra->new($fullurl); my @ret; - my $pool = SVN::Pool->new; my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool); + my ($dirent, undef, undef) = $ra->get_dir('', $r); foreach my $d (sort keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { push @ret, "$d/"; # add '/' for compat with cli svn } } - $pool->clear; return @ret; } - sub libsvn_skip_unknown_revs { my $err = shift; my $errno = $err->apr_err(); @@ -2866,9 +2763,7 @@ sub rmdirs { sub open_or_add_dir { my ($self, $full_path, $baton) = @_; - my $p = SVN::Pool->new; - my $t = $self->{ra}->check_path($full_path, $self->{r}, $p); - $p->clear; + my $t = $self->{ra}->check_path($full_path, $self->{r}); if ($t == $SVN::Node::none) { return $self->add_directory($full_path, $baton, undef, -1, $self->{pool}); @@ -3023,6 +2918,135 @@ sub abort_edit { $self->{pool}->clear; } +package Git::SVN::Ra; +use vars qw/@ISA $config_dir/; +use strict; +use warnings; +my ($can_do_switch); + +BEGIN { + # enforce temporary pool usage for some simple functions + my $e; + foreach (qw/get_latest_revnum rev_proplist get_file + check_path get_dir get_uuid get_repos_root/) { + $e .= "sub $_ { + my \$self = shift; + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_,\$pool); + \$pool->clear; + wantarray ? \@ret : \$ret[0]; }\n"; + } + eval $e; +} + +sub new { + my ($class, $url) = @_; + SVN::_Core::svn_config_ensure($config_dir, undef); + my ($baton, $callbacks) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&Git::SVN::Prompt::simple, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert_pw, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&Git::SVN::Prompt::ssl_server_trust), + SVN::Client::get_username_prompt_provider( + \&Git::SVN::Prompt::username, 2), + ]); + my $config = SVN::Core::config_get_config($config_dir); + my $self = SVN::Ra->new(url => $url, auth => $baton, + config => $config, + pool => SVN::Pool->new, + auth_provider_callbacks => $callbacks); + $self->{svn_path} = $url; + $self->{repos_root} = $self->get_repos_root; + $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; + bless $self, $class; +} + +sub DESTROY { + my $self = shift; + $self->{pool}->clear if $self->{pool}; + $self->SUPER::DESTROY(@_); +} + +sub dup { + my ($self) = @_; + my $dup = SVN::Ra->new(pool => SVN::Pool->new, + map { $_ => $self->{$_} } qw/config url + auth auth_provider_callbacks repos_root svn_path/); + bless $dup, ref $self; +} + +sub get_log { + my ($self, @args) = @_; + my $pool = SVN::Pool->new; + $args[4]-- if $args[4] && ! $::_follow_parent; + splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); + my $ret = $self->SUPER::get_log(@args, $pool); + $pool->clear; + $ret; +} + +sub get_commit_editor { + my ($self, $msg, $cb, $pool) = @_; + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); + $self->SUPER::get_commit_editor($msg, $cb, @lock, $pool); +} + +sub uuid { + my ($self) = @_; + $self->{uuid} ||= $self->get_uuid; +} + +sub gs_do_update { + my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; + my $pool = SVN::Pool->new; + my $reporter = $self->do_update($rev_b, $path, $recurse, + $editor, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + my $new = ($rev_a == $rev_b); + $reporter->set_path($path, $rev_a, $new, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + $editor->{git_commit_ok}; +} + +sub gs_do_switch { + my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; + my $pool = SVN::Pool->new; + my $reporter = $self->do_switch($rev_b, $path, $recurse, + $url_b, $editor, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path($path, $rev_a, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + $editor->{git_commit_ok}; +} + +sub can_do_switch { + my $self = shift; + unless (defined $can_do_switch) { + my $pool = SVN::Pool->new; + my $rep = eval { + $self->do_switch(1, '', 0, $self->{url}, + SVN::Delta::Editor->new, $pool); + }; + if ($@) { + $can_do_switch = 0; + } else { + $rep->abort_report($pool); + $can_do_switch = 1; + } + $pool->clear; + } + $can_do_switch; +} + __END__ Data structures: -- cgit v1.2.3 From 336f1714ae4211f97a33e2f672ec9c3479b4e5ba Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 02:14:43 -0800 Subject: git-svn: cleanup: avoid re-use()ing Git.pm in sub-packages I will be using functions from Git.pm in more modules, so I want to avoid re-importing the long argument list everywhere it's used. Also removed an unused command-line switch (--no-ignore-externals) and some variables. Signed-off-by: Eric Wong --- git-svn.perl | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 5a3a877709..55d9412ec9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -49,19 +49,28 @@ use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use POSIX qw/strftime/; use IPC::Open3; use Memoize; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; +use Git; memoize('revisions_eq'); memoize('cmt_metadata'); memoize('get_commit_time'); +BEGIN { + my $s; + foreach (qw/command command_oneline command_noisy command_output_pipe + command_input_pipe command_close_pipe/) { + $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". + "*$_ = *Git::$_; "; + } + eval $s; +} + my ($SVN); 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 $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; -my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, +my ($_revision,$_stdin,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, @@ -70,13 +79,11 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, $_pager, $_color, $_prefix); -my (@_branch_from, %tree_map, %users, %rusers, %equiv); -my ($_svn_can_do_switch); +my (@_branch_from, %tree_map, %users, %rusers); my @repo_path_split_cache; use vars qw/$_follow_parent/; -my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, - 'branch|b=s' => \@_branch_from, +my %fc_opts = ( 'branch|b=s' => \@_branch_from, 'follow-parent|follow' => \$_follow_parent, 'branch-all-refs|B' => \$_branch_all_refs, 'authors-file|A=s' => \$_authors, @@ -117,8 +124,7 @@ my %cmd = ( 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { '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, + { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], 'graft-branches' => [ \&graft_branches, 'Detect merges/branches from already imported history', @@ -2476,8 +2482,6 @@ use strict; use warnings; use Carp qw/croak/; use IO::File qw//; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -2684,8 +2688,6 @@ use strict; use warnings; use Carp qw/croak/; use IO::File; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; sub new { my $class = shift; -- cgit v1.2.3 From 9b981fc6596e369e82bc744e12afd5e54f2514f0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:14:21 -0800 Subject: git-svn: add Git::SVN module (to avoid global variables) This should make it easier to improve multi-fetch and --follow-parent by avoiding global variables. Signed-off-by: Eric Wong --- git-svn.perl | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 55d9412ec9..8abff90d97 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1907,6 +1907,491 @@ sub show_commit_normal { } } +package Git::SVN; +use strict; +use warnings; +use vars qw/$default/; +use Carp qw/croak/; +use File::Path qw/mkpath/; +use IPC::Open3; + +# properties that we do not log: +my %SKIP_PROP; +BEGIN { + %SKIP_PROP = map { $_ => 1 } qw/svn:wc:ra_dav:version-url + svn:special svn:executable + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid + svn:entry:committed-date/; +} + +sub init { + my ($class, $id, $url) = @_; + my $self = _new($class, $id); + mkpath(["$self->{dir}/info"]); + if (defined $url) { + $url =~ s!/+$!!; # strip trailing slash + s_to_file($url, "$self->{dir}/info/url"); + } + $self->{url} = $url; + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh or croak $!; + $self; +} + +sub new { + my ($class, $id) = @_; + my $self = _new($class, $id); + $self->{url} = file_to_s("$self->{dir}/info/url"); + $self; +} + +sub refname { "refs/remotes/$_[0]->{id}" } + +sub ra { + my ($self) = shift; + $self->{ra} ||= Git::SVN::Ra->new($self->{url}); +} + +sub copy_remote_ref { + my ($self) = @_; + my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; + my $ref = $self->refname; + if (command('ls-remote', $origin, $ref)) { + command_noisy('fetch', $origin, "$ref:$ref"); + } elsif ($::_cp_remote && !$::_upgrade) { + die "Unable to find remote reference: $ref on $origin\n"; + } +} + +sub traverse_ignore { + my ($self, $fh, $path, $r) = @_; + $path =~ s#^/+##g; + my ($dirent, undef, $props) = $self->ra->get_dir($path, $r); + my $p = $path; + $p =~ s#^\Q$self->{ra}->{svn_path}\E/##; + print $fh length $p ? "\n# $p\n" : "\n# /\n"; + if (my $s = $props->{'svn:ignore'}) { + $s =~ s/[\r\n]+/\n/g; + chomp $s; + if (length $p == 0) { + $s =~ s#\n#\n/$p#g; + print $fh "/$s\n"; + } else { + $s =~ s#\n#\n/$p/#g; + print $fh "/$p/$s\n"; + } + } + foreach (sort keys %$dirent) { + next if $dirent->{$_}->kind != $SVN::Node::dir; + $self->traverse_ignore($fh, "$path/$_", $r); + } +} + +# returns the newest SVN revision number and newest commit SHA1 +sub last_rev_commit { + my ($self) = @_; + if (defined $self->{last_rev} && defined $self->{last_commit}) { + return ($self->{last_rev}, $self->{last_commit}); + } + my $c = verify_ref($self->refname.'^0'); + if (defined $c && length $c) { + my $rev = (cmt_metadata($c))[1]; + if (defined $rev) { + ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); + return ($rev, $c); + } + } + my $offset = -41; # from tail + my $rl; + open my $fh, '<', $self->{db_path} or + croak "$self->{db_path} not readable: $!\n"; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + while ($c ne $rl && tell $fh != 0) { + $offset -= 41; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + } + my $rev = tell $fh; + croak $! if ($rev < 0); + $rev = ($rev - 41) / 41; + close $fh or croak $!; + ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); + return ($rev, $c); +} + +sub parse_revision { + my ($self, $base) = @_; + my $head = $self->ra->get_latest_revnum; + if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { + return ($base + 1, $head) if (defined $base); + return (0, $head); + } + return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); + return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); + if ($::_revision =~ /^BASE:(\d+)$/) { + return ($base + 1, $1) if (defined $base); + return (0, $head); + } + return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); + die "revision argument: $::_revision not understood by git-svn\n", + "Try using the command-line svn client instead\n"; +} + +sub tmp_index_do { + my ($self, $sub) = @_; + my $old_index = $ENV{GIT_INDEX_FILE}; + $ENV{GIT_INDEX_FILE} = $self->{index}; + my @ret = &$sub; + if ($old_index) { + $ENV{GIT_INDEX_FILE} = $old_index; + } else { + delete $ENV{GIT_INDEX_FILE}; + } + wantarray ? @ret : $ret[0]; +} + +sub assert_index_clean { + my ($self, $treeish) = @_; + + $self->tmp_index_do(sub { + command_noisy('read-tree', $treeish) unless -e $self->{index}; + my $x = command_oneline('write-tree'); + my ($y) = (command(qw/cat-file commit/, $treeish) =~ + /^tree ($::sha1)/mo); + if ($y ne $x) { + unlink $self->{index} or croak $!; + command_noisy('read-tree', $treeish); + } + $x = command_oneline('write-tree'); + if ($y ne $x) { + ::fatal "trees ($treeish) $y != $x\n", + "Something is seriously wrong...\n"; + } + }); +} + +sub get_commit_parents { + my ($self, $log_msg, @parents) = @_; + my (%seen, @ret, @tmp); + # commit parents can be conditionally bound to a particular + # svn revision via: "svn_revno=commit_sha1", filter them out here: + foreach my $p (@parents) { + next unless defined $p; + if ($p =~ /^(\d+)=($::sha1_short)$/o) { + push @tmp, $2 if $1 == $log_msg->{revision}; + } else { + push @tmp, $p if $p =~ /^$::sha1_short$/o; + } + } + if (my $cur = verify_ref($self->refname.'^0')) { + push @tmp, $cur; + } + push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); + while (my $p = shift @tmp) { + next if $seen{$p}; + $seen{$p} = 1; + push @ret, $p; + # MAXPARENT is defined to 16 in commit-tree.c: + last if @ret >= 16; + } + if (@tmp) { + die "r$log_msg->{revision}: No room for parents:\n\t", + join("\n\t", @tmp), "\n"; + } + @ret; +} + +sub check_upgrade_needed { + my ($self) = @_; + if (!-r $self->{db_path}) { + -d $self->{dir} or mkpath([$self->{dir}]); + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh; + } + return unless verify_ref($self->{id}.'-HEAD^0'); + my $head = verify_ref($self->refname.'^0'); + if ($@ || !$head) { + fatal("Please run: $0 rebuild --upgrade\n"); + } +} + +sub do_git_commit { + my ($self, $log_msg, @parents) = @_; + if (my $c = $self->rev_db_get($log_msg->{revision})) { + croak "$log_msg->{revision} = $c already exists! ", + "Why are we refetching it?\n"; + } + my ($name, $email) = author_name_email($log_msg->{author}, $self->ra); + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + + my $tree = $log_msg->{tree}; + if (!defined $tree) { + $tree = $self->tmp_index_do(sub { + command_oneline('write-tree') }); + } + die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; + + my @exec = ('git-commit-tree', $tree); + foreach ($self->get_commit_parents($log_msg, @parents)) { + push @exec, '-p', $_; + } + defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) + or croak $!; + print $msg_fh $log_msg->{log} or croak $!; + print $msg_fh "\ngit-svn-id: $self->{ra}->{url}\@$log_msg->{revision}", + " ", $self->ra->uuid,"\n" or croak $!; + $msg_fh->flush == 0 or croak $!; + close $msg_fh or croak $!; + chomp(my $commit = do { local $/; <$out_fh> }); + close $out_fh or croak $!; + waitpid $pid, 0; + croak $? if $?; + if ($commit !~ /^$::sha1$/o) { + die "Failed to commit, invalid sha1: $commit\n"; + } + + command_noisy('update-ref',$self->refname, $commit); + $self->rev_db_set($log_msg->{revision}, $commit); + + $self->{last_rev} = $log_msg->{revision}; + $self->{last_commit} = $commit; + print "r$log_msg->{revision} = $commit\n"; + return $commit; +} + +sub do_fetch { + my ($self, $paths, $rev) = @_; #, $author, $date, $msg) = @_; + my $ed = SVN::Git::Fetcher->new($self); + my ($last_rev, @parents); + if ($self->{last_commit}) { + $last_rev = $self->{last_rev}; + $ed->{c} = $self->{last_commit}; + @parents = ($self->{last_commit}); + } else { + $last_rev = $rev; + } + unless ($self->ra->do_update($last_rev, $rev, '', 1, $ed)) { + die "SVN connection failed somewhere...\n"; + } + $self->make_log_entry($rev, \@parents, $ed); +} + +sub write_untracked { + my ($self, $rev, $fh, $untracked) = @_; + my $h; + print $fh "r$rev\n" or croak $!; + $h = $untracked->{empty}; + foreach (sort keys %$h) { + my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; + print $fh " $act: ", uri_encode($_), "\n" or croak $!; + warn "W: $act: $_\n"; + } + foreach my $t (qw/dir_prop file_prop/) { + $h = $untracked->{$t} or next; + foreach my $path (sort keys %$h) { + my $ppath = $path eq '' ? '.' : $path; + foreach my $prop (sort keys %{$h->{$path}}) { + next if $SKIP{$prop}; + my $v = $h->{$path}->{$prop}; + if (defined $v) { + print $fh " +$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), ' ', + uri_encode($v), "\n" + or croak $!; + } else { + print $fh " -$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), "\n" + or croak $!; + } + } + } + } + foreach my $t (qw/absent_file absent_directory/) { + $h = $untracked->{$t} or next; + foreach my $parent (sort keys %$h) { + foreach my $path (sort @{$h->{$parent}}) { + print $fh " $t: ", + uri_encode("$parent/$path"), "\n" + or croak $!; + warn "W: $t: $parent/$path ", + "Insufficient permissions?\n"; + } + } + } +} + +sub make_log_entry { + my ($self, $rev, $parents, $untracked) = @_; + my $rp = $self->ra->rev_proplist($rev); + my %log_entry = ( parents => $parents || [], revision => $rev, + revprops => $rp, log => ''); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; + $self->write_untracked($rev, $un, $untracked); + foreach (sort keys %$rp) { + my $v = $rp->{$_}; + if (/^svn:(author|date|log)$/) { + $log_entry{$1} = $v; + } else { + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($v), "\n"; + } + } + close $un or croak $!; + $log_entry{date} = parse_svn_date($log_entry{date}); + $log_entry{author} = check_author($log_entry{author}); + $log_entry{log} .= "\n"; + \%log_entry; +} + +sub fetch { + my ($self, @parents) = @_; + my ($last_rev, $last_commit) = $self->last_rev_commit; + my ($base, $head) = $self->parse_revision($last_rev); + return if ($base > $head); + if (defined $last_commit) { + $self->assert_index_clean($last_commit); + } + my $inc = 1000; + my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = \&skip_unknown_revs; + while (1) { + my @revs; + $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { + my ($paths, $rev, $author, $date, $msg) = @_; + push @revs, $rev }); + foreach (@revs) { + my $log_entry = $self->do_fetch(undef, $_); + $self->do_git_commit($log_entry, @parents); + } + last if $max >= $head; + $min = $max + 1; + $max += $inc; + $max = $head if ($max > $head); + } + $SVN::Error::handler = $err_handler; +} + +sub set_tree_cb { + my ($self, $log_entry, $tree, $rev, $date, $author) = @_; + # TODO: enable and test optimized commits: + if (0 && $rev == ($self->{last_rev} + 1)) { + $log_entry->{revision} = $rev; + $log_entry->{author} = $author; + $self->do_git_commit($log_entry, "$rev=$tree"); + } else { + $self->fetch("$rev=$tree"); + } +} + +sub set_tree { + my ($self, $tree) = (shift, shift); + my $log_entry = get_commit_entry($tree); + unless ($self->{last_rev}) { + fatal("Must have an existing revision to commit\n"); + } + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, + ra => $self->ra->dup, + c => $tree, + svn_path => $self->ra->{svn_path} + }, + $self->ra->get_commit_editor( + $log_entry->{log}, sub { + $self->set_tree_cb($log_entry, + $tree, @_); + }), + $pool); + my $mods = $ed->apply_diff($self->{last_commit}, $tree); + if (@$mods == 0) { + print "No changes\nr$self->{last_rev} = $tree\n"; + } + $pool->clear; +} + +sub skip_unknown_revs { + my ($err) = @_; + my $errno = $err->apr_err(); + # Maybe the branch we're tracking didn't + # exist when the repo started, so it's + # not an error if it doesn't, just continue + # + # Wonderfully consistent library, eh? + # 160013 - svn:// and file:// + # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) + # More codes may be discovered later... + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + return; + } + croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; +} + +# rev_db: +# 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 rev_db_set { + my ($self, $rev, $commit) = @_; + length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; + open my $fh, '+<', $self->{db_path} 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) + or croak $!; + } + seek $fh, $offset, 0 or croak $!; + print $fh $commit,"\n" or croak $!; + close $fh or croak $!; +} + +sub rev_db_get { + my ($self, $rev) = @_; + my $ret; + my $offset = $rev * 41; + open my $fh, '<', $self->{db_path} or croak $!; + if (seek $fh, $offset, 0) { + $ret = readline $fh; + if (defined $ret) { + chomp $ret; + $ret = undef if ($ret =~ /^0{40}$/); + } + } + close $fh or croak $!; + $ret; +} + +sub _new { + my ($class, $id) = @_; + $id ||= $Git::SVN::default; + my $dir = "$ENV{GIT_DIR}/svn/$id"; + bless { id => $id, dir => $dir, index => "$dir/index", + db_path => "$dir/.rev_db" }, $class; +} + + package Git::SVN::Prompt; use strict; use warnings; -- cgit v1.2.3 From d2866f9e1fbe24d9ac9e24501a9be07c3c189d34 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:26:16 -0800 Subject: git-svn: convert 'init' to use Git::SVN While we're at it, fix up some bugs in Git::SVN. Signed-off-by: Eric Wong --- git-svn.perl | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 8abff90d97..5ff0c73048 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -111,7 +111,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit, my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], - init => [ \&init, "Initialize a repo for tracking" . + init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', @@ -278,27 +278,24 @@ sub rebuild { command_close_pipe($rev_list, $ctx); } -sub init { +sub cmd_init { my $url = shift or die "SVN repository location required " . "as a command-line argument\n"; - $url =~ s!/+$!!; # strip trailing slash - if (my $repo_path = shift) { unless (-d $repo_path) { mkpath([$repo_path]); } - $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git"; - init_vars(); + chdir $repo_path or croak $!; + $ENV{GIT_DIR} = $repo_path . "/.git"; } - $SVN_URL = $url; - unless (-d $GIT_DIR) { + unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); push @init_db, "--template=$_template" if defined $_template; push @init_db, "--shared" if defined $_shared; command_noisy(@init_db); } - setup_git_svn(); + Git::SVN->init(undef, $url); } sub cmd_fetch { @@ -596,7 +593,7 @@ sub multi_init { print "GIT_SVN_ID set to 'trunk' for ", "$trunk_url ($_trunk)\n"; } - init($trunk_url); + cmd_init($trunk_url); command_noisy('config', 'svn.trunk', $trunk_url); } } @@ -917,7 +914,7 @@ sub complete_url_ls_init { init_vars(); unless (-d $GIT_SVN_DIR) { print "init $u => $id\n"; - init($u); + cmd_init($u); } } exit 0; @@ -1582,6 +1579,7 @@ sub find_rev_before { sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; + $Git::SVN::default = $GIT_SVN; $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; @@ -1932,7 +1930,7 @@ sub init { mkpath(["$self->{dir}/info"]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash - s_to_file($url, "$self->{dir}/info/url"); + ::s_to_file($url, "$self->{dir}/info/url"); } $self->{url} = $url; open my $fh, '>>', $self->{db_path} or croak $!; @@ -1943,7 +1941,7 @@ sub init { sub new { my ($class, $id) = @_; my $self = _new($class, $id); - $self->{url} = file_to_s("$self->{dir}/info/url"); + $self->{url} = ::file_to_s("$self->{dir}/info/url"); $self; } @@ -1995,9 +1993,9 @@ sub last_rev_commit { if (defined $self->{last_rev} && defined $self->{last_commit}) { return ($self->{last_rev}, $self->{last_commit}); } - my $c = verify_ref($self->refname.'^0'); + my $c = ::verify_ref($self->refname.'^0'); if (defined $c && length $c) { - my $rev = (cmt_metadata($c))[1]; + my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); return ($rev, $c); @@ -2090,7 +2088,7 @@ sub get_commit_parents { push @tmp, $p if $p =~ /^$::sha1_short$/o; } } - if (my $cur = verify_ref($self->refname.'^0')) { + if (my $cur = ::verify_ref($self->refname.'^0')) { push @tmp, $cur; } push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); @@ -2115,10 +2113,10 @@ sub check_upgrade_needed { open my $fh, '>>', $self->{db_path} or croak $!; close $fh; } - return unless verify_ref($self->{id}.'-HEAD^0'); - my $head = verify_ref($self->refname.'^0'); + return unless ::verify_ref($self->{id}.'-HEAD^0'); + my $head = ::verify_ref($self->refname.'^0'); if ($@ || !$head) { - fatal("Please run: $0 rebuild --upgrade\n"); + ::fatal("Please run: $0 rebuild --upgrade\n"); } } @@ -2128,7 +2126,7 @@ sub do_git_commit { croak "$log_msg->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = author_name_email($log_msg->{author}, $self->ra); + my ($name, $email) = ::author_name_email($log_msg->{author}, $self->ra); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; -- cgit v1.2.3 From 8164b6525ef6d95d4b656a8f4226f1758a611989 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 15:35:55 -0800 Subject: git-svn: convert multi-init over to using Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 72 ++++++++++++++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 41 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 5ff0c73048..72f73ea623 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -133,7 +133,7 @@ my %cmd = ( 'branch-all-refs|B' => \$_branch_all_refs, 'no-default-regex' => \$_no_default_regex, 'no-graft-copy' => \$_no_graft_copy } ], - 'multi-init' => [ \&multi_init, + 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, @@ -278,6 +278,15 @@ sub rebuild { command_close_pipe($rev_list, $ctx); } +sub do_git_init_db { + unless (-d $ENV{GIT_DIR}) { + my @init_db = ('init'); + push @init_db, "--template=$_template" if defined $_template; + push @init_db, "--shared" if defined $_shared; + command_noisy(@init_db); + } +} + sub cmd_init { my $url = shift or die "SVN repository location required " . "as a command-line argument\n"; @@ -288,13 +297,8 @@ sub cmd_init { chdir $repo_path or croak $!; $ENV{GIT_DIR} = $repo_path . "/.git"; } + do_git_init_db(); - unless (-d $ENV{GIT_DIR}) { - my @init_db = ('init'); - push @init_db, "--template=$_template" if defined $_template; - push @init_db, "--shared" if defined $_shared; - command_noisy(@init_db); - } Git::SVN->init(undef, $url); } @@ -575,29 +579,22 @@ sub graft_branches { unlink "$gr_file~$gr_sha1" if $gr_sha1; } -sub multi_init { +sub cmd_multi_init { my $url = shift; unless (defined $_trunk || defined $_branches || defined $_tags) { usage(1); } + do_git_init_db(); + $_prefix = '' unless defined $_prefix; if (defined $_trunk) { - my $trunk_url = complete_svn_url($url, $_trunk); - my $ch_id; - if ($GIT_SVN eq 'git-svn') { - $ch_id = 1; - $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; - } - init_vars(); - unless (-d $GIT_SVN_DIR) { - if ($ch_id) { - print "GIT_SVN_ID set to 'trunk' for ", - "$trunk_url ($_trunk)\n"; - } - cmd_init($trunk_url); + my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') }; + unless ($gs_trunk) { + my $trunk_url = complete_svn_url($url, $_trunk); + $gs_trunk = Git::SVN->init($_prefix . 'trunk', + $trunk_url); command_noisy('config', 'svn.trunk', $trunk_url); } } - $_prefix = '' unless defined $_prefix; complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix); complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/'); } @@ -900,27 +897,20 @@ sub complete_url_ls_init { } my $full_url = complete_svn_url($url, $path); my @ls = libsvn_ls_fullurl($full_url); - defined(my $pid = fork) or croak $!; - if (!$pid) { - foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { - $u =~ s#/+$##; - if ($u !~ m!\Q$full_url\E/(.+)$!) { - print STDERR "W: Unrecognized URL: $u\n"; - die "This should never happen\n"; - } - # don't try to init already existing refs - my $id = $pfx.$1; - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - unless (-d $GIT_SVN_DIR) { - print "init $u => $id\n"; - cmd_init($u); - } + foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { + $u =~ s#/+$##; + if ($u !~ m!\Q$full_url\E/(.+)$!) { + print STDERR "W: Unrecognized URL: $u\n"; + die "This should never happen\n"; + } + # don't try to init already existing refs + my $id = $pfx.$1; + my $gs = eval { Git::SVN->new($id) }; + unless ($gs) { + print "init $u => $id\n"; + Git::SVN->init($id, $u); } - exit 0; } - waitpid $pid, 0; - croak $? if $?; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn.$n", $full_url); } -- cgit v1.2.3 From e7db67e6f18495332c4d688d3291b05851526a6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:09:26 -0800 Subject: git-svn: make multi-init capable of reusing the Ra connection If a user specified a seperate URL and --tags/--branches as a sepearte URL, allow the Ra object (and therefore the connection) to be reused. We'll get rid of libsvn_ls_fullurl() since it was only used in one place. Signed-off-by: Eric Wong --- git-svn.perl | 50 ++++++++++++++++++--------------------- t/t9103-git-svn-graft-branches.sh | 6 +++++ 2 files changed, 29 insertions(+), 27 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 72f73ea623..02786f1a6a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -595,8 +595,9 @@ sub cmd_multi_init { command_noisy('config', 'svn.trunk', $trunk_url); } } - complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix); - complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/'); + my $ra = $url ? Git::SVN::Ra->new($url) : undef; + complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); + complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub multi_fetch { @@ -890,29 +891,38 @@ sub complete_svn_url { } sub complete_url_ls_init { - my ($url, $path, $switch, $pfx) = @_; + my ($ra, $path, $switch, $pfx) = @_; unless ($path) { print STDERR "W: $switch not specified\n"; return; } - my $full_url = complete_svn_url($url, $path); - my @ls = libsvn_ls_fullurl($full_url); - foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { - $u =~ s#/+$##; - if ($u !~ m!\Q$full_url\E/(.+)$!) { - print STDERR "W: Unrecognized URL: $u\n"; - die "This should never happen\n"; + $path =~ s#/+$##; + if ($path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($path); + $path = ''; + } else { + $path =~ s#^/+##; + unless ($ra) { + fatal("E: '$path' is not a complete URL ", + "and a separate URL is not specified\n"); } - # don't try to init already existing refs - my $id = $pfx.$1; + } + my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; + my ($dirent, undef, undef) = $ra->get_dir($path, $r); + my $url = $ra->{url} . (length $path ? "/$path" : ''); + foreach my $d (sort keys %$dirent) { + next if ($dirent->{$d}->kind != $SVN::Node::dir); + my $u = "$url/$d"; + my $id = "$pfx$d"; my $gs = eval { Git::SVN->new($id) }; + # don't try to init already existing refs unless ($gs) { print "init $u => $id\n"; Git::SVN->init($id, $u); } } my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $full_url); + command_noisy('config', "svn.$n", $url); } sub common_prefix { @@ -2851,20 +2861,6 @@ sub libsvn_commit_cb { } } -sub libsvn_ls_fullurl { - my $fullurl = shift; - my $ra = Git::SVN::Ra->new($fullurl); - my @ret; - my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir('', $r); - foreach my $d (sort keys %$dirent) { - if ($dirent->{$d}->kind == $SVN::Node::dir) { - push @ret, "$d/"; # add '/' for compat with cli svn - } - } - return @ret; -} - sub libsvn_skip_unknown_revs { my $err = shift; my $errno = $err->apr_err(); diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index 183ae3b1c2..8d946d2aa5 100755 --- a/t/t9103-git-svn-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh @@ -26,6 +26,12 @@ test_expect_success 'initialize repo' " git-svn multi-fetch " +test_expect_success 'multi-init set .git/config correctly' " + test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && + test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && + test '$svnrepo/tags' = '`git repo-config --get svn.tags`' + " + r1=`git-rev-list remotes/trunk | tail -n1` r2=`git-rev-list remotes/tags/a | tail -n1` r3=`git-rev-list remotes/a | tail -n1` -- cgit v1.2.3 From 5969cbe13c7e65db6441632d58e7dee40795a980 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:58:39 -0800 Subject: git-svn: convert show-ignore over to Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 02786f1a6a..e0bccbcdc9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -121,7 +121,7 @@ my %cmd = ( %cmt_opts, %fc_opts } ], 'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], - 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", + 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, @@ -537,12 +537,10 @@ sub dcommit { command_noisy(@finish, $gs); } -sub show_ignore { - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $repo; - $SVN ||= Git::SVN::Ra->new($SVN_URL); - my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; - libsvn_traverse_ignore(\*STDOUT, '', $r); +sub cmd_show_ignore { + my $gs = Git::SVN->new; + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + $gs->traverse_ignore(\*STDOUT, '', $r); } sub graft_branches { -- cgit v1.2.3 From f8c9d1d27f250a7fe13d4d33a1a94604ad355529 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:35:20 -0800 Subject: git-svn: moved the 'log' command into its own namespace More cleanup to separate out functionality and make things nicer to hack on. While we're at it, centralize loading of the authors into one place and correctly handle '(no author)' cases in when showing logs after-the-fact; and not just at commit time. Signed-off-by: Eric Wong --- git-svn.perl | 652 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 325 insertions(+), 327 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index e0bccbcdc9..3a4e41389b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -6,7 +6,8 @@ use strict; use vars qw/ $AUTHOR $VERSION $SVN_URL $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $GIT_SVN_DIR $REVDB/; + $GIT_DIR $GIT_SVN_DIR $REVDB + $_follow_parent $sha1 $sha1_short/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -15,7 +16,7 @@ $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); $ENV{GIT_DIR} = $GIT_DIR; my $LC_ALL = $ENV{LC_ALL}; -my $TZ = $ENV{TZ}; +$Git::SVN::Log::TZ = $ENV{TZ}; # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; @@ -46,7 +47,6 @@ use IO::File qw//; use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; -use POSIX qw/strftime/; use IPC::Open3; use Memoize; use Git; @@ -59,7 +59,7 @@ BEGIN { foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". - "*$_ = *Git::$_; "; + "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; "; } eval $s; } @@ -67,21 +67,18 @@ BEGIN { my ($SVN); 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 $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; +$sha1 = qr/[a-f\d]{40}/; +$sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_pager, $_color, $_prefix); -my (@_branch_from, %tree_map, %users, %rusers); + $_merge, $_strategy, $_dry_run, + $_prefix); +my (@_branch_from, %tree_map, %users); my @repo_path_split_cache; -use vars qw/$_follow_parent/; my %fc_opts = ( 'branch|b=s' => \@_branch_from, 'follow-parent|follow' => \$_follow_parent, @@ -93,7 +90,6 @@ my %fc_opts = ( 'branch|b=s' => \@_branch_from, 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, - 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -145,17 +141,17 @@ my %cmd = ( 'multi-fetch' => [ \&multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], - 'log' => [ \&show_log, 'Show commit logs', - { 'limit=i' => \$_limit, + 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', + { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, - 'verbose|v' => \$_verbose, - 'incremental' => \$_incremental, - 'oneline' => \$_oneline, - 'show-commit' => \$_show_commit, - 'non-recursive' => \$_non_recursive, + 'verbose|v' => \$Git::SVN::Log::verbose, + 'incremental' => \$Git::SVN::Log::incremental, + 'oneline' => \$Git::SVN::Log::oneline, + 'show-commit' => \$Git::SVN::Log::show_commit, + 'non-recursive' => \$Git::SVN::Log::non_recursive, 'authors-file|A=s' => \$_authors, - 'color' => \$_color, - 'pager=s' => \$_pager, + 'color' => \$Git::SVN::Log::color, + 'pager=s' => \$Git::SVN::Log::pager, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, @@ -607,81 +603,6 @@ sub multi_fetch { rec_fetch('', "$GIT_DIR/svn", @_); } -sub show_log { - my (@args) = @_; - my ($r_min, $r_max); - my $r_last = -1; # prevent dupes - rload_authors() if $_authors; - if (defined $TZ) { - $ENV{TZ} = $TZ; - } else { - delete $ENV{TZ}; - } - if (defined $_revision) { - if ($_revision =~ /^(\d+):(\d+)$/) { - ($r_min, $r_max) = ($1, $2); - } elsif ($_revision =~ /^\d+$/) { - $r_min = $r_max = $_revision; - } else { - print STDERR "-r$_revision is not supported, use ", - "standard \'git log\' arguments instead\n"; - exit 1; - } - } - - config_pager(); - @args = (git_svn_log_cmd($r_min, $r_max), @args); - my $log = command_output_pipe(@args); - run_pager(); - my (@k, $c, $d); - - while (<$log>) { - if (/^${_esc_color}commit ($sha1_short)/o) { - my $cmt = $1; - if ($c && cmt_showable($c) && $c->{r} != $r_last) { - $r_last = $c->{r}; - process_commit($c, $r_min, $r_max, \@k) or - goto out; - } - $d = undef; - $c = { c => $cmt }; - } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) { - get_author_info($c, $1, $2, $3); - } elsif (/^${_esc_color}(?:tree|parent|committer) /) { - # ignore - } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) { - push @{$c->{raw}}, $_; - } elsif (/^${_esc_color}[ACRMDT]\t/) { - # we could add $SVN->{svn_path} here, but that requires - # remote access at the moment (repo_path_split)... - s#^(${_esc_color})([ACRMDT])\t#$1 $2 #; - push @{$c->{changed}}, $_; - } elsif (/^${_esc_color}diff /) { - $d = 1; - push @{$c->{diff}}, $_; - } elsif ($d) { - push @{$c->{diff}}, $_; - } elsif (/^${_esc_color} (git-svn-id:.+)$/) { - ($c->{url}, $c->{r}, undef) = extract_metadata($1); - } elsif (s/^${_esc_color} //) { - push @{$c->{l}}, $_; - } - } - if ($c && defined $c->{r} && $c->{r} != $r_last) { - $r_last = $c->{r}; - process_commit($c, $r_min, $r_max, \@k); - } - if (@k) { - my $swap = $r_max; - $r_max = $r_min; - $r_min = $swap; - process_commit($_, $r_min, $r_max) foreach reverse @k; - } -out: - close $log; - print '-' x72,"\n" unless $_incremental || $_oneline; -} - sub commit_diff_usage { print STDERR "Usage: $0 commit-diff []\n"; exit 1 @@ -751,90 +672,6 @@ sub commit_diff { ########################### 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 = command(qw/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 log_use_color { - return 1 if $_color; - my ($dc, $dcvar); - $dcvar = 'color.diff'; - $dc = `git-config --get $dcvar`; - if ($dc eq '') { - # nothing at all; fallback to "diff.color" - $dcvar = 'diff.color'; - $dc = `git-config --get $dcvar`; - } - chomp($dc); - if ($dc eq 'auto') { - my $pc; - $pc = `git-config --get color.pager`; - if ($pc eq '') { - # does not have it -- fallback to pager.color - $pc = `git-config --bool --get pager.color`; - } - else { - $pc = `git-config --bool --get color.pager`; - if ($?) { - $pc = 'false'; - } - } - chomp($pc); - if (-t *STDOUT || (defined $_pager && $pc eq 'true')) { - return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); - } - return 0; - } - return 0 if $dc eq 'never'; - return 1 if $dc eq 'always'; - chomp($dc = `git-config --bool --get $dcvar`); - return ($dc eq 'true'); -} - -sub git_svn_log_cmd { - my ($r_min, $r_max) = @_; - my @cmd = (qw/log --abbrev-commit --pretty=raw - --default/, "refs/remotes/$GIT_SVN"); - push @cmd, '-r' unless $_non_recursive; - push @cmd, qw/--raw --name-status/ if $_verbose; - push @cmd, '--color' if log_use_color(); - 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 (defined $c_min && defined $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"; @@ -1484,22 +1321,16 @@ sub load_all_refs { # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; + my $log = $cmd eq 'log'; while (<$authors>) { chomp; next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); - $users{$user} = [$name, $email]; - } - close $authors or croak $!; -} - -sub rload_authors { - open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; - while (<$authors>) { - chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; - my ($user, $name, $email) = ($1, $2, $3); - $rusers{"$name <$email>"} = $user; + if ($log) { + $Git::SVN::Log::rusers{"$name <$email>"} = $user; + } else { + $users{$user} = [$name, $email]; + } } close $authors or croak $!; } @@ -1769,140 +1600,6 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -# adapted from pager.c -sub config_pager { - $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; - if (!defined $_pager) { - $_pager = 'less'; - } elsif (length $_pager == 0 || $_pager eq 'cat') { - $_pager = undef; - } -} - -sub run_pager { - return unless -t *STDOUT; - pipe my $rfd, my $wfd or return; - defined(my $pid = fork) or croak $!; - if (!$pid) { - open STDOUT, '>&', $wfd or croak $!; - return; - } - open STDIN, '<&', $rfd or croak $!; - $ENV{LESS} ||= 'FRSX'; - exec $_pager or croak "Can't run pager: $! ($_pager)\n"; -} - -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; - } - if (!$_a) { - ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/); - } - $dest->{t} = $t; - $dest->{tz} = $tz; - $dest->{a} = $_a; - # Date::Parse isn't in the standard Perl distro :( - if ($tz =~ s/^\+//) { - $t += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $t -= tz_to_s_offset($tz); - } - $dest->{t_utc} = $t; -} - -sub process_commit { - my ($c, $r_min, $r_max, $defer) = @_; - if (defined $r_min && defined $r_max) { - if ($r_min == $c->{r} && $r_min == $r_max) { - show_commit($c); - return 0; - } - return 1 if $r_min == $r_max; - if ($r_min < $r_max) { - # we need to reverse the print order - return 0 if (defined $_limit && --$_limit < 0); - push @$defer, $c; - return 1; - } - if ($r_min != $r_max) { - return 1 if ($r_min < $c->{r}); - return 1 if ($r_max > $c->{r}); - } - } - return 0 if (defined $_limit && --$_limit < 0); - show_commit($c); - return 1; -} - -sub show_commit { - my $c = shift; - if ($_oneline) { - my $x = "\n"; - if (my $l = $c->{l}) { - while ($l->[0] =~ /^\s*$/) { shift @$l } - $x = $l->[0]; - } - $_l_fmt ||= 'A' . length($c->{r}); - print 'r',pack($_l_fmt, $c->{r}),' | '; - print "$c->{c} | " if $_show_commit; - print $x; - } else { - show_commit_normal($c); - } -} - -sub show_commit_changed_paths { - my ($c) = @_; - return unless $c->{changed}; - print "Changed paths:\n", @{$c->{changed}}; -} - -sub show_commit_normal { - my ($c) = @_; - print '-' x72, "\nr$c->{r} | "; - print "$c->{c} | " if $_show_commit; - print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", - localtime($c->{t_utc})), ' | '; - my $nr_line = 0; - - if (my $l = $c->{l}) { - while ($l->[$#$l] eq "\n" && $#$l > 0 - && $l->[($#$l - 1)] eq "\n") { - pop @$l; - } - $nr_line = scalar @$l; - if (!$nr_line) { - print "1 line\n\n\n"; - } else { - if ($nr_line == 1) { - $nr_line = '1 line'; - } else { - $nr_line .= ' lines'; - } - print $nr_line, "\n"; - show_commit_changed_paths($c); - print "\n"; - print $_ foreach @$l; - } - } else { - print "1 line\n"; - show_commit_changed_paths($c); - print "\n"; - - } - foreach my $x (qw/raw diff/) { - if ($c->{$x}) { - print "\n"; - print $_ foreach @{$c->{$x}} - } - } -} - package Git::SVN; use strict; use warnings; @@ -3516,6 +3213,307 @@ sub can_do_switch { $can_do_switch; } +package Git::SVN::Log; +use strict; +use warnings; +use POSIX qw/strftime/; +use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline + %rusers $show_commit $incremental/; +my $l_fmt; + +sub cmt_showable { + my ($c) = @_; + return 1 if defined $c->{r}; + if ($c->{l} && $c->{l}->[-1] eq "...\n" && + $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { + my @msg = command(qw/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 log_use_color { + return 1 if $color; + my ($dc, $dcvar); + $dcvar = 'color.diff'; + $dc = `git-config --get $dcvar`; + if ($dc eq '') { + # nothing at all; fallback to "diff.color" + $dcvar = 'diff.color'; + $dc = `git-config --get $dcvar`; + } + chomp($dc); + if ($dc eq 'auto') { + my $pc; + $pc = `git-config --get color.pager`; + if ($pc eq '') { + # does not have it -- fallback to pager.color + $pc = `git-config --bool --get pager.color`; + } + else { + $pc = `git-config --bool --get color.pager`; + if ($?) { + $pc = 'false'; + } + } + chomp($pc); + if (-t *STDOUT || (defined $pager && $pc eq 'true')) { + return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); + } + return 0; + } + return 0 if $dc eq 'never'; + return 1 if $dc eq 'always'; + chomp($dc = `git-config --bool --get $dcvar`); + return ($dc eq 'true'); +} + +sub git_svn_log_cmd { + my ($r_min, $r_max) = @_; + my $gs = Git::SVN->_new; + my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, + $gs->refname); + push @cmd, '-r' unless $non_recursive; + push @cmd, qw/--raw --name-status/ if $verbose; + push @cmd, '--color' if log_use_color(); + return @cmd unless defined $r_max; + if ($r_max == $r_min) { + push @cmd, '--max-count=1'; + if (my $c = $gs->rev_db_get($r_max)) { + push @cmd, $c; + } + } else { + my ($c_min, $c_max); + $c_max = $gs->rev_db_get($r_max); + $c_min = $gs->rev_db_get($r_min); + if (defined $c_min && defined $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; +} + +# adapted from pager.c +sub config_pager { + $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; + if (!defined $pager) { + $pager = 'less'; + } elsif (length $pager == 0 || $pager eq 'cat') { + $pager = undef; + } +} + +sub run_pager { + return unless -t *STDOUT; + pipe my $rfd, my $wfd or return; + defined(my $pid = fork) or ::fatal "Can't fork: $!\n"; + if (!$pid) { + open STDOUT, '>&', $wfd or + ::fatal "Can't redirect to stdout: $!\n"; + return; + } + open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n"; + $ENV{LESS} ||= 'FRSX'; + exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; +} + +sub get_author_info { + my ($dest, $author, $t, $tz) = @_; + $author =~ s/(?:^\s*|\s*$)//g; + $dest->{a_raw} = $author; + my $au; + if ($_authors) { + $au = $rusers{$author} || undef; + } + if (!$au) { + ($au) = ($author =~ /<([^>]+)\@[^>]+>$/); + } + $dest->{t} = $t; + $dest->{tz} = $tz; + $dest->{a} = $au; + # Date::Parse isn't in the standard Perl distro :( + if ($tz =~ s/^\+//) { + $t += ::tz_to_s_offset($tz); + } elsif ($tz =~ s/^\-//) { + $t -= ::tz_to_s_offset($tz); + } + $dest->{t_utc} = $t; +} + +sub process_commit { + my ($c, $r_min, $r_max, $defer) = @_; + if (defined $r_min && defined $r_max) { + if ($r_min == $c->{r} && $r_min == $r_max) { + show_commit($c); + return 0; + } + return 1 if $r_min == $r_max; + if ($r_min < $r_max) { + # we need to reverse the print order + return 0 if (defined $limit && --$limit < 0); + push @$defer, $c; + return 1; + } + if ($r_min != $r_max) { + return 1 if ($r_min < $c->{r}); + return 1 if ($r_max > $c->{r}); + } + } + return 0 if (defined $limit && --$limit < 0); + show_commit($c); + return 1; +} + +sub show_commit { + my $c = shift; + if ($oneline) { + my $x = "\n"; + if (my $l = $c->{l}) { + while ($l->[0] =~ /^\s*$/) { shift @$l } + $x = $l->[0]; + } + $l_fmt ||= 'A' . length($c->{r}); + print 'r',pack($l_fmt, $c->{r}),' | '; + print "$c->{c} | " if $show_commit; + print $x; + } else { + show_commit_normal($c); + } +} + +sub show_commit_changed_paths { + my ($c) = @_; + return unless $c->{changed}; + print "Changed paths:\n", @{$c->{changed}}; +} + +sub show_commit_normal { + my ($c) = @_; + print '-' x72, "\nr$c->{r} | "; + print "$c->{c} | " if $show_commit; + print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", + localtime($c->{t_utc})), ' | '; + my $nr_line = 0; + + if (my $l = $c->{l}) { + while ($l->[$#$l] eq "\n" && $#$l > 0 + && $l->[($#$l - 1)] eq "\n") { + pop @$l; + } + $nr_line = scalar @$l; + if (!$nr_line) { + print "1 line\n\n\n"; + } else { + if ($nr_line == 1) { + $nr_line = '1 line'; + } else { + $nr_line .= ' lines'; + } + print $nr_line, "\n"; + show_commit_changed_paths($c); + print "\n"; + print $_ foreach @$l; + } + } else { + print "1 line\n"; + show_commit_changed_paths($c); + print "\n"; + + } + foreach my $x (qw/raw diff/) { + if ($c->{$x}) { + print "\n"; + print $_ foreach @{$c->{$x}} + } + } +} + +sub cmd_show_log { + my (@args) = @_; + my ($r_min, $r_max); + my $r_last = -1; # prevent dupes + if (defined $TZ) { + $ENV{TZ} = $TZ; + } else { + delete $ENV{TZ}; + } + if (defined $::_revision) { + if ($::_revision =~ /^(\d+):(\d+)$/) { + ($r_min, $r_max) = ($1, $2); + } elsif ($::_revision =~ /^\d+$/) { + $r_min = $r_max = $::_revision; + } else { + ::fatal "-r$::_revision is not supported, use ", + "standard \'git log\' arguments instead\n"; + } + } + + config_pager(); + @args = (git_svn_log_cmd($r_min, $r_max), @args); + my $log = command_output_pipe(@args); + run_pager(); + my (@k, $c, $d); + my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; + while (<$log>) { + if (/^${esc_color}commit ($::sha1_short)/o) { + my $cmt = $1; + if ($c && cmt_showable($c) && $c->{r} != $r_last) { + $r_last = $c->{r}; + process_commit($c, $r_min, $r_max, \@k) or + goto out; + } + $d = undef; + $c = { c => $cmt }; + } elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) { + get_author_info($c, $1, $2, $3); + } elsif (/^${esc_color}(?:tree|parent|committer) /o) { + # ignore + } elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) { + push @{$c->{raw}}, $_; + } elsif (/^${esc_color}[ACRMDT]\t/) { + # we could add $SVN->{svn_path} here, but that requires + # remote access at the moment (repo_path_split)... + s#^(${esc_color})([ACRMDT])\t#$1 $2 #o; + push @{$c->{changed}}, $_; + } elsif (/^${esc_color}diff /o) { + $d = 1; + push @{$c->{diff}}, $_; + } elsif ($d) { + push @{$c->{diff}}, $_; + } elsif (/^${esc_color} (git-svn-id:.+)$/o) { + ($c->{url}, $c->{r}, undef) = ::extract_metadata($1); + } elsif (s/^${esc_color} //o) { + push @{$c->{l}}, $_; + } + } + if ($c && defined $c->{r} && $c->{r} != $r_last) { + $r_last = $c->{r}; + process_commit($c, $r_min, $r_max, \@k); + } + if (@k) { + my $swap = $r_max; + $r_max = $r_min; + $r_min = $swap; + process_commit($_, $r_min, $r_max) foreach reverse @k; + } +out: + eval { command_close_pipe($log) }; + print '-' x72,"\n" unless $incremental || $oneline; +} + __END__ Data structures: -- cgit v1.2.3 From e7f023c81a03ccdd25ce4b4c7ed77f367c8f7edd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:49:01 -0800 Subject: git-svn: port the 'rebuild' command to use Git::SVN objects Also correctly shared some variables needed for Git::SVN::Log Signed-off-by: Eric Wong --- git-svn.perl | 71 ++++++++++++++++++++++++------------------------------------ 1 file changed, 28 insertions(+), 43 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 3a4e41389b..3e4f5b73ca 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -7,7 +7,8 @@ use vars qw/ $AUTHOR $VERSION $SVN_URL $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB - $_follow_parent $sha1 $sha1_short/; + $_follow_parent $sha1 $sha1_short $_revision + $_cp_remote $_upgrade/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -69,8 +70,8 @@ my ($SVN); my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; -my ($_revision,$_stdin,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, +my ($_stdin,$_help,$_rmdir,$_edit, + $_find_copies_harder, $_l, $_cp_similarity, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, @@ -119,7 +120,7 @@ my %cmd = ( { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", + rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], 'graft-branches' => [ \&graft_branches, @@ -223,53 +224,48 @@ sub version { exit 0; } -sub rebuild { - if (!verify_ref("refs/remotes/$GIT_SVN^0")) { - copy_remote_ref(); +sub cmd_rebuild { + my $url = shift; + my $gs = $url ? Git::SVN->init(undef, $url) + : eval { Git::SVN->new }; + $gs ||= Git::SVN->_new; + if (!verify_ref($gs->refname.'^0')) { + $gs->copy_remote_ref; } - $SVN_URL = shift or undef; - my $newest_rev = 0; if ($_upgrade) { - command_noisy('update-ref',"refs/remotes/$GIT_SVN"," - $GIT_SVN-HEAD"); + command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD'); } else { - check_upgrade_needed(); + $gs->check_upgrade_needed; } - my ($rev_list, $ctx) = command_output_pipe("rev-list", - "refs/remotes/$GIT_SVN"); + my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); my $latest; my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; - croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; - my @commit = grep(/^git-svn-id: /, - command(qw/cat-file commit/, $c)); - next if (!@commit); # skip merges - my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]); - if (!defined $rev || !$uuid) { - croak "Unable to extract revision or UUID from ", - "$c, $commit[$#commit]\n"; - } + fatal "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; + my ($url, $rev, $uuid) = cmt_metadata($c); + + # ignore merges (from set-tree) + next if (!defined $rev || !$uuid); # if we merged or otherwise started elsewhere, this is # how we break out of it - next if (defined $svn_uuid && ($uuid ne $svn_uuid)); - next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); + if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + ($gs->{url} && $url && ($url ne $gs->{url}))) { + next; + } unless (defined $latest) { - if (!$SVN_URL && !$url) { - croak "SVN repository location required: $url\n"; + if (!$gs->{url} && !$url) { + fatal "SVN repository location required\n"; } - $SVN_URL ||= $url; - $svn_uuid ||= $uuid; - setup_git_svn(); + $gs = Git::SVN->init(undef, $url); $latest = $rev; } - revdb_set($REVDB, $rev, $c); + $gs->rev_db_set($rev, $c); print "r$rev = $c\n"; - $newest_rev = $rev if ($rev > $newest_rev); } command_close_pipe($rev_list, $ctx); } @@ -2617,17 +2613,6 @@ sub revdb_get { return $ret; } -sub copy_remote_ref { - my $origin = $_cp_remote ? $_cp_remote : 'origin'; - my $ref = "refs/remotes/$GIT_SVN"; - if (command('ls-remote', $origin, $ref)) { - command_noisy('fetch', $origin, "$ref:$ref"); - } elsif ($_cp_remote && !$_upgrade) { - die "Unable to find remote reference: ", - "refs/remotes/$GIT_SVN on $origin\n"; - } -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. -- cgit v1.2.3 From c843c464b83237fba65dc46a10133fda9f475cc5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 03:07:31 -0800 Subject: git-svn: do not let Git.pm warn if we prematurely close pipes This mainly quiets down warnings when running git svn log. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 3e4f5b73ca..dd639a1b9a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3495,7 +3495,7 @@ sub cmd_show_log { process_commit($_, $r_min, $r_max) foreach reverse @k; } out: - eval { command_close_pipe($log) }; + close $log; print '-' x72,"\n" unless $incremental || $oneline; } -- cgit v1.2.3 From 44320b9e0e279cd1f549e259d00753a02c86c21b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 22:35:53 -0800 Subject: git-svn: convert the 'commit-diff' command to Git::SVN Also, convert all usage of 'log_msg' to 'log_entry' for consistency's sake SVN::Git::Editor::apply_diff now drives the rest of the editor. Signed-off-by: Eric Wong --- git-svn.perl | 366 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 233 insertions(+), 133 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index dd639a1b9a..575d7936db 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,7 +8,8 @@ use vars qw/ $AUTHOR $VERSION $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade/; + $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity + $_find_copies_harder $_l/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -70,9 +71,8 @@ my ($SVN); my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; -my ($_stdin,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_cp_similarity, - $_repack, $_repack_nr, $_repack_flags, $_q, +my ($_stdin, $_help, $_edit, + $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, @@ -154,7 +154,8 @@ my %cmd = ( 'color' => \$Git::SVN::Log::color, 'pager=s' => \$Git::SVN::Log::pager, } ], - 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', + 'commit-diff' => [ \&cmd_commit_diff, + 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, 'revision|r=s' => \$_revision, @@ -354,18 +355,18 @@ sub fetch_lib { # on the limiter. $SVN->dup->get_log([''], $min, $max, 0, 1, 1, sub { - my $log_msg; + my $log_entry; if ($last_commit) { - $log_msg = libsvn_fetch( + $log_entry = libsvn_fetch( $last_commit, @_); $last_commit = git_commit( - $log_msg, + $log_entry, $last_commit, @parents); } else { - $log_msg = libsvn_new_tree(@_); + $log_entry = libsvn_new_tree(@_); $last_commit = git_commit( - $log_msg, @parents); + $log_entry, @parents); } }); exit 0; @@ -428,7 +429,7 @@ sub commit_lib { my $repo; set_svn_commit_env(); foreach my $c (@revs) { - my $log_msg = get_commit_message($c, $commit_msg); + my $log_entry = get_commit_entry($c, $commit_msg); # fork for each commit because there's a memory leak I # can't track down... (it's probably in the SVN code) @@ -438,25 +439,21 @@ sub commit_lib { my $ed = SVN::Git::Editor->new( { r => $r_last, ra => $SVN->dup, - c => $c, svn_path => $SVN->{svn_path}, }, $SVN->get_commit_editor( - $log_msg->{msg}, + $log_entry->{log}, sub { libsvn_commit_cb( @_, $c, - $log_msg->{msg}, + $log_entry->{log}, $r_last, $cmt_last) }, $pool) ); - my $mods = libsvn_checkout_tree($cmt_last, $c, $ed); + my $mods = $ed->apply_diff($cmt_last, $c); if (@$mods == 0) { print "No changes\nr$r_last = $cmt_last\n"; - $ed->abort_edit; - } else { - $ed->close_edit; } $pool->clear; exit 0; @@ -599,6 +596,55 @@ sub multi_fetch { rec_fetch('', "$GIT_DIR/svn", @_); } +# this command is special because it requires no metadata +sub cmd_commit_diff { + my ($ta, $tb, $url) = @_; + my $usage = "Usage: $0 commit-diff -r ". + " []\n"; + fatal($usage) if (!defined $ta || !defined $tb); + if (!defined $url) { + my $gs = eval { Git::SVN->new }; + if (!$gs) { + fatal("Needed URL or usable git-svn --id in ", + "the command-line\n", $usage); + } + $url = $gs->{url}; + } + unless (defined $_revision) { + fatal("-r|--revision is a required argument\n", $usage); + } + if (defined $_message && defined $_file) { + fatal("Both --message/-m and --file/-F specified ", + "for the commit message.\n", + "I have no idea what you mean\n"); + } + if (defined $_file) { + $_message = file_to_s($_file); + } else { + $_message ||= get_commit_entry($tb)->{log}; + } + my $ra ||= Git::SVN::Ra->new($url); + my $r = $_revision; + if ($r eq 'HEAD') { + $r = $ra->get_latest_revnum; + } elsif ($r !~ /^\d+$/) { + die "revision argument: $r not understood by git-svn\n"; + } + my $pool = SVN::Pool->new; + my %ed_opts = ( r => $r, + ra => $ra->dup, + svn_path => $ra->{svn_path} ); + my $ed = SVN::Git::Editor->new(\%ed_opts, + $ra->get_commit_editor($_message, + sub { print "Committed r$_[0]\n" }), + $pool); + my $mods = $ed->apply_diff($ta, $tb); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + } + $pool->clear; +} + sub commit_diff_usage { print STDERR "Usage: $0 commit-diff []\n"; exit 1 @@ -628,8 +674,8 @@ sub commit_diff { if (defined $_file) { $_message = file_to_s($_file); } else { - $_message ||= get_commit_message($tb, - "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; + $_message ||= get_commit_entry($tb, + "$GIT_DIR/.svn-commit.tmp.$$")->{log}; } $SVN ||= Git::SVN::Ra->new($SVN_URL); if ($r eq 'HEAD') { @@ -641,7 +687,6 @@ sub commit_diff { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $r, ra => $SVN->dup, - c => $tb, svn_path => $SVN->{svn_path} }, $SVN->get_commit_editor($_message, @@ -652,12 +697,9 @@ sub commit_diff { $pool) ); eval { - my $mods = libsvn_checkout_tree($ta, $tb, $ed); + my $mods = $ed->apply_diff($ta, $tb); if (@$mods == 0) { print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; } }; $pool->clear; @@ -963,7 +1005,7 @@ sub setup_git_svn { sub get_tree_from_treeish { my ($treeish) = @_; - croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o; + # $treeish can be a symbolic ref, too: my $type = command_oneline(qw/cat-file -t/, $treeish); my $expected; while ($type eq 'tag') { @@ -972,7 +1014,7 @@ sub get_tree_from_treeish { if ($type eq 'commit') { $expected = (grep /^tree /, command(qw/cat-file commit/, $treeish))[0]; - ($expected) = ($expected =~ /^tree ($sha1)$/); + ($expected) = ($expected =~ /^tree ($sha1)$/o); die "Unable to get tree from $treeish\n" unless $expected; } elsif ($type eq 'tree') { $expected = $treeish; @@ -1034,58 +1076,44 @@ sub get_diff { return \@mods; } -sub libsvn_checkout_tree { - 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) { - my $f = $m->{chg}; - if (defined $o{$f}) { - $ed->$f($m, $_q); - } else { - croak "Invalid change type: $f\n"; - } - } - $ed->rmdirs($_q) if $_rmdir; - return $mods; -} - -sub get_commit_message { - my ($commit, $commit_msg) = (@_); - my %log_msg = ( msg => '' ); - open my $msg, '>', $commit_msg or croak $!; +sub get_commit_entry { + my ($treeish) = shift; + my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); + my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG"; + my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG"; + open my $log_fh, '>', $commit_editmsg or croak $!; - my $type = command_oneline(qw/cat-file -t/, $commit); + my $type = command_oneline(qw/cat-file -t/, $treeish); if ($type eq 'commit' || $type eq 'tag') { my ($msg_fh, $ctx) = command_output_pipe('cat-file', - $type, $commit); + $type, $treeish); my $in_msg = 0; while (<$msg_fh>) { if (!$in_msg) { $in_msg = 1 if (/^\s*$/); } elsif (/^git-svn-id: /) { - # skip this, we regenerate the correct one - # on re-fetch anyways + # skip this for now, we regenerate the + # correct one on re-fetch anyways + # TODO: set *:merge properties or like... } else { - print $msg $_ or croak $!; + print $log_fh $_ or croak $!; } } command_close_pipe($msg_fh, $ctx); } - close $msg or croak $!; + close $log_fh or croak $!; if ($_edit || ($type eq 'tree')) { my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi'; - system($editor, $commit_msg); + # TODO: strip out spaces, comments, like git-commit.sh + system($editor, $commit_editmsg); } - - # file_to_s removes all trailing newlines, so just use chomp() here: - open $msg, '<', $commit_msg or croak $!; - { local $/; chomp($log_msg{msg} = <$msg>); } - close $msg or croak $!; - - return \%log_msg; + rename $commit_editmsg, $commit_msg or croak $!; + open $log_fh, '<', $commit_msg or croak $!; + { local $/; chomp($log_entry{log} = <$log_fh>); } + close $log_fh or croak $!; + unlink $commit_msg; + \%log_entry; } sub set_svn_commit_env { @@ -1150,12 +1178,12 @@ sub assert_revision_unknown { } sub git_commit { - my ($log_msg, @parents) = @_; - assert_revision_unknown($log_msg->{revision}); + my ($log_entry, @parents) = @_; + assert_revision_unknown($log_entry->{revision}); map_tree_joins() if (@_branch_from && !%tree_map); my (@tmp_parents, @exec_parents, %seen_parent); - if (my $lparents = $log_msg->{parents}) { + if (my $lparents = $log_entry->{parents}) { @tmp_parents = @$lparents } # commit parents can be conditionally bound to a particular @@ -1163,14 +1191,14 @@ sub git_commit { foreach my $p (@parents) { next unless defined $p; if ($p =~ /^(\d+)=($sha1_short)$/o) { - if ($1 == $log_msg->{revision}) { + if ($1 == $log_entry->{revision}) { push @tmp_parents, $2; } } else { push @tmp_parents, $p if $p =~ /$sha1_short/o; } } - my $tree = $log_msg->{tree}; + my $tree = $log_entry->{tree}; if (!defined $tree) { my $index = set_index($GIT_SVN_INDEX); $tree = command_oneline('write-tree'); @@ -1197,7 +1225,7 @@ sub git_commit { next if $skip; my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); next if (($SVN->uuid eq $uuid_p) && - ($log_msg->{revision} > $r_p)); + ($log_entry->{revision} > $r_p)); next if (defined $url_p && defined $SVN_URL && ($SVN->uuid eq $uuid_p) && ($url_p eq $SVN_URL)); @@ -1212,14 +1240,14 @@ sub git_commit { last if @exec_parents > 16; } - set_commit_env($log_msg); + set_commit_env($log_entry); my @exec = ('git-commit-tree', $tree); push @exec, '-p', $_ foreach @exec_parents; defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; - print $msg_fh $log_msg->{msg} or croak $!; + print $msg_fh $log_entry->{log} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision} ", + print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_entry->{revision} ", $SVN->uuid,"\n" or croak $!; } $msg_fh->flush == 0 or croak $!; @@ -1232,10 +1260,10 @@ sub git_commit { die "Failed to commit, invalid sha1: $commit\n"; } command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit); - revdb_set($REVDB, $log_msg->{revision}, $commit); + revdb_set($REVDB, $log_entry->{revision}, $commit); # this output is read via pipe, do not change: - print "r$log_msg->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit\n"; return $commit; } @@ -1248,8 +1276,8 @@ sub check_repack { } sub set_commit_env { - my ($log_msg) = @_; - my $author = $log_msg->{author}; + my ($log_entry) = @_; + my $author = $log_entry->{author}; if (!defined $author || length $author == 0) { $author = '(no author)'; } @@ -1257,7 +1285,7 @@ sub set_commit_env { : ($author,$author . '@' . $SVN->uuid); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; } sub check_upgrade_needed { @@ -1767,14 +1795,14 @@ sub assert_index_clean { } sub get_commit_parents { - my ($self, $log_msg, @parents) = @_; + my ($self, $log_entry, @parents) = @_; my (%seen, @ret, @tmp); # commit parents can be conditionally bound to a particular # svn revision via: "svn_revno=commit_sha1", filter them out here: foreach my $p (@parents) { next unless defined $p; if ($p =~ /^(\d+)=($::sha1_short)$/o) { - push @tmp, $2 if $1 == $log_msg->{revision}; + push @tmp, $2 if $1 == $log_entry->{revision}; } else { push @tmp, $p if $p =~ /^$::sha1_short$/o; } @@ -1782,7 +1810,7 @@ sub get_commit_parents { if (my $cur = ::verify_ref($self->refname.'^0')) { push @tmp, $cur; } - push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); + push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp); while (my $p = shift @tmp) { next if $seen{$p}; $seen{$p} = 1; @@ -1791,7 +1819,7 @@ sub get_commit_parents { last if @ret >= 16; } if (@tmp) { - die "r$log_msg->{revision}: No room for parents:\n\t", + die "r$log_entry->{revision}: No room for parents:\n\t", join("\n\t", @tmp), "\n"; } @ret; @@ -1812,17 +1840,18 @@ sub check_upgrade_needed { } sub do_git_commit { - my ($self, $log_msg, @parents) = @_; - if (my $c = $self->rev_db_get($log_msg->{revision})) { - croak "$log_msg->{revision} = $c already exists! ", + my ($self, $log_entry, @parents) = @_; + if (my $c = $self->rev_db_get($log_entry->{revision})) { + croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = ::author_name_email($log_msg->{author}, $self->ra); + my ($name, $email) = ::author_name_email($log_entry->{author}, + $self->ra); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; - my $tree = $log_msg->{tree}; + my $tree = $log_entry->{tree}; if (!defined $tree) { $tree = $self->tmp_index_do(sub { command_oneline('write-tree') }); @@ -1830,14 +1859,15 @@ sub do_git_commit { die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; my @exec = ('git-commit-tree', $tree); - foreach ($self->get_commit_parents($log_msg, @parents)) { + foreach ($self->get_commit_parents($log_entry, @parents)) { push @exec, '-p', $_; } defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; - print $msg_fh $log_msg->{log} or croak $!; - print $msg_fh "\ngit-svn-id: $self->{ra}->{url}\@$log_msg->{revision}", - " ", $self->ra->uuid,"\n" or croak $!; + print $msg_fh $log_entry->{log} or croak $!; + print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@', + $log_entry->{revision}, ' ', + $self->ra->uuid, "\n" or croak $!; $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; chomp(my $commit = do { local $/; <$out_fh> }); @@ -1849,16 +1879,16 @@ sub do_git_commit { } command_noisy('update-ref',$self->refname, $commit); - $self->rev_db_set($log_msg->{revision}, $commit); + $self->rev_db_set($log_entry->{revision}, $commit); - $self->{last_rev} = $log_msg->{revision}; + $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_msg->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit\n"; return $commit; } sub do_fetch { - my ($self, $paths, $rev) = @_; #, $author, $date, $msg) = @_; + my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_; my $ed = SVN::Git::Fetcher->new($self); my ($last_rev, @parents); if ($self->{last_commit}) { @@ -1958,7 +1988,7 @@ sub fetch { while (1) { my @revs; $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { - my ($paths, $rev, $author, $date, $msg) = @_; + my ($paths, $rev, $author, $date, $log) = @_; push @revs, $rev }); foreach (@revs) { my $log_entry = $self->do_fetch(undef, $_); @@ -1993,7 +2023,6 @@ sub set_tree { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, ra => $self->ra->dup, - c => $tree, svn_path => $self->ra->{svn_path} }, $self->ra->get_commit_editor( @@ -2226,7 +2255,7 @@ sub uri_decode { } sub libsvn_log_entry { - my ($rev, $author, $date, $msg, $parents, $untracked) = @_; + my ($rev, $author, $date, $log, $parents, $untracked) = @_; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; @@ -2234,7 +2263,7 @@ sub libsvn_log_entry { defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } - $msg = '' if ($rev == 0 && !defined $msg); + $log = '' if ($rev == 0 && !defined $log); open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; my $h; @@ -2290,18 +2319,18 @@ sub libsvn_log_entry { close $un or croak $!; { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, msg => $msg."\n", parents => $parents || [], + author => $author, log => $log."\n", parents => $parents || [], revprops => $rp } } sub libsvn_fetch { - my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; + my ($last_commit, $paths, $rev, $author, $date, $log) = @_; my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); my (undef, $last_rev, undef) = cmt_metadata($last_commit); unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); + libsvn_log_entry($rev, $author, $date, $log, [$last_commit], $ed); } sub svn_grab_base_rev { @@ -2390,7 +2419,7 @@ sub revisions_eq { } sub libsvn_find_parent_branch { - my ($paths, $rev, $author, $date, $msg) = @_; + my ($paths, $rev, $author, $date, $log) = @_; my $svn_path = '/'.$SVN->{svn_path}; # look for a parent from another branch: @@ -2442,7 +2471,7 @@ sub libsvn_find_parent_branch { command_noisy('read-tree', $parent); unless ($SVN->can_do_switch) { return _libsvn_new_tree($paths, $rev, $author, $date, - $msg, [$parent]); + $log, [$parent]); } # do_switch works with svn/trunk >= r22312, but that is not # included with SVN 1.4.2 (the latest version at the moment), @@ -2451,7 +2480,7 @@ sub libsvn_find_parent_branch { my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or die "SVN connection failed somewhere...\n"; - return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); + return libsvn_log_entry($rev, $author, $date, $log, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; @@ -2461,17 +2490,17 @@ sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; } - my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last - _libsvn_new_tree($paths, $rev, $author, $date, $msg, []); + my ($paths, $rev, $author, $date, $log) = @_; # $pool is last + _libsvn_new_tree($paths, $rev, $author, $date, $log, []); } sub _libsvn_new_tree { - my ($paths, $rev, $author, $date, $msg, $parents) = @_; + my ($paths, $rev, $author, $date, $log, $parents) = @_; my $ed = SVN::Git::Fetcher->new({q => $_q}); unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed); + libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); } sub find_graft_path_commit { @@ -2536,9 +2565,9 @@ sub restore_index { } sub libsvn_commit_cb { - my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_; + my ($rev, $date, $committer, $c, $log, $r_last, $cmt_last) = @_; if ($_optimize_commits && $rev == ($r_last + 1)) { - my $log = libsvn_log_entry($rev,$committer,$date,$msg); + my $log = libsvn_log_entry($rev,$committer,$date,$log); $log->{tree} = get_tree_from_treeish($c); my $cmt = git_commit($log, $cmt_last, $c); my @diff = command('diff-tree', $cmt, $c); @@ -2843,7 +2872,7 @@ sub new { my $git_svn = shift; my $self = SVN::Delta::Editor->new(@_); bless $self, $class; - foreach (qw/svn_path c r ra /) { + foreach (qw/svn_path r ra/) { die "$_ required!\n" unless (defined $git_svn->{$_}); $self->{$_} = $git_svn->{$_}; } @@ -2868,7 +2897,7 @@ sub url_path { } sub rmdirs { - my ($self, $q) = @_; + my ($self, $tree_b) = @_; my $rm = $self->{rm}; delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; @@ -2887,7 +2916,7 @@ sub rmdirs { return unless %$rm; my ($fh, $ctx) = command_output_pipe( - qw/ls-tree --name-only -r -z/, $self->{c}); + qw/ls-tree --name-only -r -z/, $tree_b); local $/ = "\0"; while (<$fh>) { chomp; @@ -2906,7 +2935,7 @@ sub rmdirs { foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) { $self->close_directory($bat->{$d}, $p); my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#); - print "\tD+\t$d/\n" unless $q; + print "\tD+\t$d/\n" unless $::_q; $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p); delete $bat->{$d}; } @@ -2945,23 +2974,23 @@ sub ensure_path { } sub A { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, undef, -1); - print "\tA\t$m->{file_b}\n" unless $q; + print "\tA\t$m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } sub C { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); - print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q; + print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -2975,12 +3004,12 @@ sub delete_entry { } sub R { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); - print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q; + print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); @@ -2990,12 +3019,12 @@ sub R { } sub M { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->open_file($self->repo_path($m->{file_b}), $pbat,$self->{r},$self->{pool}); - print "\t$m->{chg}\t$m->{file_b}\n" unless $q; + print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -3046,10 +3075,10 @@ sub chg_file { } sub D { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); - print "\tD\t$m->{file_b}\n" unless $q; + print "\tD\t$m->{file_b}\n" unless $::_q; $self->delete_entry($m->{file_b}, $pbat); } @@ -3069,6 +3098,77 @@ sub abort_edit { $self->{pool}->clear; } +# this drives the editor +sub apply_diff { + my ($self, $tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($::_cp_similarity) { + push @diff_tree, "-C$::_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; + push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + my $nl = $/; + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s + $::sha1\s($::sha1)\s + ([MTCRAD])\d*$/xo) { + push @mods, { mode_a => $1, mode_b => $2, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + $/ = $nl; + + 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) { + my $f = $m->{chg}; + if (defined $o{$f}) { + $self->$f($m); + } else { + fatal("Invalid change type: $f\n"); + } + } + $self->rmdirs($tree_b) if $::_rmdir; + if (@mods == 0) { + $self->abort_edit; + } else { + $self->close_edit; + } + \@mods; +} + package Git::SVN::Ra; use vars qw/@ISA $config_dir/; use strict; @@ -3144,9 +3244,9 @@ sub get_log { } sub get_commit_editor { - my ($self, $msg, $cb, $pool) = @_; + my ($self, $log, $cb, $pool) = @_; my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); - $self->SUPER::get_commit_editor($msg, $cb, @lock, $pool); + $self->SUPER::get_commit_editor($log, $cb, @lock, $pool); } sub uuid { @@ -3211,13 +3311,13 @@ sub cmt_showable { return 1 if defined $c->{r}; if ($c->{l} && $c->{l}->[-1] eq "...\n" && $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { - my @msg = command(qw/cat-file commit/, $c->{c}); - shift @msg while ($msg[0] ne "\n"); - shift @msg; - @{$c->{l}} = grep !/^git-svn-id: /, @msg; + my @log = command(qw/cat-file commit/, $c->{c}); + shift @log while ($log[0] ne "\n"); + shift @log; + @{$c->{l}} = grep !/^git-svn-id: /, @log; (undef, $c->{r}, undef) = ::extract_metadata( - (grep(/^git-svn-id: /, @msg))[-1]); + (grep(/^git-svn-id: /, @log))[-1]); } return defined $c->{r}; } @@ -3503,9 +3603,9 @@ __END__ Data structures: -$log_msg hashref as returned by libsvn_log_entry() +$log_entry hashref as returned by libsvn_log_entry() { - msg => 'whitespace-formatted log entry + log => 'whitespace-formatted log entry ', # trailing newline is preserved revision => '8', # integer date => '2004-02-24T17:01:44.108345Z', # commit date -- cgit v1.2.3 From 396988e0b9cd00d5d13edb157b91dbd5050ef99f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 23:00:47 -0800 Subject: git-svn: get rid of Memoize for now... I may refactor more of this stuff into separate modules --- git-svn.perl | 4 ---- 1 file changed, 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 575d7936db..acc93b9c10 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -50,11 +50,7 @@ use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use IPC::Open3; -use Memoize; use Git; -memoize('revisions_eq'); -memoize('cmt_metadata'); -memoize('get_commit_time'); BEGIN { my $s; -- cgit v1.2.3 From 1c8443b05074cfa466845c4dad98fe962f6dd4c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 02:17:00 -0800 Subject: git-svn: fetch/multi-fetch converted over to Git::SVN module --follow-parent and commit-diff are currently broken with this commit... Signed-off-by: Eric Wong --- git-svn.perl | 89 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 38 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index acc93b9c10..bc3504fd9f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,7 +9,7 @@ use vars qw/ $AUTHOR $VERSION $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity - $_find_copies_harder $_l/; + $_find_copies_harder $_l $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -71,10 +71,10 @@ my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, + $_version, $_upgrade, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_prefix); -my (@_branch_from, %tree_map, %users); +my (@_branch_from, %tree_map); my @repo_path_split_cache; my %fc_opts = ( 'branch|b=s' => \@_branch_from, @@ -135,7 +135,7 @@ my %cmd = ( 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], - 'multi-fetch' => [ \&multi_fetch, + 'multi-fetch' => [ \&cmd_multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', @@ -292,7 +292,12 @@ sub cmd_init { } sub cmd_fetch { - fetch_child_id($GIT_SVN, @_); + my $gs = Git::SVN->new; + $gs->fetch(@_); + if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { + command_noisy(qw(update-ref refs/heads/master), + $gs->{last_commit}); + } } sub fetch { @@ -583,13 +588,14 @@ sub cmd_multi_init { complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } -sub multi_fetch { +sub cmd_multi_fetch { # try to do trunk first, since branches/tags # may be descended from it. - if (-e "$GIT_DIR/svn/trunk/info/url") { - fetch_child_id('trunk', @_); + if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") { + my $gs = Git::SVN->new('trunk'); + $gs->fetch(@_); } - rec_fetch('', "$GIT_DIR/svn", @_); + rec_fetch('', "$ENV{GIT_DIR}/svn", @_); } # this command is special because it requires no metadata @@ -706,24 +712,6 @@ sub commit_diff { ########################### utility functions ######################### -sub fetch_child_id { - my $id = shift; - print "Fetching $id\n"; - my $ref = "$GIT_DIR/refs/remotes/$id"; - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - fetch(@_); - exit 0; - } - while (<$fh>) { - print $_; - check_repack() if (/^r\d+ = $sha1/o); - } - close $fh or croak $?; -} - sub rec_fetch { my ($pfx, $p, @args) = @_; my @dir; @@ -732,15 +720,16 @@ sub rec_fetch { $pfx .= '/' if $pfx && $pfx !~ m!/$!; my $id = $pfx . basename $_; next if $id eq 'trunk'; - fetch_child_id($id, @args); + my $gs = Git::SVN->new($id); + $gs->fetch(@args); } elsif (-d $_) { push @dir, $_; } } foreach (@dir) { my $x = $_; - $x =~ s!^\Q$GIT_DIR\E/svn/!!; - rec_fetch($x, $_); + $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + rec_fetch($x, $_, @args); } } @@ -1841,8 +1830,9 @@ sub do_git_commit { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = ::author_name_email($log_entry->{author}, - $self->ra); + my $author = $log_entry->{author}; + my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} + : ($author, "$author\@".$self->ra->uuid)); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; @@ -1894,7 +1884,7 @@ sub do_fetch { } else { $last_rev = $rev; } - unless ($self->ra->do_update($last_rev, $rev, '', 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1946,6 +1936,25 @@ sub write_untracked { } } +sub parse_svn_date { + my $date = shift || return '+0000 1970-01-01 00:00:00'; + my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T + (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or + croak "Unable to parse date: $date\n"; + "+0000 $Y-$m-$d $H:$M:$S"; +} + +sub check_author { + my ($author) = @_; + if (!defined $author || length $author == 0) { + $author = '(no author)'; + } + if (defined $::_authors && ! defined $::users{$author}) { + die "Author: $author not defined in $::_authors file\n"; + } + $author; +} + sub make_log_entry { my ($self, $rev, $parents, $untracked) = @_; my $rp = $self->ra->rev_proplist($rev); @@ -2105,6 +2114,11 @@ sub _new { db_path => "$dir/.rev_db" }, $class; } +sub uri_encode { + my ($f) = @_; + $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; + $f +} package Git::SVN::Prompt; use strict; @@ -2662,15 +2676,14 @@ sub new { my ($class, $git_svn) = @_; my $self = SVN::Delta::Editor->new; bless $self, $class; - $self->{c} = $git_svn->{c} if exists $git_svn->{c}; - $self->{q} = $git_svn->{q}; + $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; $self->{absent_dir} = {}; $self->{absent_file} = {}; - ($self->{gui}, $self->{ctx}) = command_input_pipe( - qw/update-index -z --index-info/); + ($self->{gui}, $self->{ctx}) = $git_svn->tmp_index_do( + sub { command_input_pipe(qw/update-index -z --index-info/) } ); require Digest::MD5; $self; } @@ -3416,7 +3429,7 @@ sub get_author_info { $author =~ s/(?:^\s*|\s*$)//g; $dest->{a_raw} = $author; my $au; - if ($_authors) { + if ($::_authors) { $au = $rusers{$author} || undef; } if (!$au) { -- cgit v1.2.3 From d7ad3bed8cfbaf21aeaaff2cd10e3696d8785b78 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 03:14:28 -0800 Subject: git-svn: switch dcommit to using Git::SVN code Signed-off-by: Eric Wong --- git-svn.perl | 122 +++++++++++++++++++---------------------------------------- 1 file changed, 38 insertions(+), 84 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index bc3504fd9f..bf53b2d69b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -107,7 +107,8 @@ my %cmd = ( init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], - dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + dcommit => [ \&cmd_dcommit, + 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, 'strategy|s=s' => \$_strategy, 'dry-run|n' => \$_dry_run, @@ -482,49 +483,65 @@ sub commit_lib { unlink $commit_msg; } -sub dcommit { - my $head = shift || 'HEAD'; - my $gs = "refs/remotes/$GIT_SVN"; - my @refs = command(qw/rev-list --no-merges/, "$gs..$head"); +sub cmd_dcommit { + my $head = shift; + my $gs = Git::SVN->new; + $head ||= 'HEAD'; + my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head"); my $last_rev; foreach my $d (reverse @refs) { if (!verify_ref("$d~1")) { - die "Commit $d\n", - "has no parent commit, and therefore ", - "nothing to diff against.\n", - "You should be working from a repository ", - "originally created by git-svn\n"; + fatal "Commit $d\n", + "has no parent commit, and therefore ", + "nothing to diff against.\n", + "You should be working from a repository ", + "originally created by git-svn\n"; } unless (defined $last_rev) { (undef, $last_rev, undef) = cmt_metadata("$d~1"); unless (defined $last_rev) { - die "Unable to extract revision information ", - "from commit $d~1\n"; + fatal "Unable to extract revision information ", + "from commit $d~1\n"; } } if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { - if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) { - $last_rev = $r; - } # else: no changes, same $last_rev + my $ra = $gs->ra; + my $pool = SVN::Pool->new; + my %ed_opts = ( r => $last_rev, + ra => $ra->dup, + svn_path => $ra->{svn_path} ); + my $ed = SVN::Git::Editor->new(\%ed_opts, + $ra->get_commit_editor($::_message, + sub { print "Committed r$_[0]\n"; + $last_rev = $_[0]; }), + $pool); + my $mods = $ed->apply_diff("$d~1", $d); + if (@$mods == 0) { + print "No changes\n$d~1 == $d\n"; + } } } return if $_dry_run; - fetch(); - my @diff = command('diff-tree', 'HEAD', $gs, '--'); + $gs->fetch; + # we always want to rebase against the current HEAD, not any + # head that was passed to us + my @diff = command('diff-tree', 'HEAD', $gs->refname, '--'); my @finish; if (@diff) { @finish = qw/rebase/; push @finish, qw/--merge/ if $_merge; push @finish, "--strategy=$_strategy" if $_strategy; - print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + print STDERR "W: HEAD and ", $gs->refname, " differ, ", + "using @finish:\n", "@diff"; } else { - print "No changes between current HEAD and $gs\n", - "Resetting to the latest $gs\n"; + print "No changes between current HEAD and ", + $gs->refname, "\nResetting to the latest ", + $gs->refname, "\n"; @finish = qw/reset --mixed/; } - command_noisy(@finish, $gs); + command_noisy(@finish, $gs->refname); } sub cmd_show_ignore { @@ -647,69 +664,6 @@ sub cmd_commit_diff { $pool->clear; } -sub commit_diff_usage { - print STDERR "Usage: $0 commit-diff []\n"; - exit 1 -} - -sub commit_diff { - my $ta = shift or commit_diff_usage(); - my $tb = shift or commit_diff_usage(); - if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) { - print STDERR "Needed URL or usable git-svn id command-line\n"; - commit_diff_usage(); - } - my $r = shift; - unless (defined $r) { - if (defined $_revision) { - $r = $_revision - } else { - die "-r|--revision is a required argument\n"; - } - } - if (defined $_message && defined $_file) { - print STDERR "Both --message/-m and --file/-F specified ", - "for the commit message.\n", - "I have no idea what you mean\n"; - exit 1; - } - if (defined $_file) { - $_message = file_to_s($_file); - } else { - $_message ||= get_commit_entry($tb, - "$GIT_DIR/.svn-commit.tmp.$$")->{log}; - } - $SVN ||= Git::SVN::Ra->new($SVN_URL); - if ($r eq 'HEAD') { - $r = $SVN->get_latest_revnum; - } elsif ($r !~ /^\d+$/) { - die "revision argument: $r not understood by git-svn\n"; - } - my $rev_committed; - my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new({ r => $r, - ra => $SVN->dup, - svn_path => $SVN->{svn_path} - }, - $SVN->get_commit_editor($_message, - sub { - $rev_committed = $_[0]; - print "Committed $_[0]\n"; - }, - $pool) - ); - eval { - my $mods = $ed->apply_diff($ta, $tb); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - } - }; - $pool->clear; - fatal "$@\n" if $@; - $_message = $_file = undef; - return $rev_committed; -} - ########################### utility functions ######################### sub rec_fetch { -- cgit v1.2.3 From 1ce255dc168cc1fcf849a7c82bdf45753b0dfe09 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 23:21:16 -0800 Subject: git-svn: convert 'set-tree' command to use Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 512 ++--------------------------------------------------------- 1 file changed, 17 insertions(+), 495 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index bf53b2d69b..261e33d023 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -24,16 +24,6 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -# properties that we do not log: -my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, - 'svn:special' => 1, - 'svn:executable' => 1, - 'svn:entry:committed-rev' => 1, - 'svn:entry:last-author' => 1, - 'svn:entry:uuid' => 1, - 'svn:entry:committed-date' => 1, -); - sub fatal (@) { print STDERR @_; exit 1 } require SVN::Core; # use()-ing this causes segfaults for me... *shrug* require SVN::Ra; @@ -113,8 +103,9 @@ my %cmd = ( 'strategy|s=s' => \$_strategy, 'dry-run|n' => \$_dry_run, %cmt_opts, %fc_opts } ], - 'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish", - { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], + 'set-tree' => [ \&cmd_set_tree, + "Set an SVN repository to a git tree-ish", + { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", @@ -301,94 +292,8 @@ sub cmd_fetch { } } -sub fetch { - check_upgrade_needed(); - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $ret = fetch_lib(@_); - if ($ret->{commit} && !verify_ref('refs/heads/master^0')) { - command_noisy(qw(update-ref refs/heads/master),$ret->{commit}); - } - return $ret; -} - -sub fetch_lib { - my (@parents) = @_; - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - $SVN ||= Git::SVN::Ra->new($SVN_URL); - my ($last_rev, $last_commit) = svn_grab_base_rev(); - my ($base, $head) = libsvn_parse_revision($last_rev); - if ($base > $head) { - return { revision => $last_rev, commit => $last_commit } - } - my $index = set_index($GIT_SVN_INDEX); - - # limit ourselves and also fork() since get_log won't release memory - # after processing a revision and SVN stuff seems to leak - my $inc = 1000; - my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - if (defined $last_commit) { - unless (-e $GIT_SVN_INDEX) { - command_noisy('read-tree', $last_commit); - } - my $x = command_oneline('write-tree'); - my ($y) = (command(qw/cat-file commit/, $last_commit) - =~ /^tree ($sha1)/m); - if ($y ne $x) { - unlink $GIT_SVN_INDEX or croak $!; - command_noisy('read-tree', $last_commit); - } - $x = command_oneline('write-tree'); - if ($y ne $x) { - print STDERR "trees ($last_commit) $y != $x\n", - "Something is seriously wrong...\n"; - } - } - while (1) { - # fork, because using SVN::Pool with get_log() still doesn't - # seem to help enough to keep memory usage down. - defined(my $pid = fork) or croak $!; - if (!$pid) { - $SVN::Error::handler = \&libsvn_skip_unknown_revs; - - # Yes I'm perfectly aware that the fourth argument - # below is the limit revisions number. Unfortunately - # performance sucks with it enabled, so it's much - # faster to fetch revision ranges instead of relying - # on the limiter. - $SVN->dup->get_log([''], $min, $max, 0, 1, 1, - sub { - my $log_entry; - if ($last_commit) { - $log_entry = libsvn_fetch( - $last_commit, @_); - $last_commit = git_commit( - $log_entry, - $last_commit, - @parents); - } else { - $log_entry = libsvn_new_tree(@_); - $last_commit = git_commit( - $log_entry, @parents); - } - }); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - ($last_rev, $last_commit) = svn_grab_base_rev(); - last if ($max >= $head); - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - $SVN = Git::SVN::Ra->new($SVN_URL); - } - restore_index($index); - return { revision => $last_rev, commit => $last_commit }; -} - -sub commit { +sub cmd_set_tree { my (@commits) = @_; - check_upgrade_needed(); if ($_stdin || !@commits) { print "Reading from stdin...\n"; @commits = (); @@ -406,81 +311,20 @@ sub commit { } elsif (scalar @tmp > 1) { push @revs, reverse(command('rev-list',@tmp)); } else { - die "Failed to rev-parse $c\n"; + fatal "Failed to rev-parse $c\n"; } } - commit_lib(@revs); - print "Done committing ",scalar @revs," revisions to SVN\n"; -} - -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(); - if ($r_last != $fetched->{revision}) { - print STDERR "There are new revisions that were fetched ", - "and need to be merged (or acknowledged) ", - "before committing.\n", - "last rev: $r_last\n", - " current: $fetched->{revision}\n"; - exit 1; - } - my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; - - my $repo; - set_svn_commit_env(); - foreach my $c (@revs) { - my $log_entry = get_commit_entry($c, $commit_msg); - - # fork for each commit because there's a memory leak I - # can't track down... (it's probably in the SVN code) - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new( - { r => $r_last, - ra => $SVN->dup, - svn_path => $SVN->{svn_path}, - }, - $SVN->get_commit_editor( - $log_entry->{log}, - sub { - libsvn_commit_cb( - @_, $c, - $log_entry->{log}, - $r_last, - $cmt_last) - }, $pool) - ); - my $mods = $ed->apply_diff($cmt_last, $c); - if (@$mods == 0) { - print "No changes\nr$r_last = $cmt_last\n"; - } - $pool->clear; - exit 0; - } - my ($r_new, $cmt_new, $no); - while (<$fh>) { - print $_; - chomp; - if (/^r(\d+) = ($sha1)$/o) { - ($r_new, $cmt_new) = ($1, $2); - } elsif ($_ eq 'No changes') { - $no = 1; - } - } - close $fh or exit 1; - if (! defined $r_new && ! defined $cmt_new) { - unless ($no) { - die "Failed to parse revision information\n"; - } - } else { - ($r_last, $cmt_last) = ($r_new, $cmt_new); - } + my $gs = Git::SVN->new; + my ($r_last, $cmt_last) = $gs->last_rev_commit; + $gs->fetch; + if ($r_last != $gs->{last_rev}) { + fatal "There are new revisions that were fetched ", + "and need to be merged (or acknowledged) ", + "before committing.\nlast rev: $r_last\n", + " current: $gs->{last_rev}\n"; } - $ENV{LC_ALL} = 'C'; - unlink $commit_msg; + $gs->set_tree($_) foreach @revs; + print "Done committing ",scalar @revs," revisions to SVN\n"; } sub cmd_dcommit { @@ -1055,14 +899,6 @@ sub get_commit_entry { \%log_entry; } -sub set_svn_commit_env { - if (defined $LC_ALL) { - $ENV{LC_ALL} = $LC_ALL; - } else { - delete $ENV{LC_ALL}; - } -} - sub rev_list_raw { my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); return { fh => $fh, ctx => $c, t => { } }; @@ -1109,124 +945,6 @@ sub file_to_s { return $ret; } -sub assert_revision_unknown { - my $r = shift; - if (my $c = revdb_get($REVDB, $r)) { - croak "$r = $c already exists! Why are we refetching it?"; - } -} - -sub git_commit { - my ($log_entry, @parents) = @_; - assert_revision_unknown($log_entry->{revision}); - map_tree_joins() if (@_branch_from && !%tree_map); - - my (@tmp_parents, @exec_parents, %seen_parent); - if (my $lparents = $log_entry->{parents}) { - @tmp_parents = @$lparents - } - # commit parents can be conditionally bound to a particular - # svn revision via: "svn_revno=commit_sha1", filter them out here: - foreach my $p (@parents) { - next unless defined $p; - if ($p =~ /^(\d+)=($sha1_short)$/o) { - if ($1 == $log_entry->{revision}) { - push @tmp_parents, $2; - } - } else { - push @tmp_parents, $p if $p =~ /$sha1_short/o; - } - } - my $tree = $log_entry->{tree}; - if (!defined $tree) { - my $index = set_index($GIT_SVN_INDEX); - $tree = command_oneline('write-tree'); - croak $? if $?; - restore_index($index); - } - # just in case we clobber the existing ref, we still want that ref - # as our parent: - if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) { - chomp $cur; - push @tmp_parents, $cur; - } - - if (exists $tree_map{$tree}) { - foreach my $p (@{$tree_map{$tree}}) { - my $skip; - foreach (@tmp_parents) { - # see if a common parent is found - my $mb = eval { command('merge-base', $_, $p) }; - next if ($@ || $?); - $skip = 1; - last; - } - next if $skip; - my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); - next if (($SVN->uuid eq $uuid_p) && - ($log_entry->{revision} > $r_p)); - next if (defined $url_p && defined $SVN_URL && - ($SVN->uuid eq $uuid_p) && - ($url_p eq $SVN_URL)); - push @tmp_parents, $p; - } - } - foreach (@tmp_parents) { - next if $seen_parent{$_}; - $seen_parent{$_} = 1; - push @exec_parents, $_; - # MAXPARENT is defined to 16 in commit-tree.c: - last if @exec_parents > 16; - } - - set_commit_env($log_entry); - my @exec = ('git-commit-tree', $tree); - push @exec, '-p', $_ foreach @exec_parents; - defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) - or croak $!; - print $msg_fh $log_entry->{log} or croak $!; - unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_entry->{revision} ", - $SVN->uuid,"\n" or croak $!; - } - $msg_fh->flush == 0 or croak $!; - close $msg_fh or croak $!; - chomp(my $commit = do { local $/; <$out_fh> }); - close $out_fh or croak $!; - waitpid $pid, 0; - croak $? if $?; - if ($commit !~ /^$sha1$/o) { - die "Failed to commit, invalid sha1: $commit\n"; - } - command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit); - revdb_set($REVDB, $log_entry->{revision}, $commit); - - # this output is read via pipe, do not change: - print "r$log_entry->{revision} = $commit\n"; - return $commit; -} - -sub check_repack { - if ($_repack && (--$_repack_nr == 0)) { - $_repack_nr = $_repack; - # repack doesn't use any arguments with spaces in them, does it? - command_noisy('repack', split(/\s+/, $_repack_flags)); - } -} - -sub set_commit_env { - my ($log_entry) = @_; - my $author = $log_entry->{author}; - if (!defined $author || length $author == 0) { - $author = '(no author)'; - } - my ($name,$email) = defined $users{$author} ? @{$users{$author}} - : ($author,$author . '@' . $SVN->uuid); - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; -} - sub check_upgrade_needed { if (!-r $REVDB) { -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); @@ -1859,7 +1577,7 @@ sub write_untracked { foreach my $path (sort keys %$h) { my $ppath = $path eq '' ? '.' : $path; foreach my $prop (sort keys %{$h->{$path}}) { - next if $SKIP{$prop}; + next if $SKIP_PROP{$prop}; my $v = $h->{$path}->{$prop}; if (defined $v) { print $fh " +$t: ", @@ -1975,7 +1693,7 @@ sub set_tree_cb { sub set_tree { my ($self, $tree) = (shift, shift); - my $log_entry = get_commit_entry($tree); + my $log_entry = ::get_commit_entry($tree); unless ($self->{last_rev}) { fatal("Must have an existing revision to commit\n"); } @@ -2218,118 +1936,6 @@ sub uri_decode { $f } -sub libsvn_log_entry { - my ($rev, $author, $date, $log, $parents, $untracked) = @_; - my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T - (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) - or die "Unable to parse date: $date\n"; - if (defined $author && length $author > 0 && - defined $_authors && ! defined $users{$author}) { - die "Author: $author not defined in $_authors file\n"; - } - $log = '' if ($rev == 0 && !defined $log); - - open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; - my $h; - print $un "r$rev\n" or croak $!; - $h = $untracked->{empty}; - foreach (sort keys %$h) { - my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; - print $un " $act: ", uri_encode($_), "\n" or croak $!; - warn "W: $act: $_\n"; - } - foreach my $t (qw/dir_prop file_prop/) { - $h = $untracked->{$t} or next; - foreach my $path (sort keys %$h) { - my $ppath = $path eq '' ? '.' : $path; - foreach my $prop (sort keys %{$h->{$path}}) { - next if $SKIP{$prop}; - my $v = $h->{$path}->{$prop}; - if (defined $v) { - print $un " +$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), ' ', - uri_encode($v), "\n" - or croak $!; - } else { - print $un " -$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), "\n" - or croak $!; - } - } - } - } - foreach my $t (qw/absent_file absent_directory/) { - $h = $untracked->{$t} or next; - foreach my $parent (sort keys %$h) { - foreach my $path (sort @{$h->{$parent}}) { - print $un " $t: ", - uri_encode("$parent/$path"), "\n" - or croak $!; - warn "W: $t: $parent/$path ", - "Insufficient permissions?\n"; - } - } - } - - # revprops (make this optional? it's an extra network trip...) - my $rp = $SVN->rev_proplist($rev); - foreach (sort keys %$rp) { - next if /^svn:(?:author|date|log)$/; - print $un " rev_prop: ", uri_encode($_), ' ', - uri_encode($rp->{$_}), "\n"; - } - close $un or croak $!; - - { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, log => $log."\n", parents => $parents || [], - revprops => $rp } -} - -sub libsvn_fetch { - my ($last_commit, $paths, $rev, $author, $date, $log) = @_; - my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); - my (undef, $last_rev, undef) = cmt_metadata($last_commit); - unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { - die "SVN connection failed somewhere...\n"; - } - libsvn_log_entry($rev, $author, $date, $log, [$last_commit], $ed); -} - -sub svn_grab_base_rev { - my $c = eval { command_oneline([qw/rev-parse --verify/, - "refs/remotes/$GIT_SVN^0"], - { STDERR => 0 }) }; - if (defined $c && length $c) { - my ($url, $rev, $uuid) = cmt_metadata($c); - return ($rev, $c) if defined $rev; - } - if ($_no_metadata) { - my $offset = -41; # from tail - my $rl; - open my $fh, '<', $REVDB or - die "--no-metadata specified and $REVDB not readable\n"; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); - chomp $rl; - while ($c ne $rl && tell $fh != 0) { - $offset -= 41; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); - chomp $rl; - } - my $rev = tell $fh; - croak $! if ($rev < -1); - $rev = ($rev - 41) / 41; - close $fh or croak $!; - return ($rev, $c); - } - return (undef, undef); -} - sub libsvn_parse_revision { my $base = shift; my $head = $SVN->get_latest_revnum(); @@ -2450,14 +2056,6 @@ sub libsvn_find_parent_branch { return undef; } -sub libsvn_new_tree { - if (my $log_entry = libsvn_find_parent_branch(@_)) { - return $log_entry; - } - my ($paths, $rev, $author, $date, $log) = @_; # $pool is last - _libsvn_new_tree($paths, $rev, $author, $date, $log, []); -} - sub _libsvn_new_tree { my ($paths, $rev, $author, $date, $log, $parents) = @_; my $ed = SVN::Git::Fetcher->new({q => $_q}); @@ -2513,82 +2111,6 @@ sub libsvn_graft_file_copies { } } -sub set_index { - my $old = $ENV{GIT_INDEX_FILE}; - $ENV{GIT_INDEX_FILE} = shift; - return $old; -} - -sub restore_index { - my ($old) = @_; - if (defined $old) { - $ENV{GIT_INDEX_FILE} = $old; - } else { - delete $ENV{GIT_INDEX_FILE}; - } -} - -sub libsvn_commit_cb { - my ($rev, $date, $committer, $c, $log, $r_last, $cmt_last) = @_; - if ($_optimize_commits && $rev == ($r_last + 1)) { - my $log = libsvn_log_entry($rev,$committer,$date,$log); - $log->{tree} = get_tree_from_treeish($c); - my $cmt = git_commit($log, $cmt_last, $c); - my @diff = command('diff-tree', $cmt, $c); - if (@diff) { - print STDERR "Trees differ: $cmt $c\n", - join('',@diff),"\n"; - exit 1; - } - } else { - fetch("$rev=$c"); - } -} - -sub libsvn_skip_unknown_revs { - my $err = shift; - my $errno = $err->apr_err(); - # Maybe the branch we're tracking didn't - # exist when the repo started, so it's - # not an error if it doesn't, just continue - # - # Wonderfully consistent library, eh? - # 160013 - svn:// and file:// - # 175002 - http(s):// - # 175007 - http(s):// (this repo required authorization, too...) - # More codes may be discovered later... - if ($errno == 175007 || $errno == 175002 || $errno == 160013) { - 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; -- cgit v1.2.3 From d05d72e07e49869fe988d4d99e6ac60711570db5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Jan 2007 22:59:26 -0800 Subject: git-svn: remove graft-branches command It's becoming a maintenance burden. I've never found it particularly useful myself, nor have I heard much feedback about it; so I'm assuming it's just as useless to everyone else. Signed-off-by: Eric Wong --- git-svn.perl | 529 +------------------------------------- t/t9103-git-svn-graft-branches.sh | 67 ----- 2 files changed, 3 insertions(+), 593 deletions(-) delete mode 100755 t/t9103-git-svn-graft-branches.sh (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 261e33d023..e75021bce2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,16 +60,13 @@ $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, - $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_version, $_upgrade, $_branch_all_refs, @_opt_m, + $_template, $_shared, + $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my (@_branch_from, %tree_map); my @repo_path_split_cache; -my %fc_opts = ( 'branch|b=s' => \@_branch_from, - 'follow-parent|follow' => \$_follow_parent, - 'branch-all-refs|B' => \$_branch_all_refs, +my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, @@ -111,13 +108,6 @@ my %cmd = ( rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], - 'graft-branches' => [ \&graft_branches, - 'Detect merges/branches from already imported history', - { 'merge-rx|m' => \@_opt_m, - 'branch|b=s' => \@_branch_from, - 'branch-all-refs|B' => \$_branch_all_refs, - 'no-default-regex' => \$_no_default_regex, - 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, @@ -167,13 +157,11 @@ my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'id|i=s' => \$GIT_SVN); exit 1 if (!$rv && $cmd ne 'log'); -set_default_vals(); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; init_vars(); load_authors() if $_authors; -load_all_refs() if $_branch_all_refs; migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -394,40 +382,6 @@ sub cmd_show_ignore { $gs->traverse_ignore(\*STDOUT, '', $r); } -sub graft_branches { - my $gr_file = "$GIT_DIR/info/grafts"; - my ($grafts, $comments) = read_grafts($gr_file); - my $gr_sha1; - - if (%$grafts) { - # temporarily disable our grafts file to make this idempotent - chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file)); - rename $gr_file, "$gr_file~$gr_sha1" or croak $!; - } - - my $l_map = read_url_paths(); - my @re = map { qr/$_/is } @_opt_m if @_opt_m; - unless ($_no_default_regex) { - push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i, - qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i, - qr/\b(?:from|of)\s+([\w\.\-]+)/i ); - } - foreach my $u (keys %$l_map) { - if (@re) { - foreach my $p (keys %{$l_map->{$u}}) { - graft_merge_msg($grafts,$l_map,$u,$p,@re); - } - } - unless ($_no_graft_copy) { - graft_file_copy_lib($grafts,$l_map,$u); - } - } - graft_tree_joins($grafts); - - write_grafts($grafts, $comments, $gr_file); - unlink "$gr_file~$gr_sha1" if $gr_sha1; -} - sub cmd_multi_init { my $url = shift; unless (defined $_trunk || defined $_branches || defined $_tags) { @@ -601,157 +555,6 @@ sub common_prefix { return ''; } -# grafts set here are 'stronger' in that they're based on actual tree -# matches, and won't be deleted from merge-base checking in write_grafts() -sub graft_tree_joins { - my $grafts = shift; - map_tree_joins() if (@_branch_from && !%tree_map); - return unless %tree_map; - - git_svn_each(sub { - my $i = shift; - my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i"); - my ($fh, $ctx) = command_output_pipe(@args); - while (<$fh>) { - next unless /^commit ($sha1)$/o; - my $c = $1; - my ($t) = (<$fh> =~ /^tree ($sha1)$/o); - next unless $tree_map{$t}; - - my $l; - do { - $l = readline $fh; - } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/); - - my ($s, $tz) = ($1, $2); - if ($tz =~ s/^\+//) { - $s += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $s -= tz_to_s_offset($tz); - } - - my ($url_a, $r_a, $uuid_a) = cmt_metadata($c); - - foreach my $p (@{$tree_map{$t}}) { - next if $p eq $c; - my $mb = eval { command('merge-base', $c, $p) }; - next unless ($@ || $?); - if (defined $r_a) { - # see if SVN says it's a relative - my ($url_b, $r_b, $uuid_b) = - cmt_metadata($p); - next if (defined $url_b && - defined $url_a && - ($url_a eq $url_b) && - ($uuid_a eq $uuid_b)); - if ($uuid_a eq $uuid_b) { - if ($r_b < $r_a) { - $grafts->{$c}->{$p} = 2; - next; - } elsif ($r_b > $r_a) { - $grafts->{$p}->{$c} = 2; - next; - } - } - } - my $ct = get_commit_time($p); - if ($ct < $s) { - $grafts->{$c}->{$p} = 2; - } elsif ($ct > $s) { - $grafts->{$p}->{$c} = 2; - } - # what should we do when $ct == $s ? - } - } - command_close_pipe($fh, $ctx); - }); -} - -sub graft_file_copy_lib { - my ($grafts, $l_map, $u) = @_; - my $tree_paths = $l_map->{$u}; - my $pfx = common_prefix([keys %$tree_paths]); - my ($repo, $path) = repo_path_split($u.$pfx); - $SVN = Git::SVN::Ra->new($repo); - - 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) { - $SVN->dup->get_log([$path], $min, $max, 0, 2, 1, - sub { - libsvn_graft_file_copies($grafts, $tree_paths, - $path, @_); - }); - last if ($max >= $head); - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - } - $SVN::Error::handler = $eh; -} - -sub process_merge_msg_matches { - my ($grafts, $l_map, $u, $p, $c, @matches) = @_; - my (@strong, @weak); - foreach (@matches) { - # merging with ourselves is not interesting - next if $_ eq $p; - if ($l_map->{$u}->{$_}) { - push @strong, $_; - } else { - push @weak, $_; - } - } - foreach my $w (@weak) { - last if @strong; - # no exact match, use branch name as regexp. - my $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - last if @strong; - $w = basename($w); - $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - } - my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+) - \s(?:[a-f\d\-]+)$/xsm); - unless (defined $rev) { - ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+) - \@(?:[a-f\d\-]+)/xsm); - return unless defined $rev; - } - foreach my $m (@strong) { - my ($r0, $s0) = find_rev_before($rev, $m, 1); - $grafts->{$c->{c}}->{$s0} = 1 if defined $s0; - } -} - -sub graft_merge_msg { - my ($grafts, $l_map, $u, $p, @re) = @_; - - my $x = $l_map->{$u}->{$p}; - my $rl = rev_list_raw("refs/remotes/$x"); - while (my $c = next_rev_list_entry($rl)) { - foreach my $re (@re) { - my (@br) = ($c->{m} =~ /$re/g); - next unless @br; - process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br); - } - } -} - sub verify_ref { my ($ref) = @_; eval { command_oneline([ 'rev-parse', '--verify', $ref ], @@ -807,58 +610,6 @@ sub get_tree_from_treeish { return $expected; } -sub get_diff { - my ($from, $treeish) = @_; - print "diff-tree $from $treeish\n"; - my @diff_tree = qw(diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - push @diff_tree, $from, $treeish; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s - $sha1\s($sha1)\s([MTCRAD])\d*$/xo) { - push @mods, { mode_a => $1, mode_b => $2, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - return \@mods; -} - sub get_commit_entry { my ($treeish) = shift; my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); @@ -899,34 +650,6 @@ sub get_commit_entry { \%log_entry; } -sub rev_list_raw { - my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); - return { fh => $fh, ctx => $c, t => { } }; -} - -sub next_rev_list_entry { - my $rl = shift; - my $fh = $rl->{fh}; - my $x = $rl->{t}; - while (<$fh>) { - if (/^commit ($sha1)$/o) { - if ($x->{c}) { - $rl->{t} = { c => $1 }; - return $x; - } else { - $x->{c} = $1; - } - } elsif (/^parent ($sha1)$/o) { - $x->{p}->{$1} = 1; - } elsif (s/^ //) { - $x->{m} ||= ''; - $x->{m} .= $_; - } - } - command_close_pipe($fh, $rl->{ctx}); - return ($x != $rl->{t}) ? $x : undef; -} - sub s_to_file { my ($str, $file, $mode) = @_; open my $fd,'>',$file or croak $!; @@ -962,43 +685,6 @@ sub check_upgrade_needed { } } -# fills %tree_map with a reverse mapping of trees to commits. Useful -# for finding parents to commit on. -sub map_tree_joins { - my %seen; - foreach my $br (@_branch_from) { - my $pipe = command_output_pipe(qw/rev-list - --topo-order --pretty=raw/, $br); - while (<$pipe>) { - if (/^commit ($sha1)$/o) { - my $commit = $1; - - # if we've seen a commit, - # we've seen its parents - last if $seen{$commit}; - my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o); - unless (defined $tree) { - die "Failed to parse commit $commit\n"; - } - push @{$tree_map{$tree}}, $commit; - $seen{$commit} = 1; - } - } - close $pipe; - } -} - -sub load_all_refs { - if (@_branch_from) { - print STDERR '--branch|-b parameters are ignored when ', - "--branch-all-refs|-B is passed\n"; - } - - # don't worry about rev-list on non-commit objects/tags, - # it shouldn't blow up if a ref is a blob or tree... - @_branch_from = command(qw/rev-parse --symbolic --all/); -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -1073,20 +759,6 @@ sub migration_check { print "Done upgrading.\n"; } -sub find_rev_before { - 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); -} - sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::default = $GIT_SVN; @@ -1094,7 +766,6 @@ sub init_vars { $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - %tree_map = (); } # convert GetOpt::Long specs for use by git-config @@ -1120,95 +791,6 @@ sub read_repo_config { } } -sub set_default_vals { - if (defined $_repack) { - $_repack = 1000 if ($_repack <= 0); - $_repack_nr = $_repack; - $_repack_flags ||= '-d'; - } -} - -sub read_grafts { - my $gr_file = shift; - my ($grafts, $comments) = ({}, {}); - if (open my $fh, '<', $gr_file) { - my @tmp; - while (<$fh>) { - if (/^($sha1)\s+/) { - my $c = $1; - if (@tmp) { - @{$comments->{$c}} = @tmp; - @tmp = (); - } - foreach my $p (split /\s+/, $_) { - $grafts->{$c}->{$p} = 1; - } - } else { - push @tmp, $_; - } - } - close $fh or croak $!; - @{$comments->{'END'}} = @tmp if @tmp; - } - return ($grafts, $comments); -} - -sub write_grafts { - my ($grafts, $comments, $gr_file) = @_; - - open my $fh, '>', $gr_file or croak $!; - foreach my $c (sort keys %$grafts) { - if ($comments->{$c}) { - print $fh $_ foreach @{$comments->{$c}}; - } - my $p = $grafts->{$c}; - my %x; # real parents - delete $p->{$c}; # commits are not self-reproducing... - my $ch = command_output_pipe(qw/cat-file commit/, $c); - while (<$ch>) { - if (/^parent ($sha1)/) { - $x{$1} = $p->{$1} = 1; - } else { - last unless /^\S/; - } - } - close $ch; # breaking the pipe - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - my (@ip, @jp, $mb); - my %del = %x; - @ip = @jp = keys %$p; - foreach my $i (@ip) { - next if $del{$i} || $p->{$i} == 2; - foreach my $j (@jp) { - next if $i eq $j || $del{$j} || $p->{$j} == 2; - $mb = eval { command('merge-base', $i, $j) }; - next unless $mb; - chomp $mb; - next if $x{$mb}; - if ($mb eq $j) { - delete $p->{$i}; - $del{$i} = 1; - } elsif ($mb eq $i) { - delete $p->{$j}; - $del{$j} = 1; - } - } - } - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - print $fh $c, ' ', join(' ', sort keys %$p),"\n"; - } - if ($comments->{'END'}) { - print $fh $_ foreach @{$comments->{'END'}}; - } - close $fh or croak $!; -} - sub read_url_paths_all { my ($l_map, $pfx, $p) = @_; my @dir; @@ -1936,48 +1518,6 @@ sub uri_decode { $f } -sub libsvn_parse_revision { - my $base = shift; - my $head = $SVN->get_latest_revnum(); - if (!defined $_revision || $_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); - return ($_revision, $_revision) if ($_revision =~ /^\d+$/); - if ($_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/); - die "revision argument: $_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; -} - -sub libsvn_traverse_ignore { - my ($fh, $path, $r) = @_; - $path =~ s#^/+##g; - my ($dirent, undef, $props) = $SVN->get_dir($path, $r); - my $p = $path; - $p =~ s#^\Q$SVN->{svn_path}\E/##; - print $fh length $p ? "\n# $p\n" : "\n# /\n"; - if (my $s = $props->{'svn:ignore'}) { - $s =~ s/[\r\n]+/\n/g; - chomp $s; - if (length $p == 0) { - $s =~ s#\n#\n/$p#g; - print $fh "/$s\n"; - } else { - $s =~ s#\n#\n/$p/#g; - print $fh "/$p/$s\n"; - } - } - foreach (sort keys %$dirent) { - next if $dirent->{$_}->kind != $SVN::Node::dir; - libsvn_traverse_ignore($fh, "$path/$_", $r); - } -} - sub revisions_eq { my ($path, $r0, $r1) = @_; return 1 if $r0 == $r1; @@ -2065,69 +1605,6 @@ sub _libsvn_new_tree { libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); } -sub find_graft_path_commit { - my ($tree_paths, $p1, $r1) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p1 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - 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; - } - return undef; -} - -sub find_graft_path_parents { - my ($grafts, $tree_paths, $c, $p0, $r0) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p0 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - my ($r, $parent) = find_rev_before($r0, $i, 1); - if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) { - my ($url_b, undef, $uuid_b) = cmt_metadata($c); - my ($url_a, undef, $uuid_a) = cmt_metadata($parent); - next if ($url_a && $url_b && $url_a eq $url_b && - $uuid_b eq $uuid_a); - $grafts->{$c}->{$parent} = 1; - } - } -} - -sub libsvn_graft_file_copies { - my ($grafts, $tree_paths, $path, $paths, $rev) = @_; - foreach (keys %$paths) { - my $i = $paths->{$_}; - my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path, - $i->copyfrom_rev); - next unless (defined $p0 && defined $r0); - - my $p1 = $_; - $p1 =~ s#^/##; - $p0 =~ s#^/##; - my $c = find_graft_path_commit($tree_paths, $p1, $rev); - next unless $c; - find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0); - } -} - -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; -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh deleted file mode 100755 index 8d946d2aa5..0000000000 --- a/t/t9103-git-svn-graft-branches.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh -test_description='git-svn graft-branches' -. ./lib-git-svn.sh - -svnrepo="$svnrepo/test-git-svn" - -test_expect_success 'initialize repo' " - mkdir import && - cd import && - mkdir -p trunk branches tags && - echo hello > trunk/readme && - svn import -m 'import for git-svn' . $svnrepo && - cd .. && - svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a && - svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a && - svn co $svnrepo wc && - cd wc && - echo feedme >> branches/a/readme && - poke branches/a/readme && - svn commit -m hungry && - cd trunk && - svn merge -r3:4 $svnrepo/branches/a && - svn commit -m 'merge with a' && - cd ../.. && - git-svn multi-init $svnrepo -T trunk -b branches -t tags && - git-svn multi-fetch - " - -test_expect_success 'multi-init set .git/config correctly' " - test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && - test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && - test '$svnrepo/tags' = '`git repo-config --get svn.tags`' - " - -r1=`git-rev-list remotes/trunk | tail -n1` -r2=`git-rev-list remotes/tags/a | tail -n1` -r3=`git-rev-list remotes/a | tail -n1` -r4=`git-rev-parse remotes/a` -r5=`git-rev-parse remotes/trunk` - -test_expect_success 'test graft-branches regexes and copies' " - test -n "$r1" && - test -n "$r2" && - test -n "$r3" && - test -n "$r4" && - test -n "$r5" && - git-svn graft-branches && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r3 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1' - " - -test_debug 'gitk --all & sleep 1' - -test_expect_success 'test graft-branches with tree-joins' " - rm $GIT_DIR/info/grafts && - git-svn graft-branches --no-default-regex --no-graft-copy -B && - grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4' - " - -# the result of this is kinda funky, we have a strange history and -# this is just a test :) -test_debug 'gitk --all &' - -test_done -- cgit v1.2.3 From 706587fc6d56db1ba6c7207d4c0c456bac6f77c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 17:50:01 -0800 Subject: git-svn: add support for metadata in .git/config Of course, we handle metadata migrations from previous versions and we have added unit tests. The new .git/config remotes resemble non-SVN remotes. Below is an example with comments: [svn-remote "git-svn"] ; like non-svn remotes, we have one URL per-remote url = http://foo.bar.org/svn ; 'fetch' keys are done in the same way as non-svn ; remotes, too. With the left-hand-side of the ':' ; being the remote (SVN) repository path relative to the ; above 'url' key; and the right-hand-side being a ; remote ref in git (refs/remotes/*). ; An empty left-hand-side means that it will fetch ; the entire contents of the 'url' key. ; old-style (migrated from previous versions of git-svn) ; are like this: fetch = :refs/remotes/git-svn ; this is created by a current version of git-svn ; using the multi-init command with an explicit ; url (specified above). This allows multi-init ; to reuse SVN::Ra connections. fetch = trunk:refs/remotes/trunk fetch = branches/a:refs/remotes/a fetch = branches/b:refs/remotes/b fetch = tags/0.1:refs/remotes/tags/0.1 fetch = tags/0.2:refs/remotes/tags/0.2 fetch = tags/0.3:refs/remotes/tags/0.3 [svn-remote "alt"] ; this is another old-style remote migrated over ; to the new config format url = http://foo.bar.org/alt fetch = :refs/remotes/alt Signed-off-by: Eric Wong --- git-svn.perl | 583 +++++++++++++++++++++++---------------------- t/t9107-git-svn-migrate.sh | 63 +++++ 2 files changed, 367 insertions(+), 279 deletions(-) create mode 100755 t/t9107-git-svn-migrate.sh (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index e75021bce2..9d50d305c1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -13,9 +13,8 @@ use vars qw/ $AUTHOR $VERSION $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; -use Cwd qw/abs_path/; -$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); -$ENV{GIT_DIR} = $GIT_DIR; +$ENV{GIT_DIR} ||= '.git'; +$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -47,6 +46,7 @@ BEGIN { foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". + "*Git::SVN::Migration::$_ = ". "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; "; } eval $s; @@ -64,17 +64,17 @@ my ($_stdin, $_help, $_edit, $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my @repo_path_split_cache; +my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, - 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); + 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, + %remote_opts ); my ($_trunk, $_tags, $_branches); my %multi_opts = ( 'trunk|T=s' => \$_trunk, @@ -110,16 +110,20 @@ my %cmd = ( 'upgrade' => \$_upgrade } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, + { %multi_opts, %init_opts, %remote_opts, 'revision|r=i' => \$_revision, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&cmd_multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], + 'migrate' => [ sub { }, + # no-op, we automatically run this anyways, + # we may add a flag to automatically optimize the + # configuration to avoid reconnects in the future + 'Migrate configuration/metadata/layout from + previous versions of git-svn', + \%remote_opts ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, @@ -154,15 +158,16 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, - 'id|i=s' => \$GIT_SVN); + 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; -init_vars(); load_authors() if $_authors; -migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; +unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { + Git::SVN::Migration::migration_check(); +} $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -203,17 +208,12 @@ sub version { sub cmd_rebuild { my $url = shift; - my $gs = $url ? Git::SVN->init(undef, $url) + my $gs = $url ? Git::SVN->init($url) : eval { Git::SVN->new }; $gs ||= Git::SVN->_new; if (!verify_ref($gs->refname.'^0')) { $gs->copy_remote_ref; } - if ($_upgrade) { - command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD'); - } else { - $gs->check_upgrade_needed; - } my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); my $latest; @@ -238,7 +238,7 @@ sub cmd_rebuild { if (!$gs->{url} && !$url) { fatal "SVN repository location required\n"; } - $gs = Git::SVN->init(undef, $url); + $gs = Git::SVN->init($url); $latest = $rev; } $gs->rev_db_set($rev, $c); @@ -268,7 +268,7 @@ sub cmd_init { } do_git_init_db(); - Git::SVN->init(undef, $url); + Git::SVN->init($url); } sub cmd_fetch { @@ -389,28 +389,35 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; + $url =~ s#/+$## if defined $url; if (defined $_trunk) { - my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') }; + my $trunk_ref = $_prefix . 'trunk'; + # try both old-style and new-style lookups: + my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; unless ($gs_trunk) { - my $trunk_url = complete_svn_url($url, $_trunk); - $gs_trunk = Git::SVN->init($_prefix . 'trunk', - $trunk_url); - command_noisy('config', 'svn.trunk', $trunk_url); + my ($trunk_url, $trunk_path) = + complete_svn_url($url, $_trunk); + $gs_trunk = Git::SVN->init($trunk_url, $trunk_path, + undef, $trunk_ref); } } + return unless defined $_branches || defined $_tags; my $ra = $url ? Git::SVN::Ra->new($url) : undef; complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub cmd_multi_fetch { - # try to do trunk first, since branches/tags - # may be descended from it. - if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") { - my $gs = Git::SVN->new('trunk'); - $gs->fetch(@_); + my @gs; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref_id) = ($1, $2, $3); + push @gs, Git::SVN->new($ref_id, $repo_id, $path); + } + foreach (@gs) { + $_->fetch; } - rec_fetch('', "$ENV{GIT_DIR}/svn", @_); } # this command is special because it requires no metadata @@ -464,95 +471,50 @@ sub cmd_commit_diff { ########################### utility functions ######################### -sub rec_fetch { - my ($pfx, $p, @args) = @_; - my @dir; - foreach (sort <$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - next if $id eq 'trunk'; - my $gs = Git::SVN->new($id); - $gs->fetch(@args); - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; - rec_fetch($x, $_, @args); - } -} - sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; - $url =~ s#/+$## if $url; if ($path !~ m#^[a-z\+]+://#) { - $path = '/' . $path if ($path !~ m#^/#); if (!defined $url || $url !~ m#^[a-z\+]+://#) { fatal("E: '$path' is not a complete URL ", "and a separate URL is not specified\n"); } - $path = $url . $path; + return ($url, $path); } - return $path; + return ($path, ''); } sub complete_url_ls_init { - my ($ra, $path, $switch, $pfx) = @_; - unless ($path) { + my ($ra, $repo_path, $switch, $pfx) = @_; + unless ($repo_path) { print STDERR "W: $switch not specified\n"; return; } - $path =~ s#/+$##; - if ($path =~ m#^[a-z\+]+://#) { - $ra = Git::SVN::Ra->new($path); - $path = ''; + $repo_path =~ s#/+$##; + if ($repo_path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($repo_path); + $repo_path = ''; } else { - $path =~ s#^/+##; + $repo_path =~ s#^/+##; unless ($ra) { - fatal("E: '$path' is not a complete URL ", + fatal("E: '$repo_path' is not a complete URL ", "and a separate URL is not specified\n"); } } my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir($path, $r); - my $url = $ra->{url} . (length $path ? "/$path" : ''); + my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); + my $url = $ra->{url}; foreach my $d (sort keys %$dirent) { next if ($dirent->{$d}->kind != $SVN::Node::dir); - my $u = "$url/$d"; - my $id = "$pfx$d"; - my $gs = eval { Git::SVN->new($id) }; + my $path = "$repo_path/$d"; + my $ref = "$pfx$d"; + my $gs = eval { Git::SVN->new($ref) }; # don't try to init already existing refs unless ($gs) { - print "init $u => $id\n"; - Git::SVN->init($id, $u); + print "init $url/$path => $ref\n"; + Git::SVN->init($url, $path, undef, $ref); } } - my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $url); -} - -sub common_prefix { - my $paths = shift; - my %common; - foreach (@$paths) { - my @tmp = split m#/#, $_; - my $p = ''; - while (my $x = shift @tmp) { - $p .= "/$x"; - $common{$p} ||= 0; - $common{$p}++; - } - } - foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @$paths) { - return $_; - } - } - return ''; } sub verify_ref { @@ -561,34 +523,6 @@ sub verify_ref { { STDERR => 0 }); }; } -sub repo_path_split { - my $full_url = shift; - $full_url =~ s#/+$##; - - foreach (@repo_path_split_cache) { - if ($full_url =~ s#$_##) { - my $u = $1; - $full_url =~ s#^/+##; - return ($u, $full_url); - } - } - my $tmp = Git::SVN::Ra->new($full_url); - return ($tmp->{repos_root}, $tmp->{svn_path}); -} - -sub setup_git_svn { - defined $SVN_URL or croak "SVN repository location required\n"; - unless (-d $GIT_DIR) { - croak "GIT_DIR=$GIT_DIR does not exist!\n"; - } - mkpath([$GIT_SVN_DIR]); - mkpath(["$GIT_SVN_DIR/info"]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url"); - -} - sub get_tree_from_treeish { my ($treeish) = @_; # $treeish can be a symbolic ref, too: @@ -668,23 +602,6 @@ sub file_to_s { return $ret; } -sub check_upgrade_needed { - if (!-r $REVDB) { - -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - } - return unless eval { - command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"], - {STDERR => 0}); - }; - my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") }; - if ($@ || !$head) { - print STDERR "Please run: $0 rebuild --upgrade\n"; - exit 1; - } -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -702,75 +619,9 @@ sub load_authors { close $authors or croak $!; } -sub git_svn_each { - my $sub = shift; - foreach (command(qw/rev-parse --symbolic --all/)) { - next unless s#^refs/remotes/##; - chomp $_; - next unless -f "$GIT_DIR/svn/$_/info/url"; - &$sub($_); - } -} - -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") { - mkdir "$GIT_DIR/svn" or croak $!; - } - print "Data from a previous version of git-svn exists, but\n\t", - "$GIT_SVN_DIR\n\t(required for this version ", - "($VERSION) of git-svn) does not.\n"; - - foreach my $x (command(qw/rev-parse --symbolic --all/)) { - next unless $x =~ s#^refs/remotes/##; - chomp $x; - next unless -f "$GIT_DIR/$x/info/url"; - my $u = eval { file_to_s("$GIT_DIR/$x/info/url") }; - next unless $u; - my $dn = dirname("$GIT_DIR/svn/$x"); - mkpath([$dn]) unless -d $dn; - rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x"; - } - migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB); - print "Done upgrading.\n"; -} - -sub init_vars { - $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; - $Git::SVN::default = $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; -} - # convert GetOpt::Long specs for use by git-config sub read_repo_config { - return unless -d $GIT_DIR; + return unless -d $ENV{GIT_DIR}; my $opts = shift; foreach my $o (keys %$opts) { my $v = $opts->{$o}; @@ -791,38 +642,6 @@ sub read_repo_config { } } -sub read_url_paths_all { - my ($l_map, $pfx, $p) = @_; - my @dir; - foreach (<$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - my $url = file_to_s("$_/info/url"); - my ($u, $p) = repo_path_split($url); - $l_map->{$u}->{$p} = $id; - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$GIT_DIR\E/svn/!!o; - read_url_paths_all($l_map, $x, $_); - } -} - -# this one only gets ids that have been imported, not new ones -sub read_url_paths { - my $l_map = {}; - git_svn_each(sub { my $x = shift; - 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; -} - sub extract_metadata { my $id = shift or return (undef, undef, undef); my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) @@ -866,7 +685,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default/; +use vars qw/$default_repo_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -882,28 +701,76 @@ BEGIN { svn:entry:committed-date/; } +# we allow dashes, unlike remotes2config.sh +sub sanitize_remote_name { + my ($name) = @_; + $name =~ tr/A-Za-z0-9-/./c; + $name; +} + sub init { - my ($class, $id, $url) = @_; - my $self = _new($class, $id); - mkpath(["$self->{dir}/info"]); + my ($class, $url, $path, $repo_id, $ref_id) = @_; + my $self = _new($class, $repo_id, $ref_id, $path); + mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash - ::s_to_file($url, "$self->{dir}/info/url"); + my $orig_url = eval { + command_oneline('config', '--get', + "svn-remote.$repo_id.url") + }; + if ($orig_url) { + if ($orig_url ne $url) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + } else { + command_noisy('config', + "svn-remote.$repo_id.url", $url); + } + command_noisy('config', '--add', + "svn-remote.$repo_id.fetch", + "$path:".$self->refname); } $self->{url} = $url; - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; + unless (-f $self->{db_path}) { + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh or croak $!; + } $self; } +sub find_ref { + my ($ref_id) = @_; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref) = ($1, $2, $3); + if ($ref eq $ref_id) { + $path = '' if ($path =~ m#^\./?#); + return ($repo_id, $path); + } + } + (undef, undef, undef); +} + sub new { - my ($class, $id) = @_; - my $self = _new($class, $id); - $self->{url} = ::file_to_s("$self->{dir}/info/url"); + my ($class, $ref_id, $repo_id, $path) = @_; + if (defined $ref_id && !defined $repo_id && !defined $path) { + ($repo_id, $path) = find_ref($ref_id); + if (!defined $repo_id) { + die "Could not find a \"svn-remote.*.fetch\" key ", + "in the repository configuration matching: ", + "refs/remotes/$ref_id\n"; + } + } + my $self = _new($class, $repo_id, $ref_id, $path); + $self->{url} = command_oneline('config', '--get', + "svn-remote.$repo_id.url") or + die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; $self; } -sub refname { "refs/remotes/$_[0]->{id}" } +sub refname { "refs/remotes/$_[0]->{ref_id}" } sub ra { my ($self) = shift; @@ -952,7 +819,7 @@ sub last_rev_commit { return ($self->{last_rev}, $self->{last_commit}); } my $c = ::verify_ref($self->refname.'^0'); - if (defined $c && length $c) { + if ($c) { my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1064,18 +931,9 @@ sub get_commit_parents { @ret; } -sub check_upgrade_needed { +sub full_url { my ($self) = @_; - if (!-r $self->{db_path}) { - -d $self->{dir} or mkpath([$self->{dir}]); - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh; - } - return unless ::verify_ref($self->{id}.'-HEAD^0'); - my $head = ::verify_ref($self->refname.'^0'); - if ($@ || !$head) { - ::fatal("Please run: $0 rebuild --upgrade\n"); - } + $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } sub do_git_commit { @@ -1105,7 +963,7 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@', + print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', $log_entry->{revision}, ' ', $self->ra->uuid, "\n" or croak $!; $msg_fh->flush == 0 or croak $!; @@ -1128,7 +986,7 @@ sub do_git_commit { } sub do_fetch { - my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_; + my ($self, $paths, $rev) = @_; my $ed = SVN::Git::Fetcher->new($self); my ($last_rev, @parents); if ($self->{last_commit}) { @@ -1138,7 +996,8 @@ sub do_fetch { } else { $last_rev = $rev; } - unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, + $self->{path}, 1, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1361,11 +1220,19 @@ sub rev_db_get { } sub _new { - my ($class, $id) = @_; - $id ||= $Git::SVN::default; - my $dir = "$ENV{GIT_DIR}/svn/$id"; - bless { id => $id, dir => $dir, index => "$dir/index", - db_path => "$dir/.rev_db" }, $class; + my ($class, $repo_id, $ref_id, $path) = @_; + unless (defined $repo_id && length $repo_id) { + $repo_id = $Git::SVN::default_repo_id; + } + unless (defined $ref_id && length $ref_id) { + $_[2] = $ref_id = $repo_id; + } + $_[1] = $repo_id = sanitize_remote_name($repo_id); + my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; + $_[3] = $path = '' unless (defined $path); + bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + path => $path, + db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; } sub uri_encode { @@ -1630,6 +1497,9 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; + if (length $git_svn->{path}) { + $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; + } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1650,33 +1520,41 @@ sub open_directory { { path => $path }; } +sub git_path { + my ($self, $path) = @_; + $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + $path; +} + sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gui = $self->{gui}; + my $gpath = $self->git_path($path); # remove entire directories. - if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) { + if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, - $self->{c}, '--', $path); + $self->{c}, '--', $gpath); local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; print "\tD\t$_\n" unless $self->{q}; } - print "\tD\t$path/\n" unless $self->{q}; + print "\tD\t$gpath/\n" unless $self->{q}; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { - print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!; - print "\tD\t$path\n" unless $self->{q}; + print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!; + print "\tD\t$gpath\n" unless $self->{q}; } undef; } sub open_file { my ($self, $path, $pb, $rev) = @_; - my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path) + my $gpath = $self->git_path($path); + my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath) =~ /^(\d{6}) blob ([a-f\d]{40})\t/); unless (defined $mode && defined $blob) { die "$path was not found in commit $self->{c} (r$rev)\n"; @@ -1775,7 +1653,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; my $hash; - my $path = $fb->{path}; + my $path = $self->git_path($fb->{path}); if (my $fh = $fb->{fh}) { seek($fh, 0, 0) or croak $!; my $md5 = Digest::MD5->new; @@ -2223,7 +2101,7 @@ sub gs_do_update { $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my $new = ($rev_a == $rev_b); - $reporter->set_path($path, $rev_a, $new, @lock, $pool); + $reporter->set_path('', $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2561,6 +2439,153 @@ out: print '-' x72,"\n" unless $incremental || $oneline; } +package Git::SVN::Migration; +# these version numbers do NOT correspond to actual version numbers +# of git nor git-svn. They are just relative. +# +# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD +# +# v1 layout: .git/$id/info/url, refs/remotes/$id +# +# v2 layout: .git/svn/$id/info/url, refs/remotes/$id +# +# v3 layout: .git/svn/$id, refs/remotes/$id +# - info/url may remain for backwards compatibility +# - this is what we migrate up to this layout automatically, +# - this will be used by git svn init on single branches +# +# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id +# - this is only created for newly multi-init-ed +# repositories. Similar in spirit to the +# --use-separate-remotes option in git-clone (now default) +# - we do not automatically migrate to this (following +# the example set by core git) +use strict; +use warnings; +use Carp qw/croak/; +use File::Path qw/mkpath/; +use File::Basename qw/dirname/; + +sub migrate_from_v0 { + my $git_dir = $ENV{GIT_DIR}; + return undef unless -d $git_dir; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + my $migrated = 0; + while (<$fh>) { + chomp; + my ($id, $orig_ref) = ($_, $_); + next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#; + next unless -f "$git_dir/$id/info/url"; + my $new_ref = "refs/remotes/$id"; + if (::verify_ref("$new_ref^0")) { + print STDERR "W: $orig_ref is probably an old ", + "branch used by an ancient version of ", + "git-svn.\n", + "However, $new_ref also exists.\n", + "We will not be able ", + "to use this branch until this ", + "ambiguity is resolved.\n"; + next; + } + print STDERR "Migrating from v0 layout...\n" if !$migrated; + print STDERR "Renaming ref: $orig_ref => $new_ref\n"; + command_noisy('update-ref', $new_ref, $orig_ref); + command_noisy('update-ref', '-d', $orig_ref, $orig_ref); + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from v0 layout...\n" if $migrated; + $migrated; +} + +sub migrate_from_v1 { + my $git_dir = $ENV{GIT_DIR}; + my $migrated = 0; + return $migrated unless -d $git_dir; + my $svn_dir = "$git_dir/svn"; + + # just in case somebody used 'svn' as their $id at some point... + return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url"; + + print STDERR "Migrating from a git-svn v1 layout...\n"; + mkpath([$svn_dir]); + print STDERR "Data from a previous version of git-svn exists, but\n\t", + "$svn_dir\n\t(required for this version ", + "($::VERSION) of git-svn) does not. exist\n"; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + while (<$fh>) { + my $x = $_; + next unless $x =~ s#^refs/remotes/##; + chomp $x; + next unless -f "$git_dir/$x/info/url"; + my $u = eval { ::file_to_s("$git_dir/$x/info/url") }; + next unless $u; + my $dn = dirname("$git_dir/svn/$x"); + mkpath([$dn]) unless -d $dn; + if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID: + mkpath(["$git_dir/svn/svn"]); + print STDERR " - $git_dir/$x/info => ", + "$git_dir/svn/$x/info\n"; + rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or + croak "$!: $x"; + # don't worry too much about these, they probably + # don't exist with repos this old (save for index, + # and we can easily regenerate that) + foreach my $f (qw/unhandled.log index .rev_db/) { + rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f"; + } + } else { + print STDERR " - $git_dir/$x => $git_dir/svn/$x\n"; + rename "$git_dir/$x", "$git_dir/svn/$x" or + croak "$!: $x"; + } + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from a git-svn v1 layout\n"; + $migrated; +} + +sub read_old_urls { + my ($l_map, $pfx, $path) = @_; + my @dir; + foreach (<$path/*>) { + if (-r "$_/info/url") { + $pfx .= '/' if $pfx && $pfx !~ m!/$!; + my $ref_id = $pfx . basename $_; + my $url = ::file_to_s("$_/info/url"); + $l_map->{$ref_id} = $url; + } elsif (-d $_) { + push @dir, $_; + } + } + foreach (@dir) { + my $x = $_; + $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + read_old_urls($l_map, $x, $_); + } +} + +sub migrate_from_v2 { + my @cfg = command(qw/config -l/); + return if grep /^svn-remote\..+\.url=/, @cfg; + my %l_map; + read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn"); + my $migrated = 0; + + foreach my $ref_id (sort keys %l_map) { + Git::SVN->init($l_map{$ref_id}, $ref_id); + $migrated++; + } + $migrated; +} + +sub migration_check { + migrate_from_v0(); + migrate_from_v1(); + migrate_from_v2(); +} + __END__ Data structures: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh new file mode 100755 index 0000000000..53318f1b1a --- /dev/null +++ b/t/t9107-git-svn-migrate.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# Copyright (c) 2006 Eric Wong +test_description='git-svn metadata migrations from previous versions' +. ./lib-git-svn.sh + +test_expect_success 'setup old-looking metadata' " + cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + git-svn init $svnrepo && + git-svn fetch && + for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && echo hello >> \$i/README || exit 1; done && + git ls-files -o trunk branches tags | git update-index --add --stdin && + git commit -m 'test' && + git-svn dcommit && + mv $GIT_DIR/svn/* $GIT_DIR/ && + rmdir $GIT_DIR/svn && + git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && + git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && + git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn + " + +head=`git rev-parse --verify refs/heads/git-svn-HEAD^0` +test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'" + +test_expect_success 'initialize old-style (v0) git-svn layout' " + mkdir -p $GIT_DIR/git-svn/info $GIT_DIR/svn/info && + echo $svnrepo > $GIT_DIR/git-svn/info/url && + echo $svnrepo > $GIT_DIR/svn/info/url && + git-svn migrate && + ! test -d $GIT_DIR/git-svn && + git-rev-parse --verify refs/remotes/git-svn^0 && + git-rev-parse --verify refs/remotes/svn^0 && + test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + ':refs/remotes/git-svn' + " + +test_expect_success 'initialize a multi-repository repo' " + git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + " + +test_expect_success 'multi-fetch works on partial urls + paths' " + git-svn multi-fetch && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1; + done && + test -z \"\`sort < refs.out | uniq -d\`\" && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do + if test \$j != \$i; then continue; fi + test -z \"\`git diff refs/remotes/\$i \ + refs/remotes/\$j\`\" ||exit 1; done; done + " + +test_done + -- cgit v1.2.3 From 780a2f58e727386ae81223d7a5c16fbc55cfd9fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:15:23 -0800 Subject: git-svn: fix a regression in dcommit that caused empty log messages Signed-off-by: Eric Wong --- git-svn.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 9d50d305c1..b5a4cb05a6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -339,13 +339,14 @@ sub cmd_dcommit { if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { + my $log = get_commit_entry($d)->{log}; my $ra = $gs->ra; my $pool = SVN::Pool->new; my %ed_opts = ( r => $last_rev, ra => $ra->dup, svn_path => $ra->{svn_path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($::_message, + $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }), $pool); -- cgit v1.2.3 From f6f0987646069bff3a7674aca257fd84f939d960 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:22:18 -0800 Subject: git-svn: reuse open SVN::Ra connections by URL Note: this can cause problems with Perl's reference counting GC, so I'm disabling Git::SVN::Ra::DESTROY. If we notice more problems down the line, we can disable this enhancement. Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b5a4cb05a6..4084e0657b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2015,6 +2015,7 @@ use vars qw/@ISA $config_dir/; use strict; use warnings; my ($can_do_switch); +my %RA; BEGIN { # enforce temporary pool usage for some simple functions @@ -2033,6 +2034,9 @@ BEGIN { sub new { my ($class, $url) = @_; + $url =~ s!/+$!!; + return $RA{$url} if $RA{$url}; + SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ SVN::Client::get_simple_provider(), @@ -2057,13 +2061,11 @@ sub new { $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; - bless $self, $class; + $RA{$url} = bless $self, $class; } sub DESTROY { - my $self = shift; - $self->{pool}->clear if $self->{pool}; - $self->SUPER::DESTROY(@_); + # do not call the real DESTROY since we store ourselves in %RA } sub dup { -- cgit v1.2.3 From 47e39c55c91993b94824b7a317ebeb965aaeb45a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 21 Jan 2007 04:27:09 -0800 Subject: git-svn: enable --minimize to simplify the config and connections --minimize will update the git-svn configuration to attempt to connect to the repository root (instead of directly to the path(s) we are tracking) in order to allow more efficient reuse of connections (for multi-fetch and follow-parent). Signed-off-by: Eric Wong --- git-svn.perl | 110 ++++++++++++++++++++++++++++++++++++++++++--- t/t9107-git-svn-migrate.sh | 25 +++++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 4084e0657b..15d65e21e1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -119,8 +119,6 @@ my %cmd = ( \%fc_opts ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, - # we may add a flag to automatically optimize the - # configuration to avoid reconnects in the future 'Migrate configuration/metadata/layout from previous versions of git-svn', \%remote_opts ], @@ -158,6 +156,8 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => + \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); @@ -702,10 +702,22 @@ BEGIN { svn:entry:committed-date/; } -# we allow dashes, unlike remotes2config.sh +sub read_all_remotes { + my $r = {}; + foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { + $r->{$1}->{fetch}->{$2} = $3; + } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { + $r->{$1}->{url} = $2; + } + } + $r; +} + +# we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; - $name =~ tr/A-Za-z0-9-/./c; + $name =~ tr{A-Za-z0-9:,/+-}{.}c; $name; } @@ -2467,7 +2479,8 @@ use strict; use warnings; use Carp qw/croak/; use File::Path qw/mkpath/; -use File::Basename qw/dirname/; +use File::Basename qw/dirname basename/; +use vars qw/$_minimize/; sub migrate_from_v0 { my $git_dir = $ENV{GIT_DIR}; @@ -2577,16 +2590,101 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, $ref_id); + Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); $migrated++; } $migrated; } +sub minimize_connections { + my $r = Git::SVN::read_all_remotes(); + my $new_urls = {}; + my $root_repos = {}; + foreach my $repo_id (keys %$r) { + my $url = $r->{$repo_id}->{url} or next; + my $fetch = $r->{$repo_id}->{fetch} or next; + my $ra = Git::SVN::Ra->new($url); + + # skip existing cases where we already connect to the root + if (($ra->{url} eq $ra->{repos_root}) || + (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq + $repo_id)) { + $root_repos->{$ra->{url}} = $repo_id; + next; + } + + my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); + my $root_path = $ra->{url}; + $root_path =~ s#^\Q$ra->{repos_root}\E/*##; + foreach my $path (keys %$fetch) { + my $ref_id = $fetch->{$path}; + my $gs = Git::SVN->new($ref_id, $repo_id, $path); + + # make sure we can read when connecting to + # a higher level of a repository + my ($last_rev, undef) = $gs->last_rev_commit; + if (!defined $last_rev) { + $last_rev = eval { + $root_ra->get_latest_revnum; + }; + next if $@; + } + my $new = $root_path; + $new .= length $path ? "/$path" : ''; + eval { + $root_ra->get_log([$new], $last_rev, $last_rev, + 0, 0, 1, sub { }); + }; + next if $@; + $new_urls->{$ra->{repos_root}}->{$new} = + { ref_id => $ref_id, + old_repo_id => $repo_id, + old_path => $path }; + } + } + + my @emptied; + foreach my $url (keys %$new_urls) { + # see if we can re-use an existing [svn-remote "repo_id"] + # instead of creating a(n ugly) new section: + my $repo_id = $root_repos->{$url} || + Git::SVN::sanitize_remote_name($url); + + my $fetch = $new_urls->{$url}; + foreach my $path (keys %$fetch) { + my $x = $fetch->{$path}; + Git::SVN->init($url, $path, $repo_id, $x->{ref_id}); + my $pfx = "svn-remote.$x->{old_repo_id}"; + + my $old_fetch = quotemeta("$x->{old_path}:". + "refs/remotes/$x->{ref_id}"); + command_noisy(qw/repo-config --unset/, + "$pfx.fetch", '^'. $old_fetch . '$'); + delete $r->{$x->{old_repo_id}}-> + {fetch}->{$x->{old_path}}; + if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { + command_noisy(qw/repo-config --unset/, + "$pfx.url"); + push @emptied, $x->{old_repo_id} + } + } + } + if (@emptied) { + my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} || + "$ENV{GIT_DIR}/config"; + print STDERR < $GIT_DIR/svn/\$ref/info/url ) || exit 1; + done && + git-svn migrate --minimize && + test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + grep '^:refs/remotes/git-svn' fetch.out + " + test_done -- cgit v1.2.3 From 15710b6f34da26d30079dbc83c797a8335040b75 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 02:20:33 -0800 Subject: git-svn: fix --follow-parent to work with Git::SVN While we're at it, beef up the test because I was getting false-passes during development. Signed-off-by: Eric Wong --- git-svn.perl | 196 +++++++++++++++++++++------------------ t/t9104-git-svn-follow-parent.sh | 6 +- 2 files changed, 110 insertions(+), 92 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 15d65e21e1..8d49959f37 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -790,6 +790,16 @@ sub ra { $self->{ra} ||= Git::SVN::Ra->new($self->{url}); } +sub rel_path { + my ($self) = @_; + my $repos_root = $self->ra->{repos_root}; + return $self->{path} if ($self->{url} eq $repos_root); + my $url = $self->{url} . + (length $self->{path} ? "/$self->{path}" : $self->{path}); + $url =~ s!^\Q$repos_root\E/*!!g; + $url; +} + sub copy_remote_ref { my ($self) = @_; my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; @@ -998,16 +1008,97 @@ sub do_git_commit { return $commit; } +sub revisions_eq { + my ($self, $r0, $r1) = @_; + return 1 if $r0 == $r1; + my $nr = 0; + $self->ra->get_log([$self->{path}], $r0, $r1, + 0, 0, 1, sub { $nr++ }); + return 0 if ($nr > 1); + return 1; +} + +sub find_parent_branch { + my ($self, $paths, $rev) = @_; + + # look for a parent from another branch: + my $i = $paths->{'/'.$self->rel_path} or return; + my $branch_from = $i->copyfrom_path or return; + my $r = $i->copyfrom_rev; + my $repos_root = $self->ra->{repos_root}; + my $url = $self->ra->{url}; + my $new_url = $repos_root . $branch_from; + print STDERR "Found possible branch point: ", + "$new_url => ", $self->full_url, ", $r\n"; + $branch_from =~ s#^/##; + my $remotes = read_all_remotes(); + my $gs; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $url ne $u; + my $fetch = $remotes->{$repo_id}->{fetch}; + foreach my $f (keys %$fetch) { + next if $f ne $branch_from; + $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); + last; + } + last if $gs; + } + unless ($gs) { + my $ref_id = $branch_from; + $ref_id .= "\@$r" if find_ref($ref_id); + # just grow a tail if we're not unique enough :x + $ref_id .= '-' while find_ref($ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + } + my ($r0, $parent) = $gs->find_rev_before($r, 1); + if ($::_follow_parent && (!defined $r0 || !defined $parent)) { + foreach (0 .. $r) { + my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->do_git_commit($log_entry) if $log_entry; + } + ($r0, $parent) = $gs->last_rev_commit; + } + if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { + print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; + command_noisy('read-tree', $parent); + my $ed; + if ($self->ra->can_do_switch) { + # do_switch works with svn/trunk >= r22312, but that + # is not included with SVN 1.4.2 (the latest version + # at the moment), so we can't rely on it + $self->{last_commit} = $parent; + $ed = SVN::Git::Fetcher->new($self); + $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $self->full_url, $ed) + or die "SVN connection failed somewhere...\n"; + } else { + $ed = SVN::Git::Fetcher->new($self); + $self->ra->gs_do_update($rev, $rev, $self->{path}, + 1, $ed) + or die "SVN connection failed somewhere...\n"; + } + return $self->make_log_entry($rev, [$parent], $ed); + } + print STDERR "Branch parent not found...\n"; + return undef; +} + sub do_fetch { my ($self, $paths, $rev) = @_; - my $ed = SVN::Git::Fetcher->new($self); + my $ed; my ($last_rev, @parents); if ($self->{last_commit}) { + $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @parents = ($self->{last_commit}); } else { $last_rev = $rev; + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + return $log_entry; + } + $ed = SVN::Git::Fetcher->new($self); } unless ($self->ra->gs_do_update($last_rev, $rev, $self->{path}, 1, $ed)) { @@ -1120,9 +1211,9 @@ sub fetch { my @revs; $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; - push @revs, $rev }); + push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(undef, $_); + my $log_entry = $self->do_fetch(@$_); $self->do_git_commit($log_entry, @parents); } last if $max >= $head; @@ -1232,6 +1323,18 @@ sub rev_db_get { $ret; } +sub find_rev_before { + my ($self, $rev, $eq_ok) = @_; + --$rev unless $eq_ok; + while ($rev > 0) { + if (my $c = $self->rev_db_get($rev)) { + return ($rev, $c); + } + --$rev; + } + return (undef, undef); +} + sub _new { my ($class, $repo_id, $ref_id, $path) = @_; unless (defined $repo_id && length $repo_id) { @@ -1398,93 +1501,6 @@ sub uri_decode { $f } -sub revisions_eq { - my ($path, $r0, $r1) = @_; - return 1 if $r0 == $r1; - my $nr = 0; - # should be OK to use Pool here (r1 - r0) should be small - $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++}); - return 0 if ($nr > 1); - return 1; -} - -sub libsvn_find_parent_branch { - my ($paths, $rev, $author, $date, $log) = @_; - my $svn_path = '/'.$SVN->{svn_path}; - - # look for a parent from another branch: - 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_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{repos_root}; - defined $l_map->{$url} or return; - my $id = $l_map->{$url}->{$branch_from}; - if (!defined $id && $_follow_parent) { - print STDERR "Following parent: $branch_from\@$r\n"; - # auto create a new branch and follow it - $id = basename($branch_from); - $id .= '@'.$r if -r "$GIT_DIR/svn/$id"; - while (-r "$GIT_DIR/svn/$id") { - # just grow a tail if we're not unique enough :x - $id .= '-'; - } - } - return unless defined $id; - - my ($r0, $parent) = find_rev_before($r,$id,1); - if ($_follow_parent && (!defined $r0 || !defined $parent)) { - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - $SVN_URL = "$url/$branch_from"; - $SVN = undef; - setup_git_svn(); - # we can't assume SVN_URL exists at r+1: - $_revision = "0:$r"; - fetch_lib(); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - ($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: ($GIT_SVN) $parent\n"; - command_noisy('read-tree', $parent); - unless ($SVN->can_do_switch) { - return _libsvn_new_tree($paths, $rev, $author, $date, - $log, [$parent]); - } - # do_switch works with svn/trunk >= r22312, but that is not - # included with SVN 1.4.2 (the latest version at the moment), - # so we can't rely on it. - my $ra = Git::SVN::Ra->new("$url/$branch_from"); - my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); - $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or - die "SVN connection failed somewhere...\n"; - return libsvn_log_entry($rev, $author, $date, $log, [$parent]); - } - print STDERR "Nope, branch point not imported or unknown\n"; - return undef; -} - -sub _libsvn_new_tree { - my ($paths, $rev, $author, $date, $log, $parents) = @_; - my $ed = SVN::Git::Fetcher->new({q => $_q}); - unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { - die "SVN connection failed somewhere...\n"; - } - libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 405b555368..91fdfe964e 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,8 +30,10 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - git-rev-parse --verify refs/remotes/trunk && - test '$?' -eq '0' + test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye " test_debug 'gitk --all &' -- cgit v1.2.3 From 8b8fc06824cde2b314807e5e3a20e0adfd948cda Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 11:44:57 -0800 Subject: git-svn: --follow-parent works with svn-remotes multiple branches Bugs fixed: * We didn't allow manually (not using git-svn) init-ed remotes/fetch refspecs to be used before. It works now because that's what I did in this test. git-svn init should offer more control in the future. * correctly strip paths in the delta editor when using do_switch(). * Make the -i / GIT_SVN_ID option work correctly when doing fetch on a multi-ref svn-remote Signed-off-by: Eric Wong --- git-svn.perl | 49 ++++++++++++++++++++++++++-------------- t/t9104-git-svn-follow-parent.sh | 13 +++++++++++ 2 files changed, 45 insertions(+), 17 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 8d49959f37..84f4679570 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -14,7 +14,8 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; +$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -158,7 +159,7 @@ my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_repo_id); + 'id|i=s' => \$Git::SVN::default_ref_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -686,7 +687,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id/; +use vars qw/$default_repo_id $default_ref_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -704,7 +705,7 @@ BEGIN { sub read_all_remotes { my $r = {}; - foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { @@ -724,7 +725,6 @@ sub sanitize_remote_name { sub init { my ($class, $url, $path, $repo_id, $ref_id) = @_; my $self = _new($class, $repo_id, $ref_id, $path); - mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash my $orig_url = eval { @@ -745,10 +745,6 @@ sub init { "$path:".$self->refname); } $self->{url} = $url; - unless (-f $self->{db_path}) { - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; - } $self; } @@ -777,6 +773,14 @@ sub new { } } my $self = _new($class, $repo_id, $ref_id, $path); + if (!defined $self->{path} || !length $self->{path}) { + my $fetch = command_oneline('config', '--get', + "svn-remote.$repo_id.fetch", + ":refs/remotes/$ref_id\$") or + die "Failed to read \"svn-remote.$repo_id.fetch\" ", + "\":refs/remotes/$ref_id\$\" in config\n"; + ($self->{path}, undef) = split(/\s*:\s*/, $fetch); + } $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; @@ -1064,6 +1068,7 @@ sub find_parent_branch { command_noisy('read-tree', $parent); my $ed; if ($self->ra->can_do_switch) { + print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.2 (the latest version # at the moment), so we can't rely on it @@ -1073,6 +1078,7 @@ sub find_parent_branch { $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { + print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); $self->ra->gs_do_update($rev, $rev, $self->{path}, 1, $ed) @@ -1209,7 +1215,7 @@ sub fetch { $SVN::Error::handler = \&skip_unknown_revs; while (1) { my @revs; - $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { @@ -1341,11 +1347,16 @@ sub _new { $repo_id = $Git::SVN::default_repo_id; } unless (defined $ref_id && length $ref_id) { - $_[2] = $ref_id = $repo_id; + $_[2] = $ref_id = $Git::SVN::default_ref_id; } $_[1] = $repo_id = sanitize_remote_name($repo_id); my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); + mkpath([$dir]); + unless (-f "$dir/.rev_db") { + open my $fh, '>>', "$dir/.rev_db" or croak $!; + close $fh or croak $!; + } bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; @@ -1526,9 +1537,6 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; - if (length $git_svn->{path}) { - $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; - } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1540,6 +1548,11 @@ sub new { $self; } +sub set_path_strip { + my ($self, $path) = @_; + $self->{path_strip} = qr/^\Q$path\E\/?/; +} + sub open_root { { path => '' }; } @@ -2128,6 +2141,7 @@ sub uuid { sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_update($rev_b, $path, $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); @@ -2141,10 +2155,11 @@ sub gs_do_update { sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_switch($rev_b, $path, $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path($path, $rev_a, 0, @lock, $pool); + $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2674,12 +2689,12 @@ sub minimize_connections { my $old_fetch = quotemeta("$x->{old_path}:". "refs/remotes/$x->{ref_id}"); - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.fetch", '^'. $old_fetch . '$'); delete $r->{$x->{old_repo_id}}-> {fetch}->{$x->{old_path}}; if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.url"); push @emptied, $x->{old_repo_id} } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 91fdfe964e..3afec978d6 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -36,6 +36,19 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'init and fetch from one svn-remote' " + git-repo-config svn-remote.git-svn.url $svnrepo && + git-repo-config --add svn-remote.git-svn.fetch \ + trunk:refs/remotes/svn/trunk && + git-repo-config --add svn-remote.git-svn.fetch \ + thunk:refs/remotes/svn/thunk && + git-svn fetch --follow-parent -i svn/thunk && + test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From b805b44a923e32251af1abd4e8d7bf5f7d4d8ef6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 13:52:04 -0800 Subject: git-svn: disallow ambigious local refspecs Having multiple fetch refspecs pointing to the same local ref would be a very bad thing. Start avoiding the use of fatal() or exit() inside the modules so we can libify more easily. Signed-off-by: Eric Wong --- git-svn.perl | 40 +++++++++++++++++++++++++++++++++------- t/t9100-git-svn-basic.sh | 11 +++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 84f4679570..f01fb9a35d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -169,7 +169,11 @@ load_authors() if $_authors; unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } -$cmd{$cmd}->[0]->(@ARGV); +eval { + Git::SVN::verify_remotes_sanity(); + $cmd{$cmd}->[0]->(@ARGV); +}; +fatal $@ if $@; exit 0; ####################### primary functions ###################### @@ -715,6 +719,22 @@ sub read_all_remotes { $r; } +sub verify_remotes_sanity { + my %seen; + foreach (command(qw/config -l/)) { + if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) { + if ($seen{$1}) { + die "Remote ref refs/remote/$1 is tracked by", + "\n \"$_\"\nand\n \"$seen{$1}\"\n", + "Please resolve this ambiguity in ", + "your git configuration file before ", + "continuing\n"; + } + $seen{$1} = $_; + } + } +} + # we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; @@ -727,16 +747,22 @@ sub init { my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash + + # verify that we aren't overwriting anything: my $orig_url = eval { command_oneline('config', '--get', "svn-remote.$repo_id.url") }; - if ($orig_url) { - if ($orig_url ne $url) { - die "svn-remote.$repo_id.url already set: ", - "$orig_url\nwanted to set to: $url\n"; - } - } else { + if ($orig_url && ($orig_url ne $url)) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + if (!$orig_url) { command_noisy('config', "svn-remote.$repo_id.url", $url); } diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 040da92756..af617486dd 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -215,4 +215,15 @@ echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected test_expect_success "$name" "diff -u a expected" +test_expect_failure 'exit if remote refs are ambigious' " + git-repo-config --add svn-remote.git-svn.fetch \ + bar:refs/remotes/git-svn && + git-svn migrate + " +test_expect_failure 'exit if init-ing a would clobber a URL' " + git-repo-config --unset svn-remote.git-svn.fetch \ + '^bar:refs/remotes/git-svn$' && + git-svn init $svnrepo/bar + " + test_done -- cgit v1.2.3 From a2003abc23a5961534e8a0cc70b881eb78d54328 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:22:50 -0800 Subject: git-svn: allow --follow-parent on deleted directories Any operations on the index in Git::SVN that is not wrapped by tmp_index_do() is wrong. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9104-git-svn-follow-parent.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index f01fb9a35d..88c022701d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1091,7 +1091,7 @@ sub find_parent_branch { } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; - command_noisy('read-tree', $parent); + $self->assert_index_clean($parent); my $ed; if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 3afec978d6..402b614c76 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -49,6 +49,18 @@ test_expect_success 'init and fetch from one svn-remote' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'follow deleted parent' " + svn cp -m 'resurrecting trunk as junk' \ + -r2 $svnrepo/trunk $svnrepo/junk && + git-repo-config --add svn-remote.git-svn.fetch \ + junk:refs/remotes/svn/junk && + git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/junk --follow-parent && + test -z \"\`git diff svn/junk svn/trunk\`\" && + test \"\`git merge-base svn/junk svn/trunk\`\" \ + = \"\`git rev-parse svn/trunk\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From 07a1c95045a8c4983d3868f6070d9fa9ba5ff596 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:47:41 -0800 Subject: git-svn: get rid of additional fetch-arguments It's not really useful anymore now that we have a better --follow-parent for the valid cases. Any other use of it is not valid. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 24 ------------------------ git-svn.perl | 7 ++++++- 2 files changed, 6 insertions(+), 25 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6ce6a3944d..6daba241e9 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,9 +49,6 @@ remotes/git-svn and work on that branch. Use the 'dcommit' command (see below) to write git commits back to remotes/git-svn. -See '<>' if you are interested in -manually joining branches on commit. - 'dcommit':: Commit each diff from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or @@ -443,27 +440,6 @@ be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. -[[fetch-args]] -ADDITIONAL FETCH ARGUMENTS --------------------------- -This is for advanced users, most users should ignore this section. - -Unfetched SVN revisions may be imported as children of existing commits -by specifying additional arguments to 'fetch'. Additional parents may -optionally be specified in the form of sha1 hex sums at the -command-line. Unfetched SVN revisions may also be tied to particular -git commits with the following syntax: - ------------------------------------------------- - svn_revision_number=git_commit_sha1 ------------------------------------------------- - -This allows you to tie unfetched SVN revision 375 to your current HEAD: - ------------------------------------------------- - git-svn fetch 375=$(git-rev-parse HEAD) ------------------------------------------------- - If you're tracking a directory that has moved, or otherwise been branched or tagged off of another directory in the repository and you care about the full history of the project, then you can use diff --git a/git-svn.perl b/git-svn.perl index 88c022701d..2e3d35527e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -277,8 +277,13 @@ sub cmd_init { } sub cmd_fetch { + if (@_) { + die "Additional fetch arguments are no longer supported.\n", + "Use --follow-parent if you have moved/copied directories + instead.\n"; + } my $gs = Git::SVN->new; - $gs->fetch(@_); + $gs->fetch; if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { command_noisy(qw(update-ref refs/heads/master), $gs->{last_commit}); -- cgit v1.2.3 From 536c4b09370f3f443fbe87284d2378fd21f94350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 11:35:53 -0800 Subject: git-svn: allow 'init' to work outside of tests Tests always ran 'git init' before we ran so that repo-config would always have something to read. However that does not work in real-world situations where the user expects 'git svn init' to work without running 'git init' first. Signed-off-by: Eric Wong --- git-svn.perl | 1 + 1 file changed, 1 insertion(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 2e3d35527e..a70e7b9110 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -725,6 +725,7 @@ sub read_all_remotes { } sub verify_remotes_sanity { + return unless -d $ENV{GIT_DIR}; my %seen; foreach (command(qw/config -l/)) { if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) { -- cgit v1.2.3 From 9bf046372b370fba8958ba6ef9dc63b232d7637c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 13:03:29 -0800 Subject: git-svn: better error reporting if --follow-parent fails This will be useful to me when I try more special-cases of parent-tracking. Signed-off-by: Eric Wong --- git-svn.perl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index a70e7b9110..de026b4e4c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1056,10 +1056,12 @@ sub revisions_eq { sub find_parent_branch { my ($self, $paths, $rev) = @_; + return undef unless $::_follow_parent; # look for a parent from another branch: - my $i = $paths->{'/'.$self->rel_path} or return; - my $branch_from = $i->copyfrom_path or return; + my $abs_path = '/'.$self->rel_path; + my $i = $paths->{$abs_path} or goto not_found; + my $branch_from = $i->copyfrom_path or goto not_found; my $r = $i->copyfrom_rev; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; @@ -1118,7 +1120,16 @@ sub find_parent_branch { } return $self->make_log_entry($rev, [$parent], $ed); } - print STDERR "Branch parent not found...\n"; +not_found: + print STDERR "Branch parent for path: '$abs_path' not found\n"; + return undef unless $paths; + foreach my $p (sort keys %$paths) { + print STDERR ' ', $p->action, ' ', $p; + if (my $cp_from = $p->copyfrom_path) { + print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; + } + print STDERR "\n"; + } return undef; } -- cgit v1.2.3 From e6434f876097f196acbd9a806637d0f6076752fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 16:29:23 -0800 Subject: git-svn: 'init' attempts to connect to the repository root if possible This allows connections to be used more efficiently and not require users to run 'git-svn migrate --minimize' for new repositories. Signed-off-by: Eric Wong --- git-svn.perl | 96 ++++++++++++++++++++++++++++++++++++++---------- t/t9100-git-svn-basic.sh | 14 ++++++- 2 files changed, 89 insertions(+), 21 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index de026b4e4c..d290a0d8ee 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -748,35 +748,78 @@ sub sanitize_remote_name { $name; } -sub init { - my ($class, $url, $path, $repo_id, $ref_id) = @_; - my $self = _new($class, $repo_id, $ref_id, $path); - if (defined $url) { - $url =~ s!/+$!!; # strip trailing slash +sub find_existing_remote { + my ($url, $remotes) = @_; + my $existing; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $u ne $url; + $existing = $repo_id; + last; + } + $existing; +} +sub init_remote_config { + my ($self, $url) = @_; + $url =~ s!/+$!!; # strip trailing slash + my $r = read_all_remotes(); + my $existing = find_existing_remote($url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } else { + my $min_url = Git::SVN::Ra->new($url)->minimize_url; + $existing = find_existing_remote($min_url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } + if ($min_url ne $url) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + my $old_path = $self->{path}; + $self->{path} = $url; + $self->{path} =~ s!^\Q$min_url\E/*!!; + if (length $old_path) { + $self->{path} .= "/$old_path"; + } + $url = $min_url; + } + } + my $orig_url; + if (!$existing) { # verify that we aren't overwriting anything: - my $orig_url = eval { + $orig_url = eval { command_oneline('config', '--get', - "svn-remote.$repo_id.url") + "svn-remote.$self->{repo_id}.url") }; if ($orig_url && ($orig_url ne $url)) { - die "svn-remote.$repo_id.url already set: ", + die "svn-remote.$self->{repo_id}.url already set: ", "$orig_url\nwanted to set to: $url\n"; } - my ($xrepo_id, $xpath) = find_ref($self->refname); - if (defined $xpath) { - die "svn-remote.$xrepo_id.fetch already set to track ", - "$xpath:refs/remotes/", $self->refname, "\n"; - } - if (!$orig_url) { - command_noisy('config', - "svn-remote.$repo_id.url", $url); - } - command_noisy('config', '--add', - "svn-remote.$repo_id.fetch", - "$path:".$self->refname); } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); $self->{url} = $url; +} + +sub init { + my ($class, $url, $path, $repo_id, $ref_id) = @_; + my $self = _new($class, $repo_id, $ref_id, $path); + if (defined $url) { + $self->init_remote_config($url); + } $self; } @@ -2208,6 +2251,19 @@ sub gs_do_switch { $editor->{git_commit_ok}; } +sub minimize_url { + my ($self) = @_; + return $self->{url} if ($self->{url} eq $self->{repos_root}); + my $url = $self->{repos_root}; + my @components = split(m!/!, $self->{svn_path}); + my $c = ''; + do { + $url .= "/$c" if length $c; + eval { (ref $self)->new($url)->get_latest_revnum }; + } while ($@ && ($c = shift @components)); + $url; +} + sub can_do_switch { my $self = shift; unless (defined $can_do_switch) { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index af617486dd..97798c4d07 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -220,10 +220,22 @@ test_expect_failure 'exit if remote refs are ambigious' " bar:refs/remotes/git-svn && git-svn migrate " + test_expect_failure 'exit if init-ing a would clobber a URL' " + svnadmin create ${PWD}/svnrepo2 && + svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && git-repo-config --unset svn-remote.git-svn.fetch \ '^bar:refs/remotes/git-svn$' && - git-svn init $svnrepo/bar + git-svn init ${svnrepo}2/bar + " + +test_expect_success \ + 'init allows us to connect to another directory in the same repo' " + git-svn init -i bar $svnrepo/bar && + git repo-config --get svn-remote.git-svn.fetch \ + '^bar:refs/remotes/bar$' && + git repo-config --get svn-remote.git-svn.fetch \ + '^:refs/remotes/git-svn$' " test_done -- cgit v1.2.3 From 7f578c55af80e9346135004bd47099cbb451f859 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 02:16:25 -0800 Subject: git-svn: --follow-parent now works on sub-directories of larger branches This means that tracking the path of: /another-larger/trunk/thunk/bump/thud inside a repository would follow: /larger-parent/trunk/thunk/bump/thud even if the svn log output looks like this: -------------------------------------------- Changed paths: A /another-larger (from /larger-parent:5) -------------------------------------------- Note: the usage of get_log() in git-svn still makes a an assumption that shouldn't be made with regard to revisions existing for a particular path. Signed-off-by: Eric Wong --- git-svn.perl | 31 +++++++++++++++++++++++-------- t/t9104-git-svn-follow-parent.sh | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index d290a0d8ee..123d4d63f4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1102,9 +1102,21 @@ sub find_parent_branch { return undef unless $::_follow_parent; # look for a parent from another branch: - my $abs_path = '/'.$self->rel_path; - my $i = $paths->{$abs_path} or goto not_found; + my @b_path_components = split m#/#, $self->rel_path; + my @a_path_components; + my $i; + while (@b_path_components) { + $i = $paths->{'/'.join('/', @b_path_components)}; + last if $i; + unshift(@a_path_components, pop(@b_path_components)); + } + goto not_found unless defined $i; my $branch_from = $i->copyfrom_path or goto not_found; + if (@a_path_components) { + print STDERR "branch_from: $branch_from => "; + $branch_from .= '/'.join('/', @a_path_components); + print STDERR $branch_from, "\n"; + } my $r = $i->copyfrom_rev; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; @@ -1134,10 +1146,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (0 .. $r) { - my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { + my ($paths, $rev) = @_; + my $log_entry = eval { $gs->do_fetch($paths, $rev) }; $gs->do_git_commit($log_entry) if $log_entry; - } + }); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1164,10 +1177,12 @@ sub find_parent_branch { return $self->make_log_entry($rev, [$parent], $ed); } not_found: - print STDERR "Branch parent for path: '$abs_path' not found\n"; + print STDERR "Branch parent for path: '/", + $self->rel_path, "' not found\n"; return undef unless $paths; - foreach my $p (sort keys %$paths) { - print STDERR ' ', $p->action, ' ', $p; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR ' ', $p->action, ' ', $x; if (my $cp_from = $p->copyfrom_path) { print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 402b614c76..22b45a6602 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -61,6 +61,23 @@ test_expect_success 'follow deleted parent' " = \"\`git rev-parse svn/trunk\`\" " +test_expect_success 'follow larger parent' " + mkdir -p import/trunk/thunk/bump/thud && + echo hi > import/trunk/thunk/bump/thud/file && + svn import -m 'import a larger parent' import $svnrepo/larger-parent && + svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && + git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && + git-svn fetch -i larger --follow-parent && + git-rev-parse --verify refs/remotes/larger && + git-rev-parse --verify \ + refs/remotes/larger-parent/trunk/thunk/bump/thud && + test \"\`git-merge-base \ + refs/remotes/larger-parent/trunk/thunk/bump/thud \ + refs/remotes/larger\`\" = \ + \"\`git-rev-parse refs/remotes/larger\`\" + true + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From ef3cfaad19f2587ea4ff7d46574d9118f8d9555e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 03:30:57 -0800 Subject: git-svn: track writes writes to the index in fetch Introducing Git::IndexInfo. This module will probably be useful outside of git-svn, so I'm not putting it in the Git::SVN namespace. This will allow me to more easily avoid the use of get_log() in the future and simply run do_update in incrementing ranges. get_log() should be avoided because there are cases where moved/deleted directories do not track correctly (until --follow-parent is run on a new branch). Signed-off-by: Eric Wong --- git-svn.perl | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 123d4d63f4..a19afb83fb 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1643,8 +1643,7 @@ sub new { $self->{file_prop} = {}; $self->{absent_dir} = {}; $self->{absent_file} = {}; - ($self->{gui}, $self->{ctx}) = $git_svn->tmp_index_do( - sub { command_input_pipe(qw/update-index -z --index-info/) } ); + $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); require Digest::MD5; $self; } @@ -1671,7 +1670,6 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - my $gui = $self->{gui}; my $gpath = $self->git_path($path); # remove entire directories. @@ -1681,14 +1679,15 @@ sub delete_entry { $self->{c}, '--', $gpath); local $/ = "\0"; while (<$ls>) { - print $gui '0 ',0 x 40,"\t",$_ or croak $!; + chomp; + $self->{gii}->remove($_); print "\tD\t$_\n" unless $self->{q}; } print "\tD\t$gpath/\n" unless $self->{q}; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { - print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!; + $self->{gii}->remove($gpath); print "\tD\t$gpath\n" unless $self->{q}; } undef; @@ -1824,22 +1823,23 @@ sub close_file { $hash = $fb->{blob} or die "no blob information\n"; } $fb->{pool}->clear; - my $gui = $self->{gui}; - print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!; + $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!; print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; undef; } sub abort_edit { my $self = shift; - eval { command_close_pipe($self->{gui}, $self->{ctx}) }; + $self->{nr} = $self->{gii}->{nr}; + delete $self->{gii}; $self->SUPER::abort_edit(@_); } sub close_edit { my $self = shift; - command_close_pipe($self->{gui}, $self->{ctx}); $self->{git_commit_ok} = 1; + $self->{nr} = $self->{gii}->{nr}; + delete $self->{gii}; $self->SUPER::close_edit(@_); } @@ -2832,6 +2832,38 @@ sub migration_check { minimize_connections() if $_minimize; } +package Git::IndexInfo; +use strict; +use warnings; +use Git qw/command_input_pipe command_close_pipe/; + +sub new { + my ($class) = @_; + my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/); + bless { gui => $gui, ctx => $ctx, nr => 0}, $class; +} + +sub remove { + my ($self, $path) = @_; + if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") { + return ++$self->{nr}; + } + undef; +} + +sub update { + my ($self, $mode, $hash, $path) = @_; + if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") { + return ++$self->{nr}; + } + undef; +} + +sub DESTROY { + my ($self) = @_; + command_close_pipe($self->{gui}, $self->{ctx}); +} + __END__ Data structures: -- cgit v1.2.3 From 97f6987afaae239f7e3ae3944e0b29343b43a894 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 11:53:13 -0800 Subject: git-svn: avoid tracking change-less revisions They simply aren't interesting to track, and this will allow us to avoid get_log(). Since r0 is covered by this, we need to update the tests to not rely on r0 (which is always empty). Signed-off-by: Eric Wong --- git-svn.perl | 61 +++++++++++++++++++++++++--------------------- t/t9100-git-svn-basic.sh | 2 -- t/t9107-git-svn-migrate.sh | 14 +++++++---- 3 files changed, 42 insertions(+), 35 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index a19afb83fb..6ff3a8c5c5 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -315,7 +315,7 @@ sub cmd_set_tree { my $gs = Git::SVN->new; my ($r_last, $cmt_last) = $gs->last_rev_commit; $gs->fetch; - if ($r_last != $gs->{last_rev}) { + if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) { fatal "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", "before committing.\nlast rev: $r_last\n", @@ -1214,50 +1214,46 @@ sub do_fetch { $self->make_log_entry($rev, \@parents, $ed); } -sub write_untracked { - my ($self, $rev, $fh, $untracked) = @_; - my $h; - print $fh "r$rev\n" or croak $!; - $h = $untracked->{empty}; +sub get_untracked { + my ($self, $ed) = @_; + my @out; + my $h = $ed->{empty}; foreach (sort keys %$h) { my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; - print $fh " $act: ", uri_encode($_), "\n" or croak $!; + push @out, " $act: " . uri_encode($_); warn "W: $act: $_\n"; } foreach my $t (qw/dir_prop file_prop/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $path (sort keys %$h) { my $ppath = $path eq '' ? '.' : $path; foreach my $prop (sort keys %{$h->{$path}}) { next if $SKIP_PROP{$prop}; my $v = $h->{$path}->{$prop}; + my $t_ppath_prop = "$t: " . + uri_encode($ppath) . ' ' . + uri_encode($prop); if (defined $v) { - print $fh " +$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), ' ', - uri_encode($v), "\n" - or croak $!; + push @out, " +$t_ppath_prop " . + uri_encode($v); } else { - print $fh " -$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), "\n" - or croak $!; + push @out, " -$t_ppath_prop"; } } } } foreach my $t (qw/absent_file absent_directory/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $parent (sort keys %$h) { foreach my $path (sort @{$h->{$parent}}) { - print $fh " $t: ", - uri_encode("$parent/$path"), "\n" - or croak $!; + push @out, " $t: " . + uri_encode("$parent/$path"); warn "W: $t: $parent/$path ", "Insufficient permissions?\n"; } } } + \@out; } sub parse_svn_date { @@ -1280,12 +1276,15 @@ sub check_author { } sub make_log_entry { - my ($self, $rev, $parents, $untracked) = @_; - my $rp = $self->ra->rev_proplist($rev); - my %log_entry = ( parents => $parents || [], revision => $rev, - revprops => $rp, log => ''); + my ($self, $rev, $parents, $ed) = @_; + my $untracked = $self->get_untracked($ed); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; - $self->write_untracked($rev, $un, $untracked); + print $un "r$rev\n" or croak $!; + print $un $_, "\n" foreach @$untracked; + my %log_entry = ( parents => $parents || [], revision => $rev, + log => ''); + my $rp = $self->ra->rev_proplist($rev); foreach (sort keys %$rp) { my $v = $rp->{$_}; if (/^svn:(author|date|log)$/) { @@ -1296,6 +1295,11 @@ sub make_log_entry { } } close $un or croak $!; + + delete $rp->{'svn:date'}; # this is the only revprop for r0 + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && + scalar keys %$rp == 0); + $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1320,8 +1324,9 @@ sub fetch { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(@$_); - $self->do_git_commit($log_entry, @parents); + if (my $log_entry = $self->do_fetch(@$_)) { + $self->do_git_commit($log_entry, @parents); + } } last if $max >= $head; $min = $max + 1; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 97798c4d07..5355243b92 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -211,8 +211,6 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF -echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected - test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 74a45ec647..f6d84ba7a5 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -5,13 +5,17 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + mkdir import && + cd import + for i in trunk branches/a branches/b \ + tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && \ + echo hello >> \$i/README || exit 1 + done && \ + svn import -m test . $svnrepo + cd .. && git-svn init $svnrepo && git-svn fetch && - for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do - mkdir -p \$i && echo hello >> \$i/README || exit 1; done && - git ls-files -o trunk branches tags | git update-index --add --stdin && - git commit -m 'test' && - git-svn dcommit && mv $GIT_DIR/svn/* $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && -- cgit v1.2.3 From e5a0b240fc237af6165b728ae9c79288ef624d3b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 15:44:54 -0800 Subject: git-svn: correctly track revisions made to deleted branches git-svn has never been able to handle deleted branches very well because svn_ra_get_log() is all-or-nothing, meaning that if the max revision passed to it does not contain the path we're tracking, we miss all the revisions in the repository. Branches fetched using --follow-parent still do this sub-optimally (will be fixed soon). --follow-parent will soon become the default, so we will assume that when using get_log(); We will also avoid tracking revprops for revisions with no path-related changes since otherwise we just end up pulling logs to paths we don't care about. Also added a test for this to t9104-git-svn-follow-parent.sh and correctly commit the log message in the preceeding test (which conflicted with a filename). Signed-off-by: Eric Wong --- git-svn.perl | 46 +++++++++++++++++++++------------------- t/t9104-git-svn-follow-parent.sh | 11 +++++++++- 2 files changed, 34 insertions(+), 23 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 6ff3a8c5c5..0e2348af35 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1100,6 +1100,11 @@ sub revisions_eq { sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; + unless (defined $paths) { + $self->ra->get_log([''], $rev, $rev, 0, 1, 1, + sub { $paths = $_[0] }); + } + return undef unless defined $paths; # look for a parent from another branch: my @b_path_components = split m#/#, $self->rel_path; @@ -1146,11 +1151,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { - my ($paths, $rev) = @_; - my $log_entry = eval { $gs->do_fetch($paths, $rev) }; - $gs->do_git_commit($log_entry) if $log_entry; - }); + foreach (1 .. $r) { + if (my $log_entry = $gs->do_fetch(undef, $_)) { + $gs->do_git_commit($log_entry); + } + } ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1178,16 +1183,8 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' not found\n"; - return undef unless $paths; - foreach my $x (sort keys %$paths) { - my $p = $paths->{$x}; - print STDERR ' ', $p->action, ' ', $x; - if (my $cp_from = $p->copyfrom_path) { - print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; - } - print STDERR "\n"; - } + $self->rel_path, "' @ $rev not found\n"; + print STDERR ' ', $_, "\n" foreach (sort keys %$paths); return undef; } @@ -1279,6 +1276,8 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; @@ -1296,10 +1295,6 @@ sub make_log_entry { } close $un or croak $!; - delete $rp->{'svn:date'}; # this is the only revprop for r0 - return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && - scalar keys %$rp == 0); - $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1317,18 +1312,26 @@ sub fetch { my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = \&skip_unknown_revs; + my $err; + $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); + if (! @revs && $err) { + print STDERR "Branch probably deleted:\n ", + $err->expanded_message, + "\nWill attempt to follow revisions ", + "committed before the deletion\n"; + @revs = map { [ undef, $_ ] } ($min .. $max); + } foreach (@revs) { if (my $log_entry = $self->do_fetch(@$_)) { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head; + last if $max >= $head || $err; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -2226,7 +2229,6 @@ sub dup { sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; - $args[4]-- if $args[4] && ! $::_follow_parent; splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); my $ret = $self->SUPER::get_log(@args, $pool); $pool->clear; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 615c863b94..a6ba0faebd 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -85,7 +85,7 @@ test_expect_success 'follow higher-level parent' " cd blob && echo hi > hi && svn add hi && - svn commit -m 'hi' && + svn commit -m 'hihi' && cd .. svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && @@ -93,6 +93,15 @@ test_expect_success 'follow higher-level parent' " git-svn fetch -i blob --follow-parent " +test_expect_success 'follow deleted directory' " + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn rm -m 'remove glob' $svnrepo/glob && + git-svn init -i glob $svnrepo/glob && + git-svn fetch -i glob && + test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && + test -z \"\`git ls-tree -z refs/remotes/glob\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From 3ebe8df7f690281c21e330eec156098c14f4e685 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 17:35:40 -0800 Subject: git-svn: fix segfaults from accessing svn_log_changed_path_t svn_log_changed_path_t structs were being used out of scope outside of svn_ra_get_log (because I wanted to eventually be able to use git-svn with only a single connection to the repository). So now we dup them into a hash. This was fixed while making --follow-parent fetches more efficient. I've moved parsing of the command-line --revision argument outside of the Git::SVN module so Git::SVN::fetch() can be used in more places (such as find_parent_branch). Signed-off-by: Eric Wong --- git-svn.perl | 104 +++++++++++++++++++++++++-------------- t/t9104-git-svn-follow-parent.sh | 1 - 2 files changed, 68 insertions(+), 37 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 0e2348af35..4c9ef7fe15 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -283,7 +283,7 @@ sub cmd_fetch { instead.\n"; } my $gs = Git::SVN->new; - $gs->fetch; + $gs->fetch(parse_revision_argument()); if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { command_noisy(qw(update-ref refs/heads/master), $gs->{last_commit}); @@ -482,6 +482,18 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub parse_revision_argument { + if (!defined $_revision || $_revision eq 'BASE:HEAD') { + return (undef, undef); + } + return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); + return ($_revision, $_revision) if ($_revision =~ /^\d+$/); + return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/); + return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/); + die "revision argument: $_revision not understood by git-svn\n", + "Try using the command-line svn client instead\n"; +} + sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -914,6 +926,9 @@ sub traverse_ignore { } } +sub last_rev { ($_[0]->last_rev_commit)[0] } +sub last_commit { ($_[0]->last_rev_commit)[1] } + # returns the newest SVN revision number and newest commit SHA1 sub last_rev_commit { my ($self) = @_; @@ -951,22 +966,11 @@ sub last_rev_commit { return ($rev, $c); } -sub parse_revision { - my ($self, $base) = @_; - my $head = $self->ra->get_latest_revnum; - if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); - return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); - if ($::_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); - die "revision argument: $::_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; +sub get_fetch_range { + my ($self, $min, $max) = @_; + $max ||= $self->ra->get_latest_revnum; + $min ||= $self->last_rev || 0; + (++$min, $max); } sub tmp_index_do { @@ -1101,8 +1105,8 @@ sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; unless (defined $paths) { - $self->ra->get_log([''], $rev, $rev, 0, 1, 1, - sub { $paths = $_[0] }); + $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, + sub { $paths = dup_changed_paths($_[0]) }); } return undef unless defined $paths; @@ -1116,13 +1120,13 @@ sub find_parent_branch { unshift(@a_path_components, pop(@b_path_components)); } goto not_found unless defined $i; - my $branch_from = $i->copyfrom_path or goto not_found; + my $branch_from = $i->{copyfrom_path} or goto not_found; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); print STDERR $branch_from, "\n"; } - my $r = $i->copyfrom_rev; + my $r = $i->{copyfrom_rev}; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; my $new_url = $repos_root . $branch_from; @@ -1151,11 +1155,7 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (1 .. $r) { - if (my $log_entry = $gs->do_fetch(undef, $_)) { - $gs->do_git_commit($log_entry); - } - } + $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1183,8 +1183,19 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' @ $rev not found\n"; - print STDERR ' ', $_, "\n" foreach (sort keys %$paths); + $self->rel_path, "' @ r$rev not found:\n"; + return undef unless $paths; + print STDERR "Changed paths:\n"; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR "\t$p->{action}\t$x"; + if ($p->{copyfrom_path}) { + print STDERR "(from $p->{copyfrom_path}: ", + "$p->{copyfrom_rev})"; + } + print STDERR "\n"; + } + print STDERR '-'x72, "\n"; return undef; } @@ -1302,9 +1313,9 @@ sub make_log_entry { } sub fetch { - my ($self, @parents) = @_; + my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; - my ($base, $head) = $self->parse_revision($last_rev); + my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); return if ($base > $head); if (defined $last_commit) { $self->assert_index_clean($last_commit); @@ -1316,13 +1327,16 @@ sub fetch { $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; - $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { - my ($paths, $rev, $author, $date, $log) = @_; - push @revs, [ $paths, $rev ] }); - if (! @revs && $err) { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, + sub { + my ($paths, $rev) = @_; + push @revs, [ dup_changed_paths($paths), $rev ]; + }); + if (! @revs && $err && $max >= $head) { print STDERR "Branch probably deleted:\n ", $err->expanded_message, "\nWill attempt to follow revisions ", + "r$min .. r$max", "committed before the deletion\n"; @revs = map { [ undef, $_ ] } ($min .. $max); } @@ -1331,7 +1345,7 @@ sub fetch { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head || $err; + last if $max >= $head; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -1347,7 +1361,7 @@ sub set_tree_cb { $log_entry->{author} = $author; $self->do_git_commit($log_entry, "$rev=$tree"); } else { - $self->fetch("$rev=$tree"); + $self->fetch(undef, undef, "$rev=$tree"); } } @@ -1393,6 +1407,24 @@ sub skip_unknown_revs { croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; } +# svn_log_changed_path_t objects passed to get_log are likely to be +# overwritten even if only the refs are copied to an external variable, +# so we should dup the structures in their entirety. Using an externally +# passed pool (instead of our temporary and quickly cleared pool in +# Git::SVN::Ra) does not help matters at all... +sub dup_changed_paths { + my ($paths) = @_; + return undef unless $paths; + my %ret; + foreach my $p (keys %$paths) { + my $i = $paths->{$p}; + my %s = map { $_ => $i->$_ } + qw/copyfrom_path copyfrom_rev action/; + $ret{$p} = \%s; + } + \%ret; +} + # rev_db: # 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 diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index a6ba0faebd..bfb718886f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -78,7 +78,6 @@ test_expect_success 'follow larger parent' " true " -# This seems to cause segfaults over HTTP... test_expect_success 'follow higher-level parent' " svn mkdir -m 'follow higher-level parent' $svnrepo/blob && svn co $svnrepo/blob blob && -- cgit v1.2.3 From d3a840dc74d2098c31aac1b89093d847e1d33dd8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 26 Jan 2007 01:32:45 -0800 Subject: git-svn: fix committing to subdirectories, add tests I broke this part with the URL minimization; since git-svn will now try to connect to the root of the repository and will end up writing files there if it can... Signed-off-by: Eric Wong --- git-svn.perl | 14 ++++++++++---- t/t9100-git-svn-basic.sh | 29 +++++++++++++++++++++++++++++ t/t9105-git-svn-commit-diff.sh | 9 +++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 4c9ef7fe15..1d448e75da 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -354,7 +354,7 @@ sub cmd_dcommit { my $pool = SVN::Pool->new; my %ed_opts = ( r => $last_rev, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $gs->{path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; @@ -437,6 +437,7 @@ sub cmd_commit_diff { my $usage = "Usage: $0 commit-diff -r ". " []\n"; fatal($usage) if (!defined $ta || !defined $tb); + my $svn_path; if (!defined $url) { my $gs = eval { Git::SVN->new }; if (!$gs) { @@ -444,6 +445,7 @@ sub cmd_commit_diff { "the command-line\n", $usage); } $url = $gs->{url}; + $svn_path = $gs->{path}; } unless (defined $_revision) { fatal("-r|--revision is a required argument\n", $usage); @@ -459,6 +461,7 @@ sub cmd_commit_diff { $_message ||= get_commit_entry($tb)->{log}; } my $ra ||= Git::SVN::Ra->new($url); + $svn_path ||= $ra->{svn_path}; my $r = $_revision; if ($r eq 'HEAD') { $r = $ra->get_latest_revnum; @@ -468,7 +471,7 @@ sub cmd_commit_diff { my $pool = SVN::Pool->new; my %ed_opts = ( r => $r, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $svn_path ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($_message, sub { print "Committed r$_[0]\n" }), @@ -1374,7 +1377,7 @@ sub set_tree { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, ra => $self->ra->dup, - svn_path => $self->ra->{svn_path} + svn_path => $self->{path} }, $self->ra->get_commit_editor( $log_entry->{log}, sub { @@ -1902,6 +1905,8 @@ sub new { $self->{pool} = SVN::Pool->new; $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) }; $self->{rm} = { }; + $self->{path_prefix} = length $self->{svn_path} ? + "$self->{svn_path}/" : ''; require Digest::MD5; return $self; } @@ -1911,7 +1916,8 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? $_[1] : '' + my ($self, $path) = @_; + $self->{path_prefix}.(defined $path ? $path : ''); } sub url_path { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 5355243b92..3dc4de2fad 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -236,4 +236,33 @@ test_expect_success \ '^:refs/remotes/git-svn$' " +test_expect_success 'able to dcommit to a subdirectory' " + git-svn fetch -i bar && + git checkout -b my-bar refs/remotes/bar && + echo abc > d && + git update-index --add d && + git commit -m '/bar/d should be in the log' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + mkdir newdir && + echo new > newdir/dir && + git update-index --add newdir/dir && + git commit -m 'add a new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + echo foo >> newdir/dir && + git update-index newdir/dir && + git commit -m 'modify a file in new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + +test_expect_success 'able to set-tree to a subdirectory' " + echo cba > d && + git update-index d && + git commit -m 'update /bar/d' && + git-svn set-tree -i bar HEAD && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + test_done diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index 6323c7e3ac..c668dd1270 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -31,4 +31,13 @@ test_expect_success 'test the commit-diff command' " cmp readme wc/readme " +test_expect_success 'commit-diff to a sub-directory (with git-svn config)' " + svn import -m 'sub-directory' import $svnrepo/subdir && + git-svn init $svnrepo/subdir && + git-svn fetch && + git-svn commit-diff -r3 '$prev' '$head' && + svn cat $svnrepo/subdir/readme > readme.2 && + cmp readme readme.2 + " + test_done -- cgit v1.2.3 From 6e8548cca888205a99773a18a73533b2b8dc651d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 01:32:00 -0800 Subject: git-svn: avoid an extra svn_ra connection during commits Before, we needed a separate svn_ra instance to run our check_path calls once the editor was active; but we can avoid that by running all the check_path calls before our editor is active. Signed-off-by: Eric Wong --- git-svn.perl | 205 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 123 insertions(+), 82 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 1d448e75da..e771bf5692 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -352,16 +352,22 @@ sub cmd_dcommit { my $log = get_commit_entry($d)->{log}; my $ra = $gs->ra; my $pool = SVN::Pool->new; + my $mods = generate_diff("$d~1", $d); + my $types = check_diff_paths($ra, + $gs->{path}, + $last_rev, + $mods); my %ed_opts = ( r => $last_rev, - ra => $ra->dup, + mods => $mods, + url => $ra->{url}, + types => $types, svn_path => $gs->{path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }), $pool); - my $mods = $ed->apply_diff("$d~1", $d); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $d)) { print "No changes\n$d~1 == $d\n"; } } @@ -469,15 +475,15 @@ sub cmd_commit_diff { die "revision argument: $r not understood by git-svn\n"; } my $pool = SVN::Pool->new; - my %ed_opts = ( r => $r, - ra => $ra->dup, - svn_path => $svn_path ); + my $mods = generate_diff($ta, $tb); + my $types = check_diff_paths($ra, $svn_path, $r, $mods); + my %ed_opts = ( r => $r, url => $ra->{url}, svn_path => $svn_path, + mods => $mods, types => $types ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($_message, sub { print "Committed r$_[0]\n" }), $pool); - my $mods = $ed->apply_diff($ta, $tb); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $tb)) { print "No changes\n$ta == $tb\n"; } $pool->clear; @@ -708,6 +714,92 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } +sub generate_diff { + my ($tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $_find_copies_harder; + push @diff_tree, "-l$_l" if defined $_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s + $sha1\s($sha1)\s + ([MTCRAD])\d*$/xo) { + push @mods, { mode_a => $1, mode_b => $2, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + \@mods; +} + +sub check_diff_paths { + my ($ra, $pfx, $rev, $mods) = @_; + my %types; + $pfx .= '/' if length $pfx; + + sub type_diff_paths { + my ($ra, $types, $path, $rev) = @_; + my @p = split m#/+#, $path; + my $c = shift @p; + unless (defined $types->{$c}) { + $types->{$c} = $ra->check_path($c, $rev); + } + while (@p) { + $c .= '/' . shift @p; + next if defined $types->{$c}; + $types->{$c} = $ra->check_path($c, $rev); + } + } + + foreach my $m (@$mods) { + foreach my $f (qw/file_a file_b/) { + next unless defined $m->{$f}; + my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); + if (length $pfx.$dir && ! defined $types{$dir}) { + type_diff_paths($ra, \%types, $pfx.$dir, $rev); + } + } + } + use Data::Dumper; + warn Dumper \%types; + warn Dumper $mods; + \%types; +} + package Git::SVN; use strict; use warnings; @@ -1375,18 +1467,20 @@ sub set_tree { fatal("Must have an existing revision to commit\n"); } my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, - ra => $self->ra->dup, - svn_path => $self->{path} - }, + my $mods = ::generate_diff($self->{last_commit}, $tree); + my $types = ::check_diff_paths($self->ra, $self->{path}, + $self->{last_rev}, $mods); + my %ed_opts = ( r => $self->{last_rev}, url => $self->ra->{url}, + svn_path => $self->{path}, + mods => $mods, types => $types ); + my $ed = SVN::Git::Editor->new(\%ed_opts, $self->ra->get_commit_editor( $log_entry->{log}, sub { $self->set_tree_cb($log_entry, $tree, @_); }), $pool); - my $mods = $ed->apply_diff($self->{last_commit}, $tree); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $tree)) { print "No changes\nr$self->{last_rev} = $tree\n"; } $pool->clear; @@ -1898,7 +1992,7 @@ sub new { my $git_svn = shift; my $self = SVN::Delta::Editor->new(@_); bless $self, $class; - foreach (qw/svn_path r ra/) { + foreach (qw/svn_path mods url types r/) { die "$_ required!\n" unless (defined $git_svn->{$_}); $self->{$_} = $git_svn->{$_}; } @@ -1922,7 +2016,7 @@ sub repo_path { sub url_path { my ($self, $path) = @_; - $self->{ra}->{url} . '/' . $self->repo_path($path); + $self->{url} . '/' . $self->repo_path($path); } sub rmdirs { @@ -1972,7 +2066,10 @@ sub rmdirs { sub open_or_add_dir { my ($self, $full_path, $baton) = @_; - my $t = $self->{ra}->check_path($full_path, $self->{r}); + my $t = $self->{types}->{$full_path}; + if (!defined $t) { + die "$full_path not known in r$self->{r} or we have a bug!\n"; + } if ($t == $SVN::Node::none) { return $self->add_directory($full_path, $baton, undef, -1, $self->{pool}); @@ -1989,9 +2086,9 @@ sub open_or_add_dir { sub ensure_path { my ($self, $path) = @_; my $bat = $self->{bat}; - $path = $self->repo_path($path); - return $bat->{''} unless (length $path); - my @p = split m#/+#, $path; + my $repo_path = $self->repo_path($path); + return $bat->{''} unless (length $repo_path); + my @p = split m#/+#, $repo_path; my $c = shift @p; $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}); while (@p) { @@ -2129,59 +2226,9 @@ sub abort_edit { # this drives the editor sub apply_diff { - my ($self, $tree_a, $tree_b) = @_; - my @diff_tree = qw(diff-tree -z -r); - if ($::_cp_similarity) { - push @diff_tree, "-C$::_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; - push @diff_tree, "-l$::_l" if defined $::_l; - push @diff_tree, $tree_a, $tree_b; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - my $nl = $/; - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s - $::sha1\s($::sha1)\s - ([MTCRAD])\d*$/xo) { - push @mods, { mode_a => $1, mode_b => $2, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - $/ = $nl; - + my ($self, $mods, $tree_b) = @_; 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) { + foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { my $f = $m->{chg}; if (defined $o{$f}) { $self->$f($m); @@ -2190,12 +2237,12 @@ sub apply_diff { } } $self->rmdirs($tree_b) if $::_rmdir; - if (@mods == 0) { + if (@$mods == 0) { $self->abort_edit; } else { $self->close_edit; } - \@mods; + return scalar @$mods; } package Git::SVN::Ra; @@ -2256,14 +2303,6 @@ sub DESTROY { # do not call the real DESTROY since we store ourselves in %RA } -sub dup { - my ($self) = @_; - my $dup = SVN::Ra->new(pool => SVN::Pool->new, - map { $_ => $self->{$_} } qw/config url - auth auth_provider_callbacks repos_root svn_path/); - bless $dup, ref $self; -} - sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; @@ -2922,6 +2961,8 @@ $log_entry hashref as returned by libsvn_log_entry() author => 'committer name' }; + +# this is generated by generate_diff(); @mods = array of diff-index line hashes, each element represents one line of diff-index output -- cgit v1.2.3 From 6139535436d7ec9808fca75821f14d4d5061f343 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:33:08 -0800 Subject: git-svn: simplify usage of the SVN::Git::Editor interface Signed-off-by: Eric Wong --- git-svn.perl | 281 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 139 insertions(+), 142 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index e771bf5692..4f7ebaf58b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -349,25 +349,16 @@ sub cmd_dcommit { if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { - my $log = get_commit_entry($d)->{log}; - my $ra = $gs->ra; - my $pool = SVN::Pool->new; - my $mods = generate_diff("$d~1", $d); - my $types = check_diff_paths($ra, - $gs->{path}, - $last_rev, - $mods); my %ed_opts = ( r => $last_rev, - mods => $mods, - url => $ra->{url}, - types => $types, + log => get_commit_entry($d)->{log}, + ra => $gs->ra, + tree_a => "$d~1", + tree_b => $d, + editor_cb => sub { + print "Committed r$_[0]\n"; + $last_rev = $_[0]; }, svn_path => $gs->{path} ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($log, - sub { print "Committed r$_[0]\n"; - $last_rev = $_[0]; }), - $pool); - if (!$ed->apply_diff($mods, $d)) { + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; } } @@ -474,19 +465,16 @@ sub cmd_commit_diff { } elsif ($r !~ /^\d+$/) { die "revision argument: $r not understood by git-svn\n"; } - my $pool = SVN::Pool->new; - my $mods = generate_diff($ta, $tb); - my $types = check_diff_paths($ra, $svn_path, $r, $mods); - my %ed_opts = ( r => $r, url => $ra->{url}, svn_path => $svn_path, - mods => $mods, types => $types ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($_message, - sub { print "Committed r$_[0]\n" }), - $pool); - if (!$ed->apply_diff($mods, $tb)) { + my %ed_opts = ( r => $r, + log => $_message, + ra => $ra, + tree_a => $ta, + tree_b => $tb, + editor_cb => sub { print "Committed r$_[0]\n" }, + svn_path => $svn_path ); + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$ta == $tb\n"; } - $pool->clear; } ########################### utility functions ######################### @@ -714,92 +702,6 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -sub generate_diff { - my ($tree_a, $tree_b) = @_; - my @diff_tree = qw(diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - push @diff_tree, $tree_a, $tree_b; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s - $sha1\s($sha1)\s - ([MTCRAD])\d*$/xo) { - push @mods, { mode_a => $1, mode_b => $2, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - \@mods; -} - -sub check_diff_paths { - my ($ra, $pfx, $rev, $mods) = @_; - my %types; - $pfx .= '/' if length $pfx; - - sub type_diff_paths { - my ($ra, $types, $path, $rev) = @_; - my @p = split m#/+#, $path; - my $c = shift @p; - unless (defined $types->{$c}) { - $types->{$c} = $ra->check_path($c, $rev); - } - while (@p) { - $c .= '/' . shift @p; - next if defined $types->{$c}; - $types->{$c} = $ra->check_path($c, $rev); - } - } - - foreach my $m (@$mods) { - foreach my $f (qw/file_a file_b/) { - next unless defined $m->{$f}; - my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); - if (length $pfx.$dir && ! defined $types{$dir}) { - type_diff_paths($ra, \%types, $pfx.$dir, $rev); - } - } - } - use Data::Dumper; - warn Dumper \%types; - warn Dumper $mods; - \%types; -} - package Git::SVN; use strict; use warnings; @@ -1466,24 +1368,17 @@ sub set_tree { unless ($self->{last_rev}) { fatal("Must have an existing revision to commit\n"); } - my $pool = SVN::Pool->new; - my $mods = ::generate_diff($self->{last_commit}, $tree); - my $types = ::check_diff_paths($self->ra, $self->{path}, - $self->{last_rev}, $mods); - my %ed_opts = ( r => $self->{last_rev}, url => $self->ra->{url}, - svn_path => $self->{path}, - mods => $mods, types => $types ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $self->ra->get_commit_editor( - $log_entry->{log}, sub { - $self->set_tree_cb($log_entry, - $tree, @_); - }), - $pool); - if (!$ed->apply_diff($mods, $tree)) { + my %ed_opts = ( r => $self->{last_rev}, + log => $log_entry->{log}, + ra => $self->ra, + tree_a => $self->{last_commit}, + tree_b => $tree, + editor_cb => sub { + $self->set_tree_cb($log_entry, $tree, @_) }, + svn_path => $self->{path} ); + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\nr$self->{last_rev} = $tree\n"; } - $pool->clear; } sub skip_unknown_revs { @@ -1988,15 +1883,28 @@ use Carp qw/croak/; use IO::File; sub new { - my $class = shift; - my $git_svn = shift; - my $self = SVN::Delta::Editor->new(@_); + my ($class, $opts) = @_; + foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) { + die "$_ required!\n" unless (defined $opts->{$_}); + } + + my $pool = SVN::Pool->new; + my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b}); + my $types = check_diff_paths($opts->{ra}, $opts->{svn_path}, + $opts->{r}, $mods); + + # $opts->{ra} functions should not be used after this: + my @ce = $opts->{ra}->get_commit_editor($opts->{log}, + $opts->{editor_cb}, $pool); + my $self = SVN::Delta::Editor->new(@ce, $pool); bless $self, $class; - foreach (qw/svn_path mods url types r/) { - die "$_ required!\n" unless (defined $git_svn->{$_}); - $self->{$_} = $git_svn->{$_}; + foreach (qw/svn_path r tree_a tree_b/) { + $self->{$_} = $opts->{$_}; } - $self->{pool} = SVN::Pool->new; + $self->{url} = $opts->{ra}->{url}; + $self->{mods} = $mods; + $self->{types} = $types; + $self->{pool} = $pool; $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) }; $self->{rm} = { }; $self->{path_prefix} = length $self->{svn_path} ? @@ -2005,6 +1913,89 @@ sub new { return $self; } +sub generate_diff { + my ($tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($::_cp_similarity) { + push @diff_tree, "-C$::_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; + push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s + $::sha1\s($::sha1)\s + ([MTCRAD])\d*$/xo) { + push @mods, { mode_a => $1, mode_b => $2, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + \@mods; +} + +sub check_diff_paths { + my ($ra, $pfx, $rev, $mods) = @_; + my %types; + $pfx .= '/' if length $pfx; + + sub type_diff_paths { + my ($ra, $types, $path, $rev) = @_; + my @p = split m#/+#, $path; + my $c = shift @p; + unless (defined $types->{$c}) { + $types->{$c} = $ra->check_path($c, $rev); + } + while (@p) { + $c .= '/' . shift @p; + next if defined $types->{$c}; + $types->{$c} = $ra->check_path($c, $rev); + } + } + + foreach my $m (@$mods) { + foreach my $f (qw/file_a file_b/) { + next unless defined $m->{$f}; + my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); + if (length $pfx.$dir && ! defined $types{$dir}) { + type_diff_paths($ra, \%types, $pfx.$dir, $rev); + } + } + } + \%types; +} + sub split_path { return ($_[0] =~ m#^(.*?)/?([^/]+)$#); } @@ -2020,7 +2011,7 @@ sub url_path { } sub rmdirs { - my ($self, $tree_b) = @_; + my ($self) = @_; my $rm = $self->{rm}; delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; @@ -2038,8 +2029,8 @@ sub rmdirs { delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; - my ($fh, $ctx) = command_output_pipe( - qw/ls-tree --name-only -r -z/, $tree_b); + my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/, + $self->{tree_b}); local $/ = "\0"; while (<$fh>) { chomp; @@ -2221,12 +2212,18 @@ sub close_edit { sub abort_edit { my ($self) = @_; $self->SUPER::abort_edit($self->{pool}); +} + +sub DESTROY { + my $self = shift; + $self->SUPER::DESTROY(@_); $self->{pool}->clear; } # this drives the editor sub apply_diff { - my ($self, $mods, $tree_b) = @_; + my ($self) = @_; + my $mods = $self->{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) { my $f = $m->{chg}; @@ -2236,7 +2233,7 @@ sub apply_diff { fatal("Invalid change type: $f\n"); } } - $self->rmdirs($tree_b) if $::_rmdir; + $self->rmdirs if $::_rmdir; if (@$mods == 0) { $self->abort_edit; } else { -- cgit v1.2.3 From 21819a370839fdae818975967cef384510e4a8cd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:38:10 -0800 Subject: git-svn: cleanup remove unused function Also move tz_to_s_offset into Git::SVN::Log since that's the only place it's used now. Signed-off-by: Eric Wong --- git-svn.perl | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 4f7ebaf58b..7249d6f417 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -679,29 +679,6 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } -sub get_commit_time { - my $cmt = shift; - my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt); - while (<$fh>) { - /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next; - my ($s, $tz) = ($1, $2); - if ($tz =~ s/^\+//) { - $s += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $s -= tz_to_s_offset($tz); - } - close $fh; - return $s; - } - die "Can't get commit time for commit: $cmt\n"; -} - -sub tz_to_s_offset { - my ($tz) = @_; - $tz =~ s/(\d\d)$//; - return ($1 * 60) + ($tz * 3600); -} - package Git::SVN; use strict; use warnings; @@ -2496,6 +2473,12 @@ sub run_pager { exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; } +sub tz_to_s_offset { + my ($tz) = @_; + $tz =~ s/(\d\d)$//; + return ($1 * 60) + ($tz * 3600); +} + sub get_author_info { my ($dest, $author, $t, $tz) = @_; $author =~ s/(?:^\s*|\s*$)//g; @@ -2512,9 +2495,9 @@ sub get_author_info { $dest->{a} = $au; # Date::Parse isn't in the standard Perl distro :( if ($tz =~ s/^\+//) { - $t += ::tz_to_s_offset($tz); + $t += tz_to_s_offset($tz); } elsif ($tz =~ s/^\-//) { - $t -= ::tz_to_s_offset($tz); + $t -= tz_to_s_offset($tz); } $dest->{t_utc} = $t; } -- cgit v1.2.3 From 0af9c9f94ae8a327536679ec1976df65ecd64b6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 22:28:56 -0800 Subject: git-svn: allow multi-fetch to fetch things chronologically Since single fetching is a special case of multi-fetch, share code with it and the fetch loop into Git::SVN::Ra since it uses a single Ra connection and multiple Git::SVN objects. Signed-off-by: Eric Wong --- git-svn.perl | 211 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 120 insertions(+), 91 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 7249d6f417..5d398ee65f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -416,15 +416,11 @@ sub cmd_multi_init { } sub cmd_multi_fetch { - my @gs; - foreach (command(qw/config -l/)) { - next unless m!^svn-remote\.(.+)\.fetch= - \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; - my ($repo_id, $path, $ref_id) = ($1, $2, $3); - push @gs, Git::SVN->new($ref_id, $repo_id, $path); - } - foreach (@gs) { - $_->fetch; + my $remotes = Git::SVN::read_all_remotes(); + foreach my $repo_id (sort keys %$remotes) { + my $url = $remotes->{$repo_id}->{url} or next; + my $fetch = $remotes->{$repo_id}->{fetch} or next; + Git::SVN::fetch_all($repo_id, $url, $fetch); } } @@ -698,6 +694,28 @@ BEGIN { svn:entry:committed-date/; } +sub fetch_all { + my ($repo_id, $url, $fetch) = @_; + my @gs; + my $ra = Git::SVN::Ra->new($url); + my $head = $ra->get_latest_revnum; + my $base = $head; + my $new_remote; + foreach my $p (sort keys %$fetch) { + my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); + my $lr = $gs->last_rev; + if (defined $lr) { + $base = $lr if ($lr < $base); + } else { + $new_remote = 1; + } + push @gs, $gs; + } + $base = 0 if $new_remote; + return if (++$base > $head); + $ra->gs_fetch_loop_common($base, $head, @gs); +} + sub read_all_remotes { my $r = {}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { @@ -981,16 +999,12 @@ sub assert_index_clean { } sub get_commit_parents { - my ($self, $log_entry, @parents) = @_; + my ($self, $log_entry) = @_; my (%seen, @ret, @tmp); - # commit parents can be conditionally bound to a particular - # svn revision via: "svn_revno=commit_sha1", filter them out here: - foreach my $p (@parents) { - next unless defined $p; - if ($p =~ /^(\d+)=($::sha1_short)$/o) { - push @tmp, $2 if $1 == $log_entry->{revision}; - } else { - push @tmp, $p if $p =~ /^$::sha1_short$/o; + # legacy support for 'set-tree'; this is only used by set_tree_cb: + if (my $ip = $self->{inject_parents}) { + if (my $commit = delete $ip->{$log_entry->{revision}}) { + push @tmp, $commit; } } if (my $cur = ::verify_ref($self->refname.'^0')) { @@ -1017,7 +1031,7 @@ sub full_url { } sub do_git_commit { - my ($self, $log_entry, @parents) = @_; + my ($self, $log_entry) = @_; if (my $c = $self->rev_db_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; @@ -1037,7 +1051,7 @@ sub do_git_commit { die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; my @exec = ('git-commit-tree', $tree); - foreach ($self->get_commit_parents($log_entry, @parents)) { + foreach ($self->get_commit_parents($log_entry)) { push @exec, '-p', $_; } defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) @@ -1291,40 +1305,7 @@ sub fetch { my ($last_rev, $last_commit) = $self->last_rev_commit; my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); return if ($base > $head); - if (defined $last_commit) { - $self->assert_index_clean($last_commit); - } - my $inc = 1000; - my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my $err_handler = $SVN::Error::handler; - my $err; - $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; - while (1) { - my @revs; - $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, - sub { - my ($paths, $rev) = @_; - push @revs, [ dup_changed_paths($paths), $rev ]; - }); - if (! @revs && $err && $max >= $head) { - print STDERR "Branch probably deleted:\n ", - $err->expanded_message, - "\nWill attempt to follow revisions ", - "r$min .. r$max", - "committed before the deletion\n"; - @revs = map { [ undef, $_ ] } ($min .. $max); - } - foreach (@revs) { - if (my $log_entry = $self->do_fetch(@$_)) { - $self->do_git_commit($log_entry, @parents); - } - } - last if $max >= $head; - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - } - $SVN::Error::handler = $err_handler; + $self->ra->gs_fetch_loop_common($base, $head, $self); } sub set_tree_cb { @@ -1335,7 +1316,8 @@ sub set_tree_cb { $log_entry->{author} = $author; $self->do_git_commit($log_entry, "$rev=$tree"); } else { - $self->fetch(undef, undef, "$rev=$tree"); + $self->{inject_parents} = { $rev => $tree }; + $self->fetch(undef, undef); } } @@ -1358,42 +1340,6 @@ sub set_tree { } } -sub skip_unknown_revs { - my ($err) = @_; - my $errno = $err->apr_err(); - # Maybe the branch we're tracking didn't - # exist when the repo started, so it's - # not an error if it doesn't, just continue - # - # Wonderfully consistent library, eh? - # 160013 - svn:// and file:// - # 175002 - http(s):// - # 175007 - http(s):// (this repo required authorization, too...) - # More codes may be discovered later... - if ($errno == 175007 || $errno == 175002 || $errno == 160013) { - return; - } - croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; -} - -# svn_log_changed_path_t objects passed to get_log are likely to be -# overwritten even if only the refs are copied to an external variable, -# so we should dup the structures in their entirety. Using an externally -# passed pool (instead of our temporary and quickly cleared pool in -# Git::SVN::Ra) does not help matters at all... -sub dup_changed_paths { - my ($paths) = @_; - return undef unless $paths; - my %ret; - foreach my $p (keys %$paths) { - my $i = $paths->{$p}; - my %s = map { $_ => $i->$_ } - qw/copyfrom_path copyfrom_rev action/; - $ret{$p} = \%s; - } - \%ret; -} - # rev_db: # 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 @@ -2324,6 +2270,53 @@ sub gs_do_switch { $editor->{git_commit_ok}; } +sub gs_fetch_loop_common { + my ($self, $base, $head, @gs) = @_; + my $inc = 1000; + my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + my $err_handler = $SVN::Error::handler; + my $err; + $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; + my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); + foreach my $gs (@gs) { + if (my $last_commit = $gs->last_commit) { + $gs->assert_index_clean($last_commit); + } + $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/; + } + while (1) { + my @revs; + $self->get_log(\@paths, $min, $max, 0, 1, 1, + sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); + if (! @revs && $err && $max >= $head) { + print STDERR "Branch probably deleted:\n ", + $err->expanded_message, + "\nWill attempt to follow revisions ", + "r$min .. r$max ", + "committed before the deletion\n"; + @revs = map { [ undef, $_ ] } ($min .. $max); + } + foreach (@revs) { + my ($paths, $r) = @$_; + foreach my $gs (@gs) { + if ($paths) { + grep /$gs->{path_regex}/, keys %$paths + or next; + } + next if defined $gs->rev_db_get($r); + if (my $log_entry = $gs->do_fetch($paths, $r)) { + $gs->do_git_commit($log_entry); + } + } + } + last if $max >= $head; + $min = $max + 1; + $max += $inc; + $max = $head if ($max > $head); + } + $SVN::Error::handler = $err_handler; +} + sub minimize_url { my ($self) = @_; return $self->{url} if ($self->{url} eq $self->{repos_root}); @@ -2356,6 +2349,42 @@ sub can_do_switch { $can_do_switch; } +sub skip_unknown_revs { + my ($err) = @_; + my $errno = $err->apr_err(); + # Maybe the branch we're tracking didn't + # exist when the repo started, so it's + # not an error if it doesn't, just continue + # + # Wonderfully consistent library, eh? + # 160013 - svn:// and file:// + # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) + # More codes may be discovered later... + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + return; + } + die "Error from SVN, ($errno): ", $err->expanded_message,"\n"; +} + +# svn_log_changed_path_t objects passed to get_log are likely to be +# overwritten even if only the refs are copied to an external variable, +# so we should dup the structures in their entirety. Using an externally +# passed pool (instead of our temporary and quickly cleared pool in +# Git::SVN::Ra) does not help matters at all... +sub dup_changed_paths { + my ($paths) = @_; + return undef unless $paths; + my %ret; + foreach my $p (keys %$paths) { + my $i = $paths->{$p}; + my %s = map { $_ => $i->$_ } + qw/copyfrom_path copyfrom_rev action/; + $ret{$p} = \%s; + } + \%ret; +} + package Git::SVN::Log; use strict; use warnings; -- cgit v1.2.3 From 2fa6a23efb200cea814add88ce1d1d193ba83860 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:02:01 -0800 Subject: git-svn: correctly track diff-less copies with do_switch Also, this should allow for the tracking of new, but empty directories where we would want to see the log message. Signed-off-by: Eric Wong --- git-svn.perl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 5d398ee65f..36e5c57adf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1167,6 +1167,7 @@ sub find_parent_branch { 1, $ed) or die "SVN connection failed somewhere...\n"; } + $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } not_found: @@ -1202,6 +1203,7 @@ sub do_fetch { return $log_entry; } $ed = SVN::Git::Fetcher->new($self); + $ed->{new_fetch} = 1; } unless ($self->ra->gs_do_update($last_rev, $rev, $self->{path}, 1, $ed)) { @@ -1275,7 +1277,7 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); - return undef if ($ed->{nr} == 0 && scalar @$untracked == 0); + return undef if (! $ed->{new_fetch} && ! $ed->{nr} && ! @$untracked); open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; -- cgit v1.2.3 From 2b27f6c8847ebee631e7ad17ac9986e461d7674b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:59:05 -0800 Subject: git-svn: correctly handle do_{switch,update} in deep directories The do_update or do_switch functions in SVN only allow for a single path component; so 'path/to/deep/dir' would be interpreted as 'path'. SVN 1.4.x has a reparent function that can let us change the session to use a higher-level root of the repository, so we can use that for do_switch (which still doesn't seem to work in SVN 1.4.3 (a fix was attempted, but they missed the rest of the typemap changes needed in trunk...)). On the do_update side, we can use set_path on higher level directories and set them to a newer revision so they don't get updated. We can't do this with do_switch, either, because the relative path we're tracking can change (directory moving into a child of itself). Because of these changes, we need to double check that our Fetch editor is correctly performing stripping on any prefixed paths from update, otherwise we'll just die() because that would be a bug. Added a test case which helped me notice and fix problems with do_switch, too. Signed-off-by: Eric Wong --- git-svn.perl | 46 +++++++++++++++++++++++++++++++++------- t/t9104-git-svn-follow-parent.sh | 31 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 36e5c57adf..dcc7fd4603 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1153,7 +1153,7 @@ sub find_parent_branch { if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that - # is not included with SVN 1.4.2 (the latest version + # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); @@ -1621,7 +1621,10 @@ sub open_directory { sub git_path { my ($self, $path) = @_; - $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + if ($self->{path_strip}) { + $path =~ s!$self->{path_strip}!! or + die "Failed to strip path '$path' ($self->{path_strip})\n"; + } $path; } @@ -2249,25 +2252,52 @@ sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; $editor->set_path_strip($path); - my $reporter = $self->do_update($rev_b, $path, $recurse, - $editor, $pool); + my (@pc) = split m#/#, $path; + my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), + $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + + # Since we can't rely on svn_ra_reparent being available, we'll + # just have to do some magic with set_path to make it so + # we only want a partial path. + my $sp = ''; + my $final = join('/', @pc); + while (@pc) { + $reporter->set_path($sp, $rev_b, 0, @lock, $pool); + $sp .= '/' if length $sp; + $sp .= shift @pc; + } + die "BUG: '$sp' != '$final'\n" if ($sp ne $final); + my $new = ($rev_a == $rev_b); - $reporter->set_path('', $rev_a, $new, @lock, $pool); + $reporter->set_path($sp, $rev_a, $new, @lock, $pool); + $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; } +# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and +# svn_ra_reparent didn't work before 1.4) sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; - $editor->set_path_strip($path); - my $reporter = $self->do_switch($rev_b, $path, $recurse, - $url_b, $editor, $pool); + + my $full_url = $self->{url}; + my $old_url = $full_url; + $full_url .= "/$path" if length $path; + SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool); + $self->{url} = $full_url; + + my $reporter = $self->do_switch($rev_b, '', + $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); + + SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); + $self->{url} = $old_url; + $pool->clear; $editor->{git_commit_ok}; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index bfb718886f..6d243f8488 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -101,6 +101,37 @@ test_expect_success 'follow deleted directory' " test -z \"\`git ls-tree -z refs/remotes/glob\`\" " +# ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) +# in trunk/subversion/bindings/swig/perl +test_expect_success '' " + mkdir -p import/trunk/subversion/bindings/swig/perl/t && + for i in a b c ; do \ + echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && + echo _\$i > import/trunk/subversion/bindings/swig/perl/t/\$i.t; \ + done && + echo 'bad delete test' > \ + import/trunk/subversion/bindings/swig/perl/t/larger-parent && + echo 'bad delete test 2' > \ + import/trunk/subversion/bindings/swig/perl/another-larger && + cd import && + svn import -m 'r9270 test' . $svnrepo/r9270 && + cd .. && + svn co $svnrepo/r9270/trunk/subversion/bindings/swig/perl r9270 && + cd r9270 && + svn mkdir native && + svn mv t native/t && + for i in a b c; do svn mv \$i.pm native/\$i.pm; done && + echo z >> native/t/c.t && + svn commit -m 'reorg test' && + cd .. && + git-svn init -i r9270-t \ + $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-t --follow-parent && + test \`git rev-list r9270-t | wc -l\` -eq 2 && + test \"\`git ls-tree --name-only r9270-t~1\`\" = \ + \"\`git ls-tree --name-only r9270-t\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From ce2a0f2f9d9fd8eee8eaa1f24a60bdafade80c24 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 21:34:23 -0800 Subject: git-svn: stop using path names as refnames with --follow-parent Using path names as refnames breaks horribly if a user is tracking one large, toplevel directory, and a lower-level directory is followed from another project is a parent of another ref, as it will cause refnames such as: 'refs/remotes/trunk/path/to/stuff', which will conflict with a refname of 'refs/remotes/trunk'. Now we just append @$revno to the end of it the current refname. And if we have followed back to a grandparent, then we'll strip any existing '@$parent_revno' strings before appending our own '@$revno' string to it. Signed-off-by: Eric Wong --- git-svn.perl | 6 ++++-- t/t9104-git-svn-follow-parent.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index dcc7fd4603..4f7938ab7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1135,10 +1135,12 @@ sub find_parent_branch { last if $gs; } unless ($gs) { - my $ref_id = $branch_from; - $ref_id .= "\@$r" if find_ref($ref_id); + my $ref_id = $self->{ref_id}; + $ref_id =~ s/\@\d+$//; + $ref_id .= "\@$r"; # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); + print STDERR "Initializing parent: $ref_id\n"; $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); } my ($r0, $parent) = $gs->find_rev_before($r, 1); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 6d243f8488..0f4e736271 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,10 +30,12 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ - sed -n -e '3p'\`\" = goodbye + sed -n -e '3p'\`\" = goodbye && + test -n \"\`git-config --get svn-remote.git-svn.fetch \ + '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " -- cgit v1.2.3 From 24e22aa8a5762b11a8025c6ad82c945828667d0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 00:07:49 -0800 Subject: git-svn: cleanup: move editor-specific variables into the editor namespace Also removed some unused/redundant functions. Signed-off-by: Eric Wong --- git-svn.perl | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 4f7938ab7e..20c6fc768b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,8 +8,8 @@ use vars qw/ $AUTHOR $VERSION $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity - $_find_copies_harder $_l $_authors %users/; + $_cp_remote $_upgrade $_q + $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -83,10 +83,10 @@ my %multi_opts = ( 'trunk|T=s' => \$_trunk, 'branches|b=s' => \$_branches ); my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); my %cmt_opts = ( 'edit|e' => \$_edit, - 'rmdir' => \$_rmdir, - 'find-copies-harder' => \$_find_copies_harder, - 'l=i' => \$_l, - 'copy-similarity|C=i'=> \$_cp_similarity + 'rmdir' => \$SVN::Git::Editor::_rmdir, + 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder, + 'l=i' => \$SVN::Git::Editor::_rename_limit, + 'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity ); my %cmd = ( @@ -1559,19 +1559,6 @@ sub _read_password { package main; -sub uri_encode { - my ($f) = @_; - $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; - $f -} - -sub uri_decode { - my ($f) = @_; - $f =~ tr/+/ /; - $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge; - $f -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. @@ -1806,7 +1793,7 @@ sub close_edit { } package SVN::Git::Editor; -use vars qw/@ISA/; +use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/; use strict; use warnings; use Carp qw/croak/; @@ -1846,13 +1833,13 @@ sub new { sub generate_diff { my ($tree_a, $tree_b) = @_; my @diff_tree = qw(diff-tree -z -r); - if ($::_cp_similarity) { - push @diff_tree, "-C$::_cp_similarity"; + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; } else { push @diff_tree, '-C'; } - push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; - push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, '--find-copies-harder' if $_find_copies_harder; + push @diff_tree, "-l$_rename_limit" if defined $_rename_limit; push @diff_tree, $tree_a, $tree_b; my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); local $/ = "\0"; @@ -2163,7 +2150,7 @@ sub apply_diff { fatal("Invalid change type: $f\n"); } } - $self->rmdirs if $::_rmdir; + $self->rmdirs if $_rmdir; if (@$mods == 0) { $self->abort_edit; } else { -- cgit v1.2.3 From 90c1b15da303fa40457e0a9e5c11a57adbfa1879 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 16:27:08 -0800 Subject: git-svn: just use Digest::MD5 instead of requiring it Historically, git-svn did not always use Digest::MD5 because it did not use the SVN::Delta::Editor interfaces. Nowadays it does, and the requires make strace more noisy. Signed-off-by: Eric Wong --- git-svn.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 20c6fc768b..e387504b88 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1577,6 +1577,7 @@ use strict; use warnings; use Carp qw/croak/; use IO::File qw//; +use Digest::MD5; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -1590,7 +1591,6 @@ sub new { $self->{absent_dir} = {}; $self->{absent_file} = {}; $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); - require Digest::MD5; $self; } @@ -1798,6 +1798,7 @@ use strict; use warnings; use Carp qw/croak/; use IO::File; +use Digest::MD5; sub new { my ($class, $opts) = @_; @@ -1826,7 +1827,6 @@ sub new { $self->{rm} = { }; $self->{path_prefix} = length $self->{svn_path} ? "$self->{svn_path}/" : ''; - require Digest::MD5; return $self; } -- cgit v1.2.3 From f7c3fc4a269ad4f12cc9d97d70e46ba3d5dfdb79 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 18:34:55 -0800 Subject: git-svn: reinstate the default SVN error handler after using get_log We don't need our own error handler for other operations. Also add a message about the successfully do_switch or do_update in follow-parent for debugging do_switch failures with svn:// and svn+ssh:// connections. Signed-off-by: Eric Wong --- git-svn.perl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index e387504b88..bbdaeea98a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1169,6 +1169,7 @@ sub find_parent_branch { 1, $ed) or die "SVN connection failed somewhere...\n"; } + print STDERR "Successfully followed parent\n"; $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } @@ -2295,9 +2296,6 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my $err_handler = $SVN::Error::handler; - my $err; - $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { @@ -2307,8 +2305,16 @@ sub gs_fetch_loop_common { } while (1) { my @revs; + my $err; + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = sub { + ($err) = @_; + skip_unknown_revs($err); + }; $self->get_log(\@paths, $min, $max, 0, 1, 1, sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); + $SVN::Error::handler = $err_handler; + if (! @revs && $err && $max >= $head) { print STDERR "Branch probably deleted:\n ", $err->expanded_message, @@ -2335,7 +2341,6 @@ sub gs_fetch_loop_common { $max += $inc; $max = $head if ($max > $head); } - $SVN::Error::handler = $err_handler; } sub minimize_url { -- cgit v1.2.3 From 5d3b7cd5fe5d0410915cc68b641415901e1b113e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 19:16:01 -0800 Subject: git-svn: don't rely on do_switch + reparenting with svn(+ssh):// I can't seem to figure out what I or the SVN libraries are doing wrong, but it appears to be related to reparent and probably some global structure that gets reset if multiple SVN connections are being used. So now, in order to use do_switch; we'll open a new connection to the repository with the complete URL; but we can't seem to ever use an existing Ra object after another one has been created... Signed-off-by: Eric Wong --- git-svn.perl | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index bbdaeea98a..4e357dfcef 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -870,7 +870,7 @@ sub refname { "refs/remotes/$_[0]->{ref_id}" } sub ra { my ($self) = shift; - $self->{ra} ||= Git::SVN::Ra->new($self->{url}); + Git::SVN::Ra->new($self->{url}); } sub rel_path { @@ -897,9 +897,10 @@ sub copy_remote_ref { sub traverse_ignore { my ($self, $fh, $path, $r) = @_; $path =~ s#^/+##g; - my ($dirent, undef, $props) = $self->ra->get_dir($path, $r); + my $ra = $self->ra; + my ($dirent, undef, $props) = $ra->get_dir($path, $r); my $p = $path; - $p =~ s#^\Q$self->{ra}->{svn_path}\E/##; + $p =~ s#^\Q$ra->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; if (my $s = $props->{'svn:ignore'}) { $s =~ s/[\r\n]+/\n/g; @@ -1027,7 +1028,7 @@ sub get_commit_parents { sub full_url { my ($self) = @_; - $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : ''); + $self->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } sub do_git_commit { @@ -2165,7 +2166,7 @@ use vars qw/@ISA $config_dir/; use strict; use warnings; my ($can_do_switch); -my %RA; +my $RA; BEGIN { # enforce temporary pool usage for some simple functions @@ -2185,7 +2186,7 @@ BEGIN { sub new { my ($class, $url) = @_; $url =~ s!/+$!!; - return $RA{$url} if $RA{$url}; + return $RA if ($RA && $RA->{url} eq $url); SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ @@ -2211,11 +2212,11 @@ sub new { $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; - $RA{$url} = bless $self, $class; + $RA = bless $self, $class; } sub DESTROY { - # do not call the real DESTROY since we store ourselves in %RA + # do not call the real DESTROY since we store ourselves in $RA } sub get_log { @@ -2276,17 +2277,28 @@ sub gs_do_switch { my $full_url = $self->{url}; my $old_url = $full_url; $full_url .= "/$path" if length $path; - SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool); - $self->{url} = $full_url; - - my $reporter = $self->do_switch($rev_b, '', - $recurse, $url_b, $editor, $pool); + my ($ra, $reparented); + if ($old_url ne $full_url) { + if ($old_url !~ m#^svn(\+ssh)?://#) { + SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, + $pool); + $self->{url} = $full_url; + $reparented = 1; + } else { + $ra = Git::SVN::Ra->new($full_url); + } + } + $ra ||= $self; + my $reporter = $ra->do_switch($rev_b, '', + $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); - SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); - $self->{url} = $old_url; + if ($reparented) { + SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); + $self->{url} = $old_url; + } $pool->clear; $editor->{git_commit_ok}; -- cgit v1.2.3 From 289370578ca5833641fbb59813173ac6db1986d1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 00:35:18 -0800 Subject: git-svn: fetch tracks initial change with --follow-parent We were still skipping path information from get_log if we are tracking /r9270/drunk/subversion/bindings/..., but got something like this in the log: A /r9270/drunk (from /r9270/trunk:14) Signed-off-by: Eric Wong --- git-svn.perl | 17 ++++++++++++++--- t/t9104-git-svn-follow-parent.sh | 14 +++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 4e357dfcef..b0248c9487 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1090,6 +1090,19 @@ sub revisions_eq { return 1; } +sub match_paths { + my ($self, $paths) = @_; + return 1 if $paths->{'/'}; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + grep /$self->{path_regex}/, keys %$paths and return 1; + my $c = ''; + foreach (split m#/#, $self->rel_path) { + $c .= "/$_"; + return 1 if $paths->{$c}; + } + return 0; +} + sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; @@ -2313,7 +2326,6 @@ sub gs_fetch_loop_common { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } - $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/; } while (1) { my @revs; @@ -2339,8 +2351,7 @@ sub gs_fetch_loop_common { my ($paths, $r) = @$_; foreach my $gs (@gs) { if ($paths) { - grep /$gs->{path_regex}/, keys %$paths - or next; + $gs->match_paths($paths) or next; } next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 0f4e736271..dcec16bda2 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -105,7 +105,7 @@ test_expect_success 'follow deleted directory' " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) # in trunk/subversion/bindings/swig/perl -test_expect_success '' " +test_expect_success 'follow-parent avoids deleting relevant info' " mkdir -p import/trunk/subversion/bindings/swig/perl/t && for i in a b c ; do \ echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && @@ -134,6 +134,18 @@ test_expect_success '' " \"\`git ls-tree --name-only r9270-t\`\" " +test_expect_success "track initial change if it was only made to parent" " + svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && + git-svn init -i r9270-d \ + $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-d --follow-parent && + test \`git rev-list r9270-d | wc -l\` -eq 3 && + test \"\`git ls-tree --name-only r9270-t\`\" = \ + \"\`git ls-tree --name-only r9270-d\`\" && + test \"\`git rev-parse r9270-t\`\" = \ + \"\`git rev-parse r9270-d~1\`\" + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From f0ecca1041aff2e1398edcdaf5f0ce08a96f5f0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 13:11:14 -0800 Subject: git-svn: remove the 'rebuild' command and make the functionality automatic Since refs/remotes/* are not automatically cloned, we expect the user to be capable of copying those references themselves anyways. Also removed the documentation for --ignore-nodate while we're at it; it has also been made automatic. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 41 ++------------------- git-svn.perl | 92 +++++++++++++++++++---------------------------- 2 files changed, 39 insertions(+), 94 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6daba241e9..22dd7b219d 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -93,16 +93,6 @@ remotes/git-svn. commit. All merging is assumed to have taken place independently of git-svn functions. -'rebuild':: - Not a part of daily usage, but this is a useful command if - you've just cloned a repository (using gitlink:git-clone[1]) that was - tracked with git-svn. Unfortunately, git-clone does not clone - git-svn metadata and the svn working tree that git-svn uses for - its operations. This rebuilds the metadata so git-svn can - resume fetch operations. A Subversion URL may be optionally - specified at the command-line if the directory/repository you're - tracking has moved or changed protocols. - 'show-ignore':: Recursively finds and lists the svn:ignore property on directories. The output is suitable for appending to @@ -322,9 +312,9 @@ config key: svn.followparent --no-metadata:: This gets rid of the git-svn-id: lines at the end of every commit. - With this, you lose the ability to use the rebuild command. If - you ever lose your .git/svn/git-svn/.rev_db file, you won't be - able to fetch again, either. This is fine for one-shot imports. + If you lose your .git/svn/git-svn/.rev_db file, git-svn will not + be able to rebuild it and you won't be able to fetch again, + either. This is fine for one-shot imports. The 'git-svn log' command will not work on repositories using this, either. @@ -333,31 +323,6 @@ config key: svn.nometadata -- -COMPATIBILITY OPTIONS ---------------------- --- - ---upgrade:: -Only used with the 'rebuild' command. - -Run this if you used an old version of git-svn that used -"git-svn-HEAD" instead of "remotes/git-svn" as the branch -for tracking the remote. - ---ignore-nodate:: -Only used with the 'fetch' command. - -By default git-svn will crash if it tries to import a revision -from SVN which has '(no date)' listed as the date of the revision. -This is repository corruption on SVN's part, plain and simple. -But sometimes you really need those revisions anyway. - -If supplied git-svn will convert '(no date)' entries to the UNIX -epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. -SVN was very wrong. - --- - Basic Examples ~~~~~~~~~~~~~~ diff --git a/git-svn.perl b/git-svn.perl index b0248c9487..845f22af59 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -106,9 +106,6 @@ my %cmd = ( { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", - { 'copy-remote|remote=s' => \$_cp_remote, - 'upgrade' => \$_upgrade } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, %remote_opts, @@ -166,7 +163,7 @@ usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; -unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { +unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } eval { @@ -211,47 +208,6 @@ sub version { exit 0; } -sub cmd_rebuild { - my $url = shift; - my $gs = $url ? Git::SVN->init($url) - : eval { Git::SVN->new }; - $gs ||= Git::SVN->_new; - if (!verify_ref($gs->refname.'^0')) { - $gs->copy_remote_ref; - } - - my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); - my $latest; - my $svn_uuid; - while (<$rev_list>) { - chomp; - my $c = $_; - fatal "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; - my ($url, $rev, $uuid) = cmt_metadata($c); - - # ignore merges (from set-tree) - next if (!defined $rev || !$uuid); - - # if we merged or otherwise started elsewhere, this is - # how we break out of it - if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || - ($gs->{url} && $url && ($url ne $gs->{url}))) { - next; - } - - unless (defined $latest) { - if (!$gs->{url} && !$url) { - fatal "SVN repository location required\n"; - } - $gs = Git::SVN->init($url); - $latest = $rev; - } - $gs->rev_db_set($rev, $c); - print "r$rev = $c\n"; - } - command_close_pipe($rev_list, $ctx); -} - sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); @@ -863,6 +819,9 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; + if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) { + $self->rebuild; + } $self; } @@ -883,17 +842,6 @@ sub rel_path { $url; } -sub copy_remote_ref { - my ($self) = @_; - my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; - my $ref = $self->refname; - if (command('ls-remote', $origin, $ref)) { - command_noisy('fetch', $origin, "$ref:$ref"); - } elsif ($::_cp_remote && !$::_upgrade) { - die "Unable to find remote reference: $ref on $origin\n"; - } -} - sub traverse_ignore { my ($self, $fh, $path, $r) = @_; $path =~ s#^/+##g; @@ -1359,6 +1307,38 @@ sub set_tree { } } +sub rebuild { + my ($self) = @_; + print "Rebuilding $self->{db_path} ...\n"; + my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); + my $latest; + my $full_url = $self->full_url; + my $svn_uuid; + while (<$rev_list>) { + chomp; + my $c = $_; + die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; + my ($url, $rev, $uuid) = ::cmt_metadata($c); + + # ignore merges (from set-tree) + next if (!defined $rev || !$uuid); + + # if we merged or otherwise started elsewhere, this is + # how we break out of it + if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + ($full_url && $url && ($url ne $full_url))) { + next; + } + $latest ||= $rev; + $svn_uuid ||= $uuid; + + $self->rev_db_set($rev, $c); + print "r$rev = $c\n"; + } + command_close_pipe($rev_list, $ctx); + print "Done rebuilding $self->{db_path}\n"; +} + # rev_db: # 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 -- cgit v1.2.3 From 8a603774def11e5a90ae2dbbc0c8d4abacdb2d99 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 02:45:50 -0800 Subject: git-svn: fix several fetch bugs related to repeated invocations We no longer delete the top-level directory even if it got deleted from the upstream repository. In gs_do_update; we double-check that the path we're tracking exists at both endpoints before proceeding. We have also added additional protection against fetching revisions out-of-order. To simplify our internal interfaces, I've disabled passing the 'recursive' flag to the gs_do_{switch,update} wrapper functions since we always want it in git-svn. We also pass the entire Git::SVN object rather than just the path because it helped me debug. When printing progress, the refname is printed out to make it less confusing when multi-fetch is running. Signed-off-by: Eric Wong --- git-svn.perl | 42 ++++++++++++++++++++++++++++------------ t/t9104-git-svn-follow-parent.sh | 16 ++++++++++++--- 2 files changed, 43 insertions(+), 15 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 845f22af59..2afa2537b9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -981,6 +981,12 @@ sub full_url { sub do_git_commit { my ($self, $log_entry) = @_; + my $lr = $self->last_rev; + if (defined $lr && $lr >= $log_entry->{revision}) { + die "Last fetched revision of ", $self->refname, + " was r$lr, but we are about to fetch: ", + "r$log_entry->{revision}!\n"; + } if (my $c = $self->rev_db_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; @@ -1024,7 +1030,7 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; return $commit; } @@ -1121,14 +1127,13 @@ sub find_parent_branch { # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); - $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); - $self->ra->gs_do_update($rev, $rev, $self->{path}, - 1, $ed) + $self->ra->gs_do_update($rev, $rev, $self, $ed) or die "SVN connection failed somewhere...\n"; } print STDERR "Successfully followed parent\n"; @@ -1170,8 +1175,7 @@ sub do_fetch { $ed = SVN::Git::Fetcher->new($self); $ed->{new_fetch} = 1; } - unless ($self->ra->gs_do_update($last_rev, $rev, - $self->{path}, 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1616,6 +1620,8 @@ sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gpath = $self->git_path($path); + return undef if ($gpath eq ''); + # remove entire directories. if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -2233,12 +2239,23 @@ sub uuid { } sub gs_do_update { - my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $editor) = @_; + my $new = ($rev_a == $rev_b); + my $path = $gs->{path}; + + my $ta = $self->check_path($path, $rev_a); + my $tb = $new ? $ta : $self->check_path($path, $rev_b); + return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir); + if ($ta == $SVN::Node::none) { + $rev_a = $rev_b; + $new = 1; + } + my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), - $recurse, $editor, $pool); + 1, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); # Since we can't rely on svn_ra_reparent being available, we'll @@ -2253,7 +2270,6 @@ sub gs_do_update { } die "BUG: '$sp' != '$final'\n" if ($sp ne $final); - my $new = ($rev_a == $rev_b); $reporter->set_path($sp, $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); @@ -2264,7 +2280,8 @@ sub gs_do_update { # this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and # svn_ra_reparent didn't work before 1.4) sub gs_do_switch { - my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_; + my $path = $gs->{path}; my $pool = SVN::Pool->new; my $full_url = $self->{url}; @@ -2282,8 +2299,7 @@ sub gs_do_switch { } } $ra ||= $self; - my $reporter = $ra->do_switch($rev_b, '', - $recurse, $url_b, $editor, $pool); + my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); @@ -2333,6 +2349,8 @@ sub gs_fetch_loop_common { if ($paths) { $gs->match_paths($paths) or next; } + my $lr = $gs->last_rev; + next if defined $lr && $lr >= $r; next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { $gs->do_git_commit($log_entry); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index dcec16bda2..41b9c19d45 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -95,12 +95,12 @@ test_expect_success 'follow higher-level parent' " " test_expect_success 'follow deleted directory' " - svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye && svn rm -m 'remove glob' $svnrepo/glob && git-svn init -i glob $svnrepo/glob && git-svn fetch -i glob && - test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && - test -z \"\`git ls-tree -z refs/remotes/glob\`\" + test \"\`git cat-file blob refs/remotes/glob:blob/bye\`\" = hi && + test \"\`git ls-tree refs/remotes/glob | wc -l \`\" -eq 1 " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) @@ -146,6 +146,16 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "multi-fetch continues to work" " + git-svn multi-fetch --follow-parent + " + +test_expect_success "multi-fetch works off a 'clean' repository" " + rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && + mkdir $GIT_DIR/svn && + git-svn multi-fetch --follow-parent + " + test_debug 'gitk --all &' test_done -- cgit v1.2.3 From 9760adccccc0cc4dccc2f28765611550db640ceb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:06:56 -0800 Subject: git-svn: reinstate --no-metadata, add --svn-remote=, variable cleanups --svn-remote allows the default remote name to be overridden (useful for tracking multiple SVN repositories). Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 6 ++++++ git-svn.perl | 42 ++++++++++++++++++------------------------ 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 22dd7b219d..2914042587 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -301,6 +301,12 @@ section on '<>' for more information on using GIT_SVN_ID. +-R:: +--svn-remote :: + Specify the [svn-remote ""] section to use, + this allows multiple repositories to be tracked. + Default: git-svn + --follow-parent:: This is especially helpful when we're tracking a directory that has been moved around within the repository, or if we diff --git a/git-svn.perl b/git-svn.perl index 2afa2537b9..cc5736d793 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -4,12 +4,8 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL - $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $GIT_SVN_DIR $REVDB - $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade $_q - $_authors %users/; + $sha1 $sha1_short $_revision + $_q $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -17,11 +13,8 @@ $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'git-svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; -my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; -# make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; -$ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT sub fatal (@) { print STDERR @_; exit 1 } @@ -60,19 +53,19 @@ $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, - $_message, $_file, $_no_metadata, + $_message, $_file, $_template, $_shared, - $_version, $_upgrade, + $_version, $_merge, $_strategy, $_dry_run, $_prefix); my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); -my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, +my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, - 'no-metadata' => \$_no_metadata, + 'no-metadata' => \$Git::SVN::_no_metadata, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, %remote_opts ); @@ -152,11 +145,10 @@ for (my $i = 0; $i < @ARGV; $i++) { my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); -my $rv = GetOptions(%opts, 'help|H|h' => \$_help, - 'version|V' => \$_version, - 'minimize-connections' => - \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_ref_id); +my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => \$Git::SVN::Migration::_minimize, + 'id|i=s' => \$Git::SVN::default_ref_id, + 'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -634,7 +626,7 @@ sub cmt_metadata { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id $default_ref_id/; +use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -1012,9 +1004,11 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', - $log_entry->{revision}, ' ', - $self->ra->uuid, "\n" or croak $!; + unless ($_no_metadata) { + print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', + $log_entry->{revision}, ' ', + $self->ra->uuid, "\n" or croak $!; + } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; chomp(my $commit = do { local $/; <$out_fh> }); @@ -1059,7 +1053,7 @@ sub match_paths { sub find_parent_branch { my ($self, $paths, $rev) = @_; - return undef unless $::_follow_parent; + return undef unless $_follow_parent; unless (defined $paths) { $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { $paths = dup_changed_paths($_[0]) }); @@ -1112,7 +1106,7 @@ sub find_parent_branch { $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); } my ($r0, $parent) = $gs->find_rev_before($r, 1); - if ($::_follow_parent && (!defined $r0 || !defined $parent)) { + if ($_follow_parent && (!defined $r0 || !defined $parent)) { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } -- cgit v1.2.3 From c7eba7163b452840c8492b9ad87846b44cc98ea7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:45:28 -0800 Subject: git-svn: gracefully handle --follow-parent failures We don't always know that a path will exist at a particular revision. Signed-off-by: Eric Wong --- git-svn.perl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index cc5736d793..b2f86e84b4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1055,8 +1055,11 @@ sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; unless (defined $paths) { + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { $paths = dup_changed_paths($_[0]) }); + $SVN::Error::handler = $err_handler; } return undef unless defined $paths; -- cgit v1.2.3 From d4eff2bda5fc28559e96d62604ecaf78a4ff806b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 14:04:22 -0800 Subject: git-svn: make (multi-)fetch safer but slower get_log with explicit paths is the safest way to get revisions that change a particular path we're interested in. Unfortunately that means we still have to run get_log multiple times for each path we're interested in, and even more if a path gets deleted. The first argument of get_log() is an array reference, but we shouldn't use more than one element in that array ref because the non-existence of _one_ of those paths for a particular range would cause an error for all paths in that range, so yes, we need multiple get_log calls to be on the safe side... Signed-off-by: Eric Wong --- git-svn.perl | 75 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 38 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b2f86e84b4..efc5515663 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1038,27 +1038,15 @@ sub revisions_eq { return 1; } -sub match_paths { - my ($self, $paths) = @_; - return 1 if $paths->{'/'}; - $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; - grep /$self->{path_regex}/, keys %$paths and return 1; - my $c = ''; - foreach (split m#/#, $self->rel_path) { - $c .= "/$_"; - return 1 if $paths->{$c}; - } - return 0; -} - sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; unless (defined $paths) { my $err_handler = $SVN::Error::handler; $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; - $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, - sub { $paths = dup_changed_paths($_[0]) }); + $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { + $paths = + Git::SVN::Ra::dup_changed_paths($_[0]) }); $SVN::Error::handler = $err_handler; } return undef unless defined $paths; @@ -1134,7 +1122,6 @@ sub find_parent_branch { or die "SVN connection failed somewhere...\n"; } print STDERR "Successfully followed parent\n"; - $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } not_found: @@ -1170,7 +1157,6 @@ sub do_fetch { return $log_entry; } $ed = SVN::Git::Fetcher->new($self); - $ed->{new_fetch} = 1; } unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) { die "SVN connection failed somewhere...\n"; @@ -1243,8 +1229,6 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); - return undef if (! $ed->{new_fetch} && ! $ed->{nr} && ! @$untracked); - open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; @@ -2314,38 +2298,53 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } } while (1) { - my @revs; + my %revs; my $err; my $err_handler = $SVN::Error::handler; $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; - $self->get_log(\@paths, $min, $max, 0, 1, 1, - sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); - $SVN::Error::handler = $err_handler; - - if (! @revs && $err && $max >= $head) { - print STDERR "Branch probably deleted:\n ", - $err->expanded_message, - "\nWill attempt to follow revisions ", - "r$min .. r$max ", - "committed before the deletion\n"; - @revs = map { [ undef, $_ ] } ($min .. $max); - } - foreach (@revs) { - my ($paths, $r) = @$_; - foreach my $gs (@gs) { - if ($paths) { - $gs->match_paths($paths) or next; + foreach my $gs (@gs) { + $self->get_log([$gs->{path}], $min, $max, 0, 1, 1, sub + { my ($paths, $rev) = @_; + push @{$revs{$rev}}, + [ $gs, + dup_changed_paths($paths) ] }); + + next unless ($err && $max >= $head); + + print STDERR "Path '$gs->{path}' ", + "was probably deleted:\n", + $err->expanded_message, + "\nWill attempt to follow ", + "revisions r$min .. r$max ", + "committed before the deletion\n"; + my $hi = $max; + while (--$hi >= $min) { + my $ok; + $self->get_log([$gs->{path}], $min, $hi, + 0, 1, 1, sub { + my ($paths, $rev) = @_; + $ok = $rev; + push @{$revs{$rev}}, [ $gs, + dup_changed_paths($_[0])]}); + if ($ok) { + print STDERR "r$min .. r$ok OK\n"; + last; } + } + } + $SVN::Error::handler = $err_handler; + foreach my $r (sort {$a <=> $b} keys %revs) { + foreach (@{$revs{$r}}) { + my ($gs, $paths) = @$_; my $lr = $gs->last_rev; next if defined $lr && $lr >= $r; next if defined $gs->rev_db_get($r); -- cgit v1.2.3 From 47a0b75e01876b6e138d78c9504eebedd45c1283 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 05:13:30 -0800 Subject: git-svn: avoid a huge memory spike with high-numbered revisions Passing very large strings as arguments is bad for memory usage as it never seems to get freed in Perl. The .rev_db format is already not optimized for projects with sparse history. Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index efc5515663..ddb0382608 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1345,8 +1345,9 @@ sub rev_db_set { seek $fh, 0, 2 or croak $!; my $pos = tell $fh; if ($pos < $offset) { - print $fh (('0' x 40),"\n") x (($offset - $pos) / 41) - or croak $!; + for (1 .. (($offset - $pos) / 41)) { + print $fh (('0' x 40),"\n") or croak $!; + } } seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; -- cgit v1.2.3 From ecc712ddc41999e5f082cb69406d30caa062c6b9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 12:28:10 -0800 Subject: git-svn: re-enable repacking flags Signed-off-by: Eric Wong --- git-svn.perl | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index ddb0382608..1fd65526d1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,6 @@ my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, - $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_template, $_shared, $_version, @@ -64,10 +63,11 @@ my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, - 'repack:i' => \$_repack, + 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, 'quiet|q' => \$_q, - 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, + 'repack-flags|repack-args|repack-opts=s' => + \$Git::SVN::_repack_flags, %remote_opts ); my ($_trunk, $_tags, $_branches); @@ -158,6 +158,7 @@ load_authors() if $_authors; unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } +Git::SVN::init_vars(); eval { Git::SVN::verify_remotes_sanity(); $cmd{$cmd}->[0]->(@ARGV); @@ -626,11 +627,13 @@ sub cmt_metadata { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent/; +use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent + $_repack $_repack_flags/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; +my $_repack_nr; # properties that we do not log: my %SKIP_PROP; BEGIN { @@ -676,6 +679,14 @@ sub read_all_remotes { $r; } +sub init_vars { + if (defined $_repack) { + $_repack = 1000 if ($_repack <= 0); + $_repack_nr = $_repack; + $_repack_flags ||= '-d'; + } +} + sub verify_remotes_sanity { return unless -d $ENV{GIT_DIR}; my %seen; @@ -1025,6 +1036,13 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; + if (defined $_repack && (--$_repack_nr == 0)) { + $_repack_nr = $_repack; + # repack doesn't use any arguments with spaces in them, does it? + print "Running git repack $_repack_flags ...\n"; + command_noisy('repack', split(/\s+/, $_repack_flags)); + print "Done repacking\n"; + } return $commit; } -- cgit v1.2.3 From 373274f978a48b62549f20059bff630d85533533 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 13:54:23 -0800 Subject: git-svn: do our best to ensure that our ref and rev_db are consistent Defer any signals that cause termination while they are updating; and put the update-ref call as close to the rename() as possible. Also, make things extra-safe (but slower) for people using --no-metadata since they can't rely on .rev_db being rebuilt if it's clobbered (well, I'm calling update-ref with the -m flag for reflogs, we don't yet have a way to rebuild .rev_db from reflogs. Signed-off-by: Eric Wong --- git-svn.perl | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 1fd65526d1..2206f1b250 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -631,6 +631,7 @@ use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags/; use Carp qw/croak/; use File::Path qw/mkpath/; +use File::Copy qw/copy/; use IPC::Open3; my $_repack_nr; @@ -645,6 +646,9 @@ BEGIN { svn:entry:committed-date/; } +my %LOCKFILES; +END { unlink keys %LOCKFILES if %LOCKFILES } + sub fetch_all { my ($repo_id, $url, $fetch) = @_; my @gs; @@ -1030,8 +1034,7 @@ sub do_git_commit { die "Failed to commit, invalid sha1: $commit\n"; } - command_noisy('update-ref',$self->refname, $commit); - $self->rev_db_set($log_entry->{revision}, $commit); + $self->rev_db_set($log_entry->{revision}, $commit, 1); $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; @@ -1353,11 +1356,28 @@ sub rebuild { # 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). +# These files are disposable unless --no-metadata is set sub rev_db_set { - my ($self, $rev, $commit) = @_; + my ($self, $rev, $commit, $update_ref) = @_; length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - open my $fh, '+<', $self->{db_path} or croak $!; + my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock"); + my $sig; + if ($update_ref) { + $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = + $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; + } + $LOCKFILES{$db_lock} = 1; + if ($_no_metadata) { + copy($db, $db_lock) or die "rev_db_set(@_): ", + "Failed to copy: ", + "$db => $db_lock ($!)\n"; + } else { + rename $db, $db_lock or die "rev_db_set(@_): ", + "Failed to rename: ", + "$db => $db_lock ($!)\n"; + } + open my $fh, '+<', $db_lock or croak $!; my $offset = $rev * 41; # assume that append is the common case: seek $fh, 0, 2 or croak $!; @@ -1370,6 +1390,18 @@ sub rev_db_set { seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; close $fh or croak $!; + if ($update_ref) { + command_noisy('update-ref', '-m', "r$rev", + $self->refname, $commit); + } + rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ", + "$db_lock => $db ($!)\n"; + delete $LOCKFILES{$db_lock}; + if ($update_ref) { + $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = + $SIG{USR1} = $SIG{USR2} = 'DEFAULT'; + kill $sig, $$ if defined $sig; + } } sub rev_db_get { -- cgit v1.2.3 From 9c93fee51e26d5db82414317fa169294f3fa94b0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:22:31 -0800 Subject: git-svn: avoid redundant get_log calls between invocations Prefill .rev_db to the maximum revision we tried to fetch; and take advantage of that so we can avoid using get_log() on ranges we've already seen (and have deemed uninteresting). Signed-off-by: Eric Wong --- git-svn.perl | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 2206f1b250..b1d91fa471 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -655,18 +655,14 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; - my $new_remote; foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->last_rev; + my $lr = $gs->rev_db_max; if (defined $lr) { $base = $lr if ($lr < $base); - } else { - $new_remote = 1; } push @gs, $gs; } - $base = 0 if $new_remote; return if (++$base > $head); $ra->gs_fetch_loop_common($base, $head, @gs); } @@ -899,13 +895,17 @@ sub last_rev_commit { $rl = readline $fh; defined $rl or return (undef, undef); chomp $rl; - while ($c ne $rl && tell $fh != 0) { + while (('0' x40) eq $rl && tell $fh != 0) { $offset -= 41; seek $fh, $offset, 2; $rl = readline $fh; defined $rl or return (undef, undef); chomp $rl; } + if ($c) { + die "$self->{db_path} and ", $self->refname, + " inconsistent!:\n$c != $rl\n"; + } my $rev = tell $fh; croak $! if ($rev < 0); $rev = ($rev - 41) / 41; @@ -917,7 +917,7 @@ sub last_rev_commit { sub get_fetch_range { my ($self, $min, $max) = @_; $max ||= $self->ra->get_latest_revnum; - $min ||= $self->last_rev || 0; + $min ||= $self->rev_db_max; (++$min, $max); } @@ -1404,6 +1404,16 @@ sub rev_db_set { } } +sub rev_db_max { + my ($self) = @_; + my @stat = stat $self->{db_path} or + die "Couldn't stat $self->{db_path}: $!\n"; + ($stat[7] % 41) == 0 or + die "$self->{db_path} inconsistent size:$stat[7]\n"; + my $max = $stat[7] / 41; + (($max > 0) ? $max - 1 : 0); +} + sub rev_db_get { my ($self, $rev) = @_; my $ret; @@ -2404,6 +2414,12 @@ sub gs_fetch_loop_common { } } } + # pre-fill the .rev_db since it'll eventually get filled in + # with '0' x40 if something new gets committed + foreach my $gs (@gs) { + next if defined $gs->rev_db_get($max); + $gs->rev_db_set($max, 0 x40); + } last if $max >= $head; $min = $max + 1; $max += $inc; -- cgit v1.2.3 From ce4b4af7ff36d4e4999da937dffd2f9a3a420277 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:57:36 -0800 Subject: git-svn: use sys* IO functions for reading rev_db Using buffered IO for reading 40-41 bytes at a time isn't very efficient. Buffering writes for a short duration is alright since we close() right away and buffers will be flushed. Signed-off-by: Eric Wong --- git-svn.perl | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b1d91fa471..1e3a3c08f1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -891,23 +891,20 @@ sub last_rev_commit { my $rl; open my $fh, '<', $self->{db_path} or croak "$self->{db_path} not readable: $!\n"; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); + sysseek($fh, $offset, 2); # don't care for errors + sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; - while (('0' x40) eq $rl && tell $fh != 0) { + while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) { $offset -= 41; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); + sysseek($fh, $offset, 2); # don't care for errors + sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; } if ($c) { die "$self->{db_path} and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } - my $rev = tell $fh; - croak $! if ($rev < 0); + my $rev = sysseek($fh, 0, 1) or croak $!; $rev = ($rev - 41) / 41; close $fh or croak $!; ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1419,12 +1416,9 @@ sub rev_db_get { my $ret; my $offset = $rev * 41; open my $fh, '<', $self->{db_path} or croak $!; - if (seek $fh, $offset, 0) { - $ret = readline $fh; - if (defined $ret) { - chomp $ret; - $ret = undef if ($ret =~ /^0{40}$/); - } + if (sysseek($fh, $offset, 0) == $offset) { + my $read = sysread($fh, $ret, 40); + $ret = undef if ($read != 40 || $ret eq ('0'x40)); } close $fh or croak $!; $ret; -- cgit v1.2.3 From d8115c5104dbee29433a7f33a3e0d3e1738a581e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:30:31 -0800 Subject: git-svn: don't write to the config file from --follow-parent Having 'fetch' entries in the config file created from --follow-parent is wasteful because it can cause *future* of invocations to follow revisions we were never interested in in the first place. Signed-off-by: Eric Wong --- git-svn.perl | 20 +++++++++++--------- t/t9104-git-svn-follow-parent.sh | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 1e3a3c08f1..a2db73fe00 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -724,7 +724,7 @@ sub find_existing_remote { } sub init_remote_config { - my ($self, $url) = @_; + my ($self, $url, $no_write) = @_; $url =~ s!/+$!!; # strip trailing slash my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); @@ -769,19 +769,21 @@ sub init_remote_config { die "svn-remote.$xrepo_id.fetch already set to track ", "$xpath:refs/remotes/", $self->refname, "\n"; } - command_noisy('config', - "svn-remote.$self->{repo_id}.url", $url); - command_noisy('config', '--add', - "svn-remote.$self->{repo_id}.fetch", - "$self->{path}:".$self->refname); + unless ($no_write) { + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); + } $self->{url} = $url; } sub init { - my ($class, $url, $path, $repo_id, $ref_id) = @_; + my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_; my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { - $self->init_remote_config($url); + $self->init_remote_config($url, $no_write); } $self; } @@ -1112,7 +1114,7 @@ sub find_parent_branch { # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); print STDERR "Initializing parent: $ref_id\n"; - $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($_follow_parent && (!defined $r0 || !defined $parent)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 41b9c19d45..7c852c1d7f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,7 +34,7 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -n \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.git-svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " -- cgit v1.2.3 From 88cf4107eb70cdcdc226f2385a3ee54fb428c41d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:59:07 -0800 Subject: git-svn: save paths to tags/branches with for future reuse Signed-off-by: Eric Wong --- git-svn.perl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index a2db73fe00..ad2ef53f8d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -469,6 +469,8 @@ sub complete_url_ls_init { my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); my $url = $ra->{url}; + my $remote_id; + my $remote_path; foreach my $d (sort keys %$dirent) { next if ($dirent->{$d}->kind != $SVN::Node::dir); my $path = "$repo_path/$d"; @@ -477,8 +479,17 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref); } + $remote_id ||= $gs->{repo_id} if $gs; + } + if (defined $remote_id) { + $remote_path = "$ra->{svn_path}/$repo_path/*"; + $remote_path =~ s#/+#/#g; + $remote_path =~ s#^/##g; + my ($n) = ($switch =~ /^--(\w+)/); + command_noisy('config', "svn-remote.$remote_id.$n", + $remote_path); } } -- cgit v1.2.3 From 471bc000528cf49928dae8872609b7fefc0c59ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:06:27 -0800 Subject: git-svn: migrations default to [svn-remote "git-svn"] It looks better (like [remote "origin"]) instead of whatever refname came up first in our directory traversal. Of course --remote= overrides this. Signed-off-by: Eric Wong --- git-svn.perl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index ad2ef53f8d..de14ed4357 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2945,7 +2945,10 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); + eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) }; + if ($@) { + Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); + } $migrated++; } $migrated; -- cgit v1.2.3 From ef70de968509c447f5c02f4ba99f1cf0cadf5c1f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:12:41 -0800 Subject: git-svn: get rid of revisions_eq check for --follow-parent This was originally needed before we used the delta fetcher and had a less-clean follow-parent implementation that could leave holes in the history. Signed-off-by: Eric Wong --- git-svn.perl | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index de14ed4357..58d0600f89 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1059,16 +1059,6 @@ sub do_git_commit { return $commit; } -sub revisions_eq { - my ($self, $r0, $r1) = @_; - return 1 if $r0 == $r1; - my $nr = 0; - $self->ra->get_log([$self->{path}], $r0, $r1, - 0, 0, 1, sub { $nr++ }); - return 0 if ($nr > 1); - return 1; -} - sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; @@ -1132,7 +1122,7 @@ sub find_parent_branch { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } - if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { + if (defined $r0 && defined $parent) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; $self->assert_index_clean($parent); my $ed; -- cgit v1.2.3 From 502c1bf629154b4a248105b10346a06a6ff07387 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:26:00 -0800 Subject: git-svn: avoid extra get_log calls when refspecs are added for fetching Since fetch_loop_common starts from the lowest revision number in a group of Git::SVN objects; we want to avoid refetching get_log for current users for things we've already cut it. Signed-off-by: Eric Wong --- git-svn.perl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 58d0600f89..8c24012e7b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2370,7 +2370,12 @@ sub gs_fetch_loop_common { skip_unknown_revs($err); }; foreach my $gs (@gs) { - $self->get_log([$gs->{path}], $min, $max, 0, 1, 1, sub + my $min_r = $min; + my $rdb_max = $gs->rev_db_max; + next if $rdb_max >= $max; + $min_r = $rdb_max + 1 if ($rdb_max > $min_r); + $self->get_log([$gs->{path}], $min_r, $max, + 0, 1, 1, sub { my ($paths, $rev) = @_; push @{$revs{$rev}}, [ $gs, -- cgit v1.2.3 From 9fa00b655cfd67bf344668a0d913f90ec9a8141d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 12:49:48 -0800 Subject: git-svn: just name the default svn-remote "svn" instead of "git-svn" It can be confusing and redundant, since historically the default remote ref (not remote itself) has been "git-svn", too. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9100-git-svn-basic.sh | 8 ++++---- t/t9104-git-svn-follow-parent.sh | 10 +++++----- t/t9107-git-svn-migrate.sh | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 8c24012e7b..66b4c20fd9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -10,7 +10,7 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::Log::TZ = $ENV{TZ}; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 3dc4de2fad..8b6c8ffe10 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -214,7 +214,7 @@ EOF test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && git-svn migrate " @@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' " test_expect_failure 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && - git-repo-config --unset svn-remote.git-svn.fetch \ + git-repo-config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && git-svn init ${svnrepo}2/bar " @@ -230,9 +230,9 @@ test_expect_failure 'exit if init-ing a would clobber a URL' " test_expect_success \ 'init allows us to connect to another directory in the same repo' " git-svn init -i bar $svnrepo/bar && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^bar:refs/remotes/bar$' && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^:refs/remotes/git-svn$' " diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 7c852c1d7f..eebb84974c 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,15 +34,15 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -z \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " - git-repo-config svn-remote.git-svn.url $svnrepo && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config svn-remote.svn.url $svnrepo && + git-repo-config --add svn-remote.svn.fetch \ trunk:refs/remotes/svn/trunk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git-svn fetch --follow-parent -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ @@ -54,7 +54,7 @@ test_expect_success 'init and fetch from one svn-remote' " test_expect_success 'follow deleted parent' " svn cp -m 'resurrecting trunk as junk' \ -r2 $svnrepo/trunk $svnrepo/junk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch --follow-parent -i svn/thunk && git-svn fetch -i svn/junk --follow-parent && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index f6d84ba7a5..0fbfd264ec 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -34,14 +34,14 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " ! test -d $GIT_DIR/git-svn && git-rev-parse --verify refs/remotes/git-svn^0 && git-rev-parse --verify refs/remotes/svn^0 && - test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && - test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + test \`git repo-config --get svn-remote.svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.svn.fetch\` = \ ':refs/remotes/git-svn' " test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && @@ -65,8 +65,8 @@ test_expect_success 'multi-fetch works on partial urls + paths' " " test_expect_success 'migrate --minimize on old multi-inited layout' " - git repo-config --unset-all svn-remote.git-svn.fetch && - git repo-config --unset-all svn-remote.git-svn.url && + git repo-config --unset-all svn-remote.svn.fetch && + git repo-config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && for i in \`cat fetch.out\`; do path=\`expr \$i : '\\([^:]*\\):.*$'\` @@ -78,7 +78,7 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " done && git-svn migrate --minimize && test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && -- cgit v1.2.3 From 4bb9ed0466e9ca6b72b3d9c454a743aea862b1f4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 13:29:17 -0800 Subject: git-svn: prepare multi-init for wildcard support Update the tests since we no longer write so many things to the config. Signed-off-by: Eric Wong --- git-svn.perl | 124 ++++++++++++++++++++++++++++++++++++++++++--- t/t9107-git-svn-migrate.sh | 20 +++++--- 2 files changed, 131 insertions(+), 13 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 66b4c20fd9..6874eabffa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,9 +367,10 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - my $url = $remotes->{$repo_id}->{url} or next; - my $fetch = $remotes->{$repo_id}->{fetch} or next; - Git::SVN::fetch_all($repo_id, $url, $fetch); + if ($remotes->{$repo_id}->{url} && + $remotes->{$repo_id}->{fetch}) { + Git::SVN::fetch_all($repo_id, $remotes); + } } } @@ -479,9 +480,12 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref, 1); + } + if ($gs) { + $remote_id = $gs->{repo_id}; + last; } - $remote_id ||= $gs->{repo_id} if $gs; } if (defined $remote_id) { $remote_path = "$ra->{svn_path}/$repo_path/*"; @@ -489,7 +493,7 @@ sub complete_url_ls_init { $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn-remote.$remote_id.$n", - $remote_path); + "$remote_path:refs/remotes/$pfx*"); } } @@ -660,9 +664,48 @@ BEGIN { my %LOCKFILES; END { unlink keys %LOCKFILES if %LOCKFILES } +sub resolve_local_globs { + my ($url, $fetch, $glob_spec) = @_; + return unless defined $glob_spec; + my $ref = $glob_spec->{ref}; + my $path = $glob_spec->{path}; + foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { + next unless m#^refs/remotes/$ref->{regex}$#; + my $p = $1; + my $pathname = $path->full_path($p); + my $refname = $ref->full_path($p); + if (my $existing = $fetch->{$pathname}) { + if ($existing ne $refname) { + die "Refspec conflict:\n", + "existing: refs/remotes/$existing\n", + " globbed: refs/remotes/$refname\n"; + } + my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; + $u =~ s!^\Q$url\E/?!! or die + "refs/remotes/$refname: '$url' not found in '$u'\n"; + if ($pathname ne $u) { + warn "W: Refspec glob conflict ", + "(ref: refs/remotes/$refname):\n", + "expected path: $pathname\n", + " real path: $u\n", + "Continuing ahead with $u\n"; + next; + } + } else { + warn "Globbed ($path->{glob}:$ref->{glob}): ", + "$pathname == $refname\n"; + $fetch->{$pathname} = $refname; + } + } +} + sub fetch_all { - my ($repo_id, $url, $fetch) = @_; + my ($repo_id, $remotes) = @_; + my $fetch = $remotes->{$repo_id}->{fetch}; + my $url = $remotes->{$repo_id}->{url}; my @gs; + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); my $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; @@ -685,6 +728,16 @@ sub read_all_remotes { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; + } elsif (m!^(.+)\.(branches|tags)= + (.*):refs/remotes/(.+)\s*$/!x) { + my ($p, $g) = ($3, $4); + my $rs = $r->{$1}->{$2} = { + path => Git::SVN::GlobSpec->new($p), + ref => Git::SVN::GlobSpec->new($g) }; + if (length($rs->{ref}->{right}) != 0) { + die "The '*' glob character must be the last ", + "character of '$g'\n"; + } } } $r; @@ -3072,10 +3125,67 @@ sub DESTROY { command_close_pipe($self->{gui}, $self->{ctx}); } +package Git::SVN::GlobSpec; +use strict; +use warnings; + +sub new { + my ($class, $glob) = @_; + warn "glob: $glob\n"; + my $re = $glob; + $re =~ s!/+$!!g; # no need for trailing slashes + my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); + my ($left, $right) = ($1, $2); + if ($nr > 1) { + warn "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; + } elsif ($nr == 0) { + warn "One '*' is needed for glob: '$glob'\n"; + } + $re = quotemeta($left) . $re . quotemeta($right); + $left =~ s!/+$!!g; + $right =~ s!^/+!!g; + bless { left => $left, right => $right, + regex => qr/$re/, glob => $glob }, $class; +} + +sub full_path { + my ($self, $path) = @_; + return (length $self->{left} ? "$self->{left}/" : '') . + $path . (length $self->{right} ? "/$self->{right}" : ''); +} + __END__ Data structures: + +$remotes = { # returned by read_all_remotes() + 'svn' => { + # svn-remote.svn.url=https://svn.musicpd.org + url => 'https://svn.musicpd.org', + # svn-remote.svn.fetch=mpd/trunk:trunk + fetch => { + 'mpd/trunk' => 'trunk', + }, + # svn-remote.svn.tags=mpd/tags/*:tags/* + tags => { + path => { + left => 'mpd/tags', + right => '', + regex => qr!mpd/tags/([^/]+)$!, + glob => 'tags/*', + }, + ref => { + left => 'tags', + right => '', + regex => qr!tags/([^/]+)$!, + glob => 'tags/*', + }, + } + } +}; + $log_entry hashref as returned by libsvn_log_entry() { log => 'whitespace-formatted log entry diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 0fbfd264ec..8376429bcb 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -6,7 +6,7 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && mkdir import && - cd import + cd import && for i in trunk branches/a branches/b \ tags/0.1 tags/0.2 tags/0.3; do mkdir -p \$i && \ @@ -43,11 +43,19 @@ test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && - grep '^branches/a:refs/remotes/a$' fetch.out && - grep '^branches/b:refs/remotes/b$' fetch.out && - grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && - grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && - grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + test -n \"\`git-config --get svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$'\`\" && + test -n \"\`git-config --get svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$'\`\" && + git config --unset svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$' && + git config --unset svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$' && + git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' && + git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' && + for i in tags/0.1 tags/0.2 tags/0.3; do + git-config --add svn-remote.svn.fetch \ + \$i:refs/remotes/\$i || exit 1; done " # refs should all be different, but the trees should all be the same: -- cgit v1.2.3 From fbcc1737d61f2b83846c59a7cc0d820e09833350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 6 Feb 2007 18:35:30 -0800 Subject: git-svn: reintroduce using a single get_log() to fetch We'll need to rely on path matching to handle wildcard support for branches and tags. Signed-off-by: Eric Wong --- git-svn.perl | 103 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 54 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 6874eabffa..d0bde71f2e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1112,6 +1112,24 @@ sub do_git_commit { return $commit; } +sub match_paths { + my ($self, $paths, $r) = @_; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + if (grep /$self->{path_regex}/, keys %$paths) { + return 1; + } + my $c = ''; + foreach (split m#/#, $self->{path}) { + $c .= "/$_"; + next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + my @x = eval { $self->ra->get_dir($self->{path}, $r) }; + if (scalar @x == 3) { + return 1; + } + } + return 0; +} + sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; @@ -1308,15 +1326,21 @@ sub make_log_entry { print $un $_, "\n" foreach @$untracked; my %log_entry = ( parents => $parents || [], revision => $rev, log => ''); - my $rp = $self->ra->rev_proplist($rev); - foreach (sort keys %$rp) { - my $v = $rp->{$_}; - if (/^svn:(author|date|log)$/) { - $log_entry{$1} = $v; - } else { - print $un " rev_prop: ", uri_encode($_), ' ', - uri_encode($v), "\n"; + + my $logged = delete $self->{logged_rev_props}; + if (!$logged || $self->{-want_extra_revprops}) { + my $rp = $self->ra->rev_proplist($rev); + foreach (sort keys %$rp) { + my $v = $rp->{$_}; + if (/^svn:(author|date|log)$/) { + $log_entry{$1} = $v; + } else { + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($v), "\n"; + } } + } else { + map { $log_entry{$_} = $logged->{$_} } keys %$logged; } close $un or croak $!; @@ -2416,55 +2440,26 @@ sub gs_fetch_loop_common { } while (1) { my %revs; - my $err; my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = sub { - ($err) = @_; - skip_unknown_revs($err); - }; - foreach my $gs (@gs) { - my $min_r = $min; - my $rdb_max = $gs->rev_db_max; - next if $rdb_max >= $max; - $min_r = $rdb_max + 1 if ($rdb_max > $min_r); - $self->get_log([$gs->{path}], $min_r, $max, - 0, 1, 1, sub - { my ($paths, $rev) = @_; - push @{$revs{$rev}}, - [ $gs, - dup_changed_paths($paths) ] }); - - next unless ($err && $max >= $head); - - print STDERR "Path '$gs->{path}' ", - "was probably deleted:\n", - $err->expanded_message, - "\nWill attempt to follow ", - "revisions r$min .. r$max ", - "committed before the deletion\n"; - my $hi = $max; - while (--$hi >= $min) { - my $ok; - $self->get_log([$gs->{path}], $min, $hi, - 0, 1, 1, sub { - my ($paths, $rev) = @_; - $ok = $rev; - push @{$revs{$rev}}, [ $gs, - dup_changed_paths($_[0])]}); - if ($ok) { - print STDERR "r$min .. r$ok OK\n"; - last; - } - } - } + $SVN::Error::handler = \&skip_unknown_revs; + $self->get_log([''], $min, $max, 0, 1, 1, sub { + my ($paths, $r, $author, $date, $log) = @_; + $revs{$r} = [ dup_changed_paths($paths), + { author => $author, + date => $date, + log => $log } ] }); $SVN::Error::handler = $err_handler; + foreach my $r (sort {$a <=> $b} keys %revs) { - foreach (@{$revs{$r}}) { - my ($gs, $paths) = @$_; - my $lr = $gs->last_rev; - next if defined $lr && $lr >= $r; - next if defined $gs->rev_db_get($r); - if (my $log_entry = $gs->do_fetch($paths, $r)) { + my ($paths, $logged) = @{$revs{$r}}; + foreach my $gs (@gs) { + if ($gs->rev_db_max >= $r) { + next; + } + next unless $gs->match_paths($paths, $r); + $gs->{logged_rev_props} = $logged; + my $log_entry = $gs->do_fetch($paths, $r); + if ($log_entry) { $gs->do_git_commit($log_entry); } } -- cgit v1.2.3 From d2ae14346c71d57c57dc7b1c1f8272c09f51ccf9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 7 Feb 2007 11:50:16 -0800 Subject: git-svn: run get_log() on a sub-directory if possible This is an optimization that should conserve network bandwidth on certain repositories and configurations. Signed-off-by: Eric Wong --- git-svn.perl | 55 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index d0bde71f2e..5e1a64c6d6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2433,21 +2433,62 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + + my %common; foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } + my @tmp = split m#/#, $gs->{path}; + my $p = ''; + foreach (@tmp) { + $p .= length($p) ? "/$_" : $_; + $common{$p} ||= 0; + $common{$p}++; + } + } + my $longest_path = ''; + foreach (sort {length $b <=> length $a} keys %common) { + if ($common{$_} == @gs) { + $longest_path = $_; + last; + } } while (1) { my %revs; + my $err; my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = \&skip_unknown_revs; - $self->get_log([''], $min, $max, 0, 1, 1, sub { - my ($paths, $r, $author, $date, $log) = @_; - $revs{$r} = [ dup_changed_paths($paths), - { author => $author, - date => $date, - log => $log } ] }); + $SVN::Error::handler = sub { + ($err) = @_; + skip_unknown_revs($err); + }; + sub _cb { + my ($paths, $r, $author, $date, $log) = @_; + [ dup_changed_paths($paths), + { author => $author, date => $date, log => $log } ]; + } + $self->get_log([$longest_path], $min, $max, 0, 1, 1, + sub { $revs{$_[1]} = _cb(@_) }); + if ($err && $max >= $head) { + print STDERR "Path '$longest_path' ", + "was probably deleted:\n", + $err->expanded_message, + "\nWill attempt to follow ", + "revisions r$min .. r$max ", + "committed before the deletion\n"; + my $hi = $max; + while (--$hi >= $min) { + my $ok; + $self->get_log([$longest_path], $min, $hi, + 0, 1, 1, sub { + $ok ||= $_[1]; + $revs{$_[1]} = _cb(@_) }); + if ($ok) { + print STDERR "r$min .. r$ok OK\n"; + last; + } + } + } $SVN::Error::handler = $err_handler; foreach my $r (sort {$a <=> $b} keys %revs) { -- cgit v1.2.3 From e518192f3be92097ba550098dbb69d769ca18429 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 8 Feb 2007 12:53:57 -0800 Subject: git-svn: implement auto-discovery of branches/tags This is similar to the way git proper handles refs, except we use the keys 'branches' and 'tags' to distinguish when we want to use wildcards. The left-hand side of the ':' contains the remote path, and must have one asterisk ('*') in it for the branch name. The asterisk may be in any component of the path as long as is it on its own directory level. The right-hand side contains the refname and must have the asterisk as the last path component. branches = branches/*:refs/remotes/* tags = tags/*:refs/remotes/tags/* Signed-off-by: Eric Wong --- git-svn.perl | 148 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 120 insertions(+), 28 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 5e1a64c6d6..21d53054f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -701,14 +701,32 @@ sub resolve_local_globs { sub fetch_all { my ($repo_id, $remotes) = @_; - my $fetch = $remotes->{$repo_id}->{fetch}; - my $url = $remotes->{$repo_id}->{url}; - my @gs; - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); + my $remote = $remotes->{$repo_id}; + my $fetch = $remote->{fetch}; + my $url = $remote->{url}; + my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); + my $uuid = $ra->uuid; my $head = $ra->get_latest_revnum; my $base = $head; + + # read the max revs for wildcard expansion (branches/*, tags/*) + foreach my $t (qw/branches tags/) { + defined $remote->{$t} or next; + push @globs, $remote->{$t}; + my $f = "$ENV{GIT_DIR}/svn/.$uuid.$t"; + if (open my $fh, '<', $f) { + chomp(my $max_rev = <$fh>); + close $fh or die "Error closing $f: $!\n"; + + if ($max_rev !~ /^\d+$/) { + die "$max_rev (in $f) is not an integer!\n"; + } + $remote->{$t}->{max_rev} = $max_rev; + $base = $max_rev if ($max_rev < $base); + } + } + foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); my $lr = $gs->rev_db_max; @@ -717,8 +735,7 @@ sub fetch_all { } push @gs, $gs; } - return if (++$base > $head); - $ra->gs_fetch_loop_common($base, $head, @gs); + $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } sub read_all_remotes { @@ -732,6 +749,7 @@ sub read_all_remotes { (.*):refs/remotes/(.+)\s*$/!x) { my ($p, $g) = ($3, $4); my $rs = $r->{$1}->{$2} = { + t => $2, path => Git::SVN::GlobSpec->new($p), ref => Git::SVN::GlobSpec->new($g) }; if (length($rs->{ref}->{right}) != 0) { @@ -793,20 +811,26 @@ sub init_remote_config { my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } else { my $min_url = Git::SVN::Ra->new($url)->minimize_url; $existing = find_existing_remote($min_url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } if ($min_url ne $url) { - print STDERR "Using higher level of URL: ", - "$url => $min_url\n"; + unless ($no_write) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + } my $old_path = $self->{path}; $self->{path} = $url; $self->{path} =~ s!^\Q$min_url\E/*!!; @@ -1122,8 +1146,8 @@ sub match_paths { foreach (split m#/#, $self->{path}) { $c .= "/$_"; next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->ra->get_dir($self->{path}, $r) }; - if (scalar @x == 3) { + if ($self->ra->check_path($self->{path}, $r) == + $SVN::Node::dir) { return 1; } } @@ -1172,6 +1196,10 @@ sub find_parent_branch { my $u = $remotes->{$repo_id}->{url} or next; next if $url ne $u; my $fetch = $remotes->{$repo_id}->{fetch}; + foreach (qw/branches tags/) { + resolve_local_globs($url, $fetch, + $remotes->{$repo_id}->{$_}); + } foreach my $f (keys %$fetch) { next if $f ne $branch_from; $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); @@ -1238,7 +1266,7 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->{last_commit}) { + if ($self->last_commit) { $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @@ -1354,8 +1382,7 @@ sub fetch { my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); - return if ($base > $head); - $self->ra->gs_fetch_loop_common($base, $head, $self); + $self->ra->gs_fetch_loop_common($base, $head, [$self]); } sub set_tree_cb { @@ -2430,12 +2457,14 @@ sub gs_do_switch { } sub gs_fetch_loop_common { - my ($self, $base, $head, @gs) = @_; + my ($self, $base, $head, $gsv, $globs) = @_; + return if ($base > $head); my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my %common; - foreach my $gs (@gs) { + my $common_max = scalar @$gsv; + + foreach my $gs (@$gsv) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } @@ -2447,9 +2476,21 @@ sub gs_fetch_loop_common { $common{$p}++; } } + $globs ||= []; + $common_max += scalar @$globs; + foreach my $glob (@$globs) { + my @tmp = split m#/#, $glob->{path}->{left}; + my $p = ''; + foreach (@tmp) { + $p .= length($p) ? "/$_" : $_; + $common{$p} ||= 0; + $common{$p}++; + } + } + my $longest_path = ''; foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @gs) { + if ($common{$_} == $common_max) { $longest_path = $_; last; } @@ -2491,9 +2532,12 @@ sub gs_fetch_loop_common { } $SVN::Error::handler = $err_handler; + my %exists = map { $_->{path} => $_ } @$gsv; foreach my $r (sort {$a <=> $b} keys %revs) { my ($paths, $logged) = @{$revs{$r}}; - foreach my $gs (@gs) { + + foreach my $gs ($self->match_globs(\%exists, $paths, + $globs, $r)) { if ($gs->rev_db_max >= $r) { next; } @@ -2504,10 +2548,22 @@ sub gs_fetch_loop_common { $gs->do_git_commit($log_entry); } } + foreach my $g (@$globs) { + my $f = "$ENV{GIT_DIR}/svn/." . + $self->uuid . ".$g->{t}"; + open my $fh, '>', "$f.tmp" or + die "Can't open $f.tmp for writing: $!"; + print $fh "$r\n" or + die "Couldn't write to $f: $!\n"; + close $fh or die "Error closing $f: $!\n"; + rename "$f.tmp", $f or + die "Couldn't rename ", + "$f.tmp => $f: $!\n"; + } } # pre-fill the .rev_db since it'll eventually get filled in # with '0' x40 if something new gets committed - foreach my $gs (@gs) { + foreach my $gs (@$gsv) { next if defined $gs->rev_db_get($max); $gs->rev_db_set($max, 0 x40); } @@ -2518,6 +2574,43 @@ sub gs_fetch_loop_common { } } +sub match_globs { + my ($self, $exists, $paths, $globs, $r) = @_; + foreach my $g (@$globs) { + foreach (keys %$paths) { + next unless /$g->{path}->{regex}/; + my $p = $1; + my $pathname = $g->{path}->full_path($p); + next if $exists->{$pathname}; + $exists->{$pathname} = Git::SVN->init( + $self->{url}, $pathname, undef, + $g->{ref}->full_path($p), 1); + } + my $c = ''; + foreach (split m#/#, $g->{path}->{left}) { + $c .= "/$_"; + next unless ($paths->{$c} && + ($paths->{$c}->{action} eq 'A')); + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + next unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != + $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, + $p, undef, + $g->{ref}->full_path($de), 1); + } + } + } + values %$exists; +} + sub minimize_url { my ($self) = @_; return $self->{url} if ($self->{url} eq $self->{repos_root}); @@ -3167,16 +3260,15 @@ use warnings; sub new { my ($class, $glob) = @_; - warn "glob: $glob\n"; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); my ($left, $right) = ($1, $2); if ($nr > 1) { - warn "Only one '*' wildcard expansion ", - "is supported (got $nr): '$glob'\n"; + die "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; } elsif ($nr == 0) { - warn "One '*' is needed for glob: '$glob'\n"; + die "One '*' is needed for glob: '$glob'\n"; } $re = quotemeta($left) . $re . quotemeta($right); $left =~ s!/+$!!g; -- cgit v1.2.3 From b9dffd8cad737a07d6a05503318c6746ac593f9c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 01:28:30 -0800 Subject: git-svn: --follow-parent tracks multi-parent paths We can have a branch that was deleted, then re-added under the same name but copied from another path, in which case we'll have multiple parents (we don't want to break the original ref, nor lose copypath info). Add a test for this, too, of course. Signed-off-by: Eric Wong --- git-svn.perl | 14 +++++++++++--- t/t9104-git-svn-follow-parent.sh | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 21d53054f6..cd35efec7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1266,11 +1266,19 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->last_commit) { + if (my $lc = $self->last_commit) { + # we can have a branch that was deleted, then re-added + # under the same name but copied from another path, in + # which case we'll have multiple parents (we don't + # want to break the original ref, nor lose copypath info): + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + push @{$log_entry->{parents}}, $lc; + return $log_entry; + } $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; - $ed->{c} = $self->{last_commit}; - @parents = ($self->{last_commit}); + $ed->{c} = $lc; + @parents = ($lc); } else { $last_rev = $rev; if (my $log_entry = $self->find_parent_branch($paths, $rev)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index eebb84974c..f5b7e5efe0 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -146,6 +146,13 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "track multi-parent paths" " + svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && + git-svn multi-fetch --follow-parent && + test \`git cat-file commit refs/remotes/glob | \ + grep '^parent ' | wc -l\` -eq 2 + " + test_expect_success "multi-fetch continues to work" " git-svn multi-fetch --follow-parent " -- cgit v1.2.3 From d542aedb9424872474e896a6d20407745d4bd627 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:19:41 -0800 Subject: git-svn: remove check_path calls before calling do_update These checks were needed before git-svn got smarter about match_paths() and using path information returned by get_log(). We also have extra checking against fetching revisions out-of-order these days; so we don't have to worry about that as much. We also check for tree deletions in match_paths() and skip those as well. Signed-off-by: Eric Wong --- git-svn.perl | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index cd35efec7e..819d25e289 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1138,6 +1138,9 @@ sub do_git_commit { sub match_paths { my ($self, $paths, $r) = @_; + if (my $path = $paths->{"/$self->{path}"}) { + return ($path->{action} eq 'D') ? 0 : 1; + } $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; if (grep /$self->{path_regex}/, keys %$paths) { return 1; @@ -2394,14 +2397,6 @@ sub gs_do_update { my $new = ($rev_a == $rev_b); my $path = $gs->{path}; - my $ta = $self->check_path($path, $rev_a); - my $tb = $new ? $ta : $self->check_path($path, $rev_b); - return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir); - if ($ta == $SVN::Node::none) { - $rev_a = $rev_b; - $new = 1; - } - my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; -- cgit v1.2.3 From e20bea654513aa7eba7e356994fa56cc90bb5b6d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:32:48 -0800 Subject: git-svn: remove some noisy debugging messages We don't need them anymore, all the rough points of the --follow-parent implementation have been worked out. The only improvement in the future will probably be --follow-parent-harder, which will track subdirectories and follow individual file history (so annotate/blame can be complete); but that is still a ways off. Signed-off-by: Eric Wong --- git-svn.perl | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 819d25e289..c8a1df3651 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -692,8 +692,6 @@ sub resolve_local_globs { next; } } else { - warn "Globbed ($path->{glob}:$ref->{glob}): ", - "$pathname == $refname\n"; $fetch->{$pathname} = $refname; } } @@ -1179,8 +1177,8 @@ sub find_parent_branch { last if $i; unshift(@a_path_components, pop(@b_path_components)); } - goto not_found unless defined $i; - my $branch_from = $i->{copyfrom_path} or goto not_found; + return undef unless defined $i; + my $branch_from = $i->{copyfrom_path} or return undef; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -1247,21 +1245,6 @@ sub find_parent_branch { print STDERR "Successfully followed parent\n"; return $self->make_log_entry($rev, [$parent], $ed); } -not_found: - print STDERR "Branch parent for path: '/", - $self->rel_path, "' @ r$rev not found:\n"; - return undef unless $paths; - print STDERR "Changed paths:\n"; - foreach my $x (sort keys %$paths) { - my $p = $paths->{$x}; - print STDERR "\t$p->{action}\t$x"; - if ($p->{copyfrom_path}) { - print STDERR "(from $p->{copyfrom_path}: ", - "$p->{copyfrom_rev})"; - } - print STDERR "\n"; - } - print STDERR '-'x72, "\n"; return undef; } -- cgit v1.2.3 From 0bed5eaa0edc3a2dfa0d5910ff1fb280539b242d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:45:03 -0800 Subject: git-svn: enable follow-parent functionality by default --no-follow-parent disables and reverts it back to the old default behavior of not following parents (if you don't care for full history). Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 3 ++- git-svn.perl | 4 ++-- t/t9104-git-svn-follow-parent.sh | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 16 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 2914042587..50dc6ac818 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -311,7 +311,8 @@ for more information on using GIT_SVN_ID. This is especially helpful when we're tracking a directory that has been moved around within the repository, or if we started tracking a branch and never tracked the trunk it was - descended from. + descended from. This feature is enabled by default, use + --no-follow-parent to disable it. config key: svn.followparent diff --git a/git-svn.perl b/git-svn.perl index c8a1df3651..b9bacc384e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -57,11 +57,11 @@ my ($_stdin, $_help, $_edit, $_version, $_merge, $_strategy, $_dry_run, $_prefix); - +$Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); -my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, +my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index f5b7e5efe0..53f5a925ac 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Eric Wong # -test_description='git-svn --follow-parent fetching' +test_description='git-svn fetching' . ./lib-git-svn.sh test_expect_success 'initialize repo' " @@ -27,9 +27,9 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'init and fetch --follow-parent a moved directory' " +test_expect_success 'init and fetch a moved directory' " git-svn init -i thunk $svnrepo/thunk && - git-svn fetch --follow-parent -i thunk && + git-svn fetch -i thunk && test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ @@ -44,7 +44,7 @@ test_expect_success 'init and fetch from one svn-remote' " trunk:refs/remotes/svn/trunk && git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && - git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ @@ -56,8 +56,8 @@ test_expect_success 'follow deleted parent' " -r2 $svnrepo/trunk $svnrepo/junk && git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && - git-svn fetch --follow-parent -i svn/thunk && - git-svn fetch -i svn/junk --follow-parent && + git-svn fetch -i svn/thunk && + git-svn fetch -i svn/junk && test -z \"\`git diff svn/junk svn/trunk\`\" && test \"\`git merge-base svn/junk svn/trunk\`\" \ = \"\`git rev-parse svn/trunk\`\" @@ -69,7 +69,7 @@ test_expect_success 'follow larger parent' " svn import -m 'import a larger parent' import $svnrepo/larger-parent && svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && - git-svn fetch -i larger --follow-parent && + git-svn fetch -i larger && git-rev-parse --verify refs/remotes/larger && git-rev-parse --verify \ refs/remotes/larger-parent/trunk/thunk/bump/thud && @@ -91,7 +91,7 @@ test_expect_success 'follow higher-level parent' " svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && git-svn init -i blob $svnrepo/glob/blob && - git-svn fetch -i blob --follow-parent + git-svn fetch -i blob " test_expect_success 'follow deleted directory' " @@ -128,7 +128,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' " cd .. && git-svn init -i r9270-t \ $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-t --follow-parent && + git-svn fetch -i r9270-t && test \`git rev-list r9270-t | wc -l\` -eq 2 && test \"\`git ls-tree --name-only r9270-t~1\`\" = \ \"\`git ls-tree --name-only r9270-t\`\" @@ -138,7 +138,7 @@ test_expect_success "track initial change if it was only made to parent" " svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && git-svn init -i r9270-d \ $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-d --follow-parent && + git-svn fetch -i r9270-d && test \`git rev-list r9270-d | wc -l\` -eq 3 && test \"\`git ls-tree --name-only r9270-t\`\" = \ \"\`git ls-tree --name-only r9270-d\`\" && @@ -148,19 +148,19 @@ test_expect_success "track initial change if it was only made to parent" " test_expect_success "track multi-parent paths" " svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && - git-svn multi-fetch --follow-parent && + git-svn multi-fetch && test \`git cat-file commit refs/remotes/glob | \ grep '^parent ' | wc -l\` -eq 2 " test_expect_success "multi-fetch continues to work" " - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_expect_success "multi-fetch works off a 'clean' repository" " rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && mkdir $GIT_DIR/svn && - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_debug 'gitk --all &' -- cgit v1.2.3 From 4e9f6cc78e5d955bd0faffe76ae9aea6590189f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:17:57 -0800 Subject: git-svn: fix buggy regular expression usage in several places I incorrectly used $path/? and $path/* to strip off leading directories, but places where $path = 'branches/0.17' would incorrectly strip changes to 'branches/0.17.1' as well. For globs, we require that our '*' is its own path component (surrounded by '/' or nothing). Enforce this when --prefix= is passed to us, too. Signed-off-by: Eric Wong --- git-svn.perl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b9bacc384e..7664b385f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -492,6 +492,9 @@ sub complete_url_ls_init { $remote_path =~ s#/+#/#g; $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); + if (length $pfx && $pfx !~ m#/$#) { + die "--prefix='$pfx' must have a trailing slash '/'\n"; + } command_noisy('config', "svn-remote.$remote_id.$n", "$remote_path:refs/remotes/$pfx*"); } @@ -681,7 +684,7 @@ sub resolve_local_globs { " globbed: refs/remotes/$refname\n"; } my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; - $u =~ s!^\Q$url\E/?!! or die + $u =~ s!^\Q$url\E(/|$)!! or die "refs/remotes/$refname: '$url' not found in '$u'\n"; if ($pathname ne $u) { warn "W: Refspec glob conflict ", @@ -831,7 +834,7 @@ sub init_remote_config { } my $old_path = $self->{path}; $self->{path} = $url; - $self->{path} =~ s!^\Q$min_url\E/*!!; + $self->{path} =~ s!^\Q$min_url\E(/|$)!!; if (length $old_path) { $self->{path} .= "/$old_path"; } @@ -927,10 +930,8 @@ sub rel_path { my ($self) = @_; my $repos_root = $self->ra->{repos_root}; return $self->{path} if ($self->{url} eq $repos_root); - my $url = $self->{url} . - (length $self->{path} ? "/$self->{path}" : $self->{path}); - $url =~ s!^\Q$repos_root\E/*!!g; - $url; + die "BUG: rel_path failed! repos_root: $repos_root, Ra URL: ", + $self->ra->{url}, " path: $self->{path}, URL: $self->{url}\n"; } sub traverse_ignore { @@ -1136,10 +1137,11 @@ sub do_git_commit { sub match_paths { my ($self, $paths, $r) = @_; + return 1 if $self->{path} eq ''; if (my $path = $paths->{"/$self->{path}"}) { return ($path->{action} eq 'D') ? 0 : 1; } - $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//; if (grep /$self->{path_regex}/, keys %$paths) { return 1; } @@ -1732,7 +1734,7 @@ sub new { sub set_path_strip { my ($self, $path) = @_; - $self->{path_strip} = qr/^\Q$path\E\/?/; + $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path; } sub open_root { @@ -2347,7 +2349,7 @@ sub new { auth_provider_callbacks => $callbacks); $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; - $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; + $self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##; $RA = bless $self, $class; } @@ -3136,7 +3138,7 @@ sub minimize_connections { my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); my $root_path = $ra->{url}; - $root_path =~ s#^\Q$ra->{repos_root}\E/*##; + $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##; foreach my $path (keys %$fetch) { my $ref_id = $fetch->{$path}; my $gs = Git::SVN->new($ref_id, $repo_id, $path); @@ -3248,7 +3250,7 @@ sub new { my ($class, $glob) = @_; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes - my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); + my $nr = ($re =~ s!^(.*)\*(.*)$!\(\[^/\]+\)!g); my ($left, $right) = ($1, $2); if ($nr > 1) { die "Only one '*' wildcard expansion ", @@ -3257,8 +3259,12 @@ sub new { die "One '*' is needed for glob: '$glob'\n"; } $re = quotemeta($left) . $re . quotemeta($right); - $left =~ s!/+$!!g; - $right =~ s!^/+!!g; + if (length $left && !($left =~ s!/+$!!g)) { + die "Missing trailing '/' on left side of: '$glob' ($left)\n"; + } + if (length $right && !($right =~ s!^/+!!g)) { + die "Missing leading '/' on right side of: '$glob' ($right)\n"; + } bless { left => $left, right => $right, regex => qr/$re/, glob => $glob }, $class; } -- cgit v1.2.3 From 9e3cdbd4f2e02bf63bfaa8f6e2747601f117cf2d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:23:47 -0800 Subject: git-svn: correctly handle the -q flag in SVN::Git::Fetcher Signed-off-by: Eric Wong --- git-svn.perl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 7664b385f6..ed363e972d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1770,14 +1770,14 @@ sub delete_entry { while (<$ls>) { chomp; $self->{gii}->remove($_); - print "\tD\t$_\n" unless $self->{q}; + print "\tD\t$_\n" unless $::_q; } - print "\tD\t$gpath/\n" unless $self->{q}; + print "\tD\t$gpath/\n" unless $::_q; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { $self->{gii}->remove($gpath); - print "\tD\t$gpath\n" unless $self->{q}; + print "\tD\t$gpath\n" unless $::_q; } undef; } @@ -1913,7 +1913,7 @@ sub close_file { } $fb->{pool}->clear; $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!; - print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; + print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q; undef; } -- cgit v1.2.3 From 74a81227f95b52b1c3f7ac7ba84ac1a6e1708995 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:28:50 -0800 Subject: git-svn: correctly handle globs with a right-hand-side path component Several bugs were found and fixed while getting this to work: * Remember the 'R'(eplace) case of actions and treat it like we would an 'A'(dd) case. * Fix a small case of follow-parent missing a parent if a subdirectory was modified in the revision where the parent was copied. * dirents returned by get_dir sometimes expire if the data structure is too big and the pool is destroyed, so we cache get_dir (along with check_path and get_revprops) temporarily along with its pool. Signed-off-by: Eric Wong --- git-svn.perl | 84 +++++++++++++++++++++++++++++++++++-------------- t/t9108-git-svn-glob.sh | 53 +++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 24 deletions(-) create mode 100755 t/t9108-git-svn-glob.sh (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index ed363e972d..50b7dcf255 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1148,7 +1148,8 @@ sub match_paths { my $c = ''; foreach (split m#/#, $self->{path}) { $c .= "/$_"; - next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + next unless ($paths->{$c} && + ($paths->{$c}->{action} =~ /^[AR]$/)); if ($self->ra->check_path($self->{path}, $r) == $SVN::Node::dir) { return 1; @@ -1176,11 +1177,11 @@ sub find_parent_branch { my $i; while (@b_path_components) { $i = $paths->{'/'.join('/', @b_path_components)}; - last if $i; + last if $i && defined $i->{copyfrom_path}; unshift(@a_path_components, pop(@b_path_components)); } - return undef unless defined $i; - my $branch_from = $i->{copyfrom_path} or return undef; + return undef unless defined $i && defined $i->{copyfrom_path}; + my $branch_from = $i->{copyfrom_path}; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -2309,8 +2310,7 @@ my $RA; BEGIN { # enforce temporary pool usage for some simple functions my $e; - foreach (qw/get_latest_revnum rev_proplist get_file - check_path get_dir get_uuid get_repos_root/) { + foreach (qw/get_latest_revnum get_uuid get_repos_root/) { $e .= "sub $_ { my \$self = shift; my \$pool = SVN::Pool->new; @@ -2318,7 +2318,30 @@ BEGIN { \$pool->clear; wantarray ? \@ret : \$ret[0]; }\n"; } - eval $e; + + # get_dir needs $pool held in cache for dirents to work, + # check_path is cacheable and rev_proplist is close enough + # for our purposes. + foreach (qw/check_path get_dir rev_proplist/) { + $e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ { + my \$self = shift; + my \$r = pop; + my \$k = join(\"\\0\", \@_); + if (my \$x = \$${_}_cache{\$r}->{\$k}) { + return wantarray ? \@\$x : \$x->[0]; + } + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool); + if (\$r != \$${_}_rev) { + \%${_}_cache = ( pool => [] ); + \$${_}_rev = \$r; + } + \$${_}_cache{\$r}->{\$k} = \\\@ret; + push \@{\$${_}_cache{pool}}, \$pool; + wantarray ? \@ret : \$ret[0]; }\n"; + } + $e .= "\n1;"; + eval $e or die $@; } sub new { @@ -2564,8 +2587,34 @@ sub gs_fetch_loop_common { sub match_globs { my ($self, $exists, $paths, $globs, $r) = @_; + + sub get_dir_check { + my ($self, $exists, $g, $r) = @_; + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + return unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, $p, undef, + $g->{ref}->full_path($de), 1); + } + } foreach my $g (@$globs) { + if (my $path = $paths->{"/$g->{path}->{left}"}) { + if ($path->{action} =~ /^[AR]$/) { + get_dir_check($self, $exists, $g, $r); + } + } foreach (keys %$paths) { + if (/$g->{path}->{left_regex}/) { + next if $paths->{$_}->{action} !~ /^[AR]$/; + get_dir_check($self, $exists, $g, $r); + } next unless /$g->{path}->{regex}/; my $p = $1; my $pathname = $g->{path}->full_path($p); @@ -2578,22 +2627,8 @@ sub match_globs { foreach (split m#/#, $g->{path}->{left}) { $c .= "/$_"; next unless ($paths->{$c} && - ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; - next unless scalar @x == 3; - my $dirents = $x[0]; - foreach my $de (keys %$dirents) { - next if $dirents->{$de}->kind != - $SVN::Node::dir; - my $p = $g->{path}->full_path($de); - next if $exists->{$p}; - next if (length $g->{path}->{right} && - ($self->check_path($p, $r) != - $SVN::Node::dir)); - $exists->{$p} = Git::SVN->init($self->{url}, - $p, undef, - $g->{ref}->full_path($de), 1); - } + ($paths->{$c}->{action} =~ /^[AR]$/)); + get_dir_check($self, $exists, $g, $r); } } values %$exists; @@ -3265,7 +3300,8 @@ sub new { if (length $right && !($right =~ s!^/+!!g)) { die "Missing leading '/' on right side of: '$glob' ($right)\n"; } - bless { left => $left, right => $right, + my $left_re = qr/^\/\Q$left\E(\/|$)/; + bless { left => $left, right => $right, left_regex => $left_re, regex => qr/$re/, glob => $glob }, $class; } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh new file mode 100755 index 0000000000..47cccdfd0e --- /dev/null +++ b/t/t9108-git-svn-glob.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Copyright (c) 2007 Eric Wong +test_description='git-svn globbing refspecs' +. ./lib-git-svn.sh + +cat > expect.end < trunk/src/a/readme && + echo 'goodbye world' > trunk/src/b/readme && + svn import -m 'initial' trunk $svnrepo/trunk && + svn co $svnrepo tmp && + cd tmp && + mkdir branches tags && + svn add branches tags && + svn cp trunk branches/start && + svn commit -m 'start a new branch' && + svn up && + echo 'hi' >> branches/start/src/b/readme && + echo 'hey' >> branches/start/src/a/readme && + svn commit -m 'hi' && + svn up && + svn cp branches/start tags/end && + echo 'bye' >> tags/end/src/b/readme && + echo 'aye' >> tags/end/src/a/readme && + svn commit -m 'the end' && + echo 'byebye' >> tags/end/src/b/readme && + svn commit -m 'nothing to see here' + cd .. && + git config --add svn-remote.svn.url $svnrepo && + git config --add svn-remote.svn.fetch \ + 'trunk/src/a:refs/remotes/trunk' && + git config --add svn-remote.svn.branches \ + 'branches/*/src/a:refs/remotes/branches/*' && + git config --add svn-remote.svn.tags\ + 'tags/*/src/a:refs/remotes/tags/*' && + git-svn multi-fetch && + git log --pretty=oneline refs/remotes/tags/end | \ + sed -e 's/^.\{41\}//' > output.end && + cmp expect.end output.end && + test \"\`git rev-parse refs/remotes/tags/end~1\`\" = \ + \"\`git rev-parse refs/remotes/branches/start\`\" && + test \"\`git rev-parse refs/remotes/branches/start~2\`\" = \ + \"\`git rev-parse refs/remotes/trunk\`\" + " + +test_done -- cgit v1.2.3 From 490f49ea5899b7aacfb82c0ed5639d722a56704a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:58:33 -0800 Subject: git-svn: remove optimized commit stuff for set-tree I may resurrect it for dcommit at some point, but nobody really uses set-tree anymore and I don't feel like introducing more complexity into the code at this point. Signed-off-by: Eric Wong --- git-svn.perl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 50b7dcf255..7e1a655259 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -48,7 +48,6 @@ BEGIN { my ($SVN); -my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, @@ -1384,15 +1383,8 @@ sub fetch { sub set_tree_cb { my ($self, $log_entry, $tree, $rev, $date, $author) = @_; - # TODO: enable and test optimized commits: - if (0 && $rev == ($self->{last_rev} + 1)) { - $log_entry->{revision} = $rev; - $log_entry->{author} = $author; - $self->do_git_commit($log_entry, "$rev=$tree"); - } else { - $self->{inject_parents} = { $rev => $tree }; - $self->fetch(undef, undef); - } + $self->{inject_parents} = { $rev => $tree }; + $self->fetch(undef, undef); } sub set_tree { -- cgit v1.2.3 From 8a49ee9759f72ba7c61e035a2ca4b10d8a51c94e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 20:46:50 -0800 Subject: git-svn: add support for SVN::Mirror/svk using revprops for metadata Pass --use-svm-props or set the svn.usesvmprops key with git-config to enable using properties set by SVN::Mirror when it mirrored the upstream URL. This is heavily based on work from Sam Vilain: > From: Sam Vilain > Date: Sun, 11 Feb 2007 12:34:45 +1300 > Subject: [PATCH] git-svn: re-map repository URLs and UUIDs on SVK mirror paths > > If an SVN revision has a property, "svm:headrev", it is likely that > the revision was created by SVN::Mirror (a part of SVK). The property > contains a repository UUID and a revision. We want to make it look > like we are mirroring the original URL, so introduce a helper function > that returns the original identity URL and UUID, and use it when > generating commit messages. Signed-off-by: Eric Wong --- git-svn.perl | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 10 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 7e1a655259..23e1d42cf7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -64,6 +64,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, + 'use-svm-props|svm-props' => \$Git::SVN::_use_svm_props, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -645,7 +646,7 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags/; + $_repack $_repack_flags $_use_svm_props/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -920,9 +921,54 @@ sub new { sub refname { "refs/remotes/$_[0]->{ref_id}" } +sub set_svm_vars { + my ($self, $ra) = @_; + my $section = "svn-remote.$self->{repo_id}"; + + # see if we have it in our config, first: + eval { + $self->{svm} = { + source => $self->tmp_config('--get', "$section.svm-source"), + uuid => $self->tmp_config('--get', "$section.svm-uuid"), + } + }; + return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); + + # nope, make sure we're connected to the repository root: + if ($ra->{repos_root} ne $self->{url}) { + $ra = Git::SVN::Ra->new($ra->{repos_root}); + } + my $r = $ra->get_latest_revnum; + my ($props) = ($ra->get_dir('', $r))[2]; + if (my $src = $props->{'svm:source'}) { + # don't know what a '!' is there for, also the + # username is of no interest + $src =~ s{!$}{}; + $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + $self->tmp_config('--add', "$section.svm-source", $src); + + my $uuid = $props->{'svm:uuid'}; + $uuid =~ m{^[0-9a-f\-]{30,}$} + or die "doesn't look right - svm:uuid is '$uuid'\n"; + $self->tmp_config('--add', "$section.svm-uuid", $uuid); + + $self->{svm} = { source => $src , uuid => $uuid }; + } + if ($ra->{repos_root} ne $self->{url}) { + $ra = Git::SVN::Ra->new($self->{url}); + } + $ra; +} + sub ra { my ($self) = shift; - Git::SVN::Ra->new($self->{url}); + my $ra = Git::SVN::Ra->new($self->{url}); + $self->{-use_svm_props} = $Git::SVN::_use_svm_props; + if ($self->{-use_svm_props} && !$self->{svm}) { + $ra = $self->set_svm_vars($ra); + $self->{-want_revprops} = 1; + } + $ra; } sub rel_path { @@ -1006,16 +1052,44 @@ sub get_fetch_range { (++$min, $max); } +sub tmp_config { + my ($self, @args) = @_; + unless (-f $self->{config}) { + open my $fh, '>', $self->{config} or + die "Can't open $self->{config}: $!\n"; + print $fh "; This file is used internally by git-svn\n" or + die "Couldn't write to $self->{config}: $!\n"; + print $fh "; You should not have to edit it\n" or + die "Couldn't write to $self->{config}: $!\n"; + close $fh or die "Couldn't close $self->{config}: $!\n"; + } + my $old_config = $ENV{GIT_CONFIG}; + $ENV{GIT_CONFIG} = $self->{config}; + $@ = undef; + my @ret = eval { command('config', @args) }; + my $err = $@; + if (defined $old_config) { + $ENV{GIT_CONFIG} = $old_config; + } else { + delete $ENV{GIT_CONFIG}; + } + die $err if $err; + wantarray ? @ret : $ret[0]; +} + sub tmp_index_do { my ($self, $sub) = @_; my $old_index = $ENV{GIT_INDEX_FILE}; $ENV{GIT_INDEX_FILE} = $self->{index}; - my @ret = &$sub; - if ($old_index) { + $@ = undef; + my @ret = eval { &$sub }; + my $err = $@; + if (defined $old_index) { $ENV{GIT_INDEX_FILE} = $old_index; } else { delete $ENV{GIT_INDEX_FILE}; } + die $err if $err; wantarray ? @ret : $ret[0]; } @@ -1105,9 +1179,8 @@ sub do_git_commit { or croak $!; print $msg_fh $log_entry->{log} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', - $log_entry->{revision}, ' ', - $self->ra->uuid, "\n" or croak $!; + print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n" + or croak $!; } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; @@ -1123,7 +1196,11 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; + print "r$log_entry->{revision}"; + if (defined $log_entry->{svm_revision}) { + print " (\@$log_entry->{svm_revision})"; + } + print " = $commit ($self->{ref_id})\n"; if (defined $_repack && (--$_repack_nr == 0)) { $_repack_nr = $_repack; # repack doesn't use any arguments with spaces in them, does it? @@ -1351,13 +1428,16 @@ sub make_log_entry { my %log_entry = ( parents => $parents || [], revision => $rev, log => ''); + my $headrev; my $logged = delete $self->{logged_rev_props}; - if (!$logged || $self->{-want_extra_revprops}) { + if (!$logged || $self->{-want_revprops}) { my $rp = $self->ra->rev_proplist($rev); foreach (sort keys %$rp) { my $v = $rp->{$_}; if (/^svn:(author|date|log)$/) { $log_entry{$1} = $v; + } elsif ($_ eq 'svm:headrev') { + $headrev = $v; } else { print $un " rev_prop: ", uri_encode($_), ' ', uri_encode($v), "\n"; @@ -1371,6 +1451,21 @@ sub make_log_entry { $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; + if (defined $headrev && $self->{-use_svm_props}) { + my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; + if ($uuid ne $self->{svm}->{uuid}) { + die "UUID mismatch on SVM path:\n", + "expected: $self->{svm}->{uuid}\n", + " got: $uuid\n"; + } + my $full_url = $self->{svm}->{source}; + $full_url .= "/$self->{path}" if length $self->{path}; + $log_entry{metadata} = "$full_url\@$r $uuid"; + $log_entry{svm_revision} = $r; + } else { + $log_entry{metadata} = $self->full_url . "\@$rev " . + $self->ra->uuid; + } \%log_entry; } @@ -1549,7 +1644,7 @@ sub _new { close $fh or croak $!; } bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", - path => $path, + path => $path, config => "$ENV{GIT_DIR}/svn/config", db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; } -- cgit v1.2.3 From 91b03282b586cc521f9b009ea8273444fb58f2f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 00:51:33 -0800 Subject: git-svn: add support for per-[svn-remote "..."] options Available options are currently: svn-remote..{noMetadata,useSvmProps,followParent} These boolean switches will override options set globally in [svn], and even override options set on the command-line (this should probably change in the future, however). Note that the noMetadata and useSvmProps options conflict. It's both technically and logically impossible to use them together. Signed-off-by: Eric Wong --- git-svn.perl | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 23e1d42cf7..becf2e0d89 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -662,6 +662,29 @@ BEGIN { svn:entry:last-author svn:entry:uuid svn:entry:committed-date/; + + # some options are read globally, but can be overridden locally + # per [svn-remote "..."] section. Command-line options will *NOT* + # override options set in an [svn-remote "..."] section + my $e; + foreach (qw/follow_parent no_metadata use_svm_props/) { + my $key = $_; + $key =~ tr/_//d; + $e .= "sub $_ { + my (\$self) = \@_; + return \$self->{-$_} if exists \$self->{-$_}; + my \$k = \"svn-remote.\$self->{repo_id}\.$key\"; + eval { command_oneline(qw/config --get/, \$k) }; + if (\$@) { + \$self->{-$_} = \$Git::SVN::_$_; + } else { + my \$v = command_oneline(qw/config --bool/,\$k); + \$self->{-$_} = \$v eq 'false' ? 0 : 1; + } + return \$self->{-$_} }\n"; + } + $e .= "1;\n"; + eval $e or die $@; } my %LOCKFILES; @@ -963,8 +986,11 @@ sub set_svm_vars { sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); - $self->{-use_svm_props} = $Git::SVN::_use_svm_props; - if ($self->{-use_svm_props} && !$self->{svm}) { + if ($self->use_svm_props && !$self->{svm}) { + if ($self->no_metadata) { + die "Can't have both --no-metadata and ", + "--use-svm-props options set!\n"; + } $ra = $self->set_svm_vars($ra); $self->{-want_revprops} = 1; } @@ -1014,7 +1040,7 @@ sub last_rev_commit { return ($self->{last_rev}, $self->{last_commit}); } my $c = ::verify_ref($self->refname.'^0'); - if ($c) { + if ($c && !$self->use_svm_props && !$self->no_metadata) { my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1034,7 +1060,7 @@ sub last_rev_commit { sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; } - if ($c) { + if ($c && $c ne $rl) { die "$self->{db_path} and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } @@ -1178,7 +1204,7 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - unless ($_no_metadata) { + unless ($self->no_metadata) { print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n" or croak $!; } @@ -1236,7 +1262,7 @@ sub match_paths { sub find_parent_branch { my ($self, $paths, $rev) = @_; - return undef unless $_follow_parent; + return undef unless $self->follow_parent; unless (defined $paths) { my $err_handler = $SVN::Error::handler; $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; @@ -1297,7 +1323,7 @@ sub find_parent_branch { $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); - if ($_follow_parent && (!defined $r0 || !defined $parent)) { + if (!defined $r0 || !defined $parent) { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } @@ -1451,7 +1477,7 @@ sub make_log_entry { $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; - if (defined $headrev && $self->{-use_svm_props}) { + if (defined $headrev && $self->use_svm_props) { my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { die "UUID mismatch on SVM path:\n", @@ -1556,7 +1582,7 @@ sub rev_db_set { $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } $LOCKFILES{$db_lock} = 1; - if ($_no_metadata) { + if ($self->no_metadata) { copy($db, $db_lock) or die "rev_db_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; -- cgit v1.2.3 From 93f2689ccdf7b62864aac40097bfd51328fae5b7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 01:20:26 -0800 Subject: git-svn: use private $GIT_DIR/svn/config file more Switch max_rev storage over to using it for globbing branches and tags. Signed-off-by: Eric Wong --- git-svn.perl | 53 +++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index becf2e0d89..b62cc067d8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -738,16 +738,10 @@ sub fetch_all { foreach my $t (qw/branches tags/) { defined $remote->{$t} or next; push @globs, $remote->{$t}; - my $f = "$ENV{GIT_DIR}/svn/.$uuid.$t"; - if (open my $fh, '<', $f) { - chomp(my $max_rev = <$fh>); - close $fh or die "Error closing $f: $!\n"; - - if ($max_rev !~ /^\d+$/) { - die "$max_rev (in $f) is not an integer!\n"; - } - $remote->{$t}->{max_rev} = $max_rev; - $base = $max_rev if ($max_rev < $base); + my $max_rev = eval { tmp_config(qw/--int --get/, + "svn-remote.$repo_id.${t}-maxRev") }; + if (defined $max_rev && ($max_rev < $base)) { + $base = $max_rev; } } @@ -774,6 +768,7 @@ sub read_all_remotes { my ($p, $g) = ($3, $4); my $rs = $r->{$1}->{$2} = { t => $2, + remote => $1, path => Git::SVN::GlobSpec->new($p), ref => Git::SVN::GlobSpec->new($g) }; if (length($rs->{ref}->{right}) != 0) { @@ -951,8 +946,8 @@ sub set_svm_vars { # see if we have it in our config, first: eval { $self->{svm} = { - source => $self->tmp_config('--get', "$section.svm-source"), - uuid => $self->tmp_config('--get', "$section.svm-uuid"), + source => tmp_config('--get', "$section.svm-source"), + uuid => tmp_config('--get', "$section.svm-uuid"), } }; return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); @@ -968,12 +963,12 @@ sub set_svm_vars { # username is of no interest $src =~ s{!$}{}; $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; - $self->tmp_config('--add', "$section.svm-source", $src); + tmp_config('--add', "$section.svm-source", $src); my $uuid = $props->{'svm:uuid'}; $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - $self->tmp_config('--add', "$section.svm-uuid", $uuid); + tmp_config('--add', "$section.svm-uuid", $uuid); $self->{svm} = { source => $src , uuid => $uuid }; } @@ -1079,18 +1074,19 @@ sub get_fetch_range { } sub tmp_config { - my ($self, @args) = @_; - unless (-f $self->{config}) { - open my $fh, '>', $self->{config} or - die "Can't open $self->{config}: $!\n"; + my (@args) = @_; + my $config = "$ENV{GIT_DIR}/svn/config"; + unless (-f $config) { + open my $fh, '>', $config or + die "Can't open $config: $!\n"; print $fh "; This file is used internally by git-svn\n" or - die "Couldn't write to $self->{config}: $!\n"; + die "Couldn't write to $config: $!\n"; print $fh "; You should not have to edit it\n" or - die "Couldn't write to $self->{config}: $!\n"; - close $fh or die "Couldn't close $self->{config}: $!\n"; + die "Couldn't write to $config: $!\n"; + close $fh or die "Couldn't close $config: $!\n"; } my $old_config = $ENV{GIT_CONFIG}; - $ENV{GIT_CONFIG} = $self->{config}; + $ENV{GIT_CONFIG} = $config; $@ = undef; my @ret = eval { command('config', @args) }; my $err = $@; @@ -2673,16 +2669,9 @@ sub gs_fetch_loop_common { } } foreach my $g (@$globs) { - my $f = "$ENV{GIT_DIR}/svn/." . - $self->uuid . ".$g->{t}"; - open my $fh, '>', "$f.tmp" or - die "Can't open $f.tmp for writing: $!"; - print $fh "$r\n" or - die "Couldn't write to $f: $!\n"; - close $fh or die "Error closing $f: $!\n"; - rename "$f.tmp", $f or - die "Couldn't rename ", - "$f.tmp => $f: $!\n"; + my $k = "svn-remote.$g->{remote}." . + "$g->{t}-maxRev"; + Git::SVN::tmp_config($k, $r); } } # pre-fill the .rev_db since it'll eventually get filled in -- cgit v1.2.3 From 97ae091169b233ecd80eb5ef2da80145f8c724f7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 15:21:24 -0800 Subject: git-svn: extra safety for noMetadata and useSvmProps users Make sure we flush our userspace buffers and and fsync(2) .rev_db information to disk if we use these options because we really don't want to lose this information. Also, disallow --use-svm-props and --no-metadata from the command-line because history will be inconsistent if they're only used occasionally. If a user wants to use these options, they must be set in the config so they're always on. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 26 +++++++++++++++++++++----- git-svn.perl | 29 ++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 50dc6ac818..d45283a53f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -316,17 +316,33 @@ for more information on using GIT_SVN_ID. config key: svn.followparent ---no-metadata:: +svn.noMetadata: +svn-remote..noMetadata: This gets rid of the git-svn-id: lines at the end of every commit. If you lose your .git/svn/git-svn/.rev_db file, git-svn will not be able to rebuild it and you won't be able to fetch again, either. This is fine for one-shot imports. - The 'git-svn log' command will not work on repositories using this, - either. - -config key: svn.nometadata + The 'git-svn log' command will not work on repositories using + this, either. Using this conflicts with the 'useSvmProps' + option for (hopefully) obvious reasons. + +svn.useSvmProps: +svn-remote..useSvmProps: + This allows git-svn to re-map repository URLs and UUIDs from + mirrors created using SVN::Mirror (or svk) for metadata. + + If an SVN revision has a property, "svm:headrev", it is likely + that the revision was created by SVN::Mirror (also used by SVK). + The property contains a repository UUID and a revision. We want + to make it look like we are mirroring the original URL, so + introduce a helper function that returns the original identity + URL and UUID, and use it when generating metadata in commit + messages. + + Using this conflicts with the 'noMetadata' option for + (hopefully) obvious reasons. -- diff --git a/git-svn.perl b/git-svn.perl index b62cc067d8..fdecffbbc7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -63,8 +63,8 @@ my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, - 'no-metadata' => \$Git::SVN::_no_metadata, - 'use-svm-props|svm-props' => \$Git::SVN::_use_svm_props, + 'noMetadata' => \$Git::SVN::_no_metadata, + 'useSvmProps' => \$Git::SVN::_use_svm_props, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -606,9 +606,14 @@ sub load_authors { sub read_repo_config { return unless -d $ENV{GIT_DIR}; my $opts = shift; + my @config_only; foreach my $o (keys %$opts) { + # if we have mixedCase and a long option-only, then + # it's a config-only variable that we don't need for + # the command-line. + push @config_only, $o if ($o =~ /[A-Z]/ && $o =~ /^[a-z]+$/i); my $v = $opts->{$o}; - my ($key) = ($o =~ /^([a-z\-]+)/); + my ($key) = ($o =~ /^([a-zA-Z\-]+)/); $key =~ s/-//g; my $arg = 'git-config'; $arg .= ' --int' if ($o =~ /[:=]i$/); @@ -623,6 +628,7 @@ sub read_repo_config { } } } + delete @$opts{@config_only} if @config_only; } sub extract_metadata { @@ -983,8 +989,8 @@ sub ra { my $ra = Git::SVN::Ra->new($self->{url}); if ($self->use_svm_props && !$self->{svm}) { if ($self->no_metadata) { - die "Can't have both --no-metadata and ", - "--use-svm-props options set!\n"; + die "Can't have both 'noMetadata' and ", + "'useSvmProps' options set!\n"; } $ra = $self->set_svm_vars($ra); $self->{-want_revprops} = 1; @@ -1566,7 +1572,7 @@ sub rebuild { # 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). -# These files are disposable unless --no-metadata is set +# These files are disposable unless noMetadata or useSvmProps is set sub rev_db_set { my ($self, $rev, $commit, $update_ref) = @_; @@ -1578,7 +1584,12 @@ sub rev_db_set { $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } $LOCKFILES{$db_lock} = 1; - if ($self->no_metadata) { + my $sync; + + # both of these options make our .rev_db file very, very important + # and we can't afford to lose it because rebuild() won't work + if ($self->use_svm_props || $self->no_metadata) { + $sync = 1; copy($db, $db_lock) or die "rev_db_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; @@ -1599,6 +1610,10 @@ sub rev_db_set { } seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; + if ($sync) { + $fh->flush or die "Couldn't flush $db_lock: $!\n"; + $fh->sync or die "Couldn't sync $db_lock: $!\n"; + } close $fh or croak $!; if ($update_ref) { command_noisy('update-ref', '-m', "r$rev", -- cgit v1.2.3 From 26a62d57a27407132d48e91b3c8f455a5fb22e4b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 13:25:25 -0800 Subject: git-svn: use separate, per-repository .rev_db files We need a separate .rev_db file for each repository we're tracking. This allows us to track the same logical path off multiple mirrors. We preserve a symlink to the old .rev_db (no-UUID) if we're (auto-)migrating from an old version to preserve backwards compatibility. Also, get rid of the uuid() wrapper since we cache UUID in our private config, and the SVN::Ra::get_uuid() function memoizes the return value per-connection. Signed-off-by: Eric Wong --- git-svn.perl | 173 ++++++++++++++++++++++++++++++++------------- t/t9107-git-svn-migrate.sh | 11 +++ 2 files changed, 134 insertions(+), 50 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index fdecffbbc7..beebe3d954 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -736,7 +736,7 @@ sub fetch_all { my $url = $remote->{url}; my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); - my $uuid = $ra->uuid; + my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; my $base = $head; @@ -937,7 +937,8 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; - if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) { + if ((-z $self->db_path || ! -e $self->db_path) && + ::verify_ref($self->refname.'^0')) { $self->rebuild; } $self; @@ -945,18 +946,36 @@ sub new { sub refname { "refs/remotes/$_[0]->{ref_id}" } -sub set_svm_vars { - my ($self, $ra) = @_; - my $section = "svn-remote.$self->{repo_id}"; +sub svm_uuid { + my ($self) = @_; + return $self->{svm}->{uuid} if $self->svm; + $self->ra; + unless ($self->{svm}) { + die "SVM UUID not cached, and reading remotely failed\n"; + } + $self->{svm}->{uuid}; +} +sub svm { + my ($self) = @_; + return $self->{svm} if $self->{svm}; + my $svm; # see if we have it in our config, first: eval { - $self->{svm} = { + my $section = "svn-remote.$self->{repo_id}"; + $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), } }; - return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); + $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + $self->{svm}; +} + +sub _set_svm_vars { + my ($self, $ra) = @_; + + return $ra if ($self->svm); # nope, make sure we're connected to the repository root: if ($ra->{repos_root} ne $self->{url}) { @@ -965,6 +984,8 @@ sub set_svm_vars { my $r = $ra->get_latest_revnum; my ($props) = ($ra->get_dir('', $r))[2]; if (my $src = $props->{'svm:source'}) { + my $section = "svn-remote.$self->{repo_id}"; + # don't know what a '!' is there for, also the # username is of no interest $src =~ s{!$}{}; @@ -984,6 +1005,24 @@ sub set_svm_vars { $ra; } +# this allows us to memoize our SVN::Ra UUID locally and avoid a +# remote lookup (useful for 'git svn log'). +sub ra_uuid { + my ($self) = @_; + unless ($self->{ra_uuid}) { + my $key = "svn-remote.$self->{repo_id}.uuid"; + my $uuid = eval { tmp_config('--get', $key) }; + if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) { + $self->{ra_uuid} = $uuid; + } else { + die "ra_uuid called without URL\n" unless $self->{url}; + $self->{ra_uuid} = $self->ra->get_uuid; + tmp_config('--add', $key, $self->{ra_uuid}); + } + } + $self->{ra_uuid}; +} + sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); @@ -992,7 +1031,7 @@ sub ra { die "Can't have both 'noMetadata' and ", "'useSvmProps' options set!\n"; } - $ra = $self->set_svm_vars($ra); + $ra = $self->_set_svm_vars($ra); $self->{-want_revprops} = 1; } $ra; @@ -1048,10 +1087,14 @@ sub last_rev_commit { return ($rev, $c); } } + my $db_path = $self->db_path; + unless (-e $db_path) { + ($self->{last_rev}, $self->{last_commit}) = (undef, undef); + return (undef, undef); + } my $offset = -41; # from tail my $rl; - open my $fh, '<', $self->{db_path} or - croak "$self->{db_path} not readable: $!\n"; + open my $fh, '<', $db_path or croak "$db_path not readable: $!\n"; sysseek($fh, $offset, 2); # don't care for errors sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; @@ -1062,7 +1105,7 @@ sub last_rev_commit { chomp $rl; } if ($c && $c ne $rl) { - die "$self->{db_path} and ", $self->refname, + die "$db_path and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } my $rev = sysseek($fh, 0, 1) or croak $!; @@ -1187,7 +1230,7 @@ sub do_git_commit { } my $author = $log_entry->{author}; my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} - : ($author, "$author\@".$self->ra->uuid)); + : ($author, "$author\@".$self->ra->get_uuid)); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; @@ -1227,6 +1270,8 @@ sub do_git_commit { print "r$log_entry->{revision}"; if (defined $log_entry->{svm_revision}) { print " (\@$log_entry->{svm_revision})"; + $self->rev_db_set($log_entry->{svm_revision}, $commit, + 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; if (defined $_repack && (--$_repack_nr == 0)) { @@ -1492,7 +1537,7 @@ sub make_log_entry { $log_entry{svm_revision} = $r; } else { $log_entry{metadata} = $self->full_url . "\@$rev " . - $self->ra->uuid; + $self->ra->get_uuid; } \%log_entry; } @@ -1531,7 +1576,16 @@ sub set_tree { sub rebuild { my ($self) = @_; - print "Rebuilding $self->{db_path} ...\n"; + my $db_path = $self->db_path; + if (-f $self->{db_root}) { + rename $self->{db_root}, $db_path or die + "rename $self->{db_root} => $db_path failed: $!\n"; + my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#); + symlink $base, $self->{db_root} or die + "symlink $base => $self->{db_root} failed: $!\n"; + return; + } + print "Rebuilding $db_path ...\n"; my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); my $latest; my $full_url = $self->full_url; @@ -1558,7 +1612,7 @@ sub rebuild { print "r$rev = $c\n"; } command_close_pipe($rev_list, $ctx); - print "Done rebuilding $self->{db_path}\n"; + print "Done rebuilding $db_path\n"; } # rev_db: @@ -1574,42 +1628,59 @@ sub rebuild { # And yes, it's still pretty fast (faster than Tie::File). # These files are disposable unless noMetadata or useSvmProps is set +sub _rev_db_set { + my ($fh, $rev, $commit) = @_; + my $offset = $rev * 41; + # assume that append is the common case: + seek $fh, 0, 2 or croak $!; + my $pos = tell $fh; + if ($pos < $offset) { + for (1 .. (($offset - $pos) / 41)) { + print $fh (('0' x 40),"\n") or croak $!; + } + } + seek $fh, $offset, 0 or croak $!; + print $fh $commit,"\n" or croak $!; +} + +sub mkfile { + my ($path) = @_; + unless (-e $path) { + my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#); + mkpath([$dir]) unless -d $dir; + open my $fh, '>>', $path or die "Couldn't create $path: $!\n"; + close $fh or die "Couldn't close (create) $path: $!\n"; + } +} + sub rev_db_set { - my ($self, $rev, $commit, $update_ref) = @_; - length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock"); + my ($self, $rev, $commit, $update_ref, $uuid) = @_; + length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; + my $db = $self->db_path($uuid); + my $db_lock = "$db.lock"; my $sig; if ($update_ref) { $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } + mkfile($db); + $LOCKFILES{$db_lock} = 1; my $sync; - # both of these options make our .rev_db file very, very important # and we can't afford to lose it because rebuild() won't work if ($self->use_svm_props || $self->no_metadata) { $sync = 1; copy($db, $db_lock) or die "rev_db_set(@_): ", - "Failed to copy: ", + "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { rename $db, $db_lock or die "rev_db_set(@_): ", - "Failed to rename: ", + "Failed to rename: ", "$db => $db_lock ($!)\n"; } - open my $fh, '+<', $db_lock 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) { - for (1 .. (($offset - $pos) / 41)) { - print $fh (('0' x 40),"\n") or croak $!; - } - } - seek $fh, $offset, 0 or croak $!; - print $fh $commit,"\n" or croak $!; + open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n"; + _rev_db_set($fh, $rev, $commit); if ($sync) { $fh->flush or die "Couldn't flush $db_lock: $!\n"; $fh->sync or die "Couldn't sync $db_lock: $!\n"; @@ -1631,19 +1702,20 @@ sub rev_db_set { sub rev_db_max { my ($self) = @_; - my @stat = stat $self->{db_path} or - die "Couldn't stat $self->{db_path}: $!\n"; - ($stat[7] % 41) == 0 or - die "$self->{db_path} inconsistent size:$stat[7]\n"; + my $db_path = $self->db_path; + my @stat = stat $db_path or return 0; + ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; my $max = $stat[7] / 41; (($max > 0) ? $max - 1 : 0); } sub rev_db_get { - my ($self, $rev) = @_; + my ($self, $rev, $uuid) = @_; my $ret; my $offset = $rev * 41; - open my $fh, '<', $self->{db_path} or croak $!; + my $db_path = $self->db_path($uuid); + return undef unless -e $db_path; + open my $fh, '<', $db_path or croak $!; if (sysseek($fh, $offset, 0) == $offset) { my $read = sysread($fh, $ret, 40); $ret = undef if ($read != 40 || $ret eq ('0'x40)); @@ -1676,13 +1748,16 @@ sub _new { my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); mkpath([$dir]); - unless (-f "$dir/.rev_db") { - open my $fh, '>>', "$dir/.rev_db" or croak $!; - close $fh or croak $!; - } - bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + bless { + ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", - db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; + db_root => "$dir/.rev_db", repo_id => $repo_id }, $class; +} + +sub db_path { + my ($self, $uuid) = @_; + $uuid ||= $self->ra_uuid; + "$self->{db_root}.$uuid"; } sub uri_encode { @@ -2519,11 +2594,6 @@ sub get_commit_editor { $self->SUPER::get_commit_editor($log, $cb, @lock, $pool); } -sub uuid { - my ($self) = @_; - $self->{uuid} ||= $self->get_uuid; -} - sub gs_do_update { my ($self, $rev_a, $rev_b, $gs, $editor) = @_; my $new = ($rev_a == $rev_b); @@ -3140,6 +3210,9 @@ package Git::SVN::Migration; # - info/url may remain for backwards compatibility # - this is what we migrate up to this layout automatically, # - this will be used by git svn init on single branches +# v3.1 layout (auto migrated): +# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink +# for backwards compatibility # # v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id # - this is only created for newly multi-init-ed diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 8376429bcb..9f107ad7bf 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -96,5 +96,16 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " grep '^:refs/remotes/git-svn' fetch.out " +test_expect_success ".rev_db auto-converted to .rev_db.UUID" " + git-svn fetch -i trunk && + expect=$GIT_DIR/svn/trunk/.rev_db.* && + test -n \"\$expect\" && + mv \$expect $GIT_DIR/svn/trunk/.rev_db && + git-svn fetch -i trunk && + test -L $GIT_DIR/svn/trunk/.rev_db && + test -f \$expect && + cmp \$expect $GIT_DIR/svn/trunk/.rev_db + " + test_done -- cgit v1.2.3 From c3560e535c67b4084852da00507ff4b7fdf98ffc Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 16:03:32 -0800 Subject: git-svn: write the highest maxRex out for branches and tags Even if nothing touched paths we care about in a fetch; increment the maxRev like we do with rev_db since we don't like having to run get_log on revisions we've seen before. Signed-off-by: Eric Wong --- git-svn.perl | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index beebe3d954..b7e46e5b0b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2765,6 +2765,10 @@ sub gs_fetch_loop_common { next if defined $gs->rev_db_get($max); $gs->rev_db_set($max, 0 x40); } + foreach my $g (@$globs) { + my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev"; + Git::SVN::tmp_config($k, $max); + } last if $max >= $head; $min = $max + 1; $max += $inc; -- cgit v1.2.3 From db03cd24a155a727349a47ce0e5ba3f4c4032cb8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 00:38:02 -0800 Subject: git-svn: handle multi-init without --trunk, UseSvmProps fixes multi-init did not write a svn-remote..url config entry without a --trunk argument. Also, The svm:mirror property is used by SVN::Mirror to track the path of the repository that we are mirroring. We need to append that to the source (which is (presumably) just the URL of the repository root). Lastly, we now look harder for svm:(source|mirror|uuid) properties in sub and parent directories. Since our relative path could be tweaked. Signed-off-by: Eric Wong --- git-svn.perl | 116 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 33 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b7e46e5b0b..d55691216c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,8 +367,7 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - if ($remotes->{$repo_id}->{url} && - $remotes->{$repo_id}->{fetch}) { + if ($remotes->{$repo_id}->{url}) { Git::SVN::fetch_all($repo_id, $remotes); } } @@ -483,6 +482,17 @@ sub complete_url_ls_init { $gs = Git::SVN->init($url, $path, undef, $ref, 1); } if ($gs) { + my $k = "svn-remote.$gs->{repo_id}.url"; + my $orig_url = eval { + command_oneline(qw/config --get/, $k) + }; + if ($orig_url && ($orig_url ne $gs->{url})) { + die "$k already set: $orig_url\n", + "wanted to set to: $gs->{url}\n"; + } + unless ($orig_url) { + command_oneline('config', $k, $gs->{url}); + } $remote_id = $gs->{repo_id}; last; } @@ -751,13 +761,15 @@ sub fetch_all { } } - foreach my $p (sort keys %$fetch) { - my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->rev_db_max; - if (defined $lr) { - $base = $lr if ($lr < $base); + if ($fetch) { + foreach my $p (sort keys %$fetch) { + my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); + my $lr = $gs->rev_db_max; + if (defined $lr) { + $base = $lr if ($lr < $base); + } + push @gs, $gs; } - push @gs, $gs; } $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } @@ -974,35 +986,69 @@ sub svm { sub _set_svm_vars { my ($self, $ra) = @_; + return $ra if $self->svm; + + my @err = ( "useSvmProps set, but failed to read SVM properties\n", + "(svm:source, svm:mirror, svm:mirror) ", + "from the following URLs:\n" ); + sub read_svm_props { + my ($self, $props) = @_; + my $src = $props->{'svm:source'}; + my $mirror = $props->{'svm:mirror'}; + my $uuid = $props->{'svm:uuid'}; + return undef if (!$src || !$mirror || !$uuid); - return $ra if ($self->svm); - - # nope, make sure we're connected to the repository root: - if ($ra->{repos_root} ne $self->{url}) { - $ra = Git::SVN::Ra->new($ra->{repos_root}); - } - my $r = $ra->get_latest_revnum; - my ($props) = ($ra->get_dir('', $r))[2]; - if (my $src = $props->{'svm:source'}) { - my $section = "svn-remote.$self->{repo_id}"; + chomp($src, $mirror, $uuid); + $uuid =~ m{^[0-9a-f\-]{30,}$} + or die "doesn't look right - svm:uuid is '$uuid'\n"; # don't know what a '!' is there for, also the # username is of no interest - $src =~ s{!$}{}; + $src =~ s{/?!$}{$mirror}; + $src =~ s{/+$}{}; # no trailing slashes please $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; - tmp_config('--add', "$section.svm-source", $src); - my $uuid = $props->{'svm:uuid'}; - $uuid =~ m{^[0-9a-f\-]{30,}$} - or die "doesn't look right - svm:uuid is '$uuid'\n"; + my $section = "svn-remote.$self->{repo_id}"; + tmp_config('--add', "$section.svm-source", $src); tmp_config('--add', "$section.svm-uuid", $uuid); - $self->{svm} = { source => $src , uuid => $uuid }; + return 1; } - if ($ra->{repos_root} ne $self->{url}) { - $ra = Git::SVN::Ra->new($self->{url}); + + my $r = $ra->get_latest_revnum; + my $path = $self->{path}; + my @tried_a = ($path); + while (length $path) { + if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) { + return $ra; + } + $path =~ s#/?[^/]+$## && push @tried_a, $path; } - $ra; + if ($self->read_svm_props(($ra->get_dir('', $r))[2])) { + return $ra; + } + + if ($ra->{repos_root} eq $self->{url}) { + die @err, map { " $self->{url}/$_\n" } @tried_a, "\n"; + } + + # nope, make sure we're connected to the repository root: + my $ok; + my @tried_b; + $path = $ra->{svn_path}; + $path =~ s#/?[^/]+$##; # we already tried this one above + $ra = Git::SVN::Ra->new($ra->{repos_root}); + while (length $path) { + $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]); + last if $ok; + $path =~ s#/?[^/]+$## && push @tried_b, $path; + } + $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok; + if (!$ok) { + die @err, map { " $self->{url}/$_\n" } @tried_a, "\n", + map { " $ra->{url}/$_\n" } @tried_b, "\n" + } + Git::SVN::Ra->new($self->{url}); } # this allows us to memoize our SVN::Ra UUID locally and avoid a @@ -1228,11 +1274,9 @@ sub do_git_commit { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my $author = $log_entry->{author}; - my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} - : ($author, "$author\@".$self->ra->get_uuid)); - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name}; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = + $log_entry->{email}; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; my $tree = $log_entry->{tree}; @@ -1522,8 +1566,10 @@ sub make_log_entry { close $un or croak $!; $log_entry{date} = parse_svn_date($log_entry{date}); - $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; + my $author = $log_entry{author} = check_author($log_entry{author}); + my ($name, $email) = defined $::users{$author} ? @{$::users{$author}} + : ($author, undef); if (defined $headrev && $self->use_svm_props) { my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { @@ -1535,10 +1581,14 @@ sub make_log_entry { $full_url .= "/$self->{path}" if length $self->{path}; $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; + $email ||= "$author\@$uuid" } else { $log_entry{metadata} = $self->full_url . "\@$rev " . $self->ra->get_uuid; + $email ||= "$author\@" . $self->ra->get_uuid; } + $log_entry{name} = $name; + $log_entry{email} = $email; \%log_entry; } -- cgit v1.2.3 From a8ae26235ced15d8ce129a8ff72c558b8a567813 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 14:22:11 -0800 Subject: git-svn: make dcommit usable for glob users * dcommit no longer requires the correct -i/GIT_SVN_ID option passed to it. Since you're committing from HEAD (or another commit that is a parent of HEAD), you'll be able to find a commit with metadata information containing the SVN URL that your HEAD was descended from anyways. * I don't think dcommit ever worked for people using the noMetadata option; so I don't think relying on metadata is an issue. * useSvmProps users shouldn't commit to SVN::Mirror created repositories anyways, right? * Users of globbing should automatically be able to commit to paths that are not explicitly set in .git/config Signed-off-by: Eric Wong --- git-svn.perl | 73 ++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 22 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index d55691216c..09c0aba8ea 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -276,11 +276,27 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; - my $gs = Git::SVN->new; $head ||= 'HEAD'; - my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head"); + my ($url, $rev, $uuid); + my ($fh, $ctx) = command_output_pipe(qw/rev-list --no-merges/, $head); + my @refs; + my $c; + while (<$fh>) { + $c = $_; + chomp $c; + ($url, $rev, $uuid) = cmt_metadata($c); + last if (defined $url && defined $rev && defined $uuid); + unshift @refs, $c; + } + close $fh; # most likely breaking the pipe + unless (defined $url && defined $rev && defined $uuid) { + die "Unable to determine upstream SVN information from ", + "$head history:\n $ctx\n"; + } + my $gs = Git::SVN->find_by_url($url) or + die "Can't determine fetch information for $url\n"; my $last_rev; - foreach my $d (reverse @refs) { + foreach my $d (@refs) { if (!verify_ref("$d~1")) { fatal "Commit $d\n", "has no parent commit, and therefore ", @@ -300,13 +316,13 @@ sub cmd_dcommit { } else { my %ed_opts = ( r => $last_rev, log => get_commit_entry($d)->{log}, - ra => $gs->ra, + ra => Git::SVN::Ra->new($url), tree_a => "$d~1", tree_b => $d, editor_cb => sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }, - svn_path => $gs->{path} ); + svn_path => ''); if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; } @@ -904,6 +920,35 @@ sub init_remote_config { $self->{url} = $url; } +sub find_by_url { # repos_root and, path are optional + my ($class, $full_url, $repos_root, $path) = @_; + my $remotes = read_all_remotes(); + if (defined $full_url && defined $repos_root && !defined $path) { + $path = $full_url; + $path =~ s#^\Q$repos_root\E(?:/|$)##; + } + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if defined $repos_root && $repos_root ne $u; + + my $fetch = $remotes->{$repo_id}->{fetch} || {}; + foreach (qw/branches tags/) { + resolve_local_globs($u, $fetch, + $remotes->{$repo_id}->{$_}); + } + my $p = $path; + unless (defined $p) { + $p = $full_url; + $p =~ s#^\Q$u\E(?:/|$)## or next; + } + foreach my $f (keys %$fetch) { + next if $f ne $p; + return Git::SVN->new($fetch->{$f}, $repo_id, $f); + } + } + undef; +} + sub init { my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_; my $self = _new($class, $repo_id, $ref_id, $path); @@ -1387,23 +1432,7 @@ sub find_parent_branch { print STDERR "Found possible branch point: ", "$new_url => ", $self->full_url, ", $r\n"; $branch_from =~ s#^/##; - my $remotes = read_all_remotes(); - my $gs; - foreach my $repo_id (keys %$remotes) { - my $u = $remotes->{$repo_id}->{url} or next; - next if $url ne $u; - my $fetch = $remotes->{$repo_id}->{fetch}; - foreach (qw/branches tags/) { - resolve_local_globs($url, $fetch, - $remotes->{$repo_id}->{$_}); - } - foreach my $f (keys %$fetch) { - next if $f ne $branch_from; - $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); - last; - } - last if $gs; - } + my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); unless ($gs) { my $ref_id = $self->{ref_id}; $ref_id =~ s/\@\d+$//; -- cgit v1.2.3 From ce207c7ad1604c6afd5014051641e40f346a59c6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 15:56:08 -0800 Subject: git-svn: include merges when calling rev-list for decommit Merge commits can be created when following certain parents, (most notably 'R' cases) and we definitely don't want to exclude them. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 09c0aba8ea..66653f9eb4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -278,7 +278,7 @@ sub cmd_dcommit { my $head = shift; $head ||= 'HEAD'; my ($url, $rev, $uuid); - my ($fh, $ctx) = command_output_pipe(qw/rev-list --no-merges/, $head); + my ($fh, $ctx) = command_output_pipe('rev-list', $head); my @refs; my $c; while (<$fh>) { -- cgit v1.2.3 From 3bc718ba66f8b101b4017e778138660d66829312 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:09:40 -0800 Subject: git-svn: usability fixes for the 'git svn log' command Similar in spirit to the recent dcommit change, we now look at 'HEAD' by default to look for a GIT_SVN_ID so the user won't have to pass -i argument. We are also more tolerant of of people passing bare remote names as a result (just $GIT_SVN_ID without the -i) Signed-off-by: Eric Wong --- git-svn.perl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 66653f9eb4..fb2c864a39 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3033,8 +3033,25 @@ sub log_use_color { } sub git_svn_log_cmd { - my ($r_min, $r_max) = @_; - my $gs = Git::SVN->_new; + my ($r_min, $r_max, @args) = @_; + my $head = 'HEAD'; + foreach my $x (@args) { + last if $x eq '--'; + next unless ::verify_ref("$x^0"); + $head = $x; + last; + } + + my $url; + my ($fh, $ctx) = command_output_pipe('rev-list', $head); + while (<$fh>) { + chomp; + $url = (::cmt_metadata($_))[0]; + last if defined $url; + } + close $fh; # break the pipe + + my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new; my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, $gs->refname); push @cmd, '-r' unless $non_recursive; @@ -3227,7 +3244,7 @@ sub cmd_show_log { } config_pager(); - @args = (git_svn_log_cmd($r_min, $r_max), @args); + @args = (git_svn_log_cmd($r_min, $r_max, @args), @args); my $log = command_output_pipe(@args); run_pager(); my (@k, $c, $d); -- cgit v1.2.3 From e98671e5c2067a39759c8f08b79c260a9f0b2771 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 02:21:19 -0800 Subject: git-svn: hopefully make 'fetch' more user-friendly multi-fetch is deprecated, "fetch -a" is easier to type By default, fetch will fetch everything from its default [svn-remote]; if fetch [--all|-a] is specified, then it will fetch from all svn remotes. Refspecs on the command-line (like git-fetch) are not supported. Also, enable -r/--revision arguments for fetch so users can shoot themselves in the foot^W^W^W^W^W skip some history and do the equivalent of a shallow clone/fetch they're not interested in. Signed-off-by: Eric Wong --- git-svn.perl | 58 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 25 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index fb2c864a39..3eed62fc0b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -53,7 +53,7 @@ $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, - $_version, + $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, $_prefix); $Git::SVN::_follow_parent = 1; @@ -84,7 +84,9 @@ my %cmt_opts = ( 'edit|e' => \$_edit, my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", - { 'revision|r=s' => \$_revision, %fc_opts } ], + { 'revision|r=s' => \$_revision, + 'all|a' => \$_fetch_all, + %fc_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], @@ -106,8 +108,8 @@ my %cmd = ( 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&cmd_multi_fetch, - 'Fetch multiple trees (like git-svnimport)', - \%fc_opts ], + "Deprecated alias for $0 fetch --all", + { 'revision|r=s' => \$_revision, %fc_opts } ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, 'Migrate configuration/metadata/layout from @@ -226,16 +228,19 @@ sub cmd_init { } sub cmd_fetch { - if (@_) { - die "Additional fetch arguments are no longer supported.\n", - "Use --follow-parent if you have moved/copied directories - instead.\n"; + if (grep /^\d+=./, @_) { + die "'=' fetch arguments are ", + "no longer supported.\n"; } - my $gs = Git::SVN->new; - $gs->fetch(parse_revision_argument()); - if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { - command_noisy(qw(update-ref refs/heads/master), - $gs->{last_commit}); + my ($remote) = @_; + if (@_ > 1) { + die "Usage: $0 fetch [--all|-a] [svn-remote]\n"; + } + $remote ||= $Git::SVN::default_repo_id; + if ($_fetch_all) { + cmd_multi_fetch(); + } else { + Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes()); } } @@ -440,18 +445,6 @@ sub cmd_commit_diff { ########################### utility functions ######################### -sub parse_revision_argument { - if (!defined $_revision || $_revision eq 'BASE:HEAD') { - return (undef, undef); - } - return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); - return ($_revision, $_revision) if ($_revision =~ /^\d+$/); - return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/); - return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/); - die "revision argument: $_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; -} - sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -755,6 +748,19 @@ sub resolve_local_globs { } } +sub parse_revision_argument { + my ($base, $head) = @_; + if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { + return ($base, $head); + } + return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); + return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); + return ($head, $head) if ($::_revision eq 'HEAD'); + return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/); + return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); + die "revision argument: $::_revision not understood by git-svn\n"; +} + sub fetch_all { my ($repo_id, $remotes) = @_; my $remote = $remotes->{$repo_id}; @@ -787,6 +793,8 @@ sub fetch_all { push @gs, $gs; } } + + ($base, $head) = parse_revision_argument($base, $head); $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } -- cgit v1.2.3 From dadc6d2a0904e55ac5a5a810dffac4d44fff0b66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 12:27:41 -0800 Subject: git-svn: allow 'init' to act as multi-init multi-init is now just an alias that requires -T/-t/-b; all options that 'init' can now accept. This will hopefully simplify usage and reduce typing. Also, allow the --shared option in 'init' to take an optional argument now that 'git-init --shared' supports an optional argument. Signed-off-by: Eric Wong --- git-svn.perl | 52 +++++++++++++++++++++++--------------- t/t9107-git-svn-migrate.sh | 4 +-- t/t9109-git-svn-svk-mirrorpaths.sh | 4 +-- 3 files changed, 36 insertions(+), 24 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 3eed62fc0b..b2931cd5aa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -71,10 +71,10 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, %remote_opts ); my ($_trunk, $_tags, $_branches); -my %multi_opts = ( 'trunk|T=s' => \$_trunk, - 'tags|t=s' => \$_tags, - 'branches|b=s' => \$_branches ); -my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); +my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, + 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, + 'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix, + %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder, @@ -90,6 +90,10 @@ my %cmd = ( init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], + 'multi-init' => [ \&cmd_multi_init, + "Deprecated alias for ". + "'$0 init -T -b -t'", + \%init_opts ], dcommit => [ \&cmd_dcommit, 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, @@ -101,12 +105,6 @@ my %cmd = ( { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - 'multi-init' => [ \&cmd_multi_init, - 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, %remote_opts, - 'revision|r=i' => \$_revision, - 'prefix=s' => \$_prefix, - } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -182,6 +180,7 @@ Usage: $0 [options] [arguments]\n next if $cmd && $cmd ne $_; print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { + next if /^multi-/; # don't show deprecated commands # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 21, join(', ', map { length $_ > 1 ? @@ -207,21 +206,31 @@ sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); push @init_db, "--template=$_template" if defined $_template; - push @init_db, "--shared" if defined $_shared; + if (defined $_shared) { + if ($_shared =~ /[a-z]/) { + push @init_db, "--shared=$_shared"; + } else { + push @init_db, "--shared"; + } + } command_noisy(@init_db); } } +sub init_subdir { + my $repo_path = shift or return; + mkpath([$repo_path]) unless -d $repo_path; + chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n"; + $ENV{GIT_DIR} = $repo_path . "/.git"; +} + sub cmd_init { - my $url = shift or die "SVN repository location required " . - "as a command-line argument\n"; - if (my $repo_path = shift) { - unless (-d $repo_path) { - mkpath([$repo_path]); - } - chdir $repo_path or croak $!; - $ENV{GIT_DIR} = $repo_path . "/.git"; + if (defined $_trunk || defined $_branches || defined $_tags) { + return cmd_multi_init(@_); } + my $url = shift or die "SVN repository location required ", + "as a command-line argument\n"; + init_subdir(@_); do_git_init_db(); Git::SVN->init($url); @@ -367,7 +376,10 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; - $url =~ s#/+$## if defined $url; + if (defined $url) { + $url =~ s#/+$##; + init_subdir(@_); + } if (defined $_trunk) { my $trunk_ref = $_prefix . 'trunk'; # try both old-style and new-style lookups: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index d26c355f05..a20038b670 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -40,7 +40,7 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " " test_expect_success 'initialize a multi-repository repo' " - git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-svn init $svnrepo -T trunk -t tags -b branches && git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && test -n \"\`git-config --get svn-remote.svn.branches \ @@ -72,7 +72,7 @@ test_expect_success 'multi-fetch works on partial urls + paths' " refs/remotes/\$j\`\" ||exit 1; done; done " -test_expect_success 'migrate --minimize on old multi-inited layout' " +test_expect_success 'migrate --minimize on old inited layout' " git config --unset-all svn-remote.svn.fetch && git config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh index 7e42151851..1e1b97b5fc 100755 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -73,8 +73,8 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'multi-init an SVK mirror path' " - git-svn multi-init -T trunk -t tags -b branches $svnrepo/mirror/foobar +test_expect_success 'init an SVK mirror path' " + git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar " test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" -- cgit v1.2.3 From 28710f74ea1f1d8a46c867ddd471dae3d7c3a664 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 13:32:21 -0800 Subject: git-svn: brown paper bag fixes * avoid skipping modification-only changes in fetch * correctly fetch when we only have branches and tags to glob from (no fetch keys defined) Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- t/t9108-git-svn-glob.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b2931cd5aa..24ca3087d6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -782,7 +782,7 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; - my $base = $head; + my $base = defined $fetch ? $head : 0; # read the max revs for wildcard expansion (branches/*, tags/*) foreach my $t (qw/branches tags/) { @@ -2901,7 +2901,8 @@ sub match_globs { } } foreach (keys %$paths) { - if (/$g->{path}->{left_regex}/) { + if (/$g->{path}->{left_regex}/ && + !/$g->{path}->{regex}/) { next if $paths->{$_}->{action} !~ /^[AR]$/; get_dir_check($self, $exists, $g, $r); } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index be21fc13b7..db4344cc84 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -55,4 +55,32 @@ test_expect_success 'test refspec globbing' " \"\`git rev-parse refs/remotes/trunk\`\" " +echo try to try > expect.two +echo nothing to see here >> expect.two +cat expect.end >> expect.two + +test_expect_success 'test left-hand-side only globbing' " + git config --add svn-remote.two.url $svnrepo && + git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk && + git config --add svn-remote.two.branches \ + 'branches/*:refs/remotes/two/branches/*' && + git config --add svn-remote.two.tags \ + 'tags/*:refs/remotes/two/tags/*' && + cd tmp && + echo 'try try' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && + svn commit -m 'try to try' + cd .. && + git-svn fetch two && + test \`git rev-list refs/remotes/two/tags/end | wc -l\` -eq 6 && + test \`git rev-list refs/remotes/two/branches/start | wc -l\` -eq 3 && + test \`git rev-parse refs/remotes/two/branches/start~2\` = \ + \`git rev-parse refs/remotes/two/trunk\` && + test \`git rev-parse refs/remotes/two/tags/end~3\` = \ + \`git rev-parse refs/remotes/two/branches/start\` && + git log --pretty=oneline refs/remotes/two/tags/end | \ + sed -e 's/^.\{41\}//' > output.two && + cmp expect.two output.two + " + test_done -- cgit v1.2.3 From b4d57e5ea3349a6bf63c3626f6fb36bac11be3c0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 15:10:44 -0800 Subject: git-svn: simplify the (multi-)init methods of fetching Also, some changes to avoid creating dead dirs under .git/svn/. We now create all directories as late as possible. Signed-off-by: Eric Wong --- git-svn.perl | 86 +++++++++++++++++++++++++----------------------------------- 1 file changed, 35 insertions(+), 51 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 24ca3087d6..f4573ed102 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -487,48 +487,24 @@ sub complete_url_ls_init { "and a separate URL is not specified\n"); } } - my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); my $url = $ra->{url}; - my $remote_id; - my $remote_path; - foreach my $d (sort keys %$dirent) { - next if ($dirent->{$d}->kind != $SVN::Node::dir); - my $path = "$repo_path/$d"; - my $ref = "$pfx$d"; - my $gs = eval { Git::SVN->new($ref) }; - # don't try to init already existing refs - unless ($gs) { - print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref, 1); - } - if ($gs) { - my $k = "svn-remote.$gs->{repo_id}.url"; - my $orig_url = eval { - command_oneline(qw/config --get/, $k) - }; - if ($orig_url && ($orig_url ne $gs->{url})) { - die "$k already set: $orig_url\n", - "wanted to set to: $gs->{url}\n"; - } - unless ($orig_url) { - command_oneline('config', $k, $gs->{url}); - } - $remote_id = $gs->{repo_id}; - last; - } + my $gs = Git::SVN->init($url, undef, undef, undef, 1); + my $k = "svn-remote.$gs->{repo_id}.url"; + my $orig_url = eval { command_oneline(qw/config --get/, $k) }; + if ($orig_url && ($orig_url ne $gs->{url})) { + die "$k already set: $orig_url\n", + "wanted to set to: $gs->{url}\n"; } - if (defined $remote_id) { - $remote_path = "$ra->{svn_path}/$repo_path/*"; - $remote_path =~ s#/+#/#g; - $remote_path =~ s#^/##g; - my ($n) = ($switch =~ /^--(\w+)/); - if (length $pfx && $pfx !~ m#/$#) { - die "--prefix='$pfx' must have a trailing slash '/'\n"; - } - command_noisy('config', "svn-remote.$remote_id.$n", - "$remote_path:refs/remotes/$pfx*"); + command_oneline('config', $k, $gs->{url}) unless $orig_url; + my $remote_path = "$ra->{svn_path}/$repo_path/*"; + $remote_path =~ s#/+#/#g; + $remote_path =~ s#^/##g; + my ($n) = ($switch =~ /^--(\w+)/); + if (length $pfx && $pfx !~ m#/$#) { + die "--prefix='$pfx' must have a trailing slash '/'\n"; } + command_noisy('config', "svn-remote.$gs->{repo_id}.$n", + "$remote_path:refs/remotes/$pfx*"); } sub verify_ref { @@ -1236,19 +1212,23 @@ sub get_fetch_range { sub tmp_config { my (@args) = @_; my $config = "$ENV{GIT_DIR}/svn/config"; - unless (-f $config) { - open my $fh, '>', $config or - die "Can't open $config: $!\n"; - print $fh "; This file is used internally by git-svn\n" or - die "Couldn't write to $config: $!\n"; - print $fh "; You should not have to edit it\n" or - die "Couldn't write to $config: $!\n"; - close $fh or die "Couldn't close $config: $!\n"; - } my $old_config = $ENV{GIT_CONFIG}; $ENV{GIT_CONFIG} = $config; $@ = undef; - my @ret = eval { command('config', @args) }; + my @ret = eval { + unless (-f $config) { + mkfile($config); + open my $fh, '>', $config or + die "Can't open $config: $!\n"; + print $fh "; This file is used internally by ", + "git-svn\n" or die + "Couldn't write to $config: $!\n"; + print $fh "; You should not have to edit it\n" or + die "Couldn't write to $config: $!\n"; + close $fh or die "Couldn't close $config: $!\n"; + } + command('config', @args); + }; my $err = $@; if (defined $old_config) { $ENV{GIT_CONFIG} = $old_config; @@ -1264,7 +1244,11 @@ sub tmp_index_do { my $old_index = $ENV{GIT_INDEX_FILE}; $ENV{GIT_INDEX_FILE} = $self->{index}; $@ = undef; - my @ret = eval { &$sub }; + my @ret = eval { + my ($dir, $base) = ($self->{index} =~ m#^(.*?)/?([^/]+)$#); + mkpath([$dir]) unless -d $dir; + &$sub; + }; my $err = $@; if (defined $old_index) { $ENV{GIT_INDEX_FILE} = $old_index; @@ -1846,7 +1830,7 @@ sub _new { $_[1] = $repo_id = sanitize_remote_name($repo_id); my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); - mkpath([$dir]); + mkpath(["$ENV{GIT_DIR}/svn"]); bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", -- cgit v1.2.3 From 6af1db447b10c03db4c04a55000efaa9aad38caa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:04:10 -0800 Subject: git-svn: allow --log-window-size to be specified, default to 100 The newer default value should should lower memory usage for large fetches and also help with fetching from less reliable servers. Previously the value was 1000 and memory usage got a bit high on some repositories and fetching became less reliable in some cases. Signed-off-by: Eric Wong --- git-svn.perl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index f4573ed102..8a80f81add 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -12,6 +12,7 @@ $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; +$Git::SVN::Ra::_log_window_size = 100; $Git::SVN::Log::TZ = $ENV{TZ}; $ENV{TZ} = 'UTC'; @@ -65,6 +66,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'repack:i' => \$Git::SVN::_repack, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, + 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -2583,7 +2585,7 @@ sub apply_diff { } package Git::SVN::Ra; -use vars qw/@ISA $config_dir/; +use vars qw/@ISA $config_dir $_log_window_size/; use strict; use warnings; my ($can_do_switch); @@ -2747,7 +2749,7 @@ sub gs_do_switch { sub gs_fetch_loop_common { my ($self, $base, $head, $gsv, $globs) = @_; return if ($base > $head); - my $inc = 1000; + my $inc = $_log_window_size; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my %common; my $common_max = scalar @$gsv; @@ -2954,6 +2956,9 @@ sub skip_unknown_revs { # 175007 - http(s):// (this repo required authorization, too...) # More codes may be discovered later... if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + warn "W: Ignoring error from SVN, path probably ", + "does not exist: ($errno): ", + $err->expanded_message,"\n"; return; } die "Error from SVN, ($errno): ", $err->expanded_message,"\n"; -- cgit v1.2.3 From e8d120bd5a7e09b24c6fa2245cf429e3411028ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:29:52 -0800 Subject: git-svn: remember to check for clean indices on globbed refs, too Also, warn about dirty indices and avoid an unncessary write-tree call if the index is clean. Signed-off-by: Eric Wong --- git-svn.perl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 8a80f81add..ace31021e7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1269,10 +1269,11 @@ sub assert_index_clean { my $x = command_oneline('write-tree'); my ($y) = (command(qw/cat-file commit/, $treeish) =~ /^tree ($::sha1)/mo); - if ($y ne $x) { - unlink $self->{index} or croak $!; - command_noisy('read-tree', $treeish); - } + return if $y eq $x; + + warn "Index mismatch: $y != $x\nrereading $treeish\n"; + unlink $self->{index} or die "unlink $self->{index}: $!\n"; + command_noisy('read-tree', $treeish); $x = command_oneline('write-tree'); if ($y ne $x) { ::fatal "trees ($treeish) $y != $x\n", @@ -2755,9 +2756,6 @@ sub gs_fetch_loop_common { my $common_max = scalar @$gsv; foreach my $gs (@$gsv) { - if (my $last_commit = $gs->last_commit) { - $gs->assert_index_clean($last_commit); - } my @tmp = split m#/#, $gs->{path}; my $p = ''; foreach (@tmp) { @@ -2833,6 +2831,9 @@ sub gs_fetch_loop_common { } next unless $gs->match_paths($paths, $r); $gs->{logged_rev_props} = $logged; + if (my $last_commit = $gs->last_commit) { + $gs->assert_index_clean($last_commit); + } my $log_entry = $gs->do_fetch($paths, $r); if ($log_entry) { $gs->do_git_commit($log_entry); -- cgit v1.2.3 From 7447b4bc837d2f73fb4cd34f2a44a0cb120c5c39 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:38:46 -0800 Subject: git-svn: error checking for invalid [svn-remote "..."] sections We don't end up trying to pass an undef URL over to SVN::Ra->new because it'll segfault. Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index ace31021e7..201418e09c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -753,9 +753,10 @@ sub parse_revision_argument { sub fetch_all { my ($repo_id, $remotes) = @_; - my $remote = $remotes->{$repo_id}; + my $remote = $remotes->{$repo_id} or + die "[svn-remote \"$repo_id\"] unknown\n"; my $fetch = $remote->{fetch}; - my $url = $remote->{url}; + my $url = $remote->{url} or die "svn-remote.$repo_id.url not defined\n"; my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; -- cgit v1.2.3 From 60d9c97adf96533e4f02d3fc2fecec998104e8ea Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:47:16 -0800 Subject: git-svn: allow dcommit for those who only fetch from SVM with useSvmProps This allows users to use SVM (SVN::Mirror) to mirror a remote repository to use dcommit to commit to the repository that SVM was mirroring. When dcommit is used in this manner, the automatic fetch + rebase/reset does not happen; in which case the user will have to manually invoke svm/svk, run 'git svn fetch', and finally 'git rebase'. Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 201418e09c..bfe5d6b97e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -309,8 +309,7 @@ sub cmd_dcommit { die "Unable to determine upstream SVN information from ", "$head history:\n $ctx\n"; } - my $gs = Git::SVN->find_by_url($url) or - die "Can't determine fetch information for $url\n"; + my $gs = Git::SVN->find_by_url($url); my $last_rev; foreach my $d (@refs) { if (!verify_ref("$d~1")) { @@ -345,6 +344,13 @@ sub cmd_dcommit { } } return if $_dry_run; + unless ($gs) { + warn "Could not determine fetch information for $url\n", + "Will not attempt to fetch and rebase commits.\n", + "This probably means you have useSvmProps and should\n", + "now resync your SVN::Mirror repository.\n"; + return; + } $gs->fetch; # we always want to rebase against the current HEAD, not any # head that was passed to us -- cgit v1.2.3 From a836a0e1729d1758b4085cd07fc79cb9acb64908 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 19:34:56 -0800 Subject: git-svn: documentation updates for new functionality Force the showing of the --minimize flag as an option in the 'migrate' help. Also, fix the usage function to correctly filter out the deprecated aliases. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 202 +++++++++++++++++++--------------------------- git-svn.perl | 5 +- 2 files changed, 84 insertions(+), 123 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index d45283a53f..ba3f7ce6f1 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -13,14 +13,13 @@ DESCRIPTION ----------- git-svn is a simple conduit for changesets between Subversion and git. It is not to be confused with gitlink:git-svnimport[1], which is -read-only and geared towards tracking multiple branches. +read-only. git-svn was originally designed for an individual developer who wants a bidirectional flow of changesets between a single branch in Subversion and an arbitrary number of branches in git. Since its inception, git-svn has gained the ability to track multiple branches in a manner -similar to git-svnimport; but it cannot (yet) automatically detect new -branches and tags like git-svnimport does. +similar to git-svnimport. git-svn is especially useful when it comes to tracking repositories not organized in the way Subversion developers recommend (trunk, @@ -31,23 +30,40 @@ COMMANDS -- 'init':: - Creates an empty git repository with additional metadata - directories for git-svn. The Subversion URL must be specified - as a command-line argument. Optionally, the target directory - to operate on can be specified as a second argument. Normally - this command initializes the current directory. + Initializes an empty git repository with additional + metadata directories for git-svn. The Subversion URL + may be specified as a command-line argument, or as full + URL arguments to -T/-t/-b. Optionally, the target + directory to operate on can be specified as a second + argument. Normally this command initializes the current + directory. -'fetch':: +-T:: +--trunk=:: +-t:: +--tags=:: +-b:: +--branches=:: + These are optional command-line options for init. Each of + these flags can point to a relative repository path + (--tags=project/tags') or a full url + (--tags=https://foo.org/project/tags) -Fetch unfetched revisions from the Subversion URL we are -tracking. refs/remotes/git-svn will be updated to the -latest revision. +--prefix= + This allows one to specify a prefix which is prepended + to the names of remotes if trunk/branches/tags are + specified. The prefix does not automatically include a + trailing slash, so be sure you include one in the + argument if that is what you want. This is useful if + you wish to track multiple projects that share a common + repository. -Note: You should never attempt to modify the remotes/git-svn -branch outside of git-svn. Instead, create a branch from -remotes/git-svn and work on that branch. Use the 'dcommit' -command (see below) to write git commits back to -remotes/git-svn. +'fetch':: + + Fetch unfetched revisions from the Subversion remote we are + tracking. The name of the [svn-remote "..."] section in the + .git/config file may be specified as an optional command-line + argument. 'dcommit':: Commit each diff from a specified head directly to the SVN @@ -109,53 +125,13 @@ remotes/git-svn. repository (that has been init-ed with git-svn). The -r option is required for this. -'graft-branches':: - This command attempts to detect merges/branches from already - imported history. Techniques used currently include regexes, - file copies, and tree-matches). This command generates (or - modifies) the $GIT_DIR/info/grafts file. This command is - considered experimental, and inherently flawed because - merge-tracking in SVN is inherently flawed and inconsistent - across different repositories. - -'multi-init':: - This command supports git-svnimport-like command-line syntax for - importing repositories that are laid out as recommended by the - SVN folks. This is a bit more tolerant than the git-svnimport - command-line syntax and doesn't require the user to figure out - where the repository URL ends and where the repository path - begins. - --T:: ---trunk=:: --t:: ---tags=:: --b:: ---branches=:: - These are the command-line options for multi-init. Each of - these flags can point to a relative repository path - (--tags=project/tags') or a full url - (--tags=https://foo.org/project/tags) - ---prefix= - This allows one to specify a prefix which is prepended to the - names of remotes. The prefix does not automatically include a - trailing slash, so be sure you include one in the argument if - that is what you want. This is useful if you wish to track - multiple projects that share a common repository. - -'multi-fetch':: - This runs fetch on all known SVN branches we're tracking. This - will NOT discover new branches (unlike git-svnimport), so - multi-init will need to be re-run (it's idempotent). - -- OPTIONS ------- -- ---shared:: +--shared[={false|true|umask|group|all|world|everybody}]:: --template=:: Only used with the 'init' command. These are passed directly to gitlink:git-init[1]. @@ -163,14 +139,15 @@ OPTIONS -r :: --revision :: -Only used with the 'fetch' command. +Used with the 'fetch' command. -Takes any valid -r svn would accept and passes it -directly to svn. -r: ranges and "{" DATE "}" syntax -is also supported. This is passed directly to svn, see svn -documentation for more details. +This allows revision ranges for partial/cauterized history +to be supported. $NUMBER, $NUMBER1:$NUMBER2 (numeric ranges), +$NUMBER:HEAD, and BASE:$NUMBER are all supported. -This can allow you to make partial mirrors when running fetch. +This can allow you to make partial mirrors when running fetch; +but is generally not recommended because history will be skipped +and lost. -:: --stdin:: @@ -276,36 +253,19 @@ ADVANCED OPTIONS ---------------- -- --b:: ---branch :: -Used with 'fetch', 'dcommit' or 'set-tree'. - -This can be used to join arbitrary git branches to remotes/git-svn -on new commits where the tree object is equivalent. - -When used with different GIT_SVN_ID values, tags and branches in -SVN can be tracked this way, as can some merges where the heads -end up having completely equivalent content. This can even be -used to track branches across multiple SVN _repositories_. - -This option may be specified multiple times, once for each -branch. - -config key: svn.branch - -i:: --id :: -This sets GIT_SVN_ID (instead of using the environment). See the -section on -'<>' -for more information on using GIT_SVN_ID. +This sets GIT_SVN_ID (instead of using the environment). This +allows the user to override the default refname to fetch from +when tracking a single URL. The 'log' and 'dcommit' commands +no longer require this switch as an argument. -R:: --svn-remote :: Specify the [svn-remote ""] section to use, - this allows multiple repositories to be tracked. - Default: git-svn + this allows SVN multiple repositories to be tracked. + Default: "svn" --follow-parent:: This is especially helpful when we're tracking a directory @@ -369,26 +329,21 @@ Tracking and contributing to a the trunk of a Subversion-managed project: Tracking and contributing to an entire Subversion-managed project (complete with a trunk, tags and branches): -See also: -'<>' ------------------------------------------------------------------------ # Initialize a repo (like git init): - git-svn multi-init http://svn.foo.org/project \ - -T trunk -b branches -t tags + git-svn init http://svn.foo.org/project -T trunk -b branches -t tags # Fetch remote revisions: - git-svn multi-fetch + git-svn fetch # Create your own branch of trunk to hack on: git checkout -b my-trunk remotes/trunk # Do some work, and then commit your new changes to SVN, as well as # automatically updating your working HEAD: - git-svn dcommit -i trunk + git-svn dcommit # Something has been committed to trunk, rebase the latest into your branch: - git-svn multi-fetch && git rebase remotes/trunk + git-svn fetch && git rebase remotes/trunk # Append svn:ignore settings of trunk to the default git exclude file: git-svn show-ignore -i trunk >> .git/info/exclude -# Check for new branches and tags (no arguments are needed): - git-svn multi-init ------------------------------------------------------------------------ REBASE VS. PULL/MERGE @@ -411,31 +366,9 @@ DESIGN PHILOSOPHY Merge tracking in Subversion is lacking and doing branched development with Subversion is cumbersome as a result. git-svn does not do automated merge/branch tracking by default and leaves it entirely up to -the user on the git side. - -[[tracking-multiple-repos]] -TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------- -Because git-svn does not care about relationships between different -branches or directories in a Subversion repository, git-svn has a simple -hack to allow it to track an arbitrary number of related _or_ unrelated -SVN repositories via one git repository. Simply use the --id/-i flag or -set the GIT_SVN_ID environment variable to a name other other than -"git-svn" (the default) and git-svn will ignore the contents of the -$GIT_DIR/svn/git-svn directory and instead do all of its work in -$GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will -be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any -remotes/$GIT_SVN_ID branch should never be modified by the user outside -of git-svn commands. - -If you're tracking a directory that has moved, or otherwise been -branched or tagged off of another directory in the repository and you -care about the full history of the project, then you can use -the --follow-parent option. - ------------------------------------------------- - git-svn fetch --follow-parent ------------------------------------------------- +the user on the git side. git-svn does however follow copy +history of the directory that it is tracking, however (much like +how 'svn log' works). BUGS ---- @@ -452,6 +385,33 @@ the possible corner cases (git doesn't do it, either). Renamed and copied files are fully supported if they're similar enough for git to detect them. +CONFIGURATION +------------- + +git-svn stores [svn-remote] configuration information in the +repository .git/config file. It is similar the core git +[remote] sections except 'fetch' keys do not accept glob +arguments; but they are instead handled by the 'branches' +and 'tags' keys. Since some SVN repositories are oddly +configured with multiple projects glob expansions such those +listed below are allowed: + +------------------------------------------------------------------------ +[svn-remote "project-a"] + url = http://server.org/svn + branches = branches/*/project-a:refs/remotes/project-a/branches/* + tags = tags/*/project-a:refs/remotes/project-a/tags/* + trunk = trunk/project-a:refs/remotes/project-a/trunk +------------------------------------------------------------------------ + +Keep in mind that the '*' (asterisk) wildcard of the local ref +(left of the ':') *must* be the farthest right path component; +however the remote wildcard may be anywhere as long as it's own +independent path componet (surrounded by '/' or EOL). This +type of configuration is not automatically created by 'init' and +should be manually entered with a text-editor or using +gitlink:git-config[1] + SEE ALSO -------- gitlink:git-rebase[1] diff --git a/git-svn.perl b/git-svn.perl index bfe5d6b97e..31e536c72f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -114,7 +114,8 @@ my %cmd = ( # no-op, we automatically run this anyways, 'Migrate configuration/metadata/layout from previous versions of git-svn', - \%remote_opts ], + { 'minimize' => \$Git::SVN::Migration::_minimize, + %remote_opts } ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, @@ -180,9 +181,9 @@ Usage: $0 [options] [arguments]\n foreach (sort keys %cmd) { next if $cmd && $cmd ne $_; + next if /^multi-/; # don't show deprecated commands print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { - next if /^multi-/; # don't show deprecated commands # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 21, join(', ', map { length $_ > 1 ? -- cgit v1.2.3 From 488a63ec233ce7edc5fd70172c196628b1d6a82b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 15 Feb 2007 00:40:42 -0800 Subject: git-svn: add support for --stat in the log command Signed-off-by: Eric Wong --- git-svn.perl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 31e536c72f..2152bf3de8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3234,7 +3234,7 @@ sub show_commit_normal { print "\n"; } - foreach my $x (qw/raw diff/) { + foreach my $x (qw/raw stat diff/) { if ($c->{$x}) { print "\n"; print $_ foreach @{$c->{$x}} @@ -3266,7 +3266,7 @@ sub cmd_show_log { @args = (git_svn_log_cmd($r_min, $r_max, @args), @args); my $log = command_output_pipe(@args); run_pager(); - my (@k, $c, $d); + my (@k, $c, $d, $stat); my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; while (<$log>) { if (/^${esc_color}commit ($::sha1_short)/o) { @@ -3294,6 +3294,13 @@ sub cmd_show_log { push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; + } elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]* + $esc_color*[\+\-]*$esc_color$/x) { + $stat = 1; + push @{$c->{stat}}, $_; + } elsif ($stat && /^ \d+ files changed, \d+ insertions/) { + push @{$c->{stat}}, $_; + $stat = undef; } elsif (/^${esc_color} (git-svn-id:.+)$/o) { ($c->{url}, $c->{r}, undef) = ::extract_metadata($1); } elsif (s/^${esc_color} //o) { -- cgit v1.2.3 From 1e889ef36c45b5554f7e317493ed3f4f901f8d9f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 01:45:13 -0800 Subject: git-svn: checkout files on new fetches On newly-created repositories, 'refs/heads/master' does not point to anything. This can be confusing to new users; so we update 'master' to point to the last imported ref after fetching is done. Once 'master' is valid; we assume HEAD points to it; and if the repository is not bare, then checkout the files if the working tree is clean and unused. Signed-off-by: Eric Wong --- git-svn.perl | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 2152bf3de8..7ffbf64139 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_template, $_shared, $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, - $_prefix); + $_prefix, $_no_checkout); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -67,6 +67,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, + 'no-checkout' => \$_no_checkout, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -167,6 +168,7 @@ eval { $cmd{$cmd}->[0]->(@ARGV); }; fatal $@ if $@; +post_fetch_checkout(); exit 0; ####################### primary functions ###################### @@ -466,6 +468,27 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub post_fetch_checkout { + return if $_no_checkout; + my $gs = $Git::SVN::_head or return; + return if verify_ref('refs/heads/master^0'); + + my $valid_head = verify_ref('HEAD^0'); + command_noisy(qw(update-ref refs/heads/master), $gs->refname); + return if ($valid_head || !verify_ref('HEAD^0')); + + return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#; + my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index"; + return if -f $index; + + chomp(my $bare = `git config --bool --get core.bare`); + return if $bare eq 'true'; + return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true'; + command_noisy(qw/read-tree -m -u -v HEAD HEAD/); + print STDERR "Checked out HEAD:\n ", + $gs->full_url, " r", $gs->last_rev, "\n"; +} + sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -668,7 +691,7 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags $_use_svm_props/; + $_repack $_repack_flags $_use_svm_props $_head/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -1781,6 +1804,7 @@ sub rev_db_set { } close $fh or croak $!; if ($update_ref) { + $_head = $self; command_noisy('update-ref', '-m', "r$rev", $self->refname, $commit); } -- cgit v1.2.3 From 905f8b7dfc2c520b91f418ab0f2aecb1c371fbe4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 03:22:40 -0800 Subject: git-svn: add a 'rebase' command This works similarly to 'svn update' or 'git pull' except that it preserves linear history with 'git rebase' instead of 'git merge' for ease of dcommit-ing with git-svn. While we're at it, put the working_head_info() logic into its own function and allow --fetch-all/--all for dcommit and rebase (which will fetch all refs in the current [svn-remote] instead of just the working one). Note that the '-a' switch (short for --fetch-all/--all) has been removed as it conflicts with the non-svn 'git fetch' Signed-off-by: Eric Wong --- git-svn.perl | 91 +++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 28 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 7ffbf64139..eca08bdd3b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_template, $_shared, $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, - $_prefix, $_no_checkout); + $_prefix, $_no_checkout, $_verbose); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -88,7 +88,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit, my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, - 'all|a' => \$_fetch_all, + 'fetch-all|all' => \$_fetch_all, %fc_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", @@ -101,7 +101,9 @@ my %cmd = ( 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, 'strategy|s=s' => \$_strategy, + 'verbose|v' => \$_verbose, 'dry-run|n' => \$_dry_run, + 'fetch-all|all' => \$_fetch_all, %cmt_opts, %fc_opts } ], 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", @@ -129,6 +131,12 @@ my %cmd = ( 'color' => \$Git::SVN::Log::color, 'pager=s' => \$Git::SVN::Log::pager, } ], + 'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory", + { 'merge|m|M' => \$_merge, + 'verbose|v' => \$_verbose, + 'strategy|s=s' => \$_strategy, + 'fetch-all|all' => \$_fetch_all, + %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, @@ -248,7 +256,7 @@ sub cmd_fetch { } my ($remote) = @_; if (@_ > 1) { - die "Usage: $0 fetch [--all|-a] [svn-remote]\n"; + die "Usage: $0 fetch [--all] [svn-remote]\n"; } $remote ||= $Git::SVN::default_repo_id; if ($_fetch_all) { @@ -296,21 +304,12 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; $head ||= 'HEAD'; - my ($url, $rev, $uuid); - my ($fh, $ctx) = command_output_pipe('rev-list', $head); my @refs; - my $c; - while (<$fh>) { - $c = $_; - chomp $c; - ($url, $rev, $uuid) = cmt_metadata($c); - last if (defined $url && defined $rev && defined $uuid); - unshift @refs, $c; - } - close $fh; # most likely breaking the pipe + my ($url, $rev, $uuid) = working_head_info($head, \@refs); + my $c = $refs[-1]; unless (defined $url && defined $rev && defined $uuid) { die "Unable to determine upstream SVN information from ", - "$head history:\n $ctx\n"; + "$head history\n"; } my $gs = Git::SVN->find_by_url($url); my $last_rev; @@ -354,15 +353,13 @@ sub cmd_dcommit { "now resync your SVN::Mirror repository.\n"; return; } - $gs->fetch; + $_fetch_all ? $gs->fetch_all : $gs->fetch; # we always want to rebase against the current HEAD, not any # head that was passed to us my @diff = command('diff-tree', 'HEAD', $gs->refname, '--'); my @finish; if (@diff) { - @finish = qw/rebase/; - push @finish, qw/--merge/ if $_merge; - push @finish, "--strategy=$_strategy" if $_strategy; + @finish = rebase_cmd(); print STDERR "W: HEAD and ", $gs->refname, " differ, ", "using @finish:\n", "@diff"; } else { @@ -374,6 +371,24 @@ sub cmd_dcommit { command_noisy(@finish, $gs->refname); } +sub cmd_rebase { + command_noisy(qw/update-index --refresh/); + my $url = (working_head_info('HEAD'))[0]; + if (!defined $url) { + die "Unable to determine upstream SVN information from ", + "working tree history\n"; + } + + my $gs = Git::SVN->find_by_url($url); + if (command(qw/diff-index HEAD --/)) { + print STDERR "Cannot rebase with uncommited changes:\n"; + command_noisy('status'); + exit 1; + } + $_fetch_all ? $gs->fetch_all : $gs->fetch; + command_noisy(rebase_cmd(), $gs->refname); +} + sub cmd_show_ignore { my $gs = Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); @@ -468,6 +483,14 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub rebase_cmd { + my @cmd = qw/rebase/; + push @cmd, '-v' if $_verbose; + push @cmd, qw/--merge/ if $_merge; + push @cmd, "--strategy=$_strategy" if $_strategy; + @cmd; +} + sub post_fetch_checkout { return if $_no_checkout; my $gs = $Git::SVN::_head or return; @@ -687,6 +710,20 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } +sub working_head_info { + my ($head, $refs) = @_; + my ($url, $rev, $uuid); + my ($fh, $ctx) = command_output_pipe('rev-list', $head); + while (<$fh>) { + chomp; + ($url, $rev, $uuid) = cmt_metadata($_); + last if (defined $url && defined $rev && defined $uuid); + unshift @$refs, $_ if $refs; + } + close $fh; # break the pipe + ($url, $rev, $uuid); +} + package Git::SVN; use strict; use warnings; @@ -783,6 +820,12 @@ sub parse_revision_argument { sub fetch_all { my ($repo_id, $remotes) = @_; + if (ref $repo_id) { + my $gs = $repo_id; + $repo_id = undef; + $repo_id = $gs->{repo_id}; + } + $remotes ||= read_all_remotes(); my $remote = $remotes->{$repo_id} or die "[svn-remote \"$repo_id\"] unknown\n"; my $fetch = $remote->{fetch}; @@ -3085,15 +3128,7 @@ sub git_svn_log_cmd { last; } - my $url; - my ($fh, $ctx) = command_output_pipe('rev-list', $head); - while (<$fh>) { - chomp; - $url = (::cmt_metadata($_))[0]; - last if defined $url; - } - close $fh; # break the pipe - + my $url = (::working_head_info($head))[0]; my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new; my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, $gs->refname); -- cgit v1.2.3 From d6d3346babaf19864ea104b0140279e62f32f7e3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:05:33 -0800 Subject: git-svn: fix some issues for people migrating from older versions * Fixed logic for renaming old .rev_db -> .rev_db.$uuid * correctly handle manual migrations for those who decide to start use globbing to handle branches/tags over individual 'fetch' keys Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index eca08bdd3b..d7fc9aad52 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -844,6 +844,8 @@ sub fetch_all { "svn-remote.$repo_id.${t}-maxRev") }; if (defined $max_rev && ($max_rev < $base)) { $base = $max_rev; + } elsif (!defined $max_rev) { + $base = 0; } } @@ -1066,10 +1068,7 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; - if ((-z $self->db_path || ! -e $self->db_path) && - ::verify_ref($self->refname.'^0')) { - $self->rebuild; - } + $self->rebuild; $self; } @@ -1737,6 +1736,8 @@ sub set_tree { sub rebuild { my ($self) = @_; my $db_path = $self->db_path; + return if (-e $db_path && ! -z $db_path); + return unless ::verify_ref($self->refname.'^0'); if (-f $self->{db_root}) { rename $self->{db_root}, $db_path or die "rename $self->{db_root} => $db_path failed: $!\n"; @@ -1863,6 +1864,7 @@ sub rev_db_set { sub rev_db_max { my ($self) = @_; + $self->rebuild; my $db_path = $self->db_path; my @stat = stat $db_path or return 0; ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; -- cgit v1.2.3 From b7e5348c7f6369554813207a24cf841f48bd8b23 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:09:28 -0800 Subject: git-svn: hide the private git-svn 'config' file as '.metadata' Having it named as 'config' prevents us from tracking a ref named 'config', which is a huge mistake. On the non-technical side, the word 'config' implies that a user can freely modify it; but that's not the case here. Signed-off-by: Eric Wong --- git-svn.perl | 7 ++++++- t/t9107-git-svn-migrate.sh | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index d7fc9aad52..571259fd09 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1286,7 +1286,12 @@ sub get_fetch_range { sub tmp_config { my (@args) = @_; - my $config = "$ENV{GIT_DIR}/svn/config"; + my $old_def_config = "$ENV{GIT_DIR}/svn/config"; + my $config = "$ENV{GIT_DIR}/svn/.metadata"; + if (-e $old_def_config && ! -e $config) { + rename $old_def_config, $config or + die "Failed rename $old_def_config => $config: $!\n"; + } my $old_config = $ENV{GIT_CONFIG}; $ENV{GIT_CONFIG} = $config; $@ = undef; diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index a20038b670..dc2afdaa45 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -17,6 +17,7 @@ test_expect_success 'setup old-looking metadata' " git-svn init $svnrepo && git-svn fetch && mv $GIT_DIR/svn/* $GIT_DIR/ && + mv $GIT_DIR/svn/.metadata $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && -- cgit v1.2.3 From 0425ea90889f967c3966ace3e5a85b9a5a44c358 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 18:45:01 -0800 Subject: git-svn: add 'clone' command, an alias for init + fetch Signed-off-by: Eric Wong --- git-svn.perl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 571259fd09..2cc7c33381 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -90,6 +90,9 @@ my %cmd = ( { 'revision|r=s' => \$_revision, 'fetch-all|all' => \$_fetch_all, %fc_opts } ], + clone => [ \&cmd_clone, "Initialize and fetch revisions", + { 'revision|r=s' => \$_revision, + %fc_opts, %init_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], @@ -167,7 +170,7 @@ usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; -unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { +unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } Git::SVN::init_vars(); @@ -237,6 +240,22 @@ sub init_subdir { $ENV{GIT_DIR} = $repo_path . "/.git"; } +sub cmd_clone { + my ($url, $path) = @_; + if (!defined $path && + (defined $_trunk || defined $_branches || defined $_tags) && + $url !~ m#^[a-z\+]+://#) { + $path = $url; + } + warn "--path: $path\n" if defined $path; + $path = basename($url) if !defined $path || !length $path; + warn "++path: $path\n" if defined $path; + mkpath([$path]); + chdir $path or die "Couldn't chdir to $path\n"; + cmd_init(@_); + Git::SVN::fetch_all($Git::SVN::default_repo_id); +} + sub cmd_init { if (defined $_trunk || defined $_branches || defined $_tags) { return cmd_multi_init(@_); -- cgit v1.2.3 From aea736cc6db64219b946adb4ca77f5d17bc7ab77 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:15:21 -0800 Subject: git-svn: allow overriding of the SVN repo root in metadata This feature allows users to create repositories from alternate URLs. For example, an administrator could run git-svn on the server locally (accessing via file://) but wish to distribute the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. Config key: svn-remote..rewriteRoot Signed-off-by: Eric Wong --- git-svn.perl | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 2cc7c33381..3e48c56d7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1406,6 +1406,26 @@ sub get_commit_parents { @ret; } +sub rewrite_root { + my ($self) = @_; + return $self->{-rewrite_root} if exists $self->{-rewrite_root}; + my $k = "svn-remote.$self->{repo_id}.rewriteRoot"; + my $rwr = eval { command_oneline(qw/config --get/, $k) }; + if ($rwr) { + $rwr =~ s#/+$##; + if ($rwr !~ m#^[a-z\+]+://#) { + die "$rwr is not a valid URL (key: $k)\n"; + } + } + $self->{-rewrite_root} = $rwr; +} + +sub metadata_url { + my ($self) = @_; + ($self->rewrite_root || $self->{url}) . + (length $self->{path} ? '/' . $self->{path} : ''); +} + sub full_url { my ($self) = @_; $self->{url} . (length $self->{path} ? '/' . $self->{path} : ''); @@ -1704,6 +1724,10 @@ sub make_log_entry { my ($name, $email) = defined $::users{$author} ? @{$::users{$author}} : ($author, undef); if (defined $headrev && $self->use_svm_props) { + if ($self->rewrite_root) { + die "Can't have both 'useSvmProps' and 'rewriteRoot' ", + "options set!\n"; + } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { die "UUID mismatch on SVM path:\n", @@ -1716,7 +1740,7 @@ sub make_log_entry { $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" } else { - $log_entry{metadata} = $self->full_url . "\@$rev " . + $log_entry{metadata} = $self->metadata_url. "\@$rev " . $self->ra->get_uuid; $email ||= "$author\@" . $self->ra->get_uuid; } -- cgit v1.2.3 From 62e349d235ecbb20c5338de5d4cbff9ce5c6aa66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:57:29 -0800 Subject: git-svn: add support for using svnsync properties This is similar to useSvmProps, but far simpler in implementation because svnsync retains a 1:1 between revision numbers and relative paths within the repository Config keys: svn.useSvnsyncProps svn-remote..useSvnsyncProps Signed-off-by: Eric Wong --- git-svn.perl | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 3e48c56d7e..7563eea352 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -66,6 +66,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'repack:i' => \$Git::SVN::_repack, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, + 'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props, 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, 'no-checkout' => \$_no_checkout, 'quiet|q' => \$_q, @@ -747,7 +748,8 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags $_use_svm_props $_head/; + $_repack $_repack_flags $_use_svm_props $_head + $_use_svnsync_props/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -768,7 +770,8 @@ BEGIN { # per [svn-remote "..."] section. Command-line options will *NOT* # override options set in an [svn-remote "..."] section my $e; - foreach (qw/follow_parent no_metadata use_svm_props/) { + foreach (qw/follow_parent no_metadata use_svm_props + use_svnsync_props/) { my $key = $_; $key =~ tr/_//d; $e .= "sub $_ { @@ -1186,6 +1189,50 @@ sub _set_svm_vars { Git::SVN::Ra->new($self->{url}); } +sub svnsync { + my ($self) = @_; + return $self->{svnsync} if $self->{svnsync}; + + if ($self->no_metadata) { + die "Can't have both 'noMetadata' and ", + "'useSvnsyncProps' options set!\n"; + } + if ($self->rewrite_root) { + die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ", + "options set!\n"; + } + + my $svnsync; + # see if we have it in our config, first: + eval { + my $section = "svn-remote.$self->{repo_id}"; + $svnsync = { + url => tmp_config('--get', "$section.svnsync-url"), + uuid => tmp_config('--get', "$section.svnsync-uuid"), + } + }; + if ($svnsync && $svnsync->{url} && $svnsync->{uuid}) { + return $self->{svnsync} = $svnsync; + } + + my $err = "useSvnsyncProps set, but failed to read " . + "svnsync property: svn:sync-from-"; + my $rp = $self->ra->rev_proplist(0); + + my $url = $rp->{'svn:sync-from-url'} or die $err . "url\n"; + $url =~ m{^[a-z\+]+://} or + die "doesn't look right - svn:sync-from-url is '$url'\n"; + + my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n"; + $uuid =~ m{^[0-9a-f\-]{30,}$} or + die "doesn't look right - svn:sync-from-uuid is '$uuid'\n"; + + my $section = "svn-remote.$self->{repo_id}"; + tmp_config('--add', "$section.svnsync-uuid", $uuid); + tmp_config('--add', "$section.svnsync-url", $url); + return $self->{svnsync} = { url => $url, uuid => $uuid }; +} + # this allows us to memoize our SVN::Ra UUID locally and avoid a # remote lookup (useful for 'git svn log'). sub ra_uuid { @@ -1211,6 +1258,9 @@ sub ra { if ($self->no_metadata) { die "Can't have both 'noMetadata' and ", "'useSvmProps' options set!\n"; + } elsif ($self->use_svnsync_props) { + die "Can't have both 'useSvnsyncProps' and ", + "'useSvmProps' options set!\n"; } $ra = $self->_set_svm_vars($ra); $self->{-want_revprops} = 1; @@ -1739,6 +1789,12 @@ sub make_log_entry { $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" + } elsif ($self->use_svnsync_props) { + my $full_url = $self->svnsync->{url}; + $full_url .= "/$self->{path}" if length $self->{path}; + my $uuid = $self->svnsync->{uuid}; + $log_entry{metadata} = "$full_url\@$rev $uuid"; + $email ||= "$author\@$uuid" } else { $log_entry{metadata} = $self->metadata_url. "\@$rev " . $self->ra->get_uuid; -- cgit v1.2.3 From befc9adc0ced7d3e1c1316d6420007357d50b202 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 17 Feb 2007 02:53:07 -0800 Subject: git-svn: fix useSvmProps, hopefully for the last time svm:mirror is not useful at all for us. Parts of the old unit test were broken and based on my misunderstanding of the svm:mirror property. When we read svm:source; make sure we correctly handle the '!' in it: it is used to separate the path of the repository root from the virtual path within the repository. We don't need to make that distinction, honestly! We also ensure that subdirectories are also mirrored with the correct URL if we're using useSvmProps. We have a new test that uses dumped repo that was really created using SVN::Mirror to avoid ambiguities and mis-understandings about the svm: properties. Note: trailing whitespace in the svm.dump file is unfortunately a reality and required by SVN; so please ignore it when applying this patch. Also, ensure that the -R/--remote/--svn-remote flag is always in effect if explicitly passed via the command-line. This allows us to track logically different mirrors sharing the same URL (probably common with SVN::Mirror/SVK users). Signed-off-by: Eric Wong --- git-svn.perl | 91 ++++--- t/t9109-git-svn-svk-mirrorpaths.sh | 106 -------- t/t9110-git-svn-use-svm-props.sh | 51 ++++ t/t9110/svm.dump | 511 +++++++++++++++++++++++++++++++++++++ 4 files changed, 619 insertions(+), 140 deletions(-) delete mode 100755 t/t9109-git-svn-svk-mirrorpaths.sh create mode 100755 t/t9110-git-svn-use-svm-props.sh create mode 100644 t/t9110/svm.dump (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 7563eea352..1bcf058ef6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -164,7 +164,9 @@ read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_ref_id, - 'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id); + 'svn-remote|remote|R=s' => sub { + $Git::SVN::no_reuse_existing = 1; + $Git::SVN::default_repo_id = $_[1] }); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -749,7 +751,7 @@ use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head - $_use_svnsync_props/; + $_use_svnsync_props $no_reuse_existing/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -944,6 +946,7 @@ sub sanitize_remote_name { sub find_existing_remote { my ($url, $remotes) = @_; + return undef if $no_reuse_existing; my $existing; foreach my $repo_id (keys %$remotes) { my $u = $remotes->{$repo_id}->{url} or next; @@ -1116,9 +1119,12 @@ sub svm { $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), + replace => tmp_config('--get', "$section.svm-replace"), } }; - $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + if ($svm && $svm->{source} && $svm->{uuid} && $svm->{replace}) { + $self->{svm} = $svm; + } $self->{svm}; } @@ -1127,64 +1133,76 @@ sub _set_svm_vars { return $ra if $self->svm; my @err = ( "useSvmProps set, but failed to read SVM properties\n", - "(svm:source, svm:mirror, svm:mirror) ", + "(svm:source, svm:uuid) ", "from the following URLs:\n" ); sub read_svm_props { - my ($self, $props) = @_; + my ($self, $ra, $path, $r) = @_; + my $props = ($ra->get_dir($path, $r))[2]; my $src = $props->{'svm:source'}; - my $mirror = $props->{'svm:mirror'}; my $uuid = $props->{'svm:uuid'}; - return undef if (!$src || !$mirror || !$uuid); + return undef if (!$src || !$uuid); - chomp($src, $mirror, $uuid); + chomp($src, $uuid); $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - # don't know what a '!' is there for, also the - # username is of no interest - $src =~ s{/?!$}{$mirror}; + + # the '!' is used to mark the repos_root!/relative/path + $src =~ s{/?!/?}{/}; $src =~ s{/+$}{}; # no trailing slashes please + # username is of no interest $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + my $replace = $ra->{url}; + $replace .= "/$path" if length $path; + my $section = "svn-remote.$self->{repo_id}"; - tmp_config('--add', "$section.svm-source", $src); - tmp_config('--add', "$section.svm-uuid", $uuid); - $self->{svm} = { source => $src , uuid => $uuid }; - return 1; + tmp_config("$section.svm-source", $src); + tmp_config("$section.svm-replace", $replace); + tmp_config("$section.svm-uuid", $uuid); + $self->{svm} = { + source => $src, + uuid => $uuid, + replace => $replace + }; } my $r = $ra->get_latest_revnum; my $path = $self->{path}; - my @tried_a = ($path); + my %tried; while (length $path) { - if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) { - return $ra; + unless ($tried{"$self->{url}/$path"}) { + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; } - $path =~ s#/?[^/]+$## && push @tried_a, $path; - } - if ($self->read_svm_props(($ra->get_dir('', $r))[2])) { - return $ra; + $path =~ s#/?[^/]+$##; } + die "Path: '$path' should be ''\n" if $path ne ''; + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; if ($ra->{repos_root} eq $self->{url}) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n"; + die @err, (map { " $_\n" } keys %tried), "\n"; } # nope, make sure we're connected to the repository root: my $ok; my @tried_b; $path = $ra->{svn_path}; - $path =~ s#/?[^/]+$##; # we already tried this one above $ra = Git::SVN::Ra->new($ra->{repos_root}); while (length $path) { - $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]); - last if $ok; - $path =~ s#/?[^/]+$## && push @tried_b, $path; + unless ($tried{"$ra->{url}/$path"}) { + $ok = $self->read_svm_props($ra, $path, $r); + last if $ok; + $tried{"$ra->{url}/$path"} = 1; + } + $path =~ s#/?[^/]+$##; } - $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok; + die "Path: '$path' should be ''\n" if $path ne ''; + $ok ||= $self->read_svm_props($ra, $path, $r); + $tried{"$ra->{url}/$path"} = 1; if (!$ok) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n", - map { " $ra->{url}/$_\n" } @tried_b, "\n" + die @err, (map { " $_\n" } keys %tried), "\n"; } Git::SVN::Ra->new($self->{url}); } @@ -1779,13 +1797,18 @@ sub make_log_entry { "options set!\n"; } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; - if ($uuid ne $self->{svm}->{uuid}) { + # we don't want "SVM: initializing mirror for junk" ... + return undef if $r == 0; + my $svm = $self->svm; + if ($uuid ne $svm->{uuid}) { die "UUID mismatch on SVM path:\n", - "expected: $self->{svm}->{uuid}\n", + "expected: $svm->{uuid}\n", " got: $uuid\n"; } - my $full_url = $self->{svm}->{source}; - $full_url .= "/$self->{path}" if length $self->{path}; + my $full_url = $self->full_url; + $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or + die "Failed to replace '$svm->{replace}' with ", + "'$svm->{source}' in $full_url\n"; $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh deleted file mode 100755 index 1e1b97b5fc..0000000000 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006 Sam Vilian -# - -test_description='git-svn on SVK mirror paths' -. ./lib-git-svn.sh - -# ok, people who don't have SVK installed probably don't care about -# this test. - -# we set up the repository manually, because even if SVK is installed -# it is difficult to use it in a way that is idempotent. - -# we are not yet testing merge tickets.. - -uuid=b00bface-b1ff-c0ff-f0ff-b0bafe775e1e -url=https://really.slow.server.com/foobar - -test_expect_success 'initialize repo' " - git config svn-remote.svn.useSvmProps true && - - echo '#!/bin/sh' > $rawsvnrepo/hooks/pre-revprop-change && - echo 'exit 0' >> $rawsvnrepo/hooks/pre-revprop-change && - chmod +x $rawsvnrepo/hooks/pre-revprop-change && - - mkdir import && - cd import && - mkdir local && - echo hello > local/readme && - svn import -m 'random local work' . $svnrepo && - cd .. && - - svn co $svnrepo wc && - cd wc && - mkdir -p mirror/foobar && - svn add mirror && - svn ps svm:source $url mirror/foobar && - svn ps svm:uuid $uuid mirror/foobar && - svn ps svm:mirror / mirror/foobar && - svn commit -m 'setup mirror/foobar as mirror of upstream' && - svn ps -r 2 --revprop svm:headrev $uuid:0 $svnrepo && - - mkdir mirror/foobar/trunk - echo hello, world > mirror/foobar/trunk/readme && - svn add mirror/foobar/trunk && - svn commit -m 'first upstream revision' && - svn ps -r 3 --revprop svm:headrev $uuid:1 $svnrepo && - - svn up && - svn mkdir mirror/foobar/branches && - svn cp mirror/foobar/trunk mirror/foobar/branches/silly && - svn commit -m 'make branch for silliness' && - svn ps -r 4 --revprop svm:headrev $uuid:2 $svnrepo && - - svn up && - echo random untested feature >> mirror/foobar/trunk/readme && - poke mirror/foobar/trunk/readme && - svn commit -m 'add a c00l feature to trunk' && - svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && - - svn up && - echo bug fix >> mirror/foobar/branches/silly/readme && - poke mirror/foobar/branches/silly/readme && - svn commit -m 'fix a bug' && - svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && - - svn mkdir mirror/foobar/tags && - svn cp mirror/foobar/branches/silly mirror/foobar/tags/blah-1.0 && - svn commit -m 'make a release' && - svn ps -r 7 --revprop svm:headrev $uuid:5 $svnrepo && - - cd .. - " - -test_expect_success 'init an SVK mirror path' " - git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar - " - -test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" - -test_expect_success 'got tag history OK' " - test \`git-log --pretty=oneline remotes/tags/blah-1.0 | wc -l\` -eq 3 - " - -test_expect_success 're-wrote git-svn-id URL, revision and UUID' " - git cat-file commit refs/remotes/trunk | \ - fgrep 'git-svn-id: $url/mirror/foobar/trunk@3 $uuid' && - git cat-file commit refs/remotes/tags/blah-1.0 | \ - fgrep 'git-svn-id: $url/mirror/foobar/tags/blah-1.0@5 $uuid' - git cat-file commit refs/remotes/silly | \ - fgrep 'git-svn-id: $url/mirror/foobar/branches/silly@4 $uuid' - " - -test_expect_success 're-wrote author e-mail domain UUID' " - test \`git log --pretty=fuller trunk | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 4 && - test \`git log --pretty=fuller remotes/silly | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 && - test \`git log --pretty=fuller remotes/tags/blah-1.0 | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 - " - -test_debug 'gitk --all &' - -test_done diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh new file mode 100755 index 0000000000..9db0d8fd8d --- /dev/null +++ b/t/t9110-git-svn-use-svm-props.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn useSvmProps test' + +. ./lib-git-svn.sh + +test_expect_success 'load svm repo' " + svnadmin load -q $rawsvnrepo < ../t9110/svm.dump && + git-svn init -R arr -i bar $svnrepo/mirror/arr && + git-svn init -R argh -i dir $svnrepo/mirror/argh && + git-svn init -R argh -i e $svnrepo/mirror/argh/a/b/c/d/e && + git-config svn.useSvmProps true && + git-svn fetch --all + " + +uuid=161ce429-a9dd-4828-af4a-52023f968c89 + +bar_url=http://mayonaise/svnrepo/bar +test_expect_success 'verify metadata for /bar' " + git-cat-file commit refs/remotes/bar | \ + grep '^git-svn-id: $bar_url@12 $uuid$' && + git-cat-file commit refs/remotes/bar~1 | \ + grep '^git-svn-id: $bar_url@11 $uuid$' && + git-cat-file commit refs/remotes/bar~2 | \ + grep '^git-svn-id: $bar_url@10 $uuid$' && + git-cat-file commit refs/remotes/bar~3 | \ + grep '^git-svn-id: $bar_url@9 $uuid$' && + git-cat-file commit refs/remotes/bar~4 | \ + grep '^git-svn-id: $bar_url@6 $uuid$' && + git-cat-file commit refs/remotes/bar~5 | \ + grep '^git-svn-id: $bar_url@1 $uuid$' + " + +e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e +test_expect_success 'verify metadata for /dir/a/b/c/d/e' " + git-cat-file commit refs/remotes/e | \ + grep '^git-svn-id: $e_url@1 $uuid$' + " + +dir_url=http://mayonaise/svnrepo/dir +test_expect_success 'verify metadata for /dir' " + git-cat-file commit refs/remotes/dir | \ + grep '^git-svn-id: $dir_url@2 $uuid$' && + git-cat-file commit refs/remotes/dir~1 | \ + grep '^git-svn-id: $dir_url@1 $uuid$' + " + +test_done diff --git a/t/t9110/svm.dump b/t/t9110/svm.dump new file mode 100644 index 0000000000..cc799c238d --- /dev/null +++ b/t/t9110/svm.dump @@ -0,0 +1,511 @@ +SVN-fs-dump-format-version: 2 + +UUID: de5973c6-545d-41da-aded-c265f9039e74 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2007-02-17T06:54:59.793104Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 200 +Content-length: 200 + +K 7 +svn:log +V 40 +SVM: initializing mirror for /mirror/arr +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:55:00.121647Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 44 +Content-length: 44 + +K 10 +svm:mirror +V 12 +/mirror/arr + +PROPS-END + + +Node-path: mirror +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 2 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/arr +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 14 + +PROPS-END +zzz + + +Revision-number: 3 +Prop-content-length: 230 +Content-length: 230 + +K 7 +svn:log +V 66 +new symlink is added to a file that was also just made executable + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:6 + +K 8 +svn:date +V 27 +2007-02-17T05:11:01.686891Z +PROPS-END + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 40 + +K 14 +svn:executable +V 1 +* +PROPS-END +zzz + + +Revision-number: 4 +Prop-content-length: 192 +Content-length: 192 + +K 7 +svn:log +V 28 +/bar/d should be in the log + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:9 + +K 8 +svn:date +V 27 +2007-02-17T05:11:07.686552Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1 +Content-length: 14 + +PROPS-END +abc + + +Revision-number: 5 +Prop-content-length: 185 +Content-length: 185 + +K 7 +svn:log +V 20 +add a new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:10 + +K 8 +svn:date +V 27 +2007-02-17T05:11:08.405953Z +PROPS-END + +Node-path: mirror/arr/newdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 9cd599a3523898e6a12e13ec787da50a +Content-length: 14 + +PROPS-END +new + + +Revision-number: 6 +Prop-content-length: 196 +Content-length: 196 + +K 7 +svn:log +V 31 +modify a file in new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:11 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.126645Z +PROPS-END + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: a950e20332358e523a5e9d571e47fa64 +Content-length: 8 + +new +foo + + +Revision-number: 7 +Prop-content-length: 179 +Content-length: 179 + +K 7 +svn:log +V 14 +update /bar/d + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:12 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.846221Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7 +Content-length: 4 + +cba + + +Revision-number: 8 +Prop-content-length: 201 +Content-length: 201 + +K 7 +svn:log +V 41 +SVM: initializing mirror for /mirror/argh +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:56:03.703677Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 57 +Content-length: 57 + +K 10 +svm:mirror +V 25 +/mirror/argh +/mirror/arr + +PROPS-END + + +Node-path: mirror/argh +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 9 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/argh +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/argh/a +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 19 + +PROPS-END +deep dir + + +Revision-number: 10 +Prop-content-length: 197 +Content-length: 197 + +K 7 +svn:log +V 33 +try a deep --rmdir with a commit + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:2 + +K 8 +svn:date +V 27 +2007-02-17T05:10:54.847015Z +PROPS-END + +Node-path: mirror/argh/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 9 +Node-copyfrom-path: mirror/argh/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Node-path: mirror/argh/a +Node-action: delete + + -- cgit v1.2.3 From 0dfaf0a4e1905a9137d3f2f691620529aeb3b4fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 02:34:09 -0800 Subject: git-svn: allow metadata options to be specified with 'init' and 'clone' Since the options that affect the way metadata is handled in git-svn, should be consistently set/unset throughout history imported by git-svn; it makes sense to allow the user to set certain options from the command-line that will write to the config file when initially creating the repository. Also, fix some formatting issues while we're updating documentation. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 24 ++++++++++++++++-------- git-svn.perl | 13 +++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) (limited to 'git-svn.perl') diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index bd163cfad6..da68f6d738 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,6 +49,15 @@ COMMANDS (--tags=project/tags') or a full url (--tags=https://foo.org/project/tags) +--no-metadata:: + Set the 'noMetadata' option in the [svn-remote] config. +--use-svm-props:: + Set the 'useSvmProps' option in the [svn-remote] config. +--use-svnsync-props:: + Set the 'useSvnsyncProps' option in the [svn-remote] config. +--rewrite-root=:: + Set the 'rewriteRoot' option in the [svn-remote] config. + --prefix= This allows one to specify a prefix which is prepended to the names of remotes if trunk/branches/tags are @@ -307,8 +316,8 @@ CONFIG FILE-ONLY OPTIONS ------------------------ -- -svn.noMetadata: -svn-remote..noMetadata: +svn.noMetadata:: +svn-remote..noMetadata:: This gets rid of the git-svn-id: lines at the end of every commit. If you lose your .git/svn/git-svn/.rev_db file, git-svn will not @@ -319,8 +328,8 @@ svn-remote..noMetadata: this, either. Using this conflicts with the 'useSvmProps' option for (hopefully) obvious reasons. -svn.useSvmProps: -svn-remote..useSvmProps: +svn.useSvmProps:: +svn-remote..useSvmProps:: This allows git-svn to re-map repository URLs and UUIDs from mirrors created using SVN::Mirror (or svk) for metadata. @@ -332,20 +341,19 @@ svn-remote..useSvmProps: URL and UUID, and use it when generating metadata in commit messages. -svn.useSvnsyncProps: -svn-remote..useSvnsyncprops: +svn.useSvnsyncProps:: +svn-remote..useSvnsyncprops:: Similar to the useSvmProps option; this is for users of the svnsync(1) command distributed with SVN 1.4.x and later. -svn-remote..rewriteRoot +svn-remote..rewriteRoot:: This allows users to create repositories from alternate URLs. For example, an administrator could run git-svn on the server locally (accessing via file://) but wish to distribute the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. - Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by git-svn; they *must* be set in the configuration file before any history is imported diff --git a/git-svn.perl b/git-svn.perl index 1bcf058ef6..dc78dcf8cf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -75,9 +75,14 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, %remote_opts ); my ($_trunk, $_tags, $_branches); +my %icv; my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, 'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix, + 'no-metadata' => sub { $icv{noMetadata} = 1 }, + 'use-svm-props' => sub { $icv{useSvmProps} = 1 }, + 'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 }, + 'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] }, %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, @@ -234,6 +239,14 @@ sub do_git_init_db { } command_noisy(@init_db); } + my $set; + my $pfx = "svn-remote.$Git::SVN::default_repo_id"; + foreach my $i (keys %icv) { + die "'$set' and '$i' cannot both be set\n" if $set; + next unless defined $icv{$i}; + command_noisy('config', "$pfx.$i", $icv{$i}); + $set = $i; + } } sub init_subdir { -- cgit v1.2.3 From 1a97a506043691741f25e8967e76123c1114d1fb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 00:43:19 -0800 Subject: git-svn: give show-ignore HEAD smarts, like dcommit and log This allows the user to run git-svn show-ignore on there current HEAD without needing to remember which branch/ref they branched from with -i. Also, find_by_url should correctly handle cases where the URL passed to it is not valid. Signed-off-by: Eric Wong --- git-svn.perl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index dc78dcf8cf..b4e8966919 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -425,7 +425,8 @@ sub cmd_rebase { } sub cmd_show_ignore { - my $gs = Git::SVN->new; + my $url = (::working_head_info('HEAD'))[0]; + my $gs = Git::SVN->find_by_url($url) || Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); $gs->traverse_ignore(\*STDOUT, '', $r); } @@ -1034,6 +1035,7 @@ sub init_remote_config { sub find_by_url { # repos_root and, path are optional my ($class, $full_url, $repos_root, $path) = @_; + return undef unless defined $full_url; my $remotes = read_all_remotes(); if (defined $full_url && defined $repos_root && !defined $path) { $path = $full_url; -- cgit v1.2.3 From 5253dc33b713e3de63a25305bfc5e966999a0fbe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 01:36:30 -0800 Subject: git-svn: ensure we're at the top-level and can access $GIT_DIR If we are run inside a subdirectory of a working tree, we'll chdir to the top first before touching anything. This also prevents the accidental creation of .git directories inside subdirectories since they need metadata. Noticed by maio on #git Signed-off-by: Eric Wong --- git-svn.perl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index b4e8966919..a6d98f1608 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,6 +9,7 @@ use vars qw/ $AUTHOR $VERSION $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; +my $git_dir_user_set = 1 if defined $ENV{GIT_DIR}; $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; @@ -178,6 +179,28 @@ usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; + +# make sure we're always running +unless ($cmd =~ /(?:clone|init|multi-init)$/) { + unless (-d $ENV{GIT_DIR}) { + if ($git_dir_user_set) { + die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ", + "but it is not a directory\n"; + } + my $git_dir = delete $ENV{GIT_DIR}; + chomp(my $cdup = command_oneline(qw/rev-parse --show-cdup/)); + unless (length $cdup) { + die "Already at toplevel, but $git_dir ", + "not found '$cdup'\n"; + } + chdir $cdup or die "Unable to chdir up to '$cdup'\n"; + unless (-d $git_dir) { + die "$git_dir still not found after going to ", + "'$cdup'\n"; + } + $ENV{GIT_DIR} = $git_dir; + } +} unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } -- cgit v1.2.3 From 18ea92bd818d38c808329abf77ee8f1ca156f446 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 23 Feb 2007 12:32:29 +1300 Subject: git-svn: don't consider SVN URL usernames significant when comparing http://foo@blah.com/path is the same as http://blah.com/path, so remove usernames from URLs before storing them in commits, and when reading them from commits. Signed-off-by: Eric Wong --- git-svn.perl | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index a6d98f1608..ea5afb7f80 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1847,6 +1847,8 @@ sub make_log_entry { $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or die "Failed to replace '$svm->{replace}' with ", "'$svm->{source}' in $full_url\n"; + # throw away username for storing in records + remove_username($full_url); $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" @@ -1915,12 +1917,14 @@ sub rebuild { my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); my $latest; my $full_url = $self->full_url; + remove_username($full_url); my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; my ($url, $rev, $uuid) = ::cmt_metadata($c); + remove_username($url); # ignore merges (from set-tree) next if (!defined $rev || !$uuid); @@ -2094,6 +2098,10 @@ sub uri_encode { $f } +sub remove_username { + $_[0] =~ s{^([^:]*://)[^@]+@}{$1}; +} + package Git::SVN::Prompt; use strict; use warnings; -- cgit v1.2.3 From f30603fcf37f44942a2173386c0f580a508158df Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:26:26 -0800 Subject: git-svn: fix clone when a target directory has been specified Several bugs caused this to fail: * GIT_DIR was set incorrectly after entering the target directory * Avoid double chdir-ing when clone is called with an explicit path * create target subdirectory *before* running git-init when using the multi-init path Signed-off-by: Eric Wong --- git-svn.perl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index ea5afb7f80..a5c6eb9fec 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -276,7 +276,7 @@ sub init_subdir { my $repo_path = shift or return; mkpath([$repo_path]) unless -d $repo_path; chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n"; - $ENV{GIT_DIR} = $repo_path . "/.git"; + $ENV{GIT_DIR} = '.git'; } sub cmd_clone { @@ -286,12 +286,8 @@ sub cmd_clone { $url !~ m#^[a-z\+]+://#) { $path = $url; } - warn "--path: $path\n" if defined $path; $path = basename($url) if !defined $path || !length $path; - warn "++path: $path\n" if defined $path; - mkpath([$path]); - chdir $path or die "Couldn't chdir to $path\n"; - cmd_init(@_); + cmd_init($url, $path); Git::SVN::fetch_all($Git::SVN::default_repo_id); } @@ -459,12 +455,12 @@ sub cmd_multi_init { unless (defined $_trunk || defined $_branches || defined $_tags) { usage(1); } - do_git_init_db(); $_prefix = '' unless defined $_prefix; if (defined $url) { $url =~ s#/+$##; init_subdir(@_); } + do_git_init_db(); if (defined $_trunk) { my $trunk_ref = $_prefix . 'trunk'; # try both old-style and new-style lookups: -- cgit v1.2.3 From e2c475d91cc65b110a20c9836142a4a7e608a87c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:57:40 -0800 Subject: git-svn: fix reconnections to different paths of svn:// repositories Clearing the pool of the previous SVN::Ra connection we have seems to to fix mysterious connection dropping errors when reconnecting to different paths of svn:// repositories hosted by rubyforge.org. Note: I'm not sure *why* this fixes things things, but it does for me. Signed-off-by: Eric Wong --- git-svn.perl | 1 + 1 file changed, 1 insertion(+) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index a5c6eb9fec..2bd70a1577 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2879,6 +2879,7 @@ sub new { my ($class, $url) = @_; $url =~ s!/+$!!; return $RA if ($RA && $RA->{url} eq $url); + $RA->{pool}->clear if $RA; SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ -- cgit v1.2.3 From 2e5e24803fc92a1da5b941495dc22f888fc612b6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 02:21:59 -0800 Subject: git-svn: fix some potential bugs with --follow-parent When using do_switch: We only need to ensure the index is clean and set to that of the parent tree) we rely on being able to reconstruct full files with deltas transferred over the network. When using do_update: We may safely unlink the index if we are fetching an entire new tree with do_update. Having an old index (from a previously deleted/abandoned directory) around can cause irrelevant files to be mistakenly kept. Signed-off-by: Eric Wong --- git-svn.perl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'git-svn.perl') diff --git a/git-svn.perl b/git-svn.perl index 2bd70a1577..41961b59f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1675,9 +1675,9 @@ sub find_parent_branch { } if (defined $r0 && defined $parent) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; - $self->assert_index_clean($parent); my $ed; if ($self->ra->can_do_switch) { + $self->assert_index_clean($parent); print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.3 (the latest version @@ -2932,6 +2932,10 @@ sub gs_do_update { my $new = ($rev_a == $rev_b); my $path = $gs->{path}; + if ($new && -e $gs->{index}) { + unlink $gs->{index} or die + "Couldn't unlink index: $gs->{index}: $!\n"; + } my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; -- cgit v1.2.3