Repository: peterbe/hashin

Issue:
Fails on pyup comments in requirements file
E.g. 
```
▶ hashin --dry-run -u -r requirements/default.txt
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/packaging/requirements.py", line 93, in __init__
    req = REQUIREMENT.parseString(requirement_string)
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1632, in parseString
    raise exc
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1622, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 3395, in parseImpl
    loc, exprtokens = e._parse( instring, loc, doActions )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 3717, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1379, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 3378, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 1383, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/local/lib/python3.6/site-packages/pyparsing.py", line 2689, in parseImpl
    raise ParseException(instring, loc, self.errmsg, self)
pyparsing.ParseException: Expected W:(abcd...) (at char 4), (line:1, col:5)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/bin/hashin", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.6/site-packages/hashin.py", line 736, in main
    interactive=args.interactive,
  File "/usr/local/lib/python3.6/site-packages/hashin.py", line 104, in run
    req = Requirement(line.split("\\")[0])
  File "/usr/local/lib/python3.6/site-packages/packaging/requirements.py", line 97, in __init__
    requirement_string[e.loc:e.loc + 8]))
packaging.requirements.InvalidRequirement: Invalid requirement, parse error at "'# pyup: '"
```

Not sure which it's failing one. [This one](https://github.com/mozilla/normandy/blob/d955673d0bbfdd2b385be40f775c7818aac2ba2a/requirements/default.txt#L34) or [this one](https://github.com/mozilla/normandy/blob/d955673d0bbfdd2b385be40f775c7818aac2ba2a/requirements/default.txt#L221) or both. Should be easy to reproduce. 

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 7a37972..a4f08b2 100644
--- a/README.rst
+++ b/README.rst
@@ -270,6 +270,11 @@ put it directly into ``pip``.
 Version History
 ===============
 
+0.14.2
+  * When using ``--update-all`` and parsing requirements file it could be fooled
+    by comments that look like package specs (e.g ``# check out foo==1.0``)
+    See See https://github.com/peterbe/hashin/issues/103
+
 0.14.1
   * All HTTP GET work to fetch information about packages from PyPI is done in
     concurrent threads. Requires backport for Python 2.7.
diff --git a/hashin.py b/hashin.py
index 34f9b21..1eed22d 100755
--- a/hashin.py
+++ b/hashin.py
@@ -115,7 +115,7 @@ def run(specs, requirements_file, *args, **kwargs):
         previous_versions = {}
         with open(requirements_file) as f:
             for line in f:
-                if regex.search(line):
+                if regex.search(line) and not line.lstrip().startswith("#"):
                     req = Requirement(line.split("\\")[0])
                     # Deliberately strip the specifier (aka. the version)
                     version = req.specifier
diff --git a/setup.py b/setup.py
index f980d4c..6158a6e 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ except ImportError:
 
 setup(
     name="hashin",
-    version="0.14.1",
+    version="0.14.2",
     description="Edits your requirements.txt by hashing them in",
     long_description=open(path.join(_here, "README.rst")).read(),
     author="Peter Bengtsson",


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 0ce49c3..74a4569 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1532,6 +1532,48 @@ def test_run_update_all(murlopen, tmpfile):
         assert lines[0] == "hashin==0.11 \\"
 
 
+def test_run_comments_with_package_spec_patterns(murlopen, tmpfile, capsys):
+    """Based on https://github.com/peterbe/hashin/issues/103
+    Essentially, the regex that looks for package specs in each line of the
+    requirements file might pick up lines that are actually comments.
+    """
+
+    def mocked_get(url, **options):
+        if url == "https://pypi.org/pypi/hashin/json":
+            return _Response(
+                {
+                    "info": {"version": "0.11", "name": "hashin"},
+                    "releases": {
+                        "0.11": [
+                            {
+                                "url": "https://pypi.org/packages/source/p/hashin/hashin-0.11.tar.gz",
+                                "digests": {"sha256": "bbbbb"},
+                            }
+                        ],
+                        "0.10": [
+                            {
+                                "url": "https://pypi.org/packages/source/p/hashin/hashin-0.10.tar.gz",
+                                "digests": {"sha256": "aaaaa"},
+                            }
+                        ],
+                    },
+                }
+            )
+
+    murlopen.side_effect = mocked_get
+
+    with tmpfile() as filename:
+        with open(filename, "w") as f:
+            f.write("# Hey, don't use hashin==1.2.3 \n")
+            f.write("hashin==0.11 \\\n")
+            f.write("    --hash=sha256:bbbbb\n")
+            f.write("\n")
+
+        retcode = hashin.run([], filename, "sha256")
+        # Since this is based a stupidity test, just be content that it works.
+        assert retcode == 0
+
+
 def test_run_dry(murlopen, tmpfile, capsys):
     """dry run should edit the requirements.txt file and print
     hashes and package name in the console