Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions environment/integration/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,36 @@ func TestRepositoryWithSubmodule(t *testing.T) {
})
}

func TestRepositoryWithNestedSubmodule(t *testing.T) {
t.Parallel()
WithRepository(t, "repository-with-nested-submodule", SetupEmptyRepo, func(t *testing.T, repo *repository.Repository, user *UserActions) {
ctx := t.Context()

// Add a submodule at a nested path (not at the repo root)
user.GitCommand("submodule", "add", "https://github.com/dagger/dagger-test-modules.git", "libs/nested-submodule")
user.GitCommand("submodule", "update", "--init")

user.GitCommand("commit", "-am", "add nested submodule")

env := user.CreateEnvironment("Test Nested Submodule", "Testing repository with nested submodule")

// Add a file to the base repo (triggers exportEnvironment + commitWorktreeChanges)
user.FileWrite(env.ID, "test.txt", "initial content\n", "Initial commit")

assert.NoError(t, repo.Update(ctx, env, "write the env back to the repo"))

// Check that the nested submodule content is readable inside the container
readmeContent, err := env.FileRead(ctx, "libs/nested-submodule/README.md", true, 0, 0)
require.NoError(t, err, "Should be able to read libs/nested-submodule/README.md from inside container")
assert.Contains(t, readmeContent, "Test fixtures used by dagger integration tests.")

// Verify that the git working tree remains clean
gitStatus, err := repository.RunGitCommand(ctx, repo.SourcePath(), "status", "--porcelain")
require.NoError(t, err, "Should be able to check git status")
assert.Empty(t, strings.TrimSpace(gitStatus), "Git working tree should remain clean")
})
}

func TestRepositoryWithRecursiveSubmodule(t *testing.T) {
t.Parallel()
WithRepository(t, "repository-with-submodule", SetupEmptyRepo, func(t *testing.T, repo *repository.Repository, user *UserActions) {
Expand Down
13 changes: 12 additions & 1 deletion repository/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,18 @@ func readSubmoduleGitdirPath(worktreePath, submodulePath string) (string, error)
return "", fmt.Errorf("invalid .git file format in submodule %s: %s", submoduleGitPath, gitContentStr)
}

return gitContentStr, nil
gitdirValue := strings.TrimPrefix(gitContentStr, "gitdir: ")

// Resolve relative paths to absolute, matching the approach used for
// the root .git pointer in exportEnvironment. Relative paths like
// "../../.git/modules/libs/foo" break after the worktree is wiped and
// the root .git is recreated as a pointer file (not a directory).
if !filepath.IsAbs(gitdirValue) {
submoduleDir := filepath.Join(worktreePath, submodulePath)
gitdirValue = filepath.Clean(filepath.Join(submoduleDir, gitdirValue))
}

return fmt.Sprintf("gitdir: %s", gitdirValue), nil
}

// addSubmoduleGitdirFiles adds .git files for all submodules to the provided directory
Expand Down
65 changes: 65 additions & 0 deletions repository/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,71 @@ func TestCommitWorktreeChanges(t *testing.T) {
})
}

func TestReadSubmoduleGitdirPath(t *testing.T) {
t.Run("root_level_submodule_relative_path", func(t *testing.T) {
dir := t.TempDir()
// Simulate a root-level submodule at "sub" with a relative gitdir pointer
subPath := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subPath, 0755))
// Relative path from sub/.git: ../.git/modules/sub -> resolves to dir/.git/modules/sub
require.NoError(t, os.WriteFile(filepath.Join(subPath, ".git"), []byte("gitdir: ../.git/modules/sub"), 0644))

result, err := readSubmoduleGitdirPath(dir, "sub")
require.NoError(t, err)

expected := filepath.Clean(filepath.Join(dir, ".git/modules/sub"))
assert.Equal(t, "gitdir: "+expected, result)
})

t.Run("nested_submodule_relative_path", func(t *testing.T) {
dir := t.TempDir()
// Simulate a nested submodule at "libs/nested-sub" with a relative gitdir pointer
subPath := filepath.Join(dir, "libs", "nested-sub")
require.NoError(t, os.MkdirAll(subPath, 0755))
// Relative path from libs/nested-sub/.git: ../../.git/modules/libs/nested-sub
require.NoError(t, os.WriteFile(filepath.Join(subPath, ".git"), []byte("gitdir: ../../.git/modules/libs/nested-sub"), 0644))

result, err := readSubmoduleGitdirPath(dir, "libs/nested-sub")
require.NoError(t, err)

expected := filepath.Clean(filepath.Join(dir, ".git/modules/libs/nested-sub"))
assert.Equal(t, "gitdir: "+expected, result)
})

t.Run("already_absolute_path", func(t *testing.T) {
dir := t.TempDir()
subPath := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subPath, 0755))
absGitdir := "/abs/path/modules/sub"
require.NoError(t, os.WriteFile(filepath.Join(subPath, ".git"), []byte("gitdir: "+absGitdir), 0644))

result, err := readSubmoduleGitdirPath(dir, "sub")
require.NoError(t, err)
assert.Equal(t, "gitdir: "+absGitdir, result)
})

t.Run("malformed_git_file", func(t *testing.T) {
dir := t.TempDir()
subPath := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subPath, 0755))
require.NoError(t, os.WriteFile(filepath.Join(subPath, ".git"), []byte("not-a-gitdir-file"), 0644))

_, err := readSubmoduleGitdirPath(dir, "sub")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid .git file format")
})

t.Run("missing_git_file", func(t *testing.T) {
dir := t.TempDir()
subPath := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subPath, 0755))

_, err := readSubmoduleGitdirPath(dir, "sub")
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to read submodule .git file")
})
}

// Test helper functions
func writeFile(t *testing.T, dir, name, content string) {
t.Helper()
Expand Down