easegit-cli 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/README.md +110 -0
- package/bin/easegit +2 -0
- package/dist/git/plumbing.d.ts +53 -0
- package/dist/git/plumbing.js +236 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +33 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +72 -0
- package/dist/snapshot/create.d.ts +4 -0
- package/dist/snapshot/create.js +23 -0
- package/dist/snapshot/restore.d.ts +4 -0
- package/dist/snapshot/restore.js +21 -0
- package/dist/status.d.ts +1 -0
- package/dist/status.js +25 -0
- package/dist/undo.d.ts +1 -0
- package/dist/undo.js +11 -0
- package/hooks/post-checkout +25 -0
- package/hooks/pre-merge-commit +8 -0
- package/hooks/pre-push +8 -0
- package/hooks/pre-rebase +8 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# EaseGit
|
|
2
|
+
|
|
3
|
+
**Automatic checkpoints before Git can hurt you.**
|
|
4
|
+
|
|
5
|
+
## What is EaseGit?
|
|
6
|
+
|
|
7
|
+
EaseGit creates automatic safety checkpoints before dangerous Git operations and lets you undo them with one command.
|
|
8
|
+
|
|
9
|
+
- Automatic protection during rebase, merge, push, checkout
|
|
10
|
+
- Restore everything in one command
|
|
11
|
+
- Zero configuration
|
|
12
|
+
- Invisible until needed
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g easegit
|
|
18
|
+
cd my-repo
|
|
19
|
+
easegit init
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Setup (one time)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
easegit init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This installs Git hooks that automatically create checkpoints before dangerous operations.
|
|
31
|
+
|
|
32
|
+
### Normal work (unchanged)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git commit
|
|
36
|
+
git merge
|
|
37
|
+
git rebase
|
|
38
|
+
git push --force
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
EaseGit runs silently in the background. You won't notice it.
|
|
42
|
+
|
|
43
|
+
### When something breaks
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
easegit undo
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This restores your working directory, staged files, and untracked files to the last safe checkpoint.
|
|
50
|
+
|
|
51
|
+
### Check status
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
easegit status
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Shows when the last checkpoint was created and what operation triggered it.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
EaseGit installs Git hooks that automatically create checkpoints before:
|
|
62
|
+
|
|
63
|
+
- `git rebase`
|
|
64
|
+
- `git merge`
|
|
65
|
+
- `git push`
|
|
66
|
+
- `git checkout`
|
|
67
|
+
- `git pull`
|
|
68
|
+
- `git reset`
|
|
69
|
+
|
|
70
|
+
Each checkpoint captures:
|
|
71
|
+
|
|
72
|
+
- All working files
|
|
73
|
+
- Staged changes
|
|
74
|
+
- Untracked files
|
|
75
|
+
|
|
76
|
+
Checkpoints are stored as Git objects under hidden refs. Your commit history is never modified.
|
|
77
|
+
|
|
78
|
+
## Recovery
|
|
79
|
+
|
|
80
|
+
If a Git operation damages your working state, run:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
easegit undo
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This restores everything exactly as it was before the operation.
|
|
87
|
+
|
|
88
|
+
## Commands
|
|
89
|
+
|
|
90
|
+
- `easegit init` - Set up EaseGit in this repository
|
|
91
|
+
- `easegit status` - Show last checkpoint information
|
|
92
|
+
- `easegit undo` - Restore last checkpoint
|
|
93
|
+
|
|
94
|
+
## What EaseGit is NOT
|
|
95
|
+
|
|
96
|
+
- Not a Git replacement
|
|
97
|
+
- Not a backup tool
|
|
98
|
+
- Not a tutorial
|
|
99
|
+
- No configuration needed
|
|
100
|
+
- No GUI
|
|
101
|
+
|
|
102
|
+
## Philosophy
|
|
103
|
+
|
|
104
|
+
Git is powerful but dangerous. EaseGit makes it safe by creating automatic undo points before operations that can lose work.
|
|
105
|
+
|
|
106
|
+
You never have to remember to create a checkpoint. EaseGit does it automatically.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/bin/easegit
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute a Git command and return output
|
|
3
|
+
*/
|
|
4
|
+
export declare function gitExec(args: string[], cwd?: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Check if current directory is a Git repository
|
|
7
|
+
*/
|
|
8
|
+
export declare function isGitRepo(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Get the Git directory path
|
|
11
|
+
*/
|
|
12
|
+
export declare function getGitDir(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Get the root directory of the Git repository
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRepoRoot(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Create a tree object from the current working directory and index
|
|
19
|
+
* Returns the tree SHA
|
|
20
|
+
*/
|
|
21
|
+
export declare function createTreeFromWorkingDir(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Create a ref under refs/easegit/checkpoints/
|
|
24
|
+
*/
|
|
25
|
+
export declare function createCheckpointRef(treeSha: string, operation: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Get the most recent checkpoint ref
|
|
28
|
+
*/
|
|
29
|
+
export declare function getLatestCheckpointRef(): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Get checkpoint info
|
|
32
|
+
*/
|
|
33
|
+
export declare function getCheckpointInfo(refName: string): {
|
|
34
|
+
timestamp: number;
|
|
35
|
+
operation: string;
|
|
36
|
+
commitSha: string;
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Restore working directory from a tree SHA
|
|
40
|
+
*/
|
|
41
|
+
export declare function restoreFromTree(commitSha: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Check if there are merge conflicts
|
|
44
|
+
*/
|
|
45
|
+
export declare function hasMergeConflicts(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Check if HEAD is detached
|
|
48
|
+
*/
|
|
49
|
+
export declare function isDetachedHead(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check if working directory is dirty
|
|
52
|
+
*/
|
|
53
|
+
export declare function isDirty(): boolean;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.gitExec = gitExec;
|
|
37
|
+
exports.isGitRepo = isGitRepo;
|
|
38
|
+
exports.getGitDir = getGitDir;
|
|
39
|
+
exports.getRepoRoot = getRepoRoot;
|
|
40
|
+
exports.createTreeFromWorkingDir = createTreeFromWorkingDir;
|
|
41
|
+
exports.createCheckpointRef = createCheckpointRef;
|
|
42
|
+
exports.getLatestCheckpointRef = getLatestCheckpointRef;
|
|
43
|
+
exports.getCheckpointInfo = getCheckpointInfo;
|
|
44
|
+
exports.restoreFromTree = restoreFromTree;
|
|
45
|
+
exports.hasMergeConflicts = hasMergeConflicts;
|
|
46
|
+
exports.isDetachedHead = isDetachedHead;
|
|
47
|
+
exports.isDirty = isDirty;
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
/**
|
|
51
|
+
* Execute a Git command and return output
|
|
52
|
+
*/
|
|
53
|
+
function gitExec(args, cwd) {
|
|
54
|
+
try {
|
|
55
|
+
return (0, child_process_1.execSync)(`git ${args.join(' ')}`, {
|
|
56
|
+
cwd: cwd || process.cwd(),
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
59
|
+
}).trim();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`Git command failed: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if current directory is a Git repository
|
|
67
|
+
*/
|
|
68
|
+
function isGitRepo() {
|
|
69
|
+
try {
|
|
70
|
+
gitExec(['rev-parse', '--git-dir']);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get the Git directory path
|
|
79
|
+
*/
|
|
80
|
+
function getGitDir() {
|
|
81
|
+
return gitExec(['rev-parse', '--git-dir']);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the root directory of the Git repository
|
|
85
|
+
*/
|
|
86
|
+
function getRepoRoot() {
|
|
87
|
+
return gitExec(['rev-parse', '--show-toplevel']);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a tree object from the current working directory and index
|
|
91
|
+
* Returns the tree SHA
|
|
92
|
+
*/
|
|
93
|
+
function createTreeFromWorkingDir() {
|
|
94
|
+
const fs = require('fs');
|
|
95
|
+
// Add all files including untracked to index temporarily
|
|
96
|
+
const originalIndex = path.join(getGitDir(), 'index');
|
|
97
|
+
const tempIndex = path.join(getGitDir(), 'index.easegit.tmp');
|
|
98
|
+
try {
|
|
99
|
+
// Copy current index
|
|
100
|
+
fs.copyFileSync(originalIndex, tempIndex);
|
|
101
|
+
// Set temporary index
|
|
102
|
+
process.env.GIT_INDEX_FILE = tempIndex;
|
|
103
|
+
// Add all files including untracked
|
|
104
|
+
gitExec(['add', '-A']);
|
|
105
|
+
// Write tree
|
|
106
|
+
const treeSha = gitExec(['write-tree']);
|
|
107
|
+
return treeSha;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
// Restore original index
|
|
111
|
+
delete process.env.GIT_INDEX_FILE;
|
|
112
|
+
// Clean up temp index
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(tempIndex);
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a ref under refs/easegit/checkpoints/
|
|
121
|
+
*/
|
|
122
|
+
function createCheckpointRef(treeSha, operation) {
|
|
123
|
+
const timestamp = Date.now();
|
|
124
|
+
const refName = `refs/easegit/checkpoints/${timestamp}`;
|
|
125
|
+
// Create a commit object for the checkpoint
|
|
126
|
+
const message = `EaseGit checkpoint before ${operation}\nTimestamp: ${timestamp}`;
|
|
127
|
+
// Use current HEAD as parent if it exists
|
|
128
|
+
const commitArgs = ['commit-tree', treeSha, '-m', message];
|
|
129
|
+
try {
|
|
130
|
+
const head = gitExec(['rev-parse', 'HEAD']);
|
|
131
|
+
commitArgs.splice(2, 0, '-p', head);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// No HEAD yet (empty repo)
|
|
135
|
+
}
|
|
136
|
+
const commitSha = gitExec(commitArgs).trim();
|
|
137
|
+
// Update ref
|
|
138
|
+
gitExec(['update-ref', refName, commitSha]);
|
|
139
|
+
return refName;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the most recent checkpoint ref
|
|
143
|
+
*/
|
|
144
|
+
function getLatestCheckpointRef() {
|
|
145
|
+
try {
|
|
146
|
+
const refs = gitExec(['for-each-ref', 'refs/easegit/checkpoints/', '--sort=-refname', '--format=%(refname)', '--count=1']);
|
|
147
|
+
return refs || null;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get checkpoint info
|
|
155
|
+
*/
|
|
156
|
+
function getCheckpointInfo(refName) {
|
|
157
|
+
try {
|
|
158
|
+
const commitSha = gitExec(['rev-parse', refName]);
|
|
159
|
+
const message = gitExec(['log', '-1', '--format=%B', commitSha]);
|
|
160
|
+
const timestampMatch = message.match(/Timestamp: (\d+)/);
|
|
161
|
+
const operationMatch = message.match(/checkpoint before (.+)/);
|
|
162
|
+
if (!timestampMatch)
|
|
163
|
+
return null;
|
|
164
|
+
return {
|
|
165
|
+
timestamp: parseInt(timestampMatch[1]),
|
|
166
|
+
operation: operationMatch ? operationMatch[1].split('\n')[0] : 'unknown',
|
|
167
|
+
commitSha
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Restore working directory from a tree SHA
|
|
176
|
+
*/
|
|
177
|
+
function restoreFromTree(commitSha) {
|
|
178
|
+
const repoRoot = getRepoRoot();
|
|
179
|
+
// Clear working directory (except .git)
|
|
180
|
+
gitExec(['clean', '-fd']);
|
|
181
|
+
// Checkout the tree without moving HEAD
|
|
182
|
+
gitExec(['checkout', commitSha, '--', '.']);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if there are merge conflicts
|
|
186
|
+
*/
|
|
187
|
+
function hasMergeConflicts() {
|
|
188
|
+
try {
|
|
189
|
+
const status = gitExec(['status', '--porcelain']);
|
|
190
|
+
return status.includes('UU ') || status.includes('AA ') || status.includes('DD ');
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if HEAD is detached
|
|
198
|
+
*/
|
|
199
|
+
function isDetachedHead() {
|
|
200
|
+
try {
|
|
201
|
+
gitExec(['symbolic-ref', 'HEAD']);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if working directory is dirty
|
|
210
|
+
*/
|
|
211
|
+
function isDirty() {
|
|
212
|
+
try {
|
|
213
|
+
const status = gitExec(['status', '--porcelain']);
|
|
214
|
+
return status.length > 0;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// CommonJS exports for hook scripts
|
|
221
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
222
|
+
module.exports = {
|
|
223
|
+
gitExec,
|
|
224
|
+
isGitRepo,
|
|
225
|
+
getGitDir,
|
|
226
|
+
getRepoRoot,
|
|
227
|
+
createTreeFromWorkingDir,
|
|
228
|
+
createCheckpointRef,
|
|
229
|
+
getLatestCheckpointRef,
|
|
230
|
+
getCheckpointInfo,
|
|
231
|
+
restoreFromTree,
|
|
232
|
+
hasMergeConflicts,
|
|
233
|
+
isDetachedHead,
|
|
234
|
+
isDirty
|
|
235
|
+
};
|
|
236
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const init_1 = require("./init");
|
|
4
|
+
const status_1 = require("./status");
|
|
5
|
+
const undo_1 = require("./undo");
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
async function main() {
|
|
8
|
+
try {
|
|
9
|
+
switch (command) {
|
|
10
|
+
case 'init':
|
|
11
|
+
await (0, init_1.init)();
|
|
12
|
+
break;
|
|
13
|
+
case 'status':
|
|
14
|
+
await (0, status_1.status)();
|
|
15
|
+
break;
|
|
16
|
+
case 'undo':
|
|
17
|
+
await (0, undo_1.undo)();
|
|
18
|
+
break;
|
|
19
|
+
default:
|
|
20
|
+
console.log('EaseGit - Automatic Git safety checkpoints\n');
|
|
21
|
+
console.log('Commands:');
|
|
22
|
+
console.log(' easegit init Set up EaseGit in this repository');
|
|
23
|
+
console.log(' easegit status Show last checkpoint information');
|
|
24
|
+
console.log(' easegit undo Restore last checkpoint');
|
|
25
|
+
process.exit(command ? 1 : 0);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
main();
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.init = init;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const plumbing_1 = require("./git/plumbing");
|
|
40
|
+
async function init() {
|
|
41
|
+
if (!(0, plumbing_1.isGitRepo)()) {
|
|
42
|
+
throw new Error('Not a Git repository. Run this command inside a Git repository.');
|
|
43
|
+
}
|
|
44
|
+
const gitDir = (0, plumbing_1.getGitDir)();
|
|
45
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
46
|
+
// Ensure hooks directory exists
|
|
47
|
+
if (!fs.existsSync(hooksDir)) {
|
|
48
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
// Hook definitions
|
|
51
|
+
const hooks = [
|
|
52
|
+
'pre-rebase',
|
|
53
|
+
'pre-merge-commit',
|
|
54
|
+
'pre-push',
|
|
55
|
+
'post-checkout'
|
|
56
|
+
];
|
|
57
|
+
// Get the hooks template directory (from installed package)
|
|
58
|
+
// This will be in node_modules/easegit/hooks or local hooks/ during development
|
|
59
|
+
const packageHooksDir = path.join(__dirname, '..', 'hooks');
|
|
60
|
+
// Install hooks
|
|
61
|
+
for (const hookName of hooks) {
|
|
62
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
63
|
+
const templatePath = path.join(packageHooksDir, hookName);
|
|
64
|
+
if (fs.existsSync(templatePath)) {
|
|
65
|
+
// Copy the hook template
|
|
66
|
+
const hookContent = fs.readFileSync(templatePath, 'utf8');
|
|
67
|
+
fs.writeFileSync(hookPath, hookContent, { mode: 0o755 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
console.log('EaseGit initialized successfully');
|
|
71
|
+
console.log('Automatic checkpoints will be created before dangerous operations');
|
|
72
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCheckpoint = createCheckpoint;
|
|
4
|
+
const plumbing_1 = require("../git/plumbing");
|
|
5
|
+
/**
|
|
6
|
+
* Create a checkpoint of the current working state
|
|
7
|
+
*/
|
|
8
|
+
function createCheckpoint(operation) {
|
|
9
|
+
try {
|
|
10
|
+
// Create tree from current working directory (includes untracked files)
|
|
11
|
+
const treeSha = (0, plumbing_1.createTreeFromWorkingDir)();
|
|
12
|
+
// Store as checkpoint ref
|
|
13
|
+
(0, plumbing_1.createCheckpointRef)(treeSha, operation);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// Silently fail - checkpoints are best-effort
|
|
17
|
+
// We never want to block the user's Git operation
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// CommonJS export for hook scripts
|
|
21
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
22
|
+
module.exports = { createCheckpoint };
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.restoreCheckpoint = restoreCheckpoint;
|
|
4
|
+
const plumbing_1 = require("../git/plumbing");
|
|
5
|
+
/**
|
|
6
|
+
* Restore the most recent checkpoint
|
|
7
|
+
*/
|
|
8
|
+
function restoreCheckpoint() {
|
|
9
|
+
const refName = (0, plumbing_1.getLatestCheckpointRef)();
|
|
10
|
+
if (!refName) {
|
|
11
|
+
throw new Error('No checkpoint found to restore');
|
|
12
|
+
}
|
|
13
|
+
const info = (0, plumbing_1.getCheckpointInfo)(refName);
|
|
14
|
+
if (!info) {
|
|
15
|
+
throw new Error('Checkpoint data is corrupted');
|
|
16
|
+
}
|
|
17
|
+
// Restore working directory from the checkpoint
|
|
18
|
+
(0, plumbing_1.restoreFromTree)(info.commitSha);
|
|
19
|
+
console.log(`Restored checkpoint from ${new Date(info.timestamp).toLocaleString()}`);
|
|
20
|
+
console.log(`Operation: ${info.operation}`);
|
|
21
|
+
}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function status(): Promise<void>;
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.status = status;
|
|
4
|
+
const plumbing_1 = require("./git/plumbing");
|
|
5
|
+
async function status() {
|
|
6
|
+
if (!(0, plumbing_1.isGitRepo)()) {
|
|
7
|
+
throw new Error('Not a Git repository');
|
|
8
|
+
}
|
|
9
|
+
const refName = (0, plumbing_1.getLatestCheckpointRef)();
|
|
10
|
+
if (!refName) {
|
|
11
|
+
console.log('No checkpoints available');
|
|
12
|
+
console.log('EaseGit will create checkpoints automatically before dangerous operations');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const info = (0, plumbing_1.getCheckpointInfo)(refName);
|
|
16
|
+
if (!info) {
|
|
17
|
+
console.log('Checkpoint data corrupted');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const date = new Date(info.timestamp);
|
|
21
|
+
console.log('Last checkpoint:');
|
|
22
|
+
console.log(` Time: ${date.toLocaleString()}`);
|
|
23
|
+
console.log(` Operation: ${info.operation}`);
|
|
24
|
+
console.log(` Undo available: yes`);
|
|
25
|
+
}
|
package/dist/undo.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function undo(): Promise<void>;
|
package/dist/undo.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.undo = undo;
|
|
4
|
+
const plumbing_1 = require("./git/plumbing");
|
|
5
|
+
const restore_1 = require("./snapshot/restore");
|
|
6
|
+
async function undo() {
|
|
7
|
+
if (!(0, plumbing_1.isGitRepo)()) {
|
|
8
|
+
throw new Error('Not a Git repository');
|
|
9
|
+
}
|
|
10
|
+
(0, restore_1.restoreCheckpoint)();
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Post-checkout hook - creates checkpoint after checkout
|
|
4
|
+
// Also detects failures and warns user
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const mainPath = path.join(__dirname, '..', '..');
|
|
7
|
+
const { createCheckpoint } = require(path.join(mainPath, 'dist', 'snapshot', 'create'));
|
|
8
|
+
const { hasMergeConflicts, isDetachedHead, isDirty } = require(path.join(mainPath, 'dist', 'git', 'plumbing'));
|
|
9
|
+
|
|
10
|
+
// Create checkpoint for the new state
|
|
11
|
+
createCheckpoint('checkout');
|
|
12
|
+
|
|
13
|
+
// Check for problems after checkout
|
|
14
|
+
try {
|
|
15
|
+
const hasConflicts = hasMergeConflicts();
|
|
16
|
+
const detached = isDetachedHead();
|
|
17
|
+
const dirty = isDirty();
|
|
18
|
+
|
|
19
|
+
if (hasConflicts || (detached && dirty)) {
|
|
20
|
+
console.error('⚠ EaseGit: last Git operation damaged your working state.');
|
|
21
|
+
console.error('Run `easegit undo` to restore the last safe checkpoint.');
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Silent fail
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Pre-merge hook - creates checkpoint before merge
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const mainPath = path.join(__dirname, '..', '..');
|
|
6
|
+
const { createCheckpoint } = require(path.join(mainPath, 'dist', 'snapshot', 'create'));
|
|
7
|
+
|
|
8
|
+
createCheckpoint('merge');
|
package/hooks/pre-push
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Pre-push hook - creates checkpoint before push
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const mainPath = path.join(__dirname, '..', '..');
|
|
6
|
+
const { createCheckpoint } = require(path.join(mainPath, 'dist', 'snapshot', 'create'));
|
|
7
|
+
|
|
8
|
+
createCheckpoint('push');
|
package/hooks/pre-rebase
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Pre-rebase hook - creates checkpoint before rebase
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const mainPath = path.join(__dirname, '..', '..');
|
|
6
|
+
const { createCheckpoint } = require(path.join(mainPath, 'dist', 'snapshot', 'create'));
|
|
7
|
+
|
|
8
|
+
createCheckpoint('rebase');
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "easegit-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatic checkpoints before Git can hurt you",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"easegit": "./bin/easegit"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"hooks",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublish": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"git",
|
|
21
|
+
"undo",
|
|
22
|
+
"safety",
|
|
23
|
+
"checkpoint",
|
|
24
|
+
"git-safety",
|
|
25
|
+
"git-undo"
|
|
26
|
+
],
|
|
27
|
+
"author": "hp-078",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/hp-078/easegit-cli.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/hp-078/easegit-cli/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/hp-078/easegit-cli#readme",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=16.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|