From 6c509f7ac1d73f71c65d5a493334e86143ff9006 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 17:33:27 +0200 Subject: [PATCH 1/2] Correctly pair backend/restic.FileType with APIs Use backend.FileType to interact with the backend. And restic.FileType to interact with the repository. --- cmd/restic/cmd_repair_index_integration_test.go | 3 +-- cmd/restic/integration_helpers_test.go | 4 ++-- cmd/restic/integration_test.go | 16 ++++++++-------- internal/backend/cache/file_test.go | 14 +++++++------- internal/checker/checker_test.go | 12 ++++++------ internal/global/global.go | 2 +- internal/repository/checker_test.go | 6 +++--- internal/repository/lock_file_test.go | 4 ++-- internal/repository/raw_test.go | 8 ++++---- internal/repository/repack_test.go | 4 ++-- internal/repository/repair_index_test.go | 6 +++--- internal/repository/repository_internal_test.go | 2 +- internal/repository/repository_test.go | 14 +++++++------- internal/repository/upgrade_repo.go | 2 +- internal/restic/lister_test.go | 11 +++++------ 15 files changed, 53 insertions(+), 55 deletions(-) diff --git a/cmd/restic/cmd_repair_index_integration_test.go b/cmd/restic/cmd_repair_index_integration_test.go index a93e3df10..1b10f88fd 100644 --- a/cmd/restic/cmd_repair_index_integration_test.go +++ b/cmd/restic/cmd_repair_index_integration_test.go @@ -11,7 +11,6 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/global" - "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -72,7 +71,7 @@ func (b *indexErrorBackend) Load(ctx context.Context, h backend.Handle, length i // protect hasErred b.lock.Lock() defer b.lock.Unlock() - if !b.hasErred && h.Type == restic.IndexFile { + if !b.hasErred && h.Type == backend.IndexFile { b.hasErred = true return consumer(errorReadCloser{rd}) } diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index e2391b8f9..614fa916f 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -299,7 +299,7 @@ func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) { defer unlock() for id := range remove { - rtest.OK(t, be().Remove(ctx, backend.Handle{Type: restic.PackFile, Name: id.String()})) + rtest.OK(t, be().Remove(ctx, backend.Handle{Type: backend.PackFile, Name: id.String()})) } return nil }) @@ -329,7 +329,7 @@ func removePacksExcept(gopts global.Options, t testing.TB, keep restic.IDSet, re if treePacks.Has(id) != removeTreePacks || keep.Has(id) { return nil } - return be().Remove(ctx, backend.Handle{Type: restic.PackFile, Name: id.String()}) + return be().Remove(ctx, backend.Handle{Type: backend.PackFile, Name: id.String()}) }) }) rtest.OK(t, err) diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index de3000527..3bda0dc7d 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -46,14 +46,14 @@ func TestCheckRestoreNoLock(t *testing.T) { // is expected by the first listing + some operations. type listOnceBackend struct { backend.Backend - listedFileType map[restic.FileType]bool + listedFileType map[backend.FileType]bool strictOrder bool } func newListOnceBackend(be backend.Backend) *listOnceBackend { return &listOnceBackend{ Backend: be, - listedFileType: make(map[restic.FileType]bool), + listedFileType: make(map[backend.FileType]bool), strictOrder: false, } } @@ -61,16 +61,16 @@ func newListOnceBackend(be backend.Backend) *listOnceBackend { func newOrderedListOnceBackend(be backend.Backend) *listOnceBackend { return &listOnceBackend{ Backend: be, - listedFileType: make(map[restic.FileType]bool), + listedFileType: make(map[backend.FileType]bool), strictOrder: true, } } -func (be *listOnceBackend) List(ctx context.Context, t restic.FileType, fn func(backend.FileInfo) error) error { - if t != restic.LockFile && be.listedFileType[t] { +func (be *listOnceBackend) List(ctx context.Context, t backend.FileType, fn func(backend.FileInfo) error) error { + if t != backend.LockFile && be.listedFileType[t] { return errors.Errorf("tried listing type %v the second time", t) } - if be.strictOrder && t == restic.SnapshotFile && be.listedFileType[restic.IndexFile] { + if be.strictOrder && t == backend.SnapshotFile && be.listedFileType[backend.IndexFile] { return errors.Errorf("tried listing type snapshots after index") } be.listedFileType[t] = true @@ -194,7 +194,7 @@ type failConfigOnceBackend struct { func (be *failConfigOnceBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error { - if !be.failedOnce && h.Type == restic.ConfigFile { + if !be.failedOnce && h.Type == backend.ConfigFile { be.failedOnce = true return fmt.Errorf("oops") } @@ -202,7 +202,7 @@ func (be *failConfigOnceBackend) Load(ctx context.Context, h backend.Handle, } func (be *failConfigOnceBackend) Stat(ctx context.Context, h backend.Handle) (backend.FileInfo, error) { - if !be.failedOnce && h.Type == restic.ConfigFile { + if !be.failedOnce && h.Type == backend.ConfigFile { be.failedOnce = true return backend.FileInfo{}, fmt.Errorf("oops") } diff --git a/internal/backend/cache/file_test.go b/internal/backend/cache/file_test.go index ace3080e0..949bbcd8c 100644 --- a/internal/backend/cache/file_test.go +++ b/internal/backend/cache/file_test.go @@ -92,10 +92,10 @@ func TestFiles(t *testing.T) { c := TestNewCache(t) - var tests = []restic.FileType{ - restic.SnapshotFile, - restic.PackFile, - restic.IndexFile, + var tests = []backend.FileType{ + backend.SnapshotFile, + backend.PackFile, + backend.IndexFile, } for _, tpe := range tests { @@ -149,7 +149,7 @@ func TestFileLoad(t *testing.T) { id := restic.ID{} copy(id[:], data) h := backend.Handle{ - Type: restic.PackFile, + Type: backend.PackFile, Name: id.String(), } if err := c.save(h, bytes.NewReader(data)); err != nil { @@ -239,7 +239,7 @@ func TestFileSaveConcurrent(t *testing.T) { random.Read(id[:]) h := backend.Handle{ - Type: restic.PackFile, + Type: backend.PackFile, Name: id.String(), } @@ -284,7 +284,7 @@ func TestFileSaveAfterDamage(t *testing.T) { data := rtest.Random(123456789, 42) id := restic.Hash(data) h := backend.Handle{ - Type: restic.PackFile, + Type: backend.PackFile, Name: id.String(), } if err := c.save(h, bytes.NewReader(data)); err == nil { diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 1bf7d392e..0de1c24cf 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -93,7 +93,7 @@ func TestMissingPack(t *testing.T) { repo, be := repository.TestFromFixture(t, checkerTestData) packID := restic.TestParseID("657f7fb64f6a854fff6fe9279998ee09034901eded4e6db9bcee0e59745bbce6") - test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.PackFile, Name: packID.String()})) + test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.PackFile, Name: packID.String()})) chkr := checker.New(repo, false) hints, errs := chkr.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory) @@ -120,7 +120,7 @@ func TestUnreferencedPack(t *testing.T) { // index 3f1a only references pack 60e0 packID := "60e0438dcb978ec6860cc1f8c43da648170ee9129af8f650f876bad19f8f788e" indexID := restic.TestParseID("3f1abfcb79c6f7d0a3be517d2c83c8562fba64ef2c8e9a3544b4edaf8b5e3b44") - test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.IndexFile, Name: indexID.String()})) + test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.IndexFile, Name: indexID.String()})) chkr := checker.New(repo, false) hints, errs := chkr.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory) @@ -145,7 +145,7 @@ func TestUnreferencedBlobs(t *testing.T) { repo, be := repository.TestFromFixture(t, checkerTestData) snapshotID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02") - test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.SnapshotFile, Name: snapshotID.String()})) + test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.SnapshotFile, Name: snapshotID.String()})) unusedBlobsBySnapshot := restic.BlobHandles{ restic.TestParseHandle("58c748bbe2929fdf30c73262bd8313fe828f8925b05d1d4a87fe109082acb849", restic.DataBlob), @@ -182,7 +182,7 @@ func TestModifiedIndex(t *testing.T) { defer close(done) h := backend.Handle{ - Type: restic.IndexFile, + Type: backend.IndexFile, Name: "90f838b4ac28735fda8644fe6a08dbc742e57aaf81b30977b4fefa357010eafd", } var data []byte @@ -194,7 +194,7 @@ func TestModifiedIndex(t *testing.T) { // save the index again with a modified name so that the hash doesn't match // the content any more h2 := backend.Handle{ - Type: restic.IndexFile, + Type: backend.IndexFile, Name: "80f838b4ac28735fda8644fe6a08dbc742e57aaf81b30977b4fefa357010eafd", } test.OK(t, be.Save(context.TODO(), h2, backend.NewByteReader(data, be.Hasher()))) @@ -294,7 +294,7 @@ type errorOnceBackend struct { func (b *errorOnceBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, consumer func(rd io.Reader) error) error { _, isRetry := b.m.LoadOrStore(h, struct{}{}) err := b.Backend.Load(ctx, h, length, offset, func(rd io.Reader) error { - if !isRetry && h.Type != restic.ConfigFile { + if !isRetry && h.Type != backend.ConfigFile { return consumer(errorReadCloser{Reader: rd, shortenBy: b.shortenBy, maxErrorOffset: b.maxErrorOffset}) } return consumer(rd) diff --git a/internal/global/global.go b/internal/global/global.go index 2448670f3..d4ab38b7d 100644 --- a/internal/global/global.go +++ b/internal/global/global.go @@ -338,7 +338,7 @@ func OpenRepository(ctx context.Context, gopts Options, printer restic.Printer) // hasRepositoryConfig checks if the repository config file exists and is not empty. func hasRepositoryConfig(ctx context.Context, be backend.Backend, repo string, gopts Options) error { - fi, err := be.Stat(ctx, backend.Handle{Type: restic.ConfigFile}) + fi, err := be.Stat(ctx, backend.Handle{Type: backend.ConfigFile}) if be.IsNotExist(err) { //nolint:staticcheck // capitalized error string is intentional return fmt.Errorf("Fatal: %w: unable to open config file: %v\nIs there a repository at the following location?\n%v", ErrNoRepository, err, location.StripPassword(gopts.Backends, repo)) diff --git a/internal/repository/checker_test.go b/internal/repository/checker_test.go index 629e8ace7..7933dca67 100644 --- a/internal/repository/checker_test.go +++ b/internal/repository/checker_test.go @@ -101,7 +101,7 @@ type lastByteFlipBackend struct { } func (b *lastByteFlipBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, consumer func(rd io.Reader) error) error { - if h.Type != restic.PackFile { + if h.Type != backend.PackFile { return b.Backend.Load(ctx, h, length, offset, consumer) } return b.Backend.Load(ctx, h, length, offset, func(rd io.Reader) error { @@ -124,7 +124,7 @@ type alwaysFailBackend struct { } func (b *alwaysFailBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, consumer func(rd io.Reader) error) error { - if h.Type == restic.PackFile { + if h.Type == backend.PackFile { return errors.New("simulated total download failure") } return b.Backend.Load(ctx, h, length, offset, consumer) @@ -137,7 +137,7 @@ type truncatingBackend struct { } func (b *truncatingBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, consumer func(rd io.Reader) error) error { - if h.Type != restic.PackFile { + if h.Type != backend.PackFile { return b.Backend.Load(ctx, h, length, offset, consumer) } return b.Backend.Load(ctx, h, length, offset, func(rd io.Reader) error { diff --git a/internal/repository/lock_file_test.go b/internal/repository/lock_file_test.go index 2c4790caf..c8b2984f2 100644 --- a/internal/repository/lock_file_test.go +++ b/internal/repository/lock_file_test.go @@ -57,7 +57,7 @@ type failLockLoadingBackend struct { } func (be *failLockLoadingBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error { - if h.Type == restic.LockFile { + if h.Type == backend.LockFile { return fmt.Errorf("error loading lock") } return be.Backend.Load(ctx, h, length, offset, fn) @@ -241,7 +241,7 @@ func TestLockRefreshStaleMissing(t *testing.T) { lockID := checkSingleLock(t, repo) // refresh must fail if lock was removed - rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.LockFile, Name: lockID.String()})) + rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.LockFile, Name: lockID.String()})) time.Sleep(time.Millisecond) err = lock.refreshStaleLock(context.TODO()) rtest.Assert(t, err == errRemovedLock, "unexpected error, expected %v, got %v", errRemovedLock, err) diff --git a/internal/repository/raw_test.go b/internal/repository/raw_test.go index 81fc25250..ff57a60ed 100644 --- a/internal/repository/raw_test.go +++ b/internal/repository/raw_test.go @@ -32,7 +32,7 @@ func TestLoadRaw(t *testing.T) { err := b.Save(context.TODO(), h, backend.NewByteReader(data, b.Hasher())) rtest.OK(t, err) - buf, err := repo.LoadRaw(context.TODO(), backend.PackFile, id) + buf, err := repo.LoadRaw(context.TODO(), restic.PackFile, id) rtest.OK(t, err) if len(buf) != len(data) { @@ -62,7 +62,7 @@ func TestLoadRawBroken(t *testing.T) { } // must detect but still return corrupt data - buf, err := repo.LoadRaw(context.TODO(), backend.PackFile, id) + buf, err := repo.LoadRaw(context.TODO(), restic.PackFile, id) rtest.Assert(t, bytes.Equal(buf, data), "wrong data returned") rtest.Assert(t, errors.Is(err, restic.ErrInvalidData), "missing expected ErrInvalidData error, got %v", err) @@ -76,7 +76,7 @@ func TestLoadRawBroken(t *testing.T) { } // must retry load of corrupted data - buf, err = repo.LoadRaw(context.TODO(), backend.PackFile, id) + buf, err = repo.LoadRaw(context.TODO(), restic.PackFile, id) rtest.OK(t, err) rtest.Assert(t, bytes.Equal(buf, data), "wrong data returned") rtest.Equals(t, 2, loadCtr, "missing retry on broken data") @@ -101,7 +101,7 @@ func TestLoadRawBrokenWithCache(t *testing.T) { } // must retry load of corrupted data - buf, err := repo.LoadRaw(context.TODO(), backend.SnapshotFile, id) + buf, err := repo.LoadRaw(context.TODO(), restic.SnapshotFile, id) rtest.OK(t, err) rtest.Assert(t, bytes.Equal(buf, data), "wrong data returned") rtest.Equals(t, 2, loadCtr, "missing retry on broken data") diff --git a/internal/repository/repack_test.go b/internal/repository/repack_test.go index d98811a7f..202381967 100644 --- a/internal/repository/repack_test.go +++ b/internal/repository/repack_test.go @@ -116,7 +116,7 @@ func listPacks(t *testing.T, repo restic.Lister) restic.IDSet { return listFiles(t, repo, restic.PackFile) } -func listFiles(t *testing.T, repo restic.Lister, tpe backend.FileType) restic.IDSet { +func listFiles(t *testing.T, repo restic.Lister, tpe restic.FileType) restic.IDSet { list := restic.NewIDSet() err := repo.List(context.TODO(), tpe, func(id restic.ID, size int64) error { list.Insert(id) @@ -153,7 +153,7 @@ func repack(t *testing.T, repo *repository.Repository, be backend.Backend, packs })) for id := range packs { - rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.PackFile, Name: id.String()})) + rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.PackFile, Name: id.String()})) } } diff --git a/internal/repository/repair_index_test.go b/internal/repository/repair_index_test.go index 5838f37ee..691474392 100644 --- a/internal/repository/repair_index_test.go +++ b/internal/repository/repair_index_test.go @@ -50,7 +50,7 @@ func TestRebuildIndex(t *testing.T) { "damaged index", func(t *testing.T, repo *repository.Repository, be backend.Backend) { index := listIndex(t, repo).List()[0] - replaceFile(t, be, backend.Handle{Type: restic.IndexFile, Name: index.String()}, func(b []byte) []byte { + replaceFile(t, be, backend.Handle{Type: backend.IndexFile, Name: index.String()}, func(b []byte) []byte { b[0] ^= 0xff return b }) @@ -60,14 +60,14 @@ func TestRebuildIndex(t *testing.T) { "missing index", func(t *testing.T, repo *repository.Repository, be backend.Backend) { index := listIndex(t, repo).List()[0] - rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.IndexFile, Name: index.String()})) + rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.IndexFile, Name: index.String()})) }, }, { "missing pack", func(t *testing.T, repo *repository.Repository, be backend.Backend) { pack := listPacks(t, repo).List()[0] - rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.PackFile, Name: pack.String()})) + rtest.OK(t, be.Remove(context.TODO(), backend.Handle{Type: backend.PackFile, Name: pack.String()})) }, }, } { diff --git a/internal/repository/repository_internal_test.go b/internal/repository/repository_internal_test.go index 635d98276..dbcecadf0 100644 --- a/internal/repository/repository_internal_test.go +++ b/internal/repository/repository_internal_test.go @@ -111,7 +111,7 @@ func benchmarkLoadIndex(b *testing.B, version uint) { rtest.OK(b, err) b.Logf("index saved as %v", id.Str()) - fi, err := be.Stat(context.TODO(), backend.Handle{Type: restic.IndexFile, Name: id.String()}) + fi, err := be.Stat(context.TODO(), backend.Handle{Type: backend.IndexFile, Name: id.String()}) rtest.OK(b, err) b.Logf("filesize is %v", fi.Size) diff --git a/internal/repository/repository_test.go b/internal/repository/repository_test.go index cc5719e9c..b830458e7 100644 --- a/internal/repository/repository_test.go +++ b/internal/repository/repository_test.go @@ -234,7 +234,7 @@ func TestLoadBlobBroken(t *testing.T) { rtest.OK(t, err) rtest.Assert(t, bytes.Equal(buf, data), "data mismatch") pack := repo.LookupBlob(restic.BlobHandle{Type: restic.TreeBlob, ID: id})[0].PackID() - rtest.Assert(t, c.Has(backend.Handle{Type: restic.PackFile, Name: pack.String()}), "expected tree pack to be cached") + rtest.Assert(t, c.Has(backend.Handle{Type: backend.PackFile, Name: pack.String()}), "expected tree pack to be cached") } func BenchmarkLoadBlob(b *testing.B) { @@ -339,7 +339,7 @@ func TestRepositoryLoadUnpackedBroken(t *testing.T) { data := rtest.Random(23, 12345) id := restic.Hash(data) - h := backend.Handle{Type: restic.IndexFile, Name: id.String()} + h := backend.Handle{Type: backend.IndexFile, Name: id.String()} // damage buffer data[0] ^= 0xff @@ -358,7 +358,7 @@ type damageOnceBackend struct { func (be *damageOnceBackend) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error { // don't break the config file as we can't retry it - if h.Type == restic.ConfigFile { + if h.Type == backend.ConfigFile { return be.Backend.Load(ctx, h, length, offset, fn) } @@ -467,7 +467,7 @@ func TestListPack(t *testing.T) { // Forcibly cache pack file packID := repo.LookupBlob(restic.BlobHandle{Type: restic.TreeBlob, ID: id})[0].PackID() - rtest.OK(t, be.Load(context.TODO(), backend.Handle{Type: restic.PackFile, IsMetadata: true, Name: packID.String()}, 0, 0, func(rd io.Reader) error { return nil })) + rtest.OK(t, be.Load(context.TODO(), backend.Handle{Type: backend.PackFile, IsMetadata: true, Name: packID.String()}, 0, 0, func(rd io.Reader) error { return nil })) // Get size to list pack var size int64 @@ -482,7 +482,7 @@ func TestListPack(t *testing.T) { rtest.OK(t, err) rtest.Assert(t, len(handles) == 1 && handles[0].ID == id, "unexpected blobs in pack: %v", handles) - rtest.Assert(t, !c.Has(backend.Handle{Type: restic.PackFile, Name: packID.String()}), "tree pack should no longer be cached as listPack does not set IsMetadata in the backend.Handle") + rtest.Assert(t, !c.Has(backend.Handle{Type: backend.PackFile, Name: packID.String()}), "tree pack should no longer be cached as listPack does not set IsMetadata in the backend.Handle") } func TestNoDoubleInit(t *testing.T) { @@ -504,8 +504,8 @@ func TestNoDoubleInit(t *testing.T) { var data [32]byte hash := restic.Hash(data[:]) rtest.OK(t, be.Save(context.TODO(), backend.Handle{Type: backend.SnapshotFile, Name: hash.String()}, backend.NewByteReader(data[:], be.Hasher()))) - rtest.OK(t, be.List(context.TODO(), restic.KeyFile, func(fi backend.FileInfo) error { - return be.Remove(context.TODO(), backend.Handle{Type: restic.KeyFile, Name: fi.Name}) + rtest.OK(t, be.List(context.TODO(), backend.KeyFile, func(fi backend.FileInfo) error { + return be.Remove(context.TODO(), backend.Handle{Type: backend.KeyFile, Name: fi.Name}) })) err = repo.Init(context.TODO(), r.Config().Version, rtest.TestPassword, &pol) rtest.Assert(t, strings.Contains(err.Error(), "repository already contains snapshots"), "expected already contains snapshots error, got %q", err) diff --git a/internal/repository/upgrade_repo.go b/internal/repository/upgrade_repo.go index 8f6bc0320..e85840323 100644 --- a/internal/repository/upgrade_repo.go +++ b/internal/repository/upgrade_repo.go @@ -63,7 +63,7 @@ func UpgradeRepo(ctx context.Context, repo *Repository) error { return fmt.Errorf("create temp dir failed: %w", err) } - h := backend.Handle{Type: restic.ConfigFile} + h := backend.Handle{Type: backend.ConfigFile} // read raw config file and save it to a temp dir, just in case rawConfigFile, err := repo.LoadRaw(ctx, restic.ConfigFile, restic.ID{}) diff --git a/internal/restic/lister_test.go b/internal/restic/lister_test.go index 245a5d3da..916b964a9 100644 --- a/internal/restic/lister_test.go +++ b/internal/restic/lister_test.go @@ -5,7 +5,6 @@ import ( "fmt" "testing" - "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -39,17 +38,17 @@ func TestMemoizeList(t *testing.T) { return nil } - mem, err := restic.MemorizeList(context.TODO(), be, backend.SnapshotFile) + mem, err := restic.MemorizeList(context.TODO(), be, restic.SnapshotFile) rtest.OK(t, err) - err = mem.List(context.TODO(), backend.IndexFile, func(id restic.ID, size int64) error { + err = mem.List(context.TODO(), restic.IndexFile, func(id restic.ID, size int64) error { t.Fatal("file type mismatch") return nil // the memoized lister must return an error by itself }) rtest.Assert(t, err != nil, "missing error on file typ mismatch") var memFiles []FileInfo - err = mem.List(context.TODO(), backend.SnapshotFile, func(id restic.ID, size int64) error { + err = mem.List(context.TODO(), restic.SnapshotFile, func(id restic.ID, size int64) error { memFiles = append(memFiles, FileInfo{ID: id, Size: size}) return nil }) @@ -60,9 +59,9 @@ func TestMemoizeList(t *testing.T) { func TestMemoizeListError(t *testing.T) { // setup backend to serve as data source for memoized list be := &ListHelper{} - be.ListFn = func(ctx context.Context, t backend.FileType, fn func(restic.ID, int64) error) error { + be.ListFn = func(ctx context.Context, t restic.FileType, fn func(restic.ID, int64) error) error { return fmt.Errorf("list error") } - _, err := restic.MemorizeList(context.TODO(), be, backend.SnapshotFile) + _, err := restic.MemorizeList(context.TODO(), be, restic.SnapshotFile) rtest.Assert(t, err != nil, "missing error on list error") } From 9ab5fc59c2ed4883fbbc08179bad3e54af0abf8c Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 14 Jun 2026 17:40:28 +0200 Subject: [PATCH 2/2] restic: split FileType for backend.FileType Equality of constants is enforced via internal/repository/filetype.go using compile time checks. --- internal/backend/file.go | 2 ++ internal/repository/checker.go | 2 +- internal/repository/filetype.go | 17 +++++++++ internal/repository/key.go | 7 ++-- internal/repository/raw.go | 2 +- internal/repository/repository.go | 18 +++++----- internal/repository/warmup.go | 5 +-- internal/restic/filetype.go | 57 +++++++++++++++++++++++++++++++ internal/restic/repository.go | 36 ------------------- 9 files changed, 90 insertions(+), 56 deletions(-) create mode 100644 internal/repository/filetype.go create mode 100644 internal/restic/filetype.go diff --git a/internal/backend/file.go b/internal/backend/file.go index 990175f9c..b49ed975b 100644 --- a/internal/backend/file.go +++ b/internal/backend/file.go @@ -7,6 +7,7 @@ import ( ) // FileType is the type of a file in the backend. +// Numeric values must match restic.FileType; enforced in internal/repository/filetype.go. type FileType uint8 // These are the different data types a backend can store. @@ -19,6 +20,7 @@ const ( ConfigFile ) +// Keep in sync with restic.FileType.String(). func (t FileType) String() string { s := "invalid" switch t { diff --git a/internal/repository/checker.go b/internal/repository/checker.go index 9b5824989..9860fb5a4 100644 --- a/internal/repository/checker.go +++ b/internal/repository/checker.go @@ -334,7 +334,7 @@ func checkPack(ctx context.Context, r *Repository, id restic.ID, blobs pack.Blob if err != nil { if r.cache != nil { // ignore error as there's not much we can do here - _ = r.cache.Forget(backend.Handle{Type: restic.PackFile, Name: id.String()}) + _ = r.cache.Forget(backend.Handle{Type: backend.PackFile, Name: id.String()}) } // retry pack verification to detect transient errors diff --git a/internal/repository/filetype.go b/internal/repository/filetype.go new file mode 100644 index 000000000..364ed794d --- /dev/null +++ b/internal/repository/filetype.go @@ -0,0 +1,17 @@ +package repository + +import ( + "github.com/restic/restic/internal/backend" + "github.com/restic/restic/internal/restic" +) + +// Compile-time checks that restic and backend FileType constants match. A constant mismatch +// would be an out-of-bounds access that is detected by the compiler. +var ( + _ = [1]struct{}{}[backend.PackFile-backend.FileType(restic.PackFile)] + _ = [1]struct{}{}[backend.KeyFile-backend.FileType(restic.KeyFile)] + _ = [1]struct{}{}[backend.LockFile-backend.FileType(restic.LockFile)] + _ = [1]struct{}{}[backend.SnapshotFile-backend.FileType(restic.SnapshotFile)] + _ = [1]struct{}{}[backend.IndexFile-backend.FileType(restic.IndexFile)] + _ = [1]struct{}{}[backend.ConfigFile-backend.FileType(restic.ConfigFile)] +) diff --git a/internal/repository/key.go b/internal/repository/key.go index 0f2db3e61..e5d1b0724 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -269,10 +269,7 @@ func AddKey(ctx context.Context, s *Repository, password, username, hostname str id := restic.Hash(buf) // store in repository and return - h := backend.Handle{ - Type: restic.KeyFile, - Name: id.String(), - } + h := backend.Handle{Type: backend.KeyFile, Name: id.String()} err = s.be.Save(ctx, h, backend.NewByteReader(buf, s.be.Hasher())) if err != nil { @@ -289,7 +286,7 @@ func RemoveKey(ctx context.Context, repo *Repository, id restic.ID) error { return errors.New("refusing to remove key currently used to access repository") } - h := backend.Handle{Type: restic.KeyFile, Name: id.String()} + h := backend.Handle{Type: backend.KeyFile, Name: id.String()} return repo.be.Remove(ctx, h) } diff --git a/internal/repository/raw.go b/internal/repository/raw.go index c5a4a72b7..fc6726f39 100644 --- a/internal/repository/raw.go +++ b/internal/repository/raw.go @@ -14,7 +14,7 @@ import ( // If the backend returns data that does not match the id, then the buffer is returned // along with an error that is a restic.ErrInvalidData error. func (r *Repository) LoadRaw(ctx context.Context, t restic.FileType, id restic.ID) (buf []byte, err error) { - h := backend.Handle{Type: t, Name: id.String()} + h := backend.Handle{Type: backend.FileType(t), Name: id.String()} buf, err = loadRaw(ctx, r.be, h) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index d8cb82d9d..35f5c78f5 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -226,7 +226,7 @@ func sortCachedPacksFirst(cache haver, blobs []*pack.PackedBlob) { noncached := make([]*pack.PackedBlob, 0, len(blobs)/2) for _, blob := range blobs { - if cache.Has(backend.Handle{Type: restic.PackFile, Name: blob.PackID().String()}) { + if cache.Has(backend.Handle{Type: backend.PackFile, Name: blob.PackID().String()}) { cached = append(cached, blob) continue } @@ -255,7 +255,7 @@ func (r *Repository) LoadBlob(ctx context.Context, bh restic.BlobHandle, buf []b if err != nil { if r.cache != nil { for _, blob := range blobs { - h := backend.Handle{Type: restic.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} // ignore errors as there's not much we can do here _ = r.cache.Forget(h) } @@ -271,7 +271,7 @@ func (r *Repository) loadBlob(ctx context.Context, blobs []*pack.PackedBlob, buf for _, blob := range blobs { debug.Log("blob %v found: %v", blob.Handle(), blob) // load blob from pack - h := backend.Handle{Type: restic.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: blob.PackID().String(), IsMetadata: blob.Blob.Type.IsMetadata()} switch { case cap(buf) < int(blob.Blob.Length): @@ -514,7 +514,7 @@ func (r *Repository) saveUnpacked(ctx context.Context, t restic.FileType, buf [] } else { id = restic.Hash(ciphertext) } - h := backend.Handle{Type: t, Name: id.String()} + h := backend.Handle{Type: backend.FileType(t), Name: id.String()} err = r.be.Save(ctx, h, backend.NewByteReader(ciphertext, r.be.Hasher())) if err != nil { @@ -558,7 +558,7 @@ func (r *internalRepository) RemoveUnpacked(ctx context.Context, t restic.FileTy } func (r *Repository) removeUnpacked(ctx context.Context, t restic.FileType, id restic.ID) error { - return r.be.Remove(ctx, backend.Handle{Type: t, Name: id.String()}) + return r.be.Remove(ctx, backend.Handle{Type: backend.FileType(t), Name: id.String()}) } func (r *Repository) WithBlobUploader(ctx context.Context, fn func(ctx context.Context, uploader restic.BlobSaverWithAsync) error) error { @@ -894,7 +894,7 @@ func (r *Repository) Init(ctx context.Context, version uint, password string, ch return fmt.Errorf("repository version %v too low", version) } - _, err := r.be.Stat(ctx, backend.Handle{Type: restic.ConfigFile}) + _, err := r.be.Stat(ctx, backend.Handle{Type: backend.ConfigFile}) if err != nil && !r.be.IsNotExist(err) { return err } @@ -956,7 +956,7 @@ func (r *Repository) KeyID() restic.ID { // List runs fn for all files of type t in the repo. func (r *Repository) List(ctx context.Context, t restic.FileType, fn func(restic.ID, int64) error) error { - return r.be.List(ctx, t, func(fi backend.FileInfo) error { + return r.be.List(ctx, backend.FileType(t), func(fi backend.FileInfo) error { id, err := restic.ParseID(fi.Name) if err != nil { debug.Log("unable to parse %v as an ID", fi.Name) @@ -968,7 +968,7 @@ func (r *Repository) List(ctx context.Context, t restic.FileType, fn func(restic // listPack returns blob entries from the pack file header including offsets. func (r *Repository) listPack(ctx context.Context, id restic.ID, size int64) (pack.Blobs, error) { - h := backend.Handle{Type: restic.PackFile, Name: id.String()} + h := backend.Handle{Type: backend.PackFile, Name: id.String()} entries, _, err := pack.List(r.Key(), backend.ReaderAt(ctx, r.be, h), size) if err != nil { @@ -1143,7 +1143,7 @@ func streamPack(ctx context.Context, beLoad backendLoadFn, loadBlobFn loadBlobFn } func streamPackPart(ctx context.Context, beLoad backendLoadFn, loadBlobFn loadBlobFn, dec *zstd.Decoder, key *crypto.Key, packID restic.ID, blobs pack.Blobs, handleBlobFn func(blob restic.BlobHandle, buf []byte, err error) error) error { - h := backend.Handle{Type: restic.PackFile, Name: packID.String(), IsMetadata: blobs[0].Type.IsMetadata()} + h := backend.Handle{Type: backend.PackFile, Name: packID.String(), IsMetadata: blobs[0].Type.IsMetadata()} dataStart := blobs[0].Offset dataEnd := blobs[len(blobs)-1].Offset + blobs[len(blobs)-1].Length diff --git a/internal/repository/warmup.go b/internal/repository/warmup.go index ef78ddaf4..c8abd3a44 100644 --- a/internal/repository/warmup.go +++ b/internal/repository/warmup.go @@ -26,10 +26,7 @@ func (job *warmupJob) Wait(ctx context.Context) error { func (r *Repository) StartWarmup(ctx context.Context, packs restic.IDSet) (restic.WarmupJob, error) { handles := make([]backend.Handle, 0, len(packs)) for pack := range packs { - handles = append( - handles, - backend.Handle{Type: restic.PackFile, Name: pack.String()}, - ) + handles = append(handles, backend.Handle{Type: backend.PackFile, Name: pack.String()}) } handlesWarmingUp, err := r.be.Warmup(ctx, handles) return &warmupJob{ diff --git a/internal/restic/filetype.go b/internal/restic/filetype.go new file mode 100644 index 000000000..fc1013286 --- /dev/null +++ b/internal/restic/filetype.go @@ -0,0 +1,57 @@ +package restic + +// FileType is the type of a file in the repository. +// Numeric values must match backend.FileType; enforced in internal/repository/filetype.go. +type FileType uint8 + +// These are the different data types a backend can store. +const ( + PackFile FileType = 1 + iota + KeyFile + LockFile + SnapshotFile + IndexFile + ConfigFile +) + +// Keep in sync with backend.FileType.String(). +func (t FileType) String() string { + s := "invalid" + switch t { + case PackFile: + // Spelled "data" instead of "pack" for historical reasons. + s = "data" + case KeyFile: + s = "key" + case LockFile: + s = "lock" + case SnapshotFile: + s = "snapshot" + case IndexFile: + s = "index" + case ConfigFile: + s = "config" + } + return s +} + +// WriteableFileType defines the different data types that can be modified via SaveUnpacked or RemoveUnpacked. +type WriteableFileType FileType + +const ( + // WriteableSnapshotFile is the WriteableFileType for snapshots. + WriteableSnapshotFile = WriteableFileType(SnapshotFile) +) + +func (w *WriteableFileType) ToFileType() FileType { + switch *w { + case WriteableSnapshotFile: + return SnapshotFile + default: + panic("invalid WriteableFileType") + } +} + +type FileTypes interface { + FileType | WriteableFileType +} diff --git a/internal/restic/repository.go b/internal/restic/repository.go index 581375848..8cd408202 100644 --- a/internal/restic/repository.go +++ b/internal/restic/repository.go @@ -4,7 +4,6 @@ import ( "context" "iter" - "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/errors" ) @@ -60,41 +59,6 @@ type Repository interface { StartWarmup(ctx context.Context, packs IDSet) (WarmupJob, error) } -type FileType = backend.FileType - -// These are the different data types a backend can store. Only filetypes contained -// in the `WriteableFileType` subset can be modified via the Repository interface. -// All other filetypes are considered internal datastructures of the Repository. -const ( - PackFile = backend.PackFile - KeyFile = backend.KeyFile - LockFile = backend.LockFile - SnapshotFile = backend.SnapshotFile - IndexFile = backend.IndexFile - ConfigFile = backend.ConfigFile -) - -// WriteableFileType defines the different data types that can be modified via SaveUnpacked or RemoveUnpacked. -type WriteableFileType backend.FileType - -const ( - // WriteableSnapshotFile is the WriteableFileType for snapshots. - WriteableSnapshotFile = WriteableFileType(SnapshotFile) -) - -func (w *WriteableFileType) ToFileType() FileType { - switch *w { - case WriteableSnapshotFile: - return SnapshotFile - default: - panic("invalid WriteableFileType") - } -} - -type FileTypes interface { - FileType | WriteableFileType -} - // LoaderUnpacked allows loading a blob not stored in a pack file type LoaderUnpacked interface { // Connections returns the maximum number of concurrent backend operations