X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=git-svn.perl;h=54d23569337f680f7936390c8a83d2e7f2868c38;hb=f5f75c652b9c2347522159a87297820103e593e4;hp=145eaa865a3bc44da2f3bc028c462cefd924f9c6;hpb=4f12d529abbf233e1df93e7ffa5f2719005a2258;p=git.git diff --git a/git-svn.perl b/git-svn.perl index 145eaa865..54d235693 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -31,6 +31,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 File::Spec qw//; +use File::Copy qw/copy/; use POSIX qw/strftime/; use IPC::Open3; use Memoize; @@ -39,8 +40,22 @@ memoize('cmt_metadata'); memoize('get_commit_time'); my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); + +sub nag_lib { + print STDERR < \$_no_ignore_ext, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, + 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -77,9 +94,6 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 'copy-similarity|C=i'=> \$_cp_similarity ); -# yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: -my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); - my %cmd = ( fetch => [ \&fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -114,12 +128,18 @@ my %cmd = ( 'incremental' => \$_incremental, 'oneline' => \$_oneline, 'show-commit' => \$_show_commit, + 'non-recursive' => \$_non_recursive, 'authors-file|A=s' => \$_authors, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, %cmt_opts } ], + dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + { 'merge|m|M' => \$_merge, + 'strategy|s=s' => \$_strategy, + 'dry-run|n' => \$_dry_run, + %cmt_opts } ], ); my $cmd; @@ -147,7 +167,7 @@ init_vars(); load_authors() if $_authors; load_all_refs() if $_branch_all_refs; svn_compat_check() unless $_use_lib; -migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init)$/; +migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -163,11 +183,11 @@ Usage: $0 [options] [arguments]\n foreach (sort keys %cmd) { next if $cmd && $cmd ne $_; - print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; + print $fd ' ',pack('A17',$_),$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 ? + print $fd ' ' x 21, join(', ', map { length $_ > 1 ? "--$_" : "-$_" } split /\|/,$_)," $x\n"; } @@ -502,6 +522,8 @@ sub commit_lib { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; + my $repo; + ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -510,9 +532,11 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + $SVN_LOG = libsvn_connect($repo); + $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN, + ra => $SVN_LOG, c => $c, svn_path => $SVN_PATH }, @@ -559,6 +583,33 @@ sub commit_lib { unlink $commit_msg; } +sub dcommit { + my $gs = "refs/remotes/$GIT_SVN"; + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + foreach my $d (reverse @refs) { + if ($_dry_run) { + print "diff-tree $d~1 $d\n"; + } else { + commit_diff("$d~1", $d); + } + } + return if $_dry_run; + fetch(); + my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + 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; + } else { + print "No changes between current HEAD and $gs\n", + "Hard resetting to the latest $gs\n"; + @finish = qw/reset --hard/; + } + sys('git', @finish, $gs); +} + sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); $_use_lib ? show_ignore_lib() : show_ignore_cmd(); @@ -646,12 +697,17 @@ sub multi_init { } $_trunk = $url . $_trunk; } + my $ch_id; if ($GIT_SVN eq 'git-svn') { - print "GIT_SVN_ID set to 'trunk' for $_trunk\n"; + $ch_id = 1; $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; } init_vars(); - init($_trunk); + unless (-d $GIT_SVN_DIR) { + print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id; + init($_trunk); + sys('git-repo-config', 'svn.trunk', $_trunk); + } complete_url_ls_init($url, $_branches, '--branches/-b', ''); complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/'); } @@ -711,13 +767,18 @@ sub show_log { # ignore } elsif (/^:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; + } elsif (/^[ACRMDT]\t/) { + # we could add $SVN_PATH here, but that requires + # remote access at the moment (repo_path_split)... + s#^([ACRMDT])\t# $1 #; + push @{$c->{changed}}, $_; } elsif (/^diff /) { $d = 1; push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; } elsif (/^ (git-svn-id:.+)$/) { - (undef, $c->{r}, undef) = extract_metadata($1); + ($c->{url}, $c->{r}, undef) = extract_metadata($1); } elsif (s/^ //) { push @{$c->{l}}, $_; } @@ -760,7 +821,7 @@ sub commit_diff { exit 1; } if (defined $_file) { - $_message = file_to_s($_message); + $_message = file_to_s($_file); } else { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; @@ -771,7 +832,7 @@ sub commit_diff { $SVN ||= libsvn_connect($repo); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum, - ra => $SVN, c => $tb, + ra => $SVN_LOG, c => $tb, svn_path => $SVN_PATH }, $SVN->get_commit_editor($_message, @@ -784,6 +845,7 @@ sub commit_diff { } else { $ed->close_edit; } + $_message = $_file = undef; } ########################### utility functions ######################### @@ -808,7 +870,8 @@ sub git_svn_log_cmd { my ($r_min, $r_max) = @_; my @cmd = (qw/git-log --abbrev-commit --pretty=raw --default/, "refs/remotes/$GIT_SVN"); - push @cmd, '--summary' if $_verbose; + push @cmd, '-r' unless $_non_recursive; + push @cmd, qw/--raw --name-status/ if $_verbose; return @cmd unless defined $r_max; if ($r_max == $r_min) { push @cmd, '--max-count=1'; @@ -819,7 +882,7 @@ sub git_svn_log_cmd { my ($c_min, $c_max); $c_max = revdb_get($REVDB, $r_max); $c_min = revdb_get($REVDB, $r_min); - if ($c_min && $c_max) { + if (defined $c_min && defined $c_max) { if ($r_max > $r_max) { push @cmd, "$c_min..$c_max"; } else { @@ -900,16 +963,21 @@ sub complete_url_ls_init { 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; - print "init $u => $id\n"; $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); - init($u); + unless (-d $GIT_SVN_DIR) { + print "init $u => $id\n"; + init($u); + } } exit 0; } waitpid $pid, 0; croak $? if $?; + my ($n) = ($switch =~ /^--(\w+)/); + sys('git-repo-config', "svn.$n", $var); } sub common_prefix { @@ -1160,27 +1228,24 @@ sub repo_path_split { } } - my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); - $path =~ s#^/+##; - my @paths = split(m#/+#, $path); - if ($_use_lib) { - while (1) { - $SVN = libsvn_connect($url); - last if (defined $SVN && - defined eval { $SVN->get_latest_revnum }); - my $n = shift @paths || last; - $url .= "/$n"; - } + my $tmp = libsvn_connect($full_url); + my $url = $tmp->get_repos_root; + $full_url =~ s#^\Q$url\E/*##; + push @repo_path_split_cache, qr/^(\Q$url\E)/; + return ($url, $full_url); } else { + my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); + $path =~ s#^/+##; + my @paths = split(m#/+#, $path); while (quiet_run(qw/svn ls --non-interactive/, $url)) { my $n = shift @paths || last; $url .= "/$n"; } + push @repo_path_split_cache, qr/^(\Q$url\E)/; + $path = join('/',@paths); + return ($url, $path); } - push @repo_path_split_cache, qr/^(\Q$url\E)/; - $path = join('/',@paths); - return ($url, $path); } sub setup_git_svn { @@ -1213,6 +1278,7 @@ sub assert_svn_wc_clean { } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); + @status = grep(!/^X/,@status) if $_no_ignore_ext; if (scalar @status) { print STDERR "Tree ($SVN_WC) is not clean:\n"; print STDERR $_ foreach @status; @@ -1518,12 +1584,12 @@ sub get_commit_message { open my $msg, '>', $commit_msg or croak $!; chomp(my $type = `git-cat-file -t $commit`); - if ($type eq 'commit') { + if ($type eq 'commit' || $type eq 'tag') { my $pid = open my $msg_fh, '-|'; defined $pid or croak $!; if ($pid == 0) { - exec(qw(git-cat-file commit), $commit) or croak $!; + exec('git-cat-file', $type, $commit) or croak $!; } my $in_msg = 0; while (<$msg_fh>) { @@ -1701,6 +1767,8 @@ sub next_log_entry { my $rev = $1; my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); ($lines) = ($lines =~ /(\d+)/); + $date = '1970-01-01 00:00:00 +0000' + if ($_ignore_nodate && $date eq '(no date)'); my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ /(\d{4})\-(\d\d)\-(\d\d)\s (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) @@ -1760,43 +1828,6 @@ sub svn_info { sub sys { system(@_) == 0 or croak $? } -sub eol_cp { - my ($from, $to) = @_; - my $es = svn_propget_base('svn:eol-style', $to); - open my $rfd, '<', $from or croak $!; - binmode $rfd or croak $!; - open my $wfd, '>', $to or croak $!; - binmode $wfd or croak $!; - eol_cp_fd($rfd, $wfd, $es); - close $rfd or croak $!; - close $wfd or croak $!; -} - -sub eol_cp_fd { - my ($rfd, $wfd, $es) = @_; - my $eol = defined $es ? $EOL{$es} : undef; - my $buf; - use bytes; - while (1) { - my ($r, $w, $t); - defined($r = sysread($rfd, $buf, 4096)) or croak $!; - return unless $r; - if ($eol) { - if ($buf =~ /\015$/) { - my $c; - defined($r = sysread($rfd,$c,1)) or croak $!; - $buf .= $c if $r > 0; - } - $buf =~ s/(?:\015\012|\015|\012)/$eol/gs; - $r = length($buf); - } - for ($w = 0; $w < $r; $w += $t) { - $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!; - } - } - no bytes; -} - sub do_update_index { my ($z_cmd, $cmd, $no_text_base) = @_; @@ -1824,9 +1855,11 @@ sub do_update_index { 'text-base',"$f.svn-base"); $tb =~ s#^/##; } + my @s = stat($x); unlink $x or croak $!; - eol_cp($tb, $x); + copy($tb, $x); chmod(($mode &~ umask), $x) or croak $!; + utime $s[8], $s[9], $x; } print $ui $x,"\0"; } @@ -2170,7 +2203,7 @@ sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; while (<$authors>) { chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); $users{$user} = [$name, $email]; } @@ -2429,7 +2462,7 @@ sub extract_metadata { \s([a-f\d\-]+)$/x); if (!$rev || !$uuid || !$url) { # some of the original repositories I made had - # indentifiers like this: + # identifiers like this: ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/); } return ($url, $rev, $uuid); @@ -2549,6 +2582,12 @@ sub show_commit { } } +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} | "; @@ -2558,7 +2597,8 @@ sub show_commit_normal { my $nr_line = 0; if (my $l = $c->{l}) { - while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") { + while ($l->[$#$l] eq "\n" && $#$l > 0 + && $l->[($#$l - 1)] eq "\n") { pop @$l; } $nr_line = scalar @$l; @@ -2570,11 +2610,15 @@ sub show_commit_normal { } else { $nr_line .= ' lines'; } - print $nr_line, "\n\n"; + print $nr_line, "\n"; + show_commit_changed_paths($c); + print "\n"; print $_ foreach @$l; } } else { - print "1 line\n\n"; + print "1 line\n"; + show_commit_changed_paths($c); + print "\n"; } foreach my $x (qw/raw diff/) { @@ -2617,7 +2661,9 @@ sub libsvn_connect { sub libsvn_get_file { my ($gui, $f, $rev) = @_; my $p = $f; - return unless ($p =~ s#^\Q$SVN_PATH\E/##); + if (length $SVN_PATH > 0) { + return unless ($p =~ s#^\Q$SVN_PATH\E/##); + } my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2664,6 +2710,7 @@ sub libsvn_log_entry { if (defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } + $msg = '' if ($rev == 0 && !defined $msg); return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", author => $author, msg => $msg."\n", parents => $parents || [] } } @@ -2709,6 +2756,12 @@ sub libsvn_fetch { } else { die "Unrecognized action: $m, ($f r$rev)\n"; } + } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { + my @traversed = (); + libsvn_traverse($gui, '', $f, $rev, \@traversed); + foreach (@traversed) { + push @amr, [ $m, $_ ] + } } $pool->clear; } @@ -2778,7 +2831,7 @@ sub libsvn_parse_revision { } sub libsvn_traverse { - my ($gui, $pfx, $path, $rev) = @_; + my ($gui, $pfx, $path, $rev, $files) = @_; my $cwd = "$pfx/$path"; my $pool = SVN::Pool->new; $cwd =~ s#^/+##g; @@ -2786,10 +2839,15 @@ sub libsvn_traverse { foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; if ($t == $SVN::Node::dir) { - libsvn_traverse($gui, $cwd, $d, $rev); + libsvn_traverse($gui, $cwd, $d, $rev, $files); } elsif ($t == $SVN::Node::file) { - print "\tA\t$cwd/$d\n" unless $_q; - libsvn_get_file($gui, "$cwd/$d", $rev); + my $file = "$cwd/$d"; + if (defined $files) { + push @$files, $file; + } else { + print "\tA\t$file\n" unless $_q; + libsvn_get_file($gui, $file, $rev); + } } } $pool->clear; @@ -2913,9 +2971,7 @@ sub libsvn_new_tree { } my ($paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; - my $pool = SVN::Pool->new; - libsvn_traverse($gui, '', $SVN_PATH, $rev, $pool); - $pool->clear; + libsvn_traverse($gui, '', $SVN_PATH, $rev); close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg); } @@ -3298,9 +3354,11 @@ sub chg_file { seek $fh, 0, 0 or croak $!; my $exp = $md5->hexdigest; - my $atd = $self->apply_textdelta($fbat, undef, $self->{pool}); - my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool}); + my $pool = SVN::Pool->new; + my $atd = $self->apply_textdelta($fbat, undef, $pool); + my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool); die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp); + $pool->clear; close $fh or croak $!; }