Repository: conda/conda

Issue:
Errors in History spec parsing with conda 4.5.5
Cond 4.5.5 has a problem:

P:\>conda update conda
Solving environment: failed

ERROR REPORT 

    Traceback (most recent call last):
      File "C:\Anaconda3\lib\site-packages\conda\exceptions.py", line 819, in __
call__
        return func(*args, **kwargs)
      File "C:\Anaconda3\lib\site-packages\conda\cli\main.py", line 78, in _main

        exit_code = do_call(args, p)
      File "C:\Anaconda3\lib\site-packages\conda\cli\conda_argparse.py", line 77
, in do_call
        exit_code = getattr(module, func_name)(args, parser)
      File "C:\Anaconda3\lib\site-packages\conda\cli\main_update.py", line 14, i
n execute
        install(args, parser, 'update')
      File "C:\Anaconda3\lib\site-packages\conda\cli\install.py", line 235, in i
nstall
        force_reinstall=context.force,
      File "C:\Anaconda3\lib\site-packages\conda\core\solve.py", line 506, in so
lve_for_transaction
        force_remove, force_reinstall)
      File "C:\Anaconda3\lib\site-packages\conda\core\solve.py", line 439, in so
lve_for_diff
        final_precs = self.solve_final_state(deps_modifier, prune, ignore_pinned
, force_remove)
      File "C:\Anaconda3\lib\site-packages\conda\core\solve.py", line 148, in so
lve_final_state
        specs_from_history_map = History(self.prefix).get_requested_specs_map()
      File "C:\Anaconda3\lib\site-packages\conda\history.py", line 230, in get_r
equested_specs_map
        for request in self.get_user_requests():
      File "C:\Anaconda3\lib\site-packages\conda\history.py", line 215, in get_u
ser_requests
        comment_items = self._parse_comment_line(line)
      File "C:\Anaconda3\lib\site-packages\conda\history.py", line 191, in _pars
e_comment_line
        specs = History._parse_old_format_specs_string(specs_string)
      File "C:\Anaconda3\lib\site-packages\conda\history.py", line 158, in _pars
e_old_format_specs_string
        specs[-1] = ','.join([specs[-1], spec])
    IndexError: list index out of range

`$ C:\Anaconda3\Scripts\conda update conda`

  environment variables:
                 CIO_TEST=<not set>
               CONDA_ROOT=C:\Anaconda3
                 HOMEPATH=\
                     PATH=C:\Anaconda3\Library\bin;C:\Program Files (x86)\Common                          Files\Oracle\Java\javapath;C:\Program Files\ImageMagic
k-7.0.7-Q16;C:\A
                          naconda3;C:\Anaconda3\Library\mingw-w64\bin;C:\Anacond
a3\Library\usr\b
                          in;C:\Anaconda3\Library\bin;C:\Anaconda3\Scripts;C:\Pr
ogram
                          Files\ImageMagick-7.0.6-Q16;C:\Program Files
                          (x86)\ImageMagick-7.0.5-Q16;C:\Program Files
                          (x86)\ImageMagick-7.0.4-Q16;C:\Program Files
                          (x86)\ImageMagick-7.0.3-Q16;C:\Program Files (x86)\Ima
geMagick-6.8.6-Q
                          16;C:\Windows\system32;C:\Windows;C:\Windows\System32\
Wbem;C:\Windows\
                          System32\WindowsPowerShell\v1.0\;C:\Program Files (x86
)\QS QualitySoft
                          GmbH\QS-Barcode OCX 4.9;C:\Windows\System32\WindowsPow
erShell\v1.0\;C:
                          \Windows\System32\WindowsPowerShell\v1.0\;C:\Program F
iles\MiKTeX 2.9\
                          miktex\bin\x64\;C:\Windows\System32\WindowsPowerShell\
v1.0\;C:\Program
                          Files (x86)\Common Files\Microsoft Shared\OFFICE15
             PSMODULEPATH=C:\Windows\system32\WindowsPowerShell\v1.0\Modules\;C:
\Program
                          Files\Microsoft Application Virtualization\Client\
               PYTHONPATH=G:\QPS\11-Software\03-Python\01-QPS
                          FRAMEWORK\03-STRATEGIES;G:\QPS\10-Sandbox\02-Mitarbeit
er\RMoo\01 - QPS
                          FRAMEWORK\11-STRATEGIES;G:\QPS\10-Sandbox\02-Mitarbeit
er\RMoo\01 - QPS
                          FRAMEWORK\01-CORE
       REQUESTS_CA_BUNDLE=<not set>
            SSL_CERT_FILE=<not set>

     active environment : None
       user config file : C:\Users\username\.condarc
 populated config files : C:\Anaconda3\.condarc
                          C:\Users\username\.condarc
          conda version : 4.5.5
    conda-build version : not installed
         python version : 3.6.2.final.0
       base environment : C:\Anaconda3  (writable)
           channel URLs : https://repo.anaconda.com/pkgs/main/win-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/free/win-64
                          https://repo.anaconda.com/pkgs/free/noarch
                          https://repo.anaconda.com/pkgs/r/win-64
                          https://repo.anaconda.com/pkgs/r/noarch
                          https://repo.anaconda.com/pkgs/pro/win-64
                          https://repo.anaconda.com/pkgs/pro/noarch
                          https://repo.anaconda.com/pkgs/msys2/win-64
                          https://repo.anaconda.com/pkgs/msys2/noarch
          package cache : C:\Anaconda3\pkgs
                          C:\Users\rmoo\AppData\Local\conda\conda\pkgs
       envs directories : C:\Anaconda3\envs
                          C:\Users\rmoo\AppData\Local\conda\conda\envs
                          C:\Users\rmoo\.conda\envs
               platform : win-64
             user-agent : conda/4.5.5 requests/2.14.2 CPython/3.6.2 Windows/7 Wi
ndows/6.1.7601
          administrator : False
             netrc file : None
           offline mode : False


An unexpected error has occurred. Conda has prepared the above report.

The problem seems to be the line
specs[-1] = ','.join([specs[-1], spec]) in history.py, which fails if specs = []




Discussion:
At your command prompt, can you please provide the output of

    type C:\Anaconda3\conda-meta\history

or alternately just attach to this issue the file `C:\Anaconda3\conda-meta\history`

Resolving patch:
diff --git a/conda/history.py b/conda/history.py
--- a/conda/history.py
+++ b/conda/history.py
@@ -8,15 +8,17 @@
 from os.path import isdir, isfile, join
 import re
 import sys
-import time
 import warnings
 
+import time
+
 from .base.constants import DEFAULTS_CHANNEL_NAME
 from .common.compat import ensure_text_type, iteritems, open, text_type
 from .core.prefix_data import PrefixData, linked
 from .exceptions import CondaFileIOError, CondaHistoryError
 from .gateways.disk.update import touch
 from .models.dist import Dist
+from .models.version import version_relation_re
 from .resolve import MatchSpec
 
 try:
@@ -148,15 +150,12 @@ def _parse_old_format_specs_string(specs_string):
         """
         specs = []
         for spec in specs_string.split(','):
-            # See https://github.com/conda/conda/issues/6691
-            if spec[0].isalpha():
-                # A valid spec starts with a letter since it is a package name
-                specs.append(spec)
-            else:
-                # Otherwise it is a condition and has to be appended to the
-                # last valid spec on the specs list
+            # If the spec starts with a version qualifier, then it actually belongs to the
+            # previous spec. But don't try to join if there was no previous spec.
+            if version_relation_re.match(spec) and specs:
                 specs[-1] = ','.join([specs[-1], spec])
-
+            else:
+                specs.append(spec)
         return specs
 
     @classmethod


Test patch:
diff --git a/tests/test_history.py b/tests/test_history.py
--- a/tests/test_history.py
+++ b/tests/test_history.py
@@ -1,23 +1,24 @@
 from os.path import dirname
+from pprint import pprint
 import unittest
 
 from .decorators import skip_if_no_mock
 from .helpers import mock
 from .test_create import make_temp_prefix
 
-from conda import history
+from conda.history import History
 from conda.resolve import MatchSpec
 
 
 class HistoryTestCase(unittest.TestCase):
     def test_works_as_context_manager(self):
-        h = history.History("/path/to/prefix")
+        h = History("/path/to/prefix")
         self.assertTrue(getattr(h, '__enter__'))
         self.assertTrue(getattr(h, '__exit__'))
 
     @skip_if_no_mock
     def test_calls_update_on_exit(self):
-        h = history.History("/path/to/prefix")
+        h = History("/path/to/prefix")
         with mock.patch.object(h, 'init_log_file') as init_log_file:
             init_log_file.return_value = None
             with mock.patch.object(h, 'update') as update:
@@ -28,7 +29,7 @@ def test_calls_update_on_exit(self):
 
     @skip_if_no_mock
     def test_returns_history_object_as_context_object(self):
-        h = history.History("/path/to/prefix")
+        h = History("/path/to/prefix")
         with mock.patch.object(h, 'init_log_file') as init_log_file:
             init_log_file.return_value = None
             with mock.patch.object(h, 'update'):
@@ -37,8 +38,8 @@ def test_returns_history_object_as_context_object(self):
 
     @skip_if_no_mock
     def test_empty_history_check_on_empty_env(self):
-        with mock.patch.object(history.History, 'file_is_empty') as mock_file_is_empty:
-            with history.History(make_temp_prefix()) as h:
+        with mock.patch.object(History, 'file_is_empty') as mock_file_is_empty:
+            with History(make_temp_prefix()) as h:
                 self.assertEqual(mock_file_is_empty.call_count, 0)
             self.assertEqual(mock_file_is_empty.call_count, 0)
             assert h.file_is_empty()
@@ -47,8 +48,8 @@ def test_empty_history_check_on_empty_env(self):
 
     @skip_if_no_mock
     def test_parse_on_empty_env(self):
-        with mock.patch.object(history.History, 'parse') as mock_parse:
-            with history.History(make_temp_prefix()) as h:
+        with mock.patch.object(History, 'parse') as mock_parse:
+            with History(make_temp_prefix()) as h:
                 self.assertEqual(mock_parse.call_count, 0)
                 self.assertEqual(len(h.parse()), 0)
         self.assertEqual(len(h.parse()), 1)
@@ -56,7 +57,7 @@ def test_parse_on_empty_env(self):
 
 class UserRequestsTestCase(unittest.TestCase):
 
-    h = history.History(dirname(__file__))
+    h = History(dirname(__file__))
     user_requests = h.get_user_requests()
 
     def test_len(self):
@@ -88,25 +89,97 @@ def test_conda_comment_version_parsin(self):
             "# conda version: 4.5.1dev0",
         ]
         for line in test_cases:
-            item = history.History._parse_comment_line(line)
+            item = History._parse_comment_line(line)
             assert not item
 
-    def test_action_command_comment_parsing(self):
-        test_cases = [
-            # New format (>=4.5)
-            "# update specs: [\"param[version='>=1.5.1,<2.0']\"]",
-            # Old format (<4.5)
-            '# install specs: param >=1.5.1,<2.0',
-            '# install specs: python>=3.5.1,jupyter >=1.0.0,<2.0,matplotlib >=1.5.1,<2.0,numpy >=1.11.0,<2.0,pandas >=0.19.2,<1.0,psycopg2 >=2.6.1,<3.0,pyyaml >=3.12,<4.0,scipy >=0.17.0,<1.0',
-        ]
-        for line in test_cases:
-            item = history.History._parse_comment_line(line)
-            specs = item.get('specs')
-            for spec in specs:
-                try:
-                    MatchSpec(spec)
-                except Exception as e:
-                    print('Specs item:', item)
-                    print('Specs:', specs)
-                    print('Invalid Spec:', spec)
-                    raise Exception(e)
+    def test_specs_line_parsing_44(self):
+        # New format (>=4.4)
+        item = History._parse_comment_line("# update specs: [\"param[version='>=1.5.1,<2.0']\"]")
+        pprint(item)
+        assert item == {
+            "action": "update",
+            "specs": [
+                "param[version='>=1.5.1,<2.0']",
+            ],
+            "update_specs": [
+                "param[version='>=1.5.1,<2.0']",
+            ],
+        }
+
+    def test_specs_line_parsing_43(self):
+        # Old format (<4.4)
+        item = History._parse_comment_line('# install specs: param >=1.5.1,<2.0')
+        pprint(item)
+        assert item == {
+            'action': 'install',
+            'specs': [
+                'param >=1.5.1,<2.0',
+            ],
+            'update_specs': [
+                'param >=1.5.1,<2.0',
+            ],
+        }
+
+        item = History._parse_comment_line('# install specs: param >=1.5.1,<2.0,0packagename >=1.0.0,<2.0')
+        pprint(item)
+        assert item == {
+            'action': 'install',
+            'specs': [
+                'param >=1.5.1,<2.0',
+                '0packagename >=1.0.0,<2.0',
+            ],
+            'update_specs': [
+                'param >=1.5.1,<2.0',
+                '0packagename >=1.0.0,<2.0',
+            ],
+        }
+
+        item = History._parse_comment_line('# install specs: python>=3.5.1,jupyter >=1.0.0,<2.0,matplotlib >=1.5.1,<2.0,numpy >=1.11.0,<2.0,pandas >=0.19.2,<1.0,psycopg2 >=2.6.1,<3.0,pyyaml >=3.12,<4.0,scipy >=0.17.0,<1.0')
+        pprint(item)
+        assert item == {
+            'action': 'install',
+            'specs': [
+                'python>=3.5.1',
+                'jupyter >=1.0.0,<2.0',
+                'matplotlib >=1.5.1,<2.0',
+                'numpy >=1.11.0,<2.0',
+                'pandas >=0.19.2,<1.0',
+                'psycopg2 >=2.6.1,<3.0',
+                'pyyaml >=3.12,<4.0',
+                'scipy >=0.17.0,<1.0',
+            ],
+            'update_specs': [
+                'python>=3.5.1',
+                'jupyter >=1.0.0,<2.0',
+                'matplotlib >=1.5.1,<2.0',
+                'numpy >=1.11.0,<2.0',
+                'pandas >=0.19.2,<1.0',
+                'psycopg2 >=2.6.1,<3.0',
+                'pyyaml >=3.12,<4.0',
+                'scipy >=0.17.0,<1.0',
+            ],
+        }
+
+        item = History._parse_comment_line('# install specs: _license >=1.0.0,<2.0')
+        pprint(item)
+        assert item == {
+            'action': 'install',
+            'specs': [
+                '_license >=1.0.0,<2.0',
+            ],
+            'update_specs': [
+                '_license >=1.0.0,<2.0',
+            ],
+        }
+
+        item = History._parse_comment_line('# install specs: pandas,_license >=1.0.0,<2.0')
+        pprint(item)
+        assert item == {
+            'action': 'install',
+            'specs': [
+                'pandas', '_license >=1.0.0,<2.0',
+            ],
+            'update_specs': [
+                'pandas', '_license >=1.0.0,<2.0',
+            ],
+        }