gitx.do 0.0.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.
Files changed (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/durable-object/object-store.d.ts +113 -0
  4. package/dist/durable-object/object-store.d.ts.map +1 -0
  5. package/dist/durable-object/object-store.js +387 -0
  6. package/dist/durable-object/object-store.js.map +1 -0
  7. package/dist/durable-object/schema.d.ts +17 -0
  8. package/dist/durable-object/schema.d.ts.map +1 -0
  9. package/dist/durable-object/schema.js +43 -0
  10. package/dist/durable-object/schema.js.map +1 -0
  11. package/dist/durable-object/wal.d.ts +111 -0
  12. package/dist/durable-object/wal.d.ts.map +1 -0
  13. package/dist/durable-object/wal.js +200 -0
  14. package/dist/durable-object/wal.js.map +1 -0
  15. package/dist/index.d.ts +24 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +101 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mcp/adapter.d.ts +231 -0
  20. package/dist/mcp/adapter.d.ts.map +1 -0
  21. package/dist/mcp/adapter.js +502 -0
  22. package/dist/mcp/adapter.js.map +1 -0
  23. package/dist/mcp/sandbox.d.ts +261 -0
  24. package/dist/mcp/sandbox.d.ts.map +1 -0
  25. package/dist/mcp/sandbox.js +983 -0
  26. package/dist/mcp/sandbox.js.map +1 -0
  27. package/dist/mcp/sdk-adapter.d.ts +413 -0
  28. package/dist/mcp/sdk-adapter.d.ts.map +1 -0
  29. package/dist/mcp/sdk-adapter.js +672 -0
  30. package/dist/mcp/sdk-adapter.js.map +1 -0
  31. package/dist/mcp/tools.d.ts +133 -0
  32. package/dist/mcp/tools.d.ts.map +1 -0
  33. package/dist/mcp/tools.js +1604 -0
  34. package/dist/mcp/tools.js.map +1 -0
  35. package/dist/ops/blame.d.ts +148 -0
  36. package/dist/ops/blame.d.ts.map +1 -0
  37. package/dist/ops/blame.js +754 -0
  38. package/dist/ops/blame.js.map +1 -0
  39. package/dist/ops/branch.d.ts +215 -0
  40. package/dist/ops/branch.d.ts.map +1 -0
  41. package/dist/ops/branch.js +608 -0
  42. package/dist/ops/branch.js.map +1 -0
  43. package/dist/ops/commit-traversal.d.ts +209 -0
  44. package/dist/ops/commit-traversal.d.ts.map +1 -0
  45. package/dist/ops/commit-traversal.js +755 -0
  46. package/dist/ops/commit-traversal.js.map +1 -0
  47. package/dist/ops/commit.d.ts +221 -0
  48. package/dist/ops/commit.d.ts.map +1 -0
  49. package/dist/ops/commit.js +606 -0
  50. package/dist/ops/commit.js.map +1 -0
  51. package/dist/ops/merge-base.d.ts +223 -0
  52. package/dist/ops/merge-base.d.ts.map +1 -0
  53. package/dist/ops/merge-base.js +581 -0
  54. package/dist/ops/merge-base.js.map +1 -0
  55. package/dist/ops/merge.d.ts +385 -0
  56. package/dist/ops/merge.d.ts.map +1 -0
  57. package/dist/ops/merge.js +1203 -0
  58. package/dist/ops/merge.js.map +1 -0
  59. package/dist/ops/tag.d.ts +182 -0
  60. package/dist/ops/tag.d.ts.map +1 -0
  61. package/dist/ops/tag.js +608 -0
  62. package/dist/ops/tag.js.map +1 -0
  63. package/dist/ops/tree-builder.d.ts +82 -0
  64. package/dist/ops/tree-builder.d.ts.map +1 -0
  65. package/dist/ops/tree-builder.js +246 -0
  66. package/dist/ops/tree-builder.js.map +1 -0
  67. package/dist/ops/tree-diff.d.ts +243 -0
  68. package/dist/ops/tree-diff.d.ts.map +1 -0
  69. package/dist/ops/tree-diff.js +657 -0
  70. package/dist/ops/tree-diff.js.map +1 -0
  71. package/dist/pack/delta.d.ts +68 -0
  72. package/dist/pack/delta.d.ts.map +1 -0
  73. package/dist/pack/delta.js +343 -0
  74. package/dist/pack/delta.js.map +1 -0
  75. package/dist/pack/format.d.ts +84 -0
  76. package/dist/pack/format.d.ts.map +1 -0
  77. package/dist/pack/format.js +261 -0
  78. package/dist/pack/format.js.map +1 -0
  79. package/dist/pack/full-generation.d.ts +327 -0
  80. package/dist/pack/full-generation.d.ts.map +1 -0
  81. package/dist/pack/full-generation.js +1159 -0
  82. package/dist/pack/full-generation.js.map +1 -0
  83. package/dist/pack/generation.d.ts +118 -0
  84. package/dist/pack/generation.d.ts.map +1 -0
  85. package/dist/pack/generation.js +459 -0
  86. package/dist/pack/generation.js.map +1 -0
  87. package/dist/pack/index.d.ts +181 -0
  88. package/dist/pack/index.d.ts.map +1 -0
  89. package/dist/pack/index.js +552 -0
  90. package/dist/pack/index.js.map +1 -0
  91. package/dist/refs/branch.d.ts +224 -0
  92. package/dist/refs/branch.d.ts.map +1 -0
  93. package/dist/refs/branch.js +170 -0
  94. package/dist/refs/branch.js.map +1 -0
  95. package/dist/refs/storage.d.ts +208 -0
  96. package/dist/refs/storage.d.ts.map +1 -0
  97. package/dist/refs/storage.js +421 -0
  98. package/dist/refs/storage.js.map +1 -0
  99. package/dist/refs/tag.d.ts +230 -0
  100. package/dist/refs/tag.d.ts.map +1 -0
  101. package/dist/refs/tag.js +188 -0
  102. package/dist/refs/tag.js.map +1 -0
  103. package/dist/storage/lru-cache.d.ts +188 -0
  104. package/dist/storage/lru-cache.d.ts.map +1 -0
  105. package/dist/storage/lru-cache.js +410 -0
  106. package/dist/storage/lru-cache.js.map +1 -0
  107. package/dist/storage/object-index.d.ts +140 -0
  108. package/dist/storage/object-index.d.ts.map +1 -0
  109. package/dist/storage/object-index.js +166 -0
  110. package/dist/storage/object-index.js.map +1 -0
  111. package/dist/storage/r2-pack.d.ts +394 -0
  112. package/dist/storage/r2-pack.d.ts.map +1 -0
  113. package/dist/storage/r2-pack.js +1062 -0
  114. package/dist/storage/r2-pack.js.map +1 -0
  115. package/dist/tiered/cdc-pipeline.d.ts +316 -0
  116. package/dist/tiered/cdc-pipeline.d.ts.map +1 -0
  117. package/dist/tiered/cdc-pipeline.js +771 -0
  118. package/dist/tiered/cdc-pipeline.js.map +1 -0
  119. package/dist/tiered/migration.d.ts +242 -0
  120. package/dist/tiered/migration.d.ts.map +1 -0
  121. package/dist/tiered/migration.js +592 -0
  122. package/dist/tiered/migration.js.map +1 -0
  123. package/dist/tiered/parquet-writer.d.ts +248 -0
  124. package/dist/tiered/parquet-writer.d.ts.map +1 -0
  125. package/dist/tiered/parquet-writer.js +555 -0
  126. package/dist/tiered/parquet-writer.js.map +1 -0
  127. package/dist/tiered/read-path.d.ts +141 -0
  128. package/dist/tiered/read-path.d.ts.map +1 -0
  129. package/dist/tiered/read-path.js +204 -0
  130. package/dist/tiered/read-path.js.map +1 -0
  131. package/dist/types/objects.d.ts +53 -0
  132. package/dist/types/objects.d.ts.map +1 -0
  133. package/dist/types/objects.js +291 -0
  134. package/dist/types/objects.js.map +1 -0
  135. package/dist/types/storage.d.ts +117 -0
  136. package/dist/types/storage.d.ts.map +1 -0
  137. package/dist/types/storage.js +8 -0
  138. package/dist/types/storage.js.map +1 -0
  139. package/dist/utils/hash.d.ts +31 -0
  140. package/dist/utils/hash.d.ts.map +1 -0
  141. package/dist/utils/hash.js +60 -0
  142. package/dist/utils/hash.js.map +1 -0
  143. package/dist/utils/sha1.d.ts +26 -0
  144. package/dist/utils/sha1.d.ts.map +1 -0
  145. package/dist/utils/sha1.js +127 -0
  146. package/dist/utils/sha1.js.map +1 -0
  147. package/dist/wire/capabilities.d.ts +236 -0
  148. package/dist/wire/capabilities.d.ts.map +1 -0
  149. package/dist/wire/capabilities.js +437 -0
  150. package/dist/wire/capabilities.js.map +1 -0
  151. package/dist/wire/pkt-line.d.ts +67 -0
  152. package/dist/wire/pkt-line.d.ts.map +1 -0
  153. package/dist/wire/pkt-line.js +145 -0
  154. package/dist/wire/pkt-line.js.map +1 -0
  155. package/dist/wire/receive-pack.d.ts +302 -0
  156. package/dist/wire/receive-pack.d.ts.map +1 -0
  157. package/dist/wire/receive-pack.js +885 -0
  158. package/dist/wire/receive-pack.js.map +1 -0
  159. package/dist/wire/smart-http.d.ts +321 -0
  160. package/dist/wire/smart-http.d.ts.map +1 -0
  161. package/dist/wire/smart-http.js +654 -0
  162. package/dist/wire/smart-http.js.map +1 -0
  163. package/dist/wire/upload-pack.d.ts +333 -0
  164. package/dist/wire/upload-pack.d.ts.map +1 -0
  165. package/dist/wire/upload-pack.js +850 -0
  166. package/dist/wire/upload-pack.js.map +1 -0
  167. package/package.json +61 -0
@@ -0,0 +1,755 @@
1
+ /**
2
+ * Commit Graph Traversal
3
+ *
4
+ * Provides functionality for walking commit graphs, finding ancestors,
5
+ * topological sorting, and revision range parsing.
6
+ */
7
+ // ============================================================================
8
+ // CommitWalker Class
9
+ // ============================================================================
10
+ /**
11
+ * Walker for traversing commit graphs
12
+ *
13
+ * Supports various traversal strategies including topological sorting,
14
+ * date-based sorting, path filtering, and revision ranges.
15
+ */
16
+ export class CommitWalker {
17
+ provider;
18
+ options;
19
+ visited = new Set();
20
+ hidden = new Set();
21
+ queue = [];
22
+ hiddenExpanded = false;
23
+ constructor(provider, options = {}) {
24
+ this.provider = provider;
25
+ this.options = options;
26
+ }
27
+ /**
28
+ * Reset the walker state
29
+ */
30
+ reset() {
31
+ this.visited = new Set();
32
+ this.hidden = new Set();
33
+ this.queue = [];
34
+ this.hiddenExpanded = false;
35
+ }
36
+ /**
37
+ * Add a starting commit to the walker
38
+ */
39
+ push(sha) {
40
+ if (!this.visited.has(sha) && !this.hidden.has(sha)) {
41
+ this.queue.push({ sha, depth: 0 });
42
+ }
43
+ }
44
+ /**
45
+ * Hide a commit and its ancestors from the walk
46
+ */
47
+ hide(sha) {
48
+ this.hidden.add(sha);
49
+ }
50
+ /**
51
+ * Expand hidden commits to include all ancestors
52
+ */
53
+ async expandHidden() {
54
+ if (this.hiddenExpanded)
55
+ return;
56
+ this.hiddenExpanded = true;
57
+ const toExpand = [...this.hidden];
58
+ const expanded = new Set(this.hidden);
59
+ while (toExpand.length > 0) {
60
+ const sha = toExpand.pop();
61
+ const commit = await this.provider.getCommit(sha);
62
+ if (!commit)
63
+ continue;
64
+ for (const parent of commit.parents) {
65
+ if (!expanded.has(parent)) {
66
+ expanded.add(parent);
67
+ toExpand.push(parent);
68
+ }
69
+ }
70
+ }
71
+ this.hidden = expanded;
72
+ }
73
+ /**
74
+ * Get the next commit in the walk
75
+ */
76
+ async next() {
77
+ // Expand hidden commits on first call
78
+ await this.expandHidden();
79
+ while (this.queue.length > 0) {
80
+ const { sha, depth } = this.queue.shift();
81
+ // Skip if visited or hidden
82
+ if (this.visited.has(sha) || this.hidden.has(sha)) {
83
+ continue;
84
+ }
85
+ const commit = await this.provider.getCommit(sha);
86
+ if (!commit)
87
+ continue;
88
+ this.visited.add(sha);
89
+ // Add parents to queue
90
+ const parentsToAdd = this.options.firstParentOnly
91
+ ? commit.parents.slice(0, 1)
92
+ : commit.parents;
93
+ for (const parent of parentsToAdd) {
94
+ if (!this.visited.has(parent) && !this.hidden.has(parent)) {
95
+ this.queue.push({ sha: parent, depth: depth + 1 });
96
+ }
97
+ }
98
+ return {
99
+ sha,
100
+ commit,
101
+ depth,
102
+ isMerge: commit.parents.length > 1
103
+ };
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Check if there are more commits to walk
109
+ */
110
+ hasNext() {
111
+ // Check if there are any unvisited, non-hidden commits in the queue
112
+ return this.queue.some(({ sha }) => !this.visited.has(sha) && !this.hidden.has(sha));
113
+ }
114
+ /**
115
+ * Iterate over all commits matching the options
116
+ */
117
+ async *[Symbol.asyncIterator]() {
118
+ let commit = await this.next();
119
+ while (commit !== null) {
120
+ yield commit;
121
+ commit = await this.next();
122
+ }
123
+ }
124
+ }
125
+ // ============================================================================
126
+ // Generator Function
127
+ // ============================================================================
128
+ /**
129
+ * Walk commits starting from the given SHA(s)
130
+ *
131
+ * @param provider - The commit provider for fetching commits
132
+ * @param start - Starting commit SHA or array of SHAs
133
+ * @param options - Traversal options
134
+ * @yields TraversalCommit objects in the requested order
135
+ */
136
+ export async function* walkCommits(provider, start, options = {}) {
137
+ const startShas = Array.isArray(start) ? start : [start];
138
+ const { maxCount, skip = 0, paths, sort = 'none', reverse = false, exclude, includeMerges = true, firstParentOnly = false, author, committer, after, before, grep } = options;
139
+ // If maxCount is 0, return immediately
140
+ if (maxCount === 0) {
141
+ return;
142
+ }
143
+ // Get commits that match path filters
144
+ let pathMatchingCommits = null;
145
+ if (paths && paths.length > 0 && provider.getCommitsForPath) {
146
+ pathMatchingCommits = new Set();
147
+ for (const path of paths) {
148
+ const commits = await provider.getCommitsForPath(path);
149
+ for (const sha of commits) {
150
+ pathMatchingCommits.add(sha);
151
+ }
152
+ }
153
+ // If no commits match paths, return empty
154
+ if (pathMatchingCommits.size === 0) {
155
+ return;
156
+ }
157
+ }
158
+ // Build set of hidden commits (exclude and their ancestors)
159
+ const hidden = new Set();
160
+ if (exclude && exclude.length > 0) {
161
+ const toExpand = [...exclude];
162
+ while (toExpand.length > 0) {
163
+ const sha = toExpand.pop();
164
+ if (hidden.has(sha))
165
+ continue;
166
+ hidden.add(sha);
167
+ const commit = await provider.getCommit(sha);
168
+ if (commit) {
169
+ for (const parent of commit.parents) {
170
+ toExpand.push(parent);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ // Collect all commits first for sorting
176
+ const allCommits = [];
177
+ const visited = new Set();
178
+ const queue = startShas.map(sha => ({
179
+ sha,
180
+ depth: 0
181
+ }));
182
+ while (queue.length > 0) {
183
+ // For date-based sorting, we need to process in date order
184
+ if (sort === 'date' || sort === 'author-date') {
185
+ // Sort queue by date (most recent first)
186
+ queue.sort((_a, _b) => {
187
+ // We need to fetch commits to sort - use simple queue position for now
188
+ return 0;
189
+ });
190
+ }
191
+ const { sha, depth } = queue.shift();
192
+ if (visited.has(sha) || hidden.has(sha)) {
193
+ continue;
194
+ }
195
+ const commit = await provider.getCommit(sha);
196
+ if (!commit)
197
+ continue;
198
+ visited.add(sha);
199
+ // Add parents to queue
200
+ const parentsToAdd = firstParentOnly
201
+ ? commit.parents.slice(0, 1)
202
+ : commit.parents;
203
+ for (const parent of parentsToAdd) {
204
+ if (!visited.has(parent) && !hidden.has(parent)) {
205
+ queue.push({ sha: parent, depth: depth + 1 });
206
+ }
207
+ }
208
+ const traversalCommit = {
209
+ sha,
210
+ commit,
211
+ depth,
212
+ isMerge: commit.parents.length > 1
213
+ };
214
+ allCommits.push(traversalCommit);
215
+ }
216
+ // Apply sorting
217
+ let sortedCommits = [...allCommits];
218
+ if (sort === 'topological') {
219
+ // Topological sort - children before parents
220
+ const shas = sortedCommits.map(c => c.sha);
221
+ const sortedShas = await topologicalSort(provider, shas);
222
+ const shaToCommit = new Map(sortedCommits.map(c => [c.sha, c]));
223
+ sortedCommits = sortedShas.map(sha => shaToCommit.get(sha)).filter(Boolean);
224
+ }
225
+ else if (sort === 'date') {
226
+ // Sort by committer date (newest first)
227
+ sortedCommits.sort((a, b) => b.commit.committer.timestamp - a.commit.committer.timestamp);
228
+ }
229
+ else if (sort === 'author-date') {
230
+ // Sort by author date (newest first)
231
+ sortedCommits.sort((a, b) => b.commit.author.timestamp - a.commit.author.timestamp);
232
+ }
233
+ // Reverse if requested
234
+ if (reverse) {
235
+ sortedCommits.reverse();
236
+ }
237
+ // Apply filters and yield commits
238
+ let skipped = 0;
239
+ let yielded = 0;
240
+ for (const traversalCommit of sortedCommits) {
241
+ const { commit, sha } = traversalCommit;
242
+ // Path filter
243
+ if (pathMatchingCommits && !pathMatchingCommits.has(sha)) {
244
+ continue;
245
+ }
246
+ // Merge filter
247
+ if (!includeMerges && commit.parents.length > 1) {
248
+ continue;
249
+ }
250
+ // Author filter
251
+ if (author && commit.author.name !== author) {
252
+ continue;
253
+ }
254
+ // Committer filter
255
+ if (committer && commit.committer.name !== committer) {
256
+ continue;
257
+ }
258
+ // Date filters
259
+ const commitTimestamp = commit.committer.timestamp * 1000;
260
+ if (after && commitTimestamp <= after.getTime()) {
261
+ continue;
262
+ }
263
+ if (before && commitTimestamp >= before.getTime()) {
264
+ continue;
265
+ }
266
+ // Grep filter
267
+ if (grep) {
268
+ const pattern = typeof grep === 'string' ? new RegExp(grep) : grep;
269
+ if (!pattern.test(commit.message)) {
270
+ continue;
271
+ }
272
+ }
273
+ // Skip handling
274
+ if (skipped < skip) {
275
+ skipped++;
276
+ continue;
277
+ }
278
+ // MaxCount handling
279
+ if (maxCount !== undefined && yielded >= maxCount) {
280
+ return;
281
+ }
282
+ yield traversalCommit;
283
+ yielded++;
284
+ }
285
+ }
286
+ // ============================================================================
287
+ // Ancestor Functions
288
+ // ============================================================================
289
+ /**
290
+ * Check if commit A is an ancestor of commit B
291
+ *
292
+ * @param provider - The commit provider for fetching commits
293
+ * @param ancestor - Potential ancestor commit SHA
294
+ * @param descendant - Potential descendant commit SHA
295
+ * @returns true if ancestor is reachable from descendant
296
+ */
297
+ export async function isAncestor(provider, ancestor, descendant) {
298
+ // Same commit is considered its own ancestor
299
+ if (ancestor === descendant) {
300
+ return true;
301
+ }
302
+ const visited = new Set();
303
+ const queue = [descendant];
304
+ while (queue.length > 0) {
305
+ const sha = queue.shift();
306
+ if (visited.has(sha))
307
+ continue;
308
+ visited.add(sha);
309
+ if (sha === ancestor) {
310
+ return true;
311
+ }
312
+ const commit = await provider.getCommit(sha);
313
+ if (!commit)
314
+ continue;
315
+ for (const parent of commit.parents) {
316
+ if (!visited.has(parent)) {
317
+ queue.push(parent);
318
+ }
319
+ }
320
+ }
321
+ return false;
322
+ }
323
+ /**
324
+ * Find the common ancestor(s) of two commits
325
+ *
326
+ * @param provider - The commit provider for fetching commits
327
+ * @param commit1 - First commit SHA
328
+ * @param commit2 - Second commit SHA
329
+ * @param all - If true, return all common ancestors; if false, return only the best one
330
+ * @returns The common ancestor SHA(s), or null if none found
331
+ */
332
+ export async function findCommonAncestor(provider, commit1, commit2, all) {
333
+ // Get all ancestors of commit1
334
+ const ancestors1 = new Set();
335
+ const queue1 = [commit1];
336
+ while (queue1.length > 0) {
337
+ const sha = queue1.shift();
338
+ if (ancestors1.has(sha))
339
+ continue;
340
+ ancestors1.add(sha);
341
+ const commit = await provider.getCommit(sha);
342
+ if (commit) {
343
+ for (const parent of commit.parents) {
344
+ queue1.push(parent);
345
+ }
346
+ }
347
+ }
348
+ // Find common ancestors by walking from commit2
349
+ const commonAncestors = [];
350
+ const visited2 = new Set();
351
+ const queue2 = [commit2];
352
+ while (queue2.length > 0) {
353
+ const sha = queue2.shift();
354
+ if (visited2.has(sha))
355
+ continue;
356
+ visited2.add(sha);
357
+ if (ancestors1.has(sha)) {
358
+ commonAncestors.push(sha);
359
+ if (!all) {
360
+ // For single result, return the first common ancestor (best merge base)
361
+ return sha;
362
+ }
363
+ // Don't explore further ancestors of common ancestors
364
+ continue;
365
+ }
366
+ const commit = await provider.getCommit(sha);
367
+ if (commit) {
368
+ for (const parent of commit.parents) {
369
+ queue2.push(parent);
370
+ }
371
+ }
372
+ }
373
+ if (commonAncestors.length === 0) {
374
+ return null;
375
+ }
376
+ if (all) {
377
+ return commonAncestors;
378
+ }
379
+ return commonAncestors[0] || null;
380
+ }
381
+ /**
382
+ * Find the merge base(s) of multiple commits
383
+ *
384
+ * @param provider - The commit provider for fetching commits
385
+ * @param commits - Array of commit SHAs
386
+ * @returns The merge base SHA(s)
387
+ */
388
+ export async function findMergeBase(provider, commits) {
389
+ if (commits.length === 0) {
390
+ return [];
391
+ }
392
+ if (commits.length === 1) {
393
+ return [commits[0]];
394
+ }
395
+ // Find common ancestor of first two commits
396
+ let result = await findCommonAncestor(provider, commits[0], commits[1], true);
397
+ if (result === null) {
398
+ return [];
399
+ }
400
+ let bases = Array.isArray(result) ? result : [result];
401
+ // For each additional commit, find common ancestor with current bases
402
+ for (let i = 2; i < commits.length; i++) {
403
+ const newBases = [];
404
+ for (const base of bases) {
405
+ const ancestor = await findCommonAncestor(provider, base, commits[i], true);
406
+ if (ancestor !== null) {
407
+ const ancestors = Array.isArray(ancestor) ? ancestor : [ancestor];
408
+ for (const a of ancestors) {
409
+ if (!newBases.includes(a)) {
410
+ newBases.push(a);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ if (newBases.length === 0) {
416
+ return [];
417
+ }
418
+ bases = newBases;
419
+ }
420
+ return bases;
421
+ }
422
+ // ============================================================================
423
+ // Revision Range Parsing
424
+ // ============================================================================
425
+ /**
426
+ * Parse a revision range specification
427
+ *
428
+ * Supports:
429
+ * - Single commit: "abc123"
430
+ * - Two-dot range: "A..B" (commits reachable from B but not from A)
431
+ * - Three-dot range: "A...B" (symmetric difference)
432
+ * - Caret exclusion: "^A B" (B excluding A)
433
+ *
434
+ * @param spec - The revision specification string
435
+ * @returns Parsed revision range
436
+ */
437
+ export function parseRevisionRange(spec) {
438
+ // Check for three-dot range first (to avoid matching .. before ...)
439
+ if (spec.includes('...')) {
440
+ const [left, right] = spec.split('...');
441
+ return {
442
+ type: 'three-dot',
443
+ left,
444
+ right
445
+ };
446
+ }
447
+ // Check for two-dot range
448
+ if (spec.includes('..')) {
449
+ const [left, right] = spec.split('..');
450
+ return {
451
+ type: 'two-dot',
452
+ left,
453
+ right
454
+ };
455
+ }
456
+ // Single commit reference
457
+ return {
458
+ type: 'single',
459
+ left: spec
460
+ };
461
+ }
462
+ /**
463
+ * Expand a revision range into include/exclude commit sets
464
+ *
465
+ * @param provider - The commit provider for fetching commits
466
+ * @param range - The parsed revision range
467
+ * @returns Object with include and exclude commit arrays
468
+ */
469
+ export async function expandRevisionRange(provider, range) {
470
+ if (range.type === 'single') {
471
+ return {
472
+ include: [range.left],
473
+ exclude: []
474
+ };
475
+ }
476
+ if (range.type === 'two-dot') {
477
+ // A..B means commits reachable from B but not from A
478
+ // Include: all commits reachable from B
479
+ // Exclude: all commits reachable from A (including A)
480
+ const include = [];
481
+ const exclude = [];
482
+ // Get commits reachable from right (B)
483
+ const visited = new Set();
484
+ const queue = [range.right];
485
+ while (queue.length > 0) {
486
+ const sha = queue.shift();
487
+ if (visited.has(sha))
488
+ continue;
489
+ visited.add(sha);
490
+ include.push(sha);
491
+ const commit = await provider.getCommit(sha);
492
+ if (commit) {
493
+ for (const parent of commit.parents) {
494
+ queue.push(parent);
495
+ }
496
+ }
497
+ }
498
+ // Get commits reachable from left (A) to exclude
499
+ const excludeVisited = new Set();
500
+ const excludeQueue = [range.left];
501
+ while (excludeQueue.length > 0) {
502
+ const sha = excludeQueue.shift();
503
+ if (excludeVisited.has(sha))
504
+ continue;
505
+ excludeVisited.add(sha);
506
+ exclude.push(sha);
507
+ const commit = await provider.getCommit(sha);
508
+ if (commit) {
509
+ for (const parent of commit.parents) {
510
+ excludeQueue.push(parent);
511
+ }
512
+ }
513
+ }
514
+ return { include, exclude };
515
+ }
516
+ if (range.type === 'three-dot') {
517
+ // A...B means symmetric difference (commits in either A or B but not both)
518
+ // Find merge base and include commits from both sides up to merge base
519
+ const mergeBase = await findCommonAncestor(provider, range.left, range.right);
520
+ const exclude = [];
521
+ if (mergeBase) {
522
+ // Exclude merge base and its ancestors
523
+ const baseCommits = Array.isArray(mergeBase) ? mergeBase : [mergeBase];
524
+ for (const base of baseCommits) {
525
+ exclude.push(base);
526
+ const visited = new Set();
527
+ const queue = [base];
528
+ while (queue.length > 0) {
529
+ const sha = queue.shift();
530
+ if (visited.has(sha))
531
+ continue;
532
+ visited.add(sha);
533
+ const commit = await provider.getCommit(sha);
534
+ if (commit) {
535
+ for (const parent of commit.parents) {
536
+ if (!visited.has(parent)) {
537
+ exclude.push(parent);
538
+ queue.push(parent);
539
+ }
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+ return {
546
+ include: [range.left, range.right],
547
+ exclude
548
+ };
549
+ }
550
+ return { include: [], exclude: [] };
551
+ }
552
+ // ============================================================================
553
+ // Sorting Functions
554
+ // ============================================================================
555
+ /**
556
+ * Sort commits topologically (children before parents)
557
+ *
558
+ * @param provider - The commit provider for fetching commits
559
+ * @param commits - Array of commit SHAs to sort
560
+ * @returns Sorted array of commit SHAs
561
+ */
562
+ export async function topologicalSort(provider, commits) {
563
+ if (commits.length === 0) {
564
+ return [];
565
+ }
566
+ const commitSet = new Set(commits);
567
+ const commitData = new Map();
568
+ // Fetch all commit data
569
+ for (const sha of commits) {
570
+ const commit = await provider.getCommit(sha);
571
+ if (commit) {
572
+ commitData.set(sha, commit);
573
+ }
574
+ }
575
+ // Build in-degree map (count of children within the set)
576
+ const inDegree = new Map();
577
+ for (const sha of commits) {
578
+ inDegree.set(sha, 0);
579
+ }
580
+ // Calculate in-degrees: for each parent, increment its in-degree
581
+ for (const sha of commits) {
582
+ const commit = commitData.get(sha);
583
+ if (commit) {
584
+ for (const parent of commit.parents) {
585
+ if (commitSet.has(parent)) {
586
+ inDegree.set(parent, (inDegree.get(parent) || 0) + 1);
587
+ }
588
+ }
589
+ }
590
+ }
591
+ // Find all commits with no children (in-degree 0) - these are the starting points
592
+ const queue = [];
593
+ for (const [sha, degree] of inDegree) {
594
+ if (degree === 0) {
595
+ queue.push(sha);
596
+ }
597
+ }
598
+ // Sort queue by timestamp (newest first) for consistent ordering
599
+ queue.sort((a, b) => {
600
+ const commitA = commitData.get(a);
601
+ const commitB = commitData.get(b);
602
+ if (!commitA || !commitB)
603
+ return 0;
604
+ return commitB.committer.timestamp - commitA.committer.timestamp;
605
+ });
606
+ const result = [];
607
+ while (queue.length > 0) {
608
+ // Take the first from queue (sorted by timestamp)
609
+ const sha = queue.shift();
610
+ result.push(sha);
611
+ const commit = commitData.get(sha);
612
+ if (commit) {
613
+ for (const parent of commit.parents) {
614
+ if (commitSet.has(parent)) {
615
+ const newDegree = (inDegree.get(parent) || 0) - 1;
616
+ inDegree.set(parent, newDegree);
617
+ if (newDegree === 0) {
618
+ // Insert in sorted order by timestamp
619
+ const parentCommit = commitData.get(parent);
620
+ if (parentCommit) {
621
+ let insertIndex = 0;
622
+ for (let i = 0; i < queue.length; i++) {
623
+ const queueCommit = commitData.get(queue[i]);
624
+ if (queueCommit &&
625
+ parentCommit.committer.timestamp <=
626
+ queueCommit.committer.timestamp) {
627
+ insertIndex = i + 1;
628
+ }
629
+ else {
630
+ break;
631
+ }
632
+ }
633
+ queue.splice(insertIndex, 0, parent);
634
+ }
635
+ else {
636
+ queue.push(parent);
637
+ }
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+ return result;
644
+ }
645
+ /**
646
+ * Sort commits by date
647
+ *
648
+ * @param provider - The commit provider for fetching commits
649
+ * @param commits - Array of commit SHAs to sort
650
+ * @param useAuthorDate - If true, use author date; otherwise use committer date
651
+ * @returns Sorted array of commit SHAs (newest first)
652
+ */
653
+ export async function sortByDate(provider, commits, useAuthorDate) {
654
+ const commitData = [];
655
+ for (const sha of commits) {
656
+ const commit = await provider.getCommit(sha);
657
+ if (commit) {
658
+ const timestamp = useAuthorDate
659
+ ? commit.author.timestamp
660
+ : commit.committer.timestamp;
661
+ commitData.push({ sha, timestamp });
662
+ }
663
+ }
664
+ // Sort by timestamp (newest first)
665
+ commitData.sort((a, b) => b.timestamp - a.timestamp);
666
+ return commitData.map(c => c.sha);
667
+ }
668
+ // ============================================================================
669
+ // Utility Functions
670
+ // ============================================================================
671
+ /**
672
+ * Get all commits between two commits (exclusive of start, inclusive of end)
673
+ *
674
+ * @param provider - The commit provider for fetching commits
675
+ * @param start - Starting commit SHA (exclusive)
676
+ * @param end - Ending commit SHA (inclusive)
677
+ * @returns Array of commit SHAs
678
+ */
679
+ export async function getCommitsBetween(provider, start, end) {
680
+ // If start equals end, return empty
681
+ if (start === end) {
682
+ return [];
683
+ }
684
+ // Check if end is an ancestor of start (wrong direction)
685
+ if (await isAncestor(provider, end, start)) {
686
+ // end is ancestor of start, so there are no commits "between" them in this direction
687
+ // Actually we need to check if start is NOT an ancestor of end
688
+ if (!(await isAncestor(provider, start, end))) {
689
+ return [];
690
+ }
691
+ }
692
+ // Walk from end back to start, collecting commits
693
+ const result = [];
694
+ const visited = new Set();
695
+ const queue = [end];
696
+ // First check if start is reachable from end
697
+ const startReachable = await isAncestor(provider, start, end);
698
+ if (!startReachable) {
699
+ return [];
700
+ }
701
+ while (queue.length > 0) {
702
+ const sha = queue.shift();
703
+ if (visited.has(sha))
704
+ continue;
705
+ visited.add(sha);
706
+ // Stop at start (exclusive)
707
+ if (sha === start) {
708
+ continue;
709
+ }
710
+ result.push(sha);
711
+ const commit = await provider.getCommit(sha);
712
+ if (commit) {
713
+ for (const parent of commit.parents) {
714
+ if (!visited.has(parent)) {
715
+ queue.push(parent);
716
+ }
717
+ }
718
+ }
719
+ }
720
+ return result;
721
+ }
722
+ /**
723
+ * Count the number of commits reachable from a commit
724
+ *
725
+ * @param provider - The commit provider for fetching commits
726
+ * @param sha - Starting commit SHA
727
+ * @param maxDepth - Maximum depth to count
728
+ * @returns Number of reachable commits
729
+ */
730
+ export async function countCommits(provider, sha, maxDepth) {
731
+ const visited = new Set();
732
+ const queue = [{ sha, depth: 0 }];
733
+ let count = 0;
734
+ while (queue.length > 0) {
735
+ const { sha: currentSha, depth } = queue.shift();
736
+ if (visited.has(currentSha))
737
+ continue;
738
+ const commit = await provider.getCommit(currentSha);
739
+ if (!commit)
740
+ continue;
741
+ visited.add(currentSha);
742
+ count++;
743
+ // Check depth limit
744
+ if (maxDepth !== undefined && depth >= maxDepth) {
745
+ continue;
746
+ }
747
+ for (const parent of commit.parents) {
748
+ if (!visited.has(parent)) {
749
+ queue.push({ sha: parent, depth: depth + 1 });
750
+ }
751
+ }
752
+ }
753
+ return count;
754
+ }
755
+ //# sourceMappingURL=commit-traversal.js.map