mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-08-24 09:56:29 +00:00
fix(restore): restore single file with [ or ] in name on Windows (#1254)
This commit is contained in:
@@ -454,9 +454,28 @@ func chunkBy[T any](items []T, chunkSize int) (chunks [][]T) {
|
||||
|
||||
var globEscapeReplacer = strings.NewReplacer(`\`, `\\`, `*`, `\*`, `?`, `\?`, `[`, `\[`, `]`, `\]`)
|
||||
|
||||
// On Windows, restic's --include uses Go's path/filepath.Match, which treats
|
||||
// '\' as a path separator (not an escape character) and rejects the POSIX
|
||||
// class self-escape `[[]` / `[]]` as an invalid pattern. There is therefore
|
||||
// no way to express a literal '[' or ']' as a match character on Windows.
|
||||
//
|
||||
// NTFS forbids '*' and '?' in real filenames, so the single-character
|
||||
// wildcard '?' is safe to use as a stand-in for '[' and ']' in a leaf-name
|
||||
// pattern: it can never collide with a sibling whose name is exactly the
|
||||
// requested file. It may, however, match a same-length sibling whose name
|
||||
// differs only in the position of the substituted bracket — typically zero,
|
||||
// occasionally one or two real-world siblings.
|
||||
//
|
||||
// Trade-off: the prior behaviour silently restored zero files when the
|
||||
// requested leaf had a bracket in its name (matches no snapshot path).
|
||||
// With this fix the requested file is restored; same-length siblings may
|
||||
// also land in the target dir. A caller that wants exact-set semantics
|
||||
// should sweep the target after restore against the requested path list.
|
||||
var windowsBracketReplacer = strings.NewReplacer(`[`, `?`, `]`, `?`)
|
||||
|
||||
func escapeGlob(s string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return s // escaping is not supported on Windows
|
||||
return windowsBracketReplacer.Replace(s)
|
||||
}
|
||||
return globEscapeReplacer.Replace(s)
|
||||
}
|
||||
|
||||
@@ -97,10 +97,13 @@ func TestBackup(t *testing.T) {
|
||||
func TestRestore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Use a filepath that exercises a few of the glob characters to test escaping
|
||||
messyFilePathToTestGlobs := "test.txt"
|
||||
if runtime.GOOS != "windows" {
|
||||
messyFilePathToTestGlobs = "test*?[].txt"
|
||||
// Use a filepath that exercises a few of the glob characters to test escaping.
|
||||
// On Windows, '*' and '?' are forbidden in filenames, so the messy name only
|
||||
// contains the brackets — '[' and ']' are legal in NTFS and are the chars
|
||||
// the Windows path of escapeGlob has to handle without backslash escape.
|
||||
messyFilePathToTestGlobs := "test*?[].txt"
|
||||
if runtime.GOOS == "windows" {
|
||||
messyFilePathToTestGlobs = "test[brackets].txt"
|
||||
}
|
||||
|
||||
testFile := path.Join(t.TempDir(), messyFilePathToTestGlobs)
|
||||
@@ -144,10 +147,6 @@ func TestRestore(t *testing.T) {
|
||||
}
|
||||
t.Logf("restore summary: %+v", restoreSummary)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
if restoreSummary.FilesRestored != 1 {
|
||||
t.Errorf("expected 1 new file, got %d", restoreSummary.FilesRestored)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user