From 618538f4a87dac9308fbe725a5ce60f2f681c3a8 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 23 Dec 2025 19:56:21 +0100 Subject: [PATCH 1/2] fix(taskfile): omit ref param when cloning remote git repos without explicit ref When no ref is specified in a remote Taskfile URL, go-getter was being passed ref=HEAD which git interprets as a literal branch name, causing "Remote branch HEAD not found" errors. Now we omit the ref parameter entirely when not specified, letting git clone the remote's default branch. --- taskfile/node_git.go | 12 ++++++------ taskfile/node_git_test.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/taskfile/node_git.go b/taskfile/node_git.go index eba1a5c646..7110a3cde2 100644 --- a/taskfile/node_git.go +++ b/taskfile/node_git.go @@ -104,13 +104,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. @@ -230,7 +230,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) diff --git a/taskfile/node_git_test.go b/taskfile/node_git_test.go index b6ae6bf271..71ea046511 100644 --- a/taskfile/node_git_test.go +++ b/taskfile/node_git_test.go @@ -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", @@ -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) { From bb90822ee036015d5e7178e234e94992dc172a09 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 23 Dec 2025 20:03:27 +0100 Subject: [PATCH 2/2] fix(taskfile): resolve directory paths in remote git includes When a remote Taskfile includes another Taskfile using a directory path (e.g., `taskfile: ./website`), use fsext.SearchPath to properly resolve it by searching for Taskfile.yml inside the directory. This also simplifies the logic by delegating all path resolution to SearchPath, which handles both file and directory paths. --- taskfile/node_git.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/taskfile/node_git.go b/taskfile/node_git.go index 7110a3cde2..62d429fe3d 100644 --- a/taskfile/node_git.go +++ b/taskfile/node_git.go @@ -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. @@ -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)