#!/usr/bin/perl # # origtargz: fetch the orig tarball of a Debian package from various sources, # and unpack it # Copyright (C) 2012-2019 Christoph Berg # # 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 . =head1 NAME origtargz - fetch the orig tarball of a Debian package from various sources, and unpack it =head1 SYNOPSIS =over =item B [I] [B<--unpack>[=B|B|B]] =item B B<--help> =back =head1 DESCRIPTION B downloads the orig tarball of a Debian package, and also unpacks it into the current directory, if it just contains a F directory. The main use for B 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. 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 is tried. =item * B is tried. =item * B is tried when B reports a matching version. =item * Finally, B is tried. =back When asked to unpack the orig tarball, B will remove all files and directories from the current directory, except the debian directory, and the VCS repository directories. I 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 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 directory and the VCS files mentioned above. =head1 NOTES Despite B being called "targz", it will work with any compression scheme used for the tarball. A similar tool to unpack orig tarballs is B(1). B creates a new working directory, unpacks the tarball, and applies the Debian F<.diff.gz> changes. In contrast, B 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(1) and B(1) for examples. B is still useful for downloading the current tarball. =head1 OPTIONS =over =item B<-p>, B<--path> I Add I 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|B|B] Unpack the downloaded orig tarball to the current directory, replacing everything except the debian directory. Existing files are removed, except for F 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 Do not unpack the orig tarball. =item B (default when B<--unpack> is not used) If the current directory contains only a F directory (and possibly some dotfiles), unpack the orig tarball. This is the default behavior. =item B (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, 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 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 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(1), B(1), B(1), B(1), B(1) =head1 AUTHOR B and this manpage have been written by Christoph Berg >. =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 = ; 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 = ; } 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;