6 # $LastChangedRevision$
16 use File
::Path
1.0404;
17 use File
::Temp
0.12 qw(tempdir tempfile);
18 use Getopt
::Long
2.25;
23 $Text::Wrap
::columns
= 72;
25 # Specify the location of the svn command.
26 my $svn = '@SVN_BINDIR@/svn';
28 # Process the command line options.
30 # The base URL for the portion of the repository to work in. Note
31 # that this does not have to be the root of the subversion repository,
32 # it can point to a subdirectory in the repository.
35 # The relative path from the repository base URL to work in to the
36 # directory to load the input directories into.
37 my $repos_load_rel_path;
39 # To specify where tags, which are simply copies of the imported
40 # directory, should be placed relative to the repository base URL, use
41 # the -t command line option. This value must contain regular
42 # expressions that match portions of the input directory names to
43 # create an unique tag for each input directory. The regular
44 # expressions are surrounded by a specified character to distinguish
45 # the regular expression from the normal directory path.
46 my $opt_import_tag_location;
48 # Do not ask for any user input. Just go ahead and do everything.
49 my $opt_no_user_input;
51 # Do not automatically set the svn:executable property based on the
55 # Username to use for commits.
58 # Password to use for commits.
64 # Path to already checked-out working copy.
65 my $opt_existing_wc_dir;
67 # List of filename patterns to ignore (as in .subversion/config's
68 # "global-ignores" option).
71 # This is the character used to separate regular expressions occuring
72 # in the tag directory path from the path itself.
73 my $REGEX_SEP_CHAR = '@';
75 # This specifies a configuration file that contains a list of regular
76 # expressions to check against a file and the properties to set on
78 my $property_config_filename;
80 GetOptions
('no_user_input' => \
$opt_no_user_input,
81 'no_auto_exe' => \
$opt_no_auto_exe,
82 'property_cfg_filename=s' => \
$property_config_filename,
83 'svn_password=s' => \
$opt_svn_password,
84 'svn_username=s' => \
$opt_svn_username,
85 'tag_location=s' => \
$opt_import_tag_location,
86 'verbose+' => \
$opt_verbose,
87 'wc=s' => \
$opt_existing_wc_dir,
88 'glob_ignores=s' => \
$opt_glob_ignores)
90 &usage
("$0: too few arguments") if @ARGV < 2;
92 $repos_base_url = shift;
93 $repos_load_rel_path = shift;
95 # Check that the repository base URL and the import directories do not
97 if ($repos_base_url =~ /\.{2}/)
99 die "$0: repos base URL $repos_base_url cannot contain ..'s.\n";
101 if ($repos_load_rel_path =~ /\.{2}/)
103 die "$0: repos import relative directory path $repos_load_rel_path ",
104 "cannot contain ..'s.\n";
107 # If there are no directories listed on the command line, then the
108 # directories are read from standard input. In this case, the
109 # -no_user_input command line option must be specified.
110 if (!@ARGV and !$opt_no_user_input)
112 &usage
("$0: must use -no_user_input if no dirs listed on command line.");
115 # The tag option cannot be used when directories are read from
116 # standard input because tags may collide and no user input can be
117 # taken to verify that the input is ok.
118 if (!@ARGV and $opt_import_tag_location)
120 &usage
("$0: cannot use -tag_location when dirs are read from stdin.");
123 # If the tag directory is set, then the import directory cannot be '.'.
124 if (defined $opt_import_tag_location and $repos_load_rel_path eq '.')
126 &usage
("$0: cannot set import_dir to '.' and use -t command line option.");
129 # Set the svn command line options that are used anytime svn connects
131 my @svn_use_repos_cmd_opts;
132 &set_svn_use_repos_cmd_opts
($opt_svn_username, $opt_svn_password);
134 # Check that the tag directories do not contain any ..'s. Also, the
135 # import and tag directories cannot be absolute.
136 if (defined $opt_import_tag_location and $opt_import_tag_location =~ /\.{2}/)
138 die "$0: repos tag relative directory path $opt_import_tag_location ",
139 "cannot contain ..'s.\n";
141 if ($repos_load_rel_path =~ m
|^/|)
143 die "$0: repos import relative directory path $repos_load_rel_path ",
144 "cannot start with /.\n";
146 if (defined $opt_import_tag_location and $opt_import_tag_location =~ m
|^/|)
148 die "$0: repos tagrelative directory path $opt_import_tag_location ",
149 "cannot start with /.\n";
152 if (defined $opt_existing_wc_dir)
154 unless (-e
$opt_existing_wc_dir)
156 die "$0: working copy '$opt_existing_wc_dir' does not exist.\n";
161 die "$0: working copy '$opt_existing_wc_dir' is not a directory.\n";
164 unless (-d
"$opt_existing_wc_dir/.svn")
166 die "$0: working copy '$opt_existing_wc_dir' does not have .svn ",
170 $opt_existing_wc_dir = Cwd
::abs_path
($opt_existing_wc_dir)
173 # If no glob_ignores specified, try to deduce from config file,
174 # or use the default below.
176 '*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store';
178 if ( defined $opt_glob_ignores)
180 $ignores_str = $opt_glob_ignores;
182 elsif ( -f
"$ENV{HOME}/.subversion/config" )
184 open my $conf, "$ENV{HOME}/.subversion/config";
187 if ( /^global-ignores\s*=\s*(.*?)\s*$/ )
195 my @glob_ignores = map
197 s/\./\\\./g; s/\*/\.\*/g; "^$_\$";
198 } split(/\s+/, $ignores_str);
199 unshift @glob_ignores, '\.svn$';
201 # Convert the string URL into a URI object.
202 $repos_base_url =~ s
|/*$||;
203 my $repos_base_uri = URI
->new($repos_base_url);
205 # Check that $repos_load_rel_path is not a directory here implying
206 # that a command line option was forgotten.
207 if ($repos_load_rel_path ne '.' and -d
$repos_load_rel_path)
209 die "$0: import_dir '$repos_load_rel_path' is a directory.\n";
212 # The remaining command line arguments should be directories. Check
213 # that they all exist and that there are no duplicates.
217 foreach my $dir (@ARGV)
221 die "$0: directory '$dir' does not exist.\n";
226 die "$0: directory '$dir' is not a directory.\n";
231 die "$0: directory '$dir' is listed more than once on command ",
238 # Create the tag locations and print them for the user to review.
239 # Check that there are no duplicate tags.
241 if (@ARGV and defined $opt_import_tag_location)
245 foreach my $load_dir (@ARGV)
247 my $load_tag = &get_tag_dir
($load_dir);
249 print "Directory $load_dir will be tagged as $load_tag\n";
251 if ($seen_tags{$load_tag})
253 die "$0: duplicate tag generated.\n";
255 $seen_tags{$load_tag} = 1;
257 $load_tags{$load_dir} = $load_tag;
260 exit 0 unless &get_answer
("Please examine identified tags. Are they " .
261 "acceptable? (Y/n) ", 'ny', 1);
265 # Load the property configuration filename, if one was specified, into
266 # an array of hashes, where each hash contains a regular expression
267 # and a property to apply to the file if the regular expression
269 my @property_settings;
270 if (defined $property_config_filename and length $property_config_filename)
272 open(CFG
, $property_config_filename)
273 or die "$0: cannot open '$property_config_filename' for reading: $!\n";
277 while (my $line = <CFG
>)
279 next if $line =~ /^\s*$/;
280 next if $line =~ /^\s*#/;
282 # Split the input line into words taking into account that
283 # single or double quotes may define a single word with
284 # whitespace in it. The format for the file is
285 # regex control property_name property_value
286 my @line = &split_line
($line);
289 unless (@line == 2 or @line == 4)
291 warn "$0: line $. of '$property_config_filename' has to have 2 ",
296 my ($regex, $control, $property_name, $property_value) = @line;
298 unless ($control eq 'break' or $control eq 'cont')
300 warn "$0: line $. of '$property_config_filename' has illegal ",
301 "value for column 3 '$control', must be 'break' or 'cont'.\n";
306 # Compile the regular expression.
308 eval { $re = qr/$regex/i };
311 warn "$0: line $. of '$property_config_filename' regex '$regex' ",
312 "does not compile:\n$@\n";
317 push(@property_settings, {name
=> $property_name,
318 value
=> $property_value,
323 or warn "$0: error in closing '$property_config_filename' for ",
329 # Check that the svn base URL works by running svn log on it. Only
330 # get the HEAD revision log message; there's no need to waste
331 # bandwidth seeing all of the log messages.
332 print "Checking that the base URL is a Subversion repository.\n";
333 read_from_process
($svn, 'log', '-r', 'HEAD',
334 @svn_use_repos_cmd_opts, $repos_base_uri);
339 # The first step is to determine the root of the svn repository. Do
340 # this with the svn log command. Take the svn_url hostname and port
341 # as the initial url and append to it successive portions of the final
342 # path until svn log succeeds.
343 print "Finding the root URL of the Subversion repository.\n";
345 my $repos_root_uri_path;
346 my $repos_base_path_segment;
348 my $r = $repos_base_uri->clone;
349 my @path_segments = grep { length($_) } $r->path_segments;
350 my @repos_base_path_segments = @path_segments;
351 unshift(@path_segments, '');
355 while (@path_segments)
357 $repos_root_uri_path = shift @path_segments;
358 push(@r_path_segments, $repos_root_uri_path);
359 $r->path_segments(@r_path_segments);
360 if (safe_read_from_pipe
($svn, 'log', '-r', 'HEAD',
361 @svn_use_repos_cmd_opts, $r) == 0)
363 $repos_root_uri = $r;
366 shift @repos_base_path_segments;
368 $repos_base_path_segment = join('/', @repos_base_path_segments);
373 print "Determined that the svn root URL is $repos_root_uri.\n\n";
377 die "$0: cannot determine root svn URL.\n";
380 # Create a temporary directory for svn to work in.
381 my $temp_dir = tempdir
( "svn_load_dirs_XXXXXXXXXX", TMPDIR
=> 1 );
383 # Put in a signal handler to clean up any temporary directories.
386 warn "$0: caught signal $signal. Quitting now.\n";
390 $SIG{HUP
} = \
&catch_signal
;
391 $SIG{INT
} = \
&catch_signal
;
392 $SIG{TERM
} = \
&catch_signal
;
393 $SIG{PIPE
} = \
&catch_signal
;
395 # Create an object that when DESTROY'ed will delete the temporary
396 # directory. The CLEANUP flag to tempdir should do this, but they
397 # call rmtree with 1 as the last argument which takes extra security
398 # measures that do not clean up the .svn directories.
399 my $temp_dir_cleanup = Temp
::Delete
->new;
401 # Determine the native end of line style for this system. Do this the
402 # most portable way, by writing a file with a single \n in non-binary
403 # mode and then reading the file in binary mode.
404 my $native_eol = &determine_native_eol
;
406 # Check if all the directories exist to load the directories into the
407 # repository. If not, ask if they should be created. For tags, do
408 # not create the tag directory itself, that is done on the svn cp.
410 print "Finding if any directories need to be created in repository.\n";
415 my @load_tags_without_last_segment;
417 # Assume that the last portion of the tag directory contains the
418 # version number and remove it from the directories to create,
419 # because the tag directory will be created by svn cp.
420 foreach my $load_tag (sort values %load_tags)
422 # Skip this tag if there is only one segment in its name.
423 my $index = rindex($load_tag, '/');
424 next if $index == -1;
426 # Trim off the last segment and record the result.
427 push(@load_tags_without_last_segment, substr($load_tag, 0, $index));
430 foreach my $dir ($repos_load_rel_path, @load_tags_without_last_segment)
432 next unless length $dir;
434 foreach my $segment (split('/', $dir))
436 $d = length $d ?
"$d/$segment" : $segment;
437 my $url = "$repos_base_url/$d";
438 unless ($seen_dir{$d})
441 if (safe_read_from_pipe
($svn, 'log', '-r', 'HEAD',
442 @svn_use_repos_cmd_opts, $url) != 0)
444 push(@dirs_to_create, $d);
445 push(@urls_to_create, $url);
453 print "The following directories do not exist and need to exist:\n";
454 foreach my $dir (@dirs_to_create)
458 exit 0 unless &get_answer
("You must add them now to load the " .
459 "directories. Continue (Y/n)? ", 'ny', 1);
461 my $message = "Create directories to load project into.\n\n";
463 foreach my $dir (@dirs_to_create)
465 if (length $repos_base_path_segment)
467 $message .= "* $repos_base_path_segment/$dir: New directory.\n";
471 $message .= "* $dir: New directory.\n";
474 $message = wrap
('', ' ', $message);
476 read_from_process
($svn, 'mkdir', @svn_use_repos_cmd_opts,
477 '-m', $message, @urls_to_create);
481 print "No directories need to be created to prepare repository.\n";
485 # Either checkout a new working copy from the repository or use an
486 # existing working copy.
487 if (defined $opt_existing_wc_dir)
489 # Update an already existing working copy.
490 print "Not checking out anything; using existing working directory at\n";
491 print "$opt_existing_wc_dir\n";
493 chdir($opt_existing_wc_dir)
494 or die "$0: cannot chdir '$opt_existing_wc_dir': $!\n";
496 read_from_process
($svn, 'update', @svn_use_repos_cmd_opts);
500 # Check out the svn repository starting at the svn URL into a
501 # fixed directory name.
502 my $checkout_dir_name = 'my_import_wc';
504 # Check out only the directory being imported to, otherwise the
505 # checkout of the entire base URL can be very huge, if it contains
506 # a large number of tags.
508 if ($repos_load_rel_path eq '.')
510 $checkout_url = $repos_base_url;
514 $checkout_url = "$repos_base_url/$repos_load_rel_path";
517 print "Checking out $checkout_url into $temp_dir/$checkout_dir_name\n";
520 or die "$0: cannot chdir '$temp_dir': $!\n";
522 read_from_process
($svn, 'checkout',
523 @svn_use_repos_cmd_opts,
524 $checkout_url, $checkout_dir_name);
526 chdir($checkout_dir_name)
527 or die "$0: cannot chdir '$checkout_dir_name': $!\n";
530 # At this point, the current working directory is the top level
531 # directory of the working copy. Record the absolute path to this
532 # location because the script will chdir back here later on.
533 my $wc_import_dir_cwd = cwd
;
535 # Set up the names for the path to the import and tag directories.
536 my $repos_load_abs_path;
537 if ($repos_load_rel_path eq '.')
539 $repos_load_abs_path = length($repos_base_path_segment) ?
540 $repos_base_path_segment : "/";
544 $repos_load_abs_path = length($repos_base_path_segment) ?
545 "$repos_base_path_segment/$repos_load_rel_path" :
546 $repos_load_rel_path;
549 # Now go through each source directory and copy each file from the
550 # source directory to the target directory. For new target files, add
551 # them to svn. For files that no longer exist, delete them.
552 my $print_rename_message = 1;
553 my @load_dirs = @ARGV;
554 while (defined (my $load_dir = &get_next_load_dir
))
556 my $load_tag = $load_tags{$load_dir};
558 if (defined $load_tag)
560 print "\nLoading $load_dir and will save in tag $load_tag.\n";
564 print "\nLoading $load_dir.\n";
567 # The first hash is keyed by the old name in a rename and the
568 # second by the new name. The last variable contains a list of
569 # old and new filenames in a rename.
570 my %rename_from_files;
572 my @renamed_filenames;
574 unless ($opt_no_user_input)
584 # Get the list of files and directories in the repository
585 # working copy. This hash is called %del_files because
586 # each file or directory will be deleted from the hash
587 # using the list of files and directories in the source
588 # directory, leaving the files and directories that need
590 %del_files = &recursive_ls_and_hash
($wc_import_dir_cwd);
592 # This anonymous subroutine finds all the files and
593 # directories in the directory to load. It notes the file
594 # type and for each file found, it deletes it from
601 my $source_path = $_;
602 my $dest_path = "$wc_import_dir_cwd/$_";
604 my ($source_type) = &file_info
($source_path);
605 my ($dest_type) = &file_info
($dest_path);
607 # Fail if the destination type exists but is of a
608 # different type of file than the source type.
609 if ($dest_type ne '0' and $source_type ne $dest_type)
611 die "$0: does not handle changing source and destination ",
612 "type for '$source_path'.\n";
615 if ($source_type ne 'd' and
616 $source_type ne 'f' and
619 warn "$0: skipping loading file '$source_path' of type ",
621 unless ($opt_no_user_input)
623 print STDERR
"Press return to continue: ";
629 unless (defined delete $del_files{$source_path})
631 $add_files{$source_path}{type
} = $source_type;
635 # Now change into the directory containing the files to
636 # load. First change to the original directory where this
637 # script was run so that if the specified directory is a
638 # relative directory path, then the script can change into
641 or die "$0: cannot chdir '$orig_cwd': $!\n";
643 or die "$0: cannot chdir '$load_dir': $!\n";
646 preprocess
=> sub { sort { $b cmp $a }
647 grep { $_ !~ /^[._]svn$/ } @_ },
651 # At this point %add_files contains the list of new files
652 # and directories to be created in the working copy tree
653 # and %del_files contains the files and directories that
654 # need to be deleted. Because there may be renames that
655 # have taken place, give the user the opportunity to
656 # rename any deleted files and directories to ones being
658 my @add_files = sort keys %add_files;
659 my @del_files = sort keys %del_files;
661 # Because the source code management system may keep the
662 # original renamed file or directory in the working copy
663 # until a commit, remove them from the list of deleted
664 # files or directories.
665 &filter_renamed_files
(\
@del_files, \
%rename_from_files);
667 # Now change into the working copy directory in case any
668 # renames need to be performed.
669 chdir($wc_import_dir_cwd)
670 or die "$0: cannot chdir '$wc_import_dir_cwd': $!\n";
672 # Only do renames if there are both added and deleted
673 # files and directories.
674 if (@add_files and @del_files)
676 my $max = @add_files > @del_files ?
@add_files : @del_files;
678 # Print the files that have been added and deleted.
679 # Find the deleted file with the longest name and use
680 # that for the width of the filename column. Add one
681 # to the filename width to let the directory /
682 # character be appended to a directory name.
683 my $line_number_width = 4;
684 my $filename_width = 0;
685 foreach my $f (@del_files)
688 $filename_width = $l if $l > $filename_width;
691 my $printf_format = "%${line_number_width}d";
693 if ($print_rename_message)
695 $print_rename_message = 0;
697 "The following table lists files and directories that\n",
698 "exist in either the Subversion repository or the\n",
699 "directory to be imported but not both. You now have\n",
700 "the opportunity to match them up as renames instead\n",
701 "of deletes and adds. This is a Good Thing as it'll\n",
702 "make the repository take less space.\n\n",
703 "The left column lists files and directories that\n",
704 "exist in the Subversion repository and do not exist\n",
705 "in the directory being imported. The right column\n",
706 "lists files and directories that exist in the\n",
707 "directory being imported. Match up a deleted item\n",
708 "from the left column with an added item from the\n",
709 "right column. Note the line numbers on the left\n",
710 "which you type into this script to have a rename\n",
714 # Sort the added and deleted files and directories by
715 # the lowercase versions of their basenames instead of
716 # their complete path, which makes finding files that
717 # were moved into different directories easier to
719 @add_files = map { $_->[0] }
720 sort { $a->[1] cmp $b->[1] }
721 map { [$_->[0], lc($_->[1])] }
722 map { [$_, m
#([^/]+)$#] }
724 @del_files = map { $_->[0] }
725 sort { $a->[1] cmp $b->[1] }
726 map { [$_->[0], lc($_->[1])] }
727 map { [$_, m
#([^/]+)$#] }
732 for (my $i=0; $i<$max; ++$i)
734 my $add_filename = '';
735 my $del_filename = '';
738 $add_filename = $add_files[$i];
739 if ($add_files{$add_filename}{type
} eq 'd')
741 $add_filename .= '/';
746 $del_filename = $del_files[$i];
747 if ($del_files{$del_filename}{type
} eq 'd')
749 $del_filename .= '/';
757 " " x
$line_number_width,
759 "Deleted", " " x
($filename_width-length("Deleted")),
764 printf $printf_format, $i;
765 print " ", $del_filename,
766 "_" x
($filename_width - length($del_filename)),
767 " ", $add_filename, "\n";
769 if (($i+1) % 22 == 0)
771 unless (&get_answer
("Continue printing (Y/n)? ",
779 # Get the feedback from the user.
787 print "Enter two indexes for each column to rename, ",
788 "(R)elist, or (F)inish: ";
790 $line = '' unless defined $line;
791 if ($line =~ /^R$/i )
800 elsif ($line =~ /^(\d+)\s+(\d+)$/)
806 if ($del_index >= @del_files)
808 print "Delete index $del_index is larger than ",
809 "maximum index of ", scalar @del_files - 1,
813 if ($add_index > @add_files)
815 print "Add index $add_index is larger than maximum ",
816 "index of ", scalar @add_files - 1, ".\n";
819 $got_line = defined $del_index && defined $add_index;
821 # Check that the file or directory to be renamed
822 # has the same file type.
825 $add_filename = $add_files[$add_index];
826 $del_filename = $del_files[$del_index];
827 if ($add_files{$add_filename}{type
} ne
828 $del_files{$del_filename}{type
})
830 print "File types for $del_filename and ",
831 "$add_filename differ.\n";
840 print "Renaming $del_filename to $add_filename.\n";
844 # Because subversion cannot rename the same file
845 # or directory twice, which includes doing a
846 # rename of a file in a directory that was
847 # previously renamed, a commit has to be
848 # performed. Check if the file or directory being
849 # renamed now would cause such a problem and
851 my $do_commit_now = 0;
852 foreach my $rename_to_filename (keys %rename_to_files)
854 if (contained_in
($del_filename,
856 $rename_to_files{$rename_to_filename}{type
}))
865 print "Now committing previously run renames.\n";
866 &commit_renames
($load_dir,
872 push(@renamed_filenames, $del_filename, $add_filename);
874 my $d = $del_files{$del_filename};
875 $rename_from_files{$del_filename} = $d;
876 $rename_to_files{$add_filename} = $d;
879 # Check that any required directories to do the
881 my @add_segments = split('/', $add_filename);
885 foreach my $segment (@add_segments)
887 $add_dir = length($add_dir) ?
"$add_dir/$segment" :
891 push(@add_dirs, $add_dir);
897 read_from_process
($svn, 'mkdir', @add_dirs);
900 read_from_process
($svn, 'mv',
901 $del_filename, $add_filename);
904 } while ($repeat_loop);
907 # If there are any renames that have not been committed, then do
909 if (@renamed_filenames)
911 &commit_renames
($load_dir,
917 # At this point all renames have been performed. Now get the
918 # final list of files and directories in the working copy
919 # directory. The %add_files hash will contain the list of files
920 # and directories to add to the working copy and %del_files starts
921 # with all the files already in the working copy and gets files
922 # removed that are in the imported directory, which results in a
923 # list of files that should be deleted. %upd_files holds the list
924 # of files that have been updated.
926 my %del_files = &recursive_ls_and_hash
($wc_import_dir_cwd);
929 # This anonymous subroutine copies files from the source directory
930 # to the working copy directory.
936 my $source_path = $_;
937 my $dest_path = "$wc_import_dir_cwd/$_";
939 my ($source_type, $source_is_exe) = &file_info
($source_path);
940 my ($dest_type) = &file_info
($dest_path);
942 return if ($source_type ne 'd' and
943 $source_type ne 'f' and
944 $source_type ne 'l');
946 # Fail if the destination type exists but is of a different
947 # type of file than the source type.
948 if ($dest_type ne '0' and $source_type ne $dest_type)
950 die "$0: does not handle changing source and destination type ",
951 "for '$source_path'.\n";
954 # Determine if the file is being added or is an update to an
955 # already existing file using the file's digest.
956 my $del_info = delete $del_files{$source_path};
957 if (defined $del_info)
959 if (defined (my $del_digest = $del_info->{digest
}))
961 my $new_digest = &digest_hash_file
($source_path);
962 if ($new_digest ne $del_digest)
964 print "U $source_path\n";
965 $upd_files{$source_path} = $del_info;
971 print "A $source_path\n";
972 $add_files{$source_path}{type
} = $source_type;
974 # Create an array reference to hold the list of properties
975 # to apply to this object.
976 unless (defined $add_files{$source_path}{properties
})
978 $add_files{$source_path}{properties
} = [];
981 # Go through the list of properties for a match on this
982 # file or directory and if there is a match, then apply
983 # the property to it.
984 foreach my $property (@property_settings)
986 my $re = $property->{re
};
987 if ($source_path =~ $re)
989 my $property_name = $property->{name
};
990 my $property_value = $property->{value
};
992 # The property value may not be set in the
993 # configuration file, since the user may just want
994 # to set the control flag.
995 if (defined $property_name and defined $property_value)
997 # Ignore properties that do not apply to
999 if ($source_type eq 'd')
1001 if ($property_name eq 'svn:eol-style' or
1002 $property_name eq 'svn:executable' or
1003 $property_name eq 'svn:keywords' or
1004 $property_name eq 'svn:mime-type')
1010 # Ignore properties that do not apply to
1012 if ($source_type eq 'f')
1014 if ($property_name eq 'svn:externals' or
1015 $property_name eq 'svn:ignore')
1021 print "Adding to '$source_path' property ",
1022 "'$property_name' with value ",
1023 "'$property_value'.\n";
1025 push(@
{$add_files{$source_path}{properties
}},
1029 last if $property->{control
} eq 'break';
1034 # Add svn:executable to files that have their executable bit
1036 if ($source_is_exe and !$opt_no_auto_exe)
1038 print "Adding to '$source_path' property 'svn:executable' with ",
1040 my $property = {name
=> 'svn:executable', value
=> '*'};
1041 push (@
{$add_files{$source_path}{properties
}},
1045 # Now make sure the file or directory in the source directory
1046 # exists in the repository.
1047 if ($source_type eq 'd')
1049 if ($dest_type eq '0')
1052 or die "$0: cannot mkdir '$dest_path': $!\n";
1056 ($source_type eq 'l') {
1057 my $link_target = readlink($source_path)
1058 or die "$0: cannot readlink '$source_path': $!\n";
1059 if ($dest_type eq 'l')
1061 my $old_target = readlink($dest_path)
1062 or die "$0: cannot readlink '$dest_path': $!\n";
1063 return if ($old_target eq $link_target);
1065 or die "$0: unlink '$dest_path' failed: $!\n";
1067 symlink($link_target, $dest_path)
1068 or die "$0: cannot symlink '$dest_path' to '$link_target': $!\n";
1071 ($source_type eq 'f') {
1072 # Only copy the file if the digests do not match.
1073 if ($add_files{$source_path} or $upd_files{$source_path})
1075 copy
($source_path, $dest_path)
1076 or die "$0: copy '$source_path' to '$dest_path': $!\n";
1081 die "$0: does not handle copying files of type '$source_type'.\n";
1085 # Now change into the directory containing the files to load.
1086 # First change to the original directory where this script was run
1087 # so that if the specified directory is a relative directory path,
1088 # then the script can change into it.
1090 or die "$0: cannot chdir '$orig_cwd': $!\n";
1092 or die "$0: cannot chdir '$load_dir': $!\n";
1094 find
({no_chdir
=> 1,
1095 preprocess
=> sub { sort { $b cmp $a }
1096 grep { $_ !~ /^[._]svn$/ } @_ },
1100 # The files and directories that are in %del_files are the files
1101 # and directories that need to be deleted. Because svn will
1102 # return an error if a file or directory is deleted in a directory
1103 # that subsequently is deleted, first find all directories and
1104 # remove from the list any files and directories inside those
1105 # directories from this list. Work through the list repeatedly
1106 # working from short to long names so that directories containing
1107 # other files and directories will be deleted first.
1112 my @del_files = sort {length($a) <=> length($b) || $a cmp $b}
1114 &filter_renamed_files
(\
@del_files, \
%rename_from_files);
1115 foreach my $file (@del_files)
1117 if ($del_files{$file}{type
} eq 'd')
1120 my $dir_length = length($dir);
1121 foreach my $f (@del_files)
1123 next if $file eq $f;
1124 if (length($f) >= $dir_length and
1125 substr($f, 0, $dir_length) eq $dir)
1128 delete $del_files{$f};
1133 # If there were any deletions of files and/or
1134 # directories inside a directory that will be deleted,
1135 # then restart the entire loop again, because one or
1136 # more keys have been deleted from %del_files.
1137 # Equally important is not to stop this loop if no
1138 # deletions have been done, otherwise later
1139 # directories that may contain files and directories
1140 # to be deleted will not be deleted.
1141 last if $repeat_loop;
1144 } while ($repeat_loop);
1146 # What is left are files that are not in any directories to be
1147 # deleted and directories to be deleted. To delete the files,
1148 # deeper files and directories must be deleted first. Because we
1149 # have a hash keyed by remaining files and directories to be
1150 # deleted, instead of trying to figure out which directories and
1151 # files are contained in other directories, just reverse sort by
1152 # the path length and then alphabetically.
1153 my @del_files = sort {length($b) <=> length($a) || $a cmp $b }
1155 &filter_renamed_files
(\
@del_files, \
%rename_from_files);
1156 foreach my $file (@del_files)
1161 # Now change back to the trunk directory and run the svn commands.
1162 chdir($wc_import_dir_cwd)
1163 or die "$0: cannot chdir '$wc_import_dir_cwd': $!\n";
1165 # If any of the added files have the svn:eol-style property set,
1166 # then pass -b to diff, otherwise diff may fail because the end of
1167 # lines have changed and the source file and file in the
1168 # repository will not be identical.
1169 my @diff_ignore_space_changes;
1171 if (keys %add_files)
1173 my @add_files = sort {length($a) <=> length($b) || $a cmp $b}
1175 my $target_filename = &make_targets_file
(@add_files);
1176 read_from_process
($svn, 'add', '-N', '--targets', $target_filename);
1177 unlink($target_filename);
1179 # Add properties on the added files.
1180 foreach my $add_file (@add_files)
1182 foreach my $property (@
{$add_files{$add_file}{properties
}})
1184 my $property_name = $property->{name
};
1185 my $property_value = $property->{value
};
1187 if ($property_name eq 'svn:eol-style')
1189 @diff_ignore_space_changes = ('-b');
1192 # Write the value to a temporary file in case it's multi-line
1193 my ($handle, $tmpfile) = tempfile
(DIR
=> $temp_dir);
1194 print $handle $property_value;
1197 read_from_process
($svn,
1208 my $target_filename = &make_targets_file
(@del_files);
1209 read_from_process
($svn, 'rm', '--targets', $target_filename);
1210 unlink($target_filename);
1213 # Go through the list of updated files and check the svn:eol-style
1214 # property. If it is set to native, then convert all CR, CRLF and
1215 # LF's in the file to the native end of line characters. Also,
1216 # modify diff's command line so that it will ignore the change in
1217 # end of line style.
1218 if (keys %upd_files)
1220 my @upd_files = sort {length($a) <=> length($b) || $a cmp $b}
1222 foreach my $upd_file (@upd_files)
1224 # Always append @BASE to a filename in case they contain a
1225 # @ character, in which case the Subversion command line
1226 # client will attempt to parse the characters after the @
1227 # as a revision and most likely fail, or if the characters
1228 # after the @ are a valid revision, then it'll possibly
1229 # get the incorrect information. So always append @BASE
1230 # and any preceding @'s will be treated normally and the
1231 # correct information will be retrieved.
1232 my @command = ($svn,
1236 my @lines = read_from_process
(@command);
1240 warn "$0: '@command' returned more than one line of output: ",
1245 my $eol_style = $lines[0];
1246 if ($eol_style eq 'native')
1248 @diff_ignore_space_changes = ('-b');
1249 if (&convert_file_to_native_eol
($upd_file))
1251 print "Native eol-style conversion modified $upd_file.\n";
1257 my $message = wrap
('', '', "Load $load_dir into $repos_load_abs_path.\n");
1258 read_from_process
($svn, 'commit',
1259 @svn_use_repos_cmd_opts,
1262 # If an update is not run now after a commit, then some file and
1263 # directory paths will have an older revisions associated with
1264 # them and any future commits will fail because they are out of
1266 read_from_process
($svn, 'update', @svn_use_repos_cmd_opts);
1268 # Now remove any files and directories to be deleted in the
1272 rmtree
(\
@del_files, 1, 0);
1275 # Now make the tag by doing a copy in the svn repository itself.
1276 if (defined $load_tag)
1278 my $repos_tag_abs_path = length($repos_base_path_segment) ?
1279 "$repos_base_path_segment/$load_tag" :
1282 my $from_url = $repos_load_rel_path eq '.' ?
1283 $repos_load_rel_path :
1284 "$repos_base_url/$repos_load_rel_path";
1285 my $to_url = "$repos_base_url/$load_tag";
1289 "Tag $repos_load_abs_path as " .
1290 "$repos_tag_abs_path.\n");
1291 read_from_process
($svn, 'cp', @svn_use_repos_cmd_opts,
1292 '-m', $message, $from_url, $to_url);
1294 # Now check out the tag and run a recursive diff between the
1295 # original source directory and the tag for a consistency
1297 my $checkout_dir_name = "my_tag_wc_named_$load_tag";
1298 print "Checking out $to_url into $temp_dir/$checkout_dir_name\n";
1301 or die "$0: cannot chdir '$temp_dir': $!\n";
1303 read_from_process
($svn, 'checkout',
1304 @svn_use_repos_cmd_opts,
1305 $to_url, $checkout_dir_name);
1307 chdir($checkout_dir_name)
1308 or die "$0: cannot chdir '$checkout_dir_name': $!\n";
1311 or die "$0: cannot chdir '$orig_cwd': $!\n";
1312 read_from_process
('diff', '-u', @diff_ignore_space_changes,
1314 '-r', $load_dir, "$temp_dir/$checkout_dir_name");
1323 die "usage: $0 [options] svn_url svn_import_dir [dir_v1 [dir_v2 [..]]]\n",
1324 " svn_url is the file:// or http:// URL of the svn repository\n",
1325 " svn_import_dir is the path relative to svn_url where to load dirs\n",
1326 " dir_v1 .. list dirs to import otherwise read from stdin\n",
1328 " -no_user_input don't ask yes/no questions and assume yes answer\n",
1329 " -no_auto_exe don't set svn:executable for executable files\n",
1330 " -p filename table listing properties to apply to matching files\n",
1331 " -svn_username username to perform commits as\n",
1332 " -svn_password password to supply to svn commit\n",
1333 " -t tag_dir create a tag copy in tag_dir, relative to svn_url\n",
1334 " -v increase program verbosity, multiple -v's allowed\n",
1335 " -wc path use the already checked-out working copy at path\n",
1336 " instead of checkout out a fresh working copy\n",
1337 " -glob_ignores List of filename patterns to ignore (as in svn's\n",
1338 " global-ignores config option)\n";
1341 # Get the next directory to load, either from the command line or from
1343 my $get_next_load_dir_init = 0;
1344 my @get_next_load_dirs;
1345 sub get_next_load_dir
1349 unless ($get_next_load_dir_init)
1351 $get_next_load_dir_init = 1;
1352 @get_next_load_dirs = @ARGV;
1354 return shift @get_next_load_dirs;
1359 print "Waiting for next directory to import on standard input:\n";
1363 print "\n" if $opt_verbose;
1366 if ($line =~ m
|(\S
+)\s
+(\S
+)|)
1369 set_svn_use_repos_cmd_opts
($2, $opt_svn_password);
1374 # This constant stores the commonly used string to indicate that a
1375 # subroutine has been passed an incorrect number of arguments.
1376 use vars
qw($INCORRECT_NUMBER_OF_ARGS);
1377 $INCORRECT_NUMBER_OF_ARGS = "passed incorrect number of arguments.\n";
1379 # Creates a temporary file in the temporary directory and stores the
1380 # arguments in it for use by the svn --targets command line option.
1381 # If any part of the file creation failed, exit the program, as
1382 # there's no workaround. Use a unique number as a counter to the
1384 my $make_targets_file_counter;
1385 sub make_targets_file
1389 confess "$0: make_targets_file $INCORRECT_NUMBER_OF_ARGS";
1392 $make_targets_file_counter = 1 unless defined $make_targets_file_counter;
1394 my $filename = sprintf "%s/targets.%05d",
1396 $make_targets_file_counter;
1397 ++$make_targets_file_counter;
1399 open(TARGETS, ">$filename")
1400 or die "$0: cannot open '$filename' for writing: $!\n";
1402 foreach my $file (@_)
1404 print TARGETS "$file\n";
1408 or die "$0: error in closing '$filename' for writing: $!\n";
1413 # Set the svn command line options that are used anytime svn connects
1414 # to the repository.
1415 sub set_svn_use_repos_cmd_opts
1419 confess "$0: set_svn_use_repos_cmd_opts $INCORRECT_NUMBER_OF_ARGS";
1422 my $username = shift;
1423 my $password = shift;
1425 @svn_use_repos_cmd_opts = ('--non-interactive');
1426 if (defined $username and length $username)
1428 push(@svn_use_repos_cmd_opts, '--username', $username);
1430 if (defined $password)
1432 push(@svn_use_repos_cmd_opts, '--password', $password);
1440 confess "$0: get_tag_dir $INCORRECT_NUMBER_OF_ARGS";
1443 my $load_dir = shift;
1445 # Take the tag relative directory, search for pairs of
1446 # REGEX_SEP_CHAR's and use the regular expression inside the pair to
1447 # put in the tag directory name.
1448 my $tag_location = $opt_import_tag_location;
1450 while ((my $i = index($tag_location, $REGEX_SEP_CHAR)) >= 0)
1452 $load_tag .= substr($tag_location, 0, $i, '');
1453 substr($tag_location, 0, 1, '');
1454 my $j = index($tag_location, $REGEX_SEP_CHAR);
1457 die "$0: -t value '$opt_import_tag_location' does not have ",
1458 "matching $REGEX_SEP_CHAR.\n";
1460 my $regex = substr($tag_location, 0, $j, '');
1461 $regex = "($regex)" unless ($regex =~ /\(.+\)/);
1462 substr($tag_location, 0, 1, '');
1463 my @results = $load_dir =~ m/$regex/;
1464 $load_tag .= join('', @results);
1466 $load_tag .= $tag_location;
1471 # Return a two element array. The first element is a single character
1472 # that represents the type of object the path points to. The second
1473 # is a boolean (1 for true, '' for false) if the path points to a file
1474 # and if the file is executable.
1477 lstat(shift) or return ('0', '');
1478 -b _ and return ('b', '');
1479 -c _ and return ('c', '');
1480 -d _ and return ('d', '');
1481 -f _ and return ('f', -x _);
1482 -l _ and return ('l', '');
1483 -p _ and return ('p', '');
1484 -S _ and return ('S', '');
1488 # Start a child process safely without using /bin/sh.
1489 sub safe_read_from_pipe
1493 croak "$0: safe_read_from_pipe $INCORRECT_NUMBER_OF_ARGS";
1496 my $openfork_available = "MSWin32" ne $OSNAME;
1497 if ($openfork_available)
1499 print "Running @_\n";
1500 my $pid = open(SAFE_READ, "-|");
1501 unless (defined $pid)
1503 die "$0: cannot fork: $!\n";
1508 open(STDERR, ">&STDOUT")
1509 or die "$0: cannot dup STDOUT: $!\n";
1511 or die "$0: cannot exec '@_': $!\n";
1516 # Redirect the comment into a temp file and use that to work around
1517 # Windoze's (non-)handling of multi-line commands.
1518 my @commandline = ();
1522 while ($command = shift)
1524 if ("-m" eq $command)
1526 my $comment = shift;
1527 my ($handle, $tmpfile) = tempfile(DIR => $temp_dir);
1528 print $handle $comment;
1531 push(@commandline, "--file");
1532 push(@commandline, $tmpfile);
1536 # Munge the command to protect it from the command line
1537 $command =~ s/\"/\\\"/g;
1538 if ($command =~ m"\s") { $command = "\"$command\""; }
1539 if ($command eq "") { $command = "\"\""; }
1540 if ($command =~ m"\n")
1542 warn "$0: carriage return detected in command - may not work\n";
1544 push(@commandline, $command);
1548 print "Running @commandline\n";
1549 if ( $comment ) { print $comment; }
1552 open(SAFE_READ, "@commandline |")
1553 or die "$0: cannot pipe to command: $!\n";
1565 my $exit = $result >> 8;
1566 my $signal = $result & 127;
1567 my $cd = $result & 128 ? "with core dump" : "";
1570 warn "$0: pipe from '@_' failed $cd: exit=$exit signal=$signal\n";
1574 return ($result, @output);
1582 # Use safe_read_from_pipe to start a child process safely and exit the
1583 # script if the child failed for whatever reason.
1584 sub read_from_process
1588 croak "$0: read_from_process $INCORRECT_NUMBER_OF_ARGS";
1590 my ($status, @output) = &safe_read_from_pipe(@_);
1593 print STDERR "$0: @_ failed with this output:\n", join("\n", @output),
1595 unless ($opt_no_user_input)
1598 "Press return to quit and clean up svn working directory: ";
1609 # Get a list of all the files and directories in the specified
1610 # directory, the type of file and a digest hash of file types.
1611 sub recursive_ls_and_hash
1615 croak "$0: recursive_ls_and_hash $INCORRECT_NUMBER_OF_ARGS";
1618 # This is the directory to change into.
1621 # Get the current directory so that the script can change into the
1622 # current working directory after changing into the specified
1624 my $return_cwd = cwd;
1627 or die "$0: cannot chdir '$dir': $!\n";
1634 return if $_ eq '.';
1635 my ($file_type) = &file_info($_);
1637 if ($file_type eq 'f' or ($file_type eq 'l' and stat($_) and -f _))
1639 $file_digest = &digest_hash_file($_);
1641 $files{$_} = {type => $file_type,
1642 digest => $file_digest};
1644 find({no_chdir => 1,
1650 foreach my $x (@glob_ignores)
1652 if ( $_ =~ /$x/ ) {$ok=0;last;}
1661 or die "$0: cannot chdir '$return_cwd': $!\n";
1666 # Given a list of files and directories which have been renamed but
1667 # not commtited, commit them with a proper log message.
1672 croak "$0: commit_renames $INCORRECT_NUMBER_OF_ARGS";
1675 my $load_dir = shift;
1676 my $renamed_filenames = shift;
1677 my $rename_from_files = shift;
1678 my $rename_to_files = shift;
1680 my $number_renames = @$renamed_filenames/2;
1682 my $message = "To prepare to load $load_dir into $repos_load_abs_path, " .
1683 "perform $number_renames rename" .
1684 ($number_renames > 1 ? "s" : "") . ".\n";
1686 # Text::Wrap::wrap appears to replace multiple consecutive \n's with
1687 # one \n, so wrap the text and then append the second \n.
1688 $message = wrap("", "", $message) . "\n";
1689 while (@$renamed_filenames)
1691 my $from = "$repos_load_abs_path/" . shift @$renamed_filenames;
1692 my $to = "$repos_load_abs_path/" . shift @$renamed_filenames;
1693 $message .= wrap("", " ", "* $to: Renamed from $from.\n");
1696 # Change to the top of the working copy so that any
1697 # directories will also be updated.
1699 chdir($wc_import_dir_cwd)
1700 or die "$0: cannot chdir '$wc_import_dir_cwd': $!\n";
1701 read_from_process($svn, 'commit', @svn_use_repos_cmd_opts, '-m', $message);
1702 read_from_process($svn, 'update', @svn_use_repos_cmd_opts);
1704 or die "$0: cannot chdir '$cwd': $!\n";
1706 # Some versions of subversion have a bug where renamed files
1707 # or directories are not deleted after a commit, so do that
1709 my @del_files = sort {length($b) <=> length($a) || $a cmp $b }
1710 keys %$rename_from_files;
1711 rmtree(\@del_files, 1, 0);
1713 # Empty the list of old and new renamed names.
1714 undef %$rename_from_files;
1715 undef %$rename_to_files;
1718 # Take a one file or directory and see if its name is equal to a
1719 # second or is contained in the second if the second file's file type
1725 croak "$0: contain_in $INCORRECT_NUMBER_OF_ARGS";
1728 my $contained = shift;
1729 my $container = shift;
1730 my $container_type = shift;
1732 if ($container eq $contained)
1737 if ($container_type eq 'd')
1739 my $dirname = "$container/";
1740 my $dirname_length = length($dirname);
1742 if ($dirname_length <= length($contained) and
1743 $dirname eq substr($contained, 0, $dirname_length))
1752 # Take an array reference containing a list of files and directories
1753 # and take a hash reference and remove from the array reference any
1754 # files and directories and the files the directory contains listed in
1756 sub filter_renamed_files
1760 croak "$0: filter_renamed_files $INCORRECT_NUMBER_OF_ARGS";
1763 my $array_ref = shift;
1764 my $hash_ref = shift;
1766 foreach my $remove_filename (keys %$hash_ref)
1768 my $remove_file_type = $hash_ref->{$remove_filename}{type};
1769 for (my $i=0; $i<@$array_ref;)
1771 if (contained_in($array_ref->[$i],
1775 splice(@$array_ref, $i, 1);
1783 # Get a digest hash of the specified filename.
1784 sub digest_hash_file
1788 croak "$0: digest_hash_file $INCORRECT_NUMBER_OF_ARGS";
1791 my $filename = shift;
1793 my $ctx = Digest::MD5->new;
1794 if (open(READ, $filename))
1797 $ctx->addfile(*READ);
1802 die "$0: cannot open '$filename' for reading: $!\n";
1807 # Read standard input until a line contains the required input or an
1808 # empty line to signify the default answer.
1813 croak "$0: get_answer $INCORRECT_NUMBER_OF_ARGS";
1816 my $message = shift;
1817 my $answers = shift;
1818 my $def_ans = shift;
1820 return $def_ans if $opt_no_user_input;
1828 if (defined $line and length $line)
1830 $char = substr($line, 0, 1);
1831 $char = '' if $char eq "\n";
1833 } until $char eq '' or $answers =~ /$char/ig;
1835 return $def_ans if $char eq '';
1836 return pos($answers) - 1;
1839 # Determine the native end of line on this system by writing a \n in
1840 # non-binary mode to an empty file and reading the same file back in
1842 sub determine_native_eol
1844 my $filename = "$temp_dir/svn_load_dirs_eol_test.$$";
1848 or die "$0: cannot unlink '$filename': $!\n";
1851 # Write the \n in non-binary mode.
1852 open(NL_TEST, ">$filename")
1853 or die "$0: cannot open '$filename' for writing: $!\n";
1856 or die "$0: error in closing '$filename' for writing: $!\n";
1858 # Read the \n in binary mode.
1859 open(NL_TEST, $filename)
1860 or die "$0: cannot open '$filename' for reading: $!\n";
1864 my $eol = <NL_TEST>;
1866 or die "$0: cannot close '$filename' for reading: $!\n";
1868 or die "$0: cannot unlink '$filename': $!\n";
1870 my $eol_length = length($eol);
1871 unless ($eol_length)
1873 die "$0: native eol length on this system is 0.\n";
1876 print "Native EOL on this system is ";
1877 for (my $i=0; $i<$eol_length; ++$i)
1879 printf "\\%03o", ord(substr($eol, $i, 1));
1886 # Take a filename, open the file and replace all CR, CRLF and LF's
1887 # with the native end of line style for this system.
1888 sub convert_file_to_native_eol
1892 croak "$0: convert_file_to_native_eol $INCORRECT_NUMBER_OF_ARGS";
1895 my $filename = shift;
1896 open(FILE, $filename)
1897 or die "$0: cannot open '$filename' for reading: $!\n";
1903 or die "$0: error in closing '$filename' for reading: $!\n";
1906 # Go through the file and transform it byte by byte.
1908 while ($i < length($in))
1910 my $cc = substr($in, $i, 2);
1911 if ($cc eq "\015\012")
1913 $out .= $native_eol;
1918 my $c = substr($cc, 0, 1);
1919 if ($c eq "\012" or $c eq "\015")
1921 $out .= $native_eol;
1930 return 0 if $in eq $out;
1932 my $tmp_filename = ".svn/tmp/svn_load_dirs.$$";
1933 open(FILE, ">$tmp_filename")
1934 or die "$0: cannot open '$tmp_filename' for writing: $!\n";
1938 or die "$0: cannot close '$tmp_filename' for writing: $!\n";
1939 rename($tmp_filename, $filename)
1940 or die "$0: cannot rename '$tmp_filename' to '$filename': $!\n";
1945 # Split the input line into words taking into account that single or
1946 # double quotes may define a single word with whitespace in it.
1951 croak "$0: split_line $INCORRECT_NUMBER_OF_ARGS";
1956 # Strip leading whitespace. Do not strip trailing whitespace which
1957 # may be part of quoted text that was never closed.
1960 my $line_length = length $line;
1962 my $current_word = '';
1964 my $in_protect = '';
1968 while ($i < $line_length)
1970 my $c = substr($line, $i, 1);
1975 if ($c eq $in_quote)
1977 $current_word .= $c;
1979 elsif ($c eq '"' or $c eq "'")
1981 $current_word .= $c;
1985 $current_word .= "$in_protect$c";
1995 if ($c eq $in_quote)
2001 $current_word .= $c;
2004 elsif ($c eq '"' or $c eq "'")
2008 elsif ($c =~ m/^\s$/)
2012 push(@words, $current_word);
2018 $current_word .= $c;
2021 $in_space = $c =~ m/^\s$/;
2024 # Handle any leftovers.
2025 $current_word .= $in_protect if $in_protect;
2026 push(@words, $current_word) if length $current_word;
2031 # This package exists just to delete the temporary directory.
2032 package Temp::Delete;
2041 print "Cleaning up $temp_dir\n";
2042 File::Path::rmtree([$temp_dir], 0, 0);