499 lines
19 KiB
Go
499 lines
19 KiB
Go
package worktree
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCreateWorktree_CheckoutBranchDirectCheckout(t *testing.T) {
|
|
cfg := newTestConfig(t)
|
|
log := newTestLogger()
|
|
store := newMockStore()
|
|
|
|
mgr, err := NewManager(cfg, store, log)
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
|
|
repoPath := initGitRepoForWorktreeTest(t)
|
|
|
|
// First worktree with checkout branch should use the branch directly.
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-1",
|
|
SessionID: "session-1",
|
|
TaskTitle: "PR Review",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
CheckoutBranch: "feature/pr-branch",
|
|
TaskDirName: "task-1",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() unexpected error: %v", err)
|
|
}
|
|
if wt.Branch != "feature/pr-branch" {
|
|
t.Fatalf("expected direct checkout branch %q, got %q", "feature/pr-branch", wt.Branch)
|
|
}
|
|
if wt.FetchWarning == "" {
|
|
t.Fatal("expected fetch warning when origin is unavailable and local branch is reused")
|
|
}
|
|
|
|
gotBranch := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "--abbrev-ref", "HEAD"))
|
|
if gotBranch != "feature/pr-branch" {
|
|
t.Fatalf("worktree HEAD branch = %q, want %q", gotBranch, "feature/pr-branch")
|
|
}
|
|
|
|
prHeadSHA := strings.TrimSpace(runGit(t, repoPath, "rev-parse", "feature/pr-branch"))
|
|
worktreeSHA := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "HEAD"))
|
|
if worktreeSHA != prHeadSHA {
|
|
t.Fatalf("worktree HEAD SHA = %q, want %q", worktreeSHA, prHeadSHA)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_CheckoutBranchFallsBackToSuffix(t *testing.T) {
|
|
cfg := newTestConfig(t)
|
|
log := newTestLogger()
|
|
store := newMockStore()
|
|
|
|
mgr, err := NewManager(cfg, store, log)
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
|
|
repoPath := initGitRepoForWorktreeTest(t)
|
|
|
|
// First worktree checks out the branch directly.
|
|
wt1, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-1",
|
|
SessionID: "session-1",
|
|
TaskTitle: "PR Review 1",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
CheckoutBranch: "feature/pr-branch",
|
|
TaskDirName: "task-1",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() first worktree: %v", err)
|
|
}
|
|
if wt1.Branch != "feature/pr-branch" {
|
|
t.Fatalf("first worktree: expected direct branch, got %q", wt1.Branch)
|
|
}
|
|
|
|
// Second worktree with the same checkout branch should fall back to a suffixed name.
|
|
wt2, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-2",
|
|
SessionID: "session-2",
|
|
TaskTitle: "PR Review 2",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
CheckoutBranch: "feature/pr-branch",
|
|
TaskDirName: "task-2",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() second worktree: %v", err)
|
|
}
|
|
if wt2.Branch == "feature/pr-branch" {
|
|
t.Fatal("second worktree should NOT use the checkout branch directly")
|
|
}
|
|
if !strings.HasPrefix(wt2.Branch, "feature/pr-branch-") {
|
|
t.Fatalf("expected suffixed branch like %q, got %q", "feature/pr-branch-xxx", wt2.Branch)
|
|
}
|
|
|
|
// Both worktrees should point to the same commit.
|
|
sha1 := strings.TrimSpace(runGit(t, wt1.Path, "rev-parse", "HEAD"))
|
|
sha2 := strings.TrimSpace(runGit(t, wt2.Path, "rev-parse", "HEAD"))
|
|
if sha1 != sha2 {
|
|
t.Fatalf("worktree SHAs differ: wt1=%q, wt2=%q", sha1, sha2)
|
|
}
|
|
}
|
|
|
|
// TestCreateWorktree_CheckoutBranchNoFetchWarningWithRemote verifies that when
|
|
// a branch is checked out in one worktree, creating a second worktree for the
|
|
// same branch does NOT produce a fetch warning. The fetch retries with a
|
|
// remote-tracking ref update instead of trying to update the checked-out local branch.
|
|
func TestCreateWorktree_CheckoutBranchNoFetchWarningWithRemote(t *testing.T) {
|
|
cfg := newTestConfig(t)
|
|
log := newTestLogger()
|
|
store := newMockStore()
|
|
|
|
mgr, err := NewManager(cfg, store, log)
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
|
|
repoPath := initGitRepoWithRemote(t)
|
|
|
|
// First worktree checks out the branch directly.
|
|
wt1, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-1",
|
|
SessionID: "session-1",
|
|
TaskTitle: "PR Review 1",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
CheckoutBranch: "feature/pr-branch",
|
|
TaskDirName: "task-1",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() first worktree: %v", err)
|
|
}
|
|
if wt1.Branch != "feature/pr-branch" {
|
|
t.Fatalf("first worktree: expected direct branch, got %q", wt1.Branch)
|
|
}
|
|
if wt1.FetchWarning != "" {
|
|
t.Fatalf("first worktree: unexpected fetch warning: %s", wt1.FetchWarning)
|
|
}
|
|
|
|
// Second worktree with the same checkout branch should fall back to a
|
|
// suffixed name but should NOT produce a fetch warning — the fetch retries
|
|
// via remote-tracking ref when the local branch is checked out.
|
|
wt2, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-2",
|
|
SessionID: "session-2",
|
|
TaskTitle: "PR Review 2",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "main",
|
|
CheckoutBranch: "feature/pr-branch",
|
|
TaskDirName: "task-2",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() second worktree: %v", err)
|
|
}
|
|
if wt2.Branch == "feature/pr-branch" {
|
|
t.Fatal("second worktree should NOT use the checkout branch directly")
|
|
}
|
|
if !strings.HasPrefix(wt2.Branch, "feature/pr-branch-") {
|
|
t.Fatalf("expected suffixed branch like %q, got %q", "feature/pr-branch-xxx", wt2.Branch)
|
|
}
|
|
if wt2.FetchWarning != "" {
|
|
t.Fatalf("second worktree: unexpected fetch warning: %s", wt2.FetchWarning)
|
|
}
|
|
|
|
// Both worktrees should point to the same commit (latest from origin).
|
|
sha1 := strings.TrimSpace(runGit(t, wt1.Path, "rev-parse", "HEAD"))
|
|
sha2 := strings.TrimSpace(runGit(t, wt2.Path, "rev-parse", "HEAD"))
|
|
if sha1 != sha2 {
|
|
t.Fatalf("worktree SHAs differ: wt1=%q, wt2=%q", sha1, sha2)
|
|
}
|
|
|
|
// Both worktrees should have upstream set to origin/feature/pr-branch
|
|
// so that "git rev-list branch...@{upstream}" gives correct ahead/behind counts.
|
|
const wantUpstream = "origin/feature/pr-branch"
|
|
upstream1 := strings.TrimSpace(runGit(t, wt1.Path, "rev-parse", "--abbrev-ref", "@{upstream}"))
|
|
if upstream1 != wantUpstream {
|
|
t.Fatalf("first worktree upstream = %q, want %q", upstream1, wantUpstream)
|
|
}
|
|
upstream2 := strings.TrimSpace(runGit(t, wt2.Path, "rev-parse", "--abbrev-ref", "@{upstream}"))
|
|
if upstream2 != wantUpstream {
|
|
t.Fatalf("second worktree upstream = %q, want %q", upstream2, wantUpstream)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_UsesAlreadyRefreshedRemoteWithoutNetwork(t *testing.T) {
|
|
cfg := newTestConfig(t)
|
|
mgr, err := NewManager(cfg, newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
repoPath := initGitRepoWithRemote(t)
|
|
// Leave the refreshed remote-tracking refs in place, but make any accidental
|
|
// network access fail. The worktree must materialize solely from local refs.
|
|
runGit(t, repoPath, "remote", "set-url", "origin", "https://127.0.0.1:1/never.git")
|
|
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-1", SessionID: "session-1", RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath, BaseBranch: "main", CheckoutBranch: "feature/pr-branch",
|
|
PullBeforeWorktree: true, RemoteSyncHandled: true,
|
|
TaskDirName: "task-1", RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() used the network after an authenticated refresh: %v", err)
|
|
}
|
|
if wt.Branch != "feature/pr-branch" || wt.FetchWarning != "" {
|
|
t.Fatalf("worktree = branch %q warning %q", wt.Branch, wt.FetchWarning)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_ManagedRefreshRunsOnlyForMaterialization(t *testing.T) {
|
|
mgr, err := NewManager(newTestConfig(t), newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
repoPath := initGitRepoWithRemote(t)
|
|
refreshCalls := 0
|
|
req := CreateRequest{
|
|
TaskID: "task-refresh", SessionID: "session-refresh", RepositoryID: "repo-refresh",
|
|
RepositoryPath: repoPath, BaseBranch: "main", PullBeforeWorktree: true,
|
|
TaskDirName: "task-refresh", RepoName: "repo-refresh",
|
|
RefreshRepository: func(context.Context) error {
|
|
refreshCalls++
|
|
return nil
|
|
},
|
|
}
|
|
if _, err := mgr.Create(context.Background(), req); err != nil {
|
|
t.Fatalf("first Create(): %v", err)
|
|
}
|
|
if refreshCalls != 1 {
|
|
t.Fatalf("refresh calls after materialization = %d, want 1", refreshCalls)
|
|
}
|
|
if _, err := mgr.Create(context.Background(), req); err != nil {
|
|
t.Fatalf("reusing Create(): %v", err)
|
|
}
|
|
if refreshCalls != 1 {
|
|
t.Fatalf("refresh calls after valid reuse = %d, want 1", refreshCalls)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_ManagedRefreshUsesRefreshedPRHead(t *testing.T) {
|
|
repoPath, wantSHA := initGitRepoWithPullRef(t, 974, "feature/fork-pr")
|
|
runGit(t, repoPath, "fetch", "origin", "pull/974/head:"+pullRequestSnapshotRef(974))
|
|
runGit(t, repoPath, "remote", "set-url", "origin", "https://127.0.0.1:1/never.git")
|
|
|
|
mgr, err := NewManager(newTestConfig(t), newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
refreshCalls := 0
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-pr-refresh", SessionID: "session-pr-refresh", RepositoryID: "repo-pr-refresh",
|
|
RepositoryPath: repoPath, BaseBranch: "main", CheckoutBranch: "feature/fork-pr", PRNumber: 974,
|
|
PullBeforeWorktree: true, TaskDirName: "task-pr-refresh", RepoName: "repo-pr-refresh",
|
|
RefreshRepository: func(context.Context) error {
|
|
refreshCalls++
|
|
return nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create(): %v", err)
|
|
}
|
|
if refreshCalls != 1 {
|
|
t.Fatalf("refresh calls = %d, want 1", refreshCalls)
|
|
}
|
|
if got := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "HEAD")); got != wantSHA {
|
|
t.Fatalf("worktree HEAD = %q, want refreshed PR head %q", got, wantSHA)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_ManagedRefreshUsesRemoteWhenLocalCheckoutBranchIsBehind(t *testing.T) {
|
|
repoPath := initGitRepoWithRemote(t)
|
|
wantSHA := advanceRemoteBranch(t, repoPath, "feature/pr-branch")
|
|
runGit(t, repoPath, "remote", "set-url", "origin", "https://127.0.0.1:1/never.git")
|
|
|
|
mgr, err := NewManager(newTestConfig(t), newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-managed-branch", SessionID: "session-managed-branch", RepositoryID: "repo-managed-branch",
|
|
RepositoryPath: repoPath, BaseBranch: "main", CheckoutBranch: "feature/pr-branch",
|
|
PullBeforeWorktree: true, TaskDirName: "task-managed-branch", RepoName: "repo-managed-branch",
|
|
RefreshRepository: func(context.Context) error { return nil },
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create(): %v", err)
|
|
}
|
|
if got := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "HEAD")); got != wantSHA {
|
|
t.Fatalf("worktree HEAD = %q, want refreshed branch head %q", got, wantSHA)
|
|
}
|
|
}
|
|
|
|
func TestCreateWorktree_ManagedRefreshUsesRemotePRHeadWhenLocalCheckoutBranchIsBehind(t *testing.T) {
|
|
repoPath, wantSHA := initManagedPRCheckoutBranch(t, 976, "feature/managed-fork-pr")
|
|
runGit(t, repoPath, "remote", "set-url", "origin", "https://127.0.0.1:1/never.git")
|
|
|
|
mgr, err := NewManager(newTestConfig(t), newMockStore(), newTestLogger())
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-managed-pr-behind", SessionID: "session-managed-pr-behind", RepositoryID: "repo-managed-pr-behind",
|
|
RepositoryPath: repoPath, BaseBranch: "main", CheckoutBranch: "feature/managed-fork-pr", PRNumber: 976,
|
|
PullBeforeWorktree: true, TaskDirName: "task-managed-pr-behind", RepoName: "repo-managed-pr-behind",
|
|
RefreshRepository: func(context.Context) error { return nil },
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create(): %v", err)
|
|
}
|
|
if got := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "HEAD")); got != wantSHA {
|
|
t.Fatalf("worktree HEAD = %q, want refreshed PR head %q", got, wantSHA)
|
|
}
|
|
}
|
|
|
|
// TestCreateWorktree_RemoteBaseRefDoesNotSetUpstream verifies that when a worktree
|
|
// is created with a remote-tracking ref as the base (e.g. origin/feature/foo),
|
|
// the new branch does NOT inherit upstream tracking from that ref.
|
|
// This was a bug: git auto-sets upstream when branching from a remote ref,
|
|
// causing the new task branch to track the original remote branch.
|
|
func TestCreateWorktree_RemoteBaseRefDoesNotSetUpstream(t *testing.T) {
|
|
cfg := newTestConfig(t)
|
|
log := newTestLogger()
|
|
store := newMockStore()
|
|
|
|
mgr, err := NewManager(cfg, store, log)
|
|
if err != nil {
|
|
t.Fatalf("NewManager failed: %v", err)
|
|
}
|
|
|
|
repoPath := initGitRepoWithRemote(t)
|
|
|
|
// Create a worktree using a remote-tracking ref as the base branch.
|
|
// This simulates the user selecting a remote branch in the UI, or
|
|
// PullBeforeWorktree resolving to origin/feature/pr-branch.
|
|
wt, err := mgr.Create(context.Background(), CreateRequest{
|
|
TaskID: "task-1",
|
|
SessionID: "session-1",
|
|
TaskTitle: "New Feature",
|
|
RepositoryID: "repo-1",
|
|
RepositoryPath: repoPath,
|
|
BaseBranch: "origin/feature/pr-branch",
|
|
TaskDirName: "task-1",
|
|
RepoName: "repo-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() unexpected error: %v", err)
|
|
}
|
|
|
|
// The new branch should NOT have upstream tracking set.
|
|
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "@{upstream}")
|
|
cmd.Dir = wt.Path
|
|
if out, err := cmd.CombinedOutput(); err == nil {
|
|
t.Fatalf("expected no upstream for new task branch, but got %q", strings.TrimSpace(string(out)))
|
|
}
|
|
|
|
// The branch should still be based on the correct commit.
|
|
featureSHA := strings.TrimSpace(runGit(t, repoPath, "rev-parse", "origin/feature/pr-branch"))
|
|
worktreeSHA := strings.TrimSpace(runGit(t, wt.Path, "rev-parse", "HEAD"))
|
|
if worktreeSHA != featureSHA {
|
|
t.Fatalf("worktree HEAD SHA = %q, want %q (origin/feature/pr-branch)", worktreeSHA, featureSHA)
|
|
}
|
|
}
|
|
|
|
// initGitRepoWithRemote creates a bare "origin" repo, clones it, and creates
|
|
// a feature branch with a commit. Returns the clone path (has origin remote).
|
|
func initGitRepoWithRemote(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
// Create a bare repo to serve as "origin"
|
|
bareDir := filepath.Join(t.TempDir(), "origin.git")
|
|
runGit(t, t.TempDir(), "init", "--bare", "-b", "main", bareDir)
|
|
|
|
// Clone it to get a working repo with origin
|
|
cloneDir := filepath.Join(t.TempDir(), "clone")
|
|
cmd := exec.Command("git", "clone", bareDir, cloneDir)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git clone failed: %v\n%s", err, out)
|
|
}
|
|
runGit(t, cloneDir, "config", "user.email", "test@example.com")
|
|
runGit(t, cloneDir, "config", "user.name", "Test User")
|
|
runGit(t, cloneDir, "config", "commit.gpgsign", "false")
|
|
|
|
// Create initial commit on main
|
|
filePath := filepath.Join(cloneDir, "README.md")
|
|
if err := os.WriteFile(filePath, []byte("initial\n"), 0644); err != nil {
|
|
t.Fatalf("failed to write README.md: %v", err)
|
|
}
|
|
runGit(t, cloneDir, "add", "README.md")
|
|
runGit(t, cloneDir, "commit", "-m", "initial commit")
|
|
runGit(t, cloneDir, "push", "origin", "main")
|
|
|
|
// Create feature branch with a commit
|
|
runGit(t, cloneDir, "checkout", "-b", "feature/pr-branch")
|
|
if err := os.WriteFile(filePath, []byte("feature change\n"), 0644); err != nil {
|
|
t.Fatalf("failed to update README.md: %v", err)
|
|
}
|
|
runGit(t, cloneDir, "add", "README.md")
|
|
runGit(t, cloneDir, "commit", "-m", "feature commit")
|
|
runGit(t, cloneDir, "push", "origin", "feature/pr-branch")
|
|
|
|
// Go back to main so the branch isn't checked out in the main repo
|
|
runGit(t, cloneDir, "checkout", "main")
|
|
|
|
return cloneDir
|
|
}
|
|
|
|
func advanceRemoteBranch(t *testing.T, repoPath, branch string) string {
|
|
t.Helper()
|
|
originURL := strings.TrimSpace(runGit(t, repoPath, "remote", "get-url", "origin"))
|
|
remoteClone := filepath.Join(t.TempDir(), "remote-clone")
|
|
runGit(t, filepath.Dir(remoteClone), "clone", originURL, remoteClone)
|
|
runGit(t, remoteClone, "config", "user.email", "remote@example.com")
|
|
runGit(t, remoteClone, "config", "user.name", "Remote User")
|
|
runGit(t, remoteClone, "config", "commit.gpgsign", "false")
|
|
runGit(t, remoteClone, "checkout", branch)
|
|
writeRepoFile(t, remoteClone, "remote-refresh.txt", "refreshed\n")
|
|
runGit(t, remoteClone, "add", "remote-refresh.txt")
|
|
runGit(t, remoteClone, "commit", "-m", "refreshed branch commit")
|
|
wantSHA := strings.TrimSpace(runGit(t, remoteClone, "rev-parse", "HEAD"))
|
|
runGit(t, remoteClone, "push", "origin", branch)
|
|
runGit(t, repoPath, "fetch", "origin", branch)
|
|
return wantSHA
|
|
}
|
|
|
|
func initManagedPRCheckoutBranch(t *testing.T, prNumber int, branch string) (string, string) {
|
|
t.Helper()
|
|
repoPath, _ := initGitRepoWithPullRef(t, prNumber, branch)
|
|
prRef := fmt.Sprintf("pull/%d/head:%s", prNumber, pullRequestSnapshotRef(prNumber))
|
|
runGit(t, repoPath, "fetch", "origin", prRef)
|
|
runGit(t, repoPath, "branch", branch, pullRequestSnapshotRef(prNumber))
|
|
originURL := strings.TrimSpace(runGit(t, repoPath, "remote", "get-url", "origin"))
|
|
remoteClone := filepath.Join(t.TempDir(), "remote-pr-clone")
|
|
runGit(t, filepath.Dir(remoteClone), "clone", originURL, remoteClone)
|
|
runGit(t, remoteClone, "config", "user.email", "remote@example.com")
|
|
runGit(t, remoteClone, "config", "user.name", "Remote User")
|
|
runGit(t, remoteClone, "config", "commit.gpgsign", "false")
|
|
runGit(t, remoteClone, "fetch", "origin", prRef)
|
|
runGit(t, remoteClone, "checkout", "-b", branch, pullRequestSnapshotRef(prNumber))
|
|
writeRepoFile(t, remoteClone, "remote-pr-refresh.txt", "refreshed PR\n")
|
|
runGit(t, remoteClone, "add", "remote-pr-refresh.txt")
|
|
runGit(t, remoteClone, "commit", "-m", "refreshed PR commit")
|
|
wantSHA := strings.TrimSpace(runGit(t, remoteClone, "rev-parse", "HEAD"))
|
|
runGit(t, remoteClone, "push", "origin", "HEAD:"+pullHeadRef(prNumber))
|
|
runGit(t, repoPath, "fetch", "origin", prRef)
|
|
return repoPath, wantSHA
|
|
}
|
|
|
|
func initGitRepoForWorktreeTest(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
repoPath := t.TempDir()
|
|
runGit(t, repoPath, "init", "-b", "main")
|
|
runGit(t, repoPath, "config", "user.email", "test@example.com")
|
|
runGit(t, repoPath, "config", "user.name", "Test User")
|
|
runGit(t, repoPath, "config", "commit.gpgsign", "false")
|
|
|
|
filePath := filepath.Join(repoPath, "README.md")
|
|
if err := os.WriteFile(filePath, []byte("initial\n"), 0644); err != nil {
|
|
t.Fatalf("failed to write README.md: %v", err)
|
|
}
|
|
runGit(t, repoPath, "add", "README.md")
|
|
runGit(t, repoPath, "commit", "-m", "initial commit")
|
|
runGit(t, repoPath, "branch", "feature/pr-branch")
|
|
|
|
return repoPath
|
|
}
|
|
|
|
func runGit(t *testing.T, repoPath string, args ...string) string {
|
|
t.Helper()
|
|
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = repoPath
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("git %s failed: %v\n%s", strings.Join(args, " "), err, string(output))
|
|
}
|
|
return string(output)
|
|
}
|