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
26 changes: 16 additions & 10 deletions taskfile/node_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fsext"
)

// An GitNode is a node that reads a Taskfile from a remote location via Git.
Expand Down Expand Up @@ -104,13 +105,13 @@ func (node *GitNode) buildURL() string {
// Get the base URL
baseURL := node.url.String()

ref := node.ref
if ref == "" {
ref = "HEAD"
}
// Always use git:: prefix for git URLs (following Terraform's pattern)
// This forces go-getter to use git protocol
return fmt.Sprintf("git::%s?ref=%s&depth=1", baseURL, ref)
if node.ref != "" {
return fmt.Sprintf("git::%s?ref=%s&depth=1", baseURL, node.ref)
}
// When no ref is specified, omit it entirely to let git clone the default branch
return fmt.Sprintf("git::%s?depth=1", baseURL)
}

// getOrCloneRepo returns the path to a cached git repository.
Expand Down Expand Up @@ -164,11 +165,16 @@ func (node *GitNode) ReadContext(ctx context.Context) ([]byte, error) {
}

// Build path to Taskfile in the cached repo
taskfilePath := node.path
if taskfilePath == "" {
taskfilePath = "Taskfile.yml"
// If node.path is empty, search in repo root; otherwise search in the specified path
// fsext.SearchPath handles both files and directories (searching for DefaultTaskfiles)
searchPath := repoDir
if node.path != "" {
searchPath = filepath.Join(repoDir, node.path)
}
filePath, err := fsext.SearchPath(searchPath, DefaultTaskfiles)
if err != nil {
return nil, err
}
filePath := filepath.Join(repoDir, taskfilePath)

// Read file from cached repo
b, err := os.ReadFile(filePath)
Expand Down Expand Up @@ -230,7 +236,7 @@ func (node *GitNode) repoCacheKey() string {

ref := node.ref
if ref == "" {
ref = "HEAD"
ref = "_default_" // Placeholder for the remote's default branch
}

return filepath.Join(node.url.Host, repoPath, ref)
Expand Down
12 changes: 6 additions & 6 deletions taskfile/node_git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ func TestGitNode_buildURL(t *testing.T) {
expectedURL: "git::https://github.com/foo/bar.git?ref=v1.0.0&depth=1",
},
{
name: "HTTPS without ref (uses remote HEAD)",
name: "HTTPS without ref (uses remote default branch)",
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml",
expectedURL: "git::https://github.com/foo/bar.git?ref=HEAD&depth=1",
expectedURL: "git::https://github.com/foo/bar.git?depth=1",
},
{
name: "SSH with directory path",
Expand Down Expand Up @@ -198,20 +198,20 @@ func TestRepoCacheKey_DifferentRepos(t *testing.T) {
assert.NotEqual(t, key1, key2, "Different repos should generate different cache keys")
}

func TestRepoCacheKey_NoRefVsHead(t *testing.T) {
func TestRepoCacheKey_NoRefVsExplicitRef(t *testing.T) {
t.Parallel()

// No ref (defaults to HEAD) vs explicit HEAD should have SAME cache key
// No ref (uses default branch) vs explicit ref should have DIFFERENT cache keys
node1, err := NewGitNode("https://github.com/foo/bar.git//file.yml", "", false)
require.NoError(t, err)

node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=HEAD", "", false)
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)

key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()

assert.Equal(t, key1, key2, "No ref and explicit HEAD should generate same cache key")
assert.NotEqual(t, key1, key2, "No ref and explicit ref should generate different cache keys")
}

func TestRepoCacheKey_SSHvsHTTPS(t *testing.T) {
Expand Down
Loading