8989 $_prefix, $_no_checkout, $_url, $_verbose,
9090 $_git_format, $_commit_url, $_tag, $_merge_info);
9191$Git::SVN::_follow_parent = 1;
92+ $SVN::Git::Fetcher::_placeholder_filename = " .gitignore" ;
9293$_q ||= 0;
9394my %remote_opts = ( ' username=s' => \$Git::SVN::Prompt::_username ,
9495 ' config-dir=s' => \$Git::SVN::Ra::config_dir ,
@@ -139,6 +140,10 @@ BEGIN
139140 %fc_opts } ],
140141 clone => [ \&cmd_clone, " Initialize and fetch revisions" ,
141142 { ' revision|r=s' => \$_revision,
143+ ' preserve-empty-dirs' =>
144+ \$SVN::Git::Fetcher::_preserve_empty_dirs ,
145+ ' placeholder-filename=s' =>
146+ \$SVN::Git::Fetcher::_placeholder_filename ,
142147 %fc_opts , %init_opts } ],
143148 init => [ \&cmd_init, " Initialize a repo for tracking" .
144149 " (requires URL argument)" ,
@@ -386,6 +391,12 @@ sub do_git_init_db {
386391 my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex ;
387392 command_noisy(' config' , " $pfx .ignore-paths" , $$ignore_regex )
388393 if defined $$ignore_regex ;
394+
395+ if (defined $SVN::Git::Fetcher::_preserve_empty_dirs ) {
396+ my $fname = \$SVN::Git::Fetcher::_placeholder_filename ;
397+ command_noisy(' config' , " $pfx .preserve-empty-dirs" , ' true' );
398+ command_noisy(' config' , " $pfx .placeholder-filename" , $$fname );
399+ }
389400}
390401
391402sub init_subdir {
@@ -4080,12 +4091,13 @@ sub _read_password {
40804091}
40814092
40824093package SVN::Git::Fetcher ;
4083- use vars qw/ @ISA/ ;
4094+ use vars qw/ @ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
4095+ @deleted_gpath %added_placeholder $repo_id/ ;
40844096use strict;
40854097use warnings;
40864098use Carp qw/ croak/ ;
4099+ use File::Basename qw/ dirname/ ;
40874100use IO::File qw/ / ;
4088- use vars qw/ $_ignore_regex/ ;
40894101
40904102# file baton members: path, mode_a, mode_b, pool, fh, blob, base
40914103sub new {
@@ -4097,8 +4109,34 @@ sub new {
40974109 $self -> {empty_symlinks } =
40984110 _mark_empty_symlinks($git_svn , $switch_path );
40994111 }
4100- $self -> {ignore_regex } = eval { command_oneline(' config' , ' --get' ,
4101- " svn-remote.$git_svn ->{repo_id}.ignore-paths" ) };
4112+
4113+ # some options are read globally, but can be overridden locally
4114+ # per [svn-remote "..."] section. Command-line options will *NOT*
4115+ # override options set in an [svn-remote "..."] section
4116+ $repo_id = $git_svn -> {repo_id };
4117+ my $k = " svn-remote.$repo_id .ignore-paths" ;
4118+ my $v = eval { command_oneline(' config' , ' --get' , $k ) };
4119+ $self -> {ignore_regex } = $v ;
4120+
4121+ $k = " svn-remote.$repo_id .preserve-empty-dirs" ;
4122+ $v = eval { command_oneline(' config' , ' --get' , ' --bool' , $k ) };
4123+ if ($v && $v eq ' true' ) {
4124+ $_preserve_empty_dirs = 1;
4125+ $k = " svn-remote.$repo_id .placeholder-filename" ;
4126+ $v = eval { command_oneline(' config' , ' --get' , $k ) };
4127+ $_placeholder_filename = $v ;
4128+ }
4129+
4130+ # Load the list of placeholder files added during previous invocations.
4131+ $k = " svn-remote.$repo_id .added-placeholder" ;
4132+ $v = eval { command_oneline(' config' , ' --get-all' , $k ) };
4133+ if ($_preserve_empty_dirs && $v ) {
4134+ # command() prints errors to stderr, so we only call it if
4135+ # command_oneline() succeeded.
4136+ my @v = command(' config' , ' --get-all' , $k );
4137+ $added_placeholder { dirname($_ ) } = $_ foreach @v ;
4138+ }
4139+
41024140 $self -> {empty } = {};
41034141 $self -> {dir_prop } = {};
41044142 $self -> {file_prop } = {};
@@ -4227,6 +4265,8 @@ sub delete_entry {
42274265 $self -> {gii }-> remove($gpath );
42284266 print " \t D\t $gpath \n " unless $: :_q;
42294267 }
4268+ # Don't add to @deleted_gpath if we're deleting a placeholder file.
4269+ push @deleted_gpath , $gpath unless $added_placeholder {dirname($path )};
42304270 $self -> {empty }-> {$path } = 0;
42314271 undef ;
42324272}
@@ -4259,7 +4299,15 @@ sub add_file {
42594299 my ($dir , $file ) = ($path =~ m # ^(.*?)/?([^/]+)$ # );
42604300 delete $self -> {empty }-> {$dir };
42614301 $mode = ' 100644' ;
4302+
4303+ if ($added_placeholder {$dir }) {
4304+ # Remove our placeholder file, if we created one.
4305+ delete_entry($self , $added_placeholder {$dir })
4306+ unless $path eq $added_placeholder {$dir };
4307+ delete $added_placeholder {$dir }
4308+ }
42624309 }
4310+
42634311 { path => $path , mode_a => $mode , mode_b => $mode ,
42644312 pool => SVN::Pool-> new, action => ' A' };
42654313}
@@ -4277,13 +4325,21 @@ sub add_directory {
42774325 chomp ;
42784326 $self -> {gii }-> remove($_ );
42794327 print " \t D\t $_ \n " unless $: :_q;
4328+ push @deleted_gpath , $gpath ;
42804329 }
42814330 command_close_pipe($ls , $ctx );
42824331 $self -> {empty }-> {$path } = 0;
42834332 }
42844333 my ($dir , $file ) = ($path =~ m # ^(.*?)/?([^/]+)$ # );
42854334 delete $self -> {empty }-> {$dir };
42864335 $self -> {empty }-> {$path } = 1;
4336+
4337+ if ($added_placeholder {$dir }) {
4338+ # Remove our placeholder file, if we created one.
4339+ delete_entry($self , $added_placeholder {$dir });
4340+ delete $added_placeholder {$dir }
4341+ }
4342+
42874343out:
42884344 { path => $path };
42894345}
@@ -4447,12 +4503,96 @@ sub abort_edit {
44474503
44484504sub close_edit {
44494505 my $self = shift ;
4506+
4507+ if ($_preserve_empty_dirs) {
4508+ my @empty_dirs ;
4509+
4510+ # Any entry flagged as empty that also has an associated
4511+ # dir_prop represents a newly created empty directory.
4512+ foreach my $i (keys %{$self -> {empty }}) {
4513+ push @empty_dirs , $i if exists $self -> {dir_prop }-> {$i };
4514+ }
4515+
4516+ # Search for directories that have become empty due subsequent
4517+ # file deletes.
4518+ push @empty_dirs , $self -> find_empty_directories();
4519+
4520+ # Finally, add a placeholder file to each empty directory.
4521+ $self -> add_placeholder_file($_ ) foreach (@empty_dirs );
4522+
4523+ $self -> stash_placeholder_list();
4524+ }
4525+
44504526 $self -> {git_commit_ok } = 1;
44514527 $self -> {nr } = $self -> {gii }-> {nr };
44524528 delete $self -> {gii };
44534529 $self -> SUPER::close_edit(@_ );
44544530}
44554531
4532+ sub find_empty_directories {
4533+ my ($self ) = @_ ;
4534+ my @empty_dirs ;
4535+ my %dirs = map { dirname($_ ) => 1 } @deleted_gpath ;
4536+
4537+ foreach my $dir (sort keys %dirs ) {
4538+ next if $dir eq " ." ;
4539+
4540+ # If there have been any additions to this directory, there is
4541+ # no reason to check if it is empty.
4542+ my $skip_added = 0;
4543+ foreach my $t (qw/ dir_prop file_prop/ ) {
4544+ foreach my $path (keys %{ $self -> {$t } }) {
4545+ if (exists $self -> {$t }-> {dirname($path )}) {
4546+ $skip_added = 1;
4547+ last ;
4548+ }
4549+ }
4550+ last if $skip_added ;
4551+ }
4552+ next if $skip_added ;
4553+
4554+ # Use `git ls-tree` to get the filenames of this directory
4555+ # that existed prior to this particular commit.
4556+ my $ls = command(' ls-tree' , ' -z' , ' --name-only' ,
4557+ $self -> {c }, " $dir /" );
4558+ my %files = map { $_ => 1 } split (/ \0 / , $ls );
4559+
4560+ # Remove the filenames that were deleted during this commit.
4561+ delete $files {$_ } foreach (@deleted_gpath );
4562+
4563+ # Report the directory if there are no filenames left.
4564+ push @empty_dirs , $dir unless (scalar %files );
4565+ }
4566+ @empty_dirs ;
4567+ }
4568+
4569+ sub add_placeholder_file {
4570+ my ($self , $dir ) = @_ ;
4571+ my $path = " $dir /$_placeholder_filename" ;
4572+ my $gpath = $self -> git_path($path );
4573+
4574+ my $fh = $: :_repository-> temp_acquire($gpath );
4575+ my $hash = $: :_repository-> hash_and_insert_object(Git::temp_path($fh ));
4576+ Git::temp_release($fh , 1);
4577+ $self -> {gii }-> update(' 100644' , $hash , $gpath ) or croak $! ;
4578+
4579+ # The directory should no longer be considered empty.
4580+ delete $self -> {empty }-> {$dir } if exists $self -> {empty }-> {$dir };
4581+
4582+ # Keep track of any placeholder files we create.
4583+ $added_placeholder {$dir } = $path ;
4584+ }
4585+
4586+ sub stash_placeholder_list {
4587+ my ($self ) = @_ ;
4588+ my $k = " svn-remote.$repo_id .added-placeholder" ;
4589+ my $v = eval { command_oneline(' config' , ' --get-all' , $k ) };
4590+ command_noisy(' config' , ' --unset-all' , $k ) if $v ;
4591+ foreach (values %added_placeholder ) {
4592+ command_noisy(' config' , ' --add' , $k , $_ );
4593+ }
4594+ }
4595+
44564596package SVN::Git::Editor ;
44574597use vars qw/ @ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/ ;
44584598use strict;
0 commit comments