use Date::Format qw(time2str);
my $sha1_pattern = qr/[0-9a-fA-F]{40}/; # pattern to validate sha1sums
+my $mtn_version = undef;
sub import {
hook(type => "checkconfig", id => "monotone", call => \&checkconfig);
exec("mtn", "version") || error("mtn version failed to run");
}
- my $version=undef;
while (<MTN>) {
- if (/^monotone (\d+\.\d+) /) {
- $version=$1;
+ if (/^monotone (\d+\.\d+)(?:(?:\.\d+){0,2}|dev)? /) {
+ $mtn_version=$1;
}
}
close MTN || debug("mtn version exited $?");
- if (!defined($version)) {
+ if (!defined($mtn_version)) {
error("Cannot determine monotone version");
}
- if ($version < 0.38) {
- error("Monotone version too old, is $version but required 0.38");
+ if ($mtn_version < 0.38) {
+ error("Monotone version too old, is $mtn_version but required 0.38");
}
if (defined $config{mtn_wrapper} && length $config{mtn_wrapper}) {
my @ret;
my %seen = ();
-
+
+ # we need to strip off the relative path to the source dir
+ # because monotone outputs all file paths absolute according
+ # to the workspace root
+ my $rel_src_dir = $config{'srcdir'};
+ $rel_src_dir =~ s/^\Q$config{'mtnrootdir'}\E\/?//;
+ $rel_src_dir .= "/" if length $rel_src_dir;
+
while ($changes =~ m/\s*(add_file|patch|delete|rename)\s"(.*?)(?<!\\)"\n/sg) {
my $file = $2;
+ # ignore all file changes outside the source dir
+ next unless $file =~ m/^\Q$rel_src_dir\E/;
+ $file =~ s/^\Q$rel_src_dir\E//;
+
# don't add the same file multiple times
if (! $seen{$file}) {
push @ret, $file;
return @ret;
}
-sub rcs_diff ($) {
+sub rcs_diff ($;$) {
my $rev=shift;
+ my $maxlines=shift;
my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
chdir $config{srcdir}
exec("mtn", "diff", "--root=$config{mtnrootdir}", "-r", "p:".$sha1, "-r", $sha1) || error("mtn diff $sha1 failed to run");
}
- my (@lines) = <MTNDIFF>;
+ my @lines;
+ while (my $line=<MTNDIFF>) {
+ last if defined $maxlines && @lines == $maxlines;
+ push @lines, $line;
+ }
close MTNDIFF || debug("mtn diff $sha1 exited $?");
"--brief", $file) || error("mtn log $file failed to run");
}
+ my $prevRev;
my $firstRev;
while (<MTNLOG>) {
if (/^($sha1_pattern)/) {
+ $prevRev=$firstRev;
$firstRev=$1;
}
}
my $automator = Monotone->new();
$automator->open(undef, $config{mtnrootdir});
+ # mtn 0.48 has a bug that makes it list the creation of parent
+ # directories as last (first) log entry... So when we're dealing
+ # with that version, let's check that the file we're looking for
+ # is actually part of the last (first) revision. Otherwise, pick
+ # the one before (after) that one.
+ if ($mtn_version == 0.48) {
+ my $changes = [get_changed_files($automator, $firstRev)];
+ if (! exists {map { $_ => 1 } @$changes}->{$file}) {
+ $firstRev = $prevRev;
+ }
+ }
my $certs = [read_certs($automator, $firstRev)];
$automator->close();
}
sub rcs_getmtime ($) {
- error "rcs_getmtime is not implemented for monotone\n"; # TODO
+ my $file=shift;
+
+ chdir $config{srcdir}
+ or error("Cannot chdir to $config{srcdir}: $!");
+
+ my $child = open(MTNLOG, "-|");
+ if (! $child) {
+ exec("mtn", "log", "--root=$config{mtnrootdir}", "--no-graph",
+ "--brief", $file) || error("mtn log $file failed to run");
+ }
+
+ my $lastRev = "";
+ while (<MTNLOG>) {
+ if (/^($sha1_pattern)/ && $lastRev eq "") {
+ $lastRev=$1;
+ }
+ }
+ close MTNLOG || debug("mtn log $file exited $?");
+
+ if (! defined $lastRev) {
+ debug "failed to parse mtn log for $file";
+ return 0;
+ }
+
+ my $automator = Monotone->new();
+ $automator->open(undef, $config{mtnrootdir});
+
+ my $certs = [read_certs($automator, $lastRev)];
+
+ $automator->close();
+
+ my $date;
+
+ foreach my $cert (@$certs) {
+ if ($cert->{signature} eq "ok" && $cert->{trust} eq "trusted") {
+ if ($cert->{name} eq "date") {
+ $date = $cert->{value};
+ }
+ }
+ }
+
+ if (! defined $date) {
+ debug "failed to find date cert for revision $lastRev when looking for creation time of $file";
+ return 0;
+ }
+
+ $date=str2time($date, 'UTC');
+ debug("found mtime ".localtime($date)." for $file");
+ return $date;
}
1