diff --git a/poetry/console/commands/lock.py b/poetry/console/commands/lock.py
index ddedb055d87..4157c02c5cc 100644
--- a/poetry/console/commands/lock.py
+++ b/poetry/console/commands/lock.py
@@ -1,3 +1,5 @@
+from cleo import option
+
from .installer_command import InstallerCommand
@@ -6,6 +8,12 @@ class LockCommand(InstallerCommand):
name = "lock"
description = "Locks the project dependencies."
+ options = [
+ option(
+ "no-update", None, "Do not update locked versions, only refresh lock file."
+ ),
+ ]
+
help = """
The lock command reads the pyproject.toml> file from the
current directory, processes it, and locks the dependencies in the poetry.lock>
@@ -21,6 +29,6 @@ def handle(self):
self.poetry.config.get("experimental.new-installer", False)
)
- self._installer.lock()
+ self._installer.lock(update=not self.option("no-update"))
return self._installer.run()
diff --git a/poetry/installation/installer.py b/poetry/installation/installer.py
index f0c9a62d65d..6956ae85fde 100644
--- a/poetry/installation/installer.py
+++ b/poetry/installation/installer.py
@@ -85,6 +85,10 @@ def set_locker(self, locker): # type: (Locker) -> Installer
return self
def run(self):
+ # Check if refresh
+ if not self._update and self._lock and self._locker.is_locked():
+ return self._do_refresh()
+
# Force update if there is no lock file present
if not self._update and not self._locker.is_locked():
self._update = True
@@ -137,11 +141,11 @@ def update(self, update=True): # type: (bool) -> Installer
return self
- def lock(self): # type: () -> Installer
+ def lock(self, update=True): # type: (bool) -> Installer
"""
Prepare the installer for locking only.
"""
- self.update()
+ self.update(update=update)
self.execute_operations(False)
self._lock = True
@@ -173,6 +177,20 @@ def use_executor(self, use_executor=True): # type: (bool) -> Installer
return self
+ def _do_refresh(self):
+ # Checking extras
+ for extra in self._extras:
+ if extra not in self._package.extras:
+ raise ValueError("Extra [{}] is not specified.".format(extra))
+
+ ops = self._get_operations_from_lock(self._locker.locked_repository(True))
+ local_repo = Repository()
+ self._populate_local_repo(local_repo, ops)
+
+ self._write_lock_file(local_repo, force=True)
+
+ return 0
+
def _do_install(self, local_repo):
from poetry.puzzle import Solver
@@ -285,8 +303,8 @@ def _do_install(self, local_repo):
# Execute operations
return self._execute(ops)
- def _write_lock_file(self, repo): # type: (Repository) -> None
- if self._update and self._write_lock:
+ def _write_lock_file(self, repo, force=True): # type: (Repository, bool) -> None
+ if force or (self._update and self._write_lock):
updated_lock = self._locker.set_lock_data(self._package, repo.packages)
if updated_lock:
diff --git a/tests/installation/fixtures/old-lock-refresh.test b/tests/installation/fixtures/old-lock-refresh.test
new file mode 100644
index 00000000000..40e46ebdd3c
--- /dev/null
+++ b/tests/installation/fixtures/old-lock-refresh.test
@@ -0,0 +1,119 @@
+[[package]]
+name = "attrs"
+version = "17.4.0"
+description = "Classes Without Boilerplate"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "zope.interface"]
+docs = ["sphinx", "zope.interface"]
+tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"]
+
+[[package]]
+name = "colorama"
+version = "0.3.9"
+description = "Cross-platform colored terminal text."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "funcsigs"
+version = "1.0.2"
+description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "more-itertools"
+version = "4.1.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = ">=1.0.0,<2.0.0"
+
+[[package]]
+name = "pluggy"
+version = "0.6.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "py"
+version = "1.5.3"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pytest"
+version = "3.5.0"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+colorama = "*"
+funcsigs = {version = "*", markers = "python_version < \"3.0\""}
+more-itertools = ">=4.0.0"
+pluggy = ">=0.5,<0.7"
+py = ">=1.5.0"
+six = ">=1.10.0"
+
+[[package]]
+name = "six"
+version = "1.11.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[metadata]
+lock-version = "1.1"
+python-versions = "*"
+content-hash = "123456789"
+
+[metadata.files]
+attrs = [
+ {file = "attrs-17.4.0-py2.py3-none-any.whl", hash = "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"},
+ {file = "attrs-17.4.0.tar.gz", hash = "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"},
+]
+colorama = [
+ {file = "colorama-0.3.9-py2.py3-none-any.whl", hash = "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda"},
+ {file = "colorama-0.3.9.tar.gz", hash = "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"},
+]
+funcsigs = [
+ {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
+ {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"},
+]
+more-itertools = [
+ {file = "more-itertools-4.1.0.tar.gz", hash = "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"},
+ {file = "more_itertools-4.1.0-py2-none-any.whl", hash = "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e"},
+ {file = "more_itertools-4.1.0-py3-none-any.whl", hash = "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea"},
+]
+pluggy = [
+ {file = "pluggy-0.6.0.tar.gz", hash = "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"},
+]
+py = [
+ {file = "py-1.5.3-py2.py3-none-any.whl", hash = "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"},
+ {file = "py-1.5.3.tar.gz", hash = "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"},
+]
+pytest = [
+ {file = "pytest-3.5.0-py2.py3-none-any.whl", hash = "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c"},
+ {file = "pytest-3.5.0.tar.gz", hash = "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"},
+]
+six = [
+ {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"},
+ {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"},
+]
diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py
index 077f6dcab40..95f98dad185 100644
--- a/tests/installation/test_installer.py
+++ b/tests/installation/test_installer.py
@@ -1772,6 +1772,44 @@ def test_installer_uses_prereleases_if_they_are_compatible(
assert 2 == installer.executor.installations_count
+def test_installer_can_refresh_old_lock_files(locker, package, repo, installed, config):
+ pool = Pool()
+ pool.add_repository(repo)
+
+ locker.locked(True)
+ locker.mock_lock_data(fixture("old-lock"))
+
+ for pkg in locker.locked_repository(with_dev_reqs=True).packages:
+ dependency = Factory.create_dependency(
+ name=pkg.name,
+ constraint=pkg.to_dependency().constraint,
+ category=pkg.category,
+ )
+ package.add_dependency(dependency)
+ repo.add_package(pkg)
+
+ installer = Installer(
+ NullIO(),
+ MockEnv(),
+ package,
+ locker,
+ pool,
+ config,
+ installed=None,
+ executor=Executor(MockEnv(), pool, config, NullIO()),
+ )
+ installer.use_executor()
+
+ installer.lock(update=False)
+ installer.run()
+
+ assert 0 == installer.executor.installations_count
+ assert 0 == installer.executor.updates_count
+ assert 0 == installer.executor.removals_count
+
+ assert locker.written_data == fixture("old-lock-refresh")
+
+
def test_installer_can_handle_old_lock_files(
installer, locker, package, repo, installed, config
):