Skip to content

Commit 973cdc4

Browse files
committed
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 specicil 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. Dmitri left a comment (#10322 (comment)) there. But it's unfortunately ignored.
1 parent 81922ad commit 973cdc4

File tree

2 files changed

+48
-55
lines changed

2 files changed

+48
-55
lines changed

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

+43-46
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,12 +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 fi, err := statAny(dir, vcs.RootNames); err == nil {
818-
// git submodule contains .git file, but its directory is not a
819-
// git repository root.
820-
if !fi.IsDir() && fi.Name() == ".git" {
821-
continue
822-
}
828+
if isVCSRoot(dir, vcs.RootNames) {
823829
// Record first VCS we find.
824830
// If allowNesting is false (as it is in GOPATH), keep looking for
825831
// repositories in parent directories and report an error if one is
@@ -832,10 +838,6 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
832838
}
833839
continue
834840
}
835-
// Allow .git inside .git, which can arise due to submodules.
836-
if vcsCmd == vcs && vcs.Cmd == "git" {
837-
continue
838-
}
839841
// Otherwise, we have one VCS inside a different VCS.
840842
return "", nil, fmt.Errorf("directory %q uses %s, but parent %q uses %s",
841843
repoDir, vcsCmd.Cmd, dir, vcs.Cmd)
@@ -855,23 +857,22 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm
855857
return repoDir, vcsCmd, nil
856858
}
857859

858-
// statAny provides FileInfo for the first filename found in the directory.
859-
// Otherwise, it returns the last error seen.
860-
func statAny(dir string, filenames []string) (os.FileInfo, error) {
861-
if len(filenames) == 0 {
862-
return nil, errors.New("invalid argument: no filenames provided")
863-
}
864-
865-
var err error
866-
var fi os.FileInfo
867-
for _, name := range filenames {
868-
fi, err = os.Stat(filepath.Join(dir, name))
869-
if err == nil {
870-
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
871867
}
872868
}
873869

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

877878
type vcsNotFoundError struct {
@@ -1031,15 +1032,11 @@ func CheckNested(vcs *Cmd, dir, srcRoot string) error {
10311032
otherDir := dir
10321033
for len(otherDir) > len(srcRoot) {
10331034
for _, otherVCS := range vcsList {
1034-
if _, err := statAny(otherDir, otherVCS.RootNames); err == nil {
1035+
if isVCSRoot(otherDir, otherVCS.RootNames) {
10351036
// Allow expected vcs in original dir.
10361037
if otherDir == dir && otherVCS == vcs {
10371038
continue
10381039
}
1039-
// Allow .git inside .git, which can arise due to submodules.
1040-
if otherVCS == vcs && vcs.Cmd == "git" {
1041-
continue
1042-
}
10431040
// Otherwise, we have one VCS inside a different VCS.
10441041
return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.Cmd, otherDir, otherVCS.Cmd)
10451042
}

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)

0 commit comments

Comments
 (0)