Improve performance of tag listing
Improve performance of tag listing

This changes the tag / head list functions GetTags and GetHeads to use
for-each-ref to let git do the age sorting internally (which is much
faster) and changes them to build off of the data already loaded by the
main ref list loading function

<?php <?php
/** /**
* GitPHP Head * GitPHP Head
* *
* Represents a single head * Represents a single head
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'Ref.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Ref.class.php');
   
/** /**
* Head class * Head class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Head extends GitPHP_Ref class GitPHP_Head extends GitPHP_Ref
{ {
   
/** /**
* commit * commit
* *
* Stores the commit internally * Stores the commit internally
* *
* @access protected * @access protected
*/ */
protected $commit; protected $commit;
   
/** /**
* __construct * __construct
* *
* Instantiates head * Instantiates head
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $head head name * @param string $head head name
* @param string $headHash head hash * @param string $headHash head hash
* @return mixed head object * @return mixed head object
* @throws Exception exception on invalid head or hash * @throws Exception exception on invalid head or hash
*/ */
public function __construct($project, $head, $headHash = '') public function __construct($project, $head, $headHash = '')
{ {
parent::__construct($project, 'heads', $head, $headHash); parent::__construct($project, 'heads', $head, $headHash);
} }
   
/** /**
* GetCommit * GetCommit
* *
* Gets the commit for this head * Gets the commit for this head
* *
* @access public * @access public
* @return mixed commit object for this tag * @return mixed commit object for this tag
*/ */
public function GetCommit() public function GetCommit()
{ {
if (!$this->commit) { if (!$this->commit) {
$this->commit = $this->project->GetCommit($this->hash); $this->commit = $this->project->GetCommit($this->GetHash());
} }
   
return $this->commit; return $this->commit;
} }
/** /**
* CompareAge * CompareAge
* *
* Compares two heads by age * Compares two heads by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first head * @param mixed $a first head
* @param mixed $b second head * @param mixed $b second head
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
$aObj = $a->GetCommit(); $aObj = $a->GetCommit();
$bObj = $b->GetCommit(); $bObj = $b->GetCommit();
if ($aObj->GetAge() === $bObj->GetAge()) if ($aObj->GetAge() === $bObj->GetAge())
return 0; return 0;
return ($aObj->GetAge() < $bObj->GetAge() ? -1 : 1); return ($aObj->GetAge() < $bObj->GetAge() ? -1 : 1);
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Project * GitPHP Project
* *
* Represents a single git project * Represents a single git project
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Head.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Head.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php');
   
/** /**
* Project class * Project class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Project class GitPHP_Project
{ {
   
/** /**
* project * project
* *
* Stores the project internally * Stores the project internally
* *
* @access protected * @access protected
*/ */
protected $project; protected $project;
   
/** /**
* owner * owner
* *
* Stores the owner internally * Stores the owner internally
* *
* @access protected * @access protected
*/ */
protected $owner = ""; protected $owner = "";
   
/** /**
* readOwner * readOwner
* *
* Stores whether the file owner has been read * Stores whether the file owner has been read
* *
* @access protected * @access protected
*/ */
protected $readOwner = false; protected $readOwner = false;
   
/** /**
* description * description
* *
* Stores the description internally * Stores the description internally
* *
* @access protected * @access protected
*/ */
protected $description; protected $description;
   
/** /**
* readDescription * readDescription
* *
* Stores whether the description has been * Stores whether the description has been
* read from the file yet * read from the file yet
* *
* @access protected * @access protected
*/ */
protected $readDescription = false; protected $readDescription = false;
   
/** /**
* category * category
* *
* Stores the category internally * Stores the category internally
* *
* @access protected * @access protected
*/ */
protected $category = ''; protected $category = '';
   
/** /**
* epoch * epoch
* *
* Stores the project epoch internally * Stores the project epoch internally
* *
* @access protected * @access protected
*/ */
protected $epoch; protected $epoch;
   
/** /**
* epochRead * epochRead
* *
* Stores whether the project epoch has been read yet * Stores whether the project epoch has been read yet
* *
* @access protected * @access protected
*/ */
protected $epochRead = false; protected $epochRead = false;
   
/** /**
* head * head
* *
* Stores the head hash internally * Stores the head hash internally
* *
* @access protected * @access protected
*/ */
protected $head; protected $head;
   
/** /**
* readHeadRef * readHeadRef
* *
* Stores whether the head ref has been read yet * Stores whether the head ref has been read yet
* *
* @access protected * @access protected
*/ */
protected $readHeadRef = false; protected $readHeadRef = false;
   
/** /**
* refs * refs
* *
* Stores the refs for the project * Stores the refs for the project
* *
* @access protected * @access protected
*/ */
protected $refs = array(); protected $refs = array();
   
/** /**
* readRefs * readRefs
* *
* Stores whether refs have been read yet * Stores whether refs have been read yet
* *
* @access protected * @access protected
*/ */
protected $readRefs = false; protected $readRefs = false;
   
/**  
* heads  
*  
* Stores the heads for the project  
*  
* @access protected  
*/  
protected $heads = array();  
   
/**  
* readHeads  
*  
* Stores whether heads have been read yet  
*  
* @access protected  
*/  
protected $readHeads = false;  
   
/**  
* tags  
*  
* Stores the tags for the project  
*  
* @access protected  
*/  
protected $tags = array();  
   
/**  
* readTags  
*  
* Stores whether tags have been read yet  
*  
* @access protected  
*/  
protected $readTags = false;  
   
/** /**
* cloneUrl * cloneUrl
* *
* Stores the clone url internally * Stores the clone url internally
* *
* @access protected * @access protected
*/ */
protected $cloneUrl = null; protected $cloneUrl = null;
   
/** /**
* pushUrl * pushUrl
* *
* Stores the push url internally * Stores the push url internally
* *
* @access protected * @access protected
*/ */
protected $pushUrl = null; protected $pushUrl = null;
   
/** /**
* bugUrl * bugUrl
* *
* Stores the bug url internally * Stores the bug url internally
* *
* @access protected * @access protected
*/ */
protected $bugUrl = null; protected $bugUrl = null;
   
/** /**
* bugPattern * bugPattern
* *
* Stores the bug pattern internally * Stores the bug pattern internally
* *
* @access protected * @access protected
*/ */
protected $bugPattern = null; protected $bugPattern = null;
   
/** /**
* commitCache * commitCache
* *
* Caches fetched commit objects in case of * Caches fetched commit objects in case of
* repeated requests for the same object * repeated requests for the same object
* *
* @access protected * @access protected
*/ */
protected $commitCache = array(); protected $commitCache = array();
   
/** /**
* __construct * __construct
* *
* Class constructor * Class constructor
* *
* @access public * @access public
* @throws Exception if project is invalid or outside of projectroot * @throws Exception if project is invalid or outside of projectroot
*/ */
public function __construct($project) public function __construct($project)
{ {
$this->SetProject($project); $this->SetProject($project);
} }
   
/** /**
* SetProject * SetProject
* *
* Attempts to set the project * Attempts to set the project
* *
* @access private * @access private
* @throws Exception if project is invalid or outside of projectroot * @throws Exception if project is invalid or outside of projectroot
*/ */
private function SetProject($project) private function SetProject($project)
{ {
$projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot')); $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
$realProjectRoot = realpath($projectRoot); $realProjectRoot = realpath($projectRoot);
$path = $projectRoot . $project; $path = $projectRoot . $project;
$fullPath = realpath($path); $fullPath = realpath($path);
   
if (!is_dir($fullPath)) { if (!is_dir($fullPath)) {
throw new Exception(sprintf(__('%1$s is not a directory'), $project)); throw new Exception(sprintf(__('%1$s is not a directory'), $project));
} }
   
if (!is_file($fullPath . '/HEAD')) { if (!is_file($fullPath . '/HEAD')) {
throw new Exception(sprintf(__('%1$s is not a git repository'), $project)); throw new Exception(sprintf(__('%1$s is not a git repository'), $project));
} }
   
if (preg_match('/(^|\/)\.{0,2}(\/|$)/', $project)) { if (preg_match('/(^|\/)\.{0,2}(\/|$)/', $project)) {
throw new Exception(sprintf(__('%1$s is attempting directory traversal'), $project)); throw new Exception(sprintf(__('%1$s is attempting directory traversal'), $project));
} }
   
$pathPiece = substr($fullPath, 0, strlen($realProjectRoot)); $pathPiece = substr($fullPath, 0, strlen($realProjectRoot));
   
if ((!is_link($path)) && (strcmp($pathPiece, $realProjectRoot) !== 0)) { if ((!is_link($path)) && (strcmp($pathPiece, $realProjectRoot) !== 0)) {
throw new Exception(sprintf(__('%1$s is outside of the projectroot'), $project)); throw new Exception(sprintf(__('%1$s is outside of the projectroot'), $project));
} }
   
$this->project = $project; $this->project = $project;
   
} }
   
/** /**
* GetOwner * GetOwner
* *
* Gets the project's owner * Gets the project's owner
* *
* @access public * @access public
* @return string project owner * @return string project owner
*/ */
public function GetOwner() public function GetOwner()
{ {
if (empty($this->owner) && !$this->readOwner) { if (empty($this->owner) && !$this->readOwner) {
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = 'gitweb.owner'; $args[] = 'gitweb.owner';
$this->owner = $exe->Execute(GIT_CONFIG, $args); $this->owner = $exe->Execute(GIT_CONFIG, $args);
unset($exe); unset($exe);
if (empty($this->owner) && function_exists('posix_getpwuid')) { if (empty($this->owner) && function_exists('posix_getpwuid')) {
$uid = fileowner($this->GetPath()); $uid = fileowner($this->GetPath());
if ($uid !== false) { if ($uid !== false) {
$data = posix_getpwuid($uid); $data = posix_getpwuid($uid);
if (isset($data['gecos']) && !empty($data['gecos'])) { if (isset($data['gecos']) && !empty($data['gecos'])) {
$this->owner = $data['gecos']; $this->owner = $data['gecos'];
} elseif (isset($data['name']) && !empty($data['name'])) { } elseif (isset($data['name']) && !empty($data['name'])) {
$this->owner = $data['name']; $this->owner = $data['name'];
} }
} }
} }
   
$this->readOwner = true; $this->readOwner = true;
} }
return $this->owner; return $this->owner;
} }
   
/** /**
* SetOwner * SetOwner
* *
* Sets the project's owner (from an external source) * Sets the project's owner (from an external source)
* *
* @access public * @access public
* @param string $owner the owner * @param string $owner the owner
*/ */
public function SetOwner($owner) public function SetOwner($owner)
{ {
$this->owner = $owner; $this->owner = $owner;
} }
   
/** /**
* GetProject * GetProject
* *
* Gets the project * Gets the project
* *
* @access public * @access public
* @return string the project * @return string the project
*/ */
public function GetProject() public function GetProject()
{ {
return $this->project; return $this->project;
} }
   
/** /**
* GetSlug * GetSlug
* *
* Gets the project as a filename/url friendly slug * Gets the project as a filename/url friendly slug
* *
* @access public * @access public
* @return string the slug * @return string the slug
*/ */
public function GetSlug() public function GetSlug()
{ {
$from = array( $from = array(
'/', '/',
'.git' '.git'
); );
$to = array( $to = array(
'-', '-',
'' ''
); );
return str_replace($from, $to, $this->project); return str_replace($from, $to, $this->project);
} }
   
/** /**
* GetPath * GetPath
* *
* Gets the full project path * Gets the full project path
* *
* @access public * @access public
* @return string project path * @return string project path
*/ */
public function GetPath() public function GetPath()
{ {
$projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot')); $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
return $projectRoot . $this->project; return $projectRoot . $this->project;
} }
   
/** /**
* GetDescription * GetDescription
* *
* Gets the project description * Gets the project description
* *
* @access public * @access public
* @param $trim length to trim description to (0 for no trim) * @param $trim length to trim description to (0 for no trim)
* @return string project description * @return string project description
*/ */
public function GetDescription($trim = 0) public function GetDescription($trim = 0)
{ {
if (!$this->readDescription) { if (!$this->readDescription) {
$this->description = file_get_contents($this->GetPath() . '/description'); $this->description = file_get_contents($this->GetPath() . '/description');
} }
if (($trim > 0) && (strlen($this->description) > $trim)) { if (($trim > 0) && (strlen($this->description) > $trim)) {
return substr($this->description, 0, $trim) . '…'; return substr($this->description, 0, $trim) . '…';
} }
   
return $this->description; return $this->description;
} }
   
/** /**
* SetDescription * SetDescription
* *
* Overrides the project description * Overrides the project description
* *
* @access public * @access public
* @param string $descr description * @param string $descr description
*/ */
public function SetDescription($descr) public function SetDescription($descr)
{ {
$this->description = $descr; $this->description = $descr;
$this->readDescription = true; $this->readDescription = true;
} }
   
/** /**
* GetDaemonEnabled * GetDaemonEnabled
* *
* Returns whether gitdaemon is allowed for this project * Returns whether gitdaemon is allowed for this project
* *
* @access public * @access public
* @return boolean git-daemon-export-ok? * @return boolean git-daemon-export-ok?
*/ */
public function GetDaemonEnabled() public function GetDaemonEnabled()
{ {
return file_exists($this->GetPath() . '/git-daemon-export-ok'); return file_exists($this->GetPath() . '/git-daemon-export-ok');
} }
   
/** /**
* GetCategory * GetCategory
* *
* Gets the project's category * Gets the project's category
* *
* @access public * @access public
* @return string category * @return string category
*/ */
public function GetCategory() public function GetCategory()
{ {
return $this->category; return $this->category;
} }
   
/** /**
* SetCategory * SetCategory
* *
* Sets the project's category * Sets the project's category
* *
* @access public * @access public
* @param string $category category * @param string $category category
*/ */
public function SetCategory($category) public function SetCategory($category)
{ {
$this->category = $category; $this->category = $category;
} }
   
/** /**
* GetCloneUrl * GetCloneUrl
* *
* Gets the clone URL for this repository, if specified * Gets the clone URL for this repository, if specified
* *
* @access public * @access public
* @return string clone url * @return string clone url
*/ */
public function GetCloneUrl() public function GetCloneUrl()
{ {
if ($this->cloneUrl !== null) if ($this->cloneUrl !== null)
return $this->cloneUrl; return $this->cloneUrl;
   
$cloneurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('cloneurl', ''), false); $cloneurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('cloneurl', ''), false);
if (!empty($cloneurl)) if (!empty($cloneurl))
$cloneurl .= $this->project; $cloneurl .= $this->project;
   
return $cloneurl; return $cloneurl;
} }
   
/** /**
* SetCloneUrl * SetCloneUrl
* *
* Overrides the clone URL for this repository * Overrides the clone URL for this repository
* *
* @access public * @access public
* @param string $cUrl clone url * @param string $cUrl clone url
*/ */
public function SetCloneUrl($cUrl) public function SetCloneUrl($cUrl)
{ {
$this->cloneUrl = $cUrl; $this->cloneUrl = $cUrl;
} }
   
/** /**
* GetPushUrl * GetPushUrl
* *
* Gets the push URL for this repository, if specified * Gets the push URL for this repository, if specified
* *
* @access public * @access public
* @return string push url * @return string push url
*/ */
public function GetPushUrl() public function GetPushUrl()
{ {
if ($this->pushUrl !== null) if ($this->pushUrl !== null)
return $this->pushUrl; return $this->pushUrl;
   
$pushurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('pushurl', ''), false); $pushurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('pushurl', ''), false);
if (!empty($pushurl)) if (!empty($pushurl))
$pushurl .= $this->project; $pushurl .= $this->project;
   
return $pushurl; return $pushurl;
} }
   
/** /**
* SetPushUrl * SetPushUrl
* *
* Overrides the push URL for this repository * Overrides the push URL for this repository
* *
* @access public * @access public
* @param string $pUrl push url * @param string $pUrl push url
*/ */
public function SetPushUrl($pUrl) public function SetPushUrl($pUrl)
{ {
$this->pushUrl = $pUrl; $this->pushUrl = $pUrl;
} }
   
/** /**
* GetBugUrl * GetBugUrl
* *
* Gets the bug URL for this repository, if specified * Gets the bug URL for this repository, if specified
* *
* @access public * @access public
* @return string bug url * @return string bug url
*/ */
public function GetBugUrl() public function GetBugUrl()
{ {
if ($this->bugUrl != null) if ($this->bugUrl != null)
return $this->bugUrl; return $this->bugUrl;
   
return GitPHP_Config::GetInstance()->GetValue('bugurl', ''); return GitPHP_Config::GetInstance()->GetValue('bugurl', '');
} }
   
/** /**
* SetBugUrl * SetBugUrl
* *
* Overrides the bug URL for this repository * Overrides the bug URL for this repository
* *
* @access public * @access public
* @param string $bUrl bug url * @param string $bUrl bug url
*/ */
public function SetBugUrl($bUrl) public function SetBugUrl($bUrl)
{ {
$this->bugUrl = $bUrl; $this->bugUrl = $bUrl;
} }
   
/** /**
* GetBugPattern * GetBugPattern
* *
* Gets the bug pattern for this repository, if specified * Gets the bug pattern for this repository, if specified
* *
* @access public * @access public
* @return string bug pattern * @return string bug pattern
*/ */
public function GetBugPattern() public function GetBugPattern()
{ {
if ($this->bugPattern != null) if ($this->bugPattern != null)
return $this->bugPattern; return $this->bugPattern;
   
return GitPHP_Config::GetInstance()->GetValue('bugpattern', ''); return GitPHP_Config::GetInstance()->GetValue('bugpattern', '');
} }
   
/** /**
* SetBugPattern * SetBugPattern
* *
* Overrides the bug pattern for this repository * Overrides the bug pattern for this repository
* *
* @access public * @access public
* @param string $bPat bug pattern * @param string $bPat bug pattern
*/ */
public function SetBugPattern($bPat) public function SetBugPattern($bPat)
{ {
$this->bugPattern = $bPat; $this->bugPattern = $bPat;
} }
   
/** /**
* GetHeadCommit * GetHeadCommit
* *
* Gets the head commit for this project * Gets the head commit for this project
* Shortcut for getting the tip commit of the HEAD branch * Shortcut for getting the tip commit of the HEAD branch
* *
* @access public * @access public
* @return mixed head commit * @return mixed head commit
*/ */
public function GetHeadCommit() public function GetHeadCommit()
{ {
if (!$this->readHeadRef) if (!$this->readHeadRef)
$this->ReadHeadCommit(); $this->ReadHeadCommit();
   
return $this->GetCommit($this->head); return $this->GetCommit($this->head);
} }
   
/** /**
* ReadHeadCommit * ReadHeadCommit
* *
* Reads the head commit hash * Reads the head commit hash
* *
* @access protected * @access protected
*/ */
public function ReadHeadCommit() public function ReadHeadCommit()
{ {
$this->readHeadRef = true; $this->readHeadRef = true;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--verify'; $args[] = '--verify';
$args[] = 'HEAD'; $args[] = 'HEAD';
$this->head = trim($exe->Execute(GIT_REV_PARSE, $args)); $this->head = trim($exe->Execute(GIT_REV_PARSE, $args));
} }
   
/** /**
* GetCommit * GetCommit
* *
* Get a commit for this project * Get a commit for this project
* *
* @access public * @access public
*/ */
public function GetCommit($hash) public function GetCommit($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
if ($hash === 'HEAD') if ($hash === 'HEAD')
return $this->GetHeadCommit(); return $this->GetHeadCommit();
   
if (substr_compare($hash, 'refs/heads/', 0, 11) === 0) { if (substr_compare($hash, 'refs/heads/', 0, 11) === 0) {
$head = $this->GetHead(substr($hash, 11)); $head = $this->GetHead(substr($hash, 11));
if ($head != null) if ($head != null)
return $head->GetCommit(); return $head->GetCommit();
} else if (substr_compare($hash, 'refs/tags/', 0, 10) === 0) { } else if (substr_compare($hash, 'refs/tags/', 0, 10) === 0) {
$tag = $this->GetTag(substr($hash, 10)); $tag = $this->GetTag(substr($hash, 10));
if ($tag != null) { if ($tag != null) {
$obj = $tag->GetObject(); $obj = $tag->GetObject();
if ($obj != null) { if ($obj != null) {
return $obj; return $obj;
} }
} }
} }
   
if (!isset($this->commitCache[$hash])) { if (!isset($this->commitCache[$hash])) {
$cacheKey = 'project|' . $this->project . '|commit|' . $hash; $cacheKey = 'project|' . $this->project . '|commit|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
$this->commitCache[$hash] = $cached; $this->commitCache[$hash] = $cached;
else else
$this->commitCache[$hash] = new GitPHP_Commit($this, $hash); $this->commitCache[$hash] = new GitPHP_Commit($this, $hash);
} }
   
return $this->commitCache[$hash]; return $this->commitCache[$hash];
} }
   
/** /**
* CompareProject * CompareProject
* *
* Compares two projects by project name * Compares two projects by project name
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareProject($a, $b) public static function CompareProject($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetProject(), $b->GetProject()); return strcmp($a->GetProject(), $b->GetProject());
} }
   
/** /**
* CompareDescription * CompareDescription
* *
* Compares two projects by description * Compares two projects by description
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareDescription($a, $b) public static function CompareDescription($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetDescription(), $b->GetDescription()); return strcmp($a->GetDescription(), $b->GetDescription());
} }
   
/** /**
* CompareOwner * CompareOwner
* *
* Compares two projects by owner * Compares two projects by owner
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareOwner($a, $b) public static function CompareOwner($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetOwner(), $b->GetOwner()); return strcmp($a->GetOwner(), $b->GetOwner());
} }
   
/** /**
* CompareAge * CompareAge
* *
* Compares two projects by age * Compares two projects by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
if ($a->GetAge() === $b->GetAge()) if ($a->GetAge() === $b->GetAge())
return 0; return 0;
return ($a->GetAge() < $b->GetAge() ? -1 : 1); return ($a->GetAge() < $b->GetAge() ? -1 : 1);
} }
   
/** /**
* GetRefs * GetRefs
* *
* Gets the list of refs for the project * Gets the list of refs for the project
* *
* @access public * @access public
* @return array array of refs * @return array array of refs
*/ */
public function GetRefs() public function GetRefs()
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
return $this->refs; return $this->refs;
} }
   
/** /**
* ReadRefList * ReadRefList
* *
* Reads the list of refs for this project * Reads the list of refs for this project
* *
* @access protected * @access protected
*/ */
public function ReadRefList() public function ReadRefList()
{ {
$this->readRefs = true; $this->readRefs = true;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--heads'; $args[] = '--heads';
$args[] = '--tags'; $args[] = '--tags';
$args[] = '--dereference'; $args[] = '--dereference';
$ret = $exe->Execute(GIT_SHOW_REF, $args); $ret = $exe->Execute(GIT_SHOW_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match('/^([0-9a-fA-F]{40}) refs\/(tags|heads)\/([^^]+)(\^{})?$/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40}) refs\/(tags|heads)\/([^^]+)(\^{})?$/', $line, $regs)) {
try { try {
$key = 'refs/' . $regs[2] . '/' . $regs[3]; $key = 'refs/' . $regs[2] . '/' . $regs[3];
if ($regs[2] == 'tags') { if ($regs[2] == 'tags') {
if ((!empty($regs[4])) && ($regs[4] == '^{}')) { if ((!empty($regs[4])) && ($regs[4] == '^{}')) {
$derefCommit = $this->GetCommit($regs[1]); $derefCommit = $this->GetCommit($regs[1]);
if ($derefCommit && isset($this->refs[$key])) { if ($derefCommit && isset($this->refs[$key])) {
$this->refs[$key]->SetCommit($derefCommit); $this->refs[$key]->SetCommit($derefCommit);
} }
} else if (!isset($this->refs[$key])) { } else if (!isset($this->refs[$key])) {
$this->refs[$key] = $this->LoadTag($regs[3], $regs[1]); $this->refs[$key] = $this->LoadTag($regs[3], $regs[1]);
} }
} else if ($regs[2] == 'heads') { } else if ($regs[2] == 'heads') {
$this->refs[$key] = new GitPHP_Head($this, $regs[3], $regs[1]); $this->refs[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
} }
   
/** /**
* GetTags * GetTags
* *
* Gets list of tags for this project * Gets list of tags for this project by age descending
* *
* @access public * @access public
  * @param integer $count number of tags to load
* @return array array of tags * @return array array of tags
*/ */
public function GetTags() public function GetTags($count = 0)
{ {
if (!$this->readTags) if (!$this->readRefs)
$this->ReadTagList(); $this->ReadRefList();
   
return $this->tags; $exe = new GitPHP_GitExe($this);
  $args = array();
  $args[] = '--sort=-creatordate';
  $args[] = '--format="%(refname)"';
  if ($count > 0) {
  $args[] = '--count=' . $count;
  }
  $args[] = '--';
  $args[] = 'refs/tags';
  $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
  unset($exe);
   
  $lines = explode("\n", $ret);
   
  $tags = array();
   
  foreach ($lines as $ref) {
  if (isset($this->refs[$ref])) {
  $tags[] = $this->refs[$ref];
  }
  }
   
  return $tags;
} }
   
/** /**
* GetTag * GetTag
* *
* Gets a single tag * Gets a single tag
* *
* @access public * @access public
* @param string $tag tag to find * @param string $tag tag to find
* @return mixed tag object * @return mixed tag object
*/ */
public function GetTag($tag) public function GetTag($tag)
{ {
if (empty($tag)) if (empty($tag))
return null; return null;
   
if (!isset($this->tags[$tag])) { $key = 'refs/tags/' . $tag;
$this->tags[$tag] = $this->LoadTag($tag);  
} if (!isset($this->refs[$key])) {
  $this->refs[$key] = $this->LoadTag($key);
return $this->tags[$tag]; }
   
  return $this->refs[$key];
} }
   
/** /**
* LoadTag * LoadTag
* *
* Attempts to load a cached tag, or creates a new object * Attempts to load a cached tag, or creates a new object
* *
* @access private * @access private
* @param string $tag tag to find * @param string $tag tag to find
* @return mixed tag object * @return mixed tag object
*/ */
private function LoadTag($tag, $hash = '') private function LoadTag($tag, $hash = '')
{ {
if (empty($tag)) if (empty($tag))
return; return;
   
$cacheKey = 'project|' . $this->project . '|tag|' . $tag; $cacheKey = 'project|' . $this->project . '|tag|' . $tag;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) { if ($cached) {
return $cached; return $cached;
} else { } else {
return new GitPHP_Tag($this, $tag, $hash); return new GitPHP_Tag($this, $tag, $hash);
} }
} }
   
/** /**
* ReadTagList * GetHeads
* *
* Reads tag list * Gets list of heads for this project by age descending
* *
* @access protected * @access public
*/ * @param integer $count number of tags to load
protected function ReadTagList() * @return array array of heads
{ */
$this->readTags = true; public function GetHeads($count = 0)
  {
  if (!$this->readRefs)
  $this->ReadRefList();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--tags'; $args[] = '--sort=-committerdate';
$args[] = '--dereference'; $args[] = '--format="%(refname)"';
$ret = $exe->Execute(GIT_SHOW_REF, $args); if ($count > 0) {
  $args[] = '--count=' . $count;
  }
  $args[] = '--';
  $args[] = 'refs/heads';
  $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
foreach ($lines as $line) { $heads = array();
if (preg_match('/^([0-9a-fA-F]{40}) refs\/tags\/([^^]+)(\^{})?$/', $line, $regs)) {  
try { foreach ($lines as $ref) {
if ((!empty($regs[3])) && ($regs[3] == '^{}')) { if (isset($this->refs[$ref])) {
$derefCommit = $this->GetCommit($regs[1]); $heads[] = $this->refs[$ref];
if ($derefCommit && isset($this->tags[$regs[2]])) {  
$this->tags[$regs[2]]->SetCommit($derefCommit);  
}  
   
} else if (!isset($this->tags[$regs[2]])) {  
$this->tags[$regs[2]] = $this->LoadTag($regs[2], $regs[1]);  
}  
} catch (Exception $e) {  
}  
} }
} }
   
uasort($this->tags, array('GitPHP_Tag', 'CompareAge')); return $heads;
}  
   
/**  
* GetHeads  
*  
* Gets list of heads for this project  
*  
* @access public  
* @return array array of heads  
*/  
public function GetHeads()  
{  
if (!$this->readHeads)  
$this->ReadHeadList();  
   
return $this->heads;  
} }
   
/** /**
* GetHead * GetHead
* *
* Gets a single head * Gets a single head
* *
* @access public * @access public
* @param string $head head to find * @param string $head head to find
* @return mixed head object * @return mixed head object
*/ */
public function GetHead($head) public function GetHead($head)
{ {
if (empty($head)) if (empty($head))
return null; return null;
   
if (!$this->readHeads) $key = 'refs/heads/' . $head;
$this->ReadHeadList();  
  if (!isset($this->refs[$key])) {
foreach ($this->heads as $h) { $this->refs[$key] = new GitPHP_Head($this, $head);
if ($h->GetName() === $head) { }
return $h;  
} return $this->refs[$key];
}  
   
return null;  
}  
   
/**  
* ReadHeadList  
*  
* Reads head list  
*  
* @access protected  
*/  
protected function ReadHeadList()  
{  
$this->readHeads = true;  
   
$exe = new GitPHP_GitExe($this);  
$args = array();  
$args[] = '--heads';  
$ret = $exe->Execute(GIT_SHOW_REF, $args);  
unset($exe);  
   
$lines = explode("\n", $ret);  
   
foreach ($lines as $line) {  
if (preg_match('/^([0-9a-fA-F]{40}) refs\/heads\/(.+)$/', $line, $regs)) {  
try {  
$this->heads[] = new GitPHP_Head($this, $regs[2], $regs[1]);  
} catch (Exception $e) {  
}  
}  
}  
   
usort($this->heads, array('GitPHP_Head', 'CompareAge'));  
   
} }
   
/** /**
* GetLogHash * GetLogHash
* *
* Gets log entries as an array of hashes * Gets log entries as an array of hashes
* *
* @access public * @access public
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of hashes * @return array array of hashes
*/ */
public function GetLogHash($hash, $count = 50, $skip = 0) public function GetLogHash($hash, $count = 50, $skip = 0)
{ {
return $this->RevList($hash, $count, $skip); return $this->RevList($hash, $count, $skip);
} }
   
/** /**
* GetLog * GetLog
* *
* Gets log entries as an array of commit objects * Gets log entries as an array of commit objects
* *
* @access public * @access public
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of commit objects * @return array array of commit objects
*/ */
public function GetLog($hash, $count = 50, $skip = 0) public function GetLog($hash, $count = 50, $skip = 0)
{ {
$log = $this->GetLogHash($hash, $count, $skip); $log = $this->GetLogHash($hash, $count, $skip);
$len = count($log); $len = count($log);
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$log[$i] = $this->GetCommit($log[$i]); $log[$i] = $this->GetCommit($log[$i]);
} }
return $log; return $log;
} }
   
/** /**
* GetBlob * GetBlob
* *
* Gets a blob from this project * Gets a blob from this project
* *
* @access public * @access public
* @param string $hash blob hash * @param string $hash blob hash
*/ */
public function GetBlob($hash) public function GetBlob($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|blob|' . $hash; $cacheKey = 'project|' . $this->project . '|blob|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Blob($this, $hash); return new GitPHP_Blob($this, $hash);
} }
   
/** /**
* GetTree * GetTree
* *
* Gets a tree from this project * Gets a tree from this project
* *
* @access public * @access public
* @param string $hash tree hash * @param string $hash tree hash
*/ */
public function GetTree($hash) public function GetTree($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|tree|' . $hash; $cacheKey = 'project|' . $this->project . '|tree|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Tree($this, $hash); return new GitPHP_Tree($this, $hash);
} }
   
/** /**
* SearchCommit * SearchCommit
* *
* Gets a list of commits with commit messages matching the given pattern * Gets a list of commits with commit messages matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--grep=\'' . $pattern . '\''; $args[] = '--grep=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchAuthor * SearchAuthor
* *
* Gets a list of commits with authors matching the given pattern * Gets a list of commits with authors matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--author=\'' . $pattern . '\''; $args[] = '--author=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchCommitter * SearchCommitter
* *
* Gets a list of commits with committers matching the given pattern * Gets a list of commits with committers matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--committer=\'' . $pattern . '\''; $args[] = '--committer=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* RevList * RevList
* *
* Common code for using rev-list command * Common code for using rev-list command
* *
* @access private * @access private
* @param string $hash hash to list from * @param string $hash hash to list from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @param array $args args to give to rev-list * @param array $args args to give to rev-list
* @return array array of hashes * @return array array of hashes
*/ */
private function RevList($hash, $count = 50, $skip = 0, $args = array()) private function RevList($hash, $count = 50, $skip = 0, $args = array())
{ {
if ($count < 1) if ($count < 1)
return; return;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$canSkip = true; $canSkip = true;
if ($skip > 0) if ($skip > 0)
$canSkip = $exe->CanSkip(); $canSkip = $exe->CanSkip();
   
if ($canSkip) { if ($canSkip) {
$args[] = '--max-count=' . $count; $args[] = '--max-count=' . $count;
if ($skip > 0) { if ($skip > 0) {
$args[] = '--skip=' . $skip; $args[] = '--skip=' . $skip;
} }
} else { } else {
$args[] = '--max-count=' . ($count + $skip); $args[] = '--max-count=' . ($count + $skip);
} }
   
$args[] = $hash; $args[] = $hash;
   
$revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args)); $revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args));
   
if (!$revlist[count($revlist)-1]) { if (!$revlist[count($revlist)-1]) {
/* the last newline creates a null entry */ /* the last newline creates a null entry */
array_splice($revlist, -1, 1); array_splice($revlist, -1, 1);
} }
   
if (($skip > 0) && (!$exe->CanSkip())) { if (($skip > 0) && (!$exe->CanSkip())) {
return array_slice($revlist, $skip, $count); return array_slice($revlist, $skip, $count);
} }
   
return $revlist; return $revlist;
} }
   
/** /**
* GetEpoch * GetEpoch
* *
* Gets this project's epoch * Gets this project's epoch
* (time of last change) * (time of last change)
* *
* @access public * @access public
* @return integer timestamp * @return integer timestamp
*/ */
public function GetEpoch() public function GetEpoch()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return $this->epoch; return $this->epoch;
} }
   
/** /**
* GetAge * GetAge
* *
* Gets this project's age * Gets this project's age
* (time since most recent change) * (time since most recent change)
* *
* @access public * @access public
* @return integer age * @return integer age
*/ */
public function GetAge() public function GetAge()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return time() - $this->epoch; return time() - $this->epoch;
} }
   
/** /**
* ReadEpoch * ReadEpoch
* *
* Reads this project's epoch * Reads this project's epoch
* (timestamp of most recent change) * (timestamp of most recent change)
* *
* @access private * @access private
*/ */
private function ReadEpoch() private function ReadEpoch()
{ {
$this->epochRead = true; $this->epochRead = true;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$args = array(); $args = array();
$args[] = '--format="%(committer)"'; $args[] = '--format="%(committer)"';
$args[] = '--sort=-committerdate'; $args[] = '--sort=-committerdate';
$args[] = '--count=1'; $args[] = '--count=1';
$args[] = 'refs/heads'; $args[] = 'refs/heads';
   
$epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args)); $epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args));
   
if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) { if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) {
$this->epoch = $regs[1]; $this->epoch = $regs[1];
} }
   
unset($exe); unset($exe);
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Tag * GitPHP Tag
* *
* Represents a single tag object * Represents a single tag object
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Ref.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Ref.class.php');
   
/** /**
* Tag class * Tag class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Tag extends GitPHP_Ref class GitPHP_Tag extends GitPHP_Ref
{ {
/** /**
* dataRead * dataRead
* *
* Indicates whether data for this tag has been read * Indicates whether data for this tag has been read
* *
* @access protected * @access protected
*/ */
protected $dataRead = false; protected $dataRead = false;
   
/** /**
* object * object
* *
* Stores the object internally * Stores the object internally
* *
* @access protected * @access protected
*/ */
protected $object; protected $object;
   
/** /**
* commit * commit
* *
* Stores the commit internally * Stores the commit internally
* *
* @access protected * @access protected
*/ */
protected $commit; protected $commit;
   
/** /**
* type * type
* *
* Stores the type internally * Stores the type internally
* *
* @access protected * @access protected
*/ */
protected $type; protected $type;
   
/** /**
* tagger * tagger
* *
* Stores the tagger internally * Stores the tagger internally
* *
* @access protected * @access protected
*/ */
protected $tagger; protected $tagger;
   
/** /**
* taggerEpoch * taggerEpoch
* *
* Stores the tagger epoch internally * Stores the tagger epoch internally
* *
* @access protected * @access protected
*/ */
protected $taggerEpoch; protected $taggerEpoch;
   
/** /**
* taggerTimezone * taggerTimezone
* *
* Stores the tagger timezone internally * Stores the tagger timezone internally
* *
* @access protected * @access protected
*/ */
protected $taggerTimezone; protected $taggerTimezone;
   
/** /**
* comment * comment
* *
* Stores the tag comment internally * Stores the tag comment internally
* *
* @access protected * @access protected
*/ */
protected $comment = array(); protected $comment = array();
   
/** /**
* objectReferenced * objectReferenced
* *
* Stores whether the object has been referenced into a pointer * Stores whether the object has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $objectReferenced = false; private $objectReferenced = false;
   
/** /**
* commitReferenced * commitReferenced
* *
* Stores whether the commit has been referenced into a pointer * Stores whether the commit has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $commitReferenced = false; private $commitReferenced = false;
   
/** /**
* __construct * __construct
* *
* Instantiates tag * Instantiates tag
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $tag tag name * @param string $tag tag name
* @param string $tagHash tag hash * @param string $tagHash tag hash
* @return mixed tag object * @return mixed tag object
* @throws Exception exception on invalid tag or hash * @throws Exception exception on invalid tag or hash
*/ */
public function __construct($project, $tag, $tagHash = '') public function __construct($project, $tag, $tagHash = '')
{ {
parent::__construct($project, 'tags', $tag, $tagHash); parent::__construct($project, 'tags', $tag, $tagHash);
} }
   
/** /**
* GetObject * GetObject
* *
* Gets the object this tag points to * Gets the object this tag points to
* *
* @access public * @access public
* @return mixed object for this tag * @return mixed object for this tag
*/ */
public function GetObject() public function GetObject()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->objectReferenced) if ($this->objectReferenced)
$this->DereferenceObject(); $this->DereferenceObject();
   
return $this->object; return $this->object;
} }
   
/** /**
* GetCommit * GetCommit
* *
* Gets the commit this tag points to * Gets the commit this tag points to
* *
* @access public * @access public
* @return mixed commit for this tag * @return mixed commit for this tag
*/ */
public function GetCommit() public function GetCommit()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
   
if (!$this->commit) if (!$this->commit)
$this->ReadCommit(); $this->ReadCommit();
   
return $this->commit; return $this->commit;
} }
   
/** /**
* SetCommit * SetCommit
* *
* Sets the commit this tag points to * Sets the commit this tag points to
* *
* @access public * @access public
* @param mixed $commit commit object * @param mixed $commit commit object
*/ */
public function SetCommit($commit) public function SetCommit($commit)
{ {
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
   
if (!$this->commit) if (!$this->commit)
$this->commit = $commit; $this->commit = $commit;
} }
   
/** /**
* GetType * GetType
* *
* Gets the tag type * Gets the tag type
* *
* @access public * @access public
* @return string tag type * @return string tag type
*/ */
public function GetType() public function GetType()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->type; return $this->type;
} }
   
/** /**
* GetTagger * GetTagger
* *
* Gets the tagger * Gets the tagger
* *
* @access public * @access public
* @return string tagger * @return string tagger
*/ */
public function GetTagger() public function GetTagger()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->tagger; return $this->tagger;
} }
   
/** /**
* GetTaggerEpoch * GetTaggerEpoch
* *
* Gets the tagger epoch * Gets the tagger epoch
* *
* @access public * @access public
* @return string tagger epoch * @return string tagger epoch
*/ */
public function GetTaggerEpoch() public function GetTaggerEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->taggerEpoch; return $this->taggerEpoch;
} }
   
/** /**
* GetTaggerLocalEpoch * GetTaggerLocalEpoch
* *
* Gets the tagger local epoch * Gets the tagger local epoch
* *
* @access public * @access public
* @return string tagger local epoch * @return string tagger local epoch
*/ */
public function GetTaggerLocalEpoch() public function GetTaggerLocalEpoch()
{ {
$epoch = $this->GetTaggerEpoch(); $epoch = $this->GetTaggerEpoch();
$tz = $this->GetTaggerTimezone(); $tz = $this->GetTaggerTimezone();
if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) { if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) {
$local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600); $local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600);
return $local; return $local;
} }
return $epoch; return $epoch;
} }
   
/** /**
* GetTaggerTimezone * GetTaggerTimezone
* *
* Gets the tagger timezone * Gets the tagger timezone
* *
* @access public * @access public
* @return string tagger timezone * @return string tagger timezone
*/ */
public function GetTaggerTimezone() public function GetTaggerTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->taggerTimezone; return $this->taggerTimezone;
} }
   
/** /**
* GetComment * GetComment
* *
* Gets the tag comment * Gets the tag comment
* *
* @access public * @access public
* @return array comment lines * @return array comment lines
*/ */
public function GetComment() public function GetComment()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->comment; return $this->comment;
} }
   
/** /**
* LightTag * LightTag
* *
* Tests if this is a light tag (tag without tag object) * Tests if this is a light tag (tag without tag object)
* *
* @access public * @access public
* @return boolean true if tag is light (has no object) * @return boolean true if tag is light (has no object)
*/ */
public function LightTag() public function LightTag()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->objectReferenced) if ($this->objectReferenced)
$this->DereferenceObject(); $this->DereferenceObject();
   
if (!$this->object) if (!$this->object)
return true; return true;
   
return $this->object->GetHash() === $this->hash; return $this->object->GetHash() === $this->GetHash();
} }
   
/** /**
* ReadData * ReadData
* *
* Reads the tag data * Reads the tag data
* *
* @access protected * @access protected
*/ */
protected function ReadData() protected function ReadData()
{ {
$this->dataRead = true; $this->dataRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '-t'; $args[] = '-t';
$args[] = $this->hash; $args[] = $this->GetHash();
$ret = trim($exe->Execute(GIT_CAT_FILE, $args)); $ret = trim($exe->Execute(GIT_CAT_FILE, $args));
if ($ret === 'commit') { if ($ret === 'commit') {
/* light tag */ /* light tag */
$this->object = $this->GetProject()->GetCommit($this->hash); $this->object = $this->GetProject()->GetCommit($this->GetHash());
$this->commit = $this->object; $this->commit = $this->object;
$this->type = 'commit'; $this->type = 'commit';
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
return; return;
} }
   
/* get data from tag object */ /* get data from tag object */
$args = array(); $args = array();
$args[] = 'tag'; $args[] = 'tag';
$args[] = $this->GetName(); $args[] = $this->GetName();
$ret = $exe->Execute(GIT_CAT_FILE, $args); $ret = $exe->Execute(GIT_CAT_FILE, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
if (!isset($lines[0])) if (!isset($lines[0]))
return; return;
   
$objectHash = null; $objectHash = null;
   
$readInitialData = false; $readInitialData = false;
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (!$readInitialData) { if (!$readInitialData) {
if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) { if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) {
$objectHash = $regs[1]; $objectHash = $regs[1];
continue; continue;
} else if (preg_match('/^type (.+)$/', $line, $regs)) { } else if (preg_match('/^type (.+)$/', $line, $regs)) {
$this->type = $regs[1]; $this->type = $regs[1];
continue; continue;
} else if (preg_match('/^tag (.+)$/', $line, $regs)) { } else if (preg_match('/^tag (.+)$/', $line, $regs)) {
continue; continue;
} else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) {
$this->tagger = $regs[1]; $this->tagger = $regs[1];
$this->taggerEpoch = $regs[2]; $this->taggerEpoch = $regs[2];
$this->taggerTimezone = $regs[3]; $this->taggerTimezone = $regs[3];
continue; continue;
} }
} }
   
$trimmed = trim($line); $trimmed = trim($line);
   
if ((strlen($trimmed) > 0) || ($readInitialData === true)) { if ((strlen($trimmed) > 0) || ($readInitialData === true)) {
$this->comment[] = $line; $this->comment[] = $line;
} }
$readInitialData = true; $readInitialData = true;
   
} }
   
switch ($this->type) { switch ($this->type) {
case 'commit': case 'commit':
try { try {
$this->object = $this->GetProject()->GetCommit($objectHash); $this->object = $this->GetProject()->GetCommit($objectHash);
$this->commit = $this->object; $this->commit = $this->object;
} catch (Exception $e) { } catch (Exception $e) {
} }
break; break;
case 'tag': case 'tag':
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = 'tag'; $args[] = 'tag';
$args[] = $objectHash; $args[] = $objectHash;
$ret = $exe->Execute(GIT_CAT_FILE, $args); $ret = $exe->Execute(GIT_CAT_FILE, $args);
unset($exe); unset($exe);
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (preg_match('/^tag (.+)$/', $line, $regs)) { if (preg_match('/^tag (.+)$/', $line, $regs)) {
$name = trim($regs[1]); $name = trim($regs[1]);
$this->object = $this->GetProject()->GetTag($name); $this->object = $this->GetProject()->GetTag($name);
if ($this->object) { if ($this->object) {
$this->object->SetHash($objectHash); $this->object->SetHash($objectHash);
} }
} }
} }
break; break;
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReadCommit * ReadCommit
* *
* Attempts to dereference the commit for this tag * Attempts to dereference the commit for this tag
* *
* @access private * @access private
*/ */
private function ReadCommit() private function ReadCommit()
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--tags'; $args[] = '--tags';
$args[] = '--dereference'; $args[] = '--dereference';
$args[] = $this->refName; $args[] = $this->refName;
$ret = $exe->Execute(GIT_SHOW_REF, $args); $ret = $exe->Execute(GIT_SHOW_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match('/^([0-9a-fA-F]{40}) refs\/tags\/' . preg_quote($this->refName) . '(\^{})$/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40}) refs\/tags\/' . preg_quote($this->refName) . '(\^{})$/', $line, $regs)) {
$this->commit = $this->GetProject()->GetCommit($regs[1]); $this->commit = $this->GetProject()->GetCommit($regs[1]);
return; return;
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReferenceObject * ReferenceObject
* *
* Turns the object into a reference pointer * Turns the object into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceObject() private function ReferenceObject()
{ {
if ($this->objectReferenced) if ($this->objectReferenced)
return; return;
   
if (!$this->object) if (!$this->object)
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$this->object = $this->object->GetHash(); $this->object = $this->object->GetHash();
} else if ($this->type == 'tag') { } else if ($this->type == 'tag') {
$this->object = $this->object->GetName(); $this->object = $this->object->GetName();
} }
   
$this->objectReferenced = true; $this->objectReferenced = true;
} }
   
/** /**
* DereferenceObject * DereferenceObject
* *
* Turns the object pointer back into an object * Turns the object pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceObject() private function DereferenceObject()
{ {
if (!$this->objectReferenced) if (!$this->objectReferenced)
return; return;
   
if (empty($this->object)) if (empty($this->object))
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$this->object = $this->GetProject()->GetCommit($this->object); $this->object = $this->GetProject()->GetCommit($this->object);
} else if ($this->type == 'tag') { } else if ($this->type == 'tag') {
$this->object = $this->GetProject()->GetTag($this->object); $this->object = $this->GetProject()->GetTag($this->object);
} }
   
$this->objectReferenced = false; $this->objectReferenced = false;
} }
   
/** /**
* ReferenceCommit * ReferenceCommit
* *
* Turns the commit into a reference pointer * Turns the commit into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceCommit() private function ReferenceCommit()
{ {
if ($this->commitReferenced) if ($this->commitReferenced)
return; return;
   
if (!$this->commit) if (!$this->commit)
return; return;
   
$this->commit = $this->commit->GetHash(); $this->commit = $this->commit->GetHash();
   
$this->commitReferenced = true; $this->commitReferenced = true;
} }
   
/** /**
* DereferenceCommit * DereferenceCommit
* *
* Turns the commit pointer back into an object * Turns the commit pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceCommit() private function DereferenceCommit()
{ {
if (!$this->commitReferenced) if (!$this->commitReferenced)
return; return;
   
if (empty($this->commit)) if (empty($this->commit))
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$obj = $this->GetObject(); $obj = $this->GetObject();
if ($obj && ($obj->GetHash() == $this->commit)) { if ($obj && ($obj->GetHash() == $this->commit)) {
/* /*
* Light tags are type commit and the commit * Light tags are type commit and the commit
* and object are the same, in which case * and object are the same, in which case
* no need to fetch the object again * no need to fetch the object again
*/ */
$this->commit = $obj; $this->commit = $obj;
$this->commitReferenced = false; $this->commitReferenced = false;
return; return;
} }
} }
   
$this->commit = $this->GetProject()->GetCommit($this->commit); $this->commit = $this->GetProject()->GetCommit($this->commit);
   
$this->commitReferenced = false; $this->commitReferenced = false;
} }
   
/** /**
* __sleep * __sleep
* *
* Called to prepare the object for serialization * Called to prepare the object for serialization
* *
* @access public * @access public
* @return array list of properties to serialize * @return array list of properties to serialize
*/ */
public function __sleep() public function __sleep()
{ {
if (!$this->objectReferenced) if (!$this->objectReferenced)
$this->ReferenceObject(); $this->ReferenceObject();
   
if (!$this->commitReferenced) if (!$this->commitReferenced)
$this->ReferenceCommit(); $this->ReferenceCommit();
   
$properties = array('dataRead', 'object', 'commit', 'type', 'tagger', 'taggerEpoch', 'taggerTimezone', 'comment', 'objectReferenced', 'commitReferenced'); $properties = array('dataRead', 'object', 'commit', 'type', 'tagger', 'taggerEpoch', 'taggerTimezone', 'comment', 'objectReferenced', 'commitReferenced');
return array_merge($properties, parent::__sleep()); return array_merge($properties, parent::__sleep());
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key to use for this object * Gets the cache key to use for this object
* *
* @access public * @access public
* @return string cache key * @return string cache key
*/ */
public function GetCacheKey() public function GetCacheKey()
{ {
$key = parent::GetCacheKey(); $key = parent::GetCacheKey();
if (!empty($key)) if (!empty($key))
$key .= '|'; $key .= '|';
   
$key .= 'tag|' . $this->refName; $key .= 'tag|' . $this->refName;
return $key; return $key;
} }
   
   
/** /**
* CompareAge * CompareAge
* *
* Compares two tags by age * Compares two tags by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first tag * @param mixed $a first tag
* @param mixed $b second tag * @param mixed $b second tag
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
$aObj = $a->GetObject(); $aObj = $a->GetObject();
$bObj = $b->GetObject(); $bObj = $b->GetObject();
if (($aObj instanceof GitPHP_Commit) && ($bObj instanceof GitPHP_Commit)) { if (($aObj instanceof GitPHP_Commit) && ($bObj instanceof GitPHP_Commit)) {
if ($aObj->GetAge() === $bObj->GetAge()) if ($aObj->GetAge() === $bObj->GetAge())
return 0; return 0;
return ($aObj->GetAge() < $bObj->GetAge() ? -1 : 1); return ($aObj->GetAge() < $bObj->GetAge() ? -1 : 1);
} }
   
if ($aObj instanceof GitPHP_Commit) if ($aObj instanceof GitPHP_Commit)
return 1; return 1;
   
if ($bObj instanceof GitPHP_Commit) if ($bObj instanceof GitPHP_Commit)
return -1; return -1;
   
return strcmp($a->GetName(), $b->GetName()); return strcmp($a->GetName(), $b->GetName());
} }
   
} }
   
comments