git-svnimport.perlon commit send-email: lazy-load Email::Valid and make it optional (567ffeb)
   1#!/usr/bin/perl -w
   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 pull and analyze SVN changes.
   7#
   8# Checking out the files is done by a single long-running SVN connection.
   9#
  10# The head revision is on branch "origin" by default.
  11# You can change that with the '-o' option.
  12
  13use strict;
  14use warnings;
  15use Getopt::Std;
  16use File::Copy;
  17use File::Spec;
  18use File::Temp qw(tempfile);
  19use File::Path qw(mkpath);
  20use File::Basename qw(basename dirname);
  21use Time::Local;
  22use IO::Pipe;
  23use POSIX qw(strftime dup2);
  24use IPC::Open2;
  25use SVN::Core;
  26use SVN::Ra;
  27
  28die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
  29
  30$SIG{'PIPE'}="IGNORE";
  31$ENV{'TZ'}="UTC";
  32
  33our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
  34    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D);
  35
  36sub usage() {
  37        print STDERR <<END;
  38Usage: ${\basename $0}     # fetch/update GIT from SVN
  39       [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
  40       [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
  41       [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
  42       [-m] [-M regex] [-A author_file] [SVN_URL]
  43END
  44        exit(1);
  45}
  46
  47getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
  48usage if $opt_h;
  49
  50my $tag_name = $opt_t || "tags";
  51my $trunk_name = $opt_T || "trunk";
  52my $branch_name = $opt_b || "branches";
  53
  54@ARGV == 1 or @ARGV == 2 or usage();
  55
  56$opt_o ||= "origin";
  57$opt_s ||= 1;
  58my $git_tree = $opt_C;
  59$git_tree ||= ".";
  60
  61my $svn_url = $ARGV[0];
  62my $svn_dir = $ARGV[1];
  63
  64our @mergerx = ();
  65if ($opt_m) {
  66        @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
  67}
  68if ($opt_M) {
  69        push (@mergerx, qr/$opt_M/);
  70}
  71
  72# Absolutize filename now, since we will have chdir'ed by the time we
  73# get around to opening it.
  74$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
  75
  76our %users = ();
  77our $users_file = undef;
  78sub read_users($) {
  79        $users_file = File::Spec->rel2abs(@_);
  80        die "Cannot open $users_file\n" unless -f $users_file;
  81        open(my $authors,$users_file);
  82        while(<$authors>) {
  83                chomp;
  84                next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
  85                (my $user,my $name,my $email) = ($1,$2,$3);
  86                $users{$user} = [$name,$email];
  87        }
  88        close($authors);
  89}
  90
  91select(STDERR); $|=1; select(STDOUT);
  92
  93
  94package SVNconn;
  95# Basic SVN connection.
  96# We're only interested in connecting and downloading, so ...
  97
  98use File::Spec;
  99use File::Temp qw(tempfile);
 100use POSIX qw(strftime dup2);
 101
 102sub new {
 103        my($what,$repo) = @_;
 104        $what=ref($what) if ref($what);
 105
 106        my $self = {};
 107        $self->{'buffer'} = "";
 108        bless($self,$what);
 109
 110        $repo =~ s#/+$##;
 111        $self->{'fullrep'} = $repo;
 112        $self->conn();
 113
 114        return $self;
 115}
 116
 117sub conn {
 118        my $self = shift;
 119        my $repo = $self->{'fullrep'};
 120        my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider,
 121                          SVN::Client::get_ssl_server_trust_file_provider,
 122                          SVN::Client::get_username_provider]);
 123        my $s = SVN::Ra->new(url => $repo, auth => $auth);
 124        die "SVN connection to $repo: $!\n" unless defined $s;
 125        $self->{'svn'} = $s;
 126        $self->{'repo'} = $repo;
 127        $self->{'maxrev'} = $s->get_latest_revnum();
 128}
 129
 130sub file {
 131        my($self,$path,$rev) = @_;
 132
 133        my ($fh, $name) = tempfile('gitsvn.XXXXXX',
 134                    DIR => File::Spec->tmpdir(), UNLINK => 1);
 135
 136        print "... $rev $path ...\n" if $opt_v;
 137        my (undef, $properties);
 138        eval { (undef, $properties)
 139                   = $self->{'svn'}->get_file($path,$rev,$fh); };
 140        if($@) {
 141                return undef if $@ =~ /Attempted to get checksum/;
 142                die $@;
 143        }
 144        my $mode;
 145        if (exists $properties->{'svn:executable'}) {
 146                $mode = '0755';
 147        } else {
 148                $mode = '0644';
 149        }
 150        close ($fh);
 151
 152        return ($name, $mode);
 153}
 154
 155sub ignore {
 156        my($self,$path,$rev) = @_;
 157
 158        print "... $rev $path ...\n" if $opt_v;
 159        my (undef,undef,$properties)
 160            = $self->{'svn'}->get_dir($path,$rev,undef);
 161        if (exists $properties->{'svn:ignore'}) {
 162                my ($fh, $name) = tempfile('gitsvn.XXXXXX',
 163                                           DIR => File::Spec->tmpdir(),
 164                                           UNLINK => 1);
 165                print $fh $properties->{'svn:ignore'};
 166                close($fh);
 167                return $name;
 168        } else {
 169                return undef;
 170        }
 171}
 172
 173package main;
 174use URI;
 175
 176our $svn = $svn_url;
 177$svn .= "/$svn_dir" if defined $svn_dir;
 178my $svn2 = SVNconn->new($svn);
 179$svn = SVNconn->new($svn);
 180
 181my $lwp_ua;
 182if($opt_d or $opt_D) {
 183        $svn_url = URI->new($svn_url)->canonical;
 184        if($opt_D) {
 185                $svn_dir =~ s#/*$#/#;
 186        } else {
 187                $svn_dir = "";
 188        }
 189        if ($svn_url->scheme eq "http") {
 190                use LWP::UserAgent;
 191                $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
 192        } else {
 193                print STDERR "Warning: not HTTP; turning off direct file access\n";
 194                $opt_d=0;
 195        }
 196}
 197
 198sub pdate($) {
 199        my($d) = @_;
 200        $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
 201                or die "Unparseable date: $d\n";
 202        my $y=$1; $y-=1900 if $y>1900;
 203        return timegm($6||0,$5,$4,$3,$2-1,$y);
 204}
 205
 206sub getwd() {
 207        my $pwd = `pwd`;
 208        chomp $pwd;
 209        return $pwd;
 210}
 211
 212
 213sub get_headref($$) {
 214    my $name    = shift;
 215    my $git_dir = shift;
 216    my $sha;
 217
 218    if (open(C,"$git_dir/refs/heads/$name")) {
 219        chomp($sha = <C>);
 220        close(C);
 221        length($sha) == 40
 222            or die "Cannot get head id for $name ($sha): $!\n";
 223    }
 224    return $sha;
 225}
 226
 227
 228-d $git_tree
 229        or mkdir($git_tree,0777)
 230        or die "Could not create $git_tree: $!";
 231chdir($git_tree);
 232
 233my $orig_branch = "";
 234my $forward_master = 0;
 235my %branches;
 236
 237my $git_dir = $ENV{"GIT_DIR"} || ".git";
 238$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
 239$ENV{"GIT_DIR"} = $git_dir;
 240my $orig_git_index;
 241$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 242my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
 243                                    DIR => File::Spec->tmpdir());
 244close ($git_ih);
 245$ENV{GIT_INDEX_FILE} = $git_index;
 246my $maxnum = 0;
 247my $last_rev = "";
 248my $last_branch;
 249my $current_rev = $opt_s || 1;
 250unless(-d $git_dir) {
 251        system("git-init-db");
 252        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
 253        system("git-read-tree");
 254        die "Cannot init an empty tree: $?\n" if $?;
 255
 256        $last_branch = $opt_o;
 257        $orig_branch = "";
 258} else {
 259        -f "$git_dir/refs/heads/$opt_o"
 260                or die "Branch '$opt_o' does not exist.\n".
 261                       "Either use the correct '-o branch' option,\n".
 262                       "or import to a new repository.\n";
 263
 264        -f "$git_dir/svn2git"
 265                or die "'$git_dir/svn2git' does not exist.\n".
 266                       "You need that file for incremental imports.\n";
 267        open(F, "git-symbolic-ref HEAD |") or
 268                die "Cannot run git-symbolic-ref: $!\n";
 269        chomp ($last_branch = <F>);
 270        $last_branch = basename($last_branch);
 271        close(F);
 272        unless($last_branch) {
 273                warn "Cannot read the last branch name: $! -- assuming 'master'\n";
 274                $last_branch = "master";
 275        }
 276        $orig_branch = $last_branch;
 277        $last_rev = get_headref($orig_branch, $git_dir);
 278        if (-f "$git_dir/SVN2GIT_HEAD") {
 279                die <<EOM;
 280SVN2GIT_HEAD exists.
 281Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
 282You may need to run
 283
 284    git-read-tree -m -u SVN2GIT_HEAD HEAD
 285EOM
 286        }
 287        system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
 288
 289        $forward_master =
 290            $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
 291            system('cmp', '-s', "$git_dir/refs/heads/master",
 292                                "$git_dir/refs/heads/$opt_o") == 0;
 293
 294        # populate index
 295        system('git-read-tree', $last_rev);
 296        die "read-tree failed: $?\n" if $?;
 297
 298        # Get the last import timestamps
 299        open my $B,"<", "$git_dir/svn2git";
 300        while(<$B>) {
 301                chomp;
 302                my($num,$branch,$ref) = split;
 303                $branches{$branch}{$num} = $ref;
 304                $branches{$branch}{"LAST"} = $ref;
 305                $current_rev = $num+1 if $current_rev <= $num;
 306        }
 307        close($B);
 308}
 309-d $git_dir
 310        or die "Could not create git subdir ($git_dir).\n";
 311
 312my $default_authors = "$git_dir/svn-authors";
 313if ($opt_A) {
 314        read_users($opt_A);
 315        copy($opt_A,$default_authors) or die "Copy failed: $!";
 316} else {
 317        read_users($default_authors) if -f $default_authors;
 318}
 319
 320open BRANCHES,">>", "$git_dir/svn2git";
 321
 322sub node_kind($$$) {
 323        my ($branch, $path, $revision) = @_;
 324        my $pool=SVN::Pool->new;
 325        my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
 326        $pool->clear;
 327        return $kind;
 328}
 329
 330sub revert_split_path($$) {
 331        my($branch,$path) = @_;
 332
 333        my $svnpath;
 334        $path = "" if $path eq "/"; # this should not happen, but ...
 335        if($branch eq "/") {
 336                $svnpath = "$trunk_name/$path";
 337        } elsif($branch =~ m#^/#) {
 338                $svnpath = "$tag_name$branch/$path";
 339        } else {
 340                $svnpath = "$branch_name/$branch/$path";
 341        }
 342
 343        $svnpath =~ s#/+$##;
 344        return $svnpath;
 345}
 346
 347sub get_file($$$) {
 348        my($rev,$branch,$path) = @_;
 349
 350        my $svnpath = revert_split_path($branch,$path);
 351
 352        # now get it
 353        my ($name,$mode);
 354        if($opt_d) {
 355                my($req,$res);
 356
 357                # /svn/!svn/bc/2/django/trunk/django-docs/build.py
 358                my $url=$svn_url->clone();
 359                $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
 360                print "... $path...\n" if $opt_v;
 361                $req = HTTP::Request->new(GET => $url);
 362                $res = $lwp_ua->request($req);
 363                if ($res->is_success) {
 364                        my $fh;
 365                        ($fh, $name) = tempfile('gitsvn.XXXXXX',
 366                        DIR => File::Spec->tmpdir(), UNLINK => 1);
 367                        print $fh $res->content;
 368                        close($fh) or die "Could not write $name: $!\n";
 369                } else {
 370                        return undef if $res->code == 301; # directory?
 371                        die $res->status_line." at $url\n";
 372                }
 373                $mode = '0644'; # can't obtain mode via direct http request?
 374        } else {
 375                ($name,$mode) = $svn->file("$svnpath",$rev);
 376                return undef unless defined $name;
 377        }
 378
 379        my $pid = open(my $F, '-|');
 380        die $! unless defined $pid;
 381        if (!$pid) {
 382            exec("git-hash-object", "-w", $name)
 383                or die "Cannot create object: $!\n";
 384        }
 385        my $sha = <$F>;
 386        chomp $sha;
 387        close $F;
 388        unlink $name;
 389        return [$mode, $sha, $path];
 390}
 391
 392sub get_ignore($$$$$) {
 393        my($new,$old,$rev,$branch,$path) = @_;
 394
 395        return unless $opt_I;
 396        my $svnpath = revert_split_path($branch,$path);
 397        my $name = $svn->ignore("$svnpath",$rev);
 398        if ($path eq '/') {
 399                $path = $opt_I;
 400        } else {
 401                $path = File::Spec->catfile($path,$opt_I);
 402        }
 403        if (defined $name) {
 404                my $pid = open(my $F, '-|');
 405                die $! unless defined $pid;
 406                if (!$pid) {
 407                        exec("git-hash-object", "-w", $name)
 408                            or die "Cannot create object: $!\n";
 409                }
 410                my $sha = <$F>;
 411                chomp $sha;
 412                close $F;
 413                unlink $name;
 414                push(@$new,['0644',$sha,$path]);
 415        } else {
 416                push(@$old,$path);
 417        }
 418}
 419
 420sub split_path($$) {
 421        my($rev,$path) = @_;
 422        my $branch;
 423
 424        if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
 425                $branch = "/$1";
 426        } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
 427                $branch = "/";
 428        } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
 429                $branch = $1;
 430        } else {
 431                my %no_error = (
 432                        "/" => 1,
 433                        "/$tag_name" => 1,
 434                        "/$branch_name" => 1
 435                );
 436                print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
 437                return ()
 438        }
 439        $path = "/" if $path eq "";
 440        return ($branch,$path);
 441}
 442
 443sub branch_rev($$) {
 444
 445        my ($srcbranch,$uptorev) = @_;
 446
 447        my $bbranches = $branches{$srcbranch};
 448        my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
 449        my $therev;
 450        foreach my $arev(@revs) {
 451                next if  ($arev eq 'LAST');
 452                if ($arev <= $uptorev) {
 453                        $therev = $arev;
 454                        last;
 455                }
 456        }
 457        return $therev;
 458}
 459
 460sub copy_path($$$$$$$$) {
 461        # Somebody copied a whole subdirectory.
 462        # We need to find the index entries from the old version which the
 463        # SVN log entry points to, and add them to the new place.
 464
 465        my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
 466
 467        my($srcbranch,$srcpath) = split_path($rev,$oldpath);
 468        unless(defined $srcbranch) {
 469                print "Path not found when copying from $oldpath @ $rev\n";
 470                return;
 471        }
 472        my $therev = branch_rev($srcbranch, $rev);
 473        my $gitrev = $branches{$srcbranch}{$therev};
 474        unless($gitrev) {
 475                print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
 476                return;
 477        }
 478        if ($srcbranch ne $newbranch) {
 479                push(@$parents, $branches{$srcbranch}{'LAST'});
 480        }
 481        print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
 482        if ($node_kind eq $SVN::Node::dir) {
 483                        $srcpath =~ s#/*$#/#;
 484        }
 485        
 486        my $pid = open my $f,'-|';
 487        die $! unless defined $pid;
 488        if (!$pid) {
 489                exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
 490                        or die $!;
 491        }
 492        local $/ = "\0";
 493        while(<$f>) {
 494                chomp;
 495                my($m,$p) = split(/\t/,$_,2);
 496                my($mode,$type,$sha1) = split(/ /,$m);
 497                next if $type ne "blob";
 498                if ($node_kind eq $SVN::Node::dir) {
 499                        $p = $path . substr($p,length($srcpath)-1);
 500                } else {
 501                        $p = $path;
 502                }
 503                push(@$new,[$mode,$sha1,$p]);   
 504        }
 505        close($f) or
 506                print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
 507}
 508
 509sub commit {
 510        my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
 511        my($author_name,$author_email,$dest);
 512        my(@old,@new,@parents);
 513
 514        if (not defined $author) {
 515                $author_name = $author_email = "unknown";
 516        } elsif (defined $users_file) {
 517                die "User $author is not listed in $users_file\n"
 518                    unless exists $users{$author};
 519                ($author_name,$author_email) = @{$users{$author}};
 520        } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
 521                ($author_name, $author_email) = ($1, $2);
 522        } else {
 523                $author =~ s/^<(.*)>$/$1/;
 524                $author_name = $author_email = $author;
 525        }
 526        $date = pdate($date);
 527
 528        my $tag;
 529        my $parent;
 530        if($branch eq "/") { # trunk
 531                $parent = $opt_o;
 532        } elsif($branch =~ m#^/(.+)#) { # tag
 533                $tag = 1;
 534                $parent = $1;
 535        } else { # "normal" branch
 536                # nothing to do
 537                $parent = $branch;
 538        }
 539        $dest = $parent;
 540
 541        my $prev = $changed_paths->{"/"};
 542        if($prev and $prev->[0] eq "A") {
 543                delete $changed_paths->{"/"};
 544                my $oldpath = $prev->[1];
 545                my $rev;
 546                if(defined $oldpath) {
 547                        my $p;
 548                        ($parent,$p) = split_path($revision,$oldpath);
 549                        if($parent eq "/") {
 550                                $parent = $opt_o;
 551                        } else {
 552                                $parent =~ s#^/##; # if it's a tag
 553                        }
 554                } else {
 555                        $parent = undef;
 556                }
 557        }
 558
 559        my $rev;
 560        if($revision > $opt_s and defined $parent) {
 561                open(H,"git-rev-parse --verify $parent |");
 562                $rev = <H>;
 563                close(H) or do {
 564                        print STDERR "$revision: cannot find commit '$parent'!\n";
 565                        return;
 566                };
 567                chop $rev;
 568                if(length($rev) != 40) {
 569                        print STDERR "$revision: cannot find commit '$parent'!\n";
 570                        return;
 571                }
 572                $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
 573                if($revision != $opt_s and not $rev) {
 574                        print STDERR "$revision: do not know ancestor for '$parent'!\n";
 575                        return;
 576                }
 577        } else {
 578                $rev = undef;
 579        }
 580
 581#       if($prev and $prev->[0] eq "A") {
 582#               if(not $tag) {
 583#                       unless(open(H,"> $git_dir/refs/heads/$branch")) {
 584#                               print STDERR "$revision: Could not create branch $branch: $!\n";
 585#                               $state=11;
 586#                               next;
 587#                       }
 588#                       print H "$rev\n"
 589#                               or die "Could not write branch $branch: $!";
 590#                       close(H)
 591#                               or die "Could not write branch $branch: $!";
 592#               }
 593#       }
 594        if(not defined $rev) {
 595                unlink($git_index);
 596        } elsif ($rev ne $last_rev) {
 597                print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
 598                system("git-read-tree", $rev);
 599                die "read-tree failed for $rev: $?\n" if $?;
 600                $last_rev = $rev;
 601        }
 602
 603        push (@parents, $rev) if defined $rev;
 604
 605        my $cid;
 606        if($tag and not %$changed_paths) {
 607                $cid = $rev;
 608        } else {
 609                my @paths = sort keys %$changed_paths;
 610                foreach my $path(@paths) {
 611                        my $action = $changed_paths->{$path};
 612
 613                        if ($action->[0] eq "R") {
 614                                # refer to a file/tree in an earlier commit
 615                                push(@old,$path); # remove any old stuff
 616                        }
 617                        if(($action->[0] eq "A") || ($action->[0] eq "R")) {
 618                                my $node_kind = node_kind($branch,$path,$revision);
 619                                if($action->[1]) {
 620                                        copy_path($revision,$branch,$path,$action->[1],$action->[2],$node_kind,\@new,\@parents);
 621                                } elsif ($node_kind eq $SVN::Node::file) {
 622                                        my $f = get_file($revision,$branch,$path);
 623                                        if ($f) {
 624                                                push(@new,$f) if $f;
 625                                        } else {
 626                                                my $opath = $action->[3];
 627                                                print STDERR "$revision: $branch: could not fetch '$opath'\n";
 628                                        }
 629                                } elsif ($node_kind eq $SVN::Node::dir) {
 630                                        get_ignore(\@new, \@old, $revision,
 631                                                   $branch,$path);
 632                                }
 633                        } elsif ($action->[0] eq "D") {
 634                                push(@old,$path);
 635                        } elsif ($action->[0] eq "M") {
 636                                my $node_kind = node_kind($branch,$path,$revision);
 637                                if ($node_kind eq $SVN::Node::file) {
 638                                        my $f = get_file($revision,$branch,$path);
 639                                        push(@new,$f) if $f;
 640                                } elsif ($node_kind eq $SVN::Node::dir) {
 641                                        get_ignore(\@new, \@old, $revision,
 642                                                   $branch,$path);
 643                                }
 644                        } else {
 645                                die "$revision: unknown action '".$action->[0]."' for $path\n";
 646                        }
 647                }
 648
 649                while(@old) {
 650                        my @o1;
 651                        if(@old > 55) {
 652                                @o1 = splice(@old,0,50);
 653                        } else {
 654                                @o1 = @old;
 655                                @old = ();
 656                        }
 657                        my $pid = open my $F, "-|";
 658                        die "$!" unless defined $pid;
 659                        if (!$pid) {
 660                                exec("git-ls-files", "-z", @o1) or die $!;
 661                        }
 662                        @o1 = ();
 663                        local $/ = "\0";
 664                        while(<$F>) {
 665                                chomp;
 666                                push(@o1,$_);
 667                        }
 668                        close($F);
 669
 670                        while(@o1) {
 671                                my @o2;
 672                                if(@o1 > 55) {
 673                                        @o2 = splice(@o1,0,50);
 674                                } else {
 675                                        @o2 = @o1;
 676                                        @o1 = ();
 677                                }
 678                                system("git-update-index","--force-remove","--",@o2);
 679                                die "Cannot remove files: $?\n" if $?;
 680                        }
 681                }
 682                while(@new) {
 683                        my @n2;
 684                        if(@new > 12) {
 685                                @n2 = splice(@new,0,10);
 686                        } else {
 687                                @n2 = @new;
 688                                @new = ();
 689                        }
 690                        system("git-update-index","--add",
 691                                (map { ('--cacheinfo', @$_) } @n2));
 692                        die "Cannot add files: $?\n" if $?;
 693                }
 694
 695                my $pid = open(C,"-|");
 696                die "Cannot fork: $!" unless defined $pid;
 697                unless($pid) {
 698                        exec("git-write-tree");
 699                        die "Cannot exec git-write-tree: $!\n";
 700                }
 701                chomp(my $tree = <C>);
 702                length($tree) == 40
 703                        or die "Cannot get tree id ($tree): $!\n";
 704                close(C)
 705                        or die "Error running git-write-tree: $?\n";
 706                print "Tree ID $tree\n" if $opt_v;
 707
 708                my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
 709                my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
 710                $pid = fork();
 711                die "Fork: $!\n" unless defined $pid;
 712                unless($pid) {
 713                        $pr->writer();
 714                        $pw->reader();
 715                        open(OUT,">&STDOUT");
 716                        dup2($pw->fileno(),0);
 717                        dup2($pr->fileno(),1);
 718                        $pr->close();
 719                        $pw->close();
 720
 721                        my @par = ();
 722
 723                        # loose detection of merges
 724                        # based on the commit msg
 725                        foreach my $rx (@mergerx) {
 726                                if ($message =~ $rx) {
 727                                        my $mparent = $1;
 728                                        if ($mparent eq 'HEAD') { $mparent = $opt_o };
 729                                        if ( -e "$git_dir/refs/heads/$mparent") {
 730                                                $mparent = get_headref($mparent, $git_dir);
 731                                                push (@parents, $mparent);
 732                                                print OUT "Merge parent branch: $mparent\n" if $opt_v;
 733                                        }
 734                                }
 735                        }
 736                        my %seen_parents = ();
 737                        my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
 738                        foreach my $bparent (@unique_parents) {
 739                                push @par, '-p', $bparent;
 740                                print OUT "Merge parent branch: $bparent\n" if $opt_v;
 741                        }
 742
 743                        exec("env",
 744                                "GIT_AUTHOR_NAME=$author_name",
 745                                "GIT_AUTHOR_EMAIL=$author_email",
 746                                "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
 747                                "GIT_COMMITTER_NAME=$author_name",
 748                                "GIT_COMMITTER_EMAIL=$author_email",
 749                                "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
 750                                "git-commit-tree", $tree,@par);
 751                        die "Cannot exec git-commit-tree: $!\n";
 752                }
 753                $pw->writer();
 754                $pr->reader();
 755
 756                $message =~ s/[\s\n]+\z//;
 757                $message = "r$revision: $message" if $opt_r;
 758
 759                print $pw "$message\n"
 760                        or die "Error writing to git-commit-tree: $!\n";
 761                $pw->close();
 762
 763                print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
 764                chomp($cid = <$pr>);
 765                length($cid) == 40
 766                        or die "Cannot get commit id ($cid): $!\n";
 767                print "Commit ID $cid\n" if $opt_v;
 768                $pr->close();
 769
 770                waitpid($pid,0);
 771                die "Error running git-commit-tree: $?\n" if $?;
 772        }
 773
 774        if (not defined $cid) {
 775                $cid = $branches{"/"}{"LAST"};
 776        }
 777
 778        if(not defined $dest) {
 779                print "... no known parent\n" if $opt_v;
 780        } elsif(not $tag) {
 781                print "Writing to refs/heads/$dest\n" if $opt_v;
 782                open(C,">$git_dir/refs/heads/$dest") and
 783                print C ("$cid\n") and
 784                close(C)
 785                        or die "Cannot write branch $dest for update: $!\n";
 786        }
 787
 788        if($tag) {
 789                my($in, $out) = ('','');
 790                $last_rev = "-" if %$changed_paths;
 791                # the tag was 'complex', i.e. did not refer to a "real" revision
 792
 793                $dest =~ tr/_/\./ if $opt_u;
 794                $branch = $dest;
 795
 796                my $pid = open2($in, $out, 'git-mktag');
 797                print $out ("object $cid\n".
 798                    "type commit\n".
 799                    "tag $dest\n".
 800                    "tagger $author_name <$author_email>\n") and
 801                close($out)
 802                    or die "Cannot create tag object $dest: $!\n";
 803
 804                my $tagobj = <$in>;
 805                chomp $tagobj;
 806
 807                if ( !close($in) or waitpid($pid, 0) != $pid or
 808                                $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
 809                        die "Cannot create tag object $dest: $!\n";
 810                }
 811
 812                open(C,">$git_dir/refs/tags/$dest") and
 813                print C ("$tagobj\n") and
 814                close(C)
 815                        or die "Cannot create tag $branch: $!\n";
 816
 817                print "Created tag '$dest' on '$branch'\n" if $opt_v;
 818        }
 819        $branches{$branch}{"LAST"} = $cid;
 820        $branches{$branch}{$revision} = $cid;
 821        $last_rev = $cid;
 822        print BRANCHES "$revision $branch $cid\n";
 823        print "DONE: $revision $dest $cid\n" if $opt_v;
 824}
 825
 826sub commit_all {
 827        # Recursive use of the SVN connection does not work
 828        local $svn = $svn2;
 829
 830        my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
 831        my %p;
 832        while(my($path,$action) = each %$changed_paths) {
 833                $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
 834        }
 835        $changed_paths = \%p;
 836
 837        my %done;
 838        my @col;
 839        my $pref;
 840        my $branch;
 841
 842        while(my($path,$action) = each %$changed_paths) {
 843                ($branch,$path) = split_path($revision,$path);
 844                next if not defined $branch;
 845                $done{$branch}{$path} = $action;
 846        }
 847        while(($branch,$changed_paths) = each %done) {
 848                commit($branch, $changed_paths, $revision, $author, $date, $message);
 849        }
 850}
 851
 852$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
 853
 854if ($svn->{'maxrev'} < $current_rev) {
 855    print "Up to date: no new revisions to fetch!\n" if $opt_v;
 856    unlink("$git_dir/SVN2GIT_HEAD");
 857    exit;
 858}
 859
 860print "Fetching from $current_rev to $opt_l ...\n" if $opt_v;
 861
 862my $pool=SVN::Pool->new;
 863$svn->{'svn'}->get_log("/",$current_rev,$opt_l,0,1,1,\&commit_all,$pool);
 864$pool->clear;
 865
 866
 867unlink($git_index);
 868
 869if (defined $orig_git_index) {
 870        $ENV{GIT_INDEX_FILE} = $orig_git_index;
 871} else {
 872        delete $ENV{GIT_INDEX_FILE};
 873}
 874
 875# Now switch back to the branch we were in before all of this happened
 876if($orig_branch) {
 877        print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
 878        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
 879                if $forward_master;
 880        unless ($opt_i) {
 881                system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
 882                die "read-tree failed: $?\n" if $?;
 883        }
 884} else {
 885        $orig_branch = "master";
 886        print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
 887        system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
 888                unless -f "$git_dir/refs/heads/master";
 889        system('git-update-ref', 'HEAD', "$orig_branch");
 890        unless ($opt_i) {
 891                system('git checkout');
 892                die "checkout failed: $?\n" if $?;
 893        }
 894}
 895unlink("$git_dir/SVN2GIT_HEAD");
 896close(BRANCHES);