Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
delete: add -G --exclude-glob
Baptiste Daroussin committed 18 days ago
commit b41761fadef08e71fc36120f293940e3eb0dc5ec
parent a0c509e
3 files changed +96 -6
modified docs/pkg-delete.8
@@ -14,7 +14,7 @@
.\"
.\"     @(#)pkg.8
.\"
-
.Dd April 8, 2021
+
.Dd April 17, 2026
.Dt PKG-DELETE 8
.Os
.Sh NAME
@@ -24,9 +24,11 @@
.Nm
.Op Fl DfnqRy
.Op Fl Cgix
+
.Op Fl G Ar exclude-glob
.Ar pkg-name ...
.Nm
.Op Fl Dnqy
+
.Op Fl G Ar exclude-glob
.Fl a
.Pp
.Nm
@@ -87,6 +89,13 @@ case sensitive.
If a deinstallation script exists for a given package, do not execute it.
.It Fl f , Cm --force
Forces packages to be removed despite leaving unresolved dependencies.
+
.It Fl G Ar glob , Cm --exclude-glob Ar glob
+
Exclude packages whose names match the given shell glob pattern from
+
deletion.
+
This option may be specified multiple times.
+
It is particularly useful in combination with
+
.Fl a
+
to delete all packages except those matching the exclusion pattern(s).
In combination with the
.Fl a
or
@@ -159,6 +168,12 @@ for further description.
.Sh FILES
See
.Xr pkg.conf 5 .
+
.Sh EXAMPLES
+
Delete all packages except those from the FreeBSD base system:
+
.Dl # pkg delete -af -G \(dqFreeBSD-*\(dq
+
.Pp
+
Delete all packages except base and pkg packages:
+
.Dl # pkg delete -af -G \(dqFreeBSD-*\(dq -G \(dqpkg-*\(dq
.Sh SEE ALSO
.Xr pkg_create 3 ,
.Xr pkg_printf 3 ,
modified src/delete.c
@@ -29,20 +29,22 @@
 */

#include <err.h>
+
#include <fnmatch.h>
#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <pkg.h>
+
#include <xmalloc.h>

#include "pkgcli.h"

void
usage_delete(void)
{
-
	fprintf(stderr, "Usage: pkg delete [-DfnqRy] [--autoremove] [-Cgix] <pkg-name> ...\n");
-
	fprintf(stderr, "       pkg delete [-Dnqy] [--autoremove] -a\n\n");
+
	fprintf(stderr, "Usage: pkg delete [-DfnqRy] [--autoremove] [-Cgix] [-G glob] <pkg-name> ...\n");
+
	fprintf(stderr, "       pkg delete [-Dnqy] [--autoremove] [-G glob] -a\n\n");
	fprintf(stderr, "For more information see 'pkg help delete'.\n");
}

@@ -57,6 +59,7 @@ exec_delete(int argc, char **argv)
	bool		 autoremove_flag = false;
	int		 retcode = EXIT_FAILURE;
	int		 ch;
+
	charv_t		 excludes = vec_init();
	int		 i;
	int		 lock_type = PKGDB_LOCK_ADVISORY;
	int		 locked_pkgs = 0;
@@ -70,6 +73,7 @@ exec_delete(int argc, char **argv)
		{ "case-sensitive",		no_argument,	NULL,	'C' },
		{ "no-scripts",			no_argument,	NULL,	'D' },
		{ "script-no-exec",		no_argument,	&scriptnoexec,	1 },
+
		{ "exclude-glob",		required_argument, NULL, 'G' },
		{ "force",			no_argument,	NULL,	'f' },
		{ "glob",			no_argument,	NULL,	'g' },
		{ "case-insensitive",		no_argument,	NULL,	'i' },
@@ -82,7 +86,7 @@ exec_delete(int argc, char **argv)
	};


-
	while ((ch = getopt_long(argc, argv, "+aCDfginqRxy", longopts, NULL)) != -1) {
+
	while ((ch = getopt_long(argc, argv, "+aCDfG:ginqRxy", longopts, NULL)) != -1) {
		switch (ch) {
		case 'a':
			match = MATCH_ALL;
@@ -97,6 +101,9 @@ exec_delete(int argc, char **argv)
			f |= PKG_FLAG_FORCE;
			force = true;
			break;
+
		case 'G':
+
			vec_push(&excludes, optarg);
+
			break;
		case 'g':
			match = MATCH_GLOB;
			break;
@@ -192,7 +199,49 @@ exec_delete(int argc, char **argv)
		}
	}

-
	if (pkg_jobs_add(jobs, match, argv, argc) == EPKG_FATAL)
+
	/*
+
	 * When exclude globs are given, query all packages and filter
+
	 * out the excluded ones, then pass the remaining names to the
+
	 * solver instead of MATCH_ALL.
+
	 */
+
	charv_t filtered = vec_init();
+
	if (excludes.len > 0) {
+
		struct pkgdb_it *eit;
+
		struct pkg *epkg = NULL;
+
		const char *name;
+

+
		eit = pkgdb_query(db, NULL, MATCH_ALL);
+
		if (eit == NULL)
+
			goto cleanup;
+
		while (pkgdb_it_next(eit, &epkg, PKG_LOAD_BASIC) == EPKG_OK) {
+
			bool excluded = false;
+
			pkg_get(epkg, PKG_ATTR_NAME, &name);
+
			vec_foreach(excludes, ei) {
+
				if (fnmatch(excludes.d[ei], name, 0) == 0) {
+
					excluded = true;
+
					break;
+
				}
+
			}
+
			if (!excluded)
+
				vec_push(&filtered, xstrdup(name));
+
		}
+
		pkgdb_it_free(eit);
+
		pkg_free(epkg);
+

+
		if (filtered.len == 0) {
+
			if (!quiet)
+
				printf("All packages excluded. Nothing to do.\n");
+
			retcode = EXIT_SUCCESS;
+
			vec_free_and_free(&filtered, free);
+
			goto cleanup;
+
		}
+
		if (pkg_jobs_add(jobs, MATCH_EXACT, filtered.d,
+
		    (int)filtered.len) == EPKG_FATAL) {
+
			vec_free_and_free(&filtered, free);
+
			goto cleanup;
+
		}
+
		vec_free_and_free(&filtered, free);
+
	} else if (pkg_jobs_add(jobs, match, argv, argc) == EPKG_FATAL)
		goto cleanup;

	if (pkg_jobs_solve(jobs) != EPKG_OK) {
@@ -260,6 +309,7 @@ exec_delete(int argc, char **argv)
	retcode = EXIT_SUCCESS;

cleanup:
+
	vec_free(&excludes);
	pkgdb_release_lock(db, lock_type);
	pkg_jobs_free(jobs);
	pkgdb_close(db);
modified tests/frontend/delete.sh
@@ -23,7 +23,8 @@ tests_init \
	delete_force_recursive \
	delete_dry_run_no_remove \
	delete_autoremove \
-
	delete_autoremove_flag
+
	delete_autoremove_flag \
+
	delete_exclude_glob

delete_all_body() {
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg "foo" "foo" "1"
@@ -561,3 +562,27 @@ EOF
	atf_check -s exit:1 pkg info -e master
	atf_check -s exit:1 pkg info -e dep
}
+

+
delete_exclude_glob_body()
+
{
+
	# Install 3 packages: base-lib, base-runtime, app
+
	atf_check sh ${RESOURCEDIR}/test_subr.sh new_pkg "base-lib" "base-lib" "1"
+
	atf_check sh ${RESOURCEDIR}/test_subr.sh new_pkg "base-runtime" "base-runtime" "1"
+
	atf_check sh ${RESOURCEDIR}/test_subr.sh new_pkg "app" "app" "1"
+
	atf_check -o ignore pkg register -M base-lib.ucl
+
	atf_check -o ignore pkg register -M base-runtime.ucl
+
	atf_check -o ignore pkg register -M app.ucl
+

+
	# Delete all except base-*
+
	atf_check \
+
		-o match:"Deinstalling app" \
+
		-o not-match:"base-lib" \
+
		-o not-match:"base-runtime" \
+
		-s exit:0 \
+
		pkg delete -yf -a -G "base-*"
+

+
	# base packages should remain, app should be gone
+
	atf_check -s exit:0 pkg info -e base-lib
+
	atf_check -s exit:0 pkg info -e base-runtime
+
	atf_check -s exit:1 pkg info -e app
+
}