Index: third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm |
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c5babfb5332877f5726dc5a8805abe07ab65135a |
--- /dev/null |
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm |
@@ -0,0 +1,767 @@ |
+# Copyright © 2008-2011 Raphaël Hertzog <hertzog@debian.org> |
+# |
+# This program is free software; you can redistribute it and/or modify |
+# it under the terms of the GNU General Public License as published by |
+# the Free Software Foundation; either version 2 of the License, or |
+# (at your option) any later version. |
+# |
+# This program is distributed in the hope that it will be useful, |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+# GNU General Public License for more details. |
+# |
+# You should have received a copy of the GNU General Public License |
+# along with this program. If not, see <https://www.gnu.org/licenses/>. |
+ |
+package Dpkg::Source::Package::V2; |
+ |
+use strict; |
+use warnings; |
+ |
+our $VERSION = '0.01'; |
+ |
+use parent qw(Dpkg::Source::Package); |
+ |
+use Dpkg; |
+use Dpkg::Gettext; |
+use Dpkg::ErrorHandling; |
+use Dpkg::File; |
+use Dpkg::Compression; |
+use Dpkg::Source::Archive; |
+use Dpkg::Source::Patch; |
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler); |
+use Dpkg::Source::Functions qw(erasedir is_binary fs_time); |
+use Dpkg::Vendor qw(run_vendor_hook); |
+use Dpkg::Control; |
+use Dpkg::Changelog::Parse; |
+ |
+use POSIX qw(:errno_h); |
+use Cwd; |
+use File::Basename; |
+use File::Temp qw(tempfile tempdir); |
+use File::Path; |
+use File::Spec; |
+use File::Find; |
+use File::Copy; |
+ |
+our $CURRENT_MINOR_VERSION = '0'; |
+ |
+sub init_options { |
+ my ($self) = @_; |
+ $self->SUPER::init_options(); |
+ $self->{options}{include_removal} = 0 |
+ unless exists $self->{options}{include_removal}; |
+ $self->{options}{include_timestamp} = 0 |
+ unless exists $self->{options}{include_timestamp}; |
+ $self->{options}{include_binaries} = 0 |
+ unless exists $self->{options}{include_binaries}; |
+ $self->{options}{preparation} = 1 |
+ unless exists $self->{options}{preparation}; |
+ $self->{options}{skip_patches} = 0 |
+ unless exists $self->{options}{skip_patches}; |
+ $self->{options}{unapply_patches} = 'auto' |
+ unless exists $self->{options}{unapply_patches}; |
+ $self->{options}{skip_debianization} = 0 |
+ unless exists $self->{options}{skip_debianization}; |
+ $self->{options}{create_empty_orig} = 0 |
+ unless exists $self->{options}{create_empty_orig}; |
+ $self->{options}{auto_commit} = 0 |
+ unless exists $self->{options}{auto_commit}; |
+} |
+ |
+sub parse_cmdline_option { |
+ my ($self, $opt) = @_; |
+ if ($opt =~ /^--include-removal$/) { |
+ $self->{options}{include_removal} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--include-timestamp$/) { |
+ $self->{options}{include_timestamp} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--include-binaries$/) { |
+ $self->{options}{include_binaries} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--no-preparation$/) { |
+ $self->{options}{preparation} = 0; |
+ return 1; |
+ } elsif ($opt =~ /^--skip-patches$/) { |
+ $self->{options}{skip_patches} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--unapply-patches$/) { |
+ $self->{options}{unapply_patches} = 'yes'; |
+ return 1; |
+ } elsif ($opt =~ /^--no-unapply-patches$/) { |
+ $self->{options}{unapply_patches} = 'no'; |
+ return 1; |
+ } elsif ($opt =~ /^--skip-debianization$/) { |
+ $self->{options}{skip_debianization} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--create-empty-orig$/) { |
+ $self->{options}{create_empty_orig} = 1; |
+ return 1; |
+ } elsif ($opt =~ /^--abort-on-upstream-changes$/) { |
+ $self->{options}{auto_commit} = 0; |
+ return 1; |
+ } elsif ($opt =~ /^--auto-commit$/) { |
+ $self->{options}{auto_commit} = 1; |
+ return 1; |
+ } |
+ return 0; |
+} |
+ |
+sub do_extract { |
+ my ($self, $newdirectory) = @_; |
+ my $fields = $self->{fields}; |
+ |
+ my $dscdir = $self->{basedir}; |
+ |
+ my $basename = $self->get_basename(); |
+ my $basenamerev = $self->get_basename(1); |
+ |
+ my ($tarfile, $debianfile, %origtar, %seen); |
+ my ($tarsign, %origtarsign); |
+ my $re_ext = compression_get_file_extension_regex(); |
+ foreach my $file ($self->get_files()) { |
+ my $uncompressed = $file; |
+ $uncompressed =~ s/\.$re_ext$/.*/; |
+ $uncompressed =~ s/\.$re_ext\.asc$/.*.asc/; |
+ error(_g('duplicate files in %s source package: %s'), 'v2.0', |
+ $uncompressed) if $seen{$uncompressed}; |
+ $seen{$uncompressed} = 1; |
+ if ($file =~ /^\Q$basename\E\.orig\.tar\.$re_ext$/) { |
+ $tarfile = $file; |
+ } elsif ($file =~ /^\Q$basename\E\.orig\.tar\.$re_ext\.asc$/) { |
+ $tarsign = $file; |
+ } elsif ($file =~ /^\Q$basename\E\.orig-([[:alnum:]-]+)\.tar\.$re_ext$/) { |
+ $origtar{$1} = $file; |
+ } elsif ($file =~ /^\Q$basename\E\.orig-([[:alnum:]-]+)\.tar\.$re_ext\.asc$/) { |
+ $origtarsign{$1} = $file; |
+ } elsif ($file =~ /^\Q$basenamerev\E\.debian\.tar\.$re_ext$/) { |
+ $debianfile = $file; |
+ } else { |
+ error(_g('unrecognized file for a %s source package: %s'), |
+ 'v2.0', $file); |
+ } |
+ } |
+ |
+ unless ($tarfile and $debianfile) { |
+ error(_g('missing orig.tar or debian.tar file in v2.0 source package')); |
+ } |
+ if ($tarsign and $tarfile ne substr $tarsign, 0, -4) { |
+ error(_g('mismatched orig.tar %s for signature %s in source package'), |
+ $tarfile, $tarsign); |
+ } |
+ foreach my $name (keys %origtarsign) { |
+ error(_g('missing addon orig.tar for signature %s in source package'), |
+ $origtarsign{$name}) |
+ if not exists $origtar{$name}; |
+ error(_g('mismatched addon orig.tar %s for signature %s in source package'), |
+ $origtar{$name}, $origtarsign{$name}) |
+ if $origtar{$name} ne substr $origtarsign{$name}, 0, -4; |
+ } |
+ |
+ erasedir($newdirectory); |
+ |
+ # Extract main tarball |
+ info(_g('unpacking %s'), $tarfile); |
+ my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile"); |
+ $tar->extract($newdirectory, no_fixperms => 1, |
+ options => [ '--anchored', '--no-wildcards-match-slash', |
+ '--exclude', '*/.pc', '--exclude', '.pc' ]); |
+ # The .pc exclusion is only needed for 3.0 (quilt) and to avoid |
+ # having an upstream tarball provide a directory with symlinks |
+ # that would be blindly followed when applying the patches |
+ |
+ # Extract additional orig tarballs |
+ foreach my $subdir (keys %origtar) { |
+ my $file = $origtar{$subdir}; |
+ info(_g('unpacking %s'), $file); |
+ if (-e "$newdirectory/$subdir") { |
+ warning(_g("required removal of `%s' installed by original tarball"), $subdir); |
+ erasedir("$newdirectory/$subdir"); |
+ } |
+ $tar = Dpkg::Source::Archive->new(filename => "$dscdir$file"); |
+ $tar->extract("$newdirectory/$subdir", no_fixperms => 1); |
+ } |
+ |
+ # Stop here if debianization is not wanted |
+ return if $self->{options}{skip_debianization}; |
+ |
+ # Extract debian tarball after removing the debian directory |
+ info(_g('unpacking %s'), $debianfile); |
+ erasedir("$newdirectory/debian"); |
+ # Exclude existing symlinks from extraction of debian.tar.gz as we |
+ # don't want to overwrite something outside of $newdirectory due to a |
+ # symlink |
+ my @exclude_symlinks; |
+ my $wanted = sub { |
+ return if not -l $_; |
+ my $fn = File::Spec->abs2rel($_, $newdirectory); |
+ push @exclude_symlinks, '--exclude', $fn; |
+ }; |
+ find({ wanted => $wanted, no_chdir => 1 }, $newdirectory); |
+ $tar = Dpkg::Source::Archive->new(filename => "$dscdir$debianfile"); |
+ $tar->extract($newdirectory, in_place => 1, |
+ options => [ '--anchored', '--no-wildcards', |
+ @exclude_symlinks ]); |
+ |
+ # Apply patches (in a separate method as it might be overriden) |
+ $self->apply_patches($newdirectory, usage => 'unpack') |
+ unless $self->{options}{skip_patches}; |
+} |
+ |
+sub get_autopatch_name { |
+ return 'zz_debian-diff-auto'; |
+} |
+ |
+sub get_patches { |
+ my ($self, $dir, %opts) = @_; |
+ $opts{skip_auto} //= 0; |
+ my @patches; |
+ my $pd = "$dir/debian/patches"; |
+ my $auto_patch = $self->get_autopatch_name(); |
+ if (-d $pd) { |
+ opendir(my $dir_dh, $pd) or syserr(_g('cannot opendir %s'), $pd); |
+ foreach my $patch (sort readdir($dir_dh)) { |
+ # patches match same rules as run-parts |
+ next unless $patch =~ /^[\w-]+$/ and -f "$pd/$patch"; |
+ next if $opts{skip_auto} and $patch eq $auto_patch; |
+ push @patches, $patch; |
+ } |
+ closedir($dir_dh); |
+ } |
+ return @patches; |
+} |
+ |
+sub apply_patches { |
+ my ($self, $dir, %opts) = @_; |
+ $opts{skip_auto} //= 0; |
+ my @patches = $self->get_patches($dir, %opts); |
+ return unless scalar(@patches); |
+ my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied'); |
+ open(my $applied_fh, '>', $applied) |
+ or syserr(_g('cannot write %s'), $applied); |
+ print { $applied_fh } "# During $opts{usage}\n"; |
+ my $timestamp = fs_time($applied); |
+ foreach my $patch ($self->get_patches($dir, %opts)) { |
+ my $path = File::Spec->catfile($dir, 'debian', 'patches', $patch); |
+ info(_g('applying %s'), $patch) unless $opts{skip_auto}; |
+ my $patch_obj = Dpkg::Source::Patch->new(filename => $path); |
+ $patch_obj->apply($dir, force_timestamp => 1, |
+ timestamp => $timestamp, |
+ add_options => [ '-E' ]); |
+ print { $applied_fh } "$patch\n"; |
+ } |
+ close($applied_fh); |
+} |
+ |
+sub unapply_patches { |
+ my ($self, $dir, %opts) = @_; |
+ my @patches = reverse($self->get_patches($dir, %opts)); |
+ return unless scalar(@patches); |
+ my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied'); |
+ my $timestamp = fs_time($applied); |
+ foreach my $patch (@patches) { |
+ my $path = File::Spec->catfile($dir, 'debian', 'patches', $patch); |
+ info(_g('unapplying %s'), $patch) unless $opts{quiet}; |
+ my $patch_obj = Dpkg::Source::Patch->new(filename => $path); |
+ $patch_obj->apply($dir, force_timestamp => 1, verbose => 0, |
+ timestamp => $timestamp, |
+ add_options => [ '-E', '-R' ]); |
+ } |
+ unlink($applied); |
+} |
+ |
+sub upstream_tarball_template { |
+ my ($self) = @_; |
+ my $ext = '{' . join(',', |
+ sort map { |
+ compression_get_property($_, 'file_ext') |
+ } compression_get_list()) . '}'; |
+ return '../' . $self->get_basename() . ".orig.tar.$ext"; |
+} |
+ |
+sub can_build { |
+ my ($self, $dir) = @_; |
+ return 1 if $self->find_original_tarballs(include_supplementary => 0); |
+ return 1 if $self->{options}{create_empty_orig} and |
+ $self->find_original_tarballs(include_main => 0); |
+ return (0, sprintf(_g('no upstream tarball found at %s'), |
+ $self->upstream_tarball_template())); |
+} |
+ |
+sub before_build { |
+ my ($self, $dir) = @_; |
+ $self->check_patches_applied($dir) if $self->{options}{preparation}; |
+} |
+ |
+sub after_build { |
+ my ($self, $dir) = @_; |
+ my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied'); |
+ my $reason = ''; |
+ if (-e $applied) { |
+ open(my $applied_fh, '<', $applied) |
+ or syserr(_g('cannot read %s'), $applied); |
+ $reason = <$applied_fh>; |
+ close($applied_fh); |
+ } |
+ my $opt_unapply = $self->{options}{unapply_patches}; |
+ if (($opt_unapply eq 'auto' and $reason =~ /^# During preparation/) or |
+ $opt_unapply eq 'yes') { |
+ $self->unapply_patches($dir); |
+ } |
+} |
+ |
+sub prepare_build { |
+ my ($self, $dir) = @_; |
+ $self->{diff_options} = { |
+ diff_ignore_regex => $self->{options}{diff_ignore_regex} . |
+ '|(^|/)debian/patches/.dpkg-source-applied$', |
+ include_removal => $self->{options}{include_removal}, |
+ include_timestamp => $self->{options}{include_timestamp}, |
+ use_dev_null => 1, |
+ }; |
+ push @{$self->{options}{tar_ignore}}, 'debian/patches/.dpkg-source-applied'; |
+ $self->check_patches_applied($dir) if $self->{options}{preparation}; |
+ if ($self->{options}{create_empty_orig} and |
+ not $self->find_original_tarballs(include_supplementary => 0)) |
+ { |
+ # No main orig.tar, create a dummy one |
+ my $filename = $self->get_basename() . '.orig.tar.' . |
+ $self->{options}{comp_ext}; |
+ my $tar = Dpkg::Source::Archive->new(filename => $filename); |
+ $tar->create(); |
+ $tar->finish(); |
+ } |
+} |
+ |
+sub check_patches_applied { |
+ my ($self, $dir) = @_; |
+ my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied'); |
+ unless (-e $applied) { |
+ info(_g('patches are not applied, applying them now')); |
+ $self->apply_patches($dir, usage => 'preparation'); |
+ } |
+} |
+ |
+sub generate_patch { |
+ my ($self, $dir, %opts) = @_; |
+ my ($dirname, $updir) = fileparse($dir); |
+ my $basedirname = $self->get_basename(); |
+ $basedirname =~ s/_/-/; |
+ |
+ # Identify original tarballs |
+ my ($tarfile, %origtar); |
+ my $comp_ext_regex = compression_get_file_extension_regex(); |
+ my @origtarballs; |
+ foreach (sort $self->find_original_tarballs()) { |
+ if (/\.orig\.tar\.$comp_ext_regex$/) { |
+ if (defined($tarfile)) { |
+ error(_g('several orig.tar files found (%s and %s) but only ' . |
+ 'one is allowed'), $tarfile, $_); |
+ } |
+ $tarfile = $_; |
+ push @origtarballs, $_; |
+ $self->add_file($_); |
+ } elsif (/\.orig-([[:alnum:]-]+)\.tar\.$comp_ext_regex$/) { |
+ $origtar{$1} = $_; |
+ push @origtarballs, $_; |
+ $self->add_file($_); |
+ } |
+ } |
+ |
+ error(_g('no upstream tarball found at %s'), |
+ $self->upstream_tarball_template()) unless $tarfile; |
+ |
+ if ($opts{usage} eq 'build') { |
+ info(_g('building %s using existing %s'), |
+ $self->{fields}{'Source'}, "@origtarballs"); |
+ } |
+ |
+ # Unpack a second copy for comparison |
+ my $tmp = tempdir("$dirname.orig.XXXXXX", DIR => $updir); |
+ push_exit_handler(sub { erasedir($tmp) }); |
+ |
+ # Extract main tarball |
+ my $tar = Dpkg::Source::Archive->new(filename => $tarfile); |
+ $tar->extract($tmp); |
+ |
+ # Extract additional orig tarballs |
+ foreach my $subdir (keys %origtar) { |
+ my $file = $origtar{$subdir}; |
+ $tar = Dpkg::Source::Archive->new(filename => $file); |
+ $tar->extract("$tmp/$subdir"); |
+ } |
+ |
+ # Copy over the debian directory |
+ erasedir("$tmp/debian"); |
+ system('cp', '-a', '--', "$dir/debian", "$tmp/"); |
+ subprocerr(_g('copy of the debian directory')) if $?; |
+ |
+ # Apply all patches except the last automatic one |
+ $opts{skip_auto} //= 0; |
+ $self->apply_patches($tmp, skip_auto => $opts{skip_auto}, usage => 'build'); |
+ |
+ # Create a patch |
+ my ($difffh, $tmpdiff) = tempfile($self->get_basename(1) . '.diff.XXXXXX', |
+ DIR => File::Spec->tmpdir(), UNLINK => 0); |
+ push_exit_handler(sub { unlink($tmpdiff) }); |
+ my $diff = Dpkg::Source::Patch->new(filename => $tmpdiff, |
+ compression => 'none'); |
+ $diff->create(); |
+ if ($opts{header_from} and -e $opts{header_from}) { |
+ my $header_from = Dpkg::Source::Patch->new( |
+ filename => $opts{header_from}); |
+ my $analysis = $header_from->analyze($dir, verbose => 0); |
+ $diff->set_header($analysis->{patchheader}); |
+ } else { |
+ $diff->set_header($self->get_patch_header($dir)); |
+ } |
+ $diff->add_diff_directory($tmp, $dir, basedirname => $basedirname, |
+ %{$self->{diff_options}}, |
+ handle_binary_func => $opts{handle_binary}, |
+ order_from => $opts{order_from}); |
+ error(_g('unrepresentable changes to source')) if not $diff->finish(); |
+ |
+ if (-s $tmpdiff) { |
+ info(_g('local changes detected, the modified files are:')); |
+ my $analysis = $diff->analyze($dir, verbose => 0); |
+ foreach my $fn (sort keys %{$analysis->{filepatched}}) { |
+ print " $fn\n"; |
+ } |
+ } |
+ |
+ # Remove the temporary directory |
+ erasedir($tmp); |
+ pop_exit_handler(); |
+ pop_exit_handler(); |
+ |
+ return $tmpdiff; |
+} |
+ |
+sub do_build { |
+ my ($self, $dir) = @_; |
+ my @argv = @{$self->{options}{ARGV}}; |
+ if (scalar(@argv)) { |
+ usageerr(_g("-b takes only one parameter with format `%s'"), |
+ $self->{fields}{'Format'}); |
+ } |
+ $self->prepare_build($dir); |
+ |
+ my $include_binaries = $self->{options}{include_binaries}; |
+ my @tar_ignore = map { "--exclude=$_" } @{$self->{options}{tar_ignore}}; |
+ |
+ my $sourcepackage = $self->{fields}{'Source'}; |
+ my $basenamerev = $self->get_basename(1); |
+ |
+ # Check if the debian directory contains unwanted binary files |
+ my $binaryfiles = Dpkg::Source::Package::V2::BinaryFiles->new($dir); |
+ my $unwanted_binaries = 0; |
+ my $check_binary = sub { |
+ if (-f $_ and is_binary($_)) { |
+ my $fn = File::Spec->abs2rel($_, $dir); |
+ $binaryfiles->new_binary_found($fn); |
+ unless ($include_binaries or $binaryfiles->binary_is_allowed($fn)) { |
+ errormsg(_g('unwanted binary file: %s'), $fn); |
+ $unwanted_binaries++; |
+ } |
+ } |
+ }; |
+ my $tar_ignore_glob = '{' . join(',', |
+ map { |
+ my $copy = $_; |
+ $copy =~ s/,/\\,/g; |
+ $copy; |
+ } @{$self->{options}{tar_ignore}}) . '}'; |
+ my $filter_ignore = sub { |
+ # Filter out files that are not going to be included in the debian |
+ # tarball due to ignores. |
+ my %exclude; |
+ my $reldir = File::Spec->abs2rel($File::Find::dir, $dir); |
+ my $cwd = getcwd(); |
+ # Apply the pattern both from the top dir and from the inspected dir |
+ chdir($dir) or syserr(_g("unable to chdir to `%s'"), $dir); |
+ $exclude{$_} = 1 foreach glob($tar_ignore_glob); |
+ chdir($cwd) or syserr(_g("unable to chdir to `%s'"), $cwd); |
+ chdir($File::Find::dir) |
+ or syserr(_g("unable to chdir to `%s'"), $File::Find::dir); |
+ $exclude{$_} = 1 foreach glob($tar_ignore_glob); |
+ chdir($cwd) or syserr(_g("unable to chdir to `%s'"), $cwd); |
+ my @result; |
+ foreach my $fn (@_) { |
+ unless (exists $exclude{$fn} or exists $exclude{"$reldir/$fn"}) { |
+ push @result, $fn; |
+ } |
+ } |
+ return @result; |
+ }; |
+ find({ wanted => $check_binary, preprocess => $filter_ignore, |
+ no_chdir => 1 }, File::Spec->catdir($dir, 'debian')); |
+ error(P_('detected %d unwanted binary file (add it in ' . |
+ 'debian/source/include-binaries to allow its inclusion).', |
+ 'detected %d unwanted binary files (add them in ' . |
+ 'debian/source/include-binaries to allow their inclusion).', |
+ $unwanted_binaries), $unwanted_binaries) |
+ if $unwanted_binaries; |
+ |
+ # Handle modified binary files detected by the auto-patch generation |
+ my $handle_binary = sub { |
+ my ($self, $old, $new) = @_; |
+ my $relfn = File::Spec->abs2rel($new, $dir); |
+ $binaryfiles->new_binary_found($relfn); |
+ unless ($include_binaries or $binaryfiles->binary_is_allowed($relfn)) { |
+ errormsg(_g('cannot represent change to %s: %s'), $relfn, |
+ _g('binary file contents changed')); |
+ errormsg(_g('add %s in debian/source/include-binaries if you want ' . |
+ 'to store the modified binary in the debian tarball'), |
+ $relfn); |
+ $self->register_error(); |
+ } |
+ }; |
+ |
+ # Create a patch |
+ my $autopatch = File::Spec->catfile($dir, 'debian', 'patches', |
+ $self->get_autopatch_name()); |
+ my $tmpdiff = $self->generate_patch($dir, order_from => $autopatch, |
+ header_from => $autopatch, |
+ handle_binary => $handle_binary, |
+ skip_auto => $self->{options}{auto_commit}, |
+ usage => 'build'); |
+ unless (-z $tmpdiff or $self->{options}{auto_commit}) { |
+ info(_g('you can integrate the local changes with %s'), |
+ 'dpkg-source --commit'); |
+ error(_g('aborting due to unexpected upstream changes, see %s'), |
+ $tmpdiff); |
+ } |
+ push_exit_handler(sub { unlink($tmpdiff) }); |
+ $binaryfiles->update_debian_source_include_binaries() if $include_binaries; |
+ |
+ # Install the diff as the new autopatch |
+ if ($self->{options}{auto_commit}) { |
+ mkpath(File::Spec->catdir($dir, 'debian', 'patches')); |
+ $autopatch = $self->register_patch($dir, $tmpdiff, |
+ $self->get_autopatch_name()); |
+ info(_g('local changes have been recorded in a new patch: %s'), |
+ $autopatch) if -e $autopatch; |
+ rmdir(File::Spec->catdir($dir, 'debian', 'patches')); # No check on purpose |
+ } |
+ unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff); |
+ pop_exit_handler(); |
+ |
+ # Create the debian.tar |
+ my $debianfile = "$basenamerev.debian.tar." . $self->{options}{comp_ext}; |
+ info(_g('building %s in %s'), $sourcepackage, $debianfile); |
+ my $tar = Dpkg::Source::Archive->new(filename => $debianfile); |
+ $tar->create(options => \@tar_ignore, chdir => $dir); |
+ $tar->add_directory('debian'); |
+ foreach my $binary ($binaryfiles->get_seen_binaries()) { |
+ $tar->add_file($binary) unless $binary =~ m{^debian/}; |
+ } |
+ $tar->finish(); |
+ |
+ $self->add_file($debianfile); |
+} |
+ |
+sub get_patch_header { |
+ my ($self, $dir) = @_; |
+ my $ph = File::Spec->catfile($dir, 'debian', 'source', 'local-patch-header'); |
+ unless (-f $ph) { |
+ $ph = File::Spec->catfile($dir, 'debian', 'source', 'patch-header'); |
+ } |
+ my $text; |
+ if (-f $ph) { |
+ open(my $ph_fh, '<', $ph) or syserr(_g('cannot read %s'), $ph); |
+ $text = file_slurp($ph_fh); |
+ close($ph_fh); |
+ return $text; |
+ } |
+ my $ch_info = changelog_parse(offset => 0, count => 1, |
+ file => File::Spec->catfile($dir, 'debian', 'changelog')); |
+ return '' if not defined $ch_info; |
+ my $header = Dpkg::Control->new(type => CTRL_UNKNOWN); |
+ $header->{'Description'} = "<short summary of the patch>\n"; |
+ $header->{'Description'} .= |
+"TODO: Put a short summary on the line above and replace this paragraph |
+with a longer explanation of this change. Complete the meta-information |
+with other relevant fields (see below for details). To make it easier, the |
+information below has been extracted from the changelog. Adjust it or drop |
+it.\n"; |
+ $header->{'Description'} .= $ch_info->{'Changes'} . "\n"; |
+ $header->{'Author'} = $ch_info->{'Maintainer'}; |
+ $text = "$header"; |
+ run_vendor_hook('extend-patch-header', \$text, $ch_info); |
+ $text .= "\n--- |
+The information above should follow the Patch Tagging Guidelines, please |
+checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here |
+are templates for supplementary fields that you might want to add: |
+ |
+Origin: <vendor|upstream|other>, <url of original patch> |
+Bug: <url in upstream bugtracker> |
+Bug-Debian: http://bugs.debian.org/<bugnumber> |
+Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber> |
+Forwarded: <no|not-needed|url proving that it has been forwarded> |
+Reviewed-By: <name and email of someone who approved the patch> |
+Last-Update: <YYYY-MM-DD>\n\n"; |
+ return $text; |
+} |
+ |
+sub register_patch { |
+ my ($self, $dir, $patch_file, $patch_name) = @_; |
+ my $patch = File::Spec->catfile($dir, 'debian', 'patches', $patch_name); |
+ if (-s $patch_file) { |
+ copy($patch_file, $patch) |
+ or syserr(_g('failed to copy %s to %s'), $patch_file, $patch); |
+ chmod(0666 & ~ umask(), $patch) |
+ or syserr(_g("unable to change permission of `%s'"), $patch); |
+ my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied'); |
+ open(my $applied_fh, '>>', $applied) |
+ or syserr(_g('cannot write %s'), $applied); |
+ print { $applied_fh } "$patch\n"; |
+ close($applied_fh) or syserr(_g('cannot close %s'), $applied); |
+ } elsif (-e $patch) { |
+ unlink($patch) or syserr(_g('cannot remove %s'), $patch); |
+ } |
+ return $patch; |
+} |
+ |
+sub _is_bad_patch_name { |
+ my ($dir, $patch_name) = @_; |
+ |
+ return 1 if not defined($patch_name); |
+ return 1 if not length($patch_name); |
+ |
+ my $patch = File::Spec->catfile($dir, 'debian', 'patches', $patch_name); |
+ if (-e $patch) { |
+ warning(_g('cannot register changes in %s, this patch already exists'), |
+ $patch); |
+ return 1; |
+ } |
+ return 0; |
+} |
+ |
+sub do_commit { |
+ my ($self, $dir) = @_; |
+ my ($patch_name, $tmpdiff) = @{$self->{options}{ARGV}}; |
+ |
+ $self->prepare_build($dir); |
+ |
+ # Try to fix up a broken relative filename for the patch |
+ if ($tmpdiff and not -e $tmpdiff) { |
+ $tmpdiff = File::Spec->catfile($dir, $tmpdiff) |
+ unless File::Spec->file_name_is_absolute($tmpdiff); |
+ error(_g("patch file '%s' doesn't exist"), $tmpdiff) if not -e $tmpdiff; |
+ } |
+ |
+ my $binaryfiles = Dpkg::Source::Package::V2::BinaryFiles->new($dir); |
+ my $handle_binary = sub { |
+ my ($self, $old, $new) = @_; |
+ my $fn = File::Spec->abs2rel($new, $dir); |
+ $binaryfiles->new_binary_found($fn); |
+ }; |
+ |
+ unless ($tmpdiff) { |
+ $tmpdiff = $self->generate_patch($dir, handle_binary => $handle_binary, |
+ usage => 'commit'); |
+ $binaryfiles->update_debian_source_include_binaries(); |
+ } |
+ push_exit_handler(sub { unlink($tmpdiff) }); |
+ unless (-s $tmpdiff) { |
+ unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff); |
+ info(_g('there are no local changes to record')); |
+ return; |
+ } |
+ while (_is_bad_patch_name($dir, $patch_name)) { |
+ # Ask the patch name interactively |
+ print _g('Enter the desired patch name: '); |
+ chomp($patch_name = <STDIN>); |
+ $patch_name =~ s/\s+/-/g; |
+ $patch_name =~ s/\///g; |
+ } |
+ mkpath(File::Spec->catdir($dir, 'debian', 'patches')); |
+ my $patch = $self->register_patch($dir, $tmpdiff, $patch_name); |
+ system('sensible-editor', $patch); |
+ subprocerr('sensible-editor') if $?; |
+ unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff); |
+ pop_exit_handler(); |
+ info(_g('local changes have been recorded in a new patch: %s'), $patch); |
+} |
+ |
+package Dpkg::Source::Package::V2::BinaryFiles; |
+ |
+use Dpkg::ErrorHandling; |
+use Dpkg::Gettext; |
+ |
+use File::Path; |
+ |
+sub new { |
+ my ($this, $dir) = @_; |
+ my $class = ref($this) || $this; |
+ |
+ my $self = { |
+ dir => $dir, |
+ allowed_binaries => {}, |
+ seen_binaries => {}, |
+ include_binaries_path => |
+ File::Spec->catfile($dir, 'debian', 'source', 'include-binaries'), |
+ }; |
+ bless $self, $class; |
+ $self->load_allowed_binaries(); |
+ return $self; |
+} |
+ |
+sub new_binary_found { |
+ my ($self, $path) = @_; |
+ |
+ $self->{seen_binaries}{$path} = 1; |
+} |
+ |
+sub load_allowed_binaries { |
+ my ($self) = @_; |
+ my $incbin_file = $self->{include_binaries_path}; |
+ if (-f $incbin_file) { |
+ open(my $incbin_fh, '<', $incbin_file) |
+ or syserr(_g('cannot read %s'), $incbin_file); |
+ while (defined($_ = <$incbin_fh>)) { |
+ chomp; s/^\s*//; s/\s*$//; |
+ next if /^#/ or /^$/; |
+ $self->{allowed_binaries}{$_} = 1; |
+ } |
+ close($incbin_fh); |
+ } |
+} |
+ |
+sub binary_is_allowed { |
+ my ($self, $path) = @_; |
+ return 1 if exists $self->{allowed_binaries}{$path}; |
+ return 0; |
+} |
+ |
+sub update_debian_source_include_binaries { |
+ my ($self) = @_; |
+ |
+ my @unknown_binaries = $self->get_unknown_binaries(); |
+ return unless scalar(@unknown_binaries); |
+ |
+ my $incbin_file = $self->{include_binaries_path}; |
+ mkpath(File::Spec->catdir($self->{dir}, 'debian', 'source')); |
+ open(my $incbin_fh, '>>', $incbin_file) |
+ or syserr(_g('cannot write %s'), $incbin_file); |
+ foreach my $binary (@unknown_binaries) { |
+ print { $incbin_fh } "$binary\n"; |
+ info(_g('adding %s to %s'), $binary, 'debian/source/include-binaries'); |
+ $self->{allowed_binaries}{$binary} = 1; |
+ } |
+ close($incbin_fh); |
+} |
+ |
+sub get_unknown_binaries { |
+ my ($self) = @_; |
+ return grep { not $self->binary_is_allowed($_) } $self->get_seen_binaries(); |
+} |
+ |
+sub get_seen_binaries { |
+ my ($self) = @_; |
+ my @seen = sort keys %{$self->{seen_binaries}}; |
+ return @seen; |
+} |
+ |
+1; |