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
Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions poetry/console/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class InstallCommand(EnvCommand):
"Output the operations but do not execute anything "
"(implicitly enables --verbose).",
),
option(
"remove-untracked", None, "Removes packages not present in the lock file.",
),
option(
"extras",
"E",
Expand Down Expand Up @@ -58,6 +61,7 @@ def handle(self):
installer.extras(extras)
installer.dev_mode(not self.option("no-dev"))
installer.dry_run(self.option("dry-run"))
installer.remove_untracked(self.option("remove-untracked"))
installer.verbose(self.option("verbose"))

return_code = installer.run()
Expand Down
22 changes: 22 additions & 0 deletions poetry/installation/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -39,6 +40,7 @@ def __init__(
self._pool = pool

self._dry_run = False
self._remove_untracked = False
self._update = False
self._verbose = False
self._write_lock = True
Expand Down Expand Up @@ -83,6 +85,14 @@ def dry_run(self, dry_run=True): # type: (bool) -> Installer
def is_dry_run(self): # type: () -> bool
return self._dry_run

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

return self

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

def verbose(self, verbose=True): # type: (bool) -> Installer
self._verbose = verbose

Expand Down Expand Up @@ -224,6 +234,18 @@ def _do_install(self, local_repo):

ops = solver.solve(use_latest=whitelist)

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
Expand Down
53 changes: 53 additions & 0 deletions tests/installation/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down