http: allow use of TLS 1.3
[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 "Unparseable date: $d\n";
604         my $y=$1; $y-=1900 if $y>1900;
605         return timegm($6||0,$5,$4,$3,$2-1,$y);
606 }
607
608 sub pmode($) {
609         my ($mode) = @_;
610         my $m = 0;
611         my $mm = 0;
612         my $um = 0;
613         for my $x(split(//,$mode)) {
614                 if ($x eq ",") {
615                         $m |= $mm&$um;
616                         $mm = 0;
617                         $um = 0;
618                 } elsif ($x eq "u") { $um |= 0700;
619                 } elsif ($x eq "g") { $um |= 0070;
620                 } elsif ($x eq "o") { $um |= 0007;
621                 } elsif ($x eq "r") { $mm |= 0444;
622                 } elsif ($x eq "w") { $mm |= 0222;
623                 } elsif ($x eq "x") { $mm |= 0111;
624                 } elsif ($x eq "=") { # do nothing
625                 } else { die "Unknown mode: $mode\n";
626                 }
627         }
628         $m |= $mm&$um;
629         return $m;
630 }
631
632 sub getwd() {
633         my $pwd = `pwd`;
634         chomp $pwd;
635         return $pwd;
636 }
637
638 sub is_sha1 {
639         my $s = shift;
640         return $s =~ /^[a-f0-9]{40}$/;
641 }
642
643 sub get_headref ($) {
644         my $name = shift;
645         $name =~ s/'/'\\''/g;
646         my $r = `git rev-parse --verify '$name' 2>/dev/null`;
647         return undef unless $? == 0;
648         chomp $r;
649         return $r;
650 }
651
652 my $user_filename_prepend = '';
653 sub munge_user_filename {
654         my $name = shift;
655         return File::Spec->file_name_is_absolute($name) ?
656                 $name :
657                 $user_filename_prepend . $name;
658 }
659
660 -d $git_tree
661         or mkdir($git_tree,0777)
662         or die "Could not create $git_tree: $!";
663 if ($git_tree ne '.') {
664         $user_filename_prepend = getwd() . '/';
665         chdir($git_tree);
666 }
667
668 my $last_branch = "";
669 my $orig_branch = "";
670 my %branch_date;
671 my $tip_at_start = undef;
672
673 my $git_dir = $ENV{"GIT_DIR"} || ".git";
674 $git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
675 $ENV{"GIT_DIR"} = $git_dir;
676 my $orig_git_index;
677 $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
678
679 my %index; # holds filenames of one index per branch
680
681 unless (-d $git_dir) {
682         system(qw(git init));
683         die "Cannot init the GIT db at $git_tree: $?\n" if $?;
684         system(qw(git read-tree --empty));
685         die "Cannot init an empty tree: $?\n" if $?;
686
687         $last_branch = $opt_o;
688         $orig_branch = "";
689 } else {
690         open(F, "-|", qw(git symbolic-ref HEAD)) or
691                 die "Cannot run git symbolic-ref: $!\n";
692         chomp ($last_branch = <F>);
693         $last_branch = basename($last_branch);
694         close(F);
695         unless ($last_branch) {
696                 warn "Cannot read the last branch name: $! -- assuming 'master'\n";
697                 $last_branch = "master";
698         }
699         $orig_branch = $last_branch;
700         $tip_at_start = `git rev-parse --verify HEAD`;
701
702         # Get the last import timestamps
703         my $fmt = '($ref, $author) = (%(refname), %(author));';
704         my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
705         open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
706         while (defined(my $entry = <H>)) {
707                 my ($ref, $author);
708                 eval($entry) || die "cannot eval refs list: $@";
709                 my ($head) = ($ref =~ m|^$remote/(.*)|);
710                 $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
711                 $branch_date{$head} = $1;
712         }
713         close(H);
714         if (!exists $branch_date{$opt_o}) {
715                 die "Branch '$opt_o' does not exist.\n".
716                        "Either use the correct '-o branch' option,\n".
717                        "or import to a new repository.\n";
718         }
719 }
720
721 -d $git_dir
722         or die "Could not create git subdir ($git_dir).\n";
723
724 # now we read (and possibly save) author-info as well
725 -f "$git_dir/cvs-authors" and
726   read_author_info("$git_dir/cvs-authors");
727 if ($opt_A) {
728         read_author_info(munge_user_filename($opt_A));
729         write_author_info("$git_dir/cvs-authors");
730 }
731
732 # open .git/cvs-revisions, if requested
733 open my $revision_map, '>>', "$git_dir/cvs-revisions"
734     or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
735         if defined $opt_R;
736
737
738 #
739 # run cvsps into a file unless we are getting
740 # it passed as a file via $opt_P
741 #
742 my $cvspsfile;
743 unless ($opt_P) {
744         print "Running cvsps...\n" if $opt_v;
745         my $pid = open(CVSPS,"-|");
746         my $cvspsfh;
747         die "Cannot fork: $!\n" unless defined $pid;
748         unless ($pid) {
749                 my @opt;
750                 @opt = split(/,/,$opt_p) if defined $opt_p;
751                 unshift @opt, '-z', $opt_z if defined $opt_z;
752                 unshift @opt, '-q'         unless defined $opt_v;
753                 unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
754                         push @opt, '--cvs-direct';
755                 }
756                 exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
757                 die "Could not start cvsps: $!\n";
758         }
759         ($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
760                                           DIR => File::Spec->tmpdir());
761         while (<CVSPS>) {
762             print $cvspsfh $_;
763         }
764         close CVSPS;
765         $? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
766         close $cvspsfh;
767 } else {
768         $cvspsfile = munge_user_filename($opt_P);
769 }
770
771 open(CVS, "<$cvspsfile") or die $!;
772
773 ## cvsps output:
774 #---------------------
775 #PatchSet 314
776 #Date: 1999/09/18 13:03:59
777 #Author: wkoch
778 #Branch: STABLE-BRANCH-1-0
779 #Ancestor branch: HEAD
780 #Tag: (none)
781 #Log:
782 #    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
783 #Members:
784 #       README:1.57->1.57.2.1
785 #       VERSION:1.96->1.96.2.1
786 #
787 #---------------------
788
789 my $state = 0;
790
791 sub update_index (\@\@) {
792         my $old = shift;
793         my $new = shift;
794         open(my $fh, '|-', qw(git update-index -z --index-info))
795                 or die "unable to open git update-index: $!";
796         print $fh
797                 (map { "0 0000000000000000000000000000000000000000\t$_\0" }
798                         @$old),
799                 (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
800                         @$new)
801                 or die "unable to write to git update-index: $!";
802         close $fh
803                 or die "unable to write to git update-index: $!";
804         $? and die "git update-index reported error: $?";
805 }
806
807 sub write_tree () {
808         open(my $fh, '-|', qw(git write-tree))
809                 or die "unable to open git write-tree: $!";
810         chomp(my $tree = <$fh>);
811         is_sha1($tree)
812                 or die "Cannot get tree id ($tree): $!";
813         close($fh)
814                 or die "Error running git write-tree: $?\n";
815         print "Tree ID $tree\n" if $opt_v;
816         return $tree;
817 }
818
819 my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);
820 my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
821
822 # commits that cvsps cannot place anywhere...
823 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
824
825 sub commit {
826         if ($branch eq $opt_o && !$index{branch} &&
827                 !get_headref("$remote/$branch")) {
828             # looks like an initial commit
829             # use the index primed by git init
830             $ENV{GIT_INDEX_FILE} = "$git_dir/index";
831             $index{$branch} = "$git_dir/index";
832         } else {
833             # use an index per branch to speed up
834             # imports of projects with many branches
835             unless ($index{$branch}) {
836                 $index{$branch} = tmpnam();
837                 $ENV{GIT_INDEX_FILE} = $index{$branch};
838                 if ($ancestor) {
839                     system("git", "read-tree", "$remote/$ancestor");
840                 } else {
841                     system("git", "read-tree", "$remote/$branch");
842                 }
843                 die "read-tree failed: $?\n" if $?;
844             }
845         }
846         $ENV{GIT_INDEX_FILE} = $index{$branch};
847
848         update_index(@old, @new);
849         @old = @new = ();
850         my $tree = write_tree();
851         my $parent = get_headref("$remote/$last_branch");
852         print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
853
854         my @commit_args;
855         push @commit_args, ("-p", $parent) if $parent;
856
857         # loose detection of merges
858         # based on the commit msg
859         foreach my $rx (@mergerx) {
860                 next unless $logmsg =~ $rx && $1;
861                 my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
862                 if (my $sha1 = get_headref("$remote/$mparent")) {
863                         push @commit_args, '-p', "$remote/$mparent";
864                         print "Merge parent branch: $mparent\n" if $opt_v;
865                 }
866         }
867
868         set_timezone($author_tz);
869         # $date is in the seconds since epoch format
870         my $tz_offset = get_tz_offset($date);
871         my $commit_date = "$date $tz_offset";
872         set_timezone('UTC');
873         $ENV{GIT_AUTHOR_NAME} = $author_name;
874         $ENV{GIT_AUTHOR_EMAIL} = $author_email;
875         $ENV{GIT_AUTHOR_DATE} = $commit_date;
876         $ENV{GIT_COMMITTER_NAME} = $author_name;
877         $ENV{GIT_COMMITTER_EMAIL} = $author_email;
878         $ENV{GIT_COMMITTER_DATE} = $commit_date;
879         my $pid = open2(my $commit_read, my $commit_write,
880                 'git', 'commit-tree', $tree, @commit_args);
881
882         # compatibility with git2cvs
883         substr($logmsg,32767) = "" if length($logmsg) > 32767;
884         $logmsg =~ s/[\s\n]+\z//;
885
886         if (@skipped) {
887             $logmsg .= "\n\n\nSKIPPED:\n\t";
888             $logmsg .= join("\n\t", @skipped) . "\n";
889             @skipped = ();
890         }
891
892         print($commit_write "$logmsg\n") && close($commit_write)
893                 or die "Error writing to git commit-tree: $!\n";
894
895         print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
896         chomp(my $cid = <$commit_read>);
897         is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
898         print "Commit ID $cid\n" if $opt_v;
899         close($commit_read);
900
901         waitpid($pid,0);
902         die "Error running git commit-tree: $?\n" if $?;
903
904         system('git' , 'update-ref', "$remote/$branch", $cid) == 0
905                 or die "Cannot write branch $branch for update: $!\n";
906
907         if ($revision_map) {
908                 print $revision_map "@$_ $cid\n" for @commit_revisions;
909         }
910         @commit_revisions = ();
911
912         if ($tag) {
913                 my ($xtag) = $tag;
914                 $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
915                 $xtag =~ tr/_/\./ if ( $opt_u );
916                 $xtag =~ s/[\/]/$opt_s/g;
917
918                 # See refs.c for these rules.
919                 # Tag cannot contain bad chars. (See bad_ref_char in refs.c.)
920                 $xtag =~ s/[ ~\^:\\\*\?\[]//g;
921                 # Other bad strings for tags:
922                 # (See check_refname_component in refs.c.)
923                 1 while $xtag =~ s/
924                         (?: \.\.        # Tag cannot contain '..'.
925                         |   \@\{        # Tag cannot contain '@{'.
926                         | ^ -           # Tag cannot begin with '-'.
927                         |   \.lock $    # Tag cannot end with '.lock'.
928                         | ^ \.          # Tag cannot begin...
929                         |   \. $        # ...or end with '.'
930                         )//xg;
931                 # Tag cannot be empty.
932                 if ($xtag eq '') {
933                         warn("warning: ignoring tag '$tag'",
934                         " with invalid tagname\n");
935                         return;
936                 }
937
938                 if (system('git' , 'tag', '-f', $xtag, $cid) != 0) {
939                         # We did our best to sanitize the tag, but still failed
940                         # for whatever reason. Bail out, and give the user
941                         # enough information to understand if/how we should
942                         # improve the translation in the future.
943                         if ($tag ne $xtag) {
944                                 print "Translated '$tag' tag to '$xtag'\n";
945                         }
946                         die "Cannot create tag $xtag: $!\n";
947                 }
948
949                 print "Created tag '$xtag' on '$branch'\n" if $opt_v;
950         }
951 };
952
953 my $commitcount = 1;
954 while (<CVS>) {
955         chomp;
956         if ($state == 0 and /^-+$/) {
957                 $state = 1;
958         } elsif ($state == 0) {
959                 $state = 1;
960                 redo;
961         } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
962                 $patchset = 0+$_;
963                 $state=2;
964         } elsif ($state == 2 and s/^Date:\s+//) {
965                 $date = pdate($_);
966                 unless ($date) {
967                         print STDERR "Could not parse date: $_\n";
968                         $state=0;
969                         next;
970                 }
971                 $state=3;
972         } elsif ($state == 3 and s/^Author:\s+//) {
973                 $author_tz = "UTC";
974                 s/\s+$//;
975                 if (/^(.*?)\s+<(.*)>/) {
976                     ($author_name, $author_email) = ($1, $2);
977                 } elsif ($conv_author_name{$_}) {
978                         $author_name = $conv_author_name{$_};
979                         $author_email = $conv_author_email{$_};
980                         $author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});
981                 } else {
982                     $author_name = $author_email = $_;
983                 }
984                 $state = 4;
985         } elsif ($state == 4 and s/^Branch:\s+//) {
986                 s/\s+$//;
987                 tr/_/\./ if ( $opt_u );
988                 s/[\/]/$opt_s/g;
989                 $branch = $_;
990                 $state = 5;
991         } elsif ($state == 5 and s/^Ancestor branch:\s+//) {
992                 s/\s+$//;
993                 $ancestor = $_;
994                 $ancestor = $opt_o if $ancestor eq "HEAD";
995                 $state = 6;
996         } elsif ($state == 5) {
997                 $ancestor = undef;
998                 $state = 6;
999                 redo;
1000         } elsif ($state == 6 and s/^Tag:\s+//) {
1001                 s/\s+$//;
1002                 if ($_ eq "(none)") {
1003                         $tag = undef;
1004                 } else {
1005                         $tag = $_;
1006                 }
1007                 $state = 7;
1008         } elsif ($state == 7 and /^Log:/) {
1009                 $logmsg = "";
1010                 $state = 8;
1011         } elsif ($state == 8 and /^Members:/) {
1012                 $branch = $opt_o if $branch eq "HEAD";
1013                 if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
1014                         # skip
1015                         print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
1016                         $state = 11;
1017                         next;
1018                 }
1019                 if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
1020                         # skip if the commit is too recent
1021                         # given that the cvsps default fuzz is 300s, we give ourselves another
1022                         # 300s just in case -- this also prevents skipping commits
1023                         # due to server clock drift
1024                         print "skip patchset $patchset: $date too recent\n" if $opt_v;
1025                         $state = 11;
1026                         next;
1027                 }
1028                 if (exists $ignorebranch{$branch}) {
1029                         print STDERR "Skipping $branch\n";
1030                         $state = 11;
1031                         next;
1032                 }
1033                 if ($ancestor) {
1034                         if ($ancestor eq $branch) {
1035                                 print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
1036                                 $ancestor = $opt_o;
1037                         }
1038                         if (defined get_headref("$remote/$branch")) {
1039                                 print STDERR "Branch $branch already exists!\n";
1040                                 $state=11;
1041                                 next;
1042                         }
1043                         my $id = get_headref("$remote/$ancestor");
1044                         if (!$id) {
1045                                 print STDERR "Branch $ancestor does not exist!\n";
1046                                 $ignorebranch{$branch} = 1;
1047                                 $state=11;
1048                                 next;
1049                         }
1050
1051                         system(qw(git update-ref -m cvsimport),
1052                                 "$remote/$branch", $id);
1053                         if($? != 0) {
1054                                 print STDERR "Could not create branch $branch\n";
1055                                 $ignorebranch{$branch} = 1;
1056                                 $state=11;
1057                                 next;
1058                         }
1059                 }
1060                 $last_branch = $branch if $branch ne $last_branch;
1061                 $state = 9;
1062         } elsif ($state == 8) {
1063                 $logmsg .= "$_\n";
1064         } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
1065 #       VERSION:1.96->1.96.2.1
1066                 my $init = ($2 eq "INITIAL");
1067                 my $fn = $1;
1068                 my $rev = $3;
1069                 $fn =~ s#^/+##;
1070                 if ($opt_S && $fn =~ m/$opt_S/) {
1071                     print "SKIPPING $fn v $rev\n";
1072                     push(@skipped, $fn);
1073                     next;
1074                 }
1075                 push @commit_revisions, [$fn, $rev];
1076                 print "Fetching $fn   v $rev\n" if $opt_v;
1077                 my ($tmpname, $size) = $cvs->file($fn,$rev);
1078                 if ($size == -1) {
1079                         push(@old,$fn);
1080                         print "Drop $fn\n" if $opt_v;
1081                 } else {
1082                         print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
1083                         my $pid = open(my $F, '-|');
1084                         die $! unless defined $pid;
1085                         if (!$pid) {
1086                             exec("git", "hash-object", "-w", $tmpname)
1087                                 or die "Cannot create object: $!\n";
1088                         }
1089                         my $sha = <$F>;
1090                         chomp $sha;
1091                         close $F;
1092                         my $mode = pmode($cvs->{'mode'});
1093                         push(@new,[$mode, $sha, $fn]); # may be resurrected!
1094                 }
1095                 unlink($tmpname);
1096         } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
1097                 my $fn = $1;
1098                 my $rev = $2;
1099                 $fn =~ s#^/+##;
1100                 push @commit_revisions, [$fn, $rev];
1101                 push(@old,$fn);
1102                 print "Delete $fn\n" if $opt_v;
1103         } elsif ($state == 9 and /^\s*$/) {
1104                 $state = 10;
1105         } elsif (($state == 9 or $state == 10) and /^-+$/) {
1106                 $commitcount++;
1107                 if ($opt_L && $commitcount > $opt_L) {
1108                         last;
1109                 }
1110                 commit();
1111                 if (($commitcount & 1023) == 0) {
1112                         system(qw(git repack -a -d));
1113                 }
1114                 $state = 1;
1115         } elsif ($state == 11 and /^-+$/) {
1116                 $state = 1;
1117         } elsif (/^-+$/) { # end of unknown-line processing
1118                 $state = 1;
1119         } elsif ($state != 11) { # ignore stuff when skipping
1120                 print STDERR "* UNKNOWN LINE * $_\n";
1121         }
1122 }
1123 commit() if $branch and $state != 11;
1124
1125 unless ($opt_P) {
1126         unlink($cvspsfile);
1127 }
1128
1129 # The heuristic of repacking every 1024 commits can leave a
1130 # lot of unpacked data.  If there is more than 1MB worth of
1131 # not-packed objects, repack once more.
1132 my $line = `git count-objects`;
1133 if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
1134   my ($n_objects, $kb) = ($1, $2);
1135   1024 < $kb
1136     and system(qw(git repack -a -d));
1137 }
1138
1139 foreach my $git_index (values %index) {
1140     if ($git_index ne "$git_dir/index") {
1141         unlink($git_index);
1142     }
1143 }
1144
1145 if (defined $orig_git_index) {
1146         $ENV{GIT_INDEX_FILE} = $orig_git_index;
1147 } else {
1148         delete $ENV{GIT_INDEX_FILE};
1149 }
1150
1151 # Now switch back to the branch we were in before all of this happened
1152 if ($orig_branch) {
1153         print "DONE.\n" if $opt_v;
1154         if ($opt_i) {
1155                 exit 0;
1156         }
1157         my $tip_at_end = `git rev-parse --verify HEAD`;
1158         if ($tip_at_start ne $tip_at_end) {
1159                 for ($tip_at_start, $tip_at_end) { chomp; }
1160                 print "Fetched into the current branch.\n" if $opt_v;
1161                 system(qw(git read-tree -u -m),
1162                        $tip_at_start, $tip_at_end);
1163                 die "Fast-forward update failed: $?\n" if $?;
1164         }
1165         else {
1166                 system(qw(git merge -m cvsimport), "$remote/$opt_o");
1167                 die "Could not merge $opt_o into the current branch.\n" if $?;
1168         }
1169 } else {
1170         $orig_branch = "master";
1171         print "DONE; creating $orig_branch branch\n" if $opt_v;
1172         system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
1173                 unless defined get_headref('refs/heads/master');
1174         system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
1175                 if ($opt_r && $opt_o ne 'HEAD');
1176         system('git', 'update-ref', 'HEAD', "$orig_branch");
1177         unless ($opt_i) {
1178                 system(qw(git checkout -f));
1179                 die "checkout failed: $?\n" if $?;
1180         }
1181 }