# Differences from invoking "patch -p0":
#
# Handles added files (does a svn add).
+# Handles added directories (does a svn add).
# Handles removed files (does a svn rm).
+# Handles removed directories--those with no more files or directories left in them
+# (does a svn rm).
# Has mode where it will roll back to svn version numbers in the patch file so svn
# can do a 3-way merge.
# Paths from Index: lines are used rather than the paths on the patch lines, which
sub addDirectoriesIfNeeded($);
sub applyPatch($$;$);
sub handleBinaryChange($$);
+sub isDirectoryEmptyForRemoval($);
sub patch($);
+sub removeDirectoriesIfNeeded();
+sub svnStatus($);
my $merge = 0;
my $showHelp = 0;
exit 1;
}
-my $startDir = getcwd();
+my %removeDirectoryIgnoreList = (
+ '.' => 1,
+ '..' => 1,
+ '.svn' => 1,
+ '_svn' => 1,
+);
+my %checkedDirectories;
my @patches;
my %versions;
patch($patch);
}
+removeDirectoriesIfNeeded();
+
exit 0;
+sub addDirectoriesIfNeeded($)
+{
+ my ($path) = @_;
+ my @dirs = File::Spec->splitdir($path);
+ my $dir = ".";
+ while (scalar @dirs) {
+ $dir = File::Spec->catdir($dir, shift @dirs);
+ next if (exists $checkedDirectories{$dir});
+ if (! -e $dir) {
+ mkdir $dir or die "Failed to create required directory '$dir' for path '$path'\n";
+ system "svn", "add", $dir;
+ $checkedDirectories{$dir} = 1;
+ }
+ elsif (-d $dir) {
+ my $svnOutput = svnStatus($dir);
+ if ($svnOutput && substr($svnOutput, 0, 1) eq "?") {
+ system "svn", "add", $dir;
+ }
+ $checkedDirectories{$dir} = 1;
+ }
+ else {
+ die "'$dir' is not a directory";
+ }
+ }
+}
+
sub applyPatch($$;$)
{
my ($patch, $fullPath, $options) = @_;
close PATCH;
}
+sub handleBinaryChange($$)
+{
+ my ($fullPath, $contents) = @_;
+ if ($contents =~ m#((\n[A-Za-z0-9+/]{76})+\n[A-Za-z0-9+/=]{4,76}\n)\n#) {
+ # Addition or Modification
+ open FILE, ">", $fullPath or die;
+ print FILE decode_base64($1);
+ close FILE;
+ my $svnOutput = svnStatus($fullPath);
+ if ($svnOutput && substr($svnOutput, 0, 1) eq "?") {
+ # Addition
+ system "svn", "add", $fullPath;
+ } else {
+ # Modification
+ print $svnOutput if $svnOutput;
+ }
+ } else {
+ # Deletion
+ system "svn", "rm", $fullPath;
+ }
+}
+
+sub isDirectoryEmptyForRemoval($)
+{
+ my ($dir) = @_;
+ opendir DIR, $dir or die "Could not open '$dir' to list files: $?";
+ my @files = grep {
+ if (exists $removeDirectoryIgnoreList{$_}) {
+ 0;
+ }
+ elsif (! -d File::Spec->catdir($dir, $_)) {
+ 1;
+ }
+ else {
+ my $svnOutput = svnStatus(File::Spec->catdir($dir, $_));
+ if ($svnOutput && substr($svnOutput, 0, 1) eq "D") {
+ 0;
+ }
+ else {
+ 1;
+ }
+ }
+ } readdir DIR;
+ closedir DIR;
+ return scalar(@files) == 0;
+}
+
sub patch($)
{
my ($patch) = @_;
return if !$patch;
- $patch =~ m|^Index: ([^\n]+)| or die "Failed to find Index: in \"$patch\"\n";
+ $patch =~ m|^Index: ([^\n]+)| or die "Failed to find 'Index:' in \"$patch\"\n";
my $fullPath = $1;
my $deletion = 0;
}
}
-sub handleBinaryChange($$)
+sub removeDirectoriesIfNeeded()
{
- my ($fullPath, $contents) = @_;
- if ($contents =~ m#((\n[A-Za-z0-9+/]{76})+\n[A-Za-z0-9+/=]{4,76}\n)\n#) {
- # Addition or Modification
- open FILE, ">", $fullPath or die;
- print FILE decode_base64($1);
- close FILE;
- open SVN, "svn stat '$fullPath' |" or die;
- my $svnStatus = <SVN>;
- close SVN;
- if (substr($svnStatus, 0 ,1) eq "?") {
- # Addition
- system "svn", "add", $fullPath;
- } else {
- # Modification
- print $svnStatus;
+ foreach my $dir (reverse sort keys %checkedDirectories) {
+ if (isDirectoryEmptyForRemoval($dir)) {
+ my $svnOutput;
+ open SVN, "svn rm '$dir' |" or die;
+ # Only save the last line since Subversion lists all changed statuses below $dir
+ while (<SVN>) {
+ $svnOutput = $_;
+ }
+ close SVN;
+ print $svnOutput;
}
- } else {
- # Deletion
- system "svn", "rm", $fullPath;
}
}
-sub addDirectoriesIfNeeded($)
+sub svnStatus($)
{
- my ($path) = @_;
- my @dirs = File::Spec->splitdir($path);
- while (my $dir = shift @dirs) {
- if (! -x $dir) {
- mkdir $dir or die "Failed create required directory: $dir for path: $path\n";
- system "svn", "add", $dir;
- }
- chdir $dir or die "Failed to chdir to $dir\n";
- }
- chdir $startDir or die "Failed to chdir to $startDir\n";
+ my ($fullPath) = @_;
+ open SVN, "svn status --non-interactive --non-recursive '$fullPath' |" or die;
+ my $svnStatus = <SVN>;
+ close SVN;
+ return $svnStatus;
}
# Differences from invoking "patch -p0 -R":
#
# Handles added files (does a svn rm).
-# Handles removed files (does a svn add).
+# Handles added directories (does a svn rm and a rmdir).
+# Handles removed files (does a svn revert).
+# Handles removed directories (does a svn revert).
# Paths from Index: lines are used rather than the paths on the patch lines, which
# makes patches generated by "cvs diff" work (increasingly unimportant since we
# use Subversion now).
# Notice a patch that's being unapplied at the "wrong level" and make it work anyway.
# Do a dry run on the whole patch and don't do anything if part of the patch is
# going to fail (probably too strict unless we do the ChangeLog thing).
-# Remove new directories that were previously added by svn-apply.
use strict;
use warnings;
use Cwd;
use File::Basename;
+use File::Spec;
use Getopt::Long;
sub patch($);
+sub revertDirectories();
+sub svnStatus($);
sub unapplyPatch($$;$);
my $showHelp = 0;
exit 1;
}
-my $startDir = getcwd();
+my %directoriesToCheck;
my $indexPath;
my $patch;
}
patch($patch);
-exit 0;
+revertDirectories();
-sub unapplyPatch($$;$)
-{
- my ($patch, $fullPath, $options) = @_;
- $options = [] if (! $options);
- my $command = "patch " . join(" ", "-p0", "-R", @{$options});
- open PATCH, "| $command" or die "Failed to patch $fullPath\n";
- print PATCH $patch;
- close PATCH;
-}
+exit 0;
sub patch($)
{
$patch =~ m|^Index: ([^\n]+)| or die "Failed to find Index: in \"$patch\"\n";
my $fullPath = $1;
+ $directoriesToCheck{dirname($fullPath)} = 1;
my $deletion = 0;
my $addition = 0;
system "svn", "revert", $fullPath;
}
}
+
+sub revertDirectories()
+{
+ my %checkedDirectories;
+ foreach my $path (reverse sort keys %directoriesToCheck) {
+ my @dirs = File::Spec->splitdir($path);
+ while (scalar @dirs) {
+ my $dir = File::Spec->catdir(@dirs);
+ pop(@dirs);
+ next if (exists $checkedDirectories{$dir});
+ if (-d $dir) {
+ my $svnOutput = svnStatus($dir);
+ if ($svnOutput && substr($svnOutput, 0, 1) eq "A") {
+ system "svn", "revert", $dir;
+ rmdir $dir;
+ }
+ elsif ($svnOutput && substr($svnOutput, 0, 1) eq "D") {
+ system "svn", "revert", $dir;
+ }
+ else {
+ # Modification
+ print $svnOutput if $svnOutput;
+ }
+ $checkedDirectories{$dir} = 1;
+ }
+ else {
+ die "'$dir' is not a directory";
+ }
+ }
+ }
+}
+
+sub svnStatus($)
+{
+ my ($fullPath) = @_;
+ open SVN, "svn status --non-interactive --non-recursive '$fullPath' |" or die;
+ my $svnStatus = <SVN>;
+ close SVN;
+ return $svnStatus;
+}
+
+sub unapplyPatch($$;$)
+{
+ my ($patch, $fullPath, $options) = @_;
+ $options = [] if (! $options);
+ my $command = "patch " . join(" ", "-p0", "-R", @{$options});
+ open PATCH, "| $command" or die "Failed to patch $fullPath\n";
+ print PATCH $patch;
+ close PATCH;
+}