Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a --remove-untracked option to the install command. #2172

Merged
merged 13 commits into from
May 1, 2020
7 changes: 7 additions & 0 deletions docs/docs/cli.md
Original file line number Diff line number Diff line change
@@ -116,6 +116,13 @@ the `--no-dev` option.
poetry install --no-dev
```

If you want to remove old dependencies no longer present in the lock file, use the
`--remove-untracked` option.

```bash
poetry install --remove-untracked
```

You can also specify the extras you want installed
by passing the `--E|--extras` option (See [Extras](#extras) for more info)

6 changes: 2 additions & 4 deletions poetry/console/commands/install.py
Original file line number Diff line number Diff line change
@@ -20,9 +20,7 @@ class InstallCommand(EnvCommand):
"(implicitly enables --verbose).",
),
option(
"keep-untracked",
None,
"Does not remove packages not present in the lock file.",
"remove-untracked", None, "Removes packages not present in the lock file.",
),
option(
"extras",
@@ -63,7 +61,7 @@ def handle(self):
installer.extras(extras)
installer.dev_mode(not self.option("no-dev"))
installer.dry_run(self.option("dry-run"))
installer.keep_untracked(self.option("keep-untracked"))
installer.remove_untracked(self.option("remove-untracked"))
installer.verbose(self.option("verbose"))

return_code = installer.run()
34 changes: 18 additions & 16 deletions poetry/installation/installer.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
from poetry.puzzle.operations import Uninstall
from poetry.puzzle.operations import Update
from poetry.puzzle.operations.operation import Operation
from poetry.puzzle.provider import Provider
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository
@@ -38,7 +39,7 @@ def __init__(
self._pool = pool

self._dry_run = False
self._keep_untracked = False
self._remove_untracked = False
self._update = False
self._verbose = False
self._write_lock = True
@@ -83,13 +84,13 @@ def dry_run(self, dry_run=True): # type: (bool) -> Installer
def is_dry_run(self): # type: () -> bool
return self._dry_run

def keep_untracked(self, keep_untracked=True): # type: (bool) -> Installer
self._keep_untracked = keep_untracked
def remove_untracked(self, remove_untracked=True): # type: (bool) -> Installer
self._remove_untracked = remove_untracked

return self

def is_keep_untracked(self): # type: () -> bool
return self._keep_untracked
def is_remove_untracked(self): # type: () -> bool
return self._remove_untracked

def verbose(self, verbose=True): # type: (bool) -> Installer
self._verbose = verbose
@@ -205,6 +206,18 @@ def _do_install(self, local_repo):
root = root.clone()
del root.dev_requires[:]

if self._remove_untracked:
locked_names = {locked.name for locked in locked_repository.packages}

for installed in self._installed_repository.packages:
if installed.name == self._package.name:
continue
if installed.name in Provider.UNSAFE_PACKAGES:
# Never remove pip, setuptools etc.
continue
if installed.name not in locked_names:
ops.append(Uninstall(installed))

# We need to filter operations so that packages
# not compatible with the current system,
# or optional and not requested, are dropped
@@ -405,17 +418,6 @@ def _get_operations_from_lock(

ops.append(op)

if not self._keep_untracked:
for installed in installed_repo.packages:
is_in_lock_file = False
for locked in locked_repository.packages:
if locked.name == installed.name:
is_in_lock_file = True
break

if not is_in_lock_file:
ops.append(Uninstall(installed))

return ops

def _filter_operations(
53 changes: 53 additions & 0 deletions tests/installation/test_installer.py
Original file line number Diff line number Diff line change
@@ -295,6 +295,59 @@ def test_run_install_no_dev(installer, locker, repo, package, installed):
assert len(removals) == 1


def test_run_install_remove_untracked(installer, locker, repo, package, installed):
locker.locked(True)
locker.mock_lock_data(
{
"package": [
{
"name": "a",
"version": "1.0",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
}
],
"metadata": {
"python-versions": "*",
"platform": "*",
"content-hash": "123456789",
"hashes": {"a": []},
},
}
)
package_a = get_package("a", "1.0")
package_b = get_package("b", "1.1")
package_c = get_package("c", "1.2")
package_pip = get_package("pip", "20.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_pip)

installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
installed.add_package(package_pip) # Always required and never removed.
installed.add_package(package) # Root package never removed.

package.add_dependency("A", "~1.0")

installer.dev_mode(True).remove_untracked(True)
installer.run()

installs = installer.installer.installs
assert len(installs) == 0

updates = installer.installer.updates
assert len(updates) == 0

removals = installer.installer.removals
assert set(r.name for r in removals) == {"b", "c"}


def test_run_whitelist_add(installer, locker, repo, package):
locker.locked(True)
locker.mock_lock_data(