{"action":"created","comment":{"url":"https://api.github.com/repos/puli/repository/pulls/comments/48695322","id":48695322,"diff_hunk":"@@ -0,0 +1,595 @@\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\\ChangeStream\\ChangeStream;\n+use Puli\\Repository\\Api\\Resource\\FilesystemResource;\n+use Puli\\Repository\\Api\\Resource\\PuliResource;\n+use Puli\\Repository\\Api\\ResourceCollection;\n+use Puli\\Repository\\Api\\ResourceNotFoundException;\n+use Puli\\Repository\\Api\\UnsupportedResourceException;\n+use Puli\\Repository\\Resource\\Collection\\ArrayResourceCollection;\n+use Puli\\Repository\\Resource\\DirectoryResource;\n+use Puli\\Repository\\Resource\\FileResource;\n+use Puli\\Repository\\Resource\\GenericResource;\n+use Puli\\Repository\\Resource\\LinkResource;\n+use RuntimeException;\n+use Webmozart\\Assert\\Assert;\n+use Webmozart\\Json\\JsonDecoder;\n+use Webmozart\\Json\\JsonEncoder;\n+use Webmozart\\PathUtil\\Path;\n+\n+/**\n+ * Abstract base for Path mapping repositories.\n+ *\n+ * @since  1.0\n+ *\n+ * @author Bernhard Schussek <bschussek@gmail.com>\n+ * @author Titouan Galopin <galopintitouan@gmail.com>\n+ */\n+abstract class AbstractJsonRepository extends AbstractEditableRepository\n+{\n+    /**\n+     * @var array\n+     */\n+    protected $json;\n+\n+    /**\n+     * @var string\n+     */\n+    protected $baseDirectory;\n+\n+    /**\n+     * @var string\n+     */\n+    private $path;\n+\n+    /**\n+     * @var string\n+     */\n+    private $schemaPath;\n+\n+    /**\n+     * @var JsonEncoder\n+     */\n+    private $encoder;\n+\n+    /**\n+     * Creates a new repository.\n+     *\n+     * @param string            $path          The path to the JSON file. If\n+     *                                         relative, it must be relative to\n+     *                                         the base directory.\n+     * @param string            $baseDirectory The base directory of the store.\n+     *                                         Paths inside that directory are\n+     *                                         stored as relative paths. Paths\n+     *                                         outside that directory are stored\n+     *                                         as absolute paths.\n+     * @param bool              $validateJson  Whether to validate the JSON file\n+     *                                         against the schema. Slow but\n+     *                                         spots problems.\n+     * @param ChangeStream|null $changeStream  If provided, the repository will\n+     *                                         append resource changes to this\n+     *                                         change stream.\n+     */\n+    public function __construct($path, $baseDirectory, $validateJson = false, ChangeStream $changeStream = null)\n+    {\n+        parent::__construct($changeStream);\n+\n+        $this->baseDirectory = $baseDirectory;\n+        $this->path = Path::makeAbsolute($path, $baseDirectory);\n+        $this->encoder = new JsonEncoder();\n+\n+        if ($validateJson) {\n+            $this->schemaPath = realpath(__DIR__.'/../res/schema/path-mappings-schema-1.0.json');\n+        }\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function add($path, $resource)\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $path = $this->sanitizePath($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->flush();\n+\n+            return;\n+        }\n+\n+        $this->ensureDirectoryExists(Path::getDirectory($path));\n+\n+        $this->addResource($path, $resource);\n+\n+        $this->flush();\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function get($path)\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $path = $this->sanitizePath($path);\n+        $references = $this->getReferencesForPath($path);\n+\n+        // Might be null, don't use isset()\n+        if (array_key_exists($path, $references)) {\n+            return $this->createResource($path, $references[$path]);\n+        }\n+\n+        throw ResourceNotFoundException::forPath($path);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function find($query, $language = 'glob')\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $this->validateSearchLanguage($language);\n+        $query = $this->sanitizePath($query);\n+        $results = $this->createResources($this->getReferencesForGlob($query));\n+\n+        ksort($results);\n+\n+        return new ArrayResourceCollection(array_values($results));\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function contains($query, $language = 'glob')\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $this->validateSearchLanguage($language);\n+        $query = $this->sanitizePath($query);\n+\n+        // Stop on the first result\n+        $results = $this->getReferencesForGlob($query, true);\n+\n+        return !empty($results);\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function remove($query, $language = 'glob')\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $this->validateSearchLanguage($language);\n+        $query = $this->sanitizePath($query);\n+\n+        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');\n+\n+        $removed = $this->removeReferences($query);\n+\n+        $this->flush();\n+\n+        return $removed;\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function clear()\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        // Subtract root which is not deleted\n+        $removed = count($this->getReferencesForRegex('/', '~.~')) - 1;\n+\n+        $this->json = array();\n+\n+        $this->flush();\n+\n+        return $removed;\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function listChildren($path)\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $path = $this->sanitizePath($path);\n+        $results = $this->createResources($this->getReferencesInDirectory($path));\n+\n+        if (empty($results)) {\n+            $pathResults = $this->getReferencesForPath($path);\n+\n+            if (empty($pathResults)) {\n+                throw ResourceNotFoundException::forPath($path);\n+            }\n+        }\n+\n+        ksort($results);\n+\n+        return new ArrayResourceCollection(array_values($results));\n+    }\n+\n+    /**\n+     * {@inheritdoc}\n+     */\n+    public function hasChildren($path)\n+    {\n+        if (null === $this->json) {\n+            $this->load();\n+        }\n+\n+        $path = $this->sanitizePath($path);\n+\n+        // Stop on the first result\n+        $results = $this->getReferencesInDirectory($path, true);\n+\n+        if (empty($results)) {\n+            $pathResults = $this->getReferencesForPath($path);\n+\n+            if (empty($pathResults)) {\n+                throw ResourceNotFoundException::forPath($path);\n+            }\n+\n+            return false;\n+        }\n+\n+        return true;\n+    }\n+\n+    /**\n+     * Inserts a path reference into the JSON file.\n+     *\n+     * The path reference can be:\n+     *\n+     *  * a link starting with `@`\n+     *  * an absolute filesystem path\n+     *\n+     * @param string      $path      The Puli path.\n+     * @param string|null $reference The path reference.\n+     */\n+    abstract protected function insertReference($path, $reference);\n+\n+    /**\n+     * Removes all path references matching the given glob from the JSON file.\n+     *\n+     * @param string $glob The glob for a list of Puli paths.\n+     */\n+    abstract protected function removeReferences($glob);\n+\n+    /**\n+     * Returns the references for a given Puli path.\n+     *\n+     * Each reference returned by this method can be:\n+     *\n+     *  * `null`\n+     *  * a link starting with `@`\n+     *  * an absolute filesystem path\n+     *\n+     * The result has either one entry or none, if no path was found. The key\n+     * of the single entry is the path passed to this method.\n+     *\n+     * @param string $path The Puli path.\n+     *\n+     * @return string[]|null[] A one-level array of references with Puli paths\n+     *                         as keys. The array has at most one entry.\n+     */\n+    abstract protected function getReferencesForPath($path);\n+\n+    /**\n+     * Returns the references matching a given Puli path glob.\n+     *\n+     * Each reference returned by this method can be:\n+     *\n+     *  * `null`\n+     *  * a link starting with `@`\n+     *  * an absolute filesystem path\n+     *\n+     * The keys of the returned array are Puli paths. Their order is undefined.\n+     *\n+     * @param string $glob        The glob.\n+     * @param bool   $stopOnFirst Whether to stop after finding a first result.\n+     *\n+     * @return string[]|null[] A one-level array of references with Puli paths\n+     *                         as keys.\n+     */\n+    abstract protected function getReferencesForGlob($glob, $stopOnFirst = false);\n+\n+    /**\n+     * Returns the references matching a given Puli path regular expression.\n+     *\n+     * Each reference returned by this method can be:\n+     *\n+     *  * `null`\n+     *  * a link starting with `@`\n+     *  * an absolute filesystem path\n+     *\n+     * The keys of the returned array are Puli paths. Their order is undefined.\n+     *\n+     * @param string $staticPrefix The static prefix of all Puli paths matching\n+     *                             the regular expression.\n+     * @param string $regex        The regular expression.\n+     * @param bool   $stopOnFirst  Whether to stop after finding a first result.\n+     *\n+     * @return string[]|null[] A one-level array of references with Puli paths\n+     *                         as keys.\n+     */\n+    abstract protected function getReferencesForRegex($staticPrefix, $regex, $stopOnFirst = false);\n+\n+    /**\n+     * Returns the references in a given Puli path.\n+     *\n+     * Each reference returned by this method can be:\n+     *\n+     *  * `null`\n+     *  * a link starting with `@`\n+     *  * an absolute filesystem path\n+     *\n+     * The keys of the returned array are Puli paths. Their order is undefined.\n+     *\n+     * @param string $path        The Puli path.\n+     * @param bool   $stopOnFirst Whether to stop after finding a first result.\n+     *\n+     * @return string[]|null[] A one-level array of references with Puli paths\n+     *                         as keys.\n+     */\n+    abstract protected function getReferencesInDirectory($path, $stopOnFirst = false);\n+\n+    /**\n+     * Adds a filesystem resource to the JSON file.\n+     *\n+     * @param string             $path     The Puli path.\n+     * @param FilesystemResource $resource The resource to add.\n+     */\n+    protected function addFilesystemResource($path, FilesystemResource $resource)\n+    {\n+        $resource->attachTo($this, $path);\n+\n+        $this->insertReference($path, $resource->getFilesystemPath());\n+\n+        $this->appendToChangeStream($resource);\n+    }\n+\n+    /**\n+     * Loads the JSON file.\n+     */\n+    protected function load()\n+    {\n+        $decoder = new JsonDecoder();\n+\n+        $this->json = file_exists($this->path)\n+            ? (array) $decoder->decodeFile($this->path, $this->schemaPath)\n+            : array();\n+\n+        if (isset($this->json['_order'])) {\n+            $this->json['_order'] = (array) $this->json['_order'];\n+\n+            foreach ($this->json['_order'] as $path => $entries) {\n+                foreach ($entries as $key => $entry) {\n+                    $this->json['_order'][$path][$key] = (array) $entry;\n+                }\n+            }\n+        }\n+\n+        // The root node always exists\n+        if (!isset($this->json['/'])) {\n+            $this->json['/'] = null;\n+        }\n+\n+        // Make sure the JSON is sorted in reverse order\n+        krsort($this->json);\n+    }\n+\n+    /**\n+     * Writes the JSON file.\n+     */\n+    protected function flush()\n+    {\n+        // The root node always exists\n+        if (!isset($this->json['/'])) {\n+            $this->json['/'] = null;\n+        }\n+\n+        // Always save in reverse order\n+        krsort($this->json);\n+\n+        // Comply to schema\n+        $json = (object) $this->json;\n+\n+        if (isset($json->{'_order'})) {\n+            foreach ($json->{'_order'} as $path => $entries) {\n+                foreach ($entries as $key => $entry) {\n+                    $json->{'_order'}[$path][$key] = (object) $entry;\n+                }\n+            }\n+\n+            $json->{'_order'} = (object) $json->{'_order'};\n+        }\n+\n+        $this->encoder->encodeFile($json, $this->path, $this->schemaPath);\n+    }\n+\n+    /**\n+     * Returns whether a reference contains a link.\n+     *\n+     * @param string $reference The reference.\n+     *\n+     * @return bool Whether the reference contains a link.\n+     */\n+    protected function isLinkReference($reference)\n+    {\n+        return isset($reference{0}) && '@' === $reference{0};\n+    }\n+\n+    /**\n+     * Returns whether a reference contains an absolute or relative filesystem\n+     * path.\n+     *\n+     * @param string $reference The reference.\n+     *\n+     * @return bool Whether the reference contains a filesystem path.\n+     */\n+    protected function isFilesystemReference($reference)\n+    {\n+        return null !== $reference && !$this->isLinkReference($reference);\n+    }\n+\n+    /**\n+     * Turns a reference into a resource.\n+     *\n+     * @param string      $path      The Puli path.\n+     * @param string|null $reference The reference.\n+     *\n+     * @return PuliResource The resource.\n+     */\n+    protected function createResource($path, $reference)\n+    {\n+        if (null === $reference) {\n+            $resource = new GenericResource();\n+        } elseif (isset($reference{0}) && '@' === $reference{0}) {\n+            $resource = new LinkResource(substr($reference, 1));\n+        } elseif (is_dir($reference)) {\n+            $resource = new DirectoryResource($reference);\n+        } elseif (is_file($reference)) {\n+            $resource = new FileResource($reference);\n+        } else {\n+            throw new RuntimeException(sprintf(\n+                'Trying to create a FilesystemResource on a non-existing file or directory \"%s\"',\n+                $reference\n+            ));\n+        }\n+\n+        $resource->attachTo($this, $path);\n+\n+        return $resource;\n+    }\n+\n+    /**\n+     * Turns a list of references into a list of resources.\n+     *\n+     * The references are expected to be in the format returned by\n+     * {@link getReferencesForPath()}, {@link getReferencesForGlob()} and\n+     * {@link getReferencesInDirectory()}.\n+     *\n+     * The result contains Puli paths as keys and {@link PuliResource}\n+     * implementations as values. The order of the results is undefined.\n+     *\n+     * @param string[]|null[] $references The references indexed by Puli paths.\n+     *\n+     * @return array\n+     */\n+    private function createResources(array $references)\n+    {\n+        foreach ($references as $path => $reference) {\n+            $references[$path] = $this->createResource($path, $reference);\n+        }\n+\n+        return $references;\n+    }\n+\n+    /**\n+     * Adds all ancestor directories of a path to the repository.\n+     *\n+     * @param string $path A Puli path.\n+     */\n+    private function ensureDirectoryExists($path)\n+    {\n+        if (array_key_exists($path, $this->json)) {\n+            return;\n+        }\n+\n+        // Recursively initialize parent directories\n+        if ('/' !== $path) {\n+            $this->ensureDirectoryExists(Path::getDirectory($path));\n+        }\n+\n+        $this->json[$path] = null;\n+    }\n+\n+    /**\n+     * Adds a resource to the repository.\n+     *\n+     * @param string                          $path     The Puli path to add the\n+     *                                                  resource at.\n+     * @param FilesystemResource|LinkResource $resource The resource to add.\n+     */\n+    private function addResource($path, $resource)\n+    {\n+        if (!$resource instanceof FilesystemResource && !$resource instanceof LinkResource) {\n+            throw new UnsupportedResourceException(sprintf(\n+                'The %s only supports adding FilesystemResource and '.\n+                'LinkedResource instances. Got: %s',\n+                // Get the short class name\n+                $this->getShortClassName(get_class($this)),\n+                $this->getShortClassName(get_class($resource))\n+            ));\n+        }\n+\n+        // Don't modify resources attached to other repositories\n+        if ($resource->isAttached()) {\n+            $resource = clone $resource;\n+        }\n+\n+        if ($resource instanceof LinkResource) {\n+            $resource->attachTo($this, $path);\n+\n+            $this->insertReference($path, '@'.$resource->getTargetPath());\n+\n+            $this->appendToChangeStream($resource);\n+        } else {\n+            // Extension point for the optimized repository\n+            $this->addFilesystemResource($path, $resource);\n+        }\n+    }\n+\n+    /**\n+     * Returns the short name of a fully-qualified class name.\n+     *\n+     * @param string $className The fully-qualified class name.\n+     *\n+     * @return string The short class name.\n+     */\n+    private function getShortClassName($className)","path":"src/AbstractJsonRepository.php","position":587,"original_position":587,"commit_id":"8a8446a6744384edb9b8c750fa183c0f633f3297","original_commit_id":"8a8446a6744384edb9b8c750fa183c0f633f3297","user":{"login":"webmozart","id":176399,"avatar_url":"https://avatars.githubusercontent.com/u/176399?v=3","gravatar_id":"","url":"https://api.github.com/users/webmozart","html_url":"https://github.com/webmozart","followers_url":"https://api.github.com/users/webmozart/followers","following_url":"https://api.github.com/users/webmozart/following{/other_user}","gists_url":"https://api.github.com/users/webmozart/gists{/gist_id}","starred_url":"https://api.github.com/users/webmozart/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webmozart/subscriptions","organizations_url":"https://api.github.com/users/webmozart/orgs","repos_url":"https://api.github.com/users/webmozart/repos","events_url":"https://api.github.com/users/webmozart/events{/privacy}","received_events_url":"https://api.github.com/users/webmozart/received_events","type":"User","site_admin":false},"body":"Yes","created_at":"2016-01-03T17:55:13Z","updated_at":"2016-01-03T17:55:13Z","html_url":"https://github.com/puli/repository/pull/79#discussion_r48695322","pull_request_url":"https://api.github.com/repos/puli/repository/pulls/79","_links":{"self":{"href":"https://api.github.com/repos/puli/repository/pulls/comments/48695322"},"html":{"href":"https://github.com/puli/repository/pull/79#discussion_r48695322"},"pull_request":{"href":"https://api.github.com/repos/puli/repository/pulls/79"}}},"pull_request":{"url":"https://api.github.com/repos/puli/repository/pulls/79","id":54832395,"html_url":"https://github.com/puli/repository/pull/79","diff_url":"https://github.com/puli/repository/pull/79.diff","patch_url":"https://github.com/puli/repository/pull/79.patch","issue_url":"https://api.github.com/repos/puli/repository/issues/79","number":79,"state":"open","locked":false,"title":"Refactored JsonRepository","user":{"login":"webmozart","id":176399,"avatar_url":"https://avatars.githubusercontent.com/u/176399?v=3","gravatar_id":"","url":"https://api.github.com/users/webmozart","html_url":"https://github.com/webmozart","followers_url":"https://api.github.com/users/webmozart/followers","following_url":"https://api.github.com/users/webmozart/following{/other_user}","gists_url":"https://api.github.com/users/webmozart/gists{/gist_id}","starred_url":"https://api.github.com/users/webmozart/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webmozart/subscriptions","organizations_url":"https://api.github.com/users/webmozart/orgs","repos_url":"https://api.github.com/users/webmozart/repos","events_url":"https://api.github.com/users/webmozart/events{/privacy}","received_events_url":"https://api.github.com/users/webmozart/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-12-30T21:50:35Z","updated_at":"2016-01-03T17:55:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c52a0a38486723e2a9671dfdcb9461aa9dc203f7","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/puli/repository/pulls/79/commits","review_comments_url":"https://api.github.com/repos/puli/repository/pulls/79/comments","review_comment_url":"https://api.github.com/repos/puli/repository/pulls/comments{/number}","comments_url":"https://api.github.com/repos/puli/repository/issues/79/comments","statuses_url":"https://api.github.com/repos/puli/repository/statuses/8a8446a6744384edb9b8c750fa183c0f633f3297","head":{"label":"webmozart:json-repo","ref":"json-repo","sha":"8a8446a6744384edb9b8c750fa183c0f633f3297","user":{"login":"webmozart","id":176399,"avatar_url":"https://avatars.githubusercontent.com/u/176399?v=3","gravatar_id":"","url":"https://api.github.com/users/webmozart","html_url":"https://github.com/webmozart","followers_url":"https://api.github.com/users/webmozart/followers","following_url":"https://api.github.com/users/webmozart/following{/other_user}","gists_url":"https://api.github.com/users/webmozart/gists{/gist_id}","starred_url":"https://api.github.com/users/webmozart/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webmozart/subscriptions","organizations_url":"https://api.github.com/users/webmozart/orgs","repos_url":"https://api.github.com/users/webmozart/repos","events_url":"https://api.github.com/users/webmozart/events{/privacy}","received_events_url":"https://api.github.com/users/webmozart/received_events","type":"User","site_admin":false},"repo":{"id":48175976,"name":"repository","full_name":"webmozart/repository","owner":{"login":"webmozart","id":176399,"avatar_url":"https://avatars.githubusercontent.com/u/176399?v=3","gravatar_id":"","url":"https://api.github.com/users/webmozart","html_url":"https://github.com/webmozart","followers_url":"https://api.github.com/users/webmozart/followers","following_url":"https://api.github.com/users/webmozart/following{/other_user}","gists_url":"https://api.github.com/users/webmozart/gists{/gist_id}","starred_url":"https://api.github.com/users/webmozart/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webmozart/subscriptions","organizations_url":"https://api.github.com/users/webmozart/orgs","repos_url":"https://api.github.com/users/webmozart/repos","events_url":"https://api.github.com/users/webmozart/events{/privacy}","received_events_url":"https://api.github.com/users/webmozart/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/webmozart/repository","description":"A filesystem-like repository for storing arbitrary resources.","fork":true,"url":"https://api.github.com/repos/webmozart/repository","forks_url":"https://api.github.com/repos/webmozart/repository/forks","keys_url":"https://api.github.com/repos/webmozart/repository/keys{/key_id}","collaborators_url":"https://api.github.com/repos/webmozart/repository/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/webmozart/repository/teams","hooks_url":"https://api.github.com/repos/webmozart/repository/hooks","issue_events_url":"https://api.github.com/repos/webmozart/repository/issues/events{/number}","events_url":"https://api.github.com/repos/webmozart/repository/events","assignees_url":"https://api.github.com/repos/webmozart/repository/assignees{/user}","branches_url":"https://api.github.com/repos/webmozart/repository/branches{/branch}","tags_url":"https://api.github.com/repos/webmozart/repository/tags","blobs_url":"https://api.github.com/repos/webmozart/repository/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/webmozart/repository/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/webmozart/repository/git/refs{/sha}","trees_url":"https://api.github.com/repos/webmozart/repository/git/trees{/sha}","statuses_url":"https://api.github.com/repos/webmozart/repository/statuses/{sha}","languages_url":"https://api.github.com/repos/webmozart/repository/languages","stargazers_url":"https://api.github.com/repos/webmozart/repository/stargazers","contributors_url":"https://api.github.com/repos/webmozart/repository/contributors","subscribers_url":"https://api.github.com/repos/webmozart/repository/subscribers","subscription_url":"https://api.github.com/repos/webmozart/repository/subscription","commits_url":"https://api.github.com/repos/webmozart/repository/commits{/sha}","git_commits_url":"https://api.github.com/repos/webmozart/repository/git/commits{/sha}","comments_url":"https://api.github.com/repos/webmozart/repository/comments{/number}","issue_comment_url":"https://api.github.com/repos/webmozart/repository/issues/comments{/number}","contents_url":"https://api.github.com/repos/webmozart/repository/contents/{+path}","compare_url":"https://api.github.com/repos/webmozart/repository/compare/{base}...{head}","merges_url":"https://api.github.com/repos/webmozart/repository/merges","archive_url":"https://api.github.com/repos/webmozart/repository/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/webmozart/repository/downloads","issues_url":"https://api.github.com/repos/webmozart/repository/issues{/number}","pulls_url":"https://api.github.com/repos/webmozart/repository/pulls{/number}","milestones_url":"https://api.github.com/repos/webmozart/repository/milestones{/number}","notifications_url":"https://api.github.com/repos/webmozart/repository/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/webmozart/repository/labels{/name}","releases_url":"https://api.github.com/repos/webmozart/repository/releases{/id}","created_at":"2015-12-17T13:30:42Z","updated_at":"2015-12-17T13:30:43Z","pushed_at":"2016-01-02T10:33:48Z","git_url":"git://github.com/webmozart/repository.git","ssh_url":"git@github.com:webmozart/repository.git","clone_url":"https://github.com/webmozart/repository.git","svn_url":"https://github.com/webmozart/repository","homepage":"http://puli.io","size":1397,"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":"9f5a0bf02eec57cfce3d17e9e605b733717e51bc","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":"2016-01-02T18:49:54Z","pushed_at":"2016-01-02T10:33:48Z","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":"http://puli.io","size":1335,"stargazers_count":400,"watchers_count":400,"language":"PHP","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":26,"mirror_url":null,"open_issues_count":3,"forks":26,"open_issues":3,"watchers":400,"default_branch":"1.0"}},"_links":{"self":{"href":"https://api.github.com/repos/puli/repository/pulls/79"},"html":{"href":"https://github.com/puli/repository/pull/79"},"issue":{"href":"https://api.github.com/repos/puli/repository/issues/79"},"comments":{"href":"https://api.github.com/repos/puli/repository/issues/79/comments"},"review_comments":{"href":"https://api.github.com/repos/puli/repository/pulls/79/comments"},"review_comment":{"href":"https://api.github.com/repos/puli/repository/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/puli/repository/pulls/79/commits"},"statuses":{"href":"https://api.github.com/repos/puli/repository/statuses/8a8446a6744384edb9b8c750fa183c0f633f3297"}}}}