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
+

file:b/cache/.gitignore (new)
--- /dev/null
+++ b/cache/.gitignore
@@ -1,1 +1,2 @@
+*
 

--- a/config/gitphp.conf.php.example
+++ b/config/gitphp.conf.php.example
@@ -176,6 +176,37 @@
 $gitphp_conf['debug'] = FALSE;
 
 /*
+ * cache
+ * Turns on smarty caching
+ * 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
+ * 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
  * Two-dimensional array list of projects
  * First array index is the name of the category the 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.php
+++ b/include/display.git_blob.php
@@ -18,65 +18,77 @@
 function git_blob($projectroot, $project, $hash, $file, $hashbase)
 {
 	global $gitphp_conf,$tpl;
-	if (!isset($hashbase))
-		$hashbase = git_read_head($projectroot . $project);
-	if (!isset($hash) && isset($file))
-		$hash = git_get_hash_by_path($projectroot . $project, $hashbase,$file,"blob");
-	$catout = git_cat_file($projectroot . $project, $hash);
-	$tpl->assign("hash",$hash);
-	$tpl->assign("hashbase",$hashbase);
-	if ($co = git_read_commit($projectroot . $project, $hashbase)) {
-		$tpl->assign("fullnav",TRUE);
-		$refs = read_info_ref($projectroot . $project);
-		$tpl->assign("tree",$co['tree']);
-		$tpl->assign("title",$co['title']);
-		if (isset($file))
-			$tpl->assign("file",$file);
-		if (isset($refs[$hashbase]))
-			$tpl->assign("hashbaseref",$refs[$hashbase]);
-	}
-	$paths = git_path_trees($projectroot . $project, $hashbase, $file);
-	$tpl->assign("paths",$paths);
 
-	if ($gitphp_conf['filemimetype']) {
-		$mime = file_mime($catout,$file);
-		if ($mime)
-			$mimetype = strtok($mime, "/");
-	}
+	$cachekey = sha1($project) . "|" . $hashbase . "|" . $hash . "|" . sha1($file);
 
-	if ($mimetype == "image") {
-		$tpl->assign("mime", $mime);
-		$tpl->assign("data", base64_encode($catout));
-	} else {
-		$usedgeshi = $gitphp_conf['geshi'];
-		if ($usedgeshi) {
-			$usedgeshi = FALSE;
-			include_once($gitphp_conf['geshiroot'] . "geshi.php");
-			if (class_exists("GeSHi")) {
-				$geshi = new GeSHi("",'php');
-				if ($geshi) {
-					$lang = "";
-					if (isset($file))
-						$lang = $geshi->get_language_name_from_extension(substr(strrchr($file,'.'),1));
-					if (isset($lang) && (strlen($lang) > 0)) {
-						$geshi->set_source($catout);
-						$geshi->set_language($lang);
-						$geshi->set_header_type(GESHI_HEADER_DIV);
-						$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);
-						$tpl->assign("geshiout",$geshi->parse_code());
-						$usedgeshi = TRUE;
+	if (!$tpl->is_cached('blob.tpl',$cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		if (!isset($hashbase))
+			$hashbase = $head;
+		if (!isset($hash) && isset($file))
+			$hash = git_get_hash_by_path($projectroot . $project, $hashbase,$file,"blob");
+		$catout = git_cat_file($projectroot . $project, $hash);
+		$tpl->assign("hash",$hash);
+		$tpl->assign("hashbase",$hashbase);
+		$tpl->assign("head", $head);
+		if ($co = git_read_commit($projectroot . $project, $hashbase)) {
+			$tpl->assign("fullnav",TRUE);
+			$refs = read_info_ref($projectroot . $project);
+			$tpl->assign("tree",$co['tree']);
+			$tpl->assign("title",$co['title']);
+			if (isset($file))
+				$tpl->assign("file",$file);
+			if ($hashbase == "HEAD") {
+				if (isset($refs[$head]))
+					$tpl->assign("hashbaseref",$refs[$head]);
+			} else {
+				if (isset($refs[$hashbase]))
+					$tpl->assign("hashbaseref",$refs[$hashbase]);
+			}
+		}
+		$paths = git_path_trees($projectroot . $project, $hashbase, $file);
+		$tpl->assign("paths",$paths);
+
+		if ($gitphp_conf['filemimetype']) {
+			$mime = file_mime($catout,$file);
+			if ($mime)
+				$mimetype = strtok($mime, "/");
+		}
+
+		if ($mimetype == "image") {
+			$tpl->assign("mime", $mime);
+			$tpl->assign("data", base64_encode($catout));
+		} else {
+			$usedgeshi = $gitphp_conf['geshi'];
+			if ($usedgeshi) {
+				$usedgeshi = FALSE;
+				include_once($gitphp_conf['geshiroot'] . "geshi.php");
+				if (class_exists("GeSHi")) {
+					$geshi = new GeSHi("",'php');
+					if ($geshi) {
+						$lang = "";
+						if (isset($file))
+							$lang = $geshi->get_language_name_from_extension(substr(strrchr($file,'.'),1));
+						if (isset($lang) && (strlen($lang) > 0)) {
+							$geshi->set_source($catout);
+							$geshi->set_language($lang);
+							$geshi->set_header_type(GESHI_HEADER_DIV);
+							$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);
+							$tpl->assign("geshiout",$geshi->parse_code());
+							$usedgeshi = TRUE;
+						}
 					}
 				}
 			}
-		}
 
-		if (!$usedgeshi) {
-			$lines = explode("\n",$catout);
-			$tpl->assign("lines",$lines);
+			if (!$usedgeshi) {
+				$lines = explode("\n",$catout);
+				$tpl->assign("lines",$lines);
+			}
 		}
 	}
 
-	$tpl->display("blob.tpl");
+	$tpl->display('blob.tpl', $cachekey);
 }
 
 ?>

--- 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.php
+++ b/include/display.git_blobdiff.php
@@ -16,29 +16,34 @@
 function git_blobdiff($projectroot,$project,$hash,$hashbase,$hashparent,$file)
 {
 	global $tpl;
-	$ret = prep_tmpdir();
-	if ($ret !== TRUE) {
-		echo $ret;
-		return;
+
+	$cachekey = sha1($project) . "|" . $hashbase . "|" . $hash . "|" . $hashparent . "|" . sha1($file);
+
+	if (!$tpl->is_cached('blobdiff.tpl', $cachekey)) {
+		$ret = prep_tmpdir();
+		if ($ret !== TRUE) {
+			echo $ret;
+			return;
+		}
+		$tpl->assign("hash",$hash);
+		$tpl->assign("hashparent",$hashparent);
+		$tpl->assign("hashbase",$hashbase);
+		if (isset($file))
+			$tpl->assign("file",$file);
+		if ($co = git_read_commit($projectroot . $project, $hashbase)) {
+			$tpl->assign("fullnav",TRUE);
+			$tpl->assign("tree",$co['tree']);
+			$tpl->assign("title",$co['title']);
+			$refs = read_info_ref($projectroot . $project);
+			if (isset($refs[$hashbase]))
+				$tpl->assign("hashbaseref",$refs[$hashbase]);
+		}
+		$paths = git_path_trees($projectroot . $project, $hashbase, $file);
+		$tpl->assign("paths",$paths);
+		$diffout = explode("\n",git_diff($projectroot . $project, $hashparent,($file?$file:$hashparent),$hash,($file?$file:$hash)));
+		$tpl->assign("diff",$diffout);
 	}
-	$tpl->assign("hash",$hash);
-	$tpl->assign("hashparent",$hashparent);
-	$tpl->assign("hashbase",$hashbase);
-	if (isset($file))
-		$tpl->assign("file",$file);
-	if ($co = git_read_commit($projectroot . $project, $hashbase)) {
-		$tpl->assign("fullnav",TRUE);
-		$tpl->assign("tree",$co['tree']);
-		$tpl->assign("title",$co['title']);
-		$refs = read_info_ref($projectroot . $project);
-		if (isset($refs[$hashbase]))
-			$tpl->assign("hashbaseref",$refs[$hashbase]);
-	}
-	$paths = git_path_trees($projectroot . $project, $hashbase, $file);
-	$tpl->assign("paths",$paths);
-	$diffout = explode("\n",git_diff($projectroot . $project, $hashparent,($file?$file:$hashparent),$hash,($file?$file:$hash)));
-	$tpl->assign("diff",$diffout);
-	$tpl->display("blobdiff.tpl");
+	$tpl->display('blobdiff.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_commit.php
+++ b/include/display.git_commit.php
@@ -16,78 +16,83 @@
 function git_commit($projectroot,$project,$hash)
 {
 	global $tpl;
-	$co = git_read_commit($projectroot . $project, $hash);
-	$ad = date_str($co['author_epoch'],$co['author_tz']);
-	$cd = date_str($co['committer_epoch'],$co['committer_tz']);
-	if (isset($co['parent'])) {
-		$root = "";
-		$parent = $co['parent'];
-	} else {
-		$root = "--root";
-		$parent = "";
+
+	$cachekey = sha1($project) . "|" . $hash;
+
+	if (!$tpl->is_cached('commit.tpl', $cachekey)) {
+		$co = git_read_commit($projectroot . $project, $hash);
+		$ad = date_str($co['author_epoch'],$co['author_tz']);
+		$cd = date_str($co['committer_epoch'],$co['committer_tz']);
+		if (isset($co['parent'])) {
+			$root = "";
+			$parent = $co['parent'];
+		} else {
+			$root = "--root";
+			$parent = "";
+		}
+		$diffout = git_diff_tree($projectroot . $project, $root . " " . $parent . " " . $hash, TRUE);
+		$difftree = explode("\n",$diffout);
+		$tpl->assign("hash",$hash);
+		$tpl->assign("tree",$co['tree']);
+		if (isset($co['parent']))
+			$tpl->assign("parent",$co['parent']);
+		$tpl->assign("title",$co['title']);
+		$refs = read_info_ref($projectroot . $project);
+		if (isset($refs[$co['id']]))
+			$tpl->assign("commitref",$refs[$co['id']]);
+		$tpl->assign("author",$co['author']);
+		$tpl->assign("adrfc2822",$ad['rfc2822']);
+		$tpl->assign("adhourlocal",$ad['hour_local']);
+		$tpl->assign("adminutelocal",$ad['minute_local']);
+		$tpl->assign("adtzlocal",$ad['tz_local']);
+		$tpl->assign("committer",$co['committer']);
+		$tpl->assign("cdrfc2822",$cd['rfc2822']);
+		$tpl->assign("cdhourlocal",$cd['hour_local']);
+		$tpl->assign("cdminutelocal",$cd['minute_local']);
+		$tpl->assign("cdtzlocal",$cd['tz_local']);
+		$tpl->assign("id",$co['id']);
+		$tpl->assign("parents",$co['parents']);
+		$tpl->assign("comment",$co['comment']);
+		$tpl->assign("difftreesize",count($difftree)+1);
+		$difftreelines = array();
+		foreach ($difftree as $i => $line) {
+			if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$",$line,$regs)) {
+				$difftreeline = array();
+				$difftreeline["from_mode"] = $regs[1];
+				$difftreeline["to_mode"] = $regs[2];
+				$difftreeline["from_mode_cut"] = substr($regs[1],-4);
+				$difftreeline["to_mode_cut"] = substr($regs[2],-4);
+				$difftreeline["from_id"] = $regs[3];
+				$difftreeline["to_id"] = $regs[4];
+				$difftreeline["status"] = $regs[5];
+				$difftreeline["similarity"] = ltrim($regs[6],"0");
+				$difftreeline["file"] = $regs[7];
+				$difftreeline["from_file"] = strtok($regs[7],"\t");
+				$difftreeline["from_filetype"] = file_type($regs[1]);
+				$difftreeline["to_file"] = strtok("\t");
+				$difftreeline["to_filetype"] = file_type($regs[2]);
+				if ((octdec($regs[2]) & 0x8000) == 0x8000)
+					$difftreeline["isreg"] = TRUE;
+				$modestr = "";
+				if ((octdec($regs[1]) & 0x17000) != (octdec($regs[2]) & 0x17000))
+					$modestr .= " from " . file_type($regs[1]) . " to " . file_type($regs[2]);
+				if ((octdec($regs[1]) & 0777) != (octdec($regs[2]) & 0777)) {
+					if ((octdec($regs[1]) & 0x8000) && (octdec($regs[2]) & 0x8000))
+						$modestr .= " mode: " . (octdec($regs[1]) & 0777) . "->" . (octdec($regs[2]) & 0777);
+					else if (octdec($regs[2]) & 0x8000)
+						$modestr .= " mode: " . (octdec($regs[2]) & 0777);
+				}
+				$difftreeline["modechange"] = $modestr;
+				$simmodechg = "";
+				if ($regs[1] != $regs[2])
+					$simmodechg .= ", mode: " . (octdec($regs[2]) & 0777);
+				$difftreeline["simmodechg"] = $simmodechg;
+				$difftreelines[] = $difftreeline;
+			}
+		}
+		$tpl->assign("difftreelines",$difftreelines);
 	}
-	$diffout = git_diff_tree($projectroot . $project, $root . " " . $parent . " " . $hash, TRUE);
-	$difftree = explode("\n",$diffout);
-	$tpl->assign("hash",$hash);
-	$tpl->assign("tree",$co['tree']);
-	if (isset($co['parent']))
-		$tpl->assign("parent",$co['parent']);
-	$tpl->assign("title",$co['title']);
-	$refs = read_info_ref($projectroot . $project);
-	if (isset($refs[$co['id']]))
-		$tpl->assign("commitref",$refs[$co['id']]);
-	$tpl->assign("author",$co['author']);
-	$tpl->assign("adrfc2822",$ad['rfc2822']);
-	$tpl->assign("adhourlocal",$ad['hour_local']);
-	$tpl->assign("adminutelocal",$ad['minute_local']);
-	$tpl->assign("adtzlocal",$ad['tz_local']);
-	$tpl->assign("committer",$co['committer']);
-	$tpl->assign("cdrfc2822",$cd['rfc2822']);
-	$tpl->assign("cdhourlocal",$cd['hour_local']);
-	$tpl->assign("cdminutelocal",$cd['minute_local']);
-	$tpl->assign("cdtzlocal",$cd['tz_local']);
-	$tpl->assign("id",$co['id']);
-	$tpl->assign("parents",$co['parents']);
-	$tpl->assign("comment",$co['comment']);
-	$tpl->assign("difftreesize",count($difftree)+1);
-	$difftreelines = array();
-	foreach ($difftree as $i => $line) {
-		if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$",$line,$regs)) {
-			$difftreeline = array();
-			$difftreeline["from_mode"] = $regs[1];
-			$difftreeline["to_mode"] = $regs[2];
-			$difftreeline["from_mode_cut"] = substr($regs[1],-4);
-			$difftreeline["to_mode_cut"] = substr($regs[2],-4);
-			$difftreeline["from_id"] = $regs[3];
-			$difftreeline["to_id"] = $regs[4];
-			$difftreeline["status"] = $regs[5];
-			$difftreeline["similarity"] = ltrim($regs[6],"0");
-			$difftreeline["file"] = $regs[7];
-			$difftreeline["from_file"] = strtok($regs[7],"\t");
-			$difftreeline["from_filetype"] = file_type($regs[1]);
-			$difftreeline["to_file"] = strtok("\t");
-			$difftreeline["to_filetype"] = file_type($regs[2]);
-			if ((octdec($regs[2]) & 0x8000) == 0x8000)
-				$difftreeline["isreg"] = TRUE;
-			$modestr = "";
-			if ((octdec($regs[1]) & 0x17000) != (octdec($regs[2]) & 0x17000))
-				$modestr .= " from " . file_type($regs[1]) . " to " . file_type($regs[2]);
-			if ((octdec($regs[1]) & 0777) != (octdec($regs[2]) & 0777)) {
-				if ((octdec($regs[1]) & 0x8000) && (octdec($regs[2]) & 0x8000))
-					$modestr .= " mode: " . (octdec($regs[1]) & 0777) . "->" . (octdec($regs[2]) & 0777);
-				else if (octdec($regs[2]) & 0x8000)
-					$modestr .= " mode: " . (octdec($regs[2]) & 0777);
-			}
-			$difftreeline["modechange"] = $modestr;
-			$simmodechg = "";
-			if ($regs[1] != $regs[2])
-				$simmodechg .= ", mode: " . (octdec($regs[2]) & 0777);
-			$difftreeline["simmodechg"] = $simmodechg;
-			$difftreelines[] = $difftreeline;
-		}
-	}
-	$tpl->assign("difftreelines",$difftreelines);
-	$tpl->display("commit.tpl");
+	$tpl->display('commit.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_commitdiff.php
+++ b/include/display.git_commitdiff.php
@@ -17,47 +17,52 @@
 function git_commitdiff($projectroot,$project,$hash,$hash_parent)
 {
 	global $tpl;
-	$ret = prep_tmpdir();
-	if ($ret !== TRUE) {
-		echo $ret;
-		return;
+
+	$cachekey = sha1($project) . "|" . $hash . "|" . $hash_parent;
+
+	if (!$tpl->is_cached('commitdiff.tpl', $cachekey)) {
+		$ret = prep_tmpdir();
+		if ($ret !== TRUE) {
+			echo $ret;
+			return;
+		}
+		$co = git_read_commit($projectroot . $project, $hash);
+		if (!isset($hash_parent))
+			$hash_parent = $co['parent'];
+		$diffout = git_diff_tree($projectroot . $project, $hash_parent . " " . $hash);
+		$difftree = explode("\n",$diffout);
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("hash",$hash);
+		$tpl->assign("tree",$co['tree']);
+		$tpl->assign("hashparent",$hash_parent);
+		$tpl->assign("title",$co['title']);
+		if (isset($refs[$co['id']]))
+			$tpl->assign("commitref",$refs[$co['id']]);
+		$tpl->assign("comment",$co['comment']);
+		$difftreelines = array();
+		foreach ($difftree as $i => $line) {
+			if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs)) {
+				$difftreeline = array();
+				$difftreeline["from_mode"] = $regs[1];
+				$difftreeline["to_mode"] = $regs[2];
+				$difftreeline["from_id"] = $regs[3];
+				$difftreeline["to_id"] = $regs[4];
+				$difftreeline["status"] = $regs[5];
+				$difftreeline["file"] = $regs[6];
+				$difftreeline["from_type"] = file_type($regs[1]);
+				$difftreeline["to_type"] = file_type($regs[2]);
+				if ($regs[5] == "A")
+					$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, null,"/dev/null",$regs[4],"b/" . $regs[6]));
+				else if ($regs[5] == "D")
+					$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, $regs[3],"a/" . $regs[6],null,"/dev/null"));
+				else if (($regs[5] == "M") && ($regs[3] != $regs[4]))
+					$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, $regs[3],"a/" . $regs[6],$regs[4],"b/" . $regs[6]));
+				$difftreelines[] = $difftreeline;
+			}
+		}
+		$tpl->assign("difftreelines",$difftreelines);
 	}
-	$co = git_read_commit($projectroot . $project, $hash);
-	if (!isset($hash_parent))
-		$hash_parent = $co['parent'];
-	$diffout = git_diff_tree($projectroot . $project, $hash_parent . " " . $hash);
-	$difftree = explode("\n",$diffout);
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("hash",$hash);
-	$tpl->assign("tree",$co['tree']);
-	$tpl->assign("hashparent",$hash_parent);
-	$tpl->assign("title",$co['title']);
-	if (isset($refs[$co['id']]))
-		$tpl->assign("commitref",$refs[$co['id']]);
-	$tpl->assign("comment",$co['comment']);
-	$difftreelines = array();
-	foreach ($difftree as $i => $line) {
-		if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs)) {
-			$difftreeline = array();
-			$difftreeline["from_mode"] = $regs[1];
-			$difftreeline["to_mode"] = $regs[2];
-			$difftreeline["from_id"] = $regs[3];
-			$difftreeline["to_id"] = $regs[4];
-			$difftreeline["status"] = $regs[5];
-			$difftreeline["file"] = $regs[6];
-			$difftreeline["from_type"] = file_type($regs[1]);
-			$difftreeline["to_type"] = file_type($regs[2]);
-			if ($regs[5] == "A")
-				$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, null,"/dev/null",$regs[4],"b/" . $regs[6]));
-			else if ($regs[5] == "D")
-				$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, $regs[3],"a/" . $regs[6],null,"/dev/null"));
-			else if (($regs[5] == "M") && ($regs[3] != $regs[4]))
-				$difftreeline['diffout'] = explode("\n",git_diff($projectroot . $project, $regs[3],"a/" . $regs[6],$regs[4],"b/" . $regs[6]));
-			$difftreelines[] = $difftreeline;
-		}
-	}
-	$tpl->assign("difftreelines",$difftreelines);
-	$tpl->display("commitdiff.tpl");
+	$tpl->display('commitdiff.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_commitdiff_plain.php
+++ b/include/display.git_commitdiff_plain.php
@@ -12,56 +12,60 @@
  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');
 
 function git_commitdiff_plain($projectroot,$project,$hash,$hash_parent)
 {
 	global $tpl;
-	$ret = prep_tmpdir();
-	if ($ret !== TRUE) {
-		echo $ret;
-		return;
-	}
-	$co = git_read_commit($projectroot . $project, $hash);
-	if (!isset($hash_parent))
-		$hash_parent = $co['parent'];
-	$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)
-			break;
-		$tok = strtok("\n");
-	}
+
+	$cachekey = sha1($project) . "|" . $hash . "|" . $hash_parent;
+
 	header("Content-type: text/plain; charset=UTF-8");
 	header("Content-disposition: inline; filename=\"git-" . $hash . ".patch\"");
-	$ad = date_str($co['author_epoch'],$co['author_tz']);
-	$tpl->assign("from",$co['author']);
-	$tpl->assign("date",$ad['rfc2822']);
-	$tpl->assign("subject",$co['title']);
-	if (isset($tagname))
-		$tpl->assign("tagname",$tagname);
-	$tpl->assign("url",script_url() . "?p=" . $project . "&a=commitdiff&h=" . $hash);
-	$tpl->assign("comment",$co['comment']);
-	$diffs = array();
-	foreach ($difftree as $i => $line) {
-		if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs)) {
-			if ($regs[5] == "A")
-				$diffs[] = git_diff($projectroot . $project, null, "/dev/null", $regs[4], "b/" . $regs[6]);
-			else if ($regs[5] == "D")
-				$diffs[] = git_diff($projectroot . $project, $regs[3], "a/" . $regs[6], null, "/dev/null");
-			else if ($regs[5] == "M")
-				$diffs[] = git_diff($projectroot . $project, $regs[3], "a/" . $regs[6], $regs[4], "b/" . $regs[6]);
+
+	if (!$tpl->is_cached('diff_plaintext.tpl', $cachekey)) {
+		$ret = prep_tmpdir();
+		if ($ret !== TRUE) {
+			echo $ret;
+			return;
 		}
+		$co = git_read_commit($projectroot . $project, $hash);
+		if (!isset($hash_parent))
+			$hash_parent = $co['parent'];
+		$diffout = git_diff_tree($projectroot . $project, $hash_parent . " " . $hash);
+		$difftree = explode("\n",$diffout);
+		$refs = read_info_ref($projectroot . $project,"tags");
+		$listout = git_read_revlist($projectroot . $project, "HEAD");
+		foreach ($listout as $i => $rev) {
+			if (isset($refs[$rev]))
+				$tagname = $refs[$rev];
+			if ($rev == $hash)
+				break;
+		}
+		$ad = date_str($co['author_epoch'],$co['author_tz']);
+		$tpl->assign("from",$co['author']);
+		$tpl->assign("date",$ad['rfc2822']);
+		$tpl->assign("subject",$co['title']);
+		if (isset($tagname))
+			$tpl->assign("tagname",$tagname);
+		$tpl->assign("url",script_url() . "?p=" . $project . "&a=commitdiff&h=" . $hash);
+		$tpl->assign("comment",$co['comment']);
+		$diffs = array();
+		foreach ($difftree as $i => $line) {
+			if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs)) {
+				if ($regs[5] == "A")
+					$diffs[] = git_diff($projectroot . $project, null, "/dev/null", $regs[4], "b/" . $regs[6]);
+				else if ($regs[5] == "D")
+					$diffs[] = git_diff($projectroot . $project, $regs[3], "a/" . $regs[6], null, "/dev/null");
+				else if ($regs[5] == "M")
+					$diffs[] = git_diff($projectroot . $project, $regs[3], "a/" . $regs[6], $regs[4], "b/" . $regs[6]);
+			}
+		}
+		$tpl->assign("diffs",$diffs);
 	}
-	$tpl->assign("diffs",$diffs);
-	$tpl->display("diff_plaintext.tpl");
+	$tpl->display('diff_plaintext.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_heads.php
+++ b/include/display.git_heads.php
@@ -13,11 +13,16 @@
 function git_heads($projectroot,$project)
 {
 	global $tpl;
-	$head = git_read_head($projectroot . $project);
-	$tpl->assign("head",$head);
-	$headlist = git_read_refs($projectroot, $project, "refs/heads");
-	$tpl->assign("headlist",$headlist);
-	$tpl->display("heads.tpl");
+
+	$cachekey = sha1($project);
+
+	if (!$tpl->is_cached('heads.tpl', $cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		$tpl->assign("head",$head);
+		$headlist = git_read_refs($projectroot, $project, "refs/heads");
+		$tpl->assign("headlist",$headlist);
+	}
+	$tpl->display('heads.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_history.php
+++ b/include/display.git_history.php
@@ -17,46 +17,51 @@
 function git_history($projectroot,$project,$hash,$file)
 {
 	global $tpl;
-	if (!isset($hash))
-		$hash = git_read_head($projectroot . $project);
-	$co = git_read_commit($projectroot . $project, $hash);
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("hash",$hash);
-	if (isset($refs[$hash]))
-		$tpl->assign("hashbaseref",$refs[$hash]);
-	$tpl->assign("tree",$co['tree']);
-	$tpl->assign("title",$co['title']);
-	$paths = git_path_trees($projectroot . $project, $hash, $file);
-	$tpl->assign("paths",$paths);
-	$cmdout = git_history_list($projectroot . $project, $hash, $file);
-	$lines = explode("\n", $cmdout);
-	$historylines = array();
-	foreach ($lines as $i => $line) {
-		if (ereg("^([0-9a-fA-F]{40})",$line,$regs))
-			$commit = $regs[1];
-		else if (ereg(":([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs) && isset($commit)) {
-				$historyline = array();
-				$co = git_read_commit($projectroot . $project, $commit);
-				$historyline["agestringage"] = $co['age_string_age'];
-				$historyline["agestringdate"] = $co['age_string_date'];
-				$historyline["authorname"] = $co['author_name'];
-				$historyline["commit"] = $commit;
-				$historyline["file"] = $file;
-				$historyline["title"] = $co['title_short'];
-				if (isset($refs[$commit]))
-					$historyline["commitref"] = $refs[$commit];
-				$blob = git_get_hash_by_path($projectroot . $project, $hash,$file);
-				$blob_parent = git_get_hash_by_path($projectroot . $project, $commit,$file);
-				if ($blob && $blob_parent && ($blob != $blob_parent)) {
-					$historyline["blob"] = $blob;
-					$historyline["blobparent"] = $blob_parent;
-				}
-				$historylines[] = $historyline;
-				unset($commit);
+
+	$cachekey = sha1($project) . "|" . $hash . "|" . sha1($file);
+
+	if (!$tpl->is_cached('history.tpl', $cachekey)) {
+		if (!isset($hash))
+			$hash = git_read_head($projectroot . $project);
+		$co = git_read_commit($projectroot . $project, $hash);
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("hash",$hash);
+		if (isset($refs[$hash]))
+			$tpl->assign("hashbaseref",$refs[$hash]);
+		$tpl->assign("tree",$co['tree']);
+		$tpl->assign("title",$co['title']);
+		$paths = git_path_trees($projectroot . $project, $hash, $file);
+		$tpl->assign("paths",$paths);
+		$cmdout = git_history_list($projectroot . $project, $hash, $file);
+		$lines = explode("\n", $cmdout);
+		$historylines = array();
+		foreach ($lines as $i => $line) {
+			if (ereg("^([0-9a-fA-F]{40})",$line,$regs))
+				$commit = $regs[1];
+			else if (ereg(":([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$",$line,$regs) && isset($commit)) {
+					$historyline = array();
+					$co = git_read_commit($projectroot . $project, $commit);
+					$historyline["agestringage"] = $co['age_string_age'];
+					$historyline["agestringdate"] = $co['age_string_date'];
+					$historyline["authorname"] = $co['author_name'];
+					$historyline["commit"] = $commit;
+					$historyline["file"] = $file;
+					$historyline["title"] = $co['title_short'];
+					if (isset($refs[$commit]))
+						$historyline["commitref"] = $refs[$commit];
+					$blob = git_get_hash_by_path($projectroot . $project, $hash,$file);
+					$blob_parent = git_get_hash_by_path($projectroot . $project, $commit,$file);
+					if ($blob && $blob_parent && ($blob != $blob_parent)) {
+						$historyline["blob"] = $blob;
+						$historyline["blobparent"] = $blob_parent;
+					}
+					$historylines[] = $historyline;
+					unset($commit);
+			}
 		}
+		$tpl->assign("historylines",$historylines);
 	}
-	$tpl->assign("historylines",$historylines);
-	$tpl->display("history.tpl");
+	$tpl->display('history.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_log.php
+++ b/include/display.git_log.php
@@ -16,51 +16,56 @@
 function git_log($projectroot,$project,$hash,$page)
 {
 	global $tpl;
-	$head = git_read_head($projectroot . $project);
-	if (!isset($hash))
-		$hash = $head;
-	if (!isset($page))
-		$page = 0;
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("hash",$hash);
-	$tpl->assign("head",$head);
 
-	if ($page)
-		$tpl->assign("page",$page);
+	$cachekey = sha1($project) . "|" . $hash . "|" . (isset($page) ? $page : 0);
 
-	$revlist = git_read_revlist($projectroot . $project, $hash, 101, ($page * 100));
+	if (!$tpl->is_cached('log.tpl', $cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		if (!isset($hash))
+			$hash = $head;
+		if (!isset($page))
+			$page = 0;
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("hash",$hash);
+		$tpl->assign("head",$head);
 
-	$revlistcount = count($revlist);
-	$tpl->assign("revlistcount",$revlistcount);
+		if ($page)
+			$tpl->assign("page",$page);
 
-	if (!$revlist) {
-		$tpl->assign("norevlist",TRUE);
-		$co = git_read_commit($hash);
-		$tpl->assign("lastchange",$co['age_string']);
+		$revlist = git_read_revlist($projectroot . $project, $hash, 101, ($page * 100));
+
+		$revlistcount = count($revlist);
+		$tpl->assign("revlistcount",$revlistcount);
+
+		if (!$revlist) {
+			$tpl->assign("norevlist",TRUE);
+			$co = git_read_commit($hash);
+			$tpl->assign("lastchange",$co['age_string']);
+		}
+
+		$commitlines = array();
+		$commitcount = min(100,$revlistcount);
+		for ($i = 0; $i < $commitcount; $i++) {
+			$commit = $revlist[$i];
+			if (isset($commit) && strlen($commit) > 1) {
+				$commitline = array();
+				$co = git_read_commit($projectroot . $project, $commit);
+				$ad = date_str($co['author_epoch']);
+				$commitline["project"] = $project;
+				$commitline["commit"] = $commit;
+				if (isset($refs[$commit]))
+					$commitline["commitref"] = $refs[$commit];
+				$commitline["agestring"] = $co['age_string'];
+				$commitline["title"] = $co['title'];
+				$commitline["authorname"] = $co['author_name'];
+				$commitline["rfc2822"] = $ad['rfc2822'];
+				$commitline["comment"] = $co['comment'];
+				$commitlines[] = $commitline;
+			}
+		}
+		$tpl->assign("commitlines",$commitlines);
 	}
-
-	$commitlines = array();
-	$commitcount = min(100,$revlistcount);
-	for ($i = 0; $i < $commitcount; $i++) {
-		$commit = $revlist[$i];
-		if (isset($commit) && strlen($commit) > 1) {
-			$commitline = array();
-			$co = git_read_commit($projectroot . $project, $commit);
-			$ad = date_str($co['author_epoch']);
-			$commitline["project"] = $project;
-			$commitline["commit"] = $commit;
-			if (isset($refs[$commit]))
-				$commitline["commitref"] = $refs[$commit];
-			$commitline["agestring"] = $co['age_string'];
-			$commitline["title"] = $co['title'];
-			$commitline["authorname"] = $co['author_name'];
-			$commitline["rfc2822"] = $ad['rfc2822'];
-			$commitline["comment"] = $co['comment'];
-			$commitlines[] = $commitline;
-		}
-	}
-	$tpl->assign("commitlines",$commitlines);
-	$tpl->display("log.tpl");
+	$tpl->display('log.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_message.php
+++ b/include/display.git_message.php
@@ -10,12 +10,17 @@
 function git_message($message, $error = FALSE, $standalone = TRUE)
 {
 	global $tpl;
-	$tpl->assign("message",$message);
-	if ($error)
-		$tpl->assign("error", TRUE);
-	if ($standalone)
-		$tpl->assign("standalone", TRUE);
-	$tpl->display("message.tpl");
+
+	$cachekey = sha1($message) . "|" . ($error ? "1" : "0") . "|" . ($standalone ? "1" : "0");
+
+	if (!$tpl->is_cached('message.tpl', $cachekey)) {
+		$tpl->assign("message",$message);
+		if ($error)
+			$tpl->assign("error", TRUE);
+		if ($standalone)
+			$tpl->assign("standalone", TRUE);
+	}
+	$tpl->display('message.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_opml.php
+++ b/include/display.git_opml.php
@@ -13,22 +13,27 @@
 function git_opml($projectroot,$projectlist)
 {
 	global $tpl,$gitphp_conf;
-	$projlist = git_read_projects($projectroot,$projectlist);
-	header("Content-type: text/xml; charset=UTF-8");
-	$tpl->assign("title",$gitphp_conf['title']);
-	$tpl->assign("self",script_url());
-	$opmllist = array();
-	foreach ($projlist as $cat => $plist) {
-		if (is_array($plist)) {
-			foreach ($plist as $i => $proj) {
-				$opmllist[] = $proj;
+
+	$cachekey = sha1(serialize($projectlist));
+
+	if (!$tpl->is_cached('opml.tpl', $cachekey)) {
+		header("Content-type: text/xml; charset=UTF-8");
+		$projlist = git_read_projects($projectroot,$projectlist);
+		$tpl->assign("title",$gitphp_conf['title']);
+		$tpl->assign("self",script_url());
+		$opmllist = array();
+		foreach ($projlist as $cat => $plist) {
+			if (is_array($plist)) {
+				foreach ($plist as $i => $proj) {
+					$opmllist[] = $proj;
+				}
+			} else {
+				$opmllist[] = $plist;
 			}
-		} else {
-			$opmllist[] = $plist;
 		}
+		$tpl->assign("opmllist",$opmllist);
 	}
-	$tpl->assign("opmllist",$opmllist);
-	$tpl->display("opml.tpl");
+	$tpl->display('opml.tpl', $cachekey);
 }
 
 ?>

--- 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_project_list.php
+++ b/include/display.git_project_list.php
@@ -16,55 +16,60 @@
 function git_project_list($projectroot,$projectlist,$order = "project")
 {
 	global $tpl,$git_projects;
-	$projects = git_read_projects($projectroot,$projectlist, TRUE);
-	if (is_array($projects)) {
-		if (count($projects) > 0) {
-			if ($order)
-				$tpl->assign("order",$order);
-			if (!isset($git_projects)) {
-				switch ($order) {
-					case "project":
-						usort($projects,"projectcmp");
-						break;
-					case "descr":
-						usort($projects,"descrcmp");
-						break;
-					case "owner":
-						usort($projects,"ownercmp");
-						break;
-					case "age":
-						usort($projects,"agecmp");
-						break;
-				}
-				$tpl->assign("projects",$projects);
-			} else {
-				foreach ($projects as $cat => $plist) {
+
+	$cachekey = sha1(serialize($projectlist)) . "|" . sha1($order);
+
+	if (!$tpl->is_cached('projectlist.tpl', $cachekey)) {
+		$projects = git_read_projects($projectroot,$projectlist, TRUE);
+		if (is_array($projects)) {
+			if (count($projects) > 0) {
+				if ($order)
+					$tpl->assign("order",$order);
+				if (!isset($git_projects)) {
 					switch ($order) {
 						case "project":
-							usort($projects[$cat],"projectcmp");
+							usort($projects,"projectcmp");
 							break;
 						case "descr":
-							usort($projects[$cat],"descrcmp");
+							usort($projects,"descrcmp");
 							break;
 						case "owner":
-							usort($projects[$cat],"ownercmp");
+							usort($projects,"ownercmp");
 							break;
 						case "age":
-							usort($projects[$cat],"agecmp");
+							usort($projects,"agecmp");
 							break;
 					}
+					$tpl->assign("projects",$projects);
+				} else {
+					foreach ($projects as $cat => $plist) {
+						switch ($order) {
+							case "project":
+								usort($projects[$cat],"projectcmp");
+								break;
+							case "descr":
+								usort($projects[$cat],"descrcmp");
+								break;
+							case "owner":
+								usort($projects[$cat],"ownercmp");
+								break;
+							case "age":
+								usort($projects[$cat],"agecmp");
+								break;
+						}
+					}
+					$tpl->assign("categorizedprojects",$projects);
 				}
-				$tpl->assign("categorizedprojects",$projects);
+			} else {
+				$tpl->assign("message","No projects found");
+				$tpl->assign("error",TRUE);
 			}
 		} else {
-			$tpl->assign("message","No projects found");
+			$tpl->assign("message",$projects);
 			$tpl->assign("error",TRUE);
 		}
-	} else {
-		$tpl->assign("message",$projects);
-		$tpl->assign("error",TRUE);
 	}
-	$tpl->display("projectlist.tpl");
+	$tpl->display('projectlist.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_rss.php
+++ b/include/display.git_rss.php
@@ -18,41 +18,46 @@
 function git_rss($projectroot,$project)
 {
 	global $tpl;
-	$head = git_read_head($projectroot . $project);
-	$revlist = git_read_revlist($projectroot . $project, $head, GITPHP_RSS_ITEMS);
 	header("Content-type: text/xml; charset=UTF-8");
-	$tpl->assign("self",script_url());
 
-	$commitlines = array();
-	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))
-			break;
-		$cd = date_str($co['committer_epoch']);
-		$difftree = array();
-		$diffout = git_diff_tree($projectroot . $project, $co['parent'] . " " . $co['id']);
-		$tok = strtok($diffout,"\n");
-		while ($tok !== false) {
-			if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$",$tok,$regs))
-				$difftree[] = $regs[7];
-			$tok = strtok("\n");
+	$cachekey = sha1($project);
+
+	if (!$tpl->is_cached('rss.tpl', $cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		$revlist = git_read_revlist($projectroot . $project, $head, GITPHP_RSS_ITEMS);
+		$tpl->assign("self",script_url());
+
+		$commitlines = array();
+		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))
+				break;
+			$cd = date_str($co['committer_epoch']);
+			$difftree = array();
+			$diffout = git_diff_tree($projectroot . $project, $co['parent'] . " " . $co['id']);
+			$tok = strtok($diffout,"\n");
+			while ($tok !== false) {
+				if (ereg("^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$",$tok,$regs))
+					$difftree[] = $regs[7];
+				$tok = strtok("\n");
+			}
+			$commitline = array();
+			$commitline["cdmday"] = $cd['mday'];
+			$commitline["cdmonth"] = $cd['month'];
+			$commitline["cdhour"] = $cd['hour'];
+			$commitline["cdminute"] = $cd['minute'];
+			$commitline["title"] = $co['title'];
+			$commitline["author"] = $co['author'];
+			$commitline["cdrfc2822"] = $cd['rfc2822'];
+			$commitline["commit"] = $commit;
+			$commitline["comment"] = $co['comment'];
+			$commitline["difftree"] = $difftree;
+			$commitlines[] = $commitline;
 		}
-		$commitline = array();
-		$commitline["cdmday"] = $cd['mday'];
-		$commitline["cdmonth"] = $cd['month'];
-		$commitline["cdhour"] = $cd['hour'];
-		$commitline["cdminute"] = $cd['minute'];
-		$commitline["title"] = $co['title'];
-		$commitline["author"] = $co['author'];
-		$commitline["cdrfc2822"] = $cd['rfc2822'];
-		$commitline["commit"] = $commit;
-		$commitline["comment"] = $co['comment'];
-		$commitline["difftree"] = $difftree;
-		$commitlines[] = $commitline;
+		$tpl->assign("commitlines",$commitlines);
 	}
-	$tpl->assign("commitlines",$commitlines);
-	$tpl->display("rss.tpl");
+	$tpl->display('rss.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_search.php
+++ b/include/display.git_search.php
@@ -10,74 +10,79 @@
 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;
 
-	if (!$gitphp_conf['search']) {
-		git_message("Search has been disabled", TRUE, TRUE);
-		return;
+	$cachekey = sha1($project) . "|" . $hash . "|" . sha1($searchtype) . "|" . sha1($search) . "|" . (isset($page) ? $page : 0);
+
+	if (!$tpl->is_cached('search.tpl', $cachekey)) {
+
+		if (!$gitphp_conf['search']) {
+			git_message("Search has been disabled", TRUE, TRUE);
+			return;
+		}
+
+		if (!isset($search) || (strlen($search) < 2)) {
+			git_message("You must enter search text of at least 2 characters", TRUE, TRUE);
+			return;
+		}
+		if (!isset($hash)) {
+			//$hash = git_read_head($projectroot . $project);
+			$hash = "HEAD";
+		}
+
+		$co = git_read_commit($projectroot . $project, $hash);
+
+		$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;
+		}
+
+		$tpl->assign("hash",$hash);
+		$tpl->assign("treehash",$co['tree']);
+
+		$tpl->assign("search",$search);
+		$tpl->assign("searchtype",$searchtype);
+		$tpl->assign("page",$page);
+		$revlistcount = count($revlist);
+		$tpl->assign("revlistcount",$revlistcount);
+
+		$tpl->assign("title",$co['title']);
+
+		$commitlines = array();
+		$commitcount = min(100,$revlistcount);
+		for ($i = 0; $i < $commitcount; $i++) {
+			$commit = $revlist[$i];
+			if (strlen(trim($commit)) > 0) {
+				$commitline = array();
+				$co2 = git_read_commit($projectroot . $project, $commit);
+				$commitline["commit"] = $commit;
+				$commitline["agestringage"] = $co2['age_string_age'];
+				$commitline["agestringdate"] = $co2['age_string_date'];
+				$commitline["authorname"] = $co2['author_name'];
+				$commitline["title_short"] = $co2['title_short'];
+				if (strlen($co2['title_short']) < strlen($co2['title']))
+					$commitline["title"] = $co2['title'];
+				$commitline["committree"] = $co2['tree'];
+				$matches = array();
+				foreach ($co2['comment'] as $comline) {
+					$hl = highlight($comline, $search, "searchmatch", GITPHP_TRIM_LENGTH);
+					if ($hl && (strlen($hl) > 0))
+						$matches[] = $hl;
+				}
+				$commitline["matches"] = $matches;
+				$commitlines[] = $commitline;
+			}
+		}
+		
+		$tpl->assign("commitlines",$commitlines);
 	}
-
-	if (!isset($search) || (strlen($search) < 2)) {
-		git_message("You must enter search text of at least 2 characters", TRUE, TRUE);
-		return;
-	}
-	if (!isset($hash)) {
-		//$hash = git_read_head($projectroot . $project);
-		$hash = "HEAD";
-	}
-
-	$co = git_read_commit($projectroot . $project, $hash);
-
-	$revlist = explode("\n",trim(git_rev_list($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;
-	}
-
-	$tpl->assign("hash",$hash);
-	$tpl->assign("treehash",$co['tree']);
-
-	$tpl->assign("search",$search);
-	$tpl->assign("searchtype",$searchtype);
-	$tpl->assign("page",$page);
-	$revlistcount = count($revlist);
-	$tpl->assign("revlistcount",$revlistcount);
-
-	$tpl->assign("title",$co['title']);
-
-	$commitlines = array();
-	$commitcount = min(100,$revlistcount);
-	for ($i = 0; $i < $commitcount; $i++) {
-		$commit = $revlist[$i];
-		if (strlen(trim($commit)) > 0) {
-			$commitline = array();
-			$co2 = git_read_commit($projectroot . $project, $commit);
-			$commitline["commit"] = $commit;
-			$commitline["agestringage"] = $co2['age_string_age'];
-			$commitline["agestringdate"] = $co2['age_string_date'];
-			$commitline["authorname"] = $co2['author_name'];
-			$commitline["title_short"] = $co2['title_short'];
-			if (strlen($co2['title_short']) < strlen($co2['title']))
-				$commitline["title"] = $co2['title'];
-			$commitline["committree"] = $co2['tree'];
-			$matches = array();
-			foreach ($co2['comment'] as $comline) {
-				$hl = highlight($comline, $search, "searchmatch", GITPHP_TRIM_LENGTH);
-				if ($hl && (strlen($hl) > 0))
-					$matches[] = $hl;
-			}
-			$commitline["matches"] = $matches;
-			$commitlines[] = $commitline;
-		}
-	}
-	
-	$tpl->assign("commitlines",$commitlines);
-	$tpl->display("search.tpl");
+	$tpl->display('search.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_search_files.php
+++ b/include/display.git_search_files.php
@@ -17,79 +17,84 @@
 {
 	global $tpl,$gitphp_conf;
 
-	if (!($gitphp_conf['search'] && $gitphp_conf['filesearch'])) {
-		git_message("File search has been disabled", TRUE, TRUE);
-		return;
+	$cachekey = sha1($project) . "|" . $hash . "|" . "filesearch" . "|" . sha1($search) . "|" . (isset($page) ? $page : 0);
+
+	if (!$tpl->is_cached('searchfiles.tpl', $cachekey)) {
+
+		if (!($gitphp_conf['search'] && $gitphp_conf['filesearch'])) {
+			git_message("File search has been disabled", TRUE, TRUE);
+			return;
+		}
+
+		if (!isset($search) || (strlen($search) < 2)) {
+			git_message("You must enter search text of at least 2 characters", TRUE, TRUE);
+			return;
+		}
+		if (!isset($hash)) {
+			//$hash = git_read_head($projectroot . $project);
+			$hash = "HEAD";
+		}
+
+		$co = git_read_commit($projectroot . $project, $hash);
+
+		$filesearch = git_filesearch($projectroot . $project, $hash, $search, false, ($page * 100), 101);
+
+		if (count($filesearch) < 1) {
+			git_message("No matches for '" . $search . "'.", FALSE, TRUE);
+			return;
+		}
+
+		$tpl->assign("hash",$hash);
+		$tpl->assign("treehash",$co['tree']);
+
+		$tpl->assign("search",$search);
+		$tpl->assign("searchtype","file");
+		$tpl->assign("page",$page);
+		$filesearchcount = count($filesearch);
+		$tpl->assign("filesearchcount",$filesearchcount);
+
+		$tpl->assign("title",$co['title']);
+
+		$filesearchlines = array();
+		$i = 0;
+		foreach ($filesearch as $file => $data) {
+			$filesearchline = array();
+			$filesearchline["file"] = $file;
+			if (strpos($file,"/") !== false) {
+				$f = basename($file);
+				$d = dirname($file);
+				if ($d == "/")
+					$d = "";
+				$hlt = highlight($f, $search, "searchmatch");
+				if ($hlt)
+					$hlt = $d . "/" . $hlt;
+			} else
+				$hlt = highlight($file, $search, "searchmatch");
+			if ($hlt)
+				$filesearchline["filename"] = $hlt;
+			else
+				$filesearchline["filename"] = $file;
+			$filesearchline["hash"] = $data['hash'];
+			if ($data['type'] == "tree")
+				$filesearchline["tree"] = TRUE;
+			if (isset($data['lines'])) {
+				$matches = array();
+				foreach ($data['lines'] as $line) {
+					$hlt = highlight($line,$search,"searchmatch",floor(GITPHP_TRIM_LENGTH*1.5),true);
+					if ($hlt)
+						$matches[] = $hlt;
+				}
+				if (count($matches) > 0)
+					$filesearchline["matches"] = $matches;
+			}
+			$filesearchlines[] = $filesearchline;
+			$i++;
+			if ($i >= 100)
+				break;
+		}
+		$tpl->assign("filesearchlines",$filesearchlines);
 	}
-
-	if (!isset($search) || (strlen($search) < 2)) {
-		git_message("You must enter search text of at least 2 characters", TRUE, TRUE);
-		return;
-	}
-	if (!isset($hash)) {
-		//$hash = git_read_head($projectroot . $project);
-		$hash = "HEAD";
-	}
-
-	$co = git_read_commit($projectroot . $project, $hash);
-
-	$filesearch = git_filesearch($projectroot . $project, $hash, $search, false, ($page * 100), 101);
-
-	if (count($filesearch) < 1) {
-		git_message("No matches for '" . $search . "'.", FALSE, TRUE);
-		return;
-	}
-
-	$tpl->assign("hash",$hash);
-	$tpl->assign("treehash",$co['tree']);
-
-	$tpl->assign("search",$search);
-	$tpl->assign("searchtype","file");
-	$tpl->assign("page",$page);
-	$filesearchcount = count($filesearch);
-	$tpl->assign("filesearchcount",$filesearchcount);
-
-	$tpl->assign("title",$co['title']);
-
-	$filesearchlines = array();
-	$i = 0;
-	foreach ($filesearch as $file => $data) {
-		$filesearchline = array();
-		$filesearchline["file"] = $file;
-		if (strpos($file,"/") !== false) {
-			$f = basename($file);
-			$d = dirname($file);
-			if ($d == "/")
-				$d = "";
-			$hlt = highlight($f, $search, "searchmatch");
-			if ($hlt)
-				$hlt = $d . "/" . $hlt;
-		} else
-			$hlt = highlight($file, $search, "searchmatch");
-		if ($hlt)
-			$filesearchline["filename"] = $hlt;
-		else
-			$filesearchline["filename"] = $file;
-		$filesearchline["hash"] = $data['hash'];
-		if ($data['type'] == "tree")
-			$filesearchline["tree"] = TRUE;
-		if (isset($data['lines'])) {
-			$matches = array();
-			foreach ($data['lines'] as $line) {
-				$hlt = highlight($line,$search,"searchmatch",floor(GITPHP_TRIM_LENGTH*1.5),true);
-				if ($hlt)
-					$matches[] = $hlt;
-			}
-			if (count($matches) > 0)
-				$filesearchline["matches"] = $matches;
-		}
-		$filesearchlines[] = $filesearchline;
-		$i++;
-		if ($i >= 100)
-			break;
-	}
-	$tpl->assign("filesearchlines",$filesearchlines);
-	$tpl->display("searchfiles.tpl");
+	$tpl->display('searchfiles.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_shortlog.php
+++ b/include/display.git_shortlog.php
@@ -16,45 +16,50 @@
 function git_shortlog($projectroot,$project,$hash,$page)
 {
 	global $tpl;
-	$head = git_read_head($projectroot . $project);
-	if (!isset($hash))
-		$hash = $head;
-	if (!isset($page))
-		$page = 0;
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("hash",$hash);
-	$tpl->assign("head",$head);
 
-	if ($page)
-		$tpl->assign("page",$page);
+	$cachekey = sha1($project) . "|" . $hash . "|" . (isset($page) ? $page : 0);
 
-	$revlist = git_read_revlist($projectroot . $project, $hash, 101, ($page * 100));
+	if (!$tpl->is_cached('shortlog.tpl', $cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		if (!isset($hash))
+			$hash = $head;
+		if (!isset($page))
+			$page = 0;
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("hash",$hash);
+		$tpl->assign("head",$head);
 
-	$revlistcount = count($revlist);
-	$tpl->assign("revlistcount",$revlistcount);
+		if ($page)
+			$tpl->assign("page",$page);
 
-	$commitlines = array();
-	$commitcount = min(100,count($revlist));
-	for ($i = 0; $i < $commitcount; $i++) {
-		$commit = $revlist[$i];
-		if (strlen(trim($commit)) > 0) {
-			$commitline = array();
-			if (isset($refs[$commit]))
-				$commitline["commitref"] = $refs[$commit];
-			$co = git_read_commit($projectroot . $project, $commit);
-			$ad = date_str($co['author_epoch']);
-			$commitline["commit"] = $commit;
-			$commitline["agestringage"] = $co['age_string_age'];
-			$commitline["agestringdate"] = $co['age_string_date'];
-			$commitline["authorname"] = $co['author_name'];
-			$commitline["title_short"] = $co['title_short'];
-			if (strlen($co['title_short']) < strlen($co['title']))
-				$commitline["title"] = $co['title'];
-			$commitlines[] = $commitline;
+		$revlist = git_read_revlist($projectroot . $project, $hash, 101, ($page * 100));
+
+		$revlistcount = count($revlist);
+		$tpl->assign("revlistcount",$revlistcount);
+
+		$commitlines = array();
+		$commitcount = min(100,count($revlist));
+		for ($i = 0; $i < $commitcount; $i++) {
+			$commit = $revlist[$i];
+			if (strlen(trim($commit)) > 0) {
+				$commitline = array();
+				if (isset($refs[$commit]))
+					$commitline["commitref"] = $refs[$commit];
+				$co = git_read_commit($projectroot . $project, $commit);
+				$ad = date_str($co['author_epoch']);
+				$commitline["commit"] = $commit;
+				$commitline["agestringage"] = $co['age_string_age'];
+				$commitline["agestringdate"] = $co['age_string_date'];
+				$commitline["authorname"] = $co['author_name'];
+				$commitline["title_short"] = $co['title_short'];
+				if (strlen($co['title_short']) < strlen($co['title']))
+					$commitline["title"] = $co['title'];
+				$commitlines[] = $commitline;
+			}
 		}
+		$tpl->assign("commitlines",$commitlines);
 	}
-	$tpl->assign("commitlines",$commitlines);
-	$tpl->display("shortlog.tpl");
+	$tpl->display('shortlog.tpl', $cachekey);
 }
 
 ?>

--- 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/display.git_summary.php
+++ b/include/display.git_summary.php
@@ -19,53 +19,58 @@
 function git_summary($projectroot,$project)
 {
 	global $tpl;
-	$descr = git_project_descr($projectroot,$project);
-	$head = git_read_head($projectroot . $project);
-	$commit = git_read_commit($projectroot . $project, $head);
-	$commitdate = date_str($commit['committer_epoch'],$commit['committer_tz']);
-	$owner = git_project_owner($projectroot,$project);
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("head",$head);
-	$tpl->assign("description",$descr);
-	$tpl->assign("owner",$owner);
-	$tpl->assign("lastchange",$commitdate['rfc2822']);
-	$revlist = git_read_revlist($projectroot . $project, $head, 17);
-	foreach ($revlist as $i => $rev) {
-		$revdata = array();
-		$revco = git_read_commit($projectroot . $project, $rev);
-		$authordate = date_str($revco['author_epoch']);
-		$revdata["commit"] = $rev;
-		if (isset($refs[$rev]))
-			$revdata["commitref"] = $refs[$rev];
-		$revdata["commitage"] = $revco['age_string'];
-		$revdata["commitauthor"] = $revco['author_name'];
-		if (strlen($revco['title_short']) < strlen($revco['title'])) {
-			$revdata["title"] = $revco['title'];
-			$revdata["title_short"] = $revco['title_short'];
-		} else
-			$revdata["title_short"] = $revco['title'];
-		$revlist[$i] = $revdata;
+
+	$cachekey = sha1($project);
+
+	if (!$tpl->is_cached('project.tpl', $cachekey)) {
+		$descr = git_project_descr($projectroot,$project);
+		$head = git_read_head($projectroot . $project);
+		$commit = git_read_commit($projectroot . $project, $head);
+		$commitdate = date_str($commit['committer_epoch'],$commit['committer_tz']);
+		$owner = git_project_owner($projectroot,$project);
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("head",$head);
+		$tpl->assign("description",$descr);
+		$tpl->assign("owner",$owner);
+		$tpl->assign("lastchange",$commitdate['rfc2822']);
+		$revlist = git_read_revlist($projectroot . $project, $head, 17);
+		foreach ($revlist as $i => $rev) {
+			$revdata = array();
+			$revco = git_read_commit($projectroot . $project, $rev);
+			$authordate = date_str($revco['author_epoch']);
+			$revdata["commit"] = $rev;
+			if (isset($refs[$rev]))
+				$revdata["commitref"] = $refs[$rev];
+			$revdata["commitage"] = $revco['age_string'];
+			$revdata["commitauthor"] = $revco['author_name'];
+			if (strlen($revco['title_short']) < strlen($revco['title'])) {
+				$revdata["title"] = $revco['title'];
+				$revdata["title_short"] = $revco['title_short'];
+			} else
+				$revdata["title_short"] = $revco['title'];
+			$revlist[$i] = $revdata;
+		}
+		$tpl->assign("revlist",$revlist);
+
+		$taglist = git_read_refs($projectroot,$project,"refs/tags");
+		if (isset($taglist) && (count($taglist) > 0)) {
+			foreach ($taglist as $i => $tag) {
+				if (isset($tag['comment'])) {
+					$com = trim($tag['comment'][0]);
+					if (strlen($com) > GITPHP_TRIM_LENGTH)
+						$com = substr($trimmed,0,GITPHP_TRIM_LENGTH) . "...";
+					$taglist[$i]['comment'] = $com;
+				}
+			}
+			$tpl->assign("taglist",$taglist);
+		}
+
+		$headlist = git_read_refs($projectroot,$project,"refs/heads");
+		if (isset($headlist) && (count($headlist) > 0)) {
+			$tpl->assign("headlist",$headlist);
+		}
 	}
-	$tpl->assign("revlist",$revlist);
-
-	$taglist = git_read_refs($projectroot,$project,"refs/tags");
-	if (isset($taglist) && (count($taglist) > 0)) {
-		foreach ($taglist as $i => $tag) {
-			if (isset($tag['comment'])) {
-				$com = trim($tag['comment'][0]);
-				if (strlen($com) > GITPHP_TRIM_LENGTH)
-					$com = substr($trimmed,0,GITPHP_TRIM_LENGTH) . "...";
-				$taglist[$i]['comment'] = $com;
-			}
-		}
-		$tpl->assign("taglist",$taglist);
-	}
-
-	$headlist = git_read_refs($projectroot,$project,"refs/heads");
-	if (isset($headlist) && (count($headlist) > 0)) {
-		$tpl->assign("headlist",$headlist);
-	}
-	$tpl->display("project.tpl");
+	$tpl->display('project.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_tag.php
+++ b/include/display.git_tag.php
@@ -15,18 +15,23 @@
 {
 	global $tpl;
 
-	$head = git_read_head($projectroot . $project);
-	$tpl->assign("head",$head);
-	$tpl->assign("hash", $hash);
+	$cachekey = sha1($project) . "|" . $hash;
 
-	$tag = git_read_tag($projectroot . $project, $hash);
+	if (!$tpl->is_cached('tag.tpl', $cachekey)) {
 
-	$tpl->assign("tag",$tag);
-	if (isset($tag['author'])) {
-		$ad = date_str($tag['epoch'],$tag['tz']);
-		$tpl->assign("datedata",$ad);
+		$head = git_read_head($projectroot . $project);
+		$tpl->assign("head",$head);
+		$tpl->assign("hash", $hash);
+
+		$tag = git_read_tag($projectroot . $project, $hash);
+
+		$tpl->assign("tag",$tag);
+		if (isset($tag['author'])) {
+			$ad = date_str($tag['epoch'],$tag['tz']);
+			$tpl->assign("datedata",$ad);
+		}
 	}
-	$tpl->display("tag.tpl");
+	$tpl->display('tag.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_tags.php
+++ b/include/display.git_tags.php
@@ -13,13 +13,18 @@
 function git_tags($projectroot,$project)
 {
 	global $tpl;
-	$head = git_read_head($projectroot . $project);
-	$tpl->assign("head",$head);
-	$taglist = git_read_refs($projectroot, $project, "refs/tags");
-	if (isset($taglist) && (count($taglist) > 0)) {
-		$tpl->assign("taglist",$taglist);
+
+	$cachekey = sha1($project);
+
+	if (!$tpl->is_cached('tags.tpl', $cachekey)) {
+		$head = git_read_head($projectroot . $project);
+		$tpl->assign("head",$head);
+		$taglist = git_read_refs($projectroot, $project, "refs/tags");
+		if (isset($taglist) && (count($taglist) > 0)) {
+			$tpl->assign("taglist",$taglist);
+		}
 	}
-	$tpl->display("tags.tpl");
+	$tpl->display('tags.tpl', $cachekey);
 }
 
 ?>

--- a/include/display.git_tree.php
+++ b/include/display.git_tree.php
@@ -18,46 +18,51 @@
 function git_tree($projectroot,$project,$hash,$file,$hashbase)
 {
 	global $tpl;
-	if (!isset($hash)) {
-		$hash = git_read_head($projectroot . $project);
+
+	$cachekey = sha1($project) . "|" . $hashbase . "|" . $hash . "|" . sha1($file);
+
+	if (!$tpl->is_cached('tree.tpl', $cachekey)) {
+		if (!isset($hash)) {
+			$hash = git_read_head($projectroot . $project);
+			if (isset($file))
+				$hash = git_get_hash_by_path($projectroot . $project, ($hashbase?$hashbase:$hash),$file,"tree");
+				if (!isset($hashbase))
+					$hashbase = $hash;
+		}
+		$lsout = git_ls_tree($projectroot . $project, $hash, TRUE);
+		$refs = read_info_ref($projectroot . $project);
+		$tpl->assign("hash",$hash);
+		if (isset($hashbase))
+			$tpl->assign("hashbase",$hashbase);
+		if (isset($hashbase) && ($co = git_read_commit($projectroot . $project, $hashbase))) {
+			$basekey = $hashbase;
+			$tpl->assign("fullnav",TRUE);
+			$tpl->assign("title",$co['title']);
+			if (isset($refs[$hashbase]))
+				$tpl->assign("hashbaseref",$refs[$hashbase]);
+		}
+		$paths = git_path_trees($projectroot . $project, $hashbase, $file);
+		$tpl->assign("paths",$paths);
+
 		if (isset($file))
-			$hash = git_get_hash_by_path($projectroot . $project, ($hashbase?$hashbase:$hash),$file,"tree");
-			if (!isset($hashbase))
-				$hashbase = $hash;
+			$tpl->assign("base",$file . "/");
+
+		$treelines = array();
+		$tok = strtok($lsout,"\0");
+		while ($tok !== false) {
+			if (ereg("^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$",$tok,$regs)) {
+				$treeline = array();
+				$treeline["filemode"] = mode_str($regs[1]);
+				$treeline["type"] = $regs[2];
+				$treeline["hash"] = $regs[3];
+				$treeline["name"] = $regs[4];
+				$treelines[] = $treeline;
+			}
+			$tok = strtok("\0");
+		}
+		$tpl->assign("treelines",$treelines);
 	}
-	$lsout = git_ls_tree($projectroot . $project, $hash, TRUE);
-	$refs = read_info_ref($projectroot . $project);
-	$tpl->assign("hash",$hash);
-	if (isset($hashbase))
-		$tpl->assign("hashbase",$hashbase);
-	if (isset($hashbase) && ($co = git_read_commit($projectroot . $project, $hashbase))) {
-		$basekey = $hashbase;
-		$tpl->assign("fullnav",TRUE);
-		$tpl->assign("title",$co['title']);
-		if (isset($refs[$hashbase]))
-			$tpl->assign("hashbaseref",$refs[$hashbase]);
-	}
-	$paths = git_path_trees($projectroot . $project, $hashbase, $file);
-	$tpl->assign("paths",$paths);
-
-	if (isset($file))
-		$tpl->assign("base",$file . "/");
-
-	$treelines = array();
-	$tok = strtok($lsout,"\0");
-	while ($tok !== false) {
-		if (ereg("^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$",$tok,$regs)) {
-			$treeline = array();
-			$treeline["filemode"] = mode_str($regs[1]);
-			$treeline["type"] = $regs[2];
-			$treeline["hash"] = $regs[3];
-			$treeline["name"] = $regs[4];
-			$treelines[] = $treeline;
-		}
-		$tok = strtok("\0");
-	}
-	$tpl->assign("treelines",$treelines);
-	$tpl->display("tree.tpl");
+	$tpl->display('tree.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
@@ -22,27 +22,45 @@
   */
  require_once('config/gitphp.conf.php');
 
- /*
-  * Debug
-  */
- if ($gitphp_conf['debug']) {
- 	define('GITPHP_START_TIME', microtime(true));
-	error_reporting(E_ALL|E_STRICT);
- }
+ $extraoutput = FALSE;
 
  /*
   * Instantiate Smarty
   */
  require_once($gitphp_conf['smarty_prefix'] . "Smarty.class.php");
  $tpl =& new Smarty;
- if (isset($_GET['a']) &&
-     ($_GET['a'] != "commitdiff_plain") &&
-     ($_GET['a'] != "blob_plain") &&
-     ($_GET['a'] != "blobdiff_plain") &&
-     ($_GET['a'] != "rss") &&
-     ($_GET['a'] != "opml")) {
+ if ((!isset($_GET['a'])) || (
+     	($_GET['a'] != "commitdiff_plain") &&
+     	($_GET['a'] != "blob_plain") &&
+     	($_GET['a'] != "blobdiff_plain") &&
+     	($_GET['a'] != "rss") &&
+     	($_GET['a'] != "opml") &&
+	($_GET['a'] != "snapshot"))) {
 	$tpl->load_filter('output','trimwhitespace');
+	$extraoutput = TRUE;
 }
+
+ /*
+  * Debug
+  */
+ if ($gitphp_conf['debug']) {
+ 	if ($extraoutput) {
+		define('GITPHP_START_TIME', microtime(true));
+		error_reporting(E_ALL|E_STRICT);
+	}
+ }
+
+/*
+ * Caching
+ */
+ 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);
+	}
+ }
 
 /*
  * Setup global assigns used everywhere (such as header/footer)
@@ -76,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") {
@@ -187,7 +210,7 @@
  	git_project_list($gitphp_conf['projectroot'],$git_projects,(isset($_GET['o']) ? $_GET['o'] : "project"));
  }
 
- if ($gitphp_conf['debug'])
+ if ($gitphp_conf['debug'] && $extraoutput)
  	echo "Execution time: " . round(microtime(true) - GITPHP_START_TIME, 8) . " sec";
 
 ?>

--- a/templates/blob.tpl
+++ b/templates/blob.tpl
@@ -13,7 +13,13 @@
    {if $fullnav}
      <a href="{$SCRIPT_NAME}?p={$project}&a=summary">summary</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=shortlog">shortlog</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=log">log</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=commit&h={$hashbase}">commit</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=commitdiff&h={$hashbase}">commitdiff</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=tree&h={$tree}&hb={$hashbase}">tree</a><br />
      {if $file}
-       <a href="{$SCRIPT_NAME}?p={$project}&a=blob_plain&h={$hash}&f={$file}">plain</a> | <a href="{$SCRIPT_NAME}?p={$project}&a=blob&hb=HEAD&f={$file}">head</a><br />
+       <a href="{$SCRIPT_NAME}?p={$project}&a=blob_plain&h={$hash}&f={$file}">plain</a> | 
+       {if ($hashbase != "HEAD") && ($hashbase != $head)}
+         <a href="{$SCRIPT_NAME}?p={$project}&a=blob&hb=HEAD&f={$file}">HEAD</a>
+       {else}
+         HEAD
+       {/if}
+       <br />
      {else}
        <a href="{$SCRIPT_NAME}?p={$project}&a=blob_plain&h={$hash}">plain</a><br />
      {/if}

--- /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/header.tpl
+++ b/templates/header.tpl
@@ -5,14 +5,13 @@
  *
  *  Copyright (C) 2006 Christopher Han <xiphux@gmail.com>
  *}
-{* $contentType = strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') === false ? 'text/html' : 'application/xhtml+xml';
-header("Content-Type: $contentType; charset=utf-8"); *}
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   <!-- gitphp web interface {$version}, (C) 2006 Christopher Han <xiphux@gmail.com> -->
   <head>
     <title>{$pagetitle}{if $project && $validproject} :: {$project}{if $action && $validaction}/{$action}{/if}{/if}</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     {if $validproject}
       <link rel="alternate" title="{$project} log" href="{$SCRIPT_NAME}?p={$project}&a=rss" type="application/rss+xml" />
     {/if}

--- 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