diff --git a/.gitignore b/.gitignore index abb9c75..61892c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/bin/* +!bin/.gitkeep /vendor /composer.lock /phpunit.xml diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json index 82a3072..0018a9a 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "symfony/process": "~2.4" }, "require-dev": { - "psr/log": "~1.0" + "psr/log": "~1.0", + "phpunit/phpunit": "~4.5" }, "suggest": { "psr/log": "Add some log" @@ -38,5 +39,8 @@ "branch-alias": { "dev-master": "1.0-dev" } + }, + "config": { + "bin-dir": "bin" } } diff --git a/src/Gitonomy/Git/Blob.php b/src/Gitonomy/Git/Blob.php index 2205d93..9e18103 100644 --- a/src/Gitonomy/Git/Blob.php +++ b/src/Gitonomy/Git/Blob.php @@ -20,89 +20,72 @@ class Blob { /** - * @var Repository + * @var BlobObject */ - protected $repository; + protected $object; /** * @var string */ - protected $hash; + protected $path; /** - * @var string - */ - protected $content; - - /** - * @var string + * @param Repository $repository Repository where the blob is located + * @param string $path Path of the blob */ - protected $mimetype; + public function __construct(BlobObject $object, $path) + { + $this->object = $object; + $this->path = $path; + } /** - * @param Repository $repository Repository where the blob is located - * @param string $hash Hash of the blob + * Returns path to the blob. + * + * @return string */ - public function __construct(Repository $repository, $hash) + public function getPath() { - $this->repository = $repository; - $this->hash = $hash; + return $this->path; } /** - * @return string + * @see Blob::getHash */ public function getHash() { - return $this->hash; + return $this->object->getHash(); } /** - * Returns content of the blob. - * - * @throws ProcessException Error occurred while getting content of blob + * @see Blob::getContent */ public function getContent() { - if (null === $this->content) { - $this->content = $this->repository->run('cat-file', array('-p', $this->hash)); - } - - return $this->content; + return $this->object->getContent(); } /** - * Determine the mimetype of the blob. - * - * @return string A mimetype + * @see Blob::getMimetype */ public function getMimetype() { - if (null === $this->mimetype) { - $finfo = new \finfo(FILEINFO_MIME); - $this->mimetype = $finfo->buffer($this->getContent()); - } - - return $this->mimetype; + return $this->object->getMimetype(); } /** - * Determines if file is binary. - * - * @return boolean + * @see Blob::isBinary */ public function isBinary() { - return !$this->isText(); + return $this->object->isBinary(); } /** - * Determines if file is text. - * - * @return boolean + * @see Blob::isText */ public function isText() { - return (bool) preg_match('#^text/|^application/xml#', $this->getMimetype()); + return $this->object->isText(); } } diff --git a/src/Gitonomy/Git/BlobObject.php b/src/Gitonomy/Git/BlobObject.php new file mode 100644 index 0000000..7ed1f99 --- /dev/null +++ b/src/Gitonomy/Git/BlobObject.php @@ -0,0 +1,116 @@ + + * (c) Julien DIDIER + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Gitonomy\Git; + +/** + * Representation of a Blob commit. + * + * @author Alexandre Salomé + */ +class BlobObject +{ + /** + * @var Repository + */ + protected $repository; + + /** + * @var string + */ + protected $hash; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $mimetype; + + /** + * @param Repository $repository Repository where the blob is located + * @param string $hash Hash of the blob + */ + public function __construct(Repository $repository, $hash) + { + $this->repository = $repository; + $this->hash = $hash; + } + + /** + * @return Repository + */ + public function getRepository() + { + return $this->repository; + } + + /** + * @return string + */ + public function getHash() + { + return $this->hash; + } + + /** + * Returns content of the blob. + * + * @throws ProcessException Error occurred while getting content of blob + */ + public function getContent() + { + if (null === $this->content) { + $this->content = $this->repository->run('cat-file', array('-p', $this->hash)); + } + + return $this->content; + } + + /** + * Determine the mimetype of the blob. + * + * @return string A mimetype + */ + public function getMimetype() + { + if (null === $this->mimetype) { + $finfo = new \finfo(FILEINFO_MIME); + $this->mimetype = $finfo->buffer($this->getContent()); + } + + return $this->mimetype; + } + + /** + * Determines if file is binary. + * + * @return boolean + */ + public function isBinary() + { + return !$this->isText(); + } + + /** + * Determines if file is text. + * + * @return boolean + */ + public function isText() + { + return (bool) preg_match('#^text/|^application/xml#', $this->getMimetype()); + } +} diff --git a/src/Gitonomy/Git/Commit.php b/src/Gitonomy/Git/Commit.php index aebf7db..f1bf873 100644 --- a/src/Gitonomy/Git/Commit.php +++ b/src/Gitonomy/Git/Commit.php @@ -353,7 +353,7 @@ private function getData($name) } if ($name === 'tree') { - $this->data['tree'] = $this->repository->getTree($this->getData('treeHash')); + $this->data['tree'] = $this->repository->getTree($this->getData('treeHash'), ''); return $this->data['tree']; } diff --git a/src/Gitonomy/Git/Repository.php b/src/Gitonomy/Git/Repository.php index aa0ac47..f7766f7 100644 --- a/src/Gitonomy/Git/Repository.php +++ b/src/Gitonomy/Git/Repository.php @@ -331,21 +331,64 @@ public function getCommit($hash) } /** - * Instanciates a tree object or fetches one from the cache. + * Fetches a Tree from repository. * - * @param string $hash A tree hash, with a length of 40 + * @param string $hash A tree hash, with a length of 40 + * @param string|null $path A path for the tree * * @return Tree */ - public function getTree($hash) + public function getTree($hash, $path = null) + { + if ($path === null) { + return $this->getTreeObject($hash); + } + + if (! isset($this->objects[$hash.':'.$path])) { + $this->objects[$hash.':'.$path] = new Tree($this->getTreeObject($hash), $path); + } + + return $this->objects[$hash.':'.$path]; + } + + /** + * Instanciates a tree object or fetches one from the cache. + * + * @param string $hash A tree hash, with a length of 40 + * @param string|null $path A path for the tree + * + * @return TreeObject + */ + public function getTreeObject($hash) { if (! isset($this->objects[$hash])) { - $this->objects[$hash] = new Tree($this, $hash); + $this->objects[$hash] = new TreeObject($this, $hash); } return $this->objects[$hash]; } + /** + * Fetches a Blob from repository. + * + * @param string $hash A blob hash, with a length of 40 + * @param string|null $path A path for the blob + * + * @return Blob + */ + public function getBlob($hash, $path = null) + { + if ($path === null) { + return $this->getBlobObject($hash); + } + + if (! isset($this->objects[$hash.':'.$path])) { + $this->objects[$hash.':'.$path] = new Blob($this->getBlobObject($hash), $path); + } + + return $this->objects[$hash.':'.$path]; + } + /** * Instanciates a blob object or fetches one from the cache. * @@ -353,15 +396,31 @@ public function getTree($hash) * * @return Blob */ - public function getBlob($hash) + public function getBlobObject($hash, $path = null) { if (! isset($this->objects[$hash])) { - $this->objects[$hash] = new Blob($this, $hash); + $this->objects[$hash] = new BlobObject($this, $hash); } return $this->objects[$hash]; } + + public function getResolved($entry, $path) + { + if ($entry instanceof BlobObject) { + return $this->getBlob($entry->getHash(), $path); + } elseif ($entry instanceof TreeObject) { + return $this->getTree($entry->getHash(), $path); + } + + throw new \RuntimeException(sprintf( + 'Unable to resolve object of type "%s" (path: %s).', + get_class($entry), + $path + )); + } + public function getBlame($revision, $file, $lineRange = null) { if (is_string($revision)) { diff --git a/src/Gitonomy/Git/Tree.php b/src/Gitonomy/Git/Tree.php index 3602e08..3f381b1 100644 --- a/src/Gitonomy/Git/Tree.php +++ b/src/Gitonomy/Git/Tree.php @@ -13,96 +13,94 @@ namespace Gitonomy\Git; use Gitonomy\Git\Exception\InvalidArgumentException; -use Gitonomy\Git\Exception\UnexpectedValueException; /** * @author Alexandre Salomé */ class Tree { - protected $repository; - protected $hash; - protected $isInitialized = false; + /** + * @var TreeObject + */ + protected $object; + + /** + * @var string + */ + protected $path; + + /** + * @var array + */ protected $entries; - public function __construct(Repository $repository, $hash) + public function __construct(TreeObject $object, $path) { - $this->repository = $repository; - $this->hash = $hash; + $this->object = $object; + $this->path = $path; } + /** + * Returns path to this tree entry. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @see Tree:getHash + */ public function getHash() { - return $this->hash; + return $this->object->getHash(); } - protected function initialize() + /** + * @see Tree::getEntries + */ + public function getEntries() { - if (true === $this->isInitialized) { - return; + if ($this->entries !== null) { + return $this->entries; } - $output = $this->repository->run('cat-file', array('-p', $this->hash)); - $parser = new Parser\TreeParser(); - $parser->parse($output); - + $entries = $this->object->getEntries(); $this->entries = array(); - foreach ($parser->entries as $entry) { - list($mode, $type, $hash, $name) = $entry; - if ($type == 'blob') { - $this->entries[$name] = array($mode, $this->repository->getBlob($hash)); - } elseif ($type == 'tree') { - $this->entries[$name] = array($mode, $this->repository->getTree($hash)); - } else { - $this->entries[$name] = array($mode, new CommitReference($hash)); - } + foreach ($entries as $name => $entry) { + $this->entries[$name] = array( + $entry[0], + $this->object->getRepository()->getResolved($entry[1], $this->path.'/'.$name) + ); } - $this->isInitialized = true; + return $this->entries; } /** - * @return array An associative array name => $object + * @see Tree::getEntry */ - public function getEntries() - { - $this->initialize(); - - return $this->entries; - } - public function getEntry($name) { - $this->initialize(); + $entries = $this->getEntries(); - if (!isset($this->entries[$name])) { + if (!isset($entries[$name])) { throw new InvalidArgumentException('No entry '.$name); } - return $this->entries[$name][1]; + return $entries[$name][1]; } + /** + * @see Tree::resolvePath($path) + */ public function resolvePath($path) { - if ($path == '') { - return $this; - } - - $path = preg_replace('#^/#', '', $path); - - $segments = explode('/', $path); - $element = $this; - foreach ($segments as $segment) { - if ($element instanceof Tree) { - $element = $element->getEntry($segment); - } elseif ($entry instanceof Blob) { - throw new InvalidArgumentException('Unresolvable path'); - } else { - throw new UnexpectedValueException('Unknow type of element'); - } - } + $object = $this->object->resolvePath($path); - return $element; + return $this->object->getRepository()->getResolved($object, $this->path.'/'.$path); } } diff --git a/src/Gitonomy/Git/TreeObject.php b/src/Gitonomy/Git/TreeObject.php new file mode 100644 index 0000000..bdd1f39 --- /dev/null +++ b/src/Gitonomy/Git/TreeObject.php @@ -0,0 +1,118 @@ + + * (c) Julien DIDIER + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Gitonomy\Git; + +use Gitonomy\Git\Exception\InvalidArgumentException; +use Gitonomy\Git\Exception\UnexpectedValueException; + +/** + * Represents the actual git tree object, not dependent of the current path. + * + * @author Alexandre Salomé + */ +class TreeObject +{ + protected $repository; + protected $hash; + protected $isInitialized = false; + protected $entries; + + public function __construct(Repository $repository, $hash) + { + $this->repository = $repository; + $this->hash = $hash; + } + + /** + * @return Repository + */ + public function getRepository() + { + return $this->repository; + } + + public function getHash() + { + return $this->hash; + } + + protected function initialize() + { + if (true === $this->isInitialized) { + return; + } + + $output = $this->repository->run('cat-file', array('-p', $this->hash)); + $parser = new Parser\TreeParser(); + $parser->parse($output); + + $this->entries = array(); + + foreach ($parser->entries as $entry) { + list($mode, $type, $hash, $name) = $entry; + if ($type == 'blob') { + $this->entries[$name] = array($mode, $this->repository->getBlob($hash)); + } elseif ($type == 'tree') { + $this->entries[$name] = array($mode, $this->repository->getTree($hash)); + } else { + $this->entries[$name] = array($mode, new CommitReference($hash)); + } + } + + $this->isInitialized = true; + } + + /** + * @return array An associative array name => $object + */ + public function getEntries() + { + $this->initialize(); + + return $this->entries; + } + + public function getEntry($name) + { + $this->initialize(); + + if (!isset($this->entries[$name])) { + throw new InvalidArgumentException('No entry '.$name); + } + + return $this->entries[$name][1]; + } + + public function resolvePath($path) + { + if ($path == '') { + return $this; + } + + $path = preg_replace('#^/#', '', $path); + + $segments = explode('/', $path); + $element = $this; + foreach ($segments as $segment) { + if ($element instanceof TreeObject) { + $element = $element->getEntry($segment); + } elseif ($element instanceof BlobObject) { + throw new InvalidArgumentException('Unresolvable path'); + } else { + throw new UnexpectedValueException('Unknow type of element'); + } + } + + return $element; + } +} diff --git a/tests/Gitonomy/Git/Tests/AbstractTest.php b/tests/Gitonomy/Git/Tests/AbstractTest.php index 47c20c5..0592b79 100644 --- a/tests/Gitonomy/Git/Tests/AbstractTest.php +++ b/tests/Gitonomy/Git/Tests/AbstractTest.php @@ -75,7 +75,11 @@ public static function provideEmpty() public static function createFoobarRepository($bare = true) { if (null === self::$localRepository) { - self::$localRepository = Admin::cloneTo(self::createTempDir(), self::REPOSITORY_URL, $bare, self::getOptions()); + if (is_dir('/tmp/g_s')) { + self::$localRepository = new Repository('/tmp/g_s'); + } else { + self::$localRepository = Admin::cloneTo('/tmp/g_s', self::REPOSITORY_URL, $bare, self::getOptions()); + } } $repository = self::$localRepository->cloneTo(self::createTempDir(), $bare, self::getOptions());