From 016e068c60ac3a9bf37bcd011628eead06e4a852 Mon Sep 17 00:00:00 2001 From: Brandon Martin Date: Mon, 24 Nov 2025 22:21:19 +0000 Subject: [PATCH] Fix: Enable git worktree support and improve pr create error handling (#850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Tea commands fail when run from git worktrees with the error: Remote repository required: Specify ID via --repo or execute from a local git repo. Even though the worktree is in a valid git repository with remotes configured. Additionally, `tea pr create` was missing context validation, showing cryptic errors like `"path segment [0] is empty"` instead of helpful messages. ## Root Cause 1. **Worktree issue**: go-git's `PlainOpenWithOptions` was not configured to read the `commondir` file that git worktrees use. This file points to the main repository's `.git` directory where remotes are actually stored (worktrees don't have their own remotes). 2. **PR create issue**: Missing `ctx.Ensure()` validation meant errors weren't caught early with clear messages. ## Solution ### 1. Enable worktree support (`modules/git/repo.go`) ```go EnableDotGitCommonDir: true, // Enable commondir support for worktrees This tells go-git to: - Read the commondir file in .git/worktrees//commondir - Follow the reference (typically ../..) to the main repository - Load remotes from the main repo's config 2. Add context validation (cmd/pulls/create.go) ctx.Ensure(context.CtxRequirement{ LocalRepo: true, RemoteRepo: true, }) Provides clear error messages and matches the pattern used in pr checkout (fixed in commit 0970b945 from 2020). 3. Add test coverage (modules/git/repo_test.go) - Creates a real git repository with a worktree - Verifies that RepoFromPath() can open the worktree - Confirms that Config() correctly reads remotes from main repo Test Results Without fix: ❌ FAIL: Should NOT be empty, but was map[] With fix: ✅ PASS: TestRepoFromPath_Worktree (0.12s) Manual test in worktree: cd /path/to/worktree tea pr create --title "test" # Now works! ✅ Checklist - Tested manually in a git worktree - Added test case that fails without the fix - All existing tests pass Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/gitea/tea/pulls/850 Reviewed-by: techknowlogick Co-authored-by: Brandon Martin Co-committed-by: Brandon Martin --- cmd/pulls/create.go | 4 +++ modules/git/repo.go | 3 +- modules/git/repo_test.go | 63 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 modules/git/repo_test.go diff --git a/cmd/pulls/create.go b/cmd/pulls/create.go index 14a1e67..6bd4a38 100644 --- a/cmd/pulls/create.go +++ b/cmd/pulls/create.go @@ -42,6 +42,10 @@ var CmdPullsCreate = cli.Command{ func runPullsCreate(_ stdctx.Context, cmd *cli.Command) error { ctx := context.InitCommand(cmd) + ctx.Ensure(context.CtxRequirement{ + LocalRepo: true, + RemoteRepo: true, + }) // no args -> interactive mode if ctx.NumFlags() == 0 { diff --git a/modules/git/repo.go b/modules/git/repo.go index c3b6c86..8baec1f 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -24,7 +24,8 @@ func RepoFromPath(path string) (*TeaRepo, error) { path = "./" } repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{ - DetectDotGit: true, + DetectDotGit: true, + EnableDotGitCommonDir: true, // Enable commondir support for worktrees }) if err != nil { return nil, err diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go new file mode 100644 index 0000000..586caaa --- /dev/null +++ b/modules/git/repo_test.go @@ -0,0 +1,63 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRepoFromPath_Worktree(t *testing.T) { + // Create a temporary directory for test + tmpDir, err := os.MkdirTemp("", "tea-worktree-test-*") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + mainRepoPath := filepath.Join(tmpDir, "main-repo") + worktreePath := filepath.Join(tmpDir, "worktree") + + // Initialize main repository + cmd := exec.Command("git", "init", mainRepoPath) + assert.NoError(t, cmd.Run()) + + // Configure git for the test + cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.email", "test@example.com") + assert.NoError(t, cmd.Run()) + cmd = exec.Command("git", "-C", mainRepoPath, "config", "user.name", "Test User") + assert.NoError(t, cmd.Run()) + + // Add a remote to the main repository + cmd = exec.Command("git", "-C", mainRepoPath, "remote", "add", "origin", "https://gitea.com/owner/repo.git") + assert.NoError(t, cmd.Run()) + + // Create an initial commit (required for worktree) + readmePath := filepath.Join(mainRepoPath, "README.md") + err = os.WriteFile(readmePath, []byte("# Test Repo\n"), 0644) + assert.NoError(t, err) + cmd = exec.Command("git", "-C", mainRepoPath, "add", "README.md") + assert.NoError(t, cmd.Run()) + cmd = exec.Command("git", "-C", mainRepoPath, "commit", "-m", "Initial commit") + assert.NoError(t, cmd.Run()) + + // Create a worktree + cmd = exec.Command("git", "-C", mainRepoPath, "worktree", "add", worktreePath, "-b", "test-branch") + assert.NoError(t, cmd.Run()) + + // Test: Open repository from worktree path + repo, err := RepoFromPath(worktreePath) + assert.NoError(t, err, "Should be able to open worktree") + + // Test: Read config from worktree (should read from main repo's config) + config, err := repo.Config() + assert.NoError(t, err, "Should be able to read config") + + // Verify that remotes are accessible from worktree + assert.NotEmpty(t, config.Remotes, "Should be able to read remotes from worktree") + assert.Contains(t, config.Remotes, "origin", "Should have origin remote") + assert.Equal(t, "https://gitea.com/owner/repo.git", config.Remotes["origin"].URLs[0], "Should have correct remote URL") +}