From c6fae0320ef7b38a789d490be1ec53a7f8ca430d Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 27 Aug 2024 11:26:52 +0200 Subject: [PATCH] archiver: hide implementation details --- internal/archiver/archiver.go | 106 ++++++++++++++------------- internal/archiver/archiver_test.go | 2 +- internal/archiver/blob_saver.go | 34 ++++----- internal/archiver/blob_saver_test.go | 16 ++-- internal/archiver/buffer.go | 26 +++---- internal/archiver/doc.go | 9 --- internal/archiver/file_saver.go | 36 ++++----- internal/archiver/file_saver_test.go | 10 +-- internal/archiver/scanner.go | 4 +- internal/archiver/tree.go | 54 +++++++------- internal/archiver/tree_saver.go | 32 ++++---- internal/archiver/tree_saver_test.go | 16 ++-- internal/archiver/tree_test.go | 102 +++++++++++++------------- 13 files changed, 223 insertions(+), 224 deletions(-) diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index 4f0990843..397347bcb 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -75,6 +75,14 @@ type archiverRepo interface { } // Archiver saves a directory structure to the repo. +// +// An Archiver has a number of worker goroutines handling saving the different +// data structures to the repository, the details are implemented by the +// fileSaver, blobSaver, and treeSaver types. +// +// The main goroutine (the one calling Snapshot()) traverses the directory tree +// and delegates all work to these worker pools. They return a futureNode which +// can be resolved later, by calling Wait() on it. type Archiver struct { Repo archiverRepo SelectByName SelectByNameFunc @@ -82,9 +90,9 @@ type Archiver struct { FS fs.FS Options Options - blobSaver *BlobSaver - fileSaver *FileSaver - treeSaver *TreeSaver + blobSaver *blobSaver + fileSaver *fileSaver + treeSaver *treeSaver mu sync.Mutex summary *Summary @@ -160,7 +168,7 @@ func (o Options) ApplyDefaults() Options { if o.SaveTreeConcurrency == 0 { // can either wait for a file, wait for a tree, serialize a tree or wait for saveblob // the last two are cpu-bound and thus mutually exclusive. - // Also allow waiting for FileReadConcurrency files, this is the maximum of FutureFiles + // Also allow waiting for FileReadConcurrency files, this is the maximum of files // which currently can be in progress. The main backup loop blocks when trying to queue // more files to read. o.SaveTreeConcurrency = uint(runtime.GOMAXPROCS(0)) + o.ReadConcurrency @@ -297,27 +305,27 @@ func (arch *Archiver) wrapLoadTreeError(id restic.ID, err error) error { // saveDir stores a directory in the repo and returns the node. snPath is the // path within the current snapshot. -func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi os.FileInfo, previous *restic.Tree, complete CompleteFunc) (d FutureNode, err error) { +func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi os.FileInfo, previous *restic.Tree, complete fileCompleteFunc) (d futureNode, err error) { debug.Log("%v %v", snPath, dir) treeNode, err := arch.nodeFromFileInfo(snPath, dir, fi, false) if err != nil { - return FutureNode{}, err + return futureNode{}, err } names, err := fs.Readdirnames(arch.FS, dir, fs.O_NOFOLLOW) if err != nil { - return FutureNode{}, err + return futureNode{}, err } sort.Strings(names) - nodes := make([]FutureNode, 0, len(names)) + nodes := make([]futureNode, 0, len(names)) for _, name := range names { // test if context has been cancelled if ctx.Err() != nil { debug.Log("context has been cancelled, aborting") - return FutureNode{}, ctx.Err() + return futureNode{}, ctx.Err() } pathname := arch.FS.Join(dir, name) @@ -333,7 +341,7 @@ func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi continue } - return FutureNode{}, err + return futureNode{}, err } if excluded { @@ -348,11 +356,11 @@ func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, fi return fn, nil } -// FutureNode holds a reference to a channel that returns a FutureNodeResult +// futureNode holds a reference to a channel that returns a FutureNodeResult // or a reference to an already existing result. If the result is available // immediately, then storing a reference directly requires less memory than // using the indirection via a channel. -type FutureNode struct { +type futureNode struct { ch <-chan futureNodeResult res *futureNodeResult } @@ -365,18 +373,18 @@ type futureNodeResult struct { err error } -func newFutureNode() (FutureNode, chan<- futureNodeResult) { +func newFutureNode() (futureNode, chan<- futureNodeResult) { ch := make(chan futureNodeResult, 1) - return FutureNode{ch: ch}, ch + return futureNode{ch: ch}, ch } -func newFutureNodeWithResult(res futureNodeResult) FutureNode { - return FutureNode{ +func newFutureNodeWithResult(res futureNodeResult) futureNode { + return futureNode{ res: &res, } } -func (fn *FutureNode) take(ctx context.Context) futureNodeResult { +func (fn *futureNode) take(ctx context.Context) futureNodeResult { if fn.res != nil { res := fn.res // free result @@ -415,19 +423,19 @@ func (arch *Archiver) allBlobsPresent(previous *restic.Node) bool { // Errors and completion needs to be handled by the caller. // // snPath is the path within the current snapshot. -func (arch *Archiver) save(ctx context.Context, snPath, target string, previous *restic.Node) (fn FutureNode, excluded bool, err error) { +func (arch *Archiver) save(ctx context.Context, snPath, target string, previous *restic.Node) (fn futureNode, excluded bool, err error) { start := time.Now() debug.Log("%v target %q, previous %v", snPath, target, previous) abstarget, err := arch.FS.Abs(target) if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } // exclude files by path before running Lstat to reduce number of lstat calls if !arch.SelectByName(abstarget) { debug.Log("%v is excluded by path", target) - return FutureNode{}, true, nil + return futureNode{}, true, nil } // get file info and run remaining select functions that require file information @@ -436,13 +444,13 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous debug.Log("lstat() for %v returned error: %v", target, err) err = arch.error(abstarget, err) if err != nil { - return FutureNode{}, false, errors.WithStack(err) + return futureNode{}, false, errors.WithStack(err) } - return FutureNode{}, true, nil + return futureNode{}, true, nil } if !arch.Select(abstarget, fi) { debug.Log("%v is excluded", target) - return FutureNode{}, true, nil + return futureNode{}, true, nil } switch { @@ -458,7 +466,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous arch.CompleteBlob(previous.Size) node, err := arch.nodeFromFileInfo(snPath, target, fi, false) if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } // copy list of blobs @@ -477,7 +485,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous err := errors.Errorf("parts of %v not found in the repository index; storing the file again", target) err = arch.error(abstarget, err) if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } } @@ -488,9 +496,9 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous debug.Log("Openfile() for %v returned error: %v", target, err) err = arch.error(abstarget, err) if err != nil { - return FutureNode{}, false, errors.WithStack(err) + return futureNode{}, false, errors.WithStack(err) } - return FutureNode{}, true, nil + return futureNode{}, true, nil } fi, err = file.Stat() @@ -499,9 +507,9 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous _ = file.Close() err = arch.error(abstarget, err) if err != nil { - return FutureNode{}, false, errors.WithStack(err) + return futureNode{}, false, errors.WithStack(err) } - return FutureNode{}, true, nil + return futureNode{}, true, nil } // make sure it's still a file @@ -510,9 +518,9 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous _ = file.Close() err = arch.error(abstarget, err) if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } - return FutureNode{}, true, nil + return futureNode{}, true, nil } // Save will close the file, we don't need to do that @@ -533,7 +541,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous err = arch.error(abstarget, err) } if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } fn, err = arch.saveDir(ctx, snPath, target, fi, oldSubtree, @@ -542,19 +550,19 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous }) if err != nil { debug.Log("SaveDir for %v returned error: %v", snPath, err) - return FutureNode{}, false, err + return futureNode{}, false, err } case fi.Mode()&os.ModeSocket > 0: debug.Log(" %v is a socket, ignoring", target) - return FutureNode{}, true, nil + return futureNode{}, true, nil default: debug.Log(" %v other", target) node, err := arch.nodeFromFileInfo(snPath, target, fi, false) if err != nil { - return FutureNode{}, false, err + return futureNode{}, false, err } fn = newFutureNodeWithResult(futureNodeResult{ snPath: snPath, @@ -621,17 +629,17 @@ func (arch *Archiver) statDir(dir string) (os.FileInfo, error) { // saveTree stores a Tree in the repo, returned is the tree. snPath is the path // within the current snapshot. -func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, previous *restic.Tree, complete CompleteFunc) (FutureNode, int, error) { +func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree, previous *restic.Tree, complete fileCompleteFunc) (futureNode, int, error) { var node *restic.Node if snPath != "/" { if atree.FileInfoPath == "" { - return FutureNode{}, 0, errors.Errorf("FileInfoPath for %v is empty", snPath) + return futureNode{}, 0, errors.Errorf("FileInfoPath for %v is empty", snPath) } fi, err := arch.statDir(atree.FileInfoPath) if err != nil { - return FutureNode{}, 0, err + return futureNode{}, 0, err } debug.Log("%v, dir node data loaded from %v", snPath, atree.FileInfoPath) @@ -639,7 +647,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, // thus ignore errors for such folders. node, err = arch.nodeFromFileInfo(snPath, atree.FileInfoPath, fi, true) if err != nil { - return FutureNode{}, 0, err + return futureNode{}, 0, err } } else { // fake root node @@ -648,7 +656,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, debug.Log("%v (%v nodes), parent %v", snPath, len(atree.Nodes), previous) nodeNames := atree.NodeNames() - nodes := make([]FutureNode, 0, len(nodeNames)) + nodes := make([]futureNode, 0, len(nodeNames)) // iterate over the nodes of atree in lexicographic (=deterministic) order for _, name := range nodeNames { @@ -656,7 +664,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, // test if context has been cancelled if ctx.Err() != nil { - return FutureNode{}, 0, ctx.Err() + return futureNode{}, 0, ctx.Err() } // this is a leaf node @@ -669,11 +677,11 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, // ignore error continue } - return FutureNode{}, 0, err + return futureNode{}, 0, err } if err != nil { - return FutureNode{}, 0, err + return futureNode{}, 0, err } if !excluded { @@ -691,7 +699,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, err = arch.error(join(snPath, name), err) } if err != nil { - return FutureNode{}, 0, err + return futureNode{}, 0, err } // not a leaf node, archive subtree @@ -699,7 +707,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *Tree, arch.trackItem(snItem, oldNode, n, is, time.Since(start)) }) if err != nil { - return FutureNode{}, 0, err + return futureNode{}, 0, err } nodes = append(nodes, fn) } @@ -779,16 +787,16 @@ func (arch *Archiver) loadParentTree(ctx context.Context, sn *restic.Snapshot) * // runWorkers starts the worker pools, which are stopped when the context is cancelled. func (arch *Archiver) runWorkers(ctx context.Context, wg *errgroup.Group) { - arch.blobSaver = NewBlobSaver(ctx, wg, arch.Repo, arch.Options.SaveBlobConcurrency) + arch.blobSaver = newBlobSaver(ctx, wg, arch.Repo, arch.Options.SaveBlobConcurrency) - arch.fileSaver = NewFileSaver(ctx, wg, + arch.fileSaver = newFileSaver(ctx, wg, arch.blobSaver.Save, arch.Repo.Config().ChunkerPolynomial, arch.Options.ReadConcurrency, arch.Options.SaveBlobConcurrency) arch.fileSaver.CompleteBlob = arch.CompleteBlob arch.fileSaver.NodeFromFileInfo = arch.nodeFromFileInfo - arch.treeSaver = NewTreeSaver(ctx, wg, arch.Options.SaveTreeConcurrency, arch.blobSaver.Save, arch.Error) + arch.treeSaver = newTreeSaver(ctx, wg, arch.Options.SaveTreeConcurrency, arch.blobSaver.Save, arch.Error) } func (arch *Archiver) stopWorkers() { @@ -809,7 +817,7 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps return nil, restic.ID{}, nil, err } - atree, err := NewTree(arch.FS, cleanTargets) + atree, err := newTree(arch.FS, cleanTargets) if err != nil { return nil, restic.ID{}, nil, err } diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index d67b5b06a..74fecef80 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -1121,7 +1121,7 @@ func TestArchiverSaveTree(t *testing.T) { test.prepare(t) } - atree, err := NewTree(testFS, test.targets) + atree, err := newTree(testFS, test.targets) if err != nil { t.Fatal(err) } diff --git a/internal/archiver/blob_saver.go b/internal/archiver/blob_saver.go index d4347a169..356a32ce2 100644 --- a/internal/archiver/blob_saver.go +++ b/internal/archiver/blob_saver.go @@ -9,22 +9,22 @@ import ( "golang.org/x/sync/errgroup" ) -// Saver allows saving a blob. -type Saver interface { +// saver allows saving a blob. +type saver interface { SaveBlob(ctx context.Context, t restic.BlobType, data []byte, id restic.ID, storeDuplicate bool) (restic.ID, bool, int, error) } -// BlobSaver concurrently saves incoming blobs to the repo. -type BlobSaver struct { - repo Saver +// blobSaver concurrently saves incoming blobs to the repo. +type blobSaver struct { + repo saver ch chan<- saveBlobJob } -// NewBlobSaver returns a new blob. A worker pool is started, it is stopped +// newBlobSaver returns a new blob. A worker pool is started, it is stopped // when ctx is cancelled. -func NewBlobSaver(ctx context.Context, wg *errgroup.Group, repo Saver, workers uint) *BlobSaver { +func newBlobSaver(ctx context.Context, wg *errgroup.Group, repo saver, workers uint) *blobSaver { ch := make(chan saveBlobJob) - s := &BlobSaver{ + s := &blobSaver{ repo: repo, ch: ch, } @@ -38,13 +38,13 @@ func NewBlobSaver(ctx context.Context, wg *errgroup.Group, repo Saver, workers u return s } -func (s *BlobSaver) TriggerShutdown() { +func (s *blobSaver) TriggerShutdown() { close(s.ch) } // Save stores a blob in the repo. It checks the index and the known blobs // before saving anything. It takes ownership of the buffer passed in. -func (s *BlobSaver) Save(ctx context.Context, t restic.BlobType, buf *Buffer, filename string, cb func(res SaveBlobResponse)) { +func (s *blobSaver) Save(ctx context.Context, t restic.BlobType, buf *buffer, filename string, cb func(res saveBlobResponse)) { select { case s.ch <- saveBlobJob{BlobType: t, buf: buf, fn: filename, cb: cb}: case <-ctx.Done(): @@ -54,26 +54,26 @@ func (s *BlobSaver) Save(ctx context.Context, t restic.BlobType, buf *Buffer, fi type saveBlobJob struct { restic.BlobType - buf *Buffer + buf *buffer fn string - cb func(res SaveBlobResponse) + cb func(res saveBlobResponse) } -type SaveBlobResponse struct { +type saveBlobResponse struct { id restic.ID length int sizeInRepo int known bool } -func (s *BlobSaver) saveBlob(ctx context.Context, t restic.BlobType, buf []byte) (SaveBlobResponse, error) { +func (s *blobSaver) saveBlob(ctx context.Context, t restic.BlobType, buf []byte) (saveBlobResponse, error) { id, known, sizeInRepo, err := s.repo.SaveBlob(ctx, t, buf, restic.ID{}, false) if err != nil { - return SaveBlobResponse{}, err + return saveBlobResponse{}, err } - return SaveBlobResponse{ + return saveBlobResponse{ id: id, length: len(buf), sizeInRepo: sizeInRepo, @@ -81,7 +81,7 @@ func (s *BlobSaver) saveBlob(ctx context.Context, t restic.BlobType, buf []byte) }, nil } -func (s *BlobSaver) worker(ctx context.Context, jobs <-chan saveBlobJob) error { +func (s *blobSaver) worker(ctx context.Context, jobs <-chan saveBlobJob) error { for { var job saveBlobJob var ok bool diff --git a/internal/archiver/blob_saver_test.go b/internal/archiver/blob_saver_test.go index f7ef2f47d..e23ed12e5 100644 --- a/internal/archiver/blob_saver_test.go +++ b/internal/archiver/blob_saver_test.go @@ -38,20 +38,20 @@ func TestBlobSaver(t *testing.T) { wg, ctx := errgroup.WithContext(ctx) saver := &saveFail{} - b := NewBlobSaver(ctx, wg, saver, uint(runtime.NumCPU())) + b := newBlobSaver(ctx, wg, saver, uint(runtime.NumCPU())) var wait sync.WaitGroup - var results []SaveBlobResponse + var results []saveBlobResponse var lock sync.Mutex wait.Add(20) for i := 0; i < 20; i++ { - buf := &Buffer{Data: []byte(fmt.Sprintf("foo%d", i))} + buf := &buffer{Data: []byte(fmt.Sprintf("foo%d", i))} idx := i lock.Lock() - results = append(results, SaveBlobResponse{}) + results = append(results, saveBlobResponse{}) lock.Unlock() - b.Save(ctx, restic.DataBlob, buf, "file", func(res SaveBlobResponse) { + b.Save(ctx, restic.DataBlob, buf, "file", func(res saveBlobResponse) { lock.Lock() results[idx] = res lock.Unlock() @@ -95,11 +95,11 @@ func TestBlobSaverError(t *testing.T) { failAt: int32(test.failAt), } - b := NewBlobSaver(ctx, wg, saver, uint(runtime.NumCPU())) + b := newBlobSaver(ctx, wg, saver, uint(runtime.NumCPU())) for i := 0; i < test.blobs; i++ { - buf := &Buffer{Data: []byte(fmt.Sprintf("foo%d", i))} - b.Save(ctx, restic.DataBlob, buf, "errfile", func(res SaveBlobResponse) {}) + buf := &buffer{Data: []byte(fmt.Sprintf("foo%d", i))} + b.Save(ctx, restic.DataBlob, buf, "errfile", func(res saveBlobResponse) {}) } b.TriggerShutdown() diff --git a/internal/archiver/buffer.go b/internal/archiver/buffer.go index 39bda2668..d5bfb46b3 100644 --- a/internal/archiver/buffer.go +++ b/internal/archiver/buffer.go @@ -1,14 +1,14 @@ package archiver -// Buffer is a reusable buffer. After the buffer has been used, Release should +// buffer is a reusable buffer. After the buffer has been used, Release should // be called so the underlying slice is put back into the pool. -type Buffer struct { +type buffer struct { Data []byte - pool *BufferPool + pool *bufferPool } // Release puts the buffer back into the pool it came from. -func (b *Buffer) Release() { +func (b *buffer) Release() { pool := b.pool if pool == nil || cap(b.Data) > pool.defaultSize { return @@ -20,32 +20,32 @@ func (b *Buffer) Release() { } } -// BufferPool implements a limited set of reusable buffers. -type BufferPool struct { - ch chan *Buffer +// bufferPool implements a limited set of reusable buffers. +type bufferPool struct { + ch chan *buffer defaultSize int } -// NewBufferPool initializes a new buffer pool. The pool stores at most max +// newBufferPool initializes a new buffer pool. The pool stores at most max // items. New buffers are created with defaultSize. Buffers that have grown // larger are not put back. -func NewBufferPool(max int, defaultSize int) *BufferPool { - b := &BufferPool{ - ch: make(chan *Buffer, max), +func newBufferPool(max int, defaultSize int) *bufferPool { + b := &bufferPool{ + ch: make(chan *buffer, max), defaultSize: defaultSize, } return b } // Get returns a new buffer, either from the pool or newly allocated. -func (pool *BufferPool) Get() *Buffer { +func (pool *bufferPool) Get() *buffer { select { case buf := <-pool.ch: return buf default: } - b := &Buffer{ + b := &buffer{ Data: make([]byte, pool.defaultSize), pool: pool, } diff --git a/internal/archiver/doc.go b/internal/archiver/doc.go index 928145aa2..1b9603975 100644 --- a/internal/archiver/doc.go +++ b/internal/archiver/doc.go @@ -1,12 +1,3 @@ // Package archiver contains the code which reads files, splits them into // chunks and saves the data to the repository. -// -// An Archiver has a number of worker goroutines handling saving the different -// data structures to the repository, the details are implemented by the -// FileSaver, BlobSaver, and TreeSaver types. -// -// The main goroutine (the one calling Snapshot()) traverses the directory tree -// and delegates all work to these worker pools. They return a type -// (FutureFile, FutureBlob, and FutureTree) which can be resolved later, by -// calling Wait() on it. package archiver diff --git a/internal/archiver/file_saver.go b/internal/archiver/file_saver.go index d10334301..fa19cab86 100644 --- a/internal/archiver/file_saver.go +++ b/internal/archiver/file_saver.go @@ -15,13 +15,13 @@ import ( "golang.org/x/sync/errgroup" ) -// SaveBlobFn saves a blob to a repo. -type SaveBlobFn func(context.Context, restic.BlobType, *Buffer, string, func(res SaveBlobResponse)) +// saveBlobFn saves a blob to a repo. +type saveBlobFn func(context.Context, restic.BlobType, *buffer, string, func(res saveBlobResponse)) -// FileSaver concurrently saves incoming files to the repo. -type FileSaver struct { - saveFilePool *BufferPool - saveBlob SaveBlobFn +// fileSaver concurrently saves incoming files to the repo. +type fileSaver struct { + saveFilePool *bufferPool + saveBlob saveBlobFn pol chunker.Pol @@ -32,18 +32,18 @@ type FileSaver struct { NodeFromFileInfo func(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) } -// NewFileSaver returns a new file saver. A worker pool with fileWorkers is +// newFileSaver returns a new file saver. A worker pool with fileWorkers is // started, it is stopped when ctx is cancelled. -func NewFileSaver(ctx context.Context, wg *errgroup.Group, save SaveBlobFn, pol chunker.Pol, fileWorkers, blobWorkers uint) *FileSaver { +func newFileSaver(ctx context.Context, wg *errgroup.Group, save saveBlobFn, pol chunker.Pol, fileWorkers, blobWorkers uint) *fileSaver { ch := make(chan saveFileJob) debug.Log("new file saver with %v file workers and %v blob workers", fileWorkers, blobWorkers) poolSize := fileWorkers + blobWorkers - s := &FileSaver{ + s := &fileSaver{ saveBlob: save, - saveFilePool: NewBufferPool(int(poolSize), chunker.MaxSize), + saveFilePool: newBufferPool(int(poolSize), chunker.MaxSize), pol: pol, ch: ch, @@ -60,18 +60,18 @@ func NewFileSaver(ctx context.Context, wg *errgroup.Group, save SaveBlobFn, pol return s } -func (s *FileSaver) TriggerShutdown() { +func (s *fileSaver) TriggerShutdown() { close(s.ch) } -// CompleteFunc is called when the file has been saved. -type CompleteFunc func(*restic.Node, ItemStats) +// fileCompleteFunc is called when the file has been saved. +type fileCompleteFunc func(*restic.Node, ItemStats) // Save stores the file f and returns the data once it has been completed. The // file is closed by Save. completeReading is only called if the file was read // successfully. complete is always called. If completeReading is called, then // this will always happen before calling complete. -func (s *FileSaver) Save(ctx context.Context, snPath string, target string, file fs.File, fi os.FileInfo, start func(), completeReading func(), complete CompleteFunc) FutureNode { +func (s *fileSaver) Save(ctx context.Context, snPath string, target string, file fs.File, fi os.FileInfo, start func(), completeReading func(), complete fileCompleteFunc) futureNode { fn, ch := newFutureNode() job := saveFileJob{ snPath: snPath, @@ -105,11 +105,11 @@ type saveFileJob struct { start func() completeReading func() - complete CompleteFunc + complete fileCompleteFunc } // saveFile stores the file f in the repo, then closes it. -func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPath string, target string, f fs.File, fi os.FileInfo, start func(), finishReading func(), finish func(res futureNodeResult)) { +func (s *fileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPath string, target string, f fs.File, fi os.FileInfo, start func(), finishReading func(), finish func(res futureNodeResult)) { start() fnr := futureNodeResult{ @@ -205,7 +205,7 @@ func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat node.Content = append(node.Content, restic.ID{}) lock.Unlock() - s.saveBlob(ctx, restic.DataBlob, buf, target, func(sbr SaveBlobResponse) { + s.saveBlob(ctx, restic.DataBlob, buf, target, func(sbr saveBlobResponse) { lock.Lock() if !sbr.known { fnr.stats.DataBlobs++ @@ -246,7 +246,7 @@ func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat completeBlob() } -func (s *FileSaver) worker(ctx context.Context, jobs <-chan saveFileJob) { +func (s *fileSaver) worker(ctx context.Context, jobs <-chan saveFileJob) { // a worker has one chunker which is reused for each file (because it contains a rather large buffer) chnker := chunker.New(nil, s.pol) diff --git a/internal/archiver/file_saver_test.go b/internal/archiver/file_saver_test.go index 948d7ce3c..ede616e28 100644 --- a/internal/archiver/file_saver_test.go +++ b/internal/archiver/file_saver_test.go @@ -30,11 +30,11 @@ func createTestFiles(t testing.TB, num int) (files []string) { return files } -func startFileSaver(ctx context.Context, t testing.TB) (*FileSaver, context.Context, *errgroup.Group) { +func startFileSaver(ctx context.Context, t testing.TB) (*fileSaver, context.Context, *errgroup.Group) { wg, ctx := errgroup.WithContext(ctx) - saveBlob := func(ctx context.Context, tpe restic.BlobType, buf *Buffer, _ string, cb func(SaveBlobResponse)) { - cb(SaveBlobResponse{ + saveBlob := func(ctx context.Context, tpe restic.BlobType, buf *buffer, _ string, cb func(saveBlobResponse)) { + cb(saveBlobResponse{ id: restic.Hash(buf.Data), length: len(buf.Data), sizeInRepo: len(buf.Data), @@ -48,7 +48,7 @@ func startFileSaver(ctx context.Context, t testing.TB) (*FileSaver, context.Cont t.Fatal(err) } - s := NewFileSaver(ctx, wg, saveBlob, pol, workers, workers) + s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers) s.NodeFromFileInfo = func(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) { return fs.NodeFromFileInfo(filename, fi, ignoreXattrListError) } @@ -69,7 +69,7 @@ func TestFileSaver(t *testing.T) { testFs := fs.Local{} s, ctx, wg := startFileSaver(ctx, t) - var results []FutureNode + var results []futureNode for _, filename := range files { f, err := testFs.OpenFile(filename, os.O_RDONLY, 0) diff --git a/internal/archiver/scanner.go b/internal/archiver/scanner.go index d61e5ce47..433d38821 100644 --- a/internal/archiver/scanner.go +++ b/internal/archiver/scanner.go @@ -38,7 +38,7 @@ type ScanStats struct { Bytes uint64 } -func (s *Scanner) scanTree(ctx context.Context, stats ScanStats, tree Tree) (ScanStats, error) { +func (s *Scanner) scanTree(ctx context.Context, stats ScanStats, tree tree) (ScanStats, error) { // traverse the path in the file system for all leaf nodes if tree.Leaf() { abstarget, err := s.FS.Abs(tree.Path) @@ -83,7 +83,7 @@ func (s *Scanner) Scan(ctx context.Context, targets []string) error { debug.Log("clean targets %v", cleanTargets) // we're using the same tree representation as the archiver does - tree, err := NewTree(s.FS, cleanTargets) + tree, err := newTree(s.FS, cleanTargets) if err != nil { return err } diff --git a/internal/archiver/tree.go b/internal/archiver/tree.go index cd03ba521..f4eb1abde 100644 --- a/internal/archiver/tree.go +++ b/internal/archiver/tree.go @@ -9,7 +9,7 @@ import ( "github.com/restic/restic/internal/fs" ) -// Tree recursively defines how a snapshot should look like when +// tree recursively defines how a snapshot should look like when // archived. // // When `Path` is set, this is a leaf node and the contents of `Path` should be @@ -20,8 +20,8 @@ import ( // // `FileInfoPath` is used to extract metadata for intermediate (=non-leaf) // trees. -type Tree struct { - Nodes map[string]Tree +type tree struct { + Nodes map[string]tree Path string // where the files/dirs to be saved are found FileInfoPath string // where the dir can be found that is not included itself, but its subdirs Root string // parent directory of the tree @@ -95,13 +95,13 @@ func rootDirectory(fs fs.FS, target string) string { } // Add adds a new file or directory to the tree. -func (t *Tree) Add(fs fs.FS, path string) error { +func (t *tree) Add(fs fs.FS, path string) error { if path == "" { panic("invalid path (empty string)") } if t.Nodes == nil { - t.Nodes = make(map[string]Tree) + t.Nodes = make(map[string]tree) } pc, virtualPrefix := pathComponents(fs, path, false) @@ -111,7 +111,7 @@ func (t *Tree) Add(fs fs.FS, path string) error { name := pc[0] root := rootDirectory(fs, path) - tree := Tree{Root: root} + tree := tree{Root: root} origName := name i := 0 @@ -152,63 +152,63 @@ func (t *Tree) Add(fs fs.FS, path string) error { } // add adds a new target path into the tree. -func (t *Tree) add(fs fs.FS, target, root string, pc []string) error { +func (t *tree) add(fs fs.FS, target, root string, pc []string) error { if len(pc) == 0 { return errors.Errorf("invalid path %q", target) } if t.Nodes == nil { - t.Nodes = make(map[string]Tree) + t.Nodes = make(map[string]tree) } name := pc[0] if len(pc) == 1 { - tree, ok := t.Nodes[name] + node, ok := t.Nodes[name] if !ok { - t.Nodes[name] = Tree{Path: target} + t.Nodes[name] = tree{Path: target} return nil } - if tree.Path != "" { + if node.Path != "" { return errors.Errorf("path is already set for target %v", target) } - tree.Path = target - t.Nodes[name] = tree + node.Path = target + t.Nodes[name] = node return nil } - tree := Tree{} + node := tree{} if other, ok := t.Nodes[name]; ok { - tree = other + node = other } subroot := fs.Join(root, name) - tree.FileInfoPath = subroot + node.FileInfoPath = subroot - err := tree.add(fs, target, subroot, pc[1:]) + err := node.add(fs, target, subroot, pc[1:]) if err != nil { return err } - t.Nodes[name] = tree + t.Nodes[name] = node return nil } -func (t Tree) String() string { +func (t tree) String() string { return formatTree(t, "") } // Leaf returns true if this is a leaf node, which means Path is set to a // non-empty string and the contents of Path should be inserted at this point // in the tree. -func (t Tree) Leaf() bool { +func (t tree) Leaf() bool { return t.Path != "" } // NodeNames returns the sorted list of subtree names. -func (t Tree) NodeNames() []string { +func (t tree) NodeNames() []string { // iterate over the nodes of atree in lexicographic (=deterministic) order names := make([]string, 0, len(t.Nodes)) for name := range t.Nodes { @@ -219,7 +219,7 @@ func (t Tree) NodeNames() []string { } // formatTree returns a text representation of the tree t. -func formatTree(t Tree, indent string) (s string) { +func formatTree(t tree, indent string) (s string) { for name, node := range t.Nodes { s += fmt.Sprintf("%v/%v, root %q, path %q, meta %q\n", indent, name, node.Root, node.Path, node.FileInfoPath) s += formatTree(node, indent+" ") @@ -228,7 +228,7 @@ func formatTree(t Tree, indent string) (s string) { } // unrollTree unrolls the tree so that only leaf nodes have Path set. -func unrollTree(f fs.FS, t *Tree) error { +func unrollTree(f fs.FS, t *tree) error { // if the current tree is a leaf node (Path is set) and has additional // nodes, add the contents of Path to the nodes. if t.Path != "" && len(t.Nodes) > 0 { @@ -252,7 +252,7 @@ func unrollTree(f fs.FS, t *Tree) error { return errors.Errorf("tree unrollTree: collision on path, node %#v, path %q", node, f.Join(t.Path, entry)) } - t.Nodes[entry] = Tree{Path: f.Join(t.Path, entry)} + t.Nodes[entry] = tree{Path: f.Join(t.Path, entry)} } t.Path = "" } @@ -269,10 +269,10 @@ func unrollTree(f fs.FS, t *Tree) error { return nil } -// NewTree creates a Tree from the target files/directories. -func NewTree(fs fs.FS, targets []string) (*Tree, error) { +// newTree creates a Tree from the target files/directories. +func newTree(fs fs.FS, targets []string) (*tree, error) { debug.Log("targets: %v", targets) - tree := &Tree{} + tree := &tree{} seen := make(map[string]struct{}) for _, target := range targets { target = fs.Clean(target) diff --git a/internal/archiver/tree_saver.go b/internal/archiver/tree_saver.go index 9c11b48f0..aeedefef5 100644 --- a/internal/archiver/tree_saver.go +++ b/internal/archiver/tree_saver.go @@ -9,20 +9,20 @@ import ( "golang.org/x/sync/errgroup" ) -// TreeSaver concurrently saves incoming trees to the repo. -type TreeSaver struct { - saveBlob SaveBlobFn +// treeSaver concurrently saves incoming trees to the repo. +type treeSaver struct { + saveBlob saveBlobFn errFn ErrorFunc ch chan<- saveTreeJob } -// NewTreeSaver returns a new tree saver. A worker pool with treeWorkers is +// newTreeSaver returns a new tree saver. A worker pool with treeWorkers is // started, it is stopped when ctx is cancelled. -func NewTreeSaver(ctx context.Context, wg *errgroup.Group, treeWorkers uint, saveBlob SaveBlobFn, errFn ErrorFunc) *TreeSaver { +func newTreeSaver(ctx context.Context, wg *errgroup.Group, treeWorkers uint, saveBlob saveBlobFn, errFn ErrorFunc) *treeSaver { ch := make(chan saveTreeJob) - s := &TreeSaver{ + s := &treeSaver{ ch: ch, saveBlob: saveBlob, errFn: errFn, @@ -37,12 +37,12 @@ func NewTreeSaver(ctx context.Context, wg *errgroup.Group, treeWorkers uint, sav return s } -func (s *TreeSaver) TriggerShutdown() { +func (s *treeSaver) TriggerShutdown() { close(s.ch) } // Save stores the dir d and returns the data once it has been completed. -func (s *TreeSaver) Save(ctx context.Context, snPath string, target string, node *restic.Node, nodes []FutureNode, complete CompleteFunc) FutureNode { +func (s *treeSaver) Save(ctx context.Context, snPath string, target string, node *restic.Node, nodes []futureNode, complete fileCompleteFunc) futureNode { fn, ch := newFutureNode() job := saveTreeJob{ snPath: snPath, @@ -66,13 +66,13 @@ type saveTreeJob struct { snPath string target string node *restic.Node - nodes []FutureNode + nodes []futureNode ch chan<- futureNodeResult - complete CompleteFunc + complete fileCompleteFunc } // save stores the nodes as a tree in the repo. -func (s *TreeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, ItemStats, error) { +func (s *treeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, ItemStats, error) { var stats ItemStats node := job.node nodes := job.nodes @@ -84,7 +84,7 @@ func (s *TreeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, I for i, fn := range nodes { // fn is a copy, so clear the original value explicitly - nodes[i] = FutureNode{} + nodes[i] = futureNode{} fnr := fn.take(ctx) // return the error if it wasn't ignored @@ -128,9 +128,9 @@ func (s *TreeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, I return nil, stats, err } - b := &Buffer{Data: buf} - ch := make(chan SaveBlobResponse, 1) - s.saveBlob(ctx, restic.TreeBlob, b, job.target, func(res SaveBlobResponse) { + b := &buffer{Data: buf} + ch := make(chan saveBlobResponse, 1) + s.saveBlob(ctx, restic.TreeBlob, b, job.target, func(res saveBlobResponse) { ch <- res }) @@ -149,7 +149,7 @@ func (s *TreeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, I } } -func (s *TreeSaver) worker(ctx context.Context, jobs <-chan saveTreeJob) error { +func (s *treeSaver) worker(ctx context.Context, jobs <-chan saveTreeJob) error { for { var job saveTreeJob var ok bool diff --git a/internal/archiver/tree_saver_test.go b/internal/archiver/tree_saver_test.go index 47a3f3842..4aa4c51f1 100644 --- a/internal/archiver/tree_saver_test.go +++ b/internal/archiver/tree_saver_test.go @@ -12,8 +12,8 @@ import ( "golang.org/x/sync/errgroup" ) -func treeSaveHelper(_ context.Context, _ restic.BlobType, buf *Buffer, _ string, cb func(res SaveBlobResponse)) { - cb(SaveBlobResponse{ +func treeSaveHelper(_ context.Context, _ restic.BlobType, buf *buffer, _ string, cb func(res saveBlobResponse)) { + cb(saveBlobResponse{ id: restic.NewRandomID(), known: false, length: len(buf.Data), @@ -21,7 +21,7 @@ func treeSaveHelper(_ context.Context, _ restic.BlobType, buf *Buffer, _ string, }) } -func setupTreeSaver() (context.Context, context.CancelFunc, *TreeSaver, func() error) { +func setupTreeSaver() (context.Context, context.CancelFunc, *treeSaver, func() error) { ctx, cancel := context.WithCancel(context.Background()) wg, ctx := errgroup.WithContext(ctx) @@ -29,7 +29,7 @@ func setupTreeSaver() (context.Context, context.CancelFunc, *TreeSaver, func() e return err } - b := NewTreeSaver(ctx, wg, uint(runtime.NumCPU()), treeSaveHelper, errFn) + b := newTreeSaver(ctx, wg, uint(runtime.NumCPU()), treeSaveHelper, errFn) shutdown := func() error { b.TriggerShutdown() @@ -43,7 +43,7 @@ func TestTreeSaver(t *testing.T) { ctx, cancel, b, shutdown := setupTreeSaver() defer cancel() - var results []FutureNode + var results []futureNode for i := 0; i < 20; i++ { node := &restic.Node{ @@ -83,13 +83,13 @@ func TestTreeSaverError(t *testing.T) { ctx, cancel, b, shutdown := setupTreeSaver() defer cancel() - var results []FutureNode + var results []futureNode for i := 0; i < test.trees; i++ { node := &restic.Node{ Name: fmt.Sprintf("file-%d", i), } - nodes := []FutureNode{ + nodes := []futureNode{ newFutureNodeWithResult(futureNodeResult{node: &restic.Node{ Name: fmt.Sprintf("child-%d", i), }}), @@ -128,7 +128,7 @@ func TestTreeSaverDuplicates(t *testing.T) { node := &restic.Node{ Name: "file", } - nodes := []FutureNode{ + nodes := []futureNode{ newFutureNodeWithResult(futureNodeResult{node: &restic.Node{ Name: "child", }}), diff --git a/internal/archiver/tree_test.go b/internal/archiver/tree_test.go index a9d2d97ff..c9fe776b1 100644 --- a/internal/archiver/tree_test.go +++ b/internal/archiver/tree_test.go @@ -12,7 +12,7 @@ import ( ) // debug.Log requires Tree.String. -var _ fmt.Stringer = Tree{} +var _ fmt.Stringer = tree{} func TestPathComponents(t *testing.T) { var tests = []struct { @@ -142,20 +142,20 @@ func TestTree(t *testing.T) { var tests = []struct { targets []string src TestDir - want Tree + want tree unix bool win bool mustError bool }{ { targets: []string{"foo"}, - want: Tree{Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ "foo": {Path: "foo", Root: "."}, }}, }, { targets: []string{"foo", "bar", "baz"}, - want: Tree{Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ "foo": {Path: "foo", Root: "."}, "bar": {Path: "bar", Root: "."}, "baz": {Path: "baz", Root: "."}, @@ -163,8 +163,8 @@ func TestTree(t *testing.T) { }, { targets: []string{"foo/user1", "foo/user2", "foo/other"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/user1")}, "user2": {Path: filepath.FromSlash("foo/user2")}, "other": {Path: filepath.FromSlash("foo/other")}, @@ -173,9 +173,9 @@ func TestTree(t *testing.T) { }, { targets: []string{"foo/work/user1", "foo/work/user2"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ - "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ + "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/work/user1")}, "user2": {Path: filepath.FromSlash("foo/work/user2")}, }}, @@ -184,50 +184,50 @@ func TestTree(t *testing.T) { }, { targets: []string{"foo/user1", "bar/user1", "foo/other"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/user1")}, "other": {Path: filepath.FromSlash("foo/other")}, }}, - "bar": {Root: ".", FileInfoPath: "bar", Nodes: map[string]Tree{ + "bar": {Root: ".", FileInfoPath: "bar", Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("bar/user1")}, }}, }}, }, { targets: []string{"../work"}, - want: Tree{Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ "work": {Root: "..", Path: filepath.FromSlash("../work")}, }}, }, { targets: []string{"../work/other"}, - want: Tree{Nodes: map[string]Tree{ - "work": {Root: "..", FileInfoPath: filepath.FromSlash("../work"), Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "work": {Root: "..", FileInfoPath: filepath.FromSlash("../work"), Nodes: map[string]tree{ "other": {Path: filepath.FromSlash("../work/other")}, }}, }}, }, { targets: []string{"foo/user1", "../work/other", "foo/user2"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/user1")}, "user2": {Path: filepath.FromSlash("foo/user2")}, }}, - "work": {Root: "..", FileInfoPath: filepath.FromSlash("../work"), Nodes: map[string]Tree{ + "work": {Root: "..", FileInfoPath: filepath.FromSlash("../work"), Nodes: map[string]tree{ "other": {Path: filepath.FromSlash("../work/other")}, }}, }}, }, { targets: []string{"foo/user1", "../foo/other", "foo/user2"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/user1")}, "user2": {Path: filepath.FromSlash("foo/user2")}, }}, - "foo-1": {Root: "..", FileInfoPath: filepath.FromSlash("../foo"), Nodes: map[string]Tree{ + "foo-1": {Root: "..", FileInfoPath: filepath.FromSlash("../foo"), Nodes: map[string]tree{ "other": {Path: filepath.FromSlash("../foo/other")}, }}, }}, @@ -240,11 +240,11 @@ func TestTree(t *testing.T) { }, }, targets: []string{"foo", "foo/work"}, - want: Tree{Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ "foo": { Root: ".", FileInfoPath: "foo", - Nodes: map[string]Tree{ + Nodes: map[string]tree{ "file": {Path: filepath.FromSlash("foo/file")}, "work": {Path: filepath.FromSlash("foo/work")}, }, @@ -261,11 +261,11 @@ func TestTree(t *testing.T) { }, }, targets: []string{"foo/work", "foo"}, - want: Tree{Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ "foo": { Root: ".", FileInfoPath: "foo", - Nodes: map[string]Tree{ + Nodes: map[string]tree{ "file": {Path: filepath.FromSlash("foo/file")}, "work": {Path: filepath.FromSlash("foo/work")}, }, @@ -282,11 +282,11 @@ func TestTree(t *testing.T) { }, }, targets: []string{"foo/work", "foo/work/user2"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "work": { FileInfoPath: filepath.FromSlash("foo/work"), - Nodes: map[string]Tree{ + Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/work/user1")}, "user2": {Path: filepath.FromSlash("foo/work/user2")}, }, @@ -304,10 +304,10 @@ func TestTree(t *testing.T) { }, }, targets: []string{"foo/work/user2", "foo/work"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "work": {FileInfoPath: filepath.FromSlash("foo/work"), - Nodes: map[string]Tree{ + Nodes: map[string]tree{ "user1": {Path: filepath.FromSlash("foo/work/user1")}, "user2": {Path: filepath.FromSlash("foo/work/user2")}, }, @@ -332,12 +332,12 @@ func TestTree(t *testing.T) { }, }, targets: []string{"foo/work/user2/data/secret", "foo"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ "other": {Path: filepath.FromSlash("foo/other")}, - "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]Tree{ - "user2": {FileInfoPath: filepath.FromSlash("foo/work/user2"), Nodes: map[string]Tree{ - "data": {FileInfoPath: filepath.FromSlash("foo/work/user2/data"), Nodes: map[string]Tree{ + "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]tree{ + "user2": {FileInfoPath: filepath.FromSlash("foo/work/user2"), Nodes: map[string]tree{ + "data": {FileInfoPath: filepath.FromSlash("foo/work/user2/data"), Nodes: map[string]tree{ "secret": { Path: filepath.FromSlash("foo/work/user2/data/secret"), }, @@ -368,10 +368,10 @@ func TestTree(t *testing.T) { }, unix: true, targets: []string{"mnt/driveA", "mnt/driveA/work/driveB"}, - want: Tree{Nodes: map[string]Tree{ - "mnt": {Root: ".", FileInfoPath: filepath.FromSlash("mnt"), Nodes: map[string]Tree{ - "driveA": {FileInfoPath: filepath.FromSlash("mnt/driveA"), Nodes: map[string]Tree{ - "work": {FileInfoPath: filepath.FromSlash("mnt/driveA/work"), Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "mnt": {Root: ".", FileInfoPath: filepath.FromSlash("mnt"), Nodes: map[string]tree{ + "driveA": {FileInfoPath: filepath.FromSlash("mnt/driveA"), Nodes: map[string]tree{ + "work": {FileInfoPath: filepath.FromSlash("mnt/driveA/work"), Nodes: map[string]tree{ "driveB": { Path: filepath.FromSlash("mnt/driveA/work/driveB"), }, @@ -384,9 +384,9 @@ func TestTree(t *testing.T) { }, { targets: []string{"foo/work/user", "foo/work/user"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ - "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ + "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]tree{ "user": {Path: filepath.FromSlash("foo/work/user")}, }}, }}, @@ -394,9 +394,9 @@ func TestTree(t *testing.T) { }, { targets: []string{"./foo/work/user", "foo/work/user"}, - want: Tree{Nodes: map[string]Tree{ - "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]Tree{ - "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "foo": {Root: ".", FileInfoPath: "foo", Nodes: map[string]tree{ + "work": {FileInfoPath: filepath.FromSlash("foo/work"), Nodes: map[string]tree{ "user": {Path: filepath.FromSlash("foo/work/user")}, }}, }}, @@ -405,10 +405,10 @@ func TestTree(t *testing.T) { { win: true, targets: []string{`c:\users\foobar\temp`}, - want: Tree{Nodes: map[string]Tree{ - "c": {Root: `c:\`, FileInfoPath: `c:\`, Nodes: map[string]Tree{ - "users": {FileInfoPath: `c:\users`, Nodes: map[string]Tree{ - "foobar": {FileInfoPath: `c:\users\foobar`, Nodes: map[string]Tree{ + want: tree{Nodes: map[string]tree{ + "c": {Root: `c:\`, FileInfoPath: `c:\`, Nodes: map[string]tree{ + "users": {FileInfoPath: `c:\users`, Nodes: map[string]tree{ + "foobar": {FileInfoPath: `c:\users\foobar`, Nodes: map[string]tree{ "temp": {Path: `c:\users\foobar\temp`}, }}, }}, @@ -445,7 +445,7 @@ func TestTree(t *testing.T) { back := rtest.Chdir(t, tempdir) defer back() - tree, err := NewTree(fs.Local{}, test.targets) + tree, err := newTree(fs.Local{}, test.targets) if test.mustError { if err == nil { t.Fatal("expected error, got nil")