@xbzbing/dsh-git-panel 1.4.0 → 1.5.0
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 +46 -13
- package/lib/host/core.d.ts +7 -1
- package/lib/host/git.d.ts +2 -0
- package/lib/host/index.d.ts +3 -2
- package/lib/host/index.js +114 -5
- package/lib/host/remote.d.ts +14 -0
- package/lib/host/types.d.ts +31 -1
- package/package.json +1 -1
package/lib/host/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GitRunner } from './git.ts';
|
|
2
2
|
import type { AgentDefaultModelFace, LlmFace } from './llm-face.ts';
|
|
3
|
-
import type { DiffViewMode, GraphStyle, GitChange, GitErrorCode, GitSnapshotResult } from './types.ts';
|
|
3
|
+
import type { DiffViewMode, GraphStyle, GitChange, GitErrorCode, GitRemote, GitSnapshotResult } from './types.ts';
|
|
4
4
|
export interface GitPanelConfig {
|
|
5
5
|
readonly timeoutMs: number;
|
|
6
6
|
/** Per-command stdout cap; also the per-side image-diff payload cap. */
|
|
@@ -85,6 +85,12 @@ export interface SnapshotDeps {
|
|
|
85
85
|
readonly getLlm?: () => LlmFace | undefined;
|
|
86
86
|
readonly getAgentDefaultModel?: () => AgentDefaultModelFace | undefined;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Pick the primary remote from `git remote -v` output and parse its fetch URL.
|
|
90
|
+
* Prefers `origin`; otherwise the first remote listed. Lines look like
|
|
91
|
+
* `origin\thttps://…\t(fetch)` — only `(fetch)` rows are considered.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parsePrimaryRemote(stdout: string): GitRemote | null;
|
|
88
94
|
export type WorkspaceResolution = {
|
|
89
95
|
readonly ok: true;
|
|
90
96
|
readonly root: string;
|
package/lib/host/git.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface SpawnSpec {
|
|
|
18
18
|
};
|
|
19
19
|
readonly graceMs: number;
|
|
20
20
|
readonly signal?: AbortSignal;
|
|
21
|
+
/** Explicit child env entries merged onto the service's scrubbed base. */
|
|
22
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
21
23
|
}
|
|
22
24
|
interface OutputRead {
|
|
23
25
|
readonly text: string;
|
package/lib/host/index.d.ts
CHANGED
|
@@ -10,10 +10,11 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
11
|
import Schema from '@deepseek-ai/schemastery';
|
|
12
12
|
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult, GitSuggestRequest, GitSuggestResult, GitVersionInfo, GitVersionRequest } from './types.ts';
|
|
13
|
-
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, } from './types.ts';
|
|
14
|
-
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace } from './core.ts';
|
|
13
|
+
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, GitRemote, RemoteHostKind, } from './types.ts';
|
|
14
|
+
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace, parsePrimaryRemote } from './core.ts';
|
|
15
15
|
export { createGitRunner } from './git.ts';
|
|
16
16
|
export { parseStatus, parseGraphLog, parseBranches, parseNameStatus, parseStashList, parseTags, sumNumstat } from './parser.ts';
|
|
17
|
+
export { parseRemote, classifyHost } from './remote.ts';
|
|
17
18
|
export { isSafePath, planAction, runAction, classifyActionFailure } from './actions.ts';
|
|
18
19
|
export { runQuery } from './queries.ts';
|
|
19
20
|
export { runSuggest } from './suggest.ts';
|
package/lib/host/index.js
CHANGED
|
@@ -65,6 +65,11 @@ function createGitRunner(subprocess, timeoutMs, maxBytes) {
|
|
|
65
65
|
const handle = subprocess.spawn({
|
|
66
66
|
argv,
|
|
67
67
|
cwd: opts.cwd,
|
|
68
|
+
// Never let a network command (fetch/pull) block on an interactive
|
|
69
|
+
// credential or host-key prompt: with stdin ignored git would hang
|
|
70
|
+
// until the timeout. GIT_TERMINAL_PROMPT=0 makes it fail fast instead,
|
|
71
|
+
// and is harmless for the non-network commands that share this runner.
|
|
72
|
+
env: { GIT_TERMINAL_PROMPT: "0" },
|
|
68
73
|
stdio: {
|
|
69
74
|
stdin: opts.stdinData === void 0 ? "ignore" : { data: opts.stdinData },
|
|
70
75
|
stdout: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } },
|
|
@@ -214,7 +219,15 @@ function parseRefs(decoration) {
|
|
|
214
219
|
continue;
|
|
215
220
|
}
|
|
216
221
|
if (token.startsWith("tag: ")) {
|
|
217
|
-
refs.push({ kind: "tag", name: token.slice("tag: ".length).trim(), head: false });
|
|
222
|
+
refs.push({ kind: "tag", name: token.slice("tag: ".length).trim().replace(/^refs\/tags\//, ""), head: false });
|
|
223
|
+
} else if (token.startsWith("refs/tags/")) {
|
|
224
|
+
refs.push({ kind: "tag", name: token.slice("refs/tags/".length), head: false });
|
|
225
|
+
} else if (token.startsWith("refs/remotes/")) {
|
|
226
|
+
refs.push({ kind: "remote", name: token.slice("refs/remotes/".length), head });
|
|
227
|
+
} else if (token.startsWith("refs/heads/")) {
|
|
228
|
+
refs.push({ kind: "branch", name: token.slice("refs/heads/".length), head });
|
|
229
|
+
} else if (head) {
|
|
230
|
+
refs.push({ kind: "branch", name: token, head });
|
|
218
231
|
} else if (token.startsWith("origin/") || token.includes("/")) {
|
|
219
232
|
refs.push({ kind: "remote", name: token, head });
|
|
220
233
|
} else {
|
|
@@ -310,6 +323,52 @@ function sumNumstat(stdout) {
|
|
|
310
323
|
return { insertions, deletions };
|
|
311
324
|
}
|
|
312
325
|
|
|
326
|
+
// src/host/remote.ts
|
|
327
|
+
function classifyHost(host) {
|
|
328
|
+
const h = host.toLowerCase();
|
|
329
|
+
if (h === "github.com" || h.endsWith(".github.com")) return "github";
|
|
330
|
+
if (h === "gitlab.com" || h.endsWith(".gitlab.com")) return "gitlab";
|
|
331
|
+
if (h === "gitee.com" || h.endsWith(".gitee.com")) return "gitee";
|
|
332
|
+
if (h === "bitbucket.org" || h.endsWith(".bitbucket.org")) return "bitbucket";
|
|
333
|
+
return "other";
|
|
334
|
+
}
|
|
335
|
+
function normalizePath(path) {
|
|
336
|
+
return path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
337
|
+
}
|
|
338
|
+
function splitHostPath(raw) {
|
|
339
|
+
const url = raw.trim();
|
|
340
|
+
if (url === "") return null;
|
|
341
|
+
const scp = url.match(/^(?:[^@/]+@)?([^/:]+):(.+)$/);
|
|
342
|
+
if (scp !== null && !/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
|
|
343
|
+
const host = scp[1];
|
|
344
|
+
const path = normalizePath(scp[2]);
|
|
345
|
+
return host !== "" && path !== "" ? { host, path } : null;
|
|
346
|
+
}
|
|
347
|
+
const scheme = url.match(/^[a-z][a-z0-9+.-]*:\/\/([^/]+)\/(.+)$/i);
|
|
348
|
+
if (scheme !== null) {
|
|
349
|
+
let authority = scheme[1];
|
|
350
|
+
const at = authority.lastIndexOf("@");
|
|
351
|
+
if (at >= 0) authority = authority.slice(at + 1);
|
|
352
|
+
const host = authority.replace(/:\d+$/, "");
|
|
353
|
+
const path = normalizePath(scheme[2]);
|
|
354
|
+
return host !== "" && path !== "" ? { host, path } : null;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
function parseRemote(name, url) {
|
|
359
|
+
const parsed = splitHostPath(url);
|
|
360
|
+
if (parsed === null) {
|
|
361
|
+
return { name, url, webUrl: null, host: null, hostKind: "other" };
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
name,
|
|
365
|
+
url,
|
|
366
|
+
webUrl: `https://${parsed.host}/${parsed.path}`,
|
|
367
|
+
host: parsed.host,
|
|
368
|
+
hostKind: classifyHost(parsed.host)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
313
372
|
// src/host/validate.ts
|
|
314
373
|
import { isAbsolute, normalize } from "node:path";
|
|
315
374
|
function isSafePath(path) {
|
|
@@ -380,6 +439,22 @@ function readBool(value, fallback) {
|
|
|
380
439
|
return typeof raw === "boolean" ? raw : fallback;
|
|
381
440
|
}
|
|
382
441
|
var NEG_CACHE_MS = 15e3;
|
|
442
|
+
function parsePrimaryRemote(stdout) {
|
|
443
|
+
const fetchUrls = /* @__PURE__ */ new Map();
|
|
444
|
+
const order = [];
|
|
445
|
+
for (const line of stdout.split("\n")) {
|
|
446
|
+
const m = line.match(/^(\S+)\t(\S+)\s+\(fetch\)$/);
|
|
447
|
+
if (m === null) continue;
|
|
448
|
+
const name2 = m[1];
|
|
449
|
+
if (!fetchUrls.has(name2)) {
|
|
450
|
+
fetchUrls.set(name2, m[2]);
|
|
451
|
+
order.push(name2);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (order.length === 0) return null;
|
|
455
|
+
const name = fetchUrls.has("origin") ? "origin" : order[0];
|
|
456
|
+
return parseRemote(name, fetchUrls.get(name));
|
|
457
|
+
}
|
|
383
458
|
var STATS_FANOUT_LIMIT = 200;
|
|
384
459
|
async function resolveBrowseRoot(deps, sessionId) {
|
|
385
460
|
const ws = await resolveWorkspace(deps, sessionId);
|
|
@@ -484,14 +559,17 @@ async function snapshotForSession(deps, config, sessionId) {
|
|
|
484
559
|
return failure;
|
|
485
560
|
}
|
|
486
561
|
const root = workspace.root;
|
|
487
|
-
const [branchRes, headRes, statusRes, aheadBehindRes, lastCommitRes, worktreeNumRes, stagedNumRes] = await Promise.all([
|
|
562
|
+
const [branchRes, headRes, statusRes, aheadBehindRes, lastCommitRes, worktreeNumRes, stagedNumRes, remoteRes] = await Promise.all([
|
|
488
563
|
runCommand(deps.run, ["git", "symbolic-ref", "--quiet", "--short", "HEAD"], root, "branch", deps.signal),
|
|
489
564
|
runCommand(deps.run, ["git", "rev-parse", "--short", "HEAD"], root, "head", deps.signal),
|
|
490
565
|
runCommand(deps.run, ["git", "status", "--porcelain=v1", "-z"], root, "status", deps.signal),
|
|
491
566
|
runCommand(deps.run, ["git", "rev-list", "--count", "--left-right", "@{upstream}...HEAD"], root, "aheadBehind", deps.signal),
|
|
492
567
|
runCommand(deps.run, ["git", "log", "-1", "--format=%H%x1f%h%x1f%s%x1f%an%x1f%aI"], root, "lastCommit", deps.signal),
|
|
493
568
|
runCommand(deps.run, ["git", "diff", "--numstat"], root, "numstat-worktree", deps.signal),
|
|
494
|
-
runCommand(deps.run, ["git", "diff", "--numstat", "--cached"], root, "numstat-staged", deps.signal)
|
|
569
|
+
runCommand(deps.run, ["git", "diff", "--numstat", "--cached"], root, "numstat-staged", deps.signal),
|
|
570
|
+
// `git remote -v` lists every remote's fetch/push URL; parsed into the
|
|
571
|
+
// primary remote (origin, else the first) for the left-column status bar.
|
|
572
|
+
runCommand(deps.run, ["git", "remote", "-v"], root, "remote", deps.signal)
|
|
495
573
|
]);
|
|
496
574
|
const branch = "run" in branchRes && branchRes.run.exitCode === 0 ? branchRes.run.stdout.trim() || null : null;
|
|
497
575
|
const unborn = "run" in headRes && headRes.run.exitCode !== 0;
|
|
@@ -514,11 +592,13 @@ async function snapshotForSession(deps, config, sessionId) {
|
|
|
514
592
|
}
|
|
515
593
|
let ahead = 0;
|
|
516
594
|
let behind = 0;
|
|
517
|
-
|
|
595
|
+
const hasUpstream = "run" in aheadBehindRes && aheadBehindRes.run.exitCode === 0;
|
|
596
|
+
if (hasUpstream && "run" in aheadBehindRes) {
|
|
518
597
|
const parts = aheadBehindRes.run.stdout.trim().split(/\s+/);
|
|
519
598
|
behind = Number(parts[0]) || 0;
|
|
520
599
|
ahead = Number(parts[1]) || 0;
|
|
521
600
|
}
|
|
601
|
+
const remote = "run" in remoteRes && remoteRes.run.exitCode === 0 ? parsePrimaryRemote(remoteRes.run.stdout) : null;
|
|
522
602
|
let lastCommit = null;
|
|
523
603
|
if ("run" in lastCommitRes && lastCommitRes.run.exitCode === 0) {
|
|
524
604
|
lastCommit = commitFromFields(lastCommitRes.run.stdout.trim().split(""));
|
|
@@ -561,6 +641,8 @@ async function snapshotForSession(deps, config, sessionId) {
|
|
|
561
641
|
untracked,
|
|
562
642
|
ahead,
|
|
563
643
|
behind,
|
|
644
|
+
hasUpstream,
|
|
645
|
+
remote,
|
|
564
646
|
lastCommit,
|
|
565
647
|
changes,
|
|
566
648
|
stats,
|
|
@@ -630,6 +712,8 @@ function planAction(action, unborn) {
|
|
|
630
712
|
return { argv: [["git", "checkout", "--end-of-options", action.name]] };
|
|
631
713
|
case "fetch":
|
|
632
714
|
return { argv: [["git", "fetch", "--all", "--prune"]] };
|
|
715
|
+
case "pull-ff":
|
|
716
|
+
return { argv: [["git", "pull", "--ff-only"]] };
|
|
633
717
|
case "tag-create": {
|
|
634
718
|
if (!isSafeBranchName(action.name)) return { error: "invalid-name", message: `unsafe tag name: ${action.name}` };
|
|
635
719
|
if (!isSafeRev(action.commit)) return { error: "invalid-name", message: `unsafe commit: ${action.commit}` };
|
|
@@ -685,6 +769,9 @@ function classifyActionFailure(stdout, stderr, exitCode) {
|
|
|
685
769
|
if (/No such ref|not a valid reference|is not a stash|no tag|tag .* not found|unknown revision|bad revision|Could not parse object|ambiguous argument/i.test(combined)) {
|
|
686
770
|
return { code: "not-found", message: err || "not found" };
|
|
687
771
|
}
|
|
772
|
+
if (/Not possible to fast-forward|cannot fast-forward|not a fast-forward|Need to specify how to reconcile divergent branches/i.test(combined)) {
|
|
773
|
+
return { code: "not-ff", message: err || "cannot fast-forward (branches diverged)" };
|
|
774
|
+
}
|
|
688
775
|
if (/would be overwritten by (checkout|merge)|local changes|overwritten by merge|Your local changes/i.test(stderr)) {
|
|
689
776
|
return { code: "local-changes-block", message: err };
|
|
690
777
|
}
|
|
@@ -861,6 +948,8 @@ async function runQuery(deps, config, request) {
|
|
|
861
948
|
return await queryLastCommitMessage(deps, root);
|
|
862
949
|
case "worktree-stats":
|
|
863
950
|
return await queryWorktreeStats(deps, config, request.sessionId);
|
|
951
|
+
case "pull-preview":
|
|
952
|
+
return await queryPullPreview(deps, root);
|
|
864
953
|
}
|
|
865
954
|
} catch (error) {
|
|
866
955
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -870,7 +959,7 @@ async function runQuery(deps, config, request) {
|
|
|
870
959
|
async function queryHistory(deps, root, q) {
|
|
871
960
|
const limit = Number.isFinite(q.limit) ? Math.min(500, Math.max(1, Math.trunc(q.limit))) : 100;
|
|
872
961
|
const skip = Number.isFinite(q.skip) ? Math.max(0, Math.trunc(q.skip)) : 0;
|
|
873
|
-
const args = ["git", "log", "--topo-order", GRAPH_FORMAT, `--max-count=${limit}`, `--skip=${skip}`];
|
|
962
|
+
const args = ["git", "log", "--topo-order", "--decorate=full", GRAPH_FORMAT, `--max-count=${limit}`, `--skip=${skip}`];
|
|
874
963
|
const search = q.search?.trim() ?? "";
|
|
875
964
|
const hexJump = search !== "" && isHexLike(search);
|
|
876
965
|
const countArgs = ["git", "rev-list", "--count"];
|
|
@@ -1226,6 +1315,23 @@ async function queryLastCommitMessage(deps, root) {
|
|
|
1226
1315
|
const message = "run" in res && res.run.exitCode === 0 ? res.run.stdout.replace(/\n+$/, "") : "";
|
|
1227
1316
|
return { ok: true, value: { kind: "last-commit-message", message } };
|
|
1228
1317
|
}
|
|
1318
|
+
async function queryPullPreview(deps, root) {
|
|
1319
|
+
const empty = { kind: "pull-preview", hasUpstream: false, commits: 0, files: 0, insertions: 0, deletions: 0 };
|
|
1320
|
+
const commitsRes = await runCommand(deps.run, ["git", "rev-list", "--count", "--end-of-options", "HEAD..@{upstream}"], root, "pull-preview-count", deps.signal);
|
|
1321
|
+
if (!("run" in commitsRes) || commitsRes.run.exitCode !== 0) return { ok: true, value: empty };
|
|
1322
|
+
const commits = Number(commitsRes.run.stdout.trim()) || 0;
|
|
1323
|
+
const diffRes = await runCommand(deps.run, ["git", "diff", "--numstat", "--end-of-options", "HEAD", "@{upstream}"], root, "pull-preview-diff", deps.signal);
|
|
1324
|
+
let files = 0;
|
|
1325
|
+
let insertions = 0;
|
|
1326
|
+
let deletions = 0;
|
|
1327
|
+
if ("run" in diffRes && diffRes.run.exitCode === 0) {
|
|
1328
|
+
files = diffRes.run.stdout.split("\n").filter((l) => l.trim() !== "").length;
|
|
1329
|
+
const sum = sumNumstat(diffRes.run.stdout);
|
|
1330
|
+
insertions = sum.insertions;
|
|
1331
|
+
deletions = sum.deletions;
|
|
1332
|
+
}
|
|
1333
|
+
return { ok: true, value: { kind: "pull-preview", hasUpstream: true, commits, files, insertions, deletions } };
|
|
1334
|
+
}
|
|
1229
1335
|
async function queryWorktreeStats(deps, config, sessionId) {
|
|
1230
1336
|
const snapshot = await snapshotForSession(deps, config, sessionId);
|
|
1231
1337
|
if (!snapshot.ok) return { ok: false, error: { code: "git-error", message: "snapshot failed" } };
|
|
@@ -1689,6 +1795,7 @@ export {
|
|
|
1689
1795
|
GitPanelService,
|
|
1690
1796
|
checkLatestVersion,
|
|
1691
1797
|
classifyActionFailure,
|
|
1798
|
+
classifyHost,
|
|
1692
1799
|
compareVersions,
|
|
1693
1800
|
createGitRunner,
|
|
1694
1801
|
index_default as default,
|
|
@@ -1697,6 +1804,8 @@ export {
|
|
|
1697
1804
|
parseBranches,
|
|
1698
1805
|
parseGraphLog,
|
|
1699
1806
|
parseNameStatus,
|
|
1807
|
+
parsePrimaryRemote,
|
|
1808
|
+
parseRemote,
|
|
1700
1809
|
parseRepository,
|
|
1701
1810
|
parseStashList,
|
|
1702
1811
|
parseStatus,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a git remote URL into a browsable https page URL + host classification.
|
|
3
|
+
*
|
|
4
|
+
* Git remotes come in several shapes — scp-like ssh (`git@host:owner/repo.git`),
|
|
5
|
+
* ssh:// , https:// , git:// — all of which must collapse to a single
|
|
6
|
+
* `https://<host>/<path>` page link and a known-host kind for the status-bar
|
|
7
|
+
* icon. Credentials in the URL (`https://user:token@host/…`) are stripped so a
|
|
8
|
+
* token can never leak into an href the browser renders.
|
|
9
|
+
*/
|
|
10
|
+
import type { GitRemote, RemoteHostKind } from './types.ts';
|
|
11
|
+
/** Map a hostname to a known-host kind (self-hosted / unknown → 'other'). */
|
|
12
|
+
export declare function classifyHost(host: string): RemoteHostKind;
|
|
13
|
+
/** Build a {@link GitRemote} from a remote name + its configured URL. */
|
|
14
|
+
export declare function parseRemote(name: string, url: string): GitRemote;
|
package/lib/host/types.d.ts
CHANGED
|
@@ -37,6 +37,21 @@ export type GitFailure = {
|
|
|
37
37
|
} | {
|
|
38
38
|
readonly code: 'cancelled';
|
|
39
39
|
};
|
|
40
|
+
/** Host classification for a remote URL (drives the status-bar icon). */
|
|
41
|
+
export type RemoteHostKind = 'github' | 'gitlab' | 'gitee' | 'bitbucket' | 'other';
|
|
42
|
+
/** The repository's primary remote (origin, else the first remote). */
|
|
43
|
+
export interface GitRemote {
|
|
44
|
+
/** Remote name (`origin`, or the first configured remote). */
|
|
45
|
+
readonly name: string;
|
|
46
|
+
/** The configured fetch URL, verbatim (ssh/scp/https/git form). */
|
|
47
|
+
readonly url: string;
|
|
48
|
+
/** A browsable https URL for the repo page; null when unparseable. */
|
|
49
|
+
readonly webUrl: string | null;
|
|
50
|
+
/** The remote hostname (e.g. `github.com`); null when unparseable. */
|
|
51
|
+
readonly host: string | null;
|
|
52
|
+
/** Known-host classification, for the status-bar icon. */
|
|
53
|
+
readonly hostKind: RemoteHostKind;
|
|
54
|
+
}
|
|
40
55
|
/** Immutable snapshot of one repository's status at `checkedAt`. */
|
|
41
56
|
export interface GitSnapshot {
|
|
42
57
|
/** Realpath of the repository root (work tree top). */
|
|
@@ -54,6 +69,10 @@ export interface GitSnapshot {
|
|
|
54
69
|
readonly untracked: number;
|
|
55
70
|
readonly ahead: number;
|
|
56
71
|
readonly behind: number;
|
|
72
|
+
/** True when the current branch tracks an upstream (@{upstream} resolves). */
|
|
73
|
+
readonly hasUpstream: boolean;
|
|
74
|
+
/** The repository's primary remote; null when none is configured. */
|
|
75
|
+
readonly remote: GitRemote | null;
|
|
57
76
|
readonly lastCommit: GitCommit | null;
|
|
58
77
|
readonly changes: readonly GitChange[];
|
|
59
78
|
/** Working-tree statistics for the changes-page header (single source). */
|
|
@@ -128,6 +147,8 @@ export type GitAction = {
|
|
|
128
147
|
readonly name: string;
|
|
129
148
|
} | {
|
|
130
149
|
readonly kind: 'fetch';
|
|
150
|
+
} | {
|
|
151
|
+
readonly kind: 'pull-ff';
|
|
131
152
|
} | {
|
|
132
153
|
readonly kind: 'tag-create';
|
|
133
154
|
readonly name: string;
|
|
@@ -162,7 +183,7 @@ export type GitAction = {
|
|
|
162
183
|
/** `git reset` modes, from least to most destructive. Single source so the
|
|
163
184
|
* client dialog and the command builder cannot drift. */
|
|
164
185
|
export type ResetMode = 'soft' | 'mixed' | 'hard';
|
|
165
|
-
export type GitErrorCode = 'cwd-unavailable' | 'not-a-git-repo' | 'git-unavailable' | 'invalid-path' | 'invalid-name' | 'invalid-index' | 'git-error' | 'timeout' | 'cancelled' | 'empty-message' | 'local-changes-block' | 'conflict' | 'revert-conflict' | 'revert-stuck' | 'revert-merge' | 'not-found' | 'index-busy' | 'empty-diff' | 'llm-unavailable' | 'llm-error' | 'llm-output' | 'suggest-disabled';
|
|
186
|
+
export type GitErrorCode = 'cwd-unavailable' | 'not-a-git-repo' | 'git-unavailable' | 'invalid-path' | 'invalid-name' | 'invalid-index' | 'git-error' | 'timeout' | 'cancelled' | 'empty-message' | 'local-changes-block' | 'not-ff' | 'conflict' | 'revert-conflict' | 'revert-stuck' | 'revert-merge' | 'not-found' | 'index-busy' | 'empty-diff' | 'llm-unavailable' | 'llm-error' | 'llm-output' | 'suggest-disabled';
|
|
166
187
|
export type GitActionResult = {
|
|
167
188
|
readonly ok: true;
|
|
168
189
|
readonly snapshot: GitSnapshot;
|
|
@@ -244,6 +265,8 @@ export type GitQuery = {
|
|
|
244
265
|
readonly kind: 'last-commit-message';
|
|
245
266
|
} | {
|
|
246
267
|
readonly kind: 'worktree-stats';
|
|
268
|
+
} | {
|
|
269
|
+
readonly kind: 'pull-preview';
|
|
247
270
|
};
|
|
248
271
|
/** One entry in a `dir-list` result. */
|
|
249
272
|
export interface DirEntry {
|
|
@@ -359,6 +382,13 @@ export type GitQueryResult = {
|
|
|
359
382
|
} | {
|
|
360
383
|
readonly kind: 'worktree-stats';
|
|
361
384
|
readonly stats: WorktreeStats;
|
|
385
|
+
} | {
|
|
386
|
+
readonly kind: 'pull-preview';
|
|
387
|
+
readonly hasUpstream: boolean;
|
|
388
|
+
readonly commits: number;
|
|
389
|
+
readonly files: number;
|
|
390
|
+
readonly insertions: number;
|
|
391
|
+
readonly deletions: number;
|
|
362
392
|
} | {
|
|
363
393
|
readonly kind: 'dir-list';
|
|
364
394
|
readonly path: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbzbing/dsh-git-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) plugin: an IDE-style Git panel in the Web GUI — branch/history overview, uncommitted changes with commit & amend, diff stats, and a zsh-style branch marker in the input bar.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|