{"action":"created","comment":{"url":"https://api.github.com/repos/webgme/webgme/pulls/comments/40316849","id":40316849,"diff_hunk":"@@ -6,134 +6,281 @@\n 'use strict';\n \n var Q = require('q'),\n-    Core = requireJS('common/core/core'),\n+    BranchMonitor = require('./branchmonitor'),\n+    EventDispatcher = requireJS('common/EventDispatcher'),\n     STORAGE_CONSTANTS = requireJS('common/storage/constants'),\n     Storage = requireJS('common/storage/nodestorage');\n \n-function AddOnManager(webGMESessionId, mainLogger, gmeConfig) {\n-    var host = '127.0.0.1',\n-        self = this,\n-        logger = mainLogger.fork('AddOnManager'),\n-        storage = Storage.createStorage(host, webGMESessionId, logger, gmeConfig),\n-        runningAddOns = {};\n-\n-    function getAddOn(name) {\n-        var addOnPath = 'addon/' + name + '/' + name + '/' + name;\n-        if (runningAddOns.hasOwnProperty(name)) {\n-            throw new Error('AddOn has already started ' + name);\n+/**\n+ * Class for managing branch-monitors within a single project.\n+ * Branch-monitors are managing the add-ons within each branch.\n+ * @param {string} projectId\n+ * @param {object} mainLogger\n+ * @param {object} gmeConfig\n+ * @param {number} gmeConfig.addOn.monitorTimeout - Time to wait before stopping a monitor after only the monitor itself\n+ * is connected to the branch.\n+ * @constructor\n+ */\n+function AddOnManager(projectId, mainLogger, gmeConfig) {\n+    var self = this,\n+        host = '127.0.0.1',\n+        logger = mainLogger.fork('AddOnManager:' + projectId),\n+        initDeferred,\n+        closeDeferred;\n+\n+    EventDispatcher.call(self);\n+\n+    this.branchMonitors = {\n+        //branchName: {\n+        // connectionCnt: {number},\n+        // instance: {BranchMonitor},\n+        // stopTimeout: {\n+        //     id: {number},\n+        //     deferred: {Promise}\n+        // }\n+    };\n+\n+    this.project = null;\n+    this.storage = null;\n+    this.initRequested = false;\n+    this.closeRequested = false;\n+\n+    this.inStoppedAndStarted = {};\n+\n+    function removeMonitor(branchName) {\n+        var remainingMonitors,\n+            stoppedAndStart;\n+\n+        delete self.branchMonitors[branchName];\n+        remainingMonitors = Object.keys(self.branchMonitors);\n+        stoppedAndStart = Object.keys(self.inStoppedAndStarted);\n+\n+\n+        logger.debug('Removing monitor [' + branchName + '] - remaining, stopAndStarted',\n+            remainingMonitors, stoppedAndStart);\n+        if (remainingMonitors.length === 0 && stoppedAndStart.length === 0) {\n+            self.dispatchEvent('NO_MONITORS');\n         }\n-        logger.debug('requireJS addOn from path: ' + addOnPath);\n-        return requireJS(addOnPath);\n     }\n \n-    this.initialize = function (callback) {\n-        storage.open(function (networkStatus) {\n-            if (networkStatus === STORAGE_CONSTANTS.CONNECTED) {\n-                callback(null);\n-            } else {\n-                //FIXME: How to handle disconnect/reconnect for addOns???\n-                callback(new Error('Problems connecting to the webgme server, network state: ' + networkState));\n-            }\n-        });\n-    };\n+    /**\n+     * Opens up the storage based on the webGMESessionId and opens and sets the project.\n+     *\n+     * @param {string} webGMESessionId\n+     * @param {function} [callback]\n+     * @returns {Promise}\n+     */\n+    this.initialize = function (webGMESessionId, callback) {\n+        if (self.initRequested === false) {\n+            initDeferred = Q.defer();\n+            self.initRequested = true;\n \n-    this.startNewAddOn = function (addOnName, projectId, branchName, userId, callback) {\n-        var deferred = Q.defer(),\n-            AddOn,\n-            addOn,\n-            startParams;\n+            self.storage = Storage.createStorage(host, webGMESessionId, logger, gmeConfig);\n+            self.storage.open(function (networkStatus) {\n+                if (networkStatus === STORAGE_CONSTANTS.CONNECTED) {\n+                    self.storage.openProject(projectId, function (err, project, branches, rights) {\n+                        if (err) {\n+                            self.close()\n+                                .finally(function () {\n+                                    initDeferred.reject(err);\n+                                });\n \n-        AddOn = getAddOn(addOnName);\n+                            return;\n+                        }\n \n-        addOn = new AddOn(Core, storage, gmeConfig, logger.fork('addOn_' + addOnName), userId);\n+                        self.project = project;\n \n-        startParams = {\n-            projectId: projectId,\n-            branchName: branchName,\n-            logger: logger.fork(addOnName)\n-        };\n+                        if (rights.write === false) {\n+                            logger.warn('AddOnManager for project [' + projectId +\n+                                '] initialized without write access.');\n+                        }\n \n-        addOn.start(startParams, function (err) {\n-            if (err) {\n-                deferred.reject(err instanceof Error ? err : new Error(err));\n-            } else {\n-                runningAddOns[addOnName] = addOn;\n-                deferred.resolve();\n-            }\n-        });\n+                        self.initialized = true;\n+                        initDeferred.resolve();\n+                    });\n+                } else {\n+                    logger.error(new Error('Connection problems' + networkStatus));\n+                    initDeferred.reject(new Error('Problems connecting to the webgme server, network status: ' +\n+                        networkStatus));\n+                }\n+            });\n+        }\n \n-        return deferred.promise.nodeify(callback);\n+        return initDeferred.promise.nodeify(callback);\n     };\n \n-    this.queryAddOn = function (addOnName, parameters, callback) {\n-        var deferred = Q.defer(),\n-            addOn;\n+    this.monitorBranch = function (webGMESessionId, branchName, callback) {\n+        var monitor = self.branchMonitors[branchName],\n+            deferred = Q.defer();\n \n-        if (!addOnName) {\n-            //TODO: This assumes a single running addOn.\n-            addOnName = Object.keys(runningAddOns)[0];\n-            logger.debug('No addOnName given for query picked one randomly', addOnName);\n-        }\n+        function startNewMonitor(initCnt) {\n+            monitor = {\n+                connectionCnt: initCnt,\n+                stopTimeout: null,\n+                instance: new BranchMonitor(webGMESessionId, self.storage, self.project, branchName, logger, gmeConfig)\n+            };\n \n-        addOn = runningAddOns[addOnName];\n+            self.branchMonitors[branchName] = monitor;\n+            logger.debug('monitorBranch [' + branchName + '] - starting new monitor');\n+            monitor.instance.start()\n+                .then(deferred.resolve)\n+                .catch(deferred.reject);\n+        }\n \n-        if (!addOn) {\n-            deferred.reject(new Error('The addOn is not running'));\n-        } else {\n-            addOn.query(parameters, function (err, message) {\n-                if (err) {\n-                    deferred.reject(err instanceof Error ? err : new Error(err));\n+        if (monitor) {\n+            monitor.connectionCnt += 1;\n+            logger.debug('monitorBranch [' + branchName + '] - connection counter [prev, now]',\n+                monitor.connectionCnt - 1, monitor.connectionCnt);\n+            if (monitor.connectionCnt === 1) {\n+                // The client disconnected before the monitor itself connected.\n+                // Register on start deferred and stop the monitor.\n+                logger.debug('The client disconnected before the monitor itself trigger connect.');\n+                monitor.instance.start()\n+                    .then(function () {\n+                        return monitor.instance.stop();\n+                    })\n+                    .then(function () {\n+                        removeMonitor(branchName);\n+                        deferred.resolve();\n+                    })\n+                    .catch(deferred.reject);\n+            } else if (monitor.connectionCnt === 2) {\n+                if (monitor.stopTimeout) {\n+                    // The monitor is in stopping stage and\n+                    // the timeout had not been triggered yet.\n+                    clearTimeout(monitor.stopTimeout.id);\n+                    logger.debug('monitorBranch [' + branchName + '] - setTimeout cleared');\n+                    monitor.stopTimeout.deferred.resolve(monitor.connectionCnt);\n+                    monitor.stopTimeout = null;\n+                    monitor.instance.start()\n+                        .then(deferred.resolve)\n+                        .catch(deferred.reject);\n+                } else if (monitor.instance.stopRequested === true) {\n+                    // [Rare case]\n+                    // The monitor is in stopping stage and the timeout has been triggered.\n+                    // We cannot simply remove the monitor before it has closed the branch,\n+                    // therefore we need to register on the stop.promise and start a new monitor\n+                    // once it has resolved.\n+                    //\n+                    // (For bookkeeping of the addOnManagers it is not the same since they create a new storage.)\n+                    self.inStoppedAndStarted[branchName] = true;\n+                    logger.debug('monitorBranch [' + branchName + '] - inStoppedAndStarted!');\n+                    monitor.instance.stop()\n+                        .then(function () {\n+                            delete self.inStoppedAndStarted[branchName];","path":"src/addon/addonmanager.js","position":225,"original_position":225,"commit_id":"11311d6d8cbfcd9f0947f9b523cb3c9736fb917e","original_commit_id":"11311d6d8cbfcd9f0947f9b523cb3c9736fb917e","user":{"login":"kecso","id":1371489,"avatar_url":"https://avatars.githubusercontent.com/u/1371489?v=3","gravatar_id":"","url":"https://api.github.com/users/kecso","html_url":"https://github.com/kecso","followers_url":"https://api.github.com/users/kecso/followers","following_url":"https://api.github.com/users/kecso/following{/other_user}","gists_url":"https://api.github.com/users/kecso/gists{/gist_id}","starred_url":"https://api.github.com/users/kecso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kecso/subscriptions","organizations_url":"https://api.github.com/users/kecso/orgs","repos_url":"https://api.github.com/users/kecso/repos","events_url":"https://api.github.com/users/kecso/events{/privacy}","received_events_url":"https://api.github.com/users/kecso/received_events","type":"User","site_admin":false},"body":"does this inStoppedandStarted has any real effect?\r\n- it seems that it can cause confusion\r\n- what if multiple watcher arrives during the stop period? (right now it seems we will have an error instead of starting all...)\r\n- if only the 'first' callback would try to start the new monitor and all the others would just connect, that could solve the confusion (and would give meaning to the flag as well)","created_at":"2015-09-24T13:16:35Z","updated_at":"2015-09-24T13:16:35Z","html_url":"https://github.com/webgme/webgme/pull/590#discussion_r40316849","pull_request_url":"https://api.github.com/repos/webgme/webgme/pulls/590","_links":{"self":{"href":"https://api.github.com/repos/webgme/webgme/pulls/comments/40316849"},"html":{"href":"https://github.com/webgme/webgme/pull/590#discussion_r40316849"},"pull_request":{"href":"https://api.github.com/repos/webgme/webgme/pulls/590"}}},"pull_request":{"url":"https://api.github.com/repos/webgme/webgme/pulls/590","id":45838725,"html_url":"https://github.com/webgme/webgme/pull/590","diff_url":"https://github.com/webgme/webgme/pull/590.diff","patch_url":"https://github.com/webgme/webgme/pull/590.patch","issue_url":"https://api.github.com/repos/webgme/webgme/issues/590","number":590,"state":"open","locked":false,"title":"AddOns closes #579","user":{"login":"pmeijer","id":6518904,"avatar_url":"https://avatars.githubusercontent.com/u/6518904?v=3","gravatar_id":"","url":"https://api.github.com/users/pmeijer","html_url":"https://github.com/pmeijer","followers_url":"https://api.github.com/users/pmeijer/followers","following_url":"https://api.github.com/users/pmeijer/following{/other_user}","gists_url":"https://api.github.com/users/pmeijer/gists{/gist_id}","starred_url":"https://api.github.com/users/pmeijer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pmeijer/subscriptions","organizations_url":"https://api.github.com/users/pmeijer/orgs","repos_url":"https://api.github.com/users/pmeijer/repos","events_url":"https://api.github.com/users/pmeijer/events{/privacy}","received_events_url":"https://api.github.com/users/pmeijer/received_events","type":"User","site_admin":false},"body":"Above add-on specifics this branch contains:\r\n\r\n- Notifications to clients when other sockets join the same branch-room.\r\n- Clear intervals/timeouts in ExecutorServer on stop.\r\n- Disconnect \"login\" sockets in common-storage tests.\r\n- Minor tweaks to tests.\r\n\r\nCloses #227\r\nCloses #467 ","created_at":"2015-09-23T22:16:07Z","updated_at":"2015-09-24T13:16:35Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ca88eb545a0078e9b424179b71ced2e71cee7830","assignee":{"login":"kecso","id":1371489,"avatar_url":"https://avatars.githubusercontent.com/u/1371489?v=3","gravatar_id":"","url":"https://api.github.com/users/kecso","html_url":"https://github.com/kecso","followers_url":"https://api.github.com/users/kecso/followers","following_url":"https://api.github.com/users/kecso/following{/other_user}","gists_url":"https://api.github.com/users/kecso/gists{/gist_id}","starred_url":"https://api.github.com/users/kecso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kecso/subscriptions","organizations_url":"https://api.github.com/users/kecso/orgs","repos_url":"https://api.github.com/users/kecso/repos","events_url":"https://api.github.com/users/kecso/events{/privacy}","received_events_url":"https://api.github.com/users/kecso/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/webgme/webgme/milestones/21","html_url":"https://github.com/webgme/webgme/milestones/v1.0.0","labels_url":"https://api.github.com/repos/webgme/webgme/milestones/21/labels","id":1281949,"number":21,"title":"v1.0.0","description":"","creator":{"login":"pmeijer","id":6518904,"avatar_url":"https://avatars.githubusercontent.com/u/6518904?v=3","gravatar_id":"","url":"https://api.github.com/users/pmeijer","html_url":"https://github.com/pmeijer","followers_url":"https://api.github.com/users/pmeijer/followers","following_url":"https://api.github.com/users/pmeijer/following{/other_user}","gists_url":"https://api.github.com/users/pmeijer/gists{/gist_id}","starred_url":"https://api.github.com/users/pmeijer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pmeijer/subscriptions","organizations_url":"https://api.github.com/users/pmeijer/orgs","repos_url":"https://api.github.com/users/pmeijer/repos","events_url":"https://api.github.com/users/pmeijer/events{/privacy}","received_events_url":"https://api.github.com/users/pmeijer/received_events","type":"User","site_admin":false},"open_issues":15,"closed_issues":72,"state":"open","created_at":"2015-08-31T14:17:29Z","updated_at":"2015-09-23T23:29:19Z","due_on":"2015-09-28T05:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/webgme/webgme/pulls/590/commits","review_comments_url":"https://api.github.com/repos/webgme/webgme/pulls/590/comments","review_comment_url":"https://api.github.com/repos/webgme/webgme/pulls/comments{/number}","comments_url":"https://api.github.com/repos/webgme/webgme/issues/590/comments","statuses_url":"https://api.github.com/repos/webgme/webgme/statuses/11311d6d8cbfcd9f0947f9b523cb3c9736fb917e","head":{"label":"webgme:issue/579_addon","ref":"issue/579_addon","sha":"11311d6d8cbfcd9f0947f9b523cb3c9736fb917e","user":{"login":"webgme","id":1107609,"avatar_url":"https://avatars.githubusercontent.com/u/1107609?v=3","gravatar_id":"","url":"https://api.github.com/users/webgme","html_url":"https://github.com/webgme","followers_url":"https://api.github.com/users/webgme/followers","following_url":"https://api.github.com/users/webgme/following{/other_user}","gists_url":"https://api.github.com/users/webgme/gists{/gist_id}","starred_url":"https://api.github.com/users/webgme/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webgme/subscriptions","organizations_url":"https://api.github.com/users/webgme/orgs","repos_url":"https://api.github.com/users/webgme/repos","events_url":"https://api.github.com/users/webgme/events{/privacy}","received_events_url":"https://api.github.com/users/webgme/received_events","type":"Organization","site_admin":false},"repo":{"id":2526173,"name":"webgme","full_name":"webgme/webgme","owner":{"login":"webgme","id":1107609,"avatar_url":"https://avatars.githubusercontent.com/u/1107609?v=3","gravatar_id":"","url":"https://api.github.com/users/webgme","html_url":"https://github.com/webgme","followers_url":"https://api.github.com/users/webgme/followers","following_url":"https://api.github.com/users/webgme/following{/other_user}","gists_url":"https://api.github.com/users/webgme/gists{/gist_id}","starred_url":"https://api.github.com/users/webgme/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webgme/subscriptions","organizations_url":"https://api.github.com/users/webgme/orgs","repos_url":"https://api.github.com/users/webgme/repos","events_url":"https://api.github.com/users/webgme/events{/privacy}","received_events_url":"https://api.github.com/users/webgme/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/webgme/webgme","description":"Web-based Generic Modeling Environment","fork":false,"url":"https://api.github.com/repos/webgme/webgme","forks_url":"https://api.github.com/repos/webgme/webgme/forks","keys_url":"https://api.github.com/repos/webgme/webgme/keys{/key_id}","collaborators_url":"https://api.github.com/repos/webgme/webgme/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/webgme/webgme/teams","hooks_url":"https://api.github.com/repos/webgme/webgme/hooks","issue_events_url":"https://api.github.com/repos/webgme/webgme/issues/events{/number}","events_url":"https://api.github.com/repos/webgme/webgme/events","assignees_url":"https://api.github.com/repos/webgme/webgme/assignees{/user}","branches_url":"https://api.github.com/repos/webgme/webgme/branches{/branch}","tags_url":"https://api.github.com/repos/webgme/webgme/tags","blobs_url":"https://api.github.com/repos/webgme/webgme/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/webgme/webgme/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/webgme/webgme/git/refs{/sha}","trees_url":"https://api.github.com/repos/webgme/webgme/git/trees{/sha}","statuses_url":"https://api.github.com/repos/webgme/webgme/statuses/{sha}","languages_url":"https://api.github.com/repos/webgme/webgme/languages","stargazers_url":"https://api.github.com/repos/webgme/webgme/stargazers","contributors_url":"https://api.github.com/repos/webgme/webgme/contributors","subscribers_url":"https://api.github.com/repos/webgme/webgme/subscribers","subscription_url":"https://api.github.com/repos/webgme/webgme/subscription","commits_url":"https://api.github.com/repos/webgme/webgme/commits{/sha}","git_commits_url":"https://api.github.com/repos/webgme/webgme/git/commits{/sha}","comments_url":"https://api.github.com/repos/webgme/webgme/comments{/number}","issue_comment_url":"https://api.github.com/repos/webgme/webgme/issues/comments{/number}","contents_url":"https://api.github.com/repos/webgme/webgme/contents/{+path}","compare_url":"https://api.github.com/repos/webgme/webgme/compare/{base}...{head}","merges_url":"https://api.github.com/repos/webgme/webgme/merges","archive_url":"https://api.github.com/repos/webgme/webgme/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/webgme/webgme/downloads","issues_url":"https://api.github.com/repos/webgme/webgme/issues{/number}","pulls_url":"https://api.github.com/repos/webgme/webgme/pulls{/number}","milestones_url":"https://api.github.com/repos/webgme/webgme/milestones{/number}","notifications_url":"https://api.github.com/repos/webgme/webgme/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/webgme/webgme/labels{/name}","releases_url":"https://api.github.com/repos/webgme/webgme/releases{/id}","created_at":"2011-10-06T14:01:08Z","updated_at":"2015-08-11T13:36:12Z","pushed_at":"2015-09-24T03:58:16Z","git_url":"git://github.com/webgme/webgme.git","ssh_url":"git@github.com:webgme/webgme.git","clone_url":"https://github.com/webgme/webgme.git","svn_url":"https://github.com/webgme/webgme","homepage":"http://webgme.org","size":140354,"stargazers_count":19,"watchers_count":19,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"open_issues_count":17,"forks":12,"open_issues":17,"watchers":19,"default_branch":"master"}},"base":{"label":"webgme:master","ref":"master","sha":"14664952d451efb622813a256ceb62e7bcf6b96d","user":{"login":"webgme","id":1107609,"avatar_url":"https://avatars.githubusercontent.com/u/1107609?v=3","gravatar_id":"","url":"https://api.github.com/users/webgme","html_url":"https://github.com/webgme","followers_url":"https://api.github.com/users/webgme/followers","following_url":"https://api.github.com/users/webgme/following{/other_user}","gists_url":"https://api.github.com/users/webgme/gists{/gist_id}","starred_url":"https://api.github.com/users/webgme/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webgme/subscriptions","organizations_url":"https://api.github.com/users/webgme/orgs","repos_url":"https://api.github.com/users/webgme/repos","events_url":"https://api.github.com/users/webgme/events{/privacy}","received_events_url":"https://api.github.com/users/webgme/received_events","type":"Organization","site_admin":false},"repo":{"id":2526173,"name":"webgme","full_name":"webgme/webgme","owner":{"login":"webgme","id":1107609,"avatar_url":"https://avatars.githubusercontent.com/u/1107609?v=3","gravatar_id":"","url":"https://api.github.com/users/webgme","html_url":"https://github.com/webgme","followers_url":"https://api.github.com/users/webgme/followers","following_url":"https://api.github.com/users/webgme/following{/other_user}","gists_url":"https://api.github.com/users/webgme/gists{/gist_id}","starred_url":"https://api.github.com/users/webgme/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/webgme/subscriptions","organizations_url":"https://api.github.com/users/webgme/orgs","repos_url":"https://api.github.com/users/webgme/repos","events_url":"https://api.github.com/users/webgme/events{/privacy}","received_events_url":"https://api.github.com/users/webgme/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/webgme/webgme","description":"Web-based Generic Modeling Environment","fork":false,"url":"https://api.github.com/repos/webgme/webgme","forks_url":"https://api.github.com/repos/webgme/webgme/forks","keys_url":"https://api.github.com/repos/webgme/webgme/keys{/key_id}","collaborators_url":"https://api.github.com/repos/webgme/webgme/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/webgme/webgme/teams","hooks_url":"https://api.github.com/repos/webgme/webgme/hooks","issue_events_url":"https://api.github.com/repos/webgme/webgme/issues/events{/number}","events_url":"https://api.github.com/repos/webgme/webgme/events","assignees_url":"https://api.github.com/repos/webgme/webgme/assignees{/user}","branches_url":"https://api.github.com/repos/webgme/webgme/branches{/branch}","tags_url":"https://api.github.com/repos/webgme/webgme/tags","blobs_url":"https://api.github.com/repos/webgme/webgme/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/webgme/webgme/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/webgme/webgme/git/refs{/sha}","trees_url":"https://api.github.com/repos/webgme/webgme/git/trees{/sha}","statuses_url":"https://api.github.com/repos/webgme/webgme/statuses/{sha}","languages_url":"https://api.github.com/repos/webgme/webgme/languages","stargazers_url":"https://api.github.com/repos/webgme/webgme/stargazers","contributors_url":"https://api.github.com/repos/webgme/webgme/contributors","subscribers_url":"https://api.github.com/repos/webgme/webgme/subscribers","subscription_url":"https://api.github.com/repos/webgme/webgme/subscription","commits_url":"https://api.github.com/repos/webgme/webgme/commits{/sha}","git_commits_url":"https://api.github.com/repos/webgme/webgme/git/commits{/sha}","comments_url":"https://api.github.com/repos/webgme/webgme/comments{/number}","issue_comment_url":"https://api.github.com/repos/webgme/webgme/issues/comments{/number}","contents_url":"https://api.github.com/repos/webgme/webgme/contents/{+path}","compare_url":"https://api.github.com/repos/webgme/webgme/compare/{base}...{head}","merges_url":"https://api.github.com/repos/webgme/webgme/merges","archive_url":"https://api.github.com/repos/webgme/webgme/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/webgme/webgme/downloads","issues_url":"https://api.github.com/repos/webgme/webgme/issues{/number}","pulls_url":"https://api.github.com/repos/webgme/webgme/pulls{/number}","milestones_url":"https://api.github.com/repos/webgme/webgme/milestones{/number}","notifications_url":"https://api.github.com/repos/webgme/webgme/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/webgme/webgme/labels{/name}","releases_url":"https://api.github.com/repos/webgme/webgme/releases{/id}","created_at":"2011-10-06T14:01:08Z","updated_at":"2015-08-11T13:36:12Z","pushed_at":"2015-09-24T03:58:16Z","git_url":"git://github.com/webgme/webgme.git","ssh_url":"git@github.com:webgme/webgme.git","clone_url":"https://github.com/webgme/webgme.git","svn_url":"https://github.com/webgme/webgme","homepage":"http://webgme.org","size":140354,"stargazers_count":19,"watchers_count":19,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"open_issues_count":17,"forks":12,"open_issues":17,"watchers":19,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/webgme/webgme/pulls/590"},"html":{"href":"https://github.com/webgme/webgme/pull/590"},"issue":{"href":"https://api.github.com/repos/webgme/webgme/issues/590"},"comments":{"href":"https://api.github.com/repos/webgme/webgme/issues/590/comments"},"review_comments":{"href":"https://api.github.com/repos/webgme/webgme/pulls/590/comments"},"review_comment":{"href":"https://api.github.com/repos/webgme/webgme/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/webgme/webgme/pulls/590/commits"},"statuses":{"href":"https://api.github.com/repos/webgme/webgme/statuses/11311d6d8cbfcd9f0947f9b523cb3c9736fb917e"}}}}