#!/usr/bin/perl -w

# Copyright (c) 2007 Anthony Towns
# GNU GPL; v2 or later
# Gives an overview of what changed between two keyrings

use strict;
use Cwd q{abs_path};
use warnings;
use strict;

if (@ARGV != 2) {
	die "usage: jetring-diff keyring1.gpg keyring2.gpg\n";
}

my $l = parse_keyring(shift);
my $r = parse_keyring(shift);

foreach my $id (sort keys %{$l}) {
	if (not exists $r->{$id}) {
		summary("-", @{$l->{$id}});
	}
	else {
		my $diff=0;
		my @out;
		my %lseen=map { comparable($_) => 1 } @{$l->{$id}};
		my %rseen=map { comparable($_) => 1 } @{$r->{$id}};
		foreach my $record (@{$l->{$id}}) {
			if ($rseen{comparable($record)}) {
				push @out, " ".outformat($record) if $record->[0] ne 'sig';
			}
			else {
				push @out, "-".outformat($record);
				$diff=1;
			}
		}
		foreach my $record (@{$r->{$id}}) {
			if (! $lseen{comparable($record)}) {
				push @out, "+".outformat($record);
				$diff=1;
			}
		}
		print @out if $diff;
	}
}
foreach my $id (sort keys %{$r}) {
	if (not exists $l->{$id}) {
		summary("+", @{$r->{$id}});
	}
}

sub parse_keyring {
	my $k=shift;

	$k=abs_path($k); # annoying gpg..
	my $cache=$k.".cache";

	my $cached=0;
	my $kmtime=(stat($k))[9];
	if (-e $cache) {
		my $cmtime=(stat($cache))[9];
		if ($kmtime == $cmtime) {
			open(DUMP, $cache) || die "$cache: $!";
			$cached=1;
		}
	}
	if (! $cached) {
		open(DUMP, "gpg --options /dev/null --no-default-keyring --no-auto-check-trustdb --keyring $k --list-sigs --fixed-list-mode --with-colons |") 
			or die "couldn't dump keyring $k: $!";
		if (! open(CACHE, ">$cache")) {
			print STDERR "watning: cannot write cache $cache\n";
			$cache=undef;
		}
	}
	my %keys;
	my $id;
	while (<DUMP>) {
		if (! $cached && defined $cache) {
			print CACHE $_;
		}
		chomp;

		my @fields=split(":", $_);
		$fields[5]="-"; # ignore creation date, varies
		next if $fields[0] eq 'tru';
		if ($fields[0] eq 'pub') {
			$id=$fields[4];
		}
		if (! defined $id) {
			die "parse error: $_";
			next;
		}
		push @{$keys{$id}}, \@fields;
	}
	close DUMP;

	if (defined $cache) {
		close CACHE;
		utime($kmtime, $kmtime, $cache) ||
			print STDERR "warning: failed setting cache time: $!";
	}

	return \%keys;
}

sub summary {
	my $prefix=shift;

	foreach my $record (@_) {
		if ($record->[0] eq 'pub' || $record->[0] eq 'uid') {
			print "$prefix".outformat($record);
		}
	}
}

sub outformat {
	return join(":", @{shift()})."\n";
}

sub comparable {
	my @record=@{shift()};
	if ($record[0] eq 'sig') {
		# Displayed user ids for sigs vary, so compare different
		# ones the same. The user-id is what matters.
		$record[9]="";
	}
	return join(":", @record);
}
