Make sure we always expire cache unless user very explicitly turns it off by setting it to false
Make sure we always expire cache unless user very explicitly turns it off by setting it to false

file:a/README -> file:b/README
--- a/README
+++ b/README
@@ -79,3 +79,47 @@
 If you want to edit the text header that appears above the project list on the
 home page, edit templates/hometext.tpl.
 
+
+[Caching]
+
+To turn on caching, set the 'cache' config item to true.  Gitphp will cache
+every page's output, including plaintext output and binary output such as
+blobs and snapshots, for the number of seconds specified in the
+'cachelifetime' config key.
+The 'cacheexpire' key is recommended for most users.  With this option on,
+gitphp will attempt to keep the cache in sync by automatically expiring any
+cached pages that are older than the most recent commit, on any branch.
+It is a slight performance hit to make this check, but the performance hit
+is tiny compared to the gain you get from turning on caching.  It will
+avoid situations where users are getting a cached version of a page that
+isn't up to date and doesn't reflect the most recent commit, or worse,
+pages that have been cached at different times and show data from both
+before and after a commit (eg page 1 of the shortlog shows the most recent
+commit but page 1 of the log was cached a while ago and doesn't show the
+most recent commit).
+However, if your project is so active that commits are constantly coming in
+and invalidating the cache, rendering it useless, it would be better to
+turn cache expiration off and just set a really short cache lifetime of
+a few seconds.  In other words:
+
+Most users:
+* Set 'cacheexpire' to TRUE
+* Set 'cachelifetime' high, 3600 seconds (1 hour) or more.  -1 means
+  cache forever
+
+Extremely active projects, with commits every few seconds, or advanced
+users that know exactly how often commits come in and want to save
+the performance of the expiration check:
+* Set 'cacheexpire' to FALSE
+* Set 'cachelifetime' low, between 5-10 seconds.
+
+If you ever run into problems with your cache not syncing correctly, or
+showing out of date information, you can use the 'expire' action to force
+expiration of all cache.  This action is not linked anywhere because it
+is not supposed to be run by users, it's more of an administrative function.
+To use it, you want to set a=expire.  So, for example, you would visit this
+address in a browser (obviously with the correct path to your gitphp
+index.php):
+
+http://yourserver.com/gitphp/index.php?a=expire
+

--- a/config/gitphp.conf.php.example
+++ b/config/gitphp.conf.php.example
@@ -178,24 +178,33 @@
 /*
  * cache
  * Turns on smarty caching
- * Be very careful with this!  Due to varying cache lifetimes,
- * you could end up seeing a mix of pages from before and after
- * a commit!
- * If in doubt, leave this off
+ * Be careful with this.  If in doubt, leave it off
  */
 $gitphp_conf['cache'] = FALSE;
+
+/*
+ * cacheexpire
+ * Attempts to automatically expire cache when a new commit renders
+ * it out of date.
+ * This is a good option for most users because it ensures the cache
+ * is always up to date and users are seeing correct information,
+ * although it is a slight performance hit.
+ * However, if your commits are coming in so quickly that the cache
+ * is constantly being expired, turn this off.
+ */
+$gitphp_conf['cacheexpire'] = TRUE;
 
 /*
  * cachelifetime
  * Sets how long a page will be cached, in seconds
- * This will vary greatly depending on how active your projects are.
- * If you set it too high, commits that happen close to one another
- * will appear to be mixed together as the user navigates between
- * pages, depending on when each page was previously accessed and
- * its current cache lifetime.
- * If in doubt, set it to something extremely low, like 3 seconds.
- */
-$gitphp_conf['cachelifetime'] = 3;
+ * If you are automatically expiring the cache
+ * (see the 'cacheexpire' option above), then this can be set
+ * relatively high - 3600 seconds (1 hour) or even longer.
+ * -1 means no timeout.
+ * If you have turned cacheexpire off because of too many
+ * cache expirations, set this low (5-10 seconds).
+ */
+$gitphp_conf['cachelifetime'] = 3600;
 
 /*
  * git_projects

--- /dev/null
+++ b/include/cache.cache_expire.php
@@ -1,1 +1,36 @@
+<?php
+/*
+ *  cache.cache_expire.pehe
+ *  gitphp: A PHP git repository browser
+ *  Component: Cache - cache expire
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ */
 
+require_once('gitutil.git_read_refs.php');
+
+function cache_expire($projectroot, $project, $projectlist, $expireall = false)
+{
+	global $tpl;
+
+	if ($expireall) {
+		$tpl->clear_all_cache();
+		return;
+	}
+
+	if ((!isset($projectroot)) || (!isset($project)))
+		return;
+
+	$headlist = git_read_refs($projectroot, $project, "refs/heads");
+
+	if (count($headlist) > 0) {
+		$age = $headlist[0]['age'];
+
+		$tpl->clear_cache(null, sha1($project), null, $age);
+
+		$tpl->clear_cache('projectlist.tpl', sha1(serialize($projectlist)), null, $age);
+	}
+}
+
+?>
+

--- a/include/display.git_blob_plain.php
+++ b/include/display.git_blob_plain.php
@@ -12,26 +12,49 @@
 
 function git_blob_plain($projectroot,$project,$hash,$file)
 {
-	global $gitphp_conf;
+	global $gitphp_conf, $tpl;
 
-	if ($file)
-		$saveas = $file;
-	else
-		$saveas = $hash . ".txt";
+	$cachekey = sha1($project) . "|" . $hash . "|" . sha1($file);
 
-	$buffer = git_cat_file($projectroot . $project, $hash);
+	$buffer = null;
 
-	if ($gitphp_conf['filemimetype'])
-		$mime = file_mime($buffer, $file);
+	// XXX: Nasty hack to cache headers
+	if (!$tpl->is_cached('blobheaders.tpl', $cachekey)) {
+		if ($file)
+			$saveas = $file;
+		else
+			$saveas = $hash . ".txt";
 
-	if ($mime)
-		header("Content-type: " . $mime);
-	else
-		header("Content-type: text/plain; charset=UTF-8");
+		$buffer = git_cat_file($projectroot . $project, $hash);
 
-	header("Content-disposition: inline; filename=\"" . $saveas . "\"");
+		if ($gitphp_conf['filemimetype'])
+			$mime = file_mime($buffer, $file);
 
-	echo $buffer;
+		$headers = array();
+
+		if ($mime)
+			$headers[] = "Content-type: " . $mime;
+		else
+			$headers[] = "Content-type: text/plain; charset=UTF-8";
+
+		$headers[] = "Content-disposition: inline; filename=\"" . $saveas . "\"";
+
+		$tpl->assign("blobheaders", serialize($headers));
+	}
+	$out = $tpl->fetch('blobheaders.tpl', $cachekey);
+
+	$returnedheaders = unserialize($out);
+
+	foreach ($returnedheaders as $i => $header)
+		header($header);
+
+
+	if (!$tpl->is_cached('blobplain.tpl', $cachekey)) {
+		if (!$buffer)
+			$buffer = git_cat_file($projectroot . $project, $hash);
+		$tpl->assign("blob", $buffer);
+	}
+	$tpl->display('blobplain.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_blobdiff_plain.php
+++ b/include/display.git_blobdiff_plain.php
@@ -12,13 +12,21 @@
 
 function git_blobdiff_plain($projectroot,$project,$hash,$hashbase,$hashparent,$file)
 {
-	$ret = prep_tmpdir();
-	if ($ret !== TRUE) {
-		echo $ret;
-		return;
+	global $tpl;
+
+	header("Content-type: text/plain; charset=UTF-8");
+
+	$cachekey = sha1($project) . "|" . $hashbase . "|" . $hash . "|" . $hashparent . "|" . sha1($file);
+
+	if (!$tpl->is_cached('blobdiffplain.tpl', $cachekey)) {
+		$ret = prep_tmpdir();
+		if ($ret !== TRUE) {
+			echo $ret;
+			return;
+		}
+		$tpl->assign("blobdiff",git_diff($projectroot . $project, $hashparent,($file?"a/".$file:$hashparent),$hash,($file?"b/".$file:$hash)));
 	}
-	header("Content-type: text/plain; charset=UTF-8");
-	echo git_diff($projectroot . $project, $hashparent,($file?"a/".$file:$hashparent),$hash,($file?"b/".$file:$hash));
+	$tpl->display('blobdiffplain.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_commitdiff_plain.php
+++ b/include/display.git_commitdiff_plain.php
@@ -12,7 +12,7 @@
  require_once('util.script_url.php');
  require_once('gitutil.git_read_commit.php');
  require_once('gitutil.git_diff_tree.php');
- require_once('gitutil.git_rev_list.php');
+ require_once('gitutil.git_read_revlist.php');
  require_once('gitutil.read_info_ref.php');
  require_once('gitutil.git_diff.php');
 
@@ -37,14 +37,12 @@
 		$diffout = git_diff_tree($projectroot . $project, $hash_parent . " " . $hash);
 		$difftree = explode("\n",$diffout);
 		$refs = read_info_ref($projectroot . $project,"tags");
-		$listout = git_rev_list($projectroot . $project, "HEAD");
-		$tok = strtok($listout,"\n");
-		while ($tok !== false) {
-			if (isset($refs[$tok]))
-				$tagname = $refs[$tok];
-			if ($tok == $hash)
+		$listout = git_read_revlist($projectroot . $project, "HEAD");
+		foreach ($listout as $i => $rev) {
+			if (isset($refs[$rev]))
+				$tagname = $refs[$rev];
+			if ($rev == $hash)
 				break;
-			$tok = strtok("\n");
 		}
 		$ad = date_str($co['author_epoch'],$co['author_tz']);
 		$tpl->assign("from",$co['author']);

--- a/include/display.git_log.php
+++ b/include/display.git_log.php
@@ -17,7 +17,7 @@
 {
 	global $tpl;
 
-	$cachekey = sha1($project) . "|" . $hash . "|" . $page;
+	$cachekey = sha1($project) . "|" . $hash . "|" . (isset($page) ? $page : 0);
 
 	if (!$tpl->is_cached('log.tpl', $cachekey)) {
 		$head = git_read_head($projectroot . $project);

--- a/include/display.git_project_index.php
+++ b/include/display.git_project_index.php
@@ -11,16 +11,20 @@
 
 function git_project_index($projectroot, $projectlist)
 {
-	$projlist = git_read_projects($projectroot, $projectlist);
+	global $tpl, $git_projects;
+
 	header("Content-type: text/plain; charset=utf-8");
 	header("Content-Disposition: inline; filename=\"index.aux\"");
-	foreach ($projlist as $cat => $plist) {
-		if (is_array($plist)) {
-			foreach ($plist as $i => $proj)
-				echo $proj . "\n";
-		} else
-			echo $plist . "\n";
+
+	$cachekey = sha1(serialize($projectlist));
+
+	if (!$tpl->is_cached('projectindex.tpl', $cachekey)) {
+		if (isset($git_projects))
+			$tpl->assign("categorized", TRUE);
+		$projlist = git_read_projects($projectroot, $projectlist);
+		$tpl->assign("projlist", $projlist);
 	}
+	$tpl->display('projectindex.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_rss.php
+++ b/include/display.git_rss.php
@@ -28,7 +28,7 @@
 		$tpl->assign("self",script_url());
 
 		$commitlines = array();
-		for ($i = 0; $i <= count($revlist); $i++) {
+		for ($i = 0; $i < count($revlist); $i++) {
 			$commit = $revlist[$i];
 			$co = git_read_commit($projectroot . $project, $commit);
 			if (($i >= 20) && ((time() - $co['committer_epoch']) > 48*60*60))

--- a/include/display.git_search.php
+++ b/include/display.git_search.php
@@ -10,14 +10,14 @@
 require_once('defs.constants.php');
 require_once('util.highlight.php');
 require_once('gitutil.git_read_commit.php');
-require_once('gitutil.git_rev_list.php');
+require_once('gitutil.git_read_revlist.php');
 require_once('display.git_message.php');
 
 function git_search($projectroot, $project, $hash, $search, $searchtype, $page = 0)
 {
 	global $tpl,$gitphp_conf;
 
-	$cachekey = sha1($project) . "|" . $hash . "|" . sha1($searchtype) . "|" . sha1($search) . "|" . $page;
+	$cachekey = sha1($project) . "|" . $hash . "|" . sha1($searchtype) . "|" . sha1($search) . "|" . (isset($page) ? $page : 0);
 
 	if (!$tpl->is_cached('search.tpl', $cachekey)) {
 
@@ -37,7 +37,7 @@
 
 		$co = git_read_commit($projectroot . $project, $hash);
 
-		$revlist = explode("\n",trim(git_rev_list($projectroot . $project, $hash, 101, ($page * 100), FALSE, FALSE, $searchtype, $search)));
+		$revlist = git_read_revlist($projectroot . $project, $hash, 101, ($page * 100), FALSE, FALSE, $searchtype, $search);
 		if (count($revlist) < 1 || (strlen($revlist[0]) < 1)) {
 			git_message("No matches for '" . $search . "'.", FALSE, TRUE);
 			return;

--- a/include/display.git_search_files.php
+++ b/include/display.git_search_files.php
@@ -17,7 +17,7 @@
 {
 	global $tpl,$gitphp_conf;
 
-	$cachekey = sha1($project) . "|" . $hash . "|" . "filesearch" . "|" . sha1($search) . "|" . $page;
+	$cachekey = sha1($project) . "|" . $hash . "|" . "filesearch" . "|" . sha1($search) . "|" . (isset($page) ? $page : 0);
 
 	if (!$tpl->is_cached('searchfiles.tpl', $cachekey)) {
 

--- a/include/display.git_shortlog.php
+++ b/include/display.git_shortlog.php
@@ -17,7 +17,7 @@
 {
 	global $tpl;
 
-	$cachekey = sha1($project) . "|" . $hash . "|" . $page;
+	$cachekey = sha1($project) . "|" . $hash . "|" . (isset($page) ? $page : 0);
 
 	if (!$tpl->is_cached('shortlog.tpl', $cachekey)) {
 		$head = git_read_head($projectroot . $project);

--- a/include/display.git_snapshot.php
+++ b/include/display.git_snapshot.php
@@ -12,33 +12,46 @@
 
 function git_snapshot($projectroot,$project,$hash)
 {
-	global $gitphp_conf;
+	global $gitphp_conf, $tpl;
+
 	if (!isset($hash))
 		$hash = "HEAD";
+
+	$cachekey = sha1($project) . "|" . $hash;
+
+	$bzcompress = false;
+	$gzencode = false;
+
 	$rname = str_replace(array("/",".git"),array("-",""),$project);
-	$arc = git_archive($projectroot . $project, $hash, $rname,
-		(($gitphp_conf['compressformat'] == GITPHP_COMPRESS_ZIP) ? "zip" : "tar"));
-
 	if ($gitphp_conf['compressformat'] == GITPHP_COMPRESS_ZIP) {
 		header("Content-Type: application/x-zip");
 		header("Content-Disposition: attachment; filename=" . $rname . ".zip");
-		echo $arc;
-		return;
 	} else if (($gitphp_conf['compressformat'] == GITPHP_COMPRESS_BZ2) && function_exists("bzcompress")) {
+		$bzcompress = true;
 		header("Content-Type: application/x-bzip2");
 		header("Content-Disposition: attachment; filename=" . $rname . ".tar.bz2");
-		echo bzcompress($arc,(isset($gitphp_conf['compresslevel'])?$gitphp_conf['compresslevel']:4));
-		return;
 	} else if (($gitphp_conf['compressformat'] == GITPHP_COMPRESS_GZ) && function_exists("gzencode")) {
+		$gzencode = true;
 		header("Content-Type: application/x-gzip");
 		header("Content-Disposition: attachment; filename=" . $rname . ".tar.gz");
-		echo gzencode($arc,(isset($gitphp_conf['compresslevel'])?$gitphp_conf['compresslevel']:-1));
-		return;
+	} else {
+		header("Content-Type: application/x-tar");
+		header("Content-Disposition: attachment; filename=" . $rname . ".tar");
 	}
 
-	header("Content-Type: application/x-tar");
-	header("Content-Disposition: attachment; filename=" . $rname . ".tar");
-	echo $arc;
+	if (!$tpl->is_cached('snapshot.tpl', $cachekey)) {
+
+		$arc = git_archive($projectroot . $project, $hash, $rname,
+			(($gitphp_conf['compressformat'] == GITPHP_COMPRESS_ZIP) ? "zip" : "tar"));
+
+		if (($gitphp_conf['compressformat'] == GITPHP_COMPRESS_BZ2) && $bzcompress) {
+			$arc = bzcompress($arc,(isset($gitphp_conf['compresslevel'])?$gitphp_conf['compresslevel']:4));
+		} else if (($gitphp_conf['compressformat'] == GITPHP_COMPRESS_GZ) && $gzencode) {
+			$arc = gzencode($arc,(isset($gitphp_conf['compresslevel'])?$gitphp_conf['compresslevel']:-1));
+		}
+		$tpl->assign("archive",$arc);
+	}
+	$tpl->display('snapshot.tpl', $cachekey);
 }
 
 ?>

--- a/include/gitutil.git_exec_nix.php
+++ b/include/gitutil.git_exec_nix.php
@@ -10,7 +10,10 @@
  function git_exec_nix($project, $command)
  {
  	global $gitphp_conf;
-	$cmd = $gitphp_conf['gitbin'] . " --git-dir=" . $project . " " . $command;
+	$cmd = $gitphp_conf['gitbin'];
+	if (isset($project) && (strlen($project) > 0))
+		$cmd .= " --git-dir=" . $project;
+	$cmd .= " " . $command;
 	return shell_exec($cmd);
  }
 

--- a/include/gitutil.git_exec_win.php
+++ b/include/gitutil.git_exec_win.php
@@ -10,7 +10,10 @@
 function git_exec_win($project, $command)
 {
 	global $gitphp_conf;
-	$cmd = $gitphp_conf['gitbin'] . " --git-dir=" . $project . " " . $command;
+	$cmd = $gitphp_conf['gitbin'];
+	if (isset($project) && (strlen($project) > 0))
+		$cmd .= " --git-dir=" . $project;
+	$cmd .= " " . $command;
 	return shell_exec($cmd);
 }
 

--- a/include/gitutil.git_read_commit.php
+++ b/include/gitutil.git_read_commit.php
@@ -9,12 +9,11 @@
 
  require_once('defs.constants.php');
  require_once('util.age_string.php');
- require_once('gitutil.git_rev_list.php');
+ require_once('gitutil.git_read_revlist.php');
 
 function git_read_commit($proj,$head)
 {
-	$revlist = git_rev_list($proj,$head,1,NULL,TRUE,TRUE);
-	$lines = explode("\n",$revlist);
+	$lines = git_read_revlist($proj,$head,1,NULL,TRUE,TRUE);
 	if (!($lines[0]) || !ereg("^[0-9a-fA-F]{40}",$lines[0]))
 		return null;
 	$commit = array();

--- a/include/gitutil.git_read_refs.php
+++ b/include/gitutil.git_read_refs.php
@@ -46,7 +46,7 @@
 			$ref_item['type'] = $type;
 			$ref_item['id'] = $ref_id;
 			$ref_item['epoch'] = 0;
-			$ref_item['age'] = "unknown";
+			$ref_item['age_string'] = "unknown";
 
 			if ($type == "tag") {
 				$tag = git_read_tag($projectroot . $project, $ref_id);
@@ -54,11 +54,13 @@
 				if ($tag['type'] == "commit") {
 					$co = git_read_commit($projectroot . $project, $tag['object']);
 					$ref_item['epoch'] = $co['committer_epoch'];
-					$ref_item['age'] = $co['age_string'];
+					$ref_item['age_string'] = $co['age_string'];
+					$ref_item['age'] = $co['age'];
 				} else if (isset($tag['epoch'])) {
 					$age = time() - $tag['epoch'];
 					$ref_item['epoch'] = $tag['epoch'];
-					$ref_item['age'] = age_string($age);
+					$ref_item['age_string'] = age_string($age);
+					$ref_item['age'] = $age;
 				}
 				$ref_item['reftype'] = $tag['type'];
 				$ref_item['name'] = $tag['name'];
@@ -70,7 +72,8 @@
 				$ref_item['title'] = $co['title'];
 				$ref_item['refid'] = $ref_id;
 				$ref_item['epoch'] = $co['committer_epoch'];
-				$ref_item['age'] = $co['age_string'];
+				$ref_item['age_string'] = $co['age_string'];
+				$ref_item['age'] = $co['age'];
 			}
 			$reflist[] = $ref_item;
 		}

--- a/include/gitutil.git_read_revlist.php
+++ b/include/gitutil.git_read_revlist.php
@@ -8,11 +8,39 @@
  */
 
  require_once('gitutil.git_rev_list.php');
+ require_once('gitutil.git_version.php');
 
-function git_read_revlist($proj,$head,$count,$skip = NULL)
+function git_read_revlist($proj,$head,$count = NULL,$skip = NULL,$header = FALSE,$parents = FALSE,$greptype = NULL, $search = NULL)
 {
-	$revs = trim(git_rev_list($proj,$head,$count, $skip));
+	$passedskip = $skip;
+	$passedcount = $count;
+	$canskip = true;
+
+	if (isset($skip) && ($skip > 0)) {
+		$version = git_version();
+		if (isset($version) && (strlen($version) > 0)) {
+			$splitver = explode(".",$version);
+
+			/* Skip only appears in git >= 1.5.0 */
+			if (($splitver[0] < 1) || (($splitver[0] == 1) && ($splitver[1] < 5))) {
+				$canskip = false;
+				$passedskip = null;
+				$passedcount += $skip;
+			}
+		}
+	}
+
+	$revs = trim(git_rev_list($proj,$head, $passedcount, $passedskip, $header, $parents, $greptype, $search));
 	$revlist = explode("\n",$revs);
+
+	if ((!$canskip) && ($skip > 0)) {
+		$tmp = array();
+		$revcount = count($revlist);
+		for ($i = $skip; $i < $revcount; $i++)
+			$tmp[] = $revlist[$i];
+		return $tmp;
+	}
+
 	return $revlist;
 }
 

--- /dev/null
+++ b/include/gitutil.git_version.php
@@ -1,1 +1,21 @@
+<?php
+/*
+ *  gitutil.git_version.php
+ *  gitphp: A PHP git repository browser
+ *  Component: Git utility - version
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ */
 
+ require_once('gitutil.git_exec.php');
+
+ function git_version()
+ {
+ 	$verstr = explode(" ",git_exec(null, "--version"));
+	if (($verstr[0] == "git") && ($verstr[1] == "version"))
+		return $verstr[2];
+	return null;
+ }
+
+?>
+

--- a/include/version.php
+++ b/include/version.php
@@ -7,7 +7,7 @@
  *  Copyright (C) 2008 Christopher Han <xiphux@gmail.com>
  */
 
- $gitphp_version = "0.0.7";
+ $gitphp_version = "0.0.8";
  $gitphp_appstring = "gitphp $gitphp_version";
 
 ?>

file:a/index.php -> file:b/index.php
--- a/index.php
+++ b/index.php
@@ -34,7 +34,8 @@
      	($_GET['a'] != "blob_plain") &&
      	($_GET['a'] != "blobdiff_plain") &&
      	($_GET['a'] != "rss") &&
-     	($_GET['a'] != "opml"))) {
+     	($_GET['a'] != "opml") &&
+	($_GET['a'] != "snapshot"))) {
 	$tpl->load_filter('output','trimwhitespace');
 	$extraoutput = TRUE;
 }
@@ -55,6 +56,10 @@
  if ($gitphp_conf['cache']) {
  	$tpl->caching = 2;
 	$tpl->cache_lifetime = $gitphp_conf['cachelifetime'];
+	if (!(isset($gitphp_conf['cacheexpire']) && ($gitphp_conf['cacheexpire'] === FALSE))) {
+		require_once('include/cache.cache_expire.php');
+		cache_expire($gitphp_conf['projectroot'], (isset($_GET['p']) ? $_GET['p'] : null), $git_projects);
+	}
  }
 
 /*
@@ -89,7 +94,12 @@
 	$tpl->assign("filesearch",TRUE);
 
 
- if (isset($_GET['a']) && $_GET['a'] == "opml") {
+ if (isset($_GET['a']) && $_GET['a'] == "expire") {
+ 	require_once('include/cache.cache_expire.php');
+	require_once('include/display.git_message.php');
+	cache_expire(null, null, null, true);
+	git_message("Cache expired");
+ } else if (isset($_GET['a']) && $_GET['a'] == "opml") {
 	require_once('include/display.git_opml.php');
 	git_opml($gitphp_conf['projectroot'],$git_projects);
  } else if (isset($_GET['a']) && $_GET['a'] == "project_index") {

--- /dev/null
+++ b/templates/blobdiffplain.tpl
@@ -1,1 +1,9 @@
+{*
+ *  blobdiffplain.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Blobdiff plain template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{$blobdiff}
 

--- /dev/null
+++ b/templates/blobheaders.tpl
@@ -1,1 +1,9 @@
+{*
+ *  blobheaders.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Blob header dummy template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{$blobheaders}
 

--- /dev/null
+++ b/templates/blobplain.tpl
@@ -1,1 +1,9 @@
+{*
+ *  blobplain.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Blob plain template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{$blob}
 

--- a/templates/heads.tpl
+++ b/templates/heads.tpl
@@ -20,7 +20,7 @@
    {* Loop and display each head *}
    {foreach from=$headlist item=head}
      <tr class="{cycle values="light,dark"}">
-       <td><i>{$head.age}</i></td>
+       <td><i>{$head.age_string}</i></td>
        <td><a href="{$SCRIPT_NAME}?p={$project}&a=shortlog&h=refs/heads/{$head.name}" class="list"><b>{$head.name}</b></a></td>
        <td class="link"><a href="{$SCRIPT_NAME}?p={$project}&a=shortlog&h=refs/heads/{$head.name}">shortlog</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=log&h=refs/heads/{$head.name}">log</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=tree&h=refs/heads/{$head.name}&hb={$head.name}">tree</a></td>
      </tr>

--- a/templates/project.tpl
+++ b/templates/project.tpl
@@ -57,7 +57,7 @@
          {if $smarty.section.tag.index == 16}
            <td><a href="{$SCRIPT_NAME}?p={$project}&a=tags">...</a></td>
          {else}
-           <td><i>{$taglist[tag].age}</i></td>
+           <td><i>{$taglist[tag].age_string}</i></td>
            <td><a href="{$SCRIPT_NAME}?p={$project}&a={$taglist[tag].reftype}&h={$taglist[tag].refid}" class="list"><b>{$taglist[tag].name}</b></a></td>
            <td>
              {if $taglist[tag].comment}
@@ -86,7 +86,7 @@
          {if $smarty.section.head.index == 16}
            <td><a href="{$SCRIPT_NAME}?p={$project}&a=heads">...</a></td>
          {else}
-           <td><i>{$headlist[head].age}</i></td>
+           <td><i>{$headlist[head].age_string}</i></td>
            <td><a href="{$SCRIPT_NAME}?p={$project}&a=shortlog&h=refs/heads/{$headlist[head].name}" class="list"><b>{$headlist[head].name}</b></td>
            <td class="link"><a href="{$SCRIPT_NAME}?p={$project}&a=shortlog&h=refs/heads/{$headlist[head].name}">shortlog</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=log&h=refs/heads/{$headlist[head].name}">log</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=tree&h=refs/heads/{$headlist[head].name}&hb={$headlist[head].name}">tree</a></td>
          {/if}

--- /dev/null
+++ b/templates/projectindex.tpl
@@ -1,1 +1,19 @@
+{*
+ *  projectindex.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Project index template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{if $categorized}
+{foreach from=$projlist item=plist}
+{foreach from=$plist item=proj}
+{$proj}
+{/foreach}
+{/foreach}
+{else}
+{foreach from=$projlist item=proj}
+{$proj}
+{/foreach}
+{/if}
 

--- /dev/null
+++ b/templates/snapshot.tpl
@@ -1,1 +1,9 @@
+{*
+ *  snapshots.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Snapshot template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{$archive}
 

--- a/templates/tags.tpl
+++ b/templates/tags.tpl
@@ -20,7 +20,7 @@
  <table cellspacing="0">
    {section name=tag loop=$taglist}
      <tr class="{cycle values="light,dark"}">
-       <td><i>{$taglist[tag].age}</i></td>
+       <td><i>{$taglist[tag].age_string}</i></td>
        <td><a href="{$SCRIPT_NAME}?p={$project}&a={$taglist[tag].reftype}&h={$taglist[tag].refid}" class="list"><b>{$taglist[tag].name}</b></a></td>
        <td>
          {if count($taglist[tag].comment) > 0}

comments