From 99d29d5a43da0efde2ed9a137627d0d310e3baad Mon Sep 17 00:00:00 2001 From: Alex Brainman Date: Thu, 19 May 2016 15:58:40 +1000 Subject: [PATCH] path/filepath: fix globbing of c:\*dir\... pattern The problem was introduced by the recent filepath.Join change. Fixes #14949 Change-Id: I7ee52f210e12bbb1369e308e584ddb2c7766e095 Reviewed-on: https://go-review.googlesource.com/23240 TryBot-Result: Gobot Gobot Reviewed-by: Russ Cox --- src/path/filepath/match.go | 40 ++++++-- src/path/filepath/match_test.go | 163 ++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 7 deletions(-) diff --git a/src/path/filepath/match.go b/src/path/filepath/match.go index d64bf84fc0..2adb0c7490 100644 --- a/src/path/filepath/match.go +++ b/src/path/filepath/match.go @@ -240,13 +240,10 @@ func Glob(pattern string) (matches []string, err error) { } dir, file := Split(pattern) - switch dir { - case "": - dir = "." - case string(Separator): - // nothing - default: - dir = dir[0 : len(dir)-1] // chop off trailing separator + if runtime.GOOS == "windows" { + dir = cleanGlobPathWindows(dir) + } else { + dir = cleanGlobPath(dir) } if !hasMeta(dir) { @@ -267,6 +264,35 @@ func Glob(pattern string) (matches []string, err error) { return } +// cleanGlobPath prepares path for glob matching. +func cleanGlobPath(path string) string { + switch path { + case "": + return "." + case string(Separator): + // do nothing to the path + return path + default: + return path[0 : len(path)-1] // chop off trailing separator + } +} + +// cleanGlobPathWindows is windows version of cleanGlobPath. +func cleanGlobPathWindows(path string) string { + vollen := volumeNameLen(path) + switch { + case path == "": + return "." + case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/ + // do nothing to the path + return path + case vollen == len(path) && len(path) == 2: // C: + return path + "." // convert C: into C:. + default: + return path[0 : len(path)-1] // chop off trailing separator + } +} + // glob searches for files matching pattern in the directory dir // and appends them to matches. If the directory cannot be // opened, it returns the existing matches. New matches are diff --git a/src/path/filepath/match_test.go b/src/path/filepath/match_test.go index d8bab7f4da..8dcfa5972e 100644 --- a/src/path/filepath/match_test.go +++ b/src/path/filepath/match_test.go @@ -5,10 +5,12 @@ package filepath_test import ( + "fmt" "io/ioutil" "os" . "path/filepath" "runtime" + "sort" "strings" "testing" ) @@ -209,3 +211,164 @@ func TestGlobSymlink(t *testing.T) { } } } + +type globTest struct { + pattern string + matches []string +} + +func (test *globTest) buildWant(root string) []string { + want := make([]string, 0) + for _, m := range test.matches { + want = append(want, root+FromSlash(m)) + } + sort.Strings(want) + return want +} + +func (test *globTest) globAbs(root, rootPattern string) error { + p := FromSlash(rootPattern + `\` + test.pattern) + have, err := Glob(p) + if err != nil { + return err + } + sort.Strings(have) + want := test.buildWant(root + `\`) + if strings.Join(want, "_") == strings.Join(have, "_") { + return nil + } + return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want) +} + +func (test *globTest) globRel(root string) error { + p := root + FromSlash(test.pattern) + have, err := Glob(p) + if err != nil { + return err + } + sort.Strings(have) + want := test.buildWant(root) + if strings.Join(want, "_") == strings.Join(have, "_") { + return nil + } + // try also matching version without root prefix + wantWithNoRoot := test.buildWant("") + if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") { + return nil + } + return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want) +} + +func TestWindowsGlob(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skipf("skipping windows specific test") + } + + tmpDir, err := ioutil.TempDir("", "TestWindowsGlob") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // /tmp may itself be a symlink + tmpDir, err = EvalSymlinks(tmpDir) + if err != nil { + t.Fatal("eval symlink for tmp dir:", err) + } + + if len(tmpDir) < 3 { + t.Fatalf("tmpDir path %q is too short", tmpDir) + } + if tmpDir[1] != ':' { + t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir) + } + + dirs := []string{ + "a", + "b", + "dir/d/bin", + } + files := []string{ + "dir/d/bin/git.exe", + } + for _, dir := range dirs { + err := os.MkdirAll(Join(tmpDir, dir), 0777) + if err != nil { + t.Fatal(err) + } + } + for _, file := range files { + err := ioutil.WriteFile(Join(tmpDir, file), nil, 0666) + if err != nil { + t.Fatal(err) + } + } + + tests := []globTest{ + {"a", []string{"a"}}, + {"b", []string{"b"}}, + {"c", []string{}}, + {"*", []string{"a", "b", "dir"}}, + {"d*", []string{"dir"}}, + {"*i*", []string{"dir"}}, + {"*r", []string{"dir"}}, + {"?ir", []string{"dir"}}, + {"?r", []string{}}, + {"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}}, + } + + // test absolute paths + for _, test := range tests { + var p string + err = test.globAbs(tmpDir, tmpDir) + if err != nil { + t.Error(err) + } + // test C:\*Documents and Settings\... + p = tmpDir + p = strings.Replace(p, `:\`, `:\*`, 1) + err = test.globAbs(tmpDir, p) + if err != nil { + t.Error(err) + } + // test C:\Documents and Settings*\... + p = tmpDir + p = strings.Replace(p, `:\`, `:`, 1) + p = strings.Replace(p, `\`, `*\`, 1) + p = strings.Replace(p, `:`, `:\`, 1) + err = test.globAbs(tmpDir, p) + if err != nil { + t.Error(err) + } + } + + // test relative paths + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + err = os.Chdir(tmpDir) + if err != nil { + t.Fatal(err) + } + defer func() { + err := os.Chdir(wd) + if err != nil { + t.Fatal(err) + } + }() + for _, test := range tests { + err := test.globRel("") + if err != nil { + t.Error(err) + } + err = test.globRel(`.\`) + if err != nil { + t.Error(err) + } + err = test.globRel(tmpDir[:2]) // C: + if err != nil { + t.Error(err) + } + } +} -- 2.48.1