Load ref list using raw git files
Load ref list using raw git files

<?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 */
$headCommit = $this->GetCommit($regs[1]); $headCommit = $this->GetCommit($regs[1]);
if ($headCommit) { if ($headCommit) {
$this->head = $headCommit->GetHash(); $this->head = $headCommit->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
*/ */
public function ReadRefList() protected function ReadRefList()
{ {
$this->readRefs = true; $this->readRefs = true;
   
  if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
  $this->ReadRefListGit();
  } else {
  $this->ReadRefListRaw();
  }
  }
   
  /**
  * ReadRefListGit
  *
  * Reads the list of refs for this project using the git executable
  *
  * @access private
  */
  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
  *
  * Reads the list of refs for this project using the raw git files
  *
  * @access private
  */
  private function ReadRefListRaw()
  {
  $pathlen = strlen($this->GetPath()) + 1;
   
  // read loose heads
  $heads = $this->ListDir($this->GetPath() . '/refs/heads');
  for ($i = 0; $i < count($heads); $i++) {
  $key = trim(substr($heads[$i], $pathlen), "/\\");
   
  if (isset($this->heads[$key])) {
  continue;
  }
   
  $hash = trim(file_get_contents($heads[$i]));
  if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
  $head = substr($key, strlen('refs/heads/'));
  $this->heads[$key] = new GitPHP_Head($this, $head, $hash);
  }
  }
   
  // read loose tags
  $tags = $this->ListDir($this->GetPath() . '/refs/tags');
  for ($i = 0; $i < count($tags); $i++) {
  $key = trim(substr($tags[$i], $pathlen), "/\\");
   
  if (isset($this->tags[$key])) {
  continue;
  }
   
  $hash = trim(file_get_contents($tags[$i]));
  if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
  $tag = substr($key, strlen('refs/tags/'));
  $this->tags[$key] = $this->LoadTag($tag, $hash);
  }
  }
   
  // check packed refs
  if (file_exists($this->GetPath() . '/packed-refs')) {
  $packedRefs = explode("\n", file_get_contents($this->GetPath() . '/packed-refs'));
   
  $lastRef = null;
  foreach ($packedRefs as $ref) {
   
  if (preg_match('/^\^([0-9A-Fa-f]{40})$/', $ref, $regs)) {
  // dereference of previous ref
  if (($lastRef != null) && ($lastRef instanceof GitPHP_Tag)) {
  $derefCommit = $this->GetCommit($regs[1]);
  if ($derefCommit) {
  $lastRef->SetCommit($derefCommit);
  }
  }
  }
   
  $lastRef = null;
   
  if (preg_match('/^([0-9A-Fa-f]{40}) refs\/(tags|heads)\/(.+)$/', $ref, $regs)) {
  // standard tag/head
  $key = 'refs/' . $regs[2] . '/' . $regs[3];
  if ($regs[2] == 'tags') {
  if (!isset($this->tags[$key])) {
  $lastRef = $this->LoadTag($regs[3], $regs[1]);
  $this->tags[$key] = $lastRef;
  }
  } else if ($regs[2] == 'heads') {
  if (!isset($this->heads[$key])) {
  $this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
  }
  }
  }
  }
  }
  }
   
  /**
  * ListDir
  *
  * Recurses into a directory and lists files inside
  *
  * @access private
  * @param string $dir directory
  * @return array array of filenames
  */
  private function ListDir($dir)
  {
  $files = array();
  if ($dh = opendir($dir)) {
  while (($file = readdir($dh)) !== false) {
  if (($file == '.') || ($file == '..')) {
  continue;
  }
  $fullFile = $dir . '/' . $file;
  if (is_dir($fullFile)) {
  $subFiles = $this->ListDir($fullFile);
  if (count($subFiles) > 0) {
  $files = array_merge($files, $subFiles);
  }
  } else {
  $files[] = $fullFile;
  }
  }
  }
  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;
   
$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;
   
$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 public
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of hashes * @return array array of hashes
*/ */
public function GetLogHash($hash, $count = 50, $skip = 0) public function GetLogHash($hash, $count = 50, $skip = 0)
{ {
return $this->RevList($hash, $count, $skip); return $this->RevList($hash, $count, $skip);
} }
   
/** /**
* GetLog * GetLog
* *
* Gets log entries as an array of commit objects * Gets log entries as an array of commit objects
* *
* @access public * @access public
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of commit objects * @return array array of commit objects
*/ */
public function GetLog($hash, $count = 50, $skip = 0) public function GetLog($hash, $count = 50, $skip = 0)
{ {
$log = $this->GetLogHash($hash, $count, $skip); $log = $this->GetLogHash($hash, $count, $skip);
$len = count($log); $len = count($log);
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$log[$i] = $this->GetCommit($log[$i]); $log[$i] = $this->GetCommit($log[$i]);
} }
return $log; return $log;
} }
   
/** /**
* GetBlob * GetBlob
* *
* Gets a blob from this project * Gets a blob from this project
* *
* @access public * @access public
* @param string $hash blob hash * @param string $hash blob hash
*/ */
public function GetBlob($hash) public function GetBlob($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|blob|' . $hash; $cacheKey = 'project|' . $this->project . '|blob|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Blob($this, $hash); return new GitPHP_Blob($this, $hash);
} }
   
/** /**
* GetTree * GetTree
* *
* Gets a tree from this project * Gets a tree from this project
* *
* @access public * @access public
* @param string $hash tree hash * @param string $hash tree hash
*/ */
public function GetTree($hash) public function GetTree($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|tree|' . $hash; $cacheKey = 'project|' . $this->project . '|tree|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Tree($this, $hash); return new GitPHP_Tree($this, $hash);
} }
   
/** /**
* SearchCommit * SearchCommit
* *
* Gets a list of commits with commit messages matching the given pattern * Gets a list of commits with commit messages matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--grep=\'' . $pattern . '\''; $args[] = '--grep=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchAuthor * SearchAuthor
* *
* Gets a list of commits with authors matching the given pattern * Gets a list of commits with authors matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--author=\'' . $pattern . '\''; $args[] = '--author=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchCommitter * SearchCommitter
* *
* Gets a list of commits with committers matching the given pattern * Gets a list of commits with committers matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--committer=\'' . $pattern . '\''; $args[] = '--committer=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* RevList * RevList
* *
* Common code for using rev-list command * Common code for using rev-list command
* *
* @access private * @access private
* @param string $hash hash to list from * @param string $hash hash to list from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @param array $args args to give to rev-list * @param array $args args to give to rev-list
* @return array array of hashes * @return array array of hashes
*/ */
private function RevList($hash, $count = 50, $skip = 0, $args = array()) private function RevList($hash, $count = 50, $skip = 0, $args = array())
{ {
if ($count < 1) if ($count < 1)
return; return;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$canSkip = true; $canSkip = true;
if ($skip > 0) if ($skip > 0)
$canSkip = $exe->CanSkip(); $canSkip = $exe->CanSkip();
   
if ($canSkip) { if ($canSkip) {
$args[] = '--max-count=' . $count; $args[] = '--max-count=' . $count;
if ($skip > 0) { if ($skip > 0) {
$args[] = '--skip=' . $skip; $args[] = '--skip=' . $skip;
} }
} else { } else {
$args[] = '--max-count=' . ($count + $skip); $args[] = '--max-count=' . ($count + $skip);
} }
   
$args[] = $hash; $args[] = $hash;
   
$revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args)); $revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args));
   
if (!$revlist[count($revlist)-1]) { if (!$revlist[count($revlist)-1]) {
/* the last newline creates a null entry */ /* the last newline creates a null entry */
array_splice($revlist, -1, 1); array_splice($revlist, -1, 1);
} }
   
if (($skip > 0) && (!$exe->CanSkip())) { if (($skip > 0) && (!$exe->CanSkip())) {
return array_slice($revlist, $skip, $count); return array_slice($revlist, $skip, $count);
} }
   
return $revlist; return $revlist;
} }
   
/** /**
* GetEpoch * GetEpoch
* *
* Gets this project's epoch * Gets this project's epoch
* (time of last change) * (time of last change)
* *
* @access public * @access public
* @return integer timestamp * @return integer timestamp
*/ */
public function GetEpoch() public function GetEpoch()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return $this->epoch; return $this->epoch;
} }
   
/** /**
* GetAge * GetAge
* *
* Gets this project's age * Gets this project's age
* (time since most recent change) * (time since most recent change)
* *
* @access public * @access public
* @return integer age * @return integer age
*/ */
public function GetAge() public function GetAge()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return time() - $this->epoch; return time() - $this->epoch;
} }
   
/** /**
* ReadEpoch * ReadEpoch
* *
* Reads this project's epoch * Reads this project's epoch
* (timestamp of most recent change) * (timestamp of most recent change)
* *
* @access private * @access private
*/ */
private function ReadEpoch() private function ReadEpoch()
{ {
$this->epochRead = true; $this->epochRead = true;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$args = array(); $args = array();
$args[] = '--format="%(committer)"'; $args[] = '--format="%(committer)"';
$args[] = '--sort=-committerdate'; $args[] = '--sort=-committerdate';
$args[] = '--count=1'; $args[] = '--count=1';
$args[] = 'refs/heads'; $args[] = 'refs/heads';
   
$epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args)); $epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args));
   
if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) { if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) {
$this->epoch = $regs[1]; $this->epoch = $regs[1];
} }
   
unset($exe); unset($exe);
} }
   
/** /**
* 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;
} }
   
} }
   
comments