#!/usr/bin/perl
#
# TarDiff - Compare two tarballs and report differences
# Homepage: http://tardiff.coolprojects.org/
# Copyright (C) 2005 Josef Spillner <josef@coolprojects.org>
# 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();

