guito 0.5.3 → 0.6.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/CHANGELOG.md +41 -0
- package/CONTRIBUTION.md +6 -0
- package/README.md +8 -1
- package/bin/guito-server.js +132 -68
- package/bin/ui/3rdpartylicenses.txt +26 -0
- package/bin/ui/browser/index.html +1 -1
- package/bin/ui/browser/main-GMXCXTX7.js +6 -0
- package/bin/ui/browser/worker-O4G7QTZ6.js +1 -0
- package/bin/working-tree.js +141 -0
- package/package.json +5 -2
- package/playwright.config.mjs +9 -0
- package/bin/ui/browser/main-A5ZTTMFS.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Guito are documented in this file. The project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and this changelog is structured around [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
4
|
|
|
5
|
+
## [0.6.0] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Stage and unstage individual files, selected ranges, or all changes from the Uncommitted changes panel, with Ctrl/Cmd and Shift selection and separate index/working-tree diff previews.
|
|
10
|
+
- Write a commit subject and optional description, then commit staged changes without leaving Guito. Drafts survive panel closure and failed commits during the session.
|
|
11
|
+
- Rename and delete local branches from the branch context menu. Deleting the checked-out branch is disabled, the branches dropdown follows renames, and it falls back to "Show All" when the selected branch is deleted.
|
|
12
|
+
- Navigate commit search results with up/down arrows (or Enter and Shift+Enter in the search box) instead of filtering the list. Matches are highlighted in place, the counter shows the focused match, and navigating to a match outside the loaded history fetches more commits until it can be selected and scrolled into view.
|
|
13
|
+
- Choose the reset type when resetting the current branch to a commit. A dialog offers soft (keep changes staged), mixed (keep changes unstaged), and hard (discard all changes) resets.
|
|
14
|
+
- Add a tag context menu with view details, delete tag, push tag, create archive, and copy tag name actions. Pushing a tag uploads it to the remote through the new `/api/tag/push` endpoint.
|
|
15
|
+
- Clear the commit search with a dedicated button.
|
|
16
|
+
- Persist column widths and the commit message draft across sessions using browser storage.
|
|
17
|
+
- Stash all, staged, or unstaged changes directly from the Uncommitted changes panel. Staged-only stashes keep unstaged work in place, and unstaged stashes keep staged changes staged.
|
|
18
|
+
- Discard selected or all unstaged changes from the Uncommitted changes panel after a confirmation dialog. Discarding unstaged edits preserves staged changes for files with both.
|
|
19
|
+
- Added browser regressions and Git integration coverage for search, table alignment, staging, committing, and branch management.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Remove the per-file stage/unstage buttons from file rows in the Uncommitted changes panel; staging and unstaging now use the selection and bulk actions in each section header.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Keep the table header, uncommitted row, graph, and commit columns aligned while scrolling horizontally or resizing columns.
|
|
28
|
+
- Restore virtual rows after searching or clearing empty results; show loading feedback during search, refresh, history loading, and graph computation.
|
|
29
|
+
- Recover pending graph work when its worker fails, ignore superseded search requests, and allow explicit retries without automatic failure loops.
|
|
30
|
+
- Preserve the last known working status after errors and support initial commits, partial staging, renamed/deleted files, and literal filenames.
|
|
31
|
+
- Reject blank subjects, empty-index commits, and unresolved conflicts, and prevent overlapping UI mutations.
|
|
32
|
+
- Keep commit graph edges connected across viewport boundaries while scrolling, so long merge edges no longer disappear from view.
|
|
33
|
+
|
|
34
|
+
## [0.5.4] - 2026-09-06
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- The commit table now renders only the visible rows with virtual scrolling, so scrolling stays smooth no matter how much history is loaded.
|
|
39
|
+
- The commit graph is drawn as a viewport-sized overlay that tracks the scroll position, replacing the full-height SVG canvas.
|
|
40
|
+
- Lane assignment for large histories (over 2,000 commits) runs in a web worker, keeping the interface responsive after "Load all" on repositories with 10k+ commits.
|
|
41
|
+
- The commit list payload omits commit bodies, roughly halving its size; the detail pane fetches the body on demand when a commit is opened.
|
|
42
|
+
- Commit body search now runs on the server, preserving full-history body matching with the trimmed list payload.
|
|
43
|
+
- Working-tree actions (discard, reset, clean) refresh only the working-tree status instead of refetching the commit history, branches, and repository info.
|
|
44
|
+
- Refreshing or searching aborts superseded in-flight requests instead of letting stale responses race newer ones.
|
|
45
|
+
|
|
5
46
|
## [0.5.3] - 2026-09-06
|
|
6
47
|
|
|
7
48
|
### Changed
|
package/CONTRIBUTION.md
CHANGED
|
@@ -96,8 +96,14 @@ Run the same core checks used by the release workflow:
|
|
|
96
96
|
npm run check
|
|
97
97
|
npm run build
|
|
98
98
|
npm test
|
|
99
|
+
npx playwright install chromium --only-shell
|
|
100
|
+
npm run test:browser
|
|
99
101
|
```
|
|
100
102
|
|
|
103
|
+
The browser suite uses a disposable repository and authenticated API fixtures. Failure traces are saved under `test-results/`.
|
|
104
|
+
|
|
105
|
+
On Windows, after building, `node tests/browser/vscode-smoke.mjs` also opens an isolated Extension Development Host, stages a fixture file, and commits it through the authenticated webview. Set `GUITO_CODE_EXE` if VS Code is installed outside its default per-user location. The test closes its host and removes its temporary profile and repository afterward.
|
|
106
|
+
|
|
101
107
|
- Add or update tests when changing server routes, authentication, repository selection, or Git behavior.
|
|
102
108
|
- Test UI changes against a disposable repository with branches, merges, remote references, working-tree changes, and untracked files where relevant.
|
|
103
109
|
- Exercise destructive operations only in a disposable repository.
|
package/README.md
CHANGED
|
@@ -9,12 +9,13 @@ The name combines **GUI** and **Git**: **G**u**IT**o. It is also a [Portuguese w
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Visual commit graph with branch and tag references.
|
|
12
|
-
- Commit search by subject, author, or hash.
|
|
12
|
+
- Commit search by subject, body, author, or hash, with loading feedback and retries.
|
|
13
13
|
- Local and remote branch filtering.
|
|
14
14
|
- Commit and working-tree diffs powered by the Monaco editor, with inline and side-by-side layouts.
|
|
15
15
|
- Fetch, pull, pull with rebase, push, and sync actions.
|
|
16
16
|
- Context actions for creating branches and tags, checking out commits, cherry-picking, reverting, merging, rebasing, resetting, and exporting archives.
|
|
17
17
|
- Working-tree actions for stashing changes, resetting tracked changes, and cleaning untracked files.
|
|
18
|
+
- Stage and unstage files with multi-selection, preview staged and unstaged diffs, and commit staged changes.
|
|
18
19
|
- Browser-based CLI and a VS Code extension with local, Remote SSH, Dev Container, and Codespaces support.
|
|
19
20
|
|
|
20
21
|
> [!CAUTION]
|
|
@@ -48,6 +49,12 @@ After installing the extension, open a trusted workspace containing a Git reposi
|
|
|
48
49
|
|
|
49
50
|
In a multi-root workspace, Guito asks which repository to open. Git must be available on the local or remote VS Code extension host.
|
|
50
51
|
|
|
52
|
+
## Stage and commit changes
|
|
53
|
+
|
|
54
|
+
Click **Uncommitted changes** to open the staged and unstaged file lists. Click to select a file, Ctrl/Cmd-click to toggle individual files, or Shift-click to select a range. Use the per-file buttons, selected-file actions, or **Stage all** / **Unstage all**. The separate **Diff** button previews only the changes in that list.
|
|
55
|
+
|
|
56
|
+
Enter a commit message and optional description, then choose **Commit staged changes**. Only staged changes are committed; unstaged edits remain on disk. Commit drafts stay available when you close and reopen the panel during the session.
|
|
57
|
+
|
|
51
58
|
## CLI options
|
|
52
59
|
|
|
53
60
|
```text
|
package/bin/guito-server.js
CHANGED
|
@@ -8,6 +8,7 @@ import fastifyCors from '@fastify/cors';
|
|
|
8
8
|
import fastifyCompress from '@fastify/compress';
|
|
9
9
|
import { simpleGit } from 'simple-git';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
|
+
import { workingTree } from './working-tree.js';
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 8080, apiToken, }) {
|
|
13
14
|
// Initialize server
|
|
@@ -128,6 +129,14 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
128
129
|
files.push(current);
|
|
129
130
|
return files;
|
|
130
131
|
}
|
|
132
|
+
const working = workingTree(git, parseUnifiedDiff);
|
|
133
|
+
// Keep validation and the corresponding index mutation in one operation.
|
|
134
|
+
let mutationQueue = Promise.resolve();
|
|
135
|
+
const mutate = (action) => {
|
|
136
|
+
const result = mutationQueue.then(action);
|
|
137
|
+
mutationQueue = result.catch(() => { });
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
131
140
|
const repoRoot = async () => (await git.revparse(['--show-toplevel'])).trim();
|
|
132
141
|
// ==================== Repository ====================
|
|
133
142
|
app.get('/api/repo', async (_req, resp) => {
|
|
@@ -147,13 +156,15 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
147
156
|
const skip = Number.parseInt(query.skip ?? '', 10);
|
|
148
157
|
// Build simple-git options conditionally: an explicit key with an
|
|
149
158
|
// undefined value would still be emitted as a bare command flag.
|
|
159
|
+
// The commit body is intentionally omitted: it roughly doubles the
|
|
160
|
+
// payload and is only needed when a commit is opened, so it is served
|
|
161
|
+
// on demand by /api/commit/detail instead.
|
|
150
162
|
const options = {
|
|
151
163
|
format: {
|
|
152
164
|
hash: '%H',
|
|
153
165
|
date: '%aI',
|
|
154
166
|
message: '%s',
|
|
155
167
|
refs: '%D',
|
|
156
|
-
body: '%b',
|
|
157
168
|
author_name: '%aN',
|
|
158
169
|
author_email: '%aE',
|
|
159
170
|
parents: '%P',
|
|
@@ -188,10 +199,42 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
188
199
|
return resp.status(400).type('application/json').send({ error: err.message });
|
|
189
200
|
}
|
|
190
201
|
});
|
|
202
|
+
// Full-text commit search. The list payload omits commit bodies, so body
|
|
203
|
+
// matching runs here (git --grep covers subject and body together) and the
|
|
204
|
+
// client unions the resulting hashes with its local subject/author/hash
|
|
205
|
+
// matches over the loaded history.
|
|
206
|
+
app.get('/api/commits/search', async (req, resp) => {
|
|
207
|
+
const query = String((req.query ?? {}).query ?? '').trim();
|
|
208
|
+
if (!query) {
|
|
209
|
+
return resp.type('application/json').send({ hashes: [] });
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const raw = await git.raw([
|
|
213
|
+
'log',
|
|
214
|
+
'--all',
|
|
215
|
+
'-i',
|
|
216
|
+
'--fixed-strings',
|
|
217
|
+
`--grep=${query}`,
|
|
218
|
+
'--format=%H',
|
|
219
|
+
]);
|
|
220
|
+
return resp.type('application/json').send({
|
|
221
|
+
hashes: raw
|
|
222
|
+
.split('\n')
|
|
223
|
+
.map((hash) => hash.trim())
|
|
224
|
+
.filter(Boolean),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (/does not have any commits yet|unknown revision|bad default revision/i.test(err.message ?? '')) {
|
|
229
|
+
return resp.type('application/json').send({ hashes: [] });
|
|
230
|
+
}
|
|
231
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
191
234
|
app.post('/api/commit', async (req, resp) => {
|
|
192
235
|
try {
|
|
193
|
-
const { message, description } = req.body;
|
|
194
|
-
await
|
|
236
|
+
const { message, description } = req.body ?? {};
|
|
237
|
+
await mutate(() => working.commit(message, description));
|
|
195
238
|
return resp.type('application/json').send({ success: true });
|
|
196
239
|
}
|
|
197
240
|
catch (err) {
|
|
@@ -241,7 +284,8 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
241
284
|
});
|
|
242
285
|
app.post('/api/reset-commit', async (req, resp) => {
|
|
243
286
|
try {
|
|
244
|
-
|
|
287
|
+
const mode = ['soft', 'mixed', 'hard'].includes(req.body?.mode) ? req.body.mode : 'hard';
|
|
288
|
+
await git.raw(['reset', `--${mode}`, req.body.commit]);
|
|
245
289
|
return resp.type('application/json').send({ success: true });
|
|
246
290
|
}
|
|
247
291
|
catch (err) {
|
|
@@ -260,10 +304,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
260
304
|
});
|
|
261
305
|
app.post('/api/stage', async (req, resp) => {
|
|
262
306
|
try {
|
|
263
|
-
|
|
264
|
-
if (Array.isArray(files) && files.length > 0) {
|
|
265
|
-
await git.add(files);
|
|
266
|
-
}
|
|
307
|
+
await mutate(() => working.stage(req.body?.files));
|
|
267
308
|
return resp.type('application/json').send({ success: true });
|
|
268
309
|
}
|
|
269
310
|
catch (err) {
|
|
@@ -272,10 +313,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
272
313
|
});
|
|
273
314
|
app.post('/api/unstage', async (req, resp) => {
|
|
274
315
|
try {
|
|
275
|
-
|
|
276
|
-
if (Array.isArray(files) && files.length > 0) {
|
|
277
|
-
await git.reset(['HEAD', ...files]);
|
|
278
|
-
}
|
|
316
|
+
await mutate(() => working.unstage(req.body?.files));
|
|
279
317
|
return resp.type('application/json').send({ success: true });
|
|
280
318
|
}
|
|
281
319
|
catch (err) {
|
|
@@ -441,8 +479,22 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
441
479
|
});
|
|
442
480
|
app.post('/api/stash/save', async (req, resp) => {
|
|
443
481
|
try {
|
|
444
|
-
const { message } = req.body;
|
|
445
|
-
|
|
482
|
+
const { message, scope } = req.body ?? {};
|
|
483
|
+
// 'staged' → index only (--staged, git ≥ 2.35); 'unstaged' → working tree only
|
|
484
|
+
// (--keep-index leaves staged changes staged); 'all' → index + working tree.
|
|
485
|
+
// Untracked files ride along for 'all'/'unstaged' since the panel lists them as unstaged.
|
|
486
|
+
const args = ['push'];
|
|
487
|
+
if (scope === 'staged') {
|
|
488
|
+
args.push('--staged');
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
if (scope === 'unstaged')
|
|
492
|
+
args.push('--keep-index');
|
|
493
|
+
args.push('-u');
|
|
494
|
+
}
|
|
495
|
+
if (message)
|
|
496
|
+
args.push('-m', message);
|
|
497
|
+
await git.stash(args);
|
|
446
498
|
return resp.type('application/json').send({ success: true });
|
|
447
499
|
}
|
|
448
500
|
catch (err) {
|
|
@@ -524,6 +576,19 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
524
576
|
return resp.status(400).type('application/json').send({ error: err.message });
|
|
525
577
|
}
|
|
526
578
|
});
|
|
579
|
+
app.post('/api/tag/push', async (req, resp) => {
|
|
580
|
+
try {
|
|
581
|
+
const { name, remote } = req.body;
|
|
582
|
+
if (!name) {
|
|
583
|
+
return resp.status(400).type('application/json').send({ error: 'name required' });
|
|
584
|
+
}
|
|
585
|
+
await git.push(remote || 'origin', `refs/tags/${name}`);
|
|
586
|
+
return resp.type('application/json').send({ success: true });
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
590
|
+
}
|
|
591
|
+
});
|
|
527
592
|
// ==================== Remote Operations ====================
|
|
528
593
|
app.get('/api/fetch', async (_req, resp) => {
|
|
529
594
|
try {
|
|
@@ -574,6 +639,41 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
574
639
|
return resp.status(400).type('application/json').send({ error: err.message });
|
|
575
640
|
}
|
|
576
641
|
});
|
|
642
|
+
// ==================== Commit Detail ====================
|
|
643
|
+
app.post('/api/commit/detail', async (req, resp) => {
|
|
644
|
+
try {
|
|
645
|
+
const { hash } = req.body;
|
|
646
|
+
if (!hash) {
|
|
647
|
+
return resp.status(400).type('application/json').send({ error: 'hash required' });
|
|
648
|
+
}
|
|
649
|
+
// Fields are NUL-separated; %b may span lines and contain anything
|
|
650
|
+
// except NUL, so it cannot be split naively by newline.
|
|
651
|
+
const raw = await git.raw([
|
|
652
|
+
'show',
|
|
653
|
+
'-s',
|
|
654
|
+
'--format=%H%x00%aI%x00%s%x00%b%x00%aN%x00%aE%x00%P',
|
|
655
|
+
hash,
|
|
656
|
+
]);
|
|
657
|
+
const [fullHash, date, message, body, authorName, authorEmail, parents] = raw
|
|
658
|
+
.trimEnd()
|
|
659
|
+
.split('\u0000');
|
|
660
|
+
return resp.type('application/json').send({
|
|
661
|
+
hash: fullHash,
|
|
662
|
+
date,
|
|
663
|
+
message,
|
|
664
|
+
refs: '',
|
|
665
|
+
body: body ?? '',
|
|
666
|
+
author_name: authorName,
|
|
667
|
+
author_email: authorEmail,
|
|
668
|
+
parents: String(parents ?? '')
|
|
669
|
+
.split(' ')
|
|
670
|
+
.filter(Boolean),
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
return resp.status(400).type('application/json').send({ error: err.message });
|
|
675
|
+
}
|
|
676
|
+
});
|
|
577
677
|
// ==================== Diff Viewer ====================
|
|
578
678
|
app.post('/api/commit/diff', async (req, resp) => {
|
|
579
679
|
try {
|
|
@@ -600,57 +700,8 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
600
700
|
// ==================== Working Changes ====================
|
|
601
701
|
app.get('/api/working-changes', async (_req, resp) => {
|
|
602
702
|
try {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const rawDiff = await git.raw(['diff', 'HEAD', '--no-color', '--find-renames']);
|
|
606
|
-
const files = parseUnifiedDiff(rawDiff);
|
|
607
|
-
// Untracked files are not part of `git diff` — add them as new files.
|
|
608
|
-
const status = await git.status();
|
|
609
|
-
for (const file of status.not_added ?? []) {
|
|
610
|
-
let isBinary = false;
|
|
611
|
-
let content = '';
|
|
612
|
-
try {
|
|
613
|
-
const buffer = await readFile(join(root, file));
|
|
614
|
-
if (buffer.includes(0)) {
|
|
615
|
-
isBinary = true;
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
content = buffer.toString('utf8');
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
catch {
|
|
622
|
-
content = '';
|
|
623
|
-
}
|
|
624
|
-
if (isBinary) {
|
|
625
|
-
files.push({
|
|
626
|
-
path: file,
|
|
627
|
-
oldPath: '',
|
|
628
|
-
status: 'binary',
|
|
629
|
-
lines: [],
|
|
630
|
-
additions: 0,
|
|
631
|
-
deletions: 0,
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
const lines = content.length > 0 ? content.split('\n') : [];
|
|
636
|
-
files.push({
|
|
637
|
-
path: file,
|
|
638
|
-
oldPath: '',
|
|
639
|
-
status: 'added',
|
|
640
|
-
lines: lines.map((text, index) => ({ type: 'add', newLine: index + 1, text })),
|
|
641
|
-
additions: lines.length,
|
|
642
|
-
deletions: 0,
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
const stagedRaw = await git.raw(['diff', '--name-only', '--cached']);
|
|
647
|
-
const unstagedRaw = await git.raw(['diff', '--name-only']);
|
|
648
|
-
return resp.type('application/json').send({
|
|
649
|
-
files,
|
|
650
|
-
staged: stagedRaw.split('\n').filter(Boolean),
|
|
651
|
-
unstaged: unstagedRaw.split('\n').filter(Boolean),
|
|
652
|
-
untracked: status.not_added ?? [],
|
|
653
|
-
});
|
|
703
|
+
await mutationQueue;
|
|
704
|
+
return resp.type('application/json').send(await working.snapshot());
|
|
654
705
|
}
|
|
655
706
|
catch (err) {
|
|
656
707
|
return resp.status(400).type('application/json').send({ error: err.message });
|
|
@@ -686,7 +737,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
686
737
|
return resp.type('application/json').send({ content: '' });
|
|
687
738
|
}
|
|
688
739
|
try {
|
|
689
|
-
const content = await git.raw(['show', `${ref}:${path}`]);
|
|
740
|
+
const content = await git.raw(['show', ref === 'INDEX' ? `:${path}` : `${ref}:${path}`]);
|
|
690
741
|
if (content.includes('\u0000')) {
|
|
691
742
|
return resp.type('application/json').send({ binary: true, content: '' });
|
|
692
743
|
}
|
|
@@ -704,7 +755,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
704
755
|
// ==================== Discard ====================
|
|
705
756
|
app.post('/api/discard', async (req, resp) => {
|
|
706
757
|
try {
|
|
707
|
-
const { files } = req.body;
|
|
758
|
+
const { files, mode } = req.body;
|
|
708
759
|
if (!Array.isArray(files) || files.length === 0) {
|
|
709
760
|
return resp.status(400).type('application/json').send({ error: 'files required' });
|
|
710
761
|
}
|
|
@@ -714,7 +765,20 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
714
765
|
const tracked = files.filter((file) => !untracked.has(file));
|
|
715
766
|
const removed = files.filter((file) => untracked.has(file));
|
|
716
767
|
if (tracked.length > 0) {
|
|
717
|
-
|
|
768
|
+
if (mode === 'unstaged') {
|
|
769
|
+
// Restore from the index so staged changes survive; unmerged (conflicted)
|
|
770
|
+
// paths cannot be restored from the index and fall back to HEAD.
|
|
771
|
+
const conflicted = new Set(status.conflicted ?? []);
|
|
772
|
+
const restorable = tracked.filter((file) => !conflicted.has(file));
|
|
773
|
+
const unmerged = tracked.filter((file) => conflicted.has(file));
|
|
774
|
+
if (restorable.length > 0)
|
|
775
|
+
await git.raw(['checkout', '--', ...restorable]);
|
|
776
|
+
if (unmerged.length > 0)
|
|
777
|
+
await git.raw(['checkout', 'HEAD', '--', ...unmerged]);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
await git.raw(['checkout', 'HEAD', '--', ...tracked]);
|
|
781
|
+
}
|
|
718
782
|
}
|
|
719
783
|
for (const file of removed) {
|
|
720
784
|
await rm(join(root, file), { force: true, recursive: true });
|
|
@@ -300,6 +300,32 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
300
300
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
301
301
|
THE SOFTWARE.
|
|
302
302
|
|
|
303
|
+
--------------------------------------------------------------------------------
|
|
304
|
+
Package: @angular/cdk
|
|
305
|
+
License: "MIT"
|
|
306
|
+
|
|
307
|
+
The MIT License
|
|
308
|
+
|
|
309
|
+
Copyright (c) 2025 Google LLC.
|
|
310
|
+
|
|
311
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
312
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
313
|
+
in the Software without restriction, including without limitation the rights
|
|
314
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
315
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
316
|
+
furnished to do so, subject to the following conditions:
|
|
317
|
+
|
|
318
|
+
The above copyright notice and this permission notice shall be included in
|
|
319
|
+
all copies or substantial portions of the Software.
|
|
320
|
+
|
|
321
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
322
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
323
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
324
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
325
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
326
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
327
|
+
THE SOFTWARE.
|
|
328
|
+
|
|
303
329
|
--------------------------------------------------------------------------------
|
|
304
330
|
Package: zone.js
|
|
305
331
|
License: "MIT"
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
<style>:root{--bg:#1e1e1e;--bg-alt:#232323;--bg-hover:#2a2d2e;--border:#3c3c3c;--text:#cccccc;--text-bright:#e8e8e8;--text-dim:#9d9d9d;--accent:#3794ff;--red:#f14c4c;--mono:ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace}*{box-sizing:border-box}html,body{height:100%;margin:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:13px;color:var(--text);background:var(--bg);overflow:hidden}</style><link rel="stylesheet" href="styles-I7K2YZTW.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-I7K2YZTW.css"></noscript></head>
|
|
10
10
|
<body>
|
|
11
11
|
<app-root></app-root>
|
|
12
|
-
<script src="polyfills-5CFQRCPP.js" type="module"></script><script src="main-
|
|
12
|
+
<script src="polyfills-5CFQRCPP.js" type="module"></script><script src="main-GMXCXTX7.js" type="module"></script></body>
|
|
13
13
|
</html>
|