{"action":"created","comment":{"url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/comments/216327483","pull_request_review_id":153781191,"id":216327483,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNjMyNzQ4Mw==","diff_hunk":"@@ -0,0 +1,110 @@\n+#\n+# The Qubes OS Project, https://www.qubes-os.org\n+#\n+# Copyright (C) 2018 Rusty Bird <rustybird@net-c.com>\n+#\n+# This library is free software; you can redistribute it and/or\n+# modify it under the terms of the GNU Lesser General Public\n+# License as published by the Free Software Foundation; either\n+# version 2.1 of the License, or (at your option) any later version.\n+#\n+# This library is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+# Lesser General Public License for more details.\n+#\n+# You should have received a copy of the GNU Lesser General Public\n+# License along with this library; if not, see <https://www.gnu.org/licenses/>.\n+#\n+\n+''' Tests for the file-reflink storage driver '''\n+\n+import os\n+import shutil\n+import subprocess\n+import sys\n+\n+import qubes.tests\n+from qubes.storage import reflink\n+\n+\n+# :pylint: disable=protected-access\n+# :pylint: disable=invalid-name\n+\n+class TC_00_ReflinkHelpers(qubes.tests.QubesTestCase):\n+    def setUp(self):\n+        super().setUp()\n+        self.test_dir = \"/var/tmp/test-reflink-helpers\"\n+        os.mkdir(self.test_dir)\n+\n+    def tearDown(self):\n+        super().tearDown()\n+        shutil.rmtree(self.test_dir)\n+\n+    def test_000_copy_file(self):\n+        source = os.path.join(self.test_dir, \"source-file\")\n+        dest = os.path.join(self.test_dir, \"new-directory\", \"dest-file\")\n+        content = os.urandom(1024**2)\n+\n+        with open(source, \"wb\") as source_io:\n+            source_io.write(content)\n+\n+        reflink._copy_file(source, dest)\n+\n+        self.assertNotEqual(os.stat(source).st_ino, os.stat(dest).st_ino)\n+        with open(source, \"rb\") as source_io:\n+            self.assertEqual(source_io.read(), content)\n+        with open(dest, \"rb\") as dest_io:\n+            self.assertEqual(dest_io.read(), content)\n+\n+    def test_001_create_and_resize_files_and_update_loopdevs(self):\n+        img_real = os.path.join(self.test_dir, \"img-real\")\n+        img_symlink = os.path.join(self.test_dir, \"img-symlink\")\n+        size_initial = 111 * 1024**2\n+        size_resized = 222 * 1024**2\n+\n+        reflink._create_sparse_file(img_real, size_initial)\n+        self.assertEqual(reflink._get_file_disk_usage(img_real), 0)\n+        self.assertEqual(os.stat(img_real).st_size, size_initial)\n+\n+        os.symlink(img_real, img_symlink)\n+\n+        loopdev_from_real = self._loopdev_setup_with_cleanup(img_real)\n+        loopdev_from_symlink = self._loopdev_setup_with_cleanup(img_symlink)\n+\n+        reflink._resize_file(img_real, size_resized)\n+        self.assertEqual(reflink._get_file_disk_usage(img_real), 0)\n+        self.assertEqual(os.stat(img_real).st_size, size_resized)\n+\n+        _update_loopdev_sizes(os.path.join(self.test_dir, \"unrelated\"))\n+\n+        for loopdev in (loopdev_from_real, loopdev_from_symlink):\n+            self.assertEqual(_get_blockdev_size(loopdev), size_initial)\n+\n+        _update_loopdev_sizes(img_symlink)\n+\n+        for loopdev in (loopdev_from_real, loopdev_from_symlink):\n+            self.assertEqual(_get_blockdev_size(loopdev), size_resized)\n+\n+    def _loopdev_setup_with_cleanup(self, img):\n+        loopdev = str.strip(_sudo(\"losetup\", \"-f\", \"--show\", img).decode())\n+        self.addCleanup(_sudo, \"losetup\", \"-d\", loopdev)\n+        return loopdev\n+\n+\n+def _get_blockdev_size(blockdev):\n+    return int(_sudo(\"blockdev\", \"--getsize64\", blockdev))\n+\n+def _update_loopdev_sizes(img):\n+    env = [k + \"=\" + v for k, v in os.environ.items()  # 'sudo -E' alone would\n+           if k.startswith(\"PYTHON\")]                  # drop some of these\n+    code = (\"from qubes.storage import reflink\\n\"\n+            \"reflink._update_loopdev_sizes(%r)\" % img)\n+    _sudo(\"-E\", \"env\", *env, sys.executable, \"-c\", code)\n+\n+def _sudo(*cmd):","path":"qubes/tests/storage_reflink.py","position":105,"original_position":105,"commit_id":"523fd0d27306b54f23ca6b3159825c7923a0737b","original_commit_id":"523fd0d27306b54f23ca6b3159825c7923a0737b","user":{"login":"rustybird","id":6682340,"node_id":"MDQ6VXNlcjY2ODIzNDA=","avatar_url":"https://avatars1.githubusercontent.com/u/6682340?v=4","gravatar_id":"","url":"https://api.github.com/users/rustybird","html_url":"https://github.com/rustybird","followers_url":"https://api.github.com/users/rustybird/followers","following_url":"https://api.github.com/users/rustybird/following{/other_user}","gists_url":"https://api.github.com/users/rustybird/gists{/gist_id}","starred_url":"https://api.github.com/users/rustybird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rustybird/subscriptions","organizations_url":"https://api.github.com/users/rustybird/orgs","repos_url":"https://api.github.com/users/rustybird/repos","events_url":"https://api.github.com/users/rustybird/events{/privacy}","received_events_url":"https://api.github.com/users/rustybird/received_events","type":"User","site_admin":false},"body":"Exactly - [one of the commits](https://github.com/QubesOS/qubes-core-admin/pull/231/commits/9aeab83b1560c4bf36f1b559cdedbb8bbb209a76) removes a couple of lines by getting rid of such a wrapper around CalledProcessError.","created_at":"2018-09-10T13:47:11Z","updated_at":"2018-09-10T13:47:11Z","html_url":"https://github.com/QubesOS/qubes-core-admin/pull/231#discussion_r216327483","pull_request_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/comments/216327483"},"html":{"href":"https://github.com/QubesOS/qubes-core-admin/pull/231#discussion_r216327483"},"pull_request":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231"}},"in_reply_to_id":216279274},"pull_request":{"url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231","id":214166669,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE0MTY2NjY5","html_url":"https://github.com/QubesOS/qubes-core-admin/pull/231","diff_url":"https://github.com/QubesOS/qubes-core-admin/pull/231.diff","patch_url":"https://github.com/QubesOS/qubes-core-admin/pull/231.patch","issue_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/231","number":231,"state":"open","locked":false,"title":"Storage improvements (mostly file-reflink)","user":{"login":"rustybird","id":6682340,"node_id":"MDQ6VXNlcjY2ODIzNDA=","avatar_url":"https://avatars1.githubusercontent.com/u/6682340?v=4","gravatar_id":"","url":"https://api.github.com/users/rustybird","html_url":"https://github.com/rustybird","followers_url":"https://api.github.com/users/rustybird/followers","following_url":"https://api.github.com/users/rustybird/following{/other_user}","gists_url":"https://api.github.com/users/rustybird/gists{/gist_id}","starred_url":"https://api.github.com/users/rustybird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rustybird/subscriptions","organizations_url":"https://api.github.com/users/rustybird/orgs","repos_url":"https://api.github.com/users/rustybird/repos","events_url":"https://api.github.com/users/rustybird/events{/privacy}","received_events_url":"https://api.github.com/users/rustybird/received_events","type":"User","site_admin":false},"body":"Some highlights:\r\n\r\n- Make file-reflink a fully non-blocking storage driver, in the sense that all API methods allowed to be coroutines, are coroutines. They still have the same synchronous code, but it's decorated to run in the event loop's thread-based default executor (under a per-volume lock). This approach should have a low risk of introducing bugs. It makes the Qubes UIs responsive during VM startup/shutdown/etc., and allows multiple volumes to be processed in parallel.\r\n\r\n- Plug file-reflink into the storage integration tests and add some initial unit tests. FWIW, in my daily use I haven't encountered any bug or data loss, and I'm confident now that it's a better and safer choice than dusty ol' `file` on btrfs. So:\r\n\r\n- ~~When creating the /var/lib/qubes pool, use file-reflink if the filesystem supports FICLONE. (Tested using a freshly built qubes-os-r4.0.conf ISO.) This is the last commit. You might not want to cherry-pick it for the release4.0 branch, depending on how conservative R4.0.1 should be.~~","created_at":"2018-09-09T21:46:23Z","updated_at":"2018-09-10T13:47:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"36db3eca78815d567096ce59ce41258f27ae9f43","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231/commits","review_comments_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231/comments","review_comment_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/comments{/number}","comments_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/231/comments","statuses_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/statuses/523fd0d27306b54f23ca6b3159825c7923a0737b","head":{"label":"rustybird:storage-improvements-mostly-reflink","ref":"storage-improvements-mostly-reflink","sha":"523fd0d27306b54f23ca6b3159825c7923a0737b","user":{"login":"rustybird","id":6682340,"node_id":"MDQ6VXNlcjY2ODIzNDA=","avatar_url":"https://avatars1.githubusercontent.com/u/6682340?v=4","gravatar_id":"","url":"https://api.github.com/users/rustybird","html_url":"https://github.com/rustybird","followers_url":"https://api.github.com/users/rustybird/followers","following_url":"https://api.github.com/users/rustybird/following{/other_user}","gists_url":"https://api.github.com/users/rustybird/gists{/gist_id}","starred_url":"https://api.github.com/users/rustybird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rustybird/subscriptions","organizations_url":"https://api.github.com/users/rustybird/orgs","repos_url":"https://api.github.com/users/rustybird/repos","events_url":"https://api.github.com/users/rustybird/events{/privacy}","received_events_url":"https://api.github.com/users/rustybird/received_events","type":"User","site_admin":false},"repo":{"id":45927031,"node_id":"MDEwOlJlcG9zaXRvcnk0NTkyNzAzMQ==","name":"qubes-core-admin","full_name":"rustybird/qubes-core-admin","owner":{"login":"rustybird","id":6682340,"node_id":"MDQ6VXNlcjY2ODIzNDA=","avatar_url":"https://avatars1.githubusercontent.com/u/6682340?v=4","gravatar_id":"","url":"https://api.github.com/users/rustybird","html_url":"https://github.com/rustybird","followers_url":"https://api.github.com/users/rustybird/followers","following_url":"https://api.github.com/users/rustybird/following{/other_user}","gists_url":"https://api.github.com/users/rustybird/gists{/gist_id}","starred_url":"https://api.github.com/users/rustybird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rustybird/subscriptions","organizations_url":"https://api.github.com/users/rustybird/orgs","repos_url":"https://api.github.com/users/rustybird/repos","events_url":"https://api.github.com/users/rustybird/events{/privacy}","received_events_url":"https://api.github.com/users/rustybird/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rustybird/qubes-core-admin","description":"Qubes component: core-admin","fork":true,"url":"https://api.github.com/repos/rustybird/qubes-core-admin","forks_url":"https://api.github.com/repos/rustybird/qubes-core-admin/forks","keys_url":"https://api.github.com/repos/rustybird/qubes-core-admin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rustybird/qubes-core-admin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rustybird/qubes-core-admin/teams","hooks_url":"https://api.github.com/repos/rustybird/qubes-core-admin/hooks","issue_events_url":"https://api.github.com/repos/rustybird/qubes-core-admin/issues/events{/number}","events_url":"https://api.github.com/repos/rustybird/qubes-core-admin/events","assignees_url":"https://api.github.com/repos/rustybird/qubes-core-admin/assignees{/user}","branches_url":"https://api.github.com/repos/rustybird/qubes-core-admin/branches{/branch}","tags_url":"https://api.github.com/repos/rustybird/qubes-core-admin/tags","blobs_url":"https://api.github.com/repos/rustybird/qubes-core-admin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rustybird/qubes-core-admin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rustybird/qubes-core-admin/git/refs{/sha}","trees_url":"https://api.github.com/repos/rustybird/qubes-core-admin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rustybird/qubes-core-admin/statuses/{sha}","languages_url":"https://api.github.com/repos/rustybird/qubes-core-admin/languages","stargazers_url":"https://api.github.com/repos/rustybird/qubes-core-admin/stargazers","contributors_url":"https://api.github.com/repos/rustybird/qubes-core-admin/contributors","subscribers_url":"https://api.github.com/repos/rustybird/qubes-core-admin/subscribers","subscription_url":"https://api.github.com/repos/rustybird/qubes-core-admin/subscription","commits_url":"https://api.github.com/repos/rustybird/qubes-core-admin/commits{/sha}","git_commits_url":"https://api.github.com/repos/rustybird/qubes-core-admin/git/commits{/sha}","comments_url":"https://api.github.com/repos/rustybird/qubes-core-admin/comments{/number}","issue_comment_url":"https://api.github.com/repos/rustybird/qubes-core-admin/issues/comments{/number}","contents_url":"https://api.github.com/repos/rustybird/qubes-core-admin/contents/{+path}","compare_url":"https://api.github.com/repos/rustybird/qubes-core-admin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rustybird/qubes-core-admin/merges","archive_url":"https://api.github.com/repos/rustybird/qubes-core-admin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rustybird/qubes-core-admin/downloads","issues_url":"https://api.github.com/repos/rustybird/qubes-core-admin/issues{/number}","pulls_url":"https://api.github.com/repos/rustybird/qubes-core-admin/pulls{/number}","milestones_url":"https://api.github.com/repos/rustybird/qubes-core-admin/milestones{/number}","notifications_url":"https://api.github.com/repos/rustybird/qubes-core-admin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rustybird/qubes-core-admin/labels{/name}","releases_url":"https://api.github.com/repos/rustybird/qubes-core-admin/releases{/id}","deployments_url":"https://api.github.com/repos/rustybird/qubes-core-admin/deployments","created_at":"2015-11-10T17:04:44Z","updated_at":"2015-11-25T08:18:45Z","pushed_at":"2018-09-10T07:24:28Z","git_url":"git://github.com/rustybird/qubes-core-admin.git","ssh_url":"git@github.com:rustybird/qubes-core-admin.git","clone_url":"https://github.com/rustybird/qubes-core-admin.git","svn_url":"https://github.com/rustybird/qubes-core-admin","homepage":null,"size":13543,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"gpl-2.0","name":"GNU General Public License v2.0","spdx_id":"GPL-2.0","url":"https://api.github.com/licenses/gpl-2.0","node_id":"MDc6TGljZW5zZTg="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"dvm"}},"base":{"label":"QubesOS:master","ref":"master","sha":"a371375f1d67841a2040c98322e690d29032a475","user":{"login":"QubesOS","id":10213205,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMjEzMjA1","avatar_url":"https://avatars3.githubusercontent.com/u/10213205?v=4","gravatar_id":"","url":"https://api.github.com/users/QubesOS","html_url":"https://github.com/QubesOS","followers_url":"https://api.github.com/users/QubesOS/followers","following_url":"https://api.github.com/users/QubesOS/following{/other_user}","gists_url":"https://api.github.com/users/QubesOS/gists{/gist_id}","starred_url":"https://api.github.com/users/QubesOS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QubesOS/subscriptions","organizations_url":"https://api.github.com/users/QubesOS/orgs","repos_url":"https://api.github.com/users/QubesOS/repos","events_url":"https://api.github.com/users/QubesOS/events{/privacy}","received_events_url":"https://api.github.com/users/QubesOS/received_events","type":"Organization","site_admin":false},"repo":{"id":30624141,"node_id":"MDEwOlJlcG9zaXRvcnkzMDYyNDE0MQ==","name":"qubes-core-admin","full_name":"QubesOS/qubes-core-admin","owner":{"login":"QubesOS","id":10213205,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMjEzMjA1","avatar_url":"https://avatars3.githubusercontent.com/u/10213205?v=4","gravatar_id":"","url":"https://api.github.com/users/QubesOS","html_url":"https://github.com/QubesOS","followers_url":"https://api.github.com/users/QubesOS/followers","following_url":"https://api.github.com/users/QubesOS/following{/other_user}","gists_url":"https://api.github.com/users/QubesOS/gists{/gist_id}","starred_url":"https://api.github.com/users/QubesOS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QubesOS/subscriptions","organizations_url":"https://api.github.com/users/QubesOS/orgs","repos_url":"https://api.github.com/users/QubesOS/repos","events_url":"https://api.github.com/users/QubesOS/events{/privacy}","received_events_url":"https://api.github.com/users/QubesOS/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/QubesOS/qubes-core-admin","description":"Qubes component: core-admin","fork":false,"url":"https://api.github.com/repos/QubesOS/qubes-core-admin","forks_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/forks","keys_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/teams","hooks_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/hooks","issue_events_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/events{/number}","events_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/events","assignees_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/assignees{/user}","branches_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/branches{/branch}","tags_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/tags","blobs_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/git/refs{/sha}","trees_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/statuses/{sha}","languages_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/languages","stargazers_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/stargazers","contributors_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/contributors","subscribers_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/subscribers","subscription_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/subscription","commits_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/commits{/sha}","git_commits_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/git/commits{/sha}","comments_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/comments{/number}","issue_comment_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/comments{/number}","contents_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/contents/{+path}","compare_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/merges","archive_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/downloads","issues_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues{/number}","pulls_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls{/number}","milestones_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/milestones{/number}","notifications_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/labels{/name}","releases_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/releases{/id}","deployments_url":"https://api.github.com/repos/QubesOS/qubes-core-admin/deployments","created_at":"2015-02-11T01:27:30Z","updated_at":"2018-09-10T07:29:08Z","pushed_at":"2018-09-10T13:36:29Z","git_url":"git://github.com/QubesOS/qubes-core-admin.git","ssh_url":"git@github.com:QubesOS/qubes-core-admin.git","clone_url":"https://github.com/QubesOS/qubes-core-admin.git","svn_url":"https://github.com/QubesOS/qubes-core-admin","homepage":null,"size":13861,"stargazers_count":46,"watchers_count":46,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":57,"mirror_url":null,"archived":false,"open_issues_count":6,"license":{"key":"lgpl-2.1","name":"GNU Lesser General Public License v2.1","spdx_id":"LGPL-2.1","url":"https://api.github.com/licenses/lgpl-2.1","node_id":"MDc6TGljZW5zZTEx"},"forks":57,"open_issues":6,"watchers":46,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231"},"html":{"href":"https://github.com/QubesOS/qubes-core-admin/pull/231"},"issue":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/231"},"comments":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/issues/231/comments"},"review_comments":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231/comments"},"review_comment":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/pulls/231/commits"},"statuses":{"href":"https://api.github.com/repos/QubesOS/qubes-core-admin/statuses/523fd0d27306b54f23ca6b3159825c7923a0737b"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"rustybird"}}