diff options
Diffstat (limited to '3rdparty/granite/git/object/index.php')
-rw-r--r-- | 3rdparty/granite/git/object/index.php | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/3rdparty/granite/git/object/index.php b/3rdparty/granite/git/object/index.php new file mode 100644 index 00000000000..239706d4efd --- /dev/null +++ b/3rdparty/granite/git/object/index.php @@ -0,0 +1,210 @@ +<?php +/** + * Index - provides an 'index' object for packfile indexes + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Index represents a packfile index + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ +class Index +{ + const INDEX_MAGIC = "\377tOc"; + + /** + * The full path to the packfile index + */ + private $path; + /** + * The offset at which the fanout begins, version 2+ indexes have a 2-byte header + */ + private $offset = 8; + /** + * The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to + * total 24 bytes, version 2+ stores offsets separately + */ + private $size = 20; + /** + * The version of the index file format, versions 1 and 2 are in use and + * currently supported + */ + private $version; + + /** + * Fetches a raw Git object and parses the result + * + * @param string $path The path to the repository root + * @param string $packname The name of the packfile index to read + */ + public function __construct($path, $packname) + { + $this->path = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.idx'; + + $this->version = $this->_readVersion(); + if ($this->version !== 1 && $this->version !== 2) { + throw new UnexpectedValueException( + "Unsupported index version (version $version)" + ); + } + + if ($this->version == 1) { + $this->offset = 0; // Version 1 index has no header/version + $this->size = 24; // Offsets + SHA-1 ids are stored together + } + } + + /** + * Returns the offset of the object stored in the index + * + * @param string $sha The SHA-1 id of the object being requested + * + * @return int The offset of the object in the packfile + */ + public function find($sha) + { + $index = fopen($this->path, 'rb'); + $offset = false; // Offset for object in packfile not found by default + + // Read the fanout to skip to the start char in the sorted SHA-1 list + list($start, $after) = $this->_readFanout($index, $sha); + + if ($start == $after) { + fclose($index); + return false; // Object is apparently located in a 0-length section + } + + // Seek $offset + 255 4-byte fanout entries and read 256th entry + fseek($index, $this->offset + 4 * 255); + $totalObjects = $this->_uint32($index); + + // Look up the SHA-1 id of the object + // TODO: Binary search + fseek($index, $this->offset + 1024 + $this->size * $start); + for ($i = $start; $i < $after; $i++) { + if ($this->version == 1) { + $offset = $this->_uint32($index); + } + + $name = fread($index, 20); + if ($name == pack('H40', $sha)) { + break; // Found it + } + } + + if ($i == $after) { + fclose($index); + return false; // Scanned entire section, couldn't find it + } + + if ($this->version == 2) { + // Jump to the offset location and read it + fseek($index, 1032 + 24 * $totalObjects + 4 * $i); + $offset = $this->_uint32($index); + if ($offset & 0x80000000) { + // Offset is a 64-bit integer; packfile is larger than 2GB + fclose($index); + throw new UnexpectedValueException( + "Packfile larger than 2GB, currently unsupported" + ); + } + } + + fclose($index); + return $offset; + } + + /** + * Converts a binary string into a 32-bit unsigned integer + * + * @param handle $file Binary string to convert + * + * @return int Integer value + */ + private function _uint32($file) + { + $val = unpack('Nx', fread($file, 4)); + return $val['x']; + } + + /** + * Reads the fanout for a particular SHA-1 id + * + * Largely modified from Glip, with some reference to Grit - largely because I + * can't see how to re-implement this in PHP + * + * @param handle $file File handle to the index file + * @param string $sha The SHA-1 id to search for + * @param int $offset The offset at which the fanout begins + * + * @return array Array containing integer 'start' and + * 'past-the-end' locations + */ + private function _readFanout($file, $sha) + { + $sha = pack('H40', $sha); + fseek($file, $this->offset); + if ($sha{0} == "\00") { + /** + * First character is 0, read first fanout entry to provide + * 'past-the-end' location (since first fanout entry provides start + * point for '1'-prefixed SHA-1 ids) + */ + $start = 0; + fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in + $after = $this->_uint32($file); + } else { + /** + * Take ASCII value of first character, minus one to get the fanout + * position of the offset (minus one because the fanout does not + * contain an entry for "\00"), multiplied by four bytes per entry + */ + fseek($file, $this->offset + (ord($sha{0}) - 1) * 4); + $start = $this->_uint32($file); + $after = $this->_uint32($file); + } + + return array($start, $after); + } + + /** + * Returns the version number of the index file, or 1 if there is no version + * information + * + * @return int + */ + private function _readVersion() + { + $file = fopen($this->path, 'rb'); + $magic = fread($file, 4); + $version = $this->_uint32($file); + + if ($magic !== self::INDEX_MAGIC) { + $version = 1; + } + + fclose($file); + return $version; + } + +} |