Skip to content

Commit 75fbd4e

Browse files
committed
Add a dfetch remove command
Fixes #26
1 parent 8cfe9ea commit 75fbd4e

15 files changed

Lines changed: 585 additions & 1 deletion

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Release 0.14.0 (unreleased)
44
* Use github purl, repo and version for a github release archive in SBOM (#1063)
55
* Allow ``dfetch freeze`` to accept project names to freeze only specific projects (#1063)
66
* Edit manifest in-place when freezing inside a git or SVN superproject, preserving comments and layout (#1063)
7+
* Add new ``remove`` command to remove projects from manifest and disk (#26)
78

89
Release 0.13.0 (released 2026-03-30)
910
====================================

dfetch/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import dfetch.commands.freeze
1818
import dfetch.commands.import_
1919
import dfetch.commands.init
20+
import dfetch.commands.remove
2021
import dfetch.commands.report
2122
import dfetch.commands.update
2223
import dfetch.commands.update_patch
@@ -52,6 +53,7 @@ def create_parser() -> argparse.ArgumentParser:
5253
dfetch.commands.freeze.Freeze.create_menu(subparsers)
5354
dfetch.commands.import_.Import.create_menu(subparsers)
5455
dfetch.commands.init.Init.create_menu(subparsers)
56+
dfetch.commands.remove.Remove.create_menu(subparsers)
5557
dfetch.commands.report.Report.create_menu(subparsers)
5658
dfetch.commands.update.Update.create_menu(subparsers)
5759
dfetch.commands.update_patch.UpdatePatch.create_menu(subparsers)

dfetch/commands/remove.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Remove projects from the manifest and delete their directories.
2+
3+
Use ``dfetch remove <project>`` to remove one or more projects.
4+
See :ref:`remove-a-project` for the full guide.
5+
6+
.. scenario-include:: ../features/remove-project.feature
7+
8+
"""
9+
10+
import argparse
11+
import shutil
12+
13+
import dfetch.commands.command
14+
from dfetch.log import get_logger
15+
from dfetch.manifest.manifest import RequestedProjectNotFoundError
16+
from dfetch.project import create_super_project
17+
from dfetch.project.superproject import NoVcsSuperProject
18+
from dfetch.util.util import in_directory, safe_rm
19+
20+
logger = get_logger(__name__)
21+
22+
23+
class Remove(dfetch.commands.command.Command):
24+
"""Remove a project from the manifest and delete its directory.
25+
26+
Edits the manifest in-place when the manifest lives inside a git or SVN
27+
superproject to preserve comments and layout. When the manifest is not
28+
inside version control, a ``.backup`` copy of the manifest is written
29+
before updating it.
30+
"""
31+
32+
@staticmethod
33+
def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None:
34+
"""Add the menu for the remove action."""
35+
parser = dfetch.commands.command.Command.parser(subparsers, Remove)
36+
parser.add_argument(
37+
"projects",
38+
metavar="<project>",
39+
type=str,
40+
nargs="+",
41+
help="Specific project(s) to remove",
42+
)
43+
44+
def __call__(self, args: argparse.Namespace) -> None:
45+
"""Perform the remove action."""
46+
superproject = create_super_project()
47+
make_backup = isinstance(superproject, NoVcsSuperProject)
48+
49+
with in_directory(superproject.root_directory):
50+
manifest_path = superproject.manifest.path
51+
52+
# Pre-validate all projects and collect their destinations
53+
projects_to_remove = []
54+
for project in args.projects:
55+
try:
56+
project_entries = superproject.manifest.selected_projects([project])
57+
destination = project_entries[0].destination
58+
projects_to_remove.append((project, destination))
59+
except RequestedProjectNotFoundError:
60+
logger.print_info_line(
61+
project, f"project '{project}' not found in manifest"
62+
)
63+
64+
if not projects_to_remove:
65+
return # Nothing to do
66+
67+
# Create backup once if any projects will be removed
68+
if make_backup:
69+
shutil.copyfile(manifest_path, manifest_path + ".backup")
70+
71+
# Remove all projects from manifest in-memory
72+
for project, _ in projects_to_remove:
73+
superproject.manifest.remove(project)
74+
75+
# Persist the manifest changes
76+
superproject.manifest.update_dump()
77+
78+
# Only after successful persistence, perform filesystem deletions and logging
79+
for project, destination in projects_to_remove:
80+
safe_rm(destination)
81+
logger.print_info_line(project, "removed")

dfetch/manifest/manifest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,19 @@ def selected_projects(self, names: Sequence[str]) -> Sequence[ProjectEntry]:
322322
self._check_all_names_found(names, projects)
323323
return projects
324324

325+
def remove(self, project_name: str) -> None:
326+
"""Remove a project from the manifest."""
327+
if project_name not in self._projects:
328+
possibles = [project.name for project in self._projects.values()]
329+
raise RequestedProjectNotFoundError([project_name], possibles)
330+
331+
del self._projects[project_name]
332+
333+
for index, project in enumerate(self._doc["manifest"]["projects"]):
334+
if project["name"].data == project_name:
335+
del self._doc["manifest"]["projects"][index]
336+
break
337+
325338
def _check_all_names_found(
326339
self, names: Sequence[str], projects: Sequence[ProjectEntry]
327340
) -> None:

doc/asciicasts/remove.cast

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{"version": 2, "width": 141, "height": 38, "timestamp": 1775680966, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}}
2+
[3.908283, "o", "\u001b[H\u001b[2J\u001b[3J"]
3+
[3.917146, "o", "$ "]
4+
[4.920026, "o", "\u001b["]
5+
[5.100307, "o", "1m"]
6+
[5.190507, "o", "ca"]
7+
[5.280647, "o", "t "]
8+
[5.370807, "o", "df"]
9+
[5.460964, "o", "et"]
10+
[5.551105, "o", "ch"]
11+
[5.641276, "o", ".y"]
12+
[5.731408, "o", "am"]
13+
[5.821579, "o", "l\u001b"]
14+
[6.001802, "o", "[0m"]
15+
[7.00335, "o", "\r\n"]
16+
[7.008837, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"]
17+
[7.015158, "o", "$ "]
18+
[8.020859, "o", "\u001b["]
19+
[8.201143, "o", "1m"]
20+
[8.293924, "o", "ls"]
21+
[8.381484, "o", " -"]
22+
[8.471613, "o", "la"]
23+
[8.561783, "o", " c"]
24+
[8.651916, "o", "pp"]
25+
[8.742054, "o", "ut"]
26+
[8.832196, "o", "es"]
27+
[8.923123, "o", "t/"]
28+
[9.103408, "o", "sr"]
29+
[9.193552, "o", "c\u001b"]
30+
[9.283692, "o", "[0"]
31+
[9.373843, "o", "m"]
32+
[10.37565, "o", "\r\n"]
33+
[10.381775, "o", "total 1764\r\ndrwxrwxrwx+ 14 dev dev 4096 Apr 8 20:42 .\r\ndrwxrwxrwx+ 3 dev dev 4096 Apr 8 20:42 ..\r\n-rw-rw-rw- 1 dev dev 2179 Apr 8 20:42 .cdtproject\r\n-rw-rw-rw- 1 dev dev 6175 Apr 8 20:42 .cproject\r\n-rw-rw-rw- 1 dev dev 347 Apr 8 20:42 .dfetch_data.yaml\r\n-rw-rw-rw- 1 dev dev 316 Apr 8 20:42 .gitignore\r\n-rw-rw-rw- 1 dev dev 2286 Apr 8 20:42 .project\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 .settings\r\n-rw-rw-rw- 1 dev dev 533 Apr 8 20:42 .travis.yml\r\n-rw-rw-rw- 1 dev dev 201 Apr 8 20:42 AUTHORS\r\n-rw-rw-rw- 1 dev dev 2462 Apr 8 20:42 CMakeLists.txt\r\n-rw-rw-rw- 1 dev dev 1515 Apr 8 20:42 COPYING\r\n-rw-rw-rw- 1 dev dev 103 Apr 8 20:42 ChangeLog\r\n-rw-rw-rw- 1 dev dev 6458 Apr 8 20:42 CppUTest.dsp\r\n-rw-rw-rw- 1 dev dev 825 Apr 8 20:42 CppUTest.dsw\r\n-rw-rw-rw- 1 dev dev 7455 Apr 8 20:42 CppUTest.mak\r\n-rw-rw-rw- 1 dev dev 9597 Apr 8 20:42 CppUTest.vcproj\r\n-rw-rw-rw- 1 dev dev 11381 Apr 8 20:42 CppUTest.vcxproj\r\n-rw-rw-rw- 1 dev dev 1500 Apr 8 20:42 CppUTest_VS2008.sln\r\n-rw-rw-rw- 1 dev dev 1316 Apr 8 20:42 CppUTest_VS2010.sln\r\n-rw-rw-rw- 1 dev dev 53997 Apr 8 20:42 Doxyfile\r\n-rw-rw-rw- 1 dev dev 15578 Apr 8 20:42 INSTALL\r\n-rw-rw-rw- 1 dev dev 9707 Apr 8 20:42 Makefile.am\r\n-rw-rw-rw- 1 dev dev 248957 Apr 8 20:42 Makefile.in\r\n-rw-rw-rw- 1 dev dev 525 Apr 8 20:42 Makefile_CppUTestExt\r\n-rw-rw-rw- 1 dev dev 5177 Apr 8 20:42 Makefile_using_MakefileWorker\r\n-rw-rw-rw- 1 dev dev 68 Apr 8 20:42 NEWS\r\n-rw-rw-rw- 1 dev dev 85 Apr 8 20:42 README\r\n-rw-rw-rw- 1 dev dev 6777 Apr 8 20:42 README.md\r\n-rw-rw-rw- 1 dev dev 2327 Apr 8 20:42 README_CppUTest_for_C.txt\r\n-rw-rw-rw- 1 dev dev 2291 Apr 8 20:42 README_InstallCppUTest.txt\r\n-rw-rw-rw- 1 dev dev 888 Apr 8 20:42 README_UsersOfPriorVersions.txt\r\n-rw-rw-rw- 1 dev dev 39778 Apr 8 20:42 aclocal.m4\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 build\r\ndrwxrwxrwx+ 3 dev dev 4096 Apr 8 20:42 cmake\r\n-rwxrwxrwx 1 dev dev 3769 Apr 8 20:42 compile\r\n-rwxrwxrwx 1 dev dev 44504 Apr 8 20:42 config.guess\r\n-rw-rw-rw- 1 dev dev 313 Apr 8 20:42 config.h.cmake\r\n-rw-rw-rw- 1 dev dev 5821 Apr 8 20:42 config.h.in\r\n-rwxrwxrwx 1 dev dev 35206 Apr 8 20:42 config.sub\r\n-rwxrwxrwx 1 dev dev 609242 Apr 8 20:42 configure\r\n-rw-rw-rw- 1 dev dev 16153 Apr 8 20:42 configure.ac\r\n-rw-rw-rw- 1 dev dev 67701 Apr 8 20:42 cpputest-hist.txt\r\n-rw-rw-rw- 1 dev dev 298 Apr 8 20:42 cpputest.pc.in\r\n-rw-rw-rw- 1 dev dev 79904 Apr 8 20:42 cpputest_doxy_gen.conf\r\n-rwxrwxrwx 1 dev dev 18615 Apr 8 20:42 depcomp\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 docs\r\ndrwxrwxrwx+ 5 dev dev 4096 Apr 8 20:42 examples\r\ndrwxrwxrwx+ 5 dev dev 4096 Apr 8 20:42 include\r\n-rwxrwxrwx 1 dev dev 13663 Apr 8 20:42 install-sh\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 lib\r\n-rwxrwxrwx 1 dev dev 282967 Apr 8 20:42 ltmain.sh\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 m4\r\n-rw-rw-rw- 1 dev dev 344 Apr 8 20:42 makeVS2008.bat\r\n-rw-rw-rw- 1 dev dev 348 Apr 8 20:42 makeVS2010.bat\r\n-rw-rw-rw- 1 dev dev 533 Apr 8 20:42 makeVc6.bat\r\n-rwxrwxrwx 1 dev dev 11419 Apr 8 20:42 missing\r\ndrwxrwxrwx+ 2 dev dev 4096 Apr 8 20:42 platforms\r\ndrwxrwxrwx+ 7 dev dev 4096 Apr 8 20:42 scripts\r\ndrwxrwxrwx+ 5 dev dev 4096 Apr 8 20:42 src\r\n-rwxrwxrwx 1 dev dev 3977 Apr 8 20:42 test-driver\r\ndrwxrwxrwx+ 3 dev dev 4096 Apr 8 20:42 tests\r\n"]
34+
[10.390098, "o", "$ "]
35+
[11.393098, "o", "\u001b["]
36+
[11.573599, "o", "1m"]
37+
[11.663756, "o", "df"]
38+
[11.753878, "o", "et"]
39+
[11.844029, "o", "ch"]
40+
[11.934154, "o", " r"]
41+
[12.024311, "o", "em"]
42+
[12.11446, "o", "ov"]
43+
[12.204584, "o", "e "]
44+
[12.294755, "o", "cp"]
45+
[12.475004, "o", "pu"]
46+
[12.565138, "o", "te"]
47+
[12.655281, "o", "st"]
48+
[12.745419, "o", "\u001b["]
49+
[12.835556, "o", "0m"]
50+
[13.837403, "o", "\r\n"]
51+
[14.451432, "o", "\u001b[1;34mDfetch (0.13.0)\u001b[0m\r\n"]
52+
[14.469446, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"]
53+
[14.470004, "o", " \u001b[1;34m> removed\u001b[0m\r\n"]
54+
[14.568244, "o", "$ "]
55+
[15.571831, "o", "\u001b["]
56+
[15.752143, "o", "1m"]
57+
[15.842258, "o", "ca"]
58+
[15.932442, "o", "t "]
59+
[16.022561, "o", "df"]
60+
[16.112731, "o", "et"]
61+
[16.202855, "o", "ch"]
62+
[16.293005, "o", ".y"]
63+
[16.383157, "o", "am"]
64+
[16.473281, "o", "l\u001b"]
65+
[16.653586, "o", "[0"]
66+
[16.74371, "o", "m"]
67+
[17.745521, "o", "\r\n"]
68+
[17.748708, "o", "manifest:\r\n version: '0.0' # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"]
69+
[17.753954, "o", "$ "]
70+
[18.757376, "o", "\u001b"]
71+
[18.937652, "o", "[1"]
72+
[19.027838, "o", "ml"]
73+
[19.117972, "o", "s "]
74+
[19.208125, "o", "-l"]
75+
[19.298262, "o", "a "]
76+
[19.388399, "o", "cp"]
77+
[19.478549, "o", "pu"]
78+
[19.568699, "o", "te"]
79+
[19.658854, "o", "st"]
80+
[19.839109, "o", "\u001b"]
81+
[19.929275, "o", "[0"]
82+
[20.019419, "o", "m"]
83+
[21.022025, "o", "\r\n"]
84+
[21.026546, "o", "total 8\r\n"]
85+
[21.027105, "o", "drwxrwxrwx+ 2 dev dev 4096 Apr 8 20:43 .\r\ndrwxrwxrwx+ 4 dev dev 4096 Apr 8 20:42 ..\r\n"]
86+
[24.036633, "o", "$ "]
87+
[24.03876, "o", "\u001b"]
88+
[24.219047, "o", "[1"]
89+
[24.309197, "o", "m\u001b"]
90+
[24.399338, "o", "[0"]
91+
[24.489575, "o", "m"]
92+
[24.490251, "o", "\r\n"]
93+
[24.49335, "o", "/workspaces/dfetch/doc/generate-casts\r\n"]

doc/generate-casts/generate-casts.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ asciinema rec --overwrite -c "./interactive-add-demo.sh" ../asciicasts/interacti
1515
asciinema rec --overwrite -c "./basic-demo.sh" ../asciicasts/basic.cast
1616
asciinema rec --overwrite -c "./init-demo.sh" ../asciicasts/init.cast
1717
asciinema rec --overwrite -c "./add-demo.sh" ../asciicasts/add.cast
18+
asciinema rec --overwrite -c "./remove-demo.sh" ../asciicasts/remove.cast
1819
asciinema rec --overwrite -c "./environment-demo.sh" ../asciicasts/environment.cast
1920
asciinema rec --overwrite -c "./validate-demo.sh" ../asciicasts/validate.cast
2021
asciinema rec --overwrite -c "./check-demo.sh" ../asciicasts/check.cast

doc/generate-casts/remove-demo.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
3+
source ./demo-magic/demo-magic.sh
4+
5+
PROMPT_TIMEOUT=1
6+
7+
# Copy example manifest and fetch projects
8+
mkdir remove
9+
pushd remove
10+
11+
dfetch init
12+
dfetch update
13+
clear
14+
15+
# Run the command
16+
pe "cat dfetch.yaml"
17+
pe "ls -la cpputest/src"
18+
pe "dfetch remove cpputest"
19+
pe "cat dfetch.yaml"
20+
pe "ls -la cpputest"
21+
22+
PROMPT_TIMEOUT=3
23+
wait
24+
25+
pei ""
26+
27+
popd
28+
rm -rf remove

doc/howto/remove-a-project.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.. _remove-a-project:
2+
3+
Remove a project
4+
================
5+
6+
``dfetch remove`` deletes a project from your manifest and removes its
7+
directory from disk. Use it when you no longer need a dependency.
8+
9+
- :ref:`removing-basic` — remove a single project
10+
- :ref:`removing-multiple` — remove several projects at once
11+
- :ref:`removing-backup` — manifest backup behavior
12+
13+
.. _removing-basic:
14+
15+
Removing a single project
16+
-------------------------
17+
18+
Pass the project name to ``dfetch remove``:
19+
20+
.. code-block:: console
21+
22+
$ dfetch remove mylib
23+
24+
*Dfetch* removes the project entry from ``dfetch.yaml`` and deletes the
25+
destination folder. The manifest is updated in-place when inside a Git or
26+
SVN repository, preserving comments and formatting. Outside version control,
27+
a ``.backup`` copy is created first.
28+
29+
.. scenario-include:: ../features/remove-project.feature
30+
31+
.. _removing-multiple:
32+
33+
Removing multiple projects
34+
--------------------------
35+
36+
List multiple project names to remove them all at once:
37+
38+
.. code-block:: console
39+
40+
$ dfetch remove lib1 lib2 lib3
41+
42+
Each project is removed from the manifest and its directory deleted.
43+
44+
.. asciinema:: ../asciicasts/remove.cast
45+
46+
.. _removing-backup:
47+
48+
Manifest backup behavior
49+
------------------------
50+
51+
When your manifest lives inside a Git or SVN repository, ``dfetch remove``
52+
edits it in-place to preserve comments, blank lines, and indentation. When
53+
outside version control (no ``.git`` or ``.svn`` directory), a backup copy
54+
is created as ``dfetch.yaml.backup`` before any changes.
55+
56+
This matches the behavior of ``dfetch freeze`` and other manifest-modifying
57+
commands.

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ upstream. See :ref:`vendoring` for background on the problem this solves.
5151
howto/migration
5252
howto/adding-a-project
5353
howto/updating-projects
54+
howto/remove-a-project
5455
howto/patching
5556
howto/check-ci
5657
howto/sbom

doc/reference/commands.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ Add
5454

5555
.. seealso:: :doc:`../howto/adding-a-project` — walks through adding a new dependency from start to finish.
5656

57+
Remove
58+
------
59+
.. argparse::
60+
:module: dfetch.__main__
61+
:func: create_parser
62+
:prog: dfetch
63+
:path: remove
64+
65+
.. asciinema:: ../asciicasts/remove.cast
66+
67+
.. automodule:: dfetch.commands.remove
68+
69+
.. seealso:: :doc:`../howto/remove-a-project` — how to remove projects from your manifest.
70+
5771
Check
5872
-----
5973
.. argparse::

0 commit comments

Comments
 (0)