isomorphic-jj 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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +971 -0
  3. package/dist/index.cjs +12609 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.mjs +12600 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +90 -0
  8. package/src/api/repository.js +6885 -0
  9. package/src/backend/isomorphic-git-backend.js +687 -0
  10. package/src/backend/lazy-git-backend.js +180 -0
  11. package/src/browser/helpers.js +189 -0
  12. package/src/browser/index.d.ts +174 -0
  13. package/src/browser/index.js +12 -0
  14. package/src/core/background-ops.js +272 -0
  15. package/src/core/bookmark-store.js +232 -0
  16. package/src/core/change-graph.js +285 -0
  17. package/src/core/conflict-model.js +505 -0
  18. package/src/core/jj-checkout.js +78 -0
  19. package/src/core/jj-operation-store.js +120 -0
  20. package/src/core/jj-tree-state.js +94 -0
  21. package/src/core/jj-view-store.js +105 -0
  22. package/src/core/merge-driver-registry.js +432 -0
  23. package/src/core/operation-log.js +272 -0
  24. package/src/core/revset-engine.js +1081 -0
  25. package/src/core/storage-manager.js +207 -0
  26. package/src/core/tag-store.js +167 -0
  27. package/src/core/user-config.js +142 -0
  28. package/src/core/working-copy.js +314 -0
  29. package/src/core/workspace-manager.js +283 -0
  30. package/src/drivers/built-in-drivers.js +296 -0
  31. package/src/index.js +15 -0
  32. package/src/protos/local_working_copy.proto +76 -0
  33. package/src/protos/simple_op_store.proto +147 -0
  34. package/src/types.d.ts +1129 -0
  35. package/src/utils/errors.js +21 -0
  36. package/src/utils/id-generation.js +54 -0
  37. package/src/utils/validation.js +124 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * ChangeGraph - Manages the change graph with stable change IDs
3
+ *
4
+ * Tracks changes, their relationships (parents/children), and evolution (amends/rewrites).
5
+ */
6
+
7
+ import { JJError } from '../utils/errors.js';
8
+ import { validateChangeId } from '../utils/validation.js';
9
+
10
+ export class ChangeGraph {
11
+ /**
12
+ * @param {Storage} storage - Storage manager instance
13
+ */
14
+ constructor(storage) {
15
+ this.storage = storage;
16
+ this.nodes = new Map(); // changeId → Change
17
+ this.commitIndex = new Map(); // commitId → changeId
18
+ }
19
+
20
+ /**
21
+ * Initialize empty graph
22
+ */
23
+ async init() {
24
+ this.nodes.clear();
25
+ this.commitIndex.clear();
26
+
27
+ await this.storage.write('repo/store/graph.json', {
28
+ version: 1,
29
+ changes: {},
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Load graph from storage
35
+ */
36
+ async load() {
37
+ const data = await this.storage.read('repo/store/graph.json');
38
+
39
+ if (!data) {
40
+ throw new JJError('STORAGE_CORRUPT', 'repo/store/graph.json not found', {
41
+ suggestion: 'Initialize repository with init() or check .jj directory',
42
+ });
43
+ }
44
+
45
+ if (data.version !== 1) {
46
+ throw new JJError('STORAGE_VERSION_MISMATCH', `Unsupported graph version: ${data.version}`, {
47
+ version: data.version,
48
+ suggestion: 'Upgrade isomorphic-jj or run migration tool',
49
+ });
50
+ }
51
+
52
+ this.nodes.clear();
53
+ this.commitIndex.clear();
54
+
55
+ for (const [changeId, change] of Object.entries(data.changes)) {
56
+ this.nodes.set(changeId, change);
57
+ this.commitIndex.set(change.commitId, changeId);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Save graph to storage
63
+ */
64
+ async save() {
65
+ const changes = {};
66
+ for (const [changeId, change] of this.nodes.entries()) {
67
+ changes[changeId] = change;
68
+ }
69
+
70
+ await this.storage.write('repo/store/graph.json', {
71
+ version: 1,
72
+ changes,
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Add a change to the graph
78
+ *
79
+ * @param {Object} change - Change object
80
+ */
81
+ async addChange(change) {
82
+ validateChangeId(change.changeId);
83
+
84
+ if (this.nodes.has(change.changeId)) {
85
+ throw new JJError('CHANGE_EXISTS', `Change ${change.changeId} already exists`, {
86
+ changeId: change.changeId,
87
+ suggestion: 'Use evolveChange() to update an existing change',
88
+ });
89
+ }
90
+
91
+ this.nodes.set(change.changeId, change);
92
+ this.commitIndex.set(change.commitId, change.changeId);
93
+
94
+ await this.save();
95
+ }
96
+
97
+ /**
98
+ * Get a change by ID
99
+ *
100
+ * @param {string} changeId - Change ID
101
+ * @returns {Object|null} Change object or null if not found
102
+ */
103
+ async getChange(changeId) {
104
+ validateChangeId(changeId);
105
+ return this.nodes.get(changeId) || null;
106
+ }
107
+
108
+ /**
109
+ * Get all changes
110
+ *
111
+ * @returns {Array} Array of all changes
112
+ */
113
+ getAll() {
114
+ return Array.from(this.nodes.values());
115
+ }
116
+
117
+ /**
118
+ * Find change by commit ID
119
+ *
120
+ * @param {string} commitId - Git commit SHA-1
121
+ * @returns {string|null} Change ID or null if not found
122
+ */
123
+ findByCommitId(commitId) {
124
+ return this.commitIndex.get(commitId) || null;
125
+ }
126
+
127
+ /**
128
+ * Get parent change IDs
129
+ *
130
+ * @param {string} changeId - Change ID
131
+ * @returns {Array<string>} Array of parent change IDs
132
+ */
133
+ getParents(changeId) {
134
+ const change = this.nodes.get(changeId);
135
+ return change ? change.parents : [];
136
+ }
137
+
138
+ /**
139
+ * Get child change IDs
140
+ *
141
+ * @param {string} changeId - Change ID
142
+ * @returns {Array<string>} Array of child change IDs
143
+ */
144
+ getChildren(changeId) {
145
+ const children = [];
146
+
147
+ for (const change of this.nodes.values()) {
148
+ if (change.parents.includes(changeId)) {
149
+ children.push(change.changeId);
150
+ }
151
+ }
152
+
153
+ return children;
154
+ }
155
+
156
+ /**
157
+ * Evolve a change (update commitId, track predecessor)
158
+ *
159
+ * Used when amending or rewriting a change.
160
+ *
161
+ * @param {string} changeId - Change ID
162
+ * @param {string} newCommitId - New commit SHA-1
163
+ */
164
+ async evolveChange(changeId, newCommitId) {
165
+ validateChangeId(changeId);
166
+
167
+ const change = this.nodes.get(changeId);
168
+ if (!change) {
169
+ throw new JJError('CHANGE_NOT_FOUND', `Change ${changeId} not found`, {
170
+ changeId,
171
+ suggestion: 'Check change ID or use log() to see available changes',
172
+ });
173
+ }
174
+
175
+ // Track predecessor
176
+ if (!change.predecessors) {
177
+ change.predecessors = [];
178
+ }
179
+ change.predecessors.push(change.commitId);
180
+
181
+ // Update commit index
182
+ this.commitIndex.delete(change.commitId);
183
+ change.commitId = newCommitId;
184
+ this.commitIndex.set(newCommitId, changeId);
185
+
186
+ await this.save();
187
+ }
188
+
189
+ /**
190
+ * Update a change (modify in place)
191
+ *
192
+ * @param {Object} change - Updated change object
193
+ */
194
+ async updateChange(change) {
195
+ validateChangeId(change.changeId);
196
+
197
+ if (!this.nodes.has(change.changeId)) {
198
+ throw new JJError('CHANGE_NOT_FOUND', `Change ${change.changeId} not found`, {
199
+ changeId: change.changeId,
200
+ suggestion: 'Use addChange() to create a new change',
201
+ });
202
+ }
203
+
204
+ this.nodes.set(change.changeId, change);
205
+ await this.save();
206
+ }
207
+
208
+ /**
209
+ * Get all ancestors of a change (recursive parent traversal)
210
+ *
211
+ * @param {string} changeId - Starting change ID
212
+ * @returns {Array<string>} Array of ancestor change IDs (breadth-first order)
213
+ */
214
+ getAncestors(changeId) {
215
+ const ancestors = [];
216
+ const visited = new Set();
217
+ const queue = [changeId];
218
+
219
+ while (queue.length > 0) {
220
+ const current = queue.shift();
221
+
222
+ if (visited.has(current)) {
223
+ continue;
224
+ }
225
+ visited.add(current);
226
+
227
+ const parents = this.getParents(current);
228
+ for (const parent of parents) {
229
+ if (!visited.has(parent)) {
230
+ ancestors.push(parent);
231
+ queue.push(parent);
232
+ }
233
+ }
234
+ }
235
+
236
+ return ancestors;
237
+ }
238
+
239
+ /**
240
+ * Create a change with default values (helper for tests)
241
+ *
242
+ * @param {Object} params - Change parameters
243
+ * @param {string[]} [params.parents] - Parent change IDs
244
+ * @param {string} [params.description] - Change description
245
+ * @param {Object} [params.fileSnapshot] - File snapshot
246
+ * @param {Object} [params.conflicts] - Conflict markers
247
+ * @returns {Promise<Object>} Created change object
248
+ */
249
+ async createChange(params = {}) {
250
+ const crypto = await import('crypto');
251
+ const changeId = params.changeId || crypto.randomBytes(16).toString('hex');
252
+ const commitId = params.commitId || crypto.randomBytes(20).toString('hex');
253
+ const tree = params.tree || crypto.randomBytes(20).toString('hex');
254
+ const timestamp = params.timestamp || new Date().toISOString();
255
+
256
+ const change = {
257
+ changeId,
258
+ parents: params.parents || [],
259
+ description: params.description || '',
260
+ fileSnapshot: params.fileSnapshot || {},
261
+ commitId,
262
+ tree,
263
+ author: params.author || {
264
+ name: 'Test User',
265
+ email: 'test@example.com',
266
+ timestamp,
267
+ },
268
+ committer: params.committer || {
269
+ name: 'Test User',
270
+ email: 'test@example.com',
271
+ timestamp,
272
+ },
273
+ timestamp,
274
+ };
275
+
276
+ // Add conflicts if provided
277
+ if (params.conflicts) {
278
+ change.conflicts = params.conflicts;
279
+ }
280
+
281
+ await this.addChange(change);
282
+
283
+ return change;
284
+ }
285
+ }