]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: remove deleted subdirectories in 'go work use'
authorBryan C. Mills <bcmills@google.com>
Wed, 2 Feb 2022 18:56:22 +0000 (13:56 -0500)
committerBryan Mills <bcmills@google.com>
Tue, 8 Feb 2022 19:03:52 +0000 (19:03 +0000)
Also remove absolute names (relative to PWD) when updating relative
directories, and relative names when updating absolute directories.

Fixes #50959

Change-Id: If129019cad7146e82face7f23427b28240d29cfc
Reviewed-on: https://go-review.googlesource.com/c/go/+/383837
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/go/internal/modload/init.go
src/cmd/go/internal/workcmd/use.go
src/cmd/go/testdata/script/work_use_deleted.txt [new file with mode: 0644]
src/cmd/go/testdata/script/work_use_dot.txt

index 23f4efd02ad6f9a78772e3cb8f05f1ba6ec03200..e5de101ed6bdac403309e3597ad6ff6eb15ffa4b 100644 (file)
@@ -301,7 +301,7 @@ func InitWorkfile() {
        }
 }
 
-// WorkFilePath returns the path of the go.work file, or "" if not in
+// WorkFilePath returns the absolute path of the go.work file, or "" if not in
 // workspace mode. WorkFilePath must be called after InitWorkfile.
 func WorkFilePath() string {
        return workFilePath
index d3bc1b7d55ea00e42c99b3228a8e4f2eb3b118c2..3d003b78ebf82dd7bfef1f6bebac39f304504e41 100644 (file)
@@ -10,7 +10,10 @@ import (
        "cmd/go/internal/base"
        "cmd/go/internal/fsys"
        "cmd/go/internal/modload"
+       "cmd/go/internal/str"
        "context"
+       "errors"
+       "fmt"
        "io/fs"
        "os"
        "path/filepath"
@@ -56,44 +59,34 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) {
        if err != nil {
                base.Fatalf("go: %v", err)
        }
+       workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute.
 
        haveDirs := make(map[string][]string) // absolute → original(s)
        for _, use := range workFile.Use {
-               var absDir string
+               var abs string
                if filepath.IsAbs(use.Path) {
-                       absDir = filepath.Clean(use.Path)
+                       abs = filepath.Clean(use.Path)
                } else {
-                       absDir = filepath.Join(filepath.Dir(gowork), use.Path)
+                       abs = filepath.Join(workDir, use.Path)
                }
-               haveDirs[absDir] = append(haveDirs[absDir], use.Path)
+               haveDirs[abs] = append(haveDirs[abs], use.Path)
        }
 
-       addDirs := make(map[string]bool)
-       removeDirs := make(map[string]bool)
+       // keepDirs maps each absolute path to keep to the literal string to use for
+       // that path (either an absolute or a relative path), or the empty string if
+       // all entries for the absolute path should be removed.
+       keepDirs := make(map[string]string)
+
+       // lookDir updates the entry in keepDirs for the directory dir,
+       // which is either absolute or relative to the current working directory
+       // (not necessarily the directory containing the workfile).
        lookDir := func(dir string) {
-               // If the path is absolute, try to keep it absolute. If it's relative,
-               // make it relative to the go.work file rather than the working directory.
-               absDir := dir
-               if !filepath.IsAbs(dir) {
-                       absDir = filepath.Join(base.Cwd(), dir)
-                       rel, err := filepath.Rel(filepath.Dir(gowork), absDir)
-                       if err == nil {
-                               // Normalize relative paths to use slashes, so that checked-in go.work
-                               // files with relative paths within the repo are platform-independent.
-                               dir = filepath.ToSlash(rel)
-                       } else {
-                               // The path can't be made relative to the go.work file,
-                               // so it must be kept absolute instead.
-                               dir = absDir
-                       }
-               }
+               absDir, dir := pathRel(workDir, dir)
 
                fi, err := os.Stat(filepath.Join(absDir, "go.mod"))
                if err != nil {
                        if os.IsNotExist(err) {
-                               for _, origDir := range haveDirs[absDir] {
-                                       removeDirs[origDir] = true
-                               }
+                               keepDirs[absDir] = ""
                                return
                        }
                        base.Errorf("go: %v", err)
@@ -103,31 +96,96 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) {
                        base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
                }
 
-               if len(haveDirs[absDir]) == 0 {
-                       addDirs[dir] = true
+               if dup := keepDirs[absDir]; dup != "" && dup != dir {
+                       base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
                }
+               keepDirs[absDir] = dir
        }
 
        for _, useDir := range args {
-               if *useR {
-                       fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
-                               if !info.IsDir() {
-                                       return nil
+               if !*useR {
+                       lookDir(useDir)
+                       continue
+               }
+
+               // Add or remove entries for any subdirectories that still exist.
+               err := fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
+                       if !info.IsDir() {
+                               if info.Mode()&fs.ModeSymlink != 0 {
+                                       if target, err := fsys.Stat(path); err == nil && target.IsDir() {
+                                               fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
+                                       }
                                }
-                               lookDir(path)
                                return nil
-                       })
-                       continue
+                       }
+                       lookDir(path)
+                       return nil
+               })
+               if err != nil && !errors.Is(err, os.ErrNotExist) {
+                       base.Errorf("go: %v", err)
                }
-               lookDir(useDir)
-       }
 
-       for dir := range removeDirs {
-               workFile.DropUse(dir)
+               // Remove entries for subdirectories that no longer exist.
+               // Because they don't exist, they will be skipped by Walk.
+               absArg, _ := pathRel(workDir, useDir)
+               for absDir, _ := range haveDirs {
+                       if str.HasFilePathPrefix(absDir, absArg) {
+                               if _, ok := keepDirs[absDir]; !ok {
+                                       keepDirs[absDir] = "" // Mark for deletion.
+                               }
+                       }
+               }
        }
-       for dir := range addDirs {
-               workFile.AddUse(dir, "")
+
+       base.ExitIfErrors()
+
+       for absDir, keepDir := range keepDirs {
+               nKept := 0
+               for _, dir := range haveDirs[absDir] {
+                       if dir == keepDir { // (note that dir is always non-empty)
+                               nKept++
+                       } else {
+                               workFile.DropUse(dir)
+                       }
+               }
+               if keepDir != "" && nKept != 1 {
+                       // If we kept more than one copy, delete them all.
+                       // We'll recreate a unique copy with AddUse.
+                       if nKept > 1 {
+                               workFile.DropUse(keepDir)
+                       }
+                       workFile.AddUse(keepDir, "")
+               }
        }
        modload.UpdateWorkFile(workFile)
        modload.WriteWorkFile(gowork, workFile)
 }
+
+// pathRel returns the absolute and canonical forms of dir for use in a
+// go.work file located in directory workDir.
+//
+// If dir is relative, it is intepreted relative to base.Cwd()
+// and its canonical form is relative to workDir if possible.
+// If dir is absolute or cannot be made relative to workDir,
+// its canonical form is absolute.
+//
+// Canonical absolute paths are clean.
+// Canonical relative paths are clean and slash-separated.
+func pathRel(workDir, dir string) (abs, canonical string) {
+       if filepath.IsAbs(dir) {
+               abs = filepath.Clean(dir)
+               return abs, abs
+       }
+
+       abs = filepath.Join(base.Cwd(), dir)
+       rel, err := filepath.Rel(workDir, abs)
+       if err != nil {
+               // The path can't be made relative to the go.work file,
+               // so it must be kept absolute instead.
+               return abs, abs
+       }
+
+       // Normalize relative paths to use slashes, so that checked-in go.work
+       // files with relative paths within the repo are platform-independent.
+       return abs, filepath.ToSlash(rel)
+}
diff --git a/src/cmd/go/testdata/script/work_use_deleted.txt b/src/cmd/go/testdata/script/work_use_deleted.txt
new file mode 100644 (file)
index 0000000..660eb56
--- /dev/null
@@ -0,0 +1,22 @@
+go work use -r .
+cmp go.work go.work.want
+
+-- go.work --
+go 1.18
+
+use (
+       .
+       sub
+       sub/dir/deleted
+)
+-- go.work.want --
+go 1.18
+
+use sub/dir
+-- sub/README.txt --
+A go.mod file has been deleted from this directory.
+In addition, the entire subdirectory sub/dir/deleted
+has been deleted, along with sub/dir/deleted/go.mod.
+-- sub/dir/go.mod --
+module example/sub/dir
+go 1.18
index c24aae33e825b05e253f56ad5da9bfb0b792fafd..ccd83d6a61a6c32c5b497733ce5cbf19fa2bb9f7 100644 (file)
@@ -1,6 +1,7 @@
 cp go.work go.work.orig
 
-# 'go work use .' should add an entry for the current directory.
+# If the current directory contains a go.mod file,
+# 'go work use .' should add an entry for it.
 cd bar/baz
 go work use .
 cmp ../../go.work ../../go.work.rel
@@ -11,9 +12,28 @@ mv go.mod go.mod.bak
 go work use .
 cmp ../../go.work ../../go.work.orig
 
+# If the path is absolute, it should remain absolute.
 mv go.mod.bak go.mod
 go work use $PWD
-cmpenv ../../go.work ../../go.work.abs
+grep -count=1 '^use ' ../../go.work
+grep '^use ["]?'$PWD'["]?$' ../../go.work
+
+# An absolute path should replace an entry for the corresponding relative path
+# and vice-versa.
+go work use .
+cmp ../../go.work ../../go.work.rel
+go work use $PWD
+grep -count=1 '^use ' ../../go.work
+grep '^use ["]?'$PWD'["]?$' ../../go.work
+
+# If both the absolute and relative paths are named, 'go work use' should error
+# out: we don't know which one to use, and shouldn't add both because the
+# resulting workspace would contain a duplicate module.
+cp ../../go.work.orig ../../go.work
+! go work use $PWD .
+stderr '^go: already added "bar/baz" as "'$PWD'"$'
+cmp ../../go.work ../../go.work.orig
+
 
 -- go.mod --
 module example
@@ -24,10 +44,6 @@ go 1.18
 go 1.18
 
 use bar/baz
--- go.work.abs --
-go 1.18
-
-use $PWD
 -- bar/baz/go.mod --
 module example/bar/baz
 go 1.18