dsh-ide-git 0.14.3 → 0.14.5
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/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +41 -15
- package/src/index.js +42 -3
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-ide-git",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.5",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ide-git",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.5",
|
|
4
4
|
"description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -6659,7 +6659,15 @@ window.__ModuleLoader__.load({
|
|
|
6659
6659
|
E('span', { className: 'dig-lf-number' }, row.old === undefined ? '' : String(row.old)),
|
|
6660
6660
|
E('span', { className: 'dig-lf-number' }, row.new === undefined ? '' : String(row.new)),
|
|
6661
6661
|
E('span', { className: 'dig-lf-sign' }, row.kind === 'add' ? '+' : row.kind === 'del' ? '\u2212' : ' '),
|
|
6662
|
-
|
|
6662
|
+
/* The guards are load-bearing (FileDiff.tsx uses `highlighted?.new?.get`):
|
|
6663
|
+
highlightLines returns undefined for unknown or not-yet-loaded
|
|
6664
|
+
grammars, so the first frame (and every plain-text file) renders
|
|
6665
|
+
with spans === undefined until the grammar store bumps a re-render.
|
|
6666
|
+
Without the guard the slot entry crashes and 0.2.x unmounts the
|
|
6667
|
+
whole preview pane (blank "Git 预览"). */
|
|
6668
|
+
E(DiffText, { text: row.text, spans: row.kind === 'add'
|
|
6669
|
+
? (spans.new === undefined ? undefined : spans.new.get(row.new))
|
|
6670
|
+
: (spans.old === undefined ? undefined : spans.old.get(row.old)) }))
|
|
6663
6671
|
}
|
|
6664
6672
|
|
|
6665
6673
|
/* The wrapped split row: two cells, each its own number + text. */
|
|
@@ -7972,13 +7980,19 @@ window.__ModuleLoader__.load({
|
|
|
7972
7980
|
setAiNote(null)
|
|
7973
7981
|
try {
|
|
7974
7982
|
const settings = props.commitSettings === undefined ? { model: '', reasoning: '', prompt: '' } : props.commitSettings
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7983
|
+
/* `base` carries the picked repository ({ cwd, repoRoot }) exactly like
|
|
7984
|
+
every other request: without it the host resolves the repository from
|
|
7985
|
+
the session cwd, which need not be one (several sibling repos). */
|
|
7986
|
+
const data = await props.request('commit-message', Object.assign(
|
|
7987
|
+
{},
|
|
7988
|
+
props.base === undefined ? { cwd: props.cwd } : props.base,
|
|
7989
|
+
{
|
|
7990
|
+
sessionId: props.sessionId,
|
|
7991
|
+
model: settings.model,
|
|
7992
|
+
reasoningEffort: settings.reasoning,
|
|
7993
|
+
prompt: settings.prompt,
|
|
7994
|
+
},
|
|
7995
|
+
))
|
|
7982
7996
|
setAiUndo(message === '' ? null : message)
|
|
7983
7997
|
setMessage(typeof data.message === 'string' ? data.message : '')
|
|
7984
7998
|
/* Say what the model did NOT see, instead of quietly sending less. */
|
|
@@ -8876,8 +8890,8 @@ window.__ModuleLoader__.load({
|
|
|
8876
8890
|
try {
|
|
8877
8891
|
const scopeObject = scope || {}
|
|
8878
8892
|
previewStore.set(commitHash !== null
|
|
8879
|
-
? { hash: commitHash, subject: seed.subject || '', cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId }
|
|
8880
|
-
: { path: seed.path, staged: seed.staged === true, cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId })
|
|
8893
|
+
? { hash: commitHash, subject: seed.subject || '', path: seed.path !== undefined && seed.path !== null ? seed.path : '', cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId }
|
|
8894
|
+
: { path: seed.path, staged: seed.staged === true, untracked: seed.untracked === true ? true : undefined, cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId })
|
|
8881
8895
|
sidebarRight.openTab(NATIVE_PREVIEW_KIND)
|
|
8882
8896
|
return true
|
|
8883
8897
|
} catch (error) { void error }
|
|
@@ -9360,7 +9374,7 @@ window.__ModuleLoader__.load({
|
|
|
9360
9374
|
const commitSettings = readCommitSettings()
|
|
9361
9375
|
const changesPane = E(ChangesPanel, {
|
|
9362
9376
|
t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
|
|
9363
|
-
request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings,
|
|
9377
|
+
request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
|
|
9364
9378
|
showIgnored: showIgnored,
|
|
9365
9379
|
onToggleIgnored: () => setShowIgnored((value) => !value),
|
|
9366
9380
|
// Commit-and-push carries the confirm flag with it: the button already
|
|
@@ -9439,7 +9453,7 @@ window.__ModuleLoader__.load({
|
|
|
9439
9453
|
['untracked', changes.untracked, false],
|
|
9440
9454
|
]
|
|
9441
9455
|
for (const entry of order) {
|
|
9442
|
-
if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2] }); return }
|
|
9456
|
+
if (entry[1].length > 0) { showDiff({ path: entry[1][0].path, staged: entry[2], untracked: entry[0] === 'untracked' }); return }
|
|
9443
9457
|
}
|
|
9444
9458
|
setNote(t('note.noChanges'))
|
|
9445
9459
|
}
|
|
@@ -10509,7 +10523,9 @@ window.__ModuleLoader__.load({
|
|
|
10509
10523
|
useEffect(() => previewStore.subscribe(() => { setSeed(previewStore.seed) }), [])
|
|
10510
10524
|
const seedKey = seed === null
|
|
10511
10525
|
? ''
|
|
10512
|
-
: (seed.hash !== undefined && seed.hash !== null
|
|
10526
|
+
: (seed.hash !== undefined && seed.hash !== null
|
|
10527
|
+
? 'c:' + String(seed.hash) + ':' + String(seed.path !== undefined && seed.path !== null ? seed.path : '')
|
|
10528
|
+
: 'w:' + String(seed.staged === true) + ':' + String(seed.untracked === true) + ':' + String(seed.path))
|
|
10513
10529
|
const [state, setState] = useState({ loading: false, patch: '', error: null })
|
|
10514
10530
|
const [refreshTick, setRefreshTick] = useState(0)
|
|
10515
10531
|
const [copied, setCopied] = useState(false)
|
|
@@ -10523,8 +10539,18 @@ window.__ModuleLoader__.load({
|
|
|
10523
10539
|
try {
|
|
10524
10540
|
const payload = { cwd: seed.cwd }
|
|
10525
10541
|
if (seed.repoRoot !== undefined && seed.repoRoot !== null && seed.repoRoot !== '') payload.repoRoot = seed.repoRoot
|
|
10526
|
-
if (seed.hash !== undefined && seed.hash !== null && seed.hash !== '')
|
|
10527
|
-
|
|
10542
|
+
if (seed.hash !== undefined && seed.hash !== null && seed.hash !== '') {
|
|
10543
|
+
payload.hash = seed.hash
|
|
10544
|
+
/* hash+path rides `git show <hash> -- <path>` (host-supported
|
|
10545
|
+
since the route exists): the preview follows the exact file
|
|
10546
|
+
the user clicked instead of the commit's first file. */
|
|
10547
|
+
if (seed.path !== undefined && seed.path !== null && seed.path !== '') payload.path = seed.path
|
|
10548
|
+
} else {
|
|
10549
|
+
payload.path = seed.path; payload.staged = seed.staged === true
|
|
10550
|
+
/* untracked files never ride `git diff -- path` (no index entry):
|
|
10551
|
+
the host switches to a --no-index /dev/null new-file patch. */
|
|
10552
|
+
if (seed.untracked === true) payload.untracked = true
|
|
10553
|
+
}
|
|
10528
10554
|
const data = await request('diff', payload)
|
|
10529
10555
|
if (alive === true) setState({ loading: false, patch: data === undefined || data === null || typeof data.patch !== 'string' ? '' : data.patch, error: null })
|
|
10530
10556
|
} catch (error) {
|
package/src/index.js
CHANGED
|
@@ -726,6 +726,38 @@ async function commitDetail(payload) {
|
|
|
726
726
|
async function diff(payload) {
|
|
727
727
|
const cwd = cwdOf(payload)
|
|
728
728
|
const root = await repoRootOf(cwd, payload)
|
|
729
|
+
/* Untracked files have no index entry, so plain `git diff [--cached] -- path`
|
|
730
|
+
is legitimately empty for them — the panel's change list still shows the
|
|
731
|
+
file and the preview must not go blank. `--no-index /dev/null <file>`
|
|
732
|
+
produces the honest "new file" patch; its exit code is 1 when differences
|
|
733
|
+
exist, which is data, not failure (only >1 rejects). */
|
|
734
|
+
if (payload.untracked === true) {
|
|
735
|
+
const rel = requireRelativePath(payload.path)
|
|
736
|
+
const absolute = path.isAbsolute(rel) ? rel : path.join(root, rel)
|
|
737
|
+
/* --no-index reads the file for real, so unlike the plain `git diff --`
|
|
738
|
+
form an absolute path must NOT be allowed to escape the repository. */
|
|
739
|
+
if (absolute !== root && !absolute.startsWith(root + path.sep)) throw badRequest('path must stay inside the repository')
|
|
740
|
+
const result = await runGit(root, ['diff', '--no-color', '--no-ext-diff', '-U3', '--no-index', '--', '/dev/null', absolute])
|
|
741
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
742
|
+
const detail = result.stderr.trim() === '' ? result.stdout.trim() : result.stderr.trim()
|
|
743
|
+
throw new PanelError('git-failed', 'git diff --no-index failed: ' + (detail === '' ? 'exit ' + result.code : detail), 409)
|
|
744
|
+
}
|
|
745
|
+
/* --no-index spells the b/ side with the absolute path we passed; rewrite
|
|
746
|
+
the header lines back to the repository-relative spelling so the preview
|
|
747
|
+
reads like every other patch (body lines are left untouched). */
|
|
748
|
+
let out = result.stdout
|
|
749
|
+
if (out.indexOf(absolute) >= 0 && out.indexOf('\n') !== -1) {
|
|
750
|
+
out = out.split('\n').map((line) => {
|
|
751
|
+
if (line.startsWith('diff --git ') && line.indexOf(absolute) >= 0) return 'diff --git a/' + rel + ' b/' + rel
|
|
752
|
+
/* the b/ side of --no-index carries the absolute path verbatim (its
|
|
753
|
+
leading slash merged into the separator): match '+++ b' + absolute. */
|
|
754
|
+
if (line.startsWith('+++ b' + absolute)) return '+++ b/' + rel
|
|
755
|
+
return line
|
|
756
|
+
}).join('\n')
|
|
757
|
+
}
|
|
758
|
+
const truncated = out.length > MAX_DIFF_CHARS
|
|
759
|
+
return { patch: truncated ? out.slice(0, MAX_DIFF_CHARS) : out, truncated }
|
|
760
|
+
}
|
|
729
761
|
const args = ['diff', '--no-color', '--no-ext-diff', '-U3']
|
|
730
762
|
if (payload.staged === true) args.push('--cached')
|
|
731
763
|
if (typeof payload.hash === 'string' && payload.hash.trim() !== '') {
|
|
@@ -1547,10 +1579,17 @@ async function commitMessage(payload) {
|
|
|
1547
1579
|
commitBusy = true
|
|
1548
1580
|
try {
|
|
1549
1581
|
const cwd = cwdOf(payload)
|
|
1582
|
+
/* The picked repository travels as `repoRoot`, and every other git call
|
|
1583
|
+
threads it through `repoRootOf`. Resolving the root from `cwd` alone made
|
|
1584
|
+
this the one route that answered not-a-repo in a workspace whose own root
|
|
1585
|
+
is not a repository — a workspace holding several sibling repos, which is
|
|
1586
|
+
exactly what the repo picker exists for. */
|
|
1587
|
+
const repoRoot = await repoRootOf(cwd, payload)
|
|
1588
|
+
const scope = { cwd: cwd, repoRoot: repoRoot }
|
|
1550
1589
|
const route = await commitRouteOf(payload)
|
|
1551
|
-
const sums = await summary(
|
|
1552
|
-
const stagedPatch = sums.changes.staged.length > 0 ? (await diff({
|
|
1553
|
-
const input = commitInputOf(stagedPatch, (await diff(
|
|
1590
|
+
const sums = await summary(scope)
|
|
1591
|
+
const stagedPatch = sums.changes.staged.length > 0 ? (await diff(Object.assign({}, scope, { staged: true }))).patch : ''
|
|
1592
|
+
const input = commitInputOf(stagedPatch, (await diff(scope)).patch)
|
|
1554
1593
|
if (input.files.length === 0) {
|
|
1555
1594
|
if (input.dropped > 0) {
|
|
1556
1595
|
throw new PanelError('only-ignored-changes', 'the only changes are lock files or binary files, which are not sent to the model; stage a source change first')
|