зеркало из https://github.com/golang/dep.git
Replace uses of filepath.HasPrefix with a path-aware function
internal.HasFilepathPrefix determines whether a given path is contained in another, being careful to take into account that "/foo" doesn't contain "/foobar" and that there are case-sensitive and case-insensitive fileystems out there. This fixes issue #296. Signed-off-by: Marcelo E. Magallon <marcelo.magallon@gmail.com>
This commit is contained in:
Родитель
a28d05c680
Коммит
c409fbd7e3
|
@ -21,9 +21,7 @@ before_script:
|
|||
script:
|
||||
- go build -v ./cmd/dep
|
||||
- go vet $PKGS
|
||||
# Ignore the deprecation warning about filepath.HasPrefix (SA1019). This flag
|
||||
# can be removed when issue #296 is resolved.
|
||||
- staticcheck -ignore='github.com/golang/dep/context.go:SA1019 github.com/golang/dep/cmd/dep/init.go:SA1019' $PKGS
|
||||
- staticcheck $PKGS
|
||||
- gosimple $PKGS
|
||||
- test -z "$(gofmt -s -l . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
|
||||
- go test -race $PKGS
|
||||
|
|
|
@ -44,7 +44,7 @@ func (cmd *initCommand) Register(fs *flag.FlagSet) {}
|
|||
type initCommand struct{}
|
||||
|
||||
func trimPathPrefix(p1, p2 string) string {
|
||||
if filepath.HasPrefix(p1, p2) {
|
||||
if internal.HasFilepathPrefix(p1, p2) {
|
||||
return p1[len(p2):]
|
||||
}
|
||||
return p1
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/Masterminds/vcs"
|
||||
"github.com/golang/dep/internal"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sdboyer/gps"
|
||||
)
|
||||
|
@ -37,7 +38,7 @@ func NewContext() (*Ctx, error) {
|
|||
for _, gp := range filepath.SplitList(buildContext.GOPATH) {
|
||||
gp = filepath.FromSlash(gp)
|
||||
|
||||
if filepath.HasPrefix(wd, gp) {
|
||||
if internal.HasFilepathPrefix(wd, gp) {
|
||||
ctx.GOPATH = gp
|
||||
}
|
||||
|
||||
|
@ -164,7 +165,7 @@ func (c *Ctx) resolveProjectRoot(path string) (string, error) {
|
|||
// Determine if the symlink is within any of the GOPATHs, in which case we're not
|
||||
// sure how to resolve it.
|
||||
for _, gp := range c.GOPATHS {
|
||||
if filepath.HasPrefix(path, gp) {
|
||||
if internal.HasFilepathPrefix(path, gp) {
|
||||
return "", errors.Errorf("'%s' is linked to another path within a GOPATH (%s)", path, gp)
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +180,7 @@ func (c *Ctx) resolveProjectRoot(path string) (string, error) {
|
|||
// The second returned string indicates which GOPATH value was used.
|
||||
func (c *Ctx) SplitAbsoluteProjectRoot(path string) (string, error) {
|
||||
srcprefix := filepath.Join(c.GOPATH, "src") + string(filepath.Separator)
|
||||
if filepath.HasPrefix(path, srcprefix) {
|
||||
if internal.HasFilepathPrefix(path, srcprefix) {
|
||||
// filepath.ToSlash because we're dealing with an import path now,
|
||||
// not an fs path
|
||||
return filepath.ToSlash(path[len(srcprefix):]), nil
|
||||
|
|
14
fs.go
14
fs.go
|
@ -12,6 +12,7 @@ import (
|
|||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/golang/dep/internal"
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -32,18 +33,7 @@ func IsRegular(name string) (bool, error) {
|
|||
}
|
||||
|
||||
func IsDir(name string) (bool, error) {
|
||||
// TODO: lstat?
|
||||
fi, err := os.Stat(name)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return false, errors.Errorf("%q is not a directory", name)
|
||||
}
|
||||
return true, nil
|
||||
return internal.IsDir(name)
|
||||
}
|
||||
|
||||
func IsNonEmptyDir(name string) (bool, error) {
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func IsDir(name string) (bool, error) {
|
||||
// TODO: lstat?
|
||||
fi, err := os.Stat(name)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return false, errors.Errorf("%q is not a directory", name)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// HasFilepathPrefix will determine if "path" starts with "prefix" from
|
||||
// the point of view of a filesystem.
|
||||
//
|
||||
// Unlike filepath.HasPrefix, this function is path-aware, meaning that
|
||||
// it knows that two directories /foo and /foobar are not the same
|
||||
// thing, and therefore HasFilepathPrefix("/foobar", "/foo") will return
|
||||
// false.
|
||||
//
|
||||
// This function also handles the case where the involved filesystems
|
||||
// are case-insensitive, meaning /foo/bar and /Foo/Bar correspond to the
|
||||
// same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo")
|
||||
// will return true. The implementation is *not* OS-specific, so a FAT32
|
||||
// filesystem mounted on Linux will be handled correctly.
|
||||
func HasFilepathPrefix(path, prefix string) bool {
|
||||
if filepath.VolumeName(path) != filepath.VolumeName(prefix) {
|
||||
return false
|
||||
}
|
||||
|
||||
var dn string
|
||||
|
||||
if isDir, err := IsDir(path); err != nil {
|
||||
return false
|
||||
} else if isDir {
|
||||
dn = path
|
||||
} else {
|
||||
dn = filepath.Dir(path)
|
||||
}
|
||||
|
||||
dn = strings.TrimSuffix(dn, string(os.PathSeparator))
|
||||
prefix = strings.TrimSuffix(prefix, string(os.PathSeparator))
|
||||
|
||||
dirs := strings.Split(dn, string(os.PathSeparator))[1:]
|
||||
prefixes := strings.Split(prefix, string(os.PathSeparator))[1:]
|
||||
|
||||
if len(prefixes) > len(dirs) {
|
||||
return false
|
||||
}
|
||||
|
||||
var d, p string
|
||||
|
||||
for i := range prefixes {
|
||||
// need to test each component of the path for
|
||||
// case-sensitiveness because on Unix we could have
|
||||
// something like ext4 filesystem mounted on FAT
|
||||
// mountpoint, mounted on ext4 filesystem, i.e. the
|
||||
// problematic filesystem is not the last one.
|
||||
if isCaseSensitiveFilesystem(filepath.Join(d, dirs[i])) {
|
||||
d = filepath.Join(d, dirs[i])
|
||||
p = filepath.Join(p, prefixes[i])
|
||||
} else {
|
||||
d = filepath.Join(d, strings.ToLower(dirs[i]))
|
||||
p = filepath.Join(p, strings.ToLower(prefixes[i]))
|
||||
}
|
||||
|
||||
if p != d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// genTestFilename returns a string with at most one rune case-flipped.
|
||||
//
|
||||
// The transformation is applied only to the first rune that can be
|
||||
// reversibly case-flipped, meaning:
|
||||
//
|
||||
// * A lowercase rune for which it's true that lower(upper(r)) == r
|
||||
// * An uppercase rune for which it's true that upper(lower(r)) == r
|
||||
//
|
||||
// All the other runes are left intact.
|
||||
func genTestFilename(str string) string {
|
||||
flip := true
|
||||
return strings.Map(func(r rune) rune {
|
||||
if flip {
|
||||
if unicode.IsLower(r) {
|
||||
u := unicode.ToUpper(r)
|
||||
if unicode.ToLower(u) == r {
|
||||
r = u
|
||||
flip = false
|
||||
}
|
||||
} else if unicode.IsUpper(r) {
|
||||
l := unicode.ToLower(r)
|
||||
if unicode.ToUpper(l) == r {
|
||||
r = l
|
||||
flip = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}, str)
|
||||
}
|
||||
|
||||
// isCaseSensitiveFilesystem determines if the filesystem where dir
|
||||
// exists is case sensitive or not.
|
||||
//
|
||||
// CAVEAT: this function works by taking the last component of the given
|
||||
// path and flipping the case of the first letter for which case
|
||||
// flipping is a reversible operation (/foo/Bar → /foo/bar), then
|
||||
// testing for the existence of the new filename. There are two
|
||||
// possibilities:
|
||||
//
|
||||
// 1. The alternate filename does not exist. We can conclude that the
|
||||
// filesystem is case sensitive.
|
||||
//
|
||||
// 2. The filename happens to exist. We have to test if the two files
|
||||
// are the same file (case insensitive file system) or different ones
|
||||
// (case sensitive filesystem).
|
||||
//
|
||||
// If the input directory is such that the last component is composed
|
||||
// exclusively of case-less codepoints (e.g. numbers), this function will
|
||||
// return false.
|
||||
func isCaseSensitiveFilesystem(dir string) bool {
|
||||
alt := filepath.Join(filepath.Dir(dir),
|
||||
genTestFilename(filepath.Base(dir)))
|
||||
|
||||
dInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
aInfo, err := os.Stat(alt)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return !os.SameFile(dInfo, aInfo)
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHasFilepathPrefix(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "dep")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
cases := []struct {
|
||||
dir string
|
||||
prefix string
|
||||
want bool
|
||||
}{
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir), true},
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir, "a"), true},
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir, "a", "b"), true},
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir, "c"), false},
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir, "a", "d", "b"), false},
|
||||
{filepath.Join(dir, "a", "b"), filepath.Join(dir, "a", "b2"), false},
|
||||
{filepath.Join(dir, "ab"), filepath.Join(dir, "a", "b"), false},
|
||||
{filepath.Join(dir, "ab"), filepath.Join(dir, "a"), false},
|
||||
{filepath.Join(dir, "123"), filepath.Join(dir, "123"), true},
|
||||
{filepath.Join(dir, "123"), filepath.Join(dir, "1"), false},
|
||||
{filepath.Join(dir, "⌘"), filepath.Join(dir, "⌘"), true},
|
||||
{filepath.Join(dir, "a"), filepath.Join(dir, "⌘"), false},
|
||||
{filepath.Join(dir, "⌘"), filepath.Join(dir, "a"), false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
err := os.MkdirAll(c.dir, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(c.prefix, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := HasFilepathPrefix(c.dir, c.prefix)
|
||||
if c.want != got {
|
||||
t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.dir, c.prefix, c.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenTestFilename(t *testing.T) {
|
||||
cases := []struct {
|
||||
str string
|
||||
want string
|
||||
}{
|
||||
{"abc", "Abc"},
|
||||
{"ABC", "aBC"},
|
||||
{"AbC", "abC"},
|
||||
{"αβγ", "Αβγ"},
|
||||
{"123", "123"},
|
||||
{"1a2", "1A2"},
|
||||
{"12a", "12A"},
|
||||
{"⌘", "⌘"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := genTestFilename(c.str)
|
||||
if c.want != got {
|
||||
t.Fatalf("str: %q, expected: %q, got: %q", c.str, c.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenTestFilename(b *testing.B) {
|
||||
cases := []string{
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"αααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααα",
|
||||
"11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||
"⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘⌘",
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, str := range cases {
|
||||
genTestFilename(str)
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче