gent-cli 2.0.0 → 5.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 +28 -42
- 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 -284
- package/src/utils/cloud-sync.js +0 -323
- package/src/utils/diff.js +0 -121
package/src/commands/status.js
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Status Command - Show working tree status
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* PURPOSE:
|
|
7
|
+
* Display staged, modified, untracked, and deleted files. Like `git status`.
|
|
8
|
+
*
|
|
9
|
+
* USAGE:
|
|
10
|
+
* gent status → Detailed status
|
|
11
|
+
* gent status -s → Short format
|
|
12
|
+
*
|
|
13
|
+
* ALGORITHM:
|
|
14
|
+
* Computes SHA-256 blob hash for each working file, compares against:
|
|
15
|
+
* - Staging entries (from gent add)
|
|
16
|
+
* - Last commit tree (from commits.json)
|
|
17
|
+
* Classifies files as staged/modified/untracked/deleted.
|
|
18
|
+
*
|
|
19
|
+
* BACKEND EXPECTATIONS:
|
|
20
|
+
* None (local only).
|
|
21
|
+
*
|
|
22
|
+
* ============================================================================
|
|
4
23
|
*/
|
|
5
24
|
|
|
6
25
|
const fs = require('fs').promises;
|
|
7
26
|
const path = require('path');
|
|
8
27
|
const chalk = require('chalk');
|
|
9
|
-
const { getGentPath, readJSON, pathExists,
|
|
28
|
+
const { getGentPath, readJSON, pathExists, getIgnorePatterns, getAllFiles } = require('../utils/fileSystem');
|
|
10
29
|
const { STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
|
|
11
|
-
const {
|
|
30
|
+
const { hashBlob } = require('../utils/hash-engine');
|
|
12
31
|
|
|
13
32
|
/**
|
|
14
33
|
* Show repository status
|
|
@@ -19,59 +38,66 @@ async function status(options) {
|
|
|
19
38
|
const gentPath = await getGentPath();
|
|
20
39
|
const cwd = process.cwd();
|
|
21
40
|
|
|
22
|
-
// Read staging area and commits
|
|
23
41
|
const staging = await readJSON(path.join(gentPath, STAGING_FILE));
|
|
24
42
|
const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
|
|
25
43
|
|
|
26
|
-
const
|
|
44
|
+
const stagedEntries = staging.entries || [];
|
|
45
|
+
const stagedFiles = stagedEntries.map(e => e.path);
|
|
27
46
|
const currentBranch = repository.currentBranch || 'main';
|
|
28
|
-
const
|
|
47
|
+
const lastCommitHash = repository.branches[currentBranch];
|
|
48
|
+
|
|
49
|
+
// Build HEAD tree map: path → blobHash
|
|
50
|
+
const lastCommit = lastCommitHash
|
|
51
|
+
? (repository.commits || []).find(c => c.hash === lastCommitHash)
|
|
52
|
+
: null;
|
|
53
|
+
const trackedMap = new Map();
|
|
54
|
+
if (lastCommit) {
|
|
55
|
+
const tree = lastCommit.tree || lastCommit.files || [];
|
|
56
|
+
for (const f of tree) trackedMap.set(f.path || f.name, f.hash);
|
|
57
|
+
}
|
|
29
58
|
|
|
30
|
-
// Get all
|
|
59
|
+
// Get all working directory files
|
|
31
60
|
const ignorePatterns = await getIgnorePatterns(cwd);
|
|
32
61
|
const allFiles = await getAllFiles(cwd, ignorePatterns);
|
|
33
62
|
|
|
34
|
-
// Get tracked files from last commit
|
|
35
|
-
const trackedFiles = await getTrackedFiles(gentPath, lastCommit);
|
|
36
|
-
|
|
37
|
-
// Categorize files
|
|
38
63
|
const stagedSet = new Set(stagedFiles);
|
|
39
|
-
const trackedSet = new Set(trackedFiles.map(f => f.path));
|
|
40
|
-
|
|
41
64
|
const modified = [];
|
|
42
65
|
const untracked = [];
|
|
43
66
|
const deleted = [];
|
|
44
67
|
|
|
45
|
-
// Check
|
|
46
|
-
for (const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
modified.push(
|
|
68
|
+
// Check working tree against HEAD
|
|
69
|
+
for (const absFile of allFiles) {
|
|
70
|
+
const relPath = path.relative(cwd, absFile);
|
|
71
|
+
const headHash = trackedMap.get(relPath);
|
|
72
|
+
|
|
73
|
+
if (headHash) {
|
|
74
|
+
// Tracked file — check modification via blob hash
|
|
75
|
+
const content = await fs.readFile(absFile);
|
|
76
|
+
const currentHash = hashBlob(content);
|
|
77
|
+
if (currentHash !== headHash && !stagedSet.has(relPath)) {
|
|
78
|
+
modified.push(relPath);
|
|
56
79
|
}
|
|
57
|
-
} else if (!stagedSet.has(
|
|
58
|
-
untracked.push(
|
|
80
|
+
} else if (!stagedSet.has(relPath)) {
|
|
81
|
+
untracked.push(relPath);
|
|
59
82
|
}
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
// Check for deleted files
|
|
63
|
-
for (const
|
|
64
|
-
const fullPath = path.join(cwd,
|
|
65
|
-
if (!await pathExists(fullPath) && !stagedSet.has(
|
|
66
|
-
deleted.push(
|
|
86
|
+
for (const [trackedPath] of trackedMap) {
|
|
87
|
+
const fullPath = path.join(cwd, trackedPath);
|
|
88
|
+
if (!await pathExists(fullPath) && !stagedSet.has(trackedPath)) {
|
|
89
|
+
deleted.push(trackedPath);
|
|
67
90
|
}
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
//
|
|
93
|
+
// Check for merge in progress
|
|
94
|
+
const mergeState = staging.mergeState || null;
|
|
95
|
+
|
|
96
|
+
// Display
|
|
71
97
|
if (options.short) {
|
|
72
|
-
displayShortStatus(
|
|
98
|
+
displayShortStatus(stagedEntries, modified, untracked, deleted);
|
|
73
99
|
} else {
|
|
74
|
-
displayDetailedStatus(currentBranch,
|
|
100
|
+
displayDetailedStatus(currentBranch, stagedEntries, modified, untracked, deleted, lastCommitHash, mergeState);
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
} catch (error) {
|
|
@@ -88,9 +114,13 @@ async function status(options) {
|
|
|
88
114
|
/**
|
|
89
115
|
* Display detailed status output
|
|
90
116
|
*/
|
|
91
|
-
function displayDetailedStatus(branch,
|
|
117
|
+
function displayDetailedStatus(branch, stagedEntries, modified, untracked, deleted, lastCommit, mergeState) {
|
|
92
118
|
console.log(chalk.bold(`On branch ${chalk.cyan(branch)}`));
|
|
93
119
|
|
|
120
|
+
if (mergeState) {
|
|
121
|
+
console.log(chalk.yellow(`Merging branch '${mergeState.sourceBranch}'`));
|
|
122
|
+
}
|
|
123
|
+
|
|
94
124
|
if (!lastCommit) {
|
|
95
125
|
console.log(chalk.gray('No commits yet\n'));
|
|
96
126
|
} else {
|
|
@@ -98,12 +128,15 @@ function displayDetailedStatus(branch, staged, modified, untracked, deleted, las
|
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
// Staged files
|
|
101
|
-
if (
|
|
131
|
+
if (stagedEntries.length > 0) {
|
|
102
132
|
console.log(chalk.green.bold('Changes to be committed:'));
|
|
103
133
|
console.log(chalk.gray(' (use "gent reset <file>..." to unstage)\n'));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
134
|
+
for (const entry of stagedEntries) {
|
|
135
|
+
const icon = entry.status === 'added' ? 'new file: '
|
|
136
|
+
: entry.status === 'deleted' ? 'deleted: '
|
|
137
|
+
: 'modified: ';
|
|
138
|
+
console.log(chalk.green(`\t${icon}${entry.path}`));
|
|
139
|
+
}
|
|
107
140
|
console.log();
|
|
108
141
|
}
|
|
109
142
|
|
|
@@ -136,9 +169,9 @@ function displayDetailedStatus(branch, staged, modified, untracked, deleted, las
|
|
|
136
169
|
}
|
|
137
170
|
|
|
138
171
|
// Status summary
|
|
139
|
-
if (
|
|
140
|
-
console.log(chalk.green('
|
|
141
|
-
} else if (
|
|
172
|
+
if (stagedEntries.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0) {
|
|
173
|
+
console.log(chalk.green('Working tree clean'));
|
|
174
|
+
} else if (stagedEntries.length === 0) {
|
|
142
175
|
console.log(chalk.yellow('No changes added to commit (use "gent add" to track files)'));
|
|
143
176
|
}
|
|
144
177
|
}
|
|
@@ -146,10 +179,11 @@ function displayDetailedStatus(branch, staged, modified, untracked, deleted, las
|
|
|
146
179
|
/**
|
|
147
180
|
* Display short status output
|
|
148
181
|
*/
|
|
149
|
-
function displayShortStatus(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
182
|
+
function displayShortStatus(stagedEntries, modified, untracked, deleted) {
|
|
183
|
+
for (const entry of stagedEntries) {
|
|
184
|
+
const code = entry.status === 'added' ? 'A ' : entry.status === 'deleted' ? 'D ' : 'M ';
|
|
185
|
+
console.log(chalk.green(code) + entry.path);
|
|
186
|
+
}
|
|
153
187
|
|
|
154
188
|
modified.forEach(file => {
|
|
155
189
|
console.log(chalk.red(' M ') + file);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Tag Command - Create, list, and delete named references to commits
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* PURPOSE:
|
|
7
|
+
* Mark specific commits with version labels (e.g. v1.0.0). Like `git tag`.
|
|
8
|
+
*
|
|
9
|
+
* USAGE:
|
|
10
|
+
* gent tag → List all tags
|
|
11
|
+
* gent tag <name> → Create lightweight tag on HEAD
|
|
12
|
+
* gent tag <name> -m <msg> → Create annotated tag with message
|
|
13
|
+
* gent tag -d <name> → Delete a tag
|
|
14
|
+
*
|
|
15
|
+
* ALGORITHM:
|
|
16
|
+
* Tags stored in commits.json under "tags" map: { name → { hash, message, ... } }
|
|
17
|
+
* Lightweight tag = just a name pointing to commit hash.
|
|
18
|
+
* Annotated tag = includes tagger info, message, timestamp.
|
|
19
|
+
*
|
|
20
|
+
* BACKEND EXPECTATIONS:
|
|
21
|
+
* POST /api/repos/:id/tags/ { name, hash, message, annotated }
|
|
22
|
+
* GET /api/repos/:id/tags/
|
|
23
|
+
* DELETE /api/repos/:id/tags/:name/
|
|
24
|
+
*
|
|
25
|
+
* ============================================================================
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const chalk = require('chalk');
|
|
30
|
+
const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
31
|
+
const { COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
|
|
32
|
+
const authStorage = require('../utils/auth-storage');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Manage tags
|
|
36
|
+
* @param {String} name - Tag name (optional)
|
|
37
|
+
* @param {Object} options
|
|
38
|
+
*/
|
|
39
|
+
async function tag(name, options) {
|
|
40
|
+
try {
|
|
41
|
+
const gentPath = await getGentPath();
|
|
42
|
+
const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
|
|
43
|
+
repository.tags = repository.tags || {};
|
|
44
|
+
|
|
45
|
+
if (options.delete) {
|
|
46
|
+
await deleteTag(options.delete, repository, gentPath);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (name) {
|
|
51
|
+
await createTag(name, repository, gentPath, options);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// List tags
|
|
56
|
+
listTags(repository);
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code === 'ENOENT' && error.message.includes('.gent')) {
|
|
60
|
+
console.error(chalk.red('Error: Not a gent repository'));
|
|
61
|
+
} else {
|
|
62
|
+
console.error(chalk.red('Error:'), error.message);
|
|
63
|
+
}
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* List all tags
|
|
70
|
+
*/
|
|
71
|
+
function listTags(repository) {
|
|
72
|
+
const tags = Object.keys(repository.tags).sort();
|
|
73
|
+
|
|
74
|
+
if (tags.length === 0) {
|
|
75
|
+
console.log(chalk.gray('No tags'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const tagName of tags) {
|
|
80
|
+
const t = repository.tags[tagName];
|
|
81
|
+
const short = t.hash ? t.hash.substring(0, 7) : '???????';
|
|
82
|
+
const msg = t.message ? chalk.gray(` — ${t.message}`) : '';
|
|
83
|
+
console.log(chalk.yellow(tagName) + chalk.gray(` → ${short}`) + msg);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a tag
|
|
89
|
+
*/
|
|
90
|
+
async function createTag(name, repository, gentPath, options) {
|
|
91
|
+
if (repository.tags[name]) {
|
|
92
|
+
console.error(chalk.red(`Tag '${name}' already exists`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const currentBranch = repository.currentBranch;
|
|
97
|
+
const commitHash = repository.branches[currentBranch];
|
|
98
|
+
|
|
99
|
+
if (!commitHash) {
|
|
100
|
+
console.error(chalk.red('No commits to tag'));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const tagObj = { hash: commitHash };
|
|
105
|
+
|
|
106
|
+
// Annotated tag
|
|
107
|
+
if (options.message) {
|
|
108
|
+
const config = await readJSON(path.join(gentPath, CONFIG_FILE));
|
|
109
|
+
let taggerName = config.user.name;
|
|
110
|
+
let taggerEmail = config.user.email;
|
|
111
|
+
|
|
112
|
+
if (!taggerName || !taggerEmail) {
|
|
113
|
+
const user = await authStorage.getUser();
|
|
114
|
+
if (user) {
|
|
115
|
+
taggerName = taggerName || [user.first_name, user.last_name].filter(Boolean).join(' ');
|
|
116
|
+
taggerEmail = taggerEmail || user.email;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
tagObj.message = options.message;
|
|
121
|
+
tagObj.annotated = true;
|
|
122
|
+
tagObj.tagger = { name: taggerName || 'Unknown', email: taggerEmail || '' };
|
|
123
|
+
tagObj.timestamp = new Date().toISOString();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
repository.tags[name] = tagObj;
|
|
127
|
+
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
128
|
+
|
|
129
|
+
console.log(chalk.green(`Created tag '${name}' → ${commitHash.substring(0, 7)}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Delete a tag
|
|
134
|
+
*/
|
|
135
|
+
async function deleteTag(name, repository, gentPath) {
|
|
136
|
+
if (!repository.tags[name]) {
|
|
137
|
+
console.error(chalk.red(`Tag '${name}' not found`));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
delete repository.tags[name];
|
|
142
|
+
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
143
|
+
console.log(chalk.green(`Deleted tag '${name}'`));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = tag;
|
package/src/index.js
CHANGED
|
@@ -1,25 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Gent CLI - A Git-like version control system with cloud backend
|
|
5
6
|
* Main entry point for the CLI application
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ============================================================================
|
|
8
|
+
*
|
|
9
|
+
* COMMANDS:
|
|
10
|
+
* Repository: init, clone
|
|
11
|
+
* Staging: add, rm, reset, status, diff
|
|
12
|
+
* History: commit, log, show, tag
|
|
13
|
+
* Branching: branch, checkout, merge, stash
|
|
14
|
+
* Remote: remote, push, pull
|
|
15
|
+
* Auth: register, login, logout, whoami
|
|
16
|
+
*
|
|
17
|
+
* @author Abdalrahman Kanawati
|
|
18
|
+
* @version 2.0.0
|
|
9
19
|
*/
|
|
10
20
|
|
|
11
21
|
const { program } = require('commander');
|
|
12
22
|
const chalk = require('chalk');
|
|
13
23
|
const packageJson = require('../package.json');
|
|
14
24
|
|
|
15
|
-
// Import commands
|
|
25
|
+
// Import core commands
|
|
16
26
|
const initCommand = require('./commands/init');
|
|
27
|
+
const cloneCommand = require('./commands/clone');
|
|
17
28
|
const statusCommand = require('./commands/status');
|
|
18
29
|
const addCommand = require('./commands/add');
|
|
30
|
+
const rmCommand = require('./commands/rm');
|
|
31
|
+
const resetCommand = require('./commands/reset');
|
|
32
|
+
const diffCommand = require('./commands/diff');
|
|
19
33
|
const commitCommand = require('./commands/commit');
|
|
20
34
|
const logCommand = require('./commands/log');
|
|
35
|
+
const showCommand = require('./commands/show');
|
|
36
|
+
const tagCommand = require('./commands/tag');
|
|
21
37
|
const branchCommand = require('./commands/branch');
|
|
22
38
|
const checkoutCommand = require('./commands/checkout');
|
|
39
|
+
const mergeCommand = require('./commands/merge');
|
|
40
|
+
const stashCommand = require('./commands/stash');
|
|
41
|
+
const remoteCommand = require('./commands/remote');
|
|
42
|
+
const pushCommand = require('./commands/push');
|
|
43
|
+
const pullCommand = require('./commands/pull');
|
|
23
44
|
|
|
24
45
|
// Import auth commands
|
|
25
46
|
const registerCommand = require('./commands/register');
|
|
@@ -27,30 +48,27 @@ const loginCommand = require('./commands/login');
|
|
|
27
48
|
const logoutCommand = require('./commands/logout');
|
|
28
49
|
const whoamiCommand = require('./commands/whoami');
|
|
29
50
|
|
|
30
|
-
// Import cloud repository commands
|
|
31
|
-
const createCommand = require('./commands/create');
|
|
32
|
-
const listCommand = require('./commands/list');
|
|
33
|
-
const cloneCommand = require('./commands/clone');
|
|
34
|
-
const pushCommand = require('./commands/push');
|
|
35
|
-
const pullCommand = require('./commands/pull');
|
|
36
|
-
const remoteCommand = require('./commands/remote');
|
|
37
|
-
|
|
38
51
|
// Configure CLI
|
|
39
52
|
program
|
|
40
53
|
.name('gent')
|
|
41
|
-
.description(chalk.cyan('
|
|
54
|
+
.description(chalk.cyan('Gent - A Git-like version control CLI with cloud backend'))
|
|
42
55
|
.version(packageJson.version, '-v, --version', 'Output the current version');
|
|
43
56
|
|
|
44
|
-
//
|
|
57
|
+
// ─── Repository Setup ───────────────────────────────────
|
|
58
|
+
|
|
45
59
|
program
|
|
46
60
|
.command('init')
|
|
47
61
|
.description('Initialize a new gent repository')
|
|
48
62
|
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
49
|
-
.option('--cloud', 'Create a corresponding cloud repository')
|
|
50
|
-
.option('-d, --description <description>', 'Repository description (for cloud)')
|
|
51
|
-
.option('-p, --private', 'Make cloud repository private')
|
|
52
63
|
.action(initCommand);
|
|
53
64
|
|
|
65
|
+
program
|
|
66
|
+
.command('clone <url> [directory]')
|
|
67
|
+
.description('Clone a remote repository')
|
|
68
|
+
.action(cloneCommand);
|
|
69
|
+
|
|
70
|
+
// ─── Staging & Working Tree ─────────────────────────────
|
|
71
|
+
|
|
54
72
|
program
|
|
55
73
|
.command('status')
|
|
56
74
|
.description('Show the working tree status')
|
|
@@ -63,12 +81,33 @@ program
|
|
|
63
81
|
.option('-A, --all', 'Add all files')
|
|
64
82
|
.action(addCommand);
|
|
65
83
|
|
|
84
|
+
program
|
|
85
|
+
.command('rm <files...>')
|
|
86
|
+
.description('Remove files from working tree and staging')
|
|
87
|
+
.option('--cached', 'Only remove from staging, keep file on disk')
|
|
88
|
+
.action(rmCommand);
|
|
89
|
+
|
|
90
|
+
program
|
|
91
|
+
.command('reset [files...]')
|
|
92
|
+
.description('Unstage files or reset HEAD to a commit')
|
|
93
|
+
.option('--hard <hash>', 'Reset HEAD and working tree to commit')
|
|
94
|
+
.option('--soft <hash>', 'Reset HEAD but keep staging')
|
|
95
|
+
.action(resetCommand);
|
|
96
|
+
|
|
97
|
+
program
|
|
98
|
+
.command('diff [files...]')
|
|
99
|
+
.description('Show changes between working tree, staging, and commits')
|
|
100
|
+
.option('--staged', 'Show staged changes vs last commit')
|
|
101
|
+
.option('--stat', 'Show diffstat summary only')
|
|
102
|
+
.action(diffCommand);
|
|
103
|
+
|
|
104
|
+
// ─── History ────────────────────────────────────────────
|
|
105
|
+
|
|
66
106
|
program
|
|
67
107
|
.command('commit')
|
|
68
108
|
.description('Record changes to the repository')
|
|
69
109
|
.option('-m, --message <message>', 'Commit message')
|
|
70
110
|
.option('-a, --all', 'Automatically stage all modified files')
|
|
71
|
-
.option('--push', 'Push to remote after commit')
|
|
72
111
|
.action(commitCommand);
|
|
73
112
|
|
|
74
113
|
program
|
|
@@ -76,8 +115,24 @@ program
|
|
|
76
115
|
.description('Show commit logs')
|
|
77
116
|
.option('-n, --number <count>', 'Limit the number of commits to show', '10')
|
|
78
117
|
.option('--oneline', 'Show each commit on a single line')
|
|
118
|
+
.option('--stat', 'Show file change statistics')
|
|
79
119
|
.action(logCommand);
|
|
80
120
|
|
|
121
|
+
program
|
|
122
|
+
.command('show [ref]')
|
|
123
|
+
.description('Show commit details and diff')
|
|
124
|
+
.option('--no-patch', 'Suppress diff output')
|
|
125
|
+
.action(showCommand);
|
|
126
|
+
|
|
127
|
+
program
|
|
128
|
+
.command('tag [name]')
|
|
129
|
+
.description('Create, list, or delete tags')
|
|
130
|
+
.option('-m, --message <message>', 'Create annotated tag with message')
|
|
131
|
+
.option('-d, --delete <name>', 'Delete a tag')
|
|
132
|
+
.action(tagCommand);
|
|
133
|
+
|
|
134
|
+
// ─── Branching & Merging ────────────────────────────────
|
|
135
|
+
|
|
81
136
|
program
|
|
82
137
|
.command('branch')
|
|
83
138
|
.description('List, create, or delete branches')
|
|
@@ -92,7 +147,40 @@ program
|
|
|
92
147
|
.option('-b, --create', 'Create a new branch')
|
|
93
148
|
.action(checkoutCommand);
|
|
94
149
|
|
|
95
|
-
|
|
150
|
+
program
|
|
151
|
+
.command('merge <branch>')
|
|
152
|
+
.description('Merge a branch into the current branch (3-way smart merge)')
|
|
153
|
+
.option('-m, --message <message>', 'Merge commit message')
|
|
154
|
+
.action(mergeCommand);
|
|
155
|
+
|
|
156
|
+
program
|
|
157
|
+
.command('stash [subcommand]')
|
|
158
|
+
.description('Stash working tree changes (pop|list|drop|apply)')
|
|
159
|
+
.option('-m, --message <message>', 'Stash message')
|
|
160
|
+
.option('-i, --index <index>', 'Stash index for pop/apply/drop')
|
|
161
|
+
.action(stashCommand);
|
|
162
|
+
|
|
163
|
+
// ─── Remote & Sync ──────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
program
|
|
166
|
+
.command('remote [subcommand] [args...]')
|
|
167
|
+
.description('Manage remote connections (add|remove|set-url)')
|
|
168
|
+
.option('-v, --verbose', 'Show remote URLs')
|
|
169
|
+
.action(remoteCommand);
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command('push [remote] [branch]')
|
|
173
|
+
.description('Push local commits to remote')
|
|
174
|
+
.option('-f, --force', 'Force push (overwrite remote)')
|
|
175
|
+
.action(pushCommand);
|
|
176
|
+
|
|
177
|
+
program
|
|
178
|
+
.command('pull [remote] [branch]')
|
|
179
|
+
.description('Pull and merge remote commits')
|
|
180
|
+
.action(pullCommand);
|
|
181
|
+
|
|
182
|
+
// ─── Authentication ─────────────────────────────────────
|
|
183
|
+
|
|
96
184
|
program
|
|
97
185
|
.command('register')
|
|
98
186
|
.description('Create a new user account')
|
|
@@ -115,43 +203,6 @@ program
|
|
|
115
203
|
.description('Display current user information')
|
|
116
204
|
.action(whoamiCommand);
|
|
117
205
|
|
|
118
|
-
// Cloud repository commands
|
|
119
|
-
program
|
|
120
|
-
.command('create <repo-name>')
|
|
121
|
-
.description('Create a new cloud repository')
|
|
122
|
-
.option('-d, --description <description>', 'Repository description')
|
|
123
|
-
.option('-p, --private', 'Make repository private')
|
|
124
|
-
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
125
|
-
.option('--init-local', 'Initialize local repository and link remote')
|
|
126
|
-
.action(createCommand);
|
|
127
|
-
|
|
128
|
-
program
|
|
129
|
-
.command('list')
|
|
130
|
-
.alias('ls')
|
|
131
|
-
.description('List all your cloud repositories')
|
|
132
|
-
.action(listCommand);
|
|
133
|
-
|
|
134
|
-
program
|
|
135
|
-
.command('clone <repo-url> [directory]')
|
|
136
|
-
.description('Clone a cloud repository (format: owner_id/repo_name)')
|
|
137
|
-
.action(cloneCommand);
|
|
138
|
-
|
|
139
|
-
program
|
|
140
|
-
.command('push [remote] [branch]')
|
|
141
|
-
.description('Push local commits to cloud')
|
|
142
|
-
.action(pushCommand);
|
|
143
|
-
|
|
144
|
-
program
|
|
145
|
-
.command('pull [remote] [branch]')
|
|
146
|
-
.description('Pull commits from cloud to local')
|
|
147
|
-
.action(pullCommand);
|
|
148
|
-
|
|
149
|
-
program
|
|
150
|
-
.command('remote [action] [name] [url]')
|
|
151
|
-
.description('Manage remote repositories')
|
|
152
|
-
.option('-v, --verbose', 'Show verbose output')
|
|
153
|
-
.action(remoteCommand);
|
|
154
|
-
|
|
155
206
|
// Help command
|
|
156
207
|
program
|
|
157
208
|
.command('help [command]')
|
package/src/utils/constants.js
CHANGED
|
@@ -11,42 +11,26 @@ module.exports = {
|
|
|
11
11
|
COMMITS_FILE: 'commits.json',
|
|
12
12
|
HEAD_FILE: 'HEAD',
|
|
13
13
|
AUTH_FILE: 'auth.json',
|
|
14
|
-
OBJECTS_DIR: 'objects',
|
|
15
|
-
REMOTE_CONFIG_FILE: 'remote.json',
|
|
16
14
|
|
|
17
15
|
// API Configuration
|
|
18
16
|
API_BASE_URL: 'https://gent-api.onrender.com',
|
|
19
17
|
API_ENDPOINTS: {
|
|
20
|
-
// Auth endpoints
|
|
21
18
|
LOGIN: '/api/auth/login/',
|
|
22
19
|
REGISTER: '/api/auth/register/',
|
|
23
20
|
LOGOUT: '/api/auth/logout/',
|
|
24
21
|
REFRESH: '/api/auth/token/refresh/',
|
|
25
22
|
PROFILE: '/api/auth/profile/',
|
|
26
23
|
|
|
27
|
-
// Repository endpoints
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Tree endpoints
|
|
38
|
-
TREE_GET: '/api/repos/{owner_id}/{project_name}/tree/{sha}/',
|
|
39
|
-
TREE_CREATE: '/api/repos/{owner_id}/{project_name}/tree/create/',
|
|
40
|
-
|
|
41
|
-
// Commit endpoints
|
|
42
|
-
COMMITS_LIST: '/api/repos/{owner_id}/{project_name}/commits/',
|
|
43
|
-
COMMITS_GET: '/api/repos/{owner_id}/{project_name}/commits/{sha}/',
|
|
44
|
-
COMMITS_CREATE: '/api/repos/{owner_id}/{project_name}/commits/create/',
|
|
45
|
-
|
|
46
|
-
// Branch endpoints
|
|
47
|
-
BRANCHES_LIST: '/api/repos/{owner_id}/{project_name}/branches/',
|
|
48
|
-
BRANCHES_GET: '/api/repos/{owner_id}/{project_name}/branches/{branch_name}/',
|
|
49
|
-
BRANCHES_CREATE: '/api/repos/{owner_id}/{project_name}/branches/create/'
|
|
24
|
+
// Repository endpoints (used by push/pull/clone)
|
|
25
|
+
// Base: /api/repos/:id/
|
|
26
|
+
REPOS: '/api/repos/',
|
|
27
|
+
REPO_PUSH: '/push/', // POST - upload commits + objects
|
|
28
|
+
REPO_PULL: '/pull/', // GET - download commits + objects since hash
|
|
29
|
+
REPO_CLONE: '/clone/', // GET - full repo download
|
|
30
|
+
REPO_REFS: '/refs/', // GET - list remote branch refs
|
|
31
|
+
REPO_TAGS: '/tags/', // GET/POST/DELETE - tag management
|
|
32
|
+
REPO_MERGE: '/merge/', // POST - server-side merge request
|
|
33
|
+
REPO_COMMITS: '/commits/', // GET - commit history
|
|
50
34
|
},
|
|
51
35
|
|
|
52
36
|
// Default ignore patterns
|