diff --git a/bin/reset_sandbox.sh b/bin/reset_sandbox.sh index a81e3ced..f8cdeeb3 100755 --- a/bin/reset_sandbox.sh +++ b/bin/reset_sandbox.sh @@ -31,3 +31,7 @@ mkdir -p my_subdir mv bazz.js my_subdir # Delete. rm buzz.js + +# Commit template. +git config commit.template _COMMIT_MESSAGE +echo 'My commit prefix' >_COMMIT_MESSAGE diff --git a/src/autofill.ts b/src/autofill.ts index 9fd508ea..785ca8ce 100644 --- a/src/autofill.ts +++ b/src/autofill.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode"; import { Repository } from "./api/git"; import { getChanges } from "./git/cli"; +import { getCommitTemplateValue } from "./git/commitTemplate"; import { getCommitMsg, setCommitMsg } from "./gitExtension"; import { generateMsg } from "./prepareCommitMsg"; @@ -21,7 +22,11 @@ Try saving your files or stage any new (untracked) files.\ * 2. Generate a message. * 3. Push message value to the commit message box. * - * This is based on `prefixCommit` from the `git-prefix` extension. + * New functionality in the extension - the commit message file is read + * explicitly and the content used. Any content in the Git pane that was the + * "old message" is ignored then. + * + * This function is based on `prefixCommit` from the `git-prefix` extension. */ export async function makeAndFillCommitMsg(repository: Repository) { const fileChanges = await getChanges(); @@ -39,5 +44,8 @@ export async function makeAndFillCommitMsg(repository: Repository) { const newMsg = generateMsg(fileChanges, oldMsg); console.debug("New message: ", newMsg); + const commitMessageValue = await getCommitTemplateValue(); + console.debug({ commitMessageValue }); + setCommitMsg(repository, newMsg); } diff --git a/src/cli.ts b/src/cli.ts index c0dfec5f..3e3b7954 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -18,6 +18,11 @@ import { generateMsg } from "./prepareCommitMsg"; * * Expect multi-line text from `git diff-index` command as the first item in the shell arguments. * + * TODO: How to reverse the order so reading the value and using it as old message can appear _first_. + * Note that, unlike the VS Code extension, the CLI logic here cannot read an + * "old message" from the Git pane so that is not passed to `generateMsg`. + * Though maybe this can be modified using `commitTemplate.ts`. + * * Returns a suitable generated commit message as text. */ function main(args: string[]) { diff --git a/src/git/cli.ts b/src/git/cli.ts index c53cb5ad..eed2c64e 100644 --- a/src/git/cli.ts +++ b/src/git/cli.ts @@ -13,7 +13,11 @@ const exec = util.promisify(childProcess.exec); /** * Run a `git` subcommand with options and return output. */ -function _execute(cwd: string, subcommand: string, options: string[] = []) { +export function execute( + cwd: string, + subcommand: string, + options: string[] = [] +) { const command = `git ${subcommand} ${options.join(" ")}`; const result = exec(command, { cwd }); @@ -44,11 +48,9 @@ async function _diffIndex(options: string[] = []): Promise> { ...options, "HEAD", ]; - const { stdout, stderr } = await _execute( - getWorkspaceFolder(), - cmd, - fullOptions - ); + + const workspace = getWorkspaceFolder(); + const { stdout, stderr } = await execute(workspace, cmd, fullOptions); if (stderr) { console.debug(`stderr for 'git ${cmd}' command:`, stderr); diff --git a/src/git/commitTemplate.ts b/src/git/commitTemplate.ts new file mode 100644 index 00000000..0be5556b --- /dev/null +++ b/src/git/commitTemplate.ts @@ -0,0 +1,113 @@ +/** + * Commit template module. + * + * TODO: Move to docs and link there. + * + * Read the message from the a configured commit template file and use it as a + * prefix in the message. + * + * A commit template is built-in Git behavior to see a value for the start of + * each commit message. This is useful if you have a ticket number, project + * name, or similar to add at the start of each commit. + * + * Note that VS Code and Git CLI both automatically read from this file when + * generating a commit. However, the value is hard to use. There is behavior in + * this extension to move the old message to the end and enter a commit type + * prefix and commit message before it, but there is no way to know from the + * content of the message for sure whether the old message is a commit template + * value or just a hand-typed message. + * + * To avoid making an extra config value for the extension that one has to + * manage say in a Settings file or internal data, the approach is rather to use + * the existing commit template pattern in Git. + */ +import * as fs from "fs"; +import * as path from "path"; +import { getWorkspaceFolder } from "../workspace"; +import { execute } from "./cli"; + +const CONFIG_SUBCOMMAND = "config"; +const COMMIT_TEMPLATE_IDENTIFIER = "commit.template"; + +/** + * Get a value from the local Git config. + */ +export async function _getConfigValue(options: string[]) { + const workspace = getWorkspaceFolder(); + + const { stdout, stderr } = await execute( + workspace, + CONFIG_SUBCOMMAND, + options + ); + + if (stderr) { + console.debug(`stderr for 'git ${CONFIG_SUBCOMMAND}' command:`, stderr); + } + + return stdout; +} + +/** + * Get commit template path. + * + * Get the configured value for a commit template path if set. + */ +async function _getCommitTemplatePath() { + try { + const options = [COMMIT_TEMPLATE_IDENTIFIER]; + + return await _getConfigValue(options); + } catch (_e) { + console.error(_e); + + return null; + } +} + +/** + * Read a file. + * + * @param filePath Path to a file to read, relative to the workspace root. + * e.g. "abc.txt" or "abc/def.txt" + */ +function _readFile(filePath: string) { + const workspace = getWorkspaceFolder(); + const p = path.join(workspace, filePath); + + let value; + + try { + value = fs.readFileSync(p, "utf-8"); + } catch (err: any) { + console.error(`Could not find template file: ${p}. ${err.toString()}`); + + return null; + } + + if (!value) { + return null; + } + + console.debug(`Found file: ${p}. Found contents: ${value}`); + + return value; +} + +/** + * Get value of commit template file. + * + * @return Contents of a configured commit message template file, or `null` if + * file is not configured or file is missing. + */ +export async function getCommitTemplateValue() { + const filePath = await _getCommitTemplatePath(); + + if (!filePath) { + console.error(`Could not read missing file: ${filePath}`); + + return null; + } + + return _readFile(filePath); +} diff --git a/src/prepareCommitMsg.ts b/src/prepareCommitMsg.ts index 2e0a9dbf..a3de2eab 100644 --- a/src/prepareCommitMsg.ts +++ b/src/prepareCommitMsg.ts @@ -218,7 +218,7 @@ export function _generateMsgWithOld(lines: string[], oldMsg: string) { * * Old message could be the current commit message value in the UI box (which might be a commit * message template that VS Code has filled in), or a commit message template read from a file in - * the case of a hook flow without VS Code. + * the case of a hook flow without VS Code (built-in Git functionality). * * @param lines A list of text values describing how files changes. */