git-send-email: Read the default SMTP server from the GIT config file
[gitweb.git] / git-svn.perl
index 7d9839e7ac53a3fa6a9755eb5fc82a3a6cff501e..37ecc517879aa2c18cc909c60d3bf6ceed82fb48 100755 (executable)
 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}/;
@@ -51,7 +65,8 @@
        $_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;
@@ -64,6 +79,7 @@
                '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;
@@ -161,11 +183,11 @@ sub usage {
 
        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";
                }
@@ -500,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);
@@ -508,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
                                        },
@@ -557,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();
@@ -644,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/');
 }
@@ -709,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}}, $_;
                }
@@ -769,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,
@@ -782,6 +845,7 @@ sub commit_diff {
        } else {
                $ed->close_edit;
        }
+       $_message = $_file = undef;
 }
 
 ########################### utility functions #########################
@@ -806,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';
@@ -817,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 {
@@ -898,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 {
@@ -1158,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 {
@@ -1211,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;
@@ -1433,10 +1501,13 @@ sub svn_checkout_tree {
                        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);
@@ -1699,6 +1770,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)
@@ -2133,7 +2206,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];
        }
@@ -2512,6 +2585,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} | ";
@@ -2521,7 +2600,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;
@@ -2533,11 +2613,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/) {
@@ -3273,9 +3357,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 $!;
 }