dsh-ide-git 0.14.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.14.4",
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.4",
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
@@ -7980,13 +7980,19 @@ window.__ModuleLoader__.load({
7980
7980
  setAiNote(null)
7981
7981
  try {
7982
7982
  const settings = props.commitSettings === undefined ? { model: '', reasoning: '', prompt: '' } : props.commitSettings
7983
- const data = await props.request('commit-message', {
7984
- cwd: props.cwd,
7985
- sessionId: props.sessionId,
7986
- model: settings.model,
7987
- reasoningEffort: settings.reasoning,
7988
- prompt: settings.prompt,
7989
- })
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
+ ))
7990
7996
  setAiUndo(message === '' ? null : message)
7991
7997
  setMessage(typeof data.message === 'string' ? data.message : '')
7992
7998
  /* Say what the model did NOT see, instead of quietly sending less. */
@@ -8885,7 +8891,7 @@ window.__ModuleLoader__.load({
8885
8891
  const scopeObject = scope || {}
8886
8892
  previewStore.set(commitHash !== null
8887
8893
  ? { hash: commitHash, subject: seed.subject || '', path: seed.path !== undefined && seed.path !== null ? seed.path : '', cwd: scopeObject.cwd, repoRoot: repoRoot, sessionId: scopeObject.sessionId }
8888
- : { path: seed.path, staged: seed.staged === true, 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 })
8889
8895
  sidebarRight.openTab(NATIVE_PREVIEW_KIND)
8890
8896
  return true
8891
8897
  } catch (error) { void error }
@@ -9368,7 +9374,7 @@ window.__ModuleLoader__.load({
9368
9374
  const commitSettings = readCommitSettings()
9369
9375
  const changesPane = E(ChangesPanel, {
9370
9376
  t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
9371
- request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings,
9377
+ request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
9372
9378
  showIgnored: showIgnored,
9373
9379
  onToggleIgnored: () => setShowIgnored((value) => !value),
9374
9380
  // Commit-and-push carries the confirm flag with it: the button already
@@ -9447,7 +9453,7 @@ window.__ModuleLoader__.load({
9447
9453
  ['untracked', changes.untracked, false],
9448
9454
  ]
9449
9455
  for (const entry of order) {
9450
- 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 }
9451
9457
  }
9452
9458
  setNote(t('note.noChanges'))
9453
9459
  }
@@ -10519,7 +10525,7 @@ window.__ModuleLoader__.load({
10519
10525
  ? ''
10520
10526
  : (seed.hash !== undefined && seed.hash !== null
10521
10527
  ? 'c:' + String(seed.hash) + ':' + String(seed.path !== undefined && seed.path !== null ? seed.path : '')
10522
- : 'w:' + String(seed.staged === true) + ':' + String(seed.path))
10528
+ : 'w:' + String(seed.staged === true) + ':' + String(seed.untracked === true) + ':' + String(seed.path))
10523
10529
  const [state, setState] = useState({ loading: false, patch: '', error: null })
10524
10530
  const [refreshTick, setRefreshTick] = useState(0)
10525
10531
  const [copied, setCopied] = useState(false)
@@ -10539,7 +10545,12 @@ window.__ModuleLoader__.load({
10539
10545
  since the route exists): the preview follows the exact file
10540
10546
  the user clicked instead of the commit's first file. */
10541
10547
  if (seed.path !== undefined && seed.path !== null && seed.path !== '') payload.path = seed.path
10542
- } else { payload.path = seed.path; payload.staged = seed.staged === true }
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
+ }
10543
10554
  const data = await request('diff', payload)
10544
10555
  if (alive === true) setState({ loading: false, patch: data === undefined || data === null || typeof data.patch !== 'string' ? '' : data.patch, error: null })
10545
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({ cwd: cwd })
1552
- const stagedPatch = sums.changes.staged.length > 0 ? (await diff({ cwd: cwd, staged: true })).patch : ''
1553
- const input = commitInputOf(stagedPatch, (await diff({ cwd: cwd })).patch)
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')