feat(vcs): fill the commit message with the repository's commit template (#1340)

An empty commit message field now starts with the file `commit.template`
names (any git config level; `~/` and repository-relative paths resolve
as git resolves them), without its `#` comment lines, since commits keep
the message as written. It is filled when the repository opens or
changes and when a commit clears the field; a typed message is never
replaced, and a prepared merge / cherry-pick / revert message still
takes precedence.

Fixes #1285
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-05 02:05:07 +08:00
1 джерело c219c81bd8
коміт ce898ea0c6
2 files changed
+76 -3

No files matched your search

+30 -1
Переглянути файл
@@ -180,6 +180,7 @@ export function keepMessageAfterCommit(committed: string, clear: boolean) {
const message = messageAfterCommit(committed, clear);
keptMessage = message || null;
useCommit.setState({ message });
if (!message) void loadTemplate();
}
/** The field holds nothing the user typed: empty, or what the last commit left there. */
@@ -200,9 +201,35 @@ export function preparedMessageText(raw: string): string {
.trim();
}
/** `commit.template` resolved as git does: `~/` in the home directory, a relative path in the repository. */
export function templatePath(value: string, root: string, home: string): string {
if (value === "~" || value.startsWith("~/")) return home.replace(/[/\\]$/, "") + value.slice(1);
if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) return value;
return `${root}/${value}`;
}
/** Fills an empty message field with the repository's commit template (`commit.template`), without its `#` lines. */
async function loadTemplate() {
const root = useVcs.getState().root;
if (!root || useCommit.getState().message.trim()) return;
try {
const value = await call(commands.vcsConfigGet(root, "commit.template"));
if (!value) return;
const home = value.startsWith("~") ? await (await import("@tauri-apps/api/path")).homeDir() : "";
const text = preparedMessageText(await call(commands.fsReadText(templatePath(value, root, home))));
if (!text || useVcs.getState().root !== root || useCommit.getState().message.trim()) return;
// Not typed: a prepared merge message may replace it.
keptMessage = text;
useCommit.setState({ message: text });
} catch {
// No readable template: the field stays empty.
}
}
/**
* Like IDEA, pre-fills the commit message (unless something was typed) with the
* message git prepared for a merge / cherry-pick / revert that stopped (e.g. on conflicts).
* message git prepared for a merge / cherry-pick / revert that stopped (e.g. on conflicts),
* otherwise with the commit template.
*/
export function installPreparedMessage(): () => void {
const load = async () => {
@@ -217,6 +244,7 @@ export function installPreparedMessage(): () => void {
};
const op = (s = useVcs.getState()) => s.branch?.operation ?? null;
if (PREPARED_MESSAGE_OPS.has(op() ?? "")) void load();
else void loadTemplate();
return useVcs.subscribe((s, prev) => {
if (s.root !== prev.root) {
// Another repository: nothing typed or ticked for the previous one carries over (Amend least of all).
@@ -233,6 +261,7 @@ export function installPreparedMessage(): () => void {
selectedShelf: null,
});
if (PREPARED_MESSAGE_OPS.has(op(s) ?? "")) void load();
else void loadTemplate();
return;
}
const now = op(s);