The second batch
[git] / git-cvsimport.perl
1 #!/usr/bin/perl
2
3 # This tool is copyright (c) 2005, Matthias Urlichs.
4 # It is released under the Gnu Public License, version 2.
5 #
6 # The basic idea is to aggregate CVS check-ins into related changes.
7 # Fortunately, "cvsps" does that for us; all we have to do is to parse
8 # its output.
9 #
10 # Checking out the files is done by a single long-running CVS connection
11 # / server process.
12 #
13 # The head revision is on branch "origin" by default.
14 # You can change that with the '-o' option.
15
16 use 5.008;
17 use strict;
18 use warnings;
19 use Getopt::Long;
20 use File::Spec;
21 use File::Temp qw(tempfile tmpnam);
22 use File::Path qw(mkpath);
23 use File::Basename qw(basename dirname);
24 use Time::Local;
25 use IO::Socket;
26 use IO::Pipe;
27 use POSIX qw(strftime tzset dup2 ENOENT);
28 use IPC::Open2;
29 use Git qw(get_tz_offset);
30
31 $SIG{'PIPE'}="IGNORE";
32 set_timezone('UTC');
33
34 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, $opt_a, $opt_r, $opt_R);
35 my (%conv_author_name, %conv_author_email, %conv_author_tz);
36
37 sub usage(;$) {
38         my $msg = shift;
39         print(STDERR "Error: $msg\n") if $msg;
40         print STDERR <<END;
41 usage: git cvsimport     # fetch/update GIT from CVS
42        [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
43        [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
44        [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
45        [-r remote] [-R] [CVS_module]
46 END
47         exit(1);
48 }
49
50 sub read_author_info($) {
51         my ($file) = @_;
52         my $user;
53         open my $f, '<', "$file" or die("Failed to open $file: $!\n");
54
55         while (<$f>) {
56                 # Expected format is this:
57                 #   exon=Andreas Ericsson <ae@op5.se>
58                 if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
59                         $user = $1;
60                         $conv_author_name{$user} = $2;
61                         $conv_author_email{$user} = $3;
62                 }
63                 # or with an optional timezone:
64                 #   spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
65                 elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) {
66                         $user = $1;
67                         $conv_author_name{$user} = $2;
68                         $conv_author_email{$user} = $3;
69                         $conv_author_tz{$user} = $4;
70                 }
71                 # However, we also read from CVSROOT/users format
72                 # to ease migration.
73                 elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
74                         my $mapped;
75                         ($user, $mapped) = ($1, $3);
76                         if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
77                                 $conv_author_name{$user} = $1;
78                                 $conv_author_email{$user} = $2;
79                         }
80                         elsif ($mapped =~ /^<?(.*)>?$/) {
81                                 $conv_author_name{$user} = $user;
82                                 $conv_author_email{$user} = $1;
83                         }
84                 }
85                 # NEEDSWORK: Maybe warn on unrecognized lines?
86         }
87         close ($f);
88 }
89
90 sub write_author_info($) {
91         my ($file) = @_;
92         open my $f, '>', $file or
93           die("Failed to open $file for writing: $!");
94
95         foreach (keys %conv_author_name) {
96                 print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>";
97                 print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_});
98                 print $f "\n";
99         }
100         close ($f);
101 }
102
103 # Versions of perl before 5.10.0 may not automatically check $TZ each
104 # time localtime is run (most platforms will do so only the first time).
105 # We can work around this by using tzset() to update the internal
106 # variable whenever we change the environment.
107 sub set_timezone {
108         $ENV{TZ} = shift;
109         tzset();
110 }
111
112 # convert getopts specs for use by git config
113 my %longmap = (
114         'A:' => 'authors-file',
115         'M:' => 'merge-regex',
116         'P:' => undef,
117         'R' => 'track-revisions',
118         'S:' => 'ignore-paths',
119 );
120
121 sub read_repo_config {
122         # Split the string between characters, unless there is a ':'
123         # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
124         my @opts = split(/ *(?!:)/, shift);
125         foreach my $o (@opts) {
126                 my $key = $o;
127                 $key =~ s/://g;
128                 my $arg = 'git config';
129                 $arg .= ' --bool' if ($o !~ /:$/);
130                 my $ckey = $key;
131
132                 if (exists $longmap{$o}) {
133                         # An uppercase option like -R cannot be
134                         # expressed in the configuration, as the
135                         # variable names are downcased.
136                         $ckey = $longmap{$o};
137                         next if (! defined $ckey);
138                         $ckey =~ s/-//g;
139                 }
140                 chomp(my $tmp = `$arg --get cvsimport.$ckey`);
141                 if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
142                         no strict 'refs';
143                         my $opt_name = "opt_" . $key;
144                         if (!$$opt_name) {
145                                 $$opt_name = $tmp;
146                         }
147                 }
148         }
149 }
150
151 my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
152 read_repo_config($opts);
153 Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
154
155 # turn the Getopt::Std specification in a Getopt::Long one,
156 # with support for multiple -M options
157 GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
158     or usage();
159 usage if $opt_h;
160
161 if (@ARGV == 0) {
162                 chomp(my $module = `git config --get cvsimport.module`);
163                 push(@ARGV, $module) if $? == 0;
164 }
165 @ARGV <= 1 or usage("You can't specify more than one CVS module");
166
167 if ($opt_d) {
168         $ENV{"CVSROOT"} = $opt_d;
169 } elsif (-f 'CVS/Root') {
170         open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
171         $opt_d = <$f>;
172         chomp $opt_d;
173         close $f;
174         $ENV{"CVSROOT"} = $opt_d;
175 } elsif ($ENV{"CVSROOT"}) {
176         $opt_d = $ENV{"CVSROOT"};
177 } else {
178         usage("CVSROOT needs to be set");
179 }
180 $opt_s ||= "-";
181 $opt_a ||= 0;
182
183 my $git_tree = $opt_C;
184 $git_tree ||= ".";
185
186 my $remote;
187 if (defined $opt_r) {
188         $remote = 'refs/remotes/' . $opt_r;
189         $opt_o ||= "master";
190 } else {
191         $opt_o ||= "origin";
192         $remote = 'refs/heads';
193 }
194
195 my $cvs_tree;
196 if ($#ARGV == 0) {
197         $cvs_tree = $ARGV[0];
198 } elsif (-f 'CVS/Repository') {
199         open my $f, '<', 'CVS/Repository' or
200             die 'Failed to open CVS/Repository';
201         $cvs_tree = <$f>;
202         chomp $cvs_tree;
203         close $f;
204 } else {
205         usage("CVS module has to be specified");
206 }
207
208 our @mergerx = ();
209 if ($opt_m) {
210         @mergerx = ( qr/\b(?:from|of|merge|merging|merged) ([-\w]+)/i );
211 }
212 if (@opt_M) {
213         push (@mergerx, map { qr/$_/ } @opt_M);
214 }
215
216 # Remember UTC of our starting time
217 # we'll want to avoid importing commits
218 # that are too recent
219 our $starttime = time();
220
221 select(STDERR); $|=1; select(STDOUT);
222
223
224 package CVSconn;
225 # Basic CVS dialog.
226 # We're only interested in connecting and downloading, so ...
227
228 use File::Spec;
229 use File::Temp qw(tempfile);
230 use POSIX qw(strftime dup2);
231
232 sub new {
233         my ($what,$repo,$subdir) = @_;
234         $what=ref($what) if ref($what);
235
236         my $self = {};
237         $self->{'buffer'} = "";
238         bless($self,$what);
239
240         $repo =~ s#/+$##;
241         $self->{'fullrep'} = $repo;
242         $self->conn();
243
244         $self->{'subdir'} = $subdir;
245         $self->{'lines'} = undef;
246
247         return $self;
248 }
249
250 sub find_password_entry {
251         my ($cvspass, @cvsroot) = @_;
252         my ($file, $delim) = @$cvspass;
253         my $pass;
254         local ($_);
255
256         if (open(my $fh, $file)) {
257                 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
258                 CVSPASSFILE:
259                 while (<$fh>) {
260                         chomp;
261                         s/^\/\d+\s+//;
262                         my ($w, $p) = split($delim,$_,2);
263                         for my $cvsroot (@cvsroot) {
264                                 if ($w eq $cvsroot) {
265                                         $pass = $p;
266                                         last CVSPASSFILE;
267                                 }
268                         }
269                 }
270                 close($fh);
271         }
272         return $pass;
273 }
274
275 sub conn {
276         my $self = shift;
277         my $repo = $self->{'fullrep'};
278         if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
279                 my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
280
281                 my ($proxyhost,$proxyport);
282                 if ($param && ($param =~ m/proxy=([^;]+)/)) {
283                         $proxyhost = $1;
284                         # Default proxyport, if not specified, is 8080.
285                         $proxyport = 8080;
286                         if ($ENV{"CVS_PROXY_PORT"}) {
287                                 $proxyport = $ENV{"CVS_PROXY_PORT"};
288                         }
289                         if ($param =~ m/proxyport=([^;]+)/) {
290                                 $proxyport = $1;
291                         }
292                 }
293                 $repo ||= '/';
294
295                 # if username is not explicit in CVSROOT, then use current user, as cvs would
296                 $user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
297                 my $rr2 = "-";
298                 unless ($port) {
299                         $rr2 = ":pserver:$user\@$serv:$repo";
300                         $port=2401;
301                 }
302                 my $rr = ":pserver:$user\@$serv:$port$repo";
303
304                 if ($pass) {
305                         $pass = $self->_scramble($pass);
306                 } else {
307                         my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/],
308                                        [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]);
309                         my @loc = ();
310                         foreach my $cvspass (@cvspass) {
311                                 my $p = find_password_entry($cvspass, $rr, $rr2);
312                                 if ($p) {
313                                         push @loc, $cvspass->[0];
314                                         $pass = $p;
315                                 }
316                         }
317
318                         if (1 < @loc) {
319                                 die("Multiple cvs password files have ".
320                                     "entries for CVSROOT $opt_d: @loc");
321                         } elsif (!$pass) {
322                                 $pass = "A";
323                         }
324                 }
325
326                 my ($s, $rep);
327                 if ($proxyhost) {
328
329                         # Use a HTTP Proxy. Only works for HTTP proxies that
330                         # don't require user authentication
331                         #
332                         # See: http://www.ietf.org/rfc/rfc2817.txt
333
334                         $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
335                         die "Socket to $proxyhost: $!\n" unless defined $s;
336                         $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
337                                 or die "Write to $proxyhost: $!\n";
338                         $s->flush();
339
340                         $rep = <$s>;
341
342                         # The answer should look like 'HTTP/1.x 2yy ....'
343                         if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
344                                 die "Proxy connect: $rep\n";
345                         }
346                         # Skip up to the empty line of the proxy server output
347                         # including the response headers.
348                         while ($rep = <$s>) {
349                                 last if (!defined $rep ||
350                                          $rep eq "\n" ||
351                                          $rep eq "\r\n");
352                         }
353                 } else {
354                         $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
355                         die "Socket to $serv: $!\n" unless defined $s;
356                 }
357
358                 $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
359                         or die "Write to $serv: $!\n";
360                 $s->flush();
361
362                 $rep = <$s>;
363
364                 if ($rep ne "I LOVE YOU\n") {
365                         $rep="<unknown>" unless $rep;
366                         die "AuthReply: $rep\n";
367                 }
368                 $self->{'socketo'} = $s;
369                 $self->{'socketi'} = $s;
370         } else { # local or ext: Fork off our own cvs server.
371                 my $pr = IO::Pipe->new();
372                 my $pw = IO::Pipe->new();
373                 my $pid = fork();
374                 die "Fork: $!\n" unless defined $pid;
375                 my $cvs = 'cvs';
376                 $cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
377                 my $rsh = 'rsh';
378                 $rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
379
380                 my @cvs = ($cvs, 'server');
381                 my ($local, $user, $host);
382                 $local = $repo =~ s/:local://;
383                 if (!$local) {
384                     $repo =~ s/:ext://;
385                     $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
386                     ($user, $host) = ($1, $2);
387                 }
388                 if (!$local) {
389                     if ($user) {
390                         unshift @cvs, $rsh, '-l', $user, $host;
391                     } else {
392                         unshift @cvs, $rsh, $host;
393                     }
394                 }
395
396                 unless ($pid) {
397                         $pr->writer();
398                         $pw->reader();
399                         dup2($pw->fileno(),0);
400                         dup2($pr->fileno(),1);
401                         $pr->close();
402                         $pw->close();
403                         exec(@cvs);
404                 }
405                 $pw->writer();
406                 $pr->reader();
407                 $self->{'socketo'} = $pw;
408                 $self->{'socketi'} = $pr;
409         }
410         $self->{'socketo'}->write("Root $repo\n");
411
412         # Trial and error says that this probably is the minimum set
413         $self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
414
415         $self->{'socketo'}->write("valid-requests\n");
416         $self->{'socketo'}->flush();
417
418         my $rep=$self->readline();
419         die "Failed to read from server" unless defined $rep;
420         chomp($rep);
421         if ($rep !~ s/^Valid-requests\s*//) {
422                 $rep="<unknown>" unless $rep;
423                 die "Expected Valid-requests from server, but got: $rep\n";
424         }
425         chomp(my $res=$self->readline());
426         die "validReply: $res\n" if $res ne "ok";
427
428         $self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
429         $self->{'repo'} = $repo;
430 }
431
432 sub readline {
433         my ($self) = @_;
434         return $self->{'socketi'}->getline();
435 }
436
437 sub _file {
438         # Request a file with a given revision.
439         # Trial and error says this is a good way to do it. :-/
440         my ($self,$fn,$rev) = @_;
441         $self->{'socketo'}->write("Argument -N\n") or return undef;
442         $self->{'socketo'}->write("Argument -P\n") or return undef;
443         # -kk: Linus' version doesn't use it - defaults to off
444         if ($opt_k) {
445             $self->{'socketo'}->write("Argument -kk\n") or return undef;
446         }
447         $self->{'socketo'}->write("Argument -r\n") or return undef;
448         $self->{'socketo'}->write("Argument $rev\n") or return undef;
449         $self->{'socketo'}->write("Argument --\n") or return undef;
450         $self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
451         $self->{'socketo'}->write("Directory .\n") or return undef;
452         $self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
453         # $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
454         $self->{'socketo'}->write("co\n") or return undef;
455         $self->{'socketo'}->flush() or return undef;
456         $self->{'lines'} = 0;
457         return 1;
458 }
459 sub _line {
460         # Read a line from the server.
461         # ... except that 'line' may be an entire file. ;-)
462         my ($self, $fh) = @_;
463         die "Not in lines" unless defined $self->{'lines'};
464
465         my $line;
466         my $res=0;
467         while (defined($line = $self->readline())) {
468                 # M U gnupg-cvs-rep/AUTHORS
469                 # Updated gnupg-cvs-rep/
470                 # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
471                 # /AUTHORS/1.1///T1.1
472                 # u=rw,g=rw,o=rw
473                 # 0
474                 # ok
475
476                 if ($line =~ s/^(?:Created|Updated) //) {
477                         $line = $self->readline(); # path
478                         $line = $self->readline(); # Entries line
479                         my $mode = $self->readline(); chomp $mode;
480                         $self->{'mode'} = $mode;
481                         defined (my $cnt = $self->readline())
482                                 or die "EOF from server after 'Changed'\n";
483                         chomp $cnt;
484                         die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
485                         $line="";
486                         $res = $self->_fetchfile($fh, $cnt);
487                 } elsif ($line =~ s/^ //) {
488                         print $fh $line;
489                         $res += length($line);
490                 } elsif ($line =~ /^M\b/) {
491                         # output, do nothing
492                 } elsif ($line =~ /^Mbinary\b/) {
493                         my $cnt;
494                         die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
495                         chomp $cnt;
496                         die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
497                         $line="";
498                         $res += $self->_fetchfile($fh, $cnt);
499                 } else {
500                         chomp $line;
501                         if ($line eq "ok") {
502                                 # print STDERR "S: ok (".length($res).")\n";
503                                 return $res;
504                         } elsif ($line =~ s/^E //) {
505                                 # print STDERR "S: $line\n";
506                         } elsif ($line =~ /^(Remove-entry|Removed) /i) {
507                                 $line = $self->readline(); # filename
508                                 $line = $self->readline(); # OK
509                                 chomp $line;
510                                 die "Unknown: $line" if $line ne "ok";
511                                 return -1;
512                         } else {
513                                 die "Unknown: $line\n";
514                         }
515                 }
516         }
517         return undef;
518 }
519 sub file {
520         my ($self,$fn,$rev) = @_;
521         my $res;
522
523         my ($fh, $name) = tempfile('gitcvs.XXXXXX',
524                     DIR => File::Spec->tmpdir(), UNLINK => 1);
525
526         $self->_file($fn,$rev) and $res = $self->_line($fh);
527
528         if (!defined $res) {
529             print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
530             truncate $fh, 0;
531             $self->conn();
532             $self->_file($fn,$rev) or die "No file command send";
533             $res = $self->_line($fh);
534             die "Retry failed" unless defined $res;
535         }
536         close ($fh);
537
538         return ($name, $res);
539 }
540 sub _fetchfile {
541         my ($self, $fh, $cnt) = @_;
542         my $res = 0;
543         my $bufsize = 1024 * 1024;
544         while ($cnt) {
545             if ($bufsize > $cnt) {
546                 $bufsize = $cnt;
547             }
548             my $buf;
549             my $num = $self->{'socketi'}->read($buf,$bufsize);
550             die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
551             print $fh $buf;
552             $res += $num;
553             $cnt -= $num;
554         }
555         return $res;
556 }
557
558 sub _scramble {
559         my ($self, $pass) = @_;
560         my $scrambled = "A";
561
562         return $scrambled unless $pass;
563
564         my $pass_len = length($pass);
565         my @pass_arr = split("", $pass);
566         my $i;
567
568         # from cvs/src/scramble.c
569         my @shifts = (
570                   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
571                  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
572                 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
573                 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
574                  41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
575                 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
576                  36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
577                  58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
578                 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
579                 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
580                 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
581                 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
582                 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
583                 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
584                 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
585                 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
586         );
587
588         for ($i = 0; $i < $pass_len; $i++) {
589                 $scrambled .= pack("C", $shifts[ord($pass_arr[$i])]);
590         }
591
592         return $scrambled;
593 }
594
595 package main;
596
597 my $cvs = CVSconn->new($opt_d, $cvs_tree);
598
599
600 sub pdate($) {
601         my ($d) = @_;
602         m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
603                 or die "Unparsable date: $d\n";
604         my $y=$1;
605         $y+=100 if $y<70;
606         $y+=1900 if $y<1000;
607         return timegm($6||0,$5,$4,$3,$2-1,$y);
608 }
609
610 sub pmode($) {
611         my ($mode) = @_;
612         my $m = 0;
613         my $mm = 0;
614         my $um = 0;
615         for my $x(split(//,$mode)) {
616                 if ($x eq ",") {
617                         $m |= $mm&$um;
618                         $mm = 0;
619                         $um = 0;
620                 } elsif ($x eq "u") { $um |= 0700;
621                 } elsif ($x eq "g") { $um |= 0070;
622                 } elsif ($x eq "o") { $um |= 0007;
623                 } elsif ($x eq "r") { $mm |= 0444;
624                 } elsif ($x eq "w") { $mm |= 0222;
625                 } elsif ($x eq "x") { $mm |= 0111;
626                 } elsif ($x eq "=") { # do nothing
627                 } else { die "Unknown mode: $mode\n";
628                 }
629         }
630         $m |= $mm&$um;
631         return $m;
632 }
633
634 sub getwd() {
635         my $pwd = `pwd`;
636         chomp $pwd;
637         return $pwd;
638 }
639
640 sub is_oid {
641         my $s = shift;
642         return $s =~ /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
643 }
644
645 sub get_headref ($) {
646         my $name = shift;
647         $name =~ s/'/'\\''/g;
648         my $r = `git rev-parse --verify '$name' 2>/dev/null`;
649         return undef unless $? == 0;
650         chomp $r;
651         return $r;
652 }
653
654 my $user_filename_prepend = '';
655 sub munge_user_filename {
656         my $name = shift;
657         return File::Spec->file_name_is_absolute($name) ?
658                 $name :
659                 $user_filename_prepend . $name;
660 }
661
662 -d $git_tree
663         or mkdir($git_tree,0777)
664         or die "Could not create $git_tree: $!";
665 if ($git_tree ne '.') {
666         $user_filename_prepend = getwd() . '/';
667         chdir($git_tree);
668 }
669
670 my $last_branch = "";
671 my $orig_branch = "";
672 my %branch_date;
673 my $tip_at_start = undef;
674
675 my $git_dir = $ENV{"GIT_DIR"} || ".git";
676 $git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
677 $ENV{"GIT_DIR"} = $git_dir;
678 my $orig_git_index;
679 $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
680
681 my %index; # holds filenames of one index per branch
682
683 unless (-d $git_dir) {
684         system(qw(git init));
685         die "Cannot init the GIT db at $git_tree: $?\n" if $?;
686         system(qw(git read-tree --empty));
687         die "Cannot init an empty tree: $?\n" if $?;
688
689         $last_branch = $opt_o;
690         $orig_branch = "";
691 } else {
692         open(F, "-|", qw(git symbolic-ref HEAD)) or
693                 die "Cannot run git symbolic-ref: $!\n";
694         chomp ($last_branch = <F>);
695         $last_branch = basename($last_branch);
696         close(F);
697         unless ($last_branch) {
698                 warn "Cannot read the last branch name: $! -- assuming 'master'\n";
699                 $last_branch = "master";
700         }
701         $orig_branch = $last_branch;
702         $tip_at_start = `git rev-parse --verify HEAD`;
703
704         # Get the last import timestamps
705         my $fmt = '($ref, $author) = (%(refname), %(author));';
706         my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
707         open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
708         while (defined(my $entry = <H>)) {
709                 my ($ref, $author);
710                 eval($entry) || die "cannot eval refs list: $@";
711                 my ($head) = ($ref =~ m|^$remote/(.*)|);
712                 $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
713                 $branch_date{$head} = $1;
714         }
715         close(H);
716         if (!exists $branch_date{$opt_o}) {
717                 die "Branch '$opt_o' does not exist.\n".
718                        "Either use the correct '-o branch' option,\n".
719                        "or import to a new repository.\n";
720         }
721 }
722
723 -d $git_dir
724         or die "Could not create git subdir ($git_dir).\n";
725
726 # now we read (and possibly save) author-info as well
727 -f "$git_dir/cvs-authors" and
728   read_author_info("$git_dir/cvs-authors");
729 if ($opt_A) {
730         read_author_info(munge_user_filename($opt_A));
731         write_author_info("$git_dir/cvs-authors");
732 }
733
734 # open .git/cvs-revisions, if requested
735 open my $revision_map, '>>', "$git_dir/cvs-revisions"
736     or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
737         if defined $opt_R;
738
739
740 #
741 # run cvsps into a file unless we are getting
742 # it passed as a file via $opt_P
743 #
744 my $cvspsfile;
745 unless ($opt_P) {
746         print "Running cvsps...\n" if $opt_v;
747         my $pid = open(CVSPS,"-|");
748         my $cvspsfh;
749         die "Cannot fork: $!\n" unless defined $pid;
750         unless ($pid) {
751                 my @opt;
752                 @opt = split(/,/,$opt_p) if defined $opt_p;
753                 unshift @opt, '-z', $opt_z if defined $opt_z;
754                 unshift @opt, '-q'         unless defined $opt_v;
755                 unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
756                         push @opt, '--cvs-direct';
757                 }
758                 exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
759                 die "Could not start cvsps: $!\n";
760         }
761         ($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
762                                           DIR => File::Spec->tmpdir());
763         while (<CVSPS>) {
764             print $cvspsfh $_;
765         }
766         close CVSPS;
767         $? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
768         close $cvspsfh;
769 } else {
770         $cvspsfile = munge_user_filename($opt_P);
771 }
772
773 open(CVS, "<$cvspsfile") or die $!;
774
775 ## cvsps output:
776 #---------------------
777 #PatchSet 314
778 #Date: 1999/09/18 13:03:59
779 #Author: wkoch
780 #Branch: STABLE-BRANCH-1-0
781 #Ancestor branch: HEAD
782 #Tag: (none)
783 #Log:
784 #    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
785 #Members:
786 #       README:1.57->1.57.2.1
787 #       VERSION:1.96->1.96.2.1
788 #
789 #---------------------
790
791 my $state = 0;
792
793 sub update_index (\@\@) {
794         my $old = shift;
795         my $new = shift;
796         open(my $fh, '|-', qw(git update-index -z --index-info))
797                 or die "unable to open git update-index: $!";
798         print $fh
799                 (map { "0 0000000000000000000000000000000000000000\t$_\0" }
800                         @$old),
801                 (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
802                         @$new)
803                 or die "unable to write to git update-index: $!";
804         close $fh
805                 or die "unable to write to git update-index: $!";
806         $? and die "git update-index reported error: $?";
807 }
808
809 sub write_tree () {
810         open(my $fh, '-|', qw(git write-tree))
811                 or die "unable to open git write-tree: $!";
812         chomp(my $tree = <$fh>);
813         is_oid($tree)
814                 or die "Cannot get tree id ($tree): $!";
815         close($fh)
816                 or die "Error running git write-tree: $?\n";
817         print "Tree ID $tree\n" if $opt_v;
818         return $tree;
819 }
820
821 my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);
822 my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
823
824 # commits that cvsps cannot place anywhere...
825 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
826
827 sub commit {
828         if ($branch eq $opt_o && !$index{branch} &&
829                 !get_headref("$remote/$branch")) {
830             # looks like an initial commit
831             # use the index primed by git init
832             $ENV{GIT_INDEX_FILE} = "$git_dir/index";
833             $index{$branch} = "$git_dir/index";
834         } else {
835             # use an index per branch to speed up
836             # imports of projects with many branches
837             unless ($index{$branch}) {
838                 $index{$branch} = tmpnam();
839                 $ENV{GIT_INDEX_FILE} = $index{$branch};
840                 if ($ancestor) {
841                     system("git", "read-tree", "$remote/$ancestor");
842                 } else {
843                     system("git", "read-tree", "$remote/$branch");
844                 }
845                 die "read-tree failed: $?\n" if $?;
846             }
847         }
848         $ENV{GIT_INDEX_FILE} = $index{$branch};
849
850         update_index(@old, @new);
851         @old = @new = ();
852         my $tree = write_tree();
853         my $parent = get_headref("$remote/$last_branch");
854         print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
855
856         my @commit_args;
857         push @commit_args, ("-p", $parent) if $parent;
858
859         # loose detection of merges
860         # based on the commit msg
861         foreach my $rx (@mergerx) {
862                 next unless $logmsg =~ $rx && $1;
863                 my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
864                 if (my $sha1 = get_headref("$remote/$mparent")) {
865                         push @commit_args, '-p', "$remote/$mparent";
866                         print "Merge parent branch: $mparent\n" if $opt_v;
867                 }
868         }
869
870         set_timezone($author_tz);
871         # $date is in the seconds since epoch format
872         my $tz_offset = get_tz_offset($date);
873         my $commit_date = "$date $tz_offset";
874         set_timezone('UTC');
875         $ENV{GIT_AUTHOR_NAME} = $author_name;
876         $ENV{GIT_AUTHOR_EMAIL} = $author_email;
877         $ENV{GIT_AUTHOR_DATE} = $commit_date;
878         $ENV{GIT_COMMITTER_NAME} = $author_name;
879         $ENV{GIT_COMMITTER_EMAIL} = $author_email;
880         $ENV{GIT_COMMITTER_DATE} = $commit_date;
881         my $pid = open2(my $commit_read, my $commit_write,
882                 'git', 'commit-tree', $tree, @commit_args);
883
884         # compatibility with git2cvs
885         substr($logmsg,32767) = "" if length($logmsg) > 32767;
886         $logmsg =~ s/[\s\n]+\z//;
887
888         if (@skipped) {
889             $logmsg .= "\n\n\nSKIPPED:\n\t";
890             $logmsg .= join("\n\t", @skipped) . "\n";
891             @skipped = ();
892         }
893
894         print($commit_write "$logmsg\n") && close($commit_write)
895                 or die "Error writing to git commit-tree: $!\n";
896
897         print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
898         chomp(my $cid = <$commit_read>);
899         is_oid($cid) or die "Cannot get commit id ($cid): $!\n";
900         print "Commit ID $cid\n" if $opt_v;
901         close($commit_read);
902
903         waitpid($pid,0);
904         die "Error running git commit-tree: $?\n" if $?;
905
906         system('git' , 'update-ref', "$remote/$branch", $cid) == 0
907                 or die "Cannot write branch $branch for update: $!\n";
908
909         if ($revision_map) {
910                 print $revision_map "@$_ $cid\n" for @commit_revisions;
911         }
912         @commit_revisions = ();
913
914         if ($tag) {
915                 my ($xtag) = $tag;
916                 $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
917                 $xtag =~ tr/_/\./ if ( $opt_u );
918                 $xtag =~ s/[\/]/$opt_s/g;
919
920                 # See refs.c for these rules.
921                 # Tag cannot contain bad chars. (See bad_ref_char in refs.c.)
922                 $xtag =~ s/[ ~\^:\\\*\?\[]//g;
923                 # Other bad strings for tags:
924                 # (See check_refname_component in refs.c.)
925                 1 while $xtag =~ s/
926                         (?: \.\.        # Tag cannot contain '..'.
927                         |   \@\{        # Tag cannot contain '@{'.
928                         | ^ -           # Tag cannot begin with '-'.
929                         |   \.lock $    # Tag cannot end with '.lock'.
930                         | ^ \.          # Tag cannot begin...
931                         |   \. $        # ...or end with '.'
932                         )//xg;
933                 # Tag cannot be empty.
934                 if ($xtag eq '') {
935                         warn("warning: ignoring tag '$tag'",
936                         " with invalid tagname\n");
937                         return;
938                 }
939
940                 if (system('git' , 'tag', '-f', $xtag, $cid) != 0) {
941                         # We did our best to sanitize the tag, but still failed
942                         # for whatever reason. Bail out, and give the user
943                         # enough information to understand if/how we should
944                         # improve the translation in the future.
945                         if ($tag ne $xtag) {
946                                 print "Translated '$tag' tag to '$xtag'\n";
947                         }
948                         die "Cannot create tag $xtag: $!\n";
949                 }
950
951                 print "Created tag '$xtag' on '$branch'\n" if $opt_v;
952         }
953 };
954
955 my $commitcount = 1;
956 while (<CVS>) {
957         chomp;
958         if ($state == 0 and /^-+$/) {
959                 $state = 1;
960         } elsif ($state == 0) {
961                 $state = 1;
962                 redo;
963         } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
964                 $patchset = 0+$_;
965                 $state=2;
966         } elsif ($state == 2 and s/^Date:\s+//) {
967                 $date = pdate($_);
968                 unless ($date) {
969                         print STDERR "Could not parse date: $_\n";
970                         $state=0;
971                         next;
972                 }
973                 $state=3;
974         } elsif ($state == 3 and s/^Author:\s+//) {
975                 $author_tz = "UTC";
976                 s/\s+$//;
977                 if (/^(.*?)\s+<(.*)>/) {
978                     ($author_name, $author_email) = ($1, $2);
979                 } elsif ($conv_author_name{$_}) {
980                         $author_name = $conv_author_name{$_};
981                         $author_email = $conv_author_email{$_};
982                         $author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});
983                 } else {
984                     $author_name = $author_email = $_;
985                 }
986                 $state = 4;
987         } elsif ($state == 4 and s/^Branch:\s+//) {
988                 s/\s+$//;
989                 tr/_/\./ if ( $opt_u );
990                 s/[\/]/$opt_s/g;
991                 $branch = $_;
992                 $state = 5;
993         } elsif ($state == 5 and s/^Ancestor branch:\s+//) {
994                 s/\s+$//;
995                 $ancestor = $_;
996                 $ancestor = $opt_o if $ancestor eq "HEAD";
997                 $state = 6;
998         } elsif ($state == 5) {
999                 $ancestor = undef;
1000                 $state = 6;
1001                 redo;
1002         } elsif ($state == 6 and s/^Tag:\s+//) {
1003                 s/\s+$//;
1004                 if ($_ eq "(none)") {
1005                         $tag = undef;
1006                 } else {
1007                         $tag = $_;
1008                 }
1009                 $state = 7;
1010         } elsif ($state == 7 and /^Log:/) {
1011                 $logmsg = "";
1012                 $state = 8;
1013         } elsif ($state == 8 and /^Members:/) {
1014                 $branch = $opt_o if $branch eq "HEAD";
1015                 if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
1016                         # skip
1017                         print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
1018                         $state = 11;
1019                         next;
1020                 }
1021                 if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
1022                         # skip if the commit is too recent
1023                         # given that the cvsps default fuzz is 300s, we give ourselves another
1024                         # 300s just in case -- this also prevents skipping commits
1025                         # due to server clock drift
1026                         print "skip patchset $patchset: $date too recent\n" if $opt_v;
1027                         $state = 11;
1028                         next;
1029                 }
1030                 if (exists $ignorebranch{$branch}) {
1031                         print STDERR "Skipping $branch\n";
1032                         $state = 11;
1033                         next;
1034                 }
1035                 if ($ancestor) {
1036                         if ($ancestor eq $branch) {
1037                                 print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
1038                                 $ancestor = $opt_o;
1039                         }
1040                         if (defined get_headref("$remote/$branch")) {
1041                                 print STDERR "Branch $branch already exists!\n";
1042                                 $state=11;
1043                                 next;
1044                         }
1045                         my $id = get_headref("$remote/$ancestor");
1046                         if (!$id) {
1047                                 print STDERR "Branch $ancestor does not exist!\n";
1048                                 $ignorebranch{$branch} = 1;
1049                                 $state=11;
1050                                 next;
1051                         }
1052
1053                         system(qw(git update-ref -m cvsimport),
1054                                 "$remote/$branch", $id);
1055                         if($? != 0) {
1056                                 print STDERR "Could not create branch $branch\n";
1057                                 $ignorebranch{$branch} = 1;
1058                                 $state=11;
1059                                 next;
1060                         }
1061                 }
1062                 $last_branch = $branch if $branch ne $last_branch;
1063                 $state = 9;
1064         } elsif ($state == 8) {
1065                 $logmsg .= "$_\n";
1066         } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
1067 #       VERSION:1.96->1.96.2.1
1068                 my $init = ($2 eq "INITIAL");
1069                 my $fn = $1;
1070                 my $rev = $3;
1071                 $fn =~ s#^/+##;
1072                 if ($opt_S && $fn =~ m/$opt_S/) {
1073                     print "SKIPPING $fn v $rev\n";
1074                     push(@skipped, $fn);
1075                     next;
1076                 }
1077                 push @commit_revisions, [$fn, $rev];
1078                 print "Fetching $fn   v $rev\n" if $opt_v;
1079                 my ($tmpname, $size) = $cvs->file($fn,$rev);
1080                 if ($size == -1) {
1081                         push(@old,$fn);
1082                         print "Drop $fn\n" if $opt_v;
1083                 } else {
1084                         print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
1085                         my $pid = open(my $F, '-|');
1086                         die $! unless defined $pid;
1087                         if (!$pid) {
1088                             exec("git", "hash-object", "-w", $tmpname)
1089                                 or die "Cannot create object: $!\n";
1090                         }
1091                         my $sha = <$F>;
1092                         chomp $sha;
1093                         close $F;
1094                         my $mode = pmode($cvs->{'mode'});
1095                         push(@new,[$mode, $sha, $fn]); # may be resurrected!
1096                 }
1097                 unlink($tmpname);
1098         } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
1099                 my $fn = $1;
1100                 my $rev = $2;
1101                 $fn =~ s#^/+##;
1102                 push @commit_revisions, [$fn, $rev];
1103                 push(@old,$fn);
1104                 print "Delete $fn\n" if $opt_v;
1105         } elsif ($state == 9 and /^\s*$/) {
1106                 $state = 10;
1107         } elsif (($state == 9 or $state == 10) and /^-+$/) {
1108                 $commitcount++;
1109                 if ($opt_L && $commitcount > $opt_L) {
1110                         last;
1111                 }
1112                 commit();
1113                 if (($commitcount & 1023) == 0) {
1114                         system(qw(git repack -a -d));
1115                 }
1116                 $state = 1;
1117         } elsif ($state == 11 and /^-+$/) {
1118                 $state = 1;
1119         } elsif (/^-+$/) { # end of unknown-line processing
1120                 $state = 1;
1121         } elsif ($state != 11) { # ignore stuff when skipping
1122                 print STDERR "* UNKNOWN LINE * $_\n";
1123         }
1124 }
1125 commit() if $branch and $state != 11;
1126
1127 unless ($opt_P) {
1128         unlink($cvspsfile);
1129 }
1130
1131 # The heuristic of repacking every 1024 commits can leave a
1132 # lot of unpacked data.  If there is more than 1MB worth of
1133 # not-packed objects, repack once more.
1134 my $line = `git count-objects`;
1135 if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
1136   my ($n_objects, $kb) = ($1, $2);
1137   1024 < $kb
1138     and system(qw(git repack -a -d));
1139 }
1140
1141 foreach my $git_index (values %index) {
1142     if ($git_index ne "$git_dir/index") {
1143         unlink($git_index);
1144     }
1145 }
1146
1147 if (defined $orig_git_index) {
1148         $ENV{GIT_INDEX_FILE} = $orig_git_index;
1149 } else {
1150         delete $ENV{GIT_INDEX_FILE};
1151 }
1152
1153 # Now switch back to the branch we were in before all of this happened
1154 if ($orig_branch) {
1155         print "DONE.\n" if $opt_v;
1156         if ($opt_i) {
1157                 exit 0;
1158         }
1159         my $tip_at_end = `git rev-parse --verify HEAD`;
1160         if ($tip_at_start ne $tip_at_end) {
1161                 for ($tip_at_start, $tip_at_end) { chomp; }
1162                 print "Fetched into the current branch.\n" if $opt_v;
1163                 system(qw(git read-tree -u -m),
1164                        $tip_at_start, $tip_at_end);
1165                 die "Fast-forward update failed: $?\n" if $?;
1166         }
1167         else {
1168                 system(qw(git merge -m cvsimport), "$remote/$opt_o");
1169                 die "Could not merge $opt_o into the current branch.\n" if $?;
1170         }
1171 } else {
1172         $orig_branch = "master";
1173         print "DONE; creating $orig_branch branch\n" if $opt_v;
1174         system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
1175                 unless defined get_headref('refs/heads/master');
1176         system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
1177                 if ($opt_r && $opt_o ne 'HEAD');
1178         system('git', 'update-ref', 'HEAD', "$orig_branch");
1179         unless ($opt_i) {
1180                 system(qw(git checkout -f));
1181                 die "checkout failed: $?\n" if $?;
1182         }
1183 }