From: Gerrit Pape Date: Tue, 30 Oct 2007 14:24:27 +0000 (+0000) Subject: No longer install git-svnimport, move to contrib/examples X-Git-Tag: v1.5.4-rc0~305 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=fee9832a8dea5d9c98c5c3a4797615d52814df16;p=git.git No longer install git-svnimport, move to contrib/examples This has been proposed for a few times without much reaction from the list. Actually remove it to see who screams. Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano --- diff --git a/.gitignore b/.gitignore index 62afef234..8670081ad 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,6 @@ git-status git-stripspace git-submodule git-svn -git-svnimport git-symbolic-ref git-tag git-tar-tree diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 1061fd8bc..8d21d423e 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -185,7 +185,6 @@ git-status mainporcelain git-stripspace purehelpers git-submodule mainporcelain git-svn foreignscminterface -git-svnimport foreignscminterface git-symbolic-ref plumbingmanipulators git-tag mainporcelain git-tar-tree plumbinginterrogators diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt deleted file mode 100644 index 71aad8b45..000000000 --- a/Documentation/git-svnimport.txt +++ /dev/null @@ -1,179 +0,0 @@ -git-svnimport(1) -================ -v0.1, July 2005 - -NAME ----- -git-svnimport - Import a SVN repository into git - - -SYNOPSIS --------- -[verse] -'git-svnimport' [ -o ] [ -h ] [ -v ] [ -d | -D ] - [ -C ] [ -i ] [ -u ] [-l limit_rev] - [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] - [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] - [ -I ] [ -A ] - [ -R ] [ -P ] - [ ] - - -DESCRIPTION ------------ -Imports a SVN repository into git. It will either create a new -repository, or incrementally import into an existing one. - -SVN access is done by the SVN::Perl module. - -git-svnimport assumes that SVN repositories are organized into one -"trunk" directory where the main development happens, "branches/FOO" -directories for branches, and "/tags/FOO" directories for tags. -Other subdirectories are ignored. - -git-svnimport creates a file ".git/svn2git", which is required for -incremental SVN imports. - -OPTIONS -------- --C :: - The GIT repository to import to. If the directory doesn't - exist, it will be created. Default is the current directory. - --s :: - Start importing at this SVN change number. The default is 1. -+ -When importing incrementally, you might need to edit the .git/svn2git file. - --i:: - Import-only: don't perform a checkout after importing. This option - ensures the working directory and index remain untouched and will - not create them if they do not exist. - --T :: - Name the SVN trunk. Default "trunk". - --t :: - Name the SVN subdirectory for tags. Default "tags". - --b :: - Name the SVN subdirectory for branches. Default "branches". - --o :: - The 'trunk' branch from SVN is imported to the 'origin' branch within - the git repository. Use this option if you want to import into a - different branch. - --r:: - Prepend 'rX: ' to commit messages, where X is the imported - subversion revision. - --u:: - Replace underscores in tag names with periods. - --I :: - Import the svn:ignore directory property to files with this - name in each directory. (The Subversion and GIT ignore - syntaxes are similar enough that using the Subversion patterns - directly with "-I .gitignore" will almost always just work.) - --A :: - Read a file with lines on the form -+ ------- - username = User's Full Name - ------- -+ -and use "User's Full Name " as the GIT -author and committer for Subversion commits made by -"username". If encountering a commit made by a user not in the -list, abort. -+ -For convenience, this data is saved to $GIT_DIR/svn-authors -each time the -A option is provided, and read from that same -file each time git-svnimport is run with an existing GIT -repository without -A. - --m:: - Attempt to detect merges based on the commit message. This option - will enable default regexes that try to capture the name source - branch name from the commit message. - --M :: - Attempt to detect merges based on the commit message with a custom - regex. It can be used with -m to also see the default regexes. - You must escape forward slashes. - --l :: - Specify a maximum revision number to pull. -+ -Formerly, this option controlled how many revisions to pull, -due to SVN memory leaks. (These have been worked around.) - --R :: - Specify how often git repository should be repacked. -+ -The default value is 1000. git-svnimport will do import in chunks of 1000 -revisions, after each chunk git repository will be repacked. To disable -this behavior specify some big value here which is mote than number of -revisions to import. - --P :: - Partial import of the SVN tree. -+ -By default, the whole tree on the SVN trunk (/trunk) is imported. -'-P my/proj' will import starting only from '/trunk/my/proj'. -This option is useful when you want to import one project from a -svn repo which hosts multiple projects under the same trunk. - --v:: - Verbosity: let 'svnimport' report what it is doing. - --d:: - Use direct HTTP requests if possible. The "" argument is used - only for retrieving the SVN logs; the path to the contents is - included in the SVN log. - --D:: - Use direct HTTP requests if possible. The "" argument is used - for retrieving the logs, as well as for the contents. -+ -There's no safe way to automatically find out which of these options to -use, so you need to try both. Usually, the one that's wrong will die -with a 40x error pretty quickly. - -:: - The URL of the SVN module you want to import. For local - repositories, use "file:///absolute/path". -+ -If you're using the "-d" or "-D" option, this is the URL of the SVN -repository itself; it usually ends in "/svn". - -:: - The path to the module you want to check out. - --h:: - Print a short usage message and exit. - -OUTPUT ------- -If '-v' is specified, the script reports what it is doing. - -Otherwise, success is indicated the Unix way, i.e. by simply exiting with -a zero exit status. - -Author ------- -Written by Matthias Urlichs , with help from -various participants of the git-list . - -Based on a cvs2git script by the same author. - -Documentation --------------- -Documentation by Matthias Urlichs . - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Makefile b/Makefile index 72f5ef43c..eb98d91bd 100644 --- a/Makefile +++ b/Makefile @@ -225,8 +225,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl git-remote.perl \ - git-svnimport.perl git-cvsexportcommit.perl \ + git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e76093074..599b2fc57 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -346,7 +346,6 @@ __git_commands () ssh-*) : transport;; stripspace) : plumbing;; svn) : import export;; - svnimport) : import;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl new file mode 100755 index 000000000..ea8c1b2f6 --- /dev/null +++ b/contrib/examples/git-svnimport.perl @@ -0,0 +1,976 @@ +#!/usr/bin/perl -w + +# This tool is copyright (c) 2005, Matthias Urlichs. +# It is released under the Gnu Public License, version 2. +# +# The basic idea is to pull and analyze SVN changes. +# +# Checking out the files is done by a single long-running SVN connection. +# +# The head revision is on branch "origin" by default. +# You can change that with the '-o' option. + +use strict; +use warnings; +use Getopt::Std; +use File::Copy; +use File::Spec; +use File::Temp qw(tempfile); +use File::Path qw(mkpath); +use File::Basename qw(basename dirname); +use Time::Local; +use IO::Pipe; +use POSIX qw(strftime dup2); +use IPC::Open2; +use SVN::Core; +use SVN::Ra; + +die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1"; + +$SIG{'PIPE'}="IGNORE"; +$ENV{'TZ'}="UTC"; + +our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F, + $opt_P,$opt_R); + +sub usage() { + print STDERR <new_default; + +@ARGV == 1 or @ARGV == 2 or usage(); + +$opt_o ||= "origin"; +$opt_s ||= 1; +my $git_tree = $opt_C; +$git_tree ||= "."; + +my $svn_url = $ARGV[0]; +my $svn_dir = $ARGV[1]; + +our @mergerx = (); +if ($opt_m) { + my $branch_esc = quotemeta ($branch_name); + my $trunk_esc = quotemeta ($trunk_name); + @mergerx = + ( + qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, + qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, + qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i + ); +} +if ($opt_M) { + unshift (@mergerx, qr/$opt_M/); +} + +# Absolutize filename now, since we will have chdir'ed by the time we +# get around to opening it. +$opt_A = File::Spec->rel2abs($opt_A) if $opt_A; + +our %users = (); +our $users_file = undef; +sub read_users($) { + $users_file = File::Spec->rel2abs(@_); + die "Cannot open $users_file\n" unless -f $users_file; + open(my $authors,$users_file); + while(<$authors>) { + chomp; + next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + (my $user,my $name,my $email) = ($1,$2,$3); + $users{$user} = [$name,$email]; + } + close($authors); +} + +select(STDERR); $|=1; select(STDOUT); + + +package SVNconn; +# Basic SVN connection. +# We're only interested in connecting and downloading, so ... + +use File::Spec; +use File::Temp qw(tempfile); +use POSIX qw(strftime dup2); +use Fcntl qw(SEEK_SET); + +sub new { + my($what,$repo) = @_; + $what=ref($what) if ref($what); + + my $self = {}; + $self->{'buffer'} = ""; + bless($self,$what); + + $repo =~ s#/+$##; + $self->{'fullrep'} = $repo; + $self->conn(); + + return $self; +} + +sub conn { + my $self = shift; + my $repo = $self->{'fullrep'}; + my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider, + SVN::Client::get_ssl_server_trust_file_provider, + SVN::Client::get_username_provider]); + my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool); + die "SVN connection to $repo: $!\n" unless defined $s; + $self->{'svn'} = $s; + $self->{'repo'} = $repo; + $self->{'maxrev'} = $s->get_latest_revnum(); +} + +sub file { + my($self,$path,$rev) = @_; + + my ($fh, $name) = tempfile('gitsvn.XXXXXX', + DIR => File::Spec->tmpdir(), UNLINK => 1); + + print "... $rev $path ...\n" if $opt_v; + my (undef, $properties); + $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; + eval { (undef, $properties) + = $self->{'svn'}->get_file($path,$rev,$fh); }; + if($@) { + return undef if $@ =~ /Attempted to get checksum/; + die $@; + } + my $mode; + if (exists $properties->{'svn:executable'}) { + $mode = '100755'; + } elsif (exists $properties->{'svn:special'}) { + my ($special_content, $filesize); + $filesize = tell $fh; + seek $fh, 0, SEEK_SET; + read $fh, $special_content, $filesize; + if ($special_content =~ s/^link //) { + $mode = '120000'; + seek $fh, 0, SEEK_SET; + truncate $fh, 0; + print $fh $special_content; + } else { + die "unexpected svn:special file encountered"; + } + } else { + $mode = '100644'; + } + close ($fh); + + return ($name, $mode); +} + +sub ignore { + my($self,$path,$rev) = @_; + + print "... $rev $path ...\n" if $opt_v; + $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; + my (undef,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + if (exists $properties->{'svn:ignore'}) { + my ($fh, $name) = tempfile('gitsvn.XXXXXX', + DIR => File::Spec->tmpdir(), + UNLINK => 1); + print $fh $properties->{'svn:ignore'}; + close($fh); + return $name; + } else { + return undef; + } +} + +sub dir_list { + my($self,$path,$rev) = @_; + $path =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; + my ($dirents,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + return $dirents; +} + +package main; +use URI; + +our $svn = $svn_url; +$svn .= "/$svn_dir" if defined $svn_dir; +my $svn2 = SVNconn->new($svn); +$svn = SVNconn->new($svn); + +my $lwp_ua; +if($opt_d or $opt_D) { + $svn_url = URI->new($svn_url)->canonical; + if($opt_D) { + $svn_dir =~ s#/*$#/#; + } else { + $svn_dir = ""; + } + if ($svn_url->scheme eq "http") { + use LWP::UserAgent; + $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []); + } else { + print STDERR "Warning: not HTTP; turning off direct file access\n"; + $opt_d=0; + } +} + +sub pdate($) { + my($d) = @_; + $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)# + or die "Unparseable date: $d\n"; + my $y=$1; $y-=1900 if $y>1900; + return timegm($6||0,$5,$4,$3,$2-1,$y); +} + +sub getwd() { + my $pwd = `pwd`; + chomp $pwd; + return $pwd; +} + + +sub get_headref($$) { + my $name = shift; + my $git_dir = shift; + my $sha; + + if (open(C,"$git_dir/refs/heads/$name")) { + chomp($sha = ); + close(C); + length($sha) == 40 + or die "Cannot get head id for $name ($sha): $!\n"; + } + return $sha; +} + + +-d $git_tree + or mkdir($git_tree,0777) + or die "Could not create $git_tree: $!"; +chdir($git_tree); + +my $orig_branch = ""; +my $forward_master = 0; +my %branches; + +my $git_dir = $ENV{"GIT_DIR"} || ".git"; +$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#; +$ENV{"GIT_DIR"} = $git_dir; +my $orig_git_index; +$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; +my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx', + DIR => File::Spec->tmpdir()); +close ($git_ih); +$ENV{GIT_INDEX_FILE} = $git_index; +my $maxnum = 0; +my $last_rev = ""; +my $last_branch; +my $current_rev = $opt_s || 1; +unless(-d $git_dir) { + system("git-init"); + die "Cannot init the GIT db at $git_tree: $?\n" if $?; + system("git-read-tree"); + die "Cannot init an empty tree: $?\n" if $?; + + $last_branch = $opt_o; + $orig_branch = ""; +} else { + -f "$git_dir/refs/heads/$opt_o" + or die "Branch '$opt_o' does not exist.\n". + "Either use the correct '-o branch' option,\n". + "or import to a new repository.\n"; + + -f "$git_dir/svn2git" + or die "'$git_dir/svn2git' does not exist.\n". + "You need that file for incremental imports.\n"; + open(F, "git-symbolic-ref HEAD |") or + die "Cannot run git-symbolic-ref: $!\n"; + chomp ($last_branch = ); + $last_branch = basename($last_branch); + close(F); + unless($last_branch) { + warn "Cannot read the last branch name: $! -- assuming 'master'\n"; + $last_branch = "master"; + } + $orig_branch = $last_branch; + $last_rev = get_headref($orig_branch, $git_dir); + if (-f "$git_dir/SVN2GIT_HEAD") { + die <) { + chomp; + my($num,$branch,$ref) = split; + $branches{$branch}{$num} = $ref; + $branches{$branch}{"LAST"} = $ref; + $current_rev = $num+1 if $current_rev <= $num; + } + close($B); +} +-d $git_dir + or die "Could not create git subdir ($git_dir).\n"; + +my $default_authors = "$git_dir/svn-authors"; +if ($opt_A) { + read_users($opt_A); + copy($opt_A,$default_authors) or die "Copy failed: $!"; +} else { + read_users($default_authors) if -f $default_authors; +} + +open BRANCHES,">>", "$git_dir/svn2git"; + +sub node_kind($$) { + my ($svnpath, $revision) = @_; + $svnpath =~ s#^/*##; + my $subpool = SVN::Pool::new_default_sub; + my $kind = $svn->{'svn'}->check_path($svnpath,$revision); + return $kind; +} + +sub get_file($$$) { + my($svnpath,$rev,$path) = @_; + + # now get it + my ($name,$mode); + if($opt_d) { + my($req,$res); + + # /svn/!svn/bc/2/django/trunk/django-docs/build.py + my $url=$svn_url->clone(); + $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath"); + print "... $path...\n" if $opt_v; + $req = HTTP::Request->new(GET => $url); + $res = $lwp_ua->request($req); + if ($res->is_success) { + my $fh; + ($fh, $name) = tempfile('gitsvn.XXXXXX', + DIR => File::Spec->tmpdir(), UNLINK => 1); + print $fh $res->content; + close($fh) or die "Could not write $name: $!\n"; + } else { + return undef if $res->code == 301; # directory? + die $res->status_line." at $url\n"; + } + $mode = '0644'; # can't obtain mode via direct http request? + } else { + ($name,$mode) = $svn->file("$svnpath",$rev); + return undef unless defined $name; + } + + my $pid = open(my $F, '-|'); + die $! unless defined $pid; + if (!$pid) { + exec("git-hash-object", "-w", $name) + or die "Cannot create object: $!\n"; + } + my $sha = <$F>; + chomp $sha; + close $F; + unlink $name; + return [$mode, $sha, $path]; +} + +sub get_ignore($$$$$) { + my($new,$old,$rev,$path,$svnpath) = @_; + + return unless $opt_I; + my $name = $svn->ignore("$svnpath",$rev); + if ($path eq '/') { + $path = $opt_I; + } else { + $path = File::Spec->catfile($path,$opt_I); + } + if (defined $name) { + my $pid = open(my $F, '-|'); + die $! unless defined $pid; + if (!$pid) { + exec("git-hash-object", "-w", $name) + or die "Cannot create object: $!\n"; + } + my $sha = <$F>; + chomp $sha; + close $F; + unlink $name; + push(@$new,['0644',$sha,$path]); + } elsif (defined $old) { + push(@$old,$path); + } +} + +sub project_path($$) +{ + my ($path, $project) = @_; + + $path = "/".$path unless ($path =~ m#^\/#) ; + return $1 if ($path =~ m#^$project\/(.*)$#); + + $path =~ s#\.#\\\.#g; + $path =~ s#\+#\\\+#g; + return "/" if ($project =~ m#^$path.*$#); + + return undef; +} + +sub split_path($$) { + my($rev,$path) = @_; + my $branch; + + if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) { + $branch = "/$1"; + } elsif($path =~ s#^/\Q$trunk_name\E/?##) { + $branch = "/"; + } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) { + $branch = $1; + } else { + my %no_error = ( + "/" => 1, + "/$tag_name" => 1, + "/$branch_name" => 1 + ); + print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path}); + return () + } + if ($path eq "") { + $path = "/"; + } elsif ($project_name) { + $path = project_path($path, $project_name); + } + return ($branch,$path); +} + +sub branch_rev($$) { + + my ($srcbranch,$uptorev) = @_; + + my $bbranches = $branches{$srcbranch}; + my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches; + my $therev; + foreach my $arev(@revs) { + next if ($arev eq 'LAST'); + if ($arev <= $uptorev) { + $therev = $arev; + last; + } + } + return $therev; +} + +sub expand_svndir($$$); + +sub expand_svndir($$$) +{ + my ($svnpath, $rev, $path) = @_; + my @list; + get_ignore(\@list, undef, $rev, $path, $svnpath); + my $dirents = $svn->dir_list($svnpath, $rev); + foreach my $p(keys %$dirents) { + my $kind = node_kind($svnpath.'/'.$p, $rev); + if ($kind eq $SVN::Node::file) { + my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p); + push(@list, $f) if $f; + } elsif ($kind eq $SVN::Node::dir) { + push(@list, + expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p)); + } + } + return @list; +} + +sub copy_path($$$$$$$$) { + # Somebody copied a whole subdirectory. + # We need to find the index entries from the old version which the + # SVN log entry points to, and add them to the new place. + + my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_; + + my($srcbranch,$srcpath) = split_path($rev,$oldpath); + unless(defined $srcbranch && defined $srcpath) { + print "Path not found when copying from $oldpath @ $rev.\n". + "Will try to copy from original SVN location...\n" + if $opt_v; + push (@$new, expand_svndir($oldpath, $rev, $path)); + return; + } + my $therev = branch_rev($srcbranch, $rev); + my $gitrev = $branches{$srcbranch}{$therev}; + unless($gitrev) { + print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n"; + return; + } + if ($srcbranch ne $newbranch) { + push(@$parents, $branches{$srcbranch}{'LAST'}); + } + print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v; + if ($node_kind eq $SVN::Node::dir) { + $srcpath =~ s#/*$#/#; + } + + my $pid = open my $f,'-|'; + die $! unless defined $pid; + if (!$pid) { + exec("git-ls-tree","-r","-z",$gitrev,$srcpath) + or die $!; + } + local $/ = "\0"; + while(<$f>) { + chomp; + my($m,$p) = split(/\t/,$_,2); + my($mode,$type,$sha1) = split(/ /,$m); + next if $type ne "blob"; + if ($node_kind eq $SVN::Node::dir) { + $p = $path . substr($p,length($srcpath)-1); + } else { + $p = $path; + } + push(@$new,[$mode,$sha1,$p]); + } + close($f) or + print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n"; +} + +sub commit { + my($branch, $changed_paths, $revision, $author, $date, $message) = @_; + my($committer_name,$committer_email,$dest); + my($author_name,$author_email); + my(@old,@new,@parents); + + if (not defined $author or $author eq "") { + $committer_name = $committer_email = "unknown"; + } elsif (defined $users_file) { + die "User $author is not listed in $users_file\n" + unless exists $users{$author}; + ($committer_name,$committer_email) = @{$users{$author}}; + } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { + ($committer_name, $committer_email) = ($1, $2); + } else { + $author =~ s/^<(.*)>$/$1/; + $committer_name = $committer_email = $author; + } + + if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + print "Author from From: $1 <$2>\n" if ($opt_v);; + } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);; + } else { + $author_name = $committer_name; + $author_email = $committer_email; + } + + $date = pdate($date); + + my $tag; + my $parent; + if($branch eq "/") { # trunk + $parent = $opt_o; + } elsif($branch =~ m#^/(.+)#) { # tag + $tag = 1; + $parent = $1; + } else { # "normal" branch + # nothing to do + $parent = $branch; + } + $dest = $parent; + + my $prev = $changed_paths->{"/"}; + if($prev and $prev->[0] eq "A") { + delete $changed_paths->{"/"}; + my $oldpath = $prev->[1]; + my $rev; + if(defined $oldpath) { + my $p; + ($parent,$p) = split_path($revision,$oldpath); + if(defined $parent) { + if($parent eq "/") { + $parent = $opt_o; + } else { + $parent =~ s#^/##; # if it's a tag + } + } + } else { + $parent = undef; + } + } + + my $rev; + if($revision > $opt_s and defined $parent) { + open(H,'-|',"git-rev-parse","--verify",$parent); + $rev = ; + close(H) or do { + print STDERR "$revision: cannot find commit '$parent'!\n"; + return; + }; + chop $rev; + if(length($rev) != 40) { + print STDERR "$revision: cannot find commit '$parent'!\n"; + return; + } + $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"}; + if($revision != $opt_s and not $rev) { + print STDERR "$revision: do not know ancestor for '$parent'!\n"; + return; + } + } else { + $rev = undef; + } + +# if($prev and $prev->[0] eq "A") { +# if(not $tag) { +# unless(open(H,"> $git_dir/refs/heads/$branch")) { +# print STDERR "$revision: Could not create branch $branch: $!\n"; +# $state=11; +# next; +# } +# print H "$rev\n" +# or die "Could not write branch $branch: $!"; +# close(H) +# or die "Could not write branch $branch: $!"; +# } +# } + if(not defined $rev) { + unlink($git_index); + } elsif ($rev ne $last_rev) { + print "Switching from $last_rev to $rev ($branch)\n" if $opt_v; + system("git-read-tree", $rev); + die "read-tree failed for $rev: $?\n" if $?; + $last_rev = $rev; + } + + push (@parents, $rev) if defined $rev; + + my $cid; + if($tag and not %$changed_paths) { + $cid = $rev; + } else { + my @paths = sort keys %$changed_paths; + foreach my $path(@paths) { + my $action = $changed_paths->{$path}; + + if ($action->[0] eq "R") { + # refer to a file/tree in an earlier commit + push(@old,$path); # remove any old stuff + } + if(($action->[0] eq "A") || ($action->[0] eq "R")) { + my $node_kind = node_kind($action->[3], $revision); + if ($node_kind eq $SVN::Node::file) { + my $f = get_file($action->[3], + $revision, $path); + if ($f) { + push(@new,$f) if $f; + } else { + my $opath = $action->[3]; + print STDERR "$revision: $branch: could not fetch '$opath'\n"; + } + } elsif ($node_kind eq $SVN::Node::dir) { + if($action->[1]) { + copy_path($revision, $branch, + $path, $action->[1], + $action->[2], $node_kind, + \@new, \@parents); + } else { + get_ignore(\@new, \@old, $revision, + $path, $action->[3]); + } + } + } elsif ($action->[0] eq "D") { + push(@old,$path); + } elsif ($action->[0] eq "M") { + my $node_kind = node_kind($action->[3], $revision); + if ($node_kind eq $SVN::Node::file) { + my $f = get_file($action->[3], + $revision, $path); + push(@new,$f) if $f; + } elsif ($node_kind eq $SVN::Node::dir) { + get_ignore(\@new, \@old, $revision, + $path, $action->[3]); + } + } else { + die "$revision: unknown action '".$action->[0]."' for $path\n"; + } + } + + while(@old) { + my @o1; + if(@old > 55) { + @o1 = splice(@old,0,50); + } else { + @o1 = @old; + @old = (); + } + my $pid = open my $F, "-|"; + die "$!" unless defined $pid; + if (!$pid) { + exec("git-ls-files", "-z", @o1) or die $!; + } + @o1 = (); + local $/ = "\0"; + while(<$F>) { + chomp; + push(@o1,$_); + } + close($F); + + while(@o1) { + my @o2; + if(@o1 > 55) { + @o2 = splice(@o1,0,50); + } else { + @o2 = @o1; + @o1 = (); + } + system("git-update-index","--force-remove","--",@o2); + die "Cannot remove files: $?\n" if $?; + } + } + while(@new) { + my @n2; + if(@new > 12) { + @n2 = splice(@new,0,10); + } else { + @n2 = @new; + @new = (); + } + system("git-update-index","--add", + (map { ('--cacheinfo', @$_) } @n2)); + die "Cannot add files: $?\n" if $?; + } + + my $pid = open(C,"-|"); + die "Cannot fork: $!" unless defined $pid; + unless($pid) { + exec("git-write-tree"); + die "Cannot exec git-write-tree: $!\n"; + } + chomp(my $tree = ); + length($tree) == 40 + or die "Cannot get tree id ($tree): $!\n"; + close(C) + or die "Error running git-write-tree: $?\n"; + print "Tree ID $tree\n" if $opt_v; + + my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n"; + my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n"; + $pid = fork(); + die "Fork: $!\n" unless defined $pid; + unless($pid) { + $pr->writer(); + $pw->reader(); + open(OUT,">&STDOUT"); + dup2($pw->fileno(),0); + dup2($pr->fileno(),1); + $pr->close(); + $pw->close(); + + my @par = (); + + # loose detection of merges + # based on the commit msg + foreach my $rx (@mergerx) { + if ($message =~ $rx) { + my $mparent = $1; + if ($mparent eq 'HEAD') { $mparent = $opt_o }; + if ( -e "$git_dir/refs/heads/$mparent") { + $mparent = get_headref($mparent, $git_dir); + push (@parents, $mparent); + print OUT "Merge parent branch: $mparent\n" if $opt_v; + } + } + } + my %seen_parents = (); + my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents; + foreach my $bparent (@unique_parents) { + push @par, '-p', $bparent; + print OUT "Merge parent branch: $bparent\n" if $opt_v; + } + + exec("env", + "GIT_AUTHOR_NAME=$author_name", + "GIT_AUTHOR_EMAIL=$author_email", + "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), + "GIT_COMMITTER_NAME=$committer_name", + "GIT_COMMITTER_EMAIL=$committer_email", + "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), + "git-commit-tree", $tree,@par); + die "Cannot exec git-commit-tree: $!\n"; + } + $pw->writer(); + $pr->reader(); + + $message =~ s/[\s\n]+\z//; + $message = "r$revision: $message" if $opt_r; + + print $pw "$message\n" + or die "Error writing to git-commit-tree: $!\n"; + $pw->close(); + + print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v; + chomp($cid = <$pr>); + length($cid) == 40 + or die "Cannot get commit id ($cid): $!\n"; + print "Commit ID $cid\n" if $opt_v; + $pr->close(); + + waitpid($pid,0); + die "Error running git-commit-tree: $?\n" if $?; + } + + if (not defined $cid) { + $cid = $branches{"/"}{"LAST"}; + } + + if(not defined $dest) { + print "... no known parent\n" if $opt_v; + } elsif(not $tag) { + print "Writing to refs/heads/$dest\n" if $opt_v; + open(C,">$git_dir/refs/heads/$dest") and + print C ("$cid\n") and + close(C) + or die "Cannot write branch $dest for update: $!\n"; + } + + if ($tag) { + $last_rev = "-" if %$changed_paths; + # the tag was 'complex', i.e. did not refer to a "real" revision + + $dest =~ tr/_/\./ if $opt_u; + + system('git-tag', '-f', $dest, $cid) == 0 + or die "Cannot create tag $dest: $!\n"; + + print "Created tag '$dest' on '$branch'\n" if $opt_v; + } + $branches{$branch}{"LAST"} = $cid; + $branches{$branch}{$revision} = $cid; + $last_rev = $cid; + print BRANCHES "$revision $branch $cid\n"; + print "DONE: $revision $dest $cid\n" if $opt_v; +} + +sub commit_all { + # Recursive use of the SVN connection does not work + local $svn = $svn2; + + my ($changed_paths, $revision, $author, $date, $message) = @_; + my %p; + while(my($path,$action) = each %$changed_paths) { + $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ]; + } + $changed_paths = \%p; + + my %done; + my @col; + my $pref; + my $branch; + + while(my($path,$action) = each %$changed_paths) { + ($branch,$path) = split_path($revision,$path); + next if not defined $branch; + next if not defined $path; + $done{$branch}{$path} = $action; + } + while(($branch,$changed_paths) = each %done) { + commit($branch, $changed_paths, $revision, $author, $date, $message); + } +} + +$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'}; + +if ($opt_l < $current_rev) { + print "Up to date: no new revisions to fetch!\n" if $opt_v; + unlink("$git_dir/SVN2GIT_HEAD"); + exit; +} + +print "Processing from $current_rev to $opt_l ...\n" if $opt_v; + +my $from_rev; +my $to_rev = $current_rev - 1; + +my $subpool = SVN::Pool::new_default_sub; +while ($to_rev < $opt_l) { + $subpool->clear; + $from_rev = $to_rev + 1; + $to_rev = $from_rev + $repack_after; + $to_rev = $opt_l if $opt_l < $to_rev; + print "Fetching from $from_rev to $to_rev ...\n" if $opt_v; + $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all); + my $pid = fork(); + die "Fork: $!\n" unless defined $pid; + unless($pid) { + exec("git-repack", "-d") + or die "Cannot repack: $!\n"; + } + waitpid($pid, 0); +} + + +unlink($git_index); + +if (defined $orig_git_index) { + $ENV{GIT_INDEX_FILE} = $orig_git_index; +} else { + delete $ENV{GIT_INDEX_FILE}; +} + +# Now switch back to the branch we were in before all of this happened +if($orig_branch) { + print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0); + system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") + if $forward_master; + unless ($opt_i) { + system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); + die "read-tree failed: $?\n" if $?; + } +} else { + $orig_branch = "master"; + print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); + system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") + unless -f "$git_dir/refs/heads/master"; + system('git-update-ref', 'HEAD', "$orig_branch"); + unless ($opt_i) { + system('git checkout'); + die "checkout failed: $?\n" if $?; + } +} +unlink("$git_dir/SVN2GIT_HEAD"); +close(BRANCHES); diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt new file mode 100644 index 000000000..71aad8b45 --- /dev/null +++ b/contrib/examples/git-svnimport.txt @@ -0,0 +1,179 @@ +git-svnimport(1) +================ +v0.1, July 2005 + +NAME +---- +git-svnimport - Import a SVN repository into git + + +SYNOPSIS +-------- +[verse] +'git-svnimport' [ -o ] [ -h ] [ -v ] [ -d | -D ] + [ -C ] [ -i ] [ -u ] [-l limit_rev] + [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] + [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] + [ -I ] [ -A ] + [ -R ] [ -P ] + [ ] + + +DESCRIPTION +----------- +Imports a SVN repository into git. It will either create a new +repository, or incrementally import into an existing one. + +SVN access is done by the SVN::Perl module. + +git-svnimport assumes that SVN repositories are organized into one +"trunk" directory where the main development happens, "branches/FOO" +directories for branches, and "/tags/FOO" directories for tags. +Other subdirectories are ignored. + +git-svnimport creates a file ".git/svn2git", which is required for +incremental SVN imports. + +OPTIONS +------- +-C :: + The GIT repository to import to. If the directory doesn't + exist, it will be created. Default is the current directory. + +-s :: + Start importing at this SVN change number. The default is 1. ++ +When importing incrementally, you might need to edit the .git/svn2git file. + +-i:: + Import-only: don't perform a checkout after importing. This option + ensures the working directory and index remain untouched and will + not create them if they do not exist. + +-T :: + Name the SVN trunk. Default "trunk". + +-t :: + Name the SVN subdirectory for tags. Default "tags". + +-b :: + Name the SVN subdirectory for branches. Default "branches". + +-o :: + The 'trunk' branch from SVN is imported to the 'origin' branch within + the git repository. Use this option if you want to import into a + different branch. + +-r:: + Prepend 'rX: ' to commit messages, where X is the imported + subversion revision. + +-u:: + Replace underscores in tag names with periods. + +-I :: + Import the svn:ignore directory property to files with this + name in each directory. (The Subversion and GIT ignore + syntaxes are similar enough that using the Subversion patterns + directly with "-I .gitignore" will almost always just work.) + +-A :: + Read a file with lines on the form ++ +------ + username = User's Full Name + +------ ++ +and use "User's Full Name " as the GIT +author and committer for Subversion commits made by +"username". If encountering a commit made by a user not in the +list, abort. ++ +For convenience, this data is saved to $GIT_DIR/svn-authors +each time the -A option is provided, and read from that same +file each time git-svnimport is run with an existing GIT +repository without -A. + +-m:: + Attempt to detect merges based on the commit message. This option + will enable default regexes that try to capture the name source + branch name from the commit message. + +-M :: + Attempt to detect merges based on the commit message with a custom + regex. It can be used with -m to also see the default regexes. + You must escape forward slashes. + +-l :: + Specify a maximum revision number to pull. ++ +Formerly, this option controlled how many revisions to pull, +due to SVN memory leaks. (These have been worked around.) + +-R :: + Specify how often git repository should be repacked. ++ +The default value is 1000. git-svnimport will do import in chunks of 1000 +revisions, after each chunk git repository will be repacked. To disable +this behavior specify some big value here which is mote than number of +revisions to import. + +-P :: + Partial import of the SVN tree. ++ +By default, the whole tree on the SVN trunk (/trunk) is imported. +'-P my/proj' will import starting only from '/trunk/my/proj'. +This option is useful when you want to import one project from a +svn repo which hosts multiple projects under the same trunk. + +-v:: + Verbosity: let 'svnimport' report what it is doing. + +-d:: + Use direct HTTP requests if possible. The "" argument is used + only for retrieving the SVN logs; the path to the contents is + included in the SVN log. + +-D:: + Use direct HTTP requests if possible. The "" argument is used + for retrieving the logs, as well as for the contents. ++ +There's no safe way to automatically find out which of these options to +use, so you need to try both. Usually, the one that's wrong will die +with a 40x error pretty quickly. + +:: + The URL of the SVN module you want to import. For local + repositories, use "file:///absolute/path". ++ +If you're using the "-d" or "-D" option, this is the URL of the SVN +repository itself; it usually ends in "/svn". + +:: + The path to the module you want to check out. + +-h:: + Print a short usage message and exit. + +OUTPUT +------ +If '-v' is specified, the script reports what it is doing. + +Otherwise, success is indicated the Unix way, i.e. by simply exiting with +a zero exit status. + +Author +------ +Written by Matthias Urlichs , with help from +various participants of the git-list . + +Based on a cvs2git script by the same author. + +Documentation +-------------- +Documentation by Matthias Urlichs . + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/git-svnimport.perl b/git-svnimport.perl deleted file mode 100755 index ea8c1b2f6..000000000 --- a/git-svnimport.perl +++ /dev/null @@ -1,976 +0,0 @@ -#!/usr/bin/perl -w - -# This tool is copyright (c) 2005, Matthias Urlichs. -# It is released under the Gnu Public License, version 2. -# -# The basic idea is to pull and analyze SVN changes. -# -# Checking out the files is done by a single long-running SVN connection. -# -# The head revision is on branch "origin" by default. -# You can change that with the '-o' option. - -use strict; -use warnings; -use Getopt::Std; -use File::Copy; -use File::Spec; -use File::Temp qw(tempfile); -use File::Path qw(mkpath); -use File::Basename qw(basename dirname); -use Time::Local; -use IO::Pipe; -use POSIX qw(strftime dup2); -use IPC::Open2; -use SVN::Core; -use SVN::Ra; - -die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1"; - -$SIG{'PIPE'}="IGNORE"; -$ENV{'TZ'}="UTC"; - -our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F, - $opt_P,$opt_R); - -sub usage() { - print STDERR <new_default; - -@ARGV == 1 or @ARGV == 2 or usage(); - -$opt_o ||= "origin"; -$opt_s ||= 1; -my $git_tree = $opt_C; -$git_tree ||= "."; - -my $svn_url = $ARGV[0]; -my $svn_dir = $ARGV[1]; - -our @mergerx = (); -if ($opt_m) { - my $branch_esc = quotemeta ($branch_name); - my $trunk_esc = quotemeta ($trunk_name); - @mergerx = - ( - qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, - qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, - qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i - ); -} -if ($opt_M) { - unshift (@mergerx, qr/$opt_M/); -} - -# Absolutize filename now, since we will have chdir'ed by the time we -# get around to opening it. -$opt_A = File::Spec->rel2abs($opt_A) if $opt_A; - -our %users = (); -our $users_file = undef; -sub read_users($) { - $users_file = File::Spec->rel2abs(@_); - die "Cannot open $users_file\n" unless -f $users_file; - open(my $authors,$users_file); - while(<$authors>) { - chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; - (my $user,my $name,my $email) = ($1,$2,$3); - $users{$user} = [$name,$email]; - } - close($authors); -} - -select(STDERR); $|=1; select(STDOUT); - - -package SVNconn; -# Basic SVN connection. -# We're only interested in connecting and downloading, so ... - -use File::Spec; -use File::Temp qw(tempfile); -use POSIX qw(strftime dup2); -use Fcntl qw(SEEK_SET); - -sub new { - my($what,$repo) = @_; - $what=ref($what) if ref($what); - - my $self = {}; - $self->{'buffer'} = ""; - bless($self,$what); - - $repo =~ s#/+$##; - $self->{'fullrep'} = $repo; - $self->conn(); - - return $self; -} - -sub conn { - my $self = shift; - my $repo = $self->{'fullrep'}; - my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider, - SVN::Client::get_ssl_server_trust_file_provider, - SVN::Client::get_username_provider]); - my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool); - die "SVN connection to $repo: $!\n" unless defined $s; - $self->{'svn'} = $s; - $self->{'repo'} = $repo; - $self->{'maxrev'} = $s->get_latest_revnum(); -} - -sub file { - my($self,$path,$rev) = @_; - - my ($fh, $name) = tempfile('gitsvn.XXXXXX', - DIR => File::Spec->tmpdir(), UNLINK => 1); - - print "... $rev $path ...\n" if $opt_v; - my (undef, $properties); - $path =~ s#^/*##; - my $subpool = SVN::Pool::new_default_sub; - eval { (undef, $properties) - = $self->{'svn'}->get_file($path,$rev,$fh); }; - if($@) { - return undef if $@ =~ /Attempted to get checksum/; - die $@; - } - my $mode; - if (exists $properties->{'svn:executable'}) { - $mode = '100755'; - } elsif (exists $properties->{'svn:special'}) { - my ($special_content, $filesize); - $filesize = tell $fh; - seek $fh, 0, SEEK_SET; - read $fh, $special_content, $filesize; - if ($special_content =~ s/^link //) { - $mode = '120000'; - seek $fh, 0, SEEK_SET; - truncate $fh, 0; - print $fh $special_content; - } else { - die "unexpected svn:special file encountered"; - } - } else { - $mode = '100644'; - } - close ($fh); - - return ($name, $mode); -} - -sub ignore { - my($self,$path,$rev) = @_; - - print "... $rev $path ...\n" if $opt_v; - $path =~ s#^/*##; - my $subpool = SVN::Pool::new_default_sub; - my (undef,undef,$properties) - = $self->{'svn'}->get_dir($path,$rev,undef); - if (exists $properties->{'svn:ignore'}) { - my ($fh, $name) = tempfile('gitsvn.XXXXXX', - DIR => File::Spec->tmpdir(), - UNLINK => 1); - print $fh $properties->{'svn:ignore'}; - close($fh); - return $name; - } else { - return undef; - } -} - -sub dir_list { - my($self,$path,$rev) = @_; - $path =~ s#^/*##; - my $subpool = SVN::Pool::new_default_sub; - my ($dirents,undef,$properties) - = $self->{'svn'}->get_dir($path,$rev,undef); - return $dirents; -} - -package main; -use URI; - -our $svn = $svn_url; -$svn .= "/$svn_dir" if defined $svn_dir; -my $svn2 = SVNconn->new($svn); -$svn = SVNconn->new($svn); - -my $lwp_ua; -if($opt_d or $opt_D) { - $svn_url = URI->new($svn_url)->canonical; - if($opt_D) { - $svn_dir =~ s#/*$#/#; - } else { - $svn_dir = ""; - } - if ($svn_url->scheme eq "http") { - use LWP::UserAgent; - $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []); - } else { - print STDERR "Warning: not HTTP; turning off direct file access\n"; - $opt_d=0; - } -} - -sub pdate($) { - my($d) = @_; - $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)# - or die "Unparseable date: $d\n"; - my $y=$1; $y-=1900 if $y>1900; - return timegm($6||0,$5,$4,$3,$2-1,$y); -} - -sub getwd() { - my $pwd = `pwd`; - chomp $pwd; - return $pwd; -} - - -sub get_headref($$) { - my $name = shift; - my $git_dir = shift; - my $sha; - - if (open(C,"$git_dir/refs/heads/$name")) { - chomp($sha = ); - close(C); - length($sha) == 40 - or die "Cannot get head id for $name ($sha): $!\n"; - } - return $sha; -} - - --d $git_tree - or mkdir($git_tree,0777) - or die "Could not create $git_tree: $!"; -chdir($git_tree); - -my $orig_branch = ""; -my $forward_master = 0; -my %branches; - -my $git_dir = $ENV{"GIT_DIR"} || ".git"; -$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#; -$ENV{"GIT_DIR"} = $git_dir; -my $orig_git_index; -$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; -my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx', - DIR => File::Spec->tmpdir()); -close ($git_ih); -$ENV{GIT_INDEX_FILE} = $git_index; -my $maxnum = 0; -my $last_rev = ""; -my $last_branch; -my $current_rev = $opt_s || 1; -unless(-d $git_dir) { - system("git-init"); - die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system("git-read-tree"); - die "Cannot init an empty tree: $?\n" if $?; - - $last_branch = $opt_o; - $orig_branch = ""; -} else { - -f "$git_dir/refs/heads/$opt_o" - or die "Branch '$opt_o' does not exist.\n". - "Either use the correct '-o branch' option,\n". - "or import to a new repository.\n"; - - -f "$git_dir/svn2git" - or die "'$git_dir/svn2git' does not exist.\n". - "You need that file for incremental imports.\n"; - open(F, "git-symbolic-ref HEAD |") or - die "Cannot run git-symbolic-ref: $!\n"; - chomp ($last_branch = ); - $last_branch = basename($last_branch); - close(F); - unless($last_branch) { - warn "Cannot read the last branch name: $! -- assuming 'master'\n"; - $last_branch = "master"; - } - $orig_branch = $last_branch; - $last_rev = get_headref($orig_branch, $git_dir); - if (-f "$git_dir/SVN2GIT_HEAD") { - die <) { - chomp; - my($num,$branch,$ref) = split; - $branches{$branch}{$num} = $ref; - $branches{$branch}{"LAST"} = $ref; - $current_rev = $num+1 if $current_rev <= $num; - } - close($B); -} --d $git_dir - or die "Could not create git subdir ($git_dir).\n"; - -my $default_authors = "$git_dir/svn-authors"; -if ($opt_A) { - read_users($opt_A); - copy($opt_A,$default_authors) or die "Copy failed: $!"; -} else { - read_users($default_authors) if -f $default_authors; -} - -open BRANCHES,">>", "$git_dir/svn2git"; - -sub node_kind($$) { - my ($svnpath, $revision) = @_; - $svnpath =~ s#^/*##; - my $subpool = SVN::Pool::new_default_sub; - my $kind = $svn->{'svn'}->check_path($svnpath,$revision); - return $kind; -} - -sub get_file($$$) { - my($svnpath,$rev,$path) = @_; - - # now get it - my ($name,$mode); - if($opt_d) { - my($req,$res); - - # /svn/!svn/bc/2/django/trunk/django-docs/build.py - my $url=$svn_url->clone(); - $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath"); - print "... $path...\n" if $opt_v; - $req = HTTP::Request->new(GET => $url); - $res = $lwp_ua->request($req); - if ($res->is_success) { - my $fh; - ($fh, $name) = tempfile('gitsvn.XXXXXX', - DIR => File::Spec->tmpdir(), UNLINK => 1); - print $fh $res->content; - close($fh) or die "Could not write $name: $!\n"; - } else { - return undef if $res->code == 301; # directory? - die $res->status_line." at $url\n"; - } - $mode = '0644'; # can't obtain mode via direct http request? - } else { - ($name,$mode) = $svn->file("$svnpath",$rev); - return undef unless defined $name; - } - - my $pid = open(my $F, '-|'); - die $! unless defined $pid; - if (!$pid) { - exec("git-hash-object", "-w", $name) - or die "Cannot create object: $!\n"; - } - my $sha = <$F>; - chomp $sha; - close $F; - unlink $name; - return [$mode, $sha, $path]; -} - -sub get_ignore($$$$$) { - my($new,$old,$rev,$path,$svnpath) = @_; - - return unless $opt_I; - my $name = $svn->ignore("$svnpath",$rev); - if ($path eq '/') { - $path = $opt_I; - } else { - $path = File::Spec->catfile($path,$opt_I); - } - if (defined $name) { - my $pid = open(my $F, '-|'); - die $! unless defined $pid; - if (!$pid) { - exec("git-hash-object", "-w", $name) - or die "Cannot create object: $!\n"; - } - my $sha = <$F>; - chomp $sha; - close $F; - unlink $name; - push(@$new,['0644',$sha,$path]); - } elsif (defined $old) { - push(@$old,$path); - } -} - -sub project_path($$) -{ - my ($path, $project) = @_; - - $path = "/".$path unless ($path =~ m#^\/#) ; - return $1 if ($path =~ m#^$project\/(.*)$#); - - $path =~ s#\.#\\\.#g; - $path =~ s#\+#\\\+#g; - return "/" if ($project =~ m#^$path.*$#); - - return undef; -} - -sub split_path($$) { - my($rev,$path) = @_; - my $branch; - - if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) { - $branch = "/$1"; - } elsif($path =~ s#^/\Q$trunk_name\E/?##) { - $branch = "/"; - } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) { - $branch = $1; - } else { - my %no_error = ( - "/" => 1, - "/$tag_name" => 1, - "/$branch_name" => 1 - ); - print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path}); - return () - } - if ($path eq "") { - $path = "/"; - } elsif ($project_name) { - $path = project_path($path, $project_name); - } - return ($branch,$path); -} - -sub branch_rev($$) { - - my ($srcbranch,$uptorev) = @_; - - my $bbranches = $branches{$srcbranch}; - my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches; - my $therev; - foreach my $arev(@revs) { - next if ($arev eq 'LAST'); - if ($arev <= $uptorev) { - $therev = $arev; - last; - } - } - return $therev; -} - -sub expand_svndir($$$); - -sub expand_svndir($$$) -{ - my ($svnpath, $rev, $path) = @_; - my @list; - get_ignore(\@list, undef, $rev, $path, $svnpath); - my $dirents = $svn->dir_list($svnpath, $rev); - foreach my $p(keys %$dirents) { - my $kind = node_kind($svnpath.'/'.$p, $rev); - if ($kind eq $SVN::Node::file) { - my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p); - push(@list, $f) if $f; - } elsif ($kind eq $SVN::Node::dir) { - push(@list, - expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p)); - } - } - return @list; -} - -sub copy_path($$$$$$$$) { - # Somebody copied a whole subdirectory. - # We need to find the index entries from the old version which the - # SVN log entry points to, and add them to the new place. - - my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_; - - my($srcbranch,$srcpath) = split_path($rev,$oldpath); - unless(defined $srcbranch && defined $srcpath) { - print "Path not found when copying from $oldpath @ $rev.\n". - "Will try to copy from original SVN location...\n" - if $opt_v; - push (@$new, expand_svndir($oldpath, $rev, $path)); - return; - } - my $therev = branch_rev($srcbranch, $rev); - my $gitrev = $branches{$srcbranch}{$therev}; - unless($gitrev) { - print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n"; - return; - } - if ($srcbranch ne $newbranch) { - push(@$parents, $branches{$srcbranch}{'LAST'}); - } - print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v; - if ($node_kind eq $SVN::Node::dir) { - $srcpath =~ s#/*$#/#; - } - - my $pid = open my $f,'-|'; - die $! unless defined $pid; - if (!$pid) { - exec("git-ls-tree","-r","-z",$gitrev,$srcpath) - or die $!; - } - local $/ = "\0"; - while(<$f>) { - chomp; - my($m,$p) = split(/\t/,$_,2); - my($mode,$type,$sha1) = split(/ /,$m); - next if $type ne "blob"; - if ($node_kind eq $SVN::Node::dir) { - $p = $path . substr($p,length($srcpath)-1); - } else { - $p = $path; - } - push(@$new,[$mode,$sha1,$p]); - } - close($f) or - print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n"; -} - -sub commit { - my($branch, $changed_paths, $revision, $author, $date, $message) = @_; - my($committer_name,$committer_email,$dest); - my($author_name,$author_email); - my(@old,@new,@parents); - - if (not defined $author or $author eq "") { - $committer_name = $committer_email = "unknown"; - } elsif (defined $users_file) { - die "User $author is not listed in $users_file\n" - unless exists $users{$author}; - ($committer_name,$committer_email) = @{$users{$author}}; - } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { - ($committer_name, $committer_email) = ($1, $2); - } else { - $author =~ s/^<(.*)>$/$1/; - $committer_name = $committer_email = $author; - } - - if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { - ($author_name, $author_email) = ($1, $2); - print "Author from From: $1 <$2>\n" if ($opt_v);; - } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { - ($author_name, $author_email) = ($1, $2); - print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);; - } else { - $author_name = $committer_name; - $author_email = $committer_email; - } - - $date = pdate($date); - - my $tag; - my $parent; - if($branch eq "/") { # trunk - $parent = $opt_o; - } elsif($branch =~ m#^/(.+)#) { # tag - $tag = 1; - $parent = $1; - } else { # "normal" branch - # nothing to do - $parent = $branch; - } - $dest = $parent; - - my $prev = $changed_paths->{"/"}; - if($prev and $prev->[0] eq "A") { - delete $changed_paths->{"/"}; - my $oldpath = $prev->[1]; - my $rev; - if(defined $oldpath) { - my $p; - ($parent,$p) = split_path($revision,$oldpath); - if(defined $parent) { - if($parent eq "/") { - $parent = $opt_o; - } else { - $parent =~ s#^/##; # if it's a tag - } - } - } else { - $parent = undef; - } - } - - my $rev; - if($revision > $opt_s and defined $parent) { - open(H,'-|',"git-rev-parse","--verify",$parent); - $rev = ; - close(H) or do { - print STDERR "$revision: cannot find commit '$parent'!\n"; - return; - }; - chop $rev; - if(length($rev) != 40) { - print STDERR "$revision: cannot find commit '$parent'!\n"; - return; - } - $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"}; - if($revision != $opt_s and not $rev) { - print STDERR "$revision: do not know ancestor for '$parent'!\n"; - return; - } - } else { - $rev = undef; - } - -# if($prev and $prev->[0] eq "A") { -# if(not $tag) { -# unless(open(H,"> $git_dir/refs/heads/$branch")) { -# print STDERR "$revision: Could not create branch $branch: $!\n"; -# $state=11; -# next; -# } -# print H "$rev\n" -# or die "Could not write branch $branch: $!"; -# close(H) -# or die "Could not write branch $branch: $!"; -# } -# } - if(not defined $rev) { - unlink($git_index); - } elsif ($rev ne $last_rev) { - print "Switching from $last_rev to $rev ($branch)\n" if $opt_v; - system("git-read-tree", $rev); - die "read-tree failed for $rev: $?\n" if $?; - $last_rev = $rev; - } - - push (@parents, $rev) if defined $rev; - - my $cid; - if($tag and not %$changed_paths) { - $cid = $rev; - } else { - my @paths = sort keys %$changed_paths; - foreach my $path(@paths) { - my $action = $changed_paths->{$path}; - - if ($action->[0] eq "R") { - # refer to a file/tree in an earlier commit - push(@old,$path); # remove any old stuff - } - if(($action->[0] eq "A") || ($action->[0] eq "R")) { - my $node_kind = node_kind($action->[3], $revision); - if ($node_kind eq $SVN::Node::file) { - my $f = get_file($action->[3], - $revision, $path); - if ($f) { - push(@new,$f) if $f; - } else { - my $opath = $action->[3]; - print STDERR "$revision: $branch: could not fetch '$opath'\n"; - } - } elsif ($node_kind eq $SVN::Node::dir) { - if($action->[1]) { - copy_path($revision, $branch, - $path, $action->[1], - $action->[2], $node_kind, - \@new, \@parents); - } else { - get_ignore(\@new, \@old, $revision, - $path, $action->[3]); - } - } - } elsif ($action->[0] eq "D") { - push(@old,$path); - } elsif ($action->[0] eq "M") { - my $node_kind = node_kind($action->[3], $revision); - if ($node_kind eq $SVN::Node::file) { - my $f = get_file($action->[3], - $revision, $path); - push(@new,$f) if $f; - } elsif ($node_kind eq $SVN::Node::dir) { - get_ignore(\@new, \@old, $revision, - $path, $action->[3]); - } - } else { - die "$revision: unknown action '".$action->[0]."' for $path\n"; - } - } - - while(@old) { - my @o1; - if(@old > 55) { - @o1 = splice(@old,0,50); - } else { - @o1 = @old; - @old = (); - } - my $pid = open my $F, "-|"; - die "$!" unless defined $pid; - if (!$pid) { - exec("git-ls-files", "-z", @o1) or die $!; - } - @o1 = (); - local $/ = "\0"; - while(<$F>) { - chomp; - push(@o1,$_); - } - close($F); - - while(@o1) { - my @o2; - if(@o1 > 55) { - @o2 = splice(@o1,0,50); - } else { - @o2 = @o1; - @o1 = (); - } - system("git-update-index","--force-remove","--",@o2); - die "Cannot remove files: $?\n" if $?; - } - } - while(@new) { - my @n2; - if(@new > 12) { - @n2 = splice(@new,0,10); - } else { - @n2 = @new; - @new = (); - } - system("git-update-index","--add", - (map { ('--cacheinfo', @$_) } @n2)); - die "Cannot add files: $?\n" if $?; - } - - my $pid = open(C,"-|"); - die "Cannot fork: $!" unless defined $pid; - unless($pid) { - exec("git-write-tree"); - die "Cannot exec git-write-tree: $!\n"; - } - chomp(my $tree = ); - length($tree) == 40 - or die "Cannot get tree id ($tree): $!\n"; - close(C) - or die "Error running git-write-tree: $?\n"; - print "Tree ID $tree\n" if $opt_v; - - my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n"; - my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n"; - $pid = fork(); - die "Fork: $!\n" unless defined $pid; - unless($pid) { - $pr->writer(); - $pw->reader(); - open(OUT,">&STDOUT"); - dup2($pw->fileno(),0); - dup2($pr->fileno(),1); - $pr->close(); - $pw->close(); - - my @par = (); - - # loose detection of merges - # based on the commit msg - foreach my $rx (@mergerx) { - if ($message =~ $rx) { - my $mparent = $1; - if ($mparent eq 'HEAD') { $mparent = $opt_o }; - if ( -e "$git_dir/refs/heads/$mparent") { - $mparent = get_headref($mparent, $git_dir); - push (@parents, $mparent); - print OUT "Merge parent branch: $mparent\n" if $opt_v; - } - } - } - my %seen_parents = (); - my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents; - foreach my $bparent (@unique_parents) { - push @par, '-p', $bparent; - print OUT "Merge parent branch: $bparent\n" if $opt_v; - } - - exec("env", - "GIT_AUTHOR_NAME=$author_name", - "GIT_AUTHOR_EMAIL=$author_email", - "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "GIT_COMMITTER_NAME=$committer_name", - "GIT_COMMITTER_EMAIL=$committer_email", - "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "git-commit-tree", $tree,@par); - die "Cannot exec git-commit-tree: $!\n"; - } - $pw->writer(); - $pr->reader(); - - $message =~ s/[\s\n]+\z//; - $message = "r$revision: $message" if $opt_r; - - print $pw "$message\n" - or die "Error writing to git-commit-tree: $!\n"; - $pw->close(); - - print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v; - chomp($cid = <$pr>); - length($cid) == 40 - or die "Cannot get commit id ($cid): $!\n"; - print "Commit ID $cid\n" if $opt_v; - $pr->close(); - - waitpid($pid,0); - die "Error running git-commit-tree: $?\n" if $?; - } - - if (not defined $cid) { - $cid = $branches{"/"}{"LAST"}; - } - - if(not defined $dest) { - print "... no known parent\n" if $opt_v; - } elsif(not $tag) { - print "Writing to refs/heads/$dest\n" if $opt_v; - open(C,">$git_dir/refs/heads/$dest") and - print C ("$cid\n") and - close(C) - or die "Cannot write branch $dest for update: $!\n"; - } - - if ($tag) { - $last_rev = "-" if %$changed_paths; - # the tag was 'complex', i.e. did not refer to a "real" revision - - $dest =~ tr/_/\./ if $opt_u; - - system('git-tag', '-f', $dest, $cid) == 0 - or die "Cannot create tag $dest: $!\n"; - - print "Created tag '$dest' on '$branch'\n" if $opt_v; - } - $branches{$branch}{"LAST"} = $cid; - $branches{$branch}{$revision} = $cid; - $last_rev = $cid; - print BRANCHES "$revision $branch $cid\n"; - print "DONE: $revision $dest $cid\n" if $opt_v; -} - -sub commit_all { - # Recursive use of the SVN connection does not work - local $svn = $svn2; - - my ($changed_paths, $revision, $author, $date, $message) = @_; - my %p; - while(my($path,$action) = each %$changed_paths) { - $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ]; - } - $changed_paths = \%p; - - my %done; - my @col; - my $pref; - my $branch; - - while(my($path,$action) = each %$changed_paths) { - ($branch,$path) = split_path($revision,$path); - next if not defined $branch; - next if not defined $path; - $done{$branch}{$path} = $action; - } - while(($branch,$changed_paths) = each %done) { - commit($branch, $changed_paths, $revision, $author, $date, $message); - } -} - -$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'}; - -if ($opt_l < $current_rev) { - print "Up to date: no new revisions to fetch!\n" if $opt_v; - unlink("$git_dir/SVN2GIT_HEAD"); - exit; -} - -print "Processing from $current_rev to $opt_l ...\n" if $opt_v; - -my $from_rev; -my $to_rev = $current_rev - 1; - -my $subpool = SVN::Pool::new_default_sub; -while ($to_rev < $opt_l) { - $subpool->clear; - $from_rev = $to_rev + 1; - $to_rev = $from_rev + $repack_after; - $to_rev = $opt_l if $opt_l < $to_rev; - print "Fetching from $from_rev to $to_rev ...\n" if $opt_v; - $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all); - my $pid = fork(); - die "Fork: $!\n" unless defined $pid; - unless($pid) { - exec("git-repack", "-d") - or die "Cannot repack: $!\n"; - } - waitpid($pid, 0); -} - - -unlink($git_index); - -if (defined $orig_git_index) { - $ENV{GIT_INDEX_FILE} = $orig_git_index; -} else { - delete $ENV{GIT_INDEX_FILE}; -} - -# Now switch back to the branch we were in before all of this happened -if($orig_branch) { - print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0); - system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") - if $forward_master; - unless ($opt_i) { - system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); - die "read-tree failed: $?\n" if $?; - } -} else { - $orig_branch = "master"; - print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); - system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") - unless -f "$git_dir/refs/heads/master"; - system('git-update-ref', 'HEAD', "$orig_branch"); - unless ($opt_i) { - system('git checkout'); - die "checkout failed: $?\n" if $?; - } -} -unlink("$git_dir/SVN2GIT_HEAD"); -close(BRANCHES);