dsh-gitbar-lite 0.2.0 → 0.2.1
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.en.md +5 -2
- package/README.md +4 -1
- package/lib/client.js +49 -2
- package/lib/host/repo.js +11 -0
- package/lib/index.js +9 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -23,6 +23,7 @@ The column is narrow (300px minimum, and a narrow viewport makes it full-screen)
|
|
|
23
23
|
| Commit | Commits exactly what is ticked — because ticking wrote the index |
|
|
24
24
|
| Commit and Push | Commits, then pushes. **A failed push never claims the commit failed**: the commit happened, and the panel says "committed `abc1234`, but the push failed" |
|
|
25
25
|
| Context button | **Push** when local commits are unpushed, **Publish** when the branch has no upstream, **Fetch** otherwise |
|
|
26
|
+
| Automatic fetch | Every five minutes while the panel is visible and the repository has a remote (`autoFetchMs`, `0` to disable). Failures are silent — no error banner |
|
|
26
27
|
| Discard | One file or all of them; the diff is archived *before* anything is restored, and the confirmation offers "delete for good" |
|
|
27
28
|
| Trash | Its own tab. Every discard keeps a diff, recorded **against the commit it was taken from** (`abc1234 initial commit`) |
|
|
28
29
|
| Diff preview | Double-click a file, or a trash entry, to open a diff tab in the column — **one tab, ever**, re-navigated in place |
|
|
@@ -137,12 +138,14 @@ The order is fixed and cannot be swapped:
|
|
|
137
138
|
| `maxFileBytes` | `2097152` | Larger untracked files are listed but not read, archived, or diffed |
|
|
138
139
|
| `trashLimit` | `200` | Trash entries kept; the oldest are dropped along with their diff files |
|
|
139
140
|
| `scanDepth` | `3` | Directory depth the picker scans below the workspace root |
|
|
141
|
+
| `autoFetchMs` | `300000` | How often the panel fetches on its own; `0` disables it, leaving the manual button |
|
|
140
142
|
|
|
141
143
|
Every field is volatile, so a change on the settings page takes effect without a restart.
|
|
142
144
|
|
|
143
145
|
**Where state lives**: `<DSH_HOME>/dsh-gitbar-lite/` (falling back to `~/.dsh`) — `bindings.json` (workspace →
|
|
144
|
-
repository), `prefs.json` (the two remembered choices),
|
|
145
|
-
goes through a temp file and a rename, so a
|
|
146
|
+
repository), `prefs.json` (the two remembered choices), `trash/index.json` plus `trash/<id>.diff`, and
|
|
147
|
+
`trash/<id>.content.json` (the raw contents a restore needs). Every write goes through a temp file and a rename, so a
|
|
148
|
+
crash mid-write cannot truncate a file.
|
|
146
149
|
|
|
147
150
|
## How it works
|
|
148
151
|
|
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ DSH 的右栏本来就是一个常驻的标签区。Git 面板放这里,意味
|
|
|
23
23
|
| Commit | 只提交勾上的(因为勾选写进了索引) |
|
|
24
24
|
| Commit and Push | 提交后立刻推送;**推送失败不会说「提交失败」**——提交已经发生了,面板会明说「已提交 xxx,但推送失败」 |
|
|
25
25
|
| 上下文按钮 | 有本地未推送提交时是 **Push**,没有上游时是 **Publish**,否则是 **Fetch** |
|
|
26
|
+
| 自动 Fetch | 面板可见且仓库有远端时,每 5 分钟自己 fetch 一次(`autoFetchMs` 可调,`0` 关掉)。失败是静默的,不弹错误 |
|
|
26
27
|
| 舍弃更改 | 单个文件或「全部舍弃」;动手前先把这次改动的 diff 归档,确认框里可以勾「彻底删除」 |
|
|
27
28
|
| 回收站 | 独立 tab。每次舍弃存一份 diff,并记下**是对哪个提交(`abc1234 初始提交`)取的 diff** |
|
|
28
29
|
| diff 预览 | 双击文件,或点回收站里的一条,在右栏开一个 diff 标签页——**永远只占一个**,再点就复用 |
|
|
@@ -132,11 +133,13 @@ dsh plugin --profile <你的 profile> remove dsh-gitbar-lite
|
|
|
132
133
|
| `maxFileBytes` | `2097152` | 比这大的未跟踪文件只列出来,不读内容、不归档、不出 diff |
|
|
133
134
|
| `trashLimit` | `200` | 回收站保留多少条;超出丢弃最旧的连同它的 diff 文件 |
|
|
134
135
|
| `scanDepth` | `3` | 「不是仓库时」扫描工作区找仓库的目录深度 |
|
|
136
|
+
| `autoFetchMs` | `300000` | 面板自己多久 fetch 一次;`0` = 关掉,只用手动按钮 |
|
|
135
137
|
|
|
136
138
|
每个字段都是 volatile 的,所以在设置页改了立刻生效、不用重启。
|
|
137
139
|
|
|
138
140
|
**状态存在哪**:`<DSH_HOME>/dsh-gitbar-lite/`(没有 `DSH_HOME` 就回落 `~/.dsh`)——
|
|
139
|
-
`bindings.json`(工作区 → 仓库)、`prefs.json`(那两个勾选偏好)、`trash/index.json` + `trash/<id>.diff
|
|
141
|
+
`bindings.json`(工作区 → 仓库)、`prefs.json`(那两个勾选偏好)、`trash/index.json` + `trash/<id>.diff` +
|
|
142
|
+
`trash/<id>.content.json`(恢复用的原始内容)。
|
|
140
143
|
都是先写临时文件再 rename,所以写到一半崩了也不会把文件截断。
|
|
141
144
|
|
|
142
145
|
## 工作原理
|
package/lib/client.js
CHANGED
|
@@ -861,8 +861,11 @@ window.__ModuleLoader__.load({
|
|
|
861
861
|
'.gitbar-status-conflicted{color:var(--dsw-alias-state-error-primary,#b91c1c)}',
|
|
862
862
|
'.gitbar-path{flex:1 1 auto;min-width:0;display:flex;align-items:baseline;gap:0;overflow:hidden}',
|
|
863
863
|
'.gitbar-dir{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left;color:var(--dsw-alias-label-tertiary,#6b7280)}',
|
|
864
|
-
|
|
865
|
-
|
|
864
|
+
// `direction` is inherited, so the rtl above would otherwise reach the
|
|
865
|
+
// separator (moving the slash to the wrong end) and the file name (right-
|
|
866
|
+
// aligning it). Both are spelled back to ltr explicitly.
|
|
867
|
+
'.gitbar-sep{flex:none;direction:ltr;color:var(--dsw-alias-label-tertiary,#6b7280)}',
|
|
868
|
+
'.gitbar-name{flex:0 1 auto;min-width:0;direction:ltr;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
866
869
|
'.gitbar-stats{flex:none;display:flex;gap:3px;font-size:10.5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}',
|
|
867
870
|
'.gitbar-plus{color:var(--dsw-alias-state-success-primary,#15803d)}',
|
|
868
871
|
'.gitbar-minus{color:var(--dsw-alias-state-error-primary,#b91c1c)}',
|
|
@@ -1334,6 +1337,50 @@ window.__ModuleLoader__.load({
|
|
|
1334
1337
|
}
|
|
1335
1338
|
}, [tab.visible, sessionId, load])
|
|
1336
1339
|
|
|
1340
|
+
// Fetch on a rhythm, unattended, while the panel is actually being looked
|
|
1341
|
+
// at. Three conditions gate it, and each one exists for a reason:
|
|
1342
|
+
//
|
|
1343
|
+
// * the tab must be visible — a hidden panel has no business touching the
|
|
1344
|
+
// network every few minutes, and the host may have several sessions;
|
|
1345
|
+
// * the repository must have a remote — otherwise `fetch` is a guaranteed
|
|
1346
|
+
// E_NO_REMOTE, and a timer that only ever fails is noise;
|
|
1347
|
+
// * nothing else may be in flight — a user mid-commit must not have an
|
|
1348
|
+
// extra git child appear under them.
|
|
1349
|
+
//
|
|
1350
|
+
// A failure is silent. The button stays there, the banner stays quiet, and
|
|
1351
|
+
// the next tick tries again: a fetch that failed once (offline, credential
|
|
1352
|
+
// prompt suppressed) usually succeeds later, and a panel that pops an error
|
|
1353
|
+
// every five minutes for a laptop that was closed is worse than one that
|
|
1354
|
+
// says nothing. Success refreshes the status, which is what moves
|
|
1355
|
+
// ahead/behind and the "last fetch" line.
|
|
1356
|
+
//
|
|
1357
|
+
// The conditions are read through a ref rather than listed as dependencies
|
|
1358
|
+
// on purpose: a dependency on `state` would restart the clock on every
|
|
1359
|
+
// refresh, and a panel the user keeps interacting with would then never
|
|
1360
|
+
// fetch at all.
|
|
1361
|
+
const latest = useRef({ state: null, busy: null })
|
|
1362
|
+
latest.current = { state, busy }
|
|
1363
|
+
|
|
1364
|
+
const autoFetchMs = typeof state?.autoFetchMs === 'number' && state.autoFetchMs > 0 ? state.autoFetchMs : 0
|
|
1365
|
+
useEffect(() => {
|
|
1366
|
+
if (autoFetchMs === 0) return undefined
|
|
1367
|
+
if (tab.visible === false) return undefined
|
|
1368
|
+
|
|
1369
|
+
const timer = setInterval(() => {
|
|
1370
|
+
const current = latest.current
|
|
1371
|
+
if (current.busy !== null) return
|
|
1372
|
+
if (!Array.isArray(current.state?.remotes) || current.state.remotes.length === 0) return
|
|
1373
|
+
void rpc(ACTIONS.FETCH, { sessionId }, tab.signal)
|
|
1374
|
+
.then(() => {
|
|
1375
|
+
if (alive.current) void load({ quiet: true })
|
|
1376
|
+
})
|
|
1377
|
+
.catch(() => {
|
|
1378
|
+
// Deliberately swallowed: see the note above.
|
|
1379
|
+
})
|
|
1380
|
+
}, autoFetchMs)
|
|
1381
|
+
return () => clearInterval(timer)
|
|
1382
|
+
}, [autoFetchMs, tab.visible, tab.signal, sessionId, load])
|
|
1383
|
+
|
|
1337
1384
|
// The column's refresh shortcut, when it has one.
|
|
1338
1385
|
useEffect(
|
|
1339
1386
|
() => (typeof tab.actions?.bindCommands === 'function' ? tab.actions.bindCommands({ refresh: () => void load({ quiet: true }) }) : undefined),
|
package/lib/host/repo.js
CHANGED
|
@@ -206,6 +206,13 @@ async function fetchTimeOf(gitDirResult) {
|
|
|
206
206
|
* @param {object} config resolved plugin config
|
|
207
207
|
*/
|
|
208
208
|
export function createRepo(git, config) {
|
|
209
|
+
/**
|
|
210
|
+
* How often the panel should fetch on its own, in milliseconds, or 0 for
|
|
211
|
+
* "never". Resolved once: the value rides along with every status payload,
|
|
212
|
+
* because a client bundle cannot read the host's config.
|
|
213
|
+
*/
|
|
214
|
+
const autoFetchMs = Number.isFinite(config.autoFetchMs) && config.autoFetchMs > 0 ? Math.floor(config.autoFetchMs) : 0
|
|
215
|
+
|
|
209
216
|
/**
|
|
210
217
|
* Identity, branch and working-tree status of one repository.
|
|
211
218
|
*
|
|
@@ -305,6 +312,10 @@ export function createRepo(git, config) {
|
|
|
305
312
|
counts,
|
|
306
313
|
primary,
|
|
307
314
|
lastFetchAt,
|
|
315
|
+
// The panel cannot read the host's config (a client bundle has no access to
|
|
316
|
+
// it), so the rhythm it should fetch on travels with the status it is
|
|
317
|
+
// already reading. Zero means "never, on your own".
|
|
318
|
+
autoFetchMs,
|
|
308
319
|
}
|
|
309
320
|
}
|
|
310
321
|
|
package/lib/index.js
CHANGED
|
@@ -67,6 +67,15 @@ export const Config = vol(
|
|
|
67
67
|
trashLimit: z.number().default(200),
|
|
68
68
|
/** Directory depth the repository picker scans below the workspace root. */
|
|
69
69
|
scanDepth: z.number().default(3),
|
|
70
|
+
/**
|
|
71
|
+
* How often the panel fetches on its own, in milliseconds.
|
|
72
|
+
*
|
|
73
|
+
* A fetch only moves remote refs, never the working tree, so it is safe to
|
|
74
|
+
* do unattended — but it does hit the network and it does write
|
|
75
|
+
* `.git/FETCH_HEAD`, which is why it is opt-out rather than opt-in. Set to 0
|
|
76
|
+
* to disable it; the button still works.
|
|
77
|
+
*/
|
|
78
|
+
autoFetchMs: z.number().default(300000),
|
|
70
79
|
}),
|
|
71
80
|
)
|
|
72
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gitbar-lite",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A GitHub Desktop-style Git panel for DSH: changed files, tick-to-stage, Summary + Description, Commit and Commit and Push, discard-to-trash, and diff preview, in the built-in right sidebar.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|