memoize('get_commit_time');
my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+
+sub nag_lib {
+ print STDERR <<EOF;
+! Please consider installing the SVN Perl libraries (version 1.1.0 or
+! newer). You will generally get better performance and fewer bugs,
+! especially if you:
+! 1) have a case-insensitive filesystem
+! 2) replace symlinks with files (and vice-versa) in commits
+
+EOF
+}
+
$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
libsvn_load();
+nag_lib() unless $_use_lib;
+
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}/;
$_message, $_file, $_follow_parent, $_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);
+ $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
+ $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache;
'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);
'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;
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$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
- print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+ print $fd ' ' x 21, join(', ', map { length $_ > 1 ?
"--$_" : "-$_" }
split /\|/,$_)," $x\n";
}
$SVN = libsvn_connect($repo);
my $ed = SVN::Git::Editor->new(
{ r => $r_last,
- ra => $SVN,
+ ra => $SVN_LOG,
c => $c,
svn_path => $SVN_PATH
},
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();
}
$_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/');
}
# 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}}, $_;
}
$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,
} else {
$ed->close_edit;
}
+ $_message = $_file = undef;
}
########################### utility functions #########################
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';
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 {
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 {
}
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;
apply_mod_line_blob($m);
svn_check_prop_executable($m);
} elsif ($m->{chg} eq 'T') {
- sys(qw(svn rm --force),$m->{file_b});
- apply_mod_line_blob($m);
- sys(qw(svn add), $m->{file_b});
svn_check_prop_executable($m);
+ apply_mod_line_blob($m);
+ if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ sys(qw(svn propdel svn:special), $m->{file_b});
+ } else {
+ sys(qw(svn propset svn:special *),$m->{file_b});
+ }
} elsif ($m->{chg} eq 'A') {
svn_ensure_parent_path( $m->{file_b} );
apply_mod_line_blob($m);
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)
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];
}
}
}
+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} | ";
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;
} 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/) {
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 $!;
}