Skip to content

Commit a4544a0

Browse files
committed
[release-branch.go1.8] cmd/go: reject update of VCS inside VCS
Cherry-pick of CL 68110. Change-Id: Iae84c6404ab5eeb6950faa2364f97a017c67c506 Reviewed-on: https://go-review.googlesource.com/68190 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Chris Broadfoot <[email protected]>
1 parent 9d1d78c commit a4544a0

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

src/cmd/go/get.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,11 @@ func downloadPackage(p *Package) error {
439439
p.build.PkgRoot = filepath.Join(list[0], "pkg")
440440
}
441441
root := filepath.Join(p.build.SrcRoot, filepath.FromSlash(rootPath))
442+
443+
if err := checkNestedVCS(vcs, root, p.build.SrcRoot); err != nil {
444+
return err
445+
}
446+
442447
// If we've considered this repository already, don't do it again.
443448
if downloadRootCache[root] {
444449
return nil

src/cmd/go/go_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,25 @@ func TestGetGitDefaultBranch(t *testing.T) {
12731273
tg.grepStdout(`\* another-branch`, "not on correct default branch")
12741274
}
12751275

1276+
func TestAccidentalGitCheckout(t *testing.T) {
1277+
testenv.MustHaveExternalNetwork(t)
1278+
if _, err := exec.LookPath("git"); err != nil {
1279+
t.Skip("skipping because git binary not found")
1280+
}
1281+
1282+
tg := testgo(t)
1283+
defer tg.cleanup()
1284+
tg.parallel()
1285+
tg.tempDir("src")
1286+
tg.setenv("GOPATH", tg.path("."))
1287+
1288+
tg.runFail("get", "-u", "vcs-test.golang.org/go/test1-svn-git")
1289+
tg.grepStderr("src[\\\\/]vcs-test.* uses git, but parent .*src[\\\\/]vcs-test.* uses svn", "get did not fail for right reason")
1290+
1291+
tg.runFail("get", "-u", "vcs-test.golang.org/go/test2-svn-git/test2main")
1292+
tg.grepStderr("src[\\\\/]vcs-test.* uses git, but parent .*src[\\\\/]vcs-test.* uses svn", "get did not fail for right reason")
1293+
}
1294+
12761295
func TestErrorMessageForSyntaxErrorInTestGoFileSaysFAIL(t *testing.T) {
12771296
tg := testgo(t)
12781297
defer tg.cleanup()

src/cmd/go/vcs.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,28 @@ func vcsFromDir(dir, srcRoot string) (vcs *vcsCmd, root string, err error) {
497497
return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
498498
}
499499

500+
var vcsRet *vcsCmd
501+
var rootRet string
502+
500503
origDir := dir
501504
for len(dir) > len(srcRoot) {
502505
for _, vcs := range vcsList {
503506
if _, err := os.Stat(filepath.Join(dir, "."+vcs.cmd)); err == nil {
504-
return vcs, filepath.ToSlash(dir[len(srcRoot)+1:]), nil
507+
root := filepath.ToSlash(dir[len(srcRoot)+1:])
508+
// Record first VCS we find, but keep looking,
509+
// to detect mistakes like one kind of VCS inside another.
510+
if vcsRet == nil {
511+
vcsRet = vcs
512+
rootRet = root
513+
continue
514+
}
515+
// Allow .git inside .git, which can arise due to submodules.
516+
if vcsRet == vcs && vcs.cmd == "git" {
517+
continue
518+
}
519+
// Otherwise, we have one VCS inside a different VCS.
520+
return nil, "", fmt.Errorf("directory %q uses %s, but parent %q uses %s",
521+
filepath.Join(srcRoot, rootRet), vcsRet.cmd, filepath.Join(srcRoot, root), vcs.cmd)
505522
}
506523
}
507524

@@ -514,9 +531,48 @@ func vcsFromDir(dir, srcRoot string) (vcs *vcsCmd, root string, err error) {
514531
dir = ndir
515532
}
516533

534+
if vcsRet != nil {
535+
return vcsRet, rootRet, nil
536+
}
537+
517538
return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir)
518539
}
519540

541+
// checkNestedVCS checks for an incorrectly-nested VCS-inside-VCS
542+
// situation for dir, checking parents up until srcRoot.
543+
func checkNestedVCS(vcs *vcsCmd, dir, srcRoot string) error {
544+
if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
545+
return fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
546+
}
547+
548+
otherDir := dir
549+
for len(otherDir) > len(srcRoot) {
550+
for _, otherVCS := range vcsList {
551+
if _, err := os.Stat(filepath.Join(dir, "."+otherVCS.cmd)); err == nil {
552+
// Allow expected vcs in original dir.
553+
if otherDir == dir && otherVCS == vcs {
554+
continue
555+
}
556+
// Allow .git inside .git, which can arise due to submodules.
557+
if otherVCS == vcs && vcs.cmd == "git" {
558+
continue
559+
}
560+
// Otherwise, we have one VCS inside a different VCS.
561+
return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.cmd, otherDir, otherVCS.cmd)
562+
}
563+
}
564+
// Move to parent.
565+
newDir := filepath.Dir(otherDir)
566+
if len(newDir) >= len(otherDir) {
567+
// Shouldn't happen, but just in case, stop.
568+
break
569+
}
570+
otherDir = newDir
571+
}
572+
573+
return nil
574+
}
575+
520576
// repoRoot represents a version control system, a repo, and a root of
521577
// where to put it on disk.
522578
type repoRoot struct {

0 commit comments

Comments
 (0)