Merge branch 'master' of git.shadowcat.co.uk:Gitalist into shadowcat
broquaint [Thu, 29 Oct 2009 14:57:41 +0000 (14:57 +0000)]
55 files changed:
.gitignore
ISSUES [new file with mode: 0644]
README
TODO [new file with mode: 0644]
gitalist.conf
lib/Gitalist/Controller/Root.pm
lib/Gitalist/Model/GPP.pm [new file with mode: 0644]
lib/Gitalist/Model/Git.pm
lib/Gitalist/View/Default.pm
lib/Gitalist/View/SyntaxHighlight.pm [new file with mode: 0644]
root/static/css/site.css [new file with mode: 0644]
root/static/css/syntax/Diff.css [new file with mode: 0644]
root/static/css/syntax/Perl.css [new file with mode: 0644]
t/lib/repositories/repo1/HEAD [new file with mode: 0644]
t/lib/repositories/repo1/config [new file with mode: 0644]
t/lib/repositories/repo1/description [new file with mode: 0644]
t/lib/repositories/repo1/hooks/applypatch-msg [new file with mode: 0644]
t/lib/repositories/repo1/hooks/commit-msg [new file with mode: 0644]
t/lib/repositories/repo1/hooks/post-commit [new file with mode: 0644]
t/lib/repositories/repo1/hooks/post-update [new file with mode: 0644]
t/lib/repositories/repo1/hooks/pre-applypatch [new file with mode: 0644]
t/lib/repositories/repo1/hooks/pre-commit [new file with mode: 0644]
t/lib/repositories/repo1/hooks/pre-rebase [new file with mode: 0644]
t/lib/repositories/repo1/hooks/update [new file with mode: 0644]
t/lib/repositories/repo1/info/exclude [new file with mode: 0644]
t/lib/repositories/repo1/objects/14/5dc3ef5d307be84cb9b325d70bd08aeed0eceb [new file with mode: 0644]
t/lib/repositories/repo1/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 [new file with mode: 0644]
t/lib/repositories/repo1/objects/36/c6c6708b8360d7023e8a1649c45bcf9b3bd818 [new file with mode: 0644]
t/lib/repositories/repo1/objects/3b/c0634310b9c62222bb0e724c11ffdfb297b4ac [new file with mode: 0644]
t/lib/repositories/repo1/objects/3f/7567c7bdf7e7ebf410926493b92d398333116e [new file with mode: 0644]
t/lib/repositories/repo1/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 [new file with mode: 0644]
t/lib/repositories/repo1/objects/72/9a7c3f6ba5453b42d16a43692205f67fb23bc1 [new file with mode: 0644]
t/lib/repositories/repo1/objects/82/b5fee28277349b6d46beff5fdf6a7152347ba0 [new file with mode: 0644]
t/lib/repositories/repo1/objects/90/62594aebb5df0de7fb92413f17a9eced196c22 [new file with mode: 0644]
t/lib/repositories/repo1/refs/heads/master [new file with mode: 0644]
t/model_Git.t
templates/_diff.tt2 [new file with mode: 0644]
templates/_diff_tree.tt2 [new file with mode: 0644]
templates/_heads.tt2 [new file with mode: 0644]
templates/_log_pager.tt2 [new file with mode: 0644]
templates/_shortlog.tt2 [new file with mode: 0644]
templates/_tree.tt2 [new file with mode: 0644]
templates/blob.tt2 [new file with mode: 0644]
templates/blobdiff.tt2 [new file with mode: 0644]
templates/commit-nav.tt2 [new file with mode: 0644]
templates/commit.tt2 [new file with mode: 0644]
templates/commitdiff.tt2 [new file with mode: 0644]
templates/default.tt2
templates/heads.tt2 [new file with mode: 0644]
templates/index.tt2
templates/log.tt2 [new file with mode: 0644]
templates/reflog.tt2 [new file with mode: 0644]
templates/shortlog.tt2 [new file with mode: 0644]
templates/summary.tt2 [new file with mode: 0644]
templates/tree.tt2 [new file with mode: 0644]

index fb42210..acaa20e 100644 (file)
@@ -6,3 +6,4 @@ inc/
 pm_to_blib
 MANIFEST
 MANIFEST.bak
+*~
diff --git a/ISSUES b/ISSUES
new file mode 100644 (file)
index 0000000..ee3bb08
--- /dev/null
+++ b/ISSUES
@@ -0,0 +1 @@
+* Sort out commitdiff so conflicted merges have an equivalent display to gitweb (d7f39bebabeb31ce9a7b4f1b6f3db9f391b78c3e as a reference)
diff --git a/README b/README
index f4294ca..cfddeac 100644 (file)
--- a/README
+++ b/README
@@ -30,6 +30,7 @@ DEPENDENCIES
   DateTime::Format::Mail
   File::Stat::ModeString
   List::MoreUtils
+  MooseX::Types::Common
   # Probably others ...
 
 COPYRIGHT AND LICENCE
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..6a05e77
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+* 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.
index 1ad2f10..6e548de 100644 (file)
@@ -43,3 +43,8 @@ favicon /git-favicon.png
 # fs traversing limit for getting project list
 # the number is relative to the projectroot
 project_maxdepth 2007
+
+<paging>
+  log = 50
+  summary = 16
+</paging>
index a71c03d..6bb73e9 100644 (file)
@@ -27,9 +27,13 @@ Gitalist::Controller::Root - Root Controller for Gitalist
 =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 ) = @_;
 
@@ -42,17 +46,45 @@ sub run_gitweb {
       $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 ) = @_;
 
@@ -72,6 +104,243 @@ sub index :Path :Args(0) {
   );
 }
 
+=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) = @_;
 
@@ -80,116 +349,117 @@ sub auto : Private {
     $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 +{
@@ -197,18 +467,18 @@ sub footer {
         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.
@@ -217,40 +487,43 @@ sub footer {
 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
 
@@ -258,7 +531,10 @@ Attempt to render a view, if needed.
 
 =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
 
diff --git a/lib/Gitalist/Model/GPP.pm b/lib/Gitalist/Model/GPP.pm
new file mode 100644 (file)
index 0000000..162ba70
--- /dev/null
@@ -0,0 +1,47 @@
+# 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;
index 260bb26..4c8c725 100644 (file)
 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;
index 986ff75..2c65caa 100644 (file)
@@ -18,7 +18,7 @@ Catalyst View.
 
 =head1 AUTHOR
 
-Dan Brook,,,
+Dan Brook
 
 =head1 LICENSE
 
diff --git a/lib/Gitalist/View/SyntaxHighlight.pm b/lib/Gitalist/View/SyntaxHighlight.pm
new file mode 100644 (file)
index 0000000..688be51
--- /dev/null
@@ -0,0 +1,88 @@
+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 => {
+                    "<"  => "&lt;",
+                    ">"  => "&gt;",
+                    "&"  => "&amp;",
+                    q{'} => "&apos;",
+                    q{"} => "&quot;",
+                },
+                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;
diff --git a/root/static/css/site.css b/root/static/css/site.css
new file mode 100644 (file)
index 0000000..f62959e
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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;
+}
diff --git a/root/static/css/syntax/Diff.css b/root/static/css/syntax/Diff.css
new file mode 100644 (file)
index 0000000..a76b6fb
--- /dev/null
@@ -0,0 +1,16 @@
+span.Keyword {
+  color: #777;
+  font-weight: bold;
+}
+span.DataType {
+  color: purple;
+}
+span.Normal {
+  color: gray;
+}
+span.String {
+  color: green;
+}
+span.Others {
+  color: red;
+}
diff --git a/root/static/css/syntax/Perl.css b/root/static/css/syntax/Perl.css
new file mode 100644 (file)
index 0000000..c28661c
--- /dev/null
@@ -0,0 +1,60 @@
+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;
+}
diff --git a/t/lib/repositories/repo1/HEAD b/t/lib/repositories/repo1/HEAD
new file mode 100644 (file)
index 0000000..cb089cd
--- /dev/null
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/t/lib/repositories/repo1/config b/t/lib/repositories/repo1/config
new file mode 100644 (file)
index 0000000..07d359d
--- /dev/null
@@ -0,0 +1,4 @@
+[core]
+       repositoryformatversion = 0
+       filemode = true
+       bare = true
diff --git a/t/lib/repositories/repo1/description b/t/lib/repositories/repo1/description
new file mode 100644 (file)
index 0000000..407d91e
--- /dev/null
@@ -0,0 +1 @@
+some test repository
diff --git a/t/lib/repositories/repo1/hooks/applypatch-msg b/t/lib/repositories/repo1/hooks/applypatch-msg
new file mode 100644 (file)
index 0000000..02de1ef
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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+"$@"}
+:
diff --git a/t/lib/repositories/repo1/hooks/commit-msg b/t/lib/repositories/repo1/hooks/commit-msg
new file mode 100644 (file)
index 0000000..9b04f2d
--- /dev/null
@@ -0,0 +1,22 @@
+#!/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
+}
+
diff --git a/t/lib/repositories/repo1/hooks/post-commit b/t/lib/repositories/repo1/hooks/post-commit
new file mode 100644 (file)
index 0000000..8be6f34
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, make this file executable.
+
+: Nothing
diff --git a/t/lib/repositories/repo1/hooks/post-update b/t/lib/repositories/repo1/hooks/post-update
new file mode 100644 (file)
index 0000000..bcba893
--- /dev/null
@@ -0,0 +1,8 @@
+#!/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
diff --git a/t/lib/repositories/repo1/hooks/pre-applypatch b/t/lib/repositories/repo1/hooks/pre-applypatch
new file mode 100644 (file)
index 0000000..5f56ce8
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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+"$@"}
+:
+
diff --git a/t/lib/repositories/repo1/hooks/pre-commit b/t/lib/repositories/repo1/hooks/pre-commit
new file mode 100644 (file)
index 0000000..723a9ef
--- /dev/null
@@ -0,0 +1,71 @@
+#!/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);
+'
+
diff --git a/t/lib/repositories/repo1/hooks/pre-rebase b/t/lib/repositories/repo1/hooks/pre-rebase
new file mode 100644 (file)
index 0000000..981c454
--- /dev/null
@@ -0,0 +1,150 @@
+#!/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".
diff --git a/t/lib/repositories/repo1/hooks/update b/t/lib/repositories/repo1/hooks/update
new file mode 100644 (file)
index 0000000..e8c536f
--- /dev/null
@@ -0,0 +1,285 @@
+#!/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
diff --git a/t/lib/repositories/repo1/info/exclude b/t/lib/repositories/repo1/info/exclude
new file mode 100644 (file)
index 0000000..2c87b72
--- /dev/null
@@ -0,0 +1,6 @@
+# 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]
+# *~
diff --git a/t/lib/repositories/repo1/objects/14/5dc3ef5d307be84cb9b325d70bd08aeed0eceb b/t/lib/repositories/repo1/objects/14/5dc3ef5d307be84cb9b325d70bd08aeed0eceb
new file mode 100644 (file)
index 0000000..d26a4fe
Binary files /dev/null and b/t/lib/repositories/repo1/objects/14/5dc3ef5d307be84cb9b325d70bd08aeed0eceb differ
diff --git a/t/lib/repositories/repo1/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/t/lib/repositories/repo1/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99
new file mode 100644 (file)
index 0000000..f95e389
Binary files /dev/null and b/t/lib/repositories/repo1/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 differ
diff --git a/t/lib/repositories/repo1/objects/36/c6c6708b8360d7023e8a1649c45bcf9b3bd818 b/t/lib/repositories/repo1/objects/36/c6c6708b8360d7023e8a1649c45bcf9b3bd818
new file mode 100644 (file)
index 0000000..fefba84
--- /dev/null
@@ -0,0 +1,2 @@
+xœÎAj\ 31\f@Ñ®}
\v‰dGc(¥«\1e 7°,95L2Ÿ\14zú–\1e\ f\ f~=n·>!Æü4‡;В¬²·dŒ¢¾-U³rL&¨†[q7ôê\1a>Ëðû\ 4n’V©¢ÖÄŵ-„9®KfÍÑ8oÌL´z(_óã\18ð¾\1f£—;\Êõ»Ï\1fx\19¥íoæú\17OǸ¾\ 2‘p$Œ’à\19      1ÔÿÅé\ fáPÌÀú së»Çð\vk:H…
\ No newline at end of file
diff --git a/t/lib/repositories/repo1/objects/3b/c0634310b9c62222bb0e724c11ffdfb297b4ac b/t/lib/repositories/repo1/objects/3b/c0634310b9c62222bb0e724c11ffdfb297b4ac
new file mode 100644 (file)
index 0000000..fa8f88a
Binary files /dev/null and b/t/lib/repositories/repo1/objects/3b/c0634310b9c62222bb0e724c11ffdfb297b4ac differ
diff --git a/t/lib/repositories/repo1/objects/3f/7567c7bdf7e7ebf410926493b92d398333116e b/t/lib/repositories/repo1/objects/3f/7567c7bdf7e7ebf410926493b92d398333116e
new file mode 100644 (file)
index 0000000..38437d2
Binary files /dev/null and b/t/lib/repositories/repo1/objects/3f/7567c7bdf7e7ebf410926493b92d398333116e differ
diff --git a/t/lib/repositories/repo1/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 b/t/lib/repositories/repo1/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6
new file mode 100644 (file)
index 0000000..fc906d8
Binary files /dev/null and b/t/lib/repositories/repo1/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 differ
diff --git a/t/lib/repositories/repo1/objects/72/9a7c3f6ba5453b42d16a43692205f67fb23bc1 b/t/lib/repositories/repo1/objects/72/9a7c3f6ba5453b42d16a43692205f67fb23bc1
new file mode 100644 (file)
index 0000000..e59ece5
Binary files /dev/null and b/t/lib/repositories/repo1/objects/72/9a7c3f6ba5453b42d16a43692205f67fb23bc1 differ
diff --git a/t/lib/repositories/repo1/objects/82/b5fee28277349b6d46beff5fdf6a7152347ba0 b/t/lib/repositories/repo1/objects/82/b5fee28277349b6d46beff5fdf6a7152347ba0
new file mode 100644 (file)
index 0000000..7a1eb5e
Binary files /dev/null and b/t/lib/repositories/repo1/objects/82/b5fee28277349b6d46beff5fdf6a7152347ba0 differ
diff --git a/t/lib/repositories/repo1/objects/90/62594aebb5df0de7fb92413f17a9eced196c22 b/t/lib/repositories/repo1/objects/90/62594aebb5df0de7fb92413f17a9eced196c22
new file mode 100644 (file)
index 0000000..236ca51
Binary files /dev/null and b/t/lib/repositories/repo1/objects/90/62594aebb5df0de7fb92413f17a9eced196c22 differ
diff --git a/t/lib/repositories/repo1/refs/heads/master b/t/lib/repositories/repo1/refs/heads/master
new file mode 100644 (file)
index 0000000..7c4a265
--- /dev/null
@@ -0,0 +1 @@
+36c6c6708b8360d7023e8a1649c45bcf9b3bd818
index c7c3cb4..43afa8f 100644 (file)
@@ -6,26 +6,60 @@ use Test::More qw/no_plan/;
 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
diff --git a/templates/_diff.tt2 b/templates/_diff.tt2
new file mode 100644 (file)
index 0000000..7450a77
--- /dev/null
@@ -0,0 +1,14 @@
+<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 %]
diff --git a/templates/_diff_tree.tt2 b/templates/_diff_tree.tt2
new file mode 100644 (file)
index 0000000..7544ab4
--- /dev/null
@@ -0,0 +1,42 @@
+<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>
diff --git a/templates/_heads.tt2 b/templates/_heads.tt2
new file mode 100644 (file)
index 0000000..c8f4e61
--- /dev/null
@@ -0,0 +1,33 @@
+<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>
+
+
diff --git a/templates/_log_pager.tt2 b/templates/_log_pager.tt2
new file mode 100644 (file)
index 0000000..3c4c1d9
--- /dev/null
@@ -0,0 +1,9 @@
+<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>
diff --git a/templates/_shortlog.tt2 b/templates/_shortlog.tt2
new file mode 100644 (file)
index 0000000..329dcd1
--- /dev/null
@@ -0,0 +1,46 @@
+<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>
diff --git a/templates/_tree.tt2 b/templates/_tree.tt2
new file mode 100644 (file)
index 0000000..f7f1bb1
--- /dev/null
@@ -0,0 +1,34 @@
+<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>
diff --git a/templates/blob.tt2 b/templates/blob.tt2
new file mode 100644 (file)
index 0000000..7b04661
--- /dev/null
@@ -0,0 +1,122 @@
+<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' );
+-->
diff --git a/templates/blobdiff.tt2 b/templates/blobdiff.tt2
new file mode 100644 (file)
index 0000000..8d6820b
--- /dev/null
@@ -0,0 +1,7 @@
+[% PROCESS 'commit-nav.tt2' object = commit %]
+
+<div class='commit-message'>
+[% commit.comment.substr(0, 85) %] ...
+</div>
+
+[% INCLUDE '_diff.tt2' %]
diff --git a/templates/commit-nav.tt2 b/templates/commit-nav.tt2
new file mode 100644 (file)
index 0000000..24f38bb
--- /dev/null
@@ -0,0 +1,8 @@
+<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>
diff --git a/templates/commit.tt2 b/templates/commit.tt2
new file mode 100644 (file)
index 0000000..c671f41
--- /dev/null
@@ -0,0 +1,178 @@
+[% 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 %] &lt;[% commit.author.email %]&gt;<br/>
+      [% commit.authored_time %]</dd>
+ <dt>committer</dt>
+  <dd>[% commit.committer.name %] &lt;[% commit.committer.email %]&gt;<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' );
+-->
diff --git a/templates/commitdiff.tt2 b/templates/commitdiff.tt2
new file mode 100644 (file)
index 0000000..a106b26
--- /dev/null
@@ -0,0 +1,30 @@
+[% 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 %]
index c476c28..a119460 100644 (file)
@@ -9,10 +9,11 @@
   <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>
@@ -51,7 +52,7 @@
       <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>
diff --git a/templates/heads.tt2 b/templates/heads.tt2
new file mode 100644 (file)
index 0000000..016b0d4
--- /dev/null
@@ -0,0 +1,7 @@
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div>
+[% project %]
+</div>
+
+[% INCLUDE '_heads.tt2' %]
index 8134ae1..24bcc59 100644 (file)
 <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>
diff --git a/templates/log.tt2 b/templates/log.tt2
new file mode 100644 (file)
index 0000000..2953d4e
--- /dev/null
@@ -0,0 +1,28 @@
+[% 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' %]
diff --git a/templates/reflog.tt2 b/templates/reflog.tt2
new file mode 100644 (file)
index 0000000..4bfb7a3
--- /dev/null
@@ -0,0 +1,12 @@
+<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>
diff --git a/templates/shortlog.tt2 b/templates/shortlog.tt2
new file mode 100644 (file)
index 0000000..c8e9ef2
--- /dev/null
@@ -0,0 +1,11 @@
+[% INCLUDE 'commit-nav.tt2' object = commit %]
+
+<div>
+[% project %]
+</div>
+
+[%
+INCLUDE '_log_pager.tt2';
+INCLUDE '_shortlog.tt2';
+INCLUDE '_log_pager.tt2';
+%]
diff --git a/templates/summary.tt2 b/templates/summary.tt2
new file mode 100644 (file)
index 0000000..f320ffc
--- /dev/null
@@ -0,0 +1,19 @@
+[% 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>
diff --git a/templates/tree.tt2 b/templates/tree.tt2
new file mode 100644 (file)
index 0000000..7b55830
--- /dev/null
@@ -0,0 +1,18 @@
+[% 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' %]