Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/search/search.go

Documentation: cmd/go/internal/search

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package search
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"cmd/go/internal/fsys"
    11  	"fmt"
    12  	"go/build"
    13  	"io/fs"
    14  	"os"
    15  	"path"
    16  	"path/filepath"
    17  	"regexp"
    18  	"strings"
    19  )
    20  
    21  // A Match represents the result of matching a single package pattern.
    22  type Match struct {
    23  	pattern string   // the pattern itself
    24  	Dirs    []string // if the pattern is local, directories that potentially contain matching packages
    25  	Pkgs    []string // matching packages (import paths)
    26  	Errs    []error  // errors matching the patterns to packages, NOT errors loading those packages
    27  
    28  	// Errs may be non-empty even if len(Pkgs) > 0, indicating that some matching
    29  	// packages could be located but results may be incomplete.
    30  	// If len(Pkgs) == 0 && len(Errs) == 0, the pattern is well-formed but did not
    31  	// match any packages.
    32  }
    33  
    34  // NewMatch returns a Match describing the given pattern,
    35  // without resolving its packages or errors.
    36  func NewMatch(pattern string) *Match {
    37  	return &Match{pattern: pattern}
    38  }
    39  
    40  // Pattern returns the pattern to be matched.
    41  func (m *Match) Pattern() string { return m.pattern }
    42  
    43  // AddError appends a MatchError wrapping err to m.Errs.
    44  func (m *Match) AddError(err error) {
    45  	m.Errs = append(m.Errs, &MatchError{Match: m, Err: err})
    46  }
    47  
    48  // Literal reports whether the pattern is free of wildcards and meta-patterns.
    49  //
    50  // A literal pattern must match at most one package.
    51  func (m *Match) IsLiteral() bool {
    52  	return !strings.Contains(m.pattern, "...") && !m.IsMeta()
    53  }
    54  
    55  // Local reports whether the pattern must be resolved from a specific root or
    56  // directory, such as a filesystem path or a single module.
    57  func (m *Match) IsLocal() bool {
    58  	return build.IsLocalImport(m.pattern) || filepath.IsAbs(m.pattern)
    59  }
    60  
    61  // Meta reports whether the pattern is a “meta-package” keyword that represents
    62  // multiple packages, such as "std", "cmd", or "all".
    63  func (m *Match) IsMeta() bool {
    64  	return IsMetaPackage(m.pattern)
    65  }
    66  
    67  // IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
    68  func IsMetaPackage(name string) bool {
    69  	return name == "std" || name == "cmd" || name == "all"
    70  }
    71  
    72  // A MatchError indicates an error that occurred while attempting to match a
    73  // pattern.
    74  type MatchError struct {
    75  	Match *Match
    76  	Err   error
    77  }
    78  
    79  func (e *MatchError) Error() string {
    80  	if e.Match.IsLiteral() {
    81  		return fmt.Sprintf("%s: %v", e.Match.Pattern(), e.Err)
    82  	}
    83  	return fmt.Sprintf("pattern %s: %v", e.Match.Pattern(), e.Err)
    84  }
    85  
    86  func (e *MatchError) Unwrap() error {
    87  	return e.Err
    88  }
    89  
    90  // MatchPackages sets m.Pkgs to a non-nil slice containing all the packages that
    91  // can be found under the $GOPATH directories and $GOROOT that match the
    92  // pattern. The pattern must be either "all" (all packages), "std" (standard
    93  // packages), "cmd" (standard commands), or a path including "...".
    94  //
    95  // If any errors may have caused the set of packages to be incomplete,
    96  // MatchPackages appends those errors to m.Errs.
    97  func (m *Match) MatchPackages() {
    98  	m.Pkgs = []string{}
    99  	if m.IsLocal() {
   100  		m.AddError(fmt.Errorf("internal error: MatchPackages: %s is not a valid package pattern", m.pattern))
   101  		return
   102  	}
   103  
   104  	if m.IsLiteral() {
   105  		m.Pkgs = []string{m.pattern}
   106  		return
   107  	}
   108  
   109  	match := func(string) bool { return true }
   110  	treeCanMatch := func(string) bool { return true }
   111  	if !m.IsMeta() {
   112  		match = MatchPattern(m.pattern)
   113  		treeCanMatch = TreeCanMatchPattern(m.pattern)
   114  	}
   115  
   116  	have := map[string]bool{
   117  		"builtin": true, // ignore pseudo-package that exists only for documentation
   118  	}
   119  	if !cfg.BuildContext.CgoEnabled {
   120  		have["runtime/cgo"] = true // ignore during walk
   121  	}
   122  
   123  	for _, src := range cfg.BuildContext.SrcDirs() {
   124  		if (m.pattern == "std" || m.pattern == "cmd") && src != cfg.GOROOTsrc {
   125  			continue
   126  		}
   127  		src = filepath.Clean(src) + string(filepath.Separator)
   128  		root := src
   129  		if m.pattern == "cmd" {
   130  			root += "cmd" + string(filepath.Separator)
   131  		}
   132  		err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
   133  			if err != nil {
   134  				return err // Likely a permission error, which could interfere with matching.
   135  			}
   136  			if path == src {
   137  				return nil // GOROOT/src and GOPATH/src cannot contain packages.
   138  			}
   139  
   140  			want := true
   141  			// Avoid .foo, _foo, and testdata directory trees.
   142  			_, elem := filepath.Split(path)
   143  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   144  				want = false
   145  			}
   146  
   147  			name := filepath.ToSlash(path[len(src):])
   148  			if m.pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
   149  				// The name "std" is only the standard library.
   150  				// If the name is cmd, it's the root of the command tree.
   151  				want = false
   152  			}
   153  			if !treeCanMatch(name) {
   154  				want = false
   155  			}
   156  
   157  			if !fi.IsDir() {
   158  				if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.pattern, "...") {
   159  					if target, err := fsys.Stat(path); err == nil && target.IsDir() {
   160  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
   161  					}
   162  				}
   163  				return nil
   164  			}
   165  			if !want {
   166  				return filepath.SkipDir
   167  			}
   168  
   169  			if have[name] {
   170  				return nil
   171  			}
   172  			have[name] = true
   173  			if !match(name) {
   174  				return nil
   175  			}
   176  			pkg, err := cfg.BuildContext.ImportDir(path, 0)
   177  			if err != nil {
   178  				if _, noGo := err.(*build.NoGoError); noGo {
   179  					// The package does not actually exist, so record neither the package
   180  					// nor the error.
   181  					return nil
   182  				}
   183  				// There was an error importing path, but not matching it,
   184  				// which is all that Match promises to do.
   185  				// Ignore the import error.
   186  			}
   187  
   188  			// If we are expanding "cmd", skip main
   189  			// packages under cmd/vendor. At least as of
   190  			// March, 2017, there is one there for the
   191  			// vendored pprof tool.
   192  			if m.pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
   193  				return nil
   194  			}
   195  
   196  			m.Pkgs = append(m.Pkgs, name)
   197  			return nil
   198  		})
   199  		if err != nil {
   200  			m.AddError(err)
   201  		}
   202  	}
   203  }
   204  
   205  var modRoot string
   206  
   207  func SetModRoot(dir string) {
   208  	modRoot = dir
   209  }
   210  
   211  // MatchDirs sets m.Dirs to a non-nil slice containing all directories that
   212  // potentially match a local pattern. The pattern must begin with an absolute
   213  // path, or "./", or "../". On Windows, the pattern may use slash or backslash
   214  // separators or a mix of both.
   215  //
   216  // If any errors may have caused the set of directories to be incomplete,
   217  // MatchDirs appends those errors to m.Errs.
   218  func (m *Match) MatchDirs() {
   219  	m.Dirs = []string{}
   220  	if !m.IsLocal() {
   221  		m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
   222  		return
   223  	}
   224  
   225  	if m.IsLiteral() {
   226  		m.Dirs = []string{m.pattern}
   227  		return
   228  	}
   229  
   230  	// Clean the path and create a matching predicate.
   231  	// filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to
   232  	// preserve these, since they are meaningful in MatchPattern and in
   233  	// returned import paths.
   234  	cleanPattern := filepath.Clean(m.pattern)
   235  	isLocal := strings.HasPrefix(m.pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(m.pattern, `.\`))
   236  	prefix := ""
   237  	if cleanPattern != "." && isLocal {
   238  		prefix = "./"
   239  		cleanPattern = "." + string(os.PathSeparator) + cleanPattern
   240  	}
   241  	slashPattern := filepath.ToSlash(cleanPattern)
   242  	match := MatchPattern(slashPattern)
   243  
   244  	// Find directory to begin the scan.
   245  	// Could be smarter but this one optimization
   246  	// is enough for now, since ... is usually at the
   247  	// end of a path.
   248  	i := strings.Index(cleanPattern, "...")
   249  	dir, _ := filepath.Split(cleanPattern[:i])
   250  
   251  	// pattern begins with ./ or ../.
   252  	// path.Clean will discard the ./ but not the ../.
   253  	// We need to preserve the ./ for pattern matching
   254  	// and in the returned import paths.
   255  
   256  	if modRoot != "" {
   257  		abs, err := filepath.Abs(dir)
   258  		if err != nil {
   259  			m.AddError(err)
   260  			return
   261  		}
   262  		if !hasFilepathPrefix(abs, modRoot) {
   263  			m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot))
   264  			return
   265  		}
   266  	}
   267  
   268  	err := fsys.Walk(dir, func(path string, fi fs.FileInfo, err error) error {
   269  		if err != nil {
   270  			return err // Likely a permission error, which could interfere with matching.
   271  		}
   272  		if !fi.IsDir() {
   273  			return nil
   274  		}
   275  		top := false
   276  		if path == dir {
   277  			// Walk starts at dir and recurses. For the recursive case,
   278  			// the path is the result of filepath.Join, which calls filepath.Clean.
   279  			// The initial case is not Cleaned, though, so we do this explicitly.
   280  			//
   281  			// This converts a path like "./io/" to "io". Without this step, running
   282  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   283  			// package, because prepending the prefix "./" to the unclean path would
   284  			// result in "././io", and match("././io") returns false.
   285  			top = true
   286  			path = filepath.Clean(path)
   287  		}
   288  
   289  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   290  		_, elem := filepath.Split(path)
   291  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   292  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   293  			return filepath.SkipDir
   294  		}
   295  
   296  		if !top && cfg.ModulesEnabled {
   297  			// Ignore other modules found in subdirectories.
   298  			if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
   299  				return filepath.SkipDir
   300  			}
   301  		}
   302  
   303  		name := prefix + filepath.ToSlash(path)
   304  		if !match(name) {
   305  			return nil
   306  		}
   307  
   308  		// We keep the directory if we can import it, or if we can't import it
   309  		// due to invalid Go source files. This means that directories containing
   310  		// parse errors will be built (and fail) instead of being silently skipped
   311  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   312  		// behavior means people miss serious mistakes.
   313  		// See golang.org/issue/11407.
   314  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   315  			if _, noGo := err.(*build.NoGoError); noGo {
   316  				// The package does not actually exist, so record neither the package
   317  				// nor the error.
   318  				return nil
   319  			}
   320  			// There was an error importing path, but not matching it,
   321  			// which is all that Match promises to do.
   322  			// Ignore the import error.
   323  		}
   324  		m.Dirs = append(m.Dirs, name)
   325  		return nil
   326  	})
   327  	if err != nil {
   328  		m.AddError(err)
   329  	}
   330  }
   331  
   332  // TreeCanMatchPattern(pattern)(name) reports whether
   333  // name or children of name can possibly match pattern.
   334  // Pattern is the same limited glob accepted by matchPattern.
   335  func TreeCanMatchPattern(pattern string) func(name string) bool {
   336  	wildCard := false
   337  	if i := strings.Index(pattern, "..."); i >= 0 {
   338  		wildCard = true
   339  		pattern = pattern[:i]
   340  	}
   341  	return func(name string) bool {
   342  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   343  			wildCard && strings.HasPrefix(name, pattern)
   344  	}
   345  }
   346  
   347  // MatchPattern(pattern)(name) reports whether
   348  // name matches pattern. Pattern is a limited glob
   349  // pattern in which '...' means 'any string' and there
   350  // is no other special syntax.
   351  // Unfortunately, there are two special cases. Quoting "go help packages":
   352  //
   353  // First, /... at the end of the pattern can match an empty string,
   354  // so that net/... matches both net and packages in its subdirectories, like net/http.
   355  // Second, any slash-separated pattern element containing a wildcard never
   356  // participates in a match of the "vendor" element in the path of a vendored
   357  // package, so that ./... does not match packages in subdirectories of
   358  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
   359  // Note, however, that a directory named vendor that itself contains code
   360  // is not a vendored package: cmd/vendor would be a command named vendor,
   361  // and the pattern cmd/... matches it.
   362  func MatchPattern(pattern string) func(name string) bool {
   363  	// Convert pattern to regular expression.
   364  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
   365  	// The strategy for the vendor exclusion is to change the unmatchable
   366  	// vendor strings to a disallowed code point (vendorChar) and to use
   367  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
   368  	// This is a bit complicated but the obvious alternative,
   369  	// namely a hand-written search like in most shell glob matchers,
   370  	// is too easy to make accidentally exponential.
   371  	// Using package regexp guarantees linear-time matching.
   372  
   373  	const vendorChar = "\x00"
   374  
   375  	if strings.Contains(pattern, vendorChar) {
   376  		return func(name string) bool { return false }
   377  	}
   378  
   379  	re := regexp.QuoteMeta(pattern)
   380  	re = replaceVendor(re, vendorChar)
   381  	switch {
   382  	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
   383  		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
   384  	case re == vendorChar+`/\.\.\.`:
   385  		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
   386  	case strings.HasSuffix(re, `/\.\.\.`):
   387  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
   388  	}
   389  	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
   390  
   391  	reg := regexp.MustCompile(`^` + re + `$`)
   392  
   393  	return func(name string) bool {
   394  		if strings.Contains(name, vendorChar) {
   395  			return false
   396  		}
   397  		return reg.MatchString(replaceVendor(name, vendorChar))
   398  	}
   399  }
   400  
   401  // replaceVendor returns the result of replacing
   402  // non-trailing vendor path elements in x with repl.
   403  func replaceVendor(x, repl string) string {
   404  	if !strings.Contains(x, "vendor") {
   405  		return x
   406  	}
   407  	elem := strings.Split(x, "/")
   408  	for i := 0; i < len(elem)-1; i++ {
   409  		if elem[i] == "vendor" {
   410  			elem[i] = repl
   411  		}
   412  	}
   413  	return strings.Join(elem, "/")
   414  }
   415  
   416  // WarnUnmatched warns about patterns that didn't match any packages.
   417  func WarnUnmatched(matches []*Match) {
   418  	for _, m := range matches {
   419  		if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
   420  			fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
   421  		}
   422  	}
   423  }
   424  
   425  // ImportPaths returns the matching paths to use for the given command line.
   426  // It calls ImportPathsQuiet and then WarnUnmatched.
   427  func ImportPaths(patterns []string) []*Match {
   428  	matches := ImportPathsQuiet(patterns)
   429  	WarnUnmatched(matches)
   430  	return matches
   431  }
   432  
   433  // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
   434  func ImportPathsQuiet(patterns []string) []*Match {
   435  	var out []*Match
   436  	for _, a := range CleanPatterns(patterns) {
   437  		m := NewMatch(a)
   438  		if m.IsLocal() {
   439  			m.MatchDirs()
   440  
   441  			// Change the file import path to a regular import path if the package
   442  			// is in GOPATH or GOROOT. We don't report errors here; LoadImport
   443  			// (or something similar) will report them later.
   444  			m.Pkgs = make([]string, len(m.Dirs))
   445  			for i, dir := range m.Dirs {
   446  				absDir := dir
   447  				if !filepath.IsAbs(dir) {
   448  					absDir = filepath.Join(base.Cwd(), dir)
   449  				}
   450  				if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
   451  					m.Pkgs[i] = bp.ImportPath
   452  				} else {
   453  					m.Pkgs[i] = dir
   454  				}
   455  			}
   456  		} else {
   457  			m.MatchPackages()
   458  		}
   459  
   460  		out = append(out, m)
   461  	}
   462  	return out
   463  }
   464  
   465  // CleanPatterns returns the patterns to use for the given command line. It
   466  // canonicalizes the patterns but does not evaluate any matches. For patterns
   467  // that are not local or absolute paths, it preserves text after '@' to avoid
   468  // modifying version queries.
   469  func CleanPatterns(patterns []string) []string {
   470  	if len(patterns) == 0 {
   471  		return []string{"."}
   472  	}
   473  	var out []string
   474  	for _, a := range patterns {
   475  		var p, v string
   476  		if build.IsLocalImport(a) || filepath.IsAbs(a) {
   477  			p = a
   478  		} else if i := strings.IndexByte(a, '@'); i < 0 {
   479  			p = a
   480  		} else {
   481  			p = a[:i]
   482  			v = a[i:]
   483  		}
   484  
   485  		// Arguments may be either file paths or import paths.
   486  		// As a courtesy to Windows developers, rewrite \ to /
   487  		// in arguments that look like import paths.
   488  		// Don't replace slashes in absolute paths.
   489  		if filepath.IsAbs(p) {
   490  			p = filepath.Clean(p)
   491  		} else {
   492  			if filepath.Separator == '\\' {
   493  				p = strings.ReplaceAll(p, `\`, `/`)
   494  			}
   495  
   496  			// Put argument in canonical form, but preserve leading ./.
   497  			if strings.HasPrefix(p, "./") {
   498  				p = "./" + path.Clean(p)
   499  				if p == "./." {
   500  					p = "."
   501  				}
   502  			} else {
   503  				p = path.Clean(p)
   504  			}
   505  		}
   506  
   507  		out = append(out, p+v)
   508  	}
   509  	return out
   510  }
   511  
   512  // hasPathPrefix reports whether the path s begins with the
   513  // elements in prefix.
   514  func hasPathPrefix(s, prefix string) bool {
   515  	switch {
   516  	default:
   517  		return false
   518  	case len(s) == len(prefix):
   519  		return s == prefix
   520  	case len(s) > len(prefix):
   521  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   522  			return strings.HasPrefix(s, prefix)
   523  		}
   524  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   525  	}
   526  }
   527  
   528  // hasFilepathPrefix reports whether the path s begins with the
   529  // elements in prefix.
   530  func hasFilepathPrefix(s, prefix string) bool {
   531  	switch {
   532  	default:
   533  		return false
   534  	case len(s) == len(prefix):
   535  		return s == prefix
   536  	case len(s) > len(prefix):
   537  		if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
   538  			return strings.HasPrefix(s, prefix)
   539  		}
   540  		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
   541  	}
   542  }
   543  
   544  // IsStandardImportPath reports whether $GOROOT/src/path should be considered
   545  // part of the standard distribution. For historical reasons we allow people to add
   546  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   547  // code will start with a domain name (dot in the first element).
   548  //
   549  // Note that this function is meant to evaluate whether a directory found in GOROOT
   550  // should be treated as part of the standard library. It should not be used to decide
   551  // that a directory found in GOPATH should be rejected: directories in GOPATH
   552  // need not have dots in the first element, and they just take their chances
   553  // with future collisions in the standard library.
   554  func IsStandardImportPath(path string) bool {
   555  	i := strings.Index(path, "/")
   556  	if i < 0 {
   557  		i = len(path)
   558  	}
   559  	elem := path[:i]
   560  	return !strings.Contains(elem, ".")
   561  }
   562  
   563  // IsRelativePath reports whether pattern should be interpreted as a directory
   564  // path relative to the current directory, as opposed to a pattern matching
   565  // import paths.
   566  func IsRelativePath(pattern string) bool {
   567  	return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
   568  }
   569  
   570  // InDir checks whether path is in the file tree rooted at dir.
   571  // If so, InDir returns an equivalent path relative to dir.
   572  // If not, InDir returns an empty string.
   573  // InDir makes some effort to succeed even in the presence of symbolic links.
   574  func InDir(path, dir string) string {
   575  	if rel := inDirLex(path, dir); rel != "" {
   576  		return rel
   577  	}
   578  	xpath, err := filepath.EvalSymlinks(path)
   579  	if err != nil || xpath == path {
   580  		xpath = ""
   581  	} else {
   582  		if rel := inDirLex(xpath, dir); rel != "" {
   583  			return rel
   584  		}
   585  	}
   586  
   587  	xdir, err := filepath.EvalSymlinks(dir)
   588  	if err == nil && xdir != dir {
   589  		if rel := inDirLex(path, xdir); rel != "" {
   590  			return rel
   591  		}
   592  		if xpath != "" {
   593  			if rel := inDirLex(xpath, xdir); rel != "" {
   594  				return rel
   595  			}
   596  		}
   597  	}
   598  	return ""
   599  }
   600  
   601  // inDirLex is like inDir but only checks the lexical form of the file names.
   602  // It does not consider symbolic links.
   603  // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
   604  // return the suffix. Most uses of str.HasFilePathPrefix should probably
   605  // be calling InDir instead.
   606  func inDirLex(path, dir string) string {
   607  	pv := strings.ToUpper(filepath.VolumeName(path))
   608  	dv := strings.ToUpper(filepath.VolumeName(dir))
   609  	path = path[len(pv):]
   610  	dir = dir[len(dv):]
   611  	switch {
   612  	default:
   613  		return ""
   614  	case pv != dv:
   615  		return ""
   616  	case len(path) == len(dir):
   617  		if path == dir {
   618  			return "."
   619  		}
   620  		return ""
   621  	case dir == "":
   622  		return path
   623  	case len(path) > len(dir):
   624  		if dir[len(dir)-1] == filepath.Separator {
   625  			if path[:len(dir)] == dir {
   626  				return path[len(dir):]
   627  			}
   628  			return ""
   629  		}
   630  		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
   631  			if len(path) == len(dir)+1 {
   632  				return "."
   633  			}
   634  			return path[len(dir)+1:]
   635  		}
   636  		return ""
   637  	}
   638  }
   639  

View as plain text