gittable 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 +22 -0
- package/README.md +459 -0
- package/cli.js +342 -0
- package/commands/add.js +159 -0
- package/commands/blame.js +33 -0
- package/commands/branch.js +234 -0
- package/commands/checkout.js +43 -0
- package/commands/cherry-pick.js +104 -0
- package/commands/clean.js +71 -0
- package/commands/clone.js +76 -0
- package/commands/commit.js +82 -0
- package/commands/config.js +171 -0
- package/commands/diff.js +30 -0
- package/commands/fetch.js +76 -0
- package/commands/grep.js +42 -0
- package/commands/init.js +45 -0
- package/commands/log.js +38 -0
- package/commands/merge.js +69 -0
- package/commands/mv.js +40 -0
- package/commands/pull.js +74 -0
- package/commands/push.js +97 -0
- package/commands/rebase.js +134 -0
- package/commands/remote.js +236 -0
- package/commands/restore.js +76 -0
- package/commands/revert.js +63 -0
- package/commands/rm.js +57 -0
- package/commands/show.js +47 -0
- package/commands/stash.js +201 -0
- package/commands/status.js +21 -0
- package/commands/sync.js +98 -0
- package/commands/tag.js +153 -0
- package/commands/undo.js +200 -0
- package/commands/uninit.js +57 -0
- package/index.d.ts +56 -0
- package/index.js +55 -0
- package/lib/commit/build-commit.js +64 -0
- package/lib/commit/get-previous-commit.js +15 -0
- package/lib/commit/questions.js +226 -0
- package/lib/config/read-config-file.js +54 -0
- package/lib/git/exec.js +222 -0
- package/lib/ui/ascii.js +154 -0
- package/lib/ui/banner.js +80 -0
- package/lib/ui/status-display.js +90 -0
- package/lib/ui/table.js +76 -0
- package/lib/utils/email-prompt.js +62 -0
- package/lib/utils/logger.js +47 -0
- package/lib/utils/spinner.js +57 -0
- package/lib/utils/terminal-link.js +55 -0
- package/lib/versions.js +17 -0
- package/package.json +73 -0
- package/standalone.js +24 -0
package/cli.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const clack = require('@clack/prompts');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { isGitRepo } = require('./lib/git/exec');
|
|
6
|
+
|
|
7
|
+
// Command aliases mapping
|
|
8
|
+
const COMMANDS = {
|
|
9
|
+
status: require('./commands/status'),
|
|
10
|
+
st: require('./commands/status'),
|
|
11
|
+
|
|
12
|
+
branch: require('./commands/branch'),
|
|
13
|
+
br: require('./commands/branch'),
|
|
14
|
+
co: require('./commands/branch'),
|
|
15
|
+
|
|
16
|
+
commit: require('./commands/commit'),
|
|
17
|
+
ci: require('./commands/commit'),
|
|
18
|
+
|
|
19
|
+
pull: require('./commands/pull'),
|
|
20
|
+
pl: require('./commands/pull'),
|
|
21
|
+
|
|
22
|
+
push: require('./commands/push'),
|
|
23
|
+
ps: require('./commands/push'),
|
|
24
|
+
|
|
25
|
+
sync: require('./commands/sync'),
|
|
26
|
+
|
|
27
|
+
merge: require('./commands/merge'),
|
|
28
|
+
|
|
29
|
+
rebase: require('./commands/rebase'),
|
|
30
|
+
|
|
31
|
+
stash: require('./commands/stash'),
|
|
32
|
+
|
|
33
|
+
log: require('./commands/log'),
|
|
34
|
+
|
|
35
|
+
undo: require('./commands/undo'),
|
|
36
|
+
reset: require('./commands/undo'),
|
|
37
|
+
|
|
38
|
+
// New commands
|
|
39
|
+
add: require('./commands/add'),
|
|
40
|
+
diff: require('./commands/diff'),
|
|
41
|
+
init: require('./commands/init'),
|
|
42
|
+
uninit: require('./commands/uninit'),
|
|
43
|
+
deinit: require('./commands/uninit'),
|
|
44
|
+
clone: require('./commands/clone'),
|
|
45
|
+
fetch: require('./commands/fetch'),
|
|
46
|
+
remote: require('./commands/remote'),
|
|
47
|
+
tag: require('./commands/tag'),
|
|
48
|
+
show: require('./commands/show'),
|
|
49
|
+
checkout: require('./commands/checkout'),
|
|
50
|
+
revert: require('./commands/revert'),
|
|
51
|
+
'cherry-pick': require('./commands/cherry-pick'),
|
|
52
|
+
clean: require('./commands/clean'),
|
|
53
|
+
config: require('./commands/config'),
|
|
54
|
+
blame: require('./commands/blame'),
|
|
55
|
+
grep: require('./commands/grep'),
|
|
56
|
+
rm: require('./commands/rm'),
|
|
57
|
+
mv: require('./commands/mv'),
|
|
58
|
+
restore: require('./commands/restore'),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const VERSION = require('./package.json').version;
|
|
62
|
+
const { showBanner } = require('./lib/ui/banner');
|
|
63
|
+
const { createLink } = require('./lib/utils/terminal-link');
|
|
64
|
+
|
|
65
|
+
// Command categories
|
|
66
|
+
const COMMAND_CATEGORIES = {
|
|
67
|
+
core: {
|
|
68
|
+
name: 'Core Commands',
|
|
69
|
+
commands: [
|
|
70
|
+
{ name: 'status', aliases: ['st'], description: 'Show repository status' },
|
|
71
|
+
{
|
|
72
|
+
name: 'branch',
|
|
73
|
+
aliases: ['br', 'co'],
|
|
74
|
+
description: 'Branch management (list, create, checkout, delete)',
|
|
75
|
+
},
|
|
76
|
+
{ name: 'commit', aliases: ['ci'], description: 'Create commits with conventional format' },
|
|
77
|
+
{ name: 'pull', aliases: ['pl'], description: 'Fetch and merge from remote' },
|
|
78
|
+
{ name: 'push', aliases: ['ps'], description: 'Push to remote' },
|
|
79
|
+
{ name: 'sync', aliases: [], description: 'Synchronize (pull + rebase + push)' },
|
|
80
|
+
{ name: 'merge', aliases: [], description: 'Merge branches' },
|
|
81
|
+
{ name: 'rebase', aliases: [], description: 'Rebase operations' },
|
|
82
|
+
{ name: 'stash', aliases: [], description: 'Stash management' },
|
|
83
|
+
{ name: 'log', aliases: [], description: 'View commit history' },
|
|
84
|
+
{ name: 'undo', aliases: ['reset'], description: 'Undo operations and reflog browser' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
file: {
|
|
88
|
+
name: 'File Operations',
|
|
89
|
+
commands: [
|
|
90
|
+
{ name: 'add', aliases: [], description: 'Stage files for commit' },
|
|
91
|
+
{ name: 'diff', aliases: [], description: 'Show changes' },
|
|
92
|
+
{ name: 'checkout', aliases: [], description: 'Checkout files' },
|
|
93
|
+
{ name: 'restore', aliases: [], description: 'Restore files from index or commit' },
|
|
94
|
+
{ name: 'rm', aliases: [], description: 'Remove files from git' },
|
|
95
|
+
{ name: 'mv', aliases: [], description: 'Move/rename files' },
|
|
96
|
+
{ name: 'clean', aliases: [], description: 'Remove untracked files' },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
repo: {
|
|
100
|
+
name: 'Repository Management',
|
|
101
|
+
commands: [
|
|
102
|
+
{ name: 'init', aliases: [], description: 'Initialize a new repository' },
|
|
103
|
+
{ name: 'uninit', aliases: ['deinit'], description: 'Remove git repository (clear history)' },
|
|
104
|
+
{ name: 'clone', aliases: [], description: 'Clone a repository' },
|
|
105
|
+
{ name: 'remote', aliases: [], description: 'Manage remote repositories' },
|
|
106
|
+
{ name: 'fetch', aliases: [], description: 'Fetch from remote' },
|
|
107
|
+
{ name: 'tag', aliases: [], description: 'Tag management' },
|
|
108
|
+
{ name: 'config', aliases: [], description: 'Git configuration management' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
history: {
|
|
112
|
+
name: 'History & Inspection',
|
|
113
|
+
commands: [
|
|
114
|
+
{ name: 'show', aliases: [], description: 'Show commit details' },
|
|
115
|
+
{ name: 'revert', aliases: [], description: 'Revert commits' },
|
|
116
|
+
{ name: 'cherry-pick', aliases: [], description: 'Apply commits from another branch' },
|
|
117
|
+
{ name: 'blame', aliases: [], description: 'Show who last modified each line' },
|
|
118
|
+
{ name: 'grep', aliases: [], description: 'Search in repository' },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const showHelp = () => {
|
|
124
|
+
// Show banner
|
|
125
|
+
showBanner('GITTABLE', { version: VERSION });
|
|
126
|
+
|
|
127
|
+
// GitHub link at the top
|
|
128
|
+
const repoLink = createLink('GitHub', 'https://github.com/GG-Santos/Gittable');
|
|
129
|
+
console.log(`${chalk.gray('├')} ${chalk.dim(repoLink)}`);
|
|
130
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Modern Git CLI with Interactive Prompts')}`);
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.gray('├') +
|
|
133
|
+
' ' +
|
|
134
|
+
chalk.yellow('Usage:') +
|
|
135
|
+
' ' +
|
|
136
|
+
chalk.white('gittable <command> [options]')
|
|
137
|
+
);
|
|
138
|
+
console.log(chalk.gray('│'));
|
|
139
|
+
|
|
140
|
+
// Core Commands
|
|
141
|
+
console.log(chalk.bold.cyan(' Core Commands:'));
|
|
142
|
+
console.log();
|
|
143
|
+
for (const cmd of COMMAND_CATEGORIES.core.commands) {
|
|
144
|
+
const cmdList = [cmd.name, ...cmd.aliases].join(', ');
|
|
145
|
+
console.log(` ${chalk.cyan(cmdList.padEnd(20))} ${chalk.gray(cmd.description)}`);
|
|
146
|
+
}
|
|
147
|
+
console.log();
|
|
148
|
+
|
|
149
|
+
// File Operations
|
|
150
|
+
console.log(chalk.bold.cyan(' File Operations:'));
|
|
151
|
+
console.log();
|
|
152
|
+
for (const cmd of COMMAND_CATEGORIES.file.commands) {
|
|
153
|
+
const cmdList = [cmd.name, ...cmd.aliases].join(', ');
|
|
154
|
+
console.log(` ${chalk.cyan(cmdList.padEnd(20))} ${chalk.gray(cmd.description)}`);
|
|
155
|
+
}
|
|
156
|
+
console.log();
|
|
157
|
+
|
|
158
|
+
// Repository Management
|
|
159
|
+
console.log(chalk.bold.cyan(' Repository Management:'));
|
|
160
|
+
console.log();
|
|
161
|
+
for (const cmd of COMMAND_CATEGORIES.repo.commands) {
|
|
162
|
+
const cmdList = [cmd.name, ...cmd.aliases].join(', ');
|
|
163
|
+
console.log(` ${chalk.cyan(cmdList.padEnd(20))} ${chalk.gray(cmd.description)}`);
|
|
164
|
+
}
|
|
165
|
+
console.log();
|
|
166
|
+
|
|
167
|
+
// History & Inspection
|
|
168
|
+
console.log(chalk.bold.cyan(' History & Inspection:'));
|
|
169
|
+
console.log();
|
|
170
|
+
for (const cmd of COMMAND_CATEGORIES.history.commands) {
|
|
171
|
+
const cmdList = [cmd.name, ...cmd.aliases].join(', ');
|
|
172
|
+
console.log(` ${chalk.cyan(cmdList.padEnd(20))} ${chalk.gray(cmd.description)}`);
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const showInteractiveMenu = async () => {
|
|
178
|
+
showBanner('GITTABLE', { version: VERSION });
|
|
179
|
+
|
|
180
|
+
const category = await clack.select({
|
|
181
|
+
message: chalk.cyan('Select a category:'),
|
|
182
|
+
options: [
|
|
183
|
+
{ value: 'core', label: chalk.cyan('Core Commands') },
|
|
184
|
+
{ value: 'file', label: chalk.cyan('File Operations') },
|
|
185
|
+
{ value: 'repo', label: chalk.cyan('Repository Management') },
|
|
186
|
+
{ value: 'history', label: chalk.cyan('History & Inspection') },
|
|
187
|
+
{ value: 'help', label: chalk.yellow('List Commands') },
|
|
188
|
+
{ value: 'exit', label: chalk.red('Exit') },
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (clack.isCancel(category) || category === 'exit') {
|
|
193
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (category === 'help') {
|
|
198
|
+
showHelp();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Show commands in selected category
|
|
203
|
+
const categoryData = COMMAND_CATEGORIES[category];
|
|
204
|
+
const commandOptions = categoryData.commands.map((cmd) => ({
|
|
205
|
+
value: cmd.name,
|
|
206
|
+
label: `${chalk.cyan(cmd.name)} ${chalk.gray(`- ${cmd.description}`)}`,
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
// Add back option
|
|
210
|
+
commandOptions.push({
|
|
211
|
+
value: '__back__',
|
|
212
|
+
label: chalk.dim('← Back to categories'),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const selectedCommand = await clack.select({
|
|
216
|
+
message: chalk.cyan(`Select a command from ${categoryData.name}:`),
|
|
217
|
+
options: commandOptions,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (clack.isCancel(selectedCommand) || selectedCommand === '__back__') {
|
|
221
|
+
// Recursively show menu again
|
|
222
|
+
return showInteractiveMenu();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Execute the selected command
|
|
226
|
+
if (COMMANDS[selectedCommand]) {
|
|
227
|
+
// Check if we're in a git repo (except for init, uninit, and clone)
|
|
228
|
+
if (
|
|
229
|
+
!isGitRepo() &&
|
|
230
|
+
selectedCommand !== 'init' &&
|
|
231
|
+
selectedCommand !== 'uninit' &&
|
|
232
|
+
selectedCommand !== 'clone'
|
|
233
|
+
) {
|
|
234
|
+
clack.cancel(chalk.red('Not a git repository'));
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(`${chalk.gray('├')} ${chalk.yellow('Tip:')}`);
|
|
237
|
+
console.log(`${chalk.gray('│')} ${chalk.gray('Initialize a new repository with:')}`);
|
|
238
|
+
console.log(`${chalk.gray('│')} ${chalk.cyan(' gittable init')}`);
|
|
239
|
+
console.log(chalk.gray('│'));
|
|
240
|
+
console.log(`${chalk.gray('└')} ${chalk.gray('Or clone an existing repository with:')}`);
|
|
241
|
+
console.log(chalk.gray(' ') + chalk.cyan(' gittable clone <url>'));
|
|
242
|
+
console.log();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
await COMMANDS[selectedCommand]([]);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
clack.cancel(chalk.red('Command failed'));
|
|
250
|
+
console.error(error);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
clack.cancel(chalk.red(`Command '${selectedCommand}' not found`));
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const main = async () => {
|
|
260
|
+
const args = process.argv.slice(2);
|
|
261
|
+
|
|
262
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
263
|
+
showHelp();
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
268
|
+
showBanner('GITTABLE', { version: VERSION });
|
|
269
|
+
console.log();
|
|
270
|
+
clack.outro(chalk.green(`Version ${VERSION}`));
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// If no arguments provided and TTY is available, show interactive menu
|
|
275
|
+
if (args.length === 0 && process.stdin.isTTY) {
|
|
276
|
+
try {
|
|
277
|
+
await showInteractiveMenu();
|
|
278
|
+
process.exit(0);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
clack.cancel(chalk.red('Menu failed'));
|
|
281
|
+
console.error(error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// If no arguments and not TTY, show help
|
|
287
|
+
if (args.length === 0) {
|
|
288
|
+
showHelp();
|
|
289
|
+
process.exit(0);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const command = args[0].toLowerCase();
|
|
293
|
+
const commandArgs = args.slice(1);
|
|
294
|
+
|
|
295
|
+
if (!COMMANDS[command]) {
|
|
296
|
+
showBanner('GITTABLE', { version: VERSION });
|
|
297
|
+
console.log();
|
|
298
|
+
clack.cancel(chalk.red(`Unknown command: ${chalk.bold(command)}`));
|
|
299
|
+
console.log();
|
|
300
|
+
console.log(`${chalk.gray('├')} ${chalk.yellow('Available commands:')}`);
|
|
301
|
+
console.log(chalk.gray('│'));
|
|
302
|
+
|
|
303
|
+
// Show some common commands as suggestions
|
|
304
|
+
const suggestions = Object.keys(COMMANDS).slice(0, 10);
|
|
305
|
+
for (const cmd of suggestions) {
|
|
306
|
+
console.log(`${chalk.gray('│')} ${chalk.cyan(` ${cmd}`)}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(chalk.gray('│'));
|
|
310
|
+
console.log(
|
|
311
|
+
`${chalk.gray('└')} ${chalk.gray('Run "gittable --help" for full usage information')}`
|
|
312
|
+
);
|
|
313
|
+
console.log();
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check if we're in a git repo (except for help/version, init, uninit, and clone)
|
|
318
|
+
if (!isGitRepo() && command !== 'init' && command !== 'uninit' && command !== 'clone') {
|
|
319
|
+
showBanner('GITTABLE', { version: VERSION });
|
|
320
|
+
console.log();
|
|
321
|
+
clack.cancel(chalk.red('Not a git repository'));
|
|
322
|
+
console.log();
|
|
323
|
+
console.log(`${chalk.gray('├')} ${chalk.yellow('Tip:')}`);
|
|
324
|
+
console.log(`${chalk.gray('│')} ${chalk.gray('Initialize a new repository with:')}`);
|
|
325
|
+
console.log(`${chalk.gray('│')} ${chalk.cyan(' gittable init')}`);
|
|
326
|
+
console.log(chalk.gray('│'));
|
|
327
|
+
console.log(`${chalk.gray('└')} ${chalk.gray('Or clone an existing repository with:')}`);
|
|
328
|
+
console.log(chalk.gray(' ') + chalk.cyan(' gittable clone <url>'));
|
|
329
|
+
console.log();
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
await COMMANDS[command](commandArgs);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
clack.cancel(chalk.red('Command failed'));
|
|
337
|
+
console.error(error);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
main();
|
package/commands/add.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const clack = require('@clack/prompts');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { execGit, getStatus } = require('../lib/git/exec');
|
|
4
|
+
const { showBanner } = require('../lib/ui/banner');
|
|
5
|
+
|
|
6
|
+
const stageFiles = async (files) => {
|
|
7
|
+
if (!files || files.length === 0) {
|
|
8
|
+
// Check if TTY is available for interactive prompts
|
|
9
|
+
if (!process.stdin.isTTY) {
|
|
10
|
+
clack.cancel(chalk.red('Interactive mode required'));
|
|
11
|
+
console.log(chalk.yellow('This command requires interactive input.'));
|
|
12
|
+
console.log(chalk.gray('Available options:'));
|
|
13
|
+
console.log(chalk.gray(' - gittable add <file1> <file2> ...'));
|
|
14
|
+
console.log(chalk.gray(' - gittable add --all (or -A)'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Interactive mode: show unstaged files
|
|
19
|
+
const status = getStatus();
|
|
20
|
+
if (!status) {
|
|
21
|
+
clack.cancel(chalk.red('Failed to get repository status'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const allFiles = [
|
|
26
|
+
...status.unstaged.map((f) => ({
|
|
27
|
+
value: f.file,
|
|
28
|
+
label: chalk.yellow(`M ${f.file}`),
|
|
29
|
+
hint: 'modified',
|
|
30
|
+
})),
|
|
31
|
+
...status.untracked.map((f) => ({
|
|
32
|
+
value: f,
|
|
33
|
+
label: chalk.green(`? ${f}`),
|
|
34
|
+
hint: 'untracked',
|
|
35
|
+
})),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
if (allFiles.length === 0) {
|
|
39
|
+
clack.cancel(chalk.yellow('No files to stage'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const selected = await clack.multiselect({
|
|
44
|
+
message: chalk.cyan('Select files to stage:'),
|
|
45
|
+
options: allFiles,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (clack.isCancel(selected)) {
|
|
49
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
files = selected;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const spinner = clack.spinner();
|
|
57
|
+
spinner.start(`Staging ${files.length} file(s)`);
|
|
58
|
+
|
|
59
|
+
const result = execGit(`add ${files.join(' ')}`, { silent: false });
|
|
60
|
+
spinner.stop();
|
|
61
|
+
|
|
62
|
+
if (result.success) {
|
|
63
|
+
clack.outro(chalk.green.bold(`Staged ${files.length} file(s)`));
|
|
64
|
+
} else {
|
|
65
|
+
clack.cancel(chalk.red('Failed to stage files'));
|
|
66
|
+
console.error(result.error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const stageAll = async () => {
|
|
72
|
+
const confirm = await clack.confirm({
|
|
73
|
+
message: chalk.cyan('Stage all changes?'),
|
|
74
|
+
initialValue: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (clack.isCancel(confirm) || !confirm) {
|
|
78
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const spinner = clack.spinner();
|
|
83
|
+
spinner.start('Staging all changes');
|
|
84
|
+
|
|
85
|
+
const result = execGit('add -A', { silent: false });
|
|
86
|
+
spinner.stop();
|
|
87
|
+
|
|
88
|
+
if (result.success) {
|
|
89
|
+
clack.outro(chalk.green.bold('Staged all changes'));
|
|
90
|
+
} else {
|
|
91
|
+
clack.cancel(chalk.red('Failed to stage files'));
|
|
92
|
+
console.error(result.error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const unstageFiles = async (files) => {
|
|
98
|
+
if (!files || files.length === 0) {
|
|
99
|
+
const status = getStatus();
|
|
100
|
+
if (!status || status.staged.length === 0) {
|
|
101
|
+
clack.cancel(chalk.yellow('No staged files to unstage'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const options = status.staged.map((f) => ({
|
|
106
|
+
value: f.file,
|
|
107
|
+
label: `${f.status} ${f.file}`,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const selected = await clack.multiselect({
|
|
111
|
+
message: chalk.cyan('Select files to unstage:'),
|
|
112
|
+
options,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (clack.isCancel(selected)) {
|
|
116
|
+
clack.cancel(chalk.yellow('Cancelled'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
files = selected;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const spinner = clack.spinner();
|
|
124
|
+
spinner.start(`Unstaging ${files.length} file(s)`);
|
|
125
|
+
|
|
126
|
+
const result = execGit(`reset HEAD -- ${files.join(' ')}`, { silent: false });
|
|
127
|
+
spinner.stop();
|
|
128
|
+
|
|
129
|
+
if (result.success) {
|
|
130
|
+
clack.outro(chalk.green.bold(`Unstaged ${files.length} file(s)`));
|
|
131
|
+
} else {
|
|
132
|
+
clack.cancel(chalk.red('Failed to unstage files'));
|
|
133
|
+
console.error(result.error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
module.exports = async (args) => {
|
|
139
|
+
const action = args[0];
|
|
140
|
+
|
|
141
|
+
if (action === '--all' || action === '-A') {
|
|
142
|
+
showBanner('ADD');
|
|
143
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Stage All Changes')}`);
|
|
144
|
+
await stageAll();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (action === '--unstage' || action === 'unstage') {
|
|
149
|
+
showBanner('ADD');
|
|
150
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Unstage Files')}`);
|
|
151
|
+
await unstageFiles(args.slice(1));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Default: stage files
|
|
156
|
+
showBanner('ADD');
|
|
157
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Stage Files')}`);
|
|
158
|
+
await stageFiles(args);
|
|
159
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const clack = require('@clack/prompts');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { execGit } = require('../lib/git/exec');
|
|
4
|
+
const { showBanner } = require('../lib/ui/banner');
|
|
5
|
+
|
|
6
|
+
module.exports = async (args) => {
|
|
7
|
+
showBanner('BLAME');
|
|
8
|
+
console.log(`${chalk.gray('├')} ${chalk.cyan.bold('Blame File')}`);
|
|
9
|
+
|
|
10
|
+
const file = args[0];
|
|
11
|
+
const revision = args.find((arg) => !arg.startsWith('--'));
|
|
12
|
+
|
|
13
|
+
if (!file) {
|
|
14
|
+
clack.cancel(chalk.yellow('No file specified'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let command = 'blame';
|
|
19
|
+
if (revision && revision !== file) {
|
|
20
|
+
command += ` ${revision}`;
|
|
21
|
+
}
|
|
22
|
+
command += ` ${file}`;
|
|
23
|
+
|
|
24
|
+
const result = execGit(command, { silent: false });
|
|
25
|
+
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
clack.cancel(chalk.red('Failed to show blame'));
|
|
28
|
+
console.error(result.error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clack.outro(chalk.green.bold('Done'));
|
|
33
|
+
};
|