mirror of
https://github.com/kopia/kopia.git
synced 2026-01-24 14:28:06 -05:00
* refactor(repository): ensure we always parse content.ID and object.ID
This changes the types to be incompatible with string to prevent direct
conversion to and from string.
This has the additional benefit of reducing number of memory allocations
and bytes for all IDs.
content.ID went from 2 allocations to 1:
typical case 32 characters + 16 bytes per-string overhead
worst-case 65 characters + 16 bytes per-string overhead
now: 34 bytes
object.ID went from 2 allocations to 1:
typical case 32 characters + 16 bytes per-string overhead
worst-case 65 characters + 16 bytes per-string overhead
now: 36 bytes
* move index.{ID,IDRange} methods to separate files
* replaced index.IDFromHash with content.IDFromHash externally
* minor tweaks and additional tests
* Update repo/content/index/id_test.go
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
* Update repo/content/index/id_test.go
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
* pr feedback
* post-merge fixes
* pr feedback
* pr feedback
* fixed subtle regression in sortedContents()
This was actually not producing invalid results because of how base36
works, just not sorting as efficiently as it could.
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
120 lines
3.8 KiB
Go
120 lines
3.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/manifest"
|
|
"github.com/kopia/kopia/repo/object"
|
|
"github.com/kopia/kopia/snapshot"
|
|
)
|
|
|
|
type commandSnapshotDelete struct {
|
|
snapshotDeleteIDs []string
|
|
snapshotDeleteConfirm bool
|
|
snapshotDeleteAllSnapshotsForSource bool
|
|
}
|
|
|
|
func (c *commandSnapshotDelete) setup(svc appServices, parent commandParent) {
|
|
cmd := parent.Command("delete", "Explicitly delete a snapshot by providing a snapshot ID.")
|
|
cmd.Arg("id", "Snapshot ID or root object ID to be deleted").Required().StringsVar(&c.snapshotDeleteIDs)
|
|
cmd.Flag("all-snapshots-for-source", "Delete all snapshots for a source").BoolVar(&c.snapshotDeleteAllSnapshotsForSource)
|
|
cmd.Flag("delete", "Confirm deletion").BoolVar(&c.snapshotDeleteConfirm)
|
|
// hidden flag for backwards compatibility
|
|
cmd.Flag("unsafe-ignore-source", "Alias for --delete").Hidden().BoolVar(&c.snapshotDeleteConfirm)
|
|
cmd.Action(svc.repositoryWriterAction(c.run))
|
|
}
|
|
|
|
func (c *commandSnapshotDelete) run(ctx context.Context, rep repo.RepositoryWriter) error {
|
|
if c.snapshotDeleteAllSnapshotsForSource {
|
|
return c.snapshotDeleteSources(ctx, rep)
|
|
}
|
|
|
|
for _, id := range c.snapshotDeleteIDs {
|
|
m, err := snapshot.LoadSnapshot(ctx, rep, manifest.ID(id))
|
|
if err == nil {
|
|
// snapshot found by manifest ID, delete it directly.
|
|
if err = c.deleteSnapshot(ctx, rep, m); err != nil {
|
|
return errors.Wrapf(err, "error deleting %v", id)
|
|
}
|
|
} else if !errors.Is(err, snapshot.ErrSnapshotNotFound) {
|
|
return errors.Wrapf(err, "error loading snapshot %v", id)
|
|
} else if err := c.deleteSnapshotsByRootObjectID(ctx, rep, id); err != nil {
|
|
return errors.Wrapf(err, "error deleting snapshots by root ID %v", id)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandSnapshotDelete) snapshotDeleteSources(ctx context.Context, rep repo.RepositoryWriter) error {
|
|
for _, source := range c.snapshotDeleteIDs {
|
|
si, err := snapshot.ParseSourceInfo(source, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid source '%s'", source)
|
|
}
|
|
|
|
manifestIDs, err := snapshot.ListSnapshotManifests(ctx, rep, &si, nil)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error listing manifests for %v", si)
|
|
}
|
|
|
|
manifests, err := snapshot.LoadSnapshots(ctx, rep, manifestIDs)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error loading manifests for %v", si)
|
|
}
|
|
|
|
if len(manifests) == 0 {
|
|
return errors.Errorf("no snapshots for source %v", si)
|
|
}
|
|
|
|
for _, m := range manifests {
|
|
if err := c.deleteSnapshot(ctx, rep, m); err != nil {
|
|
return errors.Wrap(err, "error deleting")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandSnapshotDelete) deleteSnapshot(ctx context.Context, rep repo.RepositoryWriter, m *snapshot.Manifest) error {
|
|
desc := fmt.Sprintf("snapshot %v of %v at %v", m.ID, m.Source, formatTimestamp(m.StartTime))
|
|
|
|
if !c.snapshotDeleteConfirm {
|
|
log(ctx).Infof("Would delete %v (pass --delete to confirm)", desc)
|
|
return nil
|
|
}
|
|
|
|
log(ctx).Infof("Deleting %v...", desc)
|
|
|
|
return errors.Wrap(rep.DeleteManifest(ctx, m.ID), "error deleting manifest")
|
|
}
|
|
|
|
func (c *commandSnapshotDelete) deleteSnapshotsByRootObjectID(ctx context.Context, rep repo.RepositoryWriter, rootID string) error {
|
|
rootOID, err := object.ParseID(rootID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid object ID")
|
|
}
|
|
|
|
manifests, err := snapshot.FindSnapshotsByRootObjectID(ctx, rep, rootOID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to find snapshots by root %v", rootID)
|
|
}
|
|
|
|
if len(manifests) == 0 {
|
|
return errors.Errorf("no snapshots matched %v", rootID)
|
|
}
|
|
|
|
for _, m := range manifests {
|
|
if err := c.deleteSnapshot(ctx, rep, m); err != nil {
|
|
return errors.Wrap(err, "error deleting")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|