{"action":"created","comment":{"url":"https://api.github.com/repos/pypa/pip/pulls/comments/41541413","id":41541413,"diff_hunk":"@@ -0,0 +1,47 @@\n+from __future__ import absolute_import\n+\n+import hashlib\n+import logging\n+import sys\n+\n+from pip.basecommand import Command\n+from pip.exceptions import FAVORITE_HASH\n+from pip.status_codes import ERROR\n+\n+\n+logger = logging.getLogger(__name__)\n+\n+\n+class HashCommand(Command):\n+    \"\"\"\n+    Compute a hash of a local package archive.\n+\n+    These can be used with --hash in a requirements file to do repeatable\n+    installs.\n+\n+    \"\"\"\n+    name = 'hash'\n+    usage = \"\"\"%prog [options] <file> ...\"\"\"\n+    summary = 'Compute hashes of package archives.'\n+\n+    def run(self, options, args):\n+        if not args:\n+            self.parser.print_usage(sys.stderr)\n+            return ERROR\n+\n+        for path in args:\n+            logger.info('%s:\\n--hash=%s:%s' % (path,\n+                                               FAVORITE_HASH,\n+                                               _hash_of_file(path)))\n+\n+\n+def _hash_of_file(path):","path":"pip/commands/hash.py","position":38,"original_position":38,"commit_id":"d477ae6c5cfffc44c56717767fb71d48bc8c5746","original_commit_id":"d477ae6c5cfffc44c56717767fb71d48bc8c5746","user":{"login":"jezdez","id":1610,"avatar_url":"https://avatars.githubusercontent.com/u/1610?v=3","gravatar_id":"","url":"https://api.github.com/users/jezdez","html_url":"https://github.com/jezdez","followers_url":"https://api.github.com/users/jezdez/followers","following_url":"https://api.github.com/users/jezdez/following{/other_user}","gists_url":"https://api.github.com/users/jezdez/gists{/gist_id}","starred_url":"https://api.github.com/users/jezdez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jezdez/subscriptions","organizations_url":"https://api.github.com/users/jezdez/orgs","repos_url":"https://api.github.com/users/jezdez/repos","events_url":"https://api.github.com/users/jezdez/events{/privacy}","received_events_url":"https://api.github.com/users/jezdez/received_events","type":"User","site_admin":false},"body":"I was wondering if that would be a good function to offer as an official API?","created_at":"2015-10-08T17:18:28Z","updated_at":"2015-10-08T17:18:28Z","html_url":"https://github.com/pypa/pip/pull/3137#discussion_r41541413","pull_request_url":"https://api.github.com/repos/pypa/pip/pulls/3137","_links":{"self":{"href":"https://api.github.com/repos/pypa/pip/pulls/comments/41541413"},"html":{"href":"https://github.com/pypa/pip/pull/3137#discussion_r41541413"},"pull_request":{"href":"https://api.github.com/repos/pypa/pip/pulls/3137"}}},"pull_request":{"url":"https://api.github.com/repos/pypa/pip/pulls/3137","id":45971429,"html_url":"https://github.com/pypa/pip/pull/3137","diff_url":"https://github.com/pypa/pip/pull/3137.diff","patch_url":"https://github.com/pypa/pip/pull/3137.patch","issue_url":"https://api.github.com/repos/pypa/pip/issues/3137","number":3137,"state":"open","locked":false,"title":"Repeatable installs via hashing","user":{"login":"erikrose","id":295816,"avatar_url":"https://avatars.githubusercontent.com/u/295816?v=3","gravatar_id":"","url":"https://api.github.com/users/erikrose","html_url":"https://github.com/erikrose","followers_url":"https://api.github.com/users/erikrose/followers","following_url":"https://api.github.com/users/erikrose/following{/other_user}","gists_url":"https://api.github.com/users/erikrose/gists{/gist_id}","starred_url":"https://api.github.com/users/erikrose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikrose/subscriptions","organizations_url":"https://api.github.com/users/erikrose/orgs","repos_url":"https://api.github.com/users/erikrose/repos","events_url":"https://api.github.com/users/erikrose/events{/privacy}","received_events_url":"https://api.github.com/users/erikrose/received_events","type":"User","site_admin":false},"body":"Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.\r\n\r\nThis lets you add `--hash=sha256:abcde…` to a line in a requirements file to verify a package against a good local hash, guaranteeing repeatable installs without needing to run your own index mirrors, in the vein of https://pypi.python.org/pypi/peep/.\r\n\r\n* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.\r\n  * In --require-hashes mode, at least one hash is required to match for each requirement, even subrequirements.\r\n  * Option-based requirements (--hash=sha256:...) turn on --require-hashes mode implicitly.\r\n  * Internet-derived URL-based hashes are \"necessary but not sufficient\": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.\r\n  * Locally stored URL-based requirements (#md5=...) are treated just like option-based ones, except they don't turn on --require-hashes. Basically, that spelling is deprecated in favor of the option syntax.\r\n  * --require-hashes disallows --egg, which would bypass its dependency control by delegating everything to setuptools.\r\n* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.\r\n  * Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.\r\n  * Complain about missing hashes.\r\n  * Complain about requirement types we don't know how to hash (like VCS ones and local dirs).\r\n* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.\r\n* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.\r\n* Add a `pip hash` command for hashing package archives.\r\n* Add hash-checking support for `pip download` and `pip wheel`.\r\n\r\nFor a later release:\r\n\r\n* Support for --hash options on the commandline, interspersed with packages\r\n* Possible additions to `pip freeze`\r\n* See https://github.com/erikrose/peep/wiki/PipHashing for details","created_at":"2015-09-25T02:27:13Z","updated_at":"2015-10-08T17:18:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"03659198078cc67b50461b45ff243592bbc68544","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/pypa/pip/pulls/3137/commits","review_comments_url":"https://api.github.com/repos/pypa/pip/pulls/3137/comments","review_comment_url":"https://api.github.com/repos/pypa/pip/pulls/comments{/number}","comments_url":"https://api.github.com/repos/pypa/pip/issues/3137/comments","statuses_url":"https://api.github.com/repos/pypa/pip/statuses/d477ae6c5cfffc44c56717767fb71d48bc8c5746","head":{"label":"erikrose:hashing","ref":"hashing","sha":"d477ae6c5cfffc44c56717767fb71d48bc8c5746","user":{"login":"erikrose","id":295816,"avatar_url":"https://avatars.githubusercontent.com/u/295816?v=3","gravatar_id":"","url":"https://api.github.com/users/erikrose","html_url":"https://github.com/erikrose","followers_url":"https://api.github.com/users/erikrose/followers","following_url":"https://api.github.com/users/erikrose/following{/other_user}","gists_url":"https://api.github.com/users/erikrose/gists{/gist_id}","starred_url":"https://api.github.com/users/erikrose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikrose/subscriptions","organizations_url":"https://api.github.com/users/erikrose/orgs","repos_url":"https://api.github.com/users/erikrose/repos","events_url":"https://api.github.com/users/erikrose/events{/privacy}","received_events_url":"https://api.github.com/users/erikrose/received_events","type":"User","site_admin":false},"repo":{"id":41885078,"name":"pip","full_name":"erikrose/pip","owner":{"login":"erikrose","id":295816,"avatar_url":"https://avatars.githubusercontent.com/u/295816?v=3","gravatar_id":"","url":"https://api.github.com/users/erikrose","html_url":"https://github.com/erikrose","followers_url":"https://api.github.com/users/erikrose/followers","following_url":"https://api.github.com/users/erikrose/following{/other_user}","gists_url":"https://api.github.com/users/erikrose/gists{/gist_id}","starred_url":"https://api.github.com/users/erikrose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/erikrose/subscriptions","organizations_url":"https://api.github.com/users/erikrose/orgs","repos_url":"https://api.github.com/users/erikrose/repos","events_url":"https://api.github.com/users/erikrose/events{/privacy}","received_events_url":"https://api.github.com/users/erikrose/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/erikrose/pip","description":"The PyPA recommended tool for installing Python packages","fork":true,"url":"https://api.github.com/repos/erikrose/pip","forks_url":"https://api.github.com/repos/erikrose/pip/forks","keys_url":"https://api.github.com/repos/erikrose/pip/keys{/key_id}","collaborators_url":"https://api.github.com/repos/erikrose/pip/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/erikrose/pip/teams","hooks_url":"https://api.github.com/repos/erikrose/pip/hooks","issue_events_url":"https://api.github.com/repos/erikrose/pip/issues/events{/number}","events_url":"https://api.github.com/repos/erikrose/pip/events","assignees_url":"https://api.github.com/repos/erikrose/pip/assignees{/user}","branches_url":"https://api.github.com/repos/erikrose/pip/branches{/branch}","tags_url":"https://api.github.com/repos/erikrose/pip/tags","blobs_url":"https://api.github.com/repos/erikrose/pip/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/erikrose/pip/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/erikrose/pip/git/refs{/sha}","trees_url":"https://api.github.com/repos/erikrose/pip/git/trees{/sha}","statuses_url":"https://api.github.com/repos/erikrose/pip/statuses/{sha}","languages_url":"https://api.github.com/repos/erikrose/pip/languages","stargazers_url":"https://api.github.com/repos/erikrose/pip/stargazers","contributors_url":"https://api.github.com/repos/erikrose/pip/contributors","subscribers_url":"https://api.github.com/repos/erikrose/pip/subscribers","subscription_url":"https://api.github.com/repos/erikrose/pip/subscription","commits_url":"https://api.github.com/repos/erikrose/pip/commits{/sha}","git_commits_url":"https://api.github.com/repos/erikrose/pip/git/commits{/sha}","comments_url":"https://api.github.com/repos/erikrose/pip/comments{/number}","issue_comment_url":"https://api.github.com/repos/erikrose/pip/issues/comments{/number}","contents_url":"https://api.github.com/repos/erikrose/pip/contents/{+path}","compare_url":"https://api.github.com/repos/erikrose/pip/compare/{base}...{head}","merges_url":"https://api.github.com/repos/erikrose/pip/merges","archive_url":"https://api.github.com/repos/erikrose/pip/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/erikrose/pip/downloads","issues_url":"https://api.github.com/repos/erikrose/pip/issues{/number}","pulls_url":"https://api.github.com/repos/erikrose/pip/pulls{/number}","milestones_url":"https://api.github.com/repos/erikrose/pip/milestones{/number}","notifications_url":"https://api.github.com/repos/erikrose/pip/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/erikrose/pip/labels{/name}","releases_url":"https://api.github.com/repos/erikrose/pip/releases{/id}","created_at":"2015-09-03T21:37:57Z","updated_at":"2015-09-03T21:37:59Z","pushed_at":"2015-10-08T04:36:27Z","git_url":"git://github.com/erikrose/pip.git","ssh_url":"git@github.com:erikrose/pip.git","clone_url":"https://github.com/erikrose/pip.git","svn_url":"https://github.com/erikrose/pip","homepage":"https://pip.pypa.io/","size":38395,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"pypa:develop","ref":"develop","sha":"7cea748d0fb2e8980c2676304607426585550b41","user":{"login":"pypa","id":647025,"avatar_url":"https://avatars.githubusercontent.com/u/647025?v=3","gravatar_id":"","url":"https://api.github.com/users/pypa","html_url":"https://github.com/pypa","followers_url":"https://api.github.com/users/pypa/followers","following_url":"https://api.github.com/users/pypa/following{/other_user}","gists_url":"https://api.github.com/users/pypa/gists{/gist_id}","starred_url":"https://api.github.com/users/pypa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypa/subscriptions","organizations_url":"https://api.github.com/users/pypa/orgs","repos_url":"https://api.github.com/users/pypa/repos","events_url":"https://api.github.com/users/pypa/events{/privacy}","received_events_url":"https://api.github.com/users/pypa/received_events","type":"Organization","site_admin":false},"repo":{"id":1446467,"name":"pip","full_name":"pypa/pip","owner":{"login":"pypa","id":647025,"avatar_url":"https://avatars.githubusercontent.com/u/647025?v=3","gravatar_id":"","url":"https://api.github.com/users/pypa","html_url":"https://github.com/pypa","followers_url":"https://api.github.com/users/pypa/followers","following_url":"https://api.github.com/users/pypa/following{/other_user}","gists_url":"https://api.github.com/users/pypa/gists{/gist_id}","starred_url":"https://api.github.com/users/pypa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypa/subscriptions","organizations_url":"https://api.github.com/users/pypa/orgs","repos_url":"https://api.github.com/users/pypa/repos","events_url":"https://api.github.com/users/pypa/events{/privacy}","received_events_url":"https://api.github.com/users/pypa/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/pypa/pip","description":"The PyPA recommended tool for installing Python packages","fork":false,"url":"https://api.github.com/repos/pypa/pip","forks_url":"https://api.github.com/repos/pypa/pip/forks","keys_url":"https://api.github.com/repos/pypa/pip/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pypa/pip/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pypa/pip/teams","hooks_url":"https://api.github.com/repos/pypa/pip/hooks","issue_events_url":"https://api.github.com/repos/pypa/pip/issues/events{/number}","events_url":"https://api.github.com/repos/pypa/pip/events","assignees_url":"https://api.github.com/repos/pypa/pip/assignees{/user}","branches_url":"https://api.github.com/repos/pypa/pip/branches{/branch}","tags_url":"https://api.github.com/repos/pypa/pip/tags","blobs_url":"https://api.github.com/repos/pypa/pip/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pypa/pip/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pypa/pip/git/refs{/sha}","trees_url":"https://api.github.com/repos/pypa/pip/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pypa/pip/statuses/{sha}","languages_url":"https://api.github.com/repos/pypa/pip/languages","stargazers_url":"https://api.github.com/repos/pypa/pip/stargazers","contributors_url":"https://api.github.com/repos/pypa/pip/contributors","subscribers_url":"https://api.github.com/repos/pypa/pip/subscribers","subscription_url":"https://api.github.com/repos/pypa/pip/subscription","commits_url":"https://api.github.com/repos/pypa/pip/commits{/sha}","git_commits_url":"https://api.github.com/repos/pypa/pip/git/commits{/sha}","comments_url":"https://api.github.com/repos/pypa/pip/comments{/number}","issue_comment_url":"https://api.github.com/repos/pypa/pip/issues/comments{/number}","contents_url":"https://api.github.com/repos/pypa/pip/contents/{+path}","compare_url":"https://api.github.com/repos/pypa/pip/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pypa/pip/merges","archive_url":"https://api.github.com/repos/pypa/pip/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pypa/pip/downloads","issues_url":"https://api.github.com/repos/pypa/pip/issues{/number}","pulls_url":"https://api.github.com/repos/pypa/pip/pulls{/number}","milestones_url":"https://api.github.com/repos/pypa/pip/milestones{/number}","notifications_url":"https://api.github.com/repos/pypa/pip/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pypa/pip/labels{/name}","releases_url":"https://api.github.com/repos/pypa/pip/releases{/id}","created_at":"2011-03-06T14:30:46Z","updated_at":"2015-10-08T11:26:07Z","pushed_at":"2015-10-08T04:53:36Z","git_url":"git://github.com/pypa/pip.git","ssh_url":"git@github.com:pypa/pip.git","clone_url":"https://github.com/pypa/pip.git","svn_url":"https://github.com/pypa/pip","homepage":"https://pip.pypa.io/","size":56137,"stargazers_count":2374,"watchers_count":2374,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":930,"mirror_url":null,"open_issues_count":615,"forks":930,"open_issues":615,"watchers":2374,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/pypa/pip/pulls/3137"},"html":{"href":"https://github.com/pypa/pip/pull/3137"},"issue":{"href":"https://api.github.com/repos/pypa/pip/issues/3137"},"comments":{"href":"https://api.github.com/repos/pypa/pip/issues/3137/comments"},"review_comments":{"href":"https://api.github.com/repos/pypa/pip/pulls/3137/comments"},"review_comment":{"href":"https://api.github.com/repos/pypa/pip/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/pypa/pip/pulls/3137/commits"},"statuses":{"href":"https://api.github.com/repos/pypa/pip/statuses/d477ae6c5cfffc44c56717767fb71d48bc8c5746"}}}}