Object cache support for tags
Object cache support for tags

Also improves performance by not requiring the entire tag list to be
loaded if a single tag object is needed. The full list is loaded if all
tags are needed (and it doesn't reload already loaded tags).

--- a/include/controller/Controller_Tag.class.php
+++ b/include/controller/Controller_Tag.class.php
@@ -112,7 +112,7 @@
 		$head = $this->project->GetHeadCommit();
 		$this->tpl->assign('head', $head);
 
-		$tag = new GitPHP_Tag($this->project, $this->params['hash']);
+		$tag = $this->project->GetTag($this->params['hash']);
 
 		$this->tpl->assign("tag", $tag);
 	}

--- a/include/git/Project.class.php
+++ b/include/git/Project.class.php
@@ -734,13 +734,34 @@
 		if (empty($tag))
 			return null;
 
-		if (!$this->readTags)
-			$this->ReadTagList();
-
-		if (isset($this->tags[$tag]))
-			return $this->tags[$tag];
-
-		return null;
+		if (!isset($this->tags[$tag])) {
+			$this->LoadTag($tag);
+		}
+
+		return $this->tags[$tag];
+	}
+
+	/**
+	 * LoadTag
+	 *
+	 * Attempts to load a cached tag, or creates a new object
+	 *
+	 * @access private
+	 * @param string $tag tag to find
+	 * @return mixed tag object
+	 */
+	private function LoadTag($tag)
+	{
+		if (empty($tag))
+			return;
+
+		$cacheKey = 'project|' . $this->project . '|tag|' . $tag;
+		$cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
+		if ($cached) {
+			$this->tags[$tag] = $cached;
+		} else {
+			$this->tags[$tag] = new GitPHP_Tag($this, $tag);
+		}
 	}
 
 	/**
@@ -772,8 +793,15 @@
 							$this->tags[$regs[2]]->SetCommit($derefCommit);
 						}
 							
-					} else {
-							$this->tags[$regs[2]] = new GitPHP_Tag($this, $regs[2], $regs[1]);
+					} else if (!isset($this->tags[$regs[2]])) {
+							$this->LoadTag($regs[2]);
+							if (isset($this->tags[$regs[2]])) {
+								$tagHash = $this->tags[$regs[2]]->GetHash();
+								if (empty($tagHash)) {
+									// New non-cached tag object
+									$this->tags[$regs[2]]->SetHash($regs[1]);
+								}
+							}
 					}
 				} catch (Exception $e) {
 				}

--- a/include/git/Ref.class.php
+++ b/include/git/Ref.class.php
@@ -18,7 +18,7 @@
  * @package GitPHP
  * @subpackage Git
  */
-class GitPHP_Ref extends GitPHP_GitObject
+abstract class GitPHP_Ref extends GitPHP_GitObject
 {
 	
 	/**
@@ -59,9 +59,23 @@
 		$this->refName = $refName;
 		if (!empty($refHash)) {
 			$this->SetHash($refHash);
-		} else {
+		}
+	}
+
+	/**
+	 * GetHash
+	 *
+	 * Gets the hash for this ref (overrides base)
+	 *
+	 * @access public
+	 * @return string object hash
+	 */
+	public function GetHash()
+	{
+		if (empty($this->hash))
 			$this->FindHash();
-		}
+
+		return $this->hash;
 	}
 
 	/**
@@ -74,7 +88,7 @@
 	 */
 	protected function FindHash()
 	{
-		$exe = new GitPHP_GitExe($this->project);
+		$exe = new GitPHP_GitExe($this->GetProject());
 		$args = array();
 		$args[] = '--hash';
 		$args[] = '--verify';
@@ -88,7 +102,7 @@
 	}
 
 	/**
-	 * GetName()
+	 * GetName
 	 *
 	 * Gets the ref name
 	 *
@@ -136,7 +150,22 @@
 	 */
 	public function GetFullPath()
 	{
-		return $this->project->GetPath() . '/' . $this->GetRefPath();
+		return $this->GetProject()->GetPath() . '/' . $this->GetRefPath();
+	}
+
+	/**
+	 * __sleep
+	 *
+	 * Called to prepare the object for serialization
+	 *
+	 * @access public
+	 * @return array list of properties to serialize
+	 */
+	public function __sleep()
+	{
+		$properties = array('refName', 'refDir');
+
+		return array_merge($properties, parent::__sleep());
 	}
 
 }

--- a/include/git/Tag.class.php
+++ b/include/git/Tag.class.php
@@ -95,6 +95,24 @@
 	protected $comment = array();
 
 	/**
+	 * objectReferenced
+	 *
+	 * Stores whether the object has been referenced into a pointer
+	 *
+	 * @access private
+	 */
+	private $objectReferenced = false;
+
+	/**
+	 * commitReferenced
+	 *
+	 * Stores whether the commit has been referenced into a pointer
+	 *
+	 * @access private
+	 */
+	private $commitReferenced = false;
+
+	/**
 	 * __construct
 	 *
 	 * Instantiates tag
@@ -124,6 +142,9 @@
 		if (!$this->dataRead)
 			$this->ReadData();
 
+		if ($this->objectReferenced)
+			$this->DereferenceObject();
+
 		return $this->object;
 	}
 
@@ -140,8 +161,11 @@
 		if (!$this->dataRead)
 			$this->ReadData();
 
+		if ($this->commitReferenced)
+			$this->DereferenceCommit();
+
 		if (!$this->commit)
-			$this->LoadCommit();
+			$this->ReadCommit();
 
 		return $this->commit;
 	}
@@ -156,7 +180,11 @@
 	 */
 	public function SetCommit($commit)
 	{
-		$this->commit = $commit;
+		if ($this->commitReferenced)
+			$this->DereferenceCommit();
+
+		if (!$this->commit)
+			$this->commit = $commit;
 	}
 
 	/**
@@ -271,6 +299,9 @@
 		if (!$this->dataRead)
 			$this->ReadData();
 
+		if ($this->objectReferenced)
+			$this->DereferenceObject();
+
 		if (!$this->object)
 			return true;
 
@@ -288,7 +319,7 @@
 	{
 		$this->dataRead = true;
 
-		$exe = new GitPHP_GitExe($this->project);
+		$exe = new GitPHP_GitExe($this->GetProject());
 		$args = array();
 		$args[] = '-t';
 		$args[] = $this->hash;
@@ -296,9 +327,10 @@
 		
 		if ($ret === 'commit') {
 			/* light tag */
-			$this->object = $this->project->GetCommit($this->hash);
+			$this->object = $this->GetProject()->GetCommit($this->hash);
 			$this->commit = $this->object;
 			$this->type = 'commit';
+			GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
 			return;
 		}
 
@@ -347,13 +379,13 @@
 		switch ($this->type) {
 			case 'commit':
 				try {
-					$this->object = $this->project->GetCommit($objectHash);
-					$this->commit = $this->GetObject();
+					$this->object = $this->GetProject()->GetCommit($objectHash);
+					$this->commit = $this->object;
 				} catch (Exception $e) {
 				}
 				break;
 			case 'tag':
-				$exe = new GitPHP_GitExe($this->project);
+				$exe = new GitPHP_GitExe($this->GetProject());
 				$args = array();
 				$args[] = 'tag';
 				$args[] = $objectHash;
@@ -363,12 +395,16 @@
 				foreach ($lines as $i => $line) {
 					if (preg_match('/^tag (.+)$/', $line, $regs)) {
 						$name = trim($regs[1]);
-						$this->object = new GitPHP_Tag($this->project, $name, $objectHash);
+						$this->object = $this->GetProject()->GetTag($name);
+						if ($this->object) {
+							$this->object->SetHash($objectHash);
+						}
 					}
 				}
 				break;
 		}
 
+		GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
 	}
 
 	/**
@@ -392,10 +428,155 @@
 
 		foreach ($lines as $line) {
 			if (preg_match('/^([0-9a-fA-F]{40}) refs\/tags\/' . preg_quote($this->refName) . '(\^{})$/', $line, $regs)) {
-				$this->commit = $this->project->GetCommit($regs[1]);
+				$this->commit = $this->GetProject()->GetCommit($regs[1]);
+				return;
 			}
 		}
-	}
+
+		GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
+	}
+
+	/**
+	 * ReferenceObject
+	 *
+	 * Turns the object into a reference pointer
+	 *
+	 * @access private
+	 */
+	private function ReferenceObject()
+	{
+		if ($this->objectReferenced)
+			return;
+
+		if (!$this->object)
+			return;
+
+		if ($this->type == 'commit') {
+			$this->object = $this->object->GetHash();
+		} else if ($this->type == 'tag') {
+			$this->object = $this->object->GetName();
+		}
+
+		$this->objectReferenced = true;
+	}
+
+	/**
+	 * DereferenceObject
+	 *
+	 * Turns the object pointer back into an object
+	 *
+	 * @access private
+	 */
+	private function DereferenceObject()
+	{
+		if (!$this->objectReferenced)
+			return;
+
+		if (empty($this->object))
+			return;
+
+		if ($this->type == 'commit') {
+			$this->object = $this->GetProject()->GetCommit($this->object);
+		} else if ($this->type == 'tag') {
+			$this->object = $this->GetProject()->GetTag($this->object);
+		}
+
+		$this->objectReferenced = false;
+	}
+
+	/**
+	 * ReferenceCommit
+	 *
+	 * Turns the commit into a reference pointer
+	 *
+	 * @access private
+	 */
+	private function ReferenceCommit()
+	{
+		if ($this->commitReferenced)
+			return;
+
+		if (!$this->commit)
+			return;
+
+		$this->commit = $this->commit->GetHash();
+
+		$this->commitReferenced = true;
+	}
+
+	/**
+	 * DereferenceCommit
+	 *
+	 * Turns the commit pointer back into an object
+	 *
+	 * @access private
+	 */
+	private function DereferenceCommit()
+	{
+		if (!$this->commitReferenced)
+			return;
+
+		if (empty($this->commit))
+			return;
+
+		if ($this->type == 'commit') {
+			$obj = $this->GetObject();
+			if ($obj && ($obj->GetHash() == $this->commit)) {
+				/*
+				 * Light tags are type commit and the commit
+				 * and object are the same, in which case
+				 * no need to fetch the object again
+				 */
+				$this->commit = $obj;
+				$this->commitReferenced = false;
+				return;
+			}
+		}
+
+		$this->commit = $this->GetProject()->GetCommit($this->commit);
+
+		$this->commitReferenced = false;
+	}
+
+	/**
+	 * __sleep
+	 *
+	 * Called to prepare the object for serialization
+	 *
+	 * @access public
+	 * @return array list of properties to serialize
+	 */
+	public function __sleep()
+	{
+		if (!$this->objectReferenced)
+			$this->ReferenceObject();
+
+		if (!$this->commitReferenced)
+			$this->ReferenceCommit();
+
+		$properties = array('dataRead', 'object', 'commit', 'type', 'tagger', 'taggerEpoch', 'taggerTimezone', 'comment', 'objectReferenced', 'commitReferenced');
+		return array_merge($properties, parent::__sleep());
+	}
+
+	/**
+	 * GetCacheKey
+	 *
+	 * Gets the cache key to use for this object
+	 *
+	 * @access public
+	 * @return string cache key
+	 */
+	public function GetCacheKey()
+	{
+		$key = parent::GetCacheKey();
+		if (!empty($key))
+			$key .= '|';
+
+		$key .= 'tag|' . $this->refName;
+		
+		return $key;
+	}
+
 
 	/**
 	 * CompareAge

comments