{"action":"created","comment":{"url":"https://api.github.com/repos/desec-io/desec-stack/pulls/comments/167026251","pull_request_review_id":95183145,"id":167026251,"diff_hunk":"@@ -216,80 +216,87 @@ def write_rrsets(self, datas):\n \n     @transaction.atomic\n     def _write_rrsets(self, rrsets):\n+        # Base queryset for all RRset of the current domain\n+        rrset_qs = RRset.objects.filter(domain=self)\n+\n+        # Set to check RRset uniqueness\n+        rrsets_seen = set()\n+\n+        # To-do list for non-empty RRsets, indexed by (subname, type)\n+        rrsets_meaty_todo = {}\n+\n+        # RRsets to send to pdns\n+        rrsets_to_write = {}\n+\n         # Always-false Q object: https://stackoverflow.com/a/35894246/6867099\n-        rrsets_index = {}\n-        q_update = models.Q(pk__isnull=True)\n-        q_delete = models.Q(pk__isnull=True)\n+        q_meaty = models.Q(pk__isnull=True)\n+        q_empty = models.Q(pk__isnull=True)\n \n         # Determine which RRsets need to be updated or deleted\n         for rrset, rrs in rrsets.items():\n             if rrset.domain is not self:\n                 raise ValueError('RRset has wrong domain')\n-            if (rrset.subname, rrset.type) in rrsets_index:\n+            if (rrset.subname, rrset.type) in rrsets_seen:\n                 raise ValueError('RRset repeated with same subname and type')\n             if not all(rr.rrset is rrset for rr in rrs):\n                 raise ValueError('RR has wrong parent RRset')\n \n-            # Book-keeping\n-            rrsets_index[(rrset.subname, rrset.type)] = rrset\n+            rrsets_seen.add((rrset.subname, rrset.type))\n \n-            q = models.Q(subname=rrset.subname) & models.Q(type=rrset.type)\n+            q = models.Q(subname=rrset.subname, type=rrset.type)\n             if rrs:\n-                q_update |= q\n+                rrsets_meaty_todo[(rrset.subname, rrset.type)] = rrset\n+                q_meaty |= q\n             else:\n-                q_delete |= q\n-\n-        # Lock RRsets\n-        RRset.objects.filter(q_update | q_delete, domain=self).select_for_update()\n-\n-        # Figure out which RRsets are unchanged and can be excluded\n-        exclude_from_update = []\n-        qs_update = RRset.objects.filter(q_update, domain=self)\n-        for rrset_old in qs_update.prefetch_related('records').all():\n-            rrset_new = rrsets_index[(rrset_old.subname, rrset_old.type)]\n-            if rrset_old.ttl != rrset_new.ttl:\n-                continue\n-            rrs_new = {rr.content for rr in rrsets[rrset_new]}\n-            rrs_old = {rr.content for rr in rrset_old.records.all()}\n-            if rrs_new != rrs_old:\n-                continue\n-            # Old and new contents do not differ, so we can skip this RRset\n-            del rrsets[rrset_new]\n-            exclude_from_update.append(rrset_old)\n-\n-        # Do not process new RRsets that are empty (and did not exist before)\n-        # This is to avoid unnecessary pdns requests like (A: ...; AAAA: None)\n-        qs_delete = RRset.objects.filter(q_delete, domain=self)\n-        qs_delete_values = qs_delete.values_list('subname', 'type')\n-        # We modify the rrsets dictionary and thus loop over a copy of it\n-        for rrset, rrs in list(rrsets.items()):\n-            if rrs or (rrset.subname, rrset.type) in qs_delete_values:\n-                continue\n-            # RRset up for deletion does not exist\n-            del rrsets[rrset]\n-\n-        # Clear or delete RRsets\n-        RR.objects.filter(rrset__in=qs_update).exclude(rrset__in=exclude_from_update).delete()\n-        RRset.objects.filter(q_delete, domain=self).delete()\n-\n-        # Prepare and save new RRset contents\n-        # We modify the rrsets dictionary and thus loop over a copy of it\n-        for rrset, rrs in list(rrsets.items()):\n-            if not rrs:\n-                continue\n-            # (Create and) get correct RRset and update dictionary accordingly\n-            del rrsets[rrset]\n-            (rrset, _) = RRset.objects.get_or_create(domain=self,\n-                                                     subname=rrset.subname,\n-                                                     type=rrset.type,\n-                                                     ttl=rrset.ttl)\n-            rrsets[rrset] = [RR(rrset=rrset, content=rr.content) for rr in rrs]\n-\n-        RR.objects.bulk_create([rr for rrs in rrsets.values() for rr in rrs])\n-\n-        # Send changed RRsets to pdns\n-        if rrsets and not self.owner.locked:\n-            pdns.set_rrsets(self, rrsets)\n+                rrsets_to_write[rrset] = []\n+                q_empty |= q\n+\n+        # Construct querysets representing RRsets that do (not) have RR\n+        # contents and lock them\n+        qs_meaty = rrset_qs.filter(q_meaty).select_for_update()\n+        qs_empty = rrset_qs.filter(q_empty).select_for_update()\n+\n+        # For existing RRsets, execute TTL updates and mark for RR update\n+        rrsets_same_rrs = []\n+        for rrset in qs_meaty.all():\n+            rrset_temp = rrsets_meaty_todo.pop((rrset.subname, rrset.type))\n+            rrs_temp = {rr.content for rr in rrsets[rrset_temp]}\n+            rrs = {rr.content for rr in rrset.records.all()}\n+\n+            changed_ttl = (rrset_temp.ttl != rrset.ttl)\n+            changed_rrs = (rrs_temp != rrs)\n+\n+            if changed_ttl:\n+                rrset.ttl = rrset_temp.ttl\n+                rrset.save()\n+            if changed_ttl or changed_rrs:\n+                rrsets_to_write[rrset] = [RR(rrset=rrset, content=rr_content)\n+                                          for rr_content in rrs_temp]\n+            if not changed_rrs:\n+                rrsets_same_rrs.append(rrset)\n+\n+        # At this point, rrsets_meaty_todo points to new RRsets only. Let's","path":"api/desecapi/models.py","position":236,"original_position":124,"commit_id":"f9adac797795e403f194263750db8143aa335c00","original_commit_id":"f9adac797795e403f194263750db8143aa335c00","user":{"login":"peterthomassen","id":4242683,"avatar_url":"https://avatars2.githubusercontent.com/u/4242683?v=4","gravatar_id":"","url":"https://api.github.com/users/peterthomassen","html_url":"https://github.com/peterthomassen","followers_url":"https://api.github.com/users/peterthomassen/followers","following_url":"https://api.github.com/users/peterthomassen/following{/other_user}","gists_url":"https://api.github.com/users/peterthomassen/gists{/gist_id}","starred_url":"https://api.github.com/users/peterthomassen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/peterthomassen/subscriptions","organizations_url":"https://api.github.com/users/peterthomassen/orgs","repos_url":"https://api.github.com/users/peterthomassen/repos","events_url":"https://api.github.com/users/peterthomassen/events{/privacy}","received_events_url":"https://api.github.com/users/peterthomassen/received_events","type":"User","site_admin":false},"body":"done","created_at":"2018-02-08T18:29:25Z","updated_at":"2018-02-08T18:29:25Z","html_url":"https://github.com/desec-io/desec-stack/pull/90#discussion_r167026251","pull_request_url":"https://api.github.com/repos/desec-io/desec-stack/pulls/90","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/comments/167026251"},"html":{"href":"https://github.com/desec-io/desec-stack/pull/90#discussion_r167026251"},"pull_request":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/90"}},"in_reply_to_id":166933320},"pull_request":{"url":"https://api.github.com/repos/desec-io/desec-stack/pulls/90","id":166349241,"html_url":"https://github.com/desec-io/desec-stack/pull/90","diff_url":"https://github.com/desec-io/desec-stack/pull/90.diff","patch_url":"https://github.com/desec-io/desec-stack/pull/90.patch","issue_url":"https://api.github.com/repos/desec-io/desec-stack/issues/90","number":90,"state":"open","locked":false,"title":"20171211 misc","user":{"login":"peterthomassen","id":4242683,"avatar_url":"https://avatars2.githubusercontent.com/u/4242683?v=4","gravatar_id":"","url":"https://api.github.com/users/peterthomassen","html_url":"https://github.com/peterthomassen","followers_url":"https://api.github.com/users/peterthomassen/followers","following_url":"https://api.github.com/users/peterthomassen/following{/other_user}","gists_url":"https://api.github.com/users/peterthomassen/gists{/gist_id}","starred_url":"https://api.github.com/users/peterthomassen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/peterthomassen/subscriptions","organizations_url":"https://api.github.com/users/peterthomassen/orgs","repos_url":"https://api.github.com/users/peterthomassen/repos","events_url":"https://api.github.com/users/peterthomassen/events{/privacy}","received_events_url":"https://api.github.com/users/peterthomassen/received_events","type":"User","site_admin":false},"body":"This PR fixes a number of issues, including the bug where unlocking a user would overwrite a domain that is managed in pdns exclusively (without using our API).\r\n\r\nI think the items are pretty much clear based on the commit titles. If anything is unclear, let's talk! :-)","created_at":"2018-01-31T19:08:15Z","updated_at":"2018-02-08T18:29:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1c26d7156cac41e13cebc913fe12d80df0afbf6a","assignee":{"login":"peterthomassen","id":4242683,"avatar_url":"https://avatars2.githubusercontent.com/u/4242683?v=4","gravatar_id":"","url":"https://api.github.com/users/peterthomassen","html_url":"https://github.com/peterthomassen","followers_url":"https://api.github.com/users/peterthomassen/followers","following_url":"https://api.github.com/users/peterthomassen/following{/other_user}","gists_url":"https://api.github.com/users/peterthomassen/gists{/gist_id}","starred_url":"https://api.github.com/users/peterthomassen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/peterthomassen/subscriptions","organizations_url":"https://api.github.com/users/peterthomassen/orgs","repos_url":"https://api.github.com/users/peterthomassen/repos","events_url":"https://api.github.com/users/peterthomassen/events{/privacy}","received_events_url":"https://api.github.com/users/peterthomassen/received_events","type":"User","site_admin":false},"assignees":[{"login":"peterthomassen","id":4242683,"avatar_url":"https://avatars2.githubusercontent.com/u/4242683?v=4","gravatar_id":"","url":"https://api.github.com/users/peterthomassen","html_url":"https://github.com/peterthomassen","followers_url":"https://api.github.com/users/peterthomassen/followers","following_url":"https://api.github.com/users/peterthomassen/following{/other_user}","gists_url":"https://api.github.com/users/peterthomassen/gists{/gist_id}","starred_url":"https://api.github.com/users/peterthomassen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/peterthomassen/subscriptions","organizations_url":"https://api.github.com/users/peterthomassen/orgs","repos_url":"https://api.github.com/users/peterthomassen/repos","events_url":"https://api.github.com/users/peterthomassen/events{/privacy}","received_events_url":"https://api.github.com/users/peterthomassen/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"milestone":null,"commits_url":"https://api.github.com/repos/desec-io/desec-stack/pulls/90/commits","review_comments_url":"https://api.github.com/repos/desec-io/desec-stack/pulls/90/comments","review_comment_url":"https://api.github.com/repos/desec-io/desec-stack/pulls/comments{/number}","comments_url":"https://api.github.com/repos/desec-io/desec-stack/issues/90/comments","statuses_url":"https://api.github.com/repos/desec-io/desec-stack/statuses/f9adac797795e403f194263750db8143aa335c00","head":{"label":"desec-io:20171211_misc","ref":"20171211_misc","sha":"f9adac797795e403f194263750db8143aa335c00","user":{"login":"desec-io","id":22482887,"avatar_url":"https://avatars3.githubusercontent.com/u/22482887?v=4","gravatar_id":"","url":"https://api.github.com/users/desec-io","html_url":"https://github.com/desec-io","followers_url":"https://api.github.com/users/desec-io/followers","following_url":"https://api.github.com/users/desec-io/following{/other_user}","gists_url":"https://api.github.com/users/desec-io/gists{/gist_id}","starred_url":"https://api.github.com/users/desec-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/desec-io/subscriptions","organizations_url":"https://api.github.com/users/desec-io/orgs","repos_url":"https://api.github.com/users/desec-io/repos","events_url":"https://api.github.com/users/desec-io/events{/privacy}","received_events_url":"https://api.github.com/users/desec-io/received_events","type":"Organization","site_admin":false},"repo":{"id":69393808,"name":"desec-stack","full_name":"desec-io/desec-stack","owner":{"login":"desec-io","id":22482887,"avatar_url":"https://avatars3.githubusercontent.com/u/22482887?v=4","gravatar_id":"","url":"https://api.github.com/users/desec-io","html_url":"https://github.com/desec-io","followers_url":"https://api.github.com/users/desec-io/followers","following_url":"https://api.github.com/users/desec-io/following{/other_user}","gists_url":"https://api.github.com/users/desec-io/gists{/gist_id}","starred_url":"https://api.github.com/users/desec-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/desec-io/subscriptions","organizations_url":"https://api.github.com/users/desec-io/orgs","repos_url":"https://api.github.com/users/desec-io/repos","events_url":"https://api.github.com/users/desec-io/events{/privacy}","received_events_url":"https://api.github.com/users/desec-io/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/desec-io/desec-stack","description":"Basic stack for deSEC name services, implemented as a docker-compose application","fork":false,"url":"https://api.github.com/repos/desec-io/desec-stack","forks_url":"https://api.github.com/repos/desec-io/desec-stack/forks","keys_url":"https://api.github.com/repos/desec-io/desec-stack/keys{/key_id}","collaborators_url":"https://api.github.com/repos/desec-io/desec-stack/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/desec-io/desec-stack/teams","hooks_url":"https://api.github.com/repos/desec-io/desec-stack/hooks","issue_events_url":"https://api.github.com/repos/desec-io/desec-stack/issues/events{/number}","events_url":"https://api.github.com/repos/desec-io/desec-stack/events","assignees_url":"https://api.github.com/repos/desec-io/desec-stack/assignees{/user}","branches_url":"https://api.github.com/repos/desec-io/desec-stack/branches{/branch}","tags_url":"https://api.github.com/repos/desec-io/desec-stack/tags","blobs_url":"https://api.github.com/repos/desec-io/desec-stack/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/desec-io/desec-stack/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/desec-io/desec-stack/git/refs{/sha}","trees_url":"https://api.github.com/repos/desec-io/desec-stack/git/trees{/sha}","statuses_url":"https://api.github.com/repos/desec-io/desec-stack/statuses/{sha}","languages_url":"https://api.github.com/repos/desec-io/desec-stack/languages","stargazers_url":"https://api.github.com/repos/desec-io/desec-stack/stargazers","contributors_url":"https://api.github.com/repos/desec-io/desec-stack/contributors","subscribers_url":"https://api.github.com/repos/desec-io/desec-stack/subscribers","subscription_url":"https://api.github.com/repos/desec-io/desec-stack/subscription","commits_url":"https://api.github.com/repos/desec-io/desec-stack/commits{/sha}","git_commits_url":"https://api.github.com/repos/desec-io/desec-stack/git/commits{/sha}","comments_url":"https://api.github.com/repos/desec-io/desec-stack/comments{/number}","issue_comment_url":"https://api.github.com/repos/desec-io/desec-stack/issues/comments{/number}","contents_url":"https://api.github.com/repos/desec-io/desec-stack/contents/{+path}","compare_url":"https://api.github.com/repos/desec-io/desec-stack/compare/{base}...{head}","merges_url":"https://api.github.com/repos/desec-io/desec-stack/merges","archive_url":"https://api.github.com/repos/desec-io/desec-stack/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/desec-io/desec-stack/downloads","issues_url":"https://api.github.com/repos/desec-io/desec-stack/issues{/number}","pulls_url":"https://api.github.com/repos/desec-io/desec-stack/pulls{/number}","milestones_url":"https://api.github.com/repos/desec-io/desec-stack/milestones{/number}","notifications_url":"https://api.github.com/repos/desec-io/desec-stack/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/desec-io/desec-stack/labels{/name}","releases_url":"https://api.github.com/repos/desec-io/desec-stack/releases{/id}","deployments_url":"https://api.github.com/repos/desec-io/desec-stack/deployments","created_at":"2016-09-27T20:02:32Z","updated_at":"2018-01-19T08:02:06Z","pushed_at":"2018-02-07T15:59:53Z","git_url":"git://github.com/desec-io/desec-stack.git","ssh_url":"git@github.com:desec-io/desec-stack.git","clone_url":"https://github.com/desec-io/desec-stack.git","svn_url":"https://github.com/desec-io/desec-stack","homepage":"https://desec.io/","size":664,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":17,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":2,"open_issues":17,"watchers":8,"default_branch":"master"}},"base":{"label":"desec-io:master","ref":"master","sha":"06342fe5b192ad5b087cc61ab1ecc2cc89e2a31d","user":{"login":"desec-io","id":22482887,"avatar_url":"https://avatars3.githubusercontent.com/u/22482887?v=4","gravatar_id":"","url":"https://api.github.com/users/desec-io","html_url":"https://github.com/desec-io","followers_url":"https://api.github.com/users/desec-io/followers","following_url":"https://api.github.com/users/desec-io/following{/other_user}","gists_url":"https://api.github.com/users/desec-io/gists{/gist_id}","starred_url":"https://api.github.com/users/desec-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/desec-io/subscriptions","organizations_url":"https://api.github.com/users/desec-io/orgs","repos_url":"https://api.github.com/users/desec-io/repos","events_url":"https://api.github.com/users/desec-io/events{/privacy}","received_events_url":"https://api.github.com/users/desec-io/received_events","type":"Organization","site_admin":false},"repo":{"id":69393808,"name":"desec-stack","full_name":"desec-io/desec-stack","owner":{"login":"desec-io","id":22482887,"avatar_url":"https://avatars3.githubusercontent.com/u/22482887?v=4","gravatar_id":"","url":"https://api.github.com/users/desec-io","html_url":"https://github.com/desec-io","followers_url":"https://api.github.com/users/desec-io/followers","following_url":"https://api.github.com/users/desec-io/following{/other_user}","gists_url":"https://api.github.com/users/desec-io/gists{/gist_id}","starred_url":"https://api.github.com/users/desec-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/desec-io/subscriptions","organizations_url":"https://api.github.com/users/desec-io/orgs","repos_url":"https://api.github.com/users/desec-io/repos","events_url":"https://api.github.com/users/desec-io/events{/privacy}","received_events_url":"https://api.github.com/users/desec-io/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/desec-io/desec-stack","description":"Basic stack for deSEC name services, implemented as a docker-compose application","fork":false,"url":"https://api.github.com/repos/desec-io/desec-stack","forks_url":"https://api.github.com/repos/desec-io/desec-stack/forks","keys_url":"https://api.github.com/repos/desec-io/desec-stack/keys{/key_id}","collaborators_url":"https://api.github.com/repos/desec-io/desec-stack/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/desec-io/desec-stack/teams","hooks_url":"https://api.github.com/repos/desec-io/desec-stack/hooks","issue_events_url":"https://api.github.com/repos/desec-io/desec-stack/issues/events{/number}","events_url":"https://api.github.com/repos/desec-io/desec-stack/events","assignees_url":"https://api.github.com/repos/desec-io/desec-stack/assignees{/user}","branches_url":"https://api.github.com/repos/desec-io/desec-stack/branches{/branch}","tags_url":"https://api.github.com/repos/desec-io/desec-stack/tags","blobs_url":"https://api.github.com/repos/desec-io/desec-stack/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/desec-io/desec-stack/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/desec-io/desec-stack/git/refs{/sha}","trees_url":"https://api.github.com/repos/desec-io/desec-stack/git/trees{/sha}","statuses_url":"https://api.github.com/repos/desec-io/desec-stack/statuses/{sha}","languages_url":"https://api.github.com/repos/desec-io/desec-stack/languages","stargazers_url":"https://api.github.com/repos/desec-io/desec-stack/stargazers","contributors_url":"https://api.github.com/repos/desec-io/desec-stack/contributors","subscribers_url":"https://api.github.com/repos/desec-io/desec-stack/subscribers","subscription_url":"https://api.github.com/repos/desec-io/desec-stack/subscription","commits_url":"https://api.github.com/repos/desec-io/desec-stack/commits{/sha}","git_commits_url":"https://api.github.com/repos/desec-io/desec-stack/git/commits{/sha}","comments_url":"https://api.github.com/repos/desec-io/desec-stack/comments{/number}","issue_comment_url":"https://api.github.com/repos/desec-io/desec-stack/issues/comments{/number}","contents_url":"https://api.github.com/repos/desec-io/desec-stack/contents/{+path}","compare_url":"https://api.github.com/repos/desec-io/desec-stack/compare/{base}...{head}","merges_url":"https://api.github.com/repos/desec-io/desec-stack/merges","archive_url":"https://api.github.com/repos/desec-io/desec-stack/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/desec-io/desec-stack/downloads","issues_url":"https://api.github.com/repos/desec-io/desec-stack/issues{/number}","pulls_url":"https://api.github.com/repos/desec-io/desec-stack/pulls{/number}","milestones_url":"https://api.github.com/repos/desec-io/desec-stack/milestones{/number}","notifications_url":"https://api.github.com/repos/desec-io/desec-stack/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/desec-io/desec-stack/labels{/name}","releases_url":"https://api.github.com/repos/desec-io/desec-stack/releases{/id}","deployments_url":"https://api.github.com/repos/desec-io/desec-stack/deployments","created_at":"2016-09-27T20:02:32Z","updated_at":"2018-01-19T08:02:06Z","pushed_at":"2018-02-07T15:59:53Z","git_url":"git://github.com/desec-io/desec-stack.git","ssh_url":"git@github.com:desec-io/desec-stack.git","clone_url":"https://github.com/desec-io/desec-stack.git","svn_url":"https://github.com/desec-io/desec-stack","homepage":"https://desec.io/","size":664,"stargazers_count":8,"watchers_count":8,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":17,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":2,"open_issues":17,"watchers":8,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/90"},"html":{"href":"https://github.com/desec-io/desec-stack/pull/90"},"issue":{"href":"https://api.github.com/repos/desec-io/desec-stack/issues/90"},"comments":{"href":"https://api.github.com/repos/desec-io/desec-stack/issues/90/comments"},"review_comments":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/90/comments"},"review_comment":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/desec-io/desec-stack/pulls/90/commits"},"statuses":{"href":"https://api.github.com/repos/desec-io/desec-stack/statuses/f9adac797795e403f194263750db8143aa335c00"}},"author_association":"OWNER"}}
{"actor":{"display_login":"peterthomassen"}}