+2006-06-04 David Kilzer <ddkilzer@kilzer.net>
+
+ Reviewed by darin.
+
+ http://bugzilla.opendarwin.org/show_bug.cgi?id=9299
+ Teach svn-create-patch and friends to work with binary files
+
+ * Scripts/svn-apply: Updated to use base64-encoded text for binary files when applying patches.
+ * Scripts/svn-create-patch: Updated to include binary file content as base64-encoded text in patches.
+ * Scripts/svn-unapply: Updated to recognize binary files when unapplying patches.
+
2006-06-03 David Kilzer <ddkilzer@kilzer.net>
Reviewed by Maciej.
# makes patches generated by "cvs diff" work (increasingly unimportant since we
# use Subversion now).
# ChangeLog patches use --fuzz=3 to prevent rejects.
+# Handles binary files (requires patches made by svn-create-patch).
#
# Missing features:
#
# Handle property changes.
-# Handle binary files (requires patches made by svn-create-patch).
# Handle file moves (requires patches made by svn-create-patch).
# When doing a removal, check that old file matches what's being removed.
# Notice a patch that's being applied at the "wrong level" and make it work anyway.
use strict;
use Cwd;
use Getopt::Long;
+use MIME::Base64;
my $merge = 0;
GetOptions("merge" => \$merge);
my $deletion = 0;
my $addition = 0;
+ my $isBinary = 0;
$addition = 1 if $patch =~ /\n--- .+\(revision 0\)\n/;
$deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/;
+ $isBinary = 1 if $patch =~ /\nCannot display: file marked as a binary type\./;
- if (!$addition && !$deletion) {
+ if (!$addition && !$deletion && !$isBinary) {
# Standard patch, patch tool can handle this.
if ($base eq "ChangeLog") {
my $changeLogDotOrigExisted = -f "${fullpath}.orig";
applyPatch($patch, $fullpath, ["--fuzz=3"]);
unlink("${fullpath}.orig") if (! $changeLogDotOrigExisted);
- }
- else {
+ } else {
applyPatch($patch, $fullpath);
}
} else {
- # Either a deletion or an addition.
+ # Either a deletion, an addition or a binary change.
# Change directory down into the directory in question.
chdirAddingDirectoriesIfNeeded($prefix);
- if ($deletion) {
+ if ($isBinary) {
+ # Binary change
+ handleBinaryChange($base, $patch);
+ } elsif ($deletion) {
# Deletion.
system "svn", "rm", $base;
} else {
# Addition.
- my $file = $patch;
- if ($file !~ s/^(.*\n)*@@[^\n]+@@\n//) {
- # Empty file.
- $file = "";
+ my $contents = $patch;
+ if ($contents !~ s/^(.*\n)*@@[^\n]+@@\n//) {
+ # Empty contents.
+ $contents = "";
} else {
- # Non-empty file: Remove leading + signs.
- $file =~ s/^\+//;
- $file =~ s/\n\+/\n/g;
+ # Non-empty contents: Remove leading + signs.
+ $contents =~ s/^\+//;
+ $contents =~ s/\n\+/\n/g;
}
open FILE, ">", $base or die;
- print FILE $file;
+ print FILE $contents;
close FILE;
system "svn", "add", "$base";
}
}
}
+sub handleBinaryChange
+{
+ my ($base, $contents) = @_;
+ if ($contents =~ m#((\n[A-Za-z0-9+/]{76})+\n[A-Za-z0-9+/=]{4,76}\n)\n#) {
+ # Addition or Modification
+ open FILE, ">", $base or die;
+ print FILE decode_base64($1);
+ close FILE;
+ open SVN, "svn stat '$base' |" or die;
+ my $svnStatus = <SVN>;
+ close SVN;
+ if (substr($svnStatus, 0 ,1) eq "?") {
+ # Addition
+ system "svn", "add", "$base";
+ } else {
+ # Modification
+ print $svnStatus;
+ }
+ } else {
+ # Deletion
+ system "svn", "rm", "$base";
+ }
+}
+
sub chdirAddingDirectoriesIfNeeded
{
my $path = shift;
#!/usr/bin/perl -w
-# Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+# Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
#
# Uses the real diff, not svn's built-in diff.
# Always passes "-p" to diff so it will try to include function names.
+# Handles binary files (encoded as a base64 chunk of text).
#
# Missing features:
#
# Sort the diffs, since svn emits them in a seemingly-random order.
-# Handle binary files (some text form of the binary file).
# Handle moved files.
use strict;
use Getopt::Long;
use Time::gmtime;
use File::stat;
+use File::Spec;
use POSIX qw(:errno_h);
use Config;
+use MIME::Base64;
my $startDir = getcwd();
my %paths;
chdir $dir or die;
open DIFF, "svn diff --diff-cmd diff -x -uNp '$base' |" or die;
my $indexPath;
+ my $binaryPath;
while (<DIFF>) {
if (/^Index: (.*)/) {
+ # New patch just started
$indexPath = $1;
if ($dir ne ".") {
$indexPath = "$dir/$indexPath";
s/Index: .*/Index: $indexPath/;
}
+ # Output encoded binary contents of last patch before beginning of next patch
+ outputBinaryContent(File::Spec->abs2rel($binaryPath, $dir)) if ($binaryPath);
+ undef $binaryPath;
}
if ($indexPath) {
# Fix paths on diff, ---, and +++ lines to match preceding Index: line.
s/^--- \S+/--- $indexPath/;
s/^\+\+\+ \S+/+++ $indexPath/ && undef $indexPath;
}
+ if ($binaryPath) {
+ # Fix path on "Property changes on:" line to match preceding Index: line.
+ s/^(Property changes on:) \S+/$1 $indexPath/;
+ }
+ $binaryPath = $indexPath if (/^Cannot display: file marked as a binary type\.$/);
print;
}
close DIFF;
+ # Output encoded binary contents if the last patch was binary
+ outputBinaryContent(File::Spec->abs2rel($binaryPath, $dir)) if ($binaryPath);
chdir $startDir or die;
print STDERR $errors;
}
+# Outputs binary content as encoded text
+sub outputBinaryContent
+{
+ my ($path) = @_;
+ # Deletion
+ return if (! -e $path);
+ # Addition or Modification
+ my $buffer;
+ open BINARY, $path or die;
+ while (read(BINARY, $buffer, 60*57)) {
+ print encode_base64($buffer);
+ }
+ close BINARY;
+ print "\n";
+}
+
# Generate the diff for each passed file or directory.
for my $path (sort keys %paths) {
diff($path);
# makes patches generated by "cvs diff" work (increasingly unimportant since we
# use Subversion now).
# ChangeLog patches use --fuzz=3 to prevent rejects.
+# Handles binary files (requires patches made by svn-create-patch).
#
# Missing features:
#
# Handle property changes.
-# Handle binary files (requires patches made by svn-create-patch).
# Handle file moves (requires patches made by svn-create-patch).
# Use version numbers in the patch file and do a 3-way merge.
# When reversing an addition, check that the file matches what's being removed.
my $deletion = 0;
my $addition = 0;
+ my $isBinary = 0;
$addition = 1 if $patch =~ /\n--- .+\(revision 0\)\n/;
$deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/;
+ $isBinary = 1 if $patch =~ /\nCannot display: file marked as a binary type\./;
- if (!$addition && !$deletion) {
+ if (!$addition && !$deletion && !$isBinary) {
# Standard patch, patch tool can handle this.
if ($base eq "ChangeLog") {
my $changeLogDotOrigExisted = -f "${fullpath}.orig";
unapplyPatch($patch, $fullpath, ["--fuzz=3"]);
unlink("${fullpath}.orig") if (! $changeLogDotOrigExisted);
- }
- else {
+ } else {
unapplyPatch($patch, $fullpath);
}
} else {
- # Either a deletion or an addition.
+ # Either a deletion, an addition or a binary change.
# Change directory down into the directory in question.
if ($prefix) {
chdir $prefix or die "Failed to chdir to $prefix";
}
- if ($deletion) {
- # Reverse a deletion.
- system "svn", "revert", "$base";
- } else {
- # Reverse an addition.
- system "svn", "rm", "--force", $base;
- }
+ # Reverse change by deleting current copy if it exists first
+ unlink($base) if (-e $base);
+
+ # Then run svn revert
+ system "svn", "revert", "$base";
chdir $startDir or die;
}