guito 0.5.2 → 0.5.4
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/CHANGELOG.md +25 -0
- package/bin/guito-server.js +179 -90
- package/bin/ui/3rdpartylicenses.txt +26 -0
- package/bin/ui/browser/index.html +1 -1
- package/bin/ui/browser/main-3QYX4VGI.js +6 -0
- package/bin/ui/browser/worker-O4G7QTZ6.js +1 -0
- package/package.json +3 -1
- package/bin/ui/browser/main-24WBYIHU.js +0 -5
package/bin/guito-server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
|
5
5
|
import fastify from 'fastify';
|
|
6
6
|
import fastifyStatic from '@fastify/static';
|
|
7
7
|
import fastifyCors from '@fastify/cors';
|
|
8
|
+
import fastifyCompress from '@fastify/compress';
|
|
8
9
|
import { simpleGit } from 'simple-git';
|
|
9
10
|
import { promisify } from 'node:util';
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
@@ -15,6 +16,8 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
15
16
|
});
|
|
16
17
|
// Register cors
|
|
17
18
|
await app.register(fastifyCors);
|
|
19
|
+
// Compress JSON responses (commit history payloads are highly repetitive)
|
|
20
|
+
await app.register(fastifyCompress);
|
|
18
21
|
if (apiToken) {
|
|
19
22
|
app.addHook('onRequest', async (request, reply) => {
|
|
20
23
|
const pathname = request.url.split('?', 1)[0];
|
|
@@ -130,47 +133,100 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
130
133
|
app.get('/api/repo', async (_req, resp) => {
|
|
131
134
|
try {
|
|
132
135
|
const root = (await git.revparse(['--show-toplevel'])).trim();
|
|
133
|
-
resp.type('application/json').send({ root, name: basename(root) });
|
|
136
|
+
return resp.type('application/json').send({ root, name: basename(root) });
|
|
134
137
|
}
|
|
135
138
|
catch (err) {
|
|
136
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
139
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
137
140
|
}
|
|
138
141
|
});
|
|
139
142
|
// ==================== Commits ====================
|
|
140
|
-
app.get('/api/commits', async (
|
|
141
|
-
try {
|
|
142
|
-
const
|
|
143
|
+
app.get('/api/commits', async (req, resp) => {
|
|
144
|
+
try {
|
|
145
|
+
const query = (req.query ?? {});
|
|
146
|
+
const limit = Number.parseInt(query.limit ?? '', 10);
|
|
147
|
+
const skip = Number.parseInt(query.skip ?? '', 10);
|
|
148
|
+
// Build simple-git options conditionally: an explicit key with an
|
|
149
|
+
// undefined value would still be emitted as a bare command flag.
|
|
150
|
+
// The commit body is intentionally omitted: it roughly doubles the
|
|
151
|
+
// payload and is only needed when a commit is opened, so it is served
|
|
152
|
+
// on demand by /api/commit/detail instead.
|
|
153
|
+
const options = {
|
|
143
154
|
format: {
|
|
144
155
|
hash: '%H',
|
|
145
156
|
date: '%aI',
|
|
146
157
|
message: '%s',
|
|
147
158
|
refs: '%D',
|
|
148
|
-
body: '%b',
|
|
149
159
|
author_name: '%aN',
|
|
150
160
|
author_email: '%aE',
|
|
151
161
|
parents: '%P',
|
|
152
162
|
},
|
|
153
|
-
|
|
163
|
+
'--all': null,
|
|
164
|
+
};
|
|
165
|
+
if (Number.isFinite(limit) && limit > 0) {
|
|
166
|
+
options.maxCount = limit;
|
|
167
|
+
}
|
|
168
|
+
if (Number.isFinite(skip) && skip > 0) {
|
|
169
|
+
options['--skip'] = String(skip);
|
|
170
|
+
}
|
|
171
|
+
const [log, count] = await Promise.all([
|
|
172
|
+
git.log(options),
|
|
173
|
+
git.raw(['rev-list', '--count', '--all']),
|
|
174
|
+
]);
|
|
154
175
|
const commits = log.all.map((commit) => ({
|
|
155
176
|
...commit,
|
|
156
177
|
parents: String(commit.parents ?? '')
|
|
157
178
|
.split(' ')
|
|
158
179
|
.filter(Boolean),
|
|
159
180
|
}));
|
|
160
|
-
resp
|
|
181
|
+
return resp
|
|
182
|
+
.type('application/json')
|
|
183
|
+
.send({ commits, total: Number.parseInt(count, 10) || 0 });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
// A repository without any commits has no history to list.
|
|
187
|
+
if (/does not have any commits yet|unknown revision|bad default revision/i.test(err.message ?? '')) {
|
|
188
|
+
return resp.type('application/json').send({ commits: [], total: 0 });
|
|
189
|
+
}
|
|
190
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
// Full-text commit search. The list payload omits commit bodies, so body
|
|
194
|
+
// matching runs here (git --grep covers subject and body together) and the
|
|
195
|
+
// client unions the resulting hashes with its local subject/author/hash
|
|
196
|
+
// matches over the loaded history.
|
|
197
|
+
app.get('/api/commits/search', async (req, resp) => {
|
|
198
|
+
const query = String((req.query ?? {}).query ?? '').trim();
|
|
199
|
+
if (!query) {
|
|
200
|
+
return resp.type('application/json').send({ hashes: [] });
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const raw = await git.raw([
|
|
204
|
+
'log',
|
|
205
|
+
'--all',
|
|
206
|
+
'-i',
|
|
207
|
+
'--fixed-strings',
|
|
208
|
+
`--grep=${query}`,
|
|
209
|
+
'--format=%H',
|
|
210
|
+
]);
|
|
211
|
+
return resp
|
|
212
|
+
.type('application/json')
|
|
213
|
+
.send({ hashes: raw.split('\n').map((hash) => hash.trim()).filter(Boolean) });
|
|
161
214
|
}
|
|
162
215
|
catch (err) {
|
|
163
|
-
|
|
216
|
+
if (/does not have any commits yet|unknown revision|bad default revision/i.test(err.message ?? '')) {
|
|
217
|
+
return resp.type('application/json').send({ hashes: [] });
|
|
218
|
+
}
|
|
219
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
164
220
|
}
|
|
165
221
|
});
|
|
166
222
|
app.post('/api/commit', async (req, resp) => {
|
|
167
223
|
try {
|
|
168
224
|
const { message, description } = req.body;
|
|
169
225
|
await git.commit([message, ...(description ? [description] : [])], { '--allow-empty': null });
|
|
170
|
-
resp.type('application/json').send({ success: true });
|
|
226
|
+
return resp.type('application/json').send({ success: true });
|
|
171
227
|
}
|
|
172
228
|
catch (err) {
|
|
173
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
229
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
174
230
|
}
|
|
175
231
|
});
|
|
176
232
|
app.post('/api/amend', async (req, resp) => {
|
|
@@ -180,57 +236,57 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
180
236
|
'--amend': null,
|
|
181
237
|
'--no-edit': null,
|
|
182
238
|
});
|
|
183
|
-
resp.type('application/json').send({ success: true });
|
|
239
|
+
return resp.type('application/json').send({ success: true });
|
|
184
240
|
}
|
|
185
241
|
catch (err) {
|
|
186
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
242
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
187
243
|
}
|
|
188
244
|
});
|
|
189
245
|
app.post('/api/revert', async (req, resp) => {
|
|
190
246
|
try {
|
|
191
247
|
const { commit } = req.body;
|
|
192
248
|
await git.revert(commit);
|
|
193
|
-
resp.type('application/json').send({ success: true });
|
|
249
|
+
return resp.type('application/json').send({ success: true });
|
|
194
250
|
}
|
|
195
251
|
catch (err) {
|
|
196
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
252
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
197
253
|
}
|
|
198
254
|
});
|
|
199
255
|
app.post('/api/cherry-pick', async (req, resp) => {
|
|
200
256
|
try {
|
|
201
257
|
await git.raw(['cherry-pick', req.body.commit]);
|
|
202
|
-
resp.type('application/json').send({ success: true });
|
|
258
|
+
return resp.type('application/json').send({ success: true });
|
|
203
259
|
}
|
|
204
260
|
catch (err) {
|
|
205
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
261
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
206
262
|
}
|
|
207
263
|
});
|
|
208
264
|
app.post('/api/commit/drop', async (req, resp) => {
|
|
209
265
|
try {
|
|
210
266
|
await git.raw(['reset', '--hard', `${req.body.commit}^`]);
|
|
211
|
-
resp.type('application/json').send({ success: true });
|
|
267
|
+
return resp.type('application/json').send({ success: true });
|
|
212
268
|
}
|
|
213
269
|
catch (err) {
|
|
214
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
270
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
215
271
|
}
|
|
216
272
|
});
|
|
217
273
|
app.post('/api/reset-commit', async (req, resp) => {
|
|
218
274
|
try {
|
|
219
275
|
await git.raw(['reset', '--hard', req.body.commit]);
|
|
220
|
-
resp.type('application/json').send({ success: true });
|
|
276
|
+
return resp.type('application/json').send({ success: true });
|
|
221
277
|
}
|
|
222
278
|
catch (err) {
|
|
223
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
279
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
224
280
|
}
|
|
225
281
|
});
|
|
226
282
|
// ==================== Staging ====================
|
|
227
283
|
app.get('/api/status', async (_req, resp) => {
|
|
228
284
|
try {
|
|
229
285
|
const status = await git.status();
|
|
230
|
-
resp.type('application/json').send(status);
|
|
286
|
+
return resp.type('application/json').send(status);
|
|
231
287
|
}
|
|
232
288
|
catch (err) {
|
|
233
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
289
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
234
290
|
}
|
|
235
291
|
});
|
|
236
292
|
app.post('/api/stage', async (req, resp) => {
|
|
@@ -239,10 +295,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
239
295
|
if (Array.isArray(files) && files.length > 0) {
|
|
240
296
|
await git.add(files);
|
|
241
297
|
}
|
|
242
|
-
resp.type('application/json').send({ success: true });
|
|
298
|
+
return resp.type('application/json').send({ success: true });
|
|
243
299
|
}
|
|
244
300
|
catch (err) {
|
|
245
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
301
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
246
302
|
}
|
|
247
303
|
});
|
|
248
304
|
app.post('/api/unstage', async (req, resp) => {
|
|
@@ -251,10 +307,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
251
307
|
if (Array.isArray(files) && files.length > 0) {
|
|
252
308
|
await git.reset(['HEAD', ...files]);
|
|
253
309
|
}
|
|
254
|
-
resp.type('application/json').send({ success: true });
|
|
310
|
+
return resp.type('application/json').send({ success: true });
|
|
255
311
|
}
|
|
256
312
|
catch (err) {
|
|
257
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
313
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
258
314
|
}
|
|
259
315
|
});
|
|
260
316
|
app.post('/api/stage-lines', async (req, resp) => {
|
|
@@ -262,10 +318,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
262
318
|
const { file, patch } = req.body;
|
|
263
319
|
// Stage specific lines using git add -p equivalent (patch mode)
|
|
264
320
|
await git.raw('add --patch', { input: patch });
|
|
265
|
-
resp.type('application/json').send({ success: true });
|
|
321
|
+
return resp.type('application/json').send({ success: true });
|
|
266
322
|
}
|
|
267
323
|
catch (err) {
|
|
268
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
324
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
269
325
|
}
|
|
270
326
|
});
|
|
271
327
|
// ==================== Branches ====================
|
|
@@ -287,69 +343,69 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
287
343
|
const name = refname.replace(/^refs\/(heads|remotes)\//, '');
|
|
288
344
|
return { name, commit, current: headMarker === '*', remote };
|
|
289
345
|
});
|
|
290
|
-
resp.type('application/json').send(branches);
|
|
346
|
+
return resp.type('application/json').send(branches);
|
|
291
347
|
}
|
|
292
348
|
catch (err) {
|
|
293
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
349
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
294
350
|
}
|
|
295
351
|
});
|
|
296
352
|
app.get('/api/branches', async (_req, resp) => {
|
|
297
353
|
try {
|
|
298
354
|
const branchSummary = await git.branchLocal();
|
|
299
|
-
resp.type('application/json').send(branchSummary);
|
|
355
|
+
return resp.type('application/json').send(branchSummary);
|
|
300
356
|
}
|
|
301
357
|
catch (err) {
|
|
302
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
358
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
303
359
|
}
|
|
304
360
|
});
|
|
305
361
|
app.post('/api/branch/create', async (req, resp) => {
|
|
306
362
|
try {
|
|
307
363
|
const { name, startPoint } = req.body;
|
|
308
364
|
await git.branch([...(startPoint ? [name, startPoint] : [name])]);
|
|
309
|
-
resp.type('application/json').send({ success: true });
|
|
365
|
+
return resp.type('application/json').send({ success: true });
|
|
310
366
|
}
|
|
311
367
|
catch (err) {
|
|
312
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
368
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
313
369
|
}
|
|
314
370
|
});
|
|
315
371
|
app.post('/api/branch/delete', async (req, resp) => {
|
|
316
372
|
try {
|
|
317
373
|
const { name, force } = req.body;
|
|
318
374
|
await git.deleteLocalBranch(name, force);
|
|
319
|
-
resp.type('application/json').send({ success: true });
|
|
375
|
+
return resp.type('application/json').send({ success: true });
|
|
320
376
|
}
|
|
321
377
|
catch (err) {
|
|
322
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
378
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
323
379
|
}
|
|
324
380
|
});
|
|
325
381
|
app.post('/api/branch/delete-remote', async (req, resp) => {
|
|
326
382
|
try {
|
|
327
383
|
const { remote, branch } = req.body;
|
|
328
384
|
await git.push([remote, '--delete', branch]);
|
|
329
|
-
resp.type('application/json').send({ success: true });
|
|
385
|
+
return resp.type('application/json').send({ success: true });
|
|
330
386
|
}
|
|
331
387
|
catch (err) {
|
|
332
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
388
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
333
389
|
}
|
|
334
390
|
});
|
|
335
391
|
app.post('/api/branch/rename', async (req, resp) => {
|
|
336
392
|
try {
|
|
337
393
|
const { oldName, newName } = req.body;
|
|
338
394
|
await git.branch(['-m', oldName, newName]);
|
|
339
|
-
resp.type('application/json').send({ success: true });
|
|
395
|
+
return resp.type('application/json').send({ success: true });
|
|
340
396
|
}
|
|
341
397
|
catch (err) {
|
|
342
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
398
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
343
399
|
}
|
|
344
400
|
});
|
|
345
401
|
app.post('/api/checkout', async (req, resp) => {
|
|
346
402
|
try {
|
|
347
403
|
const { ref } = req.body;
|
|
348
404
|
await git.checkout(ref);
|
|
349
|
-
resp.type('application/json').send({ success: true });
|
|
405
|
+
return resp.type('application/json').send({ success: true });
|
|
350
406
|
}
|
|
351
407
|
catch (err) {
|
|
352
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
408
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
353
409
|
}
|
|
354
410
|
});
|
|
355
411
|
// ==================== Merge & Rebase ====================
|
|
@@ -357,10 +413,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
357
413
|
try {
|
|
358
414
|
const { branch } = req.body;
|
|
359
415
|
await git.merge([branch]);
|
|
360
|
-
resp.type('application/json').send({ success: true });
|
|
416
|
+
return resp.type('application/json').send({ success: true });
|
|
361
417
|
}
|
|
362
418
|
catch (err) {
|
|
363
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
419
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
364
420
|
}
|
|
365
421
|
});
|
|
366
422
|
app.get('/api/archive', async (req, resp) => {
|
|
@@ -387,10 +443,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
387
443
|
try {
|
|
388
444
|
const { branch } = req.body;
|
|
389
445
|
await git.rebase([branch]);
|
|
390
|
-
resp.type('application/json').send({ success: true });
|
|
446
|
+
return resp.type('application/json').send({ success: true });
|
|
391
447
|
}
|
|
392
448
|
catch (err) {
|
|
393
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
449
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
394
450
|
}
|
|
395
451
|
});
|
|
396
452
|
app.post('/api/squash', async (req, resp) => {
|
|
@@ -398,80 +454,80 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
398
454
|
const { commits } = req.body;
|
|
399
455
|
// Squash: rebase -i with squash operation
|
|
400
456
|
await git.rebase(['-i', 'HEAD~' + commits]);
|
|
401
|
-
resp.type('application/json').send({ success: true });
|
|
457
|
+
return resp.type('application/json').send({ success: true });
|
|
402
458
|
}
|
|
403
459
|
catch (err) {
|
|
404
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
460
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
405
461
|
}
|
|
406
462
|
});
|
|
407
463
|
// ==================== Stash ====================
|
|
408
464
|
app.get('/api/stash/list', async (_req, resp) => {
|
|
409
465
|
try {
|
|
410
466
|
const stashList = await git.stashList();
|
|
411
|
-
resp.type('application/json').send(stashList);
|
|
467
|
+
return resp.type('application/json').send(stashList);
|
|
412
468
|
}
|
|
413
469
|
catch (err) {
|
|
414
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
470
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
415
471
|
}
|
|
416
472
|
});
|
|
417
473
|
app.post('/api/stash/save', async (req, resp) => {
|
|
418
474
|
try {
|
|
419
475
|
const { message } = req.body;
|
|
420
476
|
await git.stash(['save', message || '']);
|
|
421
|
-
resp.type('application/json').send({ success: true });
|
|
477
|
+
return resp.type('application/json').send({ success: true });
|
|
422
478
|
}
|
|
423
479
|
catch (err) {
|
|
424
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
480
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
425
481
|
}
|
|
426
482
|
});
|
|
427
483
|
app.post('/api/stash/apply', async (req, resp) => {
|
|
428
484
|
try {
|
|
429
485
|
const { index } = req.body;
|
|
430
486
|
await git.stash(['apply', `stash@{${index}}`]);
|
|
431
|
-
resp.type('application/json').send({ success: true });
|
|
487
|
+
return resp.type('application/json').send({ success: true });
|
|
432
488
|
}
|
|
433
489
|
catch (err) {
|
|
434
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
490
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
435
491
|
}
|
|
436
492
|
});
|
|
437
493
|
app.post('/api/stash/pop', async (req, resp) => {
|
|
438
494
|
try {
|
|
439
495
|
const { index } = req.body;
|
|
440
496
|
await git.stash(['pop', `stash@{${index}}`]);
|
|
441
|
-
resp.type('application/json').send({ success: true });
|
|
497
|
+
return resp.type('application/json').send({ success: true });
|
|
442
498
|
}
|
|
443
499
|
catch (err) {
|
|
444
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
500
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
445
501
|
}
|
|
446
502
|
});
|
|
447
503
|
app.post('/api/stash/drop', async (req, resp) => {
|
|
448
504
|
try {
|
|
449
505
|
const { index } = req.body;
|
|
450
506
|
await git.stash(['drop', `stash@{${index}}`]);
|
|
451
|
-
resp.type('application/json').send({ success: true });
|
|
507
|
+
return resp.type('application/json').send({ success: true });
|
|
452
508
|
}
|
|
453
509
|
catch (err) {
|
|
454
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
510
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
455
511
|
}
|
|
456
512
|
});
|
|
457
513
|
app.post('/api/stash/show', async (req, resp) => {
|
|
458
514
|
try {
|
|
459
515
|
const { index } = req.body;
|
|
460
516
|
const show = await git.stash(['show', '-p', `stash@{${index}}`]);
|
|
461
|
-
resp.type('application/json').send({ preview: show });
|
|
517
|
+
return resp.type('application/json').send({ preview: show });
|
|
462
518
|
}
|
|
463
519
|
catch (err) {
|
|
464
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
520
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
465
521
|
}
|
|
466
522
|
});
|
|
467
523
|
// ==================== Tags ====================
|
|
468
524
|
app.get('/api/tags', async (_req, resp) => {
|
|
469
525
|
try {
|
|
470
526
|
const tags = await git.tags();
|
|
471
|
-
resp.type('application/json').send(tags);
|
|
527
|
+
return resp.type('application/json').send(tags);
|
|
472
528
|
}
|
|
473
529
|
catch (err) {
|
|
474
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
530
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
475
531
|
}
|
|
476
532
|
});
|
|
477
533
|
app.post('/api/tag/create', async (req, resp) => {
|
|
@@ -483,70 +539,103 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
483
539
|
else {
|
|
484
540
|
await git.tag([name, ...(commit ? [commit] : [])]);
|
|
485
541
|
}
|
|
486
|
-
resp.type('application/json').send({ success: true });
|
|
542
|
+
return resp.type('application/json').send({ success: true });
|
|
487
543
|
}
|
|
488
544
|
catch (err) {
|
|
489
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
545
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
490
546
|
}
|
|
491
547
|
});
|
|
492
548
|
app.post('/api/tag/delete', async (req, resp) => {
|
|
493
549
|
try {
|
|
494
550
|
const { name } = req.body;
|
|
495
551
|
await git.tag(['-d', name]);
|
|
496
|
-
resp.type('application/json').send({ success: true });
|
|
552
|
+
return resp.type('application/json').send({ success: true });
|
|
497
553
|
}
|
|
498
554
|
catch (err) {
|
|
499
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
555
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
500
556
|
}
|
|
501
557
|
});
|
|
502
558
|
// ==================== Remote Operations ====================
|
|
503
559
|
app.get('/api/fetch', async (_req, resp) => {
|
|
504
560
|
try {
|
|
505
561
|
await git.fetch();
|
|
506
|
-
resp.type('application/json').send({ success: true });
|
|
562
|
+
return resp.type('application/json').send({ success: true });
|
|
507
563
|
}
|
|
508
564
|
catch (err) {
|
|
509
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
565
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
510
566
|
}
|
|
511
567
|
});
|
|
512
568
|
app.post('/api/pull', async (req, resp) => {
|
|
513
569
|
try {
|
|
514
570
|
const { remote, branch, rebase } = req.body;
|
|
515
571
|
await git.pull(remote || 'origin', branch || undefined, rebase ? { '--rebase': null } : {});
|
|
516
|
-
resp.type('application/json').send({ success: true });
|
|
572
|
+
return resp.type('application/json').send({ success: true });
|
|
517
573
|
}
|
|
518
574
|
catch (err) {
|
|
519
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
575
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
520
576
|
}
|
|
521
577
|
});
|
|
522
578
|
app.post('/api/push', async (req, resp) => {
|
|
523
579
|
try {
|
|
524
580
|
const { remote, branch, force } = req.body;
|
|
525
581
|
await git.push(remote || 'origin', branch || undefined, force ? { '-f': null } : {});
|
|
526
|
-
resp.type('application/json').send({ success: true });
|
|
582
|
+
return resp.type('application/json').send({ success: true });
|
|
527
583
|
}
|
|
528
584
|
catch (err) {
|
|
529
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
585
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
530
586
|
}
|
|
531
587
|
});
|
|
532
588
|
app.post('/api/sync', async (_req, resp) => {
|
|
533
589
|
try {
|
|
534
590
|
await git.pull('origin', undefined, {});
|
|
535
591
|
await git.push('origin', undefined, {});
|
|
536
|
-
resp.type('application/json').send({ success: true });
|
|
592
|
+
return resp.type('application/json').send({ success: true });
|
|
537
593
|
}
|
|
538
594
|
catch (err) {
|
|
539
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
595
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
540
596
|
}
|
|
541
597
|
});
|
|
542
598
|
app.post('/api/prune', async (req, resp) => {
|
|
543
599
|
try {
|
|
544
600
|
const { remote } = req.body;
|
|
545
601
|
await git.remote(['prune', remote || 'origin']);
|
|
546
|
-
resp.type('application/json').send({ success: true });
|
|
602
|
+
return resp.type('application/json').send({ success: true });
|
|
547
603
|
}
|
|
548
604
|
catch (err) {
|
|
549
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
605
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
// ==================== Commit Detail ====================
|
|
609
|
+
app.post('/api/commit/detail', async (req, resp) => {
|
|
610
|
+
try {
|
|
611
|
+
const { hash } = req.body;
|
|
612
|
+
if (!hash) {
|
|
613
|
+
return resp.status(400).type('application/json').send({ error: 'hash required' });
|
|
614
|
+
}
|
|
615
|
+
// Fields are NUL-separated; %b may span lines and contain anything
|
|
616
|
+
// except NUL, so it cannot be split naively by newline.
|
|
617
|
+
const raw = await git.raw([
|
|
618
|
+
'show',
|
|
619
|
+
'-s',
|
|
620
|
+
'--format=%H%x00%aI%x00%s%x00%b%x00%aN%x00%aE%x00%P',
|
|
621
|
+
hash,
|
|
622
|
+
]);
|
|
623
|
+
const [fullHash, date, message, body, authorName, authorEmail, parents] = raw.trimEnd().split('\u0000');
|
|
624
|
+
return resp.type('application/json').send({
|
|
625
|
+
hash: fullHash,
|
|
626
|
+
date,
|
|
627
|
+
message,
|
|
628
|
+
refs: '',
|
|
629
|
+
body: body ?? '',
|
|
630
|
+
author_name: authorName,
|
|
631
|
+
author_email: authorEmail,
|
|
632
|
+
parents: String(parents ?? '')
|
|
633
|
+
.split(' ')
|
|
634
|
+
.filter(Boolean),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
550
639
|
}
|
|
551
640
|
});
|
|
552
641
|
// ==================== Diff Viewer ====================
|
|
@@ -566,10 +655,10 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
566
655
|
'-m',
|
|
567
656
|
hash,
|
|
568
657
|
]);
|
|
569
|
-
resp.type('application/json').send({ hash, files: parseUnifiedDiff(rawDiff) });
|
|
658
|
+
return resp.type('application/json').send({ hash, files: parseUnifiedDiff(rawDiff) });
|
|
570
659
|
}
|
|
571
660
|
catch (err) {
|
|
572
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
661
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
573
662
|
}
|
|
574
663
|
});
|
|
575
664
|
// ==================== Working Changes ====================
|
|
@@ -620,7 +709,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
620
709
|
}
|
|
621
710
|
const stagedRaw = await git.raw(['diff', '--name-only', '--cached']);
|
|
622
711
|
const unstagedRaw = await git.raw(['diff', '--name-only']);
|
|
623
|
-
resp.type('application/json').send({
|
|
712
|
+
return resp.type('application/json').send({
|
|
624
713
|
files,
|
|
625
714
|
staged: stagedRaw.split('\n').filter(Boolean),
|
|
626
715
|
unstaged: unstagedRaw.split('\n').filter(Boolean),
|
|
@@ -628,7 +717,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
628
717
|
});
|
|
629
718
|
}
|
|
630
719
|
catch (err) {
|
|
631
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
720
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
632
721
|
}
|
|
633
722
|
});
|
|
634
723
|
// ==================== File Content ====================
|
|
@@ -665,15 +754,15 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
665
754
|
if (content.includes('\u0000')) {
|
|
666
755
|
return resp.type('application/json').send({ binary: true, content: '' });
|
|
667
756
|
}
|
|
668
|
-
resp.type('application/json').send({ content });
|
|
757
|
+
return resp.type('application/json').send({ content });
|
|
669
758
|
}
|
|
670
759
|
catch {
|
|
671
760
|
// Path did not exist at that revision (added file).
|
|
672
|
-
resp.type('application/json').send({ content: '' });
|
|
761
|
+
return resp.type('application/json').send({ content: '' });
|
|
673
762
|
}
|
|
674
763
|
}
|
|
675
764
|
catch (err) {
|
|
676
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
765
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
677
766
|
}
|
|
678
767
|
});
|
|
679
768
|
// ==================== Discard ====================
|
|
@@ -694,29 +783,29 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
694
783
|
for (const file of removed) {
|
|
695
784
|
await rm(join(root, file), { force: true, recursive: true });
|
|
696
785
|
}
|
|
697
|
-
resp.type('application/json').send({ success: true });
|
|
786
|
+
return resp.type('application/json').send({ success: true });
|
|
698
787
|
}
|
|
699
788
|
catch (err) {
|
|
700
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
789
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
701
790
|
}
|
|
702
791
|
});
|
|
703
792
|
// ==================== Working Tree Actions ====================
|
|
704
793
|
app.post('/api/reset', async (_req, resp) => {
|
|
705
794
|
try {
|
|
706
795
|
await git.raw(['reset', '--hard', 'HEAD']);
|
|
707
|
-
resp.type('application/json').send({ ok: true });
|
|
796
|
+
return resp.type('application/json').send({ ok: true });
|
|
708
797
|
}
|
|
709
798
|
catch (err) {
|
|
710
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
799
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
711
800
|
}
|
|
712
801
|
});
|
|
713
802
|
app.post('/api/clean', async (_req, resp) => {
|
|
714
803
|
try {
|
|
715
804
|
await git.raw(['clean', '-fd']);
|
|
716
|
-
resp.type('application/json').send({ ok: true });
|
|
805
|
+
return resp.type('application/json').send({ ok: true });
|
|
717
806
|
}
|
|
718
807
|
catch (err) {
|
|
719
|
-
resp.status(400).type('application/json').send({ error: err.message });
|
|
808
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
720
809
|
}
|
|
721
810
|
});
|
|
722
811
|
const address = await app.listen({ port, ...(host ? { host } : {}) });
|