github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/src/cmd/go/main.go (about) 1 // Copyright 2011 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 main 6 7 import ( 8 "bufio" 9 "bytes" 10 "flag" 11 "fmt" 12 "go/build" 13 "io" 14 "log" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strings" 22 "sync" 23 "text/template" 24 "unicode" 25 "unicode/utf8" 26 ) 27 28 // A Command is an implementation of a go command 29 // like go build or go fix. 30 type Command struct { 31 // Run runs the command. 32 // The args are the arguments after the command name. 33 Run func(cmd *Command, args []string) 34 35 // UsageLine is the one-line usage message. 36 // The first word in the line is taken to be the command name. 37 UsageLine string 38 39 // Short is the short description shown in the 'go help' output. 40 Short string 41 42 // Long is the long message shown in the 'go help <this-command>' output. 43 Long string 44 45 // Flag is a set of flags specific to this command. 46 Flag flag.FlagSet 47 48 // CustomFlags indicates that the command will do its own 49 // flag parsing. 50 CustomFlags bool 51 } 52 53 // Name returns the command's name: the first word in the usage line. 54 func (c *Command) Name() string { 55 name := c.UsageLine 56 i := strings.Index(name, " ") 57 if i >= 0 { 58 name = name[:i] 59 } 60 return name 61 } 62 63 func (c *Command) Usage() { 64 fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 65 fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) 66 os.Exit(2) 67 } 68 69 // Runnable reports whether the command can be run; otherwise 70 // it is a documentation pseudo-command such as importpath. 71 func (c *Command) Runnable() bool { 72 return c.Run != nil 73 } 74 75 // Commands lists the available commands and help topics. 76 // The order here is the order in which they are printed by 'go help'. 77 var commands = []*Command{ 78 cmdBuild, 79 cmdClean, 80 cmdDoc, 81 cmdEnv, 82 cmdBug, 83 cmdFix, 84 cmdFmt, 85 cmdGenerate, 86 cmdGet, 87 cmdInstall, 88 cmdList, 89 cmdRun, 90 cmdTest, 91 cmdTool, 92 cmdVersion, 93 cmdVet, 94 95 helpC, 96 helpBuildmode, 97 helpFileType, 98 helpGopath, 99 helpEnvironment, 100 helpImportPath, 101 helpPackages, 102 helpTestflag, 103 helpTestfunc, 104 } 105 106 var exitStatus = 0 107 var exitMu sync.Mutex 108 109 func setExitStatus(n int) { 110 exitMu.Lock() 111 if exitStatus < n { 112 exitStatus = n 113 } 114 exitMu.Unlock() 115 } 116 117 var origEnv []string 118 119 func main() { 120 _ = go11tag 121 flag.Usage = usage 122 flag.Parse() 123 log.SetFlags(0) 124 125 args := flag.Args() 126 if len(args) < 1 { 127 usage() 128 } 129 130 if args[0] == "help" { 131 help(args[1:]) 132 return 133 } 134 135 // Diagnose common mistake: GOPATH==GOROOT. 136 // This setting is equivalent to not setting GOPATH at all, 137 // which is not what most people want when they do it. 138 if gopath := os.Getenv("GOPATH"); gopath == runtime.GOROOT() { 139 fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath) 140 } else { 141 for _, p := range filepath.SplitList(gopath) { 142 // Note: using HasPrefix instead of Contains because a ~ can appear 143 // in the middle of directory elements, such as /tmp/git-1.8.2~rc3 144 // or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell. 145 if strings.HasPrefix(p, "~") { 146 fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p) 147 os.Exit(2) 148 } 149 if !filepath.IsAbs(p) { 150 fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p) 151 os.Exit(2) 152 } 153 } 154 } 155 156 if fi, err := os.Stat(goroot); err != nil || !fi.IsDir() { 157 fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", goroot) 158 os.Exit(2) 159 } 160 161 // Set environment (GOOS, GOARCH, etc) explicitly. 162 // In theory all the commands we invoke should have 163 // the same default computation of these as we do, 164 // but in practice there might be skew 165 // This makes sure we all agree. 166 origEnv = os.Environ() 167 for _, env := range mkEnv() { 168 if os.Getenv(env.name) != env.value { 169 os.Setenv(env.name, env.value) 170 } 171 } 172 173 for _, cmd := range commands { 174 if cmd.Name() == args[0] && cmd.Runnable() { 175 cmd.Flag.Usage = func() { cmd.Usage() } 176 if cmd.CustomFlags { 177 args = args[1:] 178 } else { 179 cmd.Flag.Parse(args[1:]) 180 args = cmd.Flag.Args() 181 } 182 cmd.Run(cmd, args) 183 exit() 184 return 185 } 186 } 187 188 fmt.Fprintf(os.Stderr, "go: unknown subcommand %q\nRun 'go help' for usage.\n", args[0]) 189 setExitStatus(2) 190 exit() 191 } 192 193 var usageTemplate = `Go is a tool for managing Go source code. 194 195 Usage: 196 197 go command [arguments] 198 199 The commands are: 200 {{range .}}{{if .Runnable}} 201 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 202 203 Use "go help [command]" for more information about a command. 204 205 Additional help topics: 206 {{range .}}{{if not .Runnable}} 207 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 208 209 Use "go help [topic]" for more information about that topic. 210 211 ` 212 213 var helpTemplate = `{{if .Runnable}}usage: go {{.UsageLine}} 214 215 {{end}}{{.Long | trim}} 216 ` 217 218 var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}} 219 220 {{end}}{{if .Runnable}}Usage: 221 222 go {{.UsageLine}} 223 224 {{end}}{{.Long | trim}} 225 226 227 {{end}}` 228 229 // commentWriter writes a Go comment to the underlying io.Writer, 230 // using line comment form (//). 231 type commentWriter struct { 232 W io.Writer 233 wroteSlashes bool // Wrote "//" at the beginning of the current line. 234 } 235 236 func (c *commentWriter) Write(p []byte) (int, error) { 237 var n int 238 for i, b := range p { 239 if !c.wroteSlashes { 240 s := "//" 241 if b != '\n' { 242 s = "// " 243 } 244 if _, err := io.WriteString(c.W, s); err != nil { 245 return n, err 246 } 247 c.wroteSlashes = true 248 } 249 n0, err := c.W.Write(p[i : i+1]) 250 n += n0 251 if err != nil { 252 return n, err 253 } 254 if b == '\n' { 255 c.wroteSlashes = false 256 } 257 } 258 return len(p), nil 259 } 260 261 // An errWriter wraps a writer, recording whether a write error occurred. 262 type errWriter struct { 263 w io.Writer 264 err error 265 } 266 267 func (w *errWriter) Write(b []byte) (int, error) { 268 n, err := w.w.Write(b) 269 if err != nil { 270 w.err = err 271 } 272 return n, err 273 } 274 275 // tmpl executes the given template text on data, writing the result to w. 276 func tmpl(w io.Writer, text string, data interface{}) { 277 t := template.New("top") 278 t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 279 template.Must(t.Parse(text)) 280 ew := &errWriter{w: w} 281 err := t.Execute(ew, data) 282 if ew.err != nil { 283 // I/O error writing. Ignore write on closed pipe. 284 if strings.Contains(ew.err.Error(), "pipe") { 285 os.Exit(1) 286 } 287 fatalf("writing output: %v", ew.err) 288 } 289 if err != nil { 290 panic(err) 291 } 292 } 293 294 func capitalize(s string) string { 295 if s == "" { 296 return s 297 } 298 r, n := utf8.DecodeRuneInString(s) 299 return string(unicode.ToTitle(r)) + s[n:] 300 } 301 302 func printUsage(w io.Writer) { 303 bw := bufio.NewWriter(w) 304 tmpl(bw, usageTemplate, commands) 305 bw.Flush() 306 } 307 308 func usage() { 309 // special case "go test -h" 310 if len(os.Args) > 1 && os.Args[1] == "test" { 311 os.Stderr.WriteString(testUsage + "\n\n" + 312 strings.TrimSpace(testFlag1) + "\n\n\t" + 313 strings.TrimSpace(testFlag2) + "\n") 314 os.Exit(2) 315 } 316 printUsage(os.Stderr) 317 os.Exit(2) 318 } 319 320 // help implements the 'help' command. 321 func help(args []string) { 322 if len(args) == 0 { 323 printUsage(os.Stdout) 324 // not exit 2: succeeded at 'go help'. 325 return 326 } 327 if len(args) != 1 { 328 fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n") 329 os.Exit(2) // failed at 'go help' 330 } 331 332 arg := args[0] 333 334 // 'go help documentation' generates doc.go. 335 if arg == "documentation" { 336 fmt.Println("// Copyright 2011 The Go Authors. All rights reserved.") 337 fmt.Println("// Use of this source code is governed by a BSD-style") 338 fmt.Println("// license that can be found in the LICENSE file.") 339 fmt.Println() 340 fmt.Println("// DO NOT EDIT THIS FILE. GENERATED BY mkalldocs.sh.") 341 fmt.Println("// Edit the documentation in other files and rerun mkalldocs.sh to generate this one.") 342 fmt.Println() 343 buf := new(bytes.Buffer) 344 printUsage(buf) 345 usage := &Command{Long: buf.String()} 346 tmpl(&commentWriter{W: os.Stdout}, documentationTemplate, append([]*Command{usage}, commands...)) 347 fmt.Println("package main") 348 return 349 } 350 351 for _, cmd := range commands { 352 if cmd.Name() == arg { 353 tmpl(os.Stdout, helpTemplate, cmd) 354 // not exit 2: succeeded at 'go help cmd'. 355 return 356 } 357 } 358 359 fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'go help'.\n", arg) 360 os.Exit(2) // failed at 'go help cmd' 361 } 362 363 // importPathsNoDotExpansion returns the import paths to use for the given 364 // command line, but it does no ... expansion. 365 func importPathsNoDotExpansion(args []string) []string { 366 if len(args) == 0 { 367 return []string{"."} 368 } 369 var out []string 370 for _, a := range args { 371 // Arguments are supposed to be import paths, but 372 // as a courtesy to Windows developers, rewrite \ to / 373 // in command-line arguments. Handles .\... and so on. 374 if filepath.Separator == '\\' { 375 a = strings.Replace(a, `\`, `/`, -1) 376 } 377 378 // Put argument in canonical form, but preserve leading ./. 379 if strings.HasPrefix(a, "./") { 380 a = "./" + path.Clean(a) 381 if a == "./." { 382 a = "." 383 } 384 } else { 385 a = path.Clean(a) 386 } 387 if isMetaPackage(a) { 388 out = append(out, allPackages(a)...) 389 continue 390 } 391 out = append(out, a) 392 } 393 return out 394 } 395 396 // importPaths returns the import paths to use for the given command line. 397 func importPaths(args []string) []string { 398 args = importPathsNoDotExpansion(args) 399 var out []string 400 for _, a := range args { 401 if strings.Contains(a, "...") { 402 if build.IsLocalImport(a) { 403 out = append(out, allPackagesInFS(a)...) 404 } else { 405 out = append(out, allPackages(a)...) 406 } 407 continue 408 } 409 out = append(out, a) 410 } 411 return out 412 } 413 414 var atexitFuncs []func() 415 416 func atexit(f func()) { 417 atexitFuncs = append(atexitFuncs, f) 418 } 419 420 func exit() { 421 for _, f := range atexitFuncs { 422 f() 423 } 424 os.Exit(exitStatus) 425 } 426 427 func fatalf(format string, args ...interface{}) { 428 errorf(format, args...) 429 exit() 430 } 431 432 func errorf(format string, args ...interface{}) { 433 log.Printf(format, args...) 434 setExitStatus(1) 435 } 436 437 func exitIfErrors() { 438 if exitStatus != 0 { 439 exit() 440 } 441 } 442 443 func run(cmdargs ...interface{}) { 444 cmdline := stringList(cmdargs...) 445 if buildN || buildX { 446 fmt.Printf("%s\n", strings.Join(cmdline, " ")) 447 if buildN { 448 return 449 } 450 } 451 452 cmd := exec.Command(cmdline[0], cmdline[1:]...) 453 cmd.Stdout = os.Stdout 454 cmd.Stderr = os.Stderr 455 if err := cmd.Run(); err != nil { 456 errorf("%v", err) 457 } 458 } 459 460 // envForDir returns a copy of the environment 461 // suitable for running in the given directory. 462 // The environment is the current process's environment 463 // but with an updated $PWD, so that an os.Getwd in the 464 // child will be faster. 465 func envForDir(dir string, base []string) []string { 466 // Internally we only use rooted paths, so dir is rooted. 467 // Even if dir is not rooted, no harm done. 468 return mergeEnvLists([]string{"PWD=" + dir}, base) 469 } 470 471 // mergeEnvLists merges the two environment lists such that 472 // variables with the same name in "in" replace those in "out". 473 // This always returns a newly allocated slice. 474 func mergeEnvLists(in, out []string) []string { 475 out = append([]string(nil), out...) 476 NextVar: 477 for _, inkv := range in { 478 k := strings.SplitAfterN(inkv, "=", 2)[0] 479 for i, outkv := range out { 480 if strings.HasPrefix(outkv, k) { 481 out[i] = inkv 482 continue NextVar 483 } 484 } 485 out = append(out, inkv) 486 } 487 return out 488 } 489 490 // matchPattern(pattern)(name) reports whether 491 // name matches pattern. Pattern is a limited glob 492 // pattern in which '...' means 'any string' and there 493 // is no other special syntax. 494 func matchPattern(pattern string) func(name string) bool { 495 re := regexp.QuoteMeta(pattern) 496 re = strings.Replace(re, `\.\.\.`, `.*`, -1) 497 // Special case: foo/... matches foo too. 498 if strings.HasSuffix(re, `/.*`) { 499 re = re[:len(re)-len(`/.*`)] + `(/.*)?` 500 } 501 reg := regexp.MustCompile(`^` + re + `$`) 502 return func(name string) bool { 503 return reg.MatchString(name) 504 } 505 } 506 507 // hasPathPrefix reports whether the path s begins with the 508 // elements in prefix. 509 func hasPathPrefix(s, prefix string) bool { 510 switch { 511 default: 512 return false 513 case len(s) == len(prefix): 514 return s == prefix 515 case len(s) > len(prefix): 516 if prefix != "" && prefix[len(prefix)-1] == '/' { 517 return strings.HasPrefix(s, prefix) 518 } 519 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 520 } 521 } 522 523 // hasFilePathPrefix reports whether the filesystem path s begins with the 524 // elements in prefix. 525 func hasFilePathPrefix(s, prefix string) bool { 526 sv := strings.ToUpper(filepath.VolumeName(s)) 527 pv := strings.ToUpper(filepath.VolumeName(prefix)) 528 s = s[len(sv):] 529 prefix = prefix[len(pv):] 530 switch { 531 default: 532 return false 533 case sv != pv: 534 return false 535 case len(s) == len(prefix): 536 return s == prefix 537 case len(s) > len(prefix): 538 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 539 return strings.HasPrefix(s, prefix) 540 } 541 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 542 } 543 } 544 545 // expandPath returns the symlink-expanded form of path. 546 func expandPath(p string) string { 547 x, err := filepath.EvalSymlinks(p) 548 if err == nil { 549 return x 550 } 551 return p 552 } 553 554 // treeCanMatchPattern(pattern)(name) reports whether 555 // name or children of name can possibly match pattern. 556 // Pattern is the same limited glob accepted by matchPattern. 557 func treeCanMatchPattern(pattern string) func(name string) bool { 558 wildCard := false 559 if i := strings.Index(pattern, "..."); i >= 0 { 560 wildCard = true 561 pattern = pattern[:i] 562 } 563 return func(name string) bool { 564 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 565 wildCard && strings.HasPrefix(name, pattern) 566 } 567 } 568 569 // allPackages returns all the packages that can be found 570 // under the $GOPATH directories and $GOROOT matching pattern. 571 // The pattern is either "all" (all packages), "std" (standard packages), 572 // "cmd" (standard commands), or a path including "...". 573 func allPackages(pattern string) []string { 574 pkgs := matchPackages(pattern) 575 if len(pkgs) == 0 { 576 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 577 } 578 return pkgs 579 } 580 581 func matchPackages(pattern string) []string { 582 match := func(string) bool { return true } 583 treeCanMatch := func(string) bool { return true } 584 if !isMetaPackage(pattern) { 585 match = matchPattern(pattern) 586 treeCanMatch = treeCanMatchPattern(pattern) 587 } 588 589 have := map[string]bool{ 590 "builtin": true, // ignore pseudo-package that exists only for documentation 591 } 592 if !buildContext.CgoEnabled { 593 have["runtime/cgo"] = true // ignore during walk 594 } 595 var pkgs []string 596 597 for _, src := range buildContext.SrcDirs() { 598 if (pattern == "std" || pattern == "cmd") && src != gorootSrc { 599 continue 600 } 601 src = filepath.Clean(src) + string(filepath.Separator) 602 root := src 603 if pattern == "cmd" { 604 root += "cmd" + string(filepath.Separator) 605 } 606 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 607 if err != nil || !fi.IsDir() || path == src { 608 return nil 609 } 610 611 // Avoid .foo, _foo, and testdata directory trees. 612 _, elem := filepath.Split(path) 613 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 614 return filepath.SkipDir 615 } 616 617 name := filepath.ToSlash(path[len(src):]) 618 if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 619 // The name "std" is only the standard library. 620 // If the name is cmd, it's the root of the command tree. 621 return filepath.SkipDir 622 } 623 if !treeCanMatch(name) { 624 return filepath.SkipDir 625 } 626 if have[name] { 627 return nil 628 } 629 have[name] = true 630 if !match(name) { 631 return nil 632 } 633 _, err = buildContext.ImportDir(path, 0) 634 if err != nil { 635 if _, noGo := err.(*build.NoGoError); noGo { 636 return nil 637 } 638 } 639 pkgs = append(pkgs, name) 640 return nil 641 }) 642 } 643 return pkgs 644 } 645 646 // allPackagesInFS is like allPackages but is passed a pattern 647 // beginning ./ or ../, meaning it should scan the tree rooted 648 // at the given directory. There are ... in the pattern too. 649 func allPackagesInFS(pattern string) []string { 650 pkgs := matchPackagesInFS(pattern) 651 if len(pkgs) == 0 { 652 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 653 } 654 return pkgs 655 } 656 657 func matchPackagesInFS(pattern string) []string { 658 // Find directory to begin the scan. 659 // Could be smarter but this one optimization 660 // is enough for now, since ... is usually at the 661 // end of a path. 662 i := strings.Index(pattern, "...") 663 dir, _ := path.Split(pattern[:i]) 664 665 // pattern begins with ./ or ../. 666 // path.Clean will discard the ./ but not the ../. 667 // We need to preserve the ./ for pattern matching 668 // and in the returned import paths. 669 prefix := "" 670 if strings.HasPrefix(pattern, "./") { 671 prefix = "./" 672 } 673 match := matchPattern(pattern) 674 675 var pkgs []string 676 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 677 if err != nil || !fi.IsDir() { 678 return nil 679 } 680 if path == dir { 681 // filepath.Walk starts at dir and recurses. For the recursive case, 682 // the path is the result of filepath.Join, which calls filepath.Clean. 683 // The initial case is not Cleaned, though, so we do this explicitly. 684 // 685 // This converts a path like "./io/" to "io". Without this step, running 686 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 687 // package, because prepending the prefix "./" to the unclean path would 688 // result in "././io", and match("././io") returns false. 689 path = filepath.Clean(path) 690 } 691 692 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 693 _, elem := filepath.Split(path) 694 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 695 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 696 return filepath.SkipDir 697 } 698 699 name := prefix + filepath.ToSlash(path) 700 if !match(name) { 701 return nil 702 } 703 704 // We keep the directory if we can import it, or if we can't import it 705 // due to invalid Go source files. This means that directories containing 706 // parse errors will be built (and fail) instead of being silently skipped 707 // as not matching the pattern. Go 1.5 and earlier skipped, but that 708 // behavior means people miss serious mistakes. 709 // See golang.org/issue/11407. 710 if p, err := buildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 711 if _, noGo := err.(*build.NoGoError); !noGo { 712 log.Print(err) 713 } 714 return nil 715 } 716 pkgs = append(pkgs, name) 717 return nil 718 }) 719 return pkgs 720 } 721 722 // stringList's arguments should be a sequence of string or []string values. 723 // stringList flattens them into a single []string. 724 func stringList(args ...interface{}) []string { 725 var x []string 726 for _, arg := range args { 727 switch arg := arg.(type) { 728 case []string: 729 x = append(x, arg...) 730 case string: 731 x = append(x, arg) 732 default: 733 panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg)) 734 } 735 } 736 return x 737 } 738 739 // toFold returns a string with the property that 740 // strings.EqualFold(s, t) iff toFold(s) == toFold(t) 741 // This lets us test a large set of strings for fold-equivalent 742 // duplicates without making a quadratic number of calls 743 // to EqualFold. Note that strings.ToUpper and strings.ToLower 744 // have the desired property in some corner cases. 745 func toFold(s string) string { 746 // Fast path: all ASCII, no upper case. 747 // Most paths look like this already. 748 for i := 0; i < len(s); i++ { 749 c := s[i] 750 if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' { 751 goto Slow 752 } 753 } 754 return s 755 756 Slow: 757 var buf bytes.Buffer 758 for _, r := range s { 759 // SimpleFold(x) cycles to the next equivalent rune > x 760 // or wraps around to smaller values. Iterate until it wraps, 761 // and we've found the minimum value. 762 for { 763 r0 := r 764 r = unicode.SimpleFold(r0) 765 if r <= r0 { 766 break 767 } 768 } 769 // Exception to allow fast path above: A-Z => a-z 770 if 'A' <= r && r <= 'Z' { 771 r += 'a' - 'A' 772 } 773 buf.WriteRune(r) 774 } 775 return buf.String() 776 } 777 778 // foldDup reports a pair of strings from the list that are 779 // equal according to strings.EqualFold. 780 // It returns "", "" if there are no such strings. 781 func foldDup(list []string) (string, string) { 782 clash := map[string]string{} 783 for _, s := range list { 784 fold := toFold(s) 785 if t := clash[fold]; t != "" { 786 if s > t { 787 s, t = t, s 788 } 789 return s, t 790 } 791 clash[fold] = s 792 } 793 return "", "" 794 }