@charara97/dsh-session-sync 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +234 -2
- package/client.js +1017 -0
- package/cordis.patch.yml +5 -0
- package/docs/design-sync-model.md +182 -0
- package/icon.svg +7 -0
- package/index.js +628 -0
- package/lib/engine.js +2613 -0
- package/lib/progress.js +96 -0
- package/lib/session.js +239 -0
- package/lib/util.js +214 -0
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +56 -4
- package/tests/client-render.test.mjs +588 -0
- package/tests/delivery.test.mjs +399 -0
- package/tests/engine.test.mjs +373 -0
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# 同步模型规格(main = 立即可信 · 对象层面交付)
|
|
2
|
+
|
|
3
|
+
> 状态:**已定稿,待实现**。本文只描述逻辑。已确认的决策见文末「已定」。
|
|
4
|
+
|
|
5
|
+
## 0. 模型
|
|
6
|
+
|
|
7
|
+
- **main 是唯一真相**,而且是「立即可信」的:本端推送时,main 立刻接受本端的改动。
|
|
8
|
+
- 本端**永远不被迫拉取**:推送不修改本机工作树、不修改本机 HEAD、不修改真实 index。
|
|
9
|
+
推送的做法是在 git 对象层面构造一个交付提交,直接推进 main。
|
|
10
|
+
- 因此本端分支**永不分叉**,只可能「落后」。想拉的时候是**纯快进**。
|
|
11
|
+
- 同步单元是**逐个会话 / 逐个文件**,不是整条分支。
|
|
12
|
+
- **部分交付**:一次推送里,能交付的立刻交付;需要人裁决的留在待裁决里,不阻塞其他单元。
|
|
13
|
+
|
|
14
|
+
## 1. 记号
|
|
15
|
+
|
|
16
|
+
对每一个「单元」(一个会话、一个工作区文件、一个设置文件……)独立地看三个版本:
|
|
17
|
+
|
|
18
|
+
| 记号 | 含义 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `B` | 共同祖先版本,即 `git merge-base(本机 HEAD, main)` 时刻该单元的内容 |
|
|
21
|
+
| `L` | 本端版本(已提交进本端历史的) |
|
|
22
|
+
| `R` | main 版本 |
|
|
23
|
+
|
|
24
|
+
推送前先算 `B`,然后看 `L` 和 `R` 相对 `B` 怎么变。**每个单元独立判定、独立决策。**
|
|
25
|
+
|
|
26
|
+
## 2. 表 A — 单元状态判定
|
|
27
|
+
|
|
28
|
+
| # | B | L | R | 状态名 | 判定条件 |
|
|
29
|
+
| --- | --- | --- | --- | --- | --- |
|
|
30
|
+
| A1 | 有 | 有 | 有 | 已同步 | `L == R` |
|
|
31
|
+
| A2 | 有 | 有 | 有 | 仅本端改 | `L != B` 且 `R == B` |
|
|
32
|
+
| A3 | 有 | 有 | 有 | 仅远端改 | `R != B` 且 `L == B` |
|
|
33
|
+
| A4 | 有 | 有 | 有 | 两边都改·可拼接 | 会话日志:`L` 是 `R` 的严格后缀,或反之(纯追加) |
|
|
34
|
+
| A5 | 有 | 有 | 有 | 两边都改·真冲突 | 其余情况(文本重叠、二进制、交错追加) |
|
|
35
|
+
| A6 | — | 有 | — | 本端新增 | |
|
|
36
|
+
| A7 | — | — | 有 | 远端新增 | |
|
|
37
|
+
| A8 | — | 有 | 有 | 双方独立新增同一 id | 退化到 A1 / A4 / A5 比较内容 |
|
|
38
|
+
| A9 | 有 | — | 有 | **本端删除**,远端未改 | `R == B` |
|
|
39
|
+
| A10 | 有 | — | — | 双方都删除 | |
|
|
40
|
+
| A11 | 有 | 有 | — | **远端删除**,本端未改 | `L == B` |
|
|
41
|
+
| A12 | 有 | — | 有(改) | 本端删除 + 远端修改 | `R != B` |
|
|
42
|
+
| A13 | 有 | 有(改) | — | 远端删除 + 本端修改 | `L != B` |
|
|
43
|
+
|
|
44
|
+
> **为什么 A4 只有「严格后缀」算可拼接**:会话日志的事件带 `seq` / `turn` / `step`,
|
|
45
|
+
> 这些是格式自身的关系。两边各自追加会让 `seq` 撞号,硬拼出来的日志无法解码。
|
|
46
|
+
> 所以「两边都追加了」是 A5(真冲突),不是 A4。
|
|
47
|
+
|
|
48
|
+
## 3. 表 B — 推送行为(本端 → main)
|
|
49
|
+
|
|
50
|
+
| 状态 | 行为 | main 的结果 | 本端的结果 |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| A1 已同步 | 不做任何事 | 不变 | 不变 |
|
|
53
|
+
| A2 仅本端改 | 把 `L` 放进交付树 | 变成 `L` | 不变 |
|
|
54
|
+
| A3 仅远端改 | 交付树保留 `R`(**不覆盖**) | 不变 | 不变(本端仍落后于它) |
|
|
55
|
+
| A4 可拼接 | 把拼接结果放进交付树 | 变成拼接结果 | 不变 |
|
|
56
|
+
| A5 真冲突 | **不进交付树**(保留 `R`),登记待裁决 | 不变 | 不变 |
|
|
57
|
+
| A6 本端新增 | 把 `L` 放进交付树 | 新增该单元 | 不变 |
|
|
58
|
+
| A7 远端新增 | 天然保留 `R` | 不变 | 不变 |
|
|
59
|
+
| A8 | 同 A1 / A4 / A5 | | |
|
|
60
|
+
| **A9 本端删除** | **登记待裁决**,交付树保留 `R` | 不变 | 不变 |
|
|
61
|
+
| A10 双方都删除 | 不做任何事 | 不变 | 不变 |
|
|
62
|
+
| A11 远端删除 | 不做任何事(删除的疑问只出现在取回侧) | 不变 | 不变 |
|
|
63
|
+
| A12 删除/修改冲突 | 登记待裁决,交付树保留 `R` | 不变 | 不变 |
|
|
64
|
+
| A13 修改/删除冲突 | 登记待裁决,交付树保持缺失(远端已删) | 不变 | 不变 |
|
|
65
|
+
|
|
66
|
+
**共同点:任何状态下,本端工作树、本端 HEAD、真实 index 都不被修改。**
|
|
67
|
+
**待裁决的单元不阻塞其他单元交付**:交付树对它保留 main 的版本,其余照常叠加并推送。
|
|
68
|
+
|
|
69
|
+
## 4. 表 C — 取回行为(main → 本端)
|
|
70
|
+
|
|
71
|
+
| 状态 | 行为 |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| A1 / A2 / A6 / A10 | 不做任何事 |
|
|
74
|
+
| A3 仅远端改 | 把 `R` 写进本机 |
|
|
75
|
+
| A4 可拼接 | 拼接结果写进本机 |
|
|
76
|
+
| A5 真冲突 | **暂停并等待裁决**,本机文件不做改动 |
|
|
77
|
+
| A7 远端新增 | 写进本机(新会话 / 新文件) |
|
|
78
|
+
| A8 | 同 A1 / A4 / A5 |
|
|
79
|
+
| A9 本端删除 | 不做任何事(本机的删除保持本机) |
|
|
80
|
+
| **A11 远端删除** | **登记待裁决**,本机文件不动 |
|
|
81
|
+
| A12 / A13 | 停手等裁决 |
|
|
82
|
+
|
|
83
|
+
## 5. 表 D — 忽略
|
|
84
|
+
|
|
85
|
+
忽略是**本机专属设置**,存 `<DSH_HOME>/.chat-sync/ignore.json`(已被 gitignore)。
|
|
86
|
+
**不放进 `chat-sync.json`**,否则 A 机器的忽略会静默改变 B 机器的行为。
|
|
87
|
+
|
|
88
|
+
| 范围 | 语义 |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| 单个会话 | 该会话在本机**完全退出同步**:不推、不拉、不产生待裁决 |
|
|
91
|
+
| 整个工作区 | 该工作区完全跳过:不建影子仓库、不推不拉 |
|
|
92
|
+
| 单个工作区文件 | 命中的路径**从不进入影子仓库**(写进 `info/exclude`),因此天然不推不拉 |
|
|
93
|
+
| 工作目录里的忽略规则 | 在工作区根目录放 ignore 文件(gitignore 语法),内容并入 `info/exclude` |
|
|
94
|
+
|
|
95
|
+
| 情形 | 行为 |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| 被忽略的会话在 main 上有新版本 | 本机不更新(忽略优先),main 不变 |
|
|
98
|
+
| 被忽略的会话在本机有新版本 | 不进交付树;main 保留自己的版本 |
|
|
99
|
+
| 被忽略的会话在本机被删除 | 不影响 main,也不产生待裁决 |
|
|
100
|
+
| 取消忽略 | 重新纳入同步,按表 A 重新判定 |
|
|
101
|
+
|
|
102
|
+
**关键性质**:被忽略的会话即使是「落后」状态,也**不阻塞**其他会话交付给 main。
|
|
103
|
+
|
|
104
|
+
## 6. 表 E — 交付提交怎么构造
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
起点 = main 的树
|
|
108
|
+
对每个 L != B 的单元,按表 B 决定是否叠加、叠加什么
|
|
109
|
+
待裁决的条目 = 交付树中保留 main 的版本(不叠加)
|
|
110
|
+
父提交 = main 与 本机 HEAD(两个父)
|
|
111
|
+
推送 = git push origin <交付提交>:refs/heads/main (快进,不需要 force)
|
|
112
|
+
不修改 = 本机 HEAD、本机工作树、真实 index
|
|
113
|
+
交付树 == main 的树时,不推送,只报告待裁决项
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**必须是双父合并提交**,不能是单父普通提交。原因:
|
|
117
|
+
单父的话本机 HEAD 永远不会成为 main 的祖先 → 本机分支永远分叉 →
|
|
118
|
+
`merge-base` 不前进 → 每次推送都要重新交付累积的全部改动,
|
|
119
|
+
而且本机无法再用快进追上。
|
|
120
|
+
|
|
121
|
+
## 7. 表 F — 边界与竞态
|
|
122
|
+
|
|
123
|
+
| 情形 | 行为 |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| 构造交付提交期间 main 又有新提交 | 无影响:交付树从最新的 main 构造 |
|
|
126
|
+
| 构造完成后、push 时 main 又动了 | push 被拒(非快进)→ 重新 fetch 并重建交付提交,重试若干次 |
|
|
127
|
+
| 无共同历史(全新机器 / 两边各自 init) | `B` 取 main → 「我的改动」= 本机全部内容;同路径不同内容 → 待裁决 |
|
|
128
|
+
| 本机没有任何提交 | 没有可交付的改动 → 直接报告,不动 main |
|
|
129
|
+
| 本机为空且 main 有内容 | 走取回路径(表 C) |
|
|
130
|
+
| 附件等不可变文件 | 内容寻址,同名 = 同内容 → 永远是 A1,不会冲突 |
|
|
131
|
+
|
|
132
|
+
### 7.1 投影缓存的例外
|
|
133
|
+
|
|
134
|
+
`storages/session_projcache/**` 是 DSH 自己会重建的**派生数据**,内容是
|
|
135
|
+
`{ver, seq, val}` 这类小 JSON。它跟着会话走,所以只要会话分叉,它几乎必然在同一行上重叠。
|
|
136
|
+
|
|
137
|
+
若对其同样「一律等待裁决」,则**每个 A4(纯追加、本可自动合并)的会话都会额外产生一条缓存
|
|
138
|
+
裁决项**,从而抵消自动合并的收益,也正是旧插件假冲突的来源。
|
|
139
|
+
|
|
140
|
+
因此:**缓存路径两边都改时直接取本机,不产生裁决项**(DSH 会重建)。
|
|
141
|
+
这是本规格里唯一的自动裁决例外,日志里会写明确。
|
|
142
|
+
|
|
143
|
+
## 8. 推送完成后本机的状态
|
|
144
|
+
|
|
145
|
+
- 本机 HEAD 不变、工作树不变、index 不变。
|
|
146
|
+
- 本机分支**落后 main N 个提交**,而这 N 个提交里**包含本端自己刚交付的那批**。
|
|
147
|
+
- 本机与 main **永不分叉** → 想拉时是纯快进。
|
|
148
|
+
- **因此界面不能用「领先 / 落后」表述** —— 「落后」里混着本端自己已经交付的东西,
|
|
149
|
+
会让人以为丢了数据。应改成逐单元状态:`已同步 / 仅本端改了 / 仅远端改了 / 两边都改了 / 已忽略`。
|
|
150
|
+
- 建议提供**「重新对齐(纯快进)」**:把本机拉到与远端一致,永远安全、不产生合并提交。
|
|
151
|
+
|
|
152
|
+
## 9. scope(范围)的真实含义
|
|
153
|
+
|
|
154
|
+
- scope **只决定「哪些改动被提交进本端历史」**(推送前的那一步 `git add` / `git commit`)。
|
|
155
|
+
- **一旦提交,下一次推送就会交付它们**,与本次 scope 无关。
|
|
156
|
+
- 这是必须的:交付提交的父提交包含本机 HEAD,如果交付树故意排除某个已提交的改动,
|
|
157
|
+
本机后续快进到该交付提交时,该改动会被**回退**。
|
|
158
|
+
|
|
159
|
+
## 10. 待裁决的选项
|
|
160
|
+
|
|
161
|
+
| 冲突类型 | 选项 | 是否记住选择 |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| A5 真内容冲突(会话) | 保留本机 / 采用远端 / 两边都留(导入为新会话) | 否(下次状态变了要重新判定) |
|
|
164
|
+
| A5 真内容冲突(其他文件) | 保留本机 / 采用远端 / 两边都留(另存副本) | 否 |
|
|
165
|
+
| A12 / A13 删除 vs 修改 | 删除 / 保留 | 否 |
|
|
166
|
+
| **A9 本端删除** | 同步删除到 main / 保留在 main(本机也随之恢复) | **是** —— 否则每次推送都会重问同一个删除 |
|
|
167
|
+
| **A11 远端删除** | 本机也删除 / 本机保留 | **是** —— 同上 |
|
|
168
|
+
|
|
169
|
+
所有裁决项都要能**看到两侧的具体内容**:会话给两侧各自的事件数 + 最后几条消息,
|
|
170
|
+
文件给两侧全文,删除类给"哪一侧删了、另一侧最后改成什么"。
|
|
171
|
+
|
|
172
|
+
## 11. 已定
|
|
173
|
+
|
|
174
|
+
1. main = **立即可信**,用对象层面合并直推 main,**不引入侧分支**。
|
|
175
|
+
2. 交付提交必须是**双父合并提交**(否则本机永远无法成为 main 的祖先)。
|
|
176
|
+
3. **部分交付**:能交付的先交付,冲突的留下等裁决,不阻塞。
|
|
177
|
+
4. **忽略**粒度:单个会话、整个工作区、单个工作区文件(含工作目录里的 ignore 文件)。
|
|
178
|
+
忽略是**本机专属**。
|
|
179
|
+
5. **删除也当冲突,每次问我**(A9 在推送侧、A11 在取回侧)。
|
|
180
|
+
6. **非会话文件的真冲突一律停手等裁决**,不设"本机优先"的默认值。
|
|
181
|
+
7. 唯一的自动裁决例外:**投影缓存**(见 §7.1)。
|
|
182
|
+
8. 已确认的裁决项要**记住选择**,避免反复问同一件事。
|
package/icon.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Chat Sync">
|
|
2
|
+
<g fill="none" stroke="#2563eb" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<path d="M14 18h24a8 8 0 0 1 8 8v10a8 8 0 0 1-8 8H26l-10 8v-8h-2a6 6 0 0 1-6-6V24a6 6 0 0 1 6-6z"/>
|
|
4
|
+
<path d="M50 26v10" stroke="#059669"/>
|
|
5
|
+
<path d="M45 32l5 5 5-5" stroke="#059669"/>
|
|
6
|
+
</g>
|
|
7
|
+
</svg>
|