pm_to_blib
MANIFEST
MANIFEST.bak
+*~
--- /dev/null
+* Sort out commitdiff so conflicted merges have an equivalent display to gitweb (d7f39bebabeb31ce9a7b4f1b6f3db9f391b78c3e as a reference)
DateTime::Format::Mail
File::Stat::ModeString
List::MoreUtils
+ MooseX::Types::Common
# Probably others ...
COPYRIGHT AND LICENCE
--- /dev/null
+* Fix git_blob_plain i.e don't use the wrapper.
+* An action to find what branches have been merged, either as a list or through a search mechanism.
+* An action to find which branches a given commit is on.
+* Fix any not text/html bits e.g the patch action.
+* Simplify the creation of links.
# fs traversing limit for getting project list
# the number is relative to the projectroot
project_maxdepth 2007
+
+<paging>
+ log = 50
+ summary = 16
+</paging>
=cut
use IO::Capture::Stdout;
-use File::Slurp qw(slurp);
-#sub default :Path {
+=head2 run_gitweb
+
+The main shim around C<gitweb.pm>.
+
+=cut
+
sub run_gitweb {
my ( $self, $c ) = @_;
$action->();
};
$capture->stop();
-
+
use Data::Dumper;
die Dumper($@)
if $@;
-
+
my $output = join '', $capture->read;
$c->stash->{gitweb_output} = $output;
$c->stash->{template} = 'gitweb.tt2';
}
}
+sub _get_commit {
+ my($self, $c, $haveh) = @_;
+
+ my $h = $haveh || $c->req->param('h');
+ my $f = $c->req->param('f');
+ my $m = $c->model('Git');
+
+ # Either use the provided h(ash) parameter, the f(ile) parameter or just use HEAD.
+ my $hash = ($h =~ /[^a-f0-9]/ ? $m->head_hash($h) : $h)
+ || ($f && $m->hash_by_path($f))
+ || $m->head_hash
+ # XXX This could definitely use more context.
+ || Carp::croak("Couldn't find a hash for the commit object!");
+
+
+ (my $pd = $m->project_dir( $m->project )) =~ s{/\.git$}();
+ my $commit = $m->get_object($hash)
+ or Carp::croak("Couldn't find a commit object for '$hash' in '$pd'!");
+
+ return $commit;
+}
+
+=head2 index
+
+Provides the project listing.
+
+=cut
+
sub index :Path :Args(0) {
my ( $self, $c ) = @_;
);
}
+=head2 summary
+
+A summary of what's happening in the repo.
+
+=cut
+
+sub summary : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c);
+ $c->stash(
+ commit => $commit,
+ info => $c->model('Git')->project_info($c->model('Git')->project),
+ log_lines => [$c->model('Git')->list_revs(
+ sha1 => $commit->sha1, count => Gitalist->config->{paging}{summary}
+ )],
+ refs => $c->model('Git')->references,
+ heads => [$c->model('Git')->heads],
+ action => 'summary',
+ );
+}
+
+=head2 heads
+
+The current list of heads (aka branches) in the repo.
+
+=cut
+
+sub heads : Local {
+ my ( $self, $c ) = @_;
+
+ $c->stash(
+ commit => $self->_get_commit($c),
+ heads => [$c->model('Git')->heads],
+ action => 'heads',
+ );
+}
+
+=head2 blob
+
+The blob action i.e the contents of a file.
+
+=cut
+
+sub blob : Local {
+ my ( $self, $c ) = @_;
+
+ my $h = $c->req->param('h')
+ || $c->model('Git')->hash_by_path($c->req->param('f'))
+ || die "No file or sha1 provided.";
+ my $hb = $c->req->param('hb')
+ || $c->model('Git')->head_hash
+ || die "Couldn't discern the corresponding head.";
+
+ my $filename = $c->req->param('f') || '';
+
+ $c->stash(
+ blob => $c->model('Git')->get_object($h)->content,
+ head => $c->model('Git')->get_object($hb),
+ filename => $filename,
+ # XXX Hack hack hack, see View::SyntaxHighlight
+ language => ($filename =~ /\.p[lm]$/ ? 'Perl' : ''),
+ action => 'blob',
+ );
+
+ $c->forward('View::SyntaxHighlight');
+}
+
+=head2 blobdiff
+
+Exposes a given diff of a blob.
+
+=cut
+
+sub blobdiff : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c);
+ my $filename = $c->req->param('f')
+ || croak("No file specified!");
+ my($tree, $patch) = $c->model('Git')->diff(
+ commit => $commit,
+ parent => $c->req->param('hp') || '',
+ file => $filename,
+ patch => 1,
+ );
+ $c->stash(
+ commit => $commit,
+ diff => $patch,
+ # XXX Hack hack hack, see View::SyntaxHighlight
+ blobs => [$patch->[0]->{diff}],
+ language => 'Diff',
+ action => 'blobdiff',
+ );
+
+ $c->forward('View::SyntaxHighlight');
+}
+
+=head2 commit
+
+Exposes a given commit.
+
+=cut
+
+sub commit : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c);
+ $c->stash(
+ commit => $commit,
+ diff_tree => ($c->model('Git')->diff(commit => $commit))[0],
+ branches_on => [$c->model('Git')->refs_for($commit->sha1)],
+ action => 'commit',
+ );
+}
+
+=head2 commitdiff
+
+Exposes a given diff of a commit.
+
+=cut
+
+sub commitdiff : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c);
+ my($tree, $patch) = $c->model('Git')->diff(
+ commit => $commit,
+ parent => $c->req->param('hp') || '',
+ patch => 1,
+ );
+ $c->stash(
+ commit => $commit,
+ diff_tree => $tree,
+ diff => $patch,
+ # XXX Hack hack hack, see View::SyntaxHighlight
+ blobs => [map $_->{diff}, @$patch],
+ language => 'Diff',
+ action => 'commitdiff',
+ );
+
+ $c->forward('View::SyntaxHighlight');
+}
+
+=head2 shortlog
+
+Expose an abbreviated log of a given sha1.
+
+=cut
+
+sub shortlog : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c);
+ my %logargs = (
+ sha1 => $commit->sha1,
+ count => Gitalist->config->{paging}{log},
+ ($c->req->param('f') ? (file => $c->req->param('f')) : ())
+ );
+
+ my $page = $c->req->param('pg') || 0;
+ $logargs{skip} = $c->req->param('pg') * $logargs{count}
+ if $c->req->param('pg');
+
+ $c->stash(
+ commit => $commit,
+ log_lines => [$c->model('Git')->list_revs(%logargs)],
+ refs => $c->model('Git')->references,
+ action => 'shortlog',
+ page => $page,
+ );
+}
+
+=head2 log
+
+Calls shortlog internally. Perhaps that should be reversed ...
+
+=cut
+sub log : Local {
+ $_[0]->shortlog($_[1]);
+ $_[1]->stash->{action} = 'log';
+}
+
+=head2 tree
+
+The tree of a given commit.
+
+=cut
+
+sub tree : Local {
+ my ( $self, $c ) = @_;
+
+ my $commit = $self->_get_commit($c, $c->req->param('hb'));
+ my $tree = $c->model('Git')->get_object($c->req->param('h') || $commit->tree_sha1);
+ $c->stash(
+ # XXX Useful defaults needed ...
+ commit => $commit,
+ tree => $tree,
+ tree_list => [$c->model('Git')->list_tree($tree->sha1)],
+ path => $c->req->param('f') || '',
+ action => 'tree',
+ );
+}
+
+=head2 reflog
+
+Expose the local reflog. This may go away.
+
+=cut
+
+sub reflog : Local {
+ my ( $self, $c ) = @_;
+
+ my @log = $c->model('Git')->reflog(
+ '--since=yesterday'
+ );
+
+ $c->stash(
+ log => \@log,
+ action => 'reflog',
+ );
+}
+
+sub search : Local {
+ Carp::croak "Not implemented.";
+}
+
+sub search_help : Local {
+ Carp::croak "Not implemented.";
+}
+
+=head2 auto
+
+Populate the header and footer. Perhaps not the best location.
+
+=cut
+
sub auto : Private {
my($self, $c) = @_;
$self->footer($c);
}
+# XXX This could probably be dropped altogether.
use Gitalist::Util qw(to_utf8);
-use HTML::Entities qw(encode_entities);
-use URI::Escape qw(uri_escape);
# Formally git_header_html
sub header {
my($self, $c) = @_;
- my $title = $c->config->{sitename};
+ my $title = $c->config->{sitename};
my $project = $c->req->param('project') || $c->req->param('p');
my $action = $c->req->param('action') || $c->req->param('a');
my $file_name = $c->req->param('filename') || $c->req->param('f');
- if(defined $project) {
- $title .= " - " . to_utf8($project);
- if (defined $action) {
- $title .= "/$action";
- if (defined $file_name) {
- $title .= " - " . encode_entities($file_name);
- if ($action eq "tree" && $file_name !~ m|/$|) {
- $title .= "/";
- }
- }
- }
- }
-
- $c->stash->{version} = $c->config->{version};
- $c->stash->{git_version} = $c->model('Git')->run_cmd('--version');
- $c->stash->{title} = $title;
+ if(defined $project) {
+ $title .= " - " . to_utf8($project);
+ if (defined $action) {
+ $title .= "/$action";
+ if (defined $file_name) {
+ $title .= " - " . $file_name;
+ if ($action eq "tree" && $file_name !~ m|/$|) {
+ $title .= "/";
+ }
+ }
+ }
+ }
+
+ $c->stash->{version} = $c->config->{version};
+ $c->stash->{git_version} = $c->model('Git')->run_cmd('--version');
+ $c->stash->{title} = $title;
#$c->stash->{baseurl} = $ENV{PATH_INFO} && uri_escape($base_url);
- $c->stash->{stylesheet} = $c->config->{stylesheet} || 'gitweb.css';
+ $c->stash->{stylesheet} = $c->config->{stylesheet} || 'gitweb.css';
- $c->stash->{project} = $project;
+ $c->stash->{project} = $project;
my @links;
- if($project) {
- my %href_params = $self->feed_info($c);
- $href_params{'-title'} ||= 'log';
+ if($project) {
+ my %href_params = $self->feed_info($c);
+ $href_params{'-title'} ||= 'log';
- foreach my $format qw(RSS Atom) {
- my $type = lc($format);
+ foreach my $format qw(RSS Atom) {
+ my $type = lc($format);
push @links, {
- rel => 'alternate',
- title => "$project - $href_params{'-title'} - $format feed",
- # XXX A bit hacky and could do with using gitweb::href() features
- href => "?a=$type;p=$project",
- type => "application/$type+xml"
+ rel => 'alternate',
+ title => "$project - $href_params{'-title'} - $format feed",
+
+ # XXX A bit hacky and could do with using gitweb::href() features
+ href => "?a=$type;p=$project",
+ type => "application/$type+xml"
}, {
- rel => 'alternate',
- # XXX This duplication also feels a bit awkward
- title => "$project - $href_params{'-title'} - $format feed (no merges)",
- href => "?a=$type;p=$project;opt=--no-merges",
- type => "application/$type+xml"
+ rel => 'alternate',
+
+ # XXX This duplication also feels a bit awkward
+ title => "$project - $href_params{'-title'} - $format feed (no merges)",
+ href => "?a=$type;p=$project;opt=--no-merges",
+ type => "application/$type+xml"
};
- }
- } else {
+ }
+ } else {
push @links, {
- rel => "alternate",
- title => $c->config->{sitename}." projects list",
- href => '?a=project_index',
- type => "text/plain; charset=utf-8"
- }, {
- rel => "alternate",
- title => $c->config->{sitename}." projects feeds",
- href => '?a=opml',
- type => "text/plain; charset=utf-8"
- };
- }
+ rel => "alternate",
+ title => $c->config->{sitename}." projects list",
+ href => '?a=project_index',
+ type => "text/plain; charset=utf-8"
+ }, {
+ rel => "alternate",
+ title => $c->config->{sitename}." projects feeds",
+ href => '?a=opml',
+ type => "text/plain; charset=utf-8"
+ };
+ }
- $c->stash->{favicon} = $c->config->{favicon};
+ $c->stash->{favicon} = $c->config->{favicon};
- # </head><body>
+ # </head><body>
- $c->stash(
- logo_url => uri_escape($c->config->{logo_url}),
- logo_label => encode_entities($c->config->{logo_label}),
- logo_img => $c->config->{logo},
- home_link => uri_escape($c->config->{home_link}),
+ $c->stash(
+ logo_url => $c->config->{logo_url},
+ logo_label => $c->config->{logo_label},
+ logo_img => $c->config->{logo},
+ home_link => $c->config->{home_link},
home_link_str => $c->config->{home_link_str},
- );
+ );
- if(defined $project) {
+ if(defined $project) {
$c->stash(
- search_text => $c->req->param('s') || $c->req->param('searchtext'),
- search_hash => $c->req->param('hb') || $c->req->param('hashbase')
- || $c->req->param('h') || $c->req->param('hash')
- || 'HEAD'
- );
- }
+ search_text => ( $c->req->param('s') || $c->req->param('searchtext') || ''),
+ search_hash => ( $c->req->param('hb') || $c->req->param('hashbase')
+ || $c->req->param('h') || $c->req->param('hash')
+ || 'HEAD' ),
+ );
+ }
}
# Formally git_footer_html
sub footer {
my($self, $c) = @_;
- my $feed_class = 'rss_logo';
+ my $feed_class = 'rss_logo';
my @feeds;
my $project = $c->req->param('project') || $c->req->param('p');
- if(defined $project) {
+ if(defined $project) {
(my $pstr = $project) =~ s[/?\.git$][];
- my $descr = $c->model('Git')->project_info($project)->{description};
- $c->stash->{project_description} = defined $descr
- ? encode_entities($descr)
- : '';
+ my $descr = $c->model('Git')->project_info($project)->{description};
+ $c->stash->{project_description} = defined $descr
+ ? $descr
+ : '';
- my %href_params = $self->feed_info($c);
- if (!%href_params) {
- $feed_class .= ' generic';
- }
- $href_params{'-title'} ||= 'log';
+ my %href_params = $self->feed_info($c);
+ if (!%href_params) {
+ $feed_class .= ' generic';
+ }
+ $href_params{'-title'} ||= 'log';
@feeds = [
map +{
title => "$href_params{'-title'} $_ feed",
href => "/?p=$project;a=\L$_",
name => lc $_,
- }, qw(RSS Atom)
- ];
- } else {
+ }, qw(RSS Atom)
+ ];
+ } else {
@feeds = [
map {
class => $feed_class,
- title => '',
- href => "/?a=$_->[0]",
- name => $_->[1],
- }, [opml=>'OPML'],[project_index=>'TXT'],
- ];
- }
+ title => '',
+ href => "/?a=$_->[0]",
+ name => $_->[1],
+ }, [opml=>'OPML'],[project_index=>'TXT'],
+ ];
+ }
}
# XXX This feels wrong here, should probably be refactored.
sub feed_info {
my($self, $c) = @_;
- my $format = shift || 'Atom';
- my %res = (action => lc($format));
+ my $format = shift || 'Atom';
+ my %res = (action => lc($format));
+
+ # feed links are possible only for project views
+ return unless $c->req->param('project');
- # feed links are possible only for project views
- return unless $c->req->param('project');
- # some views should link to OPML, or to generic project feed,
- # or don't have specific feed yet (so they should use generic)
- return if $c->req->param('action') =~ /^(?:tags|heads|forks|tag|search)$/x;
+ # some views should link to OPML, or to generic project feed,
+ # or don't have specific feed yet (so they should use generic)
+ return if $c->req->param('action') =~ /^(?:tags|heads|forks|tag|search)$/x;
- my $branch;
+ my $branch;
my $hash = $c->req->param('h') || $c->req->param('hash');
my $hash_base = $c->req->param('hb') || $c->req->param('hashbase');
- # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
- # from tag links; this also makes possible to detect branch links
- if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
- (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
- $branch = $1;
- }
- # find log type for feed description (title)
- my $type = 'log';
+
+ # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
+ # from tag links; this also makes possible to detect branch links
+ if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
+ (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
+ $branch = $1;
+ }
+
+ # find log type for feed description (title)
+ my $type = 'log';
my $file_name = $c->req->param('f') || $c->req->param('filename');
- if (defined $file_name) {
- $type = "history of $file_name";
- $type .= "/" if $c->req->param('action') eq 'tree';
- $type .= " on '$branch'" if (defined $branch);
- } else {
- $type = "log of $branch" if (defined $branch);
- }
-
- $res{-title} = $type;
- $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
- $res{'file_name'} = $file_name;
-
- return %res;
+ if (defined $file_name) {
+ $type = "history of $file_name";
+ $type .= "/" if $c->req->param('action') eq 'tree';
+ $type .= " on '$branch'" if (defined $branch);
+ } else {
+ $type = "log of $branch" if (defined $branch);
+ }
+
+ $res{-title} = $type;
+ $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
+ $res{'file_name'} = $file_name;
+
+ return %res;
}
=head2 end
=cut
-sub end : ActionClass('RenderView') {}
+sub end : ActionClass('RenderView') {
+ # Give every view the current HEAD.
+ $_[1]->stash->{HEAD} = $_[1]->model('Git')->head_hash;
+}
=head1 AUTHOR
--- /dev/null
+# XXX Deprecated.
+
+package Gitalist::Model::GPP;
+
+#use base 'Catalyst::Model::Adaptor';
+use Moose;
+use namespace::autoclean;
+
+extends 'Catalyst::Model';
+
+use Git::PurePerl;
+
+has git => (
+ #isa => 'Git::PurePerl'
+ is => 'ro',
+ required => 1,
+ lazy => 1,
+ default => sub {
+ my($self) = @_;
+ return Git::PurePerl->new(
+ directory => $self->project_path
+ );
+ },
+);
+
+has project => (
+ is => 'rw',
+ isa => 'Str',
+);
+has project_path => (
+ is => 'rw',
+);
+
+
+sub ACCEPT_CONTEXT {
+ my ( $self, $c ) = @_;
+ $self->project( $c->req->param('p') );
+ $self->project_path( $c->model('Git')->project_dir( $self->project ) );
+ # XXX Or just return a new Git:PP object?
+ return $self;
+}
+
+sub get_object {
+ $_[0]->git->get_object($_[1]);
+}
+
+1;
package Gitalist::Model::Git;
use Moose;
-use MooseX::Types::Common::String qw/NonEmptySimpleStr/; # FIXME, use Types::Path::Class and coerce
use namespace::autoclean;
-BEGIN { extends 'Catalyst::Model' }
+extends 'Catalyst::Model';
+with 'Catalyst::Component::InstancePerContext';
use DateTime;
use Path::Class;
+use File::Which;
use Carp qw/croak/;
use File::Find::Rule;
use DateTime::Format::Mail;
use File::Stat::ModeString;
-use List::MoreUtils qw/any/;
-use File::Which;
+use List::MoreUtils qw/any zip/;
+use Scalar::Util qw/blessed/;
+use MooseX::Types::Common::String qw/NonEmptySimpleStr/; # FIXME, use Types::Path::Class and coerce
+
+use Git::PurePerl;
+
+=head1 NAME
+
+Gitalist::Model::Git - the model for git interactions
+
+=head1 DESCRIPTION
+
+[enter your description here]
+
+=head1 METHODS
+
+=cut
+
+# Should these live in a separate module? Or perhaps extended Regexp::Common?
+our $SHA1RE = qr/[0-9a-fA-F]{40}/;
+
+# These are static and only need to be setup on app start.
+has repo_dir => ( isa => NonEmptySimpleStr, is => 'ro', lazy_build => 1 ); # Fixme - path::class
+has git => ( isa => NonEmptySimpleStr, is => 'ro', lazy_build => 1 );
+# These are dynamic and can be different from one request to the next.
+has project => ( isa => NonEmptySimpleStr, is => 'rw');
+has gpp => ( isa => 'Git::PurePerl', is => 'rw', lazy_build => 1 );
+
+sub build_per_context_instance {
+ my ( $self, $c ) = @_;
+
+ # If we don't have a project param it probably means we're at /
+ return $self
+ unless $c->req->param('p');
+
+ $self->project( $c->req->param('p') );
+
+ (my $pd = $self->project_dir( $self->project )) =~ s{/\.git$}();
+ $self->gpp( Git::PurePerl->new(directory => $pd) );
+
+ return $self;
+}
+
+=head2 BUILD
+
+=cut
sub BUILD {
my ($self) = @_;
$self->git; # Cause lazy value build.
+ $self->repo_dir;
}
-has git => ( isa => NonEmptySimpleStr, is => 'ro', lazy_build => 1 );
-
sub _build_git {
- my $git = File::Which::which('git');
+ my $git = File::Which::which('git');
- if (!$git) {
- die <<EOR
+ if (!$git) {
+ die <<EOR;
Could not find a git executable.
Please specify the which git executable to use in gitweb.yml
EOR
- }
+ }
- return $git;
+ return $git;
+}
+
+sub _build_repo_dir {
+ return Gitalist->config->{repo_dir};
}
+=head2 get_object
+
+A wrapper for the equivalent L<Git::PurePerl> method.
+
+=cut
+
+sub get_object {
+ my($self, $sha1) = @_;
+
+ # We either want an object or undef, *not* an empty list.
+ return $self->gpp->get_object($sha1) || undef;
+}
+
+=head2 is_git_repo
+
+Determine whether a given directory (as a L<Path::Class::Dir> object) is a
+C<git> repo.
+
+=cut
+
sub is_git_repo {
- my ($self, $dir) = @_;
+ my ($self, $dir) = @_;
- return -f $dir->file('HEAD') || -f $dir->file('.git/HEAD');
+ return -f $dir->file('HEAD') || -f $dir->file('.git/HEAD');
}
-sub project_info {
- my ($self, $project) = @_;
+=head2 run_cmd
- return {
- name => $project,
- $self->get_project_properties(
- $self->git_dir_from_project_name($project),
- ),
- };
+Call out to the C<git> binary and return a string consisting of the output.
+
+=cut
+
+sub run_cmd {
+ my ($self, @args) = @_;
+
+ print STDERR 'RUNNING: ', $self->git, qq[ @args], $/;
+
+ open my $fh, '-|', $self->git, @args
+ or die "failed to run git command";
+ binmode $fh, ':encoding(UTF-8)';
+
+ my $output = do { local $/ = undef; <$fh> };
+ close $fh;
+
+ return $output;
+}
+
+=head2 project_dir
+
+The directory under which the given project will reside i.e C<.git/..>
+
+=cut
+
+sub project_dir {
+ my($self, $project) = @_;
+
+ my $dir = blessed($project) && $project->isa('Path::Class::Dir')
+ ? $project->stringify
+ : $self->dir_from_project_name($project);
+
+ $dir .= '/.git'
+ if -f dir($dir)->file('.git/HEAD');
+
+ return $dir;
+}
+
+=head2 run_cmd_in
+
+Run a C<git> command in a given project and return the output as a string.
+
+=cut
+
+sub run_cmd_in {
+ my ($self, $project, @args) = @_;
+
+ return $self->run_cmd('--git-dir' => $self->project_dir($project), @args);
+}
+
+=head2 command
+
+Run a C<git> command for the project specified in the C<p> parameter and
+return the output as a list of strings corresponding to the lines of output.
+
+=cut
+
+sub command {
+ my($self, @args) = @_;
+
+ my $output = $self->run_cmd('--git-dir' => $self->project_dir($self->project), @args);
+
+ return $output ? split(/\n/, $output) : ();
}
+=head2 project_info
+
+Returns a hash corresponding to a given project's properties. The keys will
+be:
+
+ name
+ description (empty if .git/description is empty/unnamed)
+ owner
+ last_change
+
+=cut
+
+sub project_info {
+ my ($self, $project) = @_;
+
+ return {
+ name => $project,
+ $self->get_project_properties(
+ $self->dir_from_project_name($project),
+ ),
+ };
+}
+
+=head2 get_project_properties
+
+Called by C<project_info> to get a project's properties.
+
+=cut
+
sub get_project_properties {
- my ($self, $dir) = @_;
- my %props;
+ my ($self, $dir) = @_;
+ my %props;
- eval {
- $props{description} = $dir->file('description')->slurp;
- chomp $props{description};
+ eval {
+ $props{description} = $dir->file('description')->slurp;
+ chomp $props{description};
};
- if ($props{description} && $props{description} =~ /^Unnamed repository;/) {
- delete $props{description};
- }
+ if ($props{description} && $props{description} =~ /^Unnamed repository;/) {
+ delete $props{description};
+ }
- $props{owner} = (getpwuid $dir->stat->uid)[6];
+ ($props{owner} = (getpwuid $dir->stat->uid)[6]) =~ s/,+$//;
- my $output = $self->run_cmd_in($dir, qw{
- for-each-ref --format=%(committer)
- --sort=-committerdate --count=1 refs/heads
- });
+ my $output = $self->run_cmd_in($dir, qw{
+ for-each-ref --format=%(committer)
+ --sort=-committerdate --count=1 refs/heads
+ });
- if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
- my $dt = DateTime->from_epoch(epoch => $epoch);
- $dt->set_time_zone($tz);
- $props{last_change} = $dt;
- }
+ if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
+ my $dt = DateTime->from_epoch(epoch => $epoch);
+ $dt->set_time_zone($tz);
+ $props{last_change} = $dt;
+ }
- return %props;
+ return %props;
}
-has repo_dir => ( isa => NonEmptySimpleStr, required => 1, is => 'ro' ); # Fixme - path::class
+=head2 list_projects
+
+For the C<repo_dir> specified in the config return an array of projects where
+each item will contain the contents of L</project_info>.
+
+=cut
sub list_projects {
- my ($self) = @_;
+ my ($self, $dir) = @_;
- my $base = dir($self->repo_dir);
-
- my @ret;
- my $dh = $base->open;
- while (my $file = $dh->read) {
- next if $file =~ /^.{1,2}$/;
-
- my $obj = $base->subdir($file);
- next unless -d $obj;
- next unless $self->is_git_repo($obj);
- # XXX Leaky abstraction alert!
- my $is_bare = !-d $obj->subdir('.git');
-
- my $name = (File::Spec->splitdir($obj))[-1];
- push @ret, {
- name => ($name . ( $is_bare ? '' : '/.git' )),
- $self->get_project_properties(
- $is_bare ? $obj : $obj->subdir('.git')
- ),
- };
- }
+ my $base = dir($dir || $self->repo_dir);
- return [sort { $a->{name} cmp $b->{name} } @ret];
-}
+ my @ret;
+ my $dh = $base->open;
+ while (my $file = $dh->read) {
+ next if $file =~ /^.{1,2}$/;
-sub run_cmd {
- my ($self, @args) = @_;
+ my $obj = $base->subdir($file);
+ next unless -d $obj;
+ next unless $self->is_git_repo($obj);
- open my $fh, '-|', $self->git, @args
- or die "failed to run git command";
- binmode $fh, ':encoding(UTF-8)';
+ # XXX Leaky abstraction alert!
+ my $is_bare = !-d $obj->subdir('.git');
- my $output = do { local $/ = undef; <$fh> };
- close $fh;
+ my $name = (File::Spec->splitdir($obj))[-1];
+ push @ret, {
+ name => ($name . ( $is_bare ? '' : '/.git' )),
+ $self->get_project_properties(
+ $is_bare ? $obj : $obj->subdir('.git')
+ ),
+ };
+ }
- return $output;
+ return [sort { $a->{name} cmp $b->{name} } @ret];
}
-sub run_cmd_in {
- my ($self, $project, @args) = @_;
+=head2 dir_from_project_name
- my $path;
- if (blessed($project) && $project->isa('Path::Class::Dir')) {
- $path = $project->stringify;
- }
- else {
- $path = $self->git_dir_from_project_name($project);
- }
- return $self->run_cmd('--git-dir' => $path, @args);
-}
+Get the corresponding directory of a given project.
+
+=cut
-sub git_dir_from_project_name {
- my ($self, $project) = @_;
+sub dir_from_project_name {
+ my ($self, $project) = @_;
- return dir($self->repo_dir)->subdir($project);
+ return dir($self->repo_dir)->subdir($project);
}
-sub get_head_hash {
- my ($self, $project) = @_;
+=head2 head_hash
+
+Find the hash of a given head (defaults to HEAD) of given (or current) project.
- my $output = $self->run_cmd_in($project, qw/rev-parse --verify HEAD/ );
- return unless defined $output;
+=cut
- my ($head) = $output =~ /^([0-9a-fA-F]{40})$/;
- return $head;
+sub head_hash {
+ my ($self, $head, $project) = @_;
+
+ my $output = $self->run_cmd_in($project || $self->project, qw/rev-parse --verify/, $head || 'HEAD' );
+ return unless defined $output;
+
+ my($sha1) = $output =~ /^($SHA1RE)$/;
+ return $sha1;
}
+=head2 list_tree
+
+For a given tree sha1 return an array describing the tree's contents. Where
+the keys for each item will be:
+
+ mode
+ type
+ object
+ file
+
+=cut
+
sub list_tree {
- my ($self, $project, $rev) = @_;
+ my ($self, $rev, $project) = @_;
- $rev ||= $self->get_head_hash($project);
+ $project ||= $self->project;
+ $rev ||= $self->head_hash($project);
- my $output = $self->run_cmd_in($project, qw/ls-tree -z/, $rev);
- return unless defined $output;
+ my $output = $self->run_cmd_in($project, qw/ls-tree -z/, $rev);
+ return unless defined $output;
- my @ret;
- for my $line (split /\0/, $output) {
- my ($mode, $type, $object, $file) = split /\s+/, $line, 4;
+ my @ret;
+ for my $line (split /\0/, $output) {
+ my ($mode, $type, $object, $file) = split /\s+/, $line, 4;
- push @ret, {
- mode => oct $mode,
- type => $type,
- object => $object,
- file => $file,
- };
- }
+ push @ret, {
+ mode => oct $mode,
+ # XXX I wonder why directories always turn up as 040000 ...
+ modestr => $self->get_object_mode_string({mode=>oct $mode}),
+ type => $type,
+ object => $object,
+ file => $file,
+ };
+ }
- return @ret;
+ return @ret;
}
+=head2 get_object_mode_string
+
+Provide a string equivalent of an octal mode e.g 0644 eq '-rw-r--r--'.
+
+=cut
+
sub get_object_mode_string {
- my ($self, $object) = @_;
+ my ($self, $object) = @_;
- return unless $object && $object->{mode};
- return mode_to_string($object->{mode});
+ return unless $object && $object->{mode};
+ return mode_to_string($object->{mode});
}
+=head2 get_object_type
+
+=cut
+
sub get_object_type {
- my ($self, $project, $object) = @_;
+ my ($self, $object, $project) = @_;
- my $output = $self->run_cmd_in($project, qw/cat-file -t/, $object);
- return unless $output;
+ chomp(my $output = $self->run_cmd_in($project || $self->project, qw/cat-file -t/, $object));
+ return unless $output;
- chomp $output;
- return $output;
+ return $output;
}
+=head2 cat_file
+
+Return the contents of a given file.
+
+=cut
+
sub cat_file {
- my ($self, $project, $object) = @_;
+ my ($self, $object, $project) = @_;
- my $type = $self->get_object_type($project, $object);
- die "object `$object' is not a file\n"
- if (!defined $type || $type ne 'blob');
+ my $type = $self->get_object_type($object);
+ die "object `$object' is not a file\n"
+ if (!defined $type || $type ne 'blob');
- my $output = $self->run_cmd_in($project, qw/cat-file -p/, $object);
- return unless $output;
+ my $output = $self->run_cmd_in($project || $self->project, qw/cat-file -p/, $object);
+ return unless $output;
- return $output;
+ return $output;
}
+=head2 hash_by_path
+
+For a given sha1 and path find the corresponding hash. Useful for find blobs.
+
+=cut
+
+sub hash_by_path {
+ my($self, $base, $path, $type) = @_;
+
+ $path =~ s{/+$}();
+
+ my($line) = $self->command('ls-tree', $base, '--', $path)
+ or return;
+
+ #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
+ $line =~ m/^([0-9]+) (.+) ($SHA1RE)\t/;
+ return defined $type && $type ne $2
+ ? ()
+ : $3;
+}
+
+=head2 valid_rev
+
+Check whether a given rev is valid i.e looks like a sha1.
+
+=cut
+
sub valid_rev {
- my ($self, $rev) = @_;
+ my ($self, $rev) = @_;
- return unless $rev;
- return ($rev =~ /^([0-9a-fA-F]{40})$/);
+ return unless $rev;
+ return ($rev =~ /^($SHA1RE)$/);
}
-sub diff {
- my ($self, $project, @revs) = @_;
+=head2 raw_diff
+
+Provides the raw output of a diff.
+
+=cut
+
+# gitweb uses the following sort of command for diffing merges:
+# /home/dbrook/apps/bin/git --git-dir=/home/dbrook/dev/app/.git diff-tree -r -M --no-commit-id --patch-with-raw --full-index --cc 316cf158df3f6207afbae7270bcc5ba0 --
+# and for regular diffs
+# /home/dbrook/apps/bin/git --git-dir=/home/dbrook/dev/app/.git diff-tree -r -M --no-commit-id --patch-with-raw --full-index 2e3454ca0749641b42f063730b0090e1 316cf158df3f6207afbae7270bcc5ba0 --
- croak("Gitalist::Model::Git::diff needs a project and either one or two revisions")
- if scalar @revs < 1
- || scalar @revs > 2
- || any { !$self->valid_rev($_) } @revs;
+sub raw_diff {
+ my ($self, @args) = @_;
- my $output = $self->run_cmd_in($project, 'diff', @revs);
- return unless $output;
+ return $self->command(
+ qw(diff-tree -r -M --no-commit-id --full-index),
+ @args
+ );
+}
- return $output;
+=pod
+diff --git a/TODO b/TODO
+index 6a05e77..2071fd0 100644
+--- a/TODO
++++ b/TODO
+@@ -2,4 +2,3 @@
+ * An action to find what branches have been merged, either as a list or through a search mechanism.
+ * An action to find which branches a given commit is on.
+ * Fix any not text/html bits e.g the patch action.
+-* Simplify the creation of links.
+diff --git a/lib/Gitalist/Controller/Root.pm b/lib/Gitalist/Controller/Root.pm
+index 706d024..7fac165 100644
+--- a/lib/Gitalist/Controller/Root.pm
++++ b/lib/Gitalist/Controller/Root.pm
+@@ -157,23 +157,6 @@ sub shortlog : Local {
+ );
+ }
+
+-=head2 tree
+-
+-The tree of a given commit.
+=cut
+
+=head2 diff
+
+Returns a list of diff chunks corresponding to the files contained in the diff
+and some associated metadata.
+
+=cut
+
+# XXX Ideally this would return a wee object instead of ad hoc structures.
+sub diff {
+ my($self, %args) = @_;
+
+ # So either a parent is specifed, or we use the commit's parent if there's
+ # only one, otherwise it was a merge commit.
+ my $parent = $args{parent}
+ ? $args{parent}
+ : @{$args{commit}->parents} <= 1
+ ? $args{commit}->parent_sha1
+ : '-c';
+ my @etc = (
+ ( $args{file} ? ('--', $args{file}) : () ),
+ );
+
+ my @out = $self->raw_diff(
+ ( $args{patch} ? '--patch-with-raw' : () ),
+ $parent, $args{commit}->sha1, @etc
+ );
+
+ # XXX Yes, there is much wrongness having parse_diff_tree be destructive.
+ my @difftree = $self->parse_diff_tree(\@out);
+
+ return \@difftree
+ unless $args{patch};
+
+ # The blank line between the tree and the patch.
+ shift @out;
+
+ # XXX And no I'm not happy about having diff return tree + patch.
+ return \@difftree, [$self->parse_diff(@out)];
}
-{
- my $formatter = DateTime::Format::Mail->new;
+sub parse_diff {
+ my($self, @diff) = @_;
+
+ my @ret;
+ for (@diff) {
+ # This regex is a little pathological.
+ if(m{^diff --git (a/(.*?)) (b/\2)}) {
+ push @ret, {
+ head => $_,
+ a => $1,
+ b => $3,
+ file => $2,
+ diff => '',
+ };
+ next;
+ }
+
+ if(/^index (\w+)\.\.(\w+) (\d+)$/) {
+ @{$ret[-1]}{qw(index src dst mode)} = ($_, $1, $2, $3);
+ next
+ }
+
+ # XXX Somewhat hacky. Ahem.
+ $ret[@ret ? -1 : 0]{diff} .= "$_\n";
+ }
- sub parse_rev_list {
- my ($self, $output) = @_;
- my @ret;
+ return @ret;
+}
- my @revs = split /\0/, $output;
+# $ git diff-tree -r --no-commit-id -M b222ff0a7260cc1777c7e455dfcaf22551a512fc 7e54e579e196c6c545fee1030175f65a111039d4
+# :100644 100644 6a85d6c6315b55a99071974eb6ce643aeb2799d6 44c03ed6c328fa6de4b1d9b3f19a3de96b250370 M templates/blob.tt2
+
+=head2 parse_diff_tree
+
+Given a L<Git::PurePerl> commit object return a list of hashes corresponding
+to the C<diff-tree> output.
+
+=cut
+
+sub parse_diff_tree {
+ my($self, $diff) = @_;
+
+ my @keys = qw(modesrc modedst sha1src sha1dst status src dst);
+ my @ret;
+ while(@$diff and $diff->[0] =~ /^:\d+/) {
+ my $line = shift @$diff;
+ # see. man git-diff-tree for more info
+ # mode src, mode dst, sha1 src, sha1 dst, status, src[, dst]
+ my @vals = $line =~ /^:(\d+) (\d+) ($SHA1RE) ($SHA1RE) ([ACDMRTUX]\d*)\t([^\t]+)(?:\t([^\n]+))?$/;
+ my %line = zip @keys, @vals;
+ # Some convenience keys
+ $line{file} = $line{src};
+ $line{sha1} = $line{sha1dst};
+ $line{is_new} = $line{sha1src} =~ /^0+$/
+ if $line{sha1src};
+ @line{qw/status sim/} = $line{status} =~ /(R)(\d+)/
+ if $line{status} =~ /^R/;
+ push @ret, \%line;
+ }
+
+ return @ret;
+}
- for my $rev (split /\0/, $output) {
- for my $line (split /\n/, $rev, 6) {
- chomp $line;
- next unless $line;
+=head2 parse_rev_list
- if ($self->valid_rev($line)) {
- push @ret, {rev => $line};
- next;
- }
+Given the output of the C<rev-list> command return a list of hashes.
- if (my ($key, $value) = $line =~ /^(tree|parent)\s+(.*)$/) {
- $ret[-1]->{$key} = $value;
- next;
- }
+=cut
- if (my ($key, $value, $epoch, $tz) = $line =~ /^(author|committer)\s+(.*)\s+(\d+)\s+([+-]\d+)$/) {
- $ret[-1]->{$key} = $value;
- eval {
- $ret[-1]->{ $key . "_datetime" } = DateTime->from_epoch(epoch => $epoch);
- $ret[-1]->{ $key . "_datetime" }->set_time_zone($tz);
- $ret[-1]->{ $key . "_datetime" }->set_formatter($formatter);
- };
+sub parse_rev_list {
+ my ($self, $output) = @_;
+ my @ret;
- if ($@) {
- $ret[-1]->{ $key . "_datetime" } = "$epoch $tz";
- }
+ my @revs = split /\0/, $output;
- if (my ($name, $email) = $value =~ /^([^<]+)\s+<([^>]+)>$/) {
- $ret[-1]->{ $key . "_name" } = $name;
- $ret[-1]->{ $key . "_email" } = $email;
- }
- }
+ for my $rev (split /\0/, $output) {
+ for my $line (split /\n/, $rev, 6) {
+ chomp $line;
+ next unless $line;
- $line =~ s/^\n?\s{4}//;
- $ret[-1]->{longmessage} = $line;
- $ret[-1]->{message} = (split /\n/, $line, 2)[0];
- }
- }
+ if ($self->valid_rev($line)) {
+ push @ret, $self->get_object($line);
+ }
+ }
+ }
- return @ret;
- }
+ return @ret;
}
+=head2 list_revs
+
+Calls the C<rev-list> command (a low-level from of C<log>) and returns an
+array of hashes.
+
+=cut
+
sub list_revs {
- my ($self, $project, %args) = @_;
+ my ($self, %args) = @_;
- $args{rev} ||= $self->get_head_hash($project);
+ $args{sha1} ||= $self->head_hash($args{project});
- my $output = $self->run_cmd_in($project, 'rev-list',
- '--header',
- (defined $args{ count } ? "--max-count=$args{count}" : ()),
- (defined $args{ skip } ? "--skip=$args{skip}" : ()),
- $args{rev},
- '--',
- ($args{file} || ()),
- );
- return unless $output;
+ my $output = $self->run_cmd_in($args{project} || $self->project, 'rev-list',
+ '--header',
+ (defined $args{ count } ? "--max-count=$args{count}" : ()),
+ (defined $args{ skip } ? "--skip=$args{skip}" : ()),
+ $args{sha1},
+ '--',
+ ($args{file} ? $args{file} : ()),
+ );
+ return unless $output;
- my @revs = $self->parse_rev_list($output);
+ my @revs = $self->parse_rev_list($output);
- return \@revs;
+ return @revs;
}
+=head2 rev_info
+
+Get a single piece of revision information for a given sha1.
+
+=cut
+
sub rev_info {
- my ($self, $project, $rev) = @_;
+ my($self, $rev, $project) = @_;
+
+ return unless $self->valid_rev($rev);
+
+ return $self->list_revs(
+ rev => $rev, count => 1,
+ ( $project ? (project => $project) : () )
+ );
+}
+
+=head2 reflog
+
+Calls the C<reflog> command and returns a list of hashes.
+
+=cut
+
+sub reflog {
+ my ($self, @logargs) = @_;
+
+ my @entries
+ = $self->run_cmd_in($self->project, qw(log -g), @logargs)
+ =~ /(^commit.+?(?:(?=^commit)|(?=\z)))/msg;
+
+=pod
+ commit 02526fc15beddf2c64798a947fecdd8d11bf993d
+ Reflog: HEAD@{14} (The Git Server <git@git.dev.venda.com>)
+ Reflog message: push
+ Author: Foo Barsby <fbarsby@example.com>
+ Date: Thu Sep 17 12:26:05 2009 +0100
+
+ Merge branch 'abc123'
+=cut
+
+ return map {
+
+ # XXX Stuff like this makes me want to switch to Git::PurePerl
+ my($sha1, $type, $author, $date)
+ = m{
+ ^ commit \s+ ($SHA1RE)$
+ .*?
+ Reflog[ ]message: \s+ (.+?)$ \s+
+ Author: \s+ ([^<]+) <.*?$ \s+
+ Date: \s+ (.+?)$
+ }xms;
- return unless $self->valid_rev($rev);
+ pos($_) = index($_, $date) + length $date;
- return $self->list_revs($project, rev => $rev, count => 1);
+ # Yeah, I just did that.
+
+ my($msg) = /\G\s+(\S.*)/sg;
+
+ {
+ hash => $sha1,
+ type => $type,
+ author => $author,
+
+ # XXX Add DateTime goodness.
+ date => $date,
+ message => $msg,
+ };
+ } @entries;
}
-sub get_heads {
- my ($self, $project) = @_;
+=head2 heads
+
+Returns an array of hashes representing the heads (aka branches) for the
+given, or current, project.
+
+=cut
- my $output = $self->run_cmd_in($project, qw/for-each-ref --sort=-committerdate /, '--format=%(objectname)%00%(refname)%00%(committer)', 'refs/heads');
- return unless $output;
+sub heads {
+ my ($self, $project) = @_;
- my @ret;
- for my $line (split /\n/, $output) {
- my ($rev, $head, $commiter) = split /\0/, $line, 3;
- $head =~ s!^refs/heads/!!;
+ my @output = $self->command(qw/for-each-ref --sort=-committerdate /, '--format=%(objectname)%00%(refname)%00%(committer)', 'refs/heads');
- push @ret, { rev => $rev, name => $head };
+ my @ret;
+ for my $line (@output) {
+ my ($rev, $head, $commiter) = split /\0/, $line, 3;
+ $head =~ s!^refs/heads/!!;
- #FIXME: That isn't the time I'm looking for..
- if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
- my $dt = DateTime->from_epoch(epoch => $epoch);
- $dt->set_time_zone($tz);
- $ret[-1]->{last_change} = $dt;
- }
+ push @ret, { sha1 => $rev, name => $head };
+
+ #FIXME: That isn't the time I'm looking for..
+ if (my ($epoch, $tz) = $line =~ /\s(\d+)\s+([+-]\d+)$/) {
+ my $dt = DateTime->from_epoch(epoch => $epoch);
+ $dt->set_time_zone($tz);
+ $ret[-1]->{last_change} = $dt;
}
+ }
- return \@ret;
+ return @ret;
}
-sub archive {
- my ($self, $project, $rev) = @_;
+=head2 refs_for
+
+For a given sha1 check which branches currently point at it.
+
+=cut
+
+sub refs_for {
+ my($self, $sha1) = @_;
+
+ my $refs = $self->references->{$sha1};
+
+ return $refs ? @$refs : ();
+}
+
+=head2 references
+
+A wrapper for C<git show-ref --dereference>. Based on gitweb's
+C<git_get_references>.
+
+=cut
+
+sub references {
+ my($self) = @_;
+
+ return $self->{references}
+ if $self->{references};
+
+ # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
+ # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
+ my @reflist = $self->command(qw(show-ref --dereference))
+ or return;
+
+ my %refs;
+ for(@reflist) {
+ push @{$refs{$1}}, $2
+ if m!^($SHA1RE)\srefs/(.*)$!;
+ }
- #FIXME: huge memory consuption
- #TODO: compression
- return $self->run_cmd_in($project, qw/archive --format=tar/, "--prefix=${project}/", $rev);
+ return $self->{references} = \%refs;
}
1;
=head1 AUTHOR
-Dan Brook,,,
+Dan Brook
=head1 LICENSE
--- /dev/null
+package Gitalist::View::SyntaxHighlight;
+use Moose;
+use Gitalist; # ->path_to
+use namespace::autoclean;
+
+extends 'Catalyst::View';
+
+use Syntax::Highlight::Engine::Kate ();
+use Syntax::Highlight::Engine::Kate::Perl ();
+
+use HTML::Entities qw(encode_entities);
+
+=begin
+
+B<Notes>
+
+What should be done, but isn't currently:
+
+ broquaint> Another Cat question - if I want to have arbitrary things highlighted is pushing things through a View at all costs terribly wrong?
+ broquaint> e.g modifying this slightly to highlight anything (or arrays of anything) http://github.com/broquaint/Gitalist/blob/a7cc1ede5f9729465bb53da9c3a8b300a3aa8a0a/lib/Gitalist/View/SyntaxHighlight.pm
+ t0m> no, that's totally fine.. I'd tend to push the rendering logic into a model, so you end up doing something like: $c->model('SyntaxDriver')->highlight_all($stuff, $c->view('SyntaxHighlight'));
+ broquaint> I'm thinking it's a bad idea because the Controller needs to munge data such that the View knows what to do
+ broquaint> You just blew my mind ;)
+ t0m> ^^ That works _much_ better if you split up your view methods into process & render..
+ t0m> ala TT..
+ t0m> i.e. I'd have 'highlight this scalar' as the ->render method in the view..
+ t0m> And then the 'default' thing (i.e. process method) will do that and shove the output in the body..
+ t0m> but then you can write foreach my $thing (@things) { push(@highlighted_things, $c->view('SyntaxHighlight')->render($thing)); }
+ t0m> and then I'd move that ^^ loop down into a model which actually knows about / abstracts walking the data structures concerned..
+ t0m> But splitting render and process is the most important bit.. :) Otherwise you need to jump through hoops to render things that don't fit 'nicely' into the bits of stash / body that the view uses by 'default'
+ t0m> I wouldn't kill you for putting the structure walking code in the view given you're walking simple arrays / hashes.. It becomes more important if you have a more complex visitor..
+ t0m> (I use Visitor in the design patterns sense)
+ t0m> As the visitor is responsible for walking the structure, delegating to the ->render call in the view which is responsible for actually mangling the content..
+
+=cut
+
+sub process {
+ my($self, $c) = @_;
+
+ for($c->stash->{blobs} ? @{$c->stash->{blobs}} : $c->stash->{blob}) {
+ $_ = $self->highlight($c->stash->{language} => $_);
+ }
+
+ $c->forward('View::Default');
+}
+
+# XXX This takes for freakin' ever on big merges. A cache may be needed.
+sub highlight {
+ my($self, $lang, $blob) = @_;
+
+ my $ret;
+ if($lang) {
+ # via http://github.com/jrockway/angerwhale/blob/master/lib/Angerwhale/Format/Pod.pm#L136
+ $ret = eval {
+ no warnings 'redefine';
+ local *Syntax::Highlight::Engine::Kate::Template::logwarning
+ = sub { die @_ }; # i really don't care
+ my $hl = Syntax::Highlight::Engine::Kate->new(
+ language => $lang,
+ substitutions => {
+ "<" => "<",
+ ">" => ">",
+ "&" => "&",
+ q{'} => "'",
+ q{"} => """,
+ },
+ format_table => {
+ # convert Kate's internal representation into
+ # <span class="<internal name>"> value </span>
+ map {
+ $_ => [ qq{<span class="$_">}, '</span>' ]
+ }
+ qw/Alert BaseN BString Char Comment DataType
+ DecVal Error Float Function IString Keyword
+ Normal Operator Others RegionMarker Reserved
+ String Variable Warning/,
+ },
+ );
+
+ $hl->highlightText($blob);
+ };
+ warn $@ if $@;
+ }
+
+ return $ret || encode_entities($blob);
+}
+
+__PACKAGE__->meta->make_immutable;
--- /dev/null
+/* XXX A good framework would be handy */
+/* XXX Also colours. Lots of colours. */
+#body {
+ margin: 1em;
+}
+
+#commit-nav {
+ padding-bottom: 4px;
+ border-bottom: solid 1px #ccc;
+ font-style: italic;
+}
+
+thead, tfoot {
+ color: #ddd;
+ font-size: small;
+ font-weight: bold;
+}
+
+/* /commit page */
+.commit-message {
+ font-family: monospace;
+}
+div.commit-message {
+ background-color: #ddd;
+ padding: 5px;
+}
+.commit-info dt {
+ font-weight: bold;
+}
+.commit-info dd {
+ font-family: monospace;
+}
+
+.filename {
+ font-family: monospace;
+}
+.action-list {
+ font-size: smaller;
+}
+
+.path {
+ border-bottom: solid 1px #ddd;
+ padding: 3px 0;
+ font-weight: bold;
+}
+
+/* /heads */
+.heads .head {
+ font-weight: bold;
+}
+.heads .current {
+ text-decoration: underline;
+}
+
+/* /blob */
+pre.blob {
+ background-color: #333;
+ color: #ddd;
+ border-left: solid 3px #c33;
+ padding: 5px;
+ padding-left: 15px;
+ margin: 10px 15px;
+}
--- /dev/null
+span.Keyword {
+ color: #777;
+ font-weight: bold;
+}
+span.DataType {
+ color: purple;
+}
+span.Normal {
+ color: gray;
+}
+span.String {
+ color: green;
+}
+span.Others {
+ color: red;
+}
--- /dev/null
+span.Alert {
+ color: #0000ff;
+}
+span.BaseN {
+ color: #007f00;
+}
+span.BString {
+ color: #c9a7ff;
+}
+span.Char {
+ color: #ff00ff;
+}
+span.Comment {
+ color: #cc9900;
+}
+span.DataType {
+ color: #00ff55;
+}
+span.DecVal {
+ color: #00ffff;
+}
+span.Error {
+ color: #ff0000;
+}
+span.Float {
+ color: #5599ff;
+}
+span.Function {
+ color: #3344ff;
+}
+span.IString {
+ color: #ff0000;
+}
+span.Keyword {
+ color: #11ffff;
+ font-weight: bold;
+}
+span.Operator {
+ color: #00ff33;
+ font-weight: bold;
+}
+span.Others {
+ color: #b03060;
+}
+span.RegionMarker {
+ color: #96b9ff;
+}
+span.Reserved {
+ color: #9b30ff;
+ font-weight: bold;
+}
+span.String {
+ color: #ffaa55;
+}
+span.Variable {
+ color: #ffff00;
+}
+span.Warning {
+ color: #0000ff;
+}
--- /dev/null
+ref: refs/heads/master
--- /dev/null
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
--- /dev/null
+some test repository
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+ exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by git-commit with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
+
--- /dev/null
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, make this file executable.
+
+: Nothing
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, make this file executable by "chmod +x post-update".
+
+exec git-update-server-info
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
+
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by git-commit with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+# This is slightly modified from Andrew Morton's Perfect Patch.
+# Lines you introduce should not have trailing whitespace.
+# Also check for an indentation that has SP before a TAB.
+
+if git-rev-parse --verify HEAD 2>/dev/null
+then
+ git-diff-index -p -M --cached HEAD
+else
+ # NEEDSWORK: we should produce a diff with an empty tree here
+ # if we want to do the same verification for the initial import.
+ :
+fi |
+perl -e '
+ my $found_bad = 0;
+ my $filename;
+ my $reported_filename = "";
+ my $lineno;
+ sub bad_line {
+ my ($why, $line) = @_;
+ if (!$found_bad) {
+ print STDERR "*\n";
+ print STDERR "* You have some suspicious patch lines:\n";
+ print STDERR "*\n";
+ $found_bad = 1;
+ }
+ if ($reported_filename ne $filename) {
+ print STDERR "* In $filename\n";
+ $reported_filename = $filename;
+ }
+ print STDERR "* $why (line $lineno)\n";
+ print STDERR "$filename:$lineno:$line\n";
+ }
+ while (<>) {
+ if (m|^diff --git a/(.*) b/\1$|) {
+ $filename = $1;
+ next;
+ }
+ if (/^@@ -\S+ \+(\d+)/) {
+ $lineno = $1 - 1;
+ next;
+ }
+ if (/^ /) {
+ $lineno++;
+ next;
+ }
+ if (s/^\+//) {
+ $lineno++;
+ chomp;
+ if (/\s$/) {
+ bad_line("trailing whitespace", $_);
+ }
+ if (/^\s* /) {
+ bad_line("indent SP followed by a TAB", $_);
+ }
+ if (/^(?:[<>=]){7}/) {
+ bad_line("unresolved merge conflict", $_);
+ }
+ }
+ }
+ exit($found_bad);
+'
+
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+ topic="refs/heads/$2"
+else
+ topic=`git symbolic-ref HEAD`
+fi
+
+case "$basebranch,$topic" in
+master,refs/heads/??/*)
+ ;;
+*)
+ exit 0 ;# we do not interrupt others.
+ ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master. Is it OK to rebase it?
+
+# Is topic fully merged to master?
+not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+ echo >&2 "$topic is fully merged to master; better remove it."
+ exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next? If so you should not be rebasing it.
+only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git-rev-list ^master ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+ not_in_topic=`git-rev-list "^$topic" master`
+ if test -z "$not_in_topic"
+ then
+ echo >&2 "$topic is already up-to-date with master"
+ exit 1 ;# we could allow it, but there is no point.
+ else
+ exit 0
+ fi
+else
+ not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
+ perl -e '
+ my $topic = $ARGV[0];
+ my $msg = "* $topic has commits already merged to public branch:\n";
+ my (%not_in_next) = map {
+ /^([0-9a-f]+) /;
+ ($1 => 1);
+ } split(/\n/, $ARGV[1]);
+ for my $elem (map {
+ /^([0-9a-f]+) (.*)$/;
+ [$1 => $2];
+ } split(/\n/, $ARGV[2])) {
+ if (!exists $not_in_next{$elem->[0]}) {
+ if ($msg) {
+ print STDERR $msg;
+ undef $msg;
+ }
+ print STDERR " $elem->[1]\n";
+ }
+ }
+ ' "$topic" "$not_in_next" "$not_in_master"
+ exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+ merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+ it is deleted. If you need to build on top of it to correct
+ earlier mistakes, a new topic branch is created by forking at
+ the tip of the "master". This is not strictly necessary, but
+ it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+ branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next". Young
+ topic branches can have stupid mistakes you would rather
+ clean up before publishing, and things that have not been
+ merged into other branches can be easily rebased without
+ affecting other people. But once it is published, you would
+ not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+ Then you can delete it. More importantly, you should not
+ build on top of it -- other people may already want to
+ change things related to the topic as patches against your
+ "master", so if you need further changes, it is better to
+ fork the topic (perhaps with the same name) afresh from the
+ tip of "master".
+
+Let's look at this example:
+
+ o---o---o---o---o---o---o---o---o---o "next"
+ / / / /
+ / a---a---b A / /
+ / / / /
+ / / c---c---c---c B /
+ / / / \ /
+ / / / b---b C \ /
+ / / / / \ /
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished. It has been fully merged up to "master" and "next",
+ and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+ git-rev-list ^master ^topic next
+ git-rev-list ^master next
+
+ if these match, topic has not merged in next at all.
+
+To compute (2):
+
+ git-rev-list master..topic
+
+ if this is empty, it is fully merged to "master".
--- /dev/null
+#!/bin/sh
+#
+# An example hook script to mail out commit update information.
+# It can also blocks tags that aren't annotated.
+# Called by git-receive-pack with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, make this file executable by "chmod +x update".
+#
+# Config
+# ------
+# hooks.mailinglist
+# This is the list that all pushes will go to; leave it blank to not send
+# emails frequently. The log email will list every log entry in full between
+# the old ref value and the new ref value.
+# hooks.announcelist
+# This is the list that all pushes of annotated tags will go to. Leave it
+# blank to just use the mailinglist field. The announce emails list the
+# short log summary of the changes since the last annotated tag
+# hooks.allowunannotated
+# This boolean sets whether unannotated tags will be allowed into the
+# repository. By default they won't be.
+#
+# Notes
+# -----
+# All emails have their subjects prefixed with "[SCM]" to aid filtering.
+# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
+# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and info.
+
+# --- Constants
+EMAILPREFIX="[SCM] "
+LOGBEGIN="- Log -----------------------------------------------------------------"
+LOGEND="-----------------------------------------------------------------------"
+DATEFORMAT="%F %R %z"
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+ exit 1
+fi
+
+# --- Config
+projectdesc=$(cat $GIT_DIR/description)
+recipients=$(git-repo-config hooks.mailinglist)
+announcerecipients=$(git-repo-config hooks.announcelist)
+allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
+
+# --- Check types
+newrev_type=$(git-cat-file -t "$newrev")
+
+case "$refname","$newrev_type" in
+ refs/tags/*,commit)
+ # un-annotated tag
+ refname_type="tag"
+ short_refname=${refname##refs/tags/}
+ if [ $allowunannotated != "true" ]; then
+ echo "*** The un-annotated tag, $short_refname is not allowed in this repository" >&2
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,tag)
+ # annotated tag
+ refname_type="annotated tag"
+ short_refname=${refname##refs/tags/}
+ # change recipients
+ if [ -n "$announcerecipients" ]; then
+ recipients="$announcerecipients"
+ fi
+ ;;
+ refs/heads/*,commit)
+ # branch
+ refname_type="branch"
+ short_refname=${refname##refs/heads/}
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ refname_type="tracking branch"
+ short_refname=${refname##refs/remotes/}
+ # Should this even be allowed?
+ echo "*** Push-update of tracking branch, $refname. No email generated." >&2
+ exit 0
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update, \"$newrev_type\", to ref $refname" >&2
+ exit 1
+ ;;
+esac
+
+# Check if we've got anyone to send to
+if [ -z "$recipients" ]; then
+ # If the email isn't sent, then at least give the user some idea of what command
+ # would generate the email at a later date
+ echo "*** No recipients found - no email will be sent, but the push will continue" >&2
+ echo "*** for $0 $1 $2 $3" >&2
+ exit 0
+fi
+
+# --- Email parameters
+committer=$(git show --pretty=full -s $newrev | grep "^Commit: " | sed -e "s/^Commit: //")
+describe=$(git describe $newrev 2>/dev/null)
+if [ -z "$describe" ]; then
+ describe=$newrev
+fi
+
+# --- Email (all stdout will be the email)
+(
+# Generate header
+cat <<-EOF
+From: $committer
+To: $recipients
+Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname now at $describe
+X-Git-Refname: $refname
+X-Git-Reftype: $refname_type
+X-Git-Oldrev: $oldrev
+X-Git-Newrev: $newrev
+
+Hello,
+
+This is an automated email from the git hooks/update script, it was
+generated because a ref change was pushed to the repository.
+
+Updating $refname_type, $short_refname,
+EOF
+
+case "$refname_type" in
+ "tracking branch"|branch)
+ if expr "$oldrev" : '0*$' >/dev/null
+ then
+ # If the old reference is "0000..0000" then this is a new branch
+ # and so oldrev is not valid
+ echo " as a new $refname_type"
+ echo " to $newrev ($newrev_type)"
+ echo ""
+ echo $LOGBEGIN
+ # This shows all log entries that are not already covered by
+ # another ref - i.e. commits that are now accessible from this
+ # ref that were previously not accessible
+ git-rev-parse --not --all | git-rev-list --stdin --pretty $newref
+ echo $LOGEND
+ else
+ # oldrev is valid
+ oldrev_type=$(git-cat-file -t "$oldrev")
+
+ # Now the problem is for cases like this:
+ # * --- * --- * --- * (oldrev)
+ # \
+ # * --- * --- * (newrev)
+ # i.e. there is no guarantee that newrev is a strict subset
+ # of oldrev - (would have required a force, but that's allowed).
+ # So, we can't simply say rev-list $oldrev..$newrev. Instead
+ # we find the common base of the two revs and list from there
+ baserev=$(git-merge-base $oldrev $newrev)
+
+ # Commit with a parent
+ for rev in $(git-rev-list $newrev ^$baserev)
+ do
+ revtype=$(git-cat-file -t "$rev")
+ echo " via $rev ($revtype)"
+ done
+ if [ "$baserev" = "$oldrev" ]; then
+ echo " from $oldrev ($oldrev_type)"
+ else
+ echo " based on $baserev"
+ echo " from $oldrev ($oldrev_type)"
+ echo ""
+ echo "This ref update crossed a branch point; i.e. the old rev is not a strict subset"
+ echo "of the new rev. This occurs, when you --force push a change in a situation"
+ echo "like this:"
+ echo ""
+ echo " * -- * -- B -- O -- O -- O ($oldrev)"
+ echo " \\"
+ echo " N -- N -- N ($newrev)"
+ echo ""
+ echo "Therefore, we assume that you've already had alert emails for all of the O"
+ echo "revisions, and now give you all the revisions in the N branch from the common"
+ echo "base, B ($baserev), up to the new revision."
+ fi
+ echo ""
+ echo $LOGBEGIN
+ git-rev-list --pretty $newrev ^$baserev
+ echo $LOGEND
+ echo ""
+ echo "Diffstat:"
+ git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev
+ fi
+ ;;
+ "annotated tag")
+ # Should we allow changes to annotated tags?
+ if expr "$oldrev" : '0*$' >/dev/null
+ then
+ # If the old reference is "0000..0000" then this is a new atag
+ # and so oldrev is not valid
+ echo " to $newrev ($newrev_type)"
+ else
+ echo " to $newrev ($newrev_type)"
+ echo " from $oldrev"
+ fi
+
+ # If this tag succeeds another, then show which tag it replaces
+ prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//')
+ if [ -n "$prevtag" ]; then
+ echo " replaces $prevtag"
+ fi
+
+ # Read the tag details
+ eval $(git cat-file tag $newrev | \
+ sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
+ tagged=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$DATEFORMAT")
+
+ echo " tagged by $tagger"
+ echo " on $tagged"
+
+ echo ""
+ echo $LOGBEGIN
+ echo ""
+
+ if [ -n "$prevtag" ]; then
+ git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
+ else
+ git rev-list --pretty=short $newrev | git shortlog
+ fi
+
+ echo $LOGEND
+ echo ""
+ ;;
+ *)
+ # By default, unannotated tags aren't allowed in; if
+ # they are though, it's debatable whether we would even want an
+ # email to be generated; however, I don't want to add another config
+ # option just for that.
+ #
+ # Unannotated tags are more about marking a point than releasing
+ # a version; therefore we don't do the shortlog summary that we
+ # do for annotated tags above - we simply show that the point has
+ # been marked, and print the log message for the marked point for
+ # reference purposes
+ #
+ # Note this section also catches any other reference type (although
+ # there aren't any) and deals with them in the same way.
+ if expr "$oldrev" : '0*$' >/dev/null
+ then
+ # If the old reference is "0000..0000" then this is a new tag
+ # and so oldrev is not valid
+ echo " as a new $refname_type"
+ echo " to $newrev ($newrev_type)"
+ else
+ echo " to $newrev ($newrev_type)"
+ echo " from $oldrev"
+ fi
+ echo ""
+ echo $LOGBEGIN
+ git-show --no-color --root -s $newrev
+ echo $LOGEND
+ echo ""
+ ;;
+esac
+
+# Footer
+cat <<-EOF
+
+hooks/update
+---
+Git Source Code Management System
+$0 $1 \\
+ $2 \\
+ $3
+EOF
+#) | cat >&2
+) | /usr/sbin/sendmail -t
+
+# --- Finished
+exit 0
--- /dev/null
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
--- /dev/null
+xÎAj\ 31\f@Ñ®}
+í\vdGc(¥«\1e 7°,95L2Ÿ\14zú\1e!Û\ f\ f~=n·>!Æü4;Ь²·d¢¾-U³rL&¨[q7ôê\1a>Ëðû\ 4nV©¢ÖÄŵ-9®KfÍÑ8oÌL´z(_óã\18ð¾\1f£;\Êõ»Ï\1fx\19¥íoæú\17OǸ¾\ 2p$à\19 1ÔÿÅé\ fáPÌÀú së»Çð\vk:H
\ No newline at end of file
--- /dev/null
+36c6c6708b8360d7023e8a1649c45bcf9b3bd818
BEGIN { use_ok 'Gitalist::Model::Git' }
my $c = bless {}, 'Gitalist';
-my $Git = Gitalist::Model::Git->new($c, { repo_dir => "$Bin/lib/repositories" });
+my $m = Gitalist::Model::Git->new($c, { repo_dir => "$Bin/lib/repositories" });
+isa_ok($m, 'Gitalist::Model::Git');
# 'bare.git' is a bare git repository in the repository dir
use Path::Class;
my $repoBare = Path::Class::Dir->new('t/lib/repositories/bare.git');
-ok( $Git->is_git_repo( $repoBare ), 'is_git_repo true for bare git repo' );
+ok( $m->is_git_repo( $repoBare ), 'is_git_repo true for bare git repo' );
# 'working' is a working copy w/ git repo in the repository dir
my $repoWorking = Path::Class::Dir->new('t/lib/repositories/working');
-#ok( $Git->is_git_repo( $repoWorking ), 'is_git_repo true for git repo in working copy' );
+#ok( $m->is_git_repo( $repoWorking ), 'is_git_repo true for git repo in working copy' );
# 'empty.git' is an empty directory in the repository dir
my $repoEmpty = Path::Class::Dir->new('t/lib/repositories/empty.git');
-ok( ! $Git->is_git_repo( $repoEmpty ), 'is_git_repo is false for empty dir' );
+ok( ! $m->is_git_repo( $repoEmpty ), 'is_git_repo is false for empty dir' );
-my $projectList = $Git->list_projects;
-ok( scalar @{$projectList} == 1, 'list_projects returns an array with the correct number of members' );
-ok( $projectList->[0]->{name} eq 'bare.git', 'list_projects has correct name for "bare.git" repo' );
+my $projectList = $m->list_projects('t/lib/repositories');
+ok( scalar @{$projectList} == 2, 'list_projects returns an array with the correct number of members' );
+is( $projectList->[0]->{name}, 'bare.git', 'list_projects has correct name for "bare.git" repo' );
#ok( $projectList->[1]->{name} eq 'working/.git', 'list_projects has correct name for "working" repo' );
-use Data::Dumper;
-#warn( Dumper($projectList) );
+# Liberally borrowed from rafl's gitweb
+my $repo = 'repo1';
+
+like($m->head_hash('HEAD', $repo), qr/^([0-9a-fA-F]{40})$/, 'head_hash');
+
+{
+ my @tree = $m->list_tree('3bc0634310b9c62222bb0e724c11ffdfb297b4ac', $repo);
+
+ is(scalar @tree, 1);
+ is_deeply($tree[0], {
+ mode => oct 100644,
+ type => 'blob',
+ object => '257cc5642cb1a054f08cc83f2d943e56fd3ebe99',
+ file => 'file1'
+ });
+
+ is($m->get_object_mode_string($tree[0]), '-rw-r--r--');
+}
+
+is($m->get_object_type('729a7c3f6ba5453b42d16a43692205f67fb23bc1', $repo), 'tree');
+is($m->get_object_type('257cc5642cb1a054f08cc83f2d943e56fd3ebe99', $repo), 'blob');
+is($m->get_object_type('5716ca5987cbf97d6bb54920bea6adde242d87e6', $repo), 'blob');
+
+is($m->cat_file('257cc5642cb1a054f08cc83f2d943e56fd3ebe99', $repo), "foo\n");
+is($m->cat_file('5716ca5987cbf97d6bb54920bea6adde242d87e6', $repo), "bar\n");
+
+is($m->diff('3bc0634310b9c62222bb0e724c11ffdfb297b4ac', '3f7567c7bdf7e7ebf410926493b92d398333116e', $repo), <<EOD);
+diff --git a/file1 b/file1
+index 257cc56..5716ca5 100644
+--- a/file1
++++ b/file1
+@@ -1 +1 @@
+-foo
++bar
+EOD
\ No newline at end of file
--- /dev/null
+<link rel="stylesheet" type="text/css" href="/static/css/syntax/[% language %].css"/>
+[% FOREACH item IN diff %]
+<div class='diff-head'>
+ diff --git
+ <a href='/blob?p=[% project %];f=[% item.file %];h=[% item.src %]'>[% item.a %]</a>
+ <a href='/blob?p=[% project %];f=[% item.file %];h=[% item.dst %]'>[% item.b %]</a>
+</div>
+<div class='diff-index'>
+ [% item.index %]
+</div>
+<div class='diff-patch'>
+ <pre>[% blobs.${loop.index} %]</pre>
+</div>
+[% END %]
--- /dev/null
+<table class='diff-tree'>
+ <thead>
+ <tr>
+ <td>file</td>
+ <td>status</td>
+ <td>actions</td>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH line IN diff_tree -%]
+ <tr>
+ <td class='filename'>
+ [% line.file %]
+ </td>
+ <td class='status'>
+ [%
+ SWITCH line.status;
+ CASE 'R';
+ '[moved from ' _ line.src _ ' with ' _ line.sim _ '% similarity]';
+ CASE 'A';
+ '[new file with mode: ' _ line.modedst _ ']';
+ CASE 'D';
+ '[deleted file]';
+ END;
+ %]
+ </td>
+ <td class='action-list'>
+ [% IF !line.is_new %]<a href="/blobdiff?p=[% project %];f=[% line.file %];h=[% commit.sha1 %];hp=[% commit.parent_sha1 %]">diff</a>[% END %]
+ <a href="/blob?p=[% project %];f=[% line.file %];h=[% line.sha1 %];hb=[% commit.sha1 %]">blob</a>
+ [% IF !line.is_new %]<a href="/shortlog?p=[% project %];f=[% line.file %];hb=[% commit.sha1 %]">history</a>[% END %]
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>file</td>
+ <td>status</td>
+ <td>actions</td>
+ </tr>
+ </tfoot>
+</table>
--- /dev/null
+<table class='heads'>
+ <thead>
+ <tr>
+ <td>age</td>
+ <td>branch</td>
+ <td>actions</td>
+ </tr>
+ </thead>
+
+ <tbody>
+ [% FOREACH head IN heads %]
+ <tr>
+ <td>[% head.last_change %]</td>
+ <td class='head[% head.sha1 == HEAD ? ' current' : '' %]'>[% head.name %]</td>
+ <td class='action-list'>
+ <a href="/shortlog?p=[% project %];h=[% head.sha1 %]">shortlog</a>
+ <a href="/log?p=[% project %];h=[% head.sha1 %]">log</a>
+ <a href="/tree?p=[% project %];h=[% head.sha1 %];hb=[% head.name %]">tree</a>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+
+ <tfoot>
+ <tr>
+ <td>age</td>
+ <td>branch</td>
+ <td>actions</td>
+ </tr>
+ </tfoot>
+</table>
+
+
--- /dev/null
+<div class='pager'>
+ <a href='/[% action %]?p=[% project %];h=[% HEAD %]'>HEAD</a>
+ [% IF log_lines.size == 50 %]
+ <a href='/[% action %]?p=[% project %];h=[% commit.sha1 %];pg=[% page + 1 %]'>next</a>
+ [% END %]
+ [% IF log_lines.first.sha1 != commit.sha1 %]
+ <a href='/[% action %]?p=[% project %];h=[% commit.sha1 %];pg=[% page - 1 %]'>prev</a>
+ [% END %]
+</div>
--- /dev/null
+<table>
+ <thead>
+ <tr>
+ <td>sha1</td>
+ <td>time</td>
+ <td>author</td>
+ <td>message</td>
+ <td>actions</td>
+ </tr>
+ </thead>
+
+ <tbody>
+ [% FOREACH line IN log_lines %]
+ <tr>
+ <td>[% line.sha1.substr(0, 6) %]</td>
+ <td>[% line.authored_time %]</td>
+ <td>[% line.author.name | html %]</td>
+ <td>
+ [% line.comment.substr(0, 50) | html %]
+ <span class='refs'>
+ [% FOREACH ref IN refs.${line.sha1} %]
+ <span class='[% ref.search('^remotes/') ? 'remote' : 'head' %]'>
+ <a href='/shortlog?p=[% project %];h=refs/[% ref %]'>[% ref.replace('^(remote|head)s/', '') %]</a>
+ </span>
+ [% END %]
+ </span>
+ </td>
+ <td class='action-list'>
+ <a href="/commit?p=[% project %];h=[% line.sha1 %]">commit</a>
+ <a href="/commitdiff?p=[% project %];h=[% line.sha1 %]">commitdiff</a>
+ <a href="/tree?p=[% project %];h=[% line.sha1 %];hb=[% line.tree_sha1 %]">tree</a>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+
+ <tfoot>
+ <tr>
+ <td>sha1</td>
+ <td>time</td>
+ <td>author</td>
+ <td>message</td>
+ <td>actions</td>
+ </tr>
+ </tfoot>
+</table>
--- /dev/null
+<table>
+ <thead>
+ <tr>
+ <td>mode</td>
+ <td>file</td>
+ <td>actions</td>
+ </tr>
+ </thead>
+
+ <tbody>
+ [% FOREACH item IN tree_list %]
+ <tr>
+ <td>[% item.modestr %]</td>
+ [% theact = item.type == 'tree' ? 'tree' : 'blob' -%]
+ <td class='filename'>
+ <a href="/[% theact %]?p=[% project %];h=[% item.object %];hb=[% commit.sha1 %];f=[% IF path; path _ '/'; END; item.file %]">[% item.file %]</a>
+ </td>
+ <td class='action-list'>
+ <a href="/[% theact %]?p=[% project %];h=[% item.object %];hb=[% commit.sha1 %];f=[% item.file %]">[% theact %]</a>
+ <a href="/history?p=[% project %];h=[% item.object %]">history</a>
+ <a href="/raw?p=[% project %];h=[% item.object %]">raw</a>
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+
+ <tfoot>
+ <tr>
+ <td>mode</td>
+ <td>file</td>
+ <td>actions</td>
+ </tr>
+ </tfoot>
+</table>
--- /dev/null
+<link rel="stylesheet" type="text/css" href="/static/css/syntax/[% language %].css"/>
+
+[% PROCESS 'commit-nav.tt2' object = head %]
+<div class='commit-message'>
+[% head.comment.substr(0, 85) %] ...
+</div>
+<div class='path'>
+ <a href="/tree?p=[% project %];hb=[% head.sha1 %]">[% project %]</a>
+ [% # XXX The last part should link to blob_plain (or something) but doesn't ATM
+ FOREACH part IN filename.split('/') %]
+ / <a href="/tree?p=[% project %];hb=[% head.sha1 %]">[% part %]</a>
+ [% END %]
+</div>
+<div>
+ <pre class='blob'>[% blob %]</pre>
+</div>
+
+<!--
+$Git_PurePerl_Object_Blob1 = bless( {
+ content => "* Fix git_blob_plain i.e don't use the wrapper.\n",
+ git => bless( {
+ directory => {
+ dirs => [ '.' ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ gitdir => {
+ dirs => [ '.git' ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ loose => bless( { directory => {
+ dirs => [
+ '.git',
+ 'objects'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ } }, 'Git::PurePerl::Loose' ),
+ packs => [ bless( {
+ fh => bless( do{ require Symbol; Symbol::gensym }, 'IO::File' ),
+ filename
+ => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.pack',
+ file_spec_class
+ => undef
+ },
+ index => bless( {
+ fh => bless( Symbol::gensym, 'IO::File' ),
+ filename => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.idx',
+ file_spec_class
+ => undef
+ },
+ offsets => [
+ ( 0 ) x 23,
+ ( 1 ) x 29,
+ ( 2 ) x 11,
+ ( 3 ) x 6,
+ ( 4 ) x 22,
+ ( 5 ) x 51,
+ ( 6 ) x 78,
+ ( 7 ) x 3,
+ ( 8 ) x 34
+ ],
+ size => 8
+ }, 'Git::PurePerl::PackIndex::Version2' ),
+ index_filename
+ => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.idx',
+ file_spec_class
+ => undef
+ }
+ }, 'Git::PurePerl::Pack::WithIndex' ) ]
+ }, 'Git::PurePerl' ),
+ kind => 'blob',
+ sha1 => '8976ebc7df65475b3def53a1653533c3f61070d0',
+ size => 48
+ }, 'Git::PurePerl::Object::Blob' );
+bless( $Git_PurePerl_Object_Blob1->{git}{directory}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{gitdir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{loose}{directory}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{filename}, 'Path::Class::File' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{index}{filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{index}{filename}, 'Path::Class::File' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{index_filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Blob1->{git}{packs}[0]{index_filename}, 'Path::Class::File' );
+-->
--- /dev/null
+[% PROCESS 'commit-nav.tt2' object = commit %]
+
+<div class='commit-message'>
+[% commit.comment.substr(0, 85) %] ...
+</div>
+
+[% INCLUDE '_diff.tt2' %]
--- /dev/null
+<div id='commit-nav'>
+ <a href="/summary?p=[% project %]">summary</a> |
+ <a href="/shortlog?p=[% project %];h=[% object.sha1 %]">shortlog</a> |
+ <a href="/log?p=[% project %];h=[% object.sha1 %]">log</a> |
+ <a href="/commit?p=[% project %];h=[% object.sha1 %]">commit</a> |
+ <a href="/commitdiff?p=[% project %];h=[% object.sha1 %]">commitdiff</a> |
+ <a href="/tree?p=[% project %];h=[% object.tree_sha1 %];hb=[% object.sha1 %]">tree</a>
+</div>
--- /dev/null
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div class='commit-message'>
+[% commit.comment.substr(0, 85) %] ...
+[% FOREACH ref IN branches_on %]
+ <span class='refs'><a href='/shortlog?p=[% project %];h=[% commit.sha1 %];hb=[% ref %]'>[% ref %]</a></span>
+[% END %]
+</div>
+
+<dl class='commit-info'>
+ <dt>author</dt>
+ <dd>[% commit.author.name | html %] <[% commit.author.email %]><br/>
+ [% commit.authored_time %]</dd>
+ <dt>committer</dt>
+ <dd>[% commit.committer.name %] <[% commit.committer.email %]><br/>
+ [% commit.committed_time %]</dd>
+ <dt>commit</dt>
+ <dd>[% commit.sha1 %]</dd>
+ <dt>tree</dt>
+ <dd>[% commit.tree_sha1 %]
+ <span class='action-list'><a href="/tree?p=[% project %];h=[% commit.tree_sha1 %];hb=[% commit.sha1 %]">tree</a></span>
+ </dd>
+ [% FOREACH parent IN commit.parents %]
+ <dt>parent</dt>
+ <dd>[% parent %]
+ <span class='action-list'>
+ <a href="/commit?p=[% project %];h=[% parent %]">commit</a>
+ <a href="/commitdiff?p=[% project %];h=[% commit.sha1 %];hp=[% parent %]">diff</a>
+ </span>
+ </dd>
+ [% END %]
+</dl>
+
+<pre class='commit-message'>[% commit.comment %]</pre>
+
+[%
+# In the case of merge commits there will be no diff tree.
+IF diff_tree.size > 0;
+ INCLUDE '_diff_tree.tt2';
+END;
+%]
+
+<!--
+
+$Git_PurePerl_Object_Commit1 = bless( {
+ author => bless( {
+ email => 'broq@cpan.org',
+ name => 'broquaint'
+ }, 'Git::PurePerl::Actor' ),
+ authored_time
+ => {
+ DateTime
+ },
+ comment => 'The blob action now has simple (but functioning) syntax highlighting (thanks to jrockway\'s Angerwhale for the highlighting code).',
+ committed_time
+ => {
+ DateTime
+ },
+ committer
+ => bless( {
+ email => 'broq@cpan.org',
+ name => 'broquaint'
+ }, 'Git::PurePerl::Actor' ),
+ content => "tree 278387038d3a42dcc9b3b33d6809c71371caee90\nparent b222ff0a7260cc1777c".
+ "7e455dfcaf22551a512fc\nauthor broquaint <broq\@cpan.org> 1256204829 +0100\n".
+ "committer broquaint <broq\@cpan.org> 1256204829 +0100\n\nThe blob action no".
+ "w has simple (but functioning) syntax highlighting (thanks to jrockway's".
+ " Angerwhale for the highlighting code).\n",
+ git => bless( {
+ directory => {
+ dirs => [ '.' ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ gitdir => {
+ dirs => [ '.git' ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ loose => bless( { directory => {
+ dirs => [
+ '.git',
+ 'objects'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ } }, 'Git::PurePerl::Loose' ),
+ packs => [ bless( {
+ fh => bless( do{ require Symbol; Symbol::gensym }, 'IO::File' ),
+ filename
+ => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.pack',
+ file_spec_class
+ => undef
+ },
+ index => bless( {
+ fh => bless( Symbol::gensym, 'IO::File' ),
+ filename => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.idx',
+ file_spec_class
+ => undef
+ },
+ offsets => [
+ ( 0 ) x 23,
+ ( 1 ) x 29,
+ ( 2 ) x 11,
+ ( 3 ) x 6,
+ ( 4 ) x 22,
+ ( 5 ) x 51,
+ ( 6 ) x 78,
+ ( 7 ) x 3,
+ ( 8 ) x 34
+ ],
+ size => 8
+ }, 'Git::PurePerl::PackIndex::Version2' ),
+ index_filename
+ => {
+ dir => {
+ dirs => [
+ '.git',
+ 'objects',
+ 'pack'
+ ],
+ file_spec_class
+ => undef,
+ volume => ''
+ },
+ file => 'pack-76da0c32a0a4918d1828d110636caad32af6ec6c.idx',
+ file_spec_class
+ => undef
+ }
+ }, 'Git::PurePerl::Pack::WithIndex' ) ]
+ }, 'Git::PurePerl' ),
+ kind => 'commit',
+ parent_sha1
+ => 'b222ff0a7260cc1777c7e455dfcaf22551a512fc',
+ parents => [ 'b222ff0a7260cc1777c7e455dfcaf22551a512fc' ],
+ sha1 => '7e54e579e196c6c545fee1030175f65a111039d4',
+ size => 328,
+ tree_sha1
+ => '278387038d3a42dcc9b3b33d6809c71371caee90'
+ }, 'Git::PurePerl::Object::Commit' );
+$Git_PurePerl_Object_Commit1->{committed_time}{locale} = $Git_PurePerl_Object_Commit1->{authored_time}{locale};
+bless( $Git_PurePerl_Object_Commit1->{authored_time}, 'DateTime' );
+bless( $Git_PurePerl_Object_Commit1->{committed_time}, 'DateTime' );
+bless( $Git_PurePerl_Object_Commit1->{git}{directory}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{gitdir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{loose}{directory}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{filename}, 'Path::Class::File' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{index}{filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{index}{filename}, 'Path::Class::File' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{index_filename}{dir}, 'Path::Class::Dir' );
+bless( $Git_PurePerl_Object_Commit1->{git}{packs}[0]{index_filename}, 'Path::Class::File' );
+-->
--- /dev/null
+[% PROCESS 'commit-nav.tt2' object = commit %]
+
+<div class='commit-message'>
+[% commit.comment.substr(0, 85) %] ...
+</div>
+
+<div class='author'>
+ [% commit.author.name | html %] [[% commit.authored_time %]]
+</div>
+
+
+[%
+# In the case of merge commits there will be no diff tree.
+IF diff_tree.size > 0;
+ INCLUDE '_diff_tree.tt2';
+END;
+IF diff.size > 0;
+ INCLUDE '_diff.tt2';
+ELSE
+%]
+<div class='no-difference'>
+[%
+ IF commit.parents > 1;
+ 'Trivial merge';
+ ELSE;
+ 'No differences found';
+ END;
+%]
+</div>
+[% END %]
<meta http-equiv="content-type" content="[% content_type %]; charset=utf-8"/>
<meta name="generator" content="gitweb/[% version %] git/[% git_version %][% mod_perl_version %]"/>
<meta name="robots" content="index, nofollow"/>
- <title>[% title %] (Gitalist)</title>
+ <title>[% title | html %] (Gitalist)</title>
[% IF baseurl %]
<base href="[% baseurl %]" />
[% END %]
+ <link rel="stylesheet" type="text/css" href="/static/css/site.css"/>
<link rel="stylesheet" type="text/css" href="[% stylesheet %]"/>
[% FOR link IN links %]
<link rel="[% link.rel %]"
[% site_header %]
<div class="page_header">
- <a title="[% logo_label %]" href="[% logo_url %]"><img src="[% logo_img %]" alt="git" class="logo"/></a>
- <a href="[% home_link %]">[% home_link_str %]</a>
+ <a title="[% logo_label | url %]" href="[% logo_url | url %]"><img src="[% logo_img %]" alt="git" class="logo"/></a>
+ <a href="[% home_link | url %]">[% home_link_str %]</a>
[%- IF project %]
- / <a href="/git/gitweb.cgi?p=[% project %];a=summary">[% project %]</a>
+ / <a href="/summary?p=[% project %]">[% project %]</a>
[% IF action; " / " _ action; END;
END %]
</div>
<option value="author">author</option>
<option value="committer">committer</option>
<option value="pickaxe">pickaxe</option>
- </select><sup><a href="/?p=[% project %];a=search_help">?</a></sup> search:
+ </select><sup><a href="/search_help?p=[% project %]">?</a></sup> search:
<input type="text" name="s" value="[% search_text %]"/>
<span title="Extended regular expression"><label><input type="checkbox" name="sr" value="1" />re</label></span>
</form>
IF page_nav;
INCLUDE "page_nav.tt2";
END;
-
+%]
+<div id='body'>
+[%
IF action;
- # XXX Err ... yeah, slight hack here ... ahem.
- INCLUDE "index.tt2";
+ SET actmpl = action _ ".tt2";
+ INCLUDE $actmpl;
ELSE;
# The output of gitweb.cgi is injected at this point.
content;
END;
%]
+</div>
[%- # git_footer_html
-%]
<div class="page_footer">
[% IF project AND project_description %]
- <div class="page_footer_text">[% project_description %]</div>
+ <div class="page_footer_text">[% project_description | html %]</div>
[% END %]
[% FOR feed IN feeds %]
<a class="[% feed.class %]" title="[% feed.title %]" href="[% feed.href %]">[% feed.name %]</a>
--- /dev/null
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div>
+[% project %]
+</div>
+
+[% INCLUDE '_heads.tt2' %]
<thead>
<tr>
<th>Project</th>
- <th><a class="header" href="/projects?o=descr">Description</a></th>
- <th><a class="header" href="/projects?o=owner">Owner</a></th>
- <th><a class="header" href="/projects?o=age">Last Change</a></th>
+ <!-- XXX These do nothing presently -->
+ <th><a class="header" href="/?o=descr">Description</a></th>
+ <th><a class="header" href="/?o=owner">Owner</a></th>
+ <th><a class="header" href="/?o=age">Last Change</a></th>
<th></th>
</tr>
</thead>
USE Cycle('dark', 'light');
FOR p IN projects %]
<tr class="[% Cycle.next %]">
- <td><a class="list" href="/?p=[% p.name %];a=summary">[% p.name %]</a></td>
- <td><a class="list" title="[% p.description %]" href="/?p=[% p.name %];a=summary">[% p.description.substr(0, 20) %]</a></td>
+ <td><a class="list" href="/summary?p=[% p.name %]">[% p.name %]</a></td>
+ <td><a class="list" title="[% p.description %]" href="/summary?p=[% p.name %]">[% p.description.substr(0, 20) %]</a></td>
<td>[% p.owner %]</td>
<td class="age2">[% p.last_change %]</td>
- <td class="link"><a href="/?p=[% p.name %];a=summary">summary</a>
- | <a href="/?p=[% p.name %];a=shortlog">shortlog</a>
- | <a href="/?p=[% p.name %];a=log">log</a>
- | <a href="/?p=[% p.name %];a=tree">tree</a></td>
+ <td class="link"><a href="/summary?p=[% p.name %]">summary</a>
+ | <a href="/shortlog?p=[% p.name %]">shortlog</a>
+ | <a href="/log?p=[% p.name %]">log</a>
+ | <a href="/tree?p=[% p.name %]">tree</a></td>
</tr>
[% END %]
</tbody>
--- /dev/null
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+[% INCLUDE '_log_pager.tt2' %]
+
+[%# XXX Nabbed the HTML below from gitweb's log action. %]
+[% FOREACH line IN log_lines %]
+<div class="header">
+ <a class="title" href="/commit?p=gitweb/.git;h=[% line.sha1 %]">
+ <span class="age">[% line.authored_time %]</span>
+ [% line.comment.substr(0, 50) | html %]
+ </a>
+</div>
+
+<div class="title_text">
+ <div class="log_link">
+ <a href="/commit?p=gitweb/.git;h=[% line.sha %]">commit</a>
+ | <a href="/commitdiff?p=gitweb/.git;h=[% line.sha1 %]">commitdiff</a>
+ | <a href="/tree?p=gitweb/.git;h=[% line.sha1 %];hb=[% line.sha1 %]">tree</a>
+ </div>
+ <i>[% line.author.name | html %] [% line.authored_time %]</i>
+</div>
+
+<div class="log_body">
+ [% line.comment | html %]
+</div>
+[% END %]
+
+[% INCLUDE '_log_pager.tt2' %]
--- /dev/null
+<ul>
+[% FOR entry IN log %]
+ <li><h3>[% entry.message | html %]</h3>
+ <dl>
+ <dt>Commit</dt> <dd>[% entry.hash %]</dd>
+ <dt>Type</dt> <dd>[% entry.type %]</dd>
+ <dt>Author</dt> <dd>[% entry.author %]</dd>
+ <dt>Date</dt> <dd>[% entry.date %]</dd>
+ </dl>
+ </li>
+[% END %]
+</ul>
--- /dev/null
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div>
+[% project %]
+</div>
+
+[%
+INCLUDE '_log_pager.tt2';
+INCLUDE '_shortlog.tt2';
+INCLUDE '_log_pager.tt2';
+%]
--- /dev/null
+[% PROCESS 'commit-nav.tt2' object = head %]
+
+<div class='summary'>
+<dl>
+<dt>description</dt><dd>[% info.description %]</dd>
+<dt>owner</dt><dd>[% info.owner %]</dd>
+<dt>last change</dt><dd>[% info.last_change %]</dd>
+</dl>
+</div>
+
+<div class='shortlog'>
+<h2><a href='/shortlog?p=[% project %]'>shortlog</a></h2>
+[% INCLUDE '_shortlog.tt2' %]
+</div>
+
+<div class='heads'>
+<h2><a href='/heads?p=[% project %]'>heads</a></h2>
+[% INCLUDE '_heads.tt2' %]
+</div>
--- /dev/null
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div class='commit-message'>
+[% commit.comment.substr(0, 85) %] ...
+</div>
+
+[% IF path -%]
+<div>
+ <a href='/tree?p=[% project %];hb=[% commit.sha1 %]'>[% project %]</a>
+ [% fullpath = ''-%]
+ [% FOREACH part IN path.split('/') -%]
+ / <a href='/tree?p=[% project %];h=[% tree.sha1 %];hb=[% commit.sha1 %];f=[% fullpath _ part %]'>[% part %]</a>
+ [% fullpath = fullpath _ part _ '/'; %]
+ [% END -%]
+</div>
+[% END -%]
+
+[% INCLUDE '_tree.tt2' %]