Skip to content

Commit 9f2997e

Browse files
MichaelCurrinCopilot
andcommitted
feat: update CLI
Includes refactor, uninstall, fix bad logic and use same logic as the extension for consistency. Update the docs and add Make command. Co-authored-by: Copilot <copilot@github.com>
1 parent ac1d48d commit 9f2997e

11 files changed

Lines changed: 132 additions & 92 deletions

File tree

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ q test-quick:
4949
npx tsc -p .
5050
npm run test:unit
5151

52+
# Test the CLI tools and their help output.
53+
cli-help:
54+
npx ts-node src/cli/diffIndexGenerate.ts --help
55+
npx ts-node src/cli/diffIndexGenerateCommit.ts --help
56+
npx ts-node src/cli/generate.ts --help
57+
5258
### Build
5359

5460
.PHONY: build

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ How to install and run the extension in VS Code.
5555

5656
</div>
5757

58-
If you want to install and run as a CLI tool, see [CLI docs](/docs/cli.md).
58+
Install the CLI tool quick and easy so you are not tied to the IDE - see [CLI docs](/docs/cli.md).
5959

6060

6161
## Features

bin/uninstall_cli.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Uninstall auto-commit-msg CLI binaries.
3+
set -e
4+
5+
BINARIES=("acm" "gacm" "auto_commit_msg_generate")
6+
INSTALL_DIR="$HOME/.local/bin"
7+
8+
rm -f "${BINARIES[@]/#/$INSTALL_DIR/}"
9+
10+
echo "auto-commit-msg binaries uninstalled successfully"

docs/cli.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,31 @@ See steps below to setup and run the tool in the CLI.
1212

1313
The pre-built binaries are added to releases on GitHub so you can install them without needing the project or Node.
1414

15-
macOS and Linux:
15+
Run the command below to install (or update to the latest).
16+
17+
### macOS and Linux
1618

1719
```sh
1820
curl -fsSL https://raw.githubusercontent.com/MichaelCurrin/auto-commit-msg/refs/heads/master/bin/install_cli.sh | bash
1921
```
2022

21-
**Warning this is experimental** Windows PowerShell:
23+
Uninstall:
24+
25+
```sh
26+
curl -fsSL https://raw.githubusercontent.com/MichaelCurrin/auto-commit-msg/refs/heads/master/bin/uninstall_cli.sh | bash
27+
```
28+
29+
### Windows PowerShell
30+
31+
**Warning this is untested**
2232

2333
```powershell
2434
iwr https://github.com/username/repo/releases/latest/download/install.ps1 -useb | iex
2535
```
2636

27-
> [!TIP]
28-
> To setup as a **pre-commit hook**, see [Shell](/shell/)
37+
## Setup as a hook
38+
39+
To setup as a **pre-commit hook**, see [Shell](/shell/)
2940

3041
## Usage
3142

src/autofill.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@ import * as vscode from "vscode";
55
import { Repository } from "./api/git";
66
import { getChanges } from "./git/cli";
77
import { getCommitMsg, setCommitMsg } from "./gitExtension";
8+
import { NO_LINES_MSG } from "./lib/constants";
89
import { generateMsg } from "./prepareCommitMsg";
910

10-
export const NO_LINES_MSG = `\
11-
Unable to generate message as no changes files can be seen.
12-
Try saving your files or stage any new (untracked) files.\
13-
`;
14-
1511
/**
1612
* Generate and fill a commit message in the Git extension sidebar.
1713
*
1814
* Steps:
1915
*
20-
* 1. Read Git command output and the message in the Git Extension commit message box.
16+
* 1. Read Git command output and the message in the Git Extension commit
17+
* message box.
2118
* 2. Generate a message.
2219
* 3. Push message value to the commit message box.
2320
*

src/cli/diffIndexGenerate.ts

Lines changed: 33 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,89 +3,71 @@
33
* AutoCommitMsg CLI script - to check Git changes and generate a commit message.
44
*/
55
import { execFileSync } from "child_process";
6+
import { Repository } from "../api/git";
7+
import { getChanges } from "../git/cli";
8+
import { NO_LINES_MSG } from "../lib/constants";
69
import { generateMsg } from "../prepareCommitMsg";
710
import { shouldShowHelp } from "./utils";
811

9-
const HELP_TEXT: string = `Usage: acm [--cached] [--help|-h]
12+
const HELP_TEXT: string = `Usage: acm [--help|-h]
1013
11-
Check Git changes and generate a commit message.
14+
Check Git changes and output a generated commit message.
1215
1316
Options:
14-
--cached Use only staged changes (equivalent to \`git --cached\`).
15-
If the flag is omitted, then the standard \`git commit\` logic is followed:
16-
look for staged changes and use them, otherwise use unstaged changes.
17-
--help, -h Show this help and exit.`;
17+
--help, -h Show this help and exit.
18+
--verbose Show debug output for this run.
19+
`;
1820

19-
const DIFF_FLAGS = [
20-
"diff-index",
21-
"--name-status",
22-
"--find-renames",
23-
"--find-copies",
24-
"--no-color",
25-
];
26-
27-
/**
28-
* Run `git diff-index` and return its stdout as a string.
29-
*
30-
* TODO: Use _diffIndex instead after refactoring for flags.
31-
* @param useCached When true, include only staged changes using `--cached`.
32-
*
33-
* @returns output Diff output from git.
34-
*/
35-
function runGitDiff(useCached: boolean): string {
36-
const flags: string[] = [...DIFF_FLAGS];
37-
38-
if (useCached) {
39-
flags.push("--cached");
40-
}
41-
flags.push("HEAD");
42-
43-
const output: string = execFileSync("git", flags, {
44-
encoding: "utf8",
45-
stdio: ["ignore", "pipe", "pipe"],
46-
});
47-
48-
return output.trim();
21+
function _getCurrentRepository(): Repository {
22+
return {
23+
rootUri: {
24+
fsPath: execFileSync("git", ["rev-parse", "--show-toplevel"], {
25+
encoding: "utf8",
26+
}).trim(),
27+
},
28+
} as Repository;
4929
}
5030

5131
/**
5232
* Generate a commit message from the current repository diff.
5333
*
54-
* @param useCached When true, include only staged changes using `--cached`.
5534
* @returns Generated commit message text.
5635
*/
57-
export function generateCommitMessage(useCached: boolean): string {
58-
const diffOutput: string = runGitDiff(useCached);
59-
if (!diffOutput) {
60-
throw new Error("No file changes found");
36+
export async function generateCommitMessage(): Promise<string> {
37+
const repo = _getCurrentRepository();
38+
const fileChanges: string[] = await getChanges(repo);
39+
if (!fileChanges.length) {
40+
// todo how to handle elegantly at next level here and other script
41+
throw new Error(NO_LINES_MSG);
6142
}
6243

63-
const lines: string[] = diffOutput.split("\n");
64-
return generateMsg(lines);
44+
return generateMsg(fileChanges);
6545
}
6646

6747
/**
6848
* Command-line entry-point.
6949
*
70-
* Accepts an optional `--cached` flag to use staged changes only.
7150
* Prints the generated commit message to stdout.
7251
*/
73-
function main(argv: string[]): void {
52+
async function main(argv: string[]): Promise<void> {
7453
if (shouldShowHelp(argv)) {
7554
console.log(HELP_TEXT);
7655
return;
7756
}
78-
const useCached: boolean = argv.includes("--cached");
79-
const msg: string = generateCommitMessage(useCached);
57+
58+
const verbose: boolean = argv.includes("--verbose");
59+
if (verbose) {
60+
process.env.ACM_DEBUG = "1";
61+
}
62+
63+
const msg: string = await generateCommitMessage();
8064
console.log(msg);
8165
}
8266

8367
if (require.main === module) {
84-
try {
85-
main(process.argv.slice(2));
86-
} catch (err) {
68+
main(process.argv.slice(2)).catch((err: unknown) => {
8769
const message: string = err instanceof Error ? err.message : String(err);
8870
console.error(`Error: ${message}`);
8971
process.exit(1);
90-
}
72+
});
9173
}

src/cli/diffIndexGenerateCommit.ts

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,66 @@ import { execFileSync } from "child_process";
66
import { generateCommitMessage } from "./diffIndexGenerate";
77
import { shouldShowHelp } from "./utils";
88

9-
const HELP_TEXT: string = `Usage: gacm [--cached] [--help|-h]
9+
const HELP_TEXT: string = `Usage: gacm [--dry-run] [--no-edit|-n] [--help|-h]
1010
1111
Check Git changes, generate a commit message, and run Git commit.
1212
13+
As with standard git commit behavior, staged files will be used if there are any,
14+
then it falls back to unstaged files (like \`git commit .\`) and as per standard
15+
git behavior the untracked files will be ignored.
16+
1317
Options:
14-
--cached Use only staged changes (equivalent to \`git --cached\`).
15-
If the flag is omitted, then the standard \`git commit\` logic is followed:
16-
look for staged changes and use them, otherwise use unstaged changes.
17-
--help, -h Show this help and exit.`;
18+
--help, -h Show this help and exit.
19+
--dry-run Do not commit, just print what would be sent to git commit.
20+
--no-edit -n Do not edit the commit message manually - commit directly.
21+
`;
1822

1923
/**
2024
* Command-line entry-point.
2125
*/
22-
function main(argv: string[]): void {
26+
async function main(argv: string[]): Promise<void> {
2327
if (shouldShowHelp(argv)) {
2428
console.log(HELP_TEXT);
2529
return;
2630
}
2731

28-
const useCached: boolean = argv.includes("--cached");
29-
const passthrough: string[] = argv.filter(
30-
(arg: string) => arg !== "--cached",
31-
);
32+
const dryRun: boolean = argv.includes("--dry-run");
33+
const noEdit: boolean = argv.includes("--no-edit") || argv.includes("-n");
34+
35+
// Note that this should to be escaped/quoted. Even though it looks weird in
36+
// the dry-run output, it works correctly (otherwise quotes appear in the
37+
// commit message).
38+
const msg = await generateCommitMessage();
39+
const commitArgs: string[] = ["commit", "-m", msg];
40+
if (!noEdit) {
41+
commitArgs.push("--edit");
42+
}
3243

33-
const msg: string = generateCommitMessage(useCached);
44+
let command: string;
45+
let args: string[];
46+
47+
if (dryRun) {
48+
command = "echo";
49+
args = ["git", ...commitArgs];
50+
} else {
51+
command = "git";
52+
args = commitArgs;
53+
}
3454

35-
// FIXME: there is a bug when using changes that are not staged and without
36-
// --cached flag, so the message is unquoted.
37-
const commitArgs: string[] = ["commit", "--edit", "-m", msg, ...passthrough];
38-
execFileSync("git", commitArgs, { stdio: "inherit" });
55+
try {
56+
execFileSync(command, args, { stdio: "inherit" });
57+
} catch (err: unknown) {
58+
// Retry current directory for unstaged files. Note passing specific paths
59+
// here does not make sense as it wouldn't match the message generation
60+
// logic.
61+
execFileSync("git", [...commitArgs, "."], { stdio: "inherit" });
62+
}
3963
}
4064

4165
if (require.main === module) {
42-
try {
43-
main(process.argv.slice(2));
44-
} catch (err) {
66+
main(process.argv.slice(2)).catch((err: unknown) => {
4567
const message: string = err instanceof Error ? err.message : String(err);
4668
console.error(`Error: ${message}`);
4769
process.exit(1);
48-
}
70+
});
4971
}

src/cli/generate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ function main(args: string[]): void {
3636
console.log(HELP_TEXT);
3737
return;
3838
}
39-
const linesArg = args[0];
4039

41-
if (typeof linesArg === "undefined") {
40+
if (!args.length) {
4241
throw new Error(
4342
"Exactly one argument is required - text output from diff-index command.",
4443
);
4544
}
4645

46+
const linesArg = args[0];
4747
const lines = linesArg.split("\n");
4848

4949
if (!lines.length) {

src/extension.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ async function _handleRepo(git: API): Promise<Repository> {
6060
* Choose the relevant repo and apply autofill logic on files there.
6161
*/
6262
async function _chooseRepoForAutofill(sourceControl?: vscode.SourceControl) {
63-
const git = getGitExtension()!;
64-
_validateFoundRepos(git);
63+
const gitExtension = getGitExtension()!;
64+
_validateFoundRepos(gitExtension);
6565

6666
vscode.commands.executeCommand("workbench.view.scm");
6767

6868
const selectedRepo = sourceControl
69-
? await _handleRepos(git, sourceControl)
70-
: await _handleRepo(git);
69+
? await _handleRepos(gitExtension, sourceControl)
70+
: await _handleRepo(gitExtension);
7171

7272
if (!selectedRepo) {
7373
const msg = "No repos found";

src/git/cli.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ const DIFF_INDEX_OPTIONS = [
2121
"--no-color",
2222
];
2323

24+
// Allow debug messages to be shown (VS Code extension) or hidden (CLI tool).
25+
function debug(...args: unknown[]): void {
26+
if (process.env.ACM_DEBUG === "1") {
27+
console.debug(...args);
28+
}
29+
}
30+
2431
/**
2532
* Run a `git` subcommand and return the result, with stdout and stderr available.
2633
*/
2734
function _execute(cwd: string, subcommand: string, options: string[] = []) {
2835
const command = `git ${QUOTE_PATH} ${subcommand} ${options.join(" ")}`;
2936

30-
console.debug(`Running command: ${command}, cwd: ${cwd}`);
37+
debug(`Running command: ${command}, cwd: ${cwd}`);
3138

3239
return exec(command, { cwd });
3340
}
@@ -54,7 +61,7 @@ async function _diffIndex(
5461
const { stdout, stderr } = await _execute(cwd, DIFF_INDEX_CMD, fullOptions);
5562

5663
if (stderr) {
57-
console.debug(`stderr for 'git ${DIFF_INDEX_CMD}' command:`, stderr);
64+
debug(`stderr for 'git ${DIFF_INDEX_CMD}' command:`, stderr);
5865
}
5966

6067
const lines = stdout.split("\n");
@@ -75,19 +82,19 @@ export async function getChanges(repository: Repository) {
7582
const stagedChanges = await _diffIndex(repository, ["--cached"]);
7683

7784
if (stagedChanges.length) {
78-
console.debug("Found staged changes");
85+
debug("Found staged changes");
7986

8087
return stagedChanges;
8188
}
8289

83-
console.debug(
90+
debug(
8491
"Staging area is empty. Using unstaged files (tracked files only still).",
8592
);
8693

8794
const allChanges = await _diffIndex(repository);
8895

8996
if (!allChanges.length) {
90-
console.debug("No changes found");
97+
debug("No changes found");
9198
}
9299
return allChanges;
93100
}

0 commit comments

Comments
 (0)