Package: dpkg
Version: 1.15.8.6
Severity: normal
Tags: patch
User: [email protected]
Usertags: origin-ubuntu ubuntu-patch natty

The plymouth package in Ubuntu is maintained using 3.0 (quilt) and
--single-debian-patch.  (This isn't my personal preference - I prefer
separate patches, not to mention that Debian's package uses separate
patches and we ought to rebase on top of that - but another developer
set it up and I haven't got around to arguing yet. :-) )

debian/patches/debian-changes is stored in bzr.  I've noticed over time
that when people upload changes to plymouth without explicitly using
'quilt refresh', i.e. relying on dpkg-source to regenerate the patch
file for them, the ordering of files in the patch is shuffled.  This is
annoying as it produces enormous but almost content-free deltas in
revision control.

'quilt refresh' does not have this problem.  It sorts files in the
resulting patch as follows:

  * Any files in the existing patch come first, in the order in which
    they appear in the existing patch.

  * New files follow, sorted lexicographically.

(See the files_in_patch_ordered function in
/usr/share/quilt/scripts/patchfns.)

This seems pretty reasonable, and I think dpkg-source should do the
same.  The attached patch implements this.

Thanks,

-- 
Colin Watson                                       [[email protected]]
>From 278a62058b132fa2e239e1dade8d60e5220ef910 Mon Sep 17 00:00:00 2001
From: Colin Watson <[email protected]>
Date: Mon, 6 Dec 2010 09:42:17 +0000
Subject: [PATCH] Keep file order stable when regenerating autopatches.

---
 scripts/Dpkg/Source/Package/V2.pm |    3 +-
 scripts/Dpkg/Source/Patch.pm      |   82 +++++++++++++++++++++++++-----------
 2 files changed, 59 insertions(+), 26 deletions(-)

diff --git a/scripts/Dpkg/Source/Package/V2.pm b/scripts/Dpkg/Source/Package/V2.pm
index 8f6618e..13552a7 100644
--- a/scripts/Dpkg/Source/Package/V2.pm
+++ b/scripts/Dpkg/Source/Package/V2.pm
@@ -432,7 +432,8 @@ sub do_build {
     $diff->create();
     $diff->set_header($self->get_patch_header($dir, $autopatch));
     $diff->add_diff_directory($tmp, $dir, basedirname => $basedirname,
-            %{$self->{'diff_options'}}, handle_binary_func => $handle_binary);
+            %{$self->{'diff_options'}}, handle_binary_func => $handle_binary,
+            order_from => $autopatch);
     error(_g("unrepresentable changes to source")) if not $diff->finish();
     # The previous auto-patch must be removed, it has not been used and it
     # will be recreated if it's still needed
diff --git a/scripts/Dpkg/Source/Patch.pm b/scripts/Dpkg/Source/Patch.pm
index efeef5d..6c6fbdf 100644
--- a/scripts/Dpkg/Source/Patch.pm
+++ b/scripts/Dpkg/Source/Patch.pm
@@ -154,6 +154,7 @@ sub add_diff_directory {
         $diff_ignore = sub { return 0 };
     }
 
+    my @diff_files;
     my %files_in_new;
     my $scan_new = sub {
         my $fn = (length > length($new)) ? substr($_, length($new) + 1) : '.';
@@ -189,27 +190,8 @@ sub add_diff_directory {
             if ($opts{'use_dev_null'}) {
                 $label_old = $old_file if $old_file eq '/dev/null';
             }
-            my $success = $self->add_diff_file($old_file, "$new/$fn",
-                label_old => $label_old,
-                label_new => "$basedir/$fn",
-                %opts);
-
-            if ($success and ($old_file eq "/dev/null")) {
-                if (not $size) {
-                    warning(_g("newly created empty file '%s' will not " .
-                               "be represented in diff"), $fn);
-                } else {
-                    if ($mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
-                        warning(_g("executable mode %04o of '%s' will " .
-                                   "not be represented in diff"), $mode, $fn)
-                            unless $fn eq 'debian/rules';
-                    }
-                    if ($mode & (S_ISUID | S_ISGID | S_ISVTX)) {
-                        warning(_g("special mode %04o of '%s' will not " .
-                                   "be represented in diff"), $mode, $fn);
-                    }
-                }
-            }
+            push @diff_files, [$fn, $mode, $size, $old_file, "$new/$fn",
+                               $label_old, "$basedir/$fn"];
         } elsif (-p _) {
             unless (-p "$old/$fn") {
                 $self->_fail_not_same_type("$old/$fn", "$new/$fn");
@@ -235,10 +217,8 @@ sub add_diff_directory {
         lstat("$old/$fn") || syserr(_g("cannot stat file %s"), "$old/$fn");
         if (-f _) {
             if ($inc_removal) {
-                $self->add_diff_file("$old/$fn", "/dev/null",
-                    label_old => "$basedir.orig/$fn",
-                    label_new => "/dev/null",
-                    %opts);
+                push @diff_files, [$fn, 0, 0, "$old/$fn", "/dev/null",
+                                   "$basedir.orig/$fn", "/dev/null"];
             } else {
                 warning(_g("ignoring deletion of file %s"), $fn);
             }
@@ -253,6 +233,55 @@ sub add_diff_directory {
 
     find({ wanted => $scan_new, no_chdir => 1 }, $new);
     find({ wanted => $scan_old, no_chdir => 1 }, $old);
+
+    if ($opts{"order_from"} and -e $opts{"order_from"}) {
+        my $order_from = Dpkg::Source::Patch->new(
+            filename => $opts{"order_from"});
+        my $analysis = $order_from->analyze($basedir);
+        my %patchorder;
+        my $i = 0;
+        for my $fn (@{$analysis->{"patchorder"}}) {
+            $fn =~ s{^[^/]+/}{};
+            $patchorder{$fn} = $i++;
+        }
+        # 'quilt refresh' sorts files as follows:
+        #   - Any files in the existing patch come first, in the order in
+        #     which they appear in the existing patch.
+        #   - New files follow, sorted lexicographically.
+        # This seems a reasonable policy to follow, and avoids autopatches
+        # being shuffled when they are regenerated.
+        for my $diff_file (sort { $a->[0] cmp $b->[0] } @diff_files) {
+            my $fn = $diff_file->[0];
+            $patchorder{$fn} = $i++ unless exists $patchorder{$fn};
+        }
+        @diff_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] }
+                      map { [$_, $patchorder{$_->[0]}] } @diff_files;
+    }
+
+    for my $diff_file (@diff_files) {
+        my ($fn, $mode, $size,
+            $old_file, $new_file, $label_old, $label_new) = @$diff_file;
+        my $success = $self->add_diff_file($old_file, $new_file,
+                                           label_old => $label_old,
+                                           label_new => $label_new, %opts);
+        if ($success and
+            $old_file eq "/dev/null" and $new_file ne "/dev/null") {
+            if (not $size) {
+                warning(_g("newly created empty file '%s' will not " .
+                           "be represented in diff"), $fn);
+            } else {
+                if ($mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+                    warning(_g("executable mode %04o of '%s' will " .
+                               "not be represented in diff"), $mode, $fn)
+                        unless $fn eq 'debian/rules';
+                }
+                if ($mode & (S_ISUID | S_ISGID | S_ISVTX)) {
+                    warning(_g("special mode %04o of '%s' will not " .
+                               "be represented in diff"), $mode, $fn);
+                }
+            }
+        }
+    }
 }
 
 sub finish {
@@ -287,6 +316,7 @@ sub analyze {
     my $diff = $self->get_filename();
     my %filepatched;
     my %dirtocreate;
+    my @patchorder;
     my $diff_count = 0;
 
     sub getline {
@@ -384,6 +414,7 @@ sub analyze {
 	    error(_g("diff `%s' patches file %s twice"), $diff, $fn);
 	}
 	$filepatched{$fn} = 1;
+	push @patchorder, $fn;
 
 	# read hunks
 	my $hunk = 0;
@@ -424,6 +455,7 @@ sub analyze {
     }
     *$self->{'analysis'}{$destdir}{"dirtocreate"} = \%dirtocreate;
     *$self->{'analysis'}{$destdir}{"filepatched"} = \%filepatched;
+    *$self->{'analysis'}{$destdir}{"patchorder"} = \...@patchorder;
     return *$self->{'analysis'}{$destdir};
 }
 
-- 
1.7.2.3

Reply via email to