Merge remote branch 't0m/json' into json
Dan Brook [Sun, 2 May 2010 19:06:51 +0000 (20:06 +0100)]
Conflicts:
Makefile.PL
lib/Gitalist/Controller/Root.pm
lib/Gitalist/Git/Repository.pm

1  2 
Makefile.PL
lib/Gitalist/Controller/Root.pm
lib/Gitalist/Git/Repository.pm

diff --combined Makefile.PL
@@@ -1,8 -1,5 +1,8 @@@
  #!/usr/bin/env perl
  
 +use FindBin;
 +BEGIN { do "$FindBin::Bin/script/env" or die $@ }
 +
  use strict;
  use warnings;
  
@@@ -32,7 -29,7 +32,7 @@@ if ($ENV{GITALIST_RELEASE_TESTING}) 
      # Fill in provides info so that indexing works right (in the face of MX::Declare)
      # by just smashing filenames to package names and not trying to be smart..
      File::Find::find(sub {
 -        return unless $File::Find::name =~ /\.pm$/;
 +        return unless /^\w.*?\.pm$/;
          my $fn = $File::Find::name;
          my $ver = ExtUtils::MM_Unix->parse_version($fn);
  
@@@ -57,13 -54,11 +57,15 @@@ requires 'Catalyst::Plugin::ConfigLoade
  requires 'Catalyst::Plugin::StackTrace';
  requires 'Catalyst::Plugin::Static::Simple';
  requires 'Catalyst::Plugin::Unicode::Encoding';
 +requires 'Catalyst::Plugin::SubRequest' => '0.15';
  requires 'Catalyst::Action::RenderView';
  requires 'Catalyst::Component::InstancePerContext';
 +requires 'Catalyst::Controller::ActionRole';
 +requires 'Catalyst::View::Component::SubInclude' => '0.07';
  requires 'Catalyst::View::TT';
 +requires 'Try::Tiny';
+ requires 'Catalyst::Action::Serialize';
  requires 'Template';
  requires 'Template::Provider::Encoding';
  requires 'Template::Plugin::Cycle';
@@@ -72,37 -67,42 +74,43 @@@ requires 'Config::General'
  
  requires 'Moose';
  requires 'Moose::Autobox';
 +requires 'MooseX::MultiMethods' => '0.10';
  requires 'MooseX::Declare' => '0.32';
+ requires 'MooseX::Types::DateTime';
+ requires 'MooseX::Types::ISO8601';
  requires 'MooseX::Types::Common';
  requires 'MooseX::Types::Path::Class';
  requires 'MooseX::Types';
+ requires 'MooseX::Storage';
 -<<<<<<< HEAD
+ requires 'JSON::Any';
 -=======
+ requires 'JSON::XS';
 ->>>>>>> origin/json
  requires 'namespace::autoclean';
  
 -requires 'Git::PurePerl' => '0.43';
 +requires 'Git::PurePerl' => '0.46';
  
  requires 'aliased';
  requires 'CGI';
  requires 'DateTime';
  requires 'DateTime::Format::Mail';
  requires 'File::Copy::Recursive';
 +requires 'File::Type';
 +requires 'File::Type::WebImages';
  requires 'File::Which';
  requires 'HTML::Entities';
  requires 'IPC::Run';
 +requires 'JSON::XS';
  requires 'List::MoreUtils';
  requires 'Path::Class' => '0.17';
  requires 'Sub::Exporter';
  requires 'Syntax::Highlight::Engine::Kate';
  requires 'Sys::Hostname';
 -requires 'XML::Atom';
 -requires 'XML::RSS';
  requires 'XML::OPML::SimpleGen';
 +requires 'XML::Atom::Feed';
 +requires 'XML::RSS';
  
  test_requires 'Test::More' => '0.88';
  test_requires 'Test::utf8' => '0.02';
+ test_requires 'HTTP::Request::Common';
  
  resources bugtracker => 'http://rt.cpan.org/Public/Dist/Display.html?Name=Gitalist';
  resources repository => 'git://git.shadowcat.co.uk/catagits/Gitalist.git';
@@@ -114,16 -114,6 +122,16 @@@ if ($Module::Install::AUTHOR) 
          and die $!;
  }
  
 +if ($ENV{GITALIST_RELEASE_TESTING}) {
 +    author_tests('t/author');
 +    author_requires('Test::NoTabs');
 +    author_requires('Test::Pod' => '1.14');
 +    author_requires('Test::Pod::Coverage' => '1.04');
 +    author_requires('Test::WWW::Mechanize::Catalyst' => '0.51');
 +    author_requires('HTML::TreeBuilder::XPath');
 +    author_requires('WWW::Mechanize::TreeBuilder');
 +}
 +
  install_script glob('script/*.pl');
  auto_install;
  
@@@ -2,39 -2,646 +2,40 @@@ package Gitalist::Controller::Root
  
  use Moose;
  use Moose::Autobox;
 -use Sys::Hostname ();
 -use XML::Atom::Feed;
 -use XML::Atom::Entry;
 -use XML::RSS;
 -use XML::OPML::SimpleGen;
 -
 +use Digest::MD5 qw(md5_hex);
  use Gitalist::Utils qw/ age_string /;
  
  use namespace::autoclean;
  
 -BEGIN { extends 'Catalyst::Controller' }
 +BEGIN { extends 'Gitalist::Controller' }
  
 -__PACKAGE__->config->{namespace} = '';
 +__PACKAGE__->config(namespace => '');
  
  sub root : Chained('/') PathPart('') CaptureArgs(0) {}
  
 -sub _get_object {
 -  my($self, $c, $haveh) = @_;
 -
 -  my $h = $haveh || $c->req->param('h') || '';
 -  my $f = $c->req->param('f');
 -
 -  my $m = $c->stash->{Repository};
 -  my $pd = $m->path;
 -
 -  # 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 $obj = $m->get_object($hash)
 -    or Carp::croak("Couldn't find a object for '$hash' in '$pd'!");
 -
 -  return $obj;
 -}
 -
  sub index : Chained('base') PathPart('') Args(0) {
 -  my ( $self, $c ) = @_;
 -
 -  $c->detach($c->req->param('a'))
 -    if $c->req->param('a');
 -
 -  my @list = @{ $c->model()->repositories };
 -  die 'No repositories found in '. $c->model->repo_dir
 -    unless @list;
 -
 -  my $search = $c->req->param('s') || '';
 -  if($search) {
 -    @list = grep {
 -         index($_->name, $search) > -1
 -      or ( $_->description !~ /^Unnamed repository/ and index($_->description, $search) > -1 )
 -    } @list
 -  }
 -
 -  $c->stash(
 -    search_text => $search,
 -    repositories    => \@list,
 -    action      => 'index',
 -  );
 -}
 -
 -# FIXME - WTF is this for?
 -sub repository_index : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -
 -  my @list = @{ $c->model()->repositories };
 -  die 'No repositories found in '. $c->model->repo_dir
 -    unless @list;
 -
 -  $c->response->content_type('text/plain');
 -  $c->response->body(
 -    join "\n", map $_->name, @list
 -  );
 -  $c->response->status(200);
 -}
 -# FIXME - maintain compatibility with previous URI
 -sub project_index : Chained('base') Args(0) {
 -    my ( $self, $c) = @_;
 -    $c->detach('repository_index');
 -}
 -
 -=head2 summary
 -
 -A summary of what's happening in the repo.
 -
 -=cut
 -
 -sub summary : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{data} = $c->stash->{Repository};
 -  $c->detach('error_404') unless $repository;
 -  my $commit = $self->_get_object($c);
 -  my @heads  = @{$repository->heads};
 -  my $maxitems = Gitalist->config->{paging}{summary} || 10;
 -  $c->stash(
 -    commit    => $commit,
 -    log_lines => [$repository->list_revs(
 -        sha1 => $commit->sha1,
 -        count => $maxitems,
 -    )],
 -    refs      => $repository->references,
 -    heads     => [ @heads[0 .. ($#heads < $maxitems ? $#heads : $maxitems)] ],
 -    action    => 'summary',
 -  );
 -}
 -
 -=head2 heads
 -
 -The current list of heads (aka branches) in the repo.
 -
 -=cut
 -
 -sub heads : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{Repository};
 -  $c->stash(
 -    commit => $self->_get_object($c),
 -    heads  => $repository->heads,
 -    action => 'heads',
 -  );
 -}
 -
 -=head2 tags
 -
 -The current list of tags in the repo.
 -
 -=cut
 -
 -sub tags : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{Repository};
 -  $c->stash(
 -    commit => $self->_get_object($c),
 -    tags   => $repository->tags,
 -    action => 'tags',
 -  );
 -}
 -
 -sub blame : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  my $repository = $c->stash->{Repository};
 -  my $h  = $c->req->param('h')
 -       || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
 -       || die "No file or sha1 provided.";
 -  my $hb = $c->req->param('hb')
 -       || $repository->head_hash
 -       || die "Couldn't discern the corresponding head.";
 -  my $filename = $c->req->param('f') || '';
 -
 -  my $blame = $repository->get_object($hb)->blame($filename, $h);
 -  $c->stash(
 -    blame    => $blame,
 -    head     => $repository->get_object($hb),
 -    filename => $filename,
 -
 -    # XXX Hack hack hack, see View::SyntaxHighlight
 -    language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
 -    blob     => join("\n", map $_->{line}, @$blame),
 -  );
 -
 -  $c->forward('View::SyntaxHighlight')
 -    unless $c->stash->{no_wrapper};
 -}
 -
 -sub _blob_objs {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{Repository};
 -  my $h  = $c->req->param('h')
 -       || $repository->hash_by_path($c->req->param('hb'), $c->req->param('f'))
 -       || die "No file or sha1 provided.";
 -  my $hb = $c->req->param('hb')
 -       || $repository->head_hash
 -       || die "Couldn't discern the corresponding head.";
 -
 -  my $filename = $c->req->param('f') || '';
 -
 -  my $blob = $repository->get_object($h);
 -  $blob = $repository->get_object(
 -    $repository->hash_by_path($h || $hb, $filename)
 -  ) if $blob->type ne 'blob';
 -
 -  return $blob, $repository->get_object($hb), $filename;
 -}
 -
 -=head2 blob
 -
 -The blob action i.e the contents of a file.
 -
 -=cut
 -
 -sub blob : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -
 -  my($blob, $head, $filename) = $self->_blob_objs($c);
 -  $c->stash(
 -    blob     => $blob->content,
 -    head     => $head,
 -    filename => $filename,
 -    # XXX Hack hack hack, see View::SyntaxHighlight
 -    language => ($filename =~ /\.p[lm]$/i ? 'Perl' : ''),
 -    action   => 'blob',
 -  );
 -
 -  $c->forward('View::SyntaxHighlight')
 -    unless $c->stash->{no_wrapper};
 -}
 -
 -=head2 blob_plain
 -
 -The plain text version of blob, where file is rendered as is.
 -
 -=cut
 -
 -sub blob_plain : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  my($blob) = $self->_blob_objs($c);
 -  $c->response->content_type('text/plain; charset=utf-8');
 -  $c->response->body($blob->content);
 -  $c->response->status(200);
 -}
 -
 -=head2 blobdiff_plain
 -
 -The plain text version of blobdiff.
 -
 -=cut
 -
 -sub blobdiff_plain : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  $c->stash(no_wrapper => 1);
 -  $c->response->content_type('text/plain; charset=utf-8');
 -
 -  $c->forward('blobdiff');
 -}
 -
 -=head2 blobdiff
 -
 -Exposes a given diff of a blob.
 -
 -=cut
 -
 -sub blobdiff : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $commit = $self->_get_object($c, $c->req->param('hb'));
 -  my $filename = $c->req->param('f')
 -              || croak("No file specified!");
 -  my($tree, $patch) = $c->stash->{Repository}->diff(
 -    commit => $commit,
 -    patch  => 1,
 -    parent => $c->req->param('hpb') || undef,
 -    file   => $filename,
 -  );
 -  $c->stash(
 -    commit    => $commit,
 -    diff      => $patch,
 -    filename  => $filename,
 -    # XXX Hack hack hack, see View::SyntaxHighlight
 -    blobs     => [$patch->[0]->{diff}],
 -    language  => 'Diff',
 -    action    => 'blobdiff',
 -  );
 -
 -  $c->forward('View::SyntaxHighlight')
 -    unless $c->stash->{no_wrapper};
 -}
 -
 -=head2 commit
 -
 -Exposes a given commit.
 -
 -=cut
 -
 -sub commit : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{Repository};
 -  my $commit = $self->_get_object($c);
 -  $c->stash(
 -      commit      => $commit,
 -      diff_tree   => ($repository->diff(commit => $commit))[0],
 -      refs      => $repository->references,
 -      action      => 'commit',
 -  );
 -}
 -
 -=head2 commitdiff
 -
 -Exposes a given diff of a commit.
 -
 -=cut
 -
 -sub commitdiff : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $commit = $self->_get_object($c);
 -  my($tree, $patch) = $c->stash->{Repository}->diff(
 -      commit => $commit,
 -      parent => $c->req->param('hp') || undef,
 -      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')
 -    unless $c->stash->{no_wrapper};
 -}
 -
 -sub commitdiff_plain : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  $c->stash(no_wrapper => 1);
 -  $c->response->content_type('text/plain; charset=utf-8');
 -
 -  $c->forward('commitdiff');
 -}
 -
 -=head2 shortlog
 -
 -Expose an abbreviated log of a given sha1.
 -
 -=cut
 -
 -sub shortlog : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -
 -  my $repository  = $c->stash->{Repository};
 -  my $commit   = $self->_get_object($c, $c->req->param('hb'));
 -  my $filename = $c->req->param('f') || '';
 -
 -  my %logargs = (
 -      sha1   => $commit->sha1,
 -      count  => Gitalist->config->{paging}{log} || 25,
 -      ($filename ? (file => $filename) : ())
 -  );
 -
 -  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 => [$repository->list_revs(%logargs)],
 -      refs      => $repository->references,
 -      page      => $page,
 -      filename  => $filename,
 -      action    => 'shortlog',
 -  );
 -}
 -
 -=head2 log
 -
 -Calls shortlog internally. Perhaps that should be reversed ...
 -
 -=cut
 -
 -sub log : Chained('base') Args(0) {
 -    $_[0]->shortlog($_[1]);
 -    $_[1]->stash->{action} = 'log';
 -}
 -
 -# For legacy support.
 -sub history : Chained('base') Args(0) {
      my ( $self, $c ) = @_;
 -    $self->shortlog($c);
 -    my $repository = $c->stash->{Repository};
 -    my $file = $repository->get_object(
 -        $repository->hash_by_path(
 -            $repository->head_hash,
 -            $c->stash->{filename}
 -        )
 -    );
 -     $c->stash( action => 'history',
 -               filetype => $file->type,
 -           );
 -}
 -
 -=head2 tree
 -
 -The tree of a given commit.
 -
 -=cut
 -
 -sub tree : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my $repository = $c->stash->{Repository};
 -  my $commit  = $self->_get_object($c, $c->req->param('hb'));
 -  my $filename = $c->req->param('f') || '';
 -  my $tree    = $filename
 -    ? $repository->get_object($repository->hash_by_path($commit->sha1, $filename))
 -    : $repository->get_object($commit->tree_sha1)
 -  ;
 -  $c->stash(
 -      commit    => $commit,
 -      tree      => $tree,
 -      tree_list => [$repository->list_tree($tree->sha1)],
 -      path      => $c->req->param('f') || '',
 -      action    => 'tree',
 -  );
 -}
 -
 -=head2 reflog
 -
 -Expose the local reflog. This may go away.
 -
 -=cut
 -
 -sub reflog : Chained('base') Args(0) {
 -  my ( $self, $c ) = @_;
 -  my @log = $c->stash->{Repository}->reflog(
 -      '--since=yesterday'
 -  );
 -
 -  $c->stash(
 -      log    => \@log,
 -      action => 'reflog',
 -  );
 -}
 -
 -=head2 search
 -
 -The action for the search form.
 -
 -=cut
 -
 -sub search : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -  $c->stash(current_action => 'GitRepos');
 -  my $repository = $c->stash->{Repository};
 -  my $commit  = $self->_get_object($c);
 -  # Lifted from /shortlog.
 -  my %logargs = (
 -    sha1   => $commit->sha1,
 -    count  => Gitalist->config->{paging}{log},
 -    ($c->req->param('f') ? (file => $c->req->param('f')) : ()),
 -    search => {
 -      type   => $c->req->param('type'),
 -      text   => $c->req->param('text'),
 -      regexp => $c->req->param('regexp') || 0,
 -    },
 -  );
 -
 -  $c->stash(
 -      commit  => $commit,
 -      results => [$repository->list_revs(%logargs)],
 -      action  => 'search',
 -        # This could be added - page      => $page,
 -  );
 -}
 -
 -=head2 search_help
 -
 -Provides some help for the search form.
 -
 -=cut
 -
 -sub search_help : Chained('base') Args(0) {
 -    my ($self, $c) = @_;
 -    $c->stash(template => 'search_help.tt2');
 +    $c->stash( search_text => $c->req->param('s') || '' ) # FIXME - XSS?
  }
  
 -=head2 atom
 -
 -Provides an atom feed for a given repository.
 -
 -=cut
 -
 -sub atom : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  my $feed = XML::Atom::Feed->new;
 -
 -  my $host = lc Sys::Hostname::hostname();
 -  $feed->title($host . ' - ' . Gitalist->config->{name});
 -  $feed->updated(~~DateTime->now);
 -
 -  my $repository = $c->stash->{Repository};
 -  my %logargs = (
 -      sha1   => $repository->head_hash,
 -      count  => Gitalist->config->{paging}{log} || 25,
 -      ($c->req->param('f') ? (file => $c->req->param('f')) : ())
 -  );
 -
 -  my $mk_title = $c->stash->{short_cmt};
 -  for my $commit ($repository->list_revs(%logargs)) {
 -    my $entry = XML::Atom::Entry->new;
 -    $entry->title( $mk_title->($commit->comment) );
 -    $entry->id($c->uri_for('commit', {h=>$commit->sha1}));
 -    # XXX Needs work ...
 -    $entry->content($commit->comment);
 -    $feed->add_entry($entry);
 -  }
 -
 -  $c->response->body($feed->as_xml);
 -  $c->response->content_type('application/atom+xml');
 -  $c->response->status(200);
 -}
 -
 -=head2 rss
 -
 -Provides an RSS feed for a given repository.
 -
 -=cut
 -
 -sub rss : Chained('base') Args(0) {
 -  my ($self, $c) = @_;
 -
 -  my $repository = $c->stash->{Repository};
 -
 -  my $rss = XML::RSS->new(version => '2.0');
 -  $rss->channel(
 -    title          => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name},
 -    link           => $c->uri_for('summary', {p=>$repository->name}),
 -    language       => 'en',
 -    description    => $repository->description,
 -    pubDate        => DateTime->now,
 -    lastBuildDate  => DateTime->now,
 -  );
 -
 -  my %logargs = (
 -      sha1   => $repository->head_hash,
 -      count  => Gitalist->config->{paging}{log} || 25,
 -      ($c->req->param('f') ? (file => $c->req->param('f')) : ())
 -  );
 -  my $mk_title = $c->stash->{short_cmt};
 -  for my $commit ($repository->list_revs(%logargs)) {
 -    # XXX Needs work ....
 -    $rss->add_item(
 -        title       => $mk_title->($commit->comment),
 -        permaLink   => $c->uri_for(commit => {h=>$commit->sha1}),
 -        description => $commit->comment,
 -    );
 -  }
 -
 -  $c->response->body($rss->as_string);
 -  $c->response->content_type('application/rss+xml');
 -  $c->response->status(200);
 -}
 -
 -sub opml : Chained('base') Args(0) {
 -  my($self, $c) = @_;
 -
 -  my $opml = XML::OPML::SimpleGen->new();
 -
 -  $opml->head(title => lc(Sys::Hostname::hostname()) . ' - ' . Gitalist->config->{name});
 -
 -  my @list = @{ $c->model()->repositories };
 -  die 'No repositories found in '. $c->model->repo_dir
 -    unless @list;
 -
 -  for my $proj ( @list ) {
 -    $opml->insert_outline(
 -      text   => $proj->name. ' - '. $proj->description,
 -      xmlUrl => $c->uri_for(rss => {p => $proj->name}),
 -    );
 -  }
 -
 -  $c->response->body($opml->as_string);
 -  $c->response->content_type('application/rss');
 -  $c->response->status(200);
 -}
 -
 -=head2 patch
 -
 -A raw patch for a given commit.
 -
 -=cut
 -
 -sub patch : Chained('base') Args(0) {
 -    my ($self, $c) = @_;
 -    $c->detach('patches', [1]);
 -}
 -
 -=head2 patches
 -
 -The patcheset for a given commit ???
 -
 -=cut
 -
 -sub patches : Chained('base') Args(0) {
 -    my ($self, $c, $count) = @_;
 -    $count ||= Gitalist->config->{patches}{max};
 -    my $commit = $self->_get_object($c);
 -    my $parent = $c->req->param('hp') || undef;
 -    my $patch = $commit->get_patch( $parent, $count );
 -    $c->response->body($patch);
 -    $c->response->content_type('text/plain');
 -    $c->response->status(200);
 -}
 -
 -=head2 snapshot
 -
 -Provides a snapshot of a given commit.
 -
 -=cut
 +# XXX Fragile much?
 +sub css : Chained('/root') PathPart('core.css') Args(0) {
 +    my ( $self, $c ) = @_;
  
 -sub snapshot : Chained('base') Args(0) {
 -    my ($self, $c) = @_;
 -    my $format = $c->req->param('sf') || 'tgz';
 -    die unless $format;
 -    my $sha1 = $c->req->param('h') || $self->_get_object($c)->sha1;
 -    my @snap = $c->stash->{Repository}->snapshot(
 -        sha1 => $sha1,
 -        format => $format
 -    );
 -    $c->response->status(200);
 -    $c->response->headers->header( 'Content-Disposition' =>
 -                                       "attachment; filename=$snap[0]");
 -    $c->response->body($snap[1]);
 +    $c->response->content_type('text/css');
 +    $c->stash(template => 'static/css/core.css');
  }
  
  sub base : Chained('/root') PathPart('') CaptureArgs(0) {
    my($self, $c) = @_;
  
 -  my $repository = $c->req->param('p');
 -  if (defined $repository) {
 -    eval {
 -      $c->stash(Repository => $c->model('GitRepos')->get_repository($repository));
 -    };
 -    if ($@) {
 -      $c->detach('/error_404');
 -    }
 -  }
 -
 -  my $a_repository = $c->stash->{Repository} || $c->model()->repositories->[0];
 +  my $git_version = `git --version`;
 +  chomp($git_version);
    $c->stash(
 -    git_version => $a_repository->run_cmd('--version'),
 +    git_version => $git_version,
      version     => $Gitalist::VERSION,
  
+     # XXX Move these to a plugin!
      time_since => sub {
        return 'never' unless $_[0];
        return age_string(time - $_[0]->epoch);
      short_cmt => sub {
        my $cmt = shift;
        my($line) = split /\n/, $cmt;
 -      $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/;
 +      $line =~ s/^(.{70,80}\b).*/$1 \x{2026}/ if defined $line;
        return $line;
      },
      abridged_description => sub {
          join(' ', grep { defined } (split / /, shift)[0..10]);
      },
 +    uri_for_gravatar => sub { # FIXME - Cache these?
 +        my $email = shift;
 +        my $size = shift;
 +        my $uri = 'http://www.gravatar.com/avatar/' . md5_hex($email);
 +        $uri .= "?s=$size" if $size;
 +        return $uri;
 +    },
    );
  }
  
- sub search : Chained('base') Args(0) {}
- =head2 search_help
- Provides some help for the search form.
- =cut
- sub search_help : Chained('base') Args(0) {}
- sub end : ActionClass('RenderView') {}
+ sub end : ActionClass('Serialize') {
+     my ($self, $c) = @_;
+     # Give repository views the current HEAD.
+     if ($c->stash->{Repository}) {
+         $c->stash->{HEAD} = $c->stash->{Repository}->head_hash;
+     }
+     if ($c->stash->{data} && blessed $c->stash->{data}) {
+         $c->stash->{rest} = $c->stash->{data}->pack;
+     }
+ }
  
  sub error_404 : Action {
      my ($self, $c) = @_;
      $c->response->body('Page not found');
  }
  
+ __PACKAGE__->config(
+     default => 'text/html',
+     map => {
+         'text/html'        => [qw/ View Default /],
+         'application/json' => [qw/ JSON /],
+     }
+ );
  __PACKAGE__->meta->make_immutable;
  
  __END__
@@@ -106,8 -713,18 +114,8 @@@ Provides the repository listing
  
  Attempt to render a view, if needed.
  
 -=head2 blame
 -
 -=head2 commitdiff_plain
 -
  =head2 error_404
  
 -=head2 history
 -
 -=head2 opml
 -
 -=head2 repository_index
 -
  =head1 AUTHORS
  
  See L<Gitalist> for authors.
@@@ -3,19 -3,19 +3,20 @@@ use MooseX::Declare
  class Gitalist::Git::Repository with Gitalist::Git::HasUtils {
      # FIXME, use Types::Path::Class and coerce
      use MooseX::Types::Common::String qw/NonEmptySimpleStr/;
-     use MooseX::Types::Path::Class qw/Dir/;
      use MooseX::Types::Moose qw/Str Maybe Bool HashRef ArrayRef/;
-     use Gitalist::Git::Types qw/SHA1/;
 +    use MooseX::MultiMethods;
+     use Gitalist::Git::Types qw/SHA1 DateTime Dir/;
      use Moose::Autobox;
      use List::MoreUtils qw/any zip/;
-     use DateTime;
+     use aliased 'DateTime' => 'DT';
      use Encode qw/decode/;
      use I18N::Langinfo qw/langinfo CODESET/;
      use Gitalist::Git::Object::Blob;
      use Gitalist::Git::Object::Tree;
      use Gitalist::Git::Object::Commit;
      use Gitalist::Git::Object::Tag;
+     
+     with 'Gitalist::Serializeable';
  
      our $SHA1RE = qr/[0-9a-fA-F]{40}/;
  
@@@ -35,7 -35,8 +36,8 @@@
                    is => 'ro', required => 1 );
  
      has path => ( isa => Dir,
-                   is => 'ro', required => 1);
+                   is => 'ro', required => 1,
+                   traits => [qw/ DoNotSerialize /] );
  
      has description => ( isa => Str,
                           is => 'ro',
@@@ -47,7 -48,7 +49,7 @@@
                     lazy_build => 1,
                 );
  
-     has last_change => ( isa => Maybe['DateTime'],
+     has last_change => ( isa => Maybe[DateTime],
                           is => 'ro',
                           lazy_build => 1,
                       );
                       is => 'ro',
                       lazy => 1,
                       default => sub {
-                          -d $_[0]->path->parent->subdir->($_[0]->name)
+                          -d $_[0]->path->parent->subdir($_[0]->name)
                               ? 1 : 0
                           },
                       );
      has heads => ( isa => ArrayRef[HashRef],
                     is => 'ro',
-                    lazy_build => 1);
+                    lazy_build => 1,
+                    traits => [qw/ DoNotSerialize /],
+                    );
      has tags => ( isa => ArrayRef[HashRef],
                     is => 'ro',
                     lazy_build => 1);
      }
  
      ## Public methods
 +
 +    multi method get_object_or_head (SHA1 $sha1) {
 +        $self->get_object($sha1);
 +    }
 +    multi method get_object_or_head (NonEmptySimpleStr $ref) {
 +        my $sha1 = $self->head_hash($ref);
 +        $self->get_object($sha1);
 +    }    
 +    
      method head_hash (Str $head?) {
          my $output = $self->run_cmd(qw/rev-parse --verify/, $head || 'HEAD' );
          confess("No such head: " . $head) unless defined $output;
              if !$sha1 || $sha1 !~ $SHA1RE;
  
          my @search_opts;
 -        if ($search) {
 +        if ($search and exists $search->{text}) {
              $search->{type} = 'grep'
                  if $search->{type} eq 'commit';
              @search_opts = (
      method diff ( Gitalist::Git::Object :$commit!,
                    Bool :$patch?,
                    Maybe[NonEmptySimpleStr] :$parent?,
 -                  NonEmptySimpleStr :$file?
 +                  NonEmptySimpleStr :$filename?
                ) {
                return $commit->diff( patch => $patch,
                                      parent => $parent,
 -                                    file => $file);
 +                                    filename => $filename);
      }
  
      method reflog (@logargs) {
              $description = $self->path->file('description')->slurp;
              chomp $description;
          };
 +      $description = "Unnamed repository, edit the .git/description file to set a description"
 +          if $description eq "Unnamed repository; edit this file 'description' to name the repository.";
          return $description;
      }
  
                  --sort=-committerdate --count=1 refs/heads
            });
          if (my ($epoch, $tz) = $output =~ /\s(\d+)\s+([+-]\d+)$/) {
-             my $dt = DateTime->from_epoch(epoch => $epoch);
+             my $dt = DT->from_epoch(epoch => $epoch);
              $dt->set_time_zone($tz);
              $last_change = $dt;
          }
  
              #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);
+                 my $dt = DT->from_epoch(epoch => $epoch);
                  $dt->set_time_zone($tz);
                  $ret[-1]->{last_change} = $dt;
              }
  
              #FIXME: That isn't the time I'm looking for..
              if($epoch and $tz) {
-                 my $dt = DateTime->from_epoch(epoch => $epoch);
+                 my $dt = DT->from_epoch(epoch => $epoch);
                  $dt->set_time_zone($tz);
                  $ret[-1]->{last_change} = $dt;
              }