Skip to content

Commit bb39656

Browse files
ZekeLugopherbot
authored andcommitted
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: 7a2d6ff GitHub-Pull-Request: #56296 Reviewed-on: https://go-review.googlesource.com/c/go/+/443597 Auto-Submit: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Than McIntosh <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent 3e3a8fe commit bb39656

File tree

4 files changed

+68
-51
lines changed

4 files changed

+68
-51
lines changed

src/cmd/go/internal/vcs/vcs.go

+43-41
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import (
3434
// like Mercurial, Git, or Subversion.
3535
type Cmd struct {
3636
Name string
37-
Cmd string // name of binary to invoke command
38-
RootNames []string // filename indicating the root of a checkout directory
37+
Cmd string // name of binary to invoke command
38+
RootNames []rootName // filename and mode indicating the root of a checkout directory
3939

4040
CreateCmd []string // commands to download a fresh copy of a repository
4141
DownloadCmd []string // commands to download updates into an existing repository
@@ -150,9 +150,11 @@ func vcsByCmd(cmd string) *Cmd {
150150

151151
// vcsHg describes how to use Mercurial.
152152
var vcsHg = &Cmd{
153-
Name: "Mercurial",
154-
Cmd: "hg",
155-
RootNames: []string{".hg"},
153+
Name: "Mercurial",
154+
Cmd: "hg",
155+
RootNames: []rootName{
156+
{filename: ".hg", isDir: true},
157+
},
156158

157159
CreateCmd: []string{"clone -U -- {repo} {dir}"},
158160
DownloadCmd: []string{"pull"},
@@ -238,9 +240,11 @@ func parseRevTime(out []byte) (string, time.Time, error) {
238240

239241
// vcsGit describes how to use Git.
240242
var vcsGit = &Cmd{
241-
Name: "Git",
242-
Cmd: "git",
243-
RootNames: []string{".git"},
243+
Name: "Git",
244+
Cmd: "git",
245+
RootNames: []rootName{
246+
{filename: ".git", isDir: true},
247+
},
244248

245249
CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
246250
DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
@@ -352,9 +356,11 @@ func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
352356

353357
// vcsBzr describes how to use Bazaar.
354358
var vcsBzr = &Cmd{
355-
Name: "Bazaar",
356-
Cmd: "bzr",
357-
RootNames: []string{".bzr"},
359+
Name: "Bazaar",
360+
Cmd: "bzr",
361+
RootNames: []rootName{
362+
{filename: ".bzr", isDir: true},
363+
},
358364

359365
CreateCmd: []string{"branch -- {repo} {dir}"},
360366

@@ -473,9 +479,11 @@ func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
473479

474480
// vcsSvn describes how to use Subversion.
475481
var vcsSvn = &Cmd{
476-
Name: "Subversion",
477-
Cmd: "svn",
478-
RootNames: []string{".svn"},
482+
Name: "Subversion",
483+
Cmd: "svn",
484+
RootNames: []rootName{
485+
{filename: ".svn", isDir: true},
486+
},
479487

480488
CreateCmd: []string{"checkout -- {repo} {dir}"},
481489
DownloadCmd: []string{"update"},
@@ -524,9 +532,12 @@ const fossilRepoName = ".fossil"
524532

525533
// vcsFossil describes how to use Fossil (fossil-scm.org)
526534
var vcsFossil = &Cmd{
527-
Name: "Fossil",
528-
Cmd: "fossil",
529-
RootNames: []string{".fslckout", "_FOSSIL_"},
535+
Name: "Fossil",
536+
Cmd: "fossil",
537+
RootNames: []rootName{
538+
{filename: ".fslckout", isDir: false},
539+
{filename: "_FOSSIL_", isDir: false},
540+
},
530541

531542
CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
532543
DownloadCmd: []string{"up"},
@@ -814,7 +825,7 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
814825
origDir := dir
815826
for len(dir) > len(srcRoot) {
816827
for _, vcs := range vcsList {
817-
if _, err := statAny(dir, vcs.RootNames); err == nil {
828+
if isVCSRoot(dir, vcs.RootNames) {
818829
// Record first VCS we find.
819830
// If allowNesting is false (as it is in GOPATH), keep looking for
820831
// 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
827838
}
828839
continue
829840
}
830-
// Allow .git inside .git, which can arise due to submodules.
831-
if vcsCmd == vcs && vcs.Cmd == "git" {
832-
continue
833-
}
834841
// Otherwise, we have one VCS inside a different VCS.
835842
return "", nil, fmt.Errorf("directory %q uses %s, but parent %q uses %s",
836843
repoDir, vcsCmd.Cmd, dir, vcs.Cmd)
@@ -850,23 +857,22 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
850857
return repoDir, vcsCmd, nil
851858
}
852859

853-
// statAny provides FileInfo for the first filename found in the directory.
854-
// Otherwise, it returns the last error seen.
855-
func statAny(dir string, filenames []string) (os.FileInfo, error) {
856-
if len(filenames) == 0 {
857-
return nil, errors.New("invalid argument: no filenames provided")
858-
}
859-
860-
var err error
861-
var fi os.FileInfo
862-
for _, name := range filenames {
863-
fi, err = os.Stat(filepath.Join(dir, name))
864-
if err == nil {
865-
return fi, nil
860+
// isVCSRoot identifies a VCS root by checking whether the directory contains
861+
// any of the listed root names.
862+
func isVCSRoot(dir string, rootNames []rootName) bool {
863+
for _, root := range rootNames {
864+
fi, err := os.Stat(filepath.Join(dir, root.filename))
865+
if err == nil && fi.IsDir() == root.isDir {
866+
return true
866867
}
867868
}
868869

869-
return nil, err
870+
return false
871+
}
872+
873+
type rootName struct {
874+
filename string
875+
isDir bool
870876
}
871877

872878
type vcsNotFoundError struct {
@@ -1026,15 +1032,11 @@ func CheckNested(vcs *Cmd, dir, srcRoot string) error {
10261032
otherDir := dir
10271033
for len(otherDir) > len(srcRoot) {
10281034
for _, otherVCS := range vcsList {
1029-
if _, err := statAny(otherDir, otherVCS.RootNames); err == nil {
1035+
if isVCSRoot(otherDir, otherVCS.RootNames) {
10301036
// Allow expected vcs in original dir.
10311037
if otherDir == dir && otherVCS == vcs {
10321038
continue
10331039
}
1034-
// Allow .git inside .git, which can arise due to submodules.
1035-
if otherVCS == vcs && vcs.Cmd == "git" {
1036-
continue
1037-
}
10381040
// Otherwise, we have one VCS inside a different VCS.
10391041
return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.Cmd, otherDir, otherVCS.Cmd)
10401042
}

src/cmd/go/internal/vcs/vcs_test.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,13 @@ func TestRepoRootForImportPath(t *testing.T) {
215215
// Test that vcs.FromDir correctly inspects a given directory and returns the
216216
// right VCS and repo directory.
217217
func TestFromDir(t *testing.T) {
218-
tempDir, err := os.MkdirTemp("", "vcstest")
219-
if err != nil {
220-
t.Fatal(err)
221-
}
222-
defer os.RemoveAll(tempDir)
218+
tempDir := t.TempDir()
223219

224-
for j, vcs := range vcsList {
225-
for r, rootName := range vcs.RootNames {
220+
for _, vcs := range vcsList {
221+
for r, root := range vcs.RootNames {
226222
vcsName := fmt.Sprint(vcs.Name, r)
227-
dir := filepath.Join(tempDir, "example.com", vcsName, rootName)
228-
if j&1 == 0 {
223+
dir := filepath.Join(tempDir, "example.com", vcsName, root.filename)
224+
if root.isDir {
229225
err := os.MkdirAll(dir, 0755)
230226
if err != nil {
231227
t.Fatal(err)

src/cmd/go/testdata/script/version_buildvcs_fossil.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ rm $GOBIN/a$GOEXE
2525
# If there is a repository, but it can't be used for some reason,
2626
# there should be an error. It should hint about -buildvcs=false.
2727
cd ..
28-
mkdir $fslckout
28+
mv fslckout $fslckout
2929
env PATH=$WORK${/}fakebin${:}$oldpath
3030
chmod 0755 $WORK/fakebin/fossil
3131
! exec fossil help
@@ -82,6 +82,7 @@ exit 1
8282
-- repo/README --
8383
Far out in the uncharted backwaters of the unfashionable end of the western
8484
spiral arm of the Galaxy lies a small, unregarded yellow sun.
85+
-- repo/fslckout --
8586
-- repo/a/go.mod --
8687
module example.com/a
8788

src/cmd/go/testdata/script/version_buildvcs_git.txt

+18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ go version -m $GOBIN/a$GOEXE
1414
! stdout vcs.revision
1515
rm $GOBIN/a$GOEXE
1616

17+
# If there's an orphan .git file left by a git submodule, it's not a git
18+
# repository, and there's no VCS info.
19+
cd ../gitsubmodule
20+
go install
21+
go version -m $GOBIN/gitsubmodule$GOEXE
22+
! stdout vcs.revision
23+
rm $GOBIN/gitsubmodule$GOEXE
24+
1725
# If there is a repository, but it can't be used for some reason,
1826
# there should be an error. It should hint about -buildvcs=false.
1927
# Also ensure that multiple errors are collected by "go list -e".
@@ -141,6 +149,16 @@ module example.com/b
141149
go 1.18
142150
-- repo/b/b.go --
143151
package b
152+
-- repo/gitsubmodule/.git --
153+
gitdir: ../.git/modules/gitsubmodule
154+
-- repo/gitsubmodule/go.mod --
155+
module example.com/gitsubmodule
156+
157+
go 1.18
158+
-- repo/gitsubmodule/main.go --
159+
package main
160+
161+
func main() {}
144162
-- outside/empty.txt --
145163
-- outside/c/go.mod --
146164
module example.com/c

0 commit comments

Comments
 (0)