Ignore index files without a packfile, but not the opposite
Ignore index files without a packfile, but not the opposite

<?php <?php
/** /**
* Handles loading data from raw git objects * Handles loading data from raw git objects
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2012 Christopher Han * @copyright Copyright (c) 2012 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_GitObjectLoader class GitPHP_GitObjectLoader
{ {
/** /**
* The project * The project
* *
* @var GitPHP_Project * @var GitPHP_Project
*/ */
protected $project; protected $project;
   
/** /**
* The list of packs * The list of packs
* *
* @var GitPHP_Pack[] * @var GitPHP_Pack[]
*/ */
protected $packs = array(); protected $packs = array();
   
/** /**
* Whether packs have been read * Whether packs have been read
* *
* @var boolean * @var boolean
*/ */
protected $packsRead = false; protected $packsRead = false;
   
/** /**
* Constructor * Constructor
* *
* @param GitPHP_Project $project project * @param GitPHP_Project $project project
*/ */
public function __construct($project) public function __construct($project)
{ {
if (!$project) if (!$project)
throw new Exception('Project is required'); throw new Exception('Project is required');
   
$this->project = $project; $this->project = $project;
} }
   
/** /**
* Gets the project * Gets the project
* *
* @return GitPHP_Project project * @return GitPHP_Project project
*/ */
public function GetProject() public function GetProject()
{ {
return $this->project; return $this->project;
} }
   
/** /**
* Gets the raw content of an object * Gets the raw content of an object
* *
* @param string $hash object hash * @param string $hash object hash
* @param int $type returns the object type * @param int $type returns the object type
* @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->project->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2); $path = $this->project->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);
if (preg_match('/^([A-Za-z]+) /', $header, $typestr)) { if (preg_match('/^([A-Za-z]+) /', $header, $typestr)) {
switch ($typestr[1]) { switch ($typestr[1]) {
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;
} }
   
/** /**
* Read the list of packs in the repository * Read the list of packs in the repository
*/ */
private function ReadPacks() private function ReadPacks()
{ {
$dh = opendir($this->project->GetPath() . '/objects/pack'); $dh = opendir($this->project->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})\.pack$/', $file, $regs)) {
$this->packs[] = new GitPHP_Pack($this->project, $regs[1], $this); $this->packs[] = new GitPHP_Pack($this->project, $regs[1], $this);
} }
} }
} }
$this->packsRead = true; $this->packsRead = true;
} }
   
/** /**
* Ensures a hash prefix is unique * Ensures a hash prefix is unique
* *
* @param string $hash full hash * @param string $hash full hash
* @param string $prefix abbreviated hash prefix * @param string $prefix abbreviated hash prefix
*/ */
public function EnsureUniqueHash($hash, $prefix) public function EnsureUniqueHash($hash, $prefix)
{ {
if (empty($hash) || empty($prefix)) if (empty($hash) || empty($prefix))
return null; return null;
   
if (!(preg_match('/[0-9A-Fa-f]{40}/', $hash))) if (!(preg_match('/[0-9A-Fa-f]{40}/', $hash)))
return $hash; return $hash;
   
if (preg_match('/[0-9A-Fa-f]{40}/', $prefix)) if (preg_match('/[0-9A-Fa-f]{40}/', $prefix))
return $prefix; return $prefix;
   
$hashMap = array(); $hashMap = array();
   
$matches = $this->FindHashObjects($prefix); $matches = $this->FindHashObjects($prefix);
foreach ($matches as $matchingHash) { foreach ($matches as $matchingHash) {
$hashMap[$matchingHash] = 1; $hashMap[$matchingHash] = 1;
} }
   
if (!$this->packsRead) { if (!$this->packsRead) {
$this->ReadPacks(); $this->ReadPacks();
} }
   
foreach ($this->packs as $pack) { foreach ($this->packs as $pack) {
$matches = $pack->FindHashes($prefix); $matches = $pack->FindHashes($prefix);
foreach ($matches as $matchingHash) { foreach ($matches as $matchingHash) {
$hashMap[$matchingHash] = 1; $hashMap[$matchingHash] = 1;
} }
} }
   
if (count($hashMap) == 0) { if (count($hashMap) == 0) {
return $hash; return $hash;
} }
   
if (count($hashMap) == 1) { if (count($hashMap) == 1) {
return $prefix; return $prefix;
} }
   
for ($len = strlen($prefix)+1; $len < 40; $len++) { for ($len = strlen($prefix)+1; $len < 40; $len++) {
$prefix = substr($hash, 0, $len); $prefix = substr($hash, 0, $len);
   
foreach (array_keys($hashMap) as $matchingHash) { foreach (array_keys($hashMap) as $matchingHash) {
if (substr_compare($matchingHash, $prefix, 0, $len) !== 0) { if (substr_compare($matchingHash, $prefix, 0, $len) !== 0) {
unset($hashMap[$matchingHash]); unset($hashMap[$matchingHash]);
} }
} }
   
if (count($hashMap) == 1) { if (count($hashMap) == 1) {
return $prefix; return $prefix;
} }
} }
   
return $hash; return $hash;
} }
   
/** /**
* Expands an abbreviated hash to the full hash * Expands an abbreviated hash to the full hash
* *
* @param string $abbrevHash abbreviated hash * @param string $abbrevHash abbreviated hash
* @return string full hash * @return string full hash
*/ */
public function ExpandHash($abbrevHash) public function ExpandHash($abbrevHash)
{ {
if (!(preg_match('/[0-9A-Fa-f]{4,39}/', $abbrevHash))) { if (!(preg_match('/[0-9A-Fa-f]{4,39}/', $abbrevHash))) {
return $abbrevHash; return $abbrevHash;
} }
   
$matches = $this->FindHashObjects($abbrevHash); $matches = $this->FindHashObjects($abbrevHash);
   
if (!$this->packsRead) { if (!$this->packsRead) {
$this->ReadPacks(); $this->ReadPacks();
} }
   
foreach ($this->packs as $pack) { foreach ($this->packs as $pack) {
$matches = array_merge($matches, $pack->FindHashes($abbrevHash)); $matches = array_merge($matches, $pack->FindHashes($abbrevHash));
} }
   
$matches = array_unique($matches); $matches = array_unique($matches);
   
if (count($matches) < 1) if (count($matches) < 1)
return $abbrevHash; return $abbrevHash;
   
if (count($matches) > 1) { if (count($matches) > 1) {
throw new GitPHP_AmbiguousHashException($abbrevHash); throw new GitPHP_AmbiguousHashException($abbrevHash);
} }
   
return $matches[0]; return $matches[0];
} }
   
/** /**
* Finds loose hash files matching a given prefix * Finds loose hash files matching a given prefix
* *
* @param string $prefix hash prefix * @param string $prefix hash prefix
* @return string[] array of hashes * @return string[] array of hashes
*/ */
private function FindHashObjects($prefix) private function FindHashObjects($prefix)
{ {
$matches = array(); $matches = array();
if (empty($prefix)) { if (empty($prefix)) {
return $matches; return $matches;
} }
   
$subdir = substr($prefix, 0, 2); $subdir = substr($prefix, 0, 2);
$fulldir = $this->project->GetPath() . '/objects/' . $subdir; $fulldir = $this->project->GetPath() . '/objects/' . $subdir;
if (!is_dir($fulldir)) { if (!is_dir($fulldir)) {
return $matches; return $matches;
} }
   
$prefixlen = strlen($prefix); $prefixlen = strlen($prefix);
$dh = opendir($fulldir); $dh = opendir($fulldir);
if ($dh !== false) { if ($dh !== false) {
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
$fullhash = $subdir . $file; $fullhash = $subdir . $file;
if (substr_compare($fullhash, $prefix, 0, $prefixlen) === 0) { if (substr_compare($fullhash, $prefix, 0, $prefixlen) === 0) {
$matches[] = $fullhash; $matches[] = $fullhash;
} }
} }
} }
return $matches; return $matches;
} }
   
} }
   
comments