Fix trees with multiple names getting mixed up
Fix trees with multiple names getting mixed up

This addresses three issues:
1. Git stores trees as a hash of their content, which has nothing to do
with their name. Therefore, two different directories with the exact
same content but different names could end up pointing to the exact same
tree hash. This caused problems on the tree view, when it attempted to
look up the tree name by its hash - this is actually something you just
can't do reliably in git because of this system.
2. Previously, the tree view used the object cache when enumerating
items in the tree. However, because of the problem above, you could
have two copies of the same tree under two different names. Because the
object cache stores by reference, fetching and populating the tree with
data during its second appearance in the tree actually affected and
overwrote what was populated during its first appearance in the tree.
This happened during the model load, before the template was rendered,
so instead of showing the first instance and then the second instance,
it looked like the second instance appeared twice.
3. Previously the FilesystemObject class used a system where you would
assign the name to an object. This was a leftover from before it was
split out of the Tree object. Doing it this way meant there were
inconsistencies all over the place as far as what was being set for the
name - sometimes it was just the base file name, sometimes it was the
full file path, and every now and then it would have to be converted
back and forth.

Now, these are fixed:
1. Now, the f= parameter is taken as the source of truth and used to set
up the path for the tree/blob.
2. Now, the tree enumeration doesn't use the cache in order to prevent
this situation.
3. Now, for consistency, the ability to set a name for a filesystem
object is removed. You can only set the full path now using SetPath,
SetName is removed. The name can still be fetched but it is
automatically derived as the base file name of the path.

--- a/include/controller/Controller_Blame.class.php
+++ b/include/controller/Controller_Blame.class.php
@@ -122,7 +122,7 @@
 		
 		$blob = $this->project->GetBlob($this->params['hash']);
 		if ($this->params['file'])
-			$blob->SetName(basename($this->params['file']));
+			$blob->SetPath($this->params['file']);
 		$blob->SetCommit($commit);
 		$this->tpl->assign('blob', $blob);
 
@@ -140,7 +140,7 @@
 			if (class_exists('GeSHi')) {
 				$geshi = new GeSHi("",'php');
 				if ($geshi) {
-					$lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetPath(),'.'),1));
+					$lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetName(),'.'),1));
 					if (!empty($lang)) {
 						$geshi->enable_classes();
 						$geshi->enable_strict_mode(GESHI_MAYBE);

--- a/include/controller/Controller_Blob.class.php
+++ b/include/controller/Controller_Blob.class.php
@@ -114,7 +114,7 @@
 					$saveas = $this->params['hash'] . ".txt";
 
 				$blob = $this->project->GetBlob($this->params['hash']);
-				$blob->SetName($this->params['file']);
+				$blob->SetPath($this->params['file']);
 
 				$headers = array();
 
@@ -155,7 +155,7 @@
 
 		$blob = $this->project->GetBlob($this->params['hash']);
 		if ($this->params['file'])
-			$blob->SetName(basename($this->params['file']));
+			$blob->SetPath($this->params['file']);
 		$blob->SetCommit($commit);
 		$this->tpl->assign('blob', $blob);
 
@@ -188,7 +188,7 @@
 			if (class_exists('GeSHi')) {
 				$geshi = new GeSHi("",'php');
 				if ($geshi) {
-					$lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetPath(),'.'),1));
+					$lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetName(),'.'),1));
 					if (!empty($lang)) {
 						$geshi->enable_classes();
 						$geshi->enable_strict_mode(GESHI_MAYBE);

--- a/include/controller/Controller_Tree.class.php
+++ b/include/controller/Controller_Tree.class.php
@@ -125,10 +125,10 @@
 		if (!$tree->GetCommit()) {
 			$tree->SetCommit($commit);
 		}
+		if (isset($this->params['file'])) {
+			$tree->SetPath($this->params['file']);
+		}
 		$this->tpl->assign('tree', $tree);
-
-		if (!isset($this->params['file']))
-			$this->params['file'] = $tree->GetPath();
 	}
 
 }

--- a/include/git/Blob.class.php
+++ b/include/git/Blob.class.php
@@ -333,9 +333,6 @@
 	private function FileMime_Extension()
 	{
 		$file = $this->GetName();
-
-		if (empty($file))
-			$file = $this->GetPath();
 
 		if (empty($file))
 			return '';

--- a/include/git/Commit.class.php
+++ b/include/git/Commit.class.php
@@ -744,43 +744,13 @@
 		if (!$this->hashPathsRead)
 			$this->ReadHashPaths();
 
-		foreach ($this->blobPaths as $h => $p) {
-			if ($path == $p) {
-				return $h;
-			}
-		}
-
-		foreach ($this->treePaths as $h => $p) {
-			if ($path == $p) {
-				return $h;
-			}
-		}
-
-		return '';
-	}
-
-	/**
-	 * HashToPath
-	 *
-	 * Given a blob/tree hash, get its path
-	 *
-	 * @access public
-	 * @param string $hash hash
-	 * @return string path
-	 */
-	public function HashToPath($hash)
-	{
-		if (empty($hash))
-			return '';
-
-		if (!$this->hashPathsRead)
-			$this->ReadHashPaths();
-
-		if (isset($this->blobPaths[$hash]))
-			return $this->blobPaths[$hash];
-
-		if (isset($this->treePaths[$hash]))
-			return $this->treePaths[$hash];
+		if (isset($this->blobPaths[$path])) {
+			return $this->blobPaths[$path];
+		}
+
+		if (isset($this->treePaths[$path])) {
+			return $this->treePaths[$path];
+		}
 
 		return '';
 	}
@@ -810,10 +780,10 @@
 			if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/", $line, $regs)) {
 				switch ($regs[2]) {
 					case 'tree':
-						$this->treePaths[$regs[3]] = trim($regs[4]);
+						$this->treePaths[trim($regs[4])] = $regs[3];
 						break;
 					case 'blob';
-						$this->blobPaths[$regs[3]] = trim($regs[4]);
+						$this->blobPaths[trim($regs[4])] = $regs[3];
 						break;
 				}
 			}
@@ -839,7 +809,7 @@
 
 		$results = array();
 
-		foreach ($this->treePaths as $hash => $path) {
+		foreach ($this->treePaths as $path => $hash) {
 			if (preg_match('/' . $pattern . '/i', $path)) {
 				$obj = $this->project->GetTree($hash);
 				$obj->SetCommit($this);
@@ -847,7 +817,7 @@
 			}
 		}
 
-		foreach ($this->blobPaths as $hash => $path) {
+		foreach ($this->blobPaths as $path => $hash) {
 			if (preg_match('/' . $pattern . '/i', $path)) {
 				$obj = $this->project->GetBlob($hash);
 				$obj->SetCommit($this);

--- a/include/git/FilesystemObject.class.php
+++ b/include/git/FilesystemObject.class.php
@@ -22,13 +22,13 @@
 {
 
 	/**
-	 * name
-	 *
-	 * Stores the object name
-	 *
-	 * @access protected
-	 */
-	protected $name = '';
+	 * path
+	 *
+	 * Stores the object path
+	 *
+	 * @access protected
+	 */
+	protected $path = '';
 
 	/**
 	 * mode
@@ -101,11 +101,8 @@
 	 */
 	public function GetName()
 	{
-		if (!empty($this->name))
-			return $this->name;
-
-		if ($this->commit)
-			return basename($this->commit->HashToPath($this->hash));
+		if (!empty($this->path))
+			return basename($this->path);
 
 		return '';
 	}
@@ -120,36 +117,23 @@
 	 */
 	public function GetPath()
 	{
-		if ($this->commit) {
-			$name = $this->commit->HashToPath($this->hash);
-			if (!empty($name)) {
-				return $name;
-			}
-		}
-
-		$name = $this->name;
-
-		if ($this->parent) {
-			$parentname = $this->parent->GetPath();
-			if (!empty($parentname)) {
-				$name = $parentname . '/' . $this->name;
-			}
-		}
-
-		return $name;
-	}
-
-	/**
-	 * SetName
-	 *
-	 * Sets the object name
-	 *
-	 * @access public
-	 * @param string $name tree name
-	 */
-	public function SetName($name)
-	{
-		$this->name = $name;
+		if (!empty($this->path))
+			return $this->path;
+
+		return '';
+	}
+
+	/**
+	 * SetPath
+	 *
+	 * Sets the object path
+	 *
+	 * @access public
+	 * @param string $path object path
+	 */
+	public function SetPath($path)
+	{
+		$this->path = $path;
 	}
 
 	/**
@@ -290,29 +274,26 @@
 	{
 		$this->pathTreeRead = true;
 
-		$path = $this->GetPath();
-		
-		if (empty($path)) {
+		if (empty($this->path)) {
 			/* this is a top level tree, it has no subpath */
-			$this->SetName('[' . $this->project->GetProject() . ']');
 			return;
 		}
+
+		$path = $this->path;
 
 		while (($pos = strrpos($path, '/')) !== false) {
 			$path = substr($path, 0, $pos);
 			$pathhash = $this->commit->PathToHash($path);
 			if (!empty($pathhash)) {
 				$parent = $this->project->GetTree($pathhash);
-				$parent->SetName(basename($path));
+				$parent->SetPath($path);
 				$this->pathTree[] = $parent;
 			}
 		}
 
-		$parent = $this->commit->GetTree();
-		$parent->SetName('[' . $this->project->GetProject() . ']');
-		$this->pathTree[] = $parent;
-
-		$this->pathTree = array_reverse($this->pathTree);
+		if (count($this->pathTree) > 0) {
+			$this->pathTree = array_reverse($this->pathTree);
+		}
 	}
 
 	/**

--- a/include/git/Tree.class.php
+++ b/include/git/Tree.class.php
@@ -98,18 +98,24 @@
 			if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})(\s+[0-9]+|\s+-)?\t(.+)$/", $line, $regs)) {
 				switch($regs[2]) {
 					case 'tree':
-						$t = $this->project->GetTree($regs[3]);
+						$t = new GitPHP_Tree($this->project, $regs[3]);
 						$t->SetMode($regs[1]);
-						$t->SetName($regs[5]);
+						$path = $regs[5];
+						if (!empty($this->path))
+							$path = $this->path . '/' . $path;
+						$t->SetPath($path);
 						$t->SetParent($this);
 						if ($this->commit)
 							$t->SetCommit($this->commit);
 						$this->contents[] = $t;
 						break;
 					case 'blob':
-						$b = $this->project->GetBlob($regs[3]);
+						$b = new GitPHP_Blob($this->project, $regs[3]);
 						$b->SetMode($regs[1]);
-						$b->SetName($regs[5]);
+						$path = $regs[5];
+						if (!empty($this->path))
+							$path = $this->path . '/' . $path;
+						$b->SetPath($path);
 						$size = trim($regs[4]);
 						if (!empty($size))
 							$b->SetSize($regs[4]);

--- a/templates/path.tpl
+++ b/templates/path.tpl
@@ -11,8 +11,10 @@
 <div class="page_path">
 	{if $pathobject}
 		{assign var=pathobjectcommit value=$pathobject->GetCommit()}
+		{assign var=pathobjecttree value=$pathobjectcommit->GetTree()}
+		<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&a=tree&hb={$pathobjectcommit->GetHash()}&h={$pathobjecttree->GetHash()}"><strong>[{$project->GetProject()}]</strong></a> / 
 		{foreach from=$pathobject->GetPathTree() item=pathtreepiece}
-			<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&a=tree&hb={$pathobjectcommit->GetHash()}&h={$pathtreepiece->GetHash()}"><strong>{$pathtreepiece->GetName()}</strong></a> / 
+			<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&a=tree&hb={$pathobjectcommit->GetHash()}&h={$pathtreepiece->GetHash()}&f={$pathtreepiece->GetPath()}"><strong>{$pathtreepiece->GetName()}</strong></a> / 
 		{/foreach}
 		{if $pathobject instanceof GitPHP_Blob}
 			{if $target == 'blobplain'}
@@ -22,9 +24,9 @@
 			{else}
 				<strong>{$pathobject->GetName()}</strong>
 			{/if}
-		{else}
+		{elseif $pathobject->GetName()}
 			{if $target == 'tree'}
-				<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&a=tree&hb={$pathobjectcommit->GetHash()}&h={$pathobject->GetHash()}"><strong>{$pathobject->GetName()}</strong></a> / 
+				<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&a=tree&hb={$pathobjectcommit->GetHash()}&h={$pathobject->GetHash()}&f={$pathobject->GetPath()}"><strong>{$pathobject->GetName()}</strong></a> / 
 			{else}
 				<strong>{$pathobject->GetName()}</strong> / 
 			{/if}

comments