Skip to content

Commit f8646e0

Browse files
maria-rcksLucenx9
andauthored
fix(github): read text and mixed-case pull request templates (#25025)
Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>
1 parent 06ded0b commit f8646e0

2 files changed

Lines changed: 100 additions & 10 deletions

File tree

‎packages/source-control-github/src/server/gitHubChangeRequestTemplate.test.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,41 @@ it.effect("bounds template reads and marks truncated content", () =>
227227
}),
228228
),
229229
);
230+
231+
it.effect.each([
232+
".github/pull_request_template.txt",
233+
"PULL_REQUEST_TEMPLATE.TXT",
234+
"docs/PuLl_ReQuEsT_TeMpLaTe.Md",
235+
".github/PULL_REQUEST_TEMPLATE/template.TxT",
236+
])("reads committed text and mixed-case template $0 from a subdirectory", (relativePath) =>
237+
runWithTempDirectory((cwd) =>
238+
Effect.gen(function* () {
239+
const path = yield* Path.Path;
240+
yield* runGit(cwd, ["config", "grep.patternType", "fixed"]);
241+
yield* runGit(cwd, ["config", "color.grep", "always"]);
242+
yield* writeTemplate(cwd, relativePath, "committed template");
243+
yield* writeTemplate(cwd, "nested/README.md", "project");
244+
for (let index = 0; index < 600; index += 1) {
245+
yield* writeTemplate(cwd, `docs/${index}-${"a".repeat(180)}.md`, "guide");
246+
}
247+
yield* commitTemplates(cwd);
248+
yield* writeTemplate(cwd, relativePath, "uncommitted template");
249+
250+
assert.strictEqual(
251+
Option.getOrUndefined(yield* detectTemplate(path.join(cwd, "nested"))),
252+
"committed template",
253+
);
254+
}),
255+
),
256+
);
257+
258+
it.effect("does not guess between Markdown and text directory templates", () =>
259+
runWithTempDirectory((cwd) =>
260+
Effect.gen(function* () {
261+
yield* writeTemplate(cwd, ".github/PULL_REQUEST_TEMPLATE/a.md", "markdown");
262+
yield* writeTemplate(cwd, ".github/PULL_REQUEST_TEMPLATE/b.TXT", "text");
263+
yield* commitTemplates(cwd);
264+
assert.isTrue(Option.isNone(yield* detectTemplate(cwd)));
265+
}),
266+
),
267+
);

‎packages/source-control-github/src/server/gitHubChangeRequestTemplate.ts‎

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ const TRUNCATION_MARKER = "[truncated]";
1111
const TEMPLATE_PATHS = [
1212
".github/pull_request_template.md",
1313
".github/PULL_REQUEST_TEMPLATE.md",
14+
".github/pull_request_template.txt",
15+
".github/PULL_REQUEST_TEMPLATE.txt",
1416
"pull_request_template.md",
1517
"PULL_REQUEST_TEMPLATE.md",
18+
"pull_request_template.txt",
19+
"PULL_REQUEST_TEMPLATE.txt",
1620
"docs/pull_request_template.md",
1721
"docs/PULL_REQUEST_TEMPLATE.md",
22+
"docs/pull_request_template.txt",
23+
"docs/PULL_REQUEST_TEMPLATE.txt",
1824
] as const;
1925

2026
const TEMPLATE_DIRECTORIES = [
@@ -23,8 +29,6 @@ const TEMPLATE_DIRECTORIES = [
2329
"docs/PULL_REQUEST_TEMPLATE",
2430
] as const;
2531

26-
const TREE_PATHS = [...TEMPLATE_PATHS, ...TEMPLATE_DIRECTORIES] as const;
27-
2832
type ExecuteGit = SourceControlHost.SourceControlHost["Service"]["git"]["execute"];
2933

3034
interface TemplateTreeEntry {
@@ -105,7 +109,7 @@ const readTemplateDirectory = Effect.fn("gitHubChangeRequestTemplate.readTemplat
105109
return false;
106110
}
107111
const relativePath = entry.path.slice(prefix.length);
108-
return !relativePath.includes("/") && relativePath.toLowerCase().endsWith(".md");
112+
return !relativePath.includes("/") && /\.(?:md|txt)$/i.test(relativePath);
109113
});
110114

111115
const templates: string[] = [];
@@ -132,10 +136,50 @@ export const detect = Effect.fn("gitHubChangeRequestTemplate.detect")(function*
132136
) {
133137
return yield* Effect.gen(function* () {
134138
// Read committed regular blobs; worktree paths can become symlinks between validation and open.
139+
// ls-tree does not support case-insensitive pathspecs; grep discovers only matching names.
140+
const paths = yield* executeGit({
141+
operation: "gitHubChangeRequestTemplate.findTemplatePaths",
142+
cwd,
143+
args: [
144+
"grep",
145+
"-G",
146+
"--no-color",
147+
"-l",
148+
"-z",
149+
"--full-name",
150+
"--no-recurse-submodules",
151+
"-e",
152+
"^",
153+
treeish,
154+
"--",
155+
...TEMPLATE_PATHS.map((path) => `:(top,icase,literal)${path}`),
156+
],
157+
maxOutputBytes: TREE_LIST_MAX_BYTES,
158+
}).pipe(
159+
Effect.map((result) =>
160+
result.stdoutTruncated
161+
? []
162+
: result.stdout
163+
.split("\0")
164+
.filter((path) => path.startsWith(`${treeish}:`))
165+
.map((path) => path.slice(treeish.length + 1)),
166+
),
167+
// grep exits with status 1 when no nonempty templates match.
168+
Effect.orElseSucceed(() => []),
169+
);
135170
const result = yield* executeGit({
136171
operation: "gitHubChangeRequestTemplate.listTemplates",
137172
cwd,
138-
args: ["ls-tree", "-r", "-z", "--full-tree", treeish, "--", ...TREE_PATHS],
173+
args: [
174+
"ls-tree",
175+
"-r",
176+
"-z",
177+
"--full-tree",
178+
treeish,
179+
"--",
180+
...new Set([...TEMPLATE_PATHS, ...paths]),
181+
...TEMPLATE_DIRECTORIES,
182+
],
139183
maxOutputBytes: TREE_LIST_MAX_BYTES,
140184
appendTruncationMarker: true,
141185
});
@@ -144,12 +188,20 @@ export const detect = Effect.fn("gitHubChangeRequestTemplate.detect")(function*
144188
}
145189

146190
const entries = parseTemplateTreeEntries(result.stdout);
147-
const entriesByPath = new Map(entries.map((entry) => [entry.path, entry]));
148-
for (const templatePath of TEMPLATE_PATHS) {
149-
const entry = entriesByPath.get(templatePath);
150-
if (!entry) {
151-
continue;
152-
}
191+
const candidates = [".github/", "", "docs/"].flatMap((location) => {
192+
const paths = TEMPLATE_PATHS.filter(
193+
(path) => path.slice(0, path.lastIndexOf("/") + 1) === location,
194+
);
195+
return [
196+
...paths.flatMap((path) => entries.filter((entry) => entry.path === path)),
197+
...entries.filter(
198+
(entry) =>
199+
!paths.some((path) => path === entry.path) &&
200+
paths.some((path) => path.toLowerCase() === entry.path.toLowerCase()),
201+
),
202+
];
203+
});
204+
for (const entry of candidates) {
153205
const template = yield* readTemplateBlob({ cwd, executeGit, entry });
154206
if (Option.isSome(template)) {
155207
return template;

0 commit comments

Comments
 (0)