resulgit 1.0.5 → 1.0.7
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 +13 -8
- package/package.json +1 -1
- package/resulgit.js +156 -86
package/README.md
CHANGED
|
@@ -37,10 +37,12 @@ resulgit <command> [options]
|
|
|
37
37
|
- `resulgit repo head --repo <id> [--branch <name>]` - Get HEAD commit
|
|
38
38
|
- `resulgit repo select` - Interactive repository selection
|
|
39
39
|
|
|
40
|
-
### Clone &
|
|
40
|
+
### Clone & Initialize
|
|
41
41
|
|
|
42
|
-
- `resulgit init
|
|
43
|
-
- `resulgit
|
|
42
|
+
- `resulgit init <name>` - Initialize a new repository (creates folder and remote repo automatically)
|
|
43
|
+
- `resulgit init <name> --dir <path>` - Initialize in a specific directory
|
|
44
|
+
- `resulgit clone <name>` - Clone a repository by name
|
|
45
|
+
- `resulgit clone --repo <name> --branch <branch>` - Clone with specific branch
|
|
44
46
|
- `resulgit workspace set-root --path <dir>` - Set workspace root directory
|
|
45
47
|
|
|
46
48
|
### Branch Operations
|
|
@@ -63,6 +65,8 @@ resulgit <command> [options]
|
|
|
63
65
|
### Version Control
|
|
64
66
|
|
|
65
67
|
- `resulgit commit --message <text>` - Create a commit
|
|
68
|
+
- `resulgit commit -m <text>` - Short form (same as above)
|
|
69
|
+
- `resulgit commit -a -m <text>` - Add all changes and commit
|
|
66
70
|
- `resulgit push` - Push changes to remote
|
|
67
71
|
- `resulgit pull` - Pull changes from remote
|
|
68
72
|
- `resulgit merge --branch <name> [--squash] [--no-push]` - Merge branches
|
|
@@ -131,18 +135,19 @@ resulgit auth login --email user@example.com --password mypassword
|
|
|
131
135
|
# List repositories
|
|
132
136
|
resulgit repo list
|
|
133
137
|
|
|
134
|
-
# Initialize a new repository
|
|
135
|
-
resulgit init
|
|
138
|
+
# Initialize a new repository (creates folder + remote repo automatically)
|
|
139
|
+
resulgit init MyProject
|
|
136
140
|
|
|
137
|
-
#
|
|
138
|
-
resulgit clone
|
|
141
|
+
# Or clone an existing repository
|
|
142
|
+
resulgit clone MyProject
|
|
139
143
|
|
|
140
144
|
# Check status
|
|
145
|
+
cd MyProject
|
|
141
146
|
resulgit status
|
|
142
147
|
|
|
143
148
|
# Create and commit changes
|
|
144
149
|
resulgit add file.txt --content "Hello World"
|
|
145
|
-
resulgit commit
|
|
150
|
+
resulgit commit -m "Add file.txt"
|
|
146
151
|
resulgit push
|
|
147
152
|
|
|
148
153
|
# Create a branch
|
package/package.json
CHANGED
package/resulgit.js
CHANGED
|
@@ -37,12 +37,20 @@ function parseArgs(argv) {
|
|
|
37
37
|
const t = tokens[i]
|
|
38
38
|
if (t.startsWith('--')) {
|
|
39
39
|
const key = t.slice(2)
|
|
40
|
-
const val = tokens[i + 1] && !tokens[i + 1].startsWith('
|
|
40
|
+
const val = tokens[i + 1] && !tokens[i + 1].startsWith('-') ? tokens[++i] : 'true'
|
|
41
|
+
opts[key] = val
|
|
42
|
+
} else if (t.startsWith('-') && t.length === 2) {
|
|
43
|
+
// Short flag like -m, -a
|
|
44
|
+
const key = t.slice(1)
|
|
45
|
+
const val = tokens[i + 1] && !tokens[i + 1].startsWith('-') ? tokens[++i] : 'true'
|
|
41
46
|
opts[key] = val
|
|
42
47
|
} else if (cmd.length < 2) {
|
|
43
48
|
cmd.push(t)
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
// Map short flags to long flags
|
|
52
|
+
if (opts.m && !opts.message) { opts.message = opts.m; delete opts.m }
|
|
53
|
+
if (opts.a && !opts.all) { opts.all = opts.a; delete opts.a }
|
|
46
54
|
return { cmd, opts }
|
|
47
55
|
}
|
|
48
56
|
|
|
@@ -271,7 +279,7 @@ async function cmdClone(opts, cfg) {
|
|
|
271
279
|
const server = getServer(opts, cfg)
|
|
272
280
|
const token = getToken(opts, cfg)
|
|
273
281
|
const repo = opts.repo
|
|
274
|
-
const branch = opts.branch
|
|
282
|
+
const branch = opts.branch || 'main'
|
|
275
283
|
let dest = opts.dest
|
|
276
284
|
if (!repo || !branch) throw new Error('Missing --repo and --branch')
|
|
277
285
|
const spinner = createSpinner('Initializing clone...', opts.json)
|
|
@@ -540,10 +548,10 @@ async function cmdDiff(opts) {
|
|
|
540
548
|
const snap1 = await fetchSnapshotByCommit(server, meta.repoId, commit1, token)
|
|
541
549
|
const snap2 = await fetchSnapshotByCommit(server, meta.repoId, commit2, token)
|
|
542
550
|
const files = filePath ? [filePath] : Object.keys(new Set([...Object.keys(snap1.files), ...Object.keys(snap2.files)]))
|
|
543
|
-
|
|
551
|
+
|
|
544
552
|
let added = 0, deleted = 0, modified = 0
|
|
545
553
|
const stats = []
|
|
546
|
-
|
|
554
|
+
|
|
547
555
|
for (const p of files) {
|
|
548
556
|
const content1 = snap1.files[p] !== undefined ? String(snap1.files[p]) : null
|
|
549
557
|
const content2 = snap2.files[p] !== undefined ? String(snap2.files[p]) : null
|
|
@@ -561,7 +569,7 @@ async function cmdDiff(opts) {
|
|
|
561
569
|
const diff = Math.abs(lines2.length - lines1.length)
|
|
562
570
|
if (showStat) stats.push({ path: p, added: lines2.length > lines1.length ? diff : 0, deleted: lines1.length > lines2.length ? diff : 0 })
|
|
563
571
|
}
|
|
564
|
-
|
|
572
|
+
|
|
565
573
|
if (!showStat) {
|
|
566
574
|
if (opts.json === 'true') {
|
|
567
575
|
print({ path: p, old: content1, new: content2 }, true)
|
|
@@ -585,7 +593,7 @@ async function cmdDiff(opts) {
|
|
|
585
593
|
}
|
|
586
594
|
}
|
|
587
595
|
}
|
|
588
|
-
|
|
596
|
+
|
|
589
597
|
if (showStat) {
|
|
590
598
|
if (opts.json === 'true') {
|
|
591
599
|
print({ added, deleted, modified, files: stats }, true)
|
|
@@ -607,7 +615,7 @@ async function cmdDiff(opts) {
|
|
|
607
615
|
const parentSnap = parentId ? await fetchSnapshotByCommit(server, meta.repoId, parentId, token) : { files: {}, commitId: '' }
|
|
608
616
|
|
|
609
617
|
const files = filePath ? [filePath] : Object.keys(new Set([...Object.keys(parentSnap.files), ...Object.keys(commitSnap.files)]))
|
|
610
|
-
|
|
618
|
+
|
|
611
619
|
if (showStat) {
|
|
612
620
|
let added = 0, deleted = 0, modified = 0
|
|
613
621
|
const stats = []
|
|
@@ -640,7 +648,7 @@ async function cmdDiff(opts) {
|
|
|
640
648
|
}
|
|
641
649
|
return
|
|
642
650
|
}
|
|
643
|
-
|
|
651
|
+
|
|
644
652
|
for (const p of files) {
|
|
645
653
|
const oldContent = parentSnap.files[p] !== undefined ? String(parentSnap.files[p]) : null
|
|
646
654
|
const newContent = commitSnap.files[p] !== undefined ? String(commitSnap.files[p]) : null
|
|
@@ -726,7 +734,7 @@ async function cmdRm(opts) {
|
|
|
726
734
|
const cfg = loadConfig()
|
|
727
735
|
const server = getServer(opts, cfg) || meta.server
|
|
728
736
|
const token = getToken(opts, cfg) || meta.token
|
|
729
|
-
|
|
737
|
+
|
|
730
738
|
// Handle --cached flag (remove from index but keep file)
|
|
731
739
|
if (opts.cached === 'true') {
|
|
732
740
|
// Mark file for deletion in next commit but don't delete from filesystem
|
|
@@ -736,7 +744,7 @@ async function cmdRm(opts) {
|
|
|
736
744
|
try {
|
|
737
745
|
const s = await fs.promises.readFile(localPath, 'utf8')
|
|
738
746
|
localMeta = JSON.parse(s)
|
|
739
|
-
} catch {}
|
|
747
|
+
} catch { }
|
|
740
748
|
if (!localMeta.removedFiles) localMeta.removedFiles = []
|
|
741
749
|
if (!localMeta.removedFiles.includes(pathArg)) {
|
|
742
750
|
localMeta.removedFiles.push(pathArg)
|
|
@@ -750,7 +758,7 @@ async function cmdRm(opts) {
|
|
|
750
758
|
}
|
|
751
759
|
return
|
|
752
760
|
}
|
|
753
|
-
|
|
761
|
+
|
|
754
762
|
const u = new URL(`/api/repositories/${meta.repoId}/files`, server)
|
|
755
763
|
u.searchParams.set('branch', meta.branch)
|
|
756
764
|
u.searchParams.set('path', pathArg)
|
|
@@ -776,7 +784,7 @@ async function cmdCommit(opts) {
|
|
|
776
784
|
spinnerUpdate(spinner, 'Staging all changes...')
|
|
777
785
|
await cmdAdd({ dir, all: 'true', json: opts.json })
|
|
778
786
|
}
|
|
779
|
-
|
|
787
|
+
|
|
780
788
|
// Handle --amend flag
|
|
781
789
|
if (opts.amend === 'true') {
|
|
782
790
|
spinnerUpdate(spinner, 'Amending last commit...')
|
|
@@ -823,7 +831,7 @@ async function cmdCommit(opts) {
|
|
|
823
831
|
const local = await collectLocal(dir)
|
|
824
832
|
const files = {}
|
|
825
833
|
for (const [p, v] of Object.entries(local)) files[p] = v.content
|
|
826
|
-
|
|
834
|
+
|
|
827
835
|
// Execute pre-commit hook
|
|
828
836
|
spinnerUpdate(spinner, 'Running pre-commit hook...')
|
|
829
837
|
try {
|
|
@@ -836,7 +844,7 @@ async function cmdCommit(opts) {
|
|
|
836
844
|
if (err.message === 'pre-commit hook failed') throw err
|
|
837
845
|
// Hook doesn't exist or other error, continue
|
|
838
846
|
}
|
|
839
|
-
|
|
847
|
+
|
|
840
848
|
localMeta.pendingCommit = { message, files, createdAt: Date.now() }
|
|
841
849
|
// Clear conflicts if they were resolved
|
|
842
850
|
if (localMeta.conflicts) {
|
|
@@ -844,12 +852,12 @@ async function cmdCommit(opts) {
|
|
|
844
852
|
}
|
|
845
853
|
await fs.promises.mkdir(metaDir, { recursive: true })
|
|
846
854
|
await fs.promises.writeFile(localPath, JSON.stringify(localMeta, null, 2))
|
|
847
|
-
|
|
855
|
+
|
|
848
856
|
// Execute post-commit hook
|
|
849
857
|
try {
|
|
850
858
|
await hooks.executeHook(dir, 'post-commit', { message, files: Object.keys(files) })
|
|
851
|
-
} catch {}
|
|
852
|
-
|
|
859
|
+
} catch { }
|
|
860
|
+
|
|
853
861
|
spinnerSuccess(spinner, `Staged changes for commit: "${message}"`)
|
|
854
862
|
print({ pendingCommit: message }, opts.json === 'true')
|
|
855
863
|
} catch (err) {
|
|
@@ -1395,7 +1403,7 @@ async function cmdPush(opts) {
|
|
|
1395
1403
|
if (err.message === 'pre-push hook failed') throw err
|
|
1396
1404
|
// Hook doesn't exist or other error, continue
|
|
1397
1405
|
}
|
|
1398
|
-
|
|
1406
|
+
|
|
1399
1407
|
const body = { message: localMeta.pendingCommit?.message || (opts.message || 'Push'), files: merged, branchName: remoteMeta.branch }
|
|
1400
1408
|
spinnerUpdate(spinner, `Pushing to '${remoteMeta.branch}'...`)
|
|
1401
1409
|
const url = new URL(`/api/repositories/${remoteMeta.repoId}/commits`, server).toString()
|
|
@@ -1404,12 +1412,12 @@ async function cmdPush(opts) {
|
|
|
1404
1412
|
localMeta.baseFiles = merged
|
|
1405
1413
|
localMeta.pendingCommit = null
|
|
1406
1414
|
await fs.promises.writeFile(localPath, JSON.stringify(localMeta, null, 2))
|
|
1407
|
-
|
|
1415
|
+
|
|
1408
1416
|
// Execute post-push hook
|
|
1409
1417
|
try {
|
|
1410
1418
|
await hooks.executeHook(dir, 'post-push', { branch: remoteMeta.branch, commitId: localMeta.baseCommitId })
|
|
1411
|
-
} catch {}
|
|
1412
|
-
|
|
1419
|
+
} catch { }
|
|
1420
|
+
|
|
1413
1421
|
spinnerSuccess(spinner, `Pushed to '${remoteMeta.branch}' (commit: ${(data.id || '').slice(0, 7)})`)
|
|
1414
1422
|
print({ pushed: localMeta.baseCommitId }, opts.json === 'true')
|
|
1415
1423
|
} catch (err) {
|
|
@@ -1758,7 +1766,7 @@ async function cmdBranch(sub, opts) {
|
|
|
1758
1766
|
} catch { }
|
|
1759
1767
|
process.stdout.write(color('Branches:\n', 'bold'))
|
|
1760
1768
|
let list = (data.branches || []).map(b => ({ name: b.name, commitId: b.commitId || '', date: b.lastCommitDate || b.createdAt || '' }))
|
|
1761
|
-
|
|
1769
|
+
|
|
1762
1770
|
// Handle --sort option
|
|
1763
1771
|
if (opts.sort) {
|
|
1764
1772
|
const sortBy = opts.sort.replace(/^-/, '') // Remove leading dash
|
|
@@ -1770,7 +1778,7 @@ async function cmdBranch(sub, opts) {
|
|
|
1770
1778
|
})
|
|
1771
1779
|
}
|
|
1772
1780
|
}
|
|
1773
|
-
|
|
1781
|
+
|
|
1774
1782
|
for (const b of list) {
|
|
1775
1783
|
const isCur = b.name === current
|
|
1776
1784
|
const mark = isCur ? color('*', 'green') : ' '
|
|
@@ -1838,7 +1846,7 @@ async function cmdSwitch(opts) {
|
|
|
1838
1846
|
const cfg = loadConfig()
|
|
1839
1847
|
const server = getServer(opts, cfg) || meta.server
|
|
1840
1848
|
const token = getToken(opts, cfg) || meta.token
|
|
1841
|
-
|
|
1849
|
+
|
|
1842
1850
|
// Handle -c flag (create and switch)
|
|
1843
1851
|
if (create) {
|
|
1844
1852
|
// Check if branch exists
|
|
@@ -1850,7 +1858,7 @@ async function cmdSwitch(opts) {
|
|
|
1850
1858
|
await cmdBranch('create', { dir, name: branch, base: meta.branch, repo: meta.repoId, server, token })
|
|
1851
1859
|
}
|
|
1852
1860
|
}
|
|
1853
|
-
|
|
1861
|
+
|
|
1854
1862
|
await pullToDir(meta.repoId, branch, dir, server, token)
|
|
1855
1863
|
print({ repoId: meta.repoId, branch, dir }, opts.json === 'true')
|
|
1856
1864
|
}
|
|
@@ -2297,14 +2305,67 @@ async function cmdReset(opts) {
|
|
|
2297
2305
|
}
|
|
2298
2306
|
|
|
2299
2307
|
async function cmdInit(opts) {
|
|
2300
|
-
const dir = path.resolve(opts.dir || '.')
|
|
2301
2308
|
const cfg = loadConfig()
|
|
2302
2309
|
const server = getServer(opts, cfg)
|
|
2303
2310
|
const token = getToken(opts, cfg)
|
|
2304
|
-
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2311
|
+
// Repository name must be a non‑empty string; validate format
|
|
2312
|
+
const repo = opts.repo ? validation.validateRepoName(opts.repo) : ''
|
|
2313
|
+
// Branch name defaults to 'main' if not supplied
|
|
2314
|
+
const branch = opts.branch ? opts.branch : 'main'
|
|
2315
|
+
|
|
2316
|
+
// Determine target directory:
|
|
2317
|
+
// - If --dir is specified, use that
|
|
2318
|
+
// - Otherwise, if repo name is provided, create folder with that name
|
|
2319
|
+
// - Fallback to current directory
|
|
2320
|
+
let targetDir
|
|
2321
|
+
if (opts.dir) {
|
|
2322
|
+
targetDir = path.resolve(opts.dir)
|
|
2323
|
+
} else if (repo) {
|
|
2324
|
+
targetDir = path.resolve(repo)
|
|
2325
|
+
} else {
|
|
2326
|
+
targetDir = path.resolve('.')
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// Create the target directory if it doesn't exist
|
|
2330
|
+
await fs.promises.mkdir(targetDir, { recursive: true })
|
|
2331
|
+
|
|
2332
|
+
const spinner = createSpinner(`Initializing repository${repo ? ` '${repo}'` : ''}...`, opts.json)
|
|
2333
|
+
|
|
2334
|
+
let repoId = repo
|
|
2335
|
+
// If a repo name is provided and we have server/token, create remote repo
|
|
2336
|
+
if (repo && server && token) {
|
|
2337
|
+
try {
|
|
2338
|
+
spinnerUpdate(spinner, 'Creating remote repository...')
|
|
2339
|
+
const createUrl = new URL('/api/repositories', server).toString()
|
|
2340
|
+
const createRes = await request('POST', createUrl, {
|
|
2341
|
+
name: repo,
|
|
2342
|
+
description: opts.description || '',
|
|
2343
|
+
visibility: opts.visibility || 'private',
|
|
2344
|
+
initializeWithReadme: false
|
|
2345
|
+
}, token)
|
|
2346
|
+
// Use the ID returned by the server (could be numeric or string)
|
|
2347
|
+
repoId = String(createRes.id || repo)
|
|
2348
|
+
spinnerUpdate(spinner, `Remote repository '${repo}' created`)
|
|
2349
|
+
} catch (err) {
|
|
2350
|
+
// If repo already exists, try to fetch its ID
|
|
2351
|
+
if (err.message && err.message.includes('409')) {
|
|
2352
|
+
spinnerUpdate(spinner, `Remote repository '${repo}' already exists, linking...`)
|
|
2353
|
+
try {
|
|
2354
|
+
const listUrl = new URL('/api/repositories', server).toString()
|
|
2355
|
+
const repos = await request('GET', listUrl, null, token)
|
|
2356
|
+
const found = (repos || []).find(r => r.name === repo)
|
|
2357
|
+
if (found) repoId = String(found.id)
|
|
2358
|
+
} catch { /* ignore */ }
|
|
2359
|
+
} else {
|
|
2360
|
+
// Other error - continue with local init only
|
|
2361
|
+
spinnerUpdate(spinner, `Could not create remote repo: ${err.message}. Proceeding with local init only.`)
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
spinnerUpdate(spinner, 'Setting up local repository...')
|
|
2367
|
+
const metaDir = path.join(targetDir, '.vcs-next')
|
|
2368
|
+
const gitDir = path.join(targetDir, '.git')
|
|
2308
2369
|
await fs.promises.mkdir(metaDir, { recursive: true })
|
|
2309
2370
|
await fs.promises.mkdir(path.join(gitDir, 'refs', 'heads'), { recursive: true })
|
|
2310
2371
|
await fs.promises.writeFile(path.join(gitDir, 'HEAD'), `ref: refs/heads/${branch}\n`, 'utf8')
|
|
@@ -2318,16 +2379,18 @@ async function cmdInit(opts) {
|
|
|
2318
2379
|
'',
|
|
2319
2380
|
'[vcs-next]',
|
|
2320
2381
|
`\tserver = ${opts.server || server || ''}`,
|
|
2321
|
-
`\trepoId = ${
|
|
2382
|
+
`\trepoId = ${repoId}`,
|
|
2322
2383
|
`\tbranch = ${branch}`,
|
|
2323
2384
|
`\ttoken = ${opts.token || token || ''}`
|
|
2324
2385
|
].join('\n')
|
|
2325
2386
|
await fs.promises.writeFile(path.join(gitDir, 'config'), gitConfig, 'utf8')
|
|
2326
|
-
const remoteMeta = { repoId:
|
|
2387
|
+
const remoteMeta = { repoId: repoId, branch, commitId: '', server: opts.server || server || '', token: opts.token || token || '' }
|
|
2327
2388
|
await fs.promises.writeFile(path.join(metaDir, 'remote.json'), JSON.stringify(remoteMeta, null, 2))
|
|
2328
2389
|
const localMeta = { baseCommitId: '', baseFiles: {}, pendingCommit: null }
|
|
2329
2390
|
await fs.promises.writeFile(path.join(metaDir, 'local.json'), JSON.stringify(localMeta, null, 2))
|
|
2330
|
-
|
|
2391
|
+
|
|
2392
|
+
spinnerSuccess(spinner, `Initialized repository in ${targetDir}`)
|
|
2393
|
+
print({ initialized: targetDir, branch, repoId: repoId }, opts.json === 'true')
|
|
2331
2394
|
}
|
|
2332
2395
|
|
|
2333
2396
|
async function cmdMv(opts) {
|
|
@@ -2602,38 +2665,38 @@ async function cmdBlame(opts) {
|
|
|
2602
2665
|
const dir = path.resolve(opts.dir || '.')
|
|
2603
2666
|
const filePath = opts.path
|
|
2604
2667
|
if (!filePath) throw new errors.ValidationError('Missing --path', 'path')
|
|
2605
|
-
|
|
2668
|
+
|
|
2606
2669
|
const validPath = validation.validateFilePath(filePath)
|
|
2607
2670
|
const meta = readRemoteMeta(dir)
|
|
2608
2671
|
const cfg = loadConfig()
|
|
2609
2672
|
const server = getServer(opts, cfg) || meta.server
|
|
2610
2673
|
const token = getToken(opts, cfg) || meta.token
|
|
2611
|
-
|
|
2674
|
+
|
|
2612
2675
|
const spinner = createSpinner(`Getting blame for ${validPath}...`, opts.json)
|
|
2613
|
-
|
|
2676
|
+
|
|
2614
2677
|
try {
|
|
2615
2678
|
// Get file content
|
|
2616
2679
|
const local = await collectLocal(dir)
|
|
2617
2680
|
if (!local[validPath]) {
|
|
2618
2681
|
throw new errors.FileSystemError(`File not found: ${validPath}`, validPath, 'read')
|
|
2619
2682
|
}
|
|
2620
|
-
|
|
2683
|
+
|
|
2621
2684
|
// Get commits
|
|
2622
2685
|
const commitsUrl = new URL(`/api/repositories/${meta.repoId}/commits`, server)
|
|
2623
2686
|
commitsUrl.searchParams.set('branch', meta.branch)
|
|
2624
2687
|
const commitsRes = await fetch(commitsUrl.toString(), {
|
|
2625
2688
|
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
2626
2689
|
})
|
|
2627
|
-
|
|
2690
|
+
|
|
2628
2691
|
if (!commitsRes.ok) {
|
|
2629
2692
|
throw new errors.NetworkError('Failed to fetch commits', commitsRes.status, commitsUrl.toString())
|
|
2630
2693
|
}
|
|
2631
|
-
|
|
2694
|
+
|
|
2632
2695
|
const commits = await commitsRes.json()
|
|
2633
2696
|
const blameData = parseBlame(local[validPath].content, commits, validPath)
|
|
2634
|
-
|
|
2697
|
+
|
|
2635
2698
|
spinnerSuccess(spinner, `Blame for ${validPath}`)
|
|
2636
|
-
|
|
2699
|
+
|
|
2637
2700
|
if (opts.json === 'true') {
|
|
2638
2701
|
print(formatBlameJson(blameData), false)
|
|
2639
2702
|
} else {
|
|
@@ -2651,17 +2714,17 @@ async function cmdLog(opts) {
|
|
|
2651
2714
|
const cfg = loadConfig()
|
|
2652
2715
|
const server = getServer(opts, cfg) || meta.server
|
|
2653
2716
|
const token = getToken(opts, cfg) || meta.token
|
|
2654
|
-
|
|
2717
|
+
|
|
2655
2718
|
const spinner = createSpinner('Fetching commit history...', opts.json)
|
|
2656
|
-
|
|
2719
|
+
|
|
2657
2720
|
try {
|
|
2658
2721
|
const url = new URL(`/api/repositories/${meta.repoId}/commits`, server)
|
|
2659
2722
|
if (opts.branch) url.searchParams.set('branch', opts.branch)
|
|
2660
2723
|
else url.searchParams.set('branch', meta.branch)
|
|
2661
|
-
|
|
2724
|
+
|
|
2662
2725
|
const data = await request('GET', url.toString(), null, token)
|
|
2663
2726
|
let commits = Array.isArray(data) ? data : []
|
|
2664
|
-
|
|
2727
|
+
|
|
2665
2728
|
// Filter by file path if provided
|
|
2666
2729
|
const filePath = opts.path
|
|
2667
2730
|
if (filePath) {
|
|
@@ -2674,7 +2737,7 @@ async function cmdLog(opts) {
|
|
|
2674
2737
|
}
|
|
2675
2738
|
commits = filteredCommits
|
|
2676
2739
|
}
|
|
2677
|
-
|
|
2740
|
+
|
|
2678
2741
|
// Filter by content pattern (-G flag)
|
|
2679
2742
|
const pattern = opts.G || opts.pattern
|
|
2680
2743
|
if (pattern) {
|
|
@@ -2684,7 +2747,7 @@ async function cmdLog(opts) {
|
|
|
2684
2747
|
const commitSnap = await fetchSnapshotByCommit(server, meta.repoId, commit.id || commit._id, token)
|
|
2685
2748
|
const parentId = (Array.isArray(commit.parents) && commit.parents[0]) || ''
|
|
2686
2749
|
const parentSnap = parentId ? await fetchSnapshotByCommit(server, meta.repoId, parentId, token) : { files: {} }
|
|
2687
|
-
|
|
2750
|
+
|
|
2688
2751
|
// Check if pattern matches in any file changed in this commit
|
|
2689
2752
|
const allPaths = new Set([...Object.keys(parentSnap.files), ...Object.keys(commitSnap.files)])
|
|
2690
2753
|
let matches = false
|
|
@@ -2702,9 +2765,9 @@ async function cmdLog(opts) {
|
|
|
2702
2765
|
}
|
|
2703
2766
|
commits = filteredCommits
|
|
2704
2767
|
}
|
|
2705
|
-
|
|
2768
|
+
|
|
2706
2769
|
spinnerSuccess(spinner, `Found ${commits.length} commits`)
|
|
2707
|
-
|
|
2770
|
+
|
|
2708
2771
|
if (opts.json === 'true') {
|
|
2709
2772
|
print(commits, true)
|
|
2710
2773
|
} else if (opts.oneline === 'true') {
|
|
@@ -2733,7 +2796,7 @@ async function cmdLog(opts) {
|
|
|
2733
2796
|
|
|
2734
2797
|
async function cmdHook(sub, opts) {
|
|
2735
2798
|
const dir = path.resolve(opts.dir || '.')
|
|
2736
|
-
|
|
2799
|
+
|
|
2737
2800
|
if (sub === 'list') {
|
|
2738
2801
|
const hooksList = await hooks.listHooks(dir)
|
|
2739
2802
|
if (opts.json === 'true') {
|
|
@@ -2751,18 +2814,18 @@ async function cmdHook(sub, opts) {
|
|
|
2751
2814
|
}
|
|
2752
2815
|
return
|
|
2753
2816
|
}
|
|
2754
|
-
|
|
2817
|
+
|
|
2755
2818
|
if (sub === 'install') {
|
|
2756
2819
|
const hookName = opts.name
|
|
2757
2820
|
if (!hookName) throw new Error('Missing --name')
|
|
2758
|
-
|
|
2821
|
+
|
|
2759
2822
|
let script = opts.script
|
|
2760
2823
|
if (!script && opts.sample === 'true') {
|
|
2761
2824
|
script = hooks.SAMPLE_HOOKS[hookName]
|
|
2762
2825
|
if (!script) throw new Error(`No sample available for ${hookName}`)
|
|
2763
2826
|
}
|
|
2764
2827
|
if (!script) throw new Error('Missing --script or use --sample')
|
|
2765
|
-
|
|
2828
|
+
|
|
2766
2829
|
const result = await hooks.installHook(dir, hookName, script)
|
|
2767
2830
|
print(result, opts.json === 'true')
|
|
2768
2831
|
if (opts.json !== 'true') {
|
|
@@ -2770,20 +2833,20 @@ async function cmdHook(sub, opts) {
|
|
|
2770
2833
|
}
|
|
2771
2834
|
return
|
|
2772
2835
|
}
|
|
2773
|
-
|
|
2836
|
+
|
|
2774
2837
|
if (sub === 'remove') {
|
|
2775
2838
|
const hookName = opts.name
|
|
2776
2839
|
if (!hookName) throw new Error('Missing --name')
|
|
2777
|
-
|
|
2840
|
+
|
|
2778
2841
|
const result = await hooks.removeHook(dir, hookName)
|
|
2779
2842
|
print(result, opts.json === 'true')
|
|
2780
2843
|
return
|
|
2781
2844
|
}
|
|
2782
|
-
|
|
2845
|
+
|
|
2783
2846
|
if (sub === 'show') {
|
|
2784
2847
|
const hookName = opts.name
|
|
2785
2848
|
if (!hookName) throw new Error('Missing --name')
|
|
2786
|
-
|
|
2849
|
+
|
|
2787
2850
|
const result = await hooks.readHook(dir, hookName)
|
|
2788
2851
|
if (result.content) {
|
|
2789
2852
|
process.stdout.write(result.content + '\n')
|
|
@@ -2792,7 +2855,7 @@ async function cmdHook(sub, opts) {
|
|
|
2792
2855
|
}
|
|
2793
2856
|
return
|
|
2794
2857
|
}
|
|
2795
|
-
|
|
2858
|
+
|
|
2796
2859
|
throw new Error('Unknown hook subcommand. Use: list, install, remove, show')
|
|
2797
2860
|
}
|
|
2798
2861
|
|
|
@@ -2800,13 +2863,13 @@ async function cmdGrep(opts) {
|
|
|
2800
2863
|
const dir = path.resolve(opts.dir || '.')
|
|
2801
2864
|
const pattern = opts.pattern || opts.p || ''
|
|
2802
2865
|
if (!pattern) throw new Error('Missing --pattern or -p')
|
|
2803
|
-
|
|
2866
|
+
|
|
2804
2867
|
const meta = readRemoteMeta(dir)
|
|
2805
2868
|
const local = await collectLocal(dir)
|
|
2806
2869
|
const results = []
|
|
2807
|
-
|
|
2870
|
+
|
|
2808
2871
|
const regex = new RegExp(pattern, opts.ignoreCase === 'true' ? 'i' : '')
|
|
2809
|
-
|
|
2872
|
+
|
|
2810
2873
|
for (const [filePath, fileData] of Object.entries(local)) {
|
|
2811
2874
|
const lines = fileData.content.split(/\r?\n/)
|
|
2812
2875
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -2819,7 +2882,7 @@ async function cmdGrep(opts) {
|
|
|
2819
2882
|
}
|
|
2820
2883
|
}
|
|
2821
2884
|
}
|
|
2822
|
-
|
|
2885
|
+
|
|
2823
2886
|
if (opts.json === 'true') {
|
|
2824
2887
|
print(results, true)
|
|
2825
2888
|
} else {
|
|
@@ -2836,10 +2899,10 @@ async function cmdLsFiles(opts) {
|
|
|
2836
2899
|
const cfg = loadConfig()
|
|
2837
2900
|
const server = getServer(opts, cfg) || meta.server
|
|
2838
2901
|
const token = getToken(opts, cfg) || meta.token
|
|
2839
|
-
|
|
2902
|
+
|
|
2840
2903
|
const remote = await fetchRemoteFilesMap(server, meta.repoId, meta.branch, token)
|
|
2841
2904
|
const files = Object.keys(remote.map)
|
|
2842
|
-
|
|
2905
|
+
|
|
2843
2906
|
if (opts.json === 'true') {
|
|
2844
2907
|
print(files.map(f => ({ path: f })), true)
|
|
2845
2908
|
} else {
|
|
@@ -2853,13 +2916,13 @@ async function cmdReflog(opts) {
|
|
|
2853
2916
|
const dir = path.resolve(opts.dir || '.')
|
|
2854
2917
|
const metaDir = path.join(dir, '.vcs-next')
|
|
2855
2918
|
const reflogPath = path.join(metaDir, 'reflog.json')
|
|
2856
|
-
|
|
2919
|
+
|
|
2857
2920
|
let reflog = []
|
|
2858
2921
|
try {
|
|
2859
2922
|
const content = await fs.promises.readFile(reflogPath, 'utf8')
|
|
2860
2923
|
reflog = JSON.parse(content)
|
|
2861
|
-
} catch {}
|
|
2862
|
-
|
|
2924
|
+
} catch { }
|
|
2925
|
+
|
|
2863
2926
|
if (opts.json === 'true') {
|
|
2864
2927
|
print(reflog, true)
|
|
2865
2928
|
} else {
|
|
@@ -2878,14 +2941,14 @@ async function cmdCatFile(opts) {
|
|
|
2878
2941
|
const dir = path.resolve(opts.dir || '.')
|
|
2879
2942
|
const type = opts.type || ''
|
|
2880
2943
|
const object = opts.object || ''
|
|
2881
|
-
|
|
2944
|
+
|
|
2882
2945
|
if (!type || !object) throw new Error('Missing --type and --object')
|
|
2883
|
-
|
|
2946
|
+
|
|
2884
2947
|
const meta = readRemoteMeta(dir)
|
|
2885
2948
|
const cfg = loadConfig()
|
|
2886
2949
|
const server = getServer(opts, cfg) || meta.server
|
|
2887
2950
|
const token = getToken(opts, cfg) || meta.token
|
|
2888
|
-
|
|
2951
|
+
|
|
2889
2952
|
if (type === 'blob') {
|
|
2890
2953
|
const snap = await fetchSnapshotByCommit(server, meta.repoId, object, token)
|
|
2891
2954
|
const filePath = opts.path || ''
|
|
@@ -2912,12 +2975,12 @@ async function cmdCatFile(opts) {
|
|
|
2912
2975
|
async function cmdRevParse(opts) {
|
|
2913
2976
|
const dir = path.resolve(opts.dir || '.')
|
|
2914
2977
|
const rev = opts.rev || 'HEAD'
|
|
2915
|
-
|
|
2978
|
+
|
|
2916
2979
|
const meta = readRemoteMeta(dir)
|
|
2917
2980
|
const cfg = loadConfig()
|
|
2918
2981
|
const server = getServer(opts, cfg) || meta.server
|
|
2919
2982
|
const token = getToken(opts, cfg) || meta.token
|
|
2920
|
-
|
|
2983
|
+
|
|
2921
2984
|
if (rev === 'HEAD') {
|
|
2922
2985
|
const info = await request('GET', new URL(`/api/repositories/${meta.repoId}/branches`, server).toString(), null, token)
|
|
2923
2986
|
const found = (info.branches || []).find(b => b.name === meta.branch)
|
|
@@ -2938,20 +3001,20 @@ async function cmdRevParse(opts) {
|
|
|
2938
3001
|
async function cmdDescribe(opts) {
|
|
2939
3002
|
const dir = path.resolve(opts.dir || '.')
|
|
2940
3003
|
const commitId = opts.commit || 'HEAD'
|
|
2941
|
-
|
|
3004
|
+
|
|
2942
3005
|
const meta = readRemoteMeta(dir)
|
|
2943
3006
|
const cfg = loadConfig()
|
|
2944
3007
|
const server = getServer(opts, cfg) || meta.server
|
|
2945
3008
|
const token = getToken(opts, cfg) || meta.token
|
|
2946
|
-
|
|
3009
|
+
|
|
2947
3010
|
// Get tags
|
|
2948
3011
|
const tagsUrl = new URL(`/api/repositories/${meta.repoId}/tags`, server)
|
|
2949
3012
|
const tags = await request('GET', tagsUrl.toString(), null, token)
|
|
2950
3013
|
const tagsList = Array.isArray(tags) ? tags : []
|
|
2951
|
-
|
|
3014
|
+
|
|
2952
3015
|
// Find nearest tag (simplified - just find any tag)
|
|
2953
3016
|
const nearestTag = tagsList[0]
|
|
2954
|
-
|
|
3017
|
+
|
|
2955
3018
|
if (nearestTag) {
|
|
2956
3019
|
const desc = `${nearestTag.name}-0-g${commitId.slice(0, 7)}`
|
|
2957
3020
|
print(opts.json === 'true' ? { tag: nearestTag.name, commitId, describe: desc } : desc, opts.json === 'true')
|
|
@@ -2966,16 +3029,16 @@ async function cmdShortlog(opts) {
|
|
|
2966
3029
|
const cfg = loadConfig()
|
|
2967
3030
|
const server = getServer(opts, cfg) || meta.server
|
|
2968
3031
|
const token = getToken(opts, cfg) || meta.token
|
|
2969
|
-
|
|
3032
|
+
|
|
2970
3033
|
const url = new URL(`/api/repositories/${meta.repoId}/commits`, server)
|
|
2971
3034
|
if (opts.branch) url.searchParams.set('branch', opts.branch)
|
|
2972
3035
|
else url.searchParams.set('branch', meta.branch)
|
|
2973
|
-
|
|
3036
|
+
|
|
2974
3037
|
const commits = await request('GET', url.toString(), null, token)
|
|
2975
3038
|
const commitsList = Array.isArray(commits) ? commits : []
|
|
2976
|
-
|
|
3039
|
+
|
|
2977
3040
|
const stats = generateCommitStats(commitsList)
|
|
2978
|
-
|
|
3041
|
+
|
|
2979
3042
|
if (opts.json === 'true') {
|
|
2980
3043
|
print(stats, true)
|
|
2981
3044
|
} else {
|
|
@@ -3044,8 +3107,8 @@ function help() {
|
|
|
3044
3107
|
' >>>>>>> incoming (incoming changes)',
|
|
3045
3108
|
' Resolve conflicts manually, then commit and push. Push is blocked until conflicts are resolved.',
|
|
3046
3109
|
' pr list|create|merge [--dir <path>] [--title <t>] [--id <id>] [--source <branch>] [--target <branch>]',
|
|
3047
|
-
' clone <url> [--dest <dir>] | --repo <
|
|
3048
|
-
' init [--dir <path>] [--repo <
|
|
3110
|
+
' clone <url> [--dest <dir>] | --repo <name> --branch <name> [--dest <dir>] [--server <url>] [--token <token>]',
|
|
3111
|
+
' init [--dir <path>] [--repo <name>] [--server <url>] [--branch <name>] [--token <tok>]',
|
|
3049
3112
|
' remote show|set-url|set-token [--dir <path>] [--server <url>] [--token <tok>]',
|
|
3050
3113
|
' config list|get|set [--key <k>] [--value <v>]',
|
|
3051
3114
|
' clean [--dir <path>] [--force]',
|
|
@@ -3078,13 +3141,20 @@ async function main() {
|
|
|
3078
3141
|
return
|
|
3079
3142
|
}
|
|
3080
3143
|
if (cmd[0] === 'clone') {
|
|
3081
|
-
|
|
3144
|
+
const arg = cmd[1];
|
|
3145
|
+
if (arg && !arg.startsWith('--')) {
|
|
3146
|
+
if (arg.includes('://')) {
|
|
3147
|
+
opts.url = arg;
|
|
3148
|
+
} else {
|
|
3149
|
+
opts.repo = arg;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3082
3152
|
if (opts.url) {
|
|
3083
|
-
await cmdCloneFromUrl(opts, cfg)
|
|
3153
|
+
await cmdCloneFromUrl(opts, cfg);
|
|
3084
3154
|
} else {
|
|
3085
|
-
await cmdClone(opts, cfg)
|
|
3155
|
+
await cmdClone(opts, cfg);
|
|
3086
3156
|
}
|
|
3087
|
-
return
|
|
3157
|
+
return;
|
|
3088
3158
|
}
|
|
3089
3159
|
if (cmd[0] === 'status') {
|
|
3090
3160
|
await cmdStatus(opts)
|