@anionex/dsh-turn-rewind 0.2.2 → 0.3.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/README.md +13 -4
- package/README.zh.md +13 -4
- package/docs/FORMAT.md +5 -1
- package/lib/client.js +7 -0
- package/lib/client.js.map +1 -1
- package/lib/directory.js +162 -0
- package/lib/engine.js +94 -56
- package/lib/git.js +46 -0
- package/lib/rewind-host.js +16 -7
- package/lib/snapshot.js +13 -5
- package/lib/types/directory.d.ts +10 -0
- package/lib/types/engine.d.ts +6 -0
- package/lib/types/git.d.ts +15 -4
- package/lib/types/snapshot.d.ts +2 -2
- package/lib/types/types.d.ts +15 -2
- package/lib/types/workspace.d.ts +37 -0
- package/lib/validate.js +23 -1
- package/lib/workspace.js +45 -0
- package/package.json +5 -2
- package/src/client/index.tsx +7 -0
- package/src/directory.ts +194 -0
- package/src/engine.ts +104 -68
- package/src/git.ts +49 -4
- package/src/rewind-host.ts +17 -8
- package/src/snapshot.ts +14 -6
- package/src/types.ts +18 -2
- package/src/validate.ts +21 -1
- package/src/workspace.ts +66 -0
package/src/validate.ts
CHANGED
|
@@ -102,7 +102,8 @@ export function parseManifest(value: unknown): RestorePointManifest {
|
|
|
102
102
|
}
|
|
103
103
|
if (version === LEDGER_FORMAT_VERSION) return { version, ...common }
|
|
104
104
|
if (restorePointKind !== 'turn') corrupt('Git-native v2 restore points are automatic turn checkpoints')
|
|
105
|
-
|
|
105
|
+
if (repository.type !== 'git') corrupt('Git-native v2 restore points must fence a Git workspace')
|
|
106
|
+
return { version, ...common, repository, git: parseGitCheckpoint(record.git, id) }
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** Parse an untrusted durable restore-operation journal. */
|
|
@@ -158,9 +159,27 @@ export function validateBlobHash(value: string): string {
|
|
|
158
159
|
return value
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Parse the durable workspace fence of one manifest.
|
|
164
|
+
*
|
|
165
|
+
* A missing `type` is the only legacy interpretation: manifests written before
|
|
166
|
+
* ordinary-directory support are always Git workspaces and are upgraded in
|
|
167
|
+
* memory. Directory fences must not carry Git-only fields.
|
|
168
|
+
* @param value - persisted `repository` field.
|
|
169
|
+
* @returns the validated workspace state.
|
|
170
|
+
*/
|
|
161
171
|
function parseRepository(value: unknown): RepositoryState {
|
|
162
172
|
const record = objectRecord(value, 'repository state')
|
|
163
173
|
const root = absoluteString(record, 'root')
|
|
174
|
+
if (record.type === 'directory') {
|
|
175
|
+
for (const key of ['commonDir', 'head', 'branch', 'operation', 'stagedPaths']) {
|
|
176
|
+
if (record[key] !== undefined) corrupt(`directory workspace state must not contain ${key}`)
|
|
177
|
+
}
|
|
178
|
+
return { type: 'directory', root }
|
|
179
|
+
}
|
|
180
|
+
if (record.type !== undefined && record.type !== 'git') {
|
|
181
|
+
corrupt('workspace state type must be "git" or "directory"')
|
|
182
|
+
}
|
|
164
183
|
const commonDir = absoluteString(record, 'commonDir')
|
|
165
184
|
const head = optionalHash(record, 'head')
|
|
166
185
|
const branch = optionalString(record, 'branch')
|
|
@@ -169,6 +188,7 @@ function parseRepository(value: unknown): RepositoryState {
|
|
|
169
188
|
if (!Array.isArray(stagedValue)) corrupt('repository stagedPaths must be an array')
|
|
170
189
|
const stagedPaths = stagedValue.map((path) => validateRelativePath(requireString(path, 'staged path')))
|
|
171
190
|
return {
|
|
191
|
+
type: 'git',
|
|
172
192
|
root,
|
|
173
193
|
commonDir,
|
|
174
194
|
...(head === undefined ? {} : { head }),
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { discoverDirectory, discoverDirectoryRoot, type DirectorySnapshotSource } from './directory.js'
|
|
2
|
+
import {
|
|
3
|
+
discoverRepositoryOptional,
|
|
4
|
+
discoverRepositoryRootOptional,
|
|
5
|
+
sameRepositoryFence,
|
|
6
|
+
type RepositorySnapshotSource,
|
|
7
|
+
} from './git.js'
|
|
8
|
+
import type { ResolvedChangeLedgerConfig, WorkspaceState } from './types.js'
|
|
9
|
+
|
|
10
|
+
/** Eligible snapshot source for either supported workspace mode. */
|
|
11
|
+
export type WorkspaceSnapshotSource = RepositorySnapshotSource | DirectorySnapshotSource
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Discover the owning Git worktree, or treat `cwd` itself as an ordinary
|
|
15
|
+
* directory when it is not inside any repository.
|
|
16
|
+
* @param cwd - working directory the Session runs in.
|
|
17
|
+
* @param config - capture limits applied to ordinary-directory enumeration.
|
|
18
|
+
* @param signal - optional caller cancellation.
|
|
19
|
+
* @returns the snapshot source for the owning workspace.
|
|
20
|
+
*/
|
|
21
|
+
export async function discoverWorkspace(
|
|
22
|
+
cwd: string,
|
|
23
|
+
config: Pick<ResolvedChangeLedgerConfig, 'maxFiles' | 'maxFileBytes'>,
|
|
24
|
+
signal?: AbortSignal,
|
|
25
|
+
): Promise<WorkspaceSnapshotSource> {
|
|
26
|
+
const repository = await discoverRepositoryOptional(cwd, signal)
|
|
27
|
+
return repository ?? discoverDirectory(cwd, config, signal)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Workspace mode and root resolved without inventorying any files. */
|
|
31
|
+
export interface WorkspaceIdentity {
|
|
32
|
+
readonly type: WorkspaceState['type']
|
|
33
|
+
readonly root: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the workspace mode and root without inventorying files, for callers
|
|
38
|
+
* that only need to address the workspace (locks, skip markers, storage purge).
|
|
39
|
+
* @param cwd - working directory the Session runs in.
|
|
40
|
+
* @param signal - optional caller cancellation.
|
|
41
|
+
* @returns the workspace mode and canonical root.
|
|
42
|
+
*/
|
|
43
|
+
export async function discoverWorkspaceIdentity(cwd: string, signal?: AbortSignal): Promise<WorkspaceIdentity> {
|
|
44
|
+
const root = await discoverRepositoryRootOptional(cwd, signal)
|
|
45
|
+
return root === undefined
|
|
46
|
+
? { type: 'directory', root: await discoverDirectoryRoot(cwd) }
|
|
47
|
+
: { type: 'git', root }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Resolve the workspace root without inventorying its files. */
|
|
51
|
+
export async function discoverWorkspaceRoot(cwd: string, signal?: AbortSignal): Promise<string> {
|
|
52
|
+
return (await discoverWorkspaceIdentity(cwd, signal)).root
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether two fences still describe the same workspace.
|
|
57
|
+
*
|
|
58
|
+
* Git workspaces keep the full repository fence (HEAD, branch, operation);
|
|
59
|
+
* ordinary directories only own their canonical root, so a moved or recreated
|
|
60
|
+
* root is the only thing that can invalidate them.
|
|
61
|
+
*/
|
|
62
|
+
export function sameWorkspaceFence(left: WorkspaceState, right: WorkspaceState): boolean {
|
|
63
|
+
if (left.type !== right.type || left.root !== right.root) return false
|
|
64
|
+
if (left.type === 'directory' || right.type === 'directory') return true
|
|
65
|
+
return sameRepositoryFence(left, right)
|
|
66
|
+
}
|