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.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/durable-object/object-store.d.ts +113 -0
- package/dist/durable-object/object-store.d.ts.map +1 -0
- package/dist/durable-object/object-store.js +387 -0
- package/dist/durable-object/object-store.js.map +1 -0
- package/dist/durable-object/schema.d.ts +17 -0
- package/dist/durable-object/schema.d.ts.map +1 -0
- package/dist/durable-object/schema.js +43 -0
- package/dist/durable-object/schema.js.map +1 -0
- package/dist/durable-object/wal.d.ts +111 -0
- package/dist/durable-object/wal.d.ts.map +1 -0
- package/dist/durable-object/wal.js +200 -0
- package/dist/durable-object/wal.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/adapter.d.ts +231 -0
- package/dist/mcp/adapter.d.ts.map +1 -0
- package/dist/mcp/adapter.js +502 -0
- package/dist/mcp/adapter.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +261 -0
- package/dist/mcp/sandbox.d.ts.map +1 -0
- package/dist/mcp/sandbox.js +983 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/sdk-adapter.d.ts +413 -0
- package/dist/mcp/sdk-adapter.d.ts.map +1 -0
- package/dist/mcp/sdk-adapter.js +672 -0
- package/dist/mcp/sdk-adapter.js.map +1 -0
- package/dist/mcp/tools.d.ts +133 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +1604 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/ops/blame.d.ts +148 -0
- package/dist/ops/blame.d.ts.map +1 -0
- package/dist/ops/blame.js +754 -0
- package/dist/ops/blame.js.map +1 -0
- package/dist/ops/branch.d.ts +215 -0
- package/dist/ops/branch.d.ts.map +1 -0
- package/dist/ops/branch.js +608 -0
- package/dist/ops/branch.js.map +1 -0
- package/dist/ops/commit-traversal.d.ts +209 -0
- package/dist/ops/commit-traversal.d.ts.map +1 -0
- package/dist/ops/commit-traversal.js +755 -0
- package/dist/ops/commit-traversal.js.map +1 -0
- package/dist/ops/commit.d.ts +221 -0
- package/dist/ops/commit.d.ts.map +1 -0
- package/dist/ops/commit.js +606 -0
- package/dist/ops/commit.js.map +1 -0
- package/dist/ops/merge-base.d.ts +223 -0
- package/dist/ops/merge-base.d.ts.map +1 -0
- package/dist/ops/merge-base.js +581 -0
- package/dist/ops/merge-base.js.map +1 -0
- package/dist/ops/merge.d.ts +385 -0
- package/dist/ops/merge.d.ts.map +1 -0
- package/dist/ops/merge.js +1203 -0
- package/dist/ops/merge.js.map +1 -0
- package/dist/ops/tag.d.ts +182 -0
- package/dist/ops/tag.d.ts.map +1 -0
- package/dist/ops/tag.js +608 -0
- package/dist/ops/tag.js.map +1 -0
- package/dist/ops/tree-builder.d.ts +82 -0
- package/dist/ops/tree-builder.d.ts.map +1 -0
- package/dist/ops/tree-builder.js +246 -0
- package/dist/ops/tree-builder.js.map +1 -0
- package/dist/ops/tree-diff.d.ts +243 -0
- package/dist/ops/tree-diff.d.ts.map +1 -0
- package/dist/ops/tree-diff.js +657 -0
- package/dist/ops/tree-diff.js.map +1 -0
- package/dist/pack/delta.d.ts +68 -0
- package/dist/pack/delta.d.ts.map +1 -0
- package/dist/pack/delta.js +343 -0
- package/dist/pack/delta.js.map +1 -0
- package/dist/pack/format.d.ts +84 -0
- package/dist/pack/format.d.ts.map +1 -0
- package/dist/pack/format.js +261 -0
- package/dist/pack/format.js.map +1 -0
- package/dist/pack/full-generation.d.ts +327 -0
- package/dist/pack/full-generation.d.ts.map +1 -0
- package/dist/pack/full-generation.js +1159 -0
- package/dist/pack/full-generation.js.map +1 -0
- package/dist/pack/generation.d.ts +118 -0
- package/dist/pack/generation.d.ts.map +1 -0
- package/dist/pack/generation.js +459 -0
- package/dist/pack/generation.js.map +1 -0
- package/dist/pack/index.d.ts +181 -0
- package/dist/pack/index.d.ts.map +1 -0
- package/dist/pack/index.js +552 -0
- package/dist/pack/index.js.map +1 -0
- package/dist/refs/branch.d.ts +224 -0
- package/dist/refs/branch.d.ts.map +1 -0
- package/dist/refs/branch.js +170 -0
- package/dist/refs/branch.js.map +1 -0
- package/dist/refs/storage.d.ts +208 -0
- package/dist/refs/storage.d.ts.map +1 -0
- package/dist/refs/storage.js +421 -0
- package/dist/refs/storage.js.map +1 -0
- package/dist/refs/tag.d.ts +230 -0
- package/dist/refs/tag.d.ts.map +1 -0
- package/dist/refs/tag.js +188 -0
- package/dist/refs/tag.js.map +1 -0
- package/dist/storage/lru-cache.d.ts +188 -0
- package/dist/storage/lru-cache.d.ts.map +1 -0
- package/dist/storage/lru-cache.js +410 -0
- package/dist/storage/lru-cache.js.map +1 -0
- package/dist/storage/object-index.d.ts +140 -0
- package/dist/storage/object-index.d.ts.map +1 -0
- package/dist/storage/object-index.js +166 -0
- package/dist/storage/object-index.js.map +1 -0
- package/dist/storage/r2-pack.d.ts +394 -0
- package/dist/storage/r2-pack.d.ts.map +1 -0
- package/dist/storage/r2-pack.js +1062 -0
- package/dist/storage/r2-pack.js.map +1 -0
- package/dist/tiered/cdc-pipeline.d.ts +316 -0
- package/dist/tiered/cdc-pipeline.d.ts.map +1 -0
- package/dist/tiered/cdc-pipeline.js +771 -0
- package/dist/tiered/cdc-pipeline.js.map +1 -0
- package/dist/tiered/migration.d.ts +242 -0
- package/dist/tiered/migration.d.ts.map +1 -0
- package/dist/tiered/migration.js +592 -0
- package/dist/tiered/migration.js.map +1 -0
- package/dist/tiered/parquet-writer.d.ts +248 -0
- package/dist/tiered/parquet-writer.d.ts.map +1 -0
- package/dist/tiered/parquet-writer.js +555 -0
- package/dist/tiered/parquet-writer.js.map +1 -0
- package/dist/tiered/read-path.d.ts +141 -0
- package/dist/tiered/read-path.d.ts.map +1 -0
- package/dist/tiered/read-path.js +204 -0
- package/dist/tiered/read-path.js.map +1 -0
- package/dist/types/objects.d.ts +53 -0
- package/dist/types/objects.d.ts.map +1 -0
- package/dist/types/objects.js +291 -0
- package/dist/types/objects.js.map +1 -0
- package/dist/types/storage.d.ts +117 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/storage.js +8 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/utils/hash.d.ts +31 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +60 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/sha1.d.ts +26 -0
- package/dist/utils/sha1.d.ts.map +1 -0
- package/dist/utils/sha1.js +127 -0
- package/dist/utils/sha1.js.map +1 -0
- package/dist/wire/capabilities.d.ts +236 -0
- package/dist/wire/capabilities.d.ts.map +1 -0
- package/dist/wire/capabilities.js +437 -0
- package/dist/wire/capabilities.js.map +1 -0
- package/dist/wire/pkt-line.d.ts +67 -0
- package/dist/wire/pkt-line.d.ts.map +1 -0
- package/dist/wire/pkt-line.js +145 -0
- package/dist/wire/pkt-line.js.map +1 -0
- package/dist/wire/receive-pack.d.ts +302 -0
- package/dist/wire/receive-pack.d.ts.map +1 -0
- package/dist/wire/receive-pack.js +885 -0
- package/dist/wire/receive-pack.js.map +1 -0
- package/dist/wire/smart-http.d.ts +321 -0
- package/dist/wire/smart-http.d.ts.map +1 -0
- package/dist/wire/smart-http.js +654 -0
- package/dist/wire/smart-http.js.map +1 -0
- package/dist/wire/upload-pack.d.ts +333 -0
- package/dist/wire/upload-pack.d.ts.map +1 -0
- package/dist/wire/upload-pack.js +850 -0
- package/dist/wire/upload-pack.js.map +1 -0
- 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
|