#!/usr/bin/perl # # TarDiff - Compare two tarballs and report differences # Homepage: http://tardiff.coolprojects.org/ # Copyright (C) 2005 Josef Spillner # Published under GNU GPL conditions use strict; my $VERSION = '0.1'; my ($tarball1, $tarball2); my ($opt_list, $opt_modified, $opt_autoskip, $opt_stats); my $tempdir; $SIG{'__DIE__'} = 'cleanup'; $SIG{'TERM'} = 'cleanup'; $SIG{'INT'} = 'cleanup'; sub arguments{ for my $i(0..$#ARGV){ my $arg = $ARGV[$i]; if(($arg eq "--help") or ($arg eq "-h")){ print "TarDiff - Compare two tarballs and report differences\n"; print "Call: tardiff [options] file1.tar file2.tar[.gz/.bz2]\n"; print "\n"; print "Options:\n"; print "[-m / --modified] Report on all changed files, including those present in both tarballs\n"; print "[-l / --list ] List all files, even those not changed at all\n"; print "[-a / --autoskip] Skip files which belong to the GNU autotools (for --modified)\n"; print "[-s / --stats ] Run statistics (diffstat) on all modified files (for --modified)\n"; print "\n"; print "[-v / --version ] Display tardiff version\n"; print "[-h / --help ] Display this help screen\n"; print "\n"; exit; }elsif(($arg eq "--version")or ($arg eq "-v")){ print $VERSION, "\n"; exit; }elsif(($arg eq "--modified")or ($arg eq "-m")){ $opt_modified = 1; }elsif(($arg eq "--list") or ($arg eq "-l")){ $opt_list = 1; }elsif(($arg eq "--autoskip") or ($arg eq "-s")){ $opt_autoskip = 1; }elsif(($arg eq "--stats") or ($arg eq "-s")){ $opt_stats = 1; }else{ if(!$tarball1){ $tarball1 = $arg; }elsif(!$tarball2){ $tarball2 = $arg; }else{ print "Too much arguments: $arg\n"; exit 1; } } } if(not($tarball1 and $tarball2)){ print "Missing arguments; see --help\n"; exit 1; } } sub untar{ my $tarball = shift(@_); my $flag = ""; if($tarball =~ /\.gz$/){ $flag = "-z"; }elsif($tarball =~ /\.bz2$/){ $flag = "-j"; } my $list = `tar -C $tempdir $flag -xvf $tarball 2>/dev/null`; return $list; } sub analyzetar{ my $filelist = shift(@_); my $filehash = shift(@_); my %files = %{$filehash}; my ($uniquebase, $base, $remainder); my @remainders; foreach my $file(split(/\n/, $filelist)){ ($base, @remainders) = split(/\//, $file); $remainder = join("/", @remainders); if(!$uniquebase){ $uniquebase = $base; }else{ ($base eq $uniquebase) or die "$tarball1 contains different base dirs: $base and $uniquebase"; } if($files{$remainder}){ $files{$remainder} = "__both"; }else{ $files{$remainder} = "$uniquebase"; } } return ($uniquebase, %files); } sub comparefile{ my $base1 = shift(@_); my $base2 = shift(@_); my $file = shift(@_); my $file1 = "$tempdir/$base1/$file"; my $file2 = "$tempdir/$base2/$file"; if(-d $file1 and -d $file2){ return 0; }elsif(-f $file1 and -f $file2){ my $diff = `diff -u $file1 $file2`; if($diff){ if($opt_stats){ my $plus = 0; my $minus = 0; foreach my $line(split(/\n/, $diff)){ if($line =~ /^+\ /){ $plus++; }elsif($line =~ /^-\ /){ $minus++; } } #return "$plus +/$minus -"; my $len = 50 - length($file); return sprintf("%${len}s%9s%9s", "(", "$plus + /", "$minus -)"); }else{ return 1; } } }else{ return 1; } return 0; } sub autofile{ my $file = shift(@_); my @parts = split(/\//, $file); @parts = reverse(@parts); my $filename = @parts[0]; if($file eq "missing"){return 1}; if($file eq "aclocal.m4"){return 1}; if($file eq "config.guess"){return 1}; if($file eq "config.h.in"){return 1}; if($file eq "config.sub"){return 1}; if($file eq "configure"){return 1}; if($file eq "depcomp"){return 1}; if($file eq "install-sh"){return 1}; if($file eq "ltmain.sh"){return 1}; if($filename eq "Makefile.in"){return 1}; return 0; } sub tardiff{ my $error = 0; $tempdir = "/tmp/tardiff-$$"; mkdir $tempdir; my $filelist1 = untar($tarball1) or die "Error: Could not unpack $tarball1."; my $filelist2 = untar($tarball2) or die "Error: Could not unpack $tarball2."; my %files; my ($base1, %files) = analyzetar($filelist1, \%files); my ($base2, %files) = analyzetar($filelist2, \%files); foreach my $file(sort(keys(%files))){ next if $file eq ""; my $base = $files{$file}; if($base eq "__both"){ next if $opt_autoskip and autofile($file); my $modified = 0; if($opt_modified){ $modified = comparefile($base1, $base2, $file); if($modified){ if($opt_stats){ print "/ $file $modified\n"; }else{ print "/ $file\n"; } } } if($opt_list and not $modified){ print " $file\n"; } }elsif($base eq $base1){ print "- $file\n"; }elsif($base eq $base2){ print "+ $file\n"; }else{ print "? $file\n"; } } } sub cleanup{ my $handler = shift(@_); if($tempdir){ system("rm -rf $tempdir"); } if($handler eq "INT" or $handler eq "TERM"){ exit 1; } } arguments(); tardiff(); cleanup();