Skip to content

Commit 94f764d

Browse files
✨ Alpha 8: Added ResetManager for undo, cleanup, and recovery operations
1 parent 4ece664 commit 94f764d

File tree

2 files changed

+261
-4
lines changed

2 files changed

+261
-4
lines changed

src/config/commandsList.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,19 +396,54 @@ export const COMMANDS_LIST: PlainGitCommand[] = [
396396
handler: 'HistoryManager.showAuthorSummary',
397397
},
398398

399-
// 🧹 Reset & Cleanup
399+
// ♻️ Reset & Cleanup
400400
{
401401
category: 'Reset',
402402
name: '↩️ Undo last commit (soft)',
403403
command: 'git reset --soft HEAD~1',
404404
description: 'Undo last commit but keep changes staged.',
405-
handler: 'ResetManager.undoLastCommit',
405+
handler: 'ResetManager.undoLastCommitSoft',
406+
},
407+
{
408+
category: 'Reset',
409+
name: '🔄 Undo last commit (mixed)',
410+
command: 'git reset --mixed HEAD~1',
411+
description: 'Undo last commit but keep changes unstaged.',
412+
handler: 'ResetManager.undoLastCommitMixed',
413+
},
414+
{
415+
category: 'Reset',
416+
name: '💣 Undo last commit (hard)',
417+
command: 'git reset --hard HEAD~1',
418+
description: 'Completely discard last commit and all changes.',
419+
handler: 'ResetManager.undoLastCommitHard',
420+
},
421+
{
422+
category: 'Reset',
423+
name: '🕓 Reset to a specific commit',
424+
command: 'git reset [--soft|--mixed|--hard] <commit-hash>',
425+
description: 'Reset repository to a specific commit with chosen mode.',
426+
handler: 'ResetManager.resetToSpecificCommit',
427+
},
428+
{
429+
category: 'Reset',
430+
name: '🧩 Discard changes in files',
431+
command: 'git restore <file>',
432+
description: 'Revert specific files to the state of last commit.',
433+
handler: 'ResetManager.discardFileChanges',
406434
},
407435
{
408436
category: 'Reset',
409437
name: '🧹 Clean untracked files',
410438
command: 'git clean -fd',
411-
description: 'Remove all untracked files and folders.',
439+
description: 'Remove all untracked files and directories.',
412440
handler: 'ResetManager.cleanUntracked',
413441
},
442+
{
443+
category: 'Reset',
444+
name: '⚙️ Interactive reset mode',
445+
command: 'git reset [interactive]',
446+
description: 'Select reset type interactively from a menu.',
447+
handler: 'ResetManager.interactiveReset',
448+
},
414449
];

src/managers/ResetManager.ts

Lines changed: 223 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,223 @@
1-
export const ResetManager = {}
1+
/**
2+
* ♻️ ResetManager.ts
3+
* -------------------------------------------------------------
4+
* Handles undo, cleanup, and reset operations in the repository.
5+
* Includes safe, mixed, and hard reset modes and file discards.
6+
* -------------------------------------------------------------
7+
*/
8+
9+
import inquirer from 'inquirer';
10+
import { execSync } from 'child_process';
11+
import { GitExecutor } from '../core/GitExecutor';
12+
import { Logger } from '../utils/Logger';
13+
14+
15+
/**
16+
* Helper: list recent commits for interactive reset
17+
*/
18+
function getRecentCommits(limit = 20): string[] {
19+
try {
20+
const out = execSync(`git log --oneline -n ${limit}`, { encoding: 'utf-8' });
21+
return out.split('\n').filter(Boolean);
22+
} catch {
23+
return [];
24+
}
25+
}
26+
27+
/**
28+
* Helper: get list of modified/untracked files
29+
*/
30+
function getModifiedFiles(): string[] {
31+
try {
32+
const output = execSync('git status --porcelain', { encoding: 'utf-8' });
33+
return output
34+
.split('\n')
35+
.map((l) => l.trim())
36+
.filter(Boolean)
37+
.map((l) => l.replace(/^.. /, ''));
38+
} catch {
39+
return [];
40+
}
41+
}
42+
43+
export const ResetManager = {
44+
/**
45+
* ↩️ Undo last commit (soft reset)
46+
*/
47+
async undoLastCommitSoft() {
48+
Logger.info('↩️ Undoing last commit (keeping staged changes)...');
49+
await GitExecutor.run('git reset --soft HEAD~1');
50+
Logger.success('✅ Last commit undone, changes remain staged.');
51+
},
52+
53+
/**
54+
* 🔄 Undo last commit and unstage changes (mixed reset)
55+
*/
56+
async undoLastCommitMixed() {
57+
Logger.info('🔄 Undoing last commit (keeping unstaged changes)...');
58+
await GitExecutor.run('git reset --mixed HEAD~1');
59+
Logger.success('✅ Last commit undone, changes kept but unstaged.');
60+
},
61+
62+
/**
63+
* 💣 Undo last commit completely (hard reset)
64+
*/
65+
async undoLastCommitHard() {
66+
const { confirm } = await inquirer.prompt([
67+
{
68+
type: 'confirm',
69+
name: 'confirm',
70+
message: '⚠️ This will remove all uncommitted changes. Continue?',
71+
default: false,
72+
},
73+
]);
74+
75+
if (!confirm) {
76+
Logger.info('❎ Operation cancelled.');
77+
return;
78+
}
79+
80+
Logger.info('💣 Performing hard reset to previous commit...');
81+
await GitExecutor.run('git reset --hard HEAD~1');
82+
Logger.success('✅ Repository reset to previous commit (all changes discarded).');
83+
},
84+
85+
/**
86+
* 🕓 Reset to a specific commit
87+
*/
88+
async resetToSpecificCommit() {
89+
const commits = getRecentCommits();
90+
if (!commits.length) {
91+
Logger.info('⚠️ No commits found.');
92+
return;
93+
}
94+
95+
const { commit } = await inquirer.prompt([
96+
{ type: 'list', name: 'commit', message: 'Select commit to reset to:', choices: commits },
97+
]);
98+
99+
const { mode } = await inquirer.prompt([
100+
{
101+
type: 'list',
102+
name: 'mode',
103+
message: 'Choose reset type:',
104+
choices: [
105+
{ name: 'Soft (keep all changes staged)', value: '--soft' },
106+
{ name: 'Mixed (keep changes unstaged)', value: '--mixed' },
107+
{ name: 'Hard (discard all changes)', value: '--hard' },
108+
],
109+
},
110+
]);
111+
112+
const hash = commit.split(' ')[0];
113+
Logger.info(`🕓 Resetting repository to ${hash} (${mode.replace('--', '')} mode)...`);
114+
await GitExecutor.run(`git reset ${mode} ${hash}`);
115+
Logger.success(`✅ Repository reset to ${hash} successfully.`);
116+
},
117+
118+
/**
119+
* 🧩 Discard changes for specific file(s)
120+
*/
121+
async discardFileChanges() {
122+
const files = getModifiedFiles();
123+
if (!files.length) {
124+
Logger.info('⚠️ No modified files found.');
125+
return;
126+
}
127+
128+
const { selected } = await inquirer.prompt([
129+
{
130+
type: 'checkbox',
131+
name: 'selected',
132+
message: 'Select files to discard changes:',
133+
choices: files,
134+
},
135+
]);
136+
137+
if (selected.length === 0) {
138+
Logger.info('No files selected.');
139+
return;
140+
}
141+
142+
const { confirm } = await inquirer.prompt([
143+
{
144+
type: 'confirm',
145+
name: 'confirm',
146+
message: 'This will discard local changes. Continue?',
147+
default: false,
148+
},
149+
]);
150+
151+
if (!confirm) return;
152+
153+
for (const file of selected) {
154+
await GitExecutor.run(`git restore ${file}`);
155+
Logger.success(`✅ Discarded changes for '${file}'`);
156+
}
157+
},
158+
159+
/**
160+
* 🧹 Clean untracked files/folders
161+
*/
162+
async cleanUntracked() {
163+
const { confirm } = await inquirer.prompt([
164+
{
165+
type: 'confirm',
166+
name: 'confirm',
167+
message: '⚠️ Remove ALL untracked files and folders? (git clean -fd)',
168+
default: false,
169+
},
170+
]);
171+
172+
if (!confirm) {
173+
Logger.info('❎ Cleanup cancelled.');
174+
return;
175+
}
176+
177+
Logger.info('🧹 Cleaning untracked files and directories...');
178+
await GitExecutor.run('git clean -fd');
179+
Logger.success('✅ Untracked files removed.');
180+
},
181+
182+
/**
183+
* ⚙️ Interactive reset mode (select type)
184+
*/
185+
async interactiveReset() {
186+
const { mode } = await inquirer.prompt([
187+
{
188+
type: 'list',
189+
name: 'mode',
190+
message: 'Select reset operation:',
191+
choices: [
192+
{ name: '↩️ Undo last commit (soft)', value: 'soft' },
193+
{ name: '🔄 Undo last commit (mixed)', value: 'mixed' },
194+
{ name: '💣 Undo last commit (hard)', value: 'hard' },
195+
{ name: '🕓 Reset to a specific commit', value: 'specific' },
196+
{ name: '🧩 Discard file changes', value: 'discard' },
197+
{ name: '🧹 Clean untracked files', value: 'clean' },
198+
],
199+
},
200+
]);
201+
202+
switch (mode) {
203+
case 'soft':
204+
await this.undoLastCommitSoft();
205+
break;
206+
case 'mixed':
207+
await this.undoLastCommitMixed();
208+
break;
209+
case 'hard':
210+
await this.undoLastCommitHard();
211+
break;
212+
case 'specific':
213+
await this.resetToSpecificCommit();
214+
break;
215+
case 'discard':
216+
await this.discardFileChanges();
217+
break;
218+
case 'clean':
219+
await this.cleanUntracked();
220+
break;
221+
}
222+
},
223+
};

0 commit comments

Comments
 (0)