Files
kopia/cli/command_snapshot_fix_remove_files.go
maximilize 6c65de1176 fix(cli): only run automatic maintenance after data-modifying operations (#5473)
* fix(cli): only run automatic maintenance after data-modifying operations

Automatic maintenance previously ran after any successful repository-writer
command, including control-only operations such as `policy set`, `acl ...` and
`user ...`. Make repositoryWriterAction skip maintenance by default and add
repositoryWriterActionWithMaintenance, used only by data-modifying commands
(snapshot create/delete/expire/migrate/fix-*/copy-move-history/pin,
manifest delete).

Fixes #3174

* fix(cli): treat snapshot pin and manifest delete as control-only

Per review, move both to repositoryWriterAction so they no longer trigger
opportunistic automatic maintenance. pin orphans no content and manifest
delete is a low-level operation not meant for direct use; any content it
orphans is reclaimed at the next scheduled maintenance.

---------

Co-authored-by: max <max@example.com>
2026-07-08 19:56:58 -07:00

61 lines
1.6 KiB
Go

package cli
import (
"context"
"path"
"slices"
"github.com/pkg/errors"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot"
)
type commandSnapshotFixRemoveFiles struct {
common commonRewriteSnapshots
removeObjectIDs []string
removeFilesByName []string
}
func (c *commandSnapshotFixRemoveFiles) setup(svc appServices, parent commandParent) {
cmd := parent.Command("remove-files", "Remove references to the specified files from snapshots.")
c.common.setup(svc, cmd)
cmd.Flag("object-id", "Remove files by their object ID").StringsVar(&c.removeObjectIDs)
cmd.Flag("filename", "Remove files by filename (wildcards are supported)").StringsVar(&c.removeFilesByName)
cmd.Action(svc.repositoryWriterActionWithMaintenance(c.run))
}
func (c *commandSnapshotFixRemoveFiles) rewriteEntry(ctx context.Context, pathFromRoot string, ent *snapshot.DirEntry) (*snapshot.DirEntry, error) {
if slices.Contains(c.removeObjectIDs, ent.ObjectID.String()) {
log(ctx).Infof("will remove file %v", pathFromRoot)
return nil, nil
}
for _, n := range c.removeFilesByName {
matched, err := path.Match(n, ent.Name)
if err != nil {
return nil, errors.Wrap(err, "invalid wildcard")
}
if matched {
log(ctx).Infof("will remove file %v", pathFromRoot)
return nil, nil
}
}
return ent, nil
}
func (c *commandSnapshotFixRemoveFiles) run(ctx context.Context, rep repo.RepositoryWriter) error {
if len(c.removeObjectIDs)+len(c.removeFilesByName) == 0 {
return errors.New("must specify files to remove")
}
return c.common.rewriteMatchingSnapshots(ctx, rep, c.rewriteEntry)
}