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.
@@ -1,14 +1,33 @@
1
1
  /**
2
- * Status Command - Show the working tree status
3
- * Displays staged, modified, and untracked files
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, getTrackedFiles, getIgnorePatterns } = require('../utils/fileSystem');
28
+ const { getGentPath, readJSON, pathExists, getIgnorePatterns, getAllFiles } = require('../utils/fileSystem');
10
29
  const { STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
11
- const { getFileHash, getAllFiles } = require('../utils/helpers');
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 stagedFiles = staging.files || [];
44
+ const stagedEntries = staging.entries || [];
45
+ const stagedFiles = stagedEntries.map(e => e.path);
27
46
  const currentBranch = repository.currentBranch || 'main';
28
- const lastCommit = repository.branches[currentBranch];
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 files in the working directory
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 for modifications and untracked files
46
- for (const file of allFiles) {
47
- const relativePath = path.relative(cwd, file);
48
-
49
- if (trackedSet.has(relativePath)) {
50
- // Check if modified
51
- const currentHash = await getFileHash(file);
52
- const trackedFile = trackedFiles.find(f => f.path === relativePath);
53
-
54
- if (trackedFile && currentHash !== trackedFile.hash && !stagedSet.has(relativePath)) {
55
- modified.push(relativePath);
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(relativePath)) {
58
- untracked.push(relativePath);
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 trackedFile of trackedFiles) {
64
- const fullPath = path.join(cwd, trackedFile.path);
65
- if (!await pathExists(fullPath) && !stagedSet.has(trackedFile.path)) {
66
- deleted.push(trackedFile.path);
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
- // Display status
93
+ // Check for merge in progress
94
+ const mergeState = staging.mergeState || null;
95
+
96
+ // Display
71
97
  if (options.short) {
72
- displayShortStatus(stagedFiles, modified, untracked, deleted);
98
+ displayShortStatus(stagedEntries, modified, untracked, deleted);
73
99
  } else {
74
- displayDetailedStatus(currentBranch, stagedFiles, modified, untracked, deleted, lastCommit);
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, staged, modified, untracked, deleted, lastCommit) {
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 (staged.length > 0) {
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
- staged.forEach(file => {
105
- console.log(chalk.green(`\t${file}`));
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 (staged.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0) {
140
- console.log(chalk.green('Working tree clean'));
141
- } else if (staged.length === 0) {
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(staged, modified, untracked, deleted) {
150
- staged.forEach(file => {
151
- console.log(chalk.green('A ') + file);
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
- * Gent CLI - A Git-like version control system
4
+ * ============================================================================
5
+ * Gent CLI - A Git-like version control system with cloud backend
5
6
  * Main entry point for the CLI application
6
- *
7
- * @author Your Name
8
- * @version 1.0.0
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('🚀 Gent - A Git-like version control CLI'))
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
- // Register commands
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
- // Authentication commands
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]')
@@ -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
- REPOS_LIST: '/api/repos/',
29
- REPOS_CREATE: '/api/repos/create/',
30
- REPOS_GET: '/api/repos/{owner_id}/{project_name}/',
31
- REPOS_DELETE: '/api/repos/{owner_id}/{project_name}/delete/',
32
-
33
- // Blob endpoints
34
- BLOB_GET: '/api/repos/{owner_id}/{project_name}/blob/{sha}/',
35
- BLOB_CREATE: '/api/repos/{owner_id}/{project_name}/blob/create/',
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