mirror of
https://github.com/restic/restic.git
synced 2025-10-27 07:28:36 +00:00
Update: new restic version
Update interfaces to match the new restic version's
This commit is contained in:
parent
cb1982a6eb
commit
4064f7cbc0
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/feature"
|
||||
@ -18,7 +19,7 @@ import (
|
||||
|
||||
// Reference: cmd_copy.go (v0.18.0)
|
||||
|
||||
func newRechunkCopyCommand() *cobra.Command {
|
||||
func newRechunkCopyCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RechunkCopyOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "rechunk-copy [flags] [snapshotID ...]",
|
||||
@ -26,11 +27,13 @@ func newRechunkCopyCommand() *cobra.Command {
|
||||
Long: `
|
||||
The "rechunk-copy" command rechunk-copies one or more snapshots from one repository to another.
|
||||
|
||||
Data blobs stored in the destination repo are rechunked, and tree blobs in the destination repo are also updated accordingly.
|
||||
Data blobs will be rechunked and stored in the destination repo.
|
||||
Tree blobs in the destination repo are also updated to point to the rechunked data blobs,
|
||||
but it does not modify any other metadata.
|
||||
|
||||
NOTE: This command has largely different internal mechanism from "copy" command,
|
||||
due to restic's content defined chunking (CDC) design. Note that "rechunk-copy"
|
||||
may consume significantly more bandwidth during the process compared to "copy",
|
||||
due to restic's content defined chunking (CDC) algorithm. Note that "rechunk-copy"
|
||||
could consume significantly more bandwidth during the process compared to "copy",
|
||||
and may also need significantly more time to finish.
|
||||
|
||||
EXIT STATUS
|
||||
@ -45,9 +48,8 @@ Exit status is 12 if the password is incorrect.
|
||||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRechunkCopy(cmd.Context(), opts, globalOptions, args, term)
|
||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||
return runRechunkCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
@ -58,8 +60,8 @@ Exit status is 12 if the password is incorrect.
|
||||
// RechunkCopyOptions bundles all options for the rechunk-copy command.
|
||||
type RechunkCopyOptions struct {
|
||||
secondaryRepoOptions
|
||||
restic.SnapshotFilter
|
||||
RechunkTags restic.TagLists
|
||||
data.SnapshotFilter
|
||||
RechunkTags data.TagLists
|
||||
UsePackCache bool
|
||||
isIntegrationTest bool // skip check for RESTIC_FEATURES=rechunk-copy when integration test
|
||||
}
|
||||
@ -76,8 +78,8 @@ func runRechunkCopy(ctx context.Context, opts RechunkCopyOptions, gopts GlobalOp
|
||||
return errors.Fatal("rechunk-copy feature flag is not set. Currently, rechunk-copy is alpha feature (disabled by default).")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination", printer)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -104,13 +106,11 @@ func runRechunkCopy(ctx context.Context, opts RechunkCopyOptions, gopts GlobalOp
|
||||
}
|
||||
|
||||
debug.Log("Loading source index")
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err := srcRepo.LoadIndex(ctx, bar); err != nil {
|
||||
if err := srcRepo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
bar = newIndexTerminalProgress(printer)
|
||||
debug.Log("Loading destination index")
|
||||
if err := dstRepo.LoadIndex(ctx, bar); err != nil {
|
||||
if err := dstRepo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ func runRechunkCopy(ctx context.Context, opts RechunkCopyOptions, gopts GlobalOp
|
||||
sn.Tree = &newTreeID
|
||||
// add tags if provided by user
|
||||
sn.AddTags(opts.RechunkTags.Flatten())
|
||||
newID, err := restic.SaveSnapshot(ctx, dstRepo, sn)
|
||||
newID, err := data.SaveSnapshot(ctx, dstRepo, sn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
// Reference: cmd_copy_integration_test.go (v0.18.0)
|
||||
@ -26,8 +25,8 @@ func testRunRechunkCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOpt
|
||||
isIntegrationTest: true,
|
||||
}
|
||||
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRechunkCopy(context.TODO(), rechunkCopyOpts, gopts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRechunkCopy(context.TODO(), rechunkCopyOpts, gopts, nil, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -51,8 +50,8 @@ func TestRechunkCopy(t *testing.T) {
|
||||
copiedSnapshotIDs := testListSnapshots(t, env2.gopts, 3)
|
||||
|
||||
// Check that the copies size seems reasonable
|
||||
stat := dirStats(env.repo)
|
||||
stat2 := dirStats(env2.repo)
|
||||
stat := dirStats(t, env.repo)
|
||||
stat2 := dirStats(t, env2.repo)
|
||||
sizeDiff := int64(stat.size) - int64(stat2.size)
|
||||
if sizeDiff < 0 {
|
||||
sizeDiff = -sizeDiff
|
||||
@ -75,7 +74,7 @@ func TestRechunkCopy(t *testing.T) {
|
||||
testRunRestore(t, env2.gopts, restoredir, snapshotID.String())
|
||||
foundMatch := false
|
||||
for cmpdir := range origRestores {
|
||||
diff := directoriesContentsDiff(restoredir, cmpdir)
|
||||
diff := directoriesContentsDiff(t, restoredir, cmpdir)
|
||||
if diff == "" {
|
||||
delete(origRestores, cmpdir)
|
||||
foundMatch = true
|
||||
|
||||
@ -88,6 +88,7 @@ The full documentation can be found at https://restic.readthedocs.io/ .
|
||||
newOptionsCommand(globalOptions),
|
||||
newPruneCommand(globalOptions),
|
||||
newRebuildIndexCommand(globalOptions),
|
||||
newRechunkCopyCommand(globalOptions),
|
||||
newRecoverCommand(globalOptions),
|
||||
newRepairCommand(globalOptions),
|
||||
newRestoreCommand(globalOptions),
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/restic/chunker"
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
@ -159,7 +160,7 @@ func (rc *Rechunker) Plan(ctx context.Context, srcRepo restic.Repository, rootTr
|
||||
}
|
||||
|
||||
wg, wgCtx := errgroup.WithContext(ctx)
|
||||
treeStream := restic.StreamTrees(wgCtx, wg, srcRepo, rootTrees, func(id restic.ID) bool {
|
||||
treeStream := data.StreamTrees(wgCtx, wg, srcRepo, rootTrees, func(id restic.ID) bool {
|
||||
visited := visitedTrees.Has(id)
|
||||
visitedTrees.Insert(id)
|
||||
return visited
|
||||
@ -183,7 +184,7 @@ func (rc *Rechunker) Plan(ctx context.Context, srcRepo restic.Repository, rootTr
|
||||
}
|
||||
|
||||
for _, node := range tree.Nodes {
|
||||
if node.Type == restic.NodeTypeFile {
|
||||
if node.Type == data.NodeTypeFile {
|
||||
hashval := hashOfIDs(node.Content)
|
||||
if _, ok := visitedFiles[hashval]; ok {
|
||||
continue
|
||||
@ -734,8 +735,8 @@ func (rc *Rechunker) RechunkData(ctx context.Context, srcRepo PackedBlobLoader,
|
||||
return err
|
||||
}
|
||||
|
||||
func (rc *Rechunker) rewriteNode(node *restic.Node) error {
|
||||
if node.Type != restic.NodeTypeFile {
|
||||
func (rc *Rechunker) rewriteNode(node *data.Node) error {
|
||||
if node.Type != data.NodeTypeFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -755,12 +756,12 @@ func (rc *Rechunker) RewriteTree(ctx context.Context, srcRepo restic.BlobLoader,
|
||||
return newID, nil
|
||||
}
|
||||
|
||||
curTree, err := restic.LoadTree(ctx, srcRepo, nodeID)
|
||||
curTree, err := data.LoadTree(ctx, srcRepo, nodeID)
|
||||
if err != nil {
|
||||
return restic.ID{}, err
|
||||
}
|
||||
|
||||
tb := restic.NewTreeJSONBuilder()
|
||||
tb := data.NewTreeJSONBuilder()
|
||||
for _, node := range curTree.Nodes {
|
||||
if ctx.Err() != nil {
|
||||
return restic.ID{}, ctx.Err()
|
||||
@ -771,7 +772,7 @@ func (rc *Rechunker) RewriteTree(ctx context.Context, srcRepo restic.BlobLoader,
|
||||
return restic.ID{}, err
|
||||
}
|
||||
|
||||
if node.Type != restic.NodeTypeDir {
|
||||
if node.Type != data.NodeTypeDir {
|
||||
err = tb.AddNode(node)
|
||||
if err != nil {
|
||||
return restic.ID{}, err
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/restic/chunker"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
@ -250,7 +251,7 @@ func generateBlobIDsPair(nSrc, nDst uint) BlobIDsPair {
|
||||
type TreeMap map[restic.ID][]byte
|
||||
type TestTree map[string]interface{}
|
||||
type TestContentNode struct {
|
||||
Type restic.NodeType
|
||||
Type data.NodeType
|
||||
Size uint64
|
||||
Content restic.IDs
|
||||
}
|
||||
@ -282,7 +283,7 @@ func BuildTreeMap(tree TestTree) (m TreeMap, root restic.ID) {
|
||||
}
|
||||
|
||||
func buildTreeMap(tree TestTree, m TreeMap) restic.ID {
|
||||
tb := restic.NewTreeJSONBuilder()
|
||||
tb := data.NewTreeJSONBuilder()
|
||||
var names []string
|
||||
for name := range tree {
|
||||
names = append(names, name)
|
||||
@ -294,16 +295,16 @@ func buildTreeMap(tree TestTree, m TreeMap) restic.ID {
|
||||
switch elem := item.(type) {
|
||||
case TestTree:
|
||||
id := buildTreeMap(elem, m)
|
||||
err := tb.AddNode(&restic.Node{
|
||||
err := tb.AddNode(&data.Node{
|
||||
Name: name,
|
||||
Subtree: &id,
|
||||
Type: restic.NodeTypeDir,
|
||||
Type: data.NodeTypeDir,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case TestContentNode:
|
||||
err := tb.AddNode(&restic.Node{
|
||||
err := tb.AddNode(&data.Node{
|
||||
Name: name,
|
||||
Type: elem.Type,
|
||||
Size: elem.Size,
|
||||
@ -345,68 +346,68 @@ func TestRechunkerRewriteTree(t *testing.T) {
|
||||
|
||||
tree := TestTree{
|
||||
"zerofile": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 0,
|
||||
Content: restic.IDs{},
|
||||
},
|
||||
"a": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 1,
|
||||
Content: blobIDsMap["a"].srcBlobIDs,
|
||||
},
|
||||
"subdir": TestTree{
|
||||
"a": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 3,
|
||||
Content: blobIDsMap["subdir/a"].srcBlobIDs,
|
||||
},
|
||||
"x": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 2,
|
||||
Content: blobIDsMap["x"].srcBlobIDs,
|
||||
},
|
||||
"subdir": TestTree{
|
||||
"dup_x": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 2,
|
||||
Content: blobIDsMap["x"].srcBlobIDs,
|
||||
},
|
||||
"nonregularfile": TestContentNode{
|
||||
Type: restic.NodeTypeSymlink,
|
||||
Type: data.NodeTypeSymlink,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
wants := TestTree{
|
||||
"zerofile": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 0,
|
||||
Content: restic.IDs{},
|
||||
},
|
||||
"a": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 1,
|
||||
Content: blobIDsMap["a"].dstBlobIDs,
|
||||
},
|
||||
"subdir": TestTree{
|
||||
"a": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 3,
|
||||
Content: blobIDsMap["subdir/a"].dstBlobIDs,
|
||||
},
|
||||
"x": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 2,
|
||||
Content: blobIDsMap["x"].dstBlobIDs,
|
||||
},
|
||||
"subdir": TestTree{
|
||||
"dup_x": TestContentNode{
|
||||
Type: restic.NodeTypeFile,
|
||||
Type: data.NodeTypeFile,
|
||||
Size: 2,
|
||||
Content: blobIDsMap["x"].dstBlobIDs,
|
||||
},
|
||||
"nonregularfile": TestContentNode{
|
||||
Type: restic.NodeTypeSymlink,
|
||||
Type: data.NodeTypeSymlink,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user