dsh-git-branch-manage 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +920 -194
- package/lib/git.d.ts +90 -1
- package/lib/git.js +114 -2
- package/lib/index.js +4 -2
- package/lib/routes.d.ts +33 -3
- package/lib/routes.js +131 -4
- package/lib/wire.d.ts +50 -0
- package/lib/wire.js +4 -0
- package/package.json +1 -1
package/lib/git.d.ts
CHANGED
|
@@ -182,6 +182,28 @@ export declare function switchBranch(exec: Exec, checkoutDir: string, branch: st
|
|
|
182
182
|
* start point with `checkout` false).
|
|
183
183
|
*/
|
|
184
184
|
export declare function createBranch(exec: Exec, cwd: string, name: string, from?: string, checkout?: boolean): Promise<string>;
|
|
185
|
+
/** Outcome of {@link createTag}: the tag created and the commit it points at. */
|
|
186
|
+
export interface TagOutcome {
|
|
187
|
+
tag: string;
|
|
188
|
+
hash: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Create a tag at an existing commit (`git tag`). Tags are repository-wide
|
|
192
|
+
* refs, so the command runs at the repository root.
|
|
193
|
+
*
|
|
194
|
+
* A message makes it an ANNOTATED tag (`-a -m`) — the only shape that carries
|
|
195
|
+
* an author and date, and what `git describe` prefers; without one the tag is
|
|
196
|
+
* lightweight and is just an alias for the commit. Both are created locally
|
|
197
|
+
* only: pushing a tag is a deliberate, separate publish step that this panel
|
|
198
|
+
* does not perform on a menu click.
|
|
199
|
+
* @param exec - executor seam.
|
|
200
|
+
* @param repoRoot - main worktree directory.
|
|
201
|
+
* @param name - tag name (git validates; failures raise GitError).
|
|
202
|
+
* @param target - commit to tag; absent tags the repository's current HEAD.
|
|
203
|
+
* @param message - optional annotation message; non-empty = annotated tag.
|
|
204
|
+
* @returns the tag name and the commit hash it points at.
|
|
205
|
+
*/
|
|
206
|
+
export declare function createTag(exec: Exec, repoRoot: string, name: string, target?: string, message?: string): Promise<TagOutcome>;
|
|
185
207
|
/**
|
|
186
208
|
* Rename a LOCAL branch (`git branch -m`): branch refs are repository-wide,
|
|
187
209
|
* so the command runs at the repository root and every worktree HEAD pointing
|
|
@@ -279,6 +301,57 @@ export interface PushOutcome {
|
|
|
279
301
|
* push-specific diagnostic is preserved; in that case `pushed` is false.
|
|
280
302
|
*/
|
|
281
303
|
export declare function pushBranch(exec: Exec, cwd: string): Promise<PushOutcome>;
|
|
304
|
+
/** Every LOCAL tag name of the repository, in git's own sort order. */
|
|
305
|
+
export declare function listTagNames(exec: Exec, cwd: string): Promise<string[]>;
|
|
306
|
+
/** Where a one-off push should go, or why no remote could be picked. */
|
|
307
|
+
export type PushRemoteChoice = {
|
|
308
|
+
readonly ok: true;
|
|
309
|
+
readonly remote: string;
|
|
310
|
+
} | {
|
|
311
|
+
readonly ok: false;
|
|
312
|
+
readonly message: string;
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Pick the remote ONE tag push goes to, in fallback order: the checked-out
|
|
316
|
+
* branch's upstream remote, `remote.pushDefault`, then the only remote there
|
|
317
|
+
* is. Ambiguity is reported instead of guessed — a tag pushed to the wrong
|
|
318
|
+
* remote is a published mistake, not an inconvenience.
|
|
319
|
+
* @param exec - executor seam.
|
|
320
|
+
* @param cwd - directory whose checked-out branch supplies the upstream.
|
|
321
|
+
* @returns the remote name, or the caller-side reason none could be chosen.
|
|
322
|
+
*/
|
|
323
|
+
export declare function resolvePushRemote(exec: Exec, cwd: string): Promise<PushRemoteChoice>;
|
|
324
|
+
/** Outcome of {@link pushTag}: the tag, where it went, and whether it moved. */
|
|
325
|
+
export interface TagPushOutcome {
|
|
326
|
+
tag: string;
|
|
327
|
+
remote: string;
|
|
328
|
+
pushed: boolean;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Push ONE existing local tag to `remote` (`git push <remote>
|
|
332
|
+
* refs/tags/<name>:refs/tags/<name>`) — the publish step a tag needs and a
|
|
333
|
+
* branch push never performs: `git push` sends the current branch alone, so a
|
|
334
|
+
* tag made on an already-pushed commit is invisible to the remote.
|
|
335
|
+
*
|
|
336
|
+
* The tag must already exist locally (the route verifies it against
|
|
337
|
+
* {@link listTagNames}); a remote that already carries the SAME tag object is
|
|
338
|
+
* reported as `pushed: false` without running the push at all. The refspec
|
|
339
|
+
* always names both sides explicitly, so no configured push refspec (a mirror,
|
|
340
|
+
* a `push.default=upstream` branch) can widen what a menu click publishes, and
|
|
341
|
+
* tags are never deleted or overwritten: `--force` is deliberately absent, and
|
|
342
|
+
* a remote tag pointing somewhere else makes git refuse (mapped to 400).
|
|
343
|
+
*
|
|
344
|
+
* The cn-accelerate rewrite is never injected here (no {@link accelArgs}
|
|
345
|
+
* parameter exists, like {@link pushBranch}): a mirror has no `receive-pack` —
|
|
346
|
+
* and the pre-push probe must see the very remote the push targets, or the
|
|
347
|
+
* `pushed` flag would compare two different servers.
|
|
348
|
+
* @param exec - executor seam.
|
|
349
|
+
* @param cwd - any directory inside the repository (tag refs are shared).
|
|
350
|
+
* @param name - an existing local tag name.
|
|
351
|
+
* @param remote - the remote to publish to (see {@link resolvePushRemote}).
|
|
352
|
+
* @returns the tag, the remote, and whether the push had to run.
|
|
353
|
+
*/
|
|
354
|
+
export declare function pushTag(exec: Exec, cwd: string, name: string, remote: string): Promise<TagPushOutcome>;
|
|
282
355
|
/** Guard for route inputs: a non-empty absolute directory path. */
|
|
283
356
|
export declare function isAbsoluteDir(value: string): boolean;
|
|
284
357
|
/** Pre-delete facts of one worktree directory, for the remove-confirm dialog. */
|
|
@@ -447,18 +520,34 @@ export interface CommitEntryRaw {
|
|
|
447
520
|
body: string;
|
|
448
521
|
refs: string[];
|
|
449
522
|
}
|
|
523
|
+
/** The extra log filters the history card's toolbar drives. */
|
|
524
|
+
export interface LogFilter {
|
|
525
|
+
/** Every ref (local + remote branches, tags) instead of the checkout's
|
|
526
|
+
* HEAD — the history card's default view. Ignored when `ref` is given. */
|
|
527
|
+
all?: boolean;
|
|
528
|
+
/** Committer-author substring/regex, forwarded as `--author=<value>`. */
|
|
529
|
+
author?: string;
|
|
530
|
+
/** Lower/upper bound on the commit date, epoch milliseconds. */
|
|
531
|
+
since?: number;
|
|
532
|
+
until?: number;
|
|
533
|
+
}
|
|
450
534
|
/**
|
|
451
535
|
* Paginated history of the queried checkout. `ref` is verified with
|
|
452
536
|
* `rev-parse --verify` first, so an arbitrary string can never reach the log
|
|
453
537
|
* arguments as anything but a resolvable rev. An unborn checkout (no commits
|
|
454
538
|
* yet) answers an empty page instead of an error.
|
|
539
|
+
*
|
|
540
|
+
* `--topo-order` is unconditional: the history card draws lane lines from
|
|
541
|
+
* `parents`, and a pure date order would interleave a branch's commits
|
|
542
|
+
* before their merge base, leaving edges pointing at rows not yet painted.
|
|
455
543
|
* @param exec - executor seam.
|
|
456
544
|
* @param cwd - the checkout directory.
|
|
457
545
|
* @param limit - page size (1..200, validated upstream).
|
|
458
546
|
* @param skip - offset (>= 0, validated upstream).
|
|
459
547
|
* @param ref - optional starting rev; absent = the checkout's HEAD.
|
|
548
|
+
* @param filter - optional all-refs/author/date window.
|
|
460
549
|
*/
|
|
461
|
-
export declare function logHistory(exec: Exec, cwd: string, limit: number, skip: number, ref?: string): Promise<CommitEntryRaw[]>;
|
|
550
|
+
export declare function logHistory(exec: Exec, cwd: string, limit: number, skip: number, ref?: string, filter?: LogFilter): Promise<CommitEntryRaw[]>;
|
|
462
551
|
/** One file row of {@link commitFiles}. */
|
|
463
552
|
export interface CommitFileRow {
|
|
464
553
|
path: string;
|
package/lib/git.js
CHANGED
|
@@ -433,6 +433,35 @@ export async function createBranch(exec, cwd, name, from, checkout = false) {
|
|
|
433
433
|
await git(exec, cwd, ['branch', name, from]);
|
|
434
434
|
return name;
|
|
435
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Create a tag at an existing commit (`git tag`). Tags are repository-wide
|
|
438
|
+
* refs, so the command runs at the repository root.
|
|
439
|
+
*
|
|
440
|
+
* A message makes it an ANNOTATED tag (`-a -m`) — the only shape that carries
|
|
441
|
+
* an author and date, and what `git describe` prefers; without one the tag is
|
|
442
|
+
* lightweight and is just an alias for the commit. Both are created locally
|
|
443
|
+
* only: pushing a tag is a deliberate, separate publish step that this panel
|
|
444
|
+
* does not perform on a menu click.
|
|
445
|
+
* @param exec - executor seam.
|
|
446
|
+
* @param repoRoot - main worktree directory.
|
|
447
|
+
* @param name - tag name (git validates; failures raise GitError).
|
|
448
|
+
* @param target - commit to tag; absent tags the repository's current HEAD.
|
|
449
|
+
* @param message - optional annotation message; non-empty = annotated tag.
|
|
450
|
+
* @returns the tag name and the commit hash it points at.
|
|
451
|
+
*/
|
|
452
|
+
export async function createTag(exec, repoRoot, name, target, message) {
|
|
453
|
+
const text = message?.trim() ?? '';
|
|
454
|
+
const args = ['tag'];
|
|
455
|
+
if (text !== '')
|
|
456
|
+
args.push('-a', name, '-m', text);
|
|
457
|
+
else
|
|
458
|
+
args.push(name);
|
|
459
|
+
if (target !== undefined && target !== '')
|
|
460
|
+
args.push(target);
|
|
461
|
+
await git(exec, repoRoot, args);
|
|
462
|
+
const hash = (await git(exec, repoRoot, ['rev-list', '-n', '1', name])).trim();
|
|
463
|
+
return { tag: name, hash };
|
|
464
|
+
}
|
|
436
465
|
/**
|
|
437
466
|
* Rename a LOCAL branch (`git branch -m`): branch refs are repository-wide,
|
|
438
467
|
* so the command runs at the repository root and every worktree HEAD pointing
|
|
@@ -545,6 +574,73 @@ export async function pushBranch(exec, cwd) {
|
|
|
545
574
|
const branch = (await gitMaybe(exec, cwd, ['branch', '--show-current']))?.trim() ?? '';
|
|
546
575
|
return { branch, pushed: before !== undefined && after !== undefined && before.trim() !== after.trim() };
|
|
547
576
|
}
|
|
577
|
+
/** Every LOCAL tag name of the repository, in git's own sort order. */
|
|
578
|
+
export async function listTagNames(exec, cwd) {
|
|
579
|
+
const text = await git(exec, cwd, ['for-each-ref', 'refs/tags', '--format=%(refname:short)']);
|
|
580
|
+
return text.split('\n').map(line => line.trim()).filter(line => line !== '');
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Pick the remote ONE tag push goes to, in fallback order: the checked-out
|
|
584
|
+
* branch's upstream remote, `remote.pushDefault`, then the only remote there
|
|
585
|
+
* is. Ambiguity is reported instead of guessed — a tag pushed to the wrong
|
|
586
|
+
* remote is a published mistake, not an inconvenience.
|
|
587
|
+
* @param exec - executor seam.
|
|
588
|
+
* @param cwd - directory whose checked-out branch supplies the upstream.
|
|
589
|
+
* @returns the remote name, or the caller-side reason none could be chosen.
|
|
590
|
+
*/
|
|
591
|
+
export async function resolvePushRemote(exec, cwd) {
|
|
592
|
+
const upstream = (await gitMaybe(exec, cwd, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']))?.trim() ?? '';
|
|
593
|
+
const slash = upstream.indexOf('/');
|
|
594
|
+
if (slash > 0)
|
|
595
|
+
return { ok: true, remote: upstream.slice(0, slash) };
|
|
596
|
+
const pushDefault = (await gitMaybe(exec, cwd, ['config', '--get', 'remote.pushDefault']))?.trim() ?? '';
|
|
597
|
+
if (pushDefault !== '')
|
|
598
|
+
return { ok: true, remote: pushDefault };
|
|
599
|
+
const remotes = ((await gitMaybe(exec, cwd, ['remote'])) ?? '')
|
|
600
|
+
.split('\n').map(line => line.trim()).filter(line => line !== '');
|
|
601
|
+
const only = remotes.length === 1 ? remotes[0] : undefined;
|
|
602
|
+
if (only !== undefined)
|
|
603
|
+
return { ok: true, remote: only };
|
|
604
|
+
return { ok: false, message: remotes.length === 0
|
|
605
|
+
? 'no remote to push the tag to: the current branch has no upstream and the repository has none either'
|
|
606
|
+
: `multiple remotes (${remotes.join(', ')}) and the current branch has no upstream: set one, or git config remote.pushDefault <name>` };
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Push ONE existing local tag to `remote` (`git push <remote>
|
|
610
|
+
* refs/tags/<name>:refs/tags/<name>`) — the publish step a tag needs and a
|
|
611
|
+
* branch push never performs: `git push` sends the current branch alone, so a
|
|
612
|
+
* tag made on an already-pushed commit is invisible to the remote.
|
|
613
|
+
*
|
|
614
|
+
* The tag must already exist locally (the route verifies it against
|
|
615
|
+
* {@link listTagNames}); a remote that already carries the SAME tag object is
|
|
616
|
+
* reported as `pushed: false` without running the push at all. The refspec
|
|
617
|
+
* always names both sides explicitly, so no configured push refspec (a mirror,
|
|
618
|
+
* a `push.default=upstream` branch) can widen what a menu click publishes, and
|
|
619
|
+
* tags are never deleted or overwritten: `--force` is deliberately absent, and
|
|
620
|
+
* a remote tag pointing somewhere else makes git refuse (mapped to 400).
|
|
621
|
+
*
|
|
622
|
+
* The cn-accelerate rewrite is never injected here (no {@link accelArgs}
|
|
623
|
+
* parameter exists, like {@link pushBranch}): a mirror has no `receive-pack` —
|
|
624
|
+
* and the pre-push probe must see the very remote the push targets, or the
|
|
625
|
+
* `pushed` flag would compare two different servers.
|
|
626
|
+
* @param exec - executor seam.
|
|
627
|
+
* @param cwd - any directory inside the repository (tag refs are shared).
|
|
628
|
+
* @param name - an existing local tag name.
|
|
629
|
+
* @param remote - the remote to publish to (see {@link resolvePushRemote}).
|
|
630
|
+
* @returns the tag, the remote, and whether the push had to run.
|
|
631
|
+
*/
|
|
632
|
+
export async function pushTag(exec, cwd, name, remote) {
|
|
633
|
+
const ref = `refs/tags/${name}`;
|
|
634
|
+
const local = (await git(exec, cwd, ['rev-parse', '--verify', ref])).trim();
|
|
635
|
+
const listed = await gitMaybe(exec, cwd, ['ls-remote', '--refs', remote, ref], NETWORK_TIMEOUT_MS);
|
|
636
|
+
const present = (listed ?? '').split('\n')
|
|
637
|
+
.map(line => line.trim().split(/\s+/)[0] ?? '')
|
|
638
|
+
.includes(local);
|
|
639
|
+
if (present)
|
|
640
|
+
return { tag: name, remote, pushed: false };
|
|
641
|
+
await git(exec, cwd, ['push', remote, `${ref}:${ref}`], NETWORK_TIMEOUT_MS);
|
|
642
|
+
return { tag: name, remote, pushed: true };
|
|
643
|
+
}
|
|
548
644
|
/** Guard for route inputs: a non-empty absolute directory path. */
|
|
549
645
|
export function isAbsoluteDir(value) {
|
|
550
646
|
return value.trim() !== '' && isAbsolute(value);
|
|
@@ -879,13 +975,18 @@ export function parseLogOutput(out) {
|
|
|
879
975
|
* `rev-parse --verify` first, so an arbitrary string can never reach the log
|
|
880
976
|
* arguments as anything but a resolvable rev. An unborn checkout (no commits
|
|
881
977
|
* yet) answers an empty page instead of an error.
|
|
978
|
+
*
|
|
979
|
+
* `--topo-order` is unconditional: the history card draws lane lines from
|
|
980
|
+
* `parents`, and a pure date order would interleave a branch's commits
|
|
981
|
+
* before their merge base, leaving edges pointing at rows not yet painted.
|
|
882
982
|
* @param exec - executor seam.
|
|
883
983
|
* @param cwd - the checkout directory.
|
|
884
984
|
* @param limit - page size (1..200, validated upstream).
|
|
885
985
|
* @param skip - offset (>= 0, validated upstream).
|
|
886
986
|
* @param ref - optional starting rev; absent = the checkout's HEAD.
|
|
987
|
+
* @param filter - optional all-refs/author/date window.
|
|
887
988
|
*/
|
|
888
|
-
export async function logHistory(exec, cwd, limit, skip, ref) {
|
|
989
|
+
export async function logHistory(exec, cwd, limit, skip, ref, filter) {
|
|
889
990
|
let start;
|
|
890
991
|
if (ref !== undefined && ref !== '' && ref !== 'HEAD') {
|
|
891
992
|
const verified = await gitMaybe(exec, cwd, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
@@ -894,10 +995,21 @@ export async function logHistory(exec, cwd, limit, skip, ref) {
|
|
|
894
995
|
}
|
|
895
996
|
start = verified.trim();
|
|
896
997
|
}
|
|
998
|
+
// A ref narrows the walk to that ref's ancestry; `--all` is only the
|
|
999
|
+
// no-ref default. Combining them would list every branch under a
|
|
1000
|
+
// "this branch" filter.
|
|
1001
|
+
const all = filter?.all === true && start === undefined;
|
|
1002
|
+
const author = filter?.author?.trim() ?? '';
|
|
1003
|
+
const since = filter?.since;
|
|
1004
|
+
const until = filter?.until;
|
|
897
1005
|
try {
|
|
898
1006
|
const out = await git(exec, cwd, [
|
|
899
|
-
'log', `--max-count=${String(limit)}`, `--skip=${String(skip)}`,
|
|
1007
|
+
'log', `--max-count=${String(limit)}`, `--skip=${String(skip)}`, '--topo-order',
|
|
900
1008
|
'--date=iso-strict', `--pretty=format:${LOG_FORMAT}`,
|
|
1009
|
+
...(all ? ['--all'] : []),
|
|
1010
|
+
...(author === '' ? [] : [`--author=${author}`]),
|
|
1011
|
+
...(since === undefined ? [] : [`--since=${new Date(since).toISOString()}`]),
|
|
1012
|
+
...(until === undefined ? [] : [`--until=${new Date(until).toISOString()}`]),
|
|
901
1013
|
...(start === undefined ? [] : [start]),
|
|
902
1014
|
]);
|
|
903
1015
|
return parseLogOutput(out);
|
package/lib/index.js
CHANGED
|
@@ -9,9 +9,9 @@ import { homedir } from 'node:os';
|
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
10
|
import { childProcessExec } from './git.js';
|
|
11
11
|
import { ACCEL_DEFAULT_URL, accelerateUrlError } from './normalize.js';
|
|
12
|
-
import { handleAiCommitMessage, handleChanges, handleCommit, handleCommitFiles, handleCreateBranch, handleCreateWorktree, handleDeleteBranch, handleEnsureDirectory, handleFetch, handleGroupWorktrees, handleInspectWorktree, handleLog, handlePathExists, handlePurgeDirectory, handleRemoveWorktree, handleRenameBranch, handleStage, handleStatus, handleSwitch, handleUpdate, handlePush, handleWorktreesAll, } from './routes.js';
|
|
12
|
+
import { handleAiCommitMessage, handleChanges, handleCommit, handleCommitFiles, handleCreateBranch, handleCreateTag, handleCreateWorktree, handleDeleteBranch, handleEnsureDirectory, handleFetch, handleGroupWorktrees, handleInspectWorktree, handleLog, handlePathExists, handlePurgeDirectory, handlePushTag, handleRemoveWorktree, handleRenameBranch, handleStage, handleStatus, handleSwitch, handleUpdate, handlePush, handleWorktreesAll, } from './routes.js';
|
|
13
13
|
import { validateRootDir } from './settings.js';
|
|
14
|
-
import { ROUTE_AI_COMMIT_MESSAGE, ROUTE_BRANCH, ROUTE_BRANCH_DELETE, ROUTE_BRANCH_RENAME, ROUTE_CHANGES, ROUTE_COMMIT, ROUTE_COMMIT_FILES, ROUTE_ENSURE_DIRECTORY, ROUTE_EXISTS, ROUTE_FETCH, ROUTE_GROUP, ROUTE_INSPECT, ROUTE_LOG, ROUTE_PUSH, ROUTE_PURGE, ROUTE_REMOVE, ROUTE_STAGE, ROUTE_STATUS, ROUTE_SWITCH, ROUTE_UPDATE, ROUTE_WORKTREE, ROUTE_WORKTREES_ALL } from './wire.js';
|
|
14
|
+
import { ROUTE_AI_COMMIT_MESSAGE, ROUTE_BRANCH, ROUTE_BRANCH_DELETE, ROUTE_BRANCH_RENAME, ROUTE_CHANGES, ROUTE_COMMIT, ROUTE_COMMIT_FILES, ROUTE_ENSURE_DIRECTORY, ROUTE_EXISTS, ROUTE_FETCH, ROUTE_GROUP, ROUTE_INSPECT, ROUTE_LOG, ROUTE_PUSH, ROUTE_PURGE, ROUTE_REMOVE, ROUTE_STAGE, ROUTE_STATUS, ROUTE_SWITCH, ROUTE_TAG, ROUTE_TAG_PUSH, ROUTE_UPDATE, ROUTE_WORKTREE, ROUTE_WORKTREES_ALL } from './wire.js';
|
|
15
15
|
export const name = 'dsh-git-branch';
|
|
16
16
|
export const inject = [];
|
|
17
17
|
/** Largest accepted request body (bytes) — these payloads are a few strings. */
|
|
@@ -227,6 +227,8 @@ export function apply(ctx, config = {}) {
|
|
|
227
227
|
postRoute(ROUTE_COMMIT, 'commit', handleCommit);
|
|
228
228
|
postRoute(ROUTE_LOG, 'log', handleLog);
|
|
229
229
|
postRoute(ROUTE_COMMIT_FILES, 'commit-files', handleCommitFiles);
|
|
230
|
+
postRoute(ROUTE_TAG, 'tag', handleCreateTag);
|
|
231
|
+
postRoute(ROUTE_TAG_PUSH, 'tag-push', handlePushTag);
|
|
230
232
|
// The AI commit-message route exists only where the host tree provides
|
|
231
233
|
// the dsh-llm service: the injection resolves once the service is up and
|
|
232
234
|
// never fires without it, so an llm-less deployment answers 404 and the
|
package/lib/routes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type CopyFilesSeams } from './copy-files.js';
|
|
6
6
|
import { type DirExists, type ExcludeSeams, type Exec } from './git.js';
|
|
7
|
-
import type { AiCommitMessageResult, ChangesResult, CommitFilesResult, CommitResult, CreateBranchResult, CreateWorktreeResult, EnsureDirectoryResult, FetchResult, GroupWorkspacesResult, InspectWorktreeResult, LogResult, PathExistsResult, PurgeDirectoryResult, RemoveWorktreeResult, RepoStatus, RouteError, StageResult, SwitchResult, UpdateResult, WorktreesAllResult } from './wire.js';
|
|
7
|
+
import type { AiCommitMessageResult, ChangesResult, CommitFilesResult, CommitResult, CreateBranchResult, CreateTagResult, CreateWorktreeResult, EnsureDirectoryResult, FetchResult, GroupWorkspacesResult, InspectWorktreeResult, LogResult, PathExistsResult, PurgeDirectoryResult, PushTagResult, RemoveWorktreeResult, RepoStatus, RouteError, StageResult, SwitchResult, UpdateResult, WorktreesAllResult } from './wire.js';
|
|
8
8
|
/** Everything the handlers need from the host half. */
|
|
9
9
|
export interface RouteDeps {
|
|
10
10
|
exec: Exec;
|
|
@@ -50,7 +50,7 @@ export declare function fsMkdirRecursive(path: string): Promise<void>;
|
|
|
50
50
|
/** One route outcome: HTTP status plus the JSON body. */
|
|
51
51
|
export interface RouteOutcome {
|
|
52
52
|
status: number;
|
|
53
|
-
body: RepoStatus | CreateWorktreeResult | SwitchResult | CreateBranchResult | FetchResult | UpdateResult | GroupWorkspacesResult | InspectWorktreeResult | RemoveWorktreeResult | PathExistsResult | EnsureDirectoryResult | WorktreesAllResult | PurgeDirectoryResult | ChangesResult | StageResult | CommitResult | LogResult | CommitFilesResult | AiCommitMessageResult | RouteError;
|
|
53
|
+
body: RepoStatus | CreateWorktreeResult | SwitchResult | CreateBranchResult | FetchResult | UpdateResult | GroupWorkspacesResult | InspectWorktreeResult | RemoveWorktreeResult | PathExistsResult | EnsureDirectoryResult | WorktreesAllResult | PurgeDirectoryResult | ChangesResult | StageResult | CommitResult | LogResult | CommitFilesResult | CreateTagResult | PushTagResult | AiCommitMessageResult | RouteError;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* POST /group — git belonging facts for a batch of workspace directories.
|
|
@@ -274,10 +274,40 @@ export declare const LOG_LIMIT_MAX = 200;
|
|
|
274
274
|
* POST /log — one page of the queried checkout's history. The optional `ref`
|
|
275
275
|
* is verified server-side (`rev-parse --verify`), an unborn checkout answers
|
|
276
276
|
* an empty page, and everything else surfaces git's own stderr.
|
|
277
|
+
*
|
|
278
|
+
* `all` walks every ref instead of HEAD (the history card's default view: one
|
|
279
|
+
* graph holding all branches, tags, and their merge edges). `author` and the
|
|
280
|
+
* `since`/`until` window are pushed down to git rather than filtered in the
|
|
281
|
+
* browser — the card paginates, so a client-side filter would only narrow the
|
|
282
|
+
* page it already has and silently hide matches on later pages.
|
|
277
283
|
* @param deps - host dependencies.
|
|
278
|
-
* @param body - parsed request body: `{ repoPath, limit?, skip?, ref? }`.
|
|
284
|
+
* @param body - parsed request body: `{ repoPath, limit?, skip?, ref?, all?, author?, since?, until? }`.
|
|
279
285
|
*/
|
|
280
286
|
export declare function handleLog(deps: RouteDeps, body: unknown): Promise<RouteOutcome>;
|
|
287
|
+
/**
|
|
288
|
+
* POST /tag — create a tag at an existing commit of the queried repository.
|
|
289
|
+
* The target is restricted to a commit hash (the only shape the history card
|
|
290
|
+
* sends), so an arbitrary string can never reach `git tag` as a flag; git
|
|
291
|
+
* validates the tag name itself and a duplicate surfaces as 400.
|
|
292
|
+
* @param deps - host dependencies.
|
|
293
|
+
* @param body - parsed request body: `{ repoPath, name, target?, message? }`.
|
|
294
|
+
*/
|
|
295
|
+
export declare function handleCreateTag(deps: RouteDeps, body: unknown): Promise<RouteOutcome>;
|
|
296
|
+
/**
|
|
297
|
+
* POST /tag-push — publish ONE existing local tag to a remote. Separate from
|
|
298
|
+
* `POST /push` on purpose: a branch push sends the branch alone, so a tag on
|
|
299
|
+
* an already-pushed commit needs its own verb.
|
|
300
|
+
*
|
|
301
|
+
* The name must match a LOCAL tag this repository actually has (checked
|
|
302
|
+
* against `for-each-ref refs/tags`), so no free string ever reaches git as a
|
|
303
|
+
* flag or a refspec; the remote is resolved server-side and an ambiguous
|
|
304
|
+
* choice is 400. A remote that already carries the same tag object answers
|
|
305
|
+
* `pushed: false` (nothing was sent); a diverged remote tag is git's own
|
|
306
|
+
* refusal, mapped to 400 by {@link gitFailure} — never forced.
|
|
307
|
+
* @param deps - host dependencies.
|
|
308
|
+
* @param body - parsed request body: `{ repoPath, name }`.
|
|
309
|
+
*/
|
|
310
|
+
export declare function handlePushTag(deps: RouteDeps, body: unknown): Promise<RouteOutcome>;
|
|
281
311
|
/**
|
|
282
312
|
* POST /commit-files — the files one commit of the queried checkout changed.
|
|
283
313
|
* The hash is verified server-side; unknown revisions answer 400.
|
package/lib/routes.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { mkdir, readdir, rm, stat } from 'node:fs/promises';
|
|
7
7
|
import { runPostCreateCopy } from './copy-files.js';
|
|
8
|
-
import { appendWorktreeExclude, fsExcludeSeams, GitError, addWorktree, addWorktreeCutout, collectAiDiff, commitFiles, commitIndex, createBranch, cutoutBranchName, deleteBranch, fetchAll, fsDirExists, inspectWorktree, isAbsoluteDir, isStageablePath, logHistory, probeRepo, probeWorkspaceGit, removeWorktree, renameBranch, resolveBranch, pushBranch, stagePaths, switchBranch, updateBranch, workingChanges } from './git.js';
|
|
8
|
+
import { appendWorktreeExclude, fsExcludeSeams, GitError, addWorktree, addWorktreeCutout, collectAiDiff, commitFiles, commitIndex, createBranch, createTag, cutoutBranchName, deleteBranch, fetchAll, fsDirExists, inspectWorktree, isAbsoluteDir, isStageablePath, listTagNames, logHistory, probeRepo, probeWorkspaceGit, pushTag, removeWorktree, renameBranch, resolveBranch, resolvePushRemote, pushBranch, stagePaths, switchBranch, updateBranch, workingChanges } from './git.js';
|
|
9
9
|
import { ACCEL_DEFAULT_URL, accelerateUrlError, isAbsoluteConfigPath, normalizeAccelerateUrl, sanitizeBranchDir } from './normalize.js';
|
|
10
10
|
import { resolveRootDir } from './settings.js';
|
|
11
11
|
import { COMMIT_MESSAGE_LIMIT } from './wire.js';
|
|
@@ -1006,15 +1006,26 @@ export async function handleCommit(deps, body) {
|
|
|
1006
1006
|
/** Default and maximum page size of POST /log. */
|
|
1007
1007
|
export const LOG_LIMIT_DEFAULT = 50;
|
|
1008
1008
|
export const LOG_LIMIT_MAX = 200;
|
|
1009
|
+
/** Bound for the author filter and the tag annotation (characters). */
|
|
1010
|
+
const LOG_AUTHOR_LIMIT = 200;
|
|
1011
|
+
const TAG_MESSAGE_LIMIT = 5000;
|
|
1012
|
+
/** Bound for one tag NAME (characters), shared by create and push. */
|
|
1013
|
+
const TAG_NAME_LIMIT = 300;
|
|
1009
1014
|
/**
|
|
1010
1015
|
* POST /log — one page of the queried checkout's history. The optional `ref`
|
|
1011
1016
|
* is verified server-side (`rev-parse --verify`), an unborn checkout answers
|
|
1012
1017
|
* an empty page, and everything else surfaces git's own stderr.
|
|
1018
|
+
*
|
|
1019
|
+
* `all` walks every ref instead of HEAD (the history card's default view: one
|
|
1020
|
+
* graph holding all branches, tags, and their merge edges). `author` and the
|
|
1021
|
+
* `since`/`until` window are pushed down to git rather than filtered in the
|
|
1022
|
+
* browser — the card paginates, so a client-side filter would only narrow the
|
|
1023
|
+
* page it already has and silently hide matches on later pages.
|
|
1013
1024
|
* @param deps - host dependencies.
|
|
1014
|
-
* @param body - parsed request body: `{ repoPath, limit?, skip?, ref? }`.
|
|
1025
|
+
* @param body - parsed request body: `{ repoPath, limit?, skip?, ref?, all?, author?, since?, until? }`.
|
|
1015
1026
|
*/
|
|
1016
1027
|
export async function handleLog(deps, body) {
|
|
1017
|
-
const parsed = readBody(body, ['repoPath'], [], ['ref'], ['limit', 'skip']);
|
|
1028
|
+
const parsed = readBody(body, ['repoPath'], ['all'], ['ref', 'author'], ['limit', 'skip', 'since', 'until']);
|
|
1018
1029
|
if (isOutcome(parsed))
|
|
1019
1030
|
return parsed;
|
|
1020
1031
|
const repoPath = readRepoPath(parsed);
|
|
@@ -1031,14 +1042,130 @@ export async function handleLog(deps, body) {
|
|
|
1031
1042
|
if (ref !== undefined && (ref.trim() === '' || ref.length > 300 || ref.startsWith('-'))) {
|
|
1032
1043
|
return fail(400, '"ref" must be a non-empty revision name');
|
|
1033
1044
|
}
|
|
1045
|
+
const author = parsed.author;
|
|
1046
|
+
if (author !== undefined && (author.trim() === '' || author.length > LOG_AUTHOR_LIMIT)) {
|
|
1047
|
+
return fail(400, `"author" must be a non-empty string of at most ${String(LOG_AUTHOR_LIMIT)} characters`);
|
|
1048
|
+
}
|
|
1049
|
+
const since = parsed.since;
|
|
1050
|
+
const until = parsed.until;
|
|
1051
|
+
for (const [key, value] of [['since', since], ['until', until]]) {
|
|
1052
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
1053
|
+
return fail(400, `"${key}" must be a positive epoch-millisecond timestamp`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (since !== undefined && until !== undefined && until < since) {
|
|
1057
|
+
return fail(400, '"until" must not be earlier than "since"');
|
|
1058
|
+
}
|
|
1034
1059
|
try {
|
|
1035
|
-
const commits = await logHistory(deps.exec, repoPath, limit, skip, ref
|
|
1060
|
+
const commits = await logHistory(deps.exec, repoPath, limit, skip, ref, {
|
|
1061
|
+
all: parsed.all === true,
|
|
1062
|
+
...(author === undefined ? {} : { author }),
|
|
1063
|
+
...(since === undefined ? {} : { since }),
|
|
1064
|
+
...(until === undefined ? {} : { until }),
|
|
1065
|
+
});
|
|
1036
1066
|
return { status: 200, body: { commits } };
|
|
1037
1067
|
}
|
|
1038
1068
|
catch (error) {
|
|
1039
1069
|
return checkoutFailure(error);
|
|
1040
1070
|
}
|
|
1041
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* POST /tag — create a tag at an existing commit of the queried repository.
|
|
1074
|
+
* The target is restricted to a commit hash (the only shape the history card
|
|
1075
|
+
* sends), so an arbitrary string can never reach `git tag` as a flag; git
|
|
1076
|
+
* validates the tag name itself and a duplicate surfaces as 400.
|
|
1077
|
+
* @param deps - host dependencies.
|
|
1078
|
+
* @param body - parsed request body: `{ repoPath, name, target?, message? }`.
|
|
1079
|
+
*/
|
|
1080
|
+
export async function handleCreateTag(deps, body) {
|
|
1081
|
+
const parsed = readBody(body, ['repoPath', 'name'], [], ['target', 'message']);
|
|
1082
|
+
if (isOutcome(parsed))
|
|
1083
|
+
return parsed;
|
|
1084
|
+
const { repoPath, name, target, message } = parsed;
|
|
1085
|
+
if (!isAbsoluteDir(repoPath))
|
|
1086
|
+
return fail(400, '"repoPath" must be an absolute directory');
|
|
1087
|
+
if (name.trim() === '')
|
|
1088
|
+
return fail(400, '"name" must be non-empty');
|
|
1089
|
+
// A leading dash would ride `git tag` as a flag — reject before the exec.
|
|
1090
|
+
if (name.startsWith('-'))
|
|
1091
|
+
return fail(400, '"name" must not start with "-"');
|
|
1092
|
+
if (name.length > TAG_NAME_LIMIT)
|
|
1093
|
+
return fail(400, `"name" must be at most ${String(TAG_NAME_LIMIT)} characters`);
|
|
1094
|
+
// The target lands on `git tag <name> <target>` verbatim: restrict it to the
|
|
1095
|
+
// object id shape the history card sends rather than trusting a free string.
|
|
1096
|
+
if (target !== undefined && !/^[0-9a-fA-F]{4,64}$/.test(target)) {
|
|
1097
|
+
return fail(400, '"target" must be a git object id');
|
|
1098
|
+
}
|
|
1099
|
+
if (message !== undefined && message.length > TAG_MESSAGE_LIMIT) {
|
|
1100
|
+
return fail(400, `"message" must be at most ${String(TAG_MESSAGE_LIMIT)} characters`);
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
const facts = await probeRepo(deps.exec, repoPath, deps.dirExists);
|
|
1104
|
+
if (facts === undefined)
|
|
1105
|
+
return fail(400, `"${repoPath}" is not inside a git repository`);
|
|
1106
|
+
// Tag refs are repository-wide metadata and touch no checkout, so the
|
|
1107
|
+
// command runs at the repository root — the same scope as a branch rename.
|
|
1108
|
+
const result = await createTag(deps.exec, facts.repoRoot, name, target, message);
|
|
1109
|
+
const body = result;
|
|
1110
|
+
return { status: 200, body };
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
if (error instanceof GitError)
|
|
1114
|
+
return gitFailure(error);
|
|
1115
|
+
return fail(500, error instanceof Error ? error.message : String(error));
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* POST /tag-push — publish ONE existing local tag to a remote. Separate from
|
|
1120
|
+
* `POST /push` on purpose: a branch push sends the branch alone, so a tag on
|
|
1121
|
+
* an already-pushed commit needs its own verb.
|
|
1122
|
+
*
|
|
1123
|
+
* The name must match a LOCAL tag this repository actually has (checked
|
|
1124
|
+
* against `for-each-ref refs/tags`), so no free string ever reaches git as a
|
|
1125
|
+
* flag or a refspec; the remote is resolved server-side and an ambiguous
|
|
1126
|
+
* choice is 400. A remote that already carries the same tag object answers
|
|
1127
|
+
* `pushed: false` (nothing was sent); a diverged remote tag is git's own
|
|
1128
|
+
* refusal, mapped to 400 by {@link gitFailure} — never forced.
|
|
1129
|
+
* @param deps - host dependencies.
|
|
1130
|
+
* @param body - parsed request body: `{ repoPath, name }`.
|
|
1131
|
+
*/
|
|
1132
|
+
export async function handlePushTag(deps, body) {
|
|
1133
|
+
const parsed = readBody(body, ['repoPath', 'name']);
|
|
1134
|
+
if (isOutcome(parsed))
|
|
1135
|
+
return parsed;
|
|
1136
|
+
const { repoPath, name } = parsed;
|
|
1137
|
+
if (!isAbsoluteDir(repoPath))
|
|
1138
|
+
return fail(400, '"repoPath" must be an absolute directory');
|
|
1139
|
+
if (name.trim() === '')
|
|
1140
|
+
return fail(400, '"name" must be non-empty');
|
|
1141
|
+
// A leading dash would ride `git push` as a flag — reject before the exec.
|
|
1142
|
+
if (name.startsWith('-'))
|
|
1143
|
+
return fail(400, '"name" must not start with "-"');
|
|
1144
|
+
if (name.length > TAG_NAME_LIMIT)
|
|
1145
|
+
return fail(400, `"name" must be at most ${String(TAG_NAME_LIMIT)} characters`);
|
|
1146
|
+
try {
|
|
1147
|
+
const facts = await probeRepo(deps.exec, repoPath, deps.dirExists);
|
|
1148
|
+
if (facts === undefined)
|
|
1149
|
+
return fail(400, `"${repoPath}" is not inside a git repository`);
|
|
1150
|
+
// The push runs at the queried directory's own toplevel, so the remote is
|
|
1151
|
+
// the one ITS checked-out branch tracks (a linked worktree publishes the
|
|
1152
|
+
// remote its own branch knows); tags themselves are repository-wide.
|
|
1153
|
+
const tags = await listTagNames(deps.exec, facts.toplevel);
|
|
1154
|
+
if (!tags.includes(name))
|
|
1155
|
+
return fail(400, `"${name}" is not a local tag`);
|
|
1156
|
+
const choice = await resolvePushRemote(deps.exec, facts.toplevel);
|
|
1157
|
+
if (!choice.ok)
|
|
1158
|
+
return fail(400, choice.message);
|
|
1159
|
+
const result = await pushTag(deps.exec, facts.toplevel, name, choice.remote);
|
|
1160
|
+
const body = result;
|
|
1161
|
+
return { status: 200, body };
|
|
1162
|
+
}
|
|
1163
|
+
catch (error) {
|
|
1164
|
+
if (error instanceof GitError)
|
|
1165
|
+
return gitFailure(error);
|
|
1166
|
+
return fail(500, error instanceof Error ? error.message : String(error));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1042
1169
|
/**
|
|
1043
1170
|
* POST /commit-files — the files one commit of the queried checkout changed.
|
|
1044
1171
|
* The hash is verified server-side; unknown revisions answer 400.
|
package/lib/wire.d.ts
CHANGED
|
@@ -53,6 +53,10 @@ export declare const ROUTE_COMMIT = "/plugin/git-worktree/commit";
|
|
|
53
53
|
export declare const ROUTE_LOG = "/plugin/git-worktree/log";
|
|
54
54
|
/** POST ROUTE_PREFIX/commit-files — the files one commit changed. */
|
|
55
55
|
export declare const ROUTE_COMMIT_FILES = "/plugin/git-worktree/commit-files";
|
|
56
|
+
/** POST ROUTE_PREFIX/tag — create a tag at an existing commit. */
|
|
57
|
+
export declare const ROUTE_TAG = "/plugin/git-worktree/tag";
|
|
58
|
+
/** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
|
|
59
|
+
export declare const ROUTE_TAG_PUSH = "/plugin/git-worktree/tag-push";
|
|
56
60
|
/** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
|
|
57
61
|
* the staged diff (mounted only where the host provides the llm service). */
|
|
58
62
|
export declare const ROUTE_AI_COMMIT_MESSAGE = "/plugin/git-worktree/ai-commit-message";
|
|
@@ -472,11 +476,57 @@ export interface LogBody {
|
|
|
472
476
|
skip?: number;
|
|
473
477
|
/** Optional starting ref (a branch name, `HEAD`, …); validated server-side. */
|
|
474
478
|
ref?: string;
|
|
479
|
+
/** Walk every ref (branches + tags) instead of the checkout's HEAD — the
|
|
480
|
+
* history card's default view, which is what makes branches/merges visible.
|
|
481
|
+
* Ignored when `ref` is given. */
|
|
482
|
+
all?: boolean;
|
|
483
|
+
/** Committer-author substring/regex (`--author`); absent = every author. */
|
|
484
|
+
author?: string;
|
|
485
|
+
/** Lower bound on the commit date, epoch milliseconds (`--since`). */
|
|
486
|
+
since?: number;
|
|
487
|
+
/** Upper bound on the commit date, epoch milliseconds (`--until`). */
|
|
488
|
+
until?: number;
|
|
475
489
|
}
|
|
476
490
|
/** POST log response body. */
|
|
477
491
|
export interface LogResult {
|
|
478
492
|
commits: CommitEntry[];
|
|
479
493
|
}
|
|
494
|
+
/** POST tag request body. */
|
|
495
|
+
export interface CreateTagBody {
|
|
496
|
+
/** Any directory inside the repository (tag refs are repository-wide). */
|
|
497
|
+
repoPath: string;
|
|
498
|
+
/** User-typed tag name, verbatim (git validates it). */
|
|
499
|
+
name: string;
|
|
500
|
+
/** The commit to tag (a full or abbreviated hash, validated server-side);
|
|
501
|
+
* absent tags the repository's current HEAD. */
|
|
502
|
+
target?: string;
|
|
503
|
+
/** Optional annotation message; non-empty creates an ANNOTATED tag. */
|
|
504
|
+
message?: string;
|
|
505
|
+
}
|
|
506
|
+
/** POST tag response body. */
|
|
507
|
+
export interface CreateTagResult {
|
|
508
|
+
/** The tag created. */
|
|
509
|
+
tag: string;
|
|
510
|
+
/** The commit hash the tag points at. */
|
|
511
|
+
hash: string;
|
|
512
|
+
}
|
|
513
|
+
/** POST tag-push request body. */
|
|
514
|
+
export interface PushTagBody {
|
|
515
|
+
/** Any directory inside the repository (tag refs are repository-wide). */
|
|
516
|
+
repoPath: string;
|
|
517
|
+
/** An EXISTING local tag name, verified server-side before any push. */
|
|
518
|
+
name: string;
|
|
519
|
+
}
|
|
520
|
+
/** POST tag-push response body. */
|
|
521
|
+
export interface PushTagResult {
|
|
522
|
+
/** The tag that was the subject of the push. */
|
|
523
|
+
tag: string;
|
|
524
|
+
/** The remote the tag was pushed to (resolved server-side). */
|
|
525
|
+
remote: string;
|
|
526
|
+
/** False when the remote already carried this exact tag object — nothing
|
|
527
|
+
* was sent. True once the push command actually ran. */
|
|
528
|
+
pushed: boolean;
|
|
529
|
+
}
|
|
480
530
|
/** One file row of POST commit-files. */
|
|
481
531
|
export interface CommitFileEntry {
|
|
482
532
|
/** New path, relative to the repository root. */
|
package/lib/wire.js
CHANGED
|
@@ -53,6 +53,10 @@ export const ROUTE_COMMIT = `${ROUTE_PREFIX}/commit`;
|
|
|
53
53
|
export const ROUTE_LOG = `${ROUTE_PREFIX}/log`;
|
|
54
54
|
/** POST ROUTE_PREFIX/commit-files — the files one commit changed. */
|
|
55
55
|
export const ROUTE_COMMIT_FILES = `${ROUTE_PREFIX}/commit-files`;
|
|
56
|
+
/** POST ROUTE_PREFIX/tag — create a tag at an existing commit. */
|
|
57
|
+
export const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
|
|
58
|
+
/** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
|
|
59
|
+
export const ROUTE_TAG_PUSH = `${ROUTE_PREFIX}/tag-push`;
|
|
56
60
|
/** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
|
|
57
61
|
* the staged diff (mounted only where the host provides the llm service). */
|
|
58
62
|
export const ROUTE_AI_COMMIT_MESSAGE = `${ROUTE_PREFIX}/ai-commit-message`;
|
package/package.json
CHANGED