gent-cli 2.1.0 → 5.0.1
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 +215 -65
- package/package.json +1 -1
- package/src/commands/add.js +102 -23
- package/src/commands/clone.js +137 -86
- package/src/commands/commit.js +89 -81
- package/src/commands/diff.js +257 -0
- package/src/commands/init.js +5 -36
- package/src/commands/log.js +57 -13
- package/src/commands/merge.js +245 -0
- package/src/commands/pull.js +176 -86
- package/src/commands/push.js +172 -79
- package/src/commands/remote.js +97 -113
- package/src/commands/reset.js +149 -0
- package/src/commands/rm.js +85 -0
- package/src/commands/show.js +167 -0
- package/src/commands/stash.js +255 -0
- package/src/commands/status.js +80 -46
- package/src/commands/tag.js +146 -0
- package/src/index.js +108 -57
- package/src/utils/constants.js +10 -26
- package/src/utils/diff-engine.js +236 -0
- package/src/utils/fileSystem.js +8 -60
- package/src/utils/hash-engine.js +337 -0
- package/src/utils/merge-engine.js +379 -0
- package/src/utils/object-store.js +54 -0
- package/src/commands/create.js +0 -121
- package/src/commands/list.js +0 -67
- package/src/services/repo-service.js +0 -291
- package/src/utils/cloud-sync.js +0 -323
- package/src/utils/diff.js +0 -121
package/src/commands/clone.js
CHANGED
|
@@ -1,131 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Clone Command - Clone a remote repository to local filesystem
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* PURPOSE:
|
|
7
|
+
* Download an entire repository (commits + objects) from a remote server
|
|
8
|
+
* and set up a local working copy. Like `git clone`.
|
|
9
|
+
*
|
|
10
|
+
* USAGE:
|
|
11
|
+
* gent clone <url> → Clone into folder named after repo
|
|
12
|
+
* gent clone <url> <directory> → Clone into specific directory
|
|
13
|
+
*
|
|
14
|
+
* ALGORITHM:
|
|
15
|
+
* 1. GET <url>/clone/ → receives full repo (commits, objects, config)
|
|
16
|
+
* 2. Create .gent/ directory structure
|
|
17
|
+
* 3. Store all blob objects in local object store
|
|
18
|
+
* 4. Write commits.json with full history
|
|
19
|
+
* 5. Checkout HEAD (restore working tree from latest commit)
|
|
20
|
+
* 6. Configure remote "origin" pointing to <url>
|
|
21
|
+
*
|
|
22
|
+
* BACKEND EXPECTATIONS:
|
|
23
|
+
* GET /api/repos/:id/clone/
|
|
24
|
+
* Returns:
|
|
25
|
+
* {
|
|
26
|
+
* name: "repo-name",
|
|
27
|
+
* commits: [...],
|
|
28
|
+
* objects: [ { hash, type, data: "<base64>" } ],
|
|
29
|
+
* branches: { "main": "<hash>", ... },
|
|
30
|
+
* currentBranch: "main",
|
|
31
|
+
* tags: { ... }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* ============================================================================
|
|
4
35
|
*/
|
|
5
36
|
|
|
6
37
|
const fs = require('fs').promises;
|
|
38
|
+
const path = require('path');
|
|
7
39
|
const chalk = require('chalk');
|
|
8
40
|
const ora = require('ora');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const repoService = require('../services/repo-service');
|
|
11
|
-
const authStorage = require('../utils/auth-storage');
|
|
12
41
|
const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
|
|
13
42
|
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
|
|
14
|
-
const
|
|
43
|
+
const apiClient = require('../utils/api-client');
|
|
44
|
+
const { storeBlob, readBlobAsString } = require('../utils/hash-engine');
|
|
15
45
|
|
|
16
46
|
/**
|
|
17
|
-
* Clone
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {Object} options
|
|
47
|
+
* Clone remote repository
|
|
48
|
+
* @param {String} url - Remote repository URL
|
|
49
|
+
* @param {String} directory - Optional target directory
|
|
50
|
+
* @param {Object} options
|
|
21
51
|
*/
|
|
22
|
-
async function clone(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.error(chalk.red('Error: You must be logged in to clone a repository'));
|
|
28
|
-
console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Parse repository URL
|
|
33
|
-
if (!repoUrl) {
|
|
34
|
-
console.error(chalk.red('Error: Repository URL is required'));
|
|
35
|
-
console.log(chalk.yellow('Usage:'), chalk.cyan('gent clone <owner_id>/<repo_name> [directory]'));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const parts = repoUrl.split('/');
|
|
40
|
-
if (parts.length !== 2) {
|
|
41
|
-
console.error(chalk.red('Error: Invalid repository URL format'));
|
|
42
|
-
console.log(chalk.yellow('Expected:'), chalk.cyan('<owner_id>/<repo_name>'));
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
52
|
+
async function clone(url, directory, options) {
|
|
53
|
+
if (!url) {
|
|
54
|
+
console.error(chalk.red('Usage: gent clone <url> [directory]'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
const repoName = parts[1];
|
|
58
|
+
const spinner = ora(`Cloning from ${url}...`).start();
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
60
|
+
try {
|
|
61
|
+
// Fetch full repo from remote
|
|
62
|
+
spinner.text = 'Downloading repository data...';
|
|
63
|
+
const response = await apiClient.get(`${url}/clone/`);
|
|
53
64
|
|
|
54
|
-
|
|
65
|
+
const repoName = response.name || 'gent-repo';
|
|
55
66
|
const targetDir = directory || repoName;
|
|
56
67
|
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
57
68
|
|
|
58
|
-
// Check if directory already exists
|
|
59
69
|
if (await pathExists(targetPath)) {
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
const items = await fs.readdir(targetPath);
|
|
71
|
+
if (items.length > 0) {
|
|
72
|
+
spinner.fail(chalk.red(`Directory '${targetDir}' is not empty`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
console.log(chalk.cyan(`Cloning ${ownerId}/${repoName} into '${targetDir}'...\n`));
|
|
65
|
-
|
|
66
|
-
// Fetch repository metadata
|
|
67
|
-
const spinner = ora('Fetching repository...').start();
|
|
68
|
-
const repository = await repoService.getRepository(ownerId, repoName);
|
|
69
|
-
spinner.succeed('Repository fetched');
|
|
70
|
-
|
|
71
77
|
// Create directory structure
|
|
72
|
-
|
|
78
|
+
spinner.text = 'Setting up repository...';
|
|
73
79
|
const gentPath = path.join(targetPath, GENT_DIR);
|
|
74
80
|
await ensureDir(gentPath);
|
|
75
81
|
await ensureDir(path.join(gentPath, 'objects'));
|
|
76
82
|
await ensureDir(path.join(gentPath, 'refs', 'heads'));
|
|
77
83
|
await ensureDir(path.join(gentPath, 'refs', 'tags'));
|
|
78
84
|
|
|
79
|
-
//
|
|
85
|
+
// Store blob objects
|
|
86
|
+
const objects = response.objects || [];
|
|
87
|
+
spinner.text = `Storing ${objects.length} object(s)...`;
|
|
88
|
+
|
|
89
|
+
for (const obj of objects) {
|
|
90
|
+
if (obj.type === 'blob' && obj.data) {
|
|
91
|
+
const buf = Buffer.from(obj.data, 'base64');
|
|
92
|
+
await storeBlob(gentPath, buf);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Write commits.json
|
|
97
|
+
const repoData = {
|
|
98
|
+
commits: response.commits || [],
|
|
99
|
+
branches: response.branches || { main: null },
|
|
100
|
+
currentBranch: response.currentBranch || 'main',
|
|
101
|
+
tags: response.tags || {}
|
|
102
|
+
};
|
|
103
|
+
await writeJSON(path.join(gentPath, COMMITS_FILE), repoData);
|
|
104
|
+
|
|
105
|
+
// Write config with remote
|
|
80
106
|
const config = {
|
|
81
|
-
user: {
|
|
82
|
-
name: user.first_name && user.last_name ? `${user.first_name} ${user.last_name}` : '',
|
|
83
|
-
email: user.email
|
|
84
|
-
},
|
|
107
|
+
user: { name: '', email: '' },
|
|
85
108
|
repository: {
|
|
86
|
-
name:
|
|
87
|
-
description:
|
|
109
|
+
name: repoName,
|
|
110
|
+
description: response.description || '',
|
|
88
111
|
created: new Date().toISOString()
|
|
89
|
-
}
|
|
112
|
+
},
|
|
113
|
+
remotes: {
|
|
114
|
+
origin: { url }
|
|
115
|
+
},
|
|
116
|
+
remoteRefs: {}
|
|
90
117
|
};
|
|
118
|
+
|
|
119
|
+
// Set remote ref to head
|
|
120
|
+
const headHash = repoData.branches[repoData.currentBranch];
|
|
121
|
+
if (headHash) {
|
|
122
|
+
config.remoteRefs[`origin/${repoData.currentBranch}`] = headHash;
|
|
123
|
+
}
|
|
124
|
+
|
|
91
125
|
await writeJSON(path.join(gentPath, CONFIG_FILE), config);
|
|
92
126
|
|
|
93
|
-
//
|
|
94
|
-
await writeJSON(path.join(gentPath, STAGING_FILE), { files: [] });
|
|
95
|
-
await writeJSON(path.join(gentPath, COMMITS_FILE), {
|
|
96
|
-
commits: [],
|
|
97
|
-
branches: { [repository.default_branch]: null },
|
|
98
|
-
currentBranch: repository.default_branch
|
|
99
|
-
});
|
|
127
|
+
// Write staging.json
|
|
128
|
+
await writeJSON(path.join(gentPath, STAGING_FILE), { entries: [], files: [] });
|
|
100
129
|
|
|
101
|
-
//
|
|
130
|
+
// Write HEAD file
|
|
102
131
|
await fs.writeFile(
|
|
103
132
|
path.join(gentPath, 'HEAD'),
|
|
104
|
-
`ref: refs/heads/${
|
|
133
|
+
`ref: refs/heads/${repoData.currentBranch}\n`
|
|
105
134
|
);
|
|
106
135
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
// Create .gentignore
|
|
137
|
+
const ignorePath = path.join(targetPath, '.gentignore');
|
|
138
|
+
await fs.writeFile(ignorePath, `# Gent ignore\nnode_modules/\n.DS_Store\n*.log\n.env\n.gent/\n`);
|
|
139
|
+
|
|
140
|
+
// Checkout working tree from HEAD commit
|
|
141
|
+
if (headHash) {
|
|
142
|
+
const headCommit = repoData.commits.find(c => c.hash === headHash);
|
|
143
|
+
if (headCommit) {
|
|
144
|
+
const tree = headCommit.tree || (headCommit.files || []).map(f => ({
|
|
145
|
+
name: f.path || f.name, hash: f.hash
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
spinner.text = 'Checking out files...';
|
|
149
|
+
let fileCount = 0;
|
|
150
|
+
for (const entry of tree) {
|
|
151
|
+
try {
|
|
152
|
+
const content = await readBlobAsString(gentPath, entry.hash);
|
|
153
|
+
const fullPath = path.join(targetPath, entry.name || entry.path);
|
|
154
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
155
|
+
await fs.writeFile(fullPath, content, 'utf-8');
|
|
156
|
+
fileCount++;
|
|
157
|
+
} catch {
|
|
158
|
+
// Blob missing
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
spinner.succeed(chalk.green(`Cloned into '${targetDir}'`));
|
|
163
|
+
console.log(chalk.gray(` ${repoData.commits.length} commit(s), ${objects.length} object(s), ${fileCount} file(s)`));
|
|
164
|
+
} else {
|
|
165
|
+
spinner.succeed(chalk.green(`Cloned into '${targetDir}' (empty)`));
|
|
117
166
|
}
|
|
118
|
-
|
|
167
|
+
} else {
|
|
168
|
+
spinner.succeed(chalk.green(`Cloned into '${targetDir}' (no commits)`));
|
|
119
169
|
}
|
|
120
170
|
|
|
121
|
-
console.log(chalk.green(`\n✓ Successfully cloned into '${targetDir}'`));
|
|
122
|
-
console.log(chalk.yellow('\nNext steps:'));
|
|
123
|
-
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
124
|
-
console.log(chalk.cyan(' gent status'), chalk.gray('- Check repository status'));
|
|
125
|
-
|
|
126
171
|
} catch (error) {
|
|
127
|
-
|
|
128
|
-
|
|
172
|
+
spinner.fail(chalk.red('Clone failed'));
|
|
173
|
+
if (error.response?.status === 404) {
|
|
174
|
+
console.error(chalk.red('Repository not found'));
|
|
175
|
+
} else if (error.response?.data?.message) {
|
|
176
|
+
console.error(chalk.red(error.response.data.message));
|
|
177
|
+
} else {
|
|
178
|
+
console.error(chalk.red('Error:'), error.message);
|
|
179
|
+
}
|
|
129
180
|
process.exit(1);
|
|
130
181
|
}
|
|
131
182
|
}
|
package/src/commands/commit.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commit Command - Record changes to the repository
|
|
3
|
-
* Creates a new commit with staged
|
|
3
|
+
* Creates a new commit with tree object referencing staged blobs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const fs = require('fs').promises;
|
|
8
7
|
const chalk = require('chalk');
|
|
9
8
|
const inquirer = require('inquirer');
|
|
10
9
|
const ora = require('ora');
|
|
11
|
-
const { getGentPath, readJSON, writeJSON
|
|
10
|
+
const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
12
11
|
const authStorage = require('../utils/auth-storage');
|
|
13
12
|
const { STAGING_FILE, COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
|
|
14
|
-
const { generateCommitHash
|
|
15
|
-
const {
|
|
13
|
+
const { generateCommitHash } = require('../utils/helpers');
|
|
14
|
+
const { storeTree, snapshotFile } = require('../utils/hash-engine');
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Create a new commit
|
|
@@ -25,9 +24,10 @@ async function commit(options) {
|
|
|
25
24
|
|
|
26
25
|
// Read staging area
|
|
27
26
|
const staging = await readJSON(path.join(gentPath, STAGING_FILE));
|
|
27
|
+
const stagedEntries = staging.entries || [];
|
|
28
28
|
const stagedFiles = staging.files || [];
|
|
29
29
|
|
|
30
|
-
if (stagedFiles.length === 0) {
|
|
30
|
+
if (stagedEntries.length === 0 && stagedFiles.length === 0) {
|
|
31
31
|
console.log(chalk.yellow('No changes added to commit'));
|
|
32
32
|
console.log(chalk.gray('Use "gent add <file>..." to stage files'));
|
|
33
33
|
return;
|
|
@@ -59,20 +59,14 @@ async function commit(options) {
|
|
|
59
59
|
let authorName = config.user.name;
|
|
60
60
|
let authorEmail = config.user.email;
|
|
61
61
|
|
|
62
|
-
// Fallback to global auth if local config is empty
|
|
63
62
|
if (!authorName || !authorEmail) {
|
|
64
63
|
const globalUser = await authStorage.getUser();
|
|
65
64
|
if (globalUser) {
|
|
66
|
-
if (!authorName)
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
if (!authorEmail) {
|
|
70
|
-
authorEmail = globalUser.email;
|
|
71
|
-
}
|
|
65
|
+
if (!authorName) authorName = [globalUser.first_name, globalUser.last_name].filter(Boolean).join(' ');
|
|
66
|
+
if (!authorEmail) authorEmail = globalUser.email;
|
|
72
67
|
}
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
// Fail if still no identity
|
|
76
70
|
if (!authorName || !authorEmail) {
|
|
77
71
|
spinner.stop();
|
|
78
72
|
console.error(chalk.red('Author identity unknown'));
|
|
@@ -80,92 +74,106 @@ async function commit(options) {
|
|
|
80
74
|
return;
|
|
81
75
|
}
|
|
82
76
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
77
|
+
// Build tree entries from staged data
|
|
78
|
+
let treeEntries = [];
|
|
79
|
+
|
|
80
|
+
if (stagedEntries.length > 0) {
|
|
81
|
+
// New format: entries already have blob hashes from gent add
|
|
82
|
+
// Carry forward unchanged files from parent commit
|
|
83
|
+
const parentHash = repository.branches[repository.currentBranch] || null;
|
|
84
|
+
const parentCommit = parentHash
|
|
85
|
+
? (repository.commits || []).find(c => c.hash === parentHash)
|
|
86
|
+
: null;
|
|
87
|
+
const parentTree = parentCommit && parentCommit.tree
|
|
88
|
+
? parentCommit.tree
|
|
89
|
+
: (parentCommit ? parentCommit.files.map(f => ({ mode: '100644', name: f.path, hash: f.hash, type: 'blob' })) : []);
|
|
90
|
+
|
|
91
|
+
// Start from parent tree, overlay staged changes
|
|
92
|
+
const treeMap = new Map(parentTree.map(e => [e.name, e]));
|
|
93
|
+
|
|
94
|
+
for (const entry of stagedEntries) {
|
|
95
|
+
if (entry.status === 'deleted') {
|
|
96
|
+
treeMap.delete(entry.path);
|
|
97
|
+
} else {
|
|
98
|
+
treeMap.set(entry.path, {
|
|
99
|
+
mode: '100644',
|
|
100
|
+
name: entry.path,
|
|
101
|
+
hash: entry.hash,
|
|
102
|
+
type: 'blob'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
parentCommit.files.forEach(f => parentFilesMap.set(f.path, f.hash));
|
|
107
|
+
treeEntries = Array.from(treeMap.values());
|
|
108
|
+
} else {
|
|
109
|
+
// Legacy fallback: files array without blob hashes
|
|
110
|
+
for (const file of stagedFiles) {
|
|
111
|
+
const entry = await snapshotFile(gentPath, cwd, file);
|
|
112
|
+
treeEntries.push(entry);
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
// Store tree object
|
|
117
|
+
const treeHash = await storeTree(gentPath, treeEntries);
|
|
118
|
+
|
|
119
|
+
// Compute diff stats
|
|
120
|
+
let totalInsertions = 0;
|
|
121
|
+
let totalDeletions = 0;
|
|
122
|
+
if (stagedEntries.length > 0) {
|
|
123
|
+
for (const e of stagedEntries) {
|
|
124
|
+
if (e.stats) {
|
|
125
|
+
totalInsertions += e.stats.insertions || 0;
|
|
126
|
+
totalDeletions += e.stats.deletions || 0;
|
|
127
|
+
}
|
|
117
128
|
}
|
|
129
|
+
}
|
|
118
130
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
// Create commit object
|
|
132
|
+
const commitObj = {
|
|
133
|
+
hash: generateCommitHash(),
|
|
134
|
+
message,
|
|
135
|
+
author: {
|
|
136
|
+
name: authorName,
|
|
137
|
+
email: authorEmail
|
|
138
|
+
},
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
parent: repository.branches[repository.currentBranch] || null,
|
|
141
|
+
treeHash,
|
|
142
|
+
tree: treeEntries,
|
|
143
|
+
files: treeEntries.map(e => ({ path: e.name, hash: e.hash })), // backward compat
|
|
144
|
+
stats: {
|
|
145
|
+
filesChanged: stagedEntries.length || stagedFiles.length,
|
|
146
|
+
insertions: totalInsertions,
|
|
147
|
+
deletions: totalDeletions
|
|
129
148
|
}
|
|
149
|
+
};
|
|
130
150
|
|
|
131
|
-
|
|
132
|
-
const changes = computeDiff(oldContent, content);
|
|
133
|
-
|
|
134
|
-
commit.files.push({
|
|
135
|
-
path: file,
|
|
136
|
-
hash: hash,
|
|
137
|
-
diff: changes
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Add commit to repository
|
|
151
|
+
// Save commit
|
|
142
152
|
repository.commits = repository.commits || [];
|
|
143
|
-
repository.commits.push(
|
|
144
|
-
repository.branches[repository.currentBranch] =
|
|
153
|
+
repository.commits.push(commitObj);
|
|
154
|
+
repository.branches[repository.currentBranch] = commitObj.hash;
|
|
145
155
|
|
|
146
|
-
// Save repository and clear staging
|
|
147
156
|
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
157
|
+
|
|
158
|
+
// Clear staging
|
|
159
|
+
staging.entries = [];
|
|
148
160
|
staging.files = [];
|
|
149
161
|
await writeJSON(path.join(gentPath, STAGING_FILE), staging);
|
|
150
162
|
|
|
151
|
-
spinner.succeed(chalk.green('
|
|
152
|
-
|
|
153
|
-
// Display commit info
|
|
154
|
-
console.log(chalk.cyan(`\n[${repository.currentBranch} ${commit.sha.substring(0, 7)}] ${message}`));
|
|
155
|
-
console.log(chalk.gray(`Author: ${commit.author.name} <${commit.author.email}>`));
|
|
156
|
-
console.log(chalk.gray(`Date: ${new Date(commit.timestamp).toLocaleString()}`));
|
|
157
|
-
console.log(chalk.gray(`\n${commit.files.length} file(s) changed`));
|
|
163
|
+
spinner.succeed(chalk.green('Changes committed successfully!'));
|
|
158
164
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
165
|
+
console.log(chalk.cyan(`\n[${repository.currentBranch} ${commitObj.hash.substring(0, 7)}] ${message}`));
|
|
166
|
+
console.log(chalk.gray(`Author: ${commitObj.author.name} <${commitObj.author.email}>`));
|
|
167
|
+
console.log(chalk.gray(`Date: ${new Date(commitObj.timestamp).toLocaleString()}`));
|
|
168
|
+
console.log(chalk.gray(`Tree: ${treeHash.substring(0, 7)}`));
|
|
169
|
+
console.log(chalk.gray(`\n${commitObj.stats.filesChanged} file(s) changed, `) +
|
|
170
|
+
chalk.green(`+${totalInsertions} insertions`) + ', ' +
|
|
171
|
+
chalk.red(`-${totalDeletions} deletions`));
|
|
164
172
|
|
|
165
173
|
} catch (error) {
|
|
166
174
|
if (error.code === 'ENOENT' && error.message.includes('.gent')) {
|
|
167
175
|
console.error(chalk.red('Error: Not a gent repository'));
|
|
168
|
-
console.log(chalk.yellow('\
|
|
176
|
+
console.log(chalk.yellow('\nRun "gent init" to initialize a repository'));
|
|
169
177
|
} else {
|
|
170
178
|
console.error(chalk.red('Error:'), error.message);
|
|
171
179
|
}
|