gitx.do 0.0.3 → 0.1.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/README.md +319 -92
- package/dist/cli/commands/add.d.ts +176 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/add.js +979 -0
- package/dist/cli/commands/add.js.map +1 -0
- package/dist/cli/commands/blame.d.ts +1 -1
- package/dist/cli/commands/blame.d.ts.map +1 -1
- package/dist/cli/commands/blame.js +1 -1
- package/dist/cli/commands/blame.js.map +1 -1
- package/dist/cli/commands/branch.d.ts +1 -1
- package/dist/cli/commands/branch.d.ts.map +1 -1
- package/dist/cli/commands/branch.js +2 -2
- package/dist/cli/commands/branch.js.map +1 -1
- package/dist/cli/commands/checkout.d.ts +73 -0
- package/dist/cli/commands/checkout.d.ts.map +1 -0
- package/dist/cli/commands/checkout.js +725 -0
- package/dist/cli/commands/checkout.js.map +1 -0
- package/dist/cli/commands/commit.d.ts.map +1 -1
- package/dist/cli/commands/commit.js +22 -2
- package/dist/cli/commands/commit.js.map +1 -1
- package/dist/cli/commands/diff.d.ts +4 -4
- package/dist/cli/commands/diff.d.ts.map +1 -1
- package/dist/cli/commands/diff.js +9 -8
- package/dist/cli/commands/diff.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +1 -1
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge.d.ts +106 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +852 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/review.d.ts +1 -1
- package/dist/cli/commands/review.d.ts.map +1 -1
- package/dist/cli/commands/review.js +26 -1
- package/dist/cli/commands/review.js.map +1 -1
- package/dist/cli/commands/stash.d.ts +157 -0
- package/dist/cli/commands/stash.d.ts.map +1 -0
- package/dist/cli/commands/stash.js +655 -0
- package/dist/cli/commands/stash.js.map +1 -0
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +1 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/web.d.ts.map +1 -1
- package/dist/cli/commands/web.js +3 -2
- package/dist/cli/commands/web.js.map +1 -1
- package/dist/cli/fs-adapter.d.ts.map +1 -1
- package/dist/cli/fs-adapter.js +3 -5
- package/dist/cli/fs-adapter.js.map +1 -1
- package/dist/cli/fsx-cli-adapter.d.ts +359 -0
- package/dist/cli/fsx-cli-adapter.d.ts.map +1 -0
- package/dist/cli/fsx-cli-adapter.js +619 -0
- package/dist/cli/fsx-cli-adapter.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +68 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ui/components/DiffView.d.ts +7 -2
- package/dist/cli/ui/components/DiffView.d.ts.map +1 -1
- package/dist/cli/ui/components/DiffView.js.map +1 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -2
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -1
- package/dist/cli/ui/components/ErrorDisplay.js.map +1 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +8 -2
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -1
- package/dist/cli/ui/components/FuzzySearch.js.map +1 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -2
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -1
- package/dist/cli/ui/components/LoadingSpinner.js.map +1 -1
- package/dist/cli/ui/components/NavigationList.d.ts +7 -2
- package/dist/cli/ui/components/NavigationList.d.ts.map +1 -1
- package/dist/cli/ui/components/NavigationList.js.map +1 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +7 -2
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -1
- package/dist/cli/ui/components/ScrollableContent.js.map +1 -1
- package/dist/cli/ui/terminal-ui.d.ts +42 -9
- package/dist/cli/ui/terminal-ui.d.ts.map +1 -1
- package/dist/cli/ui/terminal-ui.js.map +1 -1
- package/dist/do/BashModule.d.ts +871 -0
- package/dist/do/BashModule.d.ts.map +1 -0
- package/dist/do/BashModule.js +1143 -0
- package/dist/do/BashModule.js.map +1 -0
- package/dist/do/FsModule.d.ts +612 -0
- package/dist/do/FsModule.d.ts.map +1 -0
- package/dist/do/FsModule.js +1120 -0
- package/dist/do/FsModule.js.map +1 -0
- package/dist/do/GitModule.d.ts +635 -0
- package/dist/do/GitModule.d.ts.map +1 -0
- package/dist/do/GitModule.js +784 -0
- package/dist/do/GitModule.js.map +1 -0
- package/dist/do/GitRepoDO.d.ts +281 -0
- package/dist/do/GitRepoDO.d.ts.map +1 -0
- package/dist/do/GitRepoDO.js +479 -0
- package/dist/do/GitRepoDO.js.map +1 -0
- package/dist/do/bash-ast.d.ts +246 -0
- package/dist/do/bash-ast.d.ts.map +1 -0
- package/dist/do/bash-ast.js +888 -0
- package/dist/do/bash-ast.js.map +1 -0
- package/dist/do/container-executor.d.ts +491 -0
- package/dist/do/container-executor.d.ts.map +1 -0
- package/dist/do/container-executor.js +731 -0
- package/dist/do/container-executor.js.map +1 -0
- package/dist/do/index.d.ts +53 -0
- package/dist/do/index.d.ts.map +1 -0
- package/dist/do/index.js +91 -0
- package/dist/do/index.js.map +1 -0
- package/dist/do/tiered-storage.d.ts +403 -0
- package/dist/do/tiered-storage.d.ts.map +1 -0
- package/dist/do/tiered-storage.js +689 -0
- package/dist/do/tiered-storage.js.map +1 -0
- package/dist/do/withBash.d.ts +231 -0
- package/dist/do/withBash.d.ts.map +1 -0
- package/dist/do/withBash.js +244 -0
- package/dist/do/withBash.js.map +1 -0
- package/dist/do/withFs.d.ts +237 -0
- package/dist/do/withFs.d.ts.map +1 -0
- package/dist/do/withFs.js +387 -0
- package/dist/do/withFs.js.map +1 -0
- package/dist/do/withGit.d.ts +180 -0
- package/dist/do/withGit.d.ts.map +1 -0
- package/dist/do/withGit.js +271 -0
- package/dist/do/withGit.js.map +1 -0
- package/dist/durable-object/object-store.d.ts +157 -15
- package/dist/durable-object/object-store.d.ts.map +1 -1
- package/dist/durable-object/object-store.js +435 -47
- package/dist/durable-object/object-store.js.map +1 -1
- package/dist/durable-object/schema.d.ts +12 -1
- package/dist/durable-object/schema.d.ts.map +1 -1
- package/dist/durable-object/schema.js +87 -2
- package/dist/durable-object/schema.js.map +1 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +22 -0
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +1 -0
- package/dist/mcp/sandbox/miniflare-evaluator.js +140 -0
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +1 -0
- package/dist/mcp/sandbox/object-store-proxy.d.ts +32 -0
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +1 -0
- package/dist/mcp/sandbox/object-store-proxy.js +30 -0
- package/dist/mcp/sandbox/object-store-proxy.js.map +1 -0
- package/dist/mcp/sandbox/template.d.ts +17 -0
- package/dist/mcp/sandbox/template.d.ts.map +1 -0
- package/dist/mcp/sandbox/template.js +71 -0
- package/dist/mcp/sandbox/template.js.map +1 -0
- package/dist/mcp/sandbox.d.ts.map +1 -1
- package/dist/mcp/sandbox.js +16 -4
- package/dist/mcp/sandbox.js.map +1 -1
- package/dist/mcp/tools/do.d.ts +32 -0
- package/dist/mcp/tools/do.d.ts.map +1 -0
- package/dist/mcp/tools/do.js +117 -0
- package/dist/mcp/tools/do.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1258 -22
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pack/delta.d.ts +8 -0
- package/dist/pack/delta.d.ts.map +1 -1
- package/dist/pack/delta.js +241 -30
- package/dist/pack/delta.js.map +1 -1
- package/dist/refs/branch.d.ts +38 -25
- package/dist/refs/branch.d.ts.map +1 -1
- package/dist/refs/branch.js +421 -94
- package/dist/refs/branch.js.map +1 -1
- package/dist/refs/storage.d.ts +77 -5
- package/dist/refs/storage.d.ts.map +1 -1
- package/dist/refs/storage.js +193 -43
- package/dist/refs/storage.js.map +1 -1
- package/dist/refs/tag.d.ts +44 -24
- package/dist/refs/tag.d.ts.map +1 -1
- package/dist/refs/tag.js +411 -70
- package/dist/refs/tag.js.map +1 -1
- package/dist/storage/backend.d.ts +425 -0
- package/dist/storage/backend.d.ts.map +1 -0
- package/dist/storage/backend.js +41 -0
- package/dist/storage/backend.js.map +1 -0
- package/dist/storage/fsx-adapter.d.ts +204 -0
- package/dist/storage/fsx-adapter.d.ts.map +1 -0
- package/dist/storage/fsx-adapter.js +518 -0
- package/dist/storage/fsx-adapter.js.map +1 -0
- package/dist/storage/r2-pack.d.ts.map +1 -1
- package/dist/storage/r2-pack.js +4 -1
- package/dist/storage/r2-pack.js.map +1 -1
- package/dist/tiered/cdc-pipeline.js +3 -3
- package/dist/tiered/cdc-pipeline.js.map +1 -1
- package/dist/tiered/migration.d.ts.map +1 -1
- package/dist/tiered/migration.js +4 -1
- package/dist/tiered/migration.js.map +1 -1
- package/dist/types/capability.d.ts +1385 -0
- package/dist/types/capability.d.ts.map +1 -0
- package/dist/types/capability.js +36 -0
- package/dist/types/capability.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.d.ts +673 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +26 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/objects.d.ts +182 -0
- package/dist/types/objects.d.ts.map +1 -1
- package/dist/types/objects.js +249 -4
- package/dist/types/objects.js.map +1 -1
- package/dist/types/storage.d.ts +114 -0
- package/dist/types/storage.d.ts.map +1 -1
- package/dist/types/storage.js +160 -1
- package/dist/types/storage.js.map +1 -1
- package/dist/types/worker-loader.d.ts +60 -0
- package/dist/types/worker-loader.d.ts.map +1 -0
- package/dist/types/worker-loader.js +62 -0
- package/dist/types/worker-loader.js.map +1 -0
- package/dist/utils/hash.d.ts +126 -80
- package/dist/utils/hash.d.ts.map +1 -1
- package/dist/utils/hash.js +191 -100
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/sha1.d.ts +206 -0
- package/dist/utils/sha1.d.ts.map +1 -1
- package/dist/utils/sha1.js +405 -0
- package/dist/utils/sha1.js.map +1 -1
- package/dist/wire/path-security.d.ts +157 -0
- package/dist/wire/path-security.d.ts.map +1 -0
- package/dist/wire/path-security.js +307 -0
- package/dist/wire/path-security.js.map +1 -0
- package/dist/wire/receive-pack.d.ts +7 -0
- package/dist/wire/receive-pack.d.ts.map +1 -1
- package/dist/wire/receive-pack.js +29 -1
- package/dist/wire/receive-pack.js.map +1 -1
- package/dist/wire/upload-pack.d.ts.map +1 -1
- package/dist/wire/upload-pack.js +4 -1
- package/dist/wire/upload-pack.js.map +1 -1
- package/package.json +10 -1
package/dist/refs/tag.js
CHANGED
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* const versions = await listTags(manager, { pattern: 'v*' })
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
+
// Re-export RefStorage for backward compatibility
|
|
36
|
+
export { RefStorage } from './storage';
|
|
35
37
|
/**
|
|
36
38
|
* Error thrown when a tag operation fails.
|
|
37
39
|
*
|
|
@@ -107,6 +109,11 @@ export class TagError extends Error {
|
|
|
107
109
|
* ```
|
|
108
110
|
*/
|
|
109
111
|
export class TagManager {
|
|
112
|
+
refStorage;
|
|
113
|
+
objectStorage;
|
|
114
|
+
gpgSigner;
|
|
115
|
+
// Simple in-memory lock to handle concurrent tag creation
|
|
116
|
+
pendingCreations = new Set();
|
|
110
117
|
/**
|
|
111
118
|
* Create a new TagManager.
|
|
112
119
|
*
|
|
@@ -115,10 +122,9 @@ export class TagManager {
|
|
|
115
122
|
* @param gpgSigner - Optional GPG signer for signed tags
|
|
116
123
|
*/
|
|
117
124
|
constructor(refStorage, objectStorage, gpgSigner) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// TODO: Implement in GREEN phase
|
|
125
|
+
this.refStorage = refStorage;
|
|
126
|
+
this.objectStorage = objectStorage;
|
|
127
|
+
this.gpgSigner = gpgSigner;
|
|
122
128
|
}
|
|
123
129
|
/**
|
|
124
130
|
* Create a new tag.
|
|
@@ -157,9 +163,98 @@ export class TagManager {
|
|
|
157
163
|
* })
|
|
158
164
|
* ```
|
|
159
165
|
*/
|
|
160
|
-
async createTag(
|
|
161
|
-
//
|
|
162
|
-
|
|
166
|
+
async createTag(name, target, options) {
|
|
167
|
+
// Validate tag name
|
|
168
|
+
if (!isValidTagName(name)) {
|
|
169
|
+
throw new TagError(`Invalid tag name: ${name}`, 'INVALID_TAG_NAME', name);
|
|
170
|
+
}
|
|
171
|
+
const refName = `refs/tags/${name}`;
|
|
172
|
+
// Synchronous check-and-lock to handle concurrent creation attempts
|
|
173
|
+
if (this.pendingCreations.has(name)) {
|
|
174
|
+
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
175
|
+
}
|
|
176
|
+
// Check if tag already exists
|
|
177
|
+
const existingSha = await this.refStorage.getRef(refName);
|
|
178
|
+
if (existingSha !== null && !options?.force) {
|
|
179
|
+
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
180
|
+
}
|
|
181
|
+
// Mark as pending (synchronous operation for atomicity)
|
|
182
|
+
if (!options?.force) {
|
|
183
|
+
if (this.pendingCreations.has(name)) {
|
|
184
|
+
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
185
|
+
}
|
|
186
|
+
this.pendingCreations.add(name);
|
|
187
|
+
}
|
|
188
|
+
// Determine if this should be an annotated tag
|
|
189
|
+
const isAnnotated = options?.annotated === true || (options?.message !== undefined && options?.message !== '');
|
|
190
|
+
try {
|
|
191
|
+
if (isAnnotated) {
|
|
192
|
+
// Validate message for annotated tags
|
|
193
|
+
const rawMessage = options?.message;
|
|
194
|
+
if (!rawMessage || rawMessage.trim().length === 0) {
|
|
195
|
+
throw new TagError('Annotated tag requires a message', 'MESSAGE_REQUIRED', name);
|
|
196
|
+
}
|
|
197
|
+
const formattedMessage = formatTagMessage(rawMessage);
|
|
198
|
+
// Validate tagger (can have timestamp set to 0 or undefined, we'll use current time)
|
|
199
|
+
let tagger = options?.tagger;
|
|
200
|
+
if (tagger && tagger.timestamp === undefined) {
|
|
201
|
+
tagger = {
|
|
202
|
+
...tagger,
|
|
203
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Handle signing
|
|
207
|
+
let signature;
|
|
208
|
+
let finalMessage = formattedMessage;
|
|
209
|
+
if (options?.sign) {
|
|
210
|
+
if (!this.gpgSigner) {
|
|
211
|
+
throw new TagError('GPG signer not available', 'GPG_ERROR', name);
|
|
212
|
+
}
|
|
213
|
+
// Sign the tag content
|
|
214
|
+
const encoder = new TextEncoder();
|
|
215
|
+
signature = await this.gpgSigner.sign(encoder.encode(formattedMessage), options?.keyId);
|
|
216
|
+
// Append signature to message (Git stores signature in the tag object)
|
|
217
|
+
finalMessage = formattedMessage + '\n' + signature;
|
|
218
|
+
}
|
|
219
|
+
// Get the target object type
|
|
220
|
+
const targetType = await this.objectStorage.readObjectType(target);
|
|
221
|
+
// Create tag object
|
|
222
|
+
const tagObj = {
|
|
223
|
+
object: target,
|
|
224
|
+
objectType: targetType || 'commit',
|
|
225
|
+
name,
|
|
226
|
+
tagger,
|
|
227
|
+
message: finalMessage
|
|
228
|
+
};
|
|
229
|
+
// Write tag object and get its SHA
|
|
230
|
+
const tagObjSha = await this.objectStorage.writeTagObject(tagObj);
|
|
231
|
+
// Write ref pointing to tag object
|
|
232
|
+
await this.refStorage.setRef(refName, tagObjSha);
|
|
233
|
+
return {
|
|
234
|
+
name,
|
|
235
|
+
type: 'annotated',
|
|
236
|
+
sha: tagObjSha,
|
|
237
|
+
targetSha: target,
|
|
238
|
+
targetType: targetType || 'commit',
|
|
239
|
+
tagger,
|
|
240
|
+
message: formattedMessage,
|
|
241
|
+
signature
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Lightweight tag - just write ref pointing to target
|
|
246
|
+
await this.refStorage.setRef(refName, target);
|
|
247
|
+
return {
|
|
248
|
+
name,
|
|
249
|
+
type: 'lightweight',
|
|
250
|
+
sha: target
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
// Clear the pending lock
|
|
256
|
+
this.pendingCreations.delete(name);
|
|
257
|
+
}
|
|
163
258
|
}
|
|
164
259
|
/**
|
|
165
260
|
* Delete a tag.
|
|
@@ -181,9 +276,19 @@ export class TagManager {
|
|
|
181
276
|
* await manager.deleteTag('maybe-exists', { force: true })
|
|
182
277
|
* ```
|
|
183
278
|
*/
|
|
184
|
-
async deleteTag(
|
|
185
|
-
|
|
186
|
-
|
|
279
|
+
async deleteTag(name, options) {
|
|
280
|
+
const refName = `refs/tags/${name}`;
|
|
281
|
+
// Check if tag exists
|
|
282
|
+
const existingSha = await this.refStorage.getRef(refName);
|
|
283
|
+
if (existingSha === null) {
|
|
284
|
+
if (options?.force) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
288
|
+
}
|
|
289
|
+
// Delete the ref (but not the tag object)
|
|
290
|
+
await this.refStorage.deleteRef(refName);
|
|
291
|
+
return true;
|
|
187
292
|
}
|
|
188
293
|
/**
|
|
189
294
|
* List all tags.
|
|
@@ -216,9 +321,79 @@ export class TagManager {
|
|
|
216
321
|
* })
|
|
217
322
|
* ```
|
|
218
323
|
*/
|
|
219
|
-
async listTags(
|
|
220
|
-
//
|
|
221
|
-
|
|
324
|
+
async listTags(options) {
|
|
325
|
+
// List all refs under refs/tags/
|
|
326
|
+
const tagRefs = await this.refStorage.listRefs('refs/tags/');
|
|
327
|
+
// Determine if we need metadata (either explicitly requested or for date sorting)
|
|
328
|
+
const needMetadata = options?.includeMetadata || options?.sort === 'date';
|
|
329
|
+
// Build tag list
|
|
330
|
+
let tags = [];
|
|
331
|
+
for (const refEntry of tagRefs) {
|
|
332
|
+
const tagName = refEntry.name.replace('refs/tags/', '');
|
|
333
|
+
const sha = refEntry.sha;
|
|
334
|
+
// Check if it's an annotated tag by trying to read the tag object
|
|
335
|
+
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
336
|
+
if (tagObj) {
|
|
337
|
+
// Annotated tag
|
|
338
|
+
const tag = {
|
|
339
|
+
name: tagName,
|
|
340
|
+
type: 'annotated',
|
|
341
|
+
sha
|
|
342
|
+
};
|
|
343
|
+
if (needMetadata) {
|
|
344
|
+
tag.targetSha = tagObj.object;
|
|
345
|
+
tag.targetType = tagObj.objectType;
|
|
346
|
+
tag.tagger = tagObj.tagger;
|
|
347
|
+
tag.message = tagObj.message;
|
|
348
|
+
}
|
|
349
|
+
tags.push(tag);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// Lightweight tag
|
|
353
|
+
tags.push({
|
|
354
|
+
name: tagName,
|
|
355
|
+
type: 'lightweight',
|
|
356
|
+
sha
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Filter by pattern if provided
|
|
361
|
+
if (options?.pattern) {
|
|
362
|
+
tags = filterTagsByPattern(tags, options.pattern);
|
|
363
|
+
}
|
|
364
|
+
// Sort tags
|
|
365
|
+
const sortType = options?.sort || 'name';
|
|
366
|
+
const sortDirection = options?.sortDirection || 'asc';
|
|
367
|
+
if (sortType === 'version') {
|
|
368
|
+
tags = sortTagsByVersion(tags, sortDirection);
|
|
369
|
+
}
|
|
370
|
+
else if (sortType === 'date') {
|
|
371
|
+
// Filter out tags without timestamps when sorting by date
|
|
372
|
+
tags = tags.filter(t => t.tagger?.timestamp !== undefined);
|
|
373
|
+
// Sort by tagger timestamp
|
|
374
|
+
tags.sort((a, b) => {
|
|
375
|
+
const aTime = a.tagger.timestamp;
|
|
376
|
+
const bTime = b.tagger.timestamp;
|
|
377
|
+
const timeDiff = sortDirection === 'asc' ? aTime - bTime : bTime - aTime;
|
|
378
|
+
// Secondary sort by name when timestamps are equal
|
|
379
|
+
if (timeDiff === 0) {
|
|
380
|
+
return a.name.localeCompare(b.name);
|
|
381
|
+
}
|
|
382
|
+
return timeDiff;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// Sort by name (default)
|
|
387
|
+
tags.sort((a, b) => {
|
|
388
|
+
const cmp = a.name.localeCompare(b.name);
|
|
389
|
+
return sortDirection === 'asc' ? cmp : -cmp;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// Limit results
|
|
393
|
+
if (options?.limit !== undefined) {
|
|
394
|
+
tags = tags.slice(0, options.limit);
|
|
395
|
+
}
|
|
396
|
+
return tags;
|
|
222
397
|
}
|
|
223
398
|
/**
|
|
224
399
|
* Get a tag by name.
|
|
@@ -244,9 +419,43 @@ export class TagManager {
|
|
|
244
419
|
* }
|
|
245
420
|
* ```
|
|
246
421
|
*/
|
|
247
|
-
async getTag(
|
|
248
|
-
|
|
249
|
-
|
|
422
|
+
async getTag(name, options) {
|
|
423
|
+
const refName = `refs/tags/${name}`;
|
|
424
|
+
const sha = await this.refStorage.getRef(refName);
|
|
425
|
+
if (sha === null) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
// Try to read as tag object (annotated tag)
|
|
429
|
+
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
430
|
+
if (tagObj) {
|
|
431
|
+
// Annotated tag
|
|
432
|
+
const tag = {
|
|
433
|
+
name,
|
|
434
|
+
type: 'annotated',
|
|
435
|
+
sha
|
|
436
|
+
};
|
|
437
|
+
if (options?.resolve) {
|
|
438
|
+
tag.targetSha = tagObj.object;
|
|
439
|
+
tag.targetType = tagObj.objectType;
|
|
440
|
+
tag.tagger = tagObj.tagger;
|
|
441
|
+
tag.message = tagObj.message;
|
|
442
|
+
// Check for signature in message
|
|
443
|
+
const parsed = parseTagMessage(tagObj.message);
|
|
444
|
+
if (parsed.signature) {
|
|
445
|
+
tag.signature = parsed.signature;
|
|
446
|
+
tag.message = parsed.message;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return tag;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Lightweight tag
|
|
453
|
+
return {
|
|
454
|
+
name,
|
|
455
|
+
type: 'lightweight',
|
|
456
|
+
sha
|
|
457
|
+
};
|
|
458
|
+
}
|
|
250
459
|
}
|
|
251
460
|
/**
|
|
252
461
|
* Check if a tag exists.
|
|
@@ -264,9 +473,10 @@ export class TagManager {
|
|
|
264
473
|
* }
|
|
265
474
|
* ```
|
|
266
475
|
*/
|
|
267
|
-
async tagExists(
|
|
268
|
-
|
|
269
|
-
|
|
476
|
+
async tagExists(name) {
|
|
477
|
+
const refName = `refs/tags/${name}`;
|
|
478
|
+
const sha = await this.refStorage.getRef(refName);
|
|
479
|
+
return sha !== null;
|
|
270
480
|
}
|
|
271
481
|
/**
|
|
272
482
|
* Get the target (commit SHA) that a tag points to.
|
|
@@ -285,9 +495,26 @@ export class TagManager {
|
|
|
285
495
|
* const commitSha = await manager.getTagTarget('v1.0.0')
|
|
286
496
|
* ```
|
|
287
497
|
*/
|
|
288
|
-
async getTagTarget(
|
|
289
|
-
|
|
290
|
-
|
|
498
|
+
async getTagTarget(name) {
|
|
499
|
+
const refName = `refs/tags/${name}`;
|
|
500
|
+
let sha = await this.refStorage.getRef(refName);
|
|
501
|
+
if (sha === null) {
|
|
502
|
+
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
503
|
+
}
|
|
504
|
+
// Resolve through tag objects to get final commit
|
|
505
|
+
let depth = 0;
|
|
506
|
+
const maxDepth = 10;
|
|
507
|
+
while (depth < maxDepth) {
|
|
508
|
+
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
509
|
+
if (!tagObj) {
|
|
510
|
+
// Not a tag object, this is the final target
|
|
511
|
+
return sha;
|
|
512
|
+
}
|
|
513
|
+
// Follow the tag to its target
|
|
514
|
+
sha = tagObj.object;
|
|
515
|
+
depth++;
|
|
516
|
+
}
|
|
517
|
+
return sha;
|
|
291
518
|
}
|
|
292
519
|
/**
|
|
293
520
|
* Verify a tag's GPG signature.
|
|
@@ -311,9 +538,30 @@ export class TagManager {
|
|
|
311
538
|
* }
|
|
312
539
|
* ```
|
|
313
540
|
*/
|
|
314
|
-
async verifyTag(
|
|
315
|
-
|
|
316
|
-
|
|
541
|
+
async verifyTag(name) {
|
|
542
|
+
const refName = `refs/tags/${name}`;
|
|
543
|
+
const sha = await this.refStorage.getRef(refName);
|
|
544
|
+
if (sha === null) {
|
|
545
|
+
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
546
|
+
}
|
|
547
|
+
// Get tag object
|
|
548
|
+
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
549
|
+
if (!tagObj) {
|
|
550
|
+
// Lightweight tag - cannot be signed
|
|
551
|
+
return { valid: false };
|
|
552
|
+
}
|
|
553
|
+
// Parse message to check for signature
|
|
554
|
+
const parsed = parseTagMessage(tagObj.message);
|
|
555
|
+
if (!parsed.signature) {
|
|
556
|
+
// No signature
|
|
557
|
+
return { valid: false };
|
|
558
|
+
}
|
|
559
|
+
// Verify signature using GPG signer
|
|
560
|
+
if (!this.gpgSigner) {
|
|
561
|
+
return { valid: false, error: 'GPG signer not available' };
|
|
562
|
+
}
|
|
563
|
+
const encoder = new TextEncoder();
|
|
564
|
+
return this.gpgSigner.verify(encoder.encode(parsed.message), parsed.signature);
|
|
317
565
|
}
|
|
318
566
|
/**
|
|
319
567
|
* Check if a tag is annotated.
|
|
@@ -332,9 +580,15 @@ export class TagManager {
|
|
|
332
580
|
* }
|
|
333
581
|
* ```
|
|
334
582
|
*/
|
|
335
|
-
async isAnnotatedTag(
|
|
336
|
-
|
|
337
|
-
|
|
583
|
+
async isAnnotatedTag(name) {
|
|
584
|
+
const refName = `refs/tags/${name}`;
|
|
585
|
+
const sha = await this.refStorage.getRef(refName);
|
|
586
|
+
if (sha === null) {
|
|
587
|
+
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
588
|
+
}
|
|
589
|
+
// Try to read as tag object
|
|
590
|
+
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
591
|
+
return tagObj !== null;
|
|
338
592
|
}
|
|
339
593
|
}
|
|
340
594
|
// ============================================================================
|
|
@@ -369,9 +623,49 @@ export class TagManager {
|
|
|
369
623
|
* isValidTagName('') // false (empty)
|
|
370
624
|
* ```
|
|
371
625
|
*/
|
|
372
|
-
export function isValidTagName(
|
|
373
|
-
//
|
|
374
|
-
|
|
626
|
+
export function isValidTagName(name) {
|
|
627
|
+
// Empty name is invalid
|
|
628
|
+
if (!name || name.length === 0 || name.trim().length === 0) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
// Cannot end with .lock
|
|
632
|
+
if (name.endsWith('.lock')) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
// Cannot contain ..
|
|
636
|
+
if (name.includes('..')) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
// Cannot contain @{
|
|
640
|
+
if (name.includes('@{')) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
// Cannot contain control characters (ASCII 0-31, 127), space, ~, ^, :, ?, *, [, \
|
|
644
|
+
const invalidChars = /[\x00-\x1f\x7f ~^:?*[\]\\]/;
|
|
645
|
+
if (invalidChars.test(name)) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
// Cannot end with /
|
|
649
|
+
if (name.endsWith('/')) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
// Split into components and check each
|
|
653
|
+
const components = name.split('/');
|
|
654
|
+
for (const component of components) {
|
|
655
|
+
// Cannot have empty components (// in path)
|
|
656
|
+
if (component.length === 0) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
// Cannot start with .
|
|
660
|
+
if (component.startsWith('.')) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
// Cannot end with .
|
|
664
|
+
if (component.endsWith('.')) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return true;
|
|
375
669
|
}
|
|
376
670
|
/**
|
|
377
671
|
* Type guard for annotated tags.
|
|
@@ -391,9 +685,10 @@ export function isValidTagName(_name) {
|
|
|
391
685
|
* }
|
|
392
686
|
* ```
|
|
393
687
|
*/
|
|
394
|
-
export function isAnnotatedTag(
|
|
395
|
-
|
|
396
|
-
|
|
688
|
+
export function isAnnotatedTag(tag) {
|
|
689
|
+
return tag.type === 'annotated' &&
|
|
690
|
+
tag.tagger !== undefined &&
|
|
691
|
+
tag.message !== undefined;
|
|
397
692
|
}
|
|
398
693
|
/**
|
|
399
694
|
* Format a tag message.
|
|
@@ -410,9 +705,18 @@ export function isAnnotatedTag(_tag) {
|
|
|
410
705
|
* formatTagMessage(' Hello World \r\n') // 'Hello World\n'
|
|
411
706
|
* ```
|
|
412
707
|
*/
|
|
413
|
-
export function formatTagMessage(
|
|
414
|
-
//
|
|
415
|
-
|
|
708
|
+
export function formatTagMessage(message) {
|
|
709
|
+
// Normalize line endings (CRLF -> LF)
|
|
710
|
+
let formatted = message.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
711
|
+
// Trim leading and trailing whitespace, but preserve internal structure
|
|
712
|
+
// The test expects 'Hello\r\nWorld\r\n' to become 'Hello\nWorld\n'
|
|
713
|
+
// So we trim leading whitespace, convert line endings, but preserve trailing newline if present
|
|
714
|
+
const hadTrailingNewline = formatted.endsWith('\n');
|
|
715
|
+
formatted = formatted.trim();
|
|
716
|
+
if (hadTrailingNewline && formatted.length > 0) {
|
|
717
|
+
formatted += '\n';
|
|
718
|
+
}
|
|
719
|
+
return formatted;
|
|
416
720
|
}
|
|
417
721
|
/**
|
|
418
722
|
* Parse a tag message from raw content.
|
|
@@ -432,9 +736,17 @@ export function formatTagMessage(_message) {
|
|
|
432
736
|
* }
|
|
433
737
|
* ```
|
|
434
738
|
*/
|
|
435
|
-
export function parseTagMessage(
|
|
436
|
-
|
|
437
|
-
|
|
739
|
+
export function parseTagMessage(content) {
|
|
740
|
+
const sigMarker = '-----BEGIN PGP SIGNATURE-----';
|
|
741
|
+
const sigIndex = content.indexOf(sigMarker);
|
|
742
|
+
if (sigIndex === -1) {
|
|
743
|
+
// No signature
|
|
744
|
+
return { message: content.trim() };
|
|
745
|
+
}
|
|
746
|
+
// Split message and signature
|
|
747
|
+
const message = content.slice(0, sigIndex).trim();
|
|
748
|
+
const signature = content.slice(sigIndex);
|
|
749
|
+
return { message, signature };
|
|
438
750
|
}
|
|
439
751
|
// ============================================================================
|
|
440
752
|
// Convenience Functions
|
|
@@ -459,9 +771,8 @@ export function parseTagMessage(_content) {
|
|
|
459
771
|
* })
|
|
460
772
|
* ```
|
|
461
773
|
*/
|
|
462
|
-
export async function createTag(
|
|
463
|
-
|
|
464
|
-
throw new Error('Not implemented');
|
|
774
|
+
export async function createTag(manager, name, target, options) {
|
|
775
|
+
return manager.createTag(name, target, options);
|
|
465
776
|
}
|
|
466
777
|
/**
|
|
467
778
|
* Create an annotated tag with message.
|
|
@@ -489,9 +800,13 @@ export async function createTag(_manager, _name, _target, _options) {
|
|
|
489
800
|
* )
|
|
490
801
|
* ```
|
|
491
802
|
*/
|
|
492
|
-
export async function createAnnotatedTag(
|
|
493
|
-
|
|
494
|
-
|
|
803
|
+
export async function createAnnotatedTag(manager, name, target, message, tagger, options) {
|
|
804
|
+
return manager.createTag(name, target, {
|
|
805
|
+
...options,
|
|
806
|
+
annotated: true,
|
|
807
|
+
message,
|
|
808
|
+
tagger
|
|
809
|
+
});
|
|
495
810
|
}
|
|
496
811
|
/**
|
|
497
812
|
* Delete a tag.
|
|
@@ -509,9 +824,8 @@ export async function createAnnotatedTag(_manager, _name, _target, _message, _ta
|
|
|
509
824
|
* await deleteTag(manager, 'v0.9.0-beta')
|
|
510
825
|
* ```
|
|
511
826
|
*/
|
|
512
|
-
export async function deleteTag(
|
|
513
|
-
|
|
514
|
-
throw new Error('Not implemented');
|
|
827
|
+
export async function deleteTag(manager, name, options) {
|
|
828
|
+
return manager.deleteTag(name, options);
|
|
515
829
|
}
|
|
516
830
|
/**
|
|
517
831
|
* List all tags.
|
|
@@ -528,9 +842,8 @@ export async function deleteTag(_manager, _name, _options) {
|
|
|
528
842
|
* const tags = await listTags(manager, { pattern: 'v1.*' })
|
|
529
843
|
* ```
|
|
530
844
|
*/
|
|
531
|
-
export async function listTags(
|
|
532
|
-
|
|
533
|
-
throw new Error('Not implemented');
|
|
845
|
+
export async function listTags(manager, options) {
|
|
846
|
+
return manager.listTags(options);
|
|
534
847
|
}
|
|
535
848
|
/**
|
|
536
849
|
* Get a tag by name.
|
|
@@ -548,9 +861,8 @@ export async function listTags(_manager, _options) {
|
|
|
548
861
|
* const tag = await getTag(manager, 'v1.0.0', { resolve: true })
|
|
549
862
|
* ```
|
|
550
863
|
*/
|
|
551
|
-
export async function getTag(
|
|
552
|
-
|
|
553
|
-
throw new Error('Not implemented');
|
|
864
|
+
export async function getTag(manager, name, options) {
|
|
865
|
+
return manager.getTag(name, options);
|
|
554
866
|
}
|
|
555
867
|
/**
|
|
556
868
|
* Check if a tag is annotated.
|
|
@@ -569,9 +881,8 @@ export async function getTag(_manager, _name, _options) {
|
|
|
569
881
|
* }
|
|
570
882
|
* ```
|
|
571
883
|
*/
|
|
572
|
-
export async function checkIsAnnotatedTag(
|
|
573
|
-
|
|
574
|
-
throw new Error('Not implemented');
|
|
884
|
+
export async function checkIsAnnotatedTag(manager, name) {
|
|
885
|
+
return manager.isAnnotatedTag(name);
|
|
575
886
|
}
|
|
576
887
|
/**
|
|
577
888
|
* Verify a tag's signature.
|
|
@@ -588,9 +899,8 @@ export async function checkIsAnnotatedTag(_manager, _name) {
|
|
|
588
899
|
* const result = await verifyTagSignature(manager, 'v1.0.0')
|
|
589
900
|
* ```
|
|
590
901
|
*/
|
|
591
|
-
export async function verifyTagSignature(
|
|
592
|
-
|
|
593
|
-
throw new Error('Not implemented');
|
|
902
|
+
export async function verifyTagSignature(manager, name) {
|
|
903
|
+
return manager.verifyTag(name);
|
|
594
904
|
}
|
|
595
905
|
/**
|
|
596
906
|
* Get the target commit SHA for a tag.
|
|
@@ -607,9 +917,8 @@ export async function verifyTagSignature(_manager, _name) {
|
|
|
607
917
|
* const sha = await getTagTarget(manager, 'v1.0.0')
|
|
608
918
|
* ```
|
|
609
919
|
*/
|
|
610
|
-
export async function getTagTarget(
|
|
611
|
-
|
|
612
|
-
throw new Error('Not implemented');
|
|
920
|
+
export async function getTagTarget(manager, name) {
|
|
921
|
+
return manager.getTagTarget(name);
|
|
613
922
|
}
|
|
614
923
|
/**
|
|
615
924
|
* Sort tags by semantic version.
|
|
@@ -628,9 +937,33 @@ export async function getTagTarget(_manager, _name) {
|
|
|
628
937
|
* // ['v2.0.0', 'v1.10.0', 'v1.9.0', 'v1.0.0', ...]
|
|
629
938
|
* ```
|
|
630
939
|
*/
|
|
631
|
-
export function sortTagsByVersion(
|
|
632
|
-
//
|
|
633
|
-
|
|
940
|
+
export function sortTagsByVersion(tags, direction = 'asc') {
|
|
941
|
+
// Parse version from tag name (handles v1.2.3, 1.2.3, v1.2.3-beta, etc.)
|
|
942
|
+
const parseVersion = (name) => {
|
|
943
|
+
// Remove 'v' prefix if present
|
|
944
|
+
const normalized = name.startsWith('v') ? name.slice(1) : name;
|
|
945
|
+
// Extract numeric version parts (split on non-digit, non-dot)
|
|
946
|
+
const parts = normalized.split(/[^0-9.]/)[0].split('.');
|
|
947
|
+
return parts.map(p => parseInt(p, 10) || 0);
|
|
948
|
+
};
|
|
949
|
+
const compareVersions = (a, b) => {
|
|
950
|
+
const maxLen = Math.max(a.length, b.length);
|
|
951
|
+
for (let i = 0; i < maxLen; i++) {
|
|
952
|
+
const aVal = a[i] || 0;
|
|
953
|
+
const bVal = b[i] || 0;
|
|
954
|
+
if (aVal !== bVal) {
|
|
955
|
+
return aVal - bVal;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return 0;
|
|
959
|
+
};
|
|
960
|
+
const sorted = [...tags].sort((a, b) => {
|
|
961
|
+
const aVer = parseVersion(a.name);
|
|
962
|
+
const bVer = parseVersion(b.name);
|
|
963
|
+
const cmp = compareVersions(aVer, bVer);
|
|
964
|
+
return direction === 'asc' ? cmp : -cmp;
|
|
965
|
+
});
|
|
966
|
+
return sorted;
|
|
634
967
|
}
|
|
635
968
|
/**
|
|
636
969
|
* Filter tags by glob pattern.
|
|
@@ -648,8 +981,16 @@ export function sortTagsByVersion(_tags, _direction = 'asc') {
|
|
|
648
981
|
* const v1Tags = filterTagsByPattern(tags, 'v1.*')
|
|
649
982
|
* ```
|
|
650
983
|
*/
|
|
651
|
-
export function filterTagsByPattern(
|
|
652
|
-
//
|
|
653
|
-
|
|
984
|
+
export function filterTagsByPattern(tags, pattern) {
|
|
985
|
+
// Convert glob pattern to regex
|
|
986
|
+
// * matches any number of characters
|
|
987
|
+
// ? matches a single character
|
|
988
|
+
// Escape special regex characters except * and ?
|
|
989
|
+
const regexPattern = pattern
|
|
990
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
|
|
991
|
+
.replace(/\*/g, '.*') // * -> .*
|
|
992
|
+
.replace(/\?/g, '.'); // ? -> .
|
|
993
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
994
|
+
return tags.filter(tag => regex.test(tag.name));
|
|
654
995
|
}
|
|
655
996
|
//# sourceMappingURL=tag.js.map
|