{"action":"created","comment":{"url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/comments/199441523","pull_request_review_id":133559157,"id":199441523,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5OTQ0MTUyMw==","diff_hunk":"@@ -55,72 +59,110 @@ - (CDTDatastore *)datastoreNamed:(NSString *)name\n        withEncryptionKeyProvider:(id<CDTEncryptionKeyProvider>)provider\n                            error:(NSError *__autoreleasing *)error\n {\n-    //    if (![TD_Database isValidDatabaseName:name]) {\n-    //      Not a public method yet\n-    //    }\n-\n-    CDTDatastore *datastore = nil;\n-\n-    NSString *errorReason = nil;\n-    TD_Database *db = [self.manager databaseNamed:name];\n-    if (db) {\n-        datastore = [[CDTDatastore alloc] initWithManager:self database:db encryptionKeyProvider:provider];\n+    @synchronized (self) {\n+        CDTDatastore *datastore = _openDatastores[name];\n+        if (datastore != nil) {\n+            NSLog(@\"returning already open CDTDatastore %@\", name);\n+            return datastore;\n+        }\n+        NSLog(@\"opening new CDTDatastore %@\", name);\n \n-        if (!datastore) {\n-            errorReason = NSLocalizedString(@\"Wrong key?\", nil);\n+        \n+        NSString *errorReason = nil;\n+        TD_Database *db = [self.manager databaseNamed:name];\n+        if (db) {\n+            datastore = [[CDTDatastore alloc] initWithManager:self database:db encryptionKeyProvider:provider];\n+            \n+            if (!datastore) {\n+                errorReason = NSLocalizedString(@\"Wrong key?\", nil);\n+            }\n+        } else {\n+            errorReason = NSLocalizedString(@\"Invalid name?\", nil);\n+        }\n+        \n+        if (!datastore && error) {\n+            NSDictionary *userInfo = @{\n+                                       NSLocalizedDescriptionKey : NSLocalizedString(@\"Couldn't create database.\", nil),\n+                                       NSLocalizedFailureReasonErrorKey : errorReason,\n+                                       NSLocalizedRecoverySuggestionErrorKey : errorReason\n+                                       };\n+            *error = [NSError errorWithDomain:CDTDatastoreErrorDomain code:400 userInfo:userInfo];\n+        }\n+        \n+        if (datastore != nil) {\n+            _openDatastores[name] = datastore;\n         }\n-    } else {\n-        errorReason = NSLocalizedString(@\"Invalid name?\", nil);\n+        return datastore;\n+        \n     }\n+}\n \n-    if (!datastore && error) {\n-        NSDictionary *userInfo = @{\n-            NSLocalizedDescriptionKey : NSLocalizedString(@\"Couldn't create database.\", nil),\n-            NSLocalizedFailureReasonErrorKey : errorReason,\n-            NSLocalizedRecoverySuggestionErrorKey : errorReason\n-        };\n-        *error = [NSError errorWithDomain:CDTDatastoreErrorDomain code:400 userInfo:userInfo];\n+- (void)closeDatastoreNamed:(NSString *)name {\n+    @synchronized (self) {\n+        NSLog(@\"closing CDTDatastore %@\", name);\n+        CDTDatastore *ds = _openDatastores[name];\n+        if (ds == nil) {\n+            // this may not be an issue if delete was already called and it was removed there\n+            NSLog(@\"warning can't find CDTDatastore to close %@\", name);\n+            return;\n+        }\n+        [[ds database] close];\n+        [_openDatastores removeObjectForKey:name];\n     }\n+}\n \n-    return datastore;\n+- (void)dealloc {\n+    NSLog(@\"dealloc CDTDatastoreManager\");\n+    /*","path":"CDTDatastore/CDTDatastoreManager.m","position":105,"original_position":105,"commit_id":"4d8d806e8437b2b9596343f251b5adaa70f2b4d4","original_commit_id":"4d8d806e8437b2b9596343f251b5adaa70f2b4d4","user":{"login":"smithsz","id":5632401,"node_id":"MDQ6VXNlcjU2MzI0MDE=","avatar_url":"https://avatars1.githubusercontent.com/u/5632401?v=4","gravatar_id":"","url":"https://api.github.com/users/smithsz","html_url":"https://github.com/smithsz","followers_url":"https://api.github.com/users/smithsz/followers","following_url":"https://api.github.com/users/smithsz/following{/other_user}","gists_url":"https://api.github.com/users/smithsz/gists{/gist_id}","starred_url":"https://api.github.com/users/smithsz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/smithsz/subscriptions","organizations_url":"https://api.github.com/users/smithsz/orgs","repos_url":"https://api.github.com/users/smithsz/repos","events_url":"https://api.github.com/users/smithsz/events{/privacy}","received_events_url":"https://api.github.com/users/smithsz/received_events","type":"User","site_admin":false},"body":"forget to delete this? I assume the dealloc is called on databases as they fall out of scope.","created_at":"2018-07-02T09:48:25Z","updated_at":"2018-07-02T09:48:25Z","html_url":"https://github.com/cloudant/CDTDatastore/pull/436#discussion_r199441523","pull_request_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/comments/199441523"},"html":{"href":"https://github.com/cloudant/CDTDatastore/pull/436#discussion_r199441523"},"pull_request":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436"}}},"pull_request":{"url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436","id":197140735,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk3MTQwNzM1","html_url":"https://github.com/cloudant/CDTDatastore/pull/436","diff_url":"https://github.com/cloudant/CDTDatastore/pull/436.diff","patch_url":"https://github.com/cloudant/CDTDatastore/pull/436.patch","issue_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/436","number":436,"state":"open","locked":false,"title":"CDTQIndexManager has weak reference to CDTDatastore","user":{"login":"tomblench","id":6302842,"node_id":"MDQ6VXNlcjYzMDI4NDI=","avatar_url":"https://avatars3.githubusercontent.com/u/6302842?v=4","gravatar_id":"","url":"https://api.github.com/users/tomblench","html_url":"https://github.com/tomblench","followers_url":"https://api.github.com/users/tomblench/followers","following_url":"https://api.github.com/users/tomblench/following{/other_user}","gists_url":"https://api.github.com/users/tomblench/gists{/gist_id}","starred_url":"https://api.github.com/users/tomblench/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomblench/subscriptions","organizations_url":"https://api.github.com/users/tomblench/orgs","repos_url":"https://api.github.com/users/tomblench/repos","events_url":"https://api.github.com/users/tomblench/events{/privacy}","received_events_url":"https://api.github.com/users/tomblench/received_events","type":"User","site_admin":false},"body":"## Checklist\r\n\r\n- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](../blob/master/DCO1.1.txt)\r\n- [x] Added tests for code changes _or_ test/build only changes\r\n- [x] Updated the change log file (`CHANGES.md`|`CHANGELOG.md`) _or_ test/build only changes\r\n- [x] Completed the PR template below:\r\n\r\n## Description\r\n\r\nFix lifecycle issues regarding proper opening and closing of resources across `CDTDatastore`, `CDTQIndexManager`, `CDTDatastoreManager`.\r\n\r\nNote that the primary motivator here is that the associated object relationship between `CDTDatastore` and `CDTQIndexManager` is currently incorrect when facading through index/query methods, and that it is currently possible to obtain multiple `CDTQIndexManager` instances for the same underlying SQLite database, which leads to incorrect behaviour.\r\n\r\nFor more details see \"Approach\" which gives an overview of the design issues.\r\n\r\nFixes #435\r\n\r\n## Approach\r\n\r\n### Lifecycle of key objects\r\n\r\n#### `CDTDatastore` -> `CDTQIndexManager`\r\n\r\n1-to-1 relationship, strong reference via `objc_{get,set}AssociatedObject` in\r\n`CDTDatastore+Query.m`.\r\n\r\nEach index manager is constructed on demand.\r\n\r\n#### `CDTQIndexManager` -> `CDTDatastore`\r\n\r\n1-to-1 relationship, weak reference via property `datastore`, therefore no retain cycles. Because\r\nreference to `CDTQIndexManager` can't 'leak' (all methods facade through `CDTDatastore+Query`), this\r\nreference will always be valid.\r\n\r\nNote that if `CDTQIndexManager` is constructed using `+managerUsingDatastore` or\r\n`-initUsingDatastore`, the above doesn't hold and it is possible for the reference to `datastore` to\r\nbe invalid. Even worse, a user could then use a mixture of API calls through the constructed index\r\nmanager and facaded through the datastore. Therefore this usage is discouraged (all of our current\r\ndocumentation shows index/query usage facaded through datastore).\r\n\r\nFor the above reason, add a comment in `CDTQIndexManager` to clarify that it's not public API.\r\n\r\n#### `CDTDatastoreManager` -> `CDTDatastore`\r\n\r\n1-to-many relationship, strong references via the `openDatastores` property, which is a map whose\r\nkeys are the datastore name, and values the datastore itself.\r\n\r\nNote that the only way of obtaining a new or existing datastore instance is through the manager.\r\n\r\nThis is a change to existing functionality and guarantees users will obtain the same reference when\r\nmultiple calls to `-datastoreNamed` are made. This is essential for the `CDTDatastore` ->\r\n`CDTQIndexManager` to work properly. As of now, this is not the case, and multiple calls to\r\n`-datastoreNamed` will result in a new and different `CDTDatastore` to be constructed each time\r\n(although they all point to the same underlying `TD_Database`). This causes errors as the different\r\nindex managers will all try to simultaneously open the same underlying sqlite database.\r\n\r\nNote that a user could still have a 'dangling' reference to a `CDTDatastore`, that is they can\r\nassign the pointer of a previously constructed datastore, then `dealloc` its manager. Now they have\r\na datastore they can't delete (they need the manager for this), and worse, they can now obtain a\r\ndifferent pointer for the same underlying datastore by constructing a new manager and calling\r\n`-datastoreNamed`. I think we have to be pragmatic and decide we can't protect users from this API\r\nabuse.\r\n\r\n#### `CDTDatastore` -> `CDTDatastoreManager`\r\n\r\n1-to-1 relationship. This is currently held as a strong reference via the property `_manager`, but\r\nwill cause retain cycles when combined with the relationship described in the above para. Is not\r\ncurrently used so can be downgraded to a weak reference or removed.\r\n\r\n## Schema & API Changes\r\n\r\nAdd new method `closeDatastoreNamed` on `CDTDatastoreManager` to allow users with many datastores open to avoid resource (file handle) starvation.\r\n\r\n## Security and Privacy\r\n\r\nNo change.\r\n\r\n## Testing\r\n\r\nSee added test class `CDTLifecycleTests`. Test approach as follows:\r\n\r\n### New instances\r\n\r\n- Calls to `CDTDatastore+Query.m` methods should always obtain the same `CDTQIndexManager` instance\r\n  for a given `CDTDatastore` instance.\r\n- Calls to `CDTDatastoreManager` `-datastoreNamed` should always obtain the same `CDTDatastore`\r\n  instance for a given datastore name.\r\n\r\n### Retain count reaching zero\r\n\r\n- When `dealloc` is called on `CDTDatastore`, `dealloc` should be called on the `CDTQIndexManager`\r\n  held as an associated object.\r\n- When `dealloc` is called on `CDTDatastoreManager`, `dealloc` should be called on all\r\n  `CDTDatastores` in `openDatastores`.\r\n- Should we have tests for `TD_Database` retain count?\r\n\r\n### Explicit close\r\n\r\n- Explicit close supported and tested via new method call on  `CDTDatastoreManager`\r\n\r\n### Explicit delete\r\n\r\n- When deleting extensions, `dealloc` should be called on `CDTQIndexManager` to allow it to close\r\n  its underlying database for the extensions directory is deleted. (In `deleteDatastoreNamed` call\r\n  `[_openDatastores removeObjectForKey:name]` first). Note that not doing this is the cause of the\r\n  following message, as reported by customer:\r\n\r\n### Other test changes\r\n\r\nUpdated some existing tests which now need an explicit close before re-opening database to reproduce expected behaviour.\r\n\r\nTests run via `Rake` now run in `release` mode, as the original bug report shows certain failure modes are only exhibited when run in `release` (as opposed to `debug`) mode.\r\n\r\nRemoved some `#ifdef(DEBUG)` guards to allow test code to compile in `release` mode.\r\n\r\n## Monitoring and Logging\r\n\r\nTODO - some debug `NSLog`s have been left in, these need to be evaluated and turned into \"proper\" log statements if necessary.","created_at":"2018-06-25T14:55:55Z","updated_at":"2018-07-02T09:48:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6fc9a87103e5b8ebbf199844356222368e96f7e5","assignee":{"login":"tomblench","id":6302842,"node_id":"MDQ6VXNlcjYzMDI4NDI=","avatar_url":"https://avatars3.githubusercontent.com/u/6302842?v=4","gravatar_id":"","url":"https://api.github.com/users/tomblench","html_url":"https://github.com/tomblench","followers_url":"https://api.github.com/users/tomblench/followers","following_url":"https://api.github.com/users/tomblench/following{/other_user}","gists_url":"https://api.github.com/users/tomblench/gists{/gist_id}","starred_url":"https://api.github.com/users/tomblench/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomblench/subscriptions","organizations_url":"https://api.github.com/users/tomblench/orgs","repos_url":"https://api.github.com/users/tomblench/repos","events_url":"https://api.github.com/users/tomblench/events{/privacy}","received_events_url":"https://api.github.com/users/tomblench/received_events","type":"User","site_admin":false},"assignees":[{"login":"tomblench","id":6302842,"node_id":"MDQ6VXNlcjYzMDI4NDI=","avatar_url":"https://avatars3.githubusercontent.com/u/6302842?v=4","gravatar_id":"","url":"https://api.github.com/users/tomblench","html_url":"https://github.com/tomblench","followers_url":"https://api.github.com/users/tomblench/followers","following_url":"https://api.github.com/users/tomblench/following{/other_user}","gists_url":"https://api.github.com/users/tomblench/gists{/gist_id}","starred_url":"https://api.github.com/users/tomblench/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomblench/subscriptions","organizations_url":"https://api.github.com/users/tomblench/orgs","repos_url":"https://api.github.com/users/tomblench/repos","events_url":"https://api.github.com/users/tomblench/events{/privacy}","received_events_url":"https://api.github.com/users/tomblench/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"emlaver","id":10552966,"node_id":"MDQ6VXNlcjEwNTUyOTY2","avatar_url":"https://avatars3.githubusercontent.com/u/10552966?v=4","gravatar_id":"","url":"https://api.github.com/users/emlaver","html_url":"https://github.com/emlaver","followers_url":"https://api.github.com/users/emlaver/followers","following_url":"https://api.github.com/users/emlaver/following{/other_user}","gists_url":"https://api.github.com/users/emlaver/gists{/gist_id}","starred_url":"https://api.github.com/users/emlaver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/emlaver/subscriptions","organizations_url":"https://api.github.com/users/emlaver/orgs","repos_url":"https://api.github.com/users/emlaver/repos","events_url":"https://api.github.com/users/emlaver/events{/privacy}","received_events_url":"https://api.github.com/users/emlaver/received_events","type":"User","site_admin":false},{"login":"ricellis","id":10698320,"node_id":"MDQ6VXNlcjEwNjk4MzIw","avatar_url":"https://avatars0.githubusercontent.com/u/10698320?v=4","gravatar_id":"","url":"https://api.github.com/users/ricellis","html_url":"https://github.com/ricellis","followers_url":"https://api.github.com/users/ricellis/followers","following_url":"https://api.github.com/users/ricellis/following{/other_user}","gists_url":"https://api.github.com/users/ricellis/gists{/gist_id}","starred_url":"https://api.github.com/users/ricellis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ricellis/subscriptions","organizations_url":"https://api.github.com/users/ricellis/orgs","repos_url":"https://api.github.com/users/ricellis/repos","events_url":"https://api.github.com/users/ricellis/events{/privacy}","received_events_url":"https://api.github.com/users/ricellis/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":{"url":"https://api.github.com/repos/cloudant/CDTDatastore/milestones/10","html_url":"https://github.com/cloudant/CDTDatastore/milestone/10","labels_url":"https://api.github.com/repos/cloudant/CDTDatastore/milestones/10/labels","id":3354683,"node_id":"MDk6TWlsZXN0b25lMzM1NDY4Mw==","number":10,"title":"2.next","description":"","creator":{"login":"ricellis","id":10698320,"node_id":"MDQ6VXNlcjEwNjk4MzIw","avatar_url":"https://avatars0.githubusercontent.com/u/10698320?v=4","gravatar_id":"","url":"https://api.github.com/users/ricellis","html_url":"https://github.com/ricellis","followers_url":"https://api.github.com/users/ricellis/followers","following_url":"https://api.github.com/users/ricellis/following{/other_user}","gists_url":"https://api.github.com/users/ricellis/gists{/gist_id}","starred_url":"https://api.github.com/users/ricellis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ricellis/subscriptions","organizations_url":"https://api.github.com/users/ricellis/orgs","repos_url":"https://api.github.com/users/ricellis/repos","events_url":"https://api.github.com/users/ricellis/events{/privacy}","received_events_url":"https://api.github.com/users/ricellis/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":1,"state":"open","created_at":"2018-05-17T13:38:55Z","updated_at":"2018-07-02T09:31:01Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436/commits","review_comments_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436/comments","review_comment_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/436/comments","statuses_url":"https://api.github.com/repos/cloudant/CDTDatastore/statuses/4d8d806e8437b2b9596343f251b5adaa70f2b4d4","head":{"label":"cloudant:fix-cdtq-weak-reference","ref":"fix-cdtq-weak-reference","sha":"4d8d806e8437b2b9596343f251b5adaa70f2b4d4","user":{"login":"cloudant","id":44642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NjQy","avatar_url":"https://avatars0.githubusercontent.com/u/44642?v=4","gravatar_id":"","url":"https://api.github.com/users/cloudant","html_url":"https://github.com/cloudant","followers_url":"https://api.github.com/users/cloudant/followers","following_url":"https://api.github.com/users/cloudant/following{/other_user}","gists_url":"https://api.github.com/users/cloudant/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudant/subscriptions","organizations_url":"https://api.github.com/users/cloudant/orgs","repos_url":"https://api.github.com/users/cloudant/repos","events_url":"https://api.github.com/users/cloudant/events{/privacy}","received_events_url":"https://api.github.com/users/cloudant/received_events","type":"Organization","site_admin":false},"repo":{"id":15022868,"node_id":"MDEwOlJlcG9zaXRvcnkxNTAyMjg2OA==","name":"CDTDatastore","full_name":"cloudant/CDTDatastore","owner":{"login":"cloudant","id":44642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NjQy","avatar_url":"https://avatars0.githubusercontent.com/u/44642?v=4","gravatar_id":"","url":"https://api.github.com/users/cloudant","html_url":"https://github.com/cloudant","followers_url":"https://api.github.com/users/cloudant/followers","following_url":"https://api.github.com/users/cloudant/following{/other_user}","gists_url":"https://api.github.com/users/cloudant/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudant/subscriptions","organizations_url":"https://api.github.com/users/cloudant/orgs","repos_url":"https://api.github.com/users/cloudant/repos","events_url":"https://api.github.com/users/cloudant/events{/privacy}","received_events_url":"https://api.github.com/users/cloudant/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cloudant/CDTDatastore","description":"Cloudant Sync iOS datastore library.","fork":false,"url":"https://api.github.com/repos/cloudant/CDTDatastore","forks_url":"https://api.github.com/repos/cloudant/CDTDatastore/forks","keys_url":"https://api.github.com/repos/cloudant/CDTDatastore/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cloudant/CDTDatastore/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cloudant/CDTDatastore/teams","hooks_url":"https://api.github.com/repos/cloudant/CDTDatastore/hooks","issue_events_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/events{/number}","events_url":"https://api.github.com/repos/cloudant/CDTDatastore/events","assignees_url":"https://api.github.com/repos/cloudant/CDTDatastore/assignees{/user}","branches_url":"https://api.github.com/repos/cloudant/CDTDatastore/branches{/branch}","tags_url":"https://api.github.com/repos/cloudant/CDTDatastore/tags","blobs_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/refs{/sha}","trees_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cloudant/CDTDatastore/statuses/{sha}","languages_url":"https://api.github.com/repos/cloudant/CDTDatastore/languages","stargazers_url":"https://api.github.com/repos/cloudant/CDTDatastore/stargazers","contributors_url":"https://api.github.com/repos/cloudant/CDTDatastore/contributors","subscribers_url":"https://api.github.com/repos/cloudant/CDTDatastore/subscribers","subscription_url":"https://api.github.com/repos/cloudant/CDTDatastore/subscription","commits_url":"https://api.github.com/repos/cloudant/CDTDatastore/commits{/sha}","git_commits_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/commits{/sha}","comments_url":"https://api.github.com/repos/cloudant/CDTDatastore/comments{/number}","issue_comment_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/comments{/number}","contents_url":"https://api.github.com/repos/cloudant/CDTDatastore/contents/{+path}","compare_url":"https://api.github.com/repos/cloudant/CDTDatastore/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cloudant/CDTDatastore/merges","archive_url":"https://api.github.com/repos/cloudant/CDTDatastore/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cloudant/CDTDatastore/downloads","issues_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues{/number}","pulls_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls{/number}","milestones_url":"https://api.github.com/repos/cloudant/CDTDatastore/milestones{/number}","notifications_url":"https://api.github.com/repos/cloudant/CDTDatastore/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cloudant/CDTDatastore/labels{/name}","releases_url":"https://api.github.com/repos/cloudant/CDTDatastore/releases{/id}","deployments_url":"https://api.github.com/repos/cloudant/CDTDatastore/deployments","created_at":"2013-12-08T11:59:05Z","updated_at":"2018-06-28T13:06:27Z","pushed_at":"2018-06-28T13:30:06Z","git_url":"git://github.com/cloudant/CDTDatastore.git","ssh_url":"git@github.com:cloudant/CDTDatastore.git","clone_url":"https://github.com/cloudant/CDTDatastore.git","svn_url":"https://github.com/cloudant/CDTDatastore","homepage":null,"size":3551,"stargazers_count":150,"watchers_count":150,"language":"Objective-C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":36,"mirror_url":null,"archived":false,"open_issues_count":36,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":36,"open_issues":36,"watchers":150,"default_branch":"master"}},"base":{"label":"cloudant:master","ref":"master","sha":"708f31c86ae3da9a93a87c61793545c932d39a12","user":{"login":"cloudant","id":44642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NjQy","avatar_url":"https://avatars0.githubusercontent.com/u/44642?v=4","gravatar_id":"","url":"https://api.github.com/users/cloudant","html_url":"https://github.com/cloudant","followers_url":"https://api.github.com/users/cloudant/followers","following_url":"https://api.github.com/users/cloudant/following{/other_user}","gists_url":"https://api.github.com/users/cloudant/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudant/subscriptions","organizations_url":"https://api.github.com/users/cloudant/orgs","repos_url":"https://api.github.com/users/cloudant/repos","events_url":"https://api.github.com/users/cloudant/events{/privacy}","received_events_url":"https://api.github.com/users/cloudant/received_events","type":"Organization","site_admin":false},"repo":{"id":15022868,"node_id":"MDEwOlJlcG9zaXRvcnkxNTAyMjg2OA==","name":"CDTDatastore","full_name":"cloudant/CDTDatastore","owner":{"login":"cloudant","id":44642,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0NjQy","avatar_url":"https://avatars0.githubusercontent.com/u/44642?v=4","gravatar_id":"","url":"https://api.github.com/users/cloudant","html_url":"https://github.com/cloudant","followers_url":"https://api.github.com/users/cloudant/followers","following_url":"https://api.github.com/users/cloudant/following{/other_user}","gists_url":"https://api.github.com/users/cloudant/gists{/gist_id}","starred_url":"https://api.github.com/users/cloudant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloudant/subscriptions","organizations_url":"https://api.github.com/users/cloudant/orgs","repos_url":"https://api.github.com/users/cloudant/repos","events_url":"https://api.github.com/users/cloudant/events{/privacy}","received_events_url":"https://api.github.com/users/cloudant/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cloudant/CDTDatastore","description":"Cloudant Sync iOS datastore library.","fork":false,"url":"https://api.github.com/repos/cloudant/CDTDatastore","forks_url":"https://api.github.com/repos/cloudant/CDTDatastore/forks","keys_url":"https://api.github.com/repos/cloudant/CDTDatastore/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cloudant/CDTDatastore/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cloudant/CDTDatastore/teams","hooks_url":"https://api.github.com/repos/cloudant/CDTDatastore/hooks","issue_events_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/events{/number}","events_url":"https://api.github.com/repos/cloudant/CDTDatastore/events","assignees_url":"https://api.github.com/repos/cloudant/CDTDatastore/assignees{/user}","branches_url":"https://api.github.com/repos/cloudant/CDTDatastore/branches{/branch}","tags_url":"https://api.github.com/repos/cloudant/CDTDatastore/tags","blobs_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/refs{/sha}","trees_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cloudant/CDTDatastore/statuses/{sha}","languages_url":"https://api.github.com/repos/cloudant/CDTDatastore/languages","stargazers_url":"https://api.github.com/repos/cloudant/CDTDatastore/stargazers","contributors_url":"https://api.github.com/repos/cloudant/CDTDatastore/contributors","subscribers_url":"https://api.github.com/repos/cloudant/CDTDatastore/subscribers","subscription_url":"https://api.github.com/repos/cloudant/CDTDatastore/subscription","commits_url":"https://api.github.com/repos/cloudant/CDTDatastore/commits{/sha}","git_commits_url":"https://api.github.com/repos/cloudant/CDTDatastore/git/commits{/sha}","comments_url":"https://api.github.com/repos/cloudant/CDTDatastore/comments{/number}","issue_comment_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues/comments{/number}","contents_url":"https://api.github.com/repos/cloudant/CDTDatastore/contents/{+path}","compare_url":"https://api.github.com/repos/cloudant/CDTDatastore/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cloudant/CDTDatastore/merges","archive_url":"https://api.github.com/repos/cloudant/CDTDatastore/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cloudant/CDTDatastore/downloads","issues_url":"https://api.github.com/repos/cloudant/CDTDatastore/issues{/number}","pulls_url":"https://api.github.com/repos/cloudant/CDTDatastore/pulls{/number}","milestones_url":"https://api.github.com/repos/cloudant/CDTDatastore/milestones{/number}","notifications_url":"https://api.github.com/repos/cloudant/CDTDatastore/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cloudant/CDTDatastore/labels{/name}","releases_url":"https://api.github.com/repos/cloudant/CDTDatastore/releases{/id}","deployments_url":"https://api.github.com/repos/cloudant/CDTDatastore/deployments","created_at":"2013-12-08T11:59:05Z","updated_at":"2018-06-28T13:06:27Z","pushed_at":"2018-06-28T13:30:06Z","git_url":"git://github.com/cloudant/CDTDatastore.git","ssh_url":"git@github.com:cloudant/CDTDatastore.git","clone_url":"https://github.com/cloudant/CDTDatastore.git","svn_url":"https://github.com/cloudant/CDTDatastore","homepage":null,"size":3551,"stargazers_count":150,"watchers_count":150,"language":"Objective-C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":36,"mirror_url":null,"archived":false,"open_issues_count":36,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":36,"open_issues":36,"watchers":150,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436"},"html":{"href":"https://github.com/cloudant/CDTDatastore/pull/436"},"issue":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/issues/436"},"comments":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/issues/436/comments"},"review_comments":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436/comments"},"review_comment":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/pulls/436/commits"},"statuses":{"href":"https://api.github.com/repos/cloudant/CDTDatastore/statuses/4d8d806e8437b2b9596343f251b5adaa70f2b4d4"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"smithsz"}}