cvsimport: set up commit environment in perl instead of using env
[gitweb.git] / git-cvsimport.perl
index b46469ab32fa5707c8056c5a32db61d7c268e50b..af331d9c4316a56db7bd4b3cfb4f1d5d8d5f6de5 100755 (executable)
 use Time::Local;
 use IO::Socket;
 use IO::Pipe;
-use POSIX qw(strftime dup2);
+use POSIX qw(strftime dup2 :errno_h);
 use IPC::Open2;
 
 $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_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S);
+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,$opt_L);
 my (%conv_author_name, %conv_author_email);
 
 sub usage() {
@@ -85,7 +85,7 @@ ($)
        close ($f);
 }
 
-getopts("hivmkuo:d:p:C:z:s:M:P:A:S:") or usage();
+getopts("hivmkuo:d:p:C:z:s:M:P:A:S:L:") or usage();
 usage if $opt_h;
 
 @ARGV <= 1 or usage();
@@ -315,15 +315,7 @@ sub _line {
                        chomp $cnt;
                        die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
                        $line="";
-                       $res=0;
-                       while($cnt) {
-                               my $buf;
-                               my $num = $self->{'socketi'}->read($buf,$cnt);
-                               die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
-                               print $fh $buf;
-                               $res += $num;
-                               $cnt -= $num;
-                       }
+                       $res = $self->_fetchfile($fh, $cnt);
                } elsif($line =~ s/^ //) {
                        print $fh $line;
                        $res += length($line);
@@ -335,14 +327,7 @@ sub _line {
                        chomp $cnt;
                        die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
                        $line="";
-                       while($cnt) {
-                               my $buf;
-                               my $num = $self->{'socketi'}->read($buf,$cnt);
-                               die "S: Mbinary $cnt: $num: $!\n" if not defined $num or $num<=0;
-                               print $fh $buf;
-                               $res += $num;
-                               $cnt -= $num;
-                       }
+                       $res += $self->_fetchfile($fh, $cnt);
                } else {
                        chomp $line;
                        if($line eq "ok") {
@@ -350,7 +335,7 @@ sub _line {
                                return $res;
                        } elsif($line =~ s/^E //) {
                                # print STDERR "S: $line\n";
-                       } elsif($line =~ /^Remove-entry /i) {
+                       } elsif($line =~ /^(Remove-entry|Removed) /i) {
                                $line = $self->readline(); # filename
                                $line = $self->readline(); # OK
                                chomp $line;
@@ -384,6 +369,23 @@ sub file {
 
        return ($name, $res);
 }
+sub _fetchfile {
+       my ($self, $fh, $cnt) = @_;
+       my $res = 0;
+       my $bufsize = 1024 * 1024;
+       while($cnt) {
+           if ($bufsize > $cnt) {
+               $bufsize = $cnt;
+           }
+           my $buf;
+           my $num = $self->{'socketi'}->read($buf,$bufsize);
+           die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+           print $fh $buf;
+           $res += $num;
+           $cnt -= $num;
+       }
+       return $res;
+}
 
 
 package main;
@@ -429,22 +431,25 @@ ()
        return $pwd;
 }
 
+sub is_sha1 {
+       my $s = shift;
+       return $s =~ /^[a-f0-9]{40}$/;
+}
 
-sub get_headref($$) {
+sub get_headref ($$) {
     my $name    = shift;
     my $git_dir = shift; 
-    my $sha;
     
-    if (open(C,"$git_dir/refs/heads/$name")) {
-       chomp($sha = <C>);
-       close(C);
-       length($sha) == 40
-           or die "Cannot get head id for $name ($sha): $!\n";
+    my $f = "$git_dir/refs/heads/$name";
+    if(open(my $fh, $f)) {
+           chomp(my $r = <$fh>);
+           is_sha1($r) or die "Cannot get head id for $name ($r): $!";
+           return $r;
     }
-    return $sha;
+    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
+    return undef;
 }
 
-
 -d $git_tree
        or mkdir($git_tree,0777)
        or die "Could not create $git_tree: $!";
@@ -452,8 +457,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#^/#;
@@ -488,21 +493,7 @@ ($$)
                $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);
@@ -575,98 +566,66 @@ ($$)
 
 my $state = 0;
 
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped);
-my $commit = sub {
-       my $pid;
-       while(@old) {
-               my @o2;
-               if(@old > 55) {
-                       @o2 = splice(@old,0,50);
-               } else {
-                       @o2 = @old;
-                       @old = ();
-               }
-               system("git-update-index","--force-remove","--",@o2);
-               die "Cannot remove files: $?\n" if $?;
-       }
-       while(@new) {
-               my @n2;
-               if(@new > 12) {
-                       @n2 = splice(@new,0,10);
-               } else {
-                       @n2 = @new;
-                       @new = ();
-               }
-               system("git-update-index","--add",
-                       (map { ('--cacheinfo', @$_) } @n2));
-               die "Cannot add files: $?\n" if $?;
-       }
+sub update_index (\@\@) {
+       my $old = shift;
+       my $new = shift;
+       open(my $fh, '|-', qw(git-update-index -z --index-info))
+               or die "unable to open git-update-index: $!";
+       print $fh
+               (map { "0 0000000000000000000000000000000000000000\t$_\0" }
+                       @$old),
+               (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
+                       @$new)
+               or die "unable to write to git-update-index: $!";
+       close $fh
+               or die "unable to write to git-update-index: $!";
+       $? and die "git-update-index reported error: $?";
+}
 
-       $pid = open(C,"-|");
-       die "Cannot fork: $!" unless defined $pid;
-       unless($pid) {
-               exec("git-write-tree");
-               die "Cannot exec git-write-tree: $!\n";
-       }
-       chomp(my $tree = <C>);
-       length($tree) == 40
-               or die "Cannot get tree id ($tree): $!\n";
-       close(C)
+sub write_tree () {
+       open(my $fh, '-|', qw(git-write-tree))
+               or die "unable to open git-write-tree: $!";
+       chomp(my $tree = <$fh>);
+       is_sha1($tree)
+               or die "Cannot get tree id ($tree): $!";
+       close($fh)
                or die "Error running git-write-tree: $?\n";
        print "Tree ID $tree\n" if $opt_v;
+       return $tree;
+}
 
-       my $parent = "";
-       if(open(C,"$git_dir/refs/heads/$last_branch")) {
-               chomp($parent = <C>);
-               close(C);
-               length($parent) == 40
-                       or die "Cannot get parent id ($parent): $!\n";
-               print "Parent ID $parent\n" if $opt_v;
-       }
-
-       my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-       my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-       $pid = fork();
-       die "Fork: $!\n" unless defined $pid;
-       unless($pid) {
-               $pr->writer();
-               $pw->reader();
-               open(OUT,">&STDOUT");
-               dup2($pw->fileno(),0);
-               dup2($pr->fileno(),1);
-               $pr->close();
-               $pw->close();
-
-               my @par = ();
-               @par = ("-p",$parent) if $parent;
-
-               # loose detection of merges
-               # based on the commit msg
-               foreach my $rx (@mergerx) {
-                       if ($logmsg =~ $rx) {
-                               my $mparent = $1;
-                               if ($mparent eq 'HEAD') { $mparent = $opt_o };
-                               if ( -e "$git_dir/refs/heads/$mparent") {
-                                       $mparent = get_headref($mparent, $git_dir);
-                                       push @par, '-p', $mparent;
-                                       print OUT "Merge parent branch: $mparent\n" if $opt_v;
-                               }
-                       }
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new,@skipped);
+sub commit {
+       update_index(@old, @new);
+       @old = @new = ();
+       my $tree = write_tree();
+       my $parent = get_headref($last_branch, $git_dir);
+       print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
+
+       my @commit_args;
+       push @commit_args, ("-p", $parent) if $parent;
+
+       # loose detection of merges
+       # based on the commit msg
+       foreach my $rx (@mergerx) {
+               next unless $logmsg =~ $rx && $1;
+               my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
+               if(my $sha1 = get_headref($mparent, $git_dir)) {
+                       push @commit_args, '-p', $mparent;
+                       print "Merge parent branch: $mparent\n" if $opt_v;
                }
-
-               exec("env",
-                       "GIT_AUTHOR_NAME=$author_name",
-                       "GIT_AUTHOR_EMAIL=$author_email",
-                       "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                       "GIT_COMMITTER_NAME=$author_name",
-                       "GIT_COMMITTER_EMAIL=$author_email",
-                       "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                       "git-commit-tree", $tree,@par);
-               die "Cannot exec git-commit-tree: $!\n";
        }
-       $pw->writer();
-       $pr->reader();
+
+       my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date));
+       $ENV{GIT_AUTHOR_NAME} = $author_name;
+       $ENV{GIT_AUTHOR_EMAIL} = $author_email;
+       $ENV{GIT_AUTHOR_DATE} = $commit_date;
+       $ENV{GIT_COMMITTER_NAME} = $author_name;
+       $ENV{GIT_COMMITTER_EMAIL} = $author_email;
+       $ENV{GIT_COMMITTER_DATE} = $commit_date;
+       my $pid = open2(my $commit_read, my $commit_write,
+               'git-commit-tree', $tree, @commit_args);
 
        # compatibility with git2cvs
        substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -675,27 +634,22 @@ ($$)
        if (@skipped) {
            $logmsg .= "\n\n\nSKIPPED:\n\t";
            $logmsg .= join("\n\t", @skipped) . "\n";
+           @skipped = ();
        }
 
-       print $pw "$logmsg\n"
+       print($commit_write "$logmsg\n") && close($commit_write)
                or die "Error writing to git-commit-tree: $!\n";
-       $pw->close();
 
-       print "Committed patch $patchset ($branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
-       chomp(my $cid = <$pr>);
-       length($cid) == 40
-               or die "Cannot get commit id ($cid): $!\n";
+       print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
+       chomp(my $cid = <$commit_read>);
+       is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
        print "Commit ID $cid\n" if $opt_v;
-       $pr->close();
+       close($commit_read);
 
        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) {
@@ -734,6 +688,7 @@ ($$)
        }
 };
 
+my $commitcount = 1;
 while(<CVS>) {
        chomp;
        if($state == 0 and /^-+$/) {
@@ -867,7 +822,14 @@ ($$)
        } elsif($state == 9 and /^\s*$/) {
                $state = 10;
        } elsif(($state == 9 or $state == 10) and /^-+$/) {
-               &$commit();
+               $commitcount++;
+               if ($opt_L && $commitcount > $opt_L) {
+                       last;
+               }
+               commit();
+               if (($commitcount & 1023) == 0) {
+                       system("git repack -a -d");
+               }
                $state = 1;
        } elsif($state == 11 and /^-+$/) {
                $state = 1;
@@ -877,7 +839,7 @@ ($$)
                print "* UNKNOWN LINE * $_\n";
        }
 }
-&$commit() if $branch and $state != 11;
+commit() if $branch and $state != 11;
 
 unlink($git_index);
 
@@ -889,17 +851,26 @@ ($$)
 
 # 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";
        system('git-update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
@@ -907,4 +878,3 @@ ($$)
                die "checkout failed: $?\n" if $?;
        }
 }
-unlink("$git_dir/CVS2GIT_HEAD");