返回 DeepSeek-Reasonix
config_programs_test.go
根目录 / internal / gitcmd / config_programs_test.go
1 package gitcmd
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 )
9
10 // Each probe builds a repository whose own config names a marker script, runs
11 // the host invocation the product uses, and fails if the marker appears —
12 // covering config keys beyond the driver list (signatures, lazy fetch, submodules).
13
14 func (f *repoFixture) payloadExit() string {
15 f.t.Helper()
16 path := filepath.Join(f.t.TempDir(), "payload-exit.sh")
17 script := "#!/bin/sh\necho ran >> '" + f.marker + "'\nexit 1\n"
18 if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
19 f.t.Fatal(err)
20 }
21 return path
22 }
23
24 // worktree merge-back (merge_autocommit.go, merge_commit.go) and studio's
25 // candidate.go run commit-tree; commit.gpgSign from .git/config makes it sign.
26 func TestReviewGapCommitTreeHonoursRepoGpgSign(t *testing.T) {
27 f := newRepoFixture(t, "", map[string]string{"f.txt": "one\n"})
28 p := f.payloadExit()
29 f.appendConfig("config", "[commit]\n\tgpgSign = true\n[gpg]\n\tprogram = "+p+"\n")
30 tree := strings.TrimSpace(f.plain("rev-parse", "HEAD^{tree}"))
31 _, _ = f.run("-c", "user.name=Reasonix", "-c", "user.email=r@local", "commit-tree", tree, "-p", "HEAD", "-m", "x")
32 f.assertNotExecuted()
33 }
34
35 // merge_commit.go runs `merge --no-ff --no-commit --no-verify <worktreeHead>`;
36 // merge.verifySignatures from .git/config verifies the merged tip.
37 func TestReviewGapMergeHonoursRepoVerifySignatures(t *testing.T) {
38 f := newRepoFixture(t, "", map[string]string{"f.txt": "one\n"})
39 f.plain("checkout", "--quiet", "-b", "side")
40 f.write("g.txt", "g\n")
41 f.plain("add", "-A")
42 f.plain("commit", "--quiet", "-m", "side")
43 signCommitHeader(f)
44 f.plain("checkout", "--quiet", "main")
45 p := f.payloadExit()
46 f.appendConfig("config", "[merge]\n\tverifySignatures = true\n[gpg]\n\tprogram = "+p+"\n")
47 _, _ = f.run("-c", "user.name=Reasonix", "-c", "user.email=r@local", "merge", "--no-ff", "--no-commit", "--no-verify", "side")
48 f.assertNotExecuted()
49 }
50
51 // A promisor remote in .git/config turns a missing blob into a lazy fetch, a
52 // network command run inside the repository with its transport config.
53 func TestReviewGapLazyFetchRunsRepoUploadPack(t *testing.T) {
54 for _, tc := range []struct {
55 name string
56 run func(f *repoFixture)
57 }{
58 {"diff HEAD", func(f *repoFixture) { _, _ = f.run("diff", "HEAD", "--", "f.txt") }},
59 {"diff --numstat HEAD", func(f *repoFixture) { _, _ = f.run("diff", "--numstat", "HEAD", "--") }},
60 {"worktree add", func(f *repoFixture) {
61 _, _ = f.run("worktree", "add", "--detach", filepath.Join(f.t.TempDir(), "wt"), "HEAD")
62 }},
63 } {
64 t.Run(tc.name, func(t *testing.T) {
65 f := newRepoFixture(t, "", map[string]string{"f.txt": "one\n"})
66 blob := strings.TrimSpace(f.plain("rev-parse", "HEAD:f.txt"))
67 if err := os.Remove(filepath.Join(f.dir, ".git", "objects", blob[:2], blob[2:])); err != nil {
68 t.Fatal(err)
69 }
70 p := f.payloadExit()
71 f.appendConfig("config", "[core]\n\trepositoryformatversion = 1\n[extensions]\n\tpartialClone = origin\n"+
72 "[remote \"origin\"]\n\turl = "+t.TempDir()+"\n\tpromisor = true\n\tuploadpack = "+p+"\n")
73 f.write("f.txt", "two\n")
74 tc.run(f)
75 f.assertNotExecuted()
76 })
77 }
78 }
79
80 // desktop workspace_git_branches.go runs `checkout <branch>`; submodule.recurse
81 // in .git/config makes it check out the submodule, whose own config is not
82 // listed.
83 func TestReviewGapCheckoutRecursesIntoSubmoduleDrivers(t *testing.T) {
84 sub := newRepoFixture(t, "s.txt filter=pwn\n", map[string]string{"s.txt": "v1\n"})
85 f := newRepoFixture(t, "", map[string]string{"top.txt": "top\n"})
86 f.plain("-c", "protocol.file.allow=always", "submodule", "--quiet", "add", sub.dir, "sm")
87 f.plain("commit", "--quiet", "-m", "add submodule")
88 f.plain("checkout", "--quiet", "-b", "other")
89 sub.write("s.txt", "v2\n")
90 sub.plain("commit", "--quiet", "-am", "v2")
91 f.plainIn(filepath.Join(f.dir, "sm"), "-c", "protocol.file.allow=always", "fetch", "--quiet")
92 f.plainIn(filepath.Join(f.dir, "sm"), "checkout", "--quiet", "origin/main")
93 f.plain("commit", "--quiet", "-am", "bump")
94 f.plain("checkout", "--quiet", "main")
95 f.plain("submodule", "--quiet", "update")
96 f.appendConfig("modules/sm/config", "[filter \"pwn\"]\n\tsmudge = "+f.payload()+"\n\tclean = "+f.payload()+"\n")
97 f.appendConfig("config", "[submodule]\n\trecurse = true\n")
98 _, _ = f.run("checkout", "other")
99 f.assertNotExecuted()
100 }
101
102 // diff.submodule=diff makes the superproject's diff start `git diff` inside
103 // the submodule, where that submodule's diff settings apply.
104 func TestReviewGapDiffSubmoduleInlineDiff(t *testing.T) {
105 sub := newRepoFixture(t, "s.txt diff=tc\n", map[string]string{"s.txt": "v1\n"})
106 f := newRepoFixture(t, "", map[string]string{"top.txt": "top\n"})
107 f.plain("-c", "protocol.file.allow=always", "submodule", "--quiet", "add", sub.dir, "sm")
108 f.plain("commit", "--quiet", "-m", "add submodule")
109 sub.write("s.txt", "v2\n")
110 sub.plain("commit", "--quiet", "-am", "v2")
111 f.plainIn(filepath.Join(f.dir, "sm"), "-c", "protocol.file.allow=always", "fetch", "--quiet")
112 f.plainIn(filepath.Join(f.dir, "sm"), "checkout", "--quiet", "origin/main")
113 p := f.payload()
114 f.appendConfig("modules/sm/config", "[diff]\n\texternal = "+p+"\n[diff \"tc\"]\n\ttextconv = "+p+"\n")
115 f.appendConfig("config", "[diff]\n\tsubmodule = diff\n[status]\n\tsubmoduleSummary = true\n")
116 out, _ := f.run("diff", "HEAD")
117 t.Logf("diff output: %q", out)
118 _, _ = f.run("diff", "--numstat", "HEAD", "--")
119 _, _ = f.run("status", "--porcelain=v1")
120 f.assertNotExecuted()
121 }
122
123 // Plugin install runs `git clone <github url> <tmp>` with dir "" (process cwd),
124 // not Detached. Probe whether clone reads the cwd repository's url/protocol.
125 func TestReviewGapPluginCloneFromRepositoryCwd(t *testing.T) {
126 f := newRepoFixture(t, "", map[string]string{"f.txt": "one\n"})
127 p := f.payloadExit()
128 f.appendConfig("config", "[url \"ext::"+p+" \"]\n\tinsteadOf = https://github.com/\n[protocol \"ext\"]\n\tallow = always\n"+
129 "[credential]\n\thelper = !"+p+"\n[core]\n\tsshCommand = "+p+"\n\taskPass = "+p+"\n")
130 t.Chdir(f.dir)
131 cmd := CommandWithConfig(f.ctx, "", []string{"core.autocrlf=false"}, "clone", "--depth=1", "https://github.com/example/none.git", filepath.Join(t.TempDir(), "c"))
132 cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0")
133 _, _ = cmd.CombinedOutput()
134 f.assertNotExecuted()
135 }
136
137 func (f *repoFixture) plainIn(dir string, args ...string) string {
138 f.t.Helper()
139 old := f.dir
140 f.dir = dir
141 defer func() { f.dir = old }()
142 return f.plain(args...)
143 }
144
145 func TestReviewGapSubmoduleSplit(t *testing.T) {
146 for _, tc := range []struct {
147 name, subcfg, supercfg string
148 args []string
149 }{
150 {"diff external via diff.submodule=diff", "[diff]\n\texternal = %P\n", "[diff]\n\tsubmodule = diff\n", []string{"diff", "HEAD"}},
151 {"textconv via diff.submodule=diff", "[diff \"tc\"]\n\ttextconv = %P\n", "[diff]\n\tsubmodule = diff\n", []string{"diff", "HEAD"}},
152 {"status submoduleSummary gpg", "[log]\n\tshowSignature = true\n[gpg]\n\tprogram = %P\n", "[status]\n\tsubmoduleSummary = true\n", []string{"status", "--porcelain=v1"}},
153 {"status submoduleSummary plain status", "[diff]\n\texternal = %P\n[diff \"tc\"]\n\ttextconv = %P\n", "[status]\n\tsubmoduleSummary = true\n", []string{"status"}},
154 } {
155 t.Run(tc.name, func(t *testing.T) {
156 sub := newRepoFixture(t, "s.txt diff=tc\n", map[string]string{"s.txt": "v1\n"})
157 f := newRepoFixture(t, "", map[string]string{"top.txt": "top\n"})
158 f.plain("-c", "protocol.file.allow=always", "submodule", "--quiet", "add", sub.dir, "sm")
159 f.plain("commit", "--quiet", "-m", "add submodule")
160 sub.write("s.txt", "v2\n")
161 sub.plain("commit", "--quiet", "-am", "v2")
162 f.plainIn(filepath.Join(f.dir, "sm"), "-c", "protocol.file.allow=always", "fetch", "--quiet")
163 f.plainIn(filepath.Join(f.dir, "sm"), "checkout", "--quiet", "origin/main")
164 p := f.payload()
165 f.appendConfig("modules/sm/config", strings.ReplaceAll(tc.subcfg, "%P", p))
166 f.appendConfig("config", tc.supercfg)
167 out, _ := f.run(tc.args...)
168 t.Logf("out: %q", out)
169 f.assertNotExecuted()
170 })
171 }
172 }
173
173 lines GO