Merge branch 'fix'
[gitweb.git] / git-cvsimport.perl
index bbb83fb71cab2d177e98a1c648974bb2bca4607a..c0ae00bda74e68f98efc63c093efb73c7ff914f4 100755 (executable)
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_s,$opt_m,$opt_M);
+our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S);
+my (%conv_author_name, %conv_author_email);
 
 sub usage() {
        print STDERR <<END;
 Usage: ${\basename $0}     # fetch/update GIT from CVS
-       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT]
-       [-p opts-for-cvsps] [-C GIT_repository] [-z fuzz]
-       [-i] [-k] [-u] [-s subst] [-m] [-M regex] [CVS_module]
+       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
+       [-p opts-for-cvsps] [-C GIT_repository] [-z fuzz] [-i] [-k] [-u]
+       [-s subst] [-m] [-M regex] [-S regex] [CVS_module]
 END
        exit(1);
 }
 
-getopts("hivmkuo:d:p:C:z:s:M:") or usage();
+sub read_author_info($) {
+       my ($file) = @_;
+       my $user;
+       open my $f, '<', "$file" or die("Failed to open $file: $!\n");
+
+       while (<$f>) {
+               # Expected format is this:
+               #   exon=Andreas Ericsson <ae@op5.se>
+               if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
+                       $user = $1;
+                       $conv_author_name{$user} = $2;
+                       $conv_author_email{$user} = $3;
+               }
+               # However, we also read from CVSROOT/users format
+               # to ease migration.
+               elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
+                       my $mapped;
+                       ($user, $mapped) = ($1, $3);
+                       if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
+                               $conv_author_name{$user} = $1;
+                               $conv_author_email{$user} = $2;
+                       }
+                       elsif ($mapped =~ /^<?(.*)>?$/) {
+                               $conv_author_name{$user} = $user;
+                               $conv_author_email{$user} = $1;
+                       }
+               }
+               # NEEDSWORK: Maybe warn on unrecognized lines?
+       }
+       close ($f);
+}
+
+sub write_author_info($) {
+       my ($file) = @_;
+       open my $f, '>', $file or
+         die("Failed to open $file for writing: $!");
+
+       foreach (keys %conv_author_name) {
+               print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>\n";
+       }
+       close ($f);
+}
+
+getopts("hivmkuo:d:p:C:z:s:M:P:A:S:") or usage();
 usage if $opt_h;
 
 @ARGV <= 1 or usage();
@@ -317,6 +361,7 @@ sub _line {
                        }
                }
        }
+       return undef;
 }
 sub file {
        my($self,$fn,$rev) = @_;
@@ -328,12 +373,12 @@ sub file {
        $self->_file($fn,$rev) and $res = $self->_line($fh);
 
        if (!defined $res) {
-           # retry
+           print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
+           truncate $fh, 0;
            $self->conn();
-           $self->_file($fn,$rev)
-                   or die "No file command send\n";
+           $self->_file($fn,$rev) or die "No file command send";
            $res = $self->_line($fh);
-           die "No input: $fn $rev\n" unless defined $res;
+           die "Retry failed" unless defined $res;
        }
        close ($fh);
 
@@ -407,8 +452,8 @@ ($$)
 
 my $last_branch = "";
 my $orig_branch = "";
-my $forward_master = 0;
 my %branch_date;
+my $tip_at_start = undef;
 
 my $git_dir = $ENV{"GIT_DIR"} || ".git";
 $git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
@@ -433,27 +478,17 @@ ($$)
                       "Either use the correct '-o branch' option,\n".
                       "or import to a new repository.\n";
 
-       $last_branch = basename(readlink("$git_dir/HEAD"));
+       open(F, "git-symbolic-ref HEAD |") or
+               die "Cannot run git-symbolic-ref: $!\n";
+       chomp ($last_branch = <F>);
+       $last_branch = basename($last_branch);
+       close(F);
        unless($last_branch) {
                warn "Cannot read the last branch name: $! -- assuming 'master'\n";
                $last_branch = "master";
        }
        $orig_branch = $last_branch;
-       if (-f "$git_dir/CVS2GIT_HEAD") {
-               die <<EOM;
-CVS2GIT_HEAD exists.
-Make sure your working directory corresponds to HEAD and remove CVS2GIT_HEAD.
-You may need to run
-
-    git-read-tree -m -u CVS2GIT_HEAD HEAD
-EOM
-       }
-       system('cp', "$git_dir/HEAD", "$git_dir/CVS2GIT_HEAD");
-
-       $forward_master =
-           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
-           system('cmp', '-s', "$git_dir/refs/heads/master", 
-                               "$git_dir/refs/heads/$opt_o") == 0;
+       $tip_at_start = `git-rev-parse --verify HEAD`;
 
        # populate index
        system('git-read-tree', $last_branch);
@@ -481,17 +516,30 @@ ($$)
 -d $git_dir
        or die "Could not create git subdir ($git_dir).\n";
 
+# now we read (and possibly save) author-info as well
+-f "$git_dir/cvs-authors" and
+  read_author_info("$git_dir/cvs-authors");
+if ($opt_A) {
+       read_author_info($opt_A);
+       write_author_info("$git_dir/cvs-authors");
+}
+
 my $pid = open(CVS,"-|");
 die "Cannot fork: $!\n" unless defined $pid;
 unless($pid) {
        my @opt;
        @opt = split(/,/,$opt_p) if defined $opt_p;
        unshift @opt, '-z', $opt_z if defined $opt_z;
+       unshift @opt, '-q'         unless defined $opt_v;
        unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
                push @opt, '--cvs-direct';
        }
-       exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
-       die "Could not start cvsps: $!\n";
+       if ($opt_P) {
+           exec("cat", $opt_P);
+       } else {
+           exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+           die "Could not start cvsps: $!\n";
+       }
 }
 
 
@@ -514,7 +562,7 @@ ($$)
 my $state = 0;
 
 my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new);
+my(@old,@new,@skipped);
 my $commit = sub {
        my $pid;
        while(@old) {
@@ -610,6 +658,11 @@ ($$)
        substr($logmsg,32767) = "" if length($logmsg) > 32767;
        $logmsg =~ s/[\s\n]+\z//;
 
+       if (@skipped) {
+           $logmsg .= "\n\n\nSKIPPED:\n\t";
+           $logmsg .= join("\n\t", @skipped) . "\n";
+       }
+
        print $pw "$logmsg\n"
                or die "Error writing to git-commit-tree: $!\n";
        $pw->close();
@@ -624,11 +677,7 @@ ($$)
        waitpid($pid,0);
        die "Error running git-commit-tree: $?\n" if $?;
 
-       open(C,">$git_dir/refs/heads/$branch")
-               or die "Cannot open branch $branch for update: $!\n";
-       print C "$cid\n"
-               or die "Cannot write branch $branch for update: $!\n";
-       close(C)
+       system("git-update-ref refs/heads/$branch $cid") == 0
                or die "Cannot write branch $branch for update: $!\n";
 
        if($tag) {
@@ -636,6 +685,7 @@ ($$)
                my($xtag) = $tag;
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
                $xtag =~ tr/_/\./ if ( $opt_u );
+               $xtag =~ s/[\/]/$opt_s/g;
                
                my $pid = open2($in, $out, 'git-mktag');
                print $out "object $cid\n".
@@ -688,6 +738,9 @@ ($$)
                s/\s+$//;
                if (/^(.*?)\s+<(.*)>/) {
                    ($author_name, $author_email) = ($1, $2);
+               } elsif ($conv_author_name{$_}) {
+                       $author_name = $conv_author_name{$_};
+                       $author_email = $conv_author_email{$_};
                } else {
                    $author_name = $author_email = $_;
                }
@@ -763,14 +816,24 @@ ($$)
                my $fn = $1;
                my $rev = $3;
                $fn =~ s#^/+##;
+               if ($opt_S && $fn =~ m/$opt_S/) {
+                   print "SKIPPING $fn v $rev\n";
+                   push(@skipped, $fn);
+                   next;
+               }
+               print "Fetching $fn   v $rev\n" if $opt_v;
                my ($tmpname, $size) = $cvs->file($fn,$rev);
                if($size == -1) {
                        push(@old,$fn);
                        print "Drop $fn\n" if $opt_v;
                } else {
                        print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
-                       open my $F, '-|', "git-hash-object -w $tmpname"
+                       my $pid = open(my $F, '-|');
+                       die $! unless defined $pid;
+                       if (!$pid) {
+                           exec("git-hash-object", "-w", $tmpname)
                                or die "Cannot create object: $!\n";
+                       }
                        my $sha = <$F>;
                        chomp $sha;
                        close $F;
@@ -808,23 +871,30 @@ ($$)
 
 # Now switch back to the branch we were in before all of this happened
 if($orig_branch) {
-       print "DONE\n" if $opt_v;
-       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
-               if $forward_master;
-       unless ($opt_i) {
-               system('git-read-tree', '-m', '-u', 'CVS2GIT_HEAD', 'HEAD');
-               die "read-tree failed: $?\n" if $?;
+       print "DONE.\n" if $opt_v;
+       if ($opt_i) {
+               exit 0;
+       }
+       my $tip_at_end = `git-rev-parse --verify HEAD`;
+       if ($tip_at_start ne $tip_at_end) {
+               for ($tip_at_start, $tip_at_end) { chomp; }
+               print "Fetched into the current branch.\n" if $opt_v;
+               system(qw(git-read-tree -u -m),
+                      $tip_at_start, $tip_at_end);
+               die "Fast-forward update failed: $?\n" if $?;
+       }
+       else {
+               system(qw(git-merge cvsimport HEAD), "refs/heads/$opt_o");
+               die "Could not merge $opt_o into the current branch.\n" if $?;
        }
 } else {
        $orig_branch = "master";
        print "DONE; creating $orig_branch branch\n" if $opt_v;
-       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+       system("git-update-ref", "refs/heads/master", "refs/heads/$opt_o")
                unless -f "$git_dir/refs/heads/master";
-       unlink("$git_dir/HEAD");
-       symlink("refs/heads/$orig_branch","$git_dir/HEAD");
+       system('git-update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
                system('git checkout');
                die "checkout failed: $?\n" if $?;
        }
 }
-unlink("$git_dir/CVS2GIT_HEAD");