upload-pack: use object pointer not copy of sha1 to keep track of has/needs.
[gitweb.git] / contrib / git-svn / git-svn.perl
index 4bdc9766cdd04cb8a62f437c71fa04b4738312d7..8bc4188e03376e04cb412bcafa01c97a29042d4a 100755 (executable)
@@ -19,6 +19,7 @@
 # make sure the svn binary gives consistent output between locales and TZs:
 $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
+$| = 1; # unbuffer STDOUT
 
 # If SVN:: library support is added, please make the dependencies
 # optional and preserve the capability to use the command-line client.
@@ -45,8 +46,8 @@
 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, $_cp_similarity, $_cp_remote,
-       $_repack, $_repack_nr, $_repack_flags,
-       $_message, $_file,
+       $_repack, $_repack_nr, $_repack_flags, $_q,
+       $_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);
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
                'branch|b=s' => \@_branch_from,
+               'follow-parent|follow' => \$_follow_parent,
                'branch-all-refs|B' => \$_branch_all_refs,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$_repack,
+               'no-metadata' => \$_no_metadata,
+               'quiet|q' => \$_q,
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
 my ($_trunk, $_tags, $_branches);
@@ -260,9 +264,19 @@ sub rebuild {
 }
 
 sub init {
-       $SVN_URL = shift or die "SVN repository location required " .
+       my $url = shift or die "SVN repository location required " .
                                "as a command-line argument\n";
-       $SVN_URL =~ s!/+$!!; # strip trailing slash
+       $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();
+       }
+
+       $SVN_URL = $url;
        unless (-d $GIT_DIR) {
                my @init_db = ('git-init-db');
                push @init_db, "--template=$_template" if defined $_template;
@@ -824,35 +838,19 @@ sub fetch_child_id {
        my $id = shift;
        print "Fetching $id\n";
        my $ref = "$GIT_DIR/refs/remotes/$id";
-       my $ca = file_to_s($ref) if (-r $ref);
-       defined(my $pid = fork) or croak $!;
+       defined(my $pid = open my $fh, '-|') or croak $!;
        if (!$pid) {
+               $_repack = undef;
                $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                init_vars();
                fetch(@_);
                exit 0;
        }
-       waitpid $pid, 0;
-       croak $? if $?;
-       return unless $_repack || -r $ref;
-
-       my $cb = file_to_s($ref);
-
-       defined($pid = open my $fh, '-|') or croak $!;
-       my $url = file_to_s("$GIT_DIR/svn/$id/info/url");
-       $url = qr/\Q$url\E/;
-       if (!$pid) {
-               exec qw/git-rev-list --pretty=raw/,
-                               $ca ? "$ca..$cb" : $cb or croak $!;
-       }
        while (<$fh>) {
-               if (/^    git-svn-id: $url\@\d+ [a-f0-9\-]+$/) {
-                       check_repack();
-               } elsif (/^    git-svn-id: \S+\@\d+ [a-f0-9\-]+$/) {
-                       last;
-               }
+               print $_;
+               check_repack() if (/^r\d+ = $sha1/);
        }
-       close $fh;
+       close $fh or croak $?;
 }
 
 sub rec_fetch {
@@ -1467,12 +1465,12 @@ sub libsvn_checkout_tree {
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
                my $f = $m->{chg};
                if (defined $o{$f}) {
-                       $ed->$f($m);
+                       $ed->$f($m, $_q);
                } else {
                        croak "Invalid change type: $f\n";
                }
        }
-       $ed->rmdirs if $_rmdir;
+       $ed->rmdirs($_q) if $_rmdir;
        return $mods;
 }
 
@@ -1919,6 +1917,13 @@ sub git_commit {
                croak $? if $?;
                restore_index($index);
        }
+
+       # just in case we clobber the existing ref, we still want that ref
+       # as our parent:
+       if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+               push @tmp_parents, $cur;
+       }
+
        if (exists $tree_map{$tree}) {
                foreach my $p (@{$tree_map{$tree}}) {
                        my $skip;
@@ -1949,31 +1954,26 @@ sub git_commit {
                last if @exec_parents > 16;
        }
 
-       defined(my $pid = open my $out_fh, '-|') or croak $!;
-       if ($pid == 0) {
-               my $msg_fh = IO::File->new_tmpfile or croak $!;
-               print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
-                                       "$SVN_URL\@$log_msg->{revision}",
+       set_commit_env($log_msg);
+       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 $!;
+       unless ($_no_metadata) {
+               print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
                                        " $SVN_UUID\n" or croak $!;
-               $msg_fh->flush == 0 or croak $!;
-               seek $msg_fh, 0, 0 or croak $!;
-               set_commit_env($log_msg);
-               my @exec = ('git-commit-tree',$tree);
-               push @exec, '-p', $_  foreach @exec_parents;
-               open STDIN, '<&', $msg_fh or croak $!;
-               exec @exec 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 $?;
+       close $out_fh or croak $!;
+       waitpid $pid, 0;
+       croak $? if $?;
        if ($commit !~ /^$sha1$/o) {
-               croak "Failed to commit, invalid sha1: $commit\n";
-       }
-       my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
-       if (my $primary_parent = shift @exec_parents) {
-               quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0");
-               push @update_ref, $primary_parent unless $?;
+               die "Failed to commit, invalid sha1: $commit\n";
        }
-       sys(@update_ref);
+       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
        revdb_set($REVDB, $log_msg->{revision}, $commit);
 
        # this output is read via pipe, do not change:
@@ -2058,6 +2058,11 @@ sub safe_qx {
 }
 
 sub svn_compat_check {
+       if ($_follow_parent) {
+               print STDERR 'E: --follow-parent functionality is only ',
+                               "available when SVN libraries are used\n";
+               exit 1;
+       }
        my @co_help = safe_qx(qw(svn co -h));
        unless (grep /ignore-externals/,@co_help) {
                print STDERR "W: Installed svn version does not support ",
@@ -2386,6 +2391,28 @@ sub write_grafts {
        close $fh or croak $!;
 }
 
+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;
@@ -2590,7 +2617,7 @@ sub libsvn_connect {
 sub libsvn_get_file {
        my ($gui, $f, $rev) = @_;
        my $p = $f;
-       return unless ($p =~ s#^\Q$SVN_PATH\E/?##);
+       return unless ($p =~ s#^\Q$SVN_PATH\E/##);
 
        my ($hash, $pid, $in, $out);
        my $pool = SVN::Pool->new;
@@ -2599,7 +2626,6 @@ sub libsvn_get_file {
        # redirect STDOUT for SVN 1.1.x compatibility
        open my $stdout, '>&', \*STDOUT or croak $!;
        open STDOUT, '>&', $in or croak $!;
-       $| = 1; # not sure if this is necessary, better safe than sorry...
        my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
        $in->flush == 0 or croak $!;
        open STDOUT, '>&', $stdout or croak $!;
@@ -2670,6 +2696,7 @@ sub libsvn_fetch {
                my $m = $paths->{$f}->action();
                $f =~ s#^/+##;
                if ($m =~ /^[DR]$/) {
+                       print "\t$m\t$f\n" unless $_q;
                        process_rm($gui, $last_commit, $f);
                        next if $m eq 'D';
                        # 'R' can be file replacements, too, right?
@@ -2678,14 +2705,17 @@ sub libsvn_fetch {
                my $t = $SVN->check_path($f, $rev, $pool);
                if ($t == $SVN::Node::file) {
                        if ($m =~ /^[AMR]$/) {
-                               push @amr, $f;
+                               push @amr, [ $m, $f ];
                        } else {
                                die "Unrecognized action: $m, ($f r$rev)\n";
                        }
                }
                $pool->clear;
        }
-       libsvn_get_file($gui, $_, $rev) foreach (@amr);
+       foreach (@amr) {
+               print "\t$_->[0]\t$_->[1]\n" unless $_q;
+               libsvn_get_file($gui, $_->[1], $rev)
+       }
        close $gui or croak $?;
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
 }
@@ -2702,6 +2732,28 @@ sub svn_grab_base_rev {
        close $fh;
        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);
@@ -2736,6 +2788,7 @@ sub libsvn_traverse {
                if ($t == $SVN::Node::dir) {
                        libsvn_traverse($gui, $cwd, $d, $rev);
                } elsif ($t == $SVN::Node::file) {
+                       print "\tA\t$cwd/$d\n" unless $_q;
                        libsvn_get_file($gui, "$cwd/$d", $rev);
                }
        }
@@ -2799,15 +2852,45 @@ sub libsvn_find_parent_branch {
        print STDERR  "Found possible branch point: ",
                                "$branch_from => $svn_path, $r\n";
        $branch_from =~ s#^/##;
-       my $l_map = read_url_paths();
+       my $l_map = {};
+       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
        my $url = $SVN->{url};
        defined $l_map->{$url} or return;
-       my $id = $l_map->{$url}->{$branch_from} 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_LOG = $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: $parent\n";
+               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
                sys(qw/git-read-tree/, $parent);
                return libsvn_fetch($parent, $paths, $rev,
                                        $author, $date, $msg);
@@ -3042,7 +3125,7 @@ sub url_path {
 }
 
 sub rmdirs {
-       my ($self) = @_;
+       my ($self, $q) = @_;
        my $rm = $self->{rm};
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
@@ -3083,6 +3166,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;
                $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
                delete $bat->{$d};
        }
@@ -3123,21 +3207,23 @@ sub ensure_path {
 }
 
 sub A {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        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;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
 
 sub C {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        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;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -3151,11 +3237,12 @@ sub delete_entry {
 }
 
 sub R {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        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;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 
@@ -3165,11 +3252,12 @@ sub R {
 }
 
 sub M {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        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;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -3218,9 +3306,10 @@ sub chg_file {
 }
 
 sub D {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
+       print "\tD\t$m->{file_b}\n" unless $q;
        $self->delete_entry($m->{file_b}, $pbat);
 }
 
@@ -3274,6 +3363,16 @@ sub abort_edit {
 }
 ;
 
+# retval of read_url_paths{,_all}();
+$l_map = {
+       # repository root url
+       'https://svn.musicpd.org' => {
+               # repository path               # GIT_SVN_ID
+               'mpd/trunk'             =>      'trunk',
+               'mpd/tags/0.11.5'       =>      'tags/0.11.5',
+       },
+}
+
 Notes:
        I don't trust the each() function on unless I created %hash myself
        because the internal iterator may not have started at base.