mirror of
https://github.com/restic/restic.git
synced 2026-06-21 08:04:18 +00:00
Merge pull request #21882 from MichaelEischer/untangle-imports
Untangle imports of repository, restorer, fs
This commit is contained in:
@@ -179,7 +179,7 @@ func parsePercentage(s string) (float64, error) {
|
||||
// - if the user explicitly requested --no-cache, we don't use any cache
|
||||
// - if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
|
||||
// - by default, we use a cache in a temporary directory that is deleted after the check
|
||||
func prepareCheckCache(opts CheckOptions, gopts *global.Options, printer progress.Printer) (cleanup func()) {
|
||||
func prepareCheckCache(opts CheckOptions, gopts *global.Options, printer restic.Printer) (cleanup func()) {
|
||||
cleanup = func() {}
|
||||
if opts.WithCache {
|
||||
// use the default cache, no setup needed
|
||||
@@ -229,7 +229,7 @@ func prepareCheckCache(opts CheckOptions, gopts *global.Options, printer progres
|
||||
func runCheck(ctx context.Context, opts CheckOptions, gopts global.Options, args []string, term ui.Terminal) (checkSummary, error) {
|
||||
summary := checkSummary{MessageType: "summary"}
|
||||
|
||||
var printer progress.Printer
|
||||
var printer restic.Printer
|
||||
if !gopts.JSON {
|
||||
printer = progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
} else {
|
||||
@@ -428,7 +428,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts global.Options, args
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func buildPacksFilter(opts CheckOptions, printer progress.Printer,
|
||||
func buildPacksFilter(opts CheckOptions, printer restic.Printer,
|
||||
filteredStatus bool) (func(packs map[restic.ID]int64) map[restic.ID]int64, error) {
|
||||
typeData := ""
|
||||
if filteredStatus {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func TestParsePercentage(t *testing.T) {
|
||||
@@ -204,7 +203,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
gopts := global.Options{CacheDir: tmpDirBase}
|
||||
cleanup := prepareCheckCache(testCase.opts, &gopts, progress.NewNoopPrinter())
|
||||
cleanup := prepareCheckCache(testCase.opts, &gopts, restic.NewNoopPrinter())
|
||||
files, err := os.ReadDir(tmpDirBase)
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -234,7 +233,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||
|
||||
func TestPrepareDefaultCheckCache(t *testing.T) {
|
||||
gopts := global.Options{CacheDir: ""}
|
||||
cleanup := prepareCheckCache(CheckOptions{}, &gopts, progress.NewNoopPrinter())
|
||||
cleanup := prepareCheckCache(CheckOptions{}, &gopts, restic.NewNoopPrinter())
|
||||
_, err := os.ReadDir(gopts.CacheDir)
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
||||
// collectAllSnapshots: select all snapshot trees to be copied
|
||||
func collectAllSnapshots(ctx context.Context, opts CopyOptions,
|
||||
srcSnapshotLister restic.Lister, srcRepo restic.Repository,
|
||||
dstSnapshotByOriginal map[restic.ID][]*data.Snapshot, args []string, printer progress.Printer,
|
||||
dstSnapshotByOriginal map[restic.ID][]*data.Snapshot, args []string, printer restic.Printer,
|
||||
) iter.Seq[*data.Snapshot] {
|
||||
return func(yield func(*data.Snapshot) bool) {
|
||||
for sn := range FindFilteredSnapshots(ctx, srcSnapshotLister, srcRepo, &opts.SnapshotFilter, args, printer) {
|
||||
@@ -190,7 +190,7 @@ func similarSnapshots(sna *data.Snapshot, snb *data.Snapshot) bool {
|
||||
// copyTreeBatched copies multiple snapshots in one go. Snapshots are written after
|
||||
// data equivalent to at least 10 packfiles was written.
|
||||
func copyTreeBatched(ctx context.Context, srcRepo *repository.Repository, dstRepo restic.Repository,
|
||||
selectedSnapshots iter.Seq[*data.Snapshot], printer progress.Printer) error {
|
||||
selectedSnapshots iter.Seq[*data.Snapshot], printer restic.Printer) error {
|
||||
|
||||
// remember already processed trees across all snapshots
|
||||
visitedTrees := srcRepo.NewAssociatedBlobSet()
|
||||
@@ -255,7 +255,7 @@ func copyTreeBatched(ctx context.Context, srcRepo *repository.Repository, dstRep
|
||||
}
|
||||
|
||||
func copyTree(ctx context.Context, srcRepo *repository.Repository, dstRepo restic.Repository,
|
||||
visitedTrees restic.AssociatedBlobSet, rootTreeID restic.ID, printer progress.Printer, uploader restic.BlobSaverWithAsync) (uint64, error) {
|
||||
visitedTrees restic.AssociatedBlobSet, rootTreeID restic.ID, printer restic.Printer, uploader restic.BlobSaverWithAsync) (uint64, error) {
|
||||
|
||||
copyBlobs := srcRepo.NewAssociatedBlobSet()
|
||||
packList := restic.NewIDSet()
|
||||
@@ -312,7 +312,7 @@ func copyTree(ctx context.Context, srcRepo *repository.Repository, dstRepo resti
|
||||
}
|
||||
|
||||
// copyStats: print statistics for the blobs to be copied
|
||||
func copyStats(srcRepo restic.Repository, copyBlobs restic.AssociatedBlobSet, packList restic.IDSet, printer progress.Printer) uint64 {
|
||||
func copyStats(srcRepo restic.Repository, copyBlobs restic.AssociatedBlobSet, packList restic.IDSet, printer restic.Printer) uint64 {
|
||||
// count and size
|
||||
countBlobs := 0
|
||||
sizeBlobs := uint64(0)
|
||||
@@ -329,7 +329,7 @@ func copyStats(srcRepo restic.Repository, copyBlobs restic.AssociatedBlobSet, pa
|
||||
return sizeBlobs
|
||||
}
|
||||
|
||||
func copySaveSnapshot(ctx context.Context, sn *data.Snapshot, dstRepo restic.Repository, printer progress.Printer) error {
|
||||
func copySaveSnapshot(ctx context.Context, sn *data.Snapshot, dstRepo restic.Repository, printer restic.Printer) error {
|
||||
sn.Parent = nil // Parent does not have relevance in the new repo.
|
||||
// Use Original as a persistent snapshot ID
|
||||
if sn.Original == nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -55,7 +56,7 @@ func (opts *generateOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&opts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)")
|
||||
}
|
||||
|
||||
func writeManpages(root *cobra.Command, dir string, printer progress.Printer) error {
|
||||
func writeManpages(root *cobra.Command, dir string, printer restic.Printer) error {
|
||||
// use a fixed date for the man pages so that generating them is deterministic
|
||||
date, err := time.Parse("Jan 2006", "Jan 2017")
|
||||
if err != nil {
|
||||
@@ -73,7 +74,7 @@ func writeManpages(root *cobra.Command, dir string, printer progress.Printer) er
|
||||
return doc.GenManTree(root, header, dir)
|
||||
}
|
||||
|
||||
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer, gopts global.Options) (err error) {
|
||||
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer restic.Printer, gopts global.Options) (err error) {
|
||||
printer.PT("writing %s completion file to %v", shell, filename)
|
||||
var outWriter io.Writer
|
||||
if filename != "-" {
|
||||
|
||||
@@ -108,7 +108,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts global.Options, args [
|
||||
return nil
|
||||
}
|
||||
|
||||
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts global.Options, printer progress.Printer) (*chunker.Pol, error) {
|
||||
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts global.Options, printer restic.Printer) (*chunker.Pol, error) {
|
||||
if opts.CopyChunkerParameters {
|
||||
otherGopts, _, err := opts.SecondaryRepoOptions.FillGlobalOpts(ctx, gopts, "secondary")
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunInit(t testing.TB, gopts global.Options) {
|
||||
@@ -57,14 +56,14 @@ func TestInitCopyChunkerParams(t *testing.T) {
|
||||
|
||||
var repo *repository.Repository
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
repo, err = global.OpenRepository(ctx, gopts, restic.NewNoopPrinter())
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
var otherRepo *repository.Repository
|
||||
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
otherRepo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
otherRepo, err = global.OpenRepository(ctx, gopts, restic.NewNoopPrinter())
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -70,7 +71,7 @@ func runKeyAdd(ctx context.Context, gopts global.Options, opts KeyAddOptions, ar
|
||||
return addKey(ctx, repo, gopts, opts, printer)
|
||||
}
|
||||
|
||||
func addKey(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyAddOptions, printer progress.Printer) error {
|
||||
func addKey(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyAddOptions, printer restic.Printer) error {
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunKeyListOtherIDs(t testing.TB, gopts global.Options) []string {
|
||||
@@ -63,7 +63,7 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts global.Options) {
|
||||
rtest.OK(t, err)
|
||||
|
||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
repo, err := global.OpenRepository(ctx, gopts, restic.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
err = repo.SearchKey(ctx, testKeyNewPassword, 2, "")
|
||||
rtest.OK(t, err)
|
||||
@@ -107,7 +107,7 @@ func testRunKeyPasswdUserHost(t testing.TB, newPassword string, gopts global.Opt
|
||||
|
||||
gopts.Password = testKeyNewPassword
|
||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
repo, err := global.OpenRepository(ctx, gopts, restic.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
err = repo.SearchKey(ctx, testKeyNewPassword, 1, "")
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -56,7 +56,7 @@ func runKeyList(ctx context.Context, gopts global.Options, args []string, term u
|
||||
return listKeys(ctx, repo, gopts, printer)
|
||||
}
|
||||
|
||||
func listKeys(ctx context.Context, s *repository.Repository, gopts global.Options, printer progress.Printer) error {
|
||||
func listKeys(ctx context.Context, s *repository.Repository, gopts global.Options, printer restic.Printer) error {
|
||||
type keyInfo struct {
|
||||
Current bool `json:"current"`
|
||||
ID string `json:"id"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -65,7 +66,7 @@ func runKeyPasswd(ctx context.Context, gopts global.Options, opts KeyPasswdOptio
|
||||
return changePassword(ctx, repo, gopts, opts, printer)
|
||||
}
|
||||
|
||||
func changePassword(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyPasswdOptions, printer progress.Printer) error {
|
||||
func changePassword(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyPasswdOptions, printer restic.Printer) error {
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -53,7 +53,7 @@ func runKeyRemove(ctx context.Context, gopts global.Options, args []string, term
|
||||
return deleteKey(ctx, repo, args[0], printer)
|
||||
}
|
||||
|
||||
func deleteKey(ctx context.Context, repo *repository.Repository, idPrefix string, printer progress.Printer) error {
|
||||
func deleteKey(ctx context.Context, repo *repository.Repository, idPrefix string, printer restic.Printer) error {
|
||||
id, err := restic.Find(ctx, repo, restic.KeyFile, idPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -96,7 +96,7 @@ func runList(ctx context.Context, gopts global.Options, args []string, term ui.T
|
||||
|
||||
// packfileList handles the list packs <snapshotID> variant.
|
||||
// It prints a sorted list of packfiles belonging to this snapshot.
|
||||
func packfileList(ctx context.Context, repo restic.Repository, snapshotID string, printer progress.Printer) error {
|
||||
func packfileList(ctx context.Context, repo restic.Repository, snapshotID string, printer restic.Printer) error {
|
||||
sn, _, err := (&data.SnapshotFilter{}).FindLatest(ctx, repo, repo, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("required snapshot ID %q not found", snapshotID)
|
||||
|
||||
@@ -53,7 +53,7 @@ func (opts *MigrateOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVarP(&opts.Force, "force", "f", false, `apply a migration a second time`)
|
||||
}
|
||||
|
||||
func checkMigrations(ctx context.Context, repo restic.Repository, printer progress.Printer) error {
|
||||
func checkMigrations(ctx context.Context, repo restic.Repository, printer restic.Printer) error {
|
||||
printer.P("available migrations:\n")
|
||||
found := false
|
||||
|
||||
@@ -76,7 +76,7 @@ func checkMigrations(ctx context.Context, repo restic.Repository, printer progre
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Options, repo restic.Repository, args []string, term ui.Terminal, printer progress.Printer) error {
|
||||
func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Options, repo restic.Repository, args []string, term ui.Terminal, printer restic.Printer) error {
|
||||
var firsterr error
|
||||
for _, name := range args {
|
||||
found := false
|
||||
|
||||
+5
-18
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
"github.com/restic/restic/internal/fuse"
|
||||
|
||||
systemFuse "github.com/anacrolix/fuse"
|
||||
"github.com/anacrolix/fuse/fs"
|
||||
fusefs "github.com/anacrolix/fuse/fs"
|
||||
)
|
||||
|
||||
func registerMountCommand(cmdRoot *cobra.Command, globalOptions *global.Options) {
|
||||
@@ -197,7 +198,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
err = fs.Serve(c, root)
|
||||
err = fusefs.Serve(c, root)
|
||||
}()
|
||||
|
||||
printer.S("Now serving the repository at %s", mountpoint)
|
||||
@@ -271,9 +272,9 @@ func checkMountpointOverlap(repoPath, mountpoint string) error {
|
||||
switch {
|
||||
case rp == mp:
|
||||
return errors.Fatal(fmt.Sprintf("mountpoint %s is the local repository directory%s", mp, tail))
|
||||
case isInside(rp, mp):
|
||||
case fs.HasPathPrefix(rp, mp):
|
||||
return errors.Fatal(fmt.Sprintf("mountpoint %s is inside the local repository directory %s%s", mp, rp, tail))
|
||||
case isInside(mp, rp):
|
||||
case fs.HasPathPrefix(mp, rp):
|
||||
return errors.Fatal(fmt.Sprintf("local repository directory %s is inside the mountpoint %s%s", rp, mp, tail))
|
||||
}
|
||||
return nil
|
||||
@@ -294,17 +295,3 @@ func resolvePath(p string) (string, error) {
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// isInside reports whether child is strictly nested inside parent. Both paths
|
||||
// must already be cleaned and absolute. Equal paths return false; the caller
|
||||
// handles equality separately so it can produce a distinct error message.
|
||||
func isInside(parent, child string) bool {
|
||||
rel, err := filepath.Rel(parent, child)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if rel == "." || rel == ".." {
|
||||
return false
|
||||
}
|
||||
return !strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func runPrune(ctx context.Context, opts PruneOptions, gopts global.Options, term
|
||||
return runPruneWithRepo(ctx, opts, gopts, repo, restic.NewIDSet(), printer)
|
||||
}
|
||||
|
||||
func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts global.Options, repo *repository.Repository, ignoreSnapshots restic.IDSet, printer progress.Printer) error {
|
||||
func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts global.Options, repo *repository.Repository, ignoreSnapshots restic.IDSet, printer restic.Printer) error {
|
||||
if repo.Cache() == nil && !gopts.JSON {
|
||||
printer.S("warning: running prune without a cache, this may be very slow!")
|
||||
}
|
||||
@@ -246,7 +246,7 @@ func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts global.Optio
|
||||
}
|
||||
|
||||
// printPruneStats prints out the statistics
|
||||
func printPruneStats(printer progress.Printer, stats repository.PruneStats) error {
|
||||
func printPruneStats(printer restic.Printer, stats repository.PruneStats) error {
|
||||
printer.V("\nused: %10d blobs / %s", stats.Blobs.Used, ui.FormatBytes(stats.Size.Used))
|
||||
if stats.Blobs.Duplicate > 0 {
|
||||
printer.V("duplicates: %10d blobs / %s", stats.Blobs.Duplicate, ui.FormatBytes(stats.Size.Duplicate))
|
||||
@@ -282,7 +282,7 @@ func printPruneStats(printer progress.Printer, stats repository.PruneStats) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUsedBlobs(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet, ignoreSnapshots restic.IDSet, printer progress.Printer) error {
|
||||
func getUsedBlobs(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet, ignoreSnapshots restic.IDSet, printer restic.Printer) error {
|
||||
var snapshotTrees restic.IDs
|
||||
printer.P("loading all snapshots...")
|
||||
err := data.ForAllSnapshots(ctx, repo, repo, ignoreSnapshots,
|
||||
|
||||
@@ -174,7 +174,7 @@ func runRecover(ctx context.Context, gopts global.Options, term ui.Terminal) err
|
||||
|
||||
}
|
||||
|
||||
func createSnapshot(ctx context.Context, printer progress.Printer, name, hostname string, tags []string, repo restic.SaverUnpacked[restic.WriteableFileType], tree *restic.ID) error {
|
||||
func createSnapshot(ctx context.Context, printer restic.Printer, name, hostname string, tags []string, repo restic.SaverUnpacked[restic.WriteableFileType], tree *restic.ID) error {
|
||||
sn, err := data.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/filter"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
restoreui "github.com/restic/restic/internal/ui/restore"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -281,7 +281,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts global.Options,
|
||||
return nil
|
||||
}
|
||||
|
||||
func getXattrSelectFilter(opts RestoreOptions, printer progress.Printer) (func(xattrName string) bool, error) {
|
||||
func getXattrSelectFilter(opts RestoreOptions, printer restic.Printer) (func(xattrName string) bool, error) {
|
||||
hasXattrExcludes := len(opts.ExcludeXattrPattern) > 0
|
||||
hasXattrIncludes := len(opts.IncludeXattrPattern) > 0
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ func (opts *RewriteOptions) AddFlags(f *pflag.FlagSet) {
|
||||
// be updated accordingly.
|
||||
type rewriteFilterFunc func(ctx context.Context, sn *data.Snapshot, uploader restic.BlobSaver) (restic.ID, *data.SnapshotSummary, error)
|
||||
|
||||
func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *data.Snapshot, opts RewriteOptions, printer progress.Printer) (bool, error) {
|
||||
func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *data.Snapshot, opts RewriteOptions, printer restic.Printer) (bool, error) {
|
||||
if sn.Tree == nil {
|
||||
return false, errors.Errorf("snapshot %v has nil tree", sn.ID().Str())
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *data.
|
||||
}
|
||||
|
||||
func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *data.Snapshot,
|
||||
filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string, printer progress.Printer,
|
||||
filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string, printer restic.Printer,
|
||||
keepEmptySnapshot bool) (bool, error) {
|
||||
|
||||
var filteredTree restic.ID
|
||||
@@ -359,7 +359,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts global.Options,
|
||||
return nil
|
||||
}
|
||||
|
||||
func gatherIncludeFilters(includeByNameFuncs []filter.IncludeByNameFunc, printer progress.Printer) (rewriteNode walker.NodeRewriteFunc, keepEmptyDirectory walker.NodeKeepEmptyDirectoryFunc) {
|
||||
func gatherIncludeFilters(includeByNameFuncs []filter.IncludeByNameFunc, printer restic.Printer) (rewriteNode walker.NodeRewriteFunc, keepEmptyDirectory walker.NodeKeepEmptyDirectoryFunc) {
|
||||
inSelectByName := func(nodepath string, node *data.Node) bool {
|
||||
for _, include := range includeByNameFuncs {
|
||||
matched, childMayMatch := include(nodepath)
|
||||
@@ -406,7 +406,7 @@ func gatherIncludeFilters(includeByNameFuncs []filter.IncludeByNameFunc, printer
|
||||
return rewriteNode, keepEmptyDirectory
|
||||
}
|
||||
|
||||
func gatherExcludeFilters(excludeByNameFuncs []filter.RejectByNameFunc, printer progress.Printer) (rewriteNode walker.NodeRewriteFunc) {
|
||||
func gatherExcludeFilters(excludeByNameFuncs []filter.RejectByNameFunc, printer restic.Printer) (rewriteNode walker.NodeRewriteFunc) {
|
||||
exSelectByName := func(nodepath string) bool {
|
||||
for _, reject := range excludeByNameFuncs {
|
||||
if reject(nodepath) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
statsui "github.com/restic/restic/internal/ui/stats"
|
||||
@@ -228,7 +227,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic
|
||||
return data.FindUsedBlobs(ctx, repo, restic.IDs{*snapshot.Tree}, stats.blobs, restic.NoopCounter)
|
||||
}
|
||||
|
||||
hardLinkIndex := restorer.NewHardlinkIndex[struct{}]()
|
||||
hardLinkIndex := data.NewHardlinkIndex[struct{}]()
|
||||
err := walker.Walk(ctx, repo, *snapshot.Tree, walker.WalkVisitor{
|
||||
ProcessNode: statsWalkTree(repo, opts, stats, hardLinkIndex, sp),
|
||||
})
|
||||
@@ -239,7 +238,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic
|
||||
return nil
|
||||
}
|
||||
|
||||
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}], progress *statsui.Progress) walker.WalkFunc {
|
||||
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *data.HardlinkIndex[struct{}], progress *statsui.Progress) walker.WalkFunc {
|
||||
return func(parentTreeID restic.ID, npath string, node *data.Node, nodeErr error) error {
|
||||
if nodeErr != nil {
|
||||
return nodeErr
|
||||
@@ -376,7 +375,7 @@ const (
|
||||
countModeDebug = "debug"
|
||||
)
|
||||
|
||||
func statsDebug(ctx context.Context, repo restic.Repository, printer progress.Printer) error {
|
||||
func statsDebug(ctx context.Context, repo restic.Repository, printer restic.Printer) error {
|
||||
printer.E("Collecting size statistics\n\n")
|
||||
for _, t := range []restic.FileType{restic.KeyFile, restic.LockFile, restic.IndexFile, restic.SnapshotFile, restic.PackFile} {
|
||||
hist, err := statsDebugFileType(ctx, repo, t)
|
||||
|
||||
+1
-2
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@@ -49,7 +48,7 @@ func finalizeSnapshotFilter(filt *data.SnapshotFilter) {
|
||||
}
|
||||
|
||||
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, f *data.SnapshotFilter, snapshotIDs []string, printer progress.Printer) <-chan *data.Snapshot {
|
||||
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, f *data.SnapshotFilter, snapshotIDs []string, printer restic.Printer) <-chan *data.Snapshot {
|
||||
out := make(chan *data.Snapshot)
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
+6
-10
@@ -5,10 +5,10 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bool, exclusive bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bool, exclusive bool, printer restic.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
repo, err := global.OpenRepository(ctx, gopts, printer)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@@ -16,9 +16,7 @@ func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bo
|
||||
|
||||
unlock := func() {}
|
||||
if !dryRun {
|
||||
var lock repository.Unlocker
|
||||
|
||||
lock, ctx, err = repository.LockRepo(ctx, repo, exclusive, gopts.RetryLock, func(msg string) {
|
||||
unlock, ctx, err = repository.LockRepo(ctx, repo, exclusive, gopts.RetryLock, func(msg string) {
|
||||
if !gopts.JSON {
|
||||
printer.P("%s", msg)
|
||||
}
|
||||
@@ -26,8 +24,6 @@ func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bo
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
unlock = lock.Unlock
|
||||
} else {
|
||||
repo.SetDryRun()
|
||||
}
|
||||
@@ -35,18 +31,18 @@ func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bo
|
||||
return ctx, repo, unlock, nil
|
||||
}
|
||||
|
||||
func openWithReadLock(ctx context.Context, gopts global.Options, noLock bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
func openWithReadLock(ctx context.Context, gopts global.Options, noLock bool, printer restic.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
// TODO enforce read-only operations once the locking code has moved to the repository
|
||||
// As in-depth hardening, put the repository into read-only mode if noLock is true
|
||||
// Not possible if the repository has to be locked.
|
||||
return internalOpenWithLocked(ctx, gopts, noLock, false, printer)
|
||||
}
|
||||
|
||||
func openWithAppendLock(ctx context.Context, gopts global.Options, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
func openWithAppendLock(ctx context.Context, gopts global.Options, dryRun bool, printer restic.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
// TODO enforce non-exclusive operations once the locking code has moved to the repository
|
||||
return internalOpenWithLocked(ctx, gopts, dryRun, false, printer)
|
||||
}
|
||||
|
||||
func openWithExclusiveLock(ctx context.Context, gopts global.Options, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
func openWithExclusiveLock(ctx context.Context, gopts global.Options, dryRun bool, printer restic.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||
return internalOpenWithLocked(ctx, gopts, dryRun, true, printer)
|
||||
}
|
||||
|
||||
@@ -184,7 +184,8 @@ default backup, to be found at ``cmd/restic/testdata/backup-data.tar.gz``.
|
||||
In this compressed archive you will find files, hardlinked files,
|
||||
symlinked files, an empty directory and a simple directory structure which is good for testing purposes.
|
||||
|
||||
Commands that require a ``progress.Printer`` should either be wrapped in ``withTermStatus`` or ``withCaptureStdout``.
|
||||
Commands that require a ``restic.Printer`` should either be wrapped in ``withTermStatus`` or ``withCaptureStdout``.
|
||||
The interface is defined in ``internal/restic``; ``progress.NewTerminalPrinter`` returns a ``restic.Printer``.
|
||||
If you want to analyze JSON output, you use ``withCaptureStdout()``.
|
||||
It returns the generated output in a ``*bytes.Buffer``.
|
||||
JSON output can be unmarshalled to produce the appropriate go structures; see
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/restic/restic/internal/backend/util"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/fileio"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
)
|
||||
@@ -151,7 +151,7 @@ func (b *Local) Save(_ context.Context, h backend.Handle, rd backend.RewindReade
|
||||
|
||||
// preallocate disk space
|
||||
if size := rd.Length(); size > 0 {
|
||||
if err := fs.PreallocateFile(f, size); err != nil {
|
||||
if err := fileio.PreallocateFile(f, size); err != nil {
|
||||
debug.Log("Failed to preallocate %v with size %v: %v", finalname, size, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package restorer
|
||||
package data
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -1,16 +1,16 @@
|
||||
package restorer_test
|
||||
package data_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/data"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
// TestHardLinks contains various tests for HardlinkIndex.
|
||||
func TestHardLinks(t *testing.T) {
|
||||
|
||||
idx := restorer.NewHardlinkIndex[string]()
|
||||
idx := data.NewHardlinkIndex[string]()
|
||||
|
||||
idx.Add(1, 2, "inode1-file1-on-device2")
|
||||
idx.Add(2, 3, "inode2-file2-on-device3")
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build !windows
|
||||
|
||||
package fileio
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// TempFile creates a temporary file which has already been deleted (on
|
||||
// supported platforms)
|
||||
func TempFile(dir, prefix string) (f *os.File, err error) {
|
||||
f, err = os.CreateTemp(dir, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(f.Name()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package fileio
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// TempFile creates a temporary file which is marked as delete-on-close
|
||||
func TempFile(dir, prefix string) (f *os.File, err error) {
|
||||
// slightly modified implementation of os.CreateTemp(dir, prefix) to allow us to add
|
||||
// the FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE flags.
|
||||
// These provide two large benefits:
|
||||
// FILE_ATTRIBUTE_TEMPORARY tells Windows to keep the file in memory only if possible
|
||||
// which reduces the amount of unnecessary disk writes.
|
||||
// FILE_FLAG_DELETE_ON_CLOSE instructs Windows to automatically delete the file once
|
||||
// all file descriptors are closed.
|
||||
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE)
|
||||
creation := uint32(windows.CREATE_NEW)
|
||||
share := uint32(0) // prevent other processes from accessing the file
|
||||
flags := uint32(windows.FILE_ATTRIBUTE_TEMPORARY | windows.FILE_FLAG_DELETE_ON_CLOSE)
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
randSuffix := strconv.Itoa(int(1e9 + rand.Intn(1e9)%1e9))[1:]
|
||||
path := filepath.Join(dir, prefix+randSuffix)
|
||||
|
||||
ptr, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := windows.CreateFile(ptr, access, share, nil, creation, flags, 0)
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
// Access denied error can occur if the tmp files conflict with each other.
|
||||
if isAccessDeniedError(err) {
|
||||
continue
|
||||
}
|
||||
return os.NewFile(uintptr(h), path), err
|
||||
}
|
||||
|
||||
// Proper error handling is still to do
|
||||
return nil, os.ErrExist
|
||||
}
|
||||
|
||||
func isAccessDeniedError(err error) bool {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
return errno == windows.ERROR_ACCESS_DENIED
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
package fs_test
|
||||
package fileio_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/fileio"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestTempFile(t *testing.T) {
|
||||
// create two temp files at the same time to check that the
|
||||
// collision avoidance works
|
||||
f, err := fs.TempFile("", "test")
|
||||
f, err := fileio.TempFile("", "test")
|
||||
fn := f.Name()
|
||||
rtest.OK(t, err)
|
||||
f2, err := fs.TempFile("", "test")
|
||||
f2, err := fileio.TempFile("", "test")
|
||||
fn2 := f2.Name()
|
||||
rtest.OK(t, err)
|
||||
rtest.Assert(t, fn != fn2, "filenames don't differ %s", fn)
|
||||
@@ -1,4 +1,4 @@
|
||||
package fs
|
||||
package fileio
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package fs
|
||||
package fileio
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package fs
|
||||
package fileio
|
||||
|
||||
import "os"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fs
|
||||
package fileio
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ func TestPreallocate(t *testing.T) {
|
||||
fi, err := wr.Stat()
|
||||
test.OK(t, err)
|
||||
|
||||
efi := ExtendedStat(fi)
|
||||
efi := fs.ExtendedStat(fi)
|
||||
test.Assert(t, efi.Size == i || efi.Blocks > 0, "Preallocated size of %v, got size %v block %v", i, efi.Size, efi.Blocks)
|
||||
})
|
||||
}
|
||||
@@ -13,21 +13,6 @@ func fixpath(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// TempFile creates a temporary file which has already been deleted (on
|
||||
// supported platforms)
|
||||
func TempFile(dir, prefix string) (f *os.File, err error) {
|
||||
f, err = os.CreateTemp(dir, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(f.Name()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// isNotSupported returns true if the error is caused by an unsupported file system feature.
|
||||
func isNotSupported(err error) bool {
|
||||
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ENOTSUP {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
@@ -43,48 +41,6 @@ func fixpath(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// TempFile creates a temporary file which is marked as delete-on-close
|
||||
func TempFile(dir, prefix string) (f *os.File, err error) {
|
||||
// slightly modified implementation of os.CreateTemp(dir, prefix) to allow us to add
|
||||
// the FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE flags.
|
||||
// These provide two large benefits:
|
||||
// FILE_ATTRIBUTE_TEMPORARY tells Windows to keep the file in memory only if possible
|
||||
// which reduces the amount of unnecessary disk writes.
|
||||
// FILE_FLAG_DELETE_ON_CLOSE instructs Windows to automatically delete the file once
|
||||
// all file descriptors are closed.
|
||||
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE)
|
||||
creation := uint32(windows.CREATE_NEW)
|
||||
share := uint32(0) // prevent other processes from accessing the file
|
||||
flags := uint32(windows.FILE_ATTRIBUTE_TEMPORARY | windows.FILE_FLAG_DELETE_ON_CLOSE)
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
randSuffix := strconv.Itoa(int(1e9 + rand.Intn(1e9)%1e9))[1:]
|
||||
path := filepath.Join(dir, prefix+randSuffix)
|
||||
|
||||
ptr, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := windows.CreateFile(ptr, access, share, nil, creation, flags, 0)
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
// Access denied error can occur if the tmp files conflict with each other.
|
||||
if isAccessDeniedError(err) {
|
||||
continue
|
||||
}
|
||||
return os.NewFile(uintptr(h), path), err
|
||||
}
|
||||
|
||||
// Proper error handling is still to do
|
||||
return nil, os.ErrExist
|
||||
}
|
||||
|
||||
// Chmod changes the mode of the named file to mode.
|
||||
func chmod(name string, mode os.FileMode) error {
|
||||
return os.Chmod(fixpath(name), mode)
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/textfile"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
@@ -298,7 +297,7 @@ func readRepo(gopts Options) (string, error) {
|
||||
const maxKeys = 20
|
||||
|
||||
// OpenRepository reads the password and opens the repository.
|
||||
func OpenRepository(ctx context.Context, gopts Options, printer progress.Printer) (*repository.Repository, error) {
|
||||
func OpenRepository(ctx context.Context, gopts Options, printer restic.Printer) (*repository.Repository, error) {
|
||||
repo, err := readRepo(gopts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -369,7 +368,7 @@ func createRepositoryInstance(be backend.Backend, gopts Options) (*repository.Re
|
||||
}
|
||||
|
||||
// decryptRepository handles password reading and decrypts the repository.
|
||||
func decryptRepository(ctx context.Context, s *repository.Repository, gopts *Options, printer progress.Printer) error {
|
||||
func decryptRepository(ctx context.Context, s *repository.Repository, gopts *Options, printer restic.Printer) error {
|
||||
passwordTriesLeft := 1
|
||||
if gopts.Term.InputIsTerminal() && gopts.Password == "" && !gopts.InsecureNoPassword {
|
||||
passwordTriesLeft = 3
|
||||
@@ -406,7 +405,7 @@ func decryptRepository(ctx context.Context, s *repository.Repository, gopts *Opt
|
||||
}
|
||||
|
||||
// printRepositoryInfo displays the repository ID, version and compression level.
|
||||
func printRepositoryInfo(s *repository.Repository, gopts Options, printer progress.Printer) {
|
||||
func printRepositoryInfo(s *repository.Repository, gopts Options, printer restic.Printer) {
|
||||
id := s.Config().ID
|
||||
if len(id) > 8 {
|
||||
id = id[:8]
|
||||
@@ -419,7 +418,7 @@ func printRepositoryInfo(s *repository.Repository, gopts Options, printer progre
|
||||
}
|
||||
|
||||
// setupCache creates a new cache and removes old cache directories if instructed to do so.
|
||||
func setupCache(s *repository.Repository, gopts Options, printer progress.Printer) error {
|
||||
func setupCache(s *repository.Repository, gopts Options, printer restic.Printer) error {
|
||||
c, err := cache.New(s.Config().ID, gopts.CacheDir)
|
||||
if err != nil {
|
||||
printer.E("unable to open cache: %v", err)
|
||||
@@ -461,7 +460,7 @@ func setupCache(s *repository.Repository, gopts Options, printer progress.Printe
|
||||
}
|
||||
|
||||
// CreateRepository a repository with the given version and chunker polynomial.
|
||||
func CreateRepository(ctx context.Context, gopts Options, version uint, chunkerPolynomial *chunker.Pol, printer progress.Printer) (*repository.Repository, error) {
|
||||
func CreateRepository(ctx context.Context, gopts Options, version uint, chunkerPolynomial *chunker.Pol, printer restic.Printer) (*repository.Repository, error) {
|
||||
if version < restic.MinRepoVersion || version > restic.MaxRepoVersion {
|
||||
return nil, errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion)
|
||||
}
|
||||
@@ -496,7 +495,7 @@ func CreateRepository(ctx context.Context, gopts Options, version uint, chunkerP
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func innerOpenBackend(ctx context.Context, s string, gopts Options, opts options.Options, create bool, printer progress.Printer) (backend.Backend, error) {
|
||||
func innerOpenBackend(ctx context.Context, s string, gopts Options, opts options.Options, create bool, printer restic.Printer) (backend.Backend, error) {
|
||||
debug.Log("parsing location %v", location.StripPassword(gopts.Backends, s))
|
||||
|
||||
scheme, cfg, err := parseConfig(gopts.Backends, s, opts)
|
||||
@@ -559,7 +558,7 @@ func setupTransport(gopts Options) (http.RoundTripper, limiter.Limiter, error) {
|
||||
}
|
||||
|
||||
// createOrOpenBackend creates or opens a backend using the appropriate factory method.
|
||||
func createOrOpenBackend(ctx context.Context, scheme string, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, gopts Options, s string, create bool, printer progress.Printer) (backend.Backend, error) {
|
||||
func createOrOpenBackend(ctx context.Context, scheme string, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, gopts Options, s string, create bool, printer restic.Printer) (backend.Backend, error) {
|
||||
factory := gopts.Backends.Lookup(scheme)
|
||||
if factory == nil {
|
||||
return nil, errors.Fatalf("invalid backend: %q", scheme)
|
||||
@@ -589,7 +588,7 @@ func createOrOpenBackend(ctx context.Context, scheme string, cfg interface{}, rt
|
||||
}
|
||||
|
||||
// wrapBackend applies debug logging, test hooks, and retry wrapper to the backend.
|
||||
func wrapBackend(be backend.Backend, gopts Options, printer progress.Printer) (backend.Backend, error) {
|
||||
func wrapBackend(be backend.Backend, gopts Options, printer restic.Printer) (backend.Backend, error) {
|
||||
// wrap with debug logging and connection limiting
|
||||
be = logger.New(sema.NewBackend(be))
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository/index"
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type packDumpEntry struct {
|
||||
@@ -46,7 +45,7 @@ func writePackDumpJSON(wr io.Writer, item any) error {
|
||||
}
|
||||
|
||||
// DumpPacks lists each pack file and writes its header blob layout as JSON to wr.
|
||||
func DumpPacks(ctx context.Context, repo *Repository, wr io.Writer, printer progress.Printer) error {
|
||||
func DumpPacks(ctx context.Context, repo *Repository, wr io.Writer, printer restic.Printer) error {
|
||||
var m sync.Mutex
|
||||
return restic.ParallelList(ctx, repo, restic.PackFile, repo.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
blobs, err := repo.listPack(ctx, id, size)
|
||||
@@ -75,7 +74,7 @@ func DumpPacks(ctx context.Context, repo *Repository, wr io.Writer, printer prog
|
||||
}
|
||||
|
||||
// DumpIndexes loads each on-disk index file and writes its debug dump to wr.
|
||||
func DumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Writer, printer progress.Printer) error {
|
||||
func DumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Writer, printer restic.Printer) error {
|
||||
return index.ForAllIndexes(ctx, repo, repo, func(id restic.ID, idx *index.Index, err error) error {
|
||||
printer.S("index_id: %v", id)
|
||||
if err != nil {
|
||||
@@ -95,7 +94,7 @@ type ExaminePackOptions struct {
|
||||
}
|
||||
|
||||
// ExaminePack loads and inspects a pack file and its index entries.
|
||||
func ExaminePack(ctx context.Context, repo *Repository, id restic.ID, opts ExaminePackOptions, printer progress.Printer) error {
|
||||
func ExaminePack(ctx context.Context, repo *Repository, id restic.ID, opts ExaminePackOptions, printer restic.Printer) error {
|
||||
printer.S("examine %v", id)
|
||||
|
||||
buf, err := repo.LoadRaw(ctx, restic.PackFile, id)
|
||||
@@ -146,7 +145,7 @@ func ExaminePack(ctx context.Context, repo *Repository, id restic.ID, opts Exami
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPackSize(blobs pack.Blobs, fileSize int, printer progress.Printer) {
|
||||
func checkPackSize(blobs pack.Blobs, fileSize int, printer restic.Printer) {
|
||||
// track current size and offset
|
||||
var size, offset uint64
|
||||
|
||||
@@ -169,7 +168,7 @@ func checkPackSize(blobs pack.Blobs, fileSize int, printer progress.Printer) {
|
||||
}
|
||||
}
|
||||
|
||||
func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool, printer progress.Printer) []byte {
|
||||
func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool, printer restic.Printer) []byte {
|
||||
if bytewise {
|
||||
printer.S(" trying to repair blob by finding a broken byte")
|
||||
} else {
|
||||
@@ -284,7 +283,7 @@ func decryptUnsigned(k *crypto.Key, buf []byte) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
func loadBlobs(ctx context.Context, opts ExaminePackOptions, repo *Repository, packID restic.ID, list pack.Blobs, printer progress.Printer) error {
|
||||
func loadBlobs(ctx context.Context, opts ExaminePackOptions, repo *Repository, packID restic.ID, list pack.Blobs, printer restic.Printer) error {
|
||||
dec, err := zstd.NewReader(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -366,7 +365,7 @@ func loadBlobs(ctx context.Context, opts ExaminePackOptions, repo *Repository, p
|
||||
return err
|
||||
}
|
||||
|
||||
func storePlainBlob(id restic.ID, prefix string, plain []byte, printer progress.Printer) error {
|
||||
func storePlainBlob(id restic.ID, prefix string, plain []byte, printer restic.Printer) error {
|
||||
filename := fmt.Sprintf("%s%s.bin", prefix, id)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,10 +13,6 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
type Unlocker interface {
|
||||
Unlock()
|
||||
}
|
||||
|
||||
type unlocker struct {
|
||||
lock *lockHandle
|
||||
cancel context.CancelFunc
|
||||
@@ -28,8 +24,6 @@ func (l *unlocker) Unlock() {
|
||||
l.refreshWG.Wait()
|
||||
}
|
||||
|
||||
var _ Unlocker = &unlocker{}
|
||||
|
||||
type locker struct {
|
||||
retrySleepStart time.Duration
|
||||
retrySleepMax time.Duration
|
||||
@@ -50,11 +44,11 @@ var lockerInst = &locker{
|
||||
|
||||
// LockRepo acquires a repository lock. The returned context is cancelled when
|
||||
// Unlock is called; cancelling the original context stops lock refresh.
|
||||
func LockRepo(ctx context.Context, repo *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (Unlocker, context.Context, error) {
|
||||
func LockRepo(ctx context.Context, repo *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (func(), context.Context, error) {
|
||||
return lockerInst.Lock(ctx, repo, exclusive, retryLock, printRetry, logger)
|
||||
}
|
||||
|
||||
func (l *locker) Lock(ctx context.Context, r *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (*unlocker, context.Context, error) {
|
||||
func (l *locker) Lock(ctx context.Context, r *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (func(), context.Context, error) {
|
||||
var lock *lockHandle
|
||||
var err error
|
||||
|
||||
@@ -113,7 +107,7 @@ retryLoop:
|
||||
go l.refreshLocks(ctx, repo.be, unlocker, refreshChan, forceRefreshChan, logger)
|
||||
go l.monitorLockRefresh(ctx, unlocker, refreshChan, forceRefreshChan, logger)
|
||||
|
||||
return unlocker, ctx, nil
|
||||
return unlocker.Unlock, ctx, nil
|
||||
}
|
||||
|
||||
func minDuration(a, b time.Duration) time.Duration {
|
||||
|
||||
@@ -34,19 +34,19 @@ func openLockTestRepo(t *testing.T, wrapper backendWrapper) (*Repository, backen
|
||||
return TestOpenBackend(t, be), be
|
||||
}
|
||||
|
||||
func checkedLockRepo(ctx context.Context, t *testing.T, repo *Repository, lockerInst *locker, retryLock time.Duration) (Unlocker, context.Context) {
|
||||
lock, wrappedCtx, err := lockerInst.Lock(ctx, repo, false, retryLock, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
func checkedLockRepo(ctx context.Context, t *testing.T, repo *Repository, lockerInst *locker, retryLock time.Duration) (func(), context.Context) {
|
||||
unlock, wrappedCtx, err := lockerInst.Lock(ctx, repo, false, retryLock, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, wrappedCtx.Err())
|
||||
return lock, wrappedCtx
|
||||
return unlock, wrappedCtx
|
||||
}
|
||||
|
||||
func TestLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo, _ := openLockTestRepo(t, nil)
|
||||
|
||||
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, lockerInst, 0)
|
||||
lock.Unlock()
|
||||
unlock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, lockerInst, 0)
|
||||
unlock()
|
||||
if wrappedCtx.Err() == nil {
|
||||
t.Fatal("unlock did not cancel context")
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func TestLockCancel(t *testing.T) {
|
||||
}
|
||||
|
||||
// Unlock should not crash
|
||||
lock.Unlock()
|
||||
lock()
|
||||
}
|
||||
|
||||
func TestLockConflict(t *testing.T) {
|
||||
@@ -73,9 +73,9 @@ func TestLockConflict(t *testing.T) {
|
||||
repo, be := openLockTestRepo(t, nil)
|
||||
repo2 := TestOpenBackend(t, be)
|
||||
|
||||
lock, _, err := LockRepo(context.Background(), repo, true, 0, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
unlock, _, err := LockRepo(context.Background(), repo, true, 0, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
rtest.OK(t, err)
|
||||
defer lock.Unlock()
|
||||
defer unlock()
|
||||
_, _, err = LockRepo(context.Background(), repo2, false, 0, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
if err == nil {
|
||||
t.Fatal("second lock should have failed")
|
||||
@@ -109,7 +109,7 @@ func TestLockFailedRefresh(t *testing.T) {
|
||||
refreshInterval: 20 * time.Millisecond,
|
||||
refreshabilityTimeout: 100 * time.Millisecond,
|
||||
}
|
||||
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
unlock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
|
||||
select {
|
||||
case <-wrappedCtx.Done():
|
||||
@@ -118,7 +118,7 @@ func TestLockFailedRefresh(t *testing.T) {
|
||||
t.Fatal("failed lock refresh did not cause context cancellation")
|
||||
}
|
||||
// Unlock should not crash
|
||||
lock.Unlock()
|
||||
unlock()
|
||||
}
|
||||
|
||||
type loggingBackend struct {
|
||||
@@ -150,7 +150,7 @@ func TestLockSuccessfulRefresh(t *testing.T) {
|
||||
refreshInterval: 60 * time.Millisecond,
|
||||
refreshabilityTimeout: 500 * time.Millisecond,
|
||||
}
|
||||
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
unlock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
|
||||
select {
|
||||
case <-wrappedCtx.Done():
|
||||
@@ -167,7 +167,7 @@ func TestLockSuccessfulRefresh(t *testing.T) {
|
||||
// expected lock refresh to work
|
||||
}
|
||||
// Unlock should not crash
|
||||
lock.Unlock()
|
||||
unlock()
|
||||
}
|
||||
|
||||
type slowBackend struct {
|
||||
@@ -201,7 +201,7 @@ func TestLockSuccessfulStaleRefresh(t *testing.T) {
|
||||
refreshabilityTimeout: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
unlock, wrappedCtx := checkedLockRepo(context.Background(), t, repo, li, 0)
|
||||
// delay lock refreshing long enough that the lock would expire
|
||||
sb.m.Lock()
|
||||
sb.sleep = li.refreshabilityTimeout + li.refreshInterval
|
||||
@@ -230,7 +230,7 @@ func TestLockSuccessfulStaleRefresh(t *testing.T) {
|
||||
}
|
||||
|
||||
// Unlock should not crash
|
||||
lock.Unlock()
|
||||
unlock()
|
||||
}
|
||||
|
||||
func TestLockWaitTimeout(t *testing.T) {
|
||||
@@ -239,7 +239,7 @@ func TestLockWaitTimeout(t *testing.T) {
|
||||
|
||||
elock, _, err := LockRepo(context.TODO(), repo, true, 0, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
rtest.OK(t, err)
|
||||
defer elock.Unlock()
|
||||
defer elock()
|
||||
|
||||
retryLock := 200 * time.Millisecond
|
||||
|
||||
@@ -261,7 +261,7 @@ func TestLockWaitCancel(t *testing.T) {
|
||||
|
||||
elock, _, err := LockRepo(context.TODO(), repo, true, 0, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
rtest.OK(t, err)
|
||||
defer elock.Unlock()
|
||||
defer elock()
|
||||
|
||||
retryLock := 200 * time.Millisecond
|
||||
cancelAfter := 40 * time.Millisecond
|
||||
@@ -292,12 +292,12 @@ func TestLockWaitSuccess(t *testing.T) {
|
||||
unlockAfter := 40 * time.Millisecond
|
||||
|
||||
time.AfterFunc(unlockAfter, func() {
|
||||
elock.Unlock()
|
||||
elock()
|
||||
})
|
||||
|
||||
lock, _, err := LockRepo(context.TODO(), repo, false, retryLock, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
unlock, _, err := LockRepo(context.TODO(), repo, false, retryLock, func(msg string) {}, func(format string, args ...interface{}) {})
|
||||
rtest.OK(t, err)
|
||||
lock.Unlock()
|
||||
unlock()
|
||||
}
|
||||
|
||||
func createFakeLock(repo *Repository, t time.Time, pid int) (restic.ID, error) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/fileio"
|
||||
"github.com/restic/restic/internal/repository/crypto"
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
)
|
||||
@@ -201,7 +201,7 @@ func (r *packerManager) forgetPacker(packer *packer) {
|
||||
// created or one is returned that already has some blobs.
|
||||
func (r *packerManager) newPacker() (pck *packer, err error) {
|
||||
debug.Log("create new pack")
|
||||
tmpfile, err := fs.TempFile("", "restic-temp-pack-")
|
||||
tmpfile, err := fileio.TempFile("", "restic-temp-pack-")
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository/index"
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
var ErrIndexIncomplete = errors.Fatal("index is not complete")
|
||||
@@ -104,7 +103,7 @@ type packInfoWithID struct {
|
||||
|
||||
// PlanPrune selects which files to rewrite and which to delete and which blobs to keep.
|
||||
// Also some summary statistics are returned.
|
||||
func PlanPrune(ctx context.Context, opts PruneOptions, repo *Repository, getUsedBlobs func(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet) error, printer progress.Printer) (*PrunePlan, error) {
|
||||
func PlanPrune(ctx context.Context, opts PruneOptions, repo *Repository, getUsedBlobs func(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet) error, printer restic.Printer) (*PrunePlan, error) {
|
||||
stats := PruneStats{MessageType: "summary"}
|
||||
|
||||
if opts.UnsafeRecovery {
|
||||
@@ -176,7 +175,7 @@ func PlanPrune(ctx context.Context, opts PruneOptions, repo *Repository, getUsed
|
||||
return &plan, nil
|
||||
}
|
||||
|
||||
func packInfoFromIndex(ctx context.Context, idx restic.ListBlobser, usedBlobs *index.AssociatedSet[uint8], stats *PruneStats, printer progress.Printer) (*index.AssociatedSet[uint8], map[restic.ID]packInfo, error) {
|
||||
func packInfoFromIndex(ctx context.Context, idx restic.ListBlobser, usedBlobs *index.AssociatedSet[uint8], stats *PruneStats, printer restic.Printer) (*index.AssociatedSet[uint8], map[restic.ID]packInfo, error) {
|
||||
// iterate over all blobs in index to find out which blobs are duplicates
|
||||
// The counter in usedBlobs describes how many instances of the blob exist in the repository index
|
||||
// Thus 0 == blob is missing, 1 == blob exists once, >= 2 == duplicates exist
|
||||
@@ -384,7 +383,7 @@ func calculateTargetPacksize(opts PruneOptions, indexPack map[restic.ID]packInfo
|
||||
return targetPackSize
|
||||
}
|
||||
|
||||
func decidePackAction(ctx context.Context, opts PruneOptions, repo *Repository, indexPack map[restic.ID]packInfo, stats *PruneStats, printer progress.Printer) (PrunePlan, error) {
|
||||
func decidePackAction(ctx context.Context, opts PruneOptions, repo *Repository, indexPack map[restic.ID]packInfo, stats *PruneStats, printer restic.Printer) (PrunePlan, error) {
|
||||
removePacksFirst := restic.NewIDSet()
|
||||
removePacks := restic.NewIDSet()
|
||||
repackPacks := restic.NewIDSet()
|
||||
@@ -592,7 +591,7 @@ func (plan *PrunePlan) Stats() PruneStats {
|
||||
// - rebuild the index while ignoring all files that will be deleted
|
||||
// - delete the files
|
||||
// plan.removePacks and plan.ignorePacks are modified in this function.
|
||||
func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) error {
|
||||
func (plan *PrunePlan) Execute(ctx context.Context, printer restic.Printer) error {
|
||||
if plan.opts.DryRun {
|
||||
printer.V("Repeated prune dry-runs can report slightly different amounts of data to keep or repack. This is expected behavior.\n\n")
|
||||
if len(plan.removePacksFirst) > 0 {
|
||||
@@ -692,7 +691,7 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
|
||||
|
||||
// deleteFiles deletes the given fileList of fileType in parallel
|
||||
// if ignoreError=true, it will print a warning if there was an error, else it will abort.
|
||||
func deleteFiles(ctx context.Context, ignoreError bool, repo restic.RemoverUnpacked[restic.FileType], fileList restic.IDSet, fileType restic.FileType, printer progress.Printer) error {
|
||||
func deleteFiles(ctx context.Context, ignoreError bool, repo restic.RemoverUnpacked[restic.FileType], fileList restic.IDSet, fileType restic.FileType, printer restic.Printer) error {
|
||||
bar := printer.NewCounter("files deleted")
|
||||
defer bar.Done()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// TestPruneMaxUnusedDuplicate checks that MaxUnused correctly accounts for duplicates.
|
||||
@@ -70,10 +69,10 @@ func TestPruneMaxUnusedDuplicate(t *testing.T) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, progress.NewNoopPrinter())
|
||||
}, restic.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), restic.NewNoopPrinter()))
|
||||
|
||||
rsize := plan.Stats().Size
|
||||
remainingUnusedSize := rsize.Duplicate + rsize.Unused - rsize.Remove - rsize.Repackrm
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
|
||||
@@ -41,10 +40,10 @@ func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, progress.NewNoopPrinter())
|
||||
}, restic.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), restic.NewNoopPrinter()))
|
||||
|
||||
repo = repository.TestOpenBackend(t, be)
|
||||
repository.TestCheckRepo(t, repo)
|
||||
@@ -165,9 +164,9 @@ func TestPruneSmall(t *testing.T) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, progress.NewNoopPrinter())
|
||||
}, restic.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), restic.NewNoopPrinter()))
|
||||
|
||||
stats := plan.Stats()
|
||||
rtest.Equals(t, stats.Size.Used/blobSize, uint64(numBlobsCreated), fmt.Sprintf("total size of blobs should be %d but is %d",
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func randomSize(random *rand.Rand, min, max int) int {
|
||||
@@ -161,7 +160,7 @@ func repack(t *testing.T, repo *repository.Repository, be backend.Backend, packs
|
||||
func rebuildAndReloadIndex(t *testing.T, repo *repository.Repository) {
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
|
||||
ReadAllPacks: true,
|
||||
}, progress.NewNoopPrinter()))
|
||||
}, restic.NewNoopPrinter()))
|
||||
|
||||
rtest.OK(t, repo.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory))
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ import (
|
||||
"github.com/restic/restic/internal/repository/index"
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type RepairIndexOptions struct {
|
||||
ReadAllPacks bool
|
||||
}
|
||||
|
||||
func RepairIndex(ctx context.Context, repo *Repository, opts RepairIndexOptions, printer progress.Printer) error {
|
||||
func RepairIndex(ctx context.Context, repo *Repository, opts RepairIndexOptions, printer restic.Printer) error {
|
||||
var obsoleteIndexes restic.IDs
|
||||
packSizeFromList := make(map[restic.ID]int64)
|
||||
packSizeFromIndex := make(map[restic.ID]int64)
|
||||
@@ -103,7 +102,7 @@ func RepairIndex(ctx context.Context, repo *Repository, opts RepairIndexOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteIndexFiles(ctx context.Context, repo *Repository, removePacks restic.IDSet, oldIndexes restic.IDSet, extraObsolete restic.IDs, printer progress.Printer) error {
|
||||
func rewriteIndexFiles(ctx context.Context, repo *Repository, removePacks restic.IDSet, oldIndexes restic.IDSet, extraObsolete restic.IDs, printer restic.Printer) error {
|
||||
printer.P("rebuilding index\n")
|
||||
|
||||
bar := printer.NewCounter("indexes processed")
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func listIndex(t *testing.T, repo restic.Lister) restic.IDSet {
|
||||
@@ -33,7 +32,7 @@ func testRebuildIndex(t *testing.T, readAllPacks bool, damage func(t *testing.T,
|
||||
repo = repository.TestOpenBackend(t, be)
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
|
||||
ReadAllPacks: readAllPacks,
|
||||
}, progress.NewNoopPrinter()))
|
||||
}, restic.NewNoopPrinter()))
|
||||
|
||||
repository.TestCheckRepo(t, repo)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/repository/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func RepairPacks(ctx context.Context, repo *Repository, ids restic.IDSet, printer progress.Printer) error {
|
||||
func RepairPacks(ctx context.Context, repo *Repository, ids restic.IDSet, printer restic.Printer) error {
|
||||
printer.P("salvaging intact data from specified pack files")
|
||||
bar := printer.NewCounter("pack files")
|
||||
bar.SetMax(uint64(len(ids)))
|
||||
@@ -92,7 +91,7 @@ func resolveBlobsForPacks(ctx context.Context, repo *Repository, ids restic.IDSe
|
||||
return packToBlobs, nil
|
||||
}
|
||||
|
||||
func reuploadBlobsFromPack(ctx context.Context, repo *Repository, packID restic.ID, blobs pack.Blobs, printer progress.Printer, uploader restic.BlobSaverWithAsync) error {
|
||||
func reuploadBlobsFromPack(ctx context.Context, repo *Repository, packID restic.ID, blobs pack.Blobs, printer restic.Printer, uploader restic.BlobSaverWithAsync) error {
|
||||
err := repo.loadBlobsFromPack(ctx, packID, blobs, func(blob restic.BlobHandle, buf []byte, err error) error {
|
||||
if err != nil {
|
||||
printer.E("failed to load blob %v: %v", blob.ID, err)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func listBlobs(repo restic.Repository) restic.BlobSet {
|
||||
@@ -105,7 +104,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
|
||||
buf, err := backendtest.LoadAll(context.TODO(), be, h)
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, be.Remove(context.TODO(), h))
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, progress.NewNoopPrinter()))
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, restic.NewNoopPrinter()))
|
||||
|
||||
rtest.OK(t, be.Save(context.TODO(), h, backend.NewByteReader(buf, be.Hasher())))
|
||||
|
||||
@@ -129,7 +128,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
|
||||
|
||||
toRepair, damagedBlobs := test.damage(t, random, repo, be, packsBefore)
|
||||
|
||||
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, progress.NewNoopPrinter()))
|
||||
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, restic.NewNoopPrinter()))
|
||||
// reload index
|
||||
rtest.OK(t, repo.LoadIndex(context.TODO(), restic.NoopTerminalCounterFactory))
|
||||
|
||||
|
||||
@@ -26,3 +26,67 @@ func (noopTerminalCounterFactory) NewCounterTerminalOnly(string) Counter {
|
||||
|
||||
// NoopTerminalCounterFactory is a TerminalCounterFactory that returns NoopCounter.
|
||||
var NoopTerminalCounterFactory TerminalCounterFactory = noopTerminalCounterFactory{}
|
||||
|
||||
// Printer can return a new counter or print messages at different log levels.
|
||||
// It must be safe to call its methods from concurrent goroutines.
|
||||
type Printer interface {
|
||||
// NewCounter returns a new progress counter. It is not shown if --quiet or --json is specified.
|
||||
NewCounter(description string) Counter
|
||||
// NewCounterTerminalOnly returns a new progress counter that is only shown if stdout points to a
|
||||
// terminal. It is not shown if --quiet or --json is specified.
|
||||
NewCounterTerminalOnly(description string) Counter
|
||||
|
||||
// E reports an error. This message is always printed to stderr.
|
||||
// Appends a newline if not present.
|
||||
E(msg string, args ...interface{})
|
||||
// S prints a message, this is should only be used for very important messages
|
||||
// that are not errors. The message is even printed if --quiet is specified.
|
||||
// Appends a newline if not present.
|
||||
S(msg string, args ...interface{})
|
||||
// PT prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified)
|
||||
// and stdout points to a terminal.
|
||||
// This is used for informational messages.
|
||||
PT(msg string, args ...interface{})
|
||||
// P prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified),
|
||||
// this is used for normal messages which are not errors. Appends a newline if not present.
|
||||
P(msg string, args ...interface{})
|
||||
// V prints a message if verbosity >= 2 (equivalent to --verbose), this is used for
|
||||
// verbose messages. Appends a newline if not present.
|
||||
V(msg string, args ...interface{})
|
||||
// VV prints a message if verbosity >= 3 (equivalent to --verbose=2), this is used for
|
||||
// debug messages. Appends a newline if not present.
|
||||
VV(msg string, args ...interface{})
|
||||
}
|
||||
|
||||
// noopPrinter discards all messages.
|
||||
type noopPrinter struct{}
|
||||
|
||||
var (
|
||||
_ Printer = (*noopPrinter)(nil)
|
||||
_ TerminalCounterFactory = (*noopPrinter)(nil)
|
||||
)
|
||||
|
||||
// NewNoopPrinter returns a Printer that discards all messages.
|
||||
func NewNoopPrinter() Printer {
|
||||
return &noopPrinter{}
|
||||
}
|
||||
|
||||
func (*noopPrinter) NewCounter(_ string) Counter {
|
||||
return NoopCounter
|
||||
}
|
||||
|
||||
func (*noopPrinter) NewCounterTerminalOnly(_ string) Counter {
|
||||
return NoopCounter
|
||||
}
|
||||
|
||||
func (*noopPrinter) E(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) S(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) PT(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) P(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) V(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) VV(_ string, _ ...interface{}) {}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/restic/restic/internal/feature"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/restore"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,7 +55,7 @@ type fileRestorer struct {
|
||||
filesWriter *filesWriter
|
||||
zeroChunk restic.ID
|
||||
sparse bool
|
||||
progress *restore.Progress
|
||||
progress ProgressReporter
|
||||
|
||||
allowRecursiveDelete bool
|
||||
|
||||
@@ -73,7 +72,7 @@ func newFileRestorer(dst string,
|
||||
sparse bool,
|
||||
allowRecursiveDelete bool,
|
||||
startWarmup startWarmupFn,
|
||||
progress *restore.Progress) *fileRestorer {
|
||||
progress ProgressReporter) *fileRestorer {
|
||||
|
||||
// as packs are streamed the concurrency is limited by IO
|
||||
workerCount := int(connections)
|
||||
@@ -85,7 +84,7 @@ func newFileRestorer(dst string,
|
||||
filesWriter: newFilesWriter(workerCount, allowRecursiveDelete),
|
||||
zeroChunk: repository.ZeroChunk(),
|
||||
sparse: sparse,
|
||||
progress: progress,
|
||||
progress: progressOrNoop(progress),
|
||||
allowRecursiveDelete: allowRecursiveDelete,
|
||||
workerCount: workerCount,
|
||||
dst: dst,
|
||||
@@ -398,9 +397,9 @@ func (r *fileRestorer) downloadBlobs(ctx context.Context, packID restic.ID,
|
||||
}
|
||||
|
||||
func (r *fileRestorer) reportBlobProgress(file *fileInfo, blobSize uint64) {
|
||||
action := restore.ActionFileUpdated
|
||||
action := ActionFileUpdated
|
||||
if file.state == nil {
|
||||
action = restore.ActionFileRestored
|
||||
action = ActionFileRestored
|
||||
}
|
||||
r.progress.AddProgress(file.location, action, blobSize, uint64(file.size))
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fileio"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
)
|
||||
|
||||
@@ -166,7 +167,7 @@ func ensureSize(f *os.File, fi os.FileInfo, createSize int64, sparse bool) (*os.
|
||||
return nil, err
|
||||
}
|
||||
} else if createSize > 0 {
|
||||
err := fs.PreallocateFile(f, createSize)
|
||||
err := fileio.PreallocateFile(f, createSize)
|
||||
if err != nil {
|
||||
// Just log the preallocate error but don't let it cause the restore process to fail.
|
||||
// Preallocate might return an error if the filesystem (implementation) does not
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package restorer
|
||||
|
||||
type ItemAction string
|
||||
|
||||
// Constants for the different CompleteItem actions.
|
||||
const (
|
||||
ActionDirRestored ItemAction = "dir restored"
|
||||
ActionFileRestored ItemAction = "file restored"
|
||||
ActionFileUpdated ItemAction = "file updated"
|
||||
ActionFileUnchanged ItemAction = "file unchanged"
|
||||
ActionOtherRestored ItemAction = "other restored"
|
||||
ActionDeleted ItemAction = "deleted"
|
||||
)
|
||||
|
||||
// ProgressReporter reports restore progress.
|
||||
type ProgressReporter interface {
|
||||
AddFile(size uint64)
|
||||
AddProgress(name string, action ItemAction, bytesWrittenPortion, bytesTotal uint64)
|
||||
AddSkippedFile(name string, size uint64)
|
||||
ReportDeletion(name string)
|
||||
}
|
||||
|
||||
type noopProgressReporter struct{}
|
||||
|
||||
var _ ProgressReporter = (*noopProgressReporter)(nil)
|
||||
|
||||
func (noopProgressReporter) AddFile(uint64) {}
|
||||
func (noopProgressReporter) AddProgress(string, ItemAction, uint64, uint64) {
|
||||
}
|
||||
func (noopProgressReporter) AddSkippedFile(string, uint64) {}
|
||||
func (noopProgressReporter) ReportDeletion(string) {}
|
||||
|
||||
func progressOrNoop(p ProgressReporter) ProgressReporter {
|
||||
if p == nil {
|
||||
return noopProgressReporter{}
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package restorer
|
||||
|
||||
type progressInfoEntry struct {
|
||||
bytesWritten uint64
|
||||
bytesTotal uint64
|
||||
}
|
||||
|
||||
// progressState mirrors the state used by the restorer ui.
|
||||
type progressState struct {
|
||||
FilesFinished uint64
|
||||
FilesTotal uint64
|
||||
FilesSkipped uint64
|
||||
FilesDeleted uint64
|
||||
AllBytesWritten uint64
|
||||
AllBytesTotal uint64
|
||||
AllBytesSkipped uint64
|
||||
}
|
||||
|
||||
type testProgress struct {
|
||||
progressInfoMap map[string]progressInfoEntry
|
||||
s progressState
|
||||
}
|
||||
|
||||
var _ ProgressReporter = (*testProgress)(nil)
|
||||
|
||||
func newTestProgress() *testProgress {
|
||||
return &testProgress{
|
||||
progressInfoMap: make(map[string]progressInfoEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *testProgress) AddFile(size uint64) {
|
||||
p.s.FilesTotal++
|
||||
p.s.AllBytesTotal += size
|
||||
}
|
||||
|
||||
func (p *testProgress) AddProgress(name string, _ ItemAction, bytesWrittenPortion, bytesTotal uint64) {
|
||||
entry, exists := p.progressInfoMap[name]
|
||||
if !exists {
|
||||
entry.bytesTotal = bytesTotal
|
||||
}
|
||||
entry.bytesWritten += bytesWrittenPortion
|
||||
p.progressInfoMap[name] = entry
|
||||
|
||||
p.s.AllBytesWritten += bytesWrittenPortion
|
||||
if entry.bytesWritten == entry.bytesTotal {
|
||||
delete(p.progressInfoMap, name)
|
||||
p.s.FilesFinished++
|
||||
}
|
||||
}
|
||||
|
||||
func (p *testProgress) AddSkippedFile(_ string, size uint64) {
|
||||
p.s.FilesSkipped++
|
||||
p.s.AllBytesSkipped += size
|
||||
}
|
||||
|
||||
func (p *testProgress) ReportDeletion(_ string) {
|
||||
p.s.FilesDeleted++
|
||||
}
|
||||
|
||||
func (p *testProgress) state() progressState {
|
||||
return p.s
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
restoreui "github.com/restic/restic/internal/ui/restore"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -41,7 +40,7 @@ var restorerAbortOnAllErrors = func(_ string, err error) error { return err }
|
||||
type Options struct {
|
||||
DryRun bool
|
||||
Sparse bool
|
||||
Progress *restoreui.Progress
|
||||
Progress ProgressReporter
|
||||
Overwrite OverwriteBehavior
|
||||
Delete bool
|
||||
OwnershipByName bool
|
||||
@@ -100,6 +99,7 @@ func (c *OverwriteBehavior) Type() string {
|
||||
|
||||
// NewRestorer creates a restorer preloaded with the content from the snapshot id.
|
||||
func NewRestorer(repo restic.Repository, sn *data.Snapshot, opts Options) *Restorer {
|
||||
opts.Progress = progressOrNoop(opts.Progress)
|
||||
r := &Restorer{
|
||||
repo: repo,
|
||||
opts: opts,
|
||||
@@ -287,7 +287,7 @@ func (res *Restorer) restoreNodeTo(node *data.Node, target, location string) err
|
||||
}
|
||||
}
|
||||
|
||||
res.opts.Progress.AddProgress(location, restoreui.ActionOtherRestored, 0, 0)
|
||||
res.opts.Progress.AddProgress(location, ActionOtherRestored, 0, 0)
|
||||
return res.restoreNodeMetadataTo(node, target, location)
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ func (res *Restorer) restoreHardlinkAt(node *data.Node, target, path, location s
|
||||
}
|
||||
}
|
||||
|
||||
res.opts.Progress.AddProgress(location, restoreui.ActionOtherRestored, 0, 0)
|
||||
res.opts.Progress.AddProgress(location, ActionOtherRestored, 0, 0)
|
||||
// TODO investigate if hardlinks have separate metadata on any supported system
|
||||
return res.restoreNodeMetadataTo(node, path, location)
|
||||
}
|
||||
@@ -360,7 +360,7 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) (uint64, error)
|
||||
}
|
||||
}
|
||||
|
||||
idx := NewHardlinkIndex[string]()
|
||||
idx := data.NewHardlinkIndex[string]()
|
||||
filerestorer := newFileRestorer(dst, res.repo.LoadBlobsFromPack, res.repo.LookupBlob,
|
||||
res.repo.Connections(), res.opts.Sparse, res.opts.Delete, res.repo.StartWarmup, res.opts.Progress)
|
||||
filerestorer.Error = res.Error
|
||||
@@ -408,9 +408,9 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) (uint64, error)
|
||||
if !res.opts.DryRun {
|
||||
filerestorer.addFile(location, node.Content, int64(node.Size), matches)
|
||||
} else {
|
||||
action := restoreui.ActionFileUpdated
|
||||
action := ActionFileUpdated
|
||||
if matches == nil {
|
||||
action = restoreui.ActionFileRestored
|
||||
action = ActionFileRestored
|
||||
}
|
||||
// immediately mark as completed
|
||||
res.opts.Progress.AddProgress(location, action, node.Size, node.Size)
|
||||
@@ -475,7 +475,7 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) (uint64, error)
|
||||
|
||||
err := res.restoreNodeMetadataTo(node, target, location)
|
||||
if err == nil {
|
||||
res.opts.Progress.AddProgress(location, restoreui.ActionDirRestored, 0, 0)
|
||||
res.opts.Progress.AddProgress(location, ActionDirRestored, 0, 0)
|
||||
}
|
||||
return err
|
||||
},
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
restoreui "github.com/restic/restic/internal/ui/restore"
|
||||
)
|
||||
|
||||
type Node interface{}
|
||||
@@ -987,22 +986,6 @@ func TestRestorerSparseOverwrite(t *testing.T) {
|
||||
saveSnapshotsAndOverwrite(t, baseSnapshot, sparseSnapshot, opts, opts)
|
||||
}
|
||||
|
||||
type printerMock struct {
|
||||
s restoreui.State
|
||||
progress.Printer
|
||||
}
|
||||
|
||||
func (p *printerMock) Update(_ restoreui.State, _ time.Duration) {
|
||||
}
|
||||
func (p *printerMock) Error(_ string, _ error) error {
|
||||
return nil
|
||||
}
|
||||
func (p *printerMock) CompleteItem(_ restoreui.ItemAction, _ string, _ uint64) {
|
||||
}
|
||||
func (p *printerMock) Finish(s restoreui.State, _ time.Duration) {
|
||||
p.s = s
|
||||
}
|
||||
|
||||
func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
baseTime := time.Now()
|
||||
baseSnapshot := Snapshot{
|
||||
@@ -1032,7 +1015,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Overwrite OverwriteBehavior
|
||||
Files map[string]string
|
||||
Progress restoreui.State
|
||||
Progress progressState
|
||||
}{
|
||||
{
|
||||
Overwrite: OverwriteAlways,
|
||||
@@ -1041,7 +1024,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
"dirtest/file": "content: file2\n",
|
||||
"dirtest/foo": "content: foo",
|
||||
},
|
||||
Progress: restoreui.State{
|
||||
Progress: progressState{
|
||||
FilesFinished: 4,
|
||||
FilesTotal: 4,
|
||||
FilesSkipped: 0,
|
||||
@@ -1057,7 +1040,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
"dirtest/file": "content: file2\n",
|
||||
"dirtest/foo": "content: foo",
|
||||
},
|
||||
Progress: restoreui.State{
|
||||
Progress: progressState{
|
||||
FilesFinished: 4,
|
||||
FilesTotal: 4,
|
||||
FilesSkipped: 0,
|
||||
@@ -1073,7 +1056,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
"dirtest/file": "content: file\n",
|
||||
"dirtest/foo": "content: foobar",
|
||||
},
|
||||
Progress: restoreui.State{
|
||||
Progress: progressState{
|
||||
FilesFinished: 2,
|
||||
FilesTotal: 2,
|
||||
FilesSkipped: 2,
|
||||
@@ -1089,7 +1072,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
"dirtest/file": "content: file\n",
|
||||
"dirtest/foo": "content: foobar",
|
||||
},
|
||||
Progress: restoreui.State{
|
||||
Progress: progressState{
|
||||
FilesFinished: 1,
|
||||
FilesTotal: 1,
|
||||
FilesSkipped: 3,
|
||||
@@ -1102,8 +1085,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
progress := newTestProgress()
|
||||
tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: test.Overwrite, Progress: progress})
|
||||
|
||||
for filename, content := range test.Files {
|
||||
@@ -1118,8 +1100,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
progress.Finish()
|
||||
rtest.Equals(t, test.Progress, mock.s)
|
||||
rtest.Equals(t, test.Progress, progress.state())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1154,18 +1135,16 @@ func TestRestorerOverwritePartial(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
progress := newTestProgress()
|
||||
saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: OverwriteAlways, Progress: progress})
|
||||
progress.Finish()
|
||||
rtest.Equals(t, restoreui.State{
|
||||
rtest.Equals(t, progressState{
|
||||
FilesFinished: 2,
|
||||
FilesTotal: 2,
|
||||
FilesSkipped: 0,
|
||||
AllBytesWritten: uint64(size),
|
||||
AllBytesTotal: uint64(size),
|
||||
AllBytesSkipped: 0,
|
||||
}, mock.s)
|
||||
}, progress.state())
|
||||
}
|
||||
|
||||
func TestRestorerOverwriteSpecial(t *testing.T) {
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/repository"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
restoreui "github.com/restic/restic/internal/ui/restore"
|
||||
)
|
||||
|
||||
func TestRestorerRestoreEmptyHardlinkedFields(t *testing.T) {
|
||||
@@ -88,8 +86,7 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) {
|
||||
},
|
||||
}, noopGetGenericAttributes)
|
||||
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
progress := newTestProgress()
|
||||
res := NewRestorer(repo, sn, Options{Progress: progress, DryRun: dryRun})
|
||||
|
||||
tempdir := rtest.TempDir(t)
|
||||
@@ -98,16 +95,15 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) {
|
||||
|
||||
_, err := res.RestoreTo(ctx, tempdir)
|
||||
rtest.OK(t, err)
|
||||
progress.Finish()
|
||||
|
||||
rtest.Equals(t, restoreui.State{
|
||||
rtest.Equals(t, progressState{
|
||||
FilesFinished: 4,
|
||||
FilesTotal: 4,
|
||||
FilesSkipped: 0,
|
||||
AllBytesWritten: 10,
|
||||
AllBytesTotal: 10,
|
||||
AllBytesSkipped: 0,
|
||||
}, mock.s)
|
||||
}, progress.state())
|
||||
}
|
||||
|
||||
func TestRestorePermissions(t *testing.T) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// jsonProgress reports progress for the `backup` command in JSON.
|
||||
type jsonProgress struct {
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
|
||||
term ui.Terminal
|
||||
v uint
|
||||
|
||||
@@ -21,7 +21,7 @@ type ProgressPrinter interface {
|
||||
Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool)
|
||||
Reset()
|
||||
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
|
||||
@@ -8,12 +8,11 @@ import (
|
||||
"github.com/restic/restic/internal/archiver"
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type mockPrinter struct {
|
||||
sync.Mutex
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
dirUnchanged, fileNew bool
|
||||
id restic.ID
|
||||
}
|
||||
@@ -48,7 +47,7 @@ func (p *mockPrinter) Reset() {}
|
||||
func TestProgress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prnt := &mockPrinter{Printer: progress.NewNoopPrinter()}
|
||||
prnt := &mockPrinter{Printer: restic.NewNoopPrinter()}
|
||||
prog := newProgress(prnt, time.Millisecond)
|
||||
|
||||
prog.StartFile("foo")
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// textProgress reports progress for the `backup` command.
|
||||
type textProgress struct {
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
|
||||
term ui.Terminal
|
||||
verbosity uint
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// A Printer can can return a new counter or print messages
|
||||
// at different log levels.
|
||||
// It must be safe to call its methods from concurrent goroutines.
|
||||
type Printer interface {
|
||||
// NewCounter returns a new progress counter. It is not shown if --quiet or --json is specified.
|
||||
NewCounter(description string) restic.Counter
|
||||
// NewCounterTerminalOnly returns a new progress counter that is only shown if stdout points to a
|
||||
// terminal. It is not shown if --quiet or --json is specified.
|
||||
NewCounterTerminalOnly(description string) restic.Counter
|
||||
|
||||
// E reports an error. This message is always printed to stderr.
|
||||
// Appends a newline if not present.
|
||||
E(msg string, args ...interface{})
|
||||
// S prints a message, this is should only be used for very important messages
|
||||
// that are not errors. The message is even printed if --quiet is specified.
|
||||
// Appends a newline if not present.
|
||||
S(msg string, args ...interface{})
|
||||
// PT prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified)
|
||||
// and stdout points to a terminal.
|
||||
// This is used for informational messages.
|
||||
PT(msg string, args ...interface{})
|
||||
// P prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified),
|
||||
// this is used for normal messages which are not errors. Appends a newline if not present.
|
||||
P(msg string, args ...interface{})
|
||||
// V prints a message if verbosity >= 2 (equivalent to --verbose), this is used for
|
||||
// verbose messages. Appends a newline if not present.
|
||||
V(msg string, args ...interface{})
|
||||
// VV prints a message if verbosity >= 3 (equivalent to --verbose=2), this is used for
|
||||
// debug messages. Appends a newline if not present.
|
||||
VV(msg string, args ...interface{})
|
||||
}
|
||||
|
||||
// noopPrinter discards all messages.
|
||||
type noopPrinter struct{}
|
||||
|
||||
var _ Printer = (*noopPrinter)(nil)
|
||||
|
||||
// NewNoopPrinter returns a Printer that discards all messages.
|
||||
func NewNoopPrinter() Printer {
|
||||
return &noopPrinter{}
|
||||
}
|
||||
|
||||
func (*noopPrinter) NewCounter(_ string) restic.Counter {
|
||||
return restic.NoopCounter
|
||||
}
|
||||
|
||||
func (*noopPrinter) NewCounterTerminalOnly(_ string) restic.Counter {
|
||||
return restic.NoopCounter
|
||||
}
|
||||
|
||||
func (*noopPrinter) E(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) S(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) PT(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) P(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) V(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*noopPrinter) VV(_ string, _ ...interface{}) {}
|
||||
@@ -98,7 +98,7 @@ func (t *terminalPrinter) VV(msg string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func NewTerminalPrinter(json bool, verbosity uint, term ui.Terminal) Printer {
|
||||
func NewTerminalPrinter(json bool, verbosity uint, term ui.Terminal) restic.Printer {
|
||||
if json {
|
||||
verbosity = 0
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package restore
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type jsonPrinter struct {
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
|
||||
terminal ui.Terminal
|
||||
verbosity uint
|
||||
@@ -60,24 +62,24 @@ func (t *jsonPrinter) Error(item string, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *jsonPrinter) CompleteItem(messageType ItemAction, item string, size uint64) {
|
||||
func (t *jsonPrinter) CompleteItem(messageType restorer.ItemAction, item string, size uint64) {
|
||||
if t.verbosity < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
var action string
|
||||
switch messageType {
|
||||
case ActionDirRestored:
|
||||
case restorer.ActionDirRestored:
|
||||
action = "restored"
|
||||
case ActionFileRestored:
|
||||
case restorer.ActionFileRestored:
|
||||
action = "restored"
|
||||
case ActionOtherRestored:
|
||||
case restorer.ActionOtherRestored:
|
||||
action = "restored"
|
||||
case ActionFileUpdated:
|
||||
case restorer.ActionFileUpdated:
|
||||
action = "updated"
|
||||
case ActionFileUnchanged:
|
||||
case restorer.ActionFileUnchanged:
|
||||
action = "unchanged"
|
||||
case ActionDeleted:
|
||||
case restorer.ActionDeleted:
|
||||
action = "deleted"
|
||||
default:
|
||||
panic("unknown message type")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
@@ -47,15 +48,15 @@ func TestJSONPrintSummaryOnSuccessWithSkipped(t *testing.T) {
|
||||
|
||||
func TestJSONPrintCompleteItem(t *testing.T) {
|
||||
for _, data := range []struct {
|
||||
action ItemAction
|
||||
action restorer.ItemAction
|
||||
size uint64
|
||||
expected string
|
||||
}{
|
||||
{ActionDirRestored, 0, "{\"message_type\":\"verbose_status\",\"action\":\"restored\",\"item\":\"test\",\"size\":0}\n"},
|
||||
{ActionFileRestored, 123, "{\"message_type\":\"verbose_status\",\"action\":\"restored\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{ActionFileUpdated, 123, "{\"message_type\":\"verbose_status\",\"action\":\"updated\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{ActionFileUnchanged, 123, "{\"message_type\":\"verbose_status\",\"action\":\"unchanged\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{ActionDeleted, 0, "{\"message_type\":\"verbose_status\",\"action\":\"deleted\",\"item\":\"test\",\"size\":0}\n"},
|
||||
{restorer.ActionDirRestored, 0, "{\"message_type\":\"verbose_status\",\"action\":\"restored\",\"item\":\"test\",\"size\":0}\n"},
|
||||
{restorer.ActionFileRestored, 123, "{\"message_type\":\"verbose_status\",\"action\":\"restored\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{restorer.ActionFileUpdated, 123, "{\"message_type\":\"verbose_status\",\"action\":\"updated\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{restorer.ActionFileUnchanged, 123, "{\"message_type\":\"verbose_status\",\"action\":\"unchanged\",\"item\":\"test\",\"size\":123}\n"},
|
||||
{restorer.ActionDeleted, 0, "{\"message_type\":\"verbose_status\",\"action\":\"deleted\",\"item\":\"test\",\"size\":0}\n"},
|
||||
} {
|
||||
term, printer := createJSONProgress()
|
||||
printer.CompleteItem(data.action, "test", data.size)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,8 @@ type Progress struct {
|
||||
printer ProgressPrinter
|
||||
}
|
||||
|
||||
var _ restorer.ProgressReporter = (*Progress)(nil)
|
||||
|
||||
type progressInfoEntry struct {
|
||||
bytesWritten uint64
|
||||
bytesTotal uint64
|
||||
@@ -36,22 +40,12 @@ type progressInfoEntry struct {
|
||||
type ProgressPrinter interface {
|
||||
Update(progress State, duration time.Duration)
|
||||
Error(item string, err error) error
|
||||
CompleteItem(action ItemAction, item string, size uint64)
|
||||
CompleteItem(action restorer.ItemAction, item string, size uint64)
|
||||
Finish(progress State, duration time.Duration)
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
}
|
||||
|
||||
type ItemAction string
|
||||
|
||||
// Constants for the different CompleteItem actions.
|
||||
const (
|
||||
ActionDirRestored ItemAction = "dir restored"
|
||||
ActionFileRestored ItemAction = "file restored"
|
||||
ActionFileUpdated ItemAction = "file updated"
|
||||
ActionFileUnchanged ItemAction = "file unchanged"
|
||||
ActionOtherRestored ItemAction = "other restored"
|
||||
ActionDeleted ItemAction = "deleted"
|
||||
)
|
||||
var _ restorer.ProgressReporter = (*Progress)(nil)
|
||||
|
||||
func NewProgress(printer ProgressPrinter, quiet, json, canUpdateStatus bool) *Progress {
|
||||
return newProgress(printer, progress.CalculateProgressInterval(!quiet, json, canUpdateStatus))
|
||||
@@ -92,7 +86,7 @@ func (p *Progress) AddFile(size uint64) {
|
||||
}
|
||||
|
||||
// AddProgress accumulates the number of bytes written for a file
|
||||
func (p *Progress) AddProgress(name string, action ItemAction, bytesWrittenPortion uint64, bytesTotal uint64) {
|
||||
func (p *Progress) AddProgress(name string, action restorer.ItemAction, bytesWrittenPortion uint64, bytesTotal uint64) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
@@ -127,7 +121,7 @@ func (p *Progress) AddSkippedFile(name string, size uint64) {
|
||||
p.s.FilesSkipped++
|
||||
p.s.AllBytesSkipped += size
|
||||
|
||||
p.printer.CompleteItem(ActionFileUnchanged, name, size)
|
||||
p.printer.CompleteItem(restorer.ActionFileUnchanged, name, size)
|
||||
}
|
||||
|
||||
func (p *Progress) ReportDeletion(name string) {
|
||||
@@ -140,7 +134,7 @@ func (p *Progress) ReportDeletion(name string) {
|
||||
|
||||
p.s.FilesDeleted++
|
||||
|
||||
p.printer.CompleteItem(ActionDeleted, name, 0)
|
||||
p.printer.CompleteItem(restorer.ActionDeleted, name, 0)
|
||||
}
|
||||
|
||||
func (p *Progress) Error(item string, err error) error {
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type printerTraceEntry struct {
|
||||
@@ -19,7 +20,7 @@ type printerTraceEntry struct {
|
||||
type printerTrace []printerTraceEntry
|
||||
|
||||
type itemTraceEntry struct {
|
||||
action ItemAction
|
||||
action restorer.ItemAction
|
||||
item string
|
||||
size uint64
|
||||
}
|
||||
@@ -37,7 +38,7 @@ type mockPrinter struct {
|
||||
trace printerTrace
|
||||
items itemTrace
|
||||
errors errorTrace
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
}
|
||||
|
||||
const mockFinishDuration = 42 * time.Second
|
||||
@@ -49,7 +50,7 @@ func (p *mockPrinter) Error(item string, err error) error {
|
||||
p.errors = append(p.errors, errorTraceEntry{item, err})
|
||||
return nil
|
||||
}
|
||||
func (p *mockPrinter) CompleteItem(action ItemAction, item string, size uint64) {
|
||||
func (p *mockPrinter) CompleteItem(action restorer.ItemAction, item string, size uint64) {
|
||||
p.items = append(p.items, itemTraceEntry{action, item, size})
|
||||
}
|
||||
func (p *mockPrinter) Finish(progress State, _ time.Duration) {
|
||||
@@ -57,7 +58,7 @@ func (p *mockPrinter) Finish(progress State, _ time.Duration) {
|
||||
}
|
||||
|
||||
func testProgress(fn func(progress *Progress) bool) (printerTrace, itemTrace, errorTrace) {
|
||||
printer := &mockPrinter{Printer: progress.NewNoopPrinter()}
|
||||
printer := &mockPrinter{Printer: restic.NewNoopPrinter()}
|
||||
progress := newProgress(printer, 0)
|
||||
final := fn(progress)
|
||||
progress.update(0, final)
|
||||
@@ -98,7 +99,7 @@ func TestFirstProgressOnAFile(t *testing.T) {
|
||||
|
||||
result, items, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(expectedBytesTotal)
|
||||
progress.AddProgress("test", ActionFileUpdated, expectedBytesWritten, expectedBytesTotal)
|
||||
progress.AddProgress("test", restorer.ActionFileUpdated, expectedBytesWritten, expectedBytesTotal)
|
||||
return false
|
||||
})
|
||||
test.Equals(t, printerTrace{
|
||||
@@ -112,16 +113,16 @@ func TestLastProgressOnAFile(t *testing.T) {
|
||||
|
||||
result, items, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(fileSize)
|
||||
progress.AddProgress("test", ActionFileUpdated, 30, fileSize)
|
||||
progress.AddProgress("test", ActionFileUpdated, 35, fileSize)
|
||||
progress.AddProgress("test", ActionFileUpdated, 35, fileSize)
|
||||
progress.AddProgress("test", restorer.ActionFileUpdated, 30, fileSize)
|
||||
progress.AddProgress("test", restorer.ActionFileUpdated, 35, fileSize)
|
||||
progress.AddProgress("test", restorer.ActionFileUpdated, 35, fileSize)
|
||||
return false
|
||||
})
|
||||
test.Equals(t, printerTrace{
|
||||
printerTraceEntry{State{1, 1, 0, 0, fileSize, fileSize, 0}, 0, false},
|
||||
}, result)
|
||||
test.Equals(t, itemTrace{
|
||||
itemTraceEntry{action: ActionFileUpdated, item: "test", size: fileSize},
|
||||
itemTraceEntry{action: restorer.ActionFileUpdated, item: "test", size: fileSize},
|
||||
}, items)
|
||||
}
|
||||
|
||||
@@ -131,17 +132,17 @@ func TestLastProgressOnLastFile(t *testing.T) {
|
||||
result, items, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(fileSize)
|
||||
progress.AddFile(50)
|
||||
progress.AddProgress("test1", ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", ActionFileUpdated, 50, fileSize)
|
||||
progress.AddProgress("test2", ActionFileUpdated, 50, fileSize)
|
||||
progress.AddProgress("test1", restorer.ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", restorer.ActionFileUpdated, 50, fileSize)
|
||||
progress.AddProgress("test2", restorer.ActionFileUpdated, 50, fileSize)
|
||||
return false
|
||||
})
|
||||
test.Equals(t, printerTrace{
|
||||
printerTraceEntry{State{2, 2, 0, 0, 50 + fileSize, 50 + fileSize, 0}, 0, false},
|
||||
}, result)
|
||||
test.Equals(t, itemTrace{
|
||||
itemTraceEntry{action: ActionFileUpdated, item: "test1", size: 50},
|
||||
itemTraceEntry{action: ActionFileUpdated, item: "test2", size: fileSize},
|
||||
itemTraceEntry{action: restorer.ActionFileUpdated, item: "test1", size: 50},
|
||||
itemTraceEntry{action: restorer.ActionFileUpdated, item: "test2", size: fileSize},
|
||||
}, items)
|
||||
}
|
||||
|
||||
@@ -151,8 +152,8 @@ func TestSummaryOnSuccess(t *testing.T) {
|
||||
result, _, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(fileSize)
|
||||
progress.AddFile(50)
|
||||
progress.AddProgress("test1", ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", ActionFileUpdated, fileSize, fileSize)
|
||||
progress.AddProgress("test1", restorer.ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", restorer.ActionFileUpdated, fileSize, fileSize)
|
||||
return true
|
||||
})
|
||||
test.Equals(t, printerTrace{
|
||||
@@ -166,8 +167,8 @@ func TestSummaryOnErrors(t *testing.T) {
|
||||
result, _, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(fileSize)
|
||||
progress.AddFile(50)
|
||||
progress.AddProgress("test1", ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", ActionFileUpdated, fileSize/2, fileSize)
|
||||
progress.AddProgress("test1", restorer.ActionFileUpdated, 50, 50)
|
||||
progress.AddProgress("test2", restorer.ActionFileUpdated, fileSize/2, fileSize)
|
||||
return true
|
||||
})
|
||||
test.Equals(t, printerTrace{
|
||||
@@ -186,7 +187,7 @@ func TestSkipFile(t *testing.T) {
|
||||
printerTraceEntry{State{0, 0, 1, 0, 0, 0, fileSize}, mockFinishDuration, true},
|
||||
}, result)
|
||||
test.Equals(t, itemTrace{
|
||||
itemTraceEntry{ActionFileUnchanged, "test", fileSize},
|
||||
itemTraceEntry{restorer.ActionFileUnchanged, "test", fileSize},
|
||||
}, items)
|
||||
}
|
||||
|
||||
@@ -196,15 +197,15 @@ func TestProgressTypes(t *testing.T) {
|
||||
_, items, _ := testProgress(func(progress *Progress) bool {
|
||||
progress.AddFile(fileSize)
|
||||
progress.AddFile(0)
|
||||
progress.AddProgress("dir", ActionDirRestored, fileSize, fileSize)
|
||||
progress.AddProgress("new", ActionFileRestored, 0, 0)
|
||||
progress.AddProgress("dir", restorer.ActionDirRestored, fileSize, fileSize)
|
||||
progress.AddProgress("new", restorer.ActionFileRestored, 0, 0)
|
||||
progress.ReportDeletion("del")
|
||||
return true
|
||||
})
|
||||
test.Equals(t, itemTrace{
|
||||
itemTraceEntry{ActionDirRestored, "dir", fileSize},
|
||||
itemTraceEntry{ActionFileRestored, "new", 0},
|
||||
itemTraceEntry{ActionDeleted, "del", 0},
|
||||
itemTraceEntry{restorer.ActionDirRestored, "dir", fileSize},
|
||||
itemTraceEntry{restorer.ActionFileRestored, "new", 0},
|
||||
itemTraceEntry{restorer.ActionDeleted, "del", 0},
|
||||
}, items)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type textPrinter struct {
|
||||
progress.Printer
|
||||
restic.Printer
|
||||
|
||||
terminal ui.Terminal
|
||||
}
|
||||
@@ -43,26 +45,26 @@ func (t *textPrinter) Error(item string, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *textPrinter) CompleteItem(messageType ItemAction, item string, size uint64) {
|
||||
func (t *textPrinter) CompleteItem(messageType restorer.ItemAction, item string, size uint64) {
|
||||
var action string
|
||||
switch messageType {
|
||||
case ActionDirRestored:
|
||||
case restorer.ActionDirRestored:
|
||||
action = "restored"
|
||||
case ActionFileRestored:
|
||||
case restorer.ActionFileRestored:
|
||||
action = "restored"
|
||||
case ActionOtherRestored:
|
||||
case restorer.ActionOtherRestored:
|
||||
action = "restored"
|
||||
case ActionFileUpdated:
|
||||
case restorer.ActionFileUpdated:
|
||||
action = "updated"
|
||||
case ActionFileUnchanged:
|
||||
case restorer.ActionFileUnchanged:
|
||||
action = "unchanged"
|
||||
case ActionDeleted:
|
||||
case restorer.ActionDeleted:
|
||||
action = "deleted"
|
||||
default:
|
||||
panic("unknown message type")
|
||||
}
|
||||
|
||||
if messageType == ActionDirRestored || messageType == ActionOtherRestored || messageType == ActionDeleted {
|
||||
if messageType == restorer.ActionDirRestored || messageType == restorer.ActionOtherRestored || messageType == restorer.ActionDeleted {
|
||||
t.VV("%-9v %v", action, item)
|
||||
} else {
|
||||
t.VV("%-9v %v with size %v", action, item, ui.FormatBytes(size))
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
@@ -47,16 +48,16 @@ func TestPrintSummaryOnSuccessWithSkipped(t *testing.T) {
|
||||
|
||||
func TestPrintCompleteItem(t *testing.T) {
|
||||
for _, data := range []struct {
|
||||
action ItemAction
|
||||
action restorer.ItemAction
|
||||
size uint64
|
||||
expected string
|
||||
}{
|
||||
{ActionDirRestored, 0, "restored test"},
|
||||
{ActionFileRestored, 123, "restored test with size 123 B"},
|
||||
{ActionOtherRestored, 0, "restored test"},
|
||||
{ActionFileUpdated, 123, "updated test with size 123 B"},
|
||||
{ActionFileUnchanged, 123, "unchanged test with size 123 B"},
|
||||
{ActionDeleted, 0, "deleted test"},
|
||||
{restorer.ActionDirRestored, 0, "restored test"},
|
||||
{restorer.ActionFileRestored, 123, "restored test with size 123 B"},
|
||||
{restorer.ActionOtherRestored, 0, "restored test"},
|
||||
{restorer.ActionFileUpdated, 123, "updated test with size 123 B"},
|
||||
{restorer.ActionFileUnchanged, 123, "unchanged test with size 123 B"},
|
||||
{restorer.ActionDeleted, 0, "deleted test"},
|
||||
} {
|
||||
term, printer := createTextProgress()
|
||||
printer.CompleteItem(data.action, "test", data.size)
|
||||
|
||||
Reference in New Issue
Block a user