guito 0.2.1 → 0.3.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.
Files changed (102) hide show
  1. package/QUICK_START.md +223 -0
  2. package/README.md +4 -0
  3. package/bin/index.js +400 -50
  4. package/bin/ui/assets/abap-DLDM7-KI.js +1 -0
  5. package/bin/ui/assets/apex-DNDY2TF8.js +1 -0
  6. package/bin/ui/assets/azcli-Y6nb8tq_.js +1 -0
  7. package/bin/ui/assets/bat-BwHxbl9M.js +1 -0
  8. package/bin/ui/assets/bicep-CFznDFnq.js +2 -0
  9. package/bin/ui/assets/cameligo-Bf6VGUru.js +1 -0
  10. package/bin/ui/assets/clojure-Dnu-v4kV.js +1 -0
  11. package/bin/ui/assets/codicon-ngg6Pgfi.ttf +0 -0
  12. package/bin/ui/assets/coffee-Bd8akH9Z.js +1 -0
  13. package/bin/ui/assets/cpp-BbWJElDN.js +1 -0
  14. package/bin/ui/assets/csharp-Co3qMtFm.js +1 -0
  15. package/bin/ui/assets/csp-D-4FJmMZ.js +1 -0
  16. package/bin/ui/assets/css-DdJfP1eB.js +3 -0
  17. package/bin/ui/assets/css.worker-BM8aVs8W.js +93 -0
  18. package/bin/ui/assets/cssMode-D6UF0pvT.js +1 -0
  19. package/bin/ui/assets/cypher-cTPe9QuQ.js +1 -0
  20. package/bin/ui/assets/dart-BOtBlQCF.js +1 -0
  21. package/bin/ui/assets/dockerfile-BG73LgW2.js +1 -0
  22. package/bin/ui/assets/ecl-BEgZUVRK.js +1 -0
  23. package/bin/ui/assets/elixir-BkW5O-1t.js +1 -0
  24. package/bin/ui/assets/flow9-BeJ5waoc.js +1 -0
  25. package/bin/ui/assets/freemarker2-CuDVhtVe.js +3 -0
  26. package/bin/ui/assets/fsharp-PahG7c26.js +1 -0
  27. package/bin/ui/assets/go-acbASCJo.js +1 -0
  28. package/bin/ui/assets/graphql-BxJiqAUM.js +1 -0
  29. package/bin/ui/assets/handlebars-BT9qXX0t.js +1 -0
  30. package/bin/ui/assets/hcl-DtV1sZF8.js +1 -0
  31. package/bin/ui/assets/html-uO5f1agE.js +1 -0
  32. package/bin/ui/assets/html.worker-CtwWtMPk.js +470 -0
  33. package/bin/ui/assets/htmlMode-4Oyo7HnK.js +1 -0
  34. package/bin/ui/assets/index-B2cKC_ky.js +1634 -0
  35. package/bin/ui/assets/index-tfc2bLEr.css +1 -0
  36. package/bin/ui/assets/ini-Kd9XrMLS.js +1 -0
  37. package/bin/ui/assets/java-CXBNlu9o.js +1 -0
  38. package/bin/ui/assets/javascript-MVQT3ZBU.js +1 -0
  39. package/bin/ui/assets/json.worker-BfEDn18l.js +58 -0
  40. package/bin/ui/assets/jsonMode-BkwogsZa.js +7 -0
  41. package/bin/ui/assets/julia-cl7-CwDS.js +1 -0
  42. package/bin/ui/assets/kotlin-s7OhZKlX.js +1 -0
  43. package/bin/ui/assets/less-9HpZscsL.js +2 -0
  44. package/bin/ui/assets/lexon-OrD6JF1K.js +1 -0
  45. package/bin/ui/assets/liquid-C7__O8so.js +1 -0
  46. package/bin/ui/assets/lspLanguageFeatures-CjaZl6Ei.js +4 -0
  47. package/bin/ui/assets/lua-Cyyb5UIc.js +1 -0
  48. package/bin/ui/assets/m3-B8OfTtLu.js +1 -0
  49. package/bin/ui/assets/markdown-BFxVWTOG.js +1 -0
  50. package/bin/ui/assets/mdx-DpX13nwZ.js +1 -0
  51. package/bin/ui/assets/mips-CiqrrVzr.js +1 -0
  52. package/bin/ui/assets/msdax-DmeGPVcC.js +1 -0
  53. package/bin/ui/assets/mysql-C_tMU-Nz.js +1 -0
  54. package/bin/ui/assets/objective-c-BDtDVThU.js +1 -0
  55. package/bin/ui/assets/pascal-vHIfCaH5.js +1 -0
  56. package/bin/ui/assets/pascaligo-DtZ0uQbO.js +1 -0
  57. package/bin/ui/assets/perl-Ub6l9XKa.js +1 -0
  58. package/bin/ui/assets/pgsql-BlNEE0v7.js +1 -0
  59. package/bin/ui/assets/php-BBUBE1dy.js +1 -0
  60. package/bin/ui/assets/pla-DSh2-awV.js +1 -0
  61. package/bin/ui/assets/postiats-CocnycG-.js +1 -0
  62. package/bin/ui/assets/powerquery-tScXyioY.js +1 -0
  63. package/bin/ui/assets/powershell-COWaemsV.js +1 -0
  64. package/bin/ui/assets/protobuf-Brw8urJB.js +2 -0
  65. package/bin/ui/assets/pug-8SOpv6rk.js +1 -0
  66. package/bin/ui/assets/python-DSbw_Utg.js +1 -0
  67. package/bin/ui/assets/qsharp-Bw9ernYp.js +1 -0
  68. package/bin/ui/assets/r-j7ic8hl3.js +1 -0
  69. package/bin/ui/assets/razor-BrjmxdA-.js +1 -0
  70. package/bin/ui/assets/redis-Bu5POkcn.js +1 -0
  71. package/bin/ui/assets/redshift-Bs9aos_-.js +1 -0
  72. package/bin/ui/assets/restructuredtext-CqXO7rUv.js +1 -0
  73. package/bin/ui/assets/ruby-zBfavPgS.js +1 -0
  74. package/bin/ui/assets/rust-BzKRNQWT.js +1 -0
  75. package/bin/ui/assets/sb-BBc9UKZt.js +1 -0
  76. package/bin/ui/assets/scala-D9hQfWCl.js +1 -0
  77. package/bin/ui/assets/scheme-BPhDTwHR.js +1 -0
  78. package/bin/ui/assets/scss-CBJaRo0y.js +3 -0
  79. package/bin/ui/assets/shell-DiJ1NA_G.js +1 -0
  80. package/bin/ui/assets/solidity-Db0IVjzk.js +1 -0
  81. package/bin/ui/assets/sophia-CnS9iZB_.js +1 -0
  82. package/bin/ui/assets/sparql-CJmd_6j2.js +1 -0
  83. package/bin/ui/assets/sql-ClhHkBeG.js +1 -0
  84. package/bin/ui/assets/st-CHwy0fLd.js +1 -0
  85. package/bin/ui/assets/svelte-CKrU6DOB.css +18683 -18683
  86. package/bin/ui/assets/svelte-dark-7cNU_wYW.css +18683 -18683
  87. package/bin/ui/assets/swift-Bqt4WxQ4.js +3 -0
  88. package/bin/ui/assets/systemverilog-Bs9z6M-B.js +1 -0
  89. package/bin/ui/assets/tcl-Dm6ycUr_.js +1 -0
  90. package/bin/ui/assets/ts.worker-a4tsX_kw.js +67731 -0
  91. package/bin/ui/assets/tsMode-CFWjBB2T.js +11 -0
  92. package/bin/ui/assets/twig-Csy3S7wG.js +1 -0
  93. package/bin/ui/assets/typescript-CgpPokS_.js +1 -0
  94. package/bin/ui/assets/typespec-Btyra-wh.js +1 -0
  95. package/bin/ui/assets/vb-Db0cS2oM.js +1 -0
  96. package/bin/ui/assets/wgsl-BTesnYfV.js +298 -0
  97. package/bin/ui/assets/xml-tZACxwqM.js +1 -0
  98. package/bin/ui/assets/yaml-JpDON7Aq.js +1 -0
  99. package/bin/ui/index.html +3 -3
  100. package/package.json +7 -1
  101. package/bin/ui/assets/index-6fEMLduw.js +0 -7876
  102. package/bin/ui/assets/index-baVP32pl.css +0 -1
package/QUICK_START.md ADDED
@@ -0,0 +1,223 @@
1
+ # Guito - Quick Reference Guide
2
+
3
+ ## Getting Started
4
+
5
+ ### Launch the App
6
+ ```bash
7
+ npm run dev # Development with hot reload
8
+ npm run build:start # Build and start production
9
+ npm start # Start pre-built server
10
+ ```
11
+
12
+ The app opens automatically in your browser at `http://localhost:8080`
13
+
14
+ ## Features by Category
15
+
16
+ ### 📝 Staging & Commits
17
+ **Header Icon:** Check Circle
18
+
19
+ 1. **Stage/Unstage Files**
20
+ - Click "Stage/Unstage Files" button
21
+ - Select files to stage or unstage
22
+ - Supports partial file staging via patch mode
23
+
24
+ 2. **Create Commit**
25
+ - Click "Commit" button
26
+ - Enter commit message (required)
27
+ - Add optional description
28
+ - Click "Commit" to save
29
+
30
+ 3. **Amend Last Commit**
31
+ - Click "Amend" button
32
+ - Modify the message/description
33
+ - Changes are applied to the last commit
34
+
35
+ 4. **Revert Commit**
36
+ - Click "Revert" button
37
+ - Enter commit hash to revert
38
+ - Creates new commit that undoes changes
39
+
40
+ ### 🌿 Branch Management
41
+ **Header Icon:** Fork
42
+
43
+ 1. **View & Switch Branches**
44
+ - Click "Branches" button
45
+ - Current branch highlighted in blue
46
+ - Click "Checkout" to switch branches
47
+ - Click delete icon to remove branches
48
+ - Click "New Branch" to create
49
+
50
+ 2. **Create Branch**
51
+ - Click "New Branch" in Branches dialog
52
+ - Enter branch name
53
+ - Branches from current commit
54
+
55
+ 3. **Merge Branch**
56
+ - Click "Merge" button
57
+ - Enter branch name to merge
58
+ - Current branch gets merged changes
59
+
60
+ 4. **Rebase Branch**
61
+ - Click "Rebase" button
62
+ - Enter branch to rebase onto
63
+ - Replays current commits on target branch
64
+
65
+ ### 📦 Stash
66
+ **Header Icon:** Inventory Box
67
+
68
+ 1. **Create Stash**
69
+ - Click "Stash" button
70
+ - Enter description for changes
71
+ - Click "Save"
72
+ - Working directory is cleaned
73
+
74
+ 2. **Restore from Stash**
75
+ - Click "Stash" button
76
+ - Click "Apply" to keep stash or "Pop" to remove it
77
+ - Changes are restored to working directory
78
+
79
+ 3. **Preview Stash**
80
+ - Click "Stash" button
81
+ - Click eye icon to see what's in stash
82
+ - Shows file diffs without applying
83
+
84
+ 4. **Delete Stash**
85
+ - Click "Stash" button
86
+ - Click delete icon to permanently remove
87
+
88
+ ### 🏷️ Tags
89
+ **Header Icon:** Label
90
+
91
+ 1. **Create Tag**
92
+ - Click "Tags" button
93
+ - Enter tag name
94
+ - Optionally check "Annotated tag" for message
95
+ - Click "Create Tag"
96
+
97
+ 2. **View Tags**
98
+ - Click "Tags" button
99
+ - All tags listed with delete option
100
+ - Tags mark specific commits for releases
101
+
102
+ 3. **Delete Tag**
103
+ - Click "Tags" button
104
+ - Click delete icon next to tag name
105
+
106
+ ### 🔄 Remote Operations
107
+ **Header Icons:** Cloud icons (Fetch, Pull, Push)
108
+
109
+ 1. **Fetch from Remote**
110
+ - Click "Fetch" button
111
+ - Updates remote tracking branches
112
+ - Safe operation - no local changes
113
+
114
+ 2. **Pull from Remote**
115
+ - Click "Pull" button
116
+ - Fetches and merges remote changes
117
+ - Equivalent to fetch + merge
118
+
119
+ 3. **Push to Remote**
120
+ - Click "Push" button
121
+ - Sends local commits to remote
122
+ - Updates remote branch
123
+
124
+ ## Header Layout
125
+
126
+ ```
127
+ [Menu] Guito Branch: main
128
+
129
+ [Staging] [Commits] [Branches] [Stash/Tags] [Remote]
130
+ 📋 📝 📝(Amend) ↩️ 🌿 🔀 ↻ 📦 🏷️ ☁️↓ ☁️↑ ☁️↔️
131
+ Stage Commit Amend Revert Branches Merge Rebase Stash Tags Fetch Pull Push
132
+ ```
133
+
134
+ ## Keyboard Tips
135
+
136
+ - **Commit Dialog**: Enter to submit (with valid message)
137
+ - **Selection**: Click checkboxes to select files
138
+ - **Dialogs**: Escape to close (on most)
139
+
140
+ ## Error Handling
141
+
142
+ All operations show error messages if something goes wrong:
143
+ - Missing required info (message, branch names)
144
+ - Git command failures
145
+ - Network issues with remote operations
146
+
147
+ ## Common Workflows
148
+
149
+ ### Create & Commit Work
150
+ 1. Make changes to files
151
+ 2. Click "Stage/Unstage Files"
152
+ 3. Select files you want to commit
153
+ 4. Click "Commit"
154
+ 5. Enter message and click "Commit"
155
+
156
+ ### Create a Feature Branch
157
+ 1. Click "Branches"
158
+ 2. Click "New Branch"
159
+ 3. Enter branch name (e.g., "feature/new-ui")
160
+ 4. Click "Create Branch"
161
+
162
+ ### Save Work Temporarily
163
+ 1. Click "Stash"
164
+ 2. Enter description
165
+ 3. Click "Save"
166
+ 4. Switch to another branch or pull
167
+ 5. Come back and click "Pop" to restore
168
+
169
+ ### Release with Tag
170
+ 1. Make sure commits are pushed
171
+ 2. Click "Tags"
172
+ 3. Enter tag name (e.g., "v1.0.0")
173
+ 4. Check "Annotated tag" for release notes
174
+ 5. Click "Create Tag"
175
+
176
+ ### Merge Completed Feature
177
+ 1. Click "Branches"
178
+ 2. Checkout main/master branch
179
+ 3. Go back to header
180
+ 4. Click "Merge"
181
+ 5. Enter feature branch name
182
+ 6. Resolve any conflicts if needed
183
+
184
+ ## Status Indicators
185
+
186
+ - **Branch Name (Blue, Bold)**: Currently checked out branch
187
+ - **Green Files**: Staged files ready to commit
188
+ - **(current)**: Shows which branch you're on
189
+ - **Preview**: Shows what changes are in a stash
190
+
191
+ ## Tips & Tricks
192
+
193
+ - Always review what you're staging before committing
194
+ - Use stash to save incomplete work before switching branches
195
+ - Annotated tags with messages are better for releases
196
+ - Fetch regularly to stay updated with remote changes
197
+ - Use descriptive commit messages and branch names
198
+
199
+ ## Troubleshooting
200
+
201
+ **"Checkout failed"**
202
+ - Ensure working directory is clean (stage/stash changes)
203
+ - Branch might not exist
204
+
205
+ **"Merge failed"**
206
+ - Might have conflicts to resolve
207
+ - Ensure both branches have commits
208
+
209
+ **"Push failed"**
210
+ - Check network connection
211
+ - Ensure you have permissions on remote
212
+ - May need to pull first
213
+
214
+ **"No changes to stage"**
215
+ - Working directory is clean
216
+ - All changes are already staged
217
+
218
+ ## Next Steps
219
+
220
+ - Explore stash for temporary work
221
+ - Use branches for different features
222
+ - Tag releases for easy reference
223
+ - Push/Pull regularly to stay in sync
package/README.md CHANGED
@@ -32,6 +32,10 @@ No arguments for now.
32
32
 
33
33
  ## Changelog
34
34
 
35
+ **Version 0.3:**
36
+
37
+ - added more git functionalities
38
+
35
39
  **Version 0.2:**
36
40
 
37
41
  - added header and table components
package/bin/index.js CHANGED
@@ -1,50 +1,400 @@
1
- #! /usr/bin/env node
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import fastify from 'fastify';
5
- import fastifyStatic from '@fastify/static';
6
- import fastifyCors from '@fastify/cors';
7
- import open from 'open';
8
- import simpleGit from 'simple-git';
9
- void (async function main() {
10
- // Calculate dirname
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- // Get port number
13
- const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === '--port');
14
- const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
15
- // Get if should not open browser
16
- const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === '--no-open') === -1;
17
- // Initialize server
18
- const app = fastify({
19
- logger: false,
20
- });
21
- // Register cors
22
- await app.register(fastifyCors);
23
- // Register static file provider
24
- app.register(fastifyStatic, {
25
- root: join(__dirname, 'ui'),
26
- });
27
- // Serve UI
28
- app.get('/', (_req, resp) => resp.sendFile('index.html'));
29
- // Initialize git lib
30
- const git = simpleGit(process.cwd());
31
- // Git Api
32
- app.get('/api/commits', (_req, resp) => {
33
- git.log({}, (err, data) => {
34
- resp.type('application/json').send(data.all);
35
- });
36
- });
37
- app.get('/api/fetch', (_req, resp) => {
38
- git.fetch({}, (err, data) => {
39
- resp.type('application/json').send();
40
- });
41
- });
42
- // Run the server
43
- app.listen({ port }, (err, address) => {
44
- if (err)
45
- throw err;
46
- console.log(`Visit "${address}" to see git explorer.`);
47
- if (shouldOpenBrowser)
48
- open(address);
49
- });
50
- })();
1
+ #! /usr/bin/env node
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import fastify from 'fastify';
5
+ import fastifyStatic from '@fastify/static';
6
+ import fastifyCors from '@fastify/cors';
7
+ import open from 'open';
8
+ import simpleGit from 'simple-git';
9
+ void (async function main() {
10
+ // Calculate dirname
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ // Get port number
13
+ const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === '--port');
14
+ const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
15
+ // Get if should not open browser
16
+ const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === '--no-open') === -1;
17
+ // Initialize server
18
+ const app = fastify({
19
+ logger: false,
20
+ });
21
+ // Register cors
22
+ await app.register(fastifyCors);
23
+ // Register static file provider
24
+ app.register(fastifyStatic, {
25
+ root: join(__dirname, 'ui'),
26
+ });
27
+ // Serve UI
28
+ app.get('/', (_req, resp) => resp.sendFile('index.html'));
29
+ // Initialize git lib
30
+ const git = simpleGit(process.cwd());
31
+ // ==================== Commits ====================
32
+ app.get('/api/commits', (_req, resp) => {
33
+ git.log({}, (err, data) => {
34
+ resp.type('application/json').send(data.all);
35
+ });
36
+ });
37
+ app.post('/api/commit', async (req, resp) => {
38
+ try {
39
+ const { message, description } = req.body;
40
+ await git.commit([message, ...(description ? [description] : [])], { '--allow-empty': null });
41
+ resp.type('application/json').send({ success: true });
42
+ }
43
+ catch (err) {
44
+ resp.status(400).type('application/json').send({ error: err.message });
45
+ }
46
+ });
47
+ app.post('/api/amend', async (req, resp) => {
48
+ try {
49
+ const { message, description } = req.body;
50
+ await git.commit([message, ...(description ? [description] : [])], {
51
+ '--amend': null,
52
+ '--no-edit': null,
53
+ });
54
+ resp.type('application/json').send({ success: true });
55
+ }
56
+ catch (err) {
57
+ resp.status(400).type('application/json').send({ error: err.message });
58
+ }
59
+ });
60
+ app.post('/api/revert', async (req, resp) => {
61
+ try {
62
+ const { commit } = req.body;
63
+ await git.revert(commit);
64
+ resp.type('application/json').send({ success: true });
65
+ }
66
+ catch (err) {
67
+ resp.status(400).type('application/json').send({ error: err.message });
68
+ }
69
+ });
70
+ // ==================== Staging ====================
71
+ app.get('/api/status', async (_req, resp) => {
72
+ try {
73
+ const status = await git.status();
74
+ resp.type('application/json').send(status);
75
+ }
76
+ catch (err) {
77
+ resp.status(400).type('application/json').send({ error: err.message });
78
+ }
79
+ });
80
+ app.post('/api/stage', async (req, resp) => {
81
+ try {
82
+ const { files } = req.body;
83
+ if (Array.isArray(files) && files.length > 0) {
84
+ await git.add(files);
85
+ }
86
+ resp.type('application/json').send({ success: true });
87
+ }
88
+ catch (err) {
89
+ resp.status(400).type('application/json').send({ error: err.message });
90
+ }
91
+ });
92
+ app.post('/api/unstage', async (req, resp) => {
93
+ try {
94
+ const { files } = req.body;
95
+ if (Array.isArray(files) && files.length > 0) {
96
+ await git.reset(['HEAD', ...files]);
97
+ }
98
+ resp.type('application/json').send({ success: true });
99
+ }
100
+ catch (err) {
101
+ resp.status(400).type('application/json').send({ error: err.message });
102
+ }
103
+ });
104
+ app.post('/api/stage-lines', async (req, resp) => {
105
+ try {
106
+ const { file, patch } = req.body;
107
+ // Stage specific lines using git add -p equivalent (patch mode)
108
+ await git.raw('add --patch', { input: patch });
109
+ resp.type('application/json').send({ success: true });
110
+ }
111
+ catch (err) {
112
+ resp.status(400).type('application/json').send({ error: err.message });
113
+ }
114
+ });
115
+ // ==================== Branches ====================
116
+ app.get('/api/branches', async (_req, resp) => {
117
+ try {
118
+ const branchSummary = await git.branchLocal();
119
+ resp.type('application/json').send(branchSummary);
120
+ }
121
+ catch (err) {
122
+ resp.status(400).type('application/json').send({ error: err.message });
123
+ }
124
+ });
125
+ app.post('/api/branch/create', async (req, resp) => {
126
+ try {
127
+ const { name, startPoint } = req.body;
128
+ await git.branch([...(startPoint ? [name, startPoint] : [name])]);
129
+ resp.type('application/json').send({ success: true });
130
+ }
131
+ catch (err) {
132
+ resp.status(400).type('application/json').send({ error: err.message });
133
+ }
134
+ });
135
+ app.post('/api/branch/delete', async (req, resp) => {
136
+ try {
137
+ const { name, force } = req.body;
138
+ await git.deleteLocalBranch(name, force);
139
+ resp.type('application/json').send({ success: true });
140
+ }
141
+ catch (err) {
142
+ resp.status(400).type('application/json').send({ error: err.message });
143
+ }
144
+ });
145
+ app.post('/api/branch/rename', async (req, resp) => {
146
+ try {
147
+ const { oldName, newName } = req.body;
148
+ await git.branch(['-m', oldName, newName]);
149
+ resp.type('application/json').send({ success: true });
150
+ }
151
+ catch (err) {
152
+ resp.status(400).type('application/json').send({ error: err.message });
153
+ }
154
+ });
155
+ app.post('/api/checkout', async (req, resp) => {
156
+ try {
157
+ const { ref } = req.body;
158
+ await git.checkout(ref);
159
+ resp.type('application/json').send({ success: true });
160
+ }
161
+ catch (err) {
162
+ resp.status(400).type('application/json').send({ error: err.message });
163
+ }
164
+ });
165
+ // ==================== Merge & Rebase ====================
166
+ app.post('/api/merge', async (req, resp) => {
167
+ try {
168
+ const { branch } = req.body;
169
+ await git.merge([branch]);
170
+ resp.type('application/json').send({ success: true });
171
+ }
172
+ catch (err) {
173
+ resp.status(400).type('application/json').send({ error: err.message });
174
+ }
175
+ });
176
+ app.post('/api/rebase', async (req, resp) => {
177
+ try {
178
+ const { branch } = req.body;
179
+ await git.rebase([branch]);
180
+ resp.type('application/json').send({ success: true });
181
+ }
182
+ catch (err) {
183
+ resp.status(400).type('application/json').send({ error: err.message });
184
+ }
185
+ });
186
+ app.post('/api/squash', async (req, resp) => {
187
+ try {
188
+ const { commits } = req.body;
189
+ // Squash: rebase -i with squash operation
190
+ await git.rebase(['-i', 'HEAD~' + commits]);
191
+ resp.type('application/json').send({ success: true });
192
+ }
193
+ catch (err) {
194
+ resp.status(400).type('application/json').send({ error: err.message });
195
+ }
196
+ });
197
+ // ==================== Stash ====================
198
+ app.get('/api/stash/list', async (_req, resp) => {
199
+ try {
200
+ const stashList = await git.stashList();
201
+ resp.type('application/json').send(stashList);
202
+ }
203
+ catch (err) {
204
+ resp.status(400).type('application/json').send({ error: err.message });
205
+ }
206
+ });
207
+ app.post('/api/stash/save', async (req, resp) => {
208
+ try {
209
+ const { message } = req.body;
210
+ await git.stash(['save', message || '']);
211
+ resp.type('application/json').send({ success: true });
212
+ }
213
+ catch (err) {
214
+ resp.status(400).type('application/json').send({ error: err.message });
215
+ }
216
+ });
217
+ app.post('/api/stash/apply', async (req, resp) => {
218
+ try {
219
+ const { index } = req.body;
220
+ await git.stash(['apply', `stash@{${index}}`]);
221
+ resp.type('application/json').send({ success: true });
222
+ }
223
+ catch (err) {
224
+ resp.status(400).type('application/json').send({ error: err.message });
225
+ }
226
+ });
227
+ app.post('/api/stash/pop', async (req, resp) => {
228
+ try {
229
+ const { index } = req.body;
230
+ await git.stash(['pop', `stash@{${index}}`]);
231
+ resp.type('application/json').send({ success: true });
232
+ }
233
+ catch (err) {
234
+ resp.status(400).type('application/json').send({ error: err.message });
235
+ }
236
+ });
237
+ app.post('/api/stash/drop', async (req, resp) => {
238
+ try {
239
+ const { index } = req.body;
240
+ await git.stash(['drop', `stash@{${index}}`]);
241
+ resp.type('application/json').send({ success: true });
242
+ }
243
+ catch (err) {
244
+ resp.status(400).type('application/json').send({ error: err.message });
245
+ }
246
+ });
247
+ app.post('/api/stash/show', async (req, resp) => {
248
+ try {
249
+ const { index } = req.body;
250
+ const show = await git.stash(['show', '-p', `stash@{${index}}`]);
251
+ resp.type('application/json').send({ preview: show });
252
+ }
253
+ catch (err) {
254
+ resp.status(400).type('application/json').send({ error: err.message });
255
+ }
256
+ });
257
+ // ==================== Tags ====================
258
+ app.get('/api/tags', async (_req, resp) => {
259
+ try {
260
+ const tags = await git.tags();
261
+ resp.type('application/json').send(tags);
262
+ }
263
+ catch (err) {
264
+ resp.status(400).type('application/json').send({ error: err.message });
265
+ }
266
+ });
267
+ app.post('/api/tag/create', async (req, resp) => {
268
+ try {
269
+ const { name, message, commit } = req.body;
270
+ if (message) {
271
+ await git.tag(['-a', name, '-m', message, ...(commit ? [commit] : [])]);
272
+ }
273
+ else {
274
+ await git.tag([name, ...(commit ? [commit] : [])]);
275
+ }
276
+ resp.type('application/json').send({ success: true });
277
+ }
278
+ catch (err) {
279
+ resp.status(400).type('application/json').send({ error: err.message });
280
+ }
281
+ });
282
+ app.post('/api/tag/delete', async (req, resp) => {
283
+ try {
284
+ const { name } = req.body;
285
+ await git.tag(['-d', name]);
286
+ resp.type('application/json').send({ success: true });
287
+ }
288
+ catch (err) {
289
+ resp.status(400).type('application/json').send({ error: err.message });
290
+ }
291
+ });
292
+ // ==================== Remote Operations ====================
293
+ app.get('/api/fetch', (_req, resp) => {
294
+ git.fetch({}, (err, data) => {
295
+ resp.type('application/json').send();
296
+ });
297
+ });
298
+ app.post('/api/pull', async (req, resp) => {
299
+ try {
300
+ const { remote, branch } = req.body;
301
+ await git.pull(remote || 'origin', branch || undefined);
302
+ resp.type('application/json').send({ success: true });
303
+ }
304
+ catch (err) {
305
+ resp.status(400).type('application/json').send({ error: err.message });
306
+ }
307
+ });
308
+ app.post('/api/push', async (req, resp) => {
309
+ try {
310
+ const { remote, branch, force } = req.body;
311
+ await git.push(remote || 'origin', branch || undefined, force ? { '-f': null } : {});
312
+ resp.type('application/json').send({ success: true });
313
+ }
314
+ catch (err) {
315
+ resp.status(400).type('application/json').send({ error: err.message });
316
+ }
317
+ });
318
+ app.post('/api/prune', async (req, resp) => {
319
+ try {
320
+ const { remote } = req.body;
321
+ await git.remote(['prune', remote || 'origin']);
322
+ resp.type('application/json').send({ success: true });
323
+ }
324
+ catch (err) {
325
+ resp.status(400).type('application/json').send({ error: err.message });
326
+ }
327
+ });
328
+ // ==================== Diff Viewer ====================
329
+ app.post('/api/commit/diff', async (req, resp) => {
330
+ try {
331
+ const { hash } = req.body;
332
+ if (!hash) {
333
+ return resp.status(400).type('application/json').send({ error: 'hash required' });
334
+ }
335
+ // Get raw diff using git show
336
+ const rawDiff = await git.show([hash]);
337
+ // Split by file sections (diff --git lines)
338
+ const files = [];
339
+ const sections = rawDiff.split(/(?=^diff --git)/m);
340
+ for (const section of sections) {
341
+ if (!section.trim())
342
+ continue;
343
+ // Extract filename from "diff --git a/path b/path"
344
+ const fileMatch = section.match(/^diff --git a\/(.*?) b\/(.*)$/m);
345
+ if (!fileMatch)
346
+ continue;
347
+ const filepath = fileMatch[2];
348
+ // Extract the actual diff content
349
+ const contentMatch = section.match(/^@@ .+? @@\s*\n([\s\S]*?)(?=^diff --git|$)/m);
350
+ if (!contentMatch) {
351
+ // File may be binary or new
352
+ files.push({
353
+ path: filepath,
354
+ original: '',
355
+ modified: '',
356
+ isBinary: section.includes('Binary files'),
357
+ });
358
+ continue;
359
+ }
360
+ const diffContent = contentMatch[1];
361
+ let original = '';
362
+ let modified = '';
363
+ // Parse unified diff format
364
+ const diffLines = diffContent.split('\n');
365
+ for (const line of diffLines) {
366
+ if (line.startsWith('-') && !line.startsWith('---')) {
367
+ original += line.substring(1) + '\n';
368
+ }
369
+ else if (line.startsWith('+') && !line.startsWith('+++')) {
370
+ modified += line.substring(1) + '\n';
371
+ }
372
+ else if (!line.startsWith('\\')) {
373
+ // Context lines appear in both
374
+ const contextLine = line.substring(1) + '\n';
375
+ original += contextLine;
376
+ modified += contextLine;
377
+ }
378
+ }
379
+ files.push({
380
+ path: filepath,
381
+ original: original.trimEnd(),
382
+ modified: modified.trimEnd(),
383
+ });
384
+ }
385
+ resp.type('application/json').send({ hash, files });
386
+ }
387
+ catch (err) {
388
+ console.error('Diff error:', err.message);
389
+ resp.status(400).type('application/json').send({ error: err.message });
390
+ }
391
+ });
392
+ // Run the server
393
+ app.listen({ port }, (err, address) => {
394
+ if (err)
395
+ throw err;
396
+ console.log(`Visit "${address}" to see git explorer.`);
397
+ if (shouldOpenBrowser)
398
+ open(address);
399
+ });
400
+ })();