返回 DeepSeek-Reasonix
nested_repo_test.go
根目录 / internal / worktree / nested_repo_test.go
1 package worktree
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9 "testing"
10 )
11
12 // What a worktree gains after the host created it — a nested repository with
13 // its own local filter, a .git file naming another git dir — never runs, and
14 // the merge still reads the identities pinned at creation.
15
16 func markerPayload(t *testing.T) (marker, payload string) {
17 t.Helper()
18 marker = filepath.Join(t.TempDir(), "executed")
19 payload = filepath.Join(t.TempDir(), "payload.sh")
20 if err := os.WriteFile(payload, []byte("#!/bin/sh\necho ran >> '"+marker+"'\ncat\n"), 0o755); err != nil {
21 t.Fatal(err)
22 }
23 return marker, payload
24 }
25
26 func requireMarkerAbsent(t *testing.T, marker, when string) {
27 t.Helper()
28 if _, err := os.Stat(marker); err == nil {
29 t.Fatalf("%s ran a repository-configured filter", when)
30 }
31 }
32
33 func requirePOSIXPayload(t *testing.T) {
34 t.Helper()
35 requireGit(t)
36 if runtime.GOOS == "windows" {
37 t.Skip("payload script is POSIX shell")
38 }
39 }
40
41 func TestMergeBackAutoCommitDoesNotEnterGitlinkRepository(t *testing.T) {
42 requirePOSIXPayload(t)
43 repo := initRepo(t)
44 managed := t.TempDir()
45 created, err := Create(context.Background(), opened(t, repo), managed)
46 if err != nil {
47 t.Fatal(err)
48 }
49 wt := created.WorktreeRoot
50 sub := filepath.Join(wt, "sub")
51 if err := os.MkdirAll(sub, 0o755); err != nil {
52 t.Fatal(err)
53 }
54 marker, payload := markerPayload(t)
55 gitTest(t, sub, "init", "-q")
56 gitCommitFile(t, sub, ".gitattributes", "f.txt filter=pwn\n", "attributes")
57 gitCommitFile(t, sub, "f.txt", "a\n", "nested")
58 gitTest(t, sub, "config", "filter.pwn.clean", payload)
59 gitTest(t, sub, "config", "filter.pwn.smudge", payload)
60 gitlink := gitTest(t, sub, "rev-parse", "HEAD")
61 gitTest(t, wt, "update-index", "--add", "--cacheinfo", "160000,"+gitlink+",sub")
62 gitTest(t, wt, "commit", "-q", "-m", "gitlink")
63 if err := os.WriteFile(filepath.Join(sub, "f.txt"), []byte("b\n"), 0o644); err != nil {
64 t.Fatal(err)
65 }
66 if err := os.WriteFile(filepath.Join(wt, "feature.go"), []byte("package feature\n"), 0o644); err != nil {
67 t.Fatal(err)
68 }
69
70 inspection := inspectMergeTest(t, created.WorkspaceRoot, managed)
71 requireMarkerAbsent(t, marker, "InspectMerge")
72 request := requestFromInspection(inspection)
73 request.AutoCommitDirty = true
74 result, err := MergeBack(context.Background(), managed, request)
75 if err != nil || !result.Merged {
76 t.Fatalf("MergeBack = %+v, %v", result, err)
77 }
78 requireMarkerAbsent(t, marker, "MergeBack")
79 tree := gitTest(t, repo, "ls-tree", "-r", result.MergedCommit)
80 for _, want := range []string{"160000 commit " + gitlink + "\tsub", "\tfeature.go", "\tREADME.md"} {
81 if !strings.Contains(tree, want) {
82 t.Fatalf("merged tree:\n%s\nwant %q", tree, want)
83 }
84 }
85 }
86
87 // The worktree's own .git file is the agent's to write; the merge reads the
88 // git dir recorded when the host added the worktree.
89 func TestMergeBackKeepsWorktreeIdentityPinnedAtCreation(t *testing.T) {
90 requirePOSIXPayload(t)
91 repo := initRepo(t)
92 managed := t.TempDir()
93 created, err := Create(context.Background(), opened(t, repo), managed)
94 if err != nil {
95 t.Fatal(err)
96 }
97 wt := created.WorktreeRoot
98 marker, payload := markerPayload(t)
99 evil := filepath.Join(t.TempDir(), "evil")
100 gitTest(t, repo, "clone", "-q", "--bare", repo, evil)
101 cfg, err := os.OpenFile(filepath.Join(evil, "config"), os.O_APPEND|os.O_WRONLY, 0)
102 if err != nil {
103 t.Fatal(err)
104 }
105 _, _ = cfg.WriteString("[probe]\n\tx = substitute\n[filter \"pwn\"]\n\tclean = " + payload + "\n\tsmudge = " + payload + "\n")
106 _ = cfg.Close()
107 if err := os.WriteFile(filepath.Join(wt, ".git"), []byte("gitdir: "+evil+"\n"), 0o644); err != nil {
108 t.Fatal(err)
109 }
110 if got := gitTest(t, wt, "config", "--get", "probe.x"); got != "substitute" {
111 t.Fatalf("discovery in the worktree read %q, want the substitute (the case being guarded)", got)
112 }
113 if err := os.WriteFile(filepath.Join(wt, ".gitattributes"), []byte("*.go filter=pwn\n"), 0o644); err != nil {
114 t.Fatal(err)
115 }
116 if err := os.WriteFile(filepath.Join(wt, "feature.go"), []byte("package feature\n"), 0o644); err != nil {
117 t.Fatal(err)
118 }
119
120 inspection := inspectMergeTest(t, created.WorkspaceRoot, managed)
121 if inspection.WorktreeBranch != created.Branch {
122 t.Fatalf("inspection branch = %q, want %q", inspection.WorktreeBranch, created.Branch)
123 }
124 request := requestFromInspection(inspection)
125 request.AutoCommitDirty = true
126 result, err := MergeBack(context.Background(), managed, request)
127 if err != nil || !result.Merged {
128 t.Fatalf("MergeBack = %+v, %v", result, err)
129 }
130 requireMarkerAbsent(t, marker, "MergeBack")
131 if _, err := os.Stat(filepath.Join(repo, "feature.go")); err != nil {
132 t.Fatalf("merged file missing from source: %v", err)
133 }
134 }
135
135 lines GO