X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=contrib%2Fgit-svn%2Fgit-svn.perl;h=f3fc3ec1a99ed483620e2662a3802c6f8392ae43;hb=79f558a5fc1c471e5db926a1272fe930f24784bb;hp=edae9d4dae2e8736a04108ab486128fb39222920;hpb=df746c5a81ebe7d7292fe4d1f672d02a5fa6efeb;p=git.git diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index edae9d4d..f3fc3ec1 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -4,19 +4,12 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL $SVN_INFO $SVN_WC + $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $REV_DIR/; $AUTHOR = 'Eric Wong '; $VERSION = '0.10.0'; $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git"; -$GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn'; -$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index"; -$ENV{GIT_DIR} ||= $GIT_DIR; -$SVN_URL = undef; -$REV_DIR = "$GIT_DIR/$GIT_SVN/revs"; -$SVN_WC = "$GIT_DIR/$GIT_SVN/tree"; - # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; @@ -24,6 +17,7 @@ $ENV{LC_ALL} = 'C'; # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load +# We don't use any modules not in the standard Perl distribution: use Carp qw/croak/; use IO::File qw//; use File::Basename qw/dirname basename/; @@ -34,28 +28,29 @@ use POSIX qw/strftime/; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_version, $_upgrade); -my (@_branch_from, %tree_map); - -GetOptions( 'revision|r=s' => \$_revision, - 'no-ignore-externals' => \$_no_ignore_ext, - 'stdin|' => \$_stdin, - 'edit|e' => \$_edit, - 'rmdir' => \$_rmdir, - 'upgrade' => \$_upgrade, - 'help|H|h' => \$_help, + $_find_copies_harder, $_l, $_version, $_upgrade, $_authors); +my (@_branch_from, %tree_map, %users); +my $_svn_co_url_revs; + +my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, - 'find-copies-harder' => \$_find_copies_harder, - 'l=i' => \$_l, - 'version|V' => \$_version, - 'no-stop-on-copy' => \$_no_stop_copy ); + 'authors-file|A=s' => \$_authors ); my %cmd = ( - fetch => [ \&fetch, "Download new revisions from SVN" ], - init => [ \&init, "Initialize and fetch (import)"], - commit => [ \&commit, "Commit git revisions to SVN" ], - 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings" ], - rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)" ], - help => [ \&usage, "Show help" ], + fetch => [ \&fetch, "Download new revisions from SVN", + { 'revision|r=s' => \$_revision, %fc_opts } ], + init => [ \&init, "Initialize and fetch (import)", { } ], + commit => [ \&commit, "Commit git revisions to SVN", + { 'stdin|' => \$_stdin, + 'edit|e' => \$_edit, + 'rmdir' => \$_rmdir, + 'find-copies-harder' => \$_find_copies_harder, + 'l=i' => \$_l, + %fc_opts, + } ], + 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ], + rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", + { 'no-ignore-externals' => \$_no_ignore_ext, + 'upgrade' => \$_upgrade } ], ); my $cmd; for (my $i = 0; $i < @ARGV; $i++) { @@ -66,17 +61,24 @@ for (my $i = 0; $i < @ARGV; $i++) { } }; -# we may be called as git-svn-(command), or git-svn(command). -foreach (keys %cmd) { - if (/git\-svn\-?($_)(?:\.\w+)?$/) { - $cmd = $1; - last; - } -} +my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); + +GetOptions(%opts, 'help|H|h' => \$_help, + 'version|V' => \$_version, + 'id|i=s' => \$GIT_SVN) or exit 1; + +$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; +$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index"; +$ENV{GIT_DIR} ||= $GIT_DIR; +$SVN_URL = undef; +$REV_DIR = "$GIT_DIR/$GIT_SVN/revs"; +$SVN_WC = "$GIT_DIR/$GIT_SVN/tree"; + usage(0) if $_help; version() if $_version; -usage(1) unless (defined $cmd); -svn_check_ignore_externals(); +usage(1) unless defined $cmd; +load_authors() if $_authors; +svn_compat_check(); $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -87,15 +89,25 @@ sub usage { print $fd <<""; git-svn - bidirectional operations between a single Subversion tree and git Usage: $0 [options] [arguments]\n -Available commands: + + print $fd "Available commands:\n" unless $cmd; foreach (sort keys %cmd) { + next if $cmd && $cmd ne $_; print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; + foreach (keys %{$cmd{$_}->[2]}) { + # prints out arguments as they should be passed: + my $x = s#=s$## ? '' : s#=i$## ? '' : ''; + print $fd ' ' x 17, join(', ', map { length $_ > 1 ? + "--$_" : "-$_" } + split /\|/,$_)," $x\n"; + } } print $fd <<""; -\nGIT_SVN_ID may be set in the environment to an arbitrary identifier if -you're tracking multiple SVN branches/repositories in one git repository -and want to keep them separate. See git-svn(1) for more information. +\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an +arbitrary identifier if you're tracking multiple SVN branches/repositories in +one git repository and want to keep them separate. See git-svn(1) for more +information. exit $exit; } @@ -107,7 +119,6 @@ sub version { sub rebuild { $SVN_URL = shift or undef; - my $repo_uuid; my $newest_rev = 0; if ($_upgrade) { sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD"); @@ -143,8 +154,8 @@ sub rebuild { # if we merged or otherwise started elsewhere, this is # how we break out of it - next if (defined $repo_uuid && ($uuid ne $repo_uuid)); - next if (defined $SVN_URL && ($url ne $SVN_URL)); + next if (defined $SVN_UUID && ($uuid ne $SVN_UUID)); + next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); print "r$rev = $c\n"; unless (defined $latest) { @@ -152,7 +163,8 @@ sub rebuild { croak "SVN repository location required: $url\n"; } $SVN_URL ||= $url; - $repo_uuid ||= setup_git_svn(); + $SVN_UUID ||= $uuid; + setup_git_svn(); $latest = $rev; } assert_revision_eq_or_unknown($rev, $c); @@ -161,9 +173,7 @@ sub rebuild { } close $rev_list or croak $?; if (!chdir $SVN_WC) { - my @svn_co = ('svn','co',"-r$latest"); - push @svn_co, '--ignore-externals' unless $_no_ignore_ext; - sys(@svn_co, $SVN_URL, $SVN_WC); + svn_cmd_checkout($SVN_URL, $latest, $SVN_WC); chdir $SVN_WC or croak $!; } @@ -212,17 +222,14 @@ sub fetch { my $base = shift @$svn_log or croak "No base revision!\n"; my $last_commit = undef; unless (-d $SVN_WC) { - my @svn_co = ('svn','co',"-r$base->{revision}"); - push @svn_co,'--ignore-externals' unless $_no_ignore_ext; - sys(@svn_co, $SVN_URL, $SVN_WC); + svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC); chdir $SVN_WC or croak $!; + read_uuid(); $last_commit = git_commit($base, @parents); - unless (-f "$GIT_DIR/refs/heads/master") { - sys(qw(git-update-ref refs/heads/master),$last_commit); - } assert_svn_wc_clean($base->{revision}, $last_commit); } else { chdir $SVN_WC or croak $!; + read_uuid(); $last_commit = file_to_s("$REV_DIR/$base->{revision}"); } my @svn_up = qw(svn up); @@ -235,6 +242,9 @@ sub fetch { $last_commit = git_commit($log_msg, $last_commit, @parents); } assert_svn_wc_clean($last_rev, $last_commit); + unless (-e "$GIT_DIR/refs/heads/master") { + sys(qw(git-update-ref refs/heads/master),$last_commit); + } return pop @$svn_log; } @@ -245,7 +255,7 @@ sub commit { print "Reading from stdin...\n"; @commits = (); while () { - if (/\b($sha1_short)\b/) { + if (/\b($sha1_short)\b/o) { unshift @commits, $1; } } @@ -265,7 +275,9 @@ sub commit { fetch(); chdir $SVN_WC or croak $!; - my $svn_current_rev = svn_info('.')->{'Last Changed Rev'}; + my $info = svn_info('.'); + read_uuid($info); + my $svn_current_rev = $info->{'Last Changed Rev'}; foreach my $c (@revs) { my $mods = svn_checkout_tree($svn_current_rev, $c); if (scalar @$mods == 0) { @@ -304,6 +316,14 @@ sub show_ignore { ########################### utility functions ######################### +sub read_uuid { + return if $SVN_UUID; + my $info = shift || svn_info('.'); + $SVN_UUID = $info->{'Repository UUID'} or + croak "Repository UUID unreadable\n"; + s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid"); +} + sub setup_git_svn { defined $SVN_URL or croak "SVN repository location required\n"; unless (-d $GIT_DIR) { @@ -313,24 +333,27 @@ sub setup_git_svn { mkpath(["$GIT_DIR/$GIT_SVN/info"]); mkpath([$REV_DIR]); s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url"); - my $uuid = svn_info($SVN_URL)->{'Repository UUID'} or - croak "Repository UUID unreadable\n"; - s_to_file($uuid,"$GIT_DIR/$GIT_SVN/info/uuid"); open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!; print $fd '.svn',"\n"; close $fd or croak $!; - return $uuid; } sub assert_svn_wc_clean { my ($svn_rev, $treeish) = @_; croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/); croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o); - my $svn_info = svn_info('.'); - if ($svn_rev != $svn_info->{'Last Changed Rev'}) { - croak "Expected r$svn_rev, got r", - $svn_info->{'Last Changed Rev'},"\n"; + my $lcr = svn_info('.')->{'Last Changed Rev'}; + if ($svn_rev != $lcr) { + print STDERR "Checking for copy-tree ... "; + # use + my @diff = grep(/^Index: /,(safe_qx(qw(svn diff), + "-r$lcr:$svn_rev"))); + if (@diff) { + croak "Nope! Expected r$svn_rev, got r$lcr\n"; + } else { + print STDERR "OK!\n"; + } } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); @@ -740,6 +763,10 @@ sub svn_log_raw { author => $author, lines => $lines, msg => '' ); + if (defined $_authors && ! defined $users{$author}) { + die "Author: $author not defined in ", + "$_authors file\n"; + } push @svn_log, \%log_msg; $state = 'msg_start'; next; @@ -823,11 +850,23 @@ sub assert_revision_unknown { } } +sub trees_eq { + my ($x, $y) = @_; + my @x = safe_qx('git-cat-file','commit',$x); + my @y = safe_qx('git-cat-file','commit',$y); + if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/ + || $y[0] !~ /^tree $sha1\n$/) { + print STDERR "Trees not equal: $y[0] != $x[0]\n"; + return 0 + } + return 1; +} + sub assert_revision_eq_or_unknown { my ($revno, $commit) = @_; if (-f "$REV_DIR/$revno") { my $current = file_to_s("$REV_DIR/$revno"); - if ($commit ne $current) { + if (($commit ne $current) && !trees_eq($commit, $current)) { croak "$REV_DIR/$revno already exists!\n", "current: $current\nexpected: $commit\n"; } @@ -839,9 +878,6 @@ sub git_commit { my ($log_msg, @parents) = @_; assert_revision_unknown($log_msg->{revision}); my $out_fh = IO::File->new_tmpfile or croak $!; - my $info = svn_info('.'); - my $uuid = $info->{'Repository UUID'}; - defined $uuid or croak "Unable to get Repository UUID\n"; map_tree_joins() if (@_branch_from && !%tree_map); @@ -880,16 +916,12 @@ sub git_commit { my $msg_fh = IO::File->new_tmpfile or croak $!; print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ", "$SVN_URL\@$log_msg->{revision}", - " $uuid\n" or croak $!; + " $SVN_UUID\n" or croak $!; $msg_fh->flush == 0 or croak $!; seek $msg_fh, 0, 0 or croak $!; - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = - $log_msg->{author}; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = - $log_msg->{author}."\@$uuid"; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = - $log_msg->{date}; + set_commit_env($log_msg); + my @exec = ('git-commit-tree',$tree); push @exec, '-p', $_ foreach @exec_parents; open STDIN, '<&', $msg_fh or croak $!; @@ -907,7 +939,16 @@ sub git_commit { } my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit); if (my $primary_parent = shift @exec_parents) { - push @update_ref, $primary_parent; + $pid = fork; + defined $pid or croak $!; + if (!$pid) { + close STDERR; + close STDOUT; + exec 'git-rev-parse','--verify', + "refs/remotes/$GIT_SVN^0"; + } + waitpid $pid, 0; + push @update_ref, $primary_parent unless $?; } sys(@update_ref); sys('git-update-ref',"$GIT_SVN/revs/$log_msg->{revision}",$commit); @@ -915,6 +956,16 @@ sub git_commit { return $commit; } +sub set_commit_env { + my ($log_msg) = @_; + my $author = $log_msg->{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_msg->{date}; +} + sub apply_mod_line_blob { my $m = shift; if ($m->{mode_b} =~ /^120/) { @@ -970,13 +1021,37 @@ sub safe_qx { return wantarray ? @ret : join('',@ret); } -sub svn_check_ignore_externals { - return if $_no_ignore_ext; - unless (grep /ignore-externals/,(safe_qx(qw(svn co -h)))) { +sub svn_compat_check { + my @co_help = safe_qx(qw(svn co -h)); + unless (grep /ignore-externals/,@co_help) { print STDERR "W: Installed svn version does not support ", "--ignore-externals\n"; $_no_ignore_ext = 1; } + if (grep /usage: checkout URL\[\@REV\]/,@co_help) { + $_svn_co_url_revs = 1; + } + + # I really, really hope nobody hits this... + unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) { + print STDERR <<''; +W: The installed svn version does not support the --stop-on-copy flag in + the log command. + Lets hope the directory you're tracking is not a branch or tag + and was never moved within the repository... + + $_no_stop_copy = 1; + } +} + +# *sigh*, new versions of svn won't honor -r without URL@, +# (and they won't honor URL@ without -r, too!) +sub svn_cmd_checkout { + my ($url, $rev, $dir) = @_; + my @cmd = ('svn','co', "-r$rev"); + push @cmd, '--ignore-externals' unless $_no_ignore_ext; + $url .= "\@$rev" if $_svn_co_url_revs; + sys(@cmd, $url, $dir); } sub check_upgrade_needed { @@ -1023,6 +1098,18 @@ sub map_tree_joins { } } +# ' = real-name ' mapping based on git-svnimport: +sub load_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); + $users{$user} = [$name, $email]; + } + close $authors or croak $!; +} + __END__ Data structures: