Start using classes for tree diffs
Start using classes for tree diffs

--- a/include/controller/Controller_Commit.class.php
+++ b/include/controller/Controller_Commit.class.php
@@ -9,9 +9,6 @@
  * @package GitPHP
  * @subpackage Controller
  */
-
-require_once(GITPHP_INCLUDEDIR . 'util.file_type.php');
-require_once(GITPHP_INCLUDEDIR . 'gitutil.git_diff_tree.php');
 
 /**
  * Commit controller class
@@ -89,60 +86,11 @@
 	protected function LoadData()
 	{
 		$commit = $this->project->GetCommit($this->params['hash']);
-		$parentObj = $commit->GetParent();
-		if ($parentObj) {
-			$root = "";
-			$parent = $parentObj->GetHash();
-		} else {
-			$root = "--root";
-			$parent = "";
-		}
-		$diffout = git_diff_tree($root . " " . $parent . " " . $hash, TRUE);
-		$difftree = explode("\n",$diffout);
-		$treeObj = $commit->GetTree();
-		if ($treeObj)
-			$this->tpl->assign("tree", $treeObj->GetHash());
-		if ($parentObj)
-			$this->tpl->assign("parent", $parentObj->GetHash());
-		$this->tpl->assign("commit", $commit);
-		$this->tpl->assign("difftreesize",count($difftree)+1);
-		$difftreelines = array();
-		foreach ($difftree as $i => $line) {
-			if (preg_match("/^:([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;
-			}
-		}
-		$this->tpl->assign("difftreelines",$difftreelines);
+		$this->tpl->assign('commit', $commit);
+		$this->tpl->assign('tree', $commit->GetTree());
+		$treediff = $commit->DiffToParent();
+		$treediff->SetRenames(true);
+		$this->tpl->assign('treediff', $treediff);
 	}
 
 }

--- a/include/controller/Controller_Rss.class.php
+++ b/include/controller/Controller_Rss.class.php
@@ -10,8 +10,7 @@
  * @subpackage Controller
  */
 
-require_once(GITPHP_INCLUDEDIR . 'gitutil.git_read_revlist.php');
-require_once(GITPHP_INCLUDEDIR . 'gitutil.git_diff_tree.php');
+define('GITPHP_RSS_ITEMS', 150);
 
 /**
  * RSS controller class
@@ -95,40 +94,24 @@
 	 */
 	protected function LoadData()
 	{
-		$head = $this->project->GetHeadCommit();;
-		$revlist = git_read_revlist($head->GetHash(), GITPHP_RSS_ITEMS);
+		$log = $this->project->GetLog('HEAD', GITPHP_RSS_ITEMS);
 
-		$commitlines = array();
-		$revlistcount = count($revlist);
-		for ($i = 0; $i < $revlistcount; ++$i) {
-			$commit = $revlist[$i];
-			$co = $this->project->GetCommit($commit);
-			if (($i >= 20) && ((time() - $co->GetCommitterEpoch()) > 48*60*60))
-				break;
-			$commitline = array();
-			$commitline["committerepoch"] = $co->GetCommitterEpoch();
-			$commitline["title"] = $co->GetTitle();
-			$commitline["author"] = $co->GetAuthor();
-			$commitline["commit"] = $commit;
-			$commitline["comment"] = $co->GetComment();
+		$entries = count($log);
 
-			$parent = $co->GetParent();
-			if ($parent) {
-				$difftree = array();
-				$diffout = git_diff_tree($parent->GetHash() . " " . $co->GetHash());
-				$tok = strtok($diffout,"\n");
-				while ($tok !== false) {
-					if (preg_match("/^:([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");
+		if ($entries > 20) {
+			/*
+			 * Don't show commits older than 48 hours,
+			 * but show a minimum of 20 entries
+			 */
+			for ($i = 20; $i < $entries; ++$i) {
+				if ((time() - $log[$i]->GetCommitterEpoch()) > 48*60*60) {
+					$log = array_slice($log, 0, $i);
+					break;
 				}
-				$commitline["difftree"] = $difftree;
 			}
+		}
 
-			$commitlines[] = $commitline;
-			unset($co);
-		}
-		$this->tpl->assign("commitlines",$commitlines);
+		$this->tpl->assign('log', $log);
 	}
 
 }

--- a/include/defs.constants.php
+++ b/include/defs.constants.php
@@ -9,8 +9,6 @@
 
 define("GITPHP_TRIM_LENGTH", 50);
 
-define("GITPHP_RSS_ITEMS", 150);
-
 define("GITPHP_COMPRESS_BZ2", 1);
 define("GITPHP_COMPRESS_GZ", 2);
 define("GITPHP_COMPRESS_ZIP", 3);

--- /dev/null
+++ b/include/git/Blob.class.php
@@ -1,1 +1,47 @@
+<?php
+/**
+ * GitPHP Blob
+ *
+ * Represents a single blob
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2010 Christopher Han
+ * @package GitPHP
+ * @subpackage Git
+ */
 
+require_once(GITPHP_GITOBJECTDIR . 'GitObject.class.php');
+
+/**
+ * Commit class
+ *
+ * @package GitPHP
+ * @subpackage Git
+ */
+class GitPHP_Blob extends GitPHP_GitObject
+{
+	
+	/**
+	 * FileType
+	 *
+	 * Gets a file type from its octal mode
+	 *
+	 * @access public
+	 * @static
+	 * @param string $octMode octal mode
+	 * @return string file type
+	 */
+	public static function FileType($octMode)
+	{
+		$mode = octdec($octMode);
+		if (($mode & 0x4000) == 0x4000)
+			return 'directory';
+		else if (($mode & 0xA000) == 0xA000)
+			return 'symlink';
+		else if (($mode & 0x8000) == 0x8000)
+			return 'file';
+		return 'unknown';
+	}
+
+}
+

--- a/include/git/Commit.class.php
+++ b/include/git/Commit.class.php
@@ -13,6 +13,7 @@
 require_once(GITPHP_INCLUDEDIR . 'defs.commands.php');
 require_once(GITPHP_GITOBJECTDIR . 'GitObject.class.php');
 require_once(GITPHP_GITOBJECTDIR . 'Tree.class.php');
+require_once(GITPHP_GITOBJECTDIR . 'TreeDiff.class.php');
 
 /**
  * Commit class
@@ -558,5 +559,18 @@
 		return $data;
 	}
 
+	/**
+	 * DiffToParent
+	 *
+	 * Diffs this commit with its immediate parent
+	 *
+	 * @access public
+	 * @return mixed Tree diff
+	 */
+	public function DiffToParent()
+	{
+		return new GitPHP_TreeDiff($this->project, $this->hash);
+	}
+
 }
 

--- /dev/null
+++ b/include/git/FileDiff.class.php
@@ -1,1 +1,444 @@
-
+<?php
+/**
+ * GitPHP File Diff
+ *
+ * Represents a single file difference
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2010 Christopher Han
+ * @package GitPHP
+ * @subpackage Git
+ */
+
+require_once(GITPHP_GITOBJECTDIR . 'Blob.class.php');
+
+/**
+ * Commit class
+ *
+ * @package GitPHP
+ * @subpackage Git
+ */
+class GitPHP_FileDiff
+{
+	/**
+	 * diffInfoRead
+	 *
+	 * Stores whether diff info has been read
+	 *
+	 * @access protected
+	 */
+	protected $diffInfoRead = false;
+
+	/**
+	 * fromMode
+	 *
+	 * Stores the from file mode
+	 *
+	 * @access protected
+	 */
+	protected $fromMode;
+
+	/**
+	 * toMode
+	 *
+	 * Stores the to file mode
+	 *
+	 * @access protected
+	 */
+	protected $toMode;
+
+	/**
+	 * fromHash
+	 *
+	 * Stores the from hash
+	 *
+	 * @access protected
+	 */
+	protected $fromHash;
+
+	/**
+	 * toHash
+	 *
+	 * Stores the to hash
+	 *
+	 * @access protected
+	 */
+	protected $toHash;
+
+	/**
+	 * status
+	 *
+	 * Stores the status
+	 *
+	 * @access protected
+	 */
+	protected $status;
+
+	/**
+	 * similarity
+	 *
+	 * Stores the similarity
+	 *
+	 * @access protected
+	 */
+	protected $similarity;
+
+	/**
+	 * fromFile
+	 *
+	 * Stores the from filename
+	 *
+	 * @access protected
+	 */
+	protected $fromFile;
+
+	/**
+	 * toFile
+	 *
+	 * Stores the to filename
+	 *
+	 * @access protected
+	 */
+	protected $toFile;
+
+	/**
+	 * fromFileType
+	 *
+	 * Stores the from file type
+	 *
+	 * @access protected
+	 */
+	protected $fromFileType;
+
+	/**
+	 * toFileType
+	 *
+	 * Stores the to file type
+	 *
+	 * @access protected
+	 */
+	protected $toFileType;
+
+	/**
+	 * __construct
+	 *
+	 * Constructor
+	 *
+	 * @access public
+	 * @param string $fromHash source hash, can also be a diff-tree info line
+	 * @param string $toHash target hash, required if $fromHash is a hash
+	 * @return mixed FileDiff object
+	 * @throws Exception on invalid parameters
+	 */
+	public function __construct($fromHash, $toHash = '')
+	{
+		if ($this->ParseDiffTreeLine($fromHash))
+			return;
+
+		if (!(preg_match('/^[0-9a-fA-F]{40}$/', $fromHash) && preg_match('/^[0-9]a-fA-F]{40}$/', $toHash))) {
+			throw new Exception('Invalid parameters for FileDiff');
+		}
+
+		
+	}
+
+	/**
+	 * ParseDiffTreeLine
+	 *
+	 * @access private
+	 * @param string $diffTreeLine line from difftree
+	 * @return boolean true if data was read from line
+	 */
+	private function ParseDiffTreeLine($diffTreeLine)
+	{
+		if (preg_match('/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/', $diffTreeLine, $regs)) {
+			$this->diffInfoRead = true;
+
+			$this->fromMode = $regs[1];
+			$this->toMode = $regs[2];
+			$this->fromHash = $regs[3];
+			$this->toHash = $regs[4];
+			$this->status = $regs[5];
+			$this->similarity = ltrim($regs[6], '0');
+			$this->fromFile = strtok($regs[7], "\t");
+			$this->toFile = strtok("\t");
+			if ($this->toFile === false) {
+				/* no filename change */
+				$this->toFile = $this->fromFile;
+			}
+
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * ReadDiffInfo
+	 *
+	 * Reads file diff info
+	 *
+	 * @access protected
+	 */
+	protected function ReadDiffInfo()
+	{
+		$this->diffInfoRead = true;
+
+		/* TODO: read a single difftree line on-demand */
+	}
+
+	/**
+	 * GetFromMode
+	 *
+	 * Gets the from file mode
+	 * (full a/u/g/o)
+	 *
+	 * @access public
+	 * @return string from file mode
+	 */
+	public function GetFromMode()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->fromMode;
+	}
+
+	/**
+	 * GetFromModeShort
+	 *
+	 * Gets the from file mode in short form
+	 * (standard u/g/o)
+	 *
+	 * @access public
+	 * @return string short from file mode
+	 */
+	public function GetFromModeShort()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return substr($this->fromMode, -4);
+	}
+
+	/**
+	 * GetToMode
+	 *
+	 * Gets the to file mode
+	 * (full a/u/g/o)
+	 *
+	 * @access public
+	 * @return string to file mode
+	 */
+	public function GetToMode()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->toMode;
+	}
+
+	/**
+	 * GetToModeShort
+	 *
+	 * Gets the to file mode in short form
+	 * (standard u/g/o)
+	 *
+	 * @access public
+	 * @return string short to file mode
+	 */
+	public function GetToModeShort()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return substr($this->toMode, -4);
+	}
+
+	/**
+	 * GetFromHash
+	 *
+	 * Gets the from hash
+	 *
+	 * @access public
+	 * @return string from hash
+	 */
+	public function GetFromHash()
+	{
+		return $this->fromHash;
+	}
+
+	/**
+	 * GetToHash
+	 *
+	 * Gets the to hash
+	 *
+	 * @access public
+	 * @return string to hash
+	 */
+	public function GetToHash()
+	{
+		return $this->toHash;
+	}
+
+	/**
+	 * GetStatus
+	 *
+	 * Gets the status of the change
+	 *
+	 * @access public
+	 * @return string status
+	 */
+	public function GetStatus()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->status;
+	}
+
+	/**
+	 * GetSimilarity
+	 *
+	 * Gets the similarity
+	 *
+	 * @access public
+	 * @return string similarity
+	 */
+	public function GetSimilarity()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->similarity;
+	}
+
+	/**
+	 * GetFromFile
+	 *
+	 * Gets the from file name
+	 *
+	 * @access public
+	 * @return string from file
+	 */
+	public function GetFromFile()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->fromFile;
+	}
+
+	/**
+	 * GetToFile
+	 *
+	 * Gets the to file name
+	 *
+	 * @access public
+	 * @return string to file
+	 */
+	public function GetToFile()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return $this->toFile;
+	}
+
+	/**
+	 * GetFromFileType
+	 *
+	 * Gets the from file type
+	 *
+	 * @access public
+	 * @return string from file type
+	 */
+	public function GetFromFileType()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return GitPHP_Blob::FileType($this->fromMode);
+	}
+
+	/**
+	 * GetToFileType
+	 *
+	 * Gets the to file type
+	 *
+	 * @access public
+	 * @return string to file type
+	 */
+	public function GetToFileType()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return GitPHP_Blob::FileType($this->toMode);
+	}
+
+	/**
+	 * FileTypeChanged
+	 *
+	 * Tests if filetype changed
+	 *
+	 * @access public
+	 * @return boolean true if file type changed
+	 */
+	public function FileTypeChanged()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return (octdec($this->fromMode) & 0x17000) != (octdec($this->toMode) & 0x17000);
+	}
+
+	/**
+	 * FileModeChanged
+	 *
+	 * Tests if file mode changed
+	 *
+	 * @access public
+	 * @return boolean true if file mode changed
+	 */
+	public function FileModeChanged()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return (octdec($this->fromMode) & 0777) != (octdec($this->toMode) & 0777);
+	}
+
+	/**
+	 * FromFileIsRegular
+	 *
+	 * Tests if the from file is a regular file
+	 *
+	 * @access public
+	 * @return boolean true if from file is regular
+	 */
+	public function FromFileIsRegular()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return (octdec($this->fromMode) & 0x8000) == 0x8000;
+	}
+
+	/**
+	 * ToFileIsRegular
+	 *
+	 * Tests if the to file is a regular file
+	 *
+	 * @access public
+	 * @return boolean true if to file is regular
+	 */
+	public function ToFileIsRegular()
+	{
+		if (!$this->diffInfoRead)
+			$this->ReadDiffInfo();
+
+		return (octdec($this->toMode) & 0x8000) == 0x8000;
+	}
+}
+

--- /dev/null
+++ b/include/git/TreeDiff.class.php
@@ -1,1 +1,289 @@
-
+<?php
+/**
+ * GitPHP Tree Diff
+ *
+ * Represents differences between two commit trees
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2010 Christopher Han
+ * @package GitPHP
+ * @subpackage Git
+ */
+
+require_once(GITPHP_GITOBJECTDIR . 'FileDiff.class.php');
+
+/**
+ * TreeDiff class
+ *
+ * @package GitPHP
+ * @subpackage Git
+ */
+class GitPHP_TreeDiff implements Iterator
+{
+	
+	/**
+	 * fromHash
+	 *
+	 * Stores the from hash
+	 *
+	 * @access protected
+	 */
+	protected $fromHash;
+
+	/**
+	 * toHash
+	 *
+	 * Stores the to hash
+	 *
+	 * @access protected
+	 */
+	protected $toHash;
+
+	/**
+	 * renames
+	 *
+	 * Stores whether to detect renames
+	 *
+	 * @access protected
+	 */
+	protected $renames;
+
+	/**
+	 * project
+	 *
+	 * Stores the project
+	 *
+	 * @access protected
+	 */
+	protected $project;
+
+	/**
+	 * fileDiffs
+	 *
+	 * Stores the individual file diffs
+	 *
+	 * @access protected
+	 */
+	protected $fileDiffs = array();
+
+	/**
+	 * dataRead
+	 *
+	 * Stores whether data has been read
+	 *
+	 * @access protected
+	 */
+	protected $dataRead = false;
+
+	/**
+	 * __construct
+	 *
+	 * Constructor
+	 *
+	 * @access public
+	 * @param mixed $project project
+	 * @param string $toHash to commit hash
+	 * @param string $fromHash from commit hash
+	 * @param boolean $renames whether to detect file renames
+	 * @return mixed TreeDiff object
+	 * @throws Exception exception on invalid parameters
+	 */
+	public function __construct($project, $toHash, $fromHash = '', $renames = false)
+	{
+		$this->project = $project;
+
+		$toCommit = $this->project->GetCommit($toHash);
+		$this->toHash = $toHash;
+
+		if (empty($fromHash)) {
+			$parent = $toCommit->GetParent();
+			if ($parent) {
+				$this->fromHash = $parent->GetHash();
+			}
+		} else {
+			$fromCommit = $this->project->GetCommit($fromHash);
+			$this->fromHash = $fromHash;
+		}
+
+		$this->renames = $renames;
+	}
+
+	/**
+	 * ReadData
+	 *
+	 * Reads the tree diff data
+	 *
+	 * @access private
+	 */
+	private function ReadData()
+	{
+		$this->dataRead = true;
+
+		$this->fileDiffs = array();
+
+		$exe = new GitPHP_GitExe($this->project);
+
+		$args = array();
+
+		$args[] = '-r';
+		if ($this->renames)
+			$args[] = '-M';
+
+		if (empty($this->fromHash))
+			$args[] = '--root';
+		else
+			$args[] = $this->fromHash;
+
+		$args[] = $this->toHash;
+
+		$diffTreeLines = explode("\n", $exe->Execute(GIT_DIFF_TREE, $args));
+
+		foreach ($diffTreeLines as $line) {
+			$trimmed = trim($line);
+			if (strlen($trimmed) > 0) {
+				$this->fileDiffs[] = new GitPHP_FileDiff($trimmed);
+			}
+		}
+
+		unset($exe);
+	}
+
+	/**
+	 * GetFromHash
+	 *
+	 * Gets the from hash for this treediff
+	 *
+	 * @access public
+	 * @return string from hash
+	 */
+	public function GetFromHash()
+	{
+		return $this->fromHash;
+	}
+
+	/**
+	 * GetToHash
+	 *
+	 * Gets the to hash for this treediff
+	 *
+	 * @access public
+	 * @return string to hash
+	 */
+	public function GetToHash()
+	{
+		return $this->toHash;
+	}
+
+	/**
+	 * GetRenames
+	 *
+	 * Get whether this treediff is set to detect renames
+	 *
+	 * @access public
+	 * @return boolean true if renames will be detected
+	 */
+	public function GetRenames()
+	{
+		return $this->renames;
+	}
+
+	/**
+	 * SetRenames
+	 *
+	 * Set whether this treediff is set to detect renames
+	 *
+	 * @access public
+	 * @param boolean $renames whether to detect renames
+	 */
+	public function SetRenames($renames)
+	{
+		if ($renames == $this->renames)
+			return;
+
+		$this->renames = $renames;
+		$this->dataRead = false;
+	}
+
+	/**
+	 * rewind
+	 *
+	 * Rewinds the iterator
+	 */
+	function rewind()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return reset($this->fileDiffs);
+	}
+
+	/**
+	 * current
+	 *
+	 * Returns the current element in the array
+	 */
+	function current()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return current($this->fileDiffs);
+	}
+
+	/**
+	 * key
+	 *
+	 * Returns the current key
+	 */
+	function key()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return key($this->fileDiffs);
+	}
+
+	/**
+	 * next
+	 *
+	 * Advance the pointer
+	 */
+	function next()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return next($this->fileDiffs);
+	}
+
+	/**
+	 * valid
+	 *
+	 * Test for a valid pointer
+	 */
+	function valid()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return key($this->fileDiffs) !== null;
+	}
+
+	/**
+	 * Count
+	 *
+	 * Gets the number of file changes in this treediff
+	 *
+	 * @access public
+	 * @return integer count of file changes
+	 */
+	public function Count()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return count($this->fileDiffs);
+	}
+
+}
+

--- a/templates/commit.tpl
+++ b/templates/commit.tpl
@@ -10,12 +10,12 @@
 
  <div class="page_nav">
    {* Nav *}
-   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=summary">summary</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=shortlog&h={$commit->GetHash()}">shortlog</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=log&h={$commit->GetHash()}">log</a> | commit | {if $parent}<a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=commitdiff&h={$commit->GetHash()}">commitdiff</a> | {/if}<a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree}&hb={$commit->GetHash()}">tree</a>
+   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=summary">summary</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=shortlog&h={$commit->GetHash()}">shortlog</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=log&h={$commit->GetHash()}">log</a> | commit | {if $commit->GetParent()}<a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=commitdiff&h={$commit->GetHash()}">commitdiff</a> | {/if}<a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree->GetHash()}&hb={$commit->GetHash()}">tree</a>
    <br /><br />
  </div>
  <div class="title">
    {* Commit header *}
-   {if $parent}
+   {if $commit->GetParent()}
      <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=commitdiff&h={$commit->GetHash()}" class="title">{$commit->GetTitle()}</a>
      <span class="refs">
      {assign var=heads value=$commit->GetHeads()}
@@ -36,7 +36,7 @@
      {/if}
      </span>
    {else}
-     <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree}&hb={$commit->GetHash()}" class="title">{$commit->GetTitle()}</a>
+     <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree->GetHash()}&hb={$commit->GetHash()}" class="title">{$commit->GetTitle()}</a>
    {/if}
  </div>
  <div class="title_text">
@@ -70,8 +70,8 @@
      <tr>
      <tr>
        <td>tree</td>
-       <td class="monospace"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree}&hb={$commit->GetHash()}" class="list">{$tree}</a></td>
-       <td class="link"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree}&hb={$commit->GetHash()}">tree</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=snapshot&h={$commit->GetHash()}">snapshot</a></td>
+       <td class="monospace"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree->GetHash()}&hb={$commit->GetHash()}" class="list">{$tree->GetHash()}</a></td>
+       <td class="link"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=tree&h={$tree->GetHash()}&hb={$commit->GetHash()}">tree</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=snapshot&h={$commit->GetHash()}">snapshot</a></td>
      </tr>
      {foreach from=$commit->GetParents() item=par}
        <tr>
@@ -88,42 +88,127 @@
    {/foreach}
  </div>
  <div class="list_head">
-   {if $difftreesize > 11}
-     {$difftreesize} files changed:
+   {if $treediff->Count() > 10}
+     {$treediff->Count()} files changed:
    {/if}
  </div>
  <table cellspacing="0">
    {* Loop and show files changed *}
-   {section name=difftree loop=$difftreelines}
+   {foreach from=$treediff item=diffline}
      <tr class="{cycle values="light,dark"}">
 	 
-       {if $difftreelines[difftree].status == "A"}
-         <td><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}" class="list">{$difftreelines[difftree].file}</a></td>
-         <td><span class="newfile">[new {$difftreelines[difftree].to_filetype}{if $difftreelines[difftree].isreg} with mode: {$difftreelines[difftree].to_mode_cut}{/if}]</span></td>
-         <td class="link"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}">blob</a></td>
-       {elseif $difftreelines[difftree].status == "D"}
-         <td><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}" class="list">{$difftreelines[difftree].file}</a></td>
-         <td><span class="deletedfile">[deleted {$difftreelines[difftree].from_filetype}]</span></td>
-         <td class="link"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}">blob</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=history&h={$commit->GetHash()}&f={$difftreelines[difftree].file}">history</a></td>
-       {elseif $difftreelines[difftree].status == "M" || $difftreelines[difftree].status == "T"}
-         <td>
-           {if $difftreelines[difftree].to_id != $difftreelines[difftree].from_id}
-             <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$difftreelines[difftree].to_id}&hp={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}" class="list">{$difftreelines[difftree].file}</a>
+       {if $diffline->GetStatus() == "A"}
+         <td>
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}" class="list">
+	     {$diffline->GetFromFile()}
+	   </a>
+	 </td>
+         <td>
+	   <span class="newfile">
+	     [
+	     {if $diffline->ToFileIsRegular()}
+	     new {$diffline->GetToFileType()} with mode: {$diffline->GetToModeShort()}
+	     {else}
+	     new {$diffline->GetToFileType()}
+	     {/if}
+	     ]
+	   </span>
+	 </td>
+         <td class="link">
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}">blob</a>
+	 </td>
+       {elseif $diffline->GetStatus() == "D"}
+         <td>
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}" class="list">
+	     {$diffline->GetFromFile()}
+	   </a>
+	 </td>
+         <td>
+	   <span class="deletedfile">
+	     [ deleted {$diffline->GetFromFileType()} ]
+	   </span>
+	 </td>
+         <td class="link">
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}">blob</a> | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=history&h={$commit->GetHash()}&f={$diffline->GetFromFile()}">history</a>
+	 </td>
+       {elseif $diffline->GetStatus() == "M" || $diffline->GetStatus() == "T"}
+         <td>
+           {if $diffline->GetToHash() != $diffline->GetFromHash()}
+             <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$diffline->GetToHash()}&hp={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}" class="list">
+	       {$diffline->GetToFile()}
+	     </a>
            {else}
-             <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}" class="list">{$difftreelines[difftree].file}</a>
+             <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}" class="list">
+	       {$diffline->GetToFile()}
+	     </a>
            {/if}
          </td>
-         <td>{if $difftreelines[difftree].from_mode != $difftreelines[difftree].to_mode} <span class="changedfile">[changed{$difftreelines[difftree].modechange}]</span>{/if}</td>
-         <td class="link">
-           <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}">blob</a>{if $difftreelines[difftree].to_id != $difftreelines[difftree].from_id} | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$difftreelines[difftree].to_id}&hp={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].file}">diff</a>{/if} | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=history&h={$commit->GetHash()}&f={$difftreelines[difftree].file}">history</a></td>
-       {elseif $difftreelines[difftree].status == "R"}
-         <td><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].to_file}" class="list">{$difftreelines[difftree].to_file}</a></td>
-         <td><span class="movedfile">[moved from <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].from_file}" class="list">{$difftreelines[difftree].from_file}</a> with {$difftreelines[difftree].similarity}% similarity{$difftreelines[difftree].simmodechg}]</span></td>
-         <td class="link"><a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$difftreelines[difftree].to_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].to_file}">blob</a>{if $difftreelines[difftree].to_id != $difftreelines[difftree].from_id} | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$difftreelines[difftree].to_id}&hp={$difftreelines[difftree].from_id}&hb={$commit->GetHash()}&f={$difftreelines[difftree].to_file}">diff</a>{/if}</td>
+         <td>
+	   {if $diffline->GetFromMode() != $diffline->GetToMode()}
+	     <span class="changedfile">
+	       [
+	       {if $diffline->FileTypeChanged()}
+	         {if $diffline->FileModeChanged()}
+		   {if $diffline->FromFileIsRegular() && $diffline->ToFileIsRegular()}
+		     changed from {$diffline->GetFromFileType()} to {$diffline->GetToFileType()} mode: {$diffline->GetFromModeShort()} -&gt; {$diffline->GetToModeShort()}
+		   {elseif $diffline->ToFileIsRegular()}
+		     changed from {$diffline->GetFromFileType()} to {$diffline->GetToFileType()} mode: {$diffline->GetToModeShort()}
+		   {else}
+		     changed from {$diffline->GetFromFileType()} to {$diffline->GetToFileType()}
+		   {/if}
+		 {else}
+		   changed from {$diffline->GetFromFileType()} to {$diffline->GetToFileType()}
+		 {/if}
+	       {else}
+	         {if $diffline->FileModeChanged()}
+		   {if $diffline->FromFileIsRegular() && $diffline->ToFileIsRegular()}
+		     changed mode: {$diffline->GetFromModeShort()} -&gt; {$diffline->GetToModeShort()}
+		   {elseif $diffline->ToFileIsRegular()}
+		     changed mode: {$diffline->GetToModeShort()}
+		   {else}
+		     changed
+		   {/if}
+		 {else}
+		   changed
+		 {/if}
+	       {/if}
+	       ]
+	     </span>
+	   {/if}
+	 </td>
+         <td class="link">
+           <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}">blob</a>
+	   {if $diffline->GetToHash() != $diffline->GetFromHash()}
+	     | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$diffline->GetToHash()}&hp={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}">diff</a>
+	   {/if}
+	     | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=history&h={$commit->GetHash()}&f={$diffline->GetFromFile()}">history</a>
+	 </td>
+       {elseif $diffline->GetStatus() == "R"}
+         <td>
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}" class="list">
+	     {$diffline->GetToFile()}</a>
+	 </td>
+         <td>
+	   <span class="movedfile">
+	     [
+	     {if $diffline->GetFromMode() != $diffline->GetToMode()}
+	       moved from <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}" class="list">{$diffline->GetFromFile()}</a> with {$diffline->GetSimilarity()}% similarity, mode: {$diffline->GetToModeShort()}
+	     {else}
+	       moved from <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetFromFile()}" class="list">{$diffline->GetFromFile()}</a> with {$diffline->GetSimilarity()}% similarity
+	     {/if}
+	     ]
+	   </span>
+	 </td>
+         <td class="link">
+	   <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blob&h={$diffline->GetToHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}">blob</a>
+	   {if $diffline->GetToHash() != $diffline->GetFromHash()}
+	     | <a href="{$SCRIPT_NAME}?p={$project->GetProject()}&a=blobdiff&h={$diffline->GetToHash()}&hp={$diffline->GetFromHash()}&hb={$commit->GetHash()}&f={$diffline->GetToFile()}">diff</a>
+	   {/if}
+	 </td>
        {/if}
 
      </tr>
-   {/section}
+   {/foreach}
  </table>
 
  {include file='footer.tpl'}

--- a/templates/rss.tpl
+++ b/templates/rss.tpl
@@ -13,26 +13,26 @@
     <description>{$project->GetProject()} log</description>
     <language>en</language>
 
-    {section name=rssitem loop=$commitlines}
+    {foreach from=$log item=logitem}
       <item>
-        <title>{$commitlines[rssitem].committerepoch|date_format:"%d %b %R"} - {$commitlines[rssitem].title|escape:'html'}</title>
-        <author>{$commitlines[rssitem].author|escape:'html'}</author>
-        <pubDate>{$commitlines[rssitem].committerepoch|date_format:"%a, %d %b %Y %H:%M:%S %z"}</pubDate>
-        <guid isPermaLink="true">{$self}?p={$project->GetProject()}&amp;a=commit&amp;h={$commitlines[rssitem].commit}</guid>
-        <link>{$self}?p={$project->GetProject()}&amp;a=commit&amp;h={$commitlines[rssitem].commit}</link>
-        <description>{$commitlines[rssitem].title|escape:'html'}</description>
+        <title>{$logitem->GetCommitterEpoch()|date_format:"%d %b %R"} - {$logitem->GetTitle()|escape:'html'}</title>
+        <author>{$logitem->GetAuthor()|escape:'html'}</author>
+        <pubDate>{$logitem->GetCommitterEpoch()|date_format:"%a, %d %b %Y %H:%M:%S %z"}</pubDate>
+        <guid isPermaLink="true">{$self}?p={$project->GetProject()}&amp;a=commit&amp;h={$logitem->GetHash()}</guid>
+        <link>{$self}?p={$project->GetProject()}&amp;a=commit&amp;h={$logitem->GetHash()}</link>
+        <description>{$logitem->GetTitle()|escape:'html'}</description>
         <content:encoded>
           <![CDATA[
-          {foreach from=$commitlines[rssitem].comment item=line}
+          {foreach from=$logitem->GetComment() item=line}
             {$line}<br />
           {/foreach}
-          {foreach from=$commitlines[rssitem].difftree item=line}
-            {$line}<br />
+          {foreach from=$logitem->DiffToParent() item=diffline}
+            {$diffline->GetToFile()}<br />
           {/foreach}
           ]]>
         </content:encoded>
       </item>
-    {/section}
+    {/foreach}
 
   </channel>
 </rss>

comments