X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=contrib%2Fgit-svn%2Fgit-svn.perl;h=d8f103ed9adada780b56d5ae9270025e0a48ec6d;hb=f8ab6b732f0a2cead5089aea2ce0c3c3aa97cafe;hp=8bc3d69fdb020eb3d4e036d8a17d3799cc645996;hpb=ce475dfcb5021339d8545a020e8697dd35a1bbea;p=git.git diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index 8bc3d69f..d8f103ed 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -6,7 +6,7 @@ use strict; use vars qw/ $AUTHOR $VERSION $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $REV_DIR/; + $GIT_DIR $REV_DIR $GIT_SVN_DIR/; $AUTHOR = 'Eric Wong '; $VERSION = '1.1.0-pre'; @@ -33,13 +33,20 @@ use POSIX qw/strftime/; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_version, $_upgrade, $_authors); + $_find_copies_harder, $_l, $_cp_similarity, + $_repack, $_repack_nr, $_repack_flags, + $_template, $_shared, + $_version, $_upgrade, $_authors, $_branch_all_refs); my (@_branch_from, %tree_map, %users); my ($_svn_co_url_revs, $_svn_pg_peg_revs); +my @repo_path_split_cache; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, - 'authors-file|A=s' => \$_authors ); + 'branch-all-refs|B' => \$_branch_all_refs, + 'authors-file|A=s' => \$_authors, + 'repack:i' => \$_repack, + 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); # yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); @@ -48,13 +55,16 @@ my %cmd = ( fetch => [ \&fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], init => [ \&init, "Initialize a repo for tracking" . - " (requires URL argument)", { } ], + " (requires URL argument)", + { 'template=s' => \$_template, + 'shared' => \$_shared } ], commit => [ \&commit, "Commit git revisions to SVN", { 'stdin|' => \$_stdin, 'edit|e' => \$_edit, 'rmdir' => \$_rmdir, 'find-copies-harder' => \$_find_copies_harder, 'l=i' => \$_l, + 'copy-similarity|C=i'=> \$_cp_similarity, %fc_opts, } ], 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ], @@ -73,40 +83,20 @@ for (my $i = 0; $i < @ARGV; $i++) { my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); -# convert GetOpt::Long specs for use by git-repo-config -foreach my $o (keys %opts) { - my $v = $opts{$o}; - my ($key) = ($o =~ /^([a-z\-]+)/); - $key =~ s/-//g; - my $arg = 'git-repo-config'; - $arg .= ' --int' if ($o =~ /=i$/); - $arg .= ' --bool' if ($o !~ /=[sfi]$/); - if (ref $v eq 'ARRAY') { - chomp(my @tmp = `$arg --get-all svn.$key`); - @$v = @tmp if @tmp; - } else { - chomp(my $tmp = `$arg --get svn.$key`); - if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) { - $$v = $tmp; - } - } -} - +read_repo_config(\%opts); GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'id|i=s' => \$GIT_SVN) or exit 1; -$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; -$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index"; -$SVN_URL = undef; -$REV_DIR = "$GIT_DIR/$GIT_SVN/revs"; -$SVN_WC = "$GIT_DIR/$GIT_SVN/tree"; - +set_default_vals(); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; +init_vars(); load_authors() if $_authors; +load_all_refs() if $_branch_all_refs; svn_compat_check(); +migration_check() unless $cmd eq 'init'; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -125,7 +115,7 @@ Usage: $0 [options] [arguments]\n print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { # prints out arguments as they should be passed: - my $x = s#=s$## ? '' : s#=i$## ? '' : ''; + my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 17, join(', ', map { length $_ > 1 ? "--$_" : "-$_" } split /\|/,$_)," $x\n"; @@ -196,7 +186,7 @@ sub rebuild { $latest = $rev; } assert_revision_eq_or_unknown($rev, $c); - sys('git-update-ref',"$GIT_SVN/revs/$rev",$c); + sys('git-update-ref',"svn/$GIT_SVN/revs/$rev",$c); $newest_rev = $rev if ($rev > $newest_rev); } close $rev_list or croak $?; @@ -213,9 +203,10 @@ sub rebuild { sys(@svn_up,"-r$newest_rev"); $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX; index_changes(); - exec('git-write-tree'); + exec('git-write-tree') or croak $!; } waitpid $pid, 0; + croak $? if $?; if ($_upgrade) { print STDERR <<""; @@ -229,7 +220,10 @@ sub init { $SVN_URL = shift or die "SVN repository location required " . "as a command-line argument\n"; unless (-d $GIT_DIR) { - sys('git-init-db'); + my @init_db = ('git-init-db'); + push @init_db, "--template=$_template" if defined $_template; + push @init_db, "--shared" if defined $_shared; + sys(@init_db); } setup_git_svn(); } @@ -237,7 +231,7 @@ sub init { sub fetch { my (@parents) = @_; check_upgrade_needed(); - $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url"); + $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL); unless ($_revision) { $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD'; @@ -258,7 +252,14 @@ sub fetch { } else { chdir $SVN_WC or croak $!; read_uuid(); - $last_commit = file_to_s("$REV_DIR/$base->{revision}"); + eval { $last_commit = file_to_s("$REV_DIR/$base->{revision}") }; + # looks like a user manually cp'd and svn switch'ed + unless ($last_commit) { + sys(qw/svn revert -R ./); + assert_svn_wc_clean($base->{revision}); + $last_commit = git_commit($base, @parents); + assert_tree($last_commit); + } } my @svn_up = qw(svn up); push @svn_up, '--ignore-externals' unless $_no_ignore_ext; @@ -281,6 +282,7 @@ sub fetch { unless (-e "$GIT_DIR/refs/heads/master") { sys(qw(git-update-ref refs/heads/master),$last_commit); } + close $svn_log->{fh}; return $last; } @@ -309,9 +311,16 @@ sub commit { } chomp @revs; - fetch(); - chdir $SVN_WC or croak $!; + chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n"; my $info = svn_info('.'); + my $fetched = fetch(); + if ($info->{Revision} != $fetched->{revision}) { + print STDERR "There are new revisions that were fetched ", + "and need to be merged (or acknowledged) ", + "before committing.\n"; + exit 1; + } + $info = svn_info('.'); read_uuid($info); my $svn_current_rev = $info->{'Last Changed Rev'}; foreach my $c (@revs) { @@ -332,7 +341,7 @@ sub show_ignore { chomp(my @excludes = (<$fh>)); close $fh or croak $!; - $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url"); + $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); chdir $SVN_WC or croak $!; my %ign; File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){ @@ -356,7 +365,44 @@ sub read_uuid { my $info = shift || svn_info('.'); $SVN_UUID = $info->{'Repository UUID'} or croak "Repository UUID unreadable\n"; - s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid"); + s_to_file($SVN_UUID,"$GIT_SVN_DIR/info/uuid"); +} + +sub quiet_run { + my $pid = fork; + defined $pid or croak $!; + if (!$pid) { + open my $null, '>', '/dev/null' or croak $!; + open STDERR, '>&', $null or croak $!; + open STDOUT, '>&', $null or croak $!; + exec @_ or croak $!; + } + waitpid $pid, 0; + return $?; +} + +sub repo_path_split { + my $full_url = shift; + $full_url =~ s#/+$##; + + foreach (@repo_path_split_cache) { + if ($full_url =~ s#$_##) { + my $u = $1; + $full_url =~ s#^/+##; + return ($u, $full_url); + } + } + + my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); + $path =~ s#^/+##; + my @paths = split(m#/+#, $path); + + while (quiet_run(qw/svn ls --non-interactive/, $url)) { + my $n = shift @paths || last; + $url .= "/$n"; + } + push @repo_path_split_cache, qr/^(\Q$url\E)/; + return ($url, $path); } sub setup_git_svn { @@ -364,14 +410,17 @@ sub setup_git_svn { unless (-d $GIT_DIR) { croak "GIT_DIR=$GIT_DIR does not exist!\n"; } - mkpath(["$GIT_DIR/$GIT_SVN"]); - mkpath(["$GIT_DIR/$GIT_SVN/info"]); + mkpath([$GIT_SVN_DIR]); + mkpath(["$GIT_SVN_DIR/info"]); mkpath([$REV_DIR]); - s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url"); + s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url"); - open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!; + open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!; print $fd '.svn',"\n"; close $fd or croak $!; + my ($url, $path) = repo_path_split($SVN_URL); + s_to_file($url, "$GIT_SVN_DIR/info/repo_url"); + s_to_file($path, "$GIT_SVN_DIR/info/repo_path"); } sub assert_svn_wc_clean { @@ -573,7 +622,12 @@ sub svn_checkout_tree { my $pid = open my $diff_fh, '-|'; defined $pid or croak $!; if ($pid == 0) { - my @diff_tree = qw(git-diff-tree -z -r -C); + my @diff_tree = qw(git-diff-tree -z -r); + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; + } else { + push @diff_tree, '-C'; + } push @diff_tree, '--find-copies-harder' if $_find_copies_harder; push @diff_tree, "-l$_l" if defined $_l; exec(@diff_tree, $from, $treeish) or croak $!; @@ -665,7 +719,7 @@ sub handle_rmdir { sub svn_commit_tree { my ($svn_rev, $commit) = @_; - my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$"; + my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my %log_msg = ( msg => '' ); open my $msg, '>', $commit_msg or croak $!; @@ -764,7 +818,7 @@ sub svn_log_raw { exec (qw(svn log), @log_args) or croak $! } waitpid $pid, 0; - croak if $?; + croak $? if $?; seek $log_fh, 0, 0 or croak $!; return { state => 'sep', fh => $log_fh }; } @@ -942,7 +996,7 @@ sub index_changes { 'remove', $no_text_base); do_update_index([qw/git-ls-files -z --others/, - "--exclude-from=$GIT_DIR/$GIT_SVN/info/exclude"], + "--exclude-from=$GIT_SVN_DIR/info/exclude"], 'add', $no_text_base); } @@ -1024,7 +1078,7 @@ sub git_commit { $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX; index_changes(); chomp(my $tree = `git-write-tree`); - croak if $?; + croak $? if $?; if (exists $tree_map{$tree}) { my %seen_parent = map { $_ => 1 } @exec_parents; foreach (@{$tree_map{$tree}}) { @@ -1052,7 +1106,7 @@ sub git_commit { exec @exec or croak $!; } waitpid($pid,0); - croak if $?; + croak $? if $?; $out_fh->flush == 0 or croak $!; seek $out_fh, 0, 0 or croak $!; @@ -1068,14 +1122,18 @@ sub git_commit { close STDERR; close STDOUT; exec 'git-rev-parse','--verify', - "refs/remotes/$GIT_SVN^0"; + "refs/remotes/$GIT_SVN^0" or croak $!; } waitpid $pid, 0; push @update_ref, $primary_parent unless $?; } sys(@update_ref); - sys('git-update-ref',"$GIT_SVN/revs/$log_msg->{revision}",$commit); + sys('git-update-ref',"svn/$GIT_SVN/revs/$log_msg->{revision}",$commit); print "r$log_msg->{revision} = $commit\n"; + if ($_repack && (--$_repack_nr == 0)) { + $_repack_nr = $_repack; + sys("git repack $_repack_flags"); + } return $commit; } @@ -1124,7 +1182,7 @@ sub blob_to_file { if ($pid == 0) { open STDOUT, '>&', $blob_fh or croak $!; - exec('git-cat-file','blob',$blob); + exec('git-cat-file','blob',$blob) or croak $!; } waitpid $pid, 0; croak $? if $?; @@ -1136,7 +1194,7 @@ sub safe_qx { my $pid = open my $child, '-|'; defined $pid or croak $!; if ($pid == 0) { - exec(@_) or croak $?; + exec(@_) or croak $!; } my @ret = (<$child>); close $child or croak $?; @@ -1186,7 +1244,7 @@ sub check_upgrade_needed { defined $pid or croak $!; if ($pid == 0) { close STDERR; - exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $?; + exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!; } my @ret = (<$child>); close $child or croak $?; @@ -1204,24 +1262,42 @@ sub check_upgrade_needed { # fills %tree_map with a reverse mapping of trees to commits. Useful # for finding parents to commit on. sub map_tree_joins { + my %seen; foreach my $br (@_branch_from) { my $pid = open my $pipe, '-|'; defined $pid or croak $!; if ($pid == 0) { - exec(qw(git-rev-list --pretty=raw), $br) or croak $?; + exec(qw(git-rev-list --topo-order --pretty=raw), $br) + or croak $!; } while (<$pipe>) { if (/^commit ($sha1)$/o) { my $commit = $1; + + # if we've seen a commit, + # we've seen its parents + last if $seen{$commit}; my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o); unless (defined $tree) { die "Failed to parse commit $commit\n"; } push @{$tree_map{$tree}}, $commit; + $seen{$commit} = 1; } } - close $pipe or croak $?; + close $pipe; # we could be breaking the pipe early + } +} + +sub load_all_refs { + if (@_branch_from) { + print STDERR '--branch|-b parameters are ignored when ', + "--branch-all-refs|-B is passed\n"; } + + # don't worry about rev-list on non-commit objects/tags, + # it shouldn't blow up if a ref is a blob or tree... + chomp(@_branch_from = `git-rev-parse --symbolic --all`); } # ' = real-name ' mapping based on git-svnimport: @@ -1242,6 +1318,72 @@ sub svn_propget_base { return safe_qx(qw/svn propget/, $p, $f); } +sub migration_check { + return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR); + print "Upgrading repository...\n"; + unless (-d "$GIT_DIR/svn") { + mkdir "$GIT_DIR/svn" or croak $!; + } + print "Data from a previous version of git-svn exists, but\n\t", + "$GIT_SVN_DIR\n\t(required for this version ", + "($VERSION) of git-svn) does not.\n"; + + foreach my $x (`git-rev-parse --symbolic --all`) { + next unless $x =~ s#^refs/remotes/##; + chomp $x; + next unless -f "$GIT_DIR/$x/info/url"; + my $u = eval { file_to_s("$GIT_DIR/$x/info/url") }; + next unless $u; + my $dn = dirname("$GIT_DIR/svn/$x"); + mkpath([$dn]) unless -d $dn; + rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x"; + my ($url, $path) = repo_path_split($u); + s_to_file($url, "$GIT_DIR/svn/$x/info/repo_url"); + s_to_file($path, "$GIT_DIR/svn/$x/info/repo_path"); + } + print "Done upgrading.\n"; +} + +sub init_vars { + $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; + $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; + $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; + $SVN_URL = undef; + $REV_DIR = "$GIT_SVN_DIR/revs"; + $SVN_WC = "$GIT_SVN_DIR/tree"; +} + +# convert GetOpt::Long specs for use by git-repo-config +sub read_repo_config { + return unless -d $GIT_DIR; + my $opts = shift; + foreach my $o (keys %$opts) { + my $v = $opts->{$o}; + my ($key) = ($o =~ /^([a-z\-]+)/); + $key =~ s/-//g; + my $arg = 'git-repo-config'; + $arg .= ' --int' if ($o =~ /[:=]i$/); + $arg .= ' --bool' if ($o !~ /[:=][sfi]$/); + if (ref $v eq 'ARRAY') { + chomp(my @tmp = `$arg --get-all svn.$key`); + @$v = @tmp if @tmp; + } else { + chomp(my $tmp = `$arg --get svn.$key`); + if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) { + $$v = $tmp; + } + } + } +} + +sub set_default_vals { + if (defined $_repack) { + $_repack = 1000 if ($_repack <= 0); + $_repack_nr = $_repack; + $_repack_flags ||= ''; + } +} + __END__ Data structures: