@caozeal/dsh-next-git 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  >
5
5
  > 原名 `dsh-plugin-git-manager`,2026-10 起包名改为 `@caozeal/dsh-next-git`(目录仍为 `dsh-next-git/`,功能不变)。
6
6
 
7
- ## 41 秒介绍片
7
+ ## 45 秒介绍片
8
8
 
9
9
  https://github.com/user-attachments/assets/c57169b4-df00-450d-b4f0-7a2c64db03b6
10
10
 
package/lib/client.js CHANGED
@@ -115,6 +115,10 @@ window.__ModuleLoader__.load({
115
115
  'diff.deletions': '-{n}',
116
116
  'diff.staged': '暂存区',
117
117
  'diff.worktree': '工作区',
118
+ 'diff.conflict': '冲突差异 · 当前分支 ↔ 合入分支',
119
+ 'diff.conflictSame': '两边内容完全一致',
120
+ 'diff.conflictDeleted': '该文件在合并的某一侧被删除',
121
+ 'diff.conflictDeletedHint': '点右上角「解决冲突」决定保留还是删除',
118
122
  'diff.commit': '提交 {hash}',
119
123
  'diff.untracked': '新文件',
120
124
  'status.loading': '加载中…',
@@ -272,6 +276,10 @@ window.__ModuleLoader__.load({
272
276
  'diff.deletions': '-{n}',
273
277
  'diff.staged': 'Staged',
274
278
  'diff.worktree': 'Working tree',
279
+ 'diff.conflict': 'Conflict · ours ↔ theirs',
280
+ 'diff.conflictSame': 'Both sides are identical',
281
+ 'diff.conflictDeleted': 'The file was deleted on one side of the merge',
282
+ 'diff.conflictDeletedHint': 'Use "Resolve conflict" to keep or delete it',
275
283
  'diff.commit': 'Commit {hash}',
276
284
  'diff.untracked': 'New file',
277
285
  'status.loading': 'Loading…',
@@ -1960,9 +1968,11 @@ window.__ModuleLoader__.load({
1960
1968
  var diff = state.diff
1961
1969
  if (state.diffLoading) return h(EmptyState, { spinner: true, title: tr('status.loading') })
1962
1970
  if (!diff) return h(EmptyState, { title: tr('diff.empty') })
1963
- var title = diff.mode === 'commit'
1964
- ? tr('diff.commit', { hash: state.selected && state.selected.hash ? state.selected.hash.slice(0, 8) : '' })
1965
- : diff.mode === 'staged' ? tr('diff.staged') : diff.mode === 'untracked' ? tr('diff.untracked') : tr('diff.worktree')
1971
+ var title = diff.conflict
1972
+ ? tr('diff.conflict')
1973
+ : diff.mode === 'commit'
1974
+ ? tr('diff.commit', { hash: state.selected && state.selected.hash ? state.selected.hash.slice(0, 8) : '' })
1975
+ : diff.mode === 'staged' ? tr('diff.staged') : diff.mode === 'untracked' ? tr('diff.untracked') : tr('diff.worktree')
1966
1976
  return h('div', { className: 'gm-scroll' },
1967
1977
  h('div', { className: 'gm-content-inner gm-diff' },
1968
1978
  h('div', { className: 'gm-diff-head' },
@@ -1971,11 +1981,22 @@ window.__ModuleLoader__.load({
1971
1981
  h('span', { className: 'gm-spacer' }),
1972
1982
  h('span', { className: 'gm-badge ok' }, '+' + diff.additions),
1973
1983
  h('span', { className: 'gm-badge err' }, '-' + diff.deletions),
1974
- diff.truncated ? h('span', { className: 'gm-badge warn' }, tr('diff.truncatedShort')) : null),
1975
- diff.binary
1976
- ? h(EmptyState, { title: tr('diff.binary') })
1977
- : diff.hunks.length === 0
1978
- ? h(EmptyState, { title: tr('diff.noText') })
1984
+ diff.truncated ? h('span', { className: 'gm-badge warn' }, tr('diff.truncatedShort')) : null,
1985
+ diff.conflict
1986
+ ? h(Btn, {
1987
+ label: tr('conflict.resolve'),
1988
+ icon: 'ai',
1989
+ small: true,
1990
+ variant: 'primary',
1991
+ onClick: function () { props.actions.openResolution({ path: diff.path }) },
1992
+ })
1993
+ : null),
1994
+ diff.conflict && (!diff.oursExists || !diff.theirsExists)
1995
+ ? h(EmptyState, { title: tr('diff.conflictDeleted'), hint: tr('diff.conflictDeletedHint') })
1996
+ : diff.binary
1997
+ ? h(EmptyState, { title: tr('diff.binary') })
1998
+ : diff.hunks.length === 0
1999
+ ? h(EmptyState, { title: diff.conflict ? tr('diff.conflictSame') : tr('diff.noText') })
1979
2000
  : diff.hunks.map(function (hunk, hunkIndex) {
1980
2001
  return h('div', { key: 'h:' + hunkIndex },
1981
2002
  h('div', { className: 'gm-hunk' }, hunk.header),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caozeal/dsh-next-git",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "DSH Git 管理插件:分支切换、变更暂存、提交/推送/拉取、AI 提交信息、提交图与 diff 查看",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/operations.js CHANGED
@@ -289,6 +289,14 @@ export function createOperations(deps) {
289
289
  }
290
290
  const path = typeof params.path === 'string' && params.path !== '' ? params.path : ''
291
291
  const safePath = path !== '' && path.startsWith('-') ? `./${path}` : path
292
+ // 未合并的文件不能走普通 git diff:它给的是 combined diff(diff --cc / @@@ / ++ 前缀),
293
+ // 不是 unified diff,解析器读不出 hunk,面板会是空的。改成 diff 两个 stage 的 blob。
294
+ if (mode === 'worktree' && safePath !== '') {
295
+ const stages = await conflictStages(root, safePath, signal)
296
+ if (Object.keys(stages).length > 0) {
297
+ return await conflictDiff(root, safePath, stages, signal)
298
+ }
299
+ }
292
300
  /** @type {string[]} */
293
301
  let args
294
302
  if (mode === 'worktree') {
@@ -594,6 +602,49 @@ export function createOperations(deps) {
594
602
  return res.code === 0 ? res.stdout : null
595
603
  }
596
604
 
605
+ /**
606
+ * 冲突文件的差异视图:当前分支(:2:)↔ 合入分支(:3:)。
607
+ * 直接 `git diff <blob> <blob>` 是合法的,拿到的是普通 unified diff,行级高亮照样能用。
608
+ * 某一侧没有这个文件(删除/改名冲突)时没有可比的两份内容,只回元数据让面板说明情况。
609
+ */
610
+ async function conflictDiff(root, path, stages, signal) {
611
+ const oursHash = stages[2]
612
+ const theirsHash = stages[3]
613
+ const base = {
614
+ mode: 'conflict',
615
+ path,
616
+ conflict: true,
617
+ oursExists: Boolean(oursHash),
618
+ theirsExists: Boolean(theirsHash),
619
+ binary: false,
620
+ additions: 0,
621
+ deletions: 0,
622
+ hunks: [],
623
+ truncated: false,
624
+ raw: '',
625
+ }
626
+ if (!oursHash || !theirsHash) return base
627
+ const args = ['diff', '--no-color', oursHash, theirsHash]
628
+ const result = await runGit(args, { cwd: root, signal, allowFailure: true })
629
+ if (result.code !== 0 && result.code !== 1) throw gitFailure(['git', ...args], result)
630
+ let raw = result.stdout
631
+ let truncated = false
632
+ if (raw.length > DIFF_MAX_CHARS) {
633
+ raw = raw.slice(0, DIFF_MAX_CHARS)
634
+ truncated = true
635
+ }
636
+ const parsed = parseUnifiedDiff(raw)
637
+ return {
638
+ ...base,
639
+ binary: parsed.binary,
640
+ additions: parsed.additions,
641
+ deletions: parsed.deletions,
642
+ hunks: parsed.hunks,
643
+ truncated,
644
+ raw,
645
+ }
646
+ }
647
+
597
648
  /** 还剩几个未解决冲突(applyConflict / takeSide 之后用它回执)。 */
598
649
  async function unmergedCount(root, signal) {
599
650
  const res = await runGit(['diff', '--name-only', '--diff-filter=U', '-z'], {