Update: new restic version

Update interfaces to match the new restic version's
This commit is contained in:
Donggyu Kim 2025-10-10 18:52:34 +09:00
parent cb1982a6eb
commit 4064f7cbc0
5 changed files with 48 additions and 46 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -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,
},
},
},