Return helpful error if subfolder syntax fails on Windows (#21813)

This commit is contained in:
Michael Eischer
2026-05-20 22:55:01 +02:00
committed by GitHub
parent dd4f1f06a1
commit f000da3b35
9 changed files with 33 additions and 11 deletions
+1 -1
View File
@@ -707,7 +707,7 @@ restic users. The changes are ordered by importance.
correctly.
Specifying volume names is now handled correctly. To restore snapshots created
before this bugfix, use the <snapshot>:<subpath> syntax. For example, to restore
before this bugfix, use the <snapshot>:<subfolder> syntax. For example, to restore
a snapshot with ID `12345678` that backed up `C:`, use the following command:
```
+1 -1
View File
@@ -6,7 +6,7 @@ resulting snapshot would result in an error. Note that using `C:\`
as backup target worked correctly.
Specifying volume names is now handled correctly. To restore snapshots
created before this bugfix, use the <snapshot>:<subpath> syntax. For
created before this bugfix, use the <snapshot>:<subfolder> syntax. For
example, to restore a snapshot with ID `12345678` that backed up `C:`,
use the following command:
+1 -1
View File
@@ -39,7 +39,7 @@ Metadata comparison will likely not work if a backup was created using the
To only compare files in specific subfolders, you can use the
"snapshotID:subfolder" syntax, where "subfolder" is a path within the
snapshot.
snapshot tree as shown by "restic ls".
EXIT STATUS
===========
+1 -1
View File
@@ -35,7 +35,7 @@ repository.
To include the folder content at the root of the archive, you can use the
"snapshotID:subfolder" syntax, where "subfolder" is a path within the
snapshot.
snapshot tree as shown by "restic ls".
EXIT STATUS
===========
+2 -1
View File
@@ -34,7 +34,8 @@ The special snapshotID "latest" can be used to restore the latest snapshot in th
repository.
To only restore a specific subfolder, you can use the "snapshotID:subfolder"
syntax, where "subfolder" is a path within the snapshot.
syntax, where "subfolder" is a path within the snapshot tree as shown by
"restic ls".
POSIX ACLs are always restored by their numeric value, while file ownership can optionally be restored by name instead of numeric value.
+2 -2
View File
@@ -609,8 +609,8 @@ and displays a small statistic, just pass the command two snapshot IDs:
To only compare files in specific subfolders, you can use the ``<snapshot>:<subfolder>``
syntax, where ``snapshot`` is the ID of a snapshot (or the string ``latest``) and ``subfolder``
is a path within the snapshot. For example, to only compare files in the ``/restic``
folder, you could use the following command:
is a path within the snapshot tree as shown by ``restic ls``. For example, to only compare files in
the ``/restic`` folder, you could use the following command:
.. code-block:: console
+4 -4
View File
@@ -54,10 +54,10 @@ This will restore the file to ``/tmp/restore/home/user/work/foo``.
To only restore a specific subfolder, you can use the ``<snapshot>:<subfolder>``
syntax, where ``snapshot`` is the ID of a snapshot (or the string ``latest``)
and ``subfolder`` is a path within the snapshot. Note that the subfolder syntax
also affects options like ``--include`` and ``--exclude``, such that their
arguments should be specified relative to ``subfolder`` (e.g. ``/foo`` instead
of ``/home/user/work/foo``).
and ``subfolder`` is a path within the snapshot tree as shown by ``restic ls``.
Note that the subfolder syntax also affects options like ``--include`` and
``--exclude``, such that their arguments should be specified relative to
``subfolder`` (e.g. ``/foo`` instead of ``/home/user/work/foo``).
.. code-block:: console
+4
View File
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"runtime"
"io"
"iter"
@@ -311,6 +312,9 @@ func FindTreeDirectory(ctx context.Context, repo restic.BlobLoader, id *restic.I
return nil, fmt.Errorf("path %s: %w", subfolder, err)
}
if node == nil {
if runtime.GOOS == "windows" && strings.Contains(dir, "\\") {
return nil, fmt.Errorf("path %s: not found; subfolder syntax currently requires forward slashes; check the output of `restic ls` for valid paths", subfolder)
}
return nil, fmt.Errorf("path %s: not found", subfolder)
}
if node.Type != NodeTypeDir || node.Subtree == nil {
+17
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
@@ -401,6 +402,22 @@ func TestFindTreeDirectory(t *testing.T) {
rtest.Assert(t, err != nil, "missing error on null tree id")
}
func TestFindTreeDirectoryWindowsBackslashHint(t *testing.T) {
repo := repository.TestRepository(t)
sn := data.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:08"), 1)
_, err := data.FindTreeDirectory(context.TODO(), repo, sn.Tree, `missing\path`)
rtest.Assert(t, err != nil, "expected error")
if runtime.GOOS == "windows" {
rtest.Assert(t, strings.Contains(err.Error(), "forward slashes"),
"expected backslash hint on Windows, got %v", err)
} else {
rtest.Assert(t, err.Error() == `path missing\path: not found`,
"unexpected err: %v", err)
}
}
func TestDualTreeIterator(t *testing.T) {
testErr := errors.New("test error")