{"action":"created","comment":{"url":"https://api.github.com/repos/puli/repository/pulls/comments/33264095","id":33264095,"diff_hunk":"@@ -0,0 +1,365 @@\n+<?php\n+\n+/*\n+ * This file is part of the puli/repository package.\n+ *\n+ * (c) Bernhard Schussek <bschussek@gmail.com>\n+ *\n+ * For the full copyright and license information, please view the LICENSE\n+ * file that was distributed with this source code.\n+ */\n+\n+namespace Puli\\Repository;\n+\n+use Puli\\Repository\\Api\\EditableRepository;\n+use Puli\\Repository\\Api\\Resource\\FilesystemResource;\n+use Puli\\Repository\\Api\\ResourceCollection;\n+use Puli\\Repository\\Resource\\Collection\\FilesystemResourceCollection;\n+use Puli\\Repository\\Api\\ResourceNotFoundException;\n+use Puli\\Repository\\Api\\UnsupportedLanguageException;\n+use Puli\\Repository\\Api\\UnsupportedResourceException;\n+use Puli\\Repository\\Resource\\DirectoryResource;\n+use Puli\\Repository\\Resource\\GenericFilesystemResource;\n+use Puli\\Repository\\Resource\\GenericResource;\n+use Webmozart\\Assert\\Assert;\n+use Webmozart\\Glob\\Iterator\\GlobFilterIterator;\n+use Webmozart\\Glob\\Iterator\\RegexFilterIterator;\n+use Webmozart\\KeyValueStore\\Api\\KeyValueStore;\n+use Webmozart\\PathUtil\\Path;\n+\n+/**\n+ * An optimized path mapping resource repository.\n+ * When a resource is added, all its children are resolved\n+ * and getting them is much faster.\n+ *\n+ * Resources can be added with the method {@link add()}:\n+ *\n+ * ```php\n+ * use Puli\\Repository\\OptimizedPathMappingRepository;\n+ *\n+ * $repo = new OptimizedPathMappingRepository();\n+ * $repo->add('/css', new DirectoryResource('/path/to/project/res/css'));\n+ * ```\n+ *\n+ * This repository only supports instances of FilesystemResource.\n+ *\n+ * @since  1.0\n+ * @author Bernhard Schussek <bschussek@gmail.com>\n+ * @author Titouan Galopin <galopintitouan@gmail.com>\n+ */\n+class OptimizedPathMappingRepository implements EditableRepository\n+{\n+    /**\n+     * @var KeyValueStore\n+     */\n+    private $store;\n+\n+    /**\n+     * Creates a new repository.\n+     *\n+     * @param KeyValueStore $store The store of all the paths.\n+     */\n+    public function __construct(KeyValueStore $store)\n+    {\n+        $this->store = $store;\n+\n+        $this->clear();\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function get($path)\n+    {\n+        Assert::stringNotEmpty($path, 'The path must be a non-empty string. Got: %s');\n+        Assert::startsWith($path, '/', 'The path %s is not absolute.');\n+\n+        $path = Path::canonicalize($path);\n+\n+        if (! $this->store->exists($path)) {\n+            throw ResourceNotFoundException::forPath($path);\n+        }\n+\n+        return $this->store->get($path);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function find($query, $language = 'glob')\n+    {\n+        if ('glob' !== $language) {\n+            throw UnsupportedLanguageException::forLanguage($language);\n+        }\n+\n+        Assert::stringNotEmpty($query, 'The glob must be a non-empty string. Got: %s');\n+        Assert::startsWith($query, '/', 'The glob %s is not absolute.');\n+\n+        $query = Path::canonicalize($query);\n+        $resources = array();\n+\n+        if (false !== strpos($query, '*')) {\n+            $resources = $this->iteratorToCollection($this->getGlobIterator($query));\n+        } elseif ($this->store->exists($query)) {\n+            $resources = array($this->store->get($query));\n+        }\n+\n+        return new FilesystemResourceCollection($resources);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function contains($query, $language = 'glob')\n+    {\n+        if ('glob' !== $language) {\n+            throw UnsupportedLanguageException::forLanguage($language);\n+        }\n+\n+        Assert::stringNotEmpty($query, 'The glob must be a non-empty string. Got: %s');\n+        Assert::startsWith($query, '/', 'The glob %s is not absolute.');\n+\n+        $query = Path::canonicalize($query);\n+\n+        if (false !== strpos($query, '*')) {\n+            $iterator = $this->getGlobIterator($query);\n+            $iterator->rewind();\n+\n+            return $iterator->valid();\n+        }\n+\n+        return $this->store->exists($query);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function add($path, $resource)\n+    {\n+        Assert::stringNotEmpty($path, 'The path must be a non-empty string. Got: %s');\n+        Assert::startsWith($path, '/', 'The path %s is not absolute.');\n+\n+        $path = Path::canonicalize($path);\n+\n+        if ($resource instanceof ResourceCollection) {\n+            $this->ensureDirectoryExists($path);\n+\n+            foreach ($resource as $child) {\n+                $this->addResource($path.'/'.$child->getName(), $child);\n+            }\n+\n+            $this->sortStore();\n+\n+            return;\n+        }\n+\n+        if ($resource instanceof FilesystemResource) {\n+            $this->ensureDirectoryExists(Path::getDirectory($path));\n+            $this->addResource($path, $resource);\n+            $this->sortStore();\n+\n+            return;\n+        }\n+\n+        throw new UnsupportedResourceException(sprintf(\n+            'The passed resource must be a FilesystemResource or a ResourceCollection. Got: %s',\n+            is_object($resource) ? get_class($resource) : gettype($resource)\n+        ));\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function remove($query, $language = 'glob')\n+    {\n+        $resources = $this->find($query, $language);\n+        $nbOfResources = count($this->store->keys());\n+\n+        // Run the assertion after find(), so that we know that $query is valid\n+        Assert::notEq('', trim($query, '/'), 'The root directory cannot be removed.');\n+\n+        foreach ($resources as $resource) {\n+            $this->removeResource($resource);\n+        }\n+\n+        return $nbOfResources - count($this->store->keys());\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function clear()\n+    {\n+        $root = new GenericFilesystemResource('/');\n+        $root->attachTo($this, '/');\n+\n+        // Subtract root\n+        $removed = count($this->store->keys()) - 1;\n+\n+        $this->store->clear();\n+        $this->store->set('/', $root);\n+\n+        return $removed;\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function listChildren($path)\n+    {\n+        $iterator = $this->getChildIterator($this->get($path));\n+        $children = $this->iteratorToCollection($iterator);\n+\n+        return new FilesystemResourceCollection($children);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function hasChildren($path)\n+    {\n+        $iterator = $this->getChildIterator($this->get($path));\n+        $iterator->rewind();\n+\n+        return $iterator->valid();\n+    }\n+\n+\n+    /**\n+     * @param string $path\n+     * @param FilesystemResource $resource\n+     */\n+    private function addResource($path, FilesystemResource $resource)\n+    {\n+        // Don't modify resources attached to other repositories\n+        if ($resource->isAttached()) {\n+            $resource = clone $resource;\n+        }\n+\n+        // Read children before attaching the resource to this repository\n+        $children = $resource->listChildren();\n+\n+        $resource->attachTo($this, $path);\n+\n+        // Add the resource before adding its children, so that the array\n+        // stays sorted\n+        $this->store->set($path, $resource);\n+\n+        $basePath = '/' === $path ? $path : $path.'/';\n+\n+        foreach ($children as $name => $child) {\n+            $this->addResource($basePath.$name, $child);\n+        }\n+    }\n+\n+\n+    /**\n+     * @param FilesystemResource $resource\n+     */\n+    private function removeResource(FilesystemResource $resource)\n+    {\n+        $path = $resource->getPath();\n+\n+        // Ignore non-existing resources\n+        if (! $this->store->exists($path)) {\n+            return;\n+        }\n+\n+        // Recursively register directory contents\n+        foreach ($this->iteratorToCollection($this->getChildIterator($resource)) as $child) {\n+            $this->removeResource($child);\n+        }\n+\n+        $this->store->remove($path);\n+\n+        // Detach from locator\n+        $resource->detach($this);\n+    }\n+\n+    /**\n+     * Returns an iterator for the children of a resource.\n+     *\n+     * @param FilesystemResource $resource The resource.\n+     *\n+     * @return RegexFilterIterator|Resource[] The iterator.\n+     */\n+    private function getChildIterator(FilesystemResource $resource)\n+    {\n+        $staticPrefix = rtrim($resource->getPath(), '/').'/';\n+        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';\n+\n+        return new RegexFilterIterator(\n+            $regExp,\n+            $staticPrefix,\n+            new \\ArrayIterator($this->store->keys())\n+        );\n+    }\n+\n+    /**\n+     * Returns an iterator for a glob.\n+     *\n+     * @param string $glob The glob.\n+     *\n+     * @return GlobFilterIterator|Resource[] The iterator.\n+     */\n+    private function getGlobIterator($glob)\n+    {\n+        return new GlobFilterIterator(\n+            $glob,\n+            new \\ArrayIterator($this->store->keys())\n+        );\n+    }\n+\n+    /**\n+     * Recursively creates a directory for a path.\n+     *\n+     * @param string $path A directory path.\n+     * @return DirectoryResource The created resource\n+     */\n+    private function ensureDirectoryExists($path)\n+    {\n+        if (! $this->store->exists($path)) {\n+            // Recursively initialize parent directories\n+            if ($path !== '/') {\n+                $this->ensureDirectoryExists(Path::getDirectory($path));\n+            }\n+\n+            $resource = new GenericFilesystemResource($path);\n+            $resource->attachTo($this, $path);\n+\n+            $this->store->set($path, $resource);\n+\n+            return;\n+        }\n+    }\n+\n+    /**\n+     * Transform an iterator of paths into a collection of resources\n+     *\n+     * @param \\Iterator $iterator\n+     * @return FilesystemResourceCollection\n+     */\n+    private function iteratorToCollection(\\Iterator $iterator)\n+    {\n+        $paths = iterator_to_array($iterator);\n+        $resources = array_values($this->store->getMultiple($paths));","path":"src/OptimizedPathMappingRepository.php","position":345,"original_position":345,"commit_id":"a25287982a5d2c64c0ed507532110af89bd614b8","original_commit_id":"a25287982a5d2c64c0ed507532110af89bd614b8","user":{"login":"tgalopin","id":1651494,"avatar_url":"https://avatars.githubusercontent.com/u/1651494?v=3","gravatar_id":"","url":"https://api.github.com/users/tgalopin","html_url":"https://github.com/tgalopin","followers_url":"https://api.github.com/users/tgalopin/followers","following_url":"https://api.github.com/users/tgalopin/following{/other_user}","gists_url":"https://api.github.com/users/tgalopin/gists{/gist_id}","starred_url":"https://api.github.com/users/tgalopin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgalopin/subscriptions","organizations_url":"https://api.github.com/users/tgalopin/orgs","repos_url":"https://api.github.com/users/tgalopin/repos","events_url":"https://api.github.com/users/tgalopin/events{/privacy}","received_events_url":"https://api.github.com/users/tgalopin/received_events","type":"User","site_admin":false},"body":"I have an iterator of path yes. Why?","created_at":"2015-06-25T14:59:25Z","updated_at":"2015-06-25T14:59:25Z","html_url":"https://github.com/puli/repository/pull/27#discussion_r33264095","pull_request_url":"https://api.github.com/repos/puli/repository/pulls/27","_links":{"self":{"href":"https://api.github.com/repos/puli/repository/pulls/comments/33264095"},"html":{"href":"https://github.com/puli/repository/pull/27#discussion_r33264095"},"pull_request":{"href":"https://api.github.com/repos/puli/repository/pulls/27"}}},"pull_request":{"url":"https://api.github.com/repos/puli/repository/pulls/27","id":38541168,"html_url":"https://github.com/puli/repository/pull/27","diff_url":"https://github.com/puli/repository/pull/27.diff","patch_url":"https://github.com/puli/repository/pull/27.patch","issue_url":"https://api.github.com/repos/puli/repository/issues/27","number":27,"state":"open","locked":false,"title":"[WIP] Implement PathMappingRepository","user":{"login":"tgalopin","id":1651494,"avatar_url":"https://avatars.githubusercontent.com/u/1651494?v=3","gravatar_id":"","url":"https://api.github.com/users/tgalopin","html_url":"https://github.com/tgalopin","followers_url":"https://api.github.com/users/tgalopin/followers","following_url":"https://api.github.com/users/tgalopin/following{/other_user}","gists_url":"https://api.github.com/users/tgalopin/gists{/gist_id}","starred_url":"https://api.github.com/users/tgalopin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgalopin/subscriptions","organizations_url":"https://api.github.com/users/tgalopin/orgs","repos_url":"https://api.github.com/users/tgalopin/repos","events_url":"https://api.github.com/users/tgalopin/events{/privacy}","received_events_url":"https://api.github.com/users/tgalopin/received_events","type":"User","site_admin":false},"body":"As explained in puli/issues#80, I implemented a PathMappingRepository. This pull-request is not ready for the moment, please do not merge it now. It's just a place for dicussion about the code.\r\n\r\nI want to explain what I did and what I plan to do to discuss, especially with @webmozart :) .\r\n\r\n1. Create the OptimizedPathMappingRepository ✓\r\n---------------------------------------------------------------------------------------\r\n\r\nhttps://github.com/puli/repository/compare/1.0...tgalopin:path-mapping?diff=unified&name=path-mapping#diff-55bbc453f8c7d643ba2cd09cc280c00f\r\n\r\nAs discussed with @webmozart, optimized and not optimized versions of the PathMappingRepository are completely different in code, it makes sense to separate them. I created the optimized version only for the moment.\r\n\r\n2. Create a GenericFilesystemResource ✓\r\n---------------------------------------------------------------------------------------\r\n\r\nhttps://github.com/puli/repository/compare/1.0...tgalopin:path-mapping?diff=unified&name=path-mapping#diff-2ba3bf2e348a04a316173aba204de2d9\r\n\r\nIt's the best solution I found to store a virtual directory in the repository. Here is the problem:\r\n\r\n- I want to store only FilesystemResource instances in the repository ;\r\n- However, when someone add the path `/webmozart/puli/file1`, the paths `/webmozart` and  `/webmozart/puli` should be created if they does not exist. But they don't have to represent a real file on the system ;\r\n\r\nSo I created this class to represent a generic, virtual filesystem resource usable in the repository while not having any real file / directory behind it. Perhaps are there better ways or just a better name for this class, please tell me if you think of something :) .\r\n\r\n3. Externalize test resources creation in order to be able to override it ✓\r\n---------------------------------------------------------------------------------------\r\n\r\nhttps://github.com/puli/repository/compare/1.0...tgalopin:path-mapping?diff=unified&name=path-mapping#diff-59ad75cfaee83a692af80d3634224473\r\n\r\nAs my repository only handle FilesystemResource instances, I needed to create specific testing objects. I had to externalize this creation, so I edited the abstract tests in order to do so.\r\n\r\n4. Add specific tests for the optimized version ✓\r\n---------------------------------------------------------------------------------------\r\n\r\nhttps://github.com/puli/repository/blob/a25287982a5d2c64c0ed507532110af89bd614b8/tests/OptimizedPathMappingRepositoryTest.php\r\n\r\nTest that the adding of a single directory does add all the children of this directory in the right place.\r\n\r\nDo you think of other specific test I could have forgotten?\r\n\r\n4. Create the PathMapping repository ✗(TODO)\r\n---------------------------------------------------------------------------------------\r\n\r\nStill to do, the creation of the classic version of the PathMapping repository.\r\n\r\n5. Test the PathMapping repository ✗(TODO)\r\n---------------------------------------------------------------------------------------\r\n\r\nStill to do, the tests of the classic version of the PathMapping repository.","created_at":"2015-06-25T12:20:53Z","updated_at":"2015-06-25T14:59:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5971a4d307a2d8f81e18227cad1fe77994006613","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/puli/repository/pulls/27/commits","review_comments_url":"https://api.github.com/repos/puli/repository/pulls/27/comments","review_comment_url":"https://api.github.com/repos/puli/repository/pulls/comments{/number}","comments_url":"https://api.github.com/repos/puli/repository/issues/27/comments","statuses_url":"https://api.github.com/repos/puli/repository/statuses/a25287982a5d2c64c0ed507532110af89bd614b8","head":{"label":"tgalopin:path-mapping","ref":"path-mapping","sha":"a25287982a5d2c64c0ed507532110af89bd614b8","user":{"login":"tgalopin","id":1651494,"avatar_url":"https://avatars.githubusercontent.com/u/1651494?v=3","gravatar_id":"","url":"https://api.github.com/users/tgalopin","html_url":"https://github.com/tgalopin","followers_url":"https://api.github.com/users/tgalopin/followers","following_url":"https://api.github.com/users/tgalopin/following{/other_user}","gists_url":"https://api.github.com/users/tgalopin/gists{/gist_id}","starred_url":"https://api.github.com/users/tgalopin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgalopin/subscriptions","organizations_url":"https://api.github.com/users/tgalopin/orgs","repos_url":"https://api.github.com/users/tgalopin/repos","events_url":"https://api.github.com/users/tgalopin/events{/privacy}","received_events_url":"https://api.github.com/users/tgalopin/received_events","type":"User","site_admin":false},"repo":{"id":37941499,"name":"repository","full_name":"tgalopin/repository","owner":{"login":"tgalopin","id":1651494,"avatar_url":"https://avatars.githubusercontent.com/u/1651494?v=3","gravatar_id":"","url":"https://api.github.com/users/tgalopin","html_url":"https://github.com/tgalopin","followers_url":"https://api.github.com/users/tgalopin/followers","following_url":"https://api.github.com/users/tgalopin/following{/other_user}","gists_url":"https://api.github.com/users/tgalopin/gists{/gist_id}","starred_url":"https://api.github.com/users/tgalopin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tgalopin/subscriptions","organizations_url":"https://api.github.com/users/tgalopin/orgs","repos_url":"https://api.github.com/users/tgalopin/repos","events_url":"https://api.github.com/users/tgalopin/events{/privacy}","received_events_url":"https://api.github.com/users/tgalopin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tgalopin/repository","description":"A filesystem-like repository for storing arbitrary resources.","fork":true,"url":"https://api.github.com/repos/tgalopin/repository","forks_url":"https://api.github.com/repos/tgalopin/repository/forks","keys_url":"https://api.github.com/repos/tgalopin/repository/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tgalopin/repository/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tgalopin/repository/teams","hooks_url":"https://api.github.com/repos/tgalopin/repository/hooks","issue_events_url":"https://api.github.com/repos/tgalopin/repository/issues/events{/number}","events_url":"https://api.github.com/repos/tgalopin/repository/events","assignees_url":"https://api.github.com/repos/tgalopin/repository/assignees{/user}","branches_url":"https://api.github.com/repos/tgalopin/repository/branches{/branch}","tags_url":"https://api.github.com/repos/tgalopin/repository/tags","blobs_url":"https://api.github.com/repos/tgalopin/repository/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tgalopin/repository/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tgalopin/repository/git/refs{/sha}","trees_url":"https://api.github.com/repos/tgalopin/repository/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tgalopin/repository/statuses/{sha}","languages_url":"https://api.github.com/repos/tgalopin/repository/languages","stargazers_url":"https://api.github.com/repos/tgalopin/repository/stargazers","contributors_url":"https://api.github.com/repos/tgalopin/repository/contributors","subscribers_url":"https://api.github.com/repos/tgalopin/repository/subscribers","subscription_url":"https://api.github.com/repos/tgalopin/repository/subscription","commits_url":"https://api.github.com/repos/tgalopin/repository/commits{/sha}","git_commits_url":"https://api.github.com/repos/tgalopin/repository/git/commits{/sha}","comments_url":"https://api.github.com/repos/tgalopin/repository/comments{/number}","issue_comment_url":"https://api.github.com/repos/tgalopin/repository/issues/comments{/number}","contents_url":"https://api.github.com/repos/tgalopin/repository/contents/{+path}","compare_url":"https://api.github.com/repos/tgalopin/repository/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tgalopin/repository/merges","archive_url":"https://api.github.com/repos/tgalopin/repository/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tgalopin/repository/downloads","issues_url":"https://api.github.com/repos/tgalopin/repository/issues{/number}","pulls_url":"https://api.github.com/repos/tgalopin/repository/pulls{/number}","milestones_url":"https://api.github.com/repos/tgalopin/repository/milestones{/number}","notifications_url":"https://api.github.com/repos/tgalopin/repository/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tgalopin/repository/labels{/name}","releases_url":"https://api.github.com/repos/tgalopin/repository/releases{/id}","created_at":"2015-06-23T19:55:13Z","updated_at":"2015-06-23T19:55:14Z","pushed_at":"2015-06-25T12:47:44Z","git_url":"git://github.com/tgalopin/repository.git","ssh_url":"git@github.com:tgalopin/repository.git","clone_url":"https://github.com/tgalopin/repository.git","svn_url":"https://github.com/tgalopin/repository","homepage":"","size":2245,"stargazers_count":0,"watchers_count":0,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"1.0"}},"base":{"label":"puli:1.0","ref":"1.0","sha":"f453e561412cdc8a4fb3e135cfcf3d616d8dc704","user":{"login":"puli","id":9706065,"avatar_url":"https://avatars.githubusercontent.com/u/9706065?v=3","gravatar_id":"","url":"https://api.github.com/users/puli","html_url":"https://github.com/puli","followers_url":"https://api.github.com/users/puli/followers","following_url":"https://api.github.com/users/puli/following{/other_user}","gists_url":"https://api.github.com/users/puli/gists{/gist_id}","starred_url":"https://api.github.com/users/puli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/puli/subscriptions","organizations_url":"https://api.github.com/users/puli/orgs","repos_url":"https://api.github.com/users/puli/repos","events_url":"https://api.github.com/users/puli/events{/privacy}","received_events_url":"https://api.github.com/users/puli/received_events","type":"Organization","site_admin":false},"repo":{"id":16406011,"name":"repository","full_name":"puli/repository","owner":{"login":"puli","id":9706065,"avatar_url":"https://avatars.githubusercontent.com/u/9706065?v=3","gravatar_id":"","url":"https://api.github.com/users/puli","html_url":"https://github.com/puli","followers_url":"https://api.github.com/users/puli/followers","following_url":"https://api.github.com/users/puli/following{/other_user}","gists_url":"https://api.github.com/users/puli/gists{/gist_id}","starred_url":"https://api.github.com/users/puli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/puli/subscriptions","organizations_url":"https://api.github.com/users/puli/orgs","repos_url":"https://api.github.com/users/puli/repos","events_url":"https://api.github.com/users/puli/events{/privacy}","received_events_url":"https://api.github.com/users/puli/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/puli/repository","description":"A filesystem-like repository for storing arbitrary resources.","fork":false,"url":"https://api.github.com/repos/puli/repository","forks_url":"https://api.github.com/repos/puli/repository/forks","keys_url":"https://api.github.com/repos/puli/repository/keys{/key_id}","collaborators_url":"https://api.github.com/repos/puli/repository/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/puli/repository/teams","hooks_url":"https://api.github.com/repos/puli/repository/hooks","issue_events_url":"https://api.github.com/repos/puli/repository/issues/events{/number}","events_url":"https://api.github.com/repos/puli/repository/events","assignees_url":"https://api.github.com/repos/puli/repository/assignees{/user}","branches_url":"https://api.github.com/repos/puli/repository/branches{/branch}","tags_url":"https://api.github.com/repos/puli/repository/tags","blobs_url":"https://api.github.com/repos/puli/repository/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/puli/repository/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/puli/repository/git/refs{/sha}","trees_url":"https://api.github.com/repos/puli/repository/git/trees{/sha}","statuses_url":"https://api.github.com/repos/puli/repository/statuses/{sha}","languages_url":"https://api.github.com/repos/puli/repository/languages","stargazers_url":"https://api.github.com/repos/puli/repository/stargazers","contributors_url":"https://api.github.com/repos/puli/repository/contributors","subscribers_url":"https://api.github.com/repos/puli/repository/subscribers","subscription_url":"https://api.github.com/repos/puli/repository/subscription","commits_url":"https://api.github.com/repos/puli/repository/commits{/sha}","git_commits_url":"https://api.github.com/repos/puli/repository/git/commits{/sha}","comments_url":"https://api.github.com/repos/puli/repository/comments{/number}","issue_comment_url":"https://api.github.com/repos/puli/repository/issues/comments{/number}","contents_url":"https://api.github.com/repos/puli/repository/contents/{+path}","compare_url":"https://api.github.com/repos/puli/repository/compare/{base}...{head}","merges_url":"https://api.github.com/repos/puli/repository/merges","archive_url":"https://api.github.com/repos/puli/repository/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/puli/repository/downloads","issues_url":"https://api.github.com/repos/puli/repository/issues{/number}","pulls_url":"https://api.github.com/repos/puli/repository/pulls{/number}","milestones_url":"https://api.github.com/repos/puli/repository/milestones{/number}","notifications_url":"https://api.github.com/repos/puli/repository/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/puli/repository/labels{/name}","releases_url":"https://api.github.com/repos/puli/repository/releases{/id}","created_at":"2014-01-31T11:14:13Z","updated_at":"2015-06-25T13:19:47Z","pushed_at":"2015-06-25T12:47:45Z","git_url":"git://github.com/puli/repository.git","ssh_url":"git@github.com:puli/repository.git","clone_url":"https://github.com/puli/repository.git","svn_url":"https://github.com/puli/repository","homepage":"","size":2245,"stargazers_count":307,"watchers_count":307,"language":"PHP","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":10,"mirror_url":null,"open_issues_count":3,"forks":10,"open_issues":3,"watchers":307,"default_branch":"1.0"}},"_links":{"self":{"href":"https://api.github.com/repos/puli/repository/pulls/27"},"html":{"href":"https://github.com/puli/repository/pull/27"},"issue":{"href":"https://api.github.com/repos/puli/repository/issues/27"},"comments":{"href":"https://api.github.com/repos/puli/repository/issues/27/comments"},"review_comments":{"href":"https://api.github.com/repos/puli/repository/pulls/27/comments"},"review_comment":{"href":"https://api.github.com/repos/puli/repository/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/puli/repository/pulls/27/commits"},"statuses":{"href":"https://api.github.com/repos/puli/repository/statuses/a25287982a5d2c64c0ed507532110af89bd614b8"}}}}