forked from TrueCloudLab/restic
Improve recover
- only save directories not referenced by any snapshot - dont't write empty snapshot - use progress bar
This commit is contained in:
parent
ec2e3b260e
commit
bce87922c0
2 changed files with 46 additions and 33 deletions
7
changelog/unreleased/pull-2880
Normal file
7
changelog/unreleased/pull-2880
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Enhancement: Improve recover command
|
||||||
|
|
||||||
|
Restic recover used to generate a snapshot that contains all root trees
|
||||||
|
even those which are already referenced by a snapshot.
|
||||||
|
It now only processes trees not referenced at all.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/pull/2880
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -11,11 +12,11 @@ import (
|
||||||
|
|
||||||
var cmdRecover = &cobra.Command{
|
var cmdRecover = &cobra.Command{
|
||||||
Use: "recover [flags]",
|
Use: "recover [flags]",
|
||||||
Short: "Recover data from the repository",
|
Short: "Recover data from the repository not referenced by snapshots",
|
||||||
Long: `
|
Long: `
|
||||||
The "recover" command builds a new snapshot from all directories it can find in
|
The "recover" command builds a new snapshot from all directories it can find in
|
||||||
the raw data of the repository. It can be used if, for example, a snapshot has
|
the raw data of the repository which are not referenced in an existing snapshot.
|
||||||
been removed by accident with "forget".
|
It can be used if, for example, a snapshot has been removed by accident with "forget".
|
||||||
|
|
||||||
EXIT STATUS
|
EXIT STATUS
|
||||||
===========
|
===========
|
||||||
|
@ -59,24 +60,14 @@ func runRecover(gopts GlobalOptions) error {
|
||||||
trees := make(map[restic.ID]bool)
|
trees := make(map[restic.ID]bool)
|
||||||
|
|
||||||
for blob := range repo.Index().Each(gopts.ctx) {
|
for blob := range repo.Index().Each(gopts.ctx) {
|
||||||
if blob.Blob.Type != restic.TreeBlob {
|
if blob.Type == restic.TreeBlob {
|
||||||
continue
|
trees[blob.Blob.ID] = false
|
||||||
}
|
}
|
||||||
trees[blob.Blob.ID] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur := 0
|
Verbosef("load %d trees\n", len(trees))
|
||||||
max := len(trees)
|
bar := newProgressMax(!gopts.Quiet, uint64(len(trees)), "trees loaded")
|
||||||
Verbosef("load %d trees\n\n", len(trees))
|
|
||||||
|
|
||||||
for id := range trees {
|
for id := range trees {
|
||||||
cur++
|
|
||||||
Verbosef("\rtree (%v/%v)", cur, max)
|
|
||||||
|
|
||||||
if !trees[id] {
|
|
||||||
trees[id] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
tree, err := repo.LoadTree(gopts.ctx, id)
|
tree, err := repo.LoadTree(gopts.ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("unable to load tree %v: %v\n", id.Str(), err)
|
Warnf("unable to load tree %v: %v\n", id.Str(), err)
|
||||||
|
@ -84,26 +75,37 @@ func runRecover(gopts GlobalOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
if node.Type != "dir" || node.Subtree == nil {
|
if node.Type == "dir" && node.Subtree != nil {
|
||||||
continue
|
trees[*node.Subtree] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
subtree := *node.Subtree
|
|
||||||
trees[subtree] = true
|
|
||||||
}
|
}
|
||||||
|
bar.Add(1)
|
||||||
}
|
}
|
||||||
Verbosef("\ndone\n")
|
bar.Done()
|
||||||
|
|
||||||
|
Verbosef("load snapshots\n")
|
||||||
|
err = restic.ForAllSnapshots(gopts.ctx, repo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
|
||||||
|
trees[*sn.Tree] = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Verbosef("done\n")
|
||||||
|
|
||||||
roots := restic.NewIDSet()
|
roots := restic.NewIDSet()
|
||||||
for id, seen := range trees {
|
for id, seen := range trees {
|
||||||
if seen {
|
if !seen {
|
||||||
continue
|
Verboseff("found root tree %v\n", id.Str())
|
||||||
|
roots.Insert(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
roots.Insert(id)
|
|
||||||
}
|
}
|
||||||
|
Printf("\nfound %d unreferenced roots\n", len(roots))
|
||||||
|
|
||||||
Verbosef("found %d roots\n", len(roots))
|
if len(roots) == 0 {
|
||||||
|
Verbosef("no snapshot to write.\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
tree := restic.NewTree()
|
tree := restic.NewTree()
|
||||||
for id := range roots {
|
for id := range roots {
|
||||||
|
@ -117,7 +119,7 @@ func runRecover(gopts GlobalOptions) error {
|
||||||
ModTime: time.Now(),
|
ModTime: time.Now(),
|
||||||
ChangeTime: time.Now(),
|
ChangeTime: time.Now(),
|
||||||
}
|
}
|
||||||
err = tree.Insert(&node)
|
err := tree.Insert(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -133,19 +135,23 @@ func runRecover(gopts GlobalOptions) error {
|
||||||
return errors.Fatalf("unable to save blobs to the repo: %v", err)
|
return errors.Fatalf("unable to save blobs to the repo: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sn, err := restic.NewSnapshot([]string{"/recover"}, []string{}, hostname, time.Now())
|
return createSnapshot(gopts.ctx, "/recover", hostname, []string{"recovered"}, repo, &treeID)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSnapshot(ctx context.Context, name, hostname string, tags []string, repo restic.Repository, tree *restic.ID) error {
|
||||||
|
sn, err := restic.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree = &treeID
|
sn.Tree = tree
|
||||||
|
|
||||||
id, err := repo.SaveJSONUnpacked(gopts.ctx, restic.SnapshotFile, sn)
|
id, err := repo.SaveJSONUnpacked(ctx, restic.SnapshotFile, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
Printf("saved new snapshot %v\n", id.Str())
|
Printf("saved new snapshot %v\n", id.Str())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue