Skip to content

Commit

Permalink
Merge pull request #60 from sdboyer/submodules
Browse files Browse the repository at this point in the history
Defend against the waking nightmare that is git submodules
  • Loading branch information
mattfarina authored Jan 4, 2017
2 parents ccb44ce + f0e30a8 commit 6abbba9
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 2 deletions.
35 changes: 33 additions & 2 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (s GitRepo) Vcs() Type {

// Get is used to perform an initial clone of a repository.
func (s *GitRepo) Get() error {
out, err := s.run("git", "clone", s.Remote(), s.LocalPath())
out, err := s.run("git", "clone", "--recursive", s.Remote(), s.LocalPath())

// There are some windows cases where Git cannot create the parent directory,
// if it does not already exist, to the location it's trying to create the
Expand Down Expand Up @@ -151,7 +151,8 @@ func (s *GitRepo) Update() error {
if err != nil {
return NewRemoteError("Unable to update repository", err, string(out))
}
return nil

return s.defendAgainstSubmodules()
}

// UpdateVersion sets the version of a package currently checked out via Git.
Expand All @@ -160,6 +161,30 @@ func (s *GitRepo) UpdateVersion(version string) error {
if err != nil {
return NewLocalError("Unable to update checked out version", err, string(out))
}

return s.defendAgainstSubmodules()
}

// defendAgainstSubmodules tries to keep repo state sane in the event of
// submodules. Or nested submodules. What a great idea, submodules.
func (s *GitRepo) defendAgainstSubmodules() error {
// First, update them to whatever they should be, if there should happen to be any.
out, err := s.RunFromDir("git", "submodule", "update", "--init", "--recursive")
if err != nil {
return NewLocalError("Unexpected error while defensively updating submodules", err, string(out))
}
// Now, do a special extra-aggressive clean in case changing versions caused
// one or more submodules to go away.
out, err = s.RunFromDir("git", "clean", "-x", "-d", "-f", "-f")
if err != nil {
return NewLocalError("Unexpected error while defensively cleaning up after possible derelict submodule directories", err, string(out))
}
// Then, repeat just in case there are any nested submodules that went away.
out, err = s.RunFromDir("git", "submodule", "foreach", "--recursive", "git", "clean", "-x", "-d", "-f", "-f")
if err != nil {
return NewLocalError("Unexpected error while defensively cleaning up after possible derelict nested submodule directories", err, string(out))
}

return nil
}

Expand Down Expand Up @@ -359,6 +384,12 @@ func (s *GitRepo) ExportDir(dir string) error {
if err != nil {
return NewLocalError("Unable to export source", err, string(out))
}
// and now, the horror of submodules
out, err = s.RunFromDir("git", "submodule", "foreach", "--recursive", "'git checkout-index -f -a --prefix=\""+filepath.Join(dir, "$path")+"\"'")
s.log(out)
if err != nil {
return NewLocalError("Error while exporting submodule sources", err, string(out))
}

return nil
}
Expand Down
126 changes: 126 additions & 0 deletions git_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vcs

import (
"fmt"
"io/ioutil"
"path/filepath"
"time"
Expand Down Expand Up @@ -356,3 +357,128 @@ func TestGitInit(t *testing.T) {
t.Error(err)
}
}

func TestGitSubmoduleHandling(t *testing.T) {
tempDir, err := ioutil.TempDir("", "go-vcs-git-submodule-tests")
if err != nil {
t.Fatal(err)
}
defer func() {
err = os.RemoveAll(tempDir)
if err != nil {
t.Error(err)
}
}()

dumplocal := func(err error) string {
if terr, ok := err.(*LocalError); ok {
return fmt.Sprintf("msg: %s\norig: %s\nout: %s", terr.Error(), terr.Original(), terr.Out())
}
return err.Error()
}

subdirExists := func(dir ...string) bool {
_, err := os.Stat(filepath.Join(append([]string{tempDir}, dir...)...))
return err == nil
}

// Initial clone should get version with two submodules, each of which have
// their own submodule
repo, err := NewGitRepo("https://github.com/sdboyer/subm", tempDir)
if err != nil {
t.Fatal(dumplocal(err))
}
err = repo.Get()
if err != nil {
t.Fatalf("unable to clone Git repo. Err was %s", dumplocal(err))
}

// Verify we are on the right version.
v, err := repo.Version()
if v != "18e3a5f6fc7f6d577e732e7a5ab2caf990efbf8f" {
t.Fatalf("did not start from expected rev, tests could fail - bailing out (got %s)", v)
}
if err != nil {
t.Fatal(dumplocal(err))
}

if !subdirExists("subm1", ".git") {
t.Fatal("subm1 submodule does not exist on initial clone/checkout")
}
if !subdirExists("subm1", "dep-test", ".git") {
t.Fatal("dep-test submodule nested under subm1 does not exist on initial clone/checkout")
}

if !subdirExists("subm-again", ".git") {
t.Fatal("subm-again submodule does not exist on initial clone/checkout")
}
if !subdirExists("subm-again", "dep-test", ".git") {
t.Fatal("dep-test submodule nested under subm-again does not exist on initial clone/checkout")
}

// Now switch to version with no submodules, make sure they all go away
err = repo.UpdateVersion("e677f82015f72ac1c8fafa66b5463163b3597af2")
if err != nil {
t.Fatalf("checking out needed version failed with err: %s", dumplocal(err))
}

if subdirExists("subm1") {
t.Fatal("checking out version without submodule did not clean up immediate submodules")
}
if subdirExists("subm1", "dep-test") {
t.Fatal("checking out version without submodule did not clean up nested submodules")
}
if subdirExists("subm-again") {
t.Fatal("checking out version without submodule did not clean up immediate submodules")
}
if subdirExists("subm-again", "dep-test") {
t.Fatal("checking out version without submodule did not clean up nested submodules")
}

err = repo.UpdateVersion("aaf7aa1bc4c3c682cc530eca8f80417088ee8540")
if err != nil {
t.Fatalf("checking out needed version failed with err: %s", dumplocal(err))
}

if !subdirExists("subm1", ".git") {
t.Fatal("checking out version with immediate submodule did not set up git subrepo")
}

err = repo.UpdateVersion("6cc4669af468f3b4f16e7e96275ad01ade5b522f")
if err != nil {
t.Fatalf("checking out needed version failed with err: %s", dumplocal(err))
}

if !subdirExists("subm1", "dep-test", ".git") {
t.Fatal("checking out version with nested submodule did not set up nested git subrepo")
}

err = repo.UpdateVersion("aaf7aa1bc4c3c682cc530eca8f80417088ee8540")
if err != nil {
t.Fatalf("checking out needed version failed with err: %s", dumplocal(err))
}

if subdirExists("subm1", "dep-test") {
t.Fatal("rolling back to version without nested submodule did not clean up the nested submodule")
}

err = repo.UpdateVersion("18e3a5f6fc7f6d577e732e7a5ab2caf990efbf8f")
if err != nil {
t.Fatalf("checking out needed version failed with err: %s", dumplocal(err))
}

if !subdirExists("subm1", ".git") {
t.Fatal("subm1 submodule does not exist after switch from other commit")
}
if !subdirExists("subm1", "dep-test", ".git") {
t.Fatal("dep-test submodule nested under subm1 does not exist after switch from other commit")
}

if !subdirExists("subm-again", ".git") {
t.Fatal("subm-again submodule does not exist after switch from other commit")
}
if !subdirExists("subm-again", "dep-test", ".git") {
t.Fatal("dep-test submodule nested under subm-again does not exist after switch from other commit")
}

}

0 comments on commit 6abbba9

Please sign in to comment.