-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli_git.py
More file actions
250 lines (210 loc) · 7.51 KB
/
cli_git.py
File metadata and controls
250 lines (210 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# CLI git commands
git_commands = '''import { execa } from 'execa';
import ora from 'ora';
import chalk from 'chalk';
import inquirer from 'inquirer';
/**
* Execute git commands with error handling
* @param {string[]} args - Git command arguments
* @param {Object} options - Execution options
* @returns {Promise<Object>} - Execution result
*/
async function execGit(args, options = {}) {
try {
const result = await execa('git', args, {
cwd: process.cwd(),
...options
});
return { success: true, stdout: result.stdout, stderr: result.stderr };
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* Check if we're in a git repository
* @returns {Promise<boolean>}
*/
async function isGitRepo() {
const result = await execGit(['rev-parse', '--git-dir']);
return result.success;
}
/**
* Get current branch name
* @returns {Promise<string|null>}
*/
async function getCurrentBranch() {
const result = await execGit(['branch', '--show-current']);
return result.success ? result.stdout.trim() : null;
}
/**
* Check if there are uncommitted changes
* @returns {Promise<boolean>}
*/
async function hasUncommittedChanges() {
const result = await execGit(['status', '--porcelain']);
return result.success && result.stdout.trim().length > 0;
}
/**
* Sync git repository: pull, resolve conflicts, push
*/
export async function gitSyncCommand() {
const spinner = ora('Checking git status...').start();
try {
// Check if we're in a git repo
if (!await isGitRepo()) {
spinner.fail(chalk.red('Not a git repository!'));
return;
}
// Check for uncommitted changes
if (await hasUncommittedChanges()) {
spinner.stop();
console.log(chalk.yellow('⚠️ You have uncommitted changes:'));
const status = await execGit(['status', '--short']);
console.log(status.stdout);
const { action } = await inquirer.prompt([{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: 'Stash changes and continue', value: 'stash' },
{ name: 'Commit changes first', value: 'commit' },
{ name: 'Cancel', value: 'cancel' }
]
}]);
if (action === 'cancel') {
console.log(chalk.yellow('Sync cancelled.'));
return;
}
spinner.start('Processing...');
if (action === 'stash') {
const stashResult = await execGit(['stash']);
if (!stashResult.success) {
spinner.fail(chalk.red('Failed to stash changes'));
return;
}
} else if (action === 'commit') {
spinner.stop();
const { message } = await inquirer.prompt([{
type: 'input',
name: 'message',
message: 'Commit message:',
default: 'WIP: sync changes'
}]);
spinner.start('Committing...');
await execGit(['add', '.']);
const commitResult = await execGit(['commit', '-m', message]);
if (!commitResult.success) {
spinner.fail(chalk.red('Failed to commit'));
return;
}
}
}
const branch = await getCurrentBranch();
spinner.text = `Pulling latest changes on ${branch}...`;
// Fetch first
const fetchResult = await execGit(['fetch', 'origin']);
if (!fetchResult.success) {
spinner.fail(chalk.red('Failed to fetch from origin'));
return;
}
// Try to pull
const pullResult = await execGit(['pull', 'origin', branch]);
if (!pullResult.success) {
// Check if it's a merge conflict
if (pullResult.error.includes('conflict') || pullResult.error.includes('CONFLICT')) {
spinner.warn(chalk.yellow('Merge conflicts detected!'));
const conflicts = await execGit(['diff', '--name-only', '--diff-filter=U']);
console.log(chalk.red('\\nConflicting files:'));
console.log(conflicts.stdout);
console.log(chalk.cyan('\\nPlease resolve conflicts manually and run:'));
console.log(chalk.white(' git add .'));
console.log(chalk.white(' git commit -m "Merge resolved"'));
console.log(chalk.white(' git push'));
return;
}
spinner.fail(chalk.red(`Pull failed: ${pullResult.error}`));
return;
}
// Check if there were merge conflicts in the output
if (pullResult.stdout.includes('CONFLICT') || pullResult.stderr.includes('CONFLICT')) {
spinner.warn(chalk.yellow('Merge conflicts detected!'));
console.log(chalk.cyan('\\nPlease resolve conflicts manually.'));
return;
}
spinner.text = 'Pushing changes...';
const pushResult = await execGit(['push', 'origin', branch]);
if (!pushResult.success) {
spinner.fail(chalk.red(`Push failed: ${pushResult.error}`));
return;
}
spinner.succeed(chalk.green(`✅ Synced ${branch} successfully!`));
// Show summary
const logResult = await execGit(['log', '--oneline', '-3']);
console.log(chalk.dim('\\nRecent commits:'));
console.log(chalk.gray(logResult.stdout));
} catch (error) {
spinner.fail(chalk.red(`Error: ${error.message}`));
}
}
/**
* Undo the last commit safely
* @param {Object} options - Command options
*/
export async function gitUndoCommand(options) {
const spinner = ora('Checking repository...').start();
try {
if (!await isGitRepo()) {
spinner.fail(chalk.red('Not a git repository!'));
return;
}
// Get the last commit info
const logResult = await execGit(['log', '-1', '--format=%h %s']);
if (!logResult.success) {
spinner.fail(chalk.red('Failed to get commit history'));
return;
}
const lastCommit = logResult.stdout.trim();
spinner.stop();
console.log(chalk.yellow('⚠️ You are about to undo the last commit:'));
console.log(chalk.cyan(` ${lastCommit}`));
const { confirm } = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: options.hard
? 'This will permanently DELETE the commit and changes. Continue?'
: 'This will keep changes in your working directory. Continue?',
default: false
}]);
if (!confirm) {
console.log(chalk.yellow('Undo cancelled.'));
return;
}
spinner.start('Undoing commit...');
const resetMode = options.hard ? '--hard' : '--soft';
const resetResult = await execGit(['reset', resetMode, 'HEAD~1']);
if (!resetResult.success) {
spinner.fail(chalk.red(`Undo failed: ${resetResult.error}`));
return;
}
spinner.succeed(chalk.green('✅ Last commit undone successfully!'));
if (!options.hard) {
const status = await execGit(['status', '--short']);
if (status.stdout.trim()) {
console.log(chalk.cyan('\\nYour changes are preserved:'));
console.log(chalk.gray(status.stdout));
console.log(chalk.dim('\\nYou can now:'));
console.log(chalk.white(' - Re-commit with: git commit -m "new message"'));
console.log(chalk.white(' - Stage different files: git add <files>'));
console.log(chalk.white(' - Discard changes: git checkout -- <files>'));
}
} else {
console.log(chalk.yellow('\\n⚠️ Changes have been permanently deleted.'));
}
} catch (error) {
spinner.fail(chalk.red(`Error: ${error.message}`));
}
}
'''
with open(f"{base_path}/packages/cli/src/commands/git.js", "w") as f:
f.write(git_commands)
print("✅ CLI git commands created")