Load project log using raw git objects
Load project log using raw git objects

Based on the log walking code from glip

Walking the log in raw php is a bit of a mixed bag as far as
performance. If you're walking commits close to the tip of the head
performance is good, because you save the shell call to git-rev-list and
performance wise it's light to load the parents of the first 50 commits.
However, in raw PHP we have to do the walking ourselves, which means we
can't --skip the first 100 or so commits - so when listing commits
several pages away from the head, we have to walk all the way from the
tip down to that page, and then discard the more recent commits we don't
care about.
So the loading time increases for each log page further away from the
tip (earlier commits).

<?php <?php
/** /**
* GitPHP Commit * GitPHP Commit
* *
* Represents a single commit * Represents a single commit
* *
* @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 . 'GitObject.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitObject.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Tree.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Tree.class.php');
require_once(GITPHP_GITOBJECTDIR . 'TreeDiff.class.php'); require_once(GITPHP_GITOBJECTDIR . 'TreeDiff.class.php');
   
/** /**
* Commit class * Commit class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Commit extends GitPHP_GitObject class GitPHP_Commit extends GitPHP_GitObject
{ {
   
/** /**
* dataRead * dataRead
* *
* Indicates whether data for this commit has been read * Indicates whether data for this commit has been read
* *
* @access protected * @access protected
*/ */
protected $dataRead = false; protected $dataRead = false;
   
/** /**
* parents * parents
* *
* Array of parent commits * Array of parent commits
* *
* @access protected * @access protected
*/ */
protected $parents = array(); protected $parents = array();
   
/** /**
* tree * tree
* *
* Tree object for this commit * Tree object for this commit
* *
* @access protected * @access protected
*/ */
protected $tree; protected $tree;
   
/** /**
* author * author
* *
* Author for this commit * Author for this commit
* *
* @access protected * @access protected
*/ */
protected $author; protected $author;
   
/** /**
* authorEpoch * authorEpoch
* *
* Author's epoch * Author's epoch
* *
* @access protected * @access protected
*/ */
protected $authorEpoch; protected $authorEpoch;
   
/** /**
* authorTimezone * authorTimezone
* *
* Author's timezone * Author's timezone
* *
* @access protected * @access protected
*/ */
protected $authorTimezone; protected $authorTimezone;
   
/** /**
* committer * committer
* *
* Committer for this commit * Committer for this commit
* *
* @access protected * @access protected
*/ */
protected $committer; protected $committer;
   
/** /**
* committerEpoch * committerEpoch
* *
* Committer's epoch * Committer's epoch
* *
* @access protected * @access protected
*/ */
protected $committerEpoch; protected $committerEpoch;
   
/** /**
* committerTimezone * committerTimezone
* *
* Committer's timezone * Committer's timezone
* *
* @access protected * @access protected
*/ */
protected $committerTimezone; protected $committerTimezone;
   
/** /**
* title * title
* *
* Stores the commit title * Stores the commit title
* *
* @access protected * @access protected
*/ */
protected $title; protected $title;
   
/** /**
* comment * comment
* *
* Stores the commit comment * Stores the commit comment
* *
* @access protected * @access protected
*/ */
protected $comment = array(); protected $comment = array();
   
/** /**
* readTree * readTree
* *
* Stores whether tree filenames have been read * Stores whether tree filenames have been read
* *
* @access protected * @access protected
*/ */
protected $readTree = false; protected $readTree = false;
   
/** /**
* blobPaths * blobPaths
* *
* Stores blob hash to path mappings * Stores blob hash to path mappings
* *
* @access protected * @access protected
*/ */
protected $blobPaths = array(); protected $blobPaths = array();
   
/** /**
* treePaths * treePaths
* *
* Stores tree hash to path mappings * Stores tree hash to path mappings
* *
* @access protected * @access protected
*/ */
protected $treePaths = array(); protected $treePaths = array();
   
/** /**
* hashPathsRead * hashPathsRead
* *
* Stores whether hash paths have been read * Stores whether hash paths have been read
* *
* @access protected * @access protected
*/ */
protected $hashPathsRead = false; protected $hashPathsRead = false;
   
/** /**
* containingTag * containingTag
* *
* Stores the tag containing the changes in this commit * Stores the tag containing the changes in this commit
* *
* @access protected * @access protected
*/ */
protected $containingTag = null; protected $containingTag = null;
   
/** /**
* containingTagRead * containingTagRead
* *
* Stores whether the containing tag has been looked up * Stores whether the containing tag has been looked up
* *
* @access public * @access public
*/ */
protected $containingTagRead = false; protected $containingTagRead = false;
   
/** /**
* parentsReferenced * parentsReferenced
* *
* Stores whether the parents have been referenced into pointers * Stores whether the parents have been referenced into pointers
* *
* @access private * @access private
*/ */
private $parentsReferenced = false; private $parentsReferenced = false;
   
/** /**
* treeReferenced * treeReferenced
* *
* Stores whether the tree has been referenced into a pointer * Stores whether the tree has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $treeReferenced = false; private $treeReferenced = false;
   
/** /**
* __construct * __construct
* *
* Instantiates object * Instantiates object
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $hash object hash * @param string $hash object hash
* @return mixed git object * @return mixed git object
* @throws Exception exception on invalid hash * @throws Exception exception on invalid hash
*/ */
public function __construct($project, $hash) public function __construct($project, $hash)
{ {
parent::__construct($project, $hash); parent::__construct($project, $hash);
} }
   
/** /**
* GetParent * GetParent
* *
* Gets the main parent of this commit * Gets the main parent of this commit
* *
* @access public * @access public
* @return mixed commit object for parent * @return mixed commit object for parent
*/ */
public function GetParent() public function GetParent()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->parentsReferenced) if ($this->parentsReferenced)
$this->DereferenceParents(); $this->DereferenceParents();
   
if (isset($this->parents[0])) if (isset($this->parents[0]))
return $this->parents[0]; return $this->parents[0];
return null; return null;
} }
   
/** /**
* GetParents * GetParents
* *
* Gets an array of parent objects for this commit * Gets an array of parent objects for this commit
* *
* @access public * @access public
* @return mixed array of commit objects * @return mixed array of commit objects
*/ */
public function GetParents() public function GetParents()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->parentsReferenced) if ($this->parentsReferenced)
$this->DereferenceParents(); $this->DereferenceParents();
   
return $this->parents; return $this->parents;
} }
   
/** /**
* GetTree * GetTree
* *
* Gets the tree for this commit * Gets the tree for this commit
* *
* @access public * @access public
* @return mixed tree object * @return mixed tree object
*/ */
public function GetTree() public function GetTree()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->treeReferenced) if ($this->treeReferenced)
$this->DereferenceTree(); $this->DereferenceTree();
   
return $this->tree; return $this->tree;
} }
   
/** /**
* GetAuthor * GetAuthor
* *
* Gets the author for this commit * Gets the author for this commit
* *
* @access public * @access public
* @return string author * @return string author
*/ */
public function GetAuthor() public function GetAuthor()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->author; return $this->author;
} }
   
/** /**
* GetAuthorName * GetAuthorName
* *
* Gets the author's name only * Gets the author's name only
* *
* @access public * @access public
* @return string author name * @return string author name
*/ */
public function GetAuthorName() public function GetAuthorName()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_replace('/ <.*/', '', $this->author); return preg_replace('/ <.*/', '', $this->author);
} }
   
/** /**
* GetAuthorEpoch * GetAuthorEpoch
* *
* Gets the author's epoch * Gets the author's epoch
* *
* @access public * @access public
* @return string author epoch * @return string author epoch
*/ */
public function GetAuthorEpoch() public function GetAuthorEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->authorEpoch; return $this->authorEpoch;
} }
   
/** /**
* GetAuthorLocalEpoch * GetAuthorLocalEpoch
* *
* Gets the author's local epoch * Gets the author's local epoch
* *
* @access public * @access public
* @return string author local epoch * @return string author local epoch
*/ */
public function GetAuthorLocalEpoch() public function GetAuthorLocalEpoch()
{ {
$epoch = $this->GetAuthorEpoch(); $epoch = $this->GetAuthorEpoch();
$tz = $this->GetAuthorTimezone(); $tz = $this->GetAuthorTimezone();
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;
} }
   
/** /**
* GetAuthorTimezone * GetAuthorTimezone
* *
* Gets the author's timezone * Gets the author's timezone
* *
* @access public * @access public
* @return string author timezone * @return string author timezone
*/ */
public function GetAuthorTimezone() public function GetAuthorTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->authorTimezone; return $this->authorTimezone;
} }
   
/** /**
* GetCommitter * GetCommitter
* *
* Gets the author for this commit * Gets the author for this commit
* *
* @access public * @access public
* @return string author * @return string author
*/ */
public function GetCommitter() public function GetCommitter()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committer; return $this->committer;
} }
   
/** /**
* GetCommitterName * GetCommitterName
* *
* Gets the author's name only * Gets the author's name only
* *
* @access public * @access public
* @return string author name * @return string author name
*/ */
public function GetCommitterName() public function GetCommitterName()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_replace('/ <.*/', '', $this->committer); return preg_replace('/ <.*/', '', $this->committer);
} }
   
/** /**
* GetCommitterEpoch * GetCommitterEpoch
* *
* Gets the committer's epoch * Gets the committer's epoch
* *
* @access public * @access public
* @return string committer epoch * @return string committer epoch
*/ */
public function GetCommitterEpoch() public function GetCommitterEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committerEpoch; return $this->committerEpoch;
} }
   
/** /**
* GetCommitterLocalEpoch * GetCommitterLocalEpoch
* *
* Gets the committer's local epoch * Gets the committer's local epoch
* *
* @access public * @access public
* @return string committer local epoch * @return string committer local epoch
*/ */
public function GetCommitterLocalEpoch() public function GetCommitterLocalEpoch()
{ {
$epoch = $this->GetCommitterEpoch(); $epoch = $this->GetCommitterEpoch();
$tz = $this->GetCommitterTimezone(); $tz = $this->GetCommitterTimezone();
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;
} }
   
/** /**
* GetCommitterTimezone * GetCommitterTimezone
* *
* Gets the author's timezone * Gets the author's timezone
* *
* @access public * @access public
* @return string author timezone * @return string author timezone
*/ */
public function GetCommitterTimezone() public function GetCommitterTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committerTimezone; return $this->committerTimezone;
} }
   
/** /**
* GetTitle * GetTitle
* *
* Gets the commit title * Gets the commit title
* *
* @access public * @access public
* @param integer $trim length to trim to (0 for no trim) * @param integer $trim length to trim to (0 for no trim)
* @return string title * @return string title
*/ */
public function GetTitle($trim = 0) public function GetTitle($trim = 0)
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
if (($trim > 0) && (strlen($this->title) > $trim)) { if (($trim > 0) && (strlen($this->title) > $trim)) {
return substr($this->title, 0, $trim) . '…'; return substr($this->title, 0, $trim) . '…';
} }
   
return $this->title; return $this->title;
} }
   
/** /**
* GetComment * GetComment
* *
* Gets the lines of comment * Gets the lines of comment
* *
* @access public * @access public
* @return array lines of comment * @return array lines of comment
*/ */
public function GetComment() public function GetComment()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->comment; return $this->comment;
} }
   
/** /**
* SearchComment * SearchComment
* *
* Gets the lines of the comment matching the given pattern * Gets the lines of the comment matching the given pattern
* *
* @access public * @access public
* @param string $pattern pattern to find * @param string $pattern pattern to find
* @return array matching lines of comment * @return array matching lines of comment
*/ */
public function SearchComment($pattern) public function SearchComment($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return $this->GetComment(); return $this->GetComment();
   
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_grep('/' . $pattern . '/i', $this->comment); return preg_grep('/' . $pattern . '/i', $this->comment);
} }
   
/** /**
* GetAge * GetAge
* *
* Gets the age of the commit * Gets the age of the commit
* *
* @access public * @access public
* @return string age * @return string age
*/ */
public function GetAge() public function GetAge()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if (!empty($this->committerEpoch)) if (!empty($this->committerEpoch))
return time() - $this->committerEpoch; return time() - $this->committerEpoch;
   
return ''; return '';
} }
   
/** /**
* ReadData * ReadData
* *
* Read the data for the commit * Read the data for the commit
* *
* @access protected * @access protected
*/ */
protected function ReadData() protected function ReadData()
{ {
$this->dataRead = true; $this->dataRead = true;
   
$lines = null; $lines = null;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
   
/* get data from git_rev_list */ /* get data from git_rev_list */
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '--header'; $args[] = '--header';
$args[] = '--parents'; $args[] = '--parents';
$args[] = '--max-count=1'; $args[] = '--max-count=1';
$args[] = $this->hash; $args[] = $this->hash;
$ret = $exe->Execute(GIT_REV_LIST, $args); $ret = $exe->Execute(GIT_REV_LIST, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
if (!isset($lines[0])) if (!isset($lines[0]))
return; return;
   
/* In case we returned something unexpected */ /* In case we returned something unexpected */
$tok = strtok($lines[0], ' '); $tok = strtok($lines[0], ' ');
if ($tok != $this->hash) if ($tok != $this->hash)
return; return;
   
array_shift($lines); array_shift($lines);
   
} else { } else {
$data = $this->GetProject()->GetObject($this->hash); $data = $this->GetProject()->GetObject($this->hash);
if (empty($data)) if (empty($data))
return; return;
   
$lines = explode("\n", $data); $lines = explode("\n", $data);
   
} }
   
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (preg_match('/^tree ([0-9a-fA-F]{40})$/', $line, $regs)) { if (preg_match('/^tree ([0-9a-fA-F]{40})$/', $line, $regs)) {
/* Tree */ /* Tree */
try { try {
$tree = $this->GetProject()->GetTree($regs[1]); $tree = $this->GetProject()->GetTree($regs[1]);
if ($tree) { if ($tree) {
$tree->SetCommit($this); $tree->SetCommit($this);
$this->tree = $tree; $this->tree = $tree;
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} else if (preg_match('/^parent ([0-9a-fA-F]{40})$/', $line, $regs)) { } else if (preg_match('/^parent ([0-9a-fA-F]{40})$/', $line, $regs)) {
/* Parent */ /* Parent */
try { try {
$this->parents[] = $this->GetProject()->GetCommit($regs[1]); $this->parents[] = $this->GetProject()->GetCommit($regs[1]);
} catch (Exception $e) { } catch (Exception $e) {
} }
} else if (preg_match('/^author (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if (preg_match('/^author (.*) ([0-9]+) (.*)$/', $line, $regs)) {
/* author data */ /* author data */
$this->author = $regs[1]; $this->author = $regs[1];
$this->authorEpoch = $regs[2]; $this->authorEpoch = $regs[2];
$this->authorTimezone = $regs[3]; $this->authorTimezone = $regs[3];
} else if (preg_match('/^committer (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if (preg_match('/^committer (.*) ([0-9]+) (.*)$/', $line, $regs)) {
/* committer data */ /* committer data */
$this->committer = $regs[1]; $this->committer = $regs[1];
$this->committerEpoch = $regs[2]; $this->committerEpoch = $regs[2];
$this->committerTimezone = $regs[3]; $this->committerTimezone = $regs[3];
} else { } else {
/* commit comment */ /* commit comment */
$trimmed = trim($line); $trimmed = trim($line);
if (empty($this->title) && (strlen($trimmed) > 0)) if (empty($this->title) && (strlen($trimmed) > 0))
$this->title = $trimmed; $this->title = $trimmed;
if (!empty($this->title)) { if (!empty($this->title)) {
if ((strlen($trimmed) > 0) || ($i < (count($lines)-1))) if ((strlen($trimmed) > 0) || ($i < (count($lines)-1)))
$this->comment[] = $trimmed; $this->comment[] = $trimmed;
} }
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* GetHeads * GetHeads
* *
* Gets heads that point to this commit * Gets heads that point to this commit
* *
* @access public * @access public
* @return array array of heads * @return array array of heads
*/ */
public function GetHeads() public function GetHeads()
{ {
$heads = array(); $heads = array();
   
$projectRefs = $this->GetProject()->GetRefs('heads'); $projectRefs = $this->GetProject()->GetRefs('heads');
   
foreach ($projectRefs as $ref) { foreach ($projectRefs as $ref) {
if ($ref->GetHash() == $this->hash) { if ($ref->GetHash() == $this->hash) {
$heads[] = $ref; $heads[] = $ref;
} }
} }
   
return $heads; return $heads;
} }
   
/** /**
* GetTags * GetTags
* *
* Gets tags that point to this commit * Gets tags that point to this commit
* *
* @access public * @access public
* @return array array of tags * @return array array of tags
*/ */
public function GetTags() public function GetTags()
{ {
$tags = array(); $tags = array();
   
$projectRefs = $this->GetProject()->GetRefs('tags'); $projectRefs = $this->GetProject()->GetRefs('tags');
   
foreach ($projectRefs as $ref) { foreach ($projectRefs as $ref) {
if ($ref->GetCommit()->GetHash() === $this->hash) { if ($ref->GetCommit()->GetHash() === $this->hash) {
$tags[] = $ref; $tags[] = $ref;
} }
} }
   
return $tags; return $tags;
} }
   
/** /**
* GetContainingTag * GetContainingTag
* *
* Gets the tag that contains the changes in this commit * Gets the tag that contains the changes in this commit
* *
* @access public * @access public
* @return tag object * @return tag object
*/ */
public function GetContainingTag() public function GetContainingTag()
{ {
if (!$this->containingTagRead) if (!$this->containingTagRead)
$this->ReadContainingTag(); $this->ReadContainingTag();
   
return $this->containingTag; return $this->containingTag;
} }
   
/** /**
* ReadContainingTag * ReadContainingTag
* *
* Looks up the tag that contains the changes in this commit * Looks up the tag that contains the changes in this commit
* *
* @access private * @access private
*/ */
public function ReadContainingTag() public function ReadContainingTag()
{ {
$this->containingTagRead = true; $this->containingTagRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '--tags'; $args[] = '--tags';
$args[] = $this->hash; $args[] = $this->hash;
$revs = explode("\n", $exe->Execute(GIT_NAME_REV, $args)); $revs = explode("\n", $exe->Execute(GIT_NAME_REV, $args));
   
foreach ($revs as $revline) { foreach ($revs as $revline) {
if (preg_match('/^([0-9a-fA-F]{40})\s+tags\/(.+)(\^[0-9]+|\~[0-9]+)$/', $revline, $regs)) { if (preg_match('/^([0-9a-fA-F]{40})\s+tags\/(.+)(\^[0-9]+|\~[0-9]+)$/', $revline, $regs)) {
if ($regs[1] == $this->hash) { if ($regs[1] == $this->hash) {
$this->containingTag = $this->GetProject()->GetTag($regs[2]); $this->containingTag = $this->GetProject()->GetTag($regs[2]);
break; break;
} }
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* DiffToParent * DiffToParent
* *
* Diffs this commit with its immediate parent * Diffs this commit with its immediate parent
* *
* @access public * @access public
* @return mixed Tree diff * @return mixed Tree diff
*/ */
public function DiffToParent() public function DiffToParent()
{ {
return new GitPHP_TreeDiff($this->GetProject(), $this->hash); return new GitPHP_TreeDiff($this->GetProject(), $this->hash);
} }
   
/** /**
* PathToHash * PathToHash
* *
* Given a filepath, get its hash * Given a filepath, get its hash
* *
* @access public * @access public
* @param string $path path * @param string $path path
* @return string hash * @return string hash
*/ */
public function PathToHash($path) public function PathToHash($path)
{ {
if (empty($path)) if (empty($path))
return ''; return '';
   
if (!$this->hashPathsRead) if (!$this->hashPathsRead)
$this->ReadHashPaths(); $this->ReadHashPaths();
   
if (isset($this->blobPaths[$path])) { if (isset($this->blobPaths[$path])) {
return $this->blobPaths[$path]; return $this->blobPaths[$path];
} }
   
if (isset($this->treePaths[$path])) { if (isset($this->treePaths[$path])) {
return $this->treePaths[$path]; return $this->treePaths[$path];
} }
   
return ''; return '';
} }
   
/** /**
* ReadHashPaths * ReadHashPaths
* *
* Read hash to path mappings * Read hash to path mappings
* *
* @access private * @access private
*/ */
private function ReadHashPaths() private function ReadHashPaths()
{ {
$this->hashPathsRead = true; $this->hashPathsRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = '--full-name'; $args[] = '--full-name';
$args[] = '-r'; $args[] = '-r';
$args[] = '-t'; $args[] = '-t';
$args[] = $this->hash; $args[] = $this->hash;
   
$lines = explode("\n", $exe->Execute(GIT_LS_TREE, $args)); $lines = explode("\n", $exe->Execute(GIT_LS_TREE, $args));
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/", $line, $regs)) { if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/", $line, $regs)) {
switch ($regs[2]) { switch ($regs[2]) {
case 'tree': case 'tree':
$this->treePaths[trim($regs[4])] = $regs[3]; $this->treePaths[trim($regs[4])] = $regs[3];
break; break;
case 'blob'; case 'blob';
$this->blobPaths[trim($regs[4])] = $regs[3]; $this->blobPaths[trim($regs[4])] = $regs[3];
break; break;
} }
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* SearchFilenames * SearchFilenames
* *
* Returns array of objects matching pattern * Returns array of objects matching pattern
* *
* @access public * @access public
* @param string $pattern pattern to find * @param string $pattern pattern to find
* @return array array of objects * @return array array of objects
*/ */
public function SearchFilenames($pattern) public function SearchFilenames($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
if (!$this->hashPathsRead) if (!$this->hashPathsRead)
$this->ReadHashPaths(); $this->ReadHashPaths();
   
$results = array(); $results = array();
   
foreach ($this->treePaths as $path => $hash) { foreach ($this->treePaths as $path => $hash) {
if (preg_match('/' . $pattern . '/i', $path)) { if (preg_match('/' . $pattern . '/i', $path)) {
$obj = $this->GetProject()->GetTree($hash); $obj = $this->GetProject()->GetTree($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$path] = $obj; $results[$path] = $obj;
} }
} }
   
foreach ($this->blobPaths as $path => $hash) { foreach ($this->blobPaths as $path => $hash) {
if (preg_match('/' . $pattern . '/i', $path)) { if (preg_match('/' . $pattern . '/i', $path)) {
$obj = $this->GetProject()->GetBlob($hash); $obj = $this->GetProject()->GetBlob($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$path] = $obj; $results[$path] = $obj;
} }
} }
   
ksort($results); ksort($results);
   
return $results; return $results;
} }
   
/** /**
* SearchFileContents * SearchFileContents
* *
* Searches for a pattern in file contents * Searches for a pattern in file contents
* *
* @access public * @access public
* @param string $pattern pattern to search for * @param string $pattern pattern to search for
* @return array multidimensional array of results * @return array multidimensional array of results
*/ */
public function SearchFileContents($pattern) public function SearchFileContents($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = '-I'; $args[] = '-I';
$args[] = '--full-name'; $args[] = '--full-name';
$args[] = '--ignore-case'; $args[] = '--ignore-case';
$args[] = '-n'; $args[] = '-n';
$args[] = '-e'; $args[] = '-e';
$args[] = $pattern; $args[] = $pattern;
$args[] = $this->hash; $args[] = $this->hash;
   
$lines = explode("\n", $exe->Execute(GIT_GREP, $args)); $lines = explode("\n", $exe->Execute(GIT_GREP, $args));
   
$results = array(); $results = array();
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) { if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) {
if (!isset($results[$regs[1]]['object'])) { if (!isset($results[$regs[1]]['object'])) {
$hash = $this->PathToHash($regs[1]); $hash = $this->PathToHash($regs[1]);
if (!empty($hash)) { if (!empty($hash)) {
$obj = $this->GetProject()->GetBlob($hash); $obj = $this->GetProject()->GetBlob($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$regs[1]]['object'] = $obj; $results[$regs[1]]['object'] = $obj;
} }
} }
$results[$regs[1]]['lines'][(int)($regs[2])] = $regs[3]; $results[$regs[1]]['lines'][(int)($regs[2])] = $regs[3];
} }
} }
   
return $results; return $results;
} }
   
/** /**
* SearchFiles * SearchFiles
* *
* Searches filenames and file contents for a pattern * Searches filenames and file contents for a pattern
* *
* @access public * @access public
* @param string $pattern pattern to search * @param string $pattern pattern to search
* @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 results * @return array array of results
*/ */
public function SearchFiles($pattern, $count = 100, $skip = 0) public function SearchFiles($pattern, $count = 100, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$grepresults = $this->SearchFileContents($pattern); $grepresults = $this->SearchFileContents($pattern);
   
$nameresults = $this->SearchFilenames($pattern); $nameresults = $this->SearchFilenames($pattern);
   
/* Merge the results together */ /* Merge the results together */
foreach ($nameresults as $path => $obj) { foreach ($nameresults as $path => $obj) {
if (!isset($grepresults[$path]['object'])) { if (!isset($grepresults[$path]['object'])) {
$grepresults[$path]['object'] = $obj; $grepresults[$path]['object'] = $obj;
} }
} }
   
ksort($grepresults); ksort($grepresults);
   
return array_slice($grepresults, $skip, $count, true); return array_slice($grepresults, $skip, $count, true);
} }
   
/** /**
* ReferenceParents * ReferenceParents
* *
* Turns the list of parents into reference pointers * Turns the list of parents into reference pointers
* *
* @access private * @access private
*/ */
private function ReferenceParents() private function ReferenceParents()
{ {
if ($this->parentsReferenced) if ($this->parentsReferenced)
return; return;
   
if ((!isset($this->parents)) || (count($this->parents) < 1)) if ((!isset($this->parents)) || (count($this->parents) < 1))
return; return;
   
for ($i = 0; $i < count($this->parents); $i++) { for ($i = 0; $i < count($this->parents); $i++) {
$this->parents[$i] = $this->parents[$i]->GetHash(); $this->parents[$i] = $this->parents[$i]->GetHash();
} }
   
$this->parentsReferenced = true; $this->parentsReferenced = true;
} }
   
/** /**
* DereferenceParents * DereferenceParents
* *
* Turns the list of parent pointers back into objects * Turns the list of parent pointers back into objects
* *
* @access private * @access private
*/ */
private function DereferenceParents() private function DereferenceParents()
{ {
if (!$this->parentsReferenced) if (!$this->parentsReferenced)
return; return;
   
if ((!$this->parents) || (count($this->parents) < 1)) if ((!$this->parents) || (count($this->parents) < 1))
return; return;
   
for ($i = 0; $i < count($this->parents); $i++) { for ($i = 0; $i < count($this->parents); $i++) {
$this->parents[$i] = $this->GetProject()->GetCommit($this->parents[$i]); $this->parents[$i] = $this->GetProject()->GetCommit($this->parents[$i]);
} }
   
$this->parentsReferenced = false; $this->parentsReferenced = false;
} }
   
/** /**
* ReferenceTree * ReferenceTree
* *
* Turns the tree into a reference pointer * Turns the tree into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceTree() private function ReferenceTree()
{ {
if ($this->treeReferenced) if ($this->treeReferenced)
return; return;
   
if (!$this->tree) if (!$this->tree)
return; return;
   
$this->tree = $this->tree->GetHash(); $this->tree = $this->tree->GetHash();
   
$this->treeReferenced = true; $this->treeReferenced = true;
} }
   
/** /**
* DereferenceTree * DereferenceTree
* *
* Turns the tree pointer back into an object * Turns the tree pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceTree() private function DereferenceTree()
{ {
if (!$this->treeReferenced) if (!$this->treeReferenced)
return; return;
   
if (empty($this->tree)) if (empty($this->tree))
return; return;
   
$this->tree = $this->GetProject()->GetTree($this->tree); $this->tree = $this->GetProject()->GetTree($this->tree);
   
if ($this->tree) if ($this->tree)
$this->tree->SetCommit($this); $this->tree->SetCommit($this);
   
$this->treeReferenced = false; $this->treeReferenced = 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->parentsReferenced) if (!$this->parentsReferenced)
$this->ReferenceParents(); $this->ReferenceParents();
   
if (!$this->treeReferenced) if (!$this->treeReferenced)
$this->ReferenceTree(); $this->ReferenceTree();
   
$properties = array('dataRead', 'parents', 'tree', 'author', 'authorEpoch', 'authorTimezone', 'committer', 'committerEpoch', 'committerTimezone', 'title', 'comment', 'readTree', 'blobPaths', 'treePaths', 'hashPathsRead', 'parentsReferenced', 'treeReferenced'); $properties = array('dataRead', 'parents', 'tree', 'author', 'authorEpoch', 'authorTimezone', 'committer', 'committerEpoch', 'committerTimezone', 'title', 'comment', 'readTree', 'blobPaths', 'treePaths', 'hashPathsRead', 'parentsReferenced', 'treeReferenced');
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 .= 'commit|' . $this->hash; $key .= 'commit|' . $this->hash;
   
return $key; return $key;
} }
   
  /**
  * CompareAge
  *
  * Compares two commits by age
  *
  * @access public
  * @static
  * @param mixed $a first commit
  * @param mixed $b second commit
  * @return integer comparison result
  */
  public static function CompareAge($a, $b)
  {
  if ($a->GetAge() === $b->GetAge())
  return 0;
  return ($a->GetAge() < $b->GetAge() ? -1 : 1);
  }
   
} }
   
<?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->GetHash()); $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()) return GitPHP_Commit::CompareAge($aObj, $bObj);
return 0;  
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');
require_once(GITPHP_GITOBJECTDIR . 'Pack.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Pack.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;
   
/** /**
* tags * tags
* *
* Stores the tags for the project * Stores the tags for the project
* *
* @access protected * @access protected
*/ */
protected $tags = array(); protected $tags = array();
   
/** /**
* heads * heads
* *
* Stores the heads for the project * Stores the heads for the project
* *
* @access protected * @access protected
*/ */
protected $heads = array(); protected $heads = 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;
   
/** /**
* 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();
   
/** /**
* packs * packs
* *
* Stores the list of packs * Stores the list of packs
* *
* @access protected * @access protected
*/ */
protected $packs = array(); protected $packs = array();
   
/** /**
* packsRead * packsRead
* *
* Stores whether packs have been read * Stores whether packs have been read
* *
* @access protected * @access protected
*/ */
protected $packsRead = false; protected $packsRead = false;
   
/** /**
* __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;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadHeadCommitGit(); $this->ReadHeadCommitGit();
} else { } else {
$this->ReadHeadCommitRaw(); $this->ReadHeadCommitRaw();
} }
} }
   
/** /**
* ReadHeadCommitGit * ReadHeadCommitGit
* *
* Read head commit using git executable * Read head commit using git executable
* *
* @access private * @access private
*/ */
private function ReadHeadCommitGit() private function ReadHeadCommitGit()
{ {
$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));
} }
   
/** /**
* ReadHeadCommitRaw * ReadHeadCommitRaw
* *
* Read head commit using raw git head pointer * Read head commit using raw git head pointer
* *
* @access private * @access private
*/ */
private function ReadHeadCommitRaw() private function ReadHeadCommitRaw()
{ {
$head = trim(file_get_contents($this->GetPath() . '/HEAD')); $head = trim(file_get_contents($this->GetPath() . '/HEAD'));
if (preg_match('/^([0-9A-Fa-f]{40})$/', $head, $regs)) { if (preg_match('/^([0-9A-Fa-f]{40})$/', $head, $regs)) {
/* Detached HEAD */ /* Detached HEAD */
$this->head = $regs[1]; $this->head = $regs[1];
} else if (preg_match('/^ref: (.+)$/', $head, $regs)) { } else if (preg_match('/^ref: (.+)$/', $head, $regs)) {
/* standard pointer to head */ /* standard pointer to head */
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (isset($this->heads[$regs[1]])) { if (isset($this->heads[$regs[1]])) {
$this->head = $this->heads[$regs[1]]->GetHash(); $this->head = $this->heads[$regs[1]]->GetHash();
} }
} }
} }
   
/** /**
* 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();
return null; return null;
} 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->GetCommit(); $obj = $tag->GetCommit();
if ($obj != null) { if ($obj != null) {
return $obj; return $obj;
} }
} }
return null; return null;
} }
   
if (preg_match('/[0-9a-f]{40}/i', $hash)) { if (preg_match('/[0-9a-f]{40}/i', $hash)) {
   
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];
   
} }
   
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (isset($this->heads['refs/heads/' . $hash])) if (isset($this->heads['refs/heads/' . $hash]))
return $this->heads['refs/heads/' . $hash]->GetCommit(); return $this->heads['refs/heads/' . $hash]->GetCommit();
   
if (isset($this->tags['refs/tags/' . $hash])) if (isset($this->tags['refs/tags/' . $hash]))
return $this->tags['refs/tags/' . $hash]->GetCommit(); return $this->tags['refs/tags/' . $hash]->GetCommit();
   
return null; return null;
} }
   
/** /**
* 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
* @param string $type type of refs to get * @param string $type type of refs to get
* @return array array of refs * @return array array of refs
*/ */
public function GetRefs($type = '') public function GetRefs($type = '')
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if ($type == 'tags') { if ($type == 'tags') {
return $this->tags; return $this->tags;
} else if ($type == 'heads') { } else if ($type == 'heads') {
return $this->heads; return $this->heads;
} }
   
return array_merge($this->heads, $this->tags); return array_merge($this->heads, $this->tags);
} }
   
/** /**
* ReadRefList * ReadRefList
* *
* Reads the list of refs for this project * Reads the list of refs for this project
* *
* @access protected * @access protected
*/ */
protected function ReadRefList() protected function ReadRefList()
{ {
$this->readRefs = true; $this->readRefs = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadRefListGit(); $this->ReadRefListGit();
} else { } else {
$this->ReadRefListRaw(); $this->ReadRefListRaw();
} }
} }
   
/** /**
* ReadRefListGit * ReadRefListGit
* *
* Reads the list of refs for this project using the git executable * Reads the list of refs for this project using the git executable
* *
* @access private * @access private
*/ */
private function ReadRefListGit() private function ReadRefListGit()
{ {
$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->tags[$key])) { if ($derefCommit && isset($this->tags[$key])) {
$this->tags[$key]->SetCommit($derefCommit); $this->tags[$key]->SetCommit($derefCommit);
} }
} else if (!isset($this->tags[$key])) { } else if (!isset($this->tags[$key])) {
$this->tags[$key] = $this->LoadTag($regs[3], $regs[1]); $this->tags[$key] = $this->LoadTag($regs[3], $regs[1]);
} }
} else if ($regs[2] == 'heads') { } else if ($regs[2] == 'heads') {
$this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]); $this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
} }
   
/** /**
* ReadRefListRaw * ReadRefListRaw
* *
* Reads the list of refs for this project using the raw git files * Reads the list of refs for this project using the raw git files
* *
* @access private * @access private
*/ */
private function ReadRefListRaw() private function ReadRefListRaw()
{ {
$pathlen = strlen($this->GetPath()) + 1; $pathlen = strlen($this->GetPath()) + 1;
   
// read loose heads // read loose heads
$heads = $this->ListDir($this->GetPath() . '/refs/heads'); $heads = $this->ListDir($this->GetPath() . '/refs/heads');
for ($i = 0; $i < count($heads); $i++) { for ($i = 0; $i < count($heads); $i++) {
$key = trim(substr($heads[$i], $pathlen), "/\\"); $key = trim(substr($heads[$i], $pathlen), "/\\");
   
if (isset($this->heads[$key])) { if (isset($this->heads[$key])) {
continue; continue;
} }
   
$hash = trim(file_get_contents($heads[$i])); $hash = trim(file_get_contents($heads[$i]));
if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
$head = substr($key, strlen('refs/heads/')); $head = substr($key, strlen('refs/heads/'));
$this->heads[$key] = new GitPHP_Head($this, $head, $hash); $this->heads[$key] = new GitPHP_Head($this, $head, $hash);
} }
} }
   
// read loose tags // read loose tags
$tags = $this->ListDir($this->GetPath() . '/refs/tags'); $tags = $this->ListDir($this->GetPath() . '/refs/tags');
for ($i = 0; $i < count($tags); $i++) { for ($i = 0; $i < count($tags); $i++) {
$key = trim(substr($tags[$i], $pathlen), "/\\"); $key = trim(substr($tags[$i], $pathlen), "/\\");
   
if (isset($this->tags[$key])) { if (isset($this->tags[$key])) {
continue; continue;
} }
   
$hash = trim(file_get_contents($tags[$i])); $hash = trim(file_get_contents($tags[$i]));
if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
$tag = substr($key, strlen('refs/tags/')); $tag = substr($key, strlen('refs/tags/'));
$this->tags[$key] = $this->LoadTag($tag, $hash); $this->tags[$key] = $this->LoadTag($tag, $hash);
} }
} }
   
// check packed refs // check packed refs
if (file_exists($this->GetPath() . '/packed-refs')) { if (file_exists($this->GetPath() . '/packed-refs')) {
$packedRefs = explode("\n", file_get_contents($this->GetPath() . '/packed-refs')); $packedRefs = explode("\n", file_get_contents($this->GetPath() . '/packed-refs'));
   
$lastRef = null; $lastRef = null;
foreach ($packedRefs as $ref) { foreach ($packedRefs as $ref) {
   
if (preg_match('/^\^([0-9A-Fa-f]{40})$/', $ref, $regs)) { if (preg_match('/^\^([0-9A-Fa-f]{40})$/', $ref, $regs)) {
// dereference of previous ref // dereference of previous ref
if (($lastRef != null) && ($lastRef instanceof GitPHP_Tag)) { if (($lastRef != null) && ($lastRef instanceof GitPHP_Tag)) {
$derefCommit = $this->GetCommit($regs[1]); $derefCommit = $this->GetCommit($regs[1]);
if ($derefCommit) { if ($derefCommit) {
$lastRef->SetCommit($derefCommit); $lastRef->SetCommit($derefCommit);
} }
} }
} }
   
$lastRef = null; $lastRef = null;
   
if (preg_match('/^([0-9A-Fa-f]{40}) refs\/(tags|heads)\/(.+)$/', $ref, $regs)) { if (preg_match('/^([0-9A-Fa-f]{40}) refs\/(tags|heads)\/(.+)$/', $ref, $regs)) {
// standard tag/head // standard tag/head
$key = 'refs/' . $regs[2] . '/' . $regs[3]; $key = 'refs/' . $regs[2] . '/' . $regs[3];
if ($regs[2] == 'tags') { if ($regs[2] == 'tags') {
if (!isset($this->tags[$key])) { if (!isset($this->tags[$key])) {
$lastRef = $this->LoadTag($regs[3], $regs[1]); $lastRef = $this->LoadTag($regs[3], $regs[1]);
$this->tags[$key] = $lastRef; $this->tags[$key] = $lastRef;
} }
} else if ($regs[2] == 'heads') { } else if ($regs[2] == 'heads') {
if (!isset($this->heads[$key])) { if (!isset($this->heads[$key])) {
$this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]); $this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
} }
} }
} }
} }
} }
} }
   
/** /**
* ListDir * ListDir
* *
* Recurses into a directory and lists files inside * Recurses into a directory and lists files inside
* *
* @access private * @access private
* @param string $dir directory * @param string $dir directory
* @return array array of filenames * @return array array of filenames
*/ */
private function ListDir($dir) private function ListDir($dir)
{ {
$files = array(); $files = array();
if ($dh = opendir($dir)) { if ($dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
if (($file == '.') || ($file == '..')) { if (($file == '.') || ($file == '..')) {
continue; continue;
} }
$fullFile = $dir . '/' . $file; $fullFile = $dir . '/' . $file;
if (is_dir($fullFile)) { if (is_dir($fullFile)) {
$subFiles = $this->ListDir($fullFile); $subFiles = $this->ListDir($fullFile);
if (count($subFiles) > 0) { if (count($subFiles) > 0) {
$files = array_merge($files, $subFiles); $files = array_merge($files, $subFiles);
} }
} else { } else {
$files[] = $fullFile; $files[] = $fullFile;
} }
} }
} }
return $files; return $files;
} }
   
/** /**
* GetTags * GetTags
* *
* Gets list of tags for this project by age descending * Gets list of tags for this project by age descending
* *
* @access public * @access public
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of tags * @return array array of tags
*/ */
public function GetTags($count = 0) public function GetTags($count = 0)
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--sort=-creatordate'; $args[] = '--sort=-creatordate';
$args[] = '--format="%(refname)"'; $args[] = '--format="%(refname)"';
if ($count > 0) { if ($count > 0) {
$args[] = '--count=' . $count; $args[] = '--count=' . $count;
} }
$args[] = '--'; $args[] = '--';
$args[] = 'refs/tags'; $args[] = 'refs/tags';
$ret = $exe->Execute(GIT_FOR_EACH_REF, $args); $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
$tags = array(); $tags = array();
   
foreach ($lines as $ref) { foreach ($lines as $ref) {
if (isset($this->tags[$ref])) { if (isset($this->tags[$ref])) {
$tags[] = $this->tags[$ref]; $tags[] = $this->tags[$ref];
} }
} }
   
return $tags; 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 (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$key = 'refs/tags/' . $tag; $key = 'refs/tags/' . $tag;
   
if (!isset($this->tags[$key])) { if (!isset($this->tags[$key])) {
$this->tags[$key] = $this->LoadTag($tag); $this->tags[$key] = $this->LoadTag($tag);
} }
   
return $this->tags[$key]; return $this->tags[$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);
} }
} }
   
/** /**
* GetHeads * GetHeads
* *
* Gets list of heads for this project by age descending * Gets list of heads for this project by age descending
* *
* @access public * @access public
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of heads * @return array array of heads
*/ */
public function GetHeads($count = 0) public function GetHeads($count = 0)
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--sort=-committerdate'; $args[] = '--sort=-committerdate';
$args[] = '--format="%(refname)"'; $args[] = '--format="%(refname)"';
if ($count > 0) { if ($count > 0) {
$args[] = '--count=' . $count; $args[] = '--count=' . $count;
} }
$args[] = '--'; $args[] = '--';
$args[] = 'refs/heads'; $args[] = 'refs/heads';
$ret = $exe->Execute(GIT_FOR_EACH_REF, $args); $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
$heads = array(); $heads = array();
   
foreach ($lines as $ref) { foreach ($lines as $ref) {
if (isset($this->heads[$ref])) { if (isset($this->heads[$ref])) {
$heads[] = $this->heads[$ref]; $heads[] = $this->heads[$ref];
} }
} }
   
return $heads; return $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->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$key = 'refs/heads/' . $head; $key = 'refs/heads/' . $head;
   
if (!isset($this->heads[$key])) { if (!isset($this->heads[$key])) {
$this->heads[$key] = new GitPHP_Head($this, $head); $this->heads[$key] = new GitPHP_Head($this, $head);
} }
   
return $this->heads[$key]; return $this->heads[$key];
} }
   
/** /**
* GetLogHash * GetLogHash
* *
* Gets log entries as an array of hashes * Gets log entries as an array of hashes
* *
* @access public * @access private
* @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) private 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)
{ {
  if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
  return $this->GetLogGit($hash, $count, $skip);
  } else {
  return $this->GetLogRaw($hash, $count, $skip);
  }
  }
   
  /**
  * GetLogGit
  *
  * Gets log entries using git exe
  *
  * @access private
  * @param string $hash hash to start the log at
  * @param integer $count number of entries to get
  * @param integer $skip number of entries to skip
  * @return array array of commit objects
  */
  private function GetLogGit($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;
  }
   
  /**
  * GetLogRaw
  *
  * Gets log entries using raw git objects
  * Based on history walking code from glip
  *
  * @access private
  */
  private function GetLogRaw($hash, $count = 50, $skip = 0)
  {
  $total = $count + $skip;
   
  $inc = array();
  $num = 0;
  $queue = array($this->GetCommit($hash));
  while (($commit = array_shift($queue)) !== null) {
  $parents = $commit->GetParents();
  foreach ($parents as $parent) {
  if (!isset($inc[$parent->GetHash()])) {
  $inc[$parent->GetHash()] = 1;
  $queue[] = $parent;
  $num++;
  } else {
  $inc[$parent->GetHash()]++;
  }
  }
  if ($num >= $total)
  break;
  }
   
  $queue = array($this->GetCommit($hash));
  $log = array();
  $num = 0;
  while (($commit = array_pop($queue)) !== null) {
  array_push($log, $commit);
  $num++;
  if ($num == $total) {
  break;
  }
  $parents = $commit->GetParents();
  foreach ($parents as $parent) {
  if (--$inc[$parent->GetHash()] == 0) {
  $queue[] = $parent;
  }
  }
  }
   
  if ($skip > 0) {
  $log = array_slice($log, $skip, $count);
  }
  usort($log, array('GitPHP_Commit', 'CompareAge'));
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);
} }
   
/** /**
* GetObject * GetObject
* *
* Gets the raw content of an object * Gets the raw content of an object
* *
* @access public * @access public
* @param string $hash object hash * @param string $hash object hash
* @return string object data * @return string object data
*/ */
public function GetObject($hash, &$type = 0) public function GetObject($hash, &$type = 0)
{ {
if (!preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (!preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
return false; return false;
} }
   
// first check if it's unpacked // first check if it's unpacked
$path = $this->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2); $path = $this->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2);
if (file_exists($path)) { if (file_exists($path)) {
list($header, $data) = explode("\0", gzuncompress(file_get_contents($path)), 2); list($header, $data) = explode("\0", gzuncompress(file_get_contents($path)), 2);
sscanf($header, "%s %d", $typestr, $size); sscanf($header, "%s %d", $typestr, $size);
switch ($typestr) { switch ($typestr) {
case 'commit': case 'commit':
$type = GitPHP_Pack::OBJ_COMMIT; $type = GitPHP_Pack::OBJ_COMMIT;
break; break;
case 'tree': case 'tree':
$type = GitPHP_Pack::OBJ_TREE; $type = GitPHP_Pack::OBJ_TREE;
break; break;
case 'blob': case 'blob':
$type = GitPHP_Pack::OBJ_BLOB; $type = GitPHP_Pack::OBJ_BLOB;
break; break;
case 'tag': case 'tag':
$type = GitPHP_Pack::OBJ_TAG; $type = GitPHP_Pack::OBJ_TAG;
break; break;
} }
return $data; return $data;
} }
   
if (!$this->packsRead) { if (!$this->packsRead) {
$this->ReadPacks(); $this->ReadPacks();
} }
   
// then try packs // then try packs
foreach ($this->packs as $pack) { foreach ($this->packs as $pack) {
$data = $pack->GetObject($hash, $type); $data = $pack->GetObject($hash, $type);
if ($data !== false) { if ($data !== false) {
return $data; return $data;
} }
} }
   
return false; return false;
} }
   
/** /**
* ReadPacks * ReadPacks
* *
* Read the list of packs in the repository * Read the list of packs in the repository
* *
* @access private * @access private
*/ */
private function ReadPacks() private function ReadPacks()
{ {
$dh = opendir($this->GetPath() . '/objects/pack'); $dh = opendir($this->GetPath() . '/objects/pack');
if ($dh !== false) { if ($dh !== false) {
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
if (preg_match('/^pack-([0-9A-Fa-f]{40})\.idx$/', $file, $regs)) { if (preg_match('/^pack-([0-9A-Fa-f]{40})\.idx$/', $file, $regs)) {
$this->packs[] = new GitPHP_Pack($this, $regs[1]); $this->packs[] = new GitPHP_Pack($this, $regs[1]);
} }
} }
} }
$this->packsRead = true; $this->packsRead = true;
} }
   
} }
   
<?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->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
   
if ($this->commit) if ($this->commit)
return $this->commit; return $this->commit;
   
if (!$this->dataRead) { if (!$this->dataRead) {
$this->ReadData(); $this->ReadData();
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
} }
   
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->GetHash(); 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;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadDataGit(); $this->ReadDataGit();
} else { } else {
$this->ReadDataRaw(); $this->ReadDataRaw();
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReadDataGit * ReadDataGit
* *
* Reads the tag data using the git executable * Reads the tag data using the git executable
* *
* @access private * @access private
*/ */
private function ReadDataGit() private function ReadDataGit()
{ {
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '-t'; $args[] = '-t';
$args[] = $this->GetHash(); $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->GetHash()); $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;
} }
} }
   
/** /**
* ReadDataRaw * ReadDataRaw
* *
* Reads the tag data using the raw git object * Reads the tag data using the raw git object
* *
* @access private * @access private
*/ */
private function ReadDataRaw() private function ReadDataRaw()
{ {
$data = $this->GetProject()->GetObject($this->GetHash(), $type); $data = $this->GetProject()->GetObject($this->GetHash(), $type);
if ($type == GitPHP_Pack::OBJ_COMMIT) { if ($type == GitPHP_Pack::OBJ_COMMIT) {
/* light tag */ /* light tag */
$this->object = $this->GetProject()->GetCommit($this->GetHash()); $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;
} }
   
$lines = explode("\n", $data); $lines = explode("\n", $data);
   
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':
$objectData = $this->GetProject()->GetObject($objectHash); $objectData = $this->GetProject()->GetObject($objectHash);
$lines = explode("\n", $objectData); $lines = explode("\n", $objectData);
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;
} }
} }
   
/** /**
* 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->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$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()) return GitPHP_Commit::CompareAge($aObj, $bObj);
return 0;  
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