diff --git a/internal/restic/restorer.go b/internal/restic/restorer.go index 41c8c6fe5..0424fdb64 100644 --- a/internal/restic/restorer.go +++ b/internal/restic/restorer.go @@ -67,6 +67,7 @@ func (res *Restorer) restoreTo(ctx context.Context, target, location string, tre nodeLocation := filepath.Join(location, nodeName) if target == nodeTarget || !fs.HasPathPrefix(target, nodeTarget) { + debug.Log("target: %v %v", target, nodeTarget) debug.Log("node %q has invalid target path %q", node.Name, nodeTarget) err := res.Error(nodeLocation, node, errors.New("node has invalid path")) if err != nil { @@ -145,6 +146,14 @@ func (res *Restorer) restoreNodeTo(ctx context.Context, node *Node, target, loca // RestoreTo creates the directories and files in the snapshot below dst. // Before an item is created, res.Filter is called. func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { + var err error + if !filepath.IsAbs(dst) { + dst, err = filepath.Abs(dst) + if err != nil { + return errors.Wrap(err, "Abs") + } + } + idx := NewHardlinkIndex() return res.restoreTo(ctx, dst, string(filepath.Separator), *res.sn.Tree, idx) } diff --git a/internal/restic/restorer_test.go b/internal/restic/restorer_test.go index 2f9d18998..43dc4d742 100644 --- a/internal/restic/restorer_test.go +++ b/internal/restic/restorer_test.go @@ -310,3 +310,101 @@ func TestRestorer(t *testing.T) { }) } } + +func chdir(t testing.TB, target string) func() { + prev, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + t.Logf("chdir to %v", target) + err = os.Chdir(target) + if err != nil { + t.Fatal(err) + } + + return func() { + t.Logf("chdir back to %v", prev) + err = os.Chdir(prev) + if err != nil { + t.Fatal(err) + } + } +} + +func TestRestorerRelative(t *testing.T) { + var tests = []struct { + Snapshot + Files map[string]string + }{ + { + Snapshot: Snapshot{ + Nodes: map[string]Node{ + "foo": File{"content: foo\n"}, + "dirtest": Dir{ + Nodes: map[string]Node{ + "file": File{"content: file\n"}, + }, + }, + }, + }, + Files: map[string]string{ + "foo": "content: foo\n", + "dirtest/file": "content: file\n", + }, + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + repo, cleanup := repository.TestRepository(t) + defer cleanup() + + _, id := saveSnapshot(t, repo, test.Snapshot) + t.Logf("snapshot saved as %v", id.Str()) + + res, err := restic.NewRestorer(repo, id) + if err != nil { + t.Fatal(err) + } + + tempdir, cleanup := rtest.TempDir(t) + defer cleanup() + + cleanup = chdir(t, tempdir) + defer cleanup() + + errors := make(map[string]string) + res.Error = func(dir string, node *restic.Node, err error) error { + t.Logf("restore returned error for %q in dir %v: %v", node.Name, dir, err) + dir = toSlash(dir) + errors[dir+"#"+node.Name] = err.Error() + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err = res.RestoreTo(ctx, "restore") + if err != nil { + t.Fatal(err) + } + + for filename, err := range errors { + t.Errorf("unexpected error for %v found: %v", filename, err) + } + + for filename, content := range test.Files { + data, err := ioutil.ReadFile(filepath.Join(tempdir, "restore", filepath.FromSlash(filename))) + if err != nil { + t.Errorf("unable to read file %v: %v", filename, err) + continue + } + + if !bytes.Equal(data, []byte(content)) { + t.Errorf("file %v has wrong content: want %q, got %q", filename, content, data) + } + } + }) + } +}