mirror of
https://github.com/octoleo/restic.git
synced 2024-11-30 00:33:57 +00:00
better error handling and correct nil tree behavior
This commit is contained in:
parent
08ae708b3b
commit
d23a2e1925
@ -84,8 +84,7 @@ func runRepair(opts RepairOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lock, err := lockRepoExclusive(globalOptions.ctx, repo)
|
lock, err := lockRepoExclusive(globalOptions.ctx, repo)
|
||||||
// to make linter happy, as unlockRepo returns an error (which is ignored)
|
defer unlockRepo(lock)
|
||||||
defer func() { _ = unlockRepo(lock) }()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -115,9 +114,11 @@ func repairSnapshots(opts RepairOptions, repo restic.Repository, snapshots []*re
|
|||||||
for _, sn := range snapshots {
|
for _, sn := range snapshots {
|
||||||
debug.Log("process snapshot %v", sn.ID())
|
debug.Log("process snapshot %v", sn.ID())
|
||||||
Printf("%v:\n", sn)
|
Printf("%v:\n", sn)
|
||||||
newID, changed, err := repairTree(opts, repo, "/", *sn.Tree, replaces, seen)
|
newID, changed, lErr, err := repairTree(opts, repo, "/", sn.Tree, replaces, seen)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case lErr:
|
||||||
Printf("the root tree is damaged -> delete snapshot.\n")
|
Printf("the root tree is damaged -> delete snapshot.\n")
|
||||||
deleteSn.Insert(*sn.ID())
|
deleteSn.Insert(*sn.ID())
|
||||||
case changed:
|
case changed:
|
||||||
@ -153,13 +154,13 @@ func repairSnapshots(opts RepairOptions, repo restic.Repository, snapshots []*re
|
|||||||
// - add the rag opts.AddTag
|
// - add the rag opts.AddTag
|
||||||
// - preserve original ID
|
// - preserve original ID
|
||||||
// if opts.DryRun is set, it doesn't change anything but only
|
// if opts.DryRun is set, it doesn't change anything but only
|
||||||
func changeSnapshot(opts RepairOptions, repo restic.Repository, sn *restic.Snapshot, newID restic.ID) error {
|
func changeSnapshot(opts RepairOptions, repo restic.Repository, sn *restic.Snapshot, newID *restic.ID) error {
|
||||||
sn.AddTags([]string{opts.AddTag})
|
sn.AddTags([]string{opts.AddTag})
|
||||||
// Retain the original snapshot id over all tag changes.
|
// Retain the original snapshot id over all tag changes.
|
||||||
if sn.Original == nil {
|
if sn.Original == nil {
|
||||||
sn.Original = sn.ID()
|
sn.Original = sn.ID()
|
||||||
}
|
}
|
||||||
sn.Tree = &newID
|
sn.Tree = newID
|
||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
newID, err := repo.SaveJSONUnpacked(globalOptions.ctx, restic.SnapshotFile, sn)
|
newID, err := repo.SaveJSONUnpacked(globalOptions.ctx, restic.SnapshotFile, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,29 +176,43 @@ func changeSnapshot(opts RepairOptions, repo restic.Repository, sn *restic.Snaps
|
|||||||
type idMap map[restic.ID]restic.ID
|
type idMap map[restic.ID]restic.ID
|
||||||
|
|
||||||
// repairTree checks and repairs a tree and all its subtrees
|
// repairTree checks and repairs a tree and all its subtrees
|
||||||
// Two error cases are checked:
|
// Three error cases are checked:
|
||||||
|
// - tree is a nil tree (-> will be replaced by an empty tree)
|
||||||
// - trees which cannot be loaded (-> the tree contents will be removed)
|
// - trees which cannot be loaded (-> the tree contents will be removed)
|
||||||
// - files whose contents are not fully available (-> file will be modified)
|
// - files whose contents are not fully available (-> file will be modified)
|
||||||
// In case of an error, the changes made depends on:
|
// In case of an error, the changes made depends on:
|
||||||
// - opts.Append: string to append to "repared" names; if empty files will not repaired but deleted
|
// - opts.Append: string to append to "repared" names; if empty files will not repaired but deleted
|
||||||
// - opts.DryRun: if set to true, only print out what to but don't change anything
|
// - opts.DryRun: if set to true, only print out what to but don't change anything
|
||||||
func repairTree(opts RepairOptions, repo restic.Repository, path string, treeID restic.ID, replaces idMap, seen restic.IDSet) (restic.ID, bool, error) {
|
// Returns:
|
||||||
|
// - the new ID
|
||||||
|
// - whether the ID changed
|
||||||
|
// - whether there was a load error when loading this tre
|
||||||
|
// - error for other errors (these are errors when saving a tree)
|
||||||
|
func repairTree(opts RepairOptions, repo restic.Repository, path string, treeID *restic.ID, replaces idMap, seen restic.IDSet) (*restic.ID, bool, bool, error) {
|
||||||
ctx := globalOptions.ctx
|
ctx := globalOptions.ctx
|
||||||
|
|
||||||
|
// handle and repair nil trees
|
||||||
|
if treeID == nil {
|
||||||
|
empty, err := emptyTree(opts.DryRun, repo)
|
||||||
|
Printf("repaired nil tree '%v'\n", path)
|
||||||
|
return &empty, true, false, err
|
||||||
|
}
|
||||||
|
|
||||||
// check if tree was already changed
|
// check if tree was already changed
|
||||||
newID, ok := replaces[treeID]
|
newID, ok := replaces[*treeID]
|
||||||
if ok {
|
if ok {
|
||||||
return newID, true, nil
|
return &newID, true, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if tree was seen but not changed
|
// check if tree was seen but not changed
|
||||||
if seen.Has(treeID) {
|
if seen.Has(*treeID) {
|
||||||
return treeID, false, nil
|
return treeID, false, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := repo.LoadTree(ctx, treeID)
|
tree, err := repo.LoadTree(ctx, *treeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newID, false, err
|
// mark as load error
|
||||||
|
return &newID, false, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var newNodes []*restic.Node
|
var newNodes []*restic.Node
|
||||||
@ -231,17 +246,23 @@ func repairTree(opts RepairOptions, repo restic.Repository, path string, treeID
|
|||||||
}
|
}
|
||||||
case "dir":
|
case "dir":
|
||||||
// rewrite if necessary
|
// rewrite if necessary
|
||||||
newID, c, err := repairTree(opts, repo, path+node.Name+"/", *node.Subtree, replaces, seen)
|
newID, c, lErr, err := repairTree(opts, repo, path+node.Name+"/", node.Subtree, replaces, seen)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
return newID, true, false, err
|
||||||
|
case lErr:
|
||||||
// If we get an error, we remove this subtree
|
// If we get an error, we remove this subtree
|
||||||
changed = true
|
changed = true
|
||||||
Printf("removed defect dir '%v'", path+node.Name)
|
Printf("removed defect dir '%v'", path+node.Name)
|
||||||
node.Name = node.Name + opts.Append
|
node.Name = node.Name + opts.Append
|
||||||
Printf("(now emtpy '%v')\n", node.Name)
|
Printf("(now emtpy '%v')\n", node.Name)
|
||||||
node.Subtree = nil
|
empty, err := emptyTree(opts.DryRun, repo)
|
||||||
|
if err != nil {
|
||||||
|
return newID, true, false, err
|
||||||
|
}
|
||||||
|
node.Subtree = &empty
|
||||||
case c:
|
case c:
|
||||||
node.Subtree = &newID
|
node.Subtree = newID
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,8 +270,8 @@ func repairTree(opts RepairOptions, repo restic.Repository, path string, treeID
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !changed {
|
if !changed {
|
||||||
seen.Insert(treeID)
|
seen.Insert(*treeID)
|
||||||
return treeID, false, nil
|
return treeID, false, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tree.Nodes = newNodes
|
tree.Nodes = newNodes
|
||||||
@ -258,13 +279,22 @@ func repairTree(opts RepairOptions, repo restic.Repository, path string, treeID
|
|||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
newID, err = repo.SaveTree(ctx, tree)
|
newID, err = repo.SaveTree(ctx, tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newID, false, err
|
return &newID, true, false, err
|
||||||
}
|
}
|
||||||
Printf("modified tree %v, new id: %v\n", treeID.Str(), newID.Str())
|
Printf("modified tree %v, new id: %v\n", treeID.Str(), newID.Str())
|
||||||
} else {
|
} else {
|
||||||
Printf("would have modified tree %v\n", treeID.Str())
|
Printf("would have modified tree %v\n", treeID.Str())
|
||||||
}
|
}
|
||||||
|
|
||||||
replaces[treeID] = newID
|
replaces[*treeID] = newID
|
||||||
return newID, true, nil
|
return &newID, true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyTree(dryRun bool, repo restic.Repository) (restic.ID, error) {
|
||||||
|
ctx := globalOptions.ctx
|
||||||
|
var tree restic.Tree
|
||||||
|
if !dryRun {
|
||||||
|
return repo.SaveTree(ctx, &tree)
|
||||||
|
}
|
||||||
|
return restic.ID{}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user