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.
Files changed (231) hide show
  1. package/README.md +319 -92
  2. package/dist/cli/commands/add.d.ts +176 -0
  3. package/dist/cli/commands/add.d.ts.map +1 -0
  4. package/dist/cli/commands/add.js +979 -0
  5. package/dist/cli/commands/add.js.map +1 -0
  6. package/dist/cli/commands/blame.d.ts +1 -1
  7. package/dist/cli/commands/blame.d.ts.map +1 -1
  8. package/dist/cli/commands/blame.js +1 -1
  9. package/dist/cli/commands/blame.js.map +1 -1
  10. package/dist/cli/commands/branch.d.ts +1 -1
  11. package/dist/cli/commands/branch.d.ts.map +1 -1
  12. package/dist/cli/commands/branch.js +2 -2
  13. package/dist/cli/commands/branch.js.map +1 -1
  14. package/dist/cli/commands/checkout.d.ts +73 -0
  15. package/dist/cli/commands/checkout.d.ts.map +1 -0
  16. package/dist/cli/commands/checkout.js +725 -0
  17. package/dist/cli/commands/checkout.js.map +1 -0
  18. package/dist/cli/commands/commit.d.ts.map +1 -1
  19. package/dist/cli/commands/commit.js +22 -2
  20. package/dist/cli/commands/commit.js.map +1 -1
  21. package/dist/cli/commands/diff.d.ts +4 -4
  22. package/dist/cli/commands/diff.d.ts.map +1 -1
  23. package/dist/cli/commands/diff.js +9 -8
  24. package/dist/cli/commands/diff.js.map +1 -1
  25. package/dist/cli/commands/log.d.ts +1 -1
  26. package/dist/cli/commands/log.d.ts.map +1 -1
  27. package/dist/cli/commands/log.js +1 -1
  28. package/dist/cli/commands/log.js.map +1 -1
  29. package/dist/cli/commands/merge.d.ts +106 -0
  30. package/dist/cli/commands/merge.d.ts.map +1 -0
  31. package/dist/cli/commands/merge.js +852 -0
  32. package/dist/cli/commands/merge.js.map +1 -0
  33. package/dist/cli/commands/review.d.ts +1 -1
  34. package/dist/cli/commands/review.d.ts.map +1 -1
  35. package/dist/cli/commands/review.js +26 -1
  36. package/dist/cli/commands/review.js.map +1 -1
  37. package/dist/cli/commands/stash.d.ts +157 -0
  38. package/dist/cli/commands/stash.d.ts.map +1 -0
  39. package/dist/cli/commands/stash.js +655 -0
  40. package/dist/cli/commands/stash.js.map +1 -0
  41. package/dist/cli/commands/status.d.ts.map +1 -1
  42. package/dist/cli/commands/status.js +1 -2
  43. package/dist/cli/commands/status.js.map +1 -1
  44. package/dist/cli/commands/web.d.ts.map +1 -1
  45. package/dist/cli/commands/web.js +3 -2
  46. package/dist/cli/commands/web.js.map +1 -1
  47. package/dist/cli/fs-adapter.d.ts.map +1 -1
  48. package/dist/cli/fs-adapter.js +3 -5
  49. package/dist/cli/fs-adapter.js.map +1 -1
  50. package/dist/cli/fsx-cli-adapter.d.ts +359 -0
  51. package/dist/cli/fsx-cli-adapter.d.ts.map +1 -0
  52. package/dist/cli/fsx-cli-adapter.js +619 -0
  53. package/dist/cli/fsx-cli-adapter.js.map +1 -0
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +68 -12
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/ui/components/DiffView.d.ts +7 -2
  58. package/dist/cli/ui/components/DiffView.d.ts.map +1 -1
  59. package/dist/cli/ui/components/DiffView.js.map +1 -1
  60. package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -2
  61. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -1
  62. package/dist/cli/ui/components/ErrorDisplay.js.map +1 -1
  63. package/dist/cli/ui/components/FuzzySearch.d.ts +8 -2
  64. package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -1
  65. package/dist/cli/ui/components/FuzzySearch.js.map +1 -1
  66. package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -2
  67. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -1
  68. package/dist/cli/ui/components/LoadingSpinner.js.map +1 -1
  69. package/dist/cli/ui/components/NavigationList.d.ts +7 -2
  70. package/dist/cli/ui/components/NavigationList.d.ts.map +1 -1
  71. package/dist/cli/ui/components/NavigationList.js.map +1 -1
  72. package/dist/cli/ui/components/ScrollableContent.d.ts +7 -2
  73. package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -1
  74. package/dist/cli/ui/components/ScrollableContent.js.map +1 -1
  75. package/dist/cli/ui/terminal-ui.d.ts +42 -9
  76. package/dist/cli/ui/terminal-ui.d.ts.map +1 -1
  77. package/dist/cli/ui/terminal-ui.js.map +1 -1
  78. package/dist/do/BashModule.d.ts +871 -0
  79. package/dist/do/BashModule.d.ts.map +1 -0
  80. package/dist/do/BashModule.js +1143 -0
  81. package/dist/do/BashModule.js.map +1 -0
  82. package/dist/do/FsModule.d.ts +612 -0
  83. package/dist/do/FsModule.d.ts.map +1 -0
  84. package/dist/do/FsModule.js +1120 -0
  85. package/dist/do/FsModule.js.map +1 -0
  86. package/dist/do/GitModule.d.ts +635 -0
  87. package/dist/do/GitModule.d.ts.map +1 -0
  88. package/dist/do/GitModule.js +784 -0
  89. package/dist/do/GitModule.js.map +1 -0
  90. package/dist/do/GitRepoDO.d.ts +281 -0
  91. package/dist/do/GitRepoDO.d.ts.map +1 -0
  92. package/dist/do/GitRepoDO.js +479 -0
  93. package/dist/do/GitRepoDO.js.map +1 -0
  94. package/dist/do/bash-ast.d.ts +246 -0
  95. package/dist/do/bash-ast.d.ts.map +1 -0
  96. package/dist/do/bash-ast.js +888 -0
  97. package/dist/do/bash-ast.js.map +1 -0
  98. package/dist/do/container-executor.d.ts +491 -0
  99. package/dist/do/container-executor.d.ts.map +1 -0
  100. package/dist/do/container-executor.js +731 -0
  101. package/dist/do/container-executor.js.map +1 -0
  102. package/dist/do/index.d.ts +53 -0
  103. package/dist/do/index.d.ts.map +1 -0
  104. package/dist/do/index.js +91 -0
  105. package/dist/do/index.js.map +1 -0
  106. package/dist/do/tiered-storage.d.ts +403 -0
  107. package/dist/do/tiered-storage.d.ts.map +1 -0
  108. package/dist/do/tiered-storage.js +689 -0
  109. package/dist/do/tiered-storage.js.map +1 -0
  110. package/dist/do/withBash.d.ts +231 -0
  111. package/dist/do/withBash.d.ts.map +1 -0
  112. package/dist/do/withBash.js +244 -0
  113. package/dist/do/withBash.js.map +1 -0
  114. package/dist/do/withFs.d.ts +237 -0
  115. package/dist/do/withFs.d.ts.map +1 -0
  116. package/dist/do/withFs.js +387 -0
  117. package/dist/do/withFs.js.map +1 -0
  118. package/dist/do/withGit.d.ts +180 -0
  119. package/dist/do/withGit.d.ts.map +1 -0
  120. package/dist/do/withGit.js +271 -0
  121. package/dist/do/withGit.js.map +1 -0
  122. package/dist/durable-object/object-store.d.ts +157 -15
  123. package/dist/durable-object/object-store.d.ts.map +1 -1
  124. package/dist/durable-object/object-store.js +435 -47
  125. package/dist/durable-object/object-store.js.map +1 -1
  126. package/dist/durable-object/schema.d.ts +12 -1
  127. package/dist/durable-object/schema.d.ts.map +1 -1
  128. package/dist/durable-object/schema.js +87 -2
  129. package/dist/durable-object/schema.js.map +1 -1
  130. package/dist/index.d.ts +84 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +34 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +22 -0
  135. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +1 -0
  136. package/dist/mcp/sandbox/miniflare-evaluator.js +140 -0
  137. package/dist/mcp/sandbox/miniflare-evaluator.js.map +1 -0
  138. package/dist/mcp/sandbox/object-store-proxy.d.ts +32 -0
  139. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +1 -0
  140. package/dist/mcp/sandbox/object-store-proxy.js +30 -0
  141. package/dist/mcp/sandbox/object-store-proxy.js.map +1 -0
  142. package/dist/mcp/sandbox/template.d.ts +17 -0
  143. package/dist/mcp/sandbox/template.d.ts.map +1 -0
  144. package/dist/mcp/sandbox/template.js +71 -0
  145. package/dist/mcp/sandbox/template.js.map +1 -0
  146. package/dist/mcp/sandbox.d.ts.map +1 -1
  147. package/dist/mcp/sandbox.js +16 -4
  148. package/dist/mcp/sandbox.js.map +1 -1
  149. package/dist/mcp/tools/do.d.ts +32 -0
  150. package/dist/mcp/tools/do.d.ts.map +1 -0
  151. package/dist/mcp/tools/do.js +117 -0
  152. package/dist/mcp/tools/do.js.map +1 -0
  153. package/dist/mcp/tools.d.ts.map +1 -1
  154. package/dist/mcp/tools.js +1258 -22
  155. package/dist/mcp/tools.js.map +1 -1
  156. package/dist/pack/delta.d.ts +8 -0
  157. package/dist/pack/delta.d.ts.map +1 -1
  158. package/dist/pack/delta.js +241 -30
  159. package/dist/pack/delta.js.map +1 -1
  160. package/dist/refs/branch.d.ts +38 -25
  161. package/dist/refs/branch.d.ts.map +1 -1
  162. package/dist/refs/branch.js +421 -94
  163. package/dist/refs/branch.js.map +1 -1
  164. package/dist/refs/storage.d.ts +77 -5
  165. package/dist/refs/storage.d.ts.map +1 -1
  166. package/dist/refs/storage.js +193 -43
  167. package/dist/refs/storage.js.map +1 -1
  168. package/dist/refs/tag.d.ts +44 -24
  169. package/dist/refs/tag.d.ts.map +1 -1
  170. package/dist/refs/tag.js +411 -70
  171. package/dist/refs/tag.js.map +1 -1
  172. package/dist/storage/backend.d.ts +425 -0
  173. package/dist/storage/backend.d.ts.map +1 -0
  174. package/dist/storage/backend.js +41 -0
  175. package/dist/storage/backend.js.map +1 -0
  176. package/dist/storage/fsx-adapter.d.ts +204 -0
  177. package/dist/storage/fsx-adapter.d.ts.map +1 -0
  178. package/dist/storage/fsx-adapter.js +518 -0
  179. package/dist/storage/fsx-adapter.js.map +1 -0
  180. package/dist/storage/r2-pack.d.ts.map +1 -1
  181. package/dist/storage/r2-pack.js +4 -1
  182. package/dist/storage/r2-pack.js.map +1 -1
  183. package/dist/tiered/cdc-pipeline.js +3 -3
  184. package/dist/tiered/cdc-pipeline.js.map +1 -1
  185. package/dist/tiered/migration.d.ts.map +1 -1
  186. package/dist/tiered/migration.js +4 -1
  187. package/dist/tiered/migration.js.map +1 -1
  188. package/dist/types/capability.d.ts +1385 -0
  189. package/dist/types/capability.d.ts.map +1 -0
  190. package/dist/types/capability.js +36 -0
  191. package/dist/types/capability.js.map +1 -0
  192. package/dist/types/index.d.ts +13 -0
  193. package/dist/types/index.d.ts.map +1 -0
  194. package/dist/types/index.js +18 -0
  195. package/dist/types/index.js.map +1 -0
  196. package/dist/types/interfaces.d.ts +673 -0
  197. package/dist/types/interfaces.d.ts.map +1 -0
  198. package/dist/types/interfaces.js +26 -0
  199. package/dist/types/interfaces.js.map +1 -0
  200. package/dist/types/objects.d.ts +182 -0
  201. package/dist/types/objects.d.ts.map +1 -1
  202. package/dist/types/objects.js +249 -4
  203. package/dist/types/objects.js.map +1 -1
  204. package/dist/types/storage.d.ts +114 -0
  205. package/dist/types/storage.d.ts.map +1 -1
  206. package/dist/types/storage.js +160 -1
  207. package/dist/types/storage.js.map +1 -1
  208. package/dist/types/worker-loader.d.ts +60 -0
  209. package/dist/types/worker-loader.d.ts.map +1 -0
  210. package/dist/types/worker-loader.js +62 -0
  211. package/dist/types/worker-loader.js.map +1 -0
  212. package/dist/utils/hash.d.ts +126 -80
  213. package/dist/utils/hash.d.ts.map +1 -1
  214. package/dist/utils/hash.js +191 -100
  215. package/dist/utils/hash.js.map +1 -1
  216. package/dist/utils/sha1.d.ts +206 -0
  217. package/dist/utils/sha1.d.ts.map +1 -1
  218. package/dist/utils/sha1.js +405 -0
  219. package/dist/utils/sha1.js.map +1 -1
  220. package/dist/wire/path-security.d.ts +157 -0
  221. package/dist/wire/path-security.d.ts.map +1 -0
  222. package/dist/wire/path-security.js +307 -0
  223. package/dist/wire/path-security.js.map +1 -0
  224. package/dist/wire/receive-pack.d.ts +7 -0
  225. package/dist/wire/receive-pack.d.ts.map +1 -1
  226. package/dist/wire/receive-pack.js +29 -1
  227. package/dist/wire/receive-pack.js.map +1 -1
  228. package/dist/wire/upload-pack.d.ts.map +1 -1
  229. package/dist/wire/upload-pack.js +4 -1
  230. package/dist/wire/upload-pack.js.map +1 -1
  231. package/package.json +10 -1
@@ -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
- void storage; // Suppress unused variable warning until implementation
106
- // TODO: Implement in GREEN phase
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(_name, _options) {
135
- // TODO: Implement in GREEN phase
136
- throw new Error('Not implemented');
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(_name, _options) {
160
- // TODO: Implement in GREEN phase
161
- throw new Error('Not implemented');
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(_oldName, _newName, _options) {
184
- // TODO: Implement in GREEN phase
185
- throw new Error('Not implemented');
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(_options) {
210
- // TODO: Implement in GREEN phase
211
- throw new Error('Not implemented');
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
- // TODO: Implement in GREEN phase
233
- throw new Error('Not implemented');
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(_name) {
253
- // TODO: Implement in GREEN phase
254
- throw new Error('Not implemented');
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(_name) {
273
- // TODO: Implement in GREEN phase
274
- throw new Error('Not implemented');
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(_branchName, _options) {
299
- // TODO: Implement in GREEN phase
300
- throw new Error('Not implemented');
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(_branchName) {
320
- // TODO: Implement in GREEN phase
321
- throw new Error('Not implemented');
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(_branchName, _into) {
341
- // TODO: Implement in GREEN phase
342
- throw new Error('Not implemented');
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(_name) {
361
- // TODO: Implement in GREEN phase
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(_name) {
393
- // TODO: Implement in GREEN phase
394
- throw new Error('Not implemented');
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(_name) {
413
- // TODO: Implement in GREEN phase
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(_name) {
432
- // TODO: Implement in GREEN phase
433
- throw new Error('Not implemented');
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(_name) {
451
- // TODO: Implement in GREEN phase
452
- throw new Error('Not implemented');
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(_storage, _name, _options) {
474
- // TODO: Implement in GREEN phase
475
- throw new Error('Not implemented');
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(_storage, _name, _options) {
493
- // TODO: Implement in GREEN phase
494
- throw new Error('Not implemented');
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(_storage, _oldName, _newName, _options) {
514
- // TODO: Implement in GREEN phase
515
- throw new Error('Not implemented');
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(_storage, _options) {
533
- // TODO: Implement in GREEN phase
534
- throw new Error('Not implemented');
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(_storage) {
551
- // TODO: Implement in GREEN phase
552
- throw new Error('Not implemented');
877
+ export async function getCurrentBranch(storage) {
878
+ const manager = new BranchManager(storage);
879
+ return manager.getCurrentBranch();
553
880
  }
554
881
  //# sourceMappingURL=branch.js.map