Repository: conda/conda

Issue:
conda 4.4 has no difference between 'conda install' and 'conda update'
That is, `conda update` installs packages, even if the package isn't already in the environment.


Discussion:

Resolving patch:
diff --git a/conda/cli/install.py b/conda/cli/install.py
--- a/conda/cli/install.py
+++ b/conda/cli/install.py
@@ -12,17 +12,21 @@
 
 from . import common
 from .common import check_non_admin
+from .. import CondaError
 from .._vendor.auxlib.ish import dals
 from ..base.constants import ROOT_ENV_NAME
 from ..base.context import context, locate_prefix_by_name
 from ..common.compat import on_win, text_type
 from ..core.index import calculate_channel_urls, get_index
+from ..core.linked_data import PrefixData
 from ..core.solve import Solver
 from ..exceptions import (CondaExitZero, CondaImportError, CondaOSError, CondaSystemExit,
                           CondaValueError, DirectoryNotFoundError, DryRunExit,
-                          EnvironmentLocationNotFound, PackagesNotFoundError,
-                          TooManyArgumentsError, UnsatisfiableError)
+                          EnvironmentLocationNotFound,
+                          PackageNotInstalledError, PackagesNotFoundError, TooManyArgumentsError,
+                          UnsatisfiableError)
 from ..misc import clone_env, explicit, touch_nonadmin
+from ..models.match_spec import MatchSpec
 from ..plan import (revert_actions)
 from ..resolve import ResolvePackageNotFound
 
@@ -187,6 +191,18 @@ def install(args, parser, command='install'):
         raise CondaValueError("too few arguments, "
                               "must supply command line package specs or --file")
 
+    # for 'conda update', make sure the requested specs actually exist in the prefix
+    # and that they are name-only specs
+    if isupdate and not args.all:
+        prefix_data = PrefixData(prefix)
+        for spec in specs:
+            spec = MatchSpec(spec)
+            if not spec.is_name_only_spec:
+                raise CondaError("Invalid spec for 'conda update': %s\n"
+                                 "Use 'conda install' instead." % spec)
+            if not prefix_data.get(spec.name, None):
+                raise PackageNotInstalledError(prefix, spec.name)
+
     if newenv and args.clone:
         if args.packages:
             raise TooManyArgumentsError(0, len(args.packages), list(args.packages),
diff --git a/conda/models/match_spec.py b/conda/models/match_spec.py
--- a/conda/models/match_spec.py
+++ b/conda/models/match_spec.py
@@ -189,6 +189,12 @@ def get(self, field_name, default=None):
         v = self.get_raw_value(field_name)
         return default if v is None else v
 
+    @property
+    def is_name_only_spec(self):
+        return (len(self._match_components) == 1
+                and 'name' in self._match_components
+                and self.name != '*')
+
     def dist_str(self):
         return self.__str__()
 


Test patch:
diff --git a/tests/test_create.py b/tests/test_create.py
--- a/tests/test_create.py
+++ b/tests/test_create.py
@@ -42,7 +42,7 @@
 from conda.core.package_cache import PackageCache
 from conda.core.repodata import create_cache_dir
 from conda.exceptions import CommandArgumentError, DryRunExit, OperationNotAllowed, \
-    PackagesNotFoundError, RemoveError, conda_exception_handler
+    PackagesNotFoundError, RemoveError, conda_exception_handler, PackageNotInstalledError
 from conda.gateways.anaconda_client import read_binstar_tokens
 from conda.gateways.disk.create import mkdir_p
 from conda.gateways.disk.delete import rm_rf
@@ -453,6 +453,15 @@ def assert_json_parsable(content):
         finally:
             rmtree(prefix, ignore_errors=True)
 
+    def test_conda_update_package_not_installed(self):
+        with make_temp_env() as prefix:
+            with pytest.raises(PackageNotInstalledError):
+                run_command(Commands.UPDATE, prefix, "sqlite openssl")
+
+            with pytest.raises(CondaError) as conda_error:
+                run_command(Commands.UPDATE, prefix, "conda-forge::*")
+            assert conda_error.value.message.startswith("Invalid spec for 'conda update'")
+
     def test_noarch_python_package_with_entry_points(self):
         with make_temp_env("-c conda-test flask") as prefix:
             py_ver = get_python_version_for_prefix(prefix)