Add IsHeld method to Holder interface and implement in apt/dnf backends

Co-authored-by: taigrr <8261498+taigrr@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-28 06:24:07 +00:00
parent c1ae8c4fac
commit 135e7c70f4
9 changed files with 70 additions and 0 deletions

View File

@@ -124,6 +124,11 @@ func (a *Apt) ListHeld(ctx context.Context) ([]snack.Package, error) {
return listHeld(ctx)
}
// IsHeld reports whether a specific package is currently held.
func (a *Apt) IsHeld(ctx context.Context, pkg string) (bool, error) {
return isHeld(ctx, pkg)
}
// Autoremove removes packages no longer required as dependencies.
func (a *Apt) Autoremove(ctx context.Context, opts ...snack.Option) error {
a.Lock()

View File

@@ -230,6 +230,16 @@ func TestIntegration_Apt(t *testing.T) {
require.NoError(t, err)
})
t.Run("IsHeld", func(t *testing.T) {
held, err := h.IsHeld(ctx, "tree")
require.NoError(t, err)
assert.True(t, held, "tree should be held")
notHeld, err := h.IsHeld(ctx, "curl")
require.NoError(t, err)
assert.False(t, notHeld, "curl should not be held")
})
t.Run("ListHeld", func(t *testing.T) {
held, err := h.ListHeld(ctx)
require.NoError(t, err)

View File

@@ -159,6 +159,15 @@ func listHeld(ctx context.Context) ([]snack.Package, error) {
return pkgs, nil
}
func isHeld(ctx context.Context, pkg string) (bool, error) {
cmd := exec.CommandContext(ctx, "apt-mark", "showhold", pkg)
out, err := cmd.Output()
if err != nil {
return false, fmt.Errorf("apt-mark showhold %s: %w", pkg, err)
}
return strings.TrimSpace(string(out)) == pkg, nil
}
// --- Cleaner ---
func autoremove(ctx context.Context, opts ...snack.Option) error {

View File

@@ -36,6 +36,10 @@ func listHeld(_ context.Context) ([]snack.Package, error) {
return nil, snack.ErrUnsupportedPlatform
}
func isHeld(_ context.Context, _ string) (bool, error) {
return false, snack.ErrUnsupportedPlatform
}
func autoremove(_ context.Context, _ ...snack.Option) error {
return snack.ErrUnsupportedPlatform
}

View File

@@ -57,6 +57,11 @@ func (d *DNF) ListHeld(ctx context.Context) ([]snack.Package, error) {
return listHeld(ctx, d.v5)
}
// IsHeld reports whether a specific package is currently held.
func (d *DNF) IsHeld(ctx context.Context, pkg string) (bool, error) {
return isHeld(ctx, pkg, d.v5)
}
// Autoremove removes orphaned packages.
func (d *DNF) Autoremove(ctx context.Context, opts ...snack.Option) error {
d.Lock()

View File

@@ -113,6 +113,26 @@ func listHeld(ctx context.Context, v5 bool) ([]snack.Package, error) {
return parseVersionLock(out), nil
}
func isHeld(ctx context.Context, pkg string, v5 bool) (bool, error) {
out, err := run(ctx, []string{"versionlock", "list", pkg}, snack.Options{})
if err != nil {
// versionlock list exits non-zero when no match is found on some versions
return false, nil
}
var pkgs []snack.Package
if v5 {
pkgs = parseVersionLockDNF5(out)
} else {
pkgs = parseVersionLock(out)
}
for _, p := range pkgs {
if p.Name == pkg {
return true, nil
}
}
return false, nil
}
func autoremove(ctx context.Context, opts ...snack.Option) error {
o := snack.ApplyOptions(opts...)
_, err := run(ctx, []string{"autoremove", "-y"}, o)

View File

@@ -36,6 +36,10 @@ func listHeld(_ context.Context, _ bool) ([]snack.Package, error) {
return nil, snack.ErrUnsupportedPlatform
}
func isHeld(_ context.Context, _ string, _ bool) (bool, error) {
return false, snack.ErrUnsupportedPlatform
}
func autoremove(_ context.Context, _ ...snack.Option) error {
return snack.ErrUnsupportedPlatform
}

View File

@@ -225,6 +225,16 @@ func TestIntegration_DNF(t *testing.T) {
t.Skipf("versionlock plugin not available: %v", err)
}
t.Run("IsHeld", func(t *testing.T) {
held, err := h.IsHeld(ctx, "tree")
require.NoError(t, err)
assert.True(t, held, "tree should be held")
notHeld, err := h.IsHeld(ctx, "curl")
require.NoError(t, err)
assert.False(t, notHeld, "curl should not be held")
})
t.Run("ListHeld", func(t *testing.T) {
held, err := h.ListHeld(ctx)
require.NoError(t, err)

View File

@@ -125,6 +125,9 @@ type Holder interface {
// ListHeld returns all currently held/pinned packages.
ListHeld(ctx context.Context) ([]Package, error)
// IsHeld reports whether a specific package is currently held/pinned.
IsHeld(ctx context.Context, pkg string) (bool, error)
}
// Cleaner provides orphan/cache cleanup operations.