summaryrefslogtreecommitdiffstats
path: root/scripts/origtargz.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/origtargz.pl')
-rwxr-xr-xscripts/origtargz.pl438
1 files changed, 438 insertions, 0 deletions
diff --git a/scripts/origtargz.pl b/scripts/origtargz.pl
new file mode 100755
index 0000000..c3152bb
--- /dev/null
+++ b/scripts/origtargz.pl
@@ -0,0 +1,438 @@
+#!/usr/bin/perl
+#
+# origtargz: fetch the orig tarball of a Debian package from various sources,
+# and unpack it
+# Copyright (C) 2012-2019 Christoph Berg <myon@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/>.
+
+=head1 NAME
+
+origtargz - fetch the orig tarball of a Debian package from various sources, and unpack it
+
+=head1 SYNOPSIS
+
+=over
+
+=item B<origtargz> [I<OPTIONS>] [B<--unpack>[=B<no>|B<once>|B<yes>]]
+
+=item B<origtargz> B<--help>
+
+=back
+
+=head1 DESCRIPTION
+
+B<origtargz> downloads the orig tarball of a Debian package, and also unpacks
+it into the current directory, if it just contains a F<debian> directory. The
+main use for B<origtargz> is with debian-dir-only repository checkouts, but it
+is useful as a general tarball download wrapper. The version number for the
+tarball to be downloaded is determined from F<debian/changelog>. It should be
+invoked from the top level directory of an unpacked Debian source package.
+
+Various download locations are tried:
+
+=over 4
+
+=item *
+
+First, an existing file is looked for.
+
+=item *
+
+Directories given with B<--path> are searched.
+
+=item *
+
+B<pristine-tar> is tried.
+
+=item *
+
+B<pristine-lfs> is tried.
+
+=item *
+
+B<apt-get source> is tried when B<apt-cache showsrc> reports a matching version.
+
+=item *
+
+Finally, B<uscan --download --download-current-version> is tried.
+
+=back
+
+When asked to unpack the orig tarball, B<origtargz> will remove all files and
+directories from the current directory, except the debian directory, and the
+VCS repository directories. I<Note that this will drop all non-committed changes>
+for the patch system in use (e.g. source format "3.0 (quilt)"), and will even
+remove all patches from the package when no patch system is in use (the
+original "1.0" source format). Some VCS control files outside F<debian/>
+preserved (F<.bzr-builddeb>, F<.bzr-ignore>, F<.gitignore>, F<.hgignore>), if
+stored in VCS.
+
+The default behavior is to unpack the orig tarball if the current directory
+is empty except for a F<debian> directory and the VCS files mentioned above.
+
+=head1 NOTES
+
+Despite B<origtargz> being called "targz", it will work with any compression
+scheme used for the tarball.
+
+A similar tool to unpack orig tarballs is B<uupdate>(1). B<uupdate> creates a
+new working directory, unpacks the tarball, and applies the Debian F<.diff.gz>
+changes. In contrast, B<origtargz> uses the current directory, keeping VCS
+metadata.
+
+For Debian package repositories that keep the full upstream source, other tools
+should be used to upgrade the repository from the new tarball. See
+B<gbp-import-orig>(1) and B<svn-upgrade>(1) for examples. B<origtargz> is still
+useful for downloading the current tarball.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-p>, B<--path> I<directory>
+
+Add I<directory> to the list of locations to search for an existing tarball.
+When found, a hardlink is created if possible, otherwise a symlink.
+
+=item B<-u>, B<--unpack>[=B<no>|B<once>|B<yes>]
+
+Unpack the downloaded orig tarball to the current directory, replacing
+everything except the debian directory. Existing files are removed, except for
+F<debian/> and VCS files. Preserved are: F<.bzr>, F<.bzrignore>,
+F<.bzr-builddeb>, F<.git>, F<.gitignore>, F<.hg>, F<.hgignore>, F<_darcs> and
+F<.svn>.
+
+=over
+
+=item B<no>
+
+Do not unpack the orig tarball.
+
+=item B<once> (default when B<--unpack> is not used)
+
+If the current directory contains only a F<debian> directory (and possibly some
+dotfiles), unpack the orig tarball. This is the default behavior.
+
+=item B<yes> (default for B<--unpack> without argument)
+
+Always unpack the orig tarball.
+
+=back
+
+=item B<-d>, B<--download-only>
+
+Alias for B<--unpack=no>.
+
+=item B<-t>, B<--tar-only>
+
+When using B<apt-get source>, pass B<--tar-only> to it. The default is to
+download the full source package including F<.dsc> and F<.diff.gz> or
+F<.debian.tar.gz> components so B<debdiff> can be used to diff the last upload
+to the next one. With B<--tar-only>, only download the F<.orig.tar.*> file.
+
+=item B<--clean>
+
+Remove existing files as with B<--unpack>. Note that like B<--unpack>, this
+will remove upstream files even if they are stored in VCS.
+
+=back
+
+=cut
+
+#=head1 CONFIGURATION VARIABLES
+#
+#The two configuration files F</etc/devscripts.conf> and
+#F<~/.devscripts> are sourced by a shell in that order to set
+#configuration variables. Command line options can be used to override
+#configuration file settings. Environment variable settings are ignored
+#for this purpose. The currently recognised variables are:
+
+=head1 SEE ALSO
+
+B<debcheckout>(1), B<gbp-import-orig>(1), B<pristine-tar>(1), B<svn-upgrade>(1), B<uupdate>(1)
+
+=head1 AUTHOR
+
+B<origtargz> and this manpage have been written by Christoph Berg
+<I<myon@debian.org>>.
+
+=cut
+
+# option parsing
+
+use strict;
+use warnings;
+use File::Temp qw/tempdir/;
+use Getopt::Long qw(:config bundling permute no_getopt_compat);
+use Pod::Usage;
+
+my @dirs = ();
+my $tar_only = 0;
+my $unpack = 'once'; # default when --unpack is not used
+my $clean = 0;
+
+GetOptions(
+ "path|p=s" => \@dirs,
+ "download-only|d" => sub { $unpack = 'no' },
+ "help|h" => sub { pod2usage({ -exitval => 0, -verbose => 1 }); },
+ "tar-only|t" => \$tar_only,
+ "unpack|u:s" => \$unpack,
+ "clean" => \$clean,
+) or pod2usage({ -exitval => 3 });
+
+$unpack = 'yes'
+ if (defined $unpack and $unpack eq '')
+ ; # default for --unpack without argument
+pod2usage({ -exitval => 3 }) if (@ARGV > 0 or $unpack !~ /^(no|once|yes)$/);
+
+# get package name and version number
+
+my ($package, $version, $origversion, $fileversion);
+
+chdir ".." if (!-f "debian/changelog" and -f "../debian/changelog");
+open F, "debian/changelog" or die "debian/changelog: $!\n";
+my $line = <F>;
+close F;
+unless ($line =~ /^(\S+) \((\S+)\)/) {
+ die "could not parse debian/changelog:1: $line";
+}
+($package, $version) = ($1, $2);
+unless ($version =~ /-/) {
+ print
+"Package with native version number $version, skipping orig.tar.* download\n";
+ exit 0;
+}
+$origversion = $version;
+$origversion =~ s/(.*)-.*/$1/; # strip everything from the last dash
+$fileversion = $origversion;
+$fileversion =~ s/^\d+://; # strip epoch
+
+# functions
+
+sub download_origtar () {
+ # look for an existing file
+
+ if (my @f = glob "../${package}_$fileversion.orig*.tar.*") {
+ print "Using existing $f[0]\n";
+ return @f;
+ }
+
+ # try other directories
+
+ foreach my $dir (@dirs) {
+ $dir =~ s!/$!!;
+
+ if (my @f = glob "$dir/${package}_$fileversion.orig*.tar.*") {
+ my @res;
+ for my $f (@f) {
+ print "Using $f\n";
+ my $basename = $f;
+ $basename =~ s!.*/!!;
+ link $f, "../$basename"
+ or symlink $f, "../$basename"
+ or die "symlink: $!";
+ push @res, "../$basename";
+ }
+ return @res;
+ }
+ }
+
+ # try pristine-tar
+
+ my @files
+ = grep { /^\Q${package}_$fileversion.orig\E(?:-[\w\-]+)?\.tar\./ }
+ map { chomp; $_; } # remove newlines
+ `pristine-tar list 2>&1`;
+ if (@files) {
+ system "pristine-tar checkout ../$_" for @files;
+ }
+
+ if (my @f = glob "../${package}_$fileversion.orig*.tar.*") {
+ return @f;
+ }
+
+ # try pristine-lfs
+
+ @files = grep { /^\Q${package}_$fileversion.orig\E(?:-[\w\-]+)?\.tar\./ }
+ map { chomp; $_; } # remove newlines
+ `pristine-lfs list 2>&1`;
+ if (@files) {
+ system "pristine-lfs checkout -o .. $_" for @files;
+ }
+
+ if (my @f = glob "../${package}_$fileversion.orig*.tar.*") {
+ return @f;
+ }
+
+ # try apt-get source
+
+ open S, "apt-cache showsrc '$package' |";
+ my @showsrc;
+ {
+ local $/ = ""; # slurp paragraphs
+ @showsrc = <S>;
+ }
+ close S;
+
+ my $bestsrcversion;
+ foreach my $src (@showsrc) {
+ $src =~ /^Package: (.*)/m or next;
+ next if ($1 ne $package);
+ ; # should never trigger, but who knows
+ $src =~ /^Version: (.*)/m or next;
+ my $srcversion = $1;
+ my $srcorigversion = $srcversion;
+ $srcorigversion =~ s/(.*)-.*/$1/; # strip everything from the last dash
+
+ if ($srcorigversion eq $origversion)
+ { # loop through all matching versions
+ $bestsrcversion = $srcversion;
+ last if ($srcversion eq $version); # break if exact match
+ }
+ }
+
+ if ($bestsrcversion) {
+ print "Trying apt-get source $package=$bestsrcversion ...\n";
+ my $t = $tar_only ? '--tar-only' : '';
+ system
+"cd .. && apt-get source --only-source --download-only $t '$package=$bestsrcversion'";
+ }
+
+ if (my @f = glob "../${package}_$fileversion.orig*.tar.*") {
+ return @f;
+ }
+
+ # try uscan
+
+ if (-f "debian/watch") {
+ print "Trying uscan --download --download-current-version ...\n";
+ system "uscan --download --download-current-version --rename\n";
+ }
+
+ if (my @f = glob "../${package}_$fileversion.orig*.tar.*") {
+ return @f;
+ }
+
+ print
+ "Could not find any location for ${package}_$fileversion.orig.tar.*\n";
+ return;
+}
+
+sub clean_checkout () {
+ # delete all files except debian/, our VCS checkout, and some files
+ # often in VCS outside debian/ even in debian-dir-only repositories
+ opendir DIR, '.' or die "opendir: $!";
+ my @rm;
+ while (my $file = readdir DIR) {
+ next if ($file eq '.' or $file eq '..');
+ next if ($file eq 'debian');
+ next if ($file =~ /^(\.bzr|\.git|\.hg|\.svn|CVS|_darcs)$/);
+ if ($file eq '.gitignore' and -d '.git')
+ { # preserve .gitignore if it's from git
+ next if `git ls-files .gitignore` eq ".gitignore\n";
+ }
+ if ( ($file =~ /^\.bzr(ignore|-builddeb)$/ and -d '.bzr')
+ or ($file eq '.hgignore' and -d '.hg')) {
+ print
+"Notice: not deleting $file (likely to come from VCS checkout)\n";
+ next;
+ }
+ push @rm, $file;
+ }
+ close DIR;
+ system('rm', '-rf', '--', @rm);
+}
+
+sub unpack_tarball (@) {
+ my @origtar = @_;
+
+ for my $origtar (@origtar) {
+ if ($origtar =~ m/\.asc$/) {
+ next;
+ }
+
+ my $tmpdir = File::Temp->newdir(DIR => ".", CLEANUP => 1);
+ print "Unpacking $origtar\n";
+ my $cmp = ($origtar =~ /orig(?:-([\w\-]+))?\.tar/)[0] || '';
+ if ($cmp) {
+ mkdir $cmp;
+ $cmp = "/$cmp";
+ mkdir "$tmpdir$cmp";
+ }
+ #print STDERR Dumper(\@origtar,$cmp);use Data::Dumper;exit;
+
+ # unpack
+ system('tar', "--directory=$tmpdir$cmp", '-xf', "$origtar");
+ if ($? >> 8) {
+ print STDERR "unpacking $origtar failed\n";
+ return 0;
+ }
+
+ # figure out which subdirectory was created by unpacking
+ my $directory;
+ my @files = glob "$tmpdir$cmp/*";
+ if (@files == 1 and -d $files[0])
+ { # exactly one directory, move its contents over
+ $directory = $files[0];
+ } else
+ { # several files were created, move these to the target directory
+ $directory = $tmpdir . $cmp;
+ }
+
+ # move all files over, except the debian directory
+ opendir DIR, $directory or die "opendir $directory: $!";
+ foreach my $file (readdir DIR) {
+ if ($file eq 'debian') {
+ system('rm', '-rf', '--', "$directory/$file");
+ next;
+ } elsif ($file eq '.' or $file eq '..') {
+ next;
+ }
+ my $dest = './' . ($cmp ? "$cmp/" : '') . $file;
+ unless (rename "$directory/$file", $dest) {
+ print `ls -l $directory/$file`;
+ print STDERR "rename $directory/$file $dest: $!\n";
+ return 0;
+ }
+ }
+ closedir DIR;
+ rmdir $directory;
+ }
+
+ return 1;
+}
+
+# main
+
+if ($clean) {
+ clean_checkout;
+ exit 0;
+}
+
+my @origtar = download_origtar;
+exit 1 unless (@origtar);
+
+if ($unpack eq 'once') {
+ my @files = glob '*'; # ignores dotfiles
+ if (@files == 1)
+ { # this is debian/, we have already opened debian/changelog
+ unpack_tarball(@origtar) or exit 1;
+ }
+} elsif ($unpack eq 'yes') {
+ clean_checkout;
+ unpack_tarball(@origtar) or exit 1;
+}
+
+exit 0;