@gitgov/core 1.13.0 → 2.0.0

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.
@@ -0,0 +1,790 @@
1
+ import picomatch from 'picomatch';
2
+
3
+ // src/record_store/memory/memory_record_store.ts
4
+ var MemoryRecordStore = class {
5
+ data;
6
+ deepClone;
7
+ constructor(options = {}) {
8
+ this.data = options.initial ?? /* @__PURE__ */ new Map();
9
+ this.deepClone = options.deepClone ?? true;
10
+ }
11
+ clone(value) {
12
+ if (!this.deepClone) return value;
13
+ return JSON.parse(JSON.stringify(value));
14
+ }
15
+ async get(id) {
16
+ const value = this.data.get(id);
17
+ return value !== void 0 ? this.clone(value) : null;
18
+ }
19
+ async put(id, value) {
20
+ this.data.set(id, this.clone(value));
21
+ }
22
+ async delete(id) {
23
+ this.data.delete(id);
24
+ }
25
+ async list() {
26
+ return Array.from(this.data.keys());
27
+ }
28
+ async exists(id) {
29
+ return this.data.has(id);
30
+ }
31
+ // ─────────────────────────────────────────────────────────
32
+ // Test Helpers (not part of RecordStore<T>, only for tests)
33
+ // ─────────────────────────────────────────────────────────
34
+ /** Clears all records from the store */
35
+ clear() {
36
+ this.data.clear();
37
+ }
38
+ /** Returns the number of records */
39
+ size() {
40
+ return this.data.size;
41
+ }
42
+ /** Returns a copy of the internal Map (for assertions) */
43
+ getAll() {
44
+ return new Map(this.data);
45
+ }
46
+ };
47
+
48
+ // src/config_store/memory/memory_config_store.ts
49
+ var MemoryConfigStore = class {
50
+ config = null;
51
+ /**
52
+ * Load configuration from memory
53
+ *
54
+ * [EARS-A1] Returns null if no config set
55
+ * [EARS-A2] Returns config set via setConfig
56
+ * [EARS-A3] Returns config saved via saveConfig
57
+ *
58
+ * @returns GitGovConfig or null if not set
59
+ */
60
+ async loadConfig() {
61
+ return this.config;
62
+ }
63
+ /**
64
+ * Save configuration to memory
65
+ *
66
+ * [EARS-A4] Persists config in memory, accessible via getConfig()
67
+ */
68
+ async saveConfig(config) {
69
+ this.config = config;
70
+ }
71
+ // ==================== Test Helper Methods ====================
72
+ /**
73
+ * Set configuration directly (for test setup)
74
+ *
75
+ * [EARS-B1] Sets config synchronously, available via getConfig()
76
+ * [EARS-B2] Accepts null to clear config
77
+ */
78
+ setConfig(config) {
79
+ this.config = config;
80
+ }
81
+ /**
82
+ * Get current configuration (for test assertions)
83
+ */
84
+ getConfig() {
85
+ return this.config;
86
+ }
87
+ /**
88
+ * Clear all stored data (for test cleanup)
89
+ *
90
+ * [EARS-B3] Resets store to initial state (config = null)
91
+ */
92
+ clear() {
93
+ this.config = null;
94
+ }
95
+ };
96
+
97
+ // src/session_store/memory/memory_session_store.ts
98
+ var MemorySessionStore = class {
99
+ session = null;
100
+ keyFiles = [];
101
+ /**
102
+ * Load session from memory
103
+ * @returns GitGovSession or null if not set
104
+ */
105
+ async loadSession() {
106
+ return this.session;
107
+ }
108
+ /**
109
+ * Save session to memory
110
+ */
111
+ async saveSession(session) {
112
+ this.session = session;
113
+ }
114
+ /**
115
+ * Detect actor from simulated .key files
116
+ *
117
+ * In MemorySessionStore, .key files are simulated via setKeyFiles().
118
+ *
119
+ * @returns Actor ID or null if no key files configured
120
+ */
121
+ async detectActorFromKeyFiles() {
122
+ const firstKeyFile = this.keyFiles[0];
123
+ if (!firstKeyFile) {
124
+ return null;
125
+ }
126
+ return firstKeyFile.replace(".key", "");
127
+ }
128
+ // ==================== Test Helper Methods ====================
129
+ /**
130
+ * Set session directly (for test setup)
131
+ */
132
+ setSession(session) {
133
+ this.session = session;
134
+ }
135
+ /**
136
+ * Get current session (for test assertions)
137
+ */
138
+ getSession() {
139
+ return this.session;
140
+ }
141
+ /**
142
+ * Set simulated .key files (for EARS-B9 testing)
143
+ * @param keyFiles - Array of key filenames (e.g., ["human:camilo.key"])
144
+ */
145
+ setKeyFiles(keyFiles) {
146
+ this.keyFiles = keyFiles;
147
+ }
148
+ /**
149
+ * Clear all stored data (for test cleanup)
150
+ */
151
+ clear() {
152
+ this.session = null;
153
+ this.keyFiles = [];
154
+ }
155
+ };
156
+
157
+ // src/key_provider/key_provider.ts
158
+ var KeyProviderError = class extends Error {
159
+ constructor(message, code, actorId) {
160
+ super(message);
161
+ this.code = code;
162
+ this.actorId = actorId;
163
+ this.name = "KeyProviderError";
164
+ }
165
+ };
166
+
167
+ // src/key_provider/memory/env_key_provider.ts
168
+ var EnvKeyProvider = class {
169
+ prefix;
170
+ env;
171
+ allowWrites;
172
+ constructor(options = {}) {
173
+ this.prefix = options.prefix ?? "GITGOV_KEY_";
174
+ this.env = options.env ?? process.env;
175
+ this.allowWrites = options.allowWrites ?? options.env !== void 0;
176
+ }
177
+ /**
178
+ * [EARS-KP01] Retrieves the private key from environment variable.
179
+ * [EARS-EKP01] Reads from {prefix}{SANITIZED_ACTOR_ID}.
180
+ * [EARS-EKP07] Returns null for empty or whitespace-only value.
181
+ * [EARS-EKP08] Trims whitespace from value.
182
+ */
183
+ async getPrivateKey(actorId) {
184
+ const varName = this.getEnvVarName(actorId);
185
+ const value = this.env[varName];
186
+ if (!value || value.trim() === "") {
187
+ return null;
188
+ }
189
+ return value.trim();
190
+ }
191
+ /**
192
+ * [EARS-KP03] Stores a private key in the environment object.
193
+ * [EARS-EKP02] Sets env var in custom env object.
194
+ * [EARS-EKP03] Throws KEY_WRITE_ERROR when writing to process.env.
195
+ */
196
+ async setPrivateKey(actorId, privateKey) {
197
+ if (!this.allowWrites) {
198
+ throw new KeyProviderError(
199
+ "Cannot write to environment variables in read-only mode. Use a custom env object with allowWrites: true for writable storage.",
200
+ "KEY_WRITE_ERROR",
201
+ actorId
202
+ );
203
+ }
204
+ const varName = this.getEnvVarName(actorId);
205
+ this.env[varName] = privateKey;
206
+ }
207
+ /**
208
+ * Checks if a private key exists in environment variables.
209
+ */
210
+ async hasPrivateKey(actorId) {
211
+ const varName = this.getEnvVarName(actorId);
212
+ const value = this.env[varName];
213
+ return value !== void 0 && value.trim() !== "";
214
+ }
215
+ /**
216
+ * [EARS-KP04] Deletes the private key from environment object.
217
+ * [EARS-EKP10] Throws KEY_DELETE_ERROR in read-only mode.
218
+ */
219
+ async deletePrivateKey(actorId) {
220
+ if (!this.allowWrites) {
221
+ throw new KeyProviderError(
222
+ "Cannot delete environment variables in read-only mode.",
223
+ "KEY_DELETE_ERROR",
224
+ actorId
225
+ );
226
+ }
227
+ const varName = this.getEnvVarName(actorId);
228
+ const existed = this.env[varName] !== void 0;
229
+ delete this.env[varName];
230
+ return existed;
231
+ }
232
+ /**
233
+ * [EARS-EKP04] Builds environment variable name from actorId.
234
+ * [EARS-EKP05] Converts to UPPERCASE.
235
+ * [EARS-EKP06] Collapses multiple underscores.
236
+ * [EARS-EKP11] Throws INVALID_ACTOR_ID if empty after sanitization.
237
+ */
238
+ getEnvVarName(actorId) {
239
+ const sanitized = actorId.toUpperCase().replace(/[^A-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
240
+ if (!sanitized) {
241
+ throw new KeyProviderError(
242
+ "Invalid actorId: empty after sanitization",
243
+ "INVALID_ACTOR_ID",
244
+ actorId
245
+ );
246
+ }
247
+ return `${this.prefix}${sanitized}`;
248
+ }
249
+ };
250
+
251
+ // src/key_provider/memory/mock_key_provider.ts
252
+ var MockKeyProvider = class {
253
+ keys;
254
+ constructor(options = {}) {
255
+ if (options.keys instanceof Map) {
256
+ this.keys = new Map(options.keys);
257
+ } else if (options.keys) {
258
+ this.keys = new Map(Object.entries(options.keys));
259
+ } else {
260
+ this.keys = /* @__PURE__ */ new Map();
261
+ }
262
+ }
263
+ /**
264
+ * [EARS-KP01] Retrieves the private key for an actor.
265
+ */
266
+ async getPrivateKey(actorId) {
267
+ return this.keys.get(actorId) ?? null;
268
+ }
269
+ /**
270
+ * [EARS-KP03] Stores a private key for an actor.
271
+ * [EARS-MKP03] Overwrites existing key if present.
272
+ */
273
+ async setPrivateKey(actorId, privateKey) {
274
+ this.keys.set(actorId, privateKey);
275
+ }
276
+ /**
277
+ * [EARS-MKP07] Checks if a private key exists for an actor.
278
+ */
279
+ async hasPrivateKey(actorId) {
280
+ return this.keys.has(actorId);
281
+ }
282
+ /**
283
+ * [EARS-KP04] Deletes the private key for an actor.
284
+ */
285
+ async deletePrivateKey(actorId) {
286
+ return this.keys.delete(actorId);
287
+ }
288
+ /**
289
+ * [EARS-MKP04] Returns the number of stored keys (useful for testing).
290
+ */
291
+ size() {
292
+ return this.keys.size;
293
+ }
294
+ /**
295
+ * [EARS-MKP05] Clears all stored keys (useful for test cleanup).
296
+ */
297
+ clear() {
298
+ this.keys.clear();
299
+ }
300
+ /**
301
+ * [EARS-MKP06] Returns all stored actor IDs (useful for testing).
302
+ */
303
+ listActorIds() {
304
+ return Array.from(this.keys.keys());
305
+ }
306
+ };
307
+
308
+ // src/file_lister/file_lister.errors.ts
309
+ var FileListerError = class extends Error {
310
+ constructor(message, code, filePath) {
311
+ super(message);
312
+ this.code = code;
313
+ this.filePath = filePath;
314
+ this.name = "FileListerError";
315
+ }
316
+ };
317
+
318
+ // src/file_lister/memory/memory_file_lister.ts
319
+ function matchPatterns(patterns, filePaths) {
320
+ const isMatch = picomatch(patterns);
321
+ return filePaths.filter((filePath) => isMatch(filePath));
322
+ }
323
+ function filterIgnored(filePaths, ignorePatterns) {
324
+ if (!ignorePatterns.length) return filePaths;
325
+ const isIgnored = picomatch(ignorePatterns);
326
+ return filePaths.filter((filePath) => !isIgnored(filePath));
327
+ }
328
+ var MemoryFileLister = class {
329
+ files;
330
+ stats;
331
+ /**
332
+ * [EARS-MFL01] Constructs MemoryFileLister with provided files.
333
+ */
334
+ constructor(options = {}) {
335
+ if (options.files instanceof Map) {
336
+ this.files = new Map(options.files);
337
+ } else if (options.files) {
338
+ this.files = new Map(Object.entries(options.files));
339
+ } else {
340
+ this.files = /* @__PURE__ */ new Map();
341
+ }
342
+ this.stats = options.stats ?? /* @__PURE__ */ new Map();
343
+ }
344
+ /**
345
+ * [EARS-FL01] Lists files matching glob patterns.
346
+ * [EARS-MFL02] Filters files using glob patterns.
347
+ */
348
+ async list(patterns, options) {
349
+ const allPaths = Array.from(this.files.keys());
350
+ let matched = matchPatterns(patterns, allPaths);
351
+ if (options?.ignore?.length) {
352
+ matched = filterIgnored(matched, options.ignore);
353
+ }
354
+ return matched.sort();
355
+ }
356
+ /**
357
+ * [EARS-FL02] Checks if a file exists.
358
+ */
359
+ async exists(filePath) {
360
+ return this.files.has(filePath);
361
+ }
362
+ /**
363
+ * [EARS-FL03] Reads file content as string.
364
+ */
365
+ async read(filePath) {
366
+ const content = this.files.get(filePath);
367
+ if (content === void 0) {
368
+ throw new FileListerError(
369
+ `File not found: ${filePath}`,
370
+ "FILE_NOT_FOUND",
371
+ filePath
372
+ );
373
+ }
374
+ return content;
375
+ }
376
+ /**
377
+ * [EARS-FL04] Gets file statistics.
378
+ * [EARS-MFL03] Generates stats from content if not explicitly provided.
379
+ */
380
+ async stat(filePath) {
381
+ if (!this.files.has(filePath)) {
382
+ throw new FileListerError(
383
+ `File not found: ${filePath}`,
384
+ "FILE_NOT_FOUND",
385
+ filePath
386
+ );
387
+ }
388
+ const explicitStats = this.stats.get(filePath);
389
+ if (explicitStats) {
390
+ return explicitStats;
391
+ }
392
+ const content = this.files.get(filePath);
393
+ return {
394
+ size: content.length,
395
+ mtime: Date.now(),
396
+ isFile: true
397
+ };
398
+ }
399
+ // ============================================
400
+ // Testing utilities
401
+ // ============================================
402
+ /**
403
+ * [EARS-MFL04] Adds a file to the mock filesystem.
404
+ */
405
+ addFile(filePath, content) {
406
+ this.files.set(filePath, content);
407
+ }
408
+ /**
409
+ * Removes a file from the mock filesystem.
410
+ */
411
+ removeFile(filePath) {
412
+ this.stats.delete(filePath);
413
+ return this.files.delete(filePath);
414
+ }
415
+ /**
416
+ * Returns the number of files.
417
+ */
418
+ size() {
419
+ return this.files.size;
420
+ }
421
+ /**
422
+ * Clears all files.
423
+ */
424
+ clear() {
425
+ this.files.clear();
426
+ this.stats.clear();
427
+ }
428
+ /**
429
+ * Returns all file paths.
430
+ */
431
+ listPaths() {
432
+ return Array.from(this.files.keys());
433
+ }
434
+ };
435
+
436
+ // src/git/errors.ts
437
+ var GitError = class _GitError extends Error {
438
+ constructor(message) {
439
+ super(message);
440
+ this.name = "GitError";
441
+ Object.setPrototypeOf(this, _GitError.prototype);
442
+ }
443
+ };
444
+ var BranchNotFoundError = class _BranchNotFoundError extends GitError {
445
+ branchName;
446
+ constructor(branchName) {
447
+ super(`Branch not found: ${branchName}`);
448
+ this.name = "BranchNotFoundError";
449
+ this.branchName = branchName;
450
+ Object.setPrototypeOf(this, _BranchNotFoundError.prototype);
451
+ }
452
+ };
453
+ var FileNotFoundError = class _FileNotFoundError extends GitError {
454
+ filePath;
455
+ commitHash;
456
+ constructor(filePath, commitHash) {
457
+ super(`File not found: ${filePath} in commit ${commitHash}`);
458
+ this.name = "FileNotFoundError";
459
+ this.filePath = filePath;
460
+ this.commitHash = commitHash;
461
+ Object.setPrototypeOf(this, _FileNotFoundError.prototype);
462
+ }
463
+ };
464
+ var RebaseNotInProgressError = class _RebaseNotInProgressError extends GitError {
465
+ constructor() {
466
+ super("No rebase in progress");
467
+ this.name = "RebaseNotInProgressError";
468
+ Object.setPrototypeOf(this, _RebaseNotInProgressError.prototype);
469
+ }
470
+ };
471
+ var BranchAlreadyExistsError = class _BranchAlreadyExistsError extends GitError {
472
+ branchName;
473
+ constructor(branchName) {
474
+ super(`Branch already exists: ${branchName}`);
475
+ this.name = "BranchAlreadyExistsError";
476
+ this.branchName = branchName;
477
+ Object.setPrototypeOf(this, _BranchAlreadyExistsError.prototype);
478
+ }
479
+ };
480
+
481
+ // src/git/memory/memory_git_module.ts
482
+ var MemoryGitModule = class {
483
+ state;
484
+ constructor(repoRoot = "/test/repo") {
485
+ this.state = {
486
+ repoRoot,
487
+ currentBranch: "main",
488
+ branches: /* @__PURE__ */ new Set(["main"]),
489
+ commits: [],
490
+ stagedFiles: [],
491
+ files: /* @__PURE__ */ new Map(),
492
+ isRebaseInProgress: false,
493
+ conflictedFiles: [],
494
+ remotes: /* @__PURE__ */ new Map([["origin", ["main"]]]),
495
+ stashes: [],
496
+ config: /* @__PURE__ */ new Map()
497
+ };
498
+ }
499
+ // ═══════════════════════════════════════════════════════════════════════
500
+ // TEST HELPERS
501
+ // ═══════════════════════════════════════════════════════════════════════
502
+ setBranch(name) {
503
+ this.state.currentBranch = name;
504
+ this.state.branches.add(name);
505
+ }
506
+ setBranches(names) {
507
+ this.state.branches = new Set(names);
508
+ if (!this.state.branches.has(this.state.currentBranch)) {
509
+ this.state.currentBranch = names[0] || "main";
510
+ }
511
+ }
512
+ setCommits(commits) {
513
+ this.state.commits = commits.map((c) => ({
514
+ ...c,
515
+ files: /* @__PURE__ */ new Map(),
516
+ branch: this.state.currentBranch
517
+ }));
518
+ }
519
+ setFiles(files) {
520
+ this.state.files = new Map(Object.entries(files));
521
+ }
522
+ setFileContent(commitHash, filePath, content) {
523
+ const commit = this.state.commits.find((c) => c.hash === commitHash);
524
+ if (commit) {
525
+ commit.files.set(filePath, content);
526
+ }
527
+ this.state.files.set(filePath, content);
528
+ }
529
+ setStagedFiles(files) {
530
+ this.state.stagedFiles = files;
531
+ }
532
+ setRebaseInProgress(inProgress, conflictedFiles = []) {
533
+ this.state.isRebaseInProgress = inProgress;
534
+ this.state.conflictedFiles = conflictedFiles;
535
+ }
536
+ setRemoteBranches(remote, branches) {
537
+ this.state.remotes.set(remote, branches);
538
+ }
539
+ clear() {
540
+ this.state = {
541
+ repoRoot: this.state.repoRoot,
542
+ currentBranch: "main",
543
+ branches: /* @__PURE__ */ new Set(["main"]),
544
+ commits: [],
545
+ stagedFiles: [],
546
+ files: /* @__PURE__ */ new Map(),
547
+ isRebaseInProgress: false,
548
+ conflictedFiles: [],
549
+ remotes: /* @__PURE__ */ new Map([["origin", ["main"]]]),
550
+ stashes: [],
551
+ config: /* @__PURE__ */ new Map()
552
+ };
553
+ }
554
+ // ═══════════════════════════════════════════════════════════════════════
555
+ // IGitModule IMPLEMENTATION
556
+ // ═══════════════════════════════════════════════════════════════════════
557
+ async exec(_command, _args, _options) {
558
+ return { exitCode: 0, stdout: "", stderr: "" };
559
+ }
560
+ async init() {
561
+ this.state.branches.add("main");
562
+ this.state.currentBranch = "main";
563
+ }
564
+ async getRepoRoot() {
565
+ return this.state.repoRoot;
566
+ }
567
+ async getCurrentBranch() {
568
+ return this.state.currentBranch;
569
+ }
570
+ async getCommitHash(ref = "HEAD") {
571
+ if (ref === "HEAD") {
572
+ const lastCommit = this.state.commits[this.state.commits.length - 1];
573
+ return lastCommit?.hash || "abc123def456";
574
+ }
575
+ const commit = this.state.commits.find((c) => c.hash.startsWith(ref));
576
+ return commit?.hash || ref;
577
+ }
578
+ async setConfig(key, value, _scope) {
579
+ this.state.config.set(key, value);
580
+ }
581
+ async getMergeBase(branchA, branchB) {
582
+ if (!this.state.branches.has(branchA)) {
583
+ throw new BranchNotFoundError(branchA);
584
+ }
585
+ if (!this.state.branches.has(branchB)) {
586
+ throw new BranchNotFoundError(branchB);
587
+ }
588
+ return this.state.commits[0]?.hash || "merge-base-hash";
589
+ }
590
+ async getChangedFiles(_fromCommit, _toCommit, _pathFilter) {
591
+ return [];
592
+ }
593
+ async getStagedFiles() {
594
+ return this.state.stagedFiles;
595
+ }
596
+ async getFileContent(commitHash, filePath) {
597
+ const commit = this.state.commits.find((c) => c.hash === commitHash);
598
+ if (commit?.files.has(filePath)) {
599
+ return commit.files.get(filePath);
600
+ }
601
+ if (this.state.files.has(filePath)) {
602
+ return this.state.files.get(filePath);
603
+ }
604
+ throw new FileNotFoundError(filePath, commitHash);
605
+ }
606
+ async getCommitHistory(_branch, options) {
607
+ let commits = [...this.state.commits];
608
+ if (options?.maxCount) {
609
+ commits = commits.slice(0, options.maxCount);
610
+ }
611
+ return commits.map((c) => ({
612
+ hash: c.hash,
613
+ message: c.message,
614
+ author: c.author,
615
+ date: c.date
616
+ }));
617
+ }
618
+ async getCommitHistoryRange(fromHash, toHash, options) {
619
+ const fromIndex = this.state.commits.findIndex((c) => c.hash === fromHash);
620
+ const toIndex = this.state.commits.findIndex((c) => c.hash === toHash);
621
+ let commits = fromIndex >= 0 && toIndex >= 0 ? this.state.commits.slice(fromIndex + 1, toIndex + 1) : [];
622
+ if (options?.maxCount) {
623
+ commits = commits.slice(0, options.maxCount);
624
+ }
625
+ return commits.map((c) => ({
626
+ hash: c.hash,
627
+ message: c.message,
628
+ author: c.author,
629
+ date: c.date
630
+ }));
631
+ }
632
+ async getCommitMessage(commitHash) {
633
+ const commit = this.state.commits.find((c) => c.hash === commitHash);
634
+ return commit?.message || "";
635
+ }
636
+ async hasUncommittedChanges(_pathFilter) {
637
+ return this.state.stagedFiles.length > 0;
638
+ }
639
+ async isRebaseInProgress() {
640
+ return this.state.isRebaseInProgress;
641
+ }
642
+ async branchExists(branchName) {
643
+ return this.state.branches.has(branchName);
644
+ }
645
+ async listRemoteBranches(remoteName) {
646
+ return this.state.remotes.get(remoteName) || [];
647
+ }
648
+ async isRemoteConfigured(remoteName) {
649
+ return this.state.remotes.has(remoteName);
650
+ }
651
+ async getBranchRemote(branchName) {
652
+ if (!this.state.branches.has(branchName)) {
653
+ throw new BranchNotFoundError(branchName);
654
+ }
655
+ for (const [remote, branches] of this.state.remotes) {
656
+ if (branches.includes(branchName)) {
657
+ return remote;
658
+ }
659
+ }
660
+ return null;
661
+ }
662
+ async getConflictedFiles() {
663
+ return this.state.conflictedFiles;
664
+ }
665
+ async checkoutBranch(branchName) {
666
+ if (!this.state.branches.has(branchName)) {
667
+ throw new BranchNotFoundError(branchName);
668
+ }
669
+ this.state.currentBranch = branchName;
670
+ }
671
+ async stash(message) {
672
+ if (this.state.stagedFiles.length === 0) {
673
+ return null;
674
+ }
675
+ const hash = `stash-${Date.now()}`;
676
+ this.state.stashes.push({
677
+ hash,
678
+ message: message || "WIP",
679
+ files: new Map(this.state.files)
680
+ });
681
+ this.state.stagedFiles = [];
682
+ return hash;
683
+ }
684
+ async stashPop() {
685
+ const stash = this.state.stashes.pop();
686
+ if (!stash) {
687
+ return false;
688
+ }
689
+ for (const [path, content] of stash.files) {
690
+ this.state.files.set(path, content);
691
+ }
692
+ return true;
693
+ }
694
+ async stashDrop(_stashHash) {
695
+ this.state.stashes.pop();
696
+ }
697
+ async checkoutOrphanBranch(branchName) {
698
+ this.state.branches.add(branchName);
699
+ this.state.currentBranch = branchName;
700
+ }
701
+ async fetch(_remote) {
702
+ }
703
+ async pull(_remote, _branchName) {
704
+ }
705
+ async pullRebase(_remote, _branchName) {
706
+ }
707
+ async resetHard(_target) {
708
+ this.state.stagedFiles = [];
709
+ }
710
+ async checkoutFilesFromBranch(sourceBranch, _filePaths) {
711
+ if (!this.state.branches.has(sourceBranch)) {
712
+ throw new BranchNotFoundError(sourceBranch);
713
+ }
714
+ }
715
+ async add(filePaths, _options) {
716
+ for (const path of filePaths) {
717
+ if (!this.state.stagedFiles.includes(path)) {
718
+ this.state.stagedFiles.push(path);
719
+ }
720
+ }
721
+ }
722
+ async rm(filePaths) {
723
+ for (const path of filePaths) {
724
+ this.state.files.delete(path);
725
+ this.state.stagedFiles = this.state.stagedFiles.filter((f) => f !== path);
726
+ }
727
+ }
728
+ async commit(message, author) {
729
+ const hash = `commit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
730
+ this.state.commits.push({
731
+ hash,
732
+ message,
733
+ author: author ? `${author.name} <${author.email}>` : "Test User <test@example.com>",
734
+ date: (/* @__PURE__ */ new Date()).toISOString(),
735
+ files: new Map(this.state.files),
736
+ branch: this.state.currentBranch
737
+ });
738
+ this.state.stagedFiles = [];
739
+ return hash;
740
+ }
741
+ async commitAllowEmpty(message, author) {
742
+ return this.commit(message, author);
743
+ }
744
+ async push(_remote, branchName) {
745
+ const branches = this.state.remotes.get("origin") || [];
746
+ if (!branches.includes(branchName)) {
747
+ branches.push(branchName);
748
+ this.state.remotes.set("origin", branches);
749
+ }
750
+ }
751
+ async pushWithUpstream(_remote, branchName) {
752
+ return this.push("origin", branchName);
753
+ }
754
+ async setUpstream(branchName, _remote, _remoteBranch) {
755
+ if (!this.state.branches.has(branchName)) {
756
+ throw new BranchNotFoundError(branchName);
757
+ }
758
+ }
759
+ async rebaseContinue() {
760
+ if (!this.state.isRebaseInProgress) {
761
+ throw new RebaseNotInProgressError();
762
+ }
763
+ this.state.isRebaseInProgress = false;
764
+ this.state.conflictedFiles = [];
765
+ return this.state.commits[this.state.commits.length - 1]?.hash || "rebase-hash";
766
+ }
767
+ async rebaseAbort() {
768
+ if (!this.state.isRebaseInProgress) {
769
+ throw new RebaseNotInProgressError();
770
+ }
771
+ this.state.isRebaseInProgress = false;
772
+ this.state.conflictedFiles = [];
773
+ }
774
+ async createBranch(branchName, _startPoint) {
775
+ if (this.state.branches.has(branchName)) {
776
+ throw new BranchAlreadyExistsError(branchName);
777
+ }
778
+ this.state.branches.add(branchName);
779
+ this.state.currentBranch = branchName;
780
+ }
781
+ async rebase(_targetBranch) {
782
+ if (this.state.conflictedFiles.length > 0) {
783
+ this.state.isRebaseInProgress = true;
784
+ }
785
+ }
786
+ };
787
+
788
+ export { EnvKeyProvider, MemoryConfigStore, MemoryFileLister, MemoryGitModule, MemoryRecordStore, MemorySessionStore, MockKeyProvider };
789
+ //# sourceMappingURL=memory.js.map
790
+ //# sourceMappingURL=memory.js.map