gent-cli 5.0.4 → 6.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 +507 -172
- package/package.json +3 -2
- package/src/commands/clone.js +2 -2
- package/src/commands/init.js +3 -2
- package/src/commands/pull.js +44 -2
- package/src/commands/register.js +27 -6
- package/src/commands/remote.js +15 -1
- package/src/commands/repos.js +4 -3
- package/src/index.js +7 -2
- package/src/utils/hash-engine.js +24 -0
package/README.md
CHANGED
|
@@ -1,306 +1,641 @@
|
|
|
1
1
|
# Gent CLI
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Cloud Authentication** — Login once, work everywhere. JWT-based auth with automatic token refresh.
|
|
13
|
-
- **Git-like Experience** — Familiar commands: `init`, `add`, `commit`, `status`, `log`, `branch`, `checkout`, `merge`, `push`, `pull`, `clone`, and more.
|
|
14
|
-
- **Zero Configuration** — `gent init` auto-detects your authenticated user profile.
|
|
15
|
-
- **Global Identity** — Commits are automatically authored with your cloud profile.
|
|
16
|
-
- **Content-Addressable Storage** — SHA-256 object store with zlib compression and deduplication.
|
|
17
|
-
- **Smart Diff & Merge** — Line-level LCS diff, three-way merge with conflict detection.
|
|
18
|
-
- **Remote Sync** — Push, pull, and clone repositories from the cloud backend.
|
|
19
|
-
- **Secure** — Tokens stored with AES encryption in `~/.gent/auth.json`.
|
|
20
|
-
|
|
21
|
-
## Table of Contents
|
|
22
|
-
|
|
23
|
-
- [Requirements](#requirements)
|
|
24
|
-
- [Installation](#installation)
|
|
25
|
-
- [Quickstart](#quickstart)
|
|
26
|
-
- [Authentication](#authentication)
|
|
27
|
-
- [Commands](#commands)
|
|
28
|
-
- [Repository Setup](#repository-setup)
|
|
29
|
-
- [Staging & Working Tree](#staging--working-tree)
|
|
30
|
-
- [History](#history)
|
|
31
|
-
- [Branching & Merging](#branching--merging)
|
|
32
|
-
- [Remote & Sync](#remote--sync)
|
|
33
|
-
- [Authentication Commands](#authentication-commands)
|
|
34
|
-
- [Usage Examples](#usage-examples)
|
|
35
|
-
- [Repository Structure](#repository-structure)
|
|
36
|
-
- [Configuration](#configuration)
|
|
37
|
-
- [Docs](#docs)
|
|
38
|
-
- [Troubleshooting](#troubleshooting)
|
|
39
|
-
- [Contributing](#contributing)
|
|
40
|
-
- [License](#license)
|
|
3
|
+
Gent is a Git-like version control CLI with cloud authentication and remote sync.
|
|
4
|
+
|
|
5
|
+
Global API URL:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
https://gent-api.onrender.com
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The CLI is configured in `src/utils/constants.js` to use that deployed API. Do not use a local API URL for normal CLI work.
|
|
41
12
|
|
|
42
13
|
## Requirements
|
|
43
14
|
|
|
44
|
-
- Node.js
|
|
15
|
+
- Node.js 14 or newer
|
|
16
|
+
- Internet access to `https://gent-api.onrender.com`
|
|
17
|
+
- A Gent account, created with `gent register`
|
|
45
18
|
|
|
46
|
-
##
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
From npm:
|
|
47
22
|
|
|
48
23
|
```bash
|
|
49
24
|
npm install -g gent-cli
|
|
25
|
+
gent --help
|
|
50
26
|
```
|
|
51
27
|
|
|
52
|
-
|
|
28
|
+
From this repository:
|
|
53
29
|
|
|
54
30
|
```bash
|
|
55
31
|
cd apps/Cli
|
|
56
32
|
npm install
|
|
33
|
+
npm link
|
|
34
|
+
gent --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run without linking:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
57
40
|
node src/index.js --help
|
|
58
41
|
```
|
|
59
42
|
|
|
60
|
-
##
|
|
43
|
+
## Full Step-by-Step Workflow
|
|
44
|
+
|
|
45
|
+
### 1. Create an account
|
|
46
|
+
|
|
47
|
+
Interactive:
|
|
61
48
|
|
|
62
49
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
gent register
|
|
51
|
+
```
|
|
65
52
|
|
|
66
|
-
|
|
67
|
-
gent init
|
|
53
|
+
Non-interactive:
|
|
68
54
|
|
|
69
|
-
|
|
70
|
-
gent
|
|
71
|
-
|
|
55
|
+
```bash
|
|
56
|
+
gent register \
|
|
57
|
+
-e user@example.com \
|
|
58
|
+
-p StrongPass123! \
|
|
59
|
+
--password-confirm StrongPass123! \
|
|
60
|
+
--first-name YourFirstName \
|
|
61
|
+
--last-name YourLastName
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
After registration, the CLI stores your encrypted auth tokens in:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
~/.gent/auth.json
|
|
68
|
+
```
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
### 2. Log in
|
|
77
71
|
|
|
78
|
-
|
|
72
|
+
Interactive:
|
|
79
73
|
|
|
80
74
|
```bash
|
|
81
|
-
gent
|
|
75
|
+
gent login
|
|
82
76
|
```
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
Non-interactive:
|
|
85
79
|
|
|
86
80
|
```bash
|
|
87
|
-
gent login
|
|
88
|
-
# or with flags
|
|
89
|
-
gent login -e user@example.com -p YourPassword
|
|
81
|
+
gent login -e user@example.com -p StrongPass123!
|
|
90
82
|
```
|
|
91
83
|
|
|
92
|
-
###
|
|
84
|
+
### 3. Confirm the logged-in user
|
|
93
85
|
|
|
94
86
|
```bash
|
|
95
87
|
gent whoami
|
|
96
88
|
```
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
Expected result: your email, name, account ID, joined date, and active status.
|
|
91
|
+
|
|
92
|
+
### 4. Create a project folder
|
|
99
93
|
|
|
100
94
|
```bash
|
|
101
|
-
|
|
95
|
+
mkdir my-project
|
|
96
|
+
cd my-project
|
|
102
97
|
```
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
### 5. Initialize a Gent repository
|
|
105
100
|
|
|
106
|
-
|
|
101
|
+
```bash
|
|
102
|
+
gent init
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This creates:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
.gent/
|
|
109
|
+
.gentignore
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The `.gent` directory stores local commits, objects, branches, tags, staging data, and config.
|
|
113
|
+
|
|
114
|
+
### 6. Create a remote repository
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
| `gent clone <url> [directory]` | Clone a remote repository from the cloud backend. |
|
|
116
|
+
```bash
|
|
117
|
+
gent repos --create my-project --description "My first Gent repository"
|
|
118
|
+
```
|
|
112
119
|
|
|
113
|
-
|
|
120
|
+
Expected output includes a remote path like:
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
| `gent add <files...> [-A]` | Add files to the staging area. Use `-A` or `--all` to add all files. |
|
|
119
|
-
| `gent rm <files...> [--cached]` | Remove files from the working tree and staging area. Use `--cached` to keep the file on disk. |
|
|
120
|
-
| `gent reset [files...] [--hard <hash> \| --soft <hash>]` | Unstage files or reset HEAD to a specific commit. |
|
|
121
|
-
| `gent diff [files...] [--staged] [--stat]` | Show changes between working tree, staging area, and commits. |
|
|
122
|
+
```text
|
|
123
|
+
/api/repos/2/my-project
|
|
124
|
+
```
|
|
122
125
|
|
|
123
|
-
|
|
126
|
+
Keep this path. It is not a local URL. The CLI combines it with the global API URL:
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
| `gent tag [name] [-m <message>] [-d <name>]` | Create, list, or delete tags. |
|
|
128
|
+
```text
|
|
129
|
+
https://gent-api.onrender.com/api/repos/2/my-project
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 7. Link the local repo to the remote repo
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
Use the `/api/repos/<owner_id>/<repo_name>` path from the previous command:
|
|
133
135
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
| `gent checkout <branch> [-b]` | Switch branches. Use `-b` to create and switch to a new branch. |
|
|
138
|
-
| `gent merge <branch> [-m <message>]` | Merge a branch into the current branch using a three-way smart merge. |
|
|
139
|
-
| `gent stash [pop \| list \| drop \| apply] [-m <message>] [-i <index>]` | Stash working tree changes. |
|
|
136
|
+
```bash
|
|
137
|
+
gent remote add origin /api/repos/2/my-project
|
|
138
|
+
```
|
|
140
139
|
|
|
141
|
-
|
|
140
|
+
If the current folder is not initialized yet, `gent remote add` initializes `.gent` first, then adds the remote. You can still run `gent init` yourself before this step if you prefer the explicit flow.
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
|---|---|
|
|
145
|
-
| `gent remote [add \| remove \| set-url] [args...] [-v]` | Manage remote connections. |
|
|
146
|
-
| `gent push [remote] [branch] [-f]` | Push local commits to the remote. Use `-f` to force push. |
|
|
147
|
-
| `gent pull [remote] [branch]` | Pull and merge remote commits into the current branch. |
|
|
142
|
+
Check it:
|
|
148
143
|
|
|
149
|
-
|
|
144
|
+
```bash
|
|
145
|
+
gent remote -v
|
|
146
|
+
```
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|---|---|
|
|
153
|
-
| `gent register` | Create a new user account interactively. |
|
|
154
|
-
| `gent login [-e <email>] [-p <password>]` | Log in to your account. |
|
|
155
|
-
| `gent logout` | Log out and clear stored tokens. |
|
|
156
|
-
| `gent whoami` | Display the currently logged-in user. |
|
|
148
|
+
Expected output:
|
|
157
149
|
|
|
158
|
-
|
|
150
|
+
```text
|
|
151
|
+
origin -> /api/repos/2/my-project
|
|
152
|
+
```
|
|
159
153
|
|
|
160
|
-
###
|
|
154
|
+
### 8. Create files
|
|
161
155
|
|
|
162
156
|
```bash
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
echo "Hello Gent" > README.md
|
|
158
|
+
mkdir src
|
|
159
|
+
echo "console.log('hello')" > src/index.js
|
|
165
160
|
```
|
|
166
161
|
|
|
167
|
-
###
|
|
162
|
+
### 9. Check status
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
gent status
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Expected result: untracked files.
|
|
169
|
+
|
|
170
|
+
### 10. Stage files
|
|
171
|
+
|
|
172
|
+
Stage specific files:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
gent add README.md src/index.js
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Or stage everything:
|
|
168
179
|
|
|
169
180
|
```bash
|
|
170
181
|
gent add .
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 11. Review staged changes
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
gent diff --staged
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Short summary:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
gent diff --staged --stat
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 12. Commit
|
|
197
|
+
|
|
198
|
+
```bash
|
|
171
199
|
gent commit -m "Initial commit"
|
|
172
200
|
```
|
|
173
201
|
|
|
174
|
-
|
|
202
|
+
Expected result: a commit hash, author, date, tree hash, and file stats.
|
|
203
|
+
|
|
204
|
+
### 13. View history
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
gent log
|
|
208
|
+
gent log --oneline
|
|
209
|
+
gent show --no-patch
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 14. Push to the remote API
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
gent push
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Expected result:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
Pushed 1 commit(s) to origin/main
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Run it again to confirm nothing else needs syncing:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
gent push
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Expected result:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
Everything up-to-date
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 15. Clone from the remote API
|
|
237
|
+
|
|
238
|
+
Go outside your current project:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
cd ..
|
|
242
|
+
gent clone /api/repos/2/my-project my-project-clone
|
|
243
|
+
cd my-project-clone
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Check the cloned files:
|
|
175
247
|
|
|
176
248
|
```bash
|
|
177
|
-
|
|
178
|
-
gent
|
|
249
|
+
cat README.md
|
|
250
|
+
gent status
|
|
251
|
+
gent log --oneline
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 16. Make another change in the original repo
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
cd ../my-project
|
|
258
|
+
echo "Second line" >> README.md
|
|
259
|
+
gent add README.md
|
|
260
|
+
gent commit -m "Update README"
|
|
261
|
+
gent push
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 17. Pull the change into the clone
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
cd ../my-project-clone
|
|
268
|
+
gent pull
|
|
269
|
+
cat README.md
|
|
270
|
+
gent status
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Expected result: the clone fast-forwards, `README.md` includes the new line, and status shows no staged changes.
|
|
274
|
+
|
|
275
|
+
### 18. Create and sync a branch
|
|
179
276
|
|
|
180
|
-
|
|
277
|
+
From a repository with at least one commit and an `origin` remote:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
gent branch feature-login
|
|
181
281
|
gent branch
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Switch to the branch:
|
|
182
285
|
|
|
183
|
-
|
|
286
|
+
```bash
|
|
287
|
+
gent checkout feature-login
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Make a change:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
echo "feature work" > feature.txt
|
|
294
|
+
gent add feature.txt
|
|
295
|
+
gent commit -m "Add feature work"
|
|
296
|
+
gent push origin feature-login
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Switch back to main:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
184
302
|
gent checkout main
|
|
303
|
+
```
|
|
185
304
|
|
|
186
|
-
|
|
187
|
-
|
|
305
|
+
### 19. Merge a branch
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
gent merge feature-login
|
|
309
|
+
gent push
|
|
188
310
|
```
|
|
189
311
|
|
|
190
|
-
|
|
312
|
+
If there are conflicts, resolve the files, then:
|
|
191
313
|
|
|
192
314
|
```bash
|
|
315
|
+
gent add .
|
|
316
|
+
gent commit -m "Resolve merge"
|
|
317
|
+
gent push
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 20. Create and sync a tag
|
|
321
|
+
|
|
322
|
+
Create a lightweight tag:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
gent tag v1.0.0
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Create an annotated tag:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
gent tag v1.0.1 -m "Release v1.0.1"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
List tags:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
gent tag
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Delete a tag:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
gent tag -d v1.0.0
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 21. Use stash when needed
|
|
347
|
+
|
|
348
|
+
Save local work:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
gent stash
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
List stashes:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
gent stash list
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Apply latest stash:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
gent stash pop
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 22. Log out
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
gent logout
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Confirm:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
gent whoami
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Expected result: not logged in.
|
|
379
|
+
|
|
380
|
+
## One-Command Remote Repo Setup
|
|
381
|
+
|
|
382
|
+
You can initialize a local repo and create the remote repo in one command:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
mkdir another-project
|
|
386
|
+
cd another-project
|
|
387
|
+
gent init --remote another-project
|
|
388
|
+
gent remote -v
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
This creates the remote repository on `https://gent-api.onrender.com` and configures `origin` automatically.
|
|
392
|
+
|
|
393
|
+
## Command Reference
|
|
394
|
+
|
|
395
|
+
### Authentication
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
gent register
|
|
399
|
+
gent register -e user@example.com -p StrongPass123! --password-confirm StrongPass123!
|
|
400
|
+
gent login
|
|
401
|
+
gent login -e user@example.com -p StrongPass123!
|
|
402
|
+
gent whoami
|
|
403
|
+
gent logout
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Repository Setup
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
gent init
|
|
410
|
+
gent init --remote my-repo
|
|
411
|
+
gent clone /api/repos/<owner_id>/<repo_name> [directory]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Staging and Working Tree
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
gent status
|
|
418
|
+
gent status -s
|
|
419
|
+
gent add <files...>
|
|
420
|
+
gent add .
|
|
421
|
+
gent rm <files...>
|
|
422
|
+
gent rm <files...> --cached
|
|
423
|
+
gent reset [files...]
|
|
424
|
+
gent reset --soft <commit_hash>
|
|
425
|
+
gent reset --hard <commit_hash>
|
|
426
|
+
gent diff
|
|
427
|
+
gent diff --staged
|
|
428
|
+
gent diff --stat
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### History
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
gent commit -m "Message"
|
|
193
435
|
gent log
|
|
194
436
|
gent log --oneline
|
|
195
437
|
gent log -n 5
|
|
438
|
+
gent show
|
|
439
|
+
gent show <commit_hash>
|
|
440
|
+
gent show --no-patch
|
|
196
441
|
```
|
|
197
442
|
|
|
198
|
-
###
|
|
443
|
+
### Branching and Merging
|
|
199
444
|
|
|
200
445
|
```bash
|
|
201
|
-
|
|
202
|
-
gent
|
|
446
|
+
gent branch
|
|
447
|
+
gent branch <name>
|
|
448
|
+
gent branch -d <name>
|
|
449
|
+
gent checkout <branch>
|
|
450
|
+
gent checkout -b <branch>
|
|
451
|
+
gent merge <branch>
|
|
452
|
+
gent merge <branch> -m "Merge message"
|
|
453
|
+
```
|
|
203
454
|
|
|
204
|
-
|
|
205
|
-
gent push origin main
|
|
455
|
+
### Tags
|
|
206
456
|
|
|
207
|
-
|
|
457
|
+
```bash
|
|
458
|
+
gent tag
|
|
459
|
+
gent tag <name>
|
|
460
|
+
gent tag <name> -m "Message"
|
|
461
|
+
gent tag -d <name>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Remotes and Sync
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
gent repos
|
|
468
|
+
gent repos --create <name>
|
|
469
|
+
gent repos --create <name> --description "Description"
|
|
470
|
+
gent repos --create <name> --private
|
|
471
|
+
gent remote
|
|
472
|
+
gent remote -v
|
|
473
|
+
gent remote add origin /api/repos/<owner_id>/<repo_name>
|
|
474
|
+
gent remote set-url origin /api/repos/<owner_id>/<repo_name>
|
|
475
|
+
gent remote remove origin
|
|
476
|
+
gent push
|
|
477
|
+
gent push origin main
|
|
478
|
+
gent pull
|
|
208
479
|
gent pull origin main
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Remote URL Rules
|
|
209
483
|
|
|
210
|
-
|
|
211
|
-
|
|
484
|
+
The global API base is fixed:
|
|
485
|
+
|
|
486
|
+
```text
|
|
487
|
+
https://gent-api.onrender.com
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Remote repository paths should be stored like this:
|
|
491
|
+
|
|
492
|
+
```text
|
|
493
|
+
/api/repos/<owner_id>/<repo_name>
|
|
212
494
|
```
|
|
213
495
|
|
|
214
|
-
|
|
496
|
+
Example:
|
|
215
497
|
|
|
216
|
-
|
|
498
|
+
```bash
|
|
499
|
+
gent remote add origin /api/repos/2/my-project
|
|
500
|
+
```
|
|
217
501
|
|
|
502
|
+
Do not use:
|
|
503
|
+
|
|
504
|
+
```text
|
|
505
|
+
http://localhost:8000
|
|
506
|
+
http://127.0.0.1:8000
|
|
218
507
|
```
|
|
508
|
+
|
|
509
|
+
## Files Created by Gent
|
|
510
|
+
|
|
511
|
+
Inside each repo:
|
|
512
|
+
|
|
513
|
+
```text
|
|
219
514
|
.gent/
|
|
220
|
-
├── config.json
|
|
221
|
-
├── commits.json
|
|
222
|
-
├── staging.json
|
|
223
|
-
├──
|
|
224
|
-
├──
|
|
225
|
-
├── objects/ # Content-addressable blob and tree store
|
|
226
|
-
│ ├── ab/ # First 2 characters of SHA-256 hash
|
|
227
|
-
│ │ └── cdef... # zlib-compressed object
|
|
228
|
-
│ └── ...
|
|
515
|
+
├── config.json
|
|
516
|
+
├── commits.json
|
|
517
|
+
├── staging.json
|
|
518
|
+
├── HEAD
|
|
519
|
+
├── objects/
|
|
229
520
|
└── refs/
|
|
230
|
-
|
|
231
|
-
|
|
521
|
+
|
|
522
|
+
.gentignore
|
|
232
523
|
```
|
|
233
524
|
|
|
234
|
-
|
|
525
|
+
Global auth:
|
|
235
526
|
|
|
236
|
-
|
|
527
|
+
```text
|
|
528
|
+
~/.gent/auth.json
|
|
529
|
+
```
|
|
237
530
|
|
|
238
|
-
|
|
531
|
+
## Ignore Rules
|
|
239
532
|
|
|
240
|
-
|
|
533
|
+
Gent creates a `.gentignore` file by default:
|
|
241
534
|
|
|
242
|
-
```
|
|
535
|
+
```text
|
|
243
536
|
node_modules/
|
|
244
|
-
|
|
245
|
-
.env
|
|
537
|
+
.DS_Store
|
|
246
538
|
*.log
|
|
539
|
+
.env
|
|
540
|
+
.gent/
|
|
247
541
|
```
|
|
248
542
|
|
|
249
|
-
|
|
543
|
+
Add project-specific ignored files there.
|
|
250
544
|
|
|
251
|
-
|
|
545
|
+
## Test the Full Remote Flow
|
|
252
546
|
|
|
253
|
-
|
|
547
|
+
This repository includes a remote-only E2E test. It uses only:
|
|
254
548
|
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
"remotes": {
|
|
258
|
-
"origin": {
|
|
259
|
-
"url": "https://gent-api.onrender.com/api/repos/123/"
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
549
|
+
```text
|
|
550
|
+
https://gent-api.onrender.com
|
|
263
551
|
```
|
|
264
552
|
|
|
553
|
+
Run syntax checks:
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
npm test
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Run the full remote scenario:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
npm run test:remote:e2e
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
The test covers:
|
|
566
|
+
|
|
567
|
+
- API health check
|
|
568
|
+
- Register, login, whoami, logout
|
|
569
|
+
- Create and list remote repositories
|
|
570
|
+
- Init, remote add, status, add, diff, commit
|
|
571
|
+
- Push and up-to-date push
|
|
572
|
+
- Branch sync
|
|
573
|
+
- Tag sync
|
|
574
|
+
- Clone from remote
|
|
575
|
+
- Second commit and push
|
|
576
|
+
- Pull into clone and verify working tree content
|
|
577
|
+
- `init --remote`
|
|
578
|
+
- Unauthenticated guard
|
|
579
|
+
- Version flag
|
|
580
|
+
|
|
265
581
|
## Troubleshooting
|
|
266
582
|
|
|
267
|
-
|
|
583
|
+
### Command not found
|
|
584
|
+
|
|
585
|
+
Install or link the CLI:
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
npm install -g gent-cli
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
or:
|
|
268
592
|
|
|
269
|
-
Make sure the CLI is linked globally:
|
|
270
593
|
```bash
|
|
594
|
+
cd apps/Cli
|
|
271
595
|
npm link
|
|
272
596
|
```
|
|
273
597
|
|
|
274
|
-
|
|
598
|
+
### Not authenticated
|
|
599
|
+
|
|
600
|
+
Log in again:
|
|
601
|
+
|
|
275
602
|
```bash
|
|
276
|
-
|
|
603
|
+
gent login
|
|
277
604
|
```
|
|
278
605
|
|
|
279
|
-
|
|
606
|
+
### Not a Gent repository
|
|
280
607
|
|
|
281
|
-
Run
|
|
608
|
+
Run commands inside a folder initialized with:
|
|
282
609
|
|
|
283
|
-
|
|
610
|
+
```bash
|
|
611
|
+
gent init
|
|
612
|
+
```
|
|
284
613
|
|
|
285
|
-
|
|
614
|
+
### Remote not found
|
|
286
615
|
|
|
287
|
-
|
|
616
|
+
Add an origin remote:
|
|
288
617
|
|
|
289
|
-
|
|
618
|
+
```bash
|
|
619
|
+
gent remote add origin /api/repos/<owner_id>/<repo_name>
|
|
620
|
+
```
|
|
290
621
|
|
|
291
|
-
|
|
622
|
+
### Push says everything up-to-date
|
|
292
623
|
|
|
293
|
-
|
|
294
|
-
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
624
|
+
That means the local branch has no new commits compared to the last pushed remote ref.
|
|
295
625
|
|
|
296
|
-
|
|
626
|
+
### Render cold start
|
|
297
627
|
|
|
298
|
-
|
|
628
|
+
The deployed API may take several seconds to respond after inactivity. Retry the command if the first request times out.
|
|
299
629
|
|
|
300
|
-
##
|
|
630
|
+
## Version
|
|
301
631
|
|
|
302
|
-
|
|
632
|
+
Show the CLI version:
|
|
303
633
|
|
|
304
|
-
|
|
634
|
+
```bash
|
|
635
|
+
gent -V
|
|
636
|
+
gent --version
|
|
637
|
+
```
|
|
305
638
|
|
|
306
|
-
|
|
639
|
+
## License
|
|
640
|
+
|
|
641
|
+
ISC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gent-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/index.js",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "node --check src/index.js && node --check tests/remote-e2e.js",
|
|
12
|
+
"test:remote:e2e": "node tests/remote-e2e.js",
|
|
12
13
|
"demo": "bash demo.sh",
|
|
13
14
|
"link": "npm link",
|
|
14
15
|
"unlink": "npm unlink",
|
package/src/commands/clone.js
CHANGED
|
@@ -32,7 +32,7 @@ const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
|
|
|
32
32
|
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
|
|
33
33
|
const apiClient = require('../utils/api-client');
|
|
34
34
|
const authStorage = require('../utils/auth-storage');
|
|
35
|
-
const { storeBlob, readBlobAsString } = require('../utils/hash-engine');
|
|
35
|
+
const { storeBlob, readBlobAsString, decodeRemoteBlobContent } = require('../utils/hash-engine');
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Clone remote repository
|
|
@@ -153,7 +153,7 @@ async function clone(url, directory, options) {
|
|
|
153
153
|
buildRepoUrl(API_ENDPOINTS.REPO_BLOB_DETAIL, { ...repoInfo, sha: entry.sha })
|
|
154
154
|
);
|
|
155
155
|
if (blob.content) {
|
|
156
|
-
const buf =
|
|
156
|
+
const buf = decodeRemoteBlobContent(blob.content, entry.sha);
|
|
157
157
|
await storeBlob(gentPath, buf);
|
|
158
158
|
objectCount++;
|
|
159
159
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -144,15 +144,16 @@ async function createRemoteRepo(cwd, gentPath, config, options) {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
const data = await apiClient.post(API_ENDPOINTS.REPOS_CREATE, payload);
|
|
147
|
+
const repo = data.repository || data;
|
|
147
148
|
|
|
148
149
|
// Update local config with remote
|
|
149
150
|
const configPath = path.join(gentPath, CONFIG_FILE);
|
|
150
151
|
const localConfig = await require('../utils/fileSystem').readJSON(configPath);
|
|
151
152
|
localConfig.remotes = localConfig.remotes || {};
|
|
152
|
-
localConfig.remotes.origin = { url: `/api/repos/${
|
|
153
|
+
localConfig.remotes.origin = { url: `/api/repos/${repo.owner_id}/${repo.name}` };
|
|
153
154
|
await writeJSON(configPath, localConfig);
|
|
154
155
|
|
|
155
|
-
console.log(chalk.green(`✓ Remote repository created: /api/repos/${
|
|
156
|
+
console.log(chalk.green(`✓ Remote repository created: /api/repos/${repo.owner_id}/${repo.name}`));
|
|
156
157
|
console.log(chalk.gray(` Remote 'origin' configured automatically`));
|
|
157
158
|
|
|
158
159
|
} catch (error) {
|
package/src/commands/pull.js
CHANGED
|
@@ -30,7 +30,7 @@ const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
|
30
30
|
const { COMMITS_FILE, CONFIG_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
|
|
31
31
|
const apiClient = require('../utils/api-client');
|
|
32
32
|
const authStorage = require('../utils/auth-storage');
|
|
33
|
-
const { storeBlob, objectExists } = require('../utils/hash-engine');
|
|
33
|
+
const { storeBlob, objectExists, readBlobAsString, decodeRemoteBlobContent } = require('../utils/hash-engine');
|
|
34
34
|
const { findMergeBase, mergeTreeEntries } = require('../utils/merge-engine');
|
|
35
35
|
const { generateCommitHash } = require('../utils/helpers');
|
|
36
36
|
|
|
@@ -140,7 +140,7 @@ async function pull(remoteName, branchName, options) {
|
|
|
140
140
|
buildRepoUrl(API_ENDPOINTS.REPO_BLOB_DETAIL, { ...repoInfo, sha: entry.sha })
|
|
141
141
|
);
|
|
142
142
|
if (blob.content) {
|
|
143
|
-
const buf =
|
|
143
|
+
const buf = decodeRemoteBlobContent(blob.content, entry.sha);
|
|
144
144
|
await storeBlob(gentPath, buf);
|
|
145
145
|
}
|
|
146
146
|
} catch {
|
|
@@ -187,8 +187,11 @@ async function pull(remoteName, branchName, options) {
|
|
|
187
187
|
|
|
188
188
|
if (!localHead || isAncestor(repository.commits, localHead, remoteHead)) {
|
|
189
189
|
// Fast-forward
|
|
190
|
+
const previousTree = localHead ? getCommitTree(repository.commits, localHead) : [];
|
|
191
|
+
const nextTree = getCommitTree(repository.commits, remoteHead);
|
|
190
192
|
repository.branches[branch] = remoteHead;
|
|
191
193
|
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
194
|
+
await checkoutTree(gentPath, process.cwd(), previousTree, nextTree);
|
|
192
195
|
|
|
193
196
|
config.remoteRefs[`${remote}/${branch}`] = remoteHead;
|
|
194
197
|
await writeJSON(path.join(gentPath, CONFIG_FILE), config);
|
|
@@ -234,6 +237,9 @@ async function pull(remoteName, branchName, options) {
|
|
|
234
237
|
repository.commits.push(mergeCommit);
|
|
235
238
|
repository.branches[branch] = mergeCommit.hash;
|
|
236
239
|
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
240
|
+
if (!mergeResult.hasConflicts) {
|
|
241
|
+
await checkoutTree(gentPath, process.cwd(), oursTree, mergeResult.mergedEntries);
|
|
242
|
+
}
|
|
237
243
|
|
|
238
244
|
config.remoteRefs[`${remote}/${branch}`] = remoteHead;
|
|
239
245
|
await writeJSON(path.join(gentPath, CONFIG_FILE), config);
|
|
@@ -276,4 +282,40 @@ function isAncestor(commits, hashA, hashB) {
|
|
|
276
282
|
return false;
|
|
277
283
|
}
|
|
278
284
|
|
|
285
|
+
function getCommitTree(commits, hash) {
|
|
286
|
+
const commit = commits.find(c => c.hash === hash);
|
|
287
|
+
if (!commit) return [];
|
|
288
|
+
return commit.tree || (commit.files || []).map(f => ({
|
|
289
|
+
mode: '100644',
|
|
290
|
+
name: f.path || f.name,
|
|
291
|
+
hash: f.hash,
|
|
292
|
+
type: 'blob'
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function checkoutTree(gentPath, cwd, previousTree, nextTree) {
|
|
297
|
+
const nextPaths = new Set(nextTree.map(e => e.name || e.path));
|
|
298
|
+
|
|
299
|
+
for (const entry of previousTree) {
|
|
300
|
+
const relPath = entry.name || entry.path;
|
|
301
|
+
if (!relPath || nextPaths.has(relPath)) continue;
|
|
302
|
+
try {
|
|
303
|
+
await fs.unlink(path.join(cwd, relPath));
|
|
304
|
+
} catch {
|
|
305
|
+
// File already absent.
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const entry of nextTree) {
|
|
310
|
+
if (entry.type && entry.type !== 'blob') continue;
|
|
311
|
+
const relPath = entry.name || entry.path;
|
|
312
|
+
if (!relPath || !entry.hash) continue;
|
|
313
|
+
|
|
314
|
+
const content = await readBlobAsString(gentPath, entry.hash);
|
|
315
|
+
const fullPath = path.join(cwd, relPath);
|
|
316
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
317
|
+
await fs.writeFile(fullPath, content, 'utf-8');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
279
321
|
module.exports = pull;
|
package/src/commands/register.js
CHANGED
|
@@ -17,12 +17,19 @@ async function register(options) {
|
|
|
17
17
|
console.log(chalk.cyan('\n🚀 Create your Gent account\n'));
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
|
+
let email = options.email;
|
|
21
|
+
let password = options.password;
|
|
22
|
+
let passwordConfirm = options.passwordConfirm;
|
|
23
|
+
let firstName = options.firstName;
|
|
24
|
+
let lastName = options.lastName;
|
|
25
|
+
|
|
20
26
|
// Prompt for user information
|
|
21
27
|
const answers = await inquirer.prompt([
|
|
22
28
|
{
|
|
23
29
|
type: 'input',
|
|
24
30
|
name: 'email',
|
|
25
31
|
message: 'Email address:',
|
|
32
|
+
when: !email,
|
|
26
33
|
validate: (input) => {
|
|
27
34
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
28
35
|
return emailRegex.test(input) || 'Please enter a valid email address';
|
|
@@ -33,6 +40,7 @@ async function register(options) {
|
|
|
33
40
|
name: 'password',
|
|
34
41
|
message: 'Password:',
|
|
35
42
|
mask: '*',
|
|
43
|
+
when: !password,
|
|
36
44
|
validate: (input) => {
|
|
37
45
|
if (input.length < 8) {
|
|
38
46
|
return 'Password must be at least 8 characters long';
|
|
@@ -45,33 +53,46 @@ async function register(options) {
|
|
|
45
53
|
name: 'passwordConfirm',
|
|
46
54
|
message: 'Confirm password:',
|
|
47
55
|
mask: '*',
|
|
56
|
+
when: !passwordConfirm,
|
|
48
57
|
validate: (input, answers) => {
|
|
49
|
-
return input === answers.password || 'Passwords do not match';
|
|
58
|
+
return input === (password || answers.password) || 'Passwords do not match';
|
|
50
59
|
}
|
|
51
60
|
},
|
|
52
61
|
{
|
|
53
62
|
type: 'input',
|
|
54
63
|
name: 'firstName',
|
|
55
64
|
message: 'First name:',
|
|
65
|
+
when: firstName === undefined,
|
|
56
66
|
default: ''
|
|
57
67
|
},
|
|
58
68
|
{
|
|
59
69
|
type: 'input',
|
|
60
70
|
name: 'lastName',
|
|
61
71
|
message: 'Last name:',
|
|
72
|
+
when: lastName === undefined,
|
|
62
73
|
default: ''
|
|
63
74
|
}
|
|
64
75
|
]);
|
|
65
76
|
|
|
77
|
+
email = email || answers.email;
|
|
78
|
+
password = password || answers.password;
|
|
79
|
+
passwordConfirm = passwordConfirm || answers.passwordConfirm;
|
|
80
|
+
firstName = firstName !== undefined ? firstName : answers.firstName;
|
|
81
|
+
lastName = lastName !== undefined ? lastName : answers.lastName;
|
|
82
|
+
|
|
83
|
+
if (!email || !password || !passwordConfirm) {
|
|
84
|
+
throw new Error('Email, password, and password confirmation are required');
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
const spinner = ora('Creating your account...').start();
|
|
67
88
|
|
|
68
89
|
// Register user
|
|
69
90
|
const user = await authService.register(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
email,
|
|
92
|
+
password,
|
|
93
|
+
passwordConfirm,
|
|
94
|
+
firstName || '',
|
|
95
|
+
lastName || ''
|
|
75
96
|
);
|
|
76
97
|
|
|
77
98
|
spinner.succeed(chalk.green('✓ Account created successfully!'));
|
package/src/commands/remote.js
CHANGED
|
@@ -30,6 +30,7 @@ const path = require('path');
|
|
|
30
30
|
const chalk = require('chalk');
|
|
31
31
|
const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
32
32
|
const { CONFIG_FILE, parseRemoteUrl } = require('../utils/constants');
|
|
33
|
+
const initCommand = require('./init');
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Manage remotes
|
|
@@ -39,7 +40,19 @@ const { CONFIG_FILE, parseRemoteUrl } = require('../utils/constants');
|
|
|
39
40
|
*/
|
|
40
41
|
async function remote(subcommand, args, options) {
|
|
41
42
|
try {
|
|
42
|
-
|
|
43
|
+
let gentPath;
|
|
44
|
+
try {
|
|
45
|
+
gentPath = await getGentPath();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (subcommand !== 'add' || error.code !== 'ENOENT') {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.yellow('Not a Gent repository yet. Initializing first...'));
|
|
52
|
+
await initCommand({});
|
|
53
|
+
gentPath = await getGentPath();
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
const configPath = path.join(gentPath, CONFIG_FILE);
|
|
44
57
|
const config = await readJSON(configPath);
|
|
45
58
|
config.remotes = config.remotes || {};
|
|
@@ -120,6 +133,7 @@ async function remote(subcommand, args, options) {
|
|
|
120
133
|
} catch (error) {
|
|
121
134
|
if (error.code === 'ENOENT' && error.message.includes('.gent')) {
|
|
122
135
|
console.error(chalk.red('Error: Not a gent repository'));
|
|
136
|
+
console.log(chalk.yellow('Run "gent init" first, then retry this command'));
|
|
123
137
|
} else {
|
|
124
138
|
console.error(chalk.red('Error:'), error.message);
|
|
125
139
|
}
|
package/src/commands/repos.js
CHANGED
|
@@ -93,10 +93,11 @@ async function createRepo(options) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const data = await apiClient.post(API_ENDPOINTS.REPOS_CREATE, payload);
|
|
96
|
+
const repo = data.repository || data;
|
|
96
97
|
|
|
97
|
-
spinner.succeed(chalk.green(`Created repository '${
|
|
98
|
-
console.log(chalk.gray(` URL: /api/repos/${
|
|
99
|
-
console.log(chalk.gray(` Use "gent remote add origin /api/repos/${
|
|
98
|
+
spinner.succeed(chalk.green(`Created repository '${repo.name}'`));
|
|
99
|
+
console.log(chalk.gray(` URL: /api/repos/${repo.owner_id}/${repo.name}`));
|
|
100
|
+
console.log(chalk.gray(` Use "gent remote add origin /api/repos/${repo.owner_id}/${repo.name}" to link`));
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
module.exports = repos;
|
package/src/index.js
CHANGED
|
@@ -53,7 +53,7 @@ const whoamiCommand = require('./commands/whoami');
|
|
|
53
53
|
program
|
|
54
54
|
.name('gent')
|
|
55
55
|
.description(chalk.cyan('Gent - A Git-like version control CLI with cloud backend'))
|
|
56
|
-
.version(packageJson.version, '-
|
|
56
|
+
.version(packageJson.version, '-V, --version', 'Output the current version');
|
|
57
57
|
|
|
58
58
|
// ─── Repository Setup ───────────────────────────────────
|
|
59
59
|
|
|
@@ -195,6 +195,11 @@ program
|
|
|
195
195
|
program
|
|
196
196
|
.command('register')
|
|
197
197
|
.description('Create a new user account')
|
|
198
|
+
.option('-e, --email <email>', 'Email address')
|
|
199
|
+
.option('-p, --password <password>', 'Password')
|
|
200
|
+
.option('--password-confirm <password>', 'Password confirmation')
|
|
201
|
+
.option('--first-name <name>', 'First name')
|
|
202
|
+
.option('--last-name <name>', 'Last name')
|
|
198
203
|
.action(registerCommand);
|
|
199
204
|
|
|
200
205
|
program
|
|
@@ -237,7 +242,7 @@ try {
|
|
|
237
242
|
program.outputHelp();
|
|
238
243
|
}
|
|
239
244
|
} catch (err) {
|
|
240
|
-
if (err.code !== 'commander.help' && err.code !== 'commander.helpDisplayed') {
|
|
245
|
+
if (err.code !== 'commander.help' && err.code !== 'commander.helpDisplayed' && err.code !== 'commander.version') {
|
|
241
246
|
console.error(chalk.red('Error:'), err.message);
|
|
242
247
|
process.exit(1);
|
|
243
248
|
}
|
package/src/utils/hash-engine.js
CHANGED
|
@@ -137,6 +137,29 @@ function hashBlob(content) {
|
|
|
137
137
|
return hashObject('blob', content);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Decode blob content returned by the backend.
|
|
142
|
+
* Current Django endpoints return raw UTF-8 content, while older/planned pull
|
|
143
|
+
* responses may return base64. Prefer the representation whose blob hash
|
|
144
|
+
* matches the expected object SHA.
|
|
145
|
+
* @param {String} content
|
|
146
|
+
* @param {String} expectedHash
|
|
147
|
+
* @returns {Buffer}
|
|
148
|
+
*/
|
|
149
|
+
function decodeRemoteBlobContent(content, expectedHash) {
|
|
150
|
+
const raw = Buffer.from(content, 'utf-8');
|
|
151
|
+
if (!expectedHash || hashBlob(raw) === expectedHash) {
|
|
152
|
+
return raw;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const decoded = Buffer.from(content, 'base64');
|
|
156
|
+
if (hashBlob(decoded) === expectedHash) {
|
|
157
|
+
return decoded;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return raw;
|
|
161
|
+
}
|
|
162
|
+
|
|
140
163
|
/**
|
|
141
164
|
* Hash a tree structure.
|
|
142
165
|
* @param {Array<{mode: String, name: String, hash: String, type: String}>} entries
|
|
@@ -324,6 +347,7 @@ module.exports = {
|
|
|
324
347
|
hashObject,
|
|
325
348
|
hashBlob,
|
|
326
349
|
hashTree,
|
|
350
|
+
decodeRemoteBlobContent,
|
|
327
351
|
objectExists,
|
|
328
352
|
storeBlob,
|
|
329
353
|
readBlob,
|