mirror of
https://github.com/golang/go
synced 2024-09-29 16:24:28 -06:00
cmd/go/internal/vcs: also check file mode when identifying VCS root
Currently, FromDir identifies a VCS checkout directory just by checking
whether it contains a specified file. This is not enough. For example,
although there is a ".git" file (a plain file, not a directory) in a
git submodule directory, this directory is not a git repository root.
This change takes the file mode into account. As of now, the filename
and file mode for the supported VCS tools are:
- Mercurial: .hg directory
- Git: .git directory
- Bazaar: .bzr directory
- Subversion: .svn directory
- Fossil: .fslckout plain file
- Fossil: _FOSSIL_ plain file
This CL effectively reverts CL 30948 for #10322.
Fixes #53640.
Change-Id: Iea316c7e983232903bddb7e7f6dbaa55e8498685
GitHub-Last-Rev: 7a2d6ff6f9
GitHub-Pull-Request: golang/go#56296
Reviewed-on: https://go-review.googlesource.com/c/go/+/443597
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
This commit is contained in:
parent
3e3a8fe5be
commit
bb3965695d
@ -34,8 +34,8 @@ import (
|
||||
// like Mercurial, Git, or Subversion.
|
||||
type Cmd struct {
|
||||
Name string
|
||||
Cmd string // name of binary to invoke command
|
||||
RootNames []string // filename indicating the root of a checkout directory
|
||||
Cmd string // name of binary to invoke command
|
||||
RootNames []rootName // filename and mode indicating the root of a checkout directory
|
||||
|
||||
CreateCmd []string // commands to download a fresh copy of a repository
|
||||
DownloadCmd []string // commands to download updates into an existing repository
|
||||
@ -150,9 +150,11 @@ func vcsByCmd(cmd string) *Cmd {
|
||||
|
||||
// vcsHg describes how to use Mercurial.
|
||||
var vcsHg = &Cmd{
|
||||
Name: "Mercurial",
|
||||
Cmd: "hg",
|
||||
RootNames: []string{".hg"},
|
||||
Name: "Mercurial",
|
||||
Cmd: "hg",
|
||||
RootNames: []rootName{
|
||||
{filename: ".hg", isDir: true},
|
||||
},
|
||||
|
||||
CreateCmd: []string{"clone -U -- {repo} {dir}"},
|
||||
DownloadCmd: []string{"pull"},
|
||||
@ -238,9 +240,11 @@ func parseRevTime(out []byte) (string, time.Time, error) {
|
||||
|
||||
// vcsGit describes how to use Git.
|
||||
var vcsGit = &Cmd{
|
||||
Name: "Git",
|
||||
Cmd: "git",
|
||||
RootNames: []string{".git"},
|
||||
Name: "Git",
|
||||
Cmd: "git",
|
||||
RootNames: []rootName{
|
||||
{filename: ".git", isDir: true},
|
||||
},
|
||||
|
||||
CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
|
||||
DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
|
||||
@ -352,9 +356,11 @@ func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
|
||||
|
||||
// vcsBzr describes how to use Bazaar.
|
||||
var vcsBzr = &Cmd{
|
||||
Name: "Bazaar",
|
||||
Cmd: "bzr",
|
||||
RootNames: []string{".bzr"},
|
||||
Name: "Bazaar",
|
||||
Cmd: "bzr",
|
||||
RootNames: []rootName{
|
||||
{filename: ".bzr", isDir: true},
|
||||
},
|
||||
|
||||
CreateCmd: []string{"branch -- {repo} {dir}"},
|
||||
|
||||
@ -473,9 +479,11 @@ func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
|
||||
|
||||
// vcsSvn describes how to use Subversion.
|
||||
var vcsSvn = &Cmd{
|
||||
Name: "Subversion",
|
||||
Cmd: "svn",
|
||||
RootNames: []string{".svn"},
|
||||
Name: "Subversion",
|
||||
Cmd: "svn",
|
||||
RootNames: []rootName{
|
||||
{filename: ".svn", isDir: true},
|
||||
},
|
||||
|
||||
CreateCmd: []string{"checkout -- {repo} {dir}"},
|
||||
DownloadCmd: []string{"update"},
|
||||
@ -524,9 +532,12 @@ const fossilRepoName = ".fossil"
|
||||
|
||||
// vcsFossil describes how to use Fossil (fossil-scm.org)
|
||||
var vcsFossil = &Cmd{
|
||||
Name: "Fossil",
|
||||
Cmd: "fossil",
|
||||
RootNames: []string{".fslckout", "_FOSSIL_"},
|
||||
Name: "Fossil",
|
||||
Cmd: "fossil",
|
||||
RootNames: []rootName{
|
||||
{filename: ".fslckout", isDir: false},
|
||||
{filename: "_FOSSIL_", isDir: false},
|
||||
},
|
||||
|
||||
CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
|
||||
DownloadCmd: []string{"up"},
|
||||
@ -814,7 +825,7 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
|
||||
origDir := dir
|
||||
for len(dir) > len(srcRoot) {
|
||||
for _, vcs := range vcsList {
|
||||
if _, err := statAny(dir, vcs.RootNames); err == nil {
|
||||
if isVCSRoot(dir, vcs.RootNames) {
|
||||
// Record first VCS we find.
|
||||
// If allowNesting is false (as it is in GOPATH), keep looking for
|
||||
// repositories in parent directories and report an error if one is
|
||||
@ -827,10 +838,6 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Allow .git inside .git, which can arise due to submodules.
|
||||
if vcsCmd == vcs && vcs.Cmd == "git" {
|
||||
continue
|
||||
}
|
||||
// Otherwise, we have one VCS inside a different VCS.
|
||||
return "", nil, fmt.Errorf("directory %q uses %s, but parent %q uses %s",
|
||||
repoDir, vcsCmd.Cmd, dir, vcs.Cmd)
|
||||
@ -850,23 +857,22 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
|
||||
return repoDir, vcsCmd, nil
|
||||
}
|
||||
|
||||
// statAny provides FileInfo for the first filename found in the directory.
|
||||
// Otherwise, it returns the last error seen.
|
||||
func statAny(dir string, filenames []string) (os.FileInfo, error) {
|
||||
if len(filenames) == 0 {
|
||||
return nil, errors.New("invalid argument: no filenames provided")
|
||||
}
|
||||
|
||||
var err error
|
||||
var fi os.FileInfo
|
||||
for _, name := range filenames {
|
||||
fi, err = os.Stat(filepath.Join(dir, name))
|
||||
if err == nil {
|
||||
return fi, nil
|
||||
// isVCSRoot identifies a VCS root by checking whether the directory contains
|
||||
// any of the listed root names.
|
||||
func isVCSRoot(dir string, rootNames []rootName) bool {
|
||||
for _, root := range rootNames {
|
||||
fi, err := os.Stat(filepath.Join(dir, root.filename))
|
||||
if err == nil && fi.IsDir() == root.isDir {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return false
|
||||
}
|
||||
|
||||
type rootName struct {
|
||||
filename string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
type vcsNotFoundError struct {
|
||||
@ -1026,15 +1032,11 @@ func CheckNested(vcs *Cmd, dir, srcRoot string) error {
|
||||
otherDir := dir
|
||||
for len(otherDir) > len(srcRoot) {
|
||||
for _, otherVCS := range vcsList {
|
||||
if _, err := statAny(otherDir, otherVCS.RootNames); err == nil {
|
||||
if isVCSRoot(otherDir, otherVCS.RootNames) {
|
||||
// Allow expected vcs in original dir.
|
||||
if otherDir == dir && otherVCS == vcs {
|
||||
continue
|
||||
}
|
||||
// Allow .git inside .git, which can arise due to submodules.
|
||||
if otherVCS == vcs && vcs.Cmd == "git" {
|
||||
continue
|
||||
}
|
||||
// Otherwise, we have one VCS inside a different VCS.
|
||||
return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.Cmd, otherDir, otherVCS.Cmd)
|
||||
}
|
||||
|
@ -215,17 +215,13 @@ func TestRepoRootForImportPath(t *testing.T) {
|
||||
// Test that vcs.FromDir correctly inspects a given directory and returns the
|
||||
// right VCS and repo directory.
|
||||
func TestFromDir(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "vcstest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
tempDir := t.TempDir()
|
||||
|
||||
for j, vcs := range vcsList {
|
||||
for r, rootName := range vcs.RootNames {
|
||||
for _, vcs := range vcsList {
|
||||
for r, root := range vcs.RootNames {
|
||||
vcsName := fmt.Sprint(vcs.Name, r)
|
||||
dir := filepath.Join(tempDir, "example.com", vcsName, rootName)
|
||||
if j&1 == 0 {
|
||||
dir := filepath.Join(tempDir, "example.com", vcsName, root.filename)
|
||||
if root.isDir {
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -25,7 +25,7 @@ rm $GOBIN/a$GOEXE
|
||||
# If there is a repository, but it can't be used for some reason,
|
||||
# there should be an error. It should hint about -buildvcs=false.
|
||||
cd ..
|
||||
mkdir $fslckout
|
||||
mv fslckout $fslckout
|
||||
env PATH=$WORK${/}fakebin${:}$oldpath
|
||||
chmod 0755 $WORK/fakebin/fossil
|
||||
! exec fossil help
|
||||
@ -82,6 +82,7 @@ exit 1
|
||||
-- repo/README --
|
||||
Far out in the uncharted backwaters of the unfashionable end of the western
|
||||
spiral arm of the Galaxy lies a small, unregarded yellow sun.
|
||||
-- repo/fslckout --
|
||||
-- repo/a/go.mod --
|
||||
module example.com/a
|
||||
|
||||
|
@ -14,6 +14,14 @@ go version -m $GOBIN/a$GOEXE
|
||||
! stdout vcs.revision
|
||||
rm $GOBIN/a$GOEXE
|
||||
|
||||
# If there's an orphan .git file left by a git submodule, it's not a git
|
||||
# repository, and there's no VCS info.
|
||||
cd ../gitsubmodule
|
||||
go install
|
||||
go version -m $GOBIN/gitsubmodule$GOEXE
|
||||
! stdout vcs.revision
|
||||
rm $GOBIN/gitsubmodule$GOEXE
|
||||
|
||||
# If there is a repository, but it can't be used for some reason,
|
||||
# there should be an error. It should hint about -buildvcs=false.
|
||||
# Also ensure that multiple errors are collected by "go list -e".
|
||||
@ -141,6 +149,16 @@ module example.com/b
|
||||
go 1.18
|
||||
-- repo/b/b.go --
|
||||
package b
|
||||
-- repo/gitsubmodule/.git --
|
||||
gitdir: ../.git/modules/gitsubmodule
|
||||
-- repo/gitsubmodule/go.mod --
|
||||
module example.com/gitsubmodule
|
||||
|
||||
go 1.18
|
||||
-- repo/gitsubmodule/main.go --
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
-- outside/empty.txt --
|
||||
-- outside/c/go.mod --
|
||||
module example.com/c
|
||||
|
Loading…
Reference in New Issue
Block a user