pristine-xz
authorJoey Hess <joey@kitenet.net>
Mon, 9 Jan 2012 16:20:59 +0000 (12:20 -0400)
committerJoey Hess <joey@kitenet.net>
Mon, 9 Jan 2012 16:57:39 +0000 (12:57 -0400)
works, at least minimally

Pristine/Tar/Formats.pm
debian/changelog
delta-format.txt
pristine-tar
pristine-xz [new file with mode: 0755]

index a141a3d3810f3f34a0cd6e17c81c2621a8d7c364..42afca15caa6b21a6ddbc2bcb607d52e39ad9b6e 100644 (file)
@@ -6,7 +6,7 @@ package Pristine::Tar::Formats;
 use warnings;
 use strict;
 use Exporter q{import};
-our @EXPORT=qw{is_gz is_bz2 %fconstants};
+our @EXPORT=qw{is_gz is_bz2 is_xz %fconstants};
 
 our %fconstants=(
        # magic identification
@@ -14,6 +14,12 @@ our %fconstants=(
        GZIP_ID2 => 0x8B,
        BZIP2_ID1 => 0x42,
        BZIP2_ID2 => 0x5a,
+       XZ_ID1 => 0xFD,
+       XZ_ID2 => 0x37,
+       XZ_ID3 => 0x7A,
+       XZ_ID4 => 0x58,
+       XZ_ID5 => 0x5A,
+       XZ_ID6 => 0x00,
 
        # compression methods
        # 0x00-0x07 are reserved
@@ -78,4 +84,10 @@ sub is_bz2 {
                $fconstants{BZIP2_METHOD_HUFFMAN});
 }
 
+sub is_xz {
+       magic(shift, $fconstants{XZ_ID1}, $fconstants{XZ_ID2},
+               $fconstants{XZ_ID3}, $fconstants{XZ_ID4},
+               $fconstants{XZ_ID5}, $fconstants{XZ_ID6});
+}
+
 1
index 074d2b1d9c9ad89a84ab26191e2a267342fc30b5..3133f3869de454e4f7d1257d449a50cb71f874ed 100644 (file)
@@ -1,3 +1,9 @@
+pristine-tar (1.18) UNRELEASED; urgency=low
+
+  * pristine-xz: A very simplistic xz recreator.
+
+ -- Joey Hess <joeyh@debian.org>  Mon, 09 Jan 2012 12:36:57 -0400
+
 pristine-tar (1.17) unstable; urgency=low
 
   * pristine-tar: Fail when the delta is excessively large, probably
index 406063116999fabad000dd1c2e8a7de6d5099f35..644a7c0225b55ce93da58007b4a6211c354d8707 100644 (file)
@@ -45,3 +45,10 @@ program
 
        It may also be zgz (the params will include --old-bzip2 in this
        case).
+
+For xz files, the wrapper contains:
+
+params
+       Typically, only the compression level is needed.
+program
+       Program used to compress. Almost everytime, it is xz.
index 06dae6b7d1a27a9e10cf93e789f1d6deb23c9b17..e3c2a11c7d5ca13bab51622db1104f08dc4719bd 100755 (executable)
@@ -27,8 +27,9 @@ the I<upstream> branch, thus allowing Debian packages to be built entirely
 using sources in version control, without the need to keep copies of
 upstream tarballs.
 
-pristine-tar supports compressed tarballs, calling out to pristine-gz(1)
-and pristine-bz2(1) to produce the pristine gzip and bzip2 files.
+pristine-tar supports compressed tarballs, calling out to pristine-gz(1),
+pristine-bz2(1), and pristine-xz(1) to produce the pristine gzip, bzip2,
+and xz files.
 
 =head1 COMMANDS
 
@@ -145,8 +146,8 @@ available.
 
 =head1 LIMITATIONS
 
-Only tarballs, gzipped tarballs, and bzip2ed tarballs are currently
-supported.
+Only tarballs, gzipped tarballs, bzip2ed tarballs, and xzed tarballs
+are currently supported.
 
 Currently only the git revision control system is supported by the
 "checkout" and "commit" commands. It's ok if the working copy
@@ -395,21 +396,14 @@ sub gentar {
 
        if (defined $delta->{wrapper}) {
                my $delta_wrapper=Pristine::Tar::Delta::read(Tarball => $delta->{wrapper});
-               if ($delta_wrapper->{type} eq 'gz') {
-                       doit("pristine-gz"
+               if (grep { $_ eq $delta_wrapper->{type} } qw{gz bz2 xz}) {
+                       doit("pristine-".$delta_wrapper->{type}
                                ($verbose ? "-v" : "--no-verbose"),
                                ($debug ? "-d" : "--no-debug"),
                                ($keep ? "-k" : "--no-keep"),
-                               "gengz", $delta->{wrapper}, $out);
-                       doit("mv", "-f", $out.".gz", $tarball);
-               }
-               elsif ($delta_wrapper->{type} eq 'bz2') {
-                       doit("pristine-bz2",
-                               ($verbose ? "-v" : "--no-verbose"),
-                               ($debug ? "-d" : "--no-debug"),
-                               ($keep ? "-k" : "--no-keep"),
-                               "genbz2", $delta->{wrapper}, $out);
-                       doit("mv", "-f", $out.".bz2", $tarball);
+                               "gen".$delta_wrapper->{type},
+                               $delta->{wrapper}, $out);
+                       doit("mv", "-f", $out.".".$delta_wrapper->{type}, $tarball);
                }
                else {
                        error "unknown wrapper file type: ".
@@ -460,6 +454,14 @@ sub gendelta {
                close IN || die "bzcat: $!";
                close OUT || die "$tempdir/origtarball: $!";
        }
+       elsif (is_xz($tarball)) {
+               $compression='xz';
+               open(IN, "-|", "xzcat", $tarball) || die "xzcat: $!";
+               open(OUT, ">", "$tempdir/origtarball") || die "$tempdir/origtarball: $!";
+               print OUT $_ while <IN>;
+               close IN || die "xzcat: $!";
+               close OUT || die "$tempdir/origtarball: $!";
+       }
        close IN;
        
        # Generate a wrapper file to recreate the compressed file.
diff --git a/pristine-xz b/pristine-xz
new file mode 100755 (executable)
index 0000000..a0365b3
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+pristine-xz - regenerate pristine xz files
+
+=head1 SYNOPSIS
+
+B<pristine-xz> [-vdk] gendelta I<file.xz> I<delta>
+
+B<pristine-xz> [-vdk] genxz I<delta> I<file>
+
+=head1 DESCRIPTION
+
+This is a complement to the pristine-tar(1) command. Normally you
+don't need to run it by hand, since pristine-tar calls it as necessary
+to handle .tar.xz files.
+
+pristine-xz gendelta takes the specified I<xz> file, and generates a
+small binary I<delta> file that can later be used by pristine-xz genxz
+to recreate the original file.
+
+pristine-xz genxz takes the specified I<delta> file, and compresses the
+specified input I<file> (which must be identical to the contents of the
+original xz file). The resulting file will be identical to
+the original gz file used to create the delta.
+
+The approach used to regenerate the original xz file is to figure out
+how it was produced -- what compression level was used, etc. Currently
+support is poor for xz files produced with unusual compression options.
+
+If the delta filename is "-", pristine-xz reads or writes it to stdio.
+
+=head1 OPTIONS
+
+=over 4
+
+=item -v
+
+Verbose mode, show each command that is run.
+
+=item -d
+
+Debug mode.
+
+=item -k
+
+Don't clean up the temporary directory on exit.
+
+=item -t
+
+Try harder to determine how to generate deltas of difficult xz files.
+
+=back
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item B<TMPDIR>
+
+Specifies a location to place temporary files, other than the default.
+
+=back
+
+=head1 AUTHOR
+
+Joey Hess <joeyh@debian.org>,
+Faidon Liambotis <paravoid@debian.org>,
+Cyril Brulebois <cyril.brulebois@enst-bretagne.fr>
+
+Licensed under the GPL, version 2.
+
+=cut
+
+use warnings;
+use strict;
+use Pristine::Tar;
+use Pristine::Tar::Delta;
+use Pristine::Tar::Formats;
+use File::Basename qw/basename/;
+use IO::Handle;
+
+my @supported_xz_programs = qw(xz);
+
+my $try=0;
+
+dispatch(
+       commands => {
+               usage => [\&usage],
+               genxz => [\&genxz, 2],
+               gendelta => [\&gendelta, 2],
+       },
+       options => {
+               "t|try!" => \$try,
+       },
+);
+
+sub usage {
+       print STDERR "Usage: pristine-xz [-vdkt] gendelta file.xz delta\n";
+       print STDERR "       pristine-xz [-vdkt] genxz delta file\n";
+}
+
+sub readxz {
+       my $filename = shift;
+
+       if (! is_xz($filename)) {
+               error "This is not a valid xz archive.";
+       }
+
+       # XXX This is the default compression level; we don't currently have
+       # a way to guess the level from the file format, as this level only
+       # presets several other tunables. Correct handling would involve
+       # finding as many preset values as possible, and reconstructing the
+       # compression level from that.
+       my $level = 6;
+
+       return ($level);
+}
+
+sub predictxzargs {
+       my ($level, $program) = @_;
+
+       my @args=["-z", "-$level"];
+
+       return @args;
+}
+
+sub testvariant {
+       my ($old, $tmpin, $xz_program, @args) = @_;
+
+       my $new=$tmpin.'.xz';
+       unlink($new);
+
+       # Note that file name, mode, mtime do not matter to xz.
+
+       # try xz'ing with the arguments passed
+       doit_redir($tmpin, $new, $xz_program, @args);
+
+       unless (-e $new) {
+               die("$xz_program failed, aborting");
+       }
+
+       # and compare the generated with the original
+       return !comparefiles($old, $new);
+}
+
+sub reproducexz {
+       my $orig=shift;
+
+       my $wd=tempdir();
+       
+       my $tmpin="$wd/test";
+       doit_redir($orig, $tmpin, "xz", "-dc");
+
+       # read fields from xz headers
+       my ($level) = readxz($orig);
+       debug("level: $level");
+
+       foreach my $program (@supported_xz_programs) {
+               # try to guess the xz arguments that are needed by the
+               # header information
+               foreach my $args (predictxzargs($level, $program)) {
+                       testvariant($orig, $tmpin, $program, @$args)
+                               && return $program, @$args;
+               }
+       }
+
+       print STDERR "pristine-xz failed to reproduce build of $orig\n";
+       print STDERR "(Please file a bug report.)\n";
+       exit 1;
+}
+
+sub genxz {
+       my $deltafile=shift;
+       my $file=shift;
+
+       my $delta=Pristine::Tar::Delta::read(Tarball => $deltafile);
+       Pristine::Tar::Delta::assert($delta, type => "xz", maxversion => 2, 
+               fields => [qw{params program}]);
+
+       my @params=split(' ', $delta->{params});
+       while (@params) {
+               my $param=shift @params;
+
+               next if $param=~/^(-[1-9])$/;
+               next if $param eq '-z';
+               die "paranoia check failed on params from delta (@params)";
+       }
+       @params=split(' ', $delta->{params});
+
+       my $program=$delta->{program};
+       if (! grep { $program eq $_ } @supported_xz_programs) {
+               die "paranoia check failed on program from delta ($program)";
+       }
+
+       doit($program, @params, $file);
+}
+
+sub gendelta {
+       my $xzfile=shift;
+       my $deltafile=shift;
+
+       my ($program, @params) = reproducexz($xzfile);
+
+       Pristine::Tar::Delta::write(Tarball => $deltafile, {
+               version => '2.0',
+               type => 'xz',
+               params => "@params",
+               program => $program,
+       });
+}