memory-git 1.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.
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/index.d.ts +376 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1024 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MemoryGit = void 0;
|
|
7
|
+
const isomorphic_git_1 = __importDefault(require("isomorphic-git"));
|
|
8
|
+
const node_1 = __importDefault(require("isomorphic-git/http/node"));
|
|
9
|
+
const memfs_1 = require("memfs");
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
/**
|
|
13
|
+
* MemoryGit - In-memory Git implementation
|
|
14
|
+
*
|
|
15
|
+
* Loads the project into memory, executes all git operations in memory,
|
|
16
|
+
* and syncs to disk only when flush() is called.
|
|
17
|
+
*
|
|
18
|
+
* All real disk operations use async versions to not block the Node.js event loop.
|
|
19
|
+
*/
|
|
20
|
+
class MemoryGit {
|
|
21
|
+
/** Instance name */
|
|
22
|
+
name;
|
|
23
|
+
/** Memory filesystem */
|
|
24
|
+
fs;
|
|
25
|
+
/** Volume instance */
|
|
26
|
+
vol;
|
|
27
|
+
/** In-memory repository directory */
|
|
28
|
+
dir = '/repo';
|
|
29
|
+
/** Real disk directory (if loaded from disk) */
|
|
30
|
+
realDir = null;
|
|
31
|
+
/** Whether the repository is initialized */
|
|
32
|
+
isInitialized = false;
|
|
33
|
+
/** Author information for commits */
|
|
34
|
+
author = { name: 'Memory Git', email: 'memory@git.local' };
|
|
35
|
+
operations = [];
|
|
36
|
+
_stash = [];
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new MemoryGit instance
|
|
39
|
+
* @param name - Unique name to identify the instance
|
|
40
|
+
*/
|
|
41
|
+
constructor(name = 'memory-git') {
|
|
42
|
+
this.name = name;
|
|
43
|
+
this.fs = memfs_1.fs;
|
|
44
|
+
this.vol = memfs_1.vol;
|
|
45
|
+
// Clear volume to ensure clean state
|
|
46
|
+
this.vol.reset();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Logs an operation
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
_logOperation(operation, params, result = null, error = null) {
|
|
53
|
+
const entry = {
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
operation,
|
|
56
|
+
params: this._sanitizeParams(params),
|
|
57
|
+
success: error === null,
|
|
58
|
+
result: result,
|
|
59
|
+
error: error ? error.message : null
|
|
60
|
+
};
|
|
61
|
+
this.operations.push(entry);
|
|
62
|
+
return entry;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes large data from params for logging
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
_sanitizeParams(params) {
|
|
69
|
+
const sanitized = { ...params };
|
|
70
|
+
if (sanitized.content && typeof sanitized.content === 'string' && sanitized.content.length > 100) {
|
|
71
|
+
sanitized.content = `[${sanitized.content.length} bytes]`;
|
|
72
|
+
}
|
|
73
|
+
if (Buffer.isBuffer(sanitized.content)) {
|
|
74
|
+
sanitized.content = `[Buffer: ${sanitized.content.length} bytes]`;
|
|
75
|
+
}
|
|
76
|
+
return sanitized;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Checks if a path exists on real disk (async)
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
async _realPathExists(filepath) {
|
|
83
|
+
try {
|
|
84
|
+
await fs_1.promises.access(filepath);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Sets the author for commits
|
|
93
|
+
* @param name - Author name
|
|
94
|
+
* @param email - Author email
|
|
95
|
+
*/
|
|
96
|
+
setAuthor(name, email) {
|
|
97
|
+
this.author = { name, email };
|
|
98
|
+
this._logOperation('setAuthor', { name, email });
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Initializes a new repository in memory
|
|
102
|
+
*/
|
|
103
|
+
async init() {
|
|
104
|
+
try {
|
|
105
|
+
this.fs.mkdirSync(this.dir, { recursive: true });
|
|
106
|
+
await isomorphic_git_1.default.init({ fs: this.fs, dir: this.dir, defaultBranch: 'main' });
|
|
107
|
+
this.isInitialized = true;
|
|
108
|
+
this._logOperation('init', { dir: this.dir }, { success: true });
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this._logOperation('init', { dir: this.dir }, null, error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Loads an existing repository from disk to memory
|
|
118
|
+
* @param sourcePath - Path to the repository on disk
|
|
119
|
+
* @param options - Loading options
|
|
120
|
+
* @returns Number of files loaded
|
|
121
|
+
*/
|
|
122
|
+
async loadFromDisk(sourcePath, options = {}) {
|
|
123
|
+
try {
|
|
124
|
+
this.realDir = path_1.default.resolve(sourcePath);
|
|
125
|
+
const ignore = options.ignore || ['node_modules', '.pnpm-store'];
|
|
126
|
+
// Create base directory in memory
|
|
127
|
+
this.fs.mkdirSync(this.dir, { recursive: true });
|
|
128
|
+
// Copy recursively from disk to memory (async)
|
|
129
|
+
const fileCount = await this._copyToMemoryAsync(this.realDir, this.dir, ignore);
|
|
130
|
+
this.isInitialized = true;
|
|
131
|
+
this._logOperation('loadFromDisk', { sourcePath: this.realDir, ignore }, {
|
|
132
|
+
success: true,
|
|
133
|
+
filesLoaded: fileCount
|
|
134
|
+
});
|
|
135
|
+
return fileCount;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
this._logOperation('loadFromDisk', { sourcePath }, null, error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Copies files from real disk to memory filesystem (async)
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
async _copyToMemoryAsync(realPath, memoryPath, ignore = []) {
|
|
147
|
+
const entries = await fs_1.promises.readdir(realPath, { withFileTypes: true });
|
|
148
|
+
// Process entries in parallel for better performance
|
|
149
|
+
const promises = entries.map(async (entry) => {
|
|
150
|
+
// Check if should ignore
|
|
151
|
+
if (ignore.includes(entry.name))
|
|
152
|
+
return 0;
|
|
153
|
+
const realEntryPath = path_1.default.join(realPath, entry.name);
|
|
154
|
+
const memoryEntryPath = path_1.default.posix.join(memoryPath, entry.name);
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
this.fs.mkdirSync(memoryEntryPath, { recursive: true });
|
|
157
|
+
return await this._copyToMemoryAsync(realEntryPath, memoryEntryPath, ignore);
|
|
158
|
+
}
|
|
159
|
+
else if (entry.isFile()) {
|
|
160
|
+
const content = await fs_1.promises.readFile(realEntryPath);
|
|
161
|
+
this.fs.writeFileSync(memoryEntryPath, content);
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
return 0;
|
|
165
|
+
});
|
|
166
|
+
const results = await Promise.all(promises);
|
|
167
|
+
return results.reduce((acc, val) => acc + val, 0);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Counts files in a directory in memory
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
_countFiles(dir) {
|
|
174
|
+
let count = 0;
|
|
175
|
+
const entries = this.fs.readdirSync(dir);
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const fullPath = path_1.default.posix.join(dir, entry);
|
|
178
|
+
const stat = this.fs.statSync(fullPath);
|
|
179
|
+
if (stat.isDirectory()) {
|
|
180
|
+
count += this._countFiles(fullPath);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
count++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return count;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Writes a file to the in-memory repository
|
|
190
|
+
* @param filepath - Relative file path
|
|
191
|
+
* @param content - File content
|
|
192
|
+
*/
|
|
193
|
+
async writeFile(filepath, content) {
|
|
194
|
+
try {
|
|
195
|
+
const fullPath = path_1.default.posix.join(this.dir, filepath);
|
|
196
|
+
const dir = path_1.default.posix.dirname(fullPath);
|
|
197
|
+
// Create directories if needed
|
|
198
|
+
this.fs.mkdirSync(dir, { recursive: true });
|
|
199
|
+
this.fs.writeFileSync(fullPath, content);
|
|
200
|
+
this._logOperation('writeFile', { filepath, content }, { success: true });
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this._logOperation('writeFile', { filepath }, null, error);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Reads a file from the in-memory repository
|
|
210
|
+
* @param filepath - Relative file path
|
|
211
|
+
* @returns File content
|
|
212
|
+
*/
|
|
213
|
+
async readFile(filepath) {
|
|
214
|
+
try {
|
|
215
|
+
const fullPath = path_1.default.posix.join(this.dir, filepath);
|
|
216
|
+
const content = this.fs.readFileSync(fullPath, 'utf8');
|
|
217
|
+
this._logOperation('readFile', { filepath }, { success: true, size: content.length });
|
|
218
|
+
return content;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
this._logOperation('readFile', { filepath }, null, error);
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Checks if a file exists
|
|
227
|
+
* @param filepath - Relative file path
|
|
228
|
+
*/
|
|
229
|
+
async fileExists(filepath) {
|
|
230
|
+
try {
|
|
231
|
+
const fullPath = path_1.default.posix.join(this.dir, filepath);
|
|
232
|
+
return this.fs.existsSync(fullPath);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Deletes a file from the in-memory repository
|
|
240
|
+
* @param filepath - Relative file path
|
|
241
|
+
*/
|
|
242
|
+
async deleteFile(filepath) {
|
|
243
|
+
try {
|
|
244
|
+
const fullPath = path_1.default.posix.join(this.dir, filepath);
|
|
245
|
+
this.fs.unlinkSync(fullPath);
|
|
246
|
+
this._logOperation('deleteFile', { filepath }, { success: true });
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this._logOperation('deleteFile', { filepath }, null, error);
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Adds file(s) to the staging area
|
|
256
|
+
* @param filepath - Relative file path(s)
|
|
257
|
+
*/
|
|
258
|
+
async add(filepath) {
|
|
259
|
+
try {
|
|
260
|
+
const files = Array.isArray(filepath) ? filepath : [filepath];
|
|
261
|
+
for (const file of files) {
|
|
262
|
+
await isomorphic_git_1.default.add({ fs: this.fs, dir: this.dir, filepath: file });
|
|
263
|
+
}
|
|
264
|
+
this._logOperation('add', { filepath: files }, { success: true });
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
this._logOperation('add', { filepath }, null, error);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Removes file(s) from the staging area and working tree
|
|
274
|
+
* @param filepath - Relative file path
|
|
275
|
+
*/
|
|
276
|
+
async remove(filepath) {
|
|
277
|
+
try {
|
|
278
|
+
await isomorphic_git_1.default.remove({ fs: this.fs, dir: this.dir, filepath });
|
|
279
|
+
this._logOperation('remove', { filepath }, { success: true });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
this._logOperation('remove', { filepath }, null, error);
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Creates a commit with staged changes
|
|
289
|
+
* @param message - Commit message
|
|
290
|
+
* @returns SHA of the created commit
|
|
291
|
+
*/
|
|
292
|
+
async commit(message) {
|
|
293
|
+
try {
|
|
294
|
+
const sha = await isomorphic_git_1.default.commit({
|
|
295
|
+
fs: this.fs,
|
|
296
|
+
dir: this.dir,
|
|
297
|
+
message,
|
|
298
|
+
author: this.author
|
|
299
|
+
});
|
|
300
|
+
this._logOperation('commit', { message }, { success: true, sha });
|
|
301
|
+
return sha;
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
this._logOperation('commit', { message }, null, error);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Gets repository status
|
|
310
|
+
* @returns List of files with their status
|
|
311
|
+
*/
|
|
312
|
+
async status() {
|
|
313
|
+
try {
|
|
314
|
+
const statusMatrix = await isomorphic_git_1.default.statusMatrix({ fs: this.fs, dir: this.dir });
|
|
315
|
+
const result = statusMatrix.map(([filepath, head, workdir, stage]) => ({
|
|
316
|
+
filepath: filepath,
|
|
317
|
+
head: head,
|
|
318
|
+
workdir: workdir,
|
|
319
|
+
stage: stage,
|
|
320
|
+
status: this._getStatusText(head, workdir, stage)
|
|
321
|
+
}));
|
|
322
|
+
this._logOperation('status', {}, { success: true, files: result.length });
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
this._logOperation('status', {}, null, error);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Converts numeric status to readable text
|
|
332
|
+
* @private
|
|
333
|
+
*/
|
|
334
|
+
_getStatusText(head, workdir, stage) {
|
|
335
|
+
if (head === 0 && workdir === 2 && stage === 0)
|
|
336
|
+
return 'new, untracked';
|
|
337
|
+
if (head === 0 && workdir === 2 && stage === 2)
|
|
338
|
+
return 'added, staged';
|
|
339
|
+
if (head === 0 && workdir === 2 && stage === 3)
|
|
340
|
+
return 'added, staged, with unstaged changes';
|
|
341
|
+
if (head === 1 && workdir === 1 && stage === 1)
|
|
342
|
+
return 'unmodified';
|
|
343
|
+
if (head === 1 && workdir === 2 && stage === 1)
|
|
344
|
+
return 'modified, unstaged';
|
|
345
|
+
if (head === 1 && workdir === 2 && stage === 2)
|
|
346
|
+
return 'modified, staged';
|
|
347
|
+
if (head === 1 && workdir === 2 && stage === 3)
|
|
348
|
+
return 'modified, staged, with unstaged changes';
|
|
349
|
+
if (head === 1 && workdir === 0 && stage === 0)
|
|
350
|
+
return 'deleted, unstaged';
|
|
351
|
+
if (head === 1 && workdir === 0 && stage === 1)
|
|
352
|
+
return 'deleted, staged';
|
|
353
|
+
if (head === 1 && workdir === 1 && stage === 0)
|
|
354
|
+
return 'deleted, staged';
|
|
355
|
+
return `unknown (${head}, ${workdir}, ${stage})`;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Gets commit log
|
|
359
|
+
* @param depth - Number of commits to return
|
|
360
|
+
* @returns List of commits
|
|
361
|
+
*/
|
|
362
|
+
async log(depth = 10) {
|
|
363
|
+
try {
|
|
364
|
+
const commits = await isomorphic_git_1.default.log({ fs: this.fs, dir: this.dir, depth });
|
|
365
|
+
const result = commits.map(commit => ({
|
|
366
|
+
sha: commit.oid,
|
|
367
|
+
message: commit.commit.message,
|
|
368
|
+
author: commit.commit.author.name,
|
|
369
|
+
email: commit.commit.author.email,
|
|
370
|
+
timestamp: new Date(commit.commit.author.timestamp * 1000).toISOString()
|
|
371
|
+
}));
|
|
372
|
+
this._logOperation('log', { depth }, { success: true, commits: result.length });
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
this._logOperation('log', { depth }, null, error);
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Creates a new branch
|
|
382
|
+
* @param branchName - Branch name
|
|
383
|
+
*/
|
|
384
|
+
async createBranch(branchName) {
|
|
385
|
+
try {
|
|
386
|
+
await isomorphic_git_1.default.branch({ fs: this.fs, dir: this.dir, ref: branchName });
|
|
387
|
+
this._logOperation('createBranch', { branchName }, { success: true });
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
this._logOperation('createBranch', { branchName }, null, error);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Deletes a branch
|
|
397
|
+
* @param branchName - Branch name
|
|
398
|
+
*/
|
|
399
|
+
async deleteBranch(branchName) {
|
|
400
|
+
try {
|
|
401
|
+
await isomorphic_git_1.default.deleteBranch({ fs: this.fs, dir: this.dir, ref: branchName });
|
|
402
|
+
this._logOperation('deleteBranch', { branchName }, { success: true });
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
this._logOperation('deleteBranch', { branchName }, null, error);
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Switches to a branch
|
|
412
|
+
* @param branchName - Branch name
|
|
413
|
+
*/
|
|
414
|
+
async checkout(branchName) {
|
|
415
|
+
try {
|
|
416
|
+
await isomorphic_git_1.default.checkout({ fs: this.fs, dir: this.dir, ref: branchName });
|
|
417
|
+
this._logOperation('checkout', { branchName }, { success: true });
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
this._logOperation('checkout', { branchName }, null, error);
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Lists all branches
|
|
427
|
+
* @returns List of branches
|
|
428
|
+
*/
|
|
429
|
+
async listBranches() {
|
|
430
|
+
try {
|
|
431
|
+
const branches = await isomorphic_git_1.default.listBranches({ fs: this.fs, dir: this.dir });
|
|
432
|
+
const current = await isomorphic_git_1.default.currentBranch({ fs: this.fs, dir: this.dir });
|
|
433
|
+
const result = branches.map(branch => ({
|
|
434
|
+
name: branch,
|
|
435
|
+
current: branch === current
|
|
436
|
+
}));
|
|
437
|
+
this._logOperation('listBranches', {}, { success: true, branches: result });
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
this._logOperation('listBranches', {}, null, error);
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Gets the current branch
|
|
447
|
+
* @returns Current branch name
|
|
448
|
+
*/
|
|
449
|
+
async currentBranch() {
|
|
450
|
+
try {
|
|
451
|
+
const branch = await isomorphic_git_1.default.currentBranch({ fs: this.fs, dir: this.dir });
|
|
452
|
+
this._logOperation('currentBranch', {}, { success: true, branch });
|
|
453
|
+
return branch || undefined;
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
this._logOperation('currentBranch', {}, null, error);
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Merges a branch into the current branch
|
|
462
|
+
* @param theirBranch - Branch name to merge
|
|
463
|
+
*/
|
|
464
|
+
async merge(theirBranch) {
|
|
465
|
+
try {
|
|
466
|
+
const result = await isomorphic_git_1.default.merge({
|
|
467
|
+
fs: this.fs,
|
|
468
|
+
dir: this.dir,
|
|
469
|
+
theirs: theirBranch,
|
|
470
|
+
author: this.author
|
|
471
|
+
});
|
|
472
|
+
this._logOperation('merge', { theirBranch }, { success: true, ...result });
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
this._logOperation('merge', { theirBranch }, null, error);
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Adds a remote
|
|
482
|
+
* @param remoteName - Remote name
|
|
483
|
+
* @param url - Remote URL
|
|
484
|
+
*/
|
|
485
|
+
async addRemote(remoteName, url) {
|
|
486
|
+
try {
|
|
487
|
+
await isomorphic_git_1.default.addRemote({ fs: this.fs, dir: this.dir, remote: remoteName, url });
|
|
488
|
+
this._logOperation('addRemote', { remoteName, url }, { success: true });
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
this._logOperation('addRemote', { remoteName, url }, null, error);
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Removes a remote
|
|
498
|
+
* @param remoteName - Remote name
|
|
499
|
+
*/
|
|
500
|
+
async deleteRemote(remoteName) {
|
|
501
|
+
try {
|
|
502
|
+
await isomorphic_git_1.default.deleteRemote({ fs: this.fs, dir: this.dir, remote: remoteName });
|
|
503
|
+
this._logOperation('deleteRemote', { remoteName }, { success: true });
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
this._logOperation('deleteRemote', { remoteName }, null, error);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Lists configured remotes
|
|
513
|
+
* @returns List of remotes
|
|
514
|
+
*/
|
|
515
|
+
async listRemotes() {
|
|
516
|
+
try {
|
|
517
|
+
const remotes = await isomorphic_git_1.default.listRemotes({ fs: this.fs, dir: this.dir });
|
|
518
|
+
this._logOperation('listRemotes', {}, { success: true, remotes });
|
|
519
|
+
return remotes;
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
this._logOperation('listRemotes', {}, null, error);
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Creates a tag
|
|
528
|
+
* @param tagName - Tag name
|
|
529
|
+
* @param ref - Reference (commit SHA or branch)
|
|
530
|
+
*/
|
|
531
|
+
async createTag(tagName, ref = 'HEAD') {
|
|
532
|
+
try {
|
|
533
|
+
await isomorphic_git_1.default.tag({ fs: this.fs, dir: this.dir, ref: tagName, object: ref });
|
|
534
|
+
this._logOperation('createTag', { tagName, ref }, { success: true });
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
this._logOperation('createTag', { tagName, ref }, null, error);
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Lists all tags
|
|
544
|
+
* @returns List of tags
|
|
545
|
+
*/
|
|
546
|
+
async listTags() {
|
|
547
|
+
try {
|
|
548
|
+
const tags = await isomorphic_git_1.default.listTags({ fs: this.fs, dir: this.dir });
|
|
549
|
+
this._logOperation('listTags', {}, { success: true, tags });
|
|
550
|
+
return tags;
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
this._logOperation('listTags', {}, null, error);
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Returns the history of all operations performed
|
|
559
|
+
* @returns List of operations
|
|
560
|
+
*/
|
|
561
|
+
getOperationsLog() {
|
|
562
|
+
return [...this.operations];
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Clears the operation log
|
|
566
|
+
*/
|
|
567
|
+
clearOperationsLog() {
|
|
568
|
+
this.operations = [];
|
|
569
|
+
this._logOperation('clearOperationsLog', {}, { success: true });
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Gets operation statistics
|
|
573
|
+
* @returns Statistics
|
|
574
|
+
*/
|
|
575
|
+
getOperationsStats() {
|
|
576
|
+
const stats = {
|
|
577
|
+
total: this.operations.length,
|
|
578
|
+
successful: this.operations.filter(op => op.success).length,
|
|
579
|
+
failed: this.operations.filter(op => !op.success).length,
|
|
580
|
+
byOperation: {}
|
|
581
|
+
};
|
|
582
|
+
for (const op of this.operations) {
|
|
583
|
+
if (!stats.byOperation[op.operation]) {
|
|
584
|
+
stats.byOperation[op.operation] = { total: 0, successful: 0, failed: 0 };
|
|
585
|
+
}
|
|
586
|
+
stats.byOperation[op.operation].total++;
|
|
587
|
+
if (op.success) {
|
|
588
|
+
stats.byOperation[op.operation].successful++;
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
stats.byOperation[op.operation].failed++;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return stats;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Exports the operation log in JSON format
|
|
598
|
+
* @returns JSON string of operations
|
|
599
|
+
*/
|
|
600
|
+
exportOperationsLog() {
|
|
601
|
+
return JSON.stringify({
|
|
602
|
+
name: this.name,
|
|
603
|
+
exportedAt: new Date().toISOString(),
|
|
604
|
+
stats: this.getOperationsStats(),
|
|
605
|
+
operations: this.operations
|
|
606
|
+
}, null, 2);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Syncs all changes from memory to disk
|
|
610
|
+
* @param targetPath - Destination path (optional, uses original path if not specified)
|
|
611
|
+
* @param options - Flush options
|
|
612
|
+
* @returns Number of files flushed
|
|
613
|
+
*/
|
|
614
|
+
async flush(targetPath = null, options = {}) {
|
|
615
|
+
try {
|
|
616
|
+
const destination = targetPath ? path_1.default.resolve(targetPath) : this.realDir;
|
|
617
|
+
if (!destination) {
|
|
618
|
+
throw new Error('No destination path specified and repository was not loaded from disk');
|
|
619
|
+
}
|
|
620
|
+
// Create destination directory if it doesn't exist (async)
|
|
621
|
+
const destinationExists = await this._realPathExists(destination);
|
|
622
|
+
if (!destinationExists) {
|
|
623
|
+
await fs_1.promises.mkdir(destination, { recursive: true });
|
|
624
|
+
}
|
|
625
|
+
// Copy recursively from memory to disk (async)
|
|
626
|
+
const fileCount = await this._copyToDiskAsync(this.dir, destination);
|
|
627
|
+
this._logOperation('flush', { targetPath: destination, options }, {
|
|
628
|
+
success: true,
|
|
629
|
+
filesFlushed: fileCount
|
|
630
|
+
});
|
|
631
|
+
return fileCount;
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
this._logOperation('flush', { targetPath }, null, error);
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Copies files from memory to disk (async)
|
|
640
|
+
* @private
|
|
641
|
+
*/
|
|
642
|
+
async _copyToDiskAsync(memoryPath, realPath) {
|
|
643
|
+
const entries = this.fs.readdirSync(memoryPath);
|
|
644
|
+
// Process entries in parallel for better performance
|
|
645
|
+
const promises = entries.map(async (entry) => {
|
|
646
|
+
const memoryEntryPath = path_1.default.posix.join(memoryPath, entry);
|
|
647
|
+
const realEntryPath = path_1.default.join(realPath, entry);
|
|
648
|
+
const stat = this.fs.statSync(memoryEntryPath);
|
|
649
|
+
if (stat.isDirectory()) {
|
|
650
|
+
const dirExists = await this._realPathExists(realEntryPath);
|
|
651
|
+
if (!dirExists) {
|
|
652
|
+
await fs_1.promises.mkdir(realEntryPath, { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
return await this._copyToDiskAsync(memoryEntryPath, realEntryPath);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
const content = this.fs.readFileSync(memoryEntryPath);
|
|
658
|
+
await fs_1.promises.writeFile(realEntryPath, content);
|
|
659
|
+
return 1;
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
const results = await Promise.all(promises);
|
|
663
|
+
return results.reduce((acc, val) => acc + val, 0);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Lists files in the in-memory repository
|
|
667
|
+
* @param dir - Relative directory (optional)
|
|
668
|
+
* @param includeGit - Include .git folder in listing
|
|
669
|
+
* @returns List of files
|
|
670
|
+
*/
|
|
671
|
+
async listFiles(dir = '', includeGit = false) {
|
|
672
|
+
try {
|
|
673
|
+
const fullPath = path_1.default.posix.join(this.dir, dir);
|
|
674
|
+
const files = this._listFilesRecursive(fullPath, '', includeGit);
|
|
675
|
+
this._logOperation('listFiles', { dir }, { success: true, files: files.length });
|
|
676
|
+
return files;
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
this._logOperation('listFiles', { dir }, null, error);
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Lists files recursively
|
|
685
|
+
* @private
|
|
686
|
+
*/
|
|
687
|
+
_listFilesRecursive(dir, base = '', includeGit = false) {
|
|
688
|
+
const files = [];
|
|
689
|
+
const entries = this.fs.readdirSync(dir);
|
|
690
|
+
for (const entry of entries) {
|
|
691
|
+
const fullPath = path_1.default.posix.join(dir, entry);
|
|
692
|
+
const relativePath = base ? path_1.default.posix.join(base, entry) : entry;
|
|
693
|
+
const stat = this.fs.statSync(fullPath);
|
|
694
|
+
if (stat.isDirectory()) {
|
|
695
|
+
if (entry === '.git' && !includeGit)
|
|
696
|
+
continue;
|
|
697
|
+
files.push(...this._listFilesRecursive(fullPath, relativePath, includeGit));
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
files.push(relativePath);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return files;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Gets the diff between working tree and HEAD
|
|
707
|
+
* @returns List of modified files
|
|
708
|
+
*/
|
|
709
|
+
async diff() {
|
|
710
|
+
try {
|
|
711
|
+
const changes = [];
|
|
712
|
+
const statusMatrix = await isomorphic_git_1.default.statusMatrix({ fs: this.fs, dir: this.dir });
|
|
713
|
+
for (const [filepath, head, workdir, stage] of statusMatrix) {
|
|
714
|
+
if (head !== workdir || head !== stage) {
|
|
715
|
+
changes.push({
|
|
716
|
+
filepath: filepath,
|
|
717
|
+
status: this._getStatusText(head, workdir, stage)
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
this._logOperation('diff', {}, {
|
|
722
|
+
success: true,
|
|
723
|
+
changes: changes.length
|
|
724
|
+
});
|
|
725
|
+
return changes;
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
this._logOperation('diff', {}, null, error);
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Gets file content at a specific commit
|
|
734
|
+
* @param filepath - File path
|
|
735
|
+
* @param ref - Reference (commit SHA, branch, tag)
|
|
736
|
+
* @returns File content
|
|
737
|
+
*/
|
|
738
|
+
async readFileAtRef(filepath, ref = 'HEAD') {
|
|
739
|
+
try {
|
|
740
|
+
const { blob } = await isomorphic_git_1.default.readBlob({
|
|
741
|
+
fs: this.fs,
|
|
742
|
+
dir: this.dir,
|
|
743
|
+
oid: await isomorphic_git_1.default.resolveRef({ fs: this.fs, dir: this.dir, ref }),
|
|
744
|
+
filepath
|
|
745
|
+
});
|
|
746
|
+
const content = Buffer.from(blob).toString('utf8');
|
|
747
|
+
this._logOperation('readFileAtRef', { filepath, ref }, { success: true });
|
|
748
|
+
return content;
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
this._logOperation('readFileAtRef', { filepath, ref }, null, error);
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Resets file changes
|
|
757
|
+
* @param filepath - File path
|
|
758
|
+
*/
|
|
759
|
+
async resetFile(filepath) {
|
|
760
|
+
try {
|
|
761
|
+
await isomorphic_git_1.default.checkout({
|
|
762
|
+
fs: this.fs,
|
|
763
|
+
dir: this.dir,
|
|
764
|
+
filepaths: [filepath],
|
|
765
|
+
force: true
|
|
766
|
+
});
|
|
767
|
+
this._logOperation('resetFile', { filepath }, { success: true });
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
catch (error) {
|
|
771
|
+
this._logOperation('resetFile', { filepath }, null, error);
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Stashes current changes (simulates by saving in memory)
|
|
777
|
+
* @returns Number of files saved to stash
|
|
778
|
+
*/
|
|
779
|
+
async stash() {
|
|
780
|
+
try {
|
|
781
|
+
const statusMatrix = await isomorphic_git_1.default.statusMatrix({ fs: this.fs, dir: this.dir });
|
|
782
|
+
const stashedFiles = [];
|
|
783
|
+
for (const [filepath, head, workdir] of statusMatrix) {
|
|
784
|
+
if (workdir === 2 || workdir === 0) {
|
|
785
|
+
const fullPath = path_1.default.posix.join(this.dir, filepath);
|
|
786
|
+
try {
|
|
787
|
+
const content = this.fs.readFileSync(fullPath);
|
|
788
|
+
stashedFiles.push({
|
|
789
|
+
filepath: filepath,
|
|
790
|
+
content: content,
|
|
791
|
+
wasNew: head === 0
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
// Deleted file
|
|
796
|
+
stashedFiles.push({ filepath: filepath, deleted: true });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
this._stash.push(stashedFiles);
|
|
801
|
+
// Reset to HEAD
|
|
802
|
+
for (const file of stashedFiles) {
|
|
803
|
+
const fullPath = path_1.default.posix.join(this.dir, file.filepath);
|
|
804
|
+
if (file.deleted) {
|
|
805
|
+
// Restore deleted file
|
|
806
|
+
try {
|
|
807
|
+
await isomorphic_git_1.default.checkout({
|
|
808
|
+
fs: this.fs,
|
|
809
|
+
dir: this.dir,
|
|
810
|
+
filepaths: [file.filepath],
|
|
811
|
+
force: true
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
// Ignore if didn't exist
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
else if (file.wasNew) {
|
|
819
|
+
// Remove new file
|
|
820
|
+
try {
|
|
821
|
+
this.fs.unlinkSync(fullPath);
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
// Ignore
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
// Restore modified file
|
|
829
|
+
await isomorphic_git_1.default.checkout({
|
|
830
|
+
fs: this.fs,
|
|
831
|
+
dir: this.dir,
|
|
832
|
+
filepaths: [file.filepath],
|
|
833
|
+
force: true
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
this._logOperation('stash', {}, { success: true, files: stashedFiles.length });
|
|
838
|
+
return stashedFiles.length;
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
this._logOperation('stash', {}, null, error);
|
|
842
|
+
throw error;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Restores from stash
|
|
847
|
+
* @returns Number of files restored
|
|
848
|
+
*/
|
|
849
|
+
async stashPop() {
|
|
850
|
+
try {
|
|
851
|
+
if (this._stash.length === 0) {
|
|
852
|
+
throw new Error('No stash available');
|
|
853
|
+
}
|
|
854
|
+
const stashedFiles = this._stash.pop();
|
|
855
|
+
for (const file of stashedFiles) {
|
|
856
|
+
const fullPath = path_1.default.posix.join(this.dir, file.filepath);
|
|
857
|
+
if (file.deleted) {
|
|
858
|
+
try {
|
|
859
|
+
this.fs.unlinkSync(fullPath);
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
// Ignore
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
// Create directory if needed
|
|
867
|
+
const dir = path_1.default.posix.dirname(fullPath);
|
|
868
|
+
this.fs.mkdirSync(dir, { recursive: true });
|
|
869
|
+
this.fs.writeFileSync(fullPath, file.content);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
this._logOperation('stashPop', {}, { success: true, files: stashedFiles.length });
|
|
873
|
+
return stashedFiles.length;
|
|
874
|
+
}
|
|
875
|
+
catch (error) {
|
|
876
|
+
this._logOperation('stashPop', {}, null, error);
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Lists available stashes
|
|
882
|
+
* @returns Number of stashes
|
|
883
|
+
*/
|
|
884
|
+
stashList() {
|
|
885
|
+
return this._stash.length;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Clones a remote repository to memory
|
|
889
|
+
* @param url - Repository URL
|
|
890
|
+
* @param options - Clone options
|
|
891
|
+
*/
|
|
892
|
+
async clone(url, options = {}) {
|
|
893
|
+
try {
|
|
894
|
+
this.fs.mkdirSync(this.dir, { recursive: true });
|
|
895
|
+
await isomorphic_git_1.default.clone({
|
|
896
|
+
fs: this.fs,
|
|
897
|
+
http: node_1.default,
|
|
898
|
+
dir: this.dir,
|
|
899
|
+
url,
|
|
900
|
+
depth: options.depth || undefined,
|
|
901
|
+
singleBranch: options.singleBranch || false,
|
|
902
|
+
...options
|
|
903
|
+
});
|
|
904
|
+
this.isInitialized = true;
|
|
905
|
+
this._logOperation('clone', { url, options }, { success: true });
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
this._logOperation('clone', { url, options }, null, error);
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Fetches from a remote
|
|
915
|
+
* @param remote - Remote name (default: 'origin')
|
|
916
|
+
*/
|
|
917
|
+
async fetch(remote = 'origin') {
|
|
918
|
+
try {
|
|
919
|
+
await isomorphic_git_1.default.fetch({
|
|
920
|
+
fs: this.fs,
|
|
921
|
+
http: node_1.default,
|
|
922
|
+
dir: this.dir,
|
|
923
|
+
remote
|
|
924
|
+
});
|
|
925
|
+
this._logOperation('fetch', { remote }, { success: true });
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
this._logOperation('fetch', { remote }, null, error);
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Pulls from a remote
|
|
935
|
+
* @param remote - Remote name (default: 'origin')
|
|
936
|
+
* @param branch - Branch name
|
|
937
|
+
*/
|
|
938
|
+
async pull(remote = 'origin', branch = null) {
|
|
939
|
+
try {
|
|
940
|
+
const currentBranchName = branch || await this.currentBranch();
|
|
941
|
+
await isomorphic_git_1.default.pull({
|
|
942
|
+
fs: this.fs,
|
|
943
|
+
http: node_1.default,
|
|
944
|
+
dir: this.dir,
|
|
945
|
+
remote,
|
|
946
|
+
ref: currentBranchName,
|
|
947
|
+
author: this.author
|
|
948
|
+
});
|
|
949
|
+
this._logOperation('pull', { remote, branch: currentBranchName }, { success: true });
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
this._logOperation('pull', { remote, branch }, null, error);
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Clears the in-memory filesystem and reinitializes
|
|
959
|
+
*/
|
|
960
|
+
async clear() {
|
|
961
|
+
try {
|
|
962
|
+
this.vol.reset();
|
|
963
|
+
this.isInitialized = false;
|
|
964
|
+
this._stash = [];
|
|
965
|
+
this._logOperation('clear', {}, { success: true });
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
this._logOperation('clear', {}, null, error);
|
|
970
|
+
throw error;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Gets repository information
|
|
975
|
+
* @returns Repository information
|
|
976
|
+
*/
|
|
977
|
+
async getRepoInfo() {
|
|
978
|
+
const info = {
|
|
979
|
+
initialized: this.isInitialized,
|
|
980
|
+
memoryDir: this.dir,
|
|
981
|
+
realDir: this.realDir,
|
|
982
|
+
currentBranch: null,
|
|
983
|
+
branches: [],
|
|
984
|
+
remotes: [],
|
|
985
|
+
fileCount: 0,
|
|
986
|
+
commits: 0
|
|
987
|
+
};
|
|
988
|
+
if (this.isInitialized) {
|
|
989
|
+
info.currentBranch = (await this.currentBranch()) || null;
|
|
990
|
+
info.branches = await this.listBranches();
|
|
991
|
+
info.remotes = await this.listRemotes();
|
|
992
|
+
info.fileCount = this._countFiles(this.dir);
|
|
993
|
+
try {
|
|
994
|
+
const logEntries = await isomorphic_git_1.default.log({ fs: this.fs, dir: this.dir });
|
|
995
|
+
info.commits = logEntries.length;
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
// Repo without commits
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return info;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Gets estimated memory usage
|
|
1005
|
+
* @returns Memory usage information
|
|
1006
|
+
*/
|
|
1007
|
+
getMemoryUsage() {
|
|
1008
|
+
const json = this.vol.toJSON();
|
|
1009
|
+
const totalSize = Object.values(json).reduce((acc, content) => {
|
|
1010
|
+
if (typeof content === 'string') {
|
|
1011
|
+
return acc + content.length;
|
|
1012
|
+
}
|
|
1013
|
+
return acc;
|
|
1014
|
+
}, 0);
|
|
1015
|
+
return {
|
|
1016
|
+
files: Object.keys(json).length,
|
|
1017
|
+
estimatedSizeBytes: totalSize,
|
|
1018
|
+
estimatedSizeMB: (totalSize / 1024 / 1024).toFixed(2),
|
|
1019
|
+
operationsLogged: this.operations.length
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
exports.MemoryGit = MemoryGit;
|
|
1024
|
+
//# sourceMappingURL=index.js.map
|