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/branch.js
CHANGED
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
* const branches = await listBranches(refStorage)
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
+
import { isValidSha } from './storage';
|
|
32
|
+
import { isValidRefName as _isValidRefName } from './storage';
|
|
33
|
+
void _isValidRefName;
|
|
31
34
|
/**
|
|
32
35
|
* Error thrown when a branch operation fails.
|
|
33
36
|
*
|
|
@@ -65,45 +68,21 @@ export class BranchError extends Error {
|
|
|
65
68
|
this.name = 'BranchError';
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// BranchManager Class
|
|
70
|
-
// ============================================================================
|
|
71
|
-
/**
|
|
72
|
-
* Branch manager for performing Git branch operations.
|
|
73
|
-
*
|
|
74
|
-
* @description
|
|
75
|
-
* Provides a comprehensive API for branch management. Uses RefStorage
|
|
76
|
-
* internally for ref manipulation.
|
|
77
|
-
*
|
|
78
|
-
* Note: Many methods are currently stubs (TODO) and will throw 'Not implemented'.
|
|
79
|
-
* These will be implemented in the GREEN phase of TDD development.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* const manager = new BranchManager(refStorage)
|
|
84
|
-
*
|
|
85
|
-
* // Create a feature branch
|
|
86
|
-
* const branch = await manager.createBranch('feature/auth', {
|
|
87
|
-
* startPoint: 'main',
|
|
88
|
-
* track: true
|
|
89
|
-
* })
|
|
90
|
-
*
|
|
91
|
-
* // List all branches
|
|
92
|
-
* const branches = await manager.listBranches({ includeRemotes: true })
|
|
93
|
-
*
|
|
94
|
-
* // Delete a merged branch
|
|
95
|
-
* await manager.deleteBranch('feature/auth')
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
71
|
export class BranchManager {
|
|
72
|
+
storage;
|
|
73
|
+
/** Storage for tracking information (simulated config) */
|
|
74
|
+
trackingInfo = new Map();
|
|
75
|
+
/** Optional callback to check if commits exist */
|
|
76
|
+
commitExists;
|
|
99
77
|
/**
|
|
100
78
|
* Create a new BranchManager.
|
|
101
79
|
*
|
|
102
80
|
* @param storage - RefStorage instance for ref operations
|
|
81
|
+
* @param options - Optional configuration
|
|
103
82
|
*/
|
|
104
|
-
constructor(storage) {
|
|
105
|
-
|
|
106
|
-
|
|
83
|
+
constructor(storage, options) {
|
|
84
|
+
this.storage = storage;
|
|
85
|
+
this.commitExists = options?.commitExists;
|
|
107
86
|
}
|
|
108
87
|
/**
|
|
109
88
|
* Create a new branch.
|
|
@@ -131,9 +110,101 @@ export class BranchManager {
|
|
|
131
110
|
* const branch = await manager.createBranch('main', { force: true, startPoint: 'HEAD' })
|
|
132
111
|
* ```
|
|
133
112
|
*/
|
|
134
|
-
async createBranch(
|
|
135
|
-
//
|
|
136
|
-
|
|
113
|
+
async createBranch(name, options) {
|
|
114
|
+
// Validate branch name
|
|
115
|
+
const validation = validateBranchName(name);
|
|
116
|
+
if (!validation.valid) {
|
|
117
|
+
throw new BranchError(validation.error, 'INVALID_NAME', name);
|
|
118
|
+
}
|
|
119
|
+
const branchRef = getBranchRefName(name);
|
|
120
|
+
const normalizedName = normalizeBranchName(name);
|
|
121
|
+
// Check if branch already exists
|
|
122
|
+
if (!options?.force) {
|
|
123
|
+
const existing = await this.storage.getRef(branchRef);
|
|
124
|
+
if (existing) {
|
|
125
|
+
throw new BranchError(`Branch '${name}' already exists`, 'ALREADY_EXISTS', name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Resolve start point to SHA
|
|
129
|
+
const startPoint = options?.startPoint ?? 'HEAD';
|
|
130
|
+
// Check for empty start point
|
|
131
|
+
if (startPoint === '') {
|
|
132
|
+
throw new BranchError('Start point cannot be empty', 'INVALID_START_POINT', name);
|
|
133
|
+
}
|
|
134
|
+
let sha;
|
|
135
|
+
// If startPoint is a valid SHA, use it directly
|
|
136
|
+
if (isValidSha(startPoint)) {
|
|
137
|
+
// If we have a commitExists validator, verify the commit exists
|
|
138
|
+
if (this.commitExists) {
|
|
139
|
+
const exists = await this.commitExists(startPoint);
|
|
140
|
+
if (!exists) {
|
|
141
|
+
throw new BranchError(`Invalid start point: ${startPoint}`, 'INVALID_START_POINT', name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
sha = startPoint;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Try to resolve as ref
|
|
148
|
+
try {
|
|
149
|
+
// First try as branch name
|
|
150
|
+
let resolved;
|
|
151
|
+
try {
|
|
152
|
+
resolved = await this.storage.resolveRef(getBranchRefName(startPoint));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Try as full ref path
|
|
156
|
+
try {
|
|
157
|
+
resolved = await this.storage.resolveRef(startPoint);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw new BranchError(`Invalid start point: ${startPoint}`, 'INVALID_START_POINT', name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
sha = resolved.sha;
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
if (e instanceof BranchError)
|
|
167
|
+
throw e;
|
|
168
|
+
throw new BranchError(`Invalid start point: ${startPoint}`, 'INVALID_START_POINT', name);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Get current branch to check if new branch is current
|
|
172
|
+
void await this.getCurrentBranch(); // Available for future use
|
|
173
|
+
const isCurrent = false; // New branch is never current
|
|
174
|
+
// Create tracking info if requested
|
|
175
|
+
let tracking;
|
|
176
|
+
if (options?.track) {
|
|
177
|
+
const remoteBranch = typeof options.track === 'string'
|
|
178
|
+
? options.track
|
|
179
|
+
: (startPoint.startsWith('refs/remotes/') ? startPoint : undefined);
|
|
180
|
+
if (remoteBranch) {
|
|
181
|
+
tracking = {
|
|
182
|
+
remote: remoteBranch.split('/')[2] || 'origin',
|
|
183
|
+
remoteBranch,
|
|
184
|
+
ahead: 0,
|
|
185
|
+
behind: 0,
|
|
186
|
+
gone: false
|
|
187
|
+
};
|
|
188
|
+
// Store tracking info
|
|
189
|
+
this.trackingInfo.set(normalizedName, tracking);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Build the branch object first (for dryRun)
|
|
193
|
+
const branch = {
|
|
194
|
+
name: normalizedName,
|
|
195
|
+
ref: branchRef,
|
|
196
|
+
sha,
|
|
197
|
+
isCurrent,
|
|
198
|
+
isRemote: false,
|
|
199
|
+
tracking
|
|
200
|
+
};
|
|
201
|
+
// If dryRun, return without actually creating
|
|
202
|
+
if (options?.dryRun) {
|
|
203
|
+
return branch;
|
|
204
|
+
}
|
|
205
|
+
// Create the ref
|
|
206
|
+
await this.storage.updateRef(branchRef, sha, { create: true, force: options?.force });
|
|
207
|
+
return branch;
|
|
137
208
|
}
|
|
138
209
|
/**
|
|
139
210
|
* Delete a branch.
|
|
@@ -156,9 +227,45 @@ export class BranchManager {
|
|
|
156
227
|
* await manager.deleteBranch('experiment', { force: true })
|
|
157
228
|
* ```
|
|
158
229
|
*/
|
|
159
|
-
async deleteBranch(
|
|
160
|
-
|
|
161
|
-
|
|
230
|
+
async deleteBranch(name, options) {
|
|
231
|
+
const normalizedName = normalizeBranchName(name);
|
|
232
|
+
// Handle remote branch deletion
|
|
233
|
+
if (options?.remote) {
|
|
234
|
+
const remoteRef = `refs/remotes/${options.remote}/${normalizedName}`;
|
|
235
|
+
const remoteExists = await this.storage.getRef(remoteRef);
|
|
236
|
+
if (remoteExists) {
|
|
237
|
+
if (!options?.dryRun) {
|
|
238
|
+
await this.storage.deleteRef(remoteRef);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const branchRef = getBranchRefName(normalizedName);
|
|
244
|
+
// Check if branch exists
|
|
245
|
+
const existing = await this.storage.getRef(branchRef);
|
|
246
|
+
if (!existing) {
|
|
247
|
+
throw new BranchError(`Branch '${name}' not found`, 'NOT_FOUND', name);
|
|
248
|
+
}
|
|
249
|
+
// Check if this is the current branch
|
|
250
|
+
const currentBranch = await this.getCurrentBranch();
|
|
251
|
+
if (currentBranch && currentBranch.name === normalizedName) {
|
|
252
|
+
throw new BranchError(`Cannot delete the checked out branch '${name}'`, 'CANNOT_DELETE_CURRENT', name);
|
|
253
|
+
}
|
|
254
|
+
// Check if branch is fully merged (unless force is true)
|
|
255
|
+
if (!options?.force) {
|
|
256
|
+
const isMerged = await this.isMerged(normalizedName);
|
|
257
|
+
if (!isMerged) {
|
|
258
|
+
throw new BranchError(`Branch '${name}' is not fully merged. Use force to delete anyway.`, 'NOT_FULLY_MERGED', name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// If dryRun, return without actually deleting
|
|
262
|
+
if (options?.dryRun) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Delete the ref
|
|
266
|
+
await this.storage.deleteRef(branchRef);
|
|
267
|
+
// Remove tracking info
|
|
268
|
+
this.trackingInfo.delete(normalizedName);
|
|
162
269
|
}
|
|
163
270
|
/**
|
|
164
271
|
* Rename a branch.
|
|
@@ -180,9 +287,62 @@ export class BranchManager {
|
|
|
180
287
|
* const branch = await manager.renameBranch('old-name', 'new-name')
|
|
181
288
|
* ```
|
|
182
289
|
*/
|
|
183
|
-
async renameBranch(
|
|
184
|
-
//
|
|
185
|
-
|
|
290
|
+
async renameBranch(oldName, newName, options) {
|
|
291
|
+
// Validate new branch name
|
|
292
|
+
const validation = validateBranchName(newName);
|
|
293
|
+
if (!validation.valid) {
|
|
294
|
+
throw new BranchError(validation.error, 'INVALID_NAME', newName);
|
|
295
|
+
}
|
|
296
|
+
const oldNormalized = normalizeBranchName(oldName);
|
|
297
|
+
const newNormalized = normalizeBranchName(newName);
|
|
298
|
+
const oldRef = getBranchRefName(oldNormalized);
|
|
299
|
+
const newRef = getBranchRefName(newNormalized);
|
|
300
|
+
// Check if old branch exists
|
|
301
|
+
const oldBranch = await this.storage.getRef(oldRef);
|
|
302
|
+
if (!oldBranch) {
|
|
303
|
+
throw new BranchError(`Branch '${oldName}' not found`, 'NOT_FOUND', oldName);
|
|
304
|
+
}
|
|
305
|
+
// Check if new branch already exists (unless force)
|
|
306
|
+
if (!options?.force) {
|
|
307
|
+
const existingNew = await this.storage.getRef(newRef);
|
|
308
|
+
if (existingNew) {
|
|
309
|
+
throw new BranchError(`Branch '${newName}' already exists`, 'ALREADY_EXISTS', newName);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Get the SHA from old branch
|
|
313
|
+
const sha = oldBranch.target;
|
|
314
|
+
// Check if this is the current branch
|
|
315
|
+
const currentBranch = await this.getCurrentBranch();
|
|
316
|
+
const wasCurrent = currentBranch && currentBranch.name === oldNormalized;
|
|
317
|
+
// Get tracking info from old branch
|
|
318
|
+
const oldTracking = this.trackingInfo.get(oldNormalized);
|
|
319
|
+
// Build result branch object
|
|
320
|
+
const branch = {
|
|
321
|
+
name: newNormalized,
|
|
322
|
+
ref: newRef,
|
|
323
|
+
sha,
|
|
324
|
+
isCurrent: wasCurrent ?? false,
|
|
325
|
+
isRemote: false,
|
|
326
|
+
tracking: oldTracking
|
|
327
|
+
};
|
|
328
|
+
// If dryRun, return without actually renaming
|
|
329
|
+
if (options?.dryRun) {
|
|
330
|
+
return branch;
|
|
331
|
+
}
|
|
332
|
+
// Create new ref with the same SHA
|
|
333
|
+
await this.storage.updateRef(newRef, sha, { create: true, force: options?.force });
|
|
334
|
+
// Delete old ref
|
|
335
|
+
await this.storage.deleteRef(oldRef);
|
|
336
|
+
// If this was the current branch, update HEAD to point to new branch
|
|
337
|
+
if (wasCurrent) {
|
|
338
|
+
await this.storage.updateHead(newRef, true);
|
|
339
|
+
}
|
|
340
|
+
// Transfer tracking info
|
|
341
|
+
if (oldTracking) {
|
|
342
|
+
this.trackingInfo.delete(oldNormalized);
|
|
343
|
+
this.trackingInfo.set(newNormalized, oldTracking);
|
|
344
|
+
}
|
|
345
|
+
return branch;
|
|
186
346
|
}
|
|
187
347
|
/**
|
|
188
348
|
* List all branches.
|
|
@@ -206,9 +366,46 @@ export class BranchManager {
|
|
|
206
366
|
* const merged = await manager.listBranches({ mergedInto: 'main' })
|
|
207
367
|
* ```
|
|
208
368
|
*/
|
|
209
|
-
async listBranches(
|
|
210
|
-
|
|
211
|
-
|
|
369
|
+
async listBranches(options) {
|
|
370
|
+
const branches = [];
|
|
371
|
+
// Get current branch for isCurrent flag
|
|
372
|
+
const currentBranch = await this.getCurrentBranch();
|
|
373
|
+
// List local branches
|
|
374
|
+
if (!options?.remotesOnly) {
|
|
375
|
+
const localRefs = await this.storage.listRefs({ pattern: 'refs/heads/*' });
|
|
376
|
+
for (const ref of localRefs) {
|
|
377
|
+
const name = normalizeBranchName(ref.name);
|
|
378
|
+
// Apply pattern filter if specified
|
|
379
|
+
if (options?.pattern) {
|
|
380
|
+
const regex = new RegExp('^' + options.pattern.replace(/\*/g, '.*') + '$');
|
|
381
|
+
if (!regex.test(name))
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
branches.push({
|
|
385
|
+
name,
|
|
386
|
+
ref: ref.name,
|
|
387
|
+
sha: ref.target,
|
|
388
|
+
isCurrent: currentBranch?.name === name,
|
|
389
|
+
isRemote: false,
|
|
390
|
+
tracking: options?.includeTracking ? this.trackingInfo.get(name) : undefined
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// List remote branches if requested
|
|
395
|
+
if (options?.includeRemotes || options?.remotesOnly) {
|
|
396
|
+
const remoteRefs = await this.storage.listRefs({ pattern: 'refs/remotes/*' });
|
|
397
|
+
for (const ref of remoteRefs) {
|
|
398
|
+
const name = ref.name.replace(/^refs\/remotes\//, '');
|
|
399
|
+
branches.push({
|
|
400
|
+
name,
|
|
401
|
+
ref: ref.name,
|
|
402
|
+
sha: ref.target,
|
|
403
|
+
isCurrent: false,
|
|
404
|
+
isRemote: true
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return branches;
|
|
212
409
|
}
|
|
213
410
|
/**
|
|
214
411
|
* Get the current branch.
|
|
@@ -229,8 +426,30 @@ export class BranchManager {
|
|
|
229
426
|
* ```
|
|
230
427
|
*/
|
|
231
428
|
async getCurrentBranch() {
|
|
232
|
-
|
|
233
|
-
|
|
429
|
+
const head = await this.storage.getRef('HEAD');
|
|
430
|
+
if (!head)
|
|
431
|
+
return null;
|
|
432
|
+
// If HEAD is direct (detached), there is no current branch
|
|
433
|
+
if (head.type === 'direct') {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
// HEAD is symbolic, pointing to a branch
|
|
437
|
+
const branchRef = head.target;
|
|
438
|
+
if (!branchRef.startsWith('refs/heads/')) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
const ref = await this.storage.getRef(branchRef);
|
|
442
|
+
if (!ref)
|
|
443
|
+
return null;
|
|
444
|
+
const name = normalizeBranchName(branchRef);
|
|
445
|
+
return {
|
|
446
|
+
name,
|
|
447
|
+
ref: branchRef,
|
|
448
|
+
sha: ref.target,
|
|
449
|
+
isCurrent: true,
|
|
450
|
+
isRemote: false,
|
|
451
|
+
tracking: this.trackingInfo.get(name)
|
|
452
|
+
};
|
|
234
453
|
}
|
|
235
454
|
/**
|
|
236
455
|
* Get a specific branch by name.
|
|
@@ -249,9 +468,21 @@ export class BranchManager {
|
|
|
249
468
|
* }
|
|
250
469
|
* ```
|
|
251
470
|
*/
|
|
252
|
-
async getBranch(
|
|
253
|
-
|
|
254
|
-
|
|
471
|
+
async getBranch(name) {
|
|
472
|
+
const normalizedName = normalizeBranchName(name);
|
|
473
|
+
const branchRef = getBranchRefName(normalizedName);
|
|
474
|
+
const ref = await this.storage.getRef(branchRef);
|
|
475
|
+
if (!ref)
|
|
476
|
+
return null;
|
|
477
|
+
const currentBranch = await this.getCurrentBranch();
|
|
478
|
+
return {
|
|
479
|
+
name: normalizedName,
|
|
480
|
+
ref: branchRef,
|
|
481
|
+
sha: ref.target,
|
|
482
|
+
isCurrent: currentBranch?.name === normalizedName,
|
|
483
|
+
isRemote: false,
|
|
484
|
+
tracking: this.trackingInfo.get(normalizedName)
|
|
485
|
+
};
|
|
255
486
|
}
|
|
256
487
|
/**
|
|
257
488
|
* Check if a branch exists.
|
|
@@ -269,9 +500,22 @@ export class BranchManager {
|
|
|
269
500
|
* }
|
|
270
501
|
* ```
|
|
271
502
|
*/
|
|
272
|
-
async branchExists(
|
|
273
|
-
//
|
|
274
|
-
|
|
503
|
+
async branchExists(name) {
|
|
504
|
+
// Handle remote branch names like 'origin/main'
|
|
505
|
+
if (name.includes('/') && !name.startsWith('refs/')) {
|
|
506
|
+
const parts = name.split('/');
|
|
507
|
+
if (parts.length >= 2) {
|
|
508
|
+
// Check if it's a remote reference
|
|
509
|
+
const remoteRef = `refs/remotes/${name}`;
|
|
510
|
+
const remoteExists = await this.storage.getRef(remoteRef);
|
|
511
|
+
if (remoteExists)
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const normalizedName = normalizeBranchName(name);
|
|
516
|
+
const branchRef = getBranchRefName(normalizedName);
|
|
517
|
+
const ref = await this.storage.getRef(branchRef);
|
|
518
|
+
return ref !== null;
|
|
275
519
|
}
|
|
276
520
|
/**
|
|
277
521
|
* Set upstream branch for tracking.
|
|
@@ -295,9 +539,31 @@ export class BranchManager {
|
|
|
295
539
|
* await manager.setUpstream('feature', { unset: true })
|
|
296
540
|
* ```
|
|
297
541
|
*/
|
|
298
|
-
async setUpstream(
|
|
299
|
-
|
|
300
|
-
|
|
542
|
+
async setUpstream(branchName, options) {
|
|
543
|
+
const normalizedName = normalizeBranchName(branchName);
|
|
544
|
+
const branchRef = getBranchRefName(normalizedName);
|
|
545
|
+
// Check if branch exists
|
|
546
|
+
const existing = await this.storage.getRef(branchRef);
|
|
547
|
+
if (!existing) {
|
|
548
|
+
throw new BranchError(`Branch '${branchName}' not found`, 'NOT_FOUND', branchName);
|
|
549
|
+
}
|
|
550
|
+
// If unset, remove tracking info
|
|
551
|
+
if (options.unset) {
|
|
552
|
+
this.trackingInfo.delete(normalizedName);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
// Build the remote branch ref
|
|
556
|
+
const remote = options.remote || 'origin';
|
|
557
|
+
const remoteBranchName = options.remoteBranch || normalizedName;
|
|
558
|
+
const remoteBranch = `refs/remotes/${remote}/${remoteBranchName}`;
|
|
559
|
+
const tracking = {
|
|
560
|
+
remote,
|
|
561
|
+
remoteBranch,
|
|
562
|
+
ahead: 0,
|
|
563
|
+
behind: 0,
|
|
564
|
+
gone: false
|
|
565
|
+
};
|
|
566
|
+
this.trackingInfo.set(normalizedName, tracking);
|
|
301
567
|
}
|
|
302
568
|
/**
|
|
303
569
|
* Get tracking info for a branch.
|
|
@@ -316,9 +582,9 @@ export class BranchManager {
|
|
|
316
582
|
* }
|
|
317
583
|
* ```
|
|
318
584
|
*/
|
|
319
|
-
async getTrackingInfo(
|
|
320
|
-
|
|
321
|
-
|
|
585
|
+
async getTrackingInfo(branchName) {
|
|
586
|
+
const normalizedName = normalizeBranchName(branchName);
|
|
587
|
+
return this.trackingInfo.get(normalizedName) ?? null;
|
|
322
588
|
}
|
|
323
589
|
/**
|
|
324
590
|
* Check if a branch is fully merged into another branch.
|
|
@@ -337,9 +603,35 @@ export class BranchManager {
|
|
|
337
603
|
* }
|
|
338
604
|
* ```
|
|
339
605
|
*/
|
|
340
|
-
async isMerged(
|
|
341
|
-
|
|
342
|
-
|
|
606
|
+
async isMerged(branchName, into) {
|
|
607
|
+
const normalizedName = normalizeBranchName(branchName);
|
|
608
|
+
const branchRef = getBranchRefName(normalizedName);
|
|
609
|
+
// Get the branch SHA
|
|
610
|
+
const branchSha = await this.storage.getRef(branchRef);
|
|
611
|
+
if (!branchSha) {
|
|
612
|
+
throw new BranchError(`Branch '${branchName}' not found`, 'NOT_FOUND', branchName);
|
|
613
|
+
}
|
|
614
|
+
// Get the target branch SHA
|
|
615
|
+
let targetSha;
|
|
616
|
+
if (into) {
|
|
617
|
+
const targetRef = getBranchRefName(normalizeBranchName(into));
|
|
618
|
+
const resolved = await this.storage.getRef(targetRef);
|
|
619
|
+
if (!resolved) {
|
|
620
|
+
throw new BranchError(`Branch '${into}' not found`, 'NOT_FOUND', into);
|
|
621
|
+
}
|
|
622
|
+
targetSha = resolved.target;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
// Use current branch
|
|
626
|
+
const current = await this.getCurrentBranch();
|
|
627
|
+
if (!current) {
|
|
628
|
+
throw new BranchError('No current branch', 'DETACHED_HEAD');
|
|
629
|
+
}
|
|
630
|
+
targetSha = current.sha;
|
|
631
|
+
}
|
|
632
|
+
// Simple check: if the branch points to the same SHA as target, it's merged
|
|
633
|
+
// For a more accurate check, we'd need to walk the commit graph
|
|
634
|
+
return branchSha.target === targetSha;
|
|
343
635
|
}
|
|
344
636
|
/**
|
|
345
637
|
* Force delete an unmerged branch.
|
|
@@ -357,9 +649,8 @@ export class BranchManager {
|
|
|
357
649
|
* await manager.forceDeleteBranch('abandoned-feature')
|
|
358
650
|
* ```
|
|
359
651
|
*/
|
|
360
|
-
async forceDeleteBranch(
|
|
361
|
-
|
|
362
|
-
throw new Error('Not implemented');
|
|
652
|
+
async forceDeleteBranch(name) {
|
|
653
|
+
return this.deleteBranch(name, { force: true });
|
|
363
654
|
}
|
|
364
655
|
}
|
|
365
656
|
// ============================================================================
|
|
@@ -372,8 +663,6 @@ export class BranchManager {
|
|
|
372
663
|
* Checks if a branch name is valid and returns detailed validation results
|
|
373
664
|
* including the normalized form of the name.
|
|
374
665
|
*
|
|
375
|
-
* Note: This is a stub implementation. Full validation will be added in GREEN phase.
|
|
376
|
-
*
|
|
377
666
|
* @param name - Branch name to validate
|
|
378
667
|
* @returns Validation result with valid flag, error message, and normalized name
|
|
379
668
|
*
|
|
@@ -389,9 +678,44 @@ export class BranchManager {
|
|
|
389
678
|
* }
|
|
390
679
|
* ```
|
|
391
680
|
*/
|
|
392
|
-
export function validateBranchName(
|
|
393
|
-
//
|
|
394
|
-
|
|
681
|
+
export function validateBranchName(name) {
|
|
682
|
+
// Empty name is invalid
|
|
683
|
+
if (!name || name.length === 0) {
|
|
684
|
+
return { valid: false, error: 'Branch name cannot be empty' };
|
|
685
|
+
}
|
|
686
|
+
// HEAD is not a valid branch name
|
|
687
|
+
if (name === 'HEAD') {
|
|
688
|
+
return { valid: false, error: 'HEAD is not a valid branch name' };
|
|
689
|
+
}
|
|
690
|
+
// Cannot start with dash
|
|
691
|
+
if (name.startsWith('-')) {
|
|
692
|
+
return { valid: false, error: 'Branch name cannot start with a dash' };
|
|
693
|
+
}
|
|
694
|
+
// Cannot contain spaces
|
|
695
|
+
if (name.includes(' ')) {
|
|
696
|
+
return { valid: false, error: 'Branch name cannot contain spaces' };
|
|
697
|
+
}
|
|
698
|
+
// Cannot contain double dots
|
|
699
|
+
if (name.includes('..')) {
|
|
700
|
+
return { valid: false, error: 'Branch name cannot contain double dots (..)' };
|
|
701
|
+
}
|
|
702
|
+
// Cannot end with .lock
|
|
703
|
+
if (name.endsWith('.lock')) {
|
|
704
|
+
return { valid: false, error: 'Branch name cannot end with .lock' };
|
|
705
|
+
}
|
|
706
|
+
// Cannot contain control characters (ASCII 0-31, 127)
|
|
707
|
+
const controlCharRegex = /[\x00-\x1f\x7f]/;
|
|
708
|
+
if (controlCharRegex.test(name)) {
|
|
709
|
+
return { valid: false, error: 'Branch name cannot contain control characters' };
|
|
710
|
+
}
|
|
711
|
+
// Cannot contain ~, ^, :, ?, *, [, ], \
|
|
712
|
+
const invalidChars = /[~^:?*[\]\\]/;
|
|
713
|
+
if (invalidChars.test(name)) {
|
|
714
|
+
return { valid: false, error: 'Branch name contains invalid characters (~, ^, :, ?, *, [, ], \\)' };
|
|
715
|
+
}
|
|
716
|
+
// Normalize the name (strip refs/heads/ if present)
|
|
717
|
+
const normalized = normalizeBranchName(name);
|
|
718
|
+
return { valid: true, normalized };
|
|
395
719
|
}
|
|
396
720
|
/**
|
|
397
721
|
* Check if a string is a valid branch name.
|
|
@@ -409,9 +733,8 @@ export function validateBranchName(_name) {
|
|
|
409
733
|
* }
|
|
410
734
|
* ```
|
|
411
735
|
*/
|
|
412
|
-
export function isValidBranchName(
|
|
413
|
-
|
|
414
|
-
throw new Error('Not implemented');
|
|
736
|
+
export function isValidBranchName(name) {
|
|
737
|
+
return validateBranchName(name).valid;
|
|
415
738
|
}
|
|
416
739
|
/**
|
|
417
740
|
* Normalize a branch name.
|
|
@@ -428,9 +751,11 @@ export function isValidBranchName(_name) {
|
|
|
428
751
|
* normalizeBranchName('main') // 'main'
|
|
429
752
|
* ```
|
|
430
753
|
*/
|
|
431
|
-
export function normalizeBranchName(
|
|
432
|
-
|
|
433
|
-
|
|
754
|
+
export function normalizeBranchName(name) {
|
|
755
|
+
if (name.startsWith('refs/heads/')) {
|
|
756
|
+
return name.slice('refs/heads/'.length);
|
|
757
|
+
}
|
|
758
|
+
return name;
|
|
434
759
|
}
|
|
435
760
|
/**
|
|
436
761
|
* Get the full ref name for a branch.
|
|
@@ -447,9 +772,11 @@ export function normalizeBranchName(_name) {
|
|
|
447
772
|
* getBranchRefName('refs/heads/main') // 'refs/heads/main'
|
|
448
773
|
* ```
|
|
449
774
|
*/
|
|
450
|
-
export function getBranchRefName(
|
|
451
|
-
|
|
452
|
-
|
|
775
|
+
export function getBranchRefName(name) {
|
|
776
|
+
if (name.startsWith('refs/heads/')) {
|
|
777
|
+
return name;
|
|
778
|
+
}
|
|
779
|
+
return `refs/heads/${name}`;
|
|
453
780
|
}
|
|
454
781
|
// ============================================================================
|
|
455
782
|
// Convenience Functions
|
|
@@ -470,9 +797,9 @@ export function getBranchRefName(_name) {
|
|
|
470
797
|
* const branch = await createBranch(storage, 'feature', { startPoint: 'main' })
|
|
471
798
|
* ```
|
|
472
799
|
*/
|
|
473
|
-
export async function createBranch(
|
|
474
|
-
|
|
475
|
-
|
|
800
|
+
export async function createBranch(storage, name, options) {
|
|
801
|
+
const manager = new BranchManager(storage);
|
|
802
|
+
return manager.createBranch(name, options);
|
|
476
803
|
}
|
|
477
804
|
/**
|
|
478
805
|
* Delete a branch.
|
|
@@ -489,9 +816,9 @@ export async function createBranch(_storage, _name, _options) {
|
|
|
489
816
|
* await deleteBranch(storage, 'feature', { force: true })
|
|
490
817
|
* ```
|
|
491
818
|
*/
|
|
492
|
-
export async function deleteBranch(
|
|
493
|
-
|
|
494
|
-
|
|
819
|
+
export async function deleteBranch(storage, name, options) {
|
|
820
|
+
const manager = new BranchManager(storage);
|
|
821
|
+
return manager.deleteBranch(name, options);
|
|
495
822
|
}
|
|
496
823
|
/**
|
|
497
824
|
* Rename a branch.
|
|
@@ -510,9 +837,9 @@ export async function deleteBranch(_storage, _name, _options) {
|
|
|
510
837
|
* const branch = await renameBranch(storage, 'old', 'new')
|
|
511
838
|
* ```
|
|
512
839
|
*/
|
|
513
|
-
export async function renameBranch(
|
|
514
|
-
|
|
515
|
-
|
|
840
|
+
export async function renameBranch(storage, oldName, newName, options) {
|
|
841
|
+
const manager = new BranchManager(storage);
|
|
842
|
+
return manager.renameBranch(oldName, newName, options);
|
|
516
843
|
}
|
|
517
844
|
/**
|
|
518
845
|
* List all branches.
|
|
@@ -529,9 +856,9 @@ export async function renameBranch(_storage, _oldName, _newName, _options) {
|
|
|
529
856
|
* const branches = await listBranches(storage, { includeRemotes: true })
|
|
530
857
|
* ```
|
|
531
858
|
*/
|
|
532
|
-
export async function listBranches(
|
|
533
|
-
|
|
534
|
-
|
|
859
|
+
export async function listBranches(storage, options) {
|
|
860
|
+
const manager = new BranchManager(storage);
|
|
861
|
+
return manager.listBranches(options);
|
|
535
862
|
}
|
|
536
863
|
/**
|
|
537
864
|
* Get the current branch.
|
|
@@ -547,8 +874,8 @@ export async function listBranches(_storage, _options) {
|
|
|
547
874
|
* const current = await getCurrentBranch(storage)
|
|
548
875
|
* ```
|
|
549
876
|
*/
|
|
550
|
-
export async function getCurrentBranch(
|
|
551
|
-
|
|
552
|
-
|
|
877
|
+
export async function getCurrentBranch(storage) {
|
|
878
|
+
const manager = new BranchManager(storage);
|
|
879
|
+
return manager.getCurrentBranch();
|
|
553
880
|
}
|
|
554
881
|
//# sourceMappingURL=branch.js.map
|