/usr/local/CPAN/VCI/VCI/VCS/Hg/Commit.pm
package VCI::VCS::Hg::Commit;
use Moose;
use VCI::VCS::Hg::Diff;
use VCI::VCS::Hg::File;
extends 'VCI::Abstract::Commit';
has 'x_changes' => (is => 'ro', isa => 'HashRef', lazy_build => 1);
use constant DIFF_HEADER => qr/^([\-\+]{3}) (\S+)\t\w{3} \w{3} \d\d \d\d:\d\d:\d\d \d{4} [\+\-]\d{4}$/;
# XXX Hg does have a concept of revno, but we can't access it via hgweb.
# So we have to use the default "revno is revision id".
sub _build_added {
my $self = shift;
my $files = $self->x_changes->{added};
return [map { $self->file_class->new(path => $_, project => $self->project,
revision => $self->revision, time => $self->time) }
@$files];
}
sub _build_removed {
my $self = shift;
my $files = $self->x_changes->{removed};
return [map { $self->file_class->new(path => $_, project => $self->project,
revision => $self->revision, time => $self->time) }
@$files];
}
sub _build_modified {
my $self = shift;
my $files = $self->x_changes->{modified};
return [map { $self->file_class->new(path => $_, project => $self->project,
revision => $self->revision, time => $self->time) }
@$files];
}
sub x_from_rss_item {
my ($class, $item, $project) = @_;
my $project_path = $project->repository->root . $project->name;
my $revision = $item->{'guid'}->{'content'};
# Older versions of hgweb have it in "link"
if (!$revision) {
$revision = $item->{'link'};
}
# Sometimes revisions come to us with log{$rev}. We also include
# log/$rev/File as an option in case they fix the bug in Hg.
$revision =~ s#^\Q$project_path\E(/rev/|/log[{/])##;
$revision =~ s#}?/.+$##;
my $time = $item->{pubDate};
my $message = $item->{description};
# As far as I know, this is the only HTML that hgweb adds here.
$message =~ s/<br\/>//g;
return $class->new(
message => $message,
revision => $revision,
time => $time,
committer => $item->{author},
project => $project);
}
sub _build_as_diff {
my $self = shift;
my $text = $self->project->x_get(['raw-rev', $self->revision]);
my @lines = split("\n", $text);
my $line = shift @lines;
# XXX This may break if there's a line identical to DIFF_HEADER
# in the log message.
while ($line !~ DIFF_HEADER) {
$line = shift @lines;
last if (!defined $line);
}
unshift(@lines, $line) if defined $line;
return $self->diff_class->new(raw => join("\n", @lines),
project => $self->project);
}
# Mercurial doesn't say anything about directories in its logs, so we have
# no idea when directories are added or removed.
sub _build_x_changes {
my $self = shift;
my $text = $self->as_diff->raw;
my $files = _diff_files($text);
my (@added, @removed, @modified);
foreach my $set (@$files) {
my ($file1, $file2) = @$set;
my $changed_file = $file1 eq '/dev/null' ? $file2 : $file1;
if ($file1 eq $file2) {
push(@modified, $changed_file);
}
elsif ($file1 eq '/dev/null') {
push(@added, $changed_file);
}
else {
push(@removed, $changed_file);
}
}
return { added => \@added, removed => \@removed, modified => \@modified };
}
# There is currently a bug in Text::Diff::Parser where it doesn't process
# correctly beyond the first file in the diff. So I just do this manually
# instead of parsing the diff.
sub _diff_files {
my $text = shift;
my @files;
my @current_set = (undef, undef);
foreach my $line (split("\n", $text)) {
if ($line =~ DIFF_HEADER) {
my ($type, $file) = ($1, $2);
# Strip a/ or b/ if the file starts with that.
$file =~ s|^[ab]/||;
if ($type eq '---') {
$current_set[0] = $file;
}
else {
$current_set[1] = $file;
push(@files, [@current_set]);
@current_set = (undef, undef);
}
}
}
return \@files;
}
__PACKAGE__->meta->make_immutable;
1;