resulgit 1.0.19 → 1.0.20
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 +143 -0
- package/package.json +1 -1
- package/resulgit.js +237 -59
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# resulgit
|
|
2
|
+
|
|
3
|
+
A powerful command-line interface (CLI) tool for version control system operations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g resulgit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install locally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install resulgit
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
resulgit <command> [options]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
### Authentication
|
|
26
|
+
|
|
27
|
+
- `resulgit auth set-token --token <token>` - Set authentication token
|
|
28
|
+
- `resulgit auth set-server --server <url>` - Set server URL
|
|
29
|
+
- `resulgit auth login --email <email> --password <password>` - Login to server
|
|
30
|
+
- `resulgit auth register --username <name> --email <email> --password <password>` - Register new account
|
|
31
|
+
|
|
32
|
+
### Repository Management
|
|
33
|
+
|
|
34
|
+
- `resulgit repo list` - List all repositories
|
|
35
|
+
- `resulgit repo create --name <name> [--description <text>] [--visibility <private|public>]` - Create new repository
|
|
36
|
+
- `resulgit repo log --repo <id> [--branch <name>]` - View commit log
|
|
37
|
+
- `resulgit repo head --repo <id> [--branch <name>]` - Get HEAD commit
|
|
38
|
+
- `resulgit repo select` - Interactive repository selection
|
|
39
|
+
|
|
40
|
+
### Clone & Workspace
|
|
41
|
+
|
|
42
|
+
- `resulgit clone --repo <id> --branch <name> [--dest <dir>]` - Clone a repository
|
|
43
|
+
- `resulgit workspace set-root --path <dir>` - Set workspace root directory
|
|
44
|
+
|
|
45
|
+
### Branch Operations
|
|
46
|
+
|
|
47
|
+
- `resulgit branch list` - List all branches
|
|
48
|
+
- `resulgit branch create --name <branch> [--base <branch>]` - Create new branch
|
|
49
|
+
- `resulgit branch delete --name <branch>` - Delete a branch
|
|
50
|
+
- `resulgit switch --branch <name>` - Switch to a branch
|
|
51
|
+
- `resulgit checkout --branch <name>` - Checkout a branch
|
|
52
|
+
|
|
53
|
+
### File Operations
|
|
54
|
+
|
|
55
|
+
- `resulgit status` - Show working directory status
|
|
56
|
+
- `resulgit diff [--path <file>] [--commit <id>]` - Show differences
|
|
57
|
+
- `resulgit add <file> [--content <text>] [--all]` - Add files
|
|
58
|
+
- `resulgit rm --path <file>` - Remove files
|
|
59
|
+
- `resulgit mv --from <old> --to <new>` - Move/rename files
|
|
60
|
+
- `resulgit restore --path <file> [--source <commit>]` - Restore file from commit
|
|
61
|
+
|
|
62
|
+
### Version Control
|
|
63
|
+
|
|
64
|
+
- `resulgit commit --message <text>` - Create a commit
|
|
65
|
+
- `resulgit push` - Push changes to remote
|
|
66
|
+
- `resulgit pull` - Pull changes from remote
|
|
67
|
+
- `resulgit merge --branch <name> [--squash] [--no-push]` - Merge branches
|
|
68
|
+
- `resulgit cherry-pick --commit <id> [--branch <name>] [--no-push]` - Cherry-pick a commit
|
|
69
|
+
- `resulgit revert --commit <id> [--no-push]` - Revert a commit
|
|
70
|
+
- `resulgit reset [--commit <id>] [--mode <soft|mixed|hard>]` - Reset to commit
|
|
71
|
+
|
|
72
|
+
### Stash Operations
|
|
73
|
+
|
|
74
|
+
- `resulgit stash` or `resulgit stash save [--message <msg>]` - Save changes to stash
|
|
75
|
+
- `resulgit stash list` - List all stashes
|
|
76
|
+
- `resulgit stash pop [--index <n>]` - Apply and remove stash
|
|
77
|
+
- `resulgit stash apply [--index <n>]` - Apply stash without removing
|
|
78
|
+
- `resulgit stash drop [--index <n>]` - Delete a stash
|
|
79
|
+
- `resulgit stash clear` - Clear all stashes
|
|
80
|
+
|
|
81
|
+
### Tags
|
|
82
|
+
|
|
83
|
+
- `resulgit tag list` - List all tags
|
|
84
|
+
- `resulgit tag create --name <tag> [--branch <name>]` - Create a tag
|
|
85
|
+
- `resulgit tag delete --name <tag>` - Delete a tag
|
|
86
|
+
|
|
87
|
+
### Pull Requests
|
|
88
|
+
|
|
89
|
+
- `resulgit pr list` - List pull requests
|
|
90
|
+
- `resulgit pr create --title <title> [--source <branch>] [--target <branch>]` - Create pull request
|
|
91
|
+
- `resulgit pr merge --id <id>` - Merge a pull request
|
|
92
|
+
|
|
93
|
+
### Information
|
|
94
|
+
|
|
95
|
+
- `resulgit current` - Show current repository and branch
|
|
96
|
+
- `resulgit head` - Show HEAD commit ID
|
|
97
|
+
- `resulgit show --commit <id>` - Show commit details
|
|
98
|
+
|
|
99
|
+
## Global Options
|
|
100
|
+
|
|
101
|
+
- `--server <url>` - Override default server
|
|
102
|
+
- `--token <token>` - Override stored token
|
|
103
|
+
- `--json` - Output in JSON format
|
|
104
|
+
- `--dir <path>` - Specify working directory
|
|
105
|
+
|
|
106
|
+
## Examples
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Login to server
|
|
110
|
+
resulgit auth login --email user@example.com --password mypassword
|
|
111
|
+
|
|
112
|
+
# List repositories
|
|
113
|
+
resulgit repo list
|
|
114
|
+
|
|
115
|
+
# Clone a repository
|
|
116
|
+
resulgit clone --repo 123 --branch main
|
|
117
|
+
|
|
118
|
+
# Check status
|
|
119
|
+
resulgit status
|
|
120
|
+
|
|
121
|
+
# Create and commit changes
|
|
122
|
+
resulgit add file.txt --content "Hello World"
|
|
123
|
+
resulgit commit --message "Add file.txt"
|
|
124
|
+
resulgit push
|
|
125
|
+
|
|
126
|
+
# Create a branch
|
|
127
|
+
resulgit branch create --name feature-branch
|
|
128
|
+
|
|
129
|
+
# Merge branches
|
|
130
|
+
resulgit merge --branch feature-branch
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
Configuration is stored in `~/.resulgit/config.json`. You can set:
|
|
136
|
+
- `server`: Default server URL
|
|
137
|
+
- `token`: Authentication token
|
|
138
|
+
- `workspaceRoot`: Default workspace directory
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
143
|
+
|
package/package.json
CHANGED
package/resulgit.js
CHANGED
|
@@ -339,15 +339,134 @@ function hashContent(buf) {
|
|
|
339
339
|
return crypto.createHash('sha1').update(buf).digest('hex')
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// LCS-based diff: computes edit script (list of equal/insert/delete operations)
|
|
343
|
+
function computeDiff(oldLines, newLines) {
|
|
344
|
+
const m = oldLines.length
|
|
345
|
+
const n = newLines.length
|
|
346
|
+
// Build LCS table
|
|
347
|
+
const dp = Array.from({ length: m + 1 }, () => new Uint16Array(n + 1))
|
|
348
|
+
for (let i = 1; i <= m; i++) {
|
|
349
|
+
for (let j = 1; j <= n; j++) {
|
|
350
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
351
|
+
dp[i][j] = dp[i - 1][j - 1] + 1
|
|
352
|
+
} else {
|
|
353
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Backtrack to produce edit operations
|
|
358
|
+
const ops = [] // { type: 'equal'|'delete'|'insert', oldIdx, newIdx, line }
|
|
359
|
+
let i = m, j = n
|
|
360
|
+
while (i > 0 || j > 0) {
|
|
361
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
362
|
+
ops.push({ type: 'equal', oldIdx: i - 1, newIdx: j - 1, line: oldLines[i - 1] })
|
|
363
|
+
i--; j--
|
|
364
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
365
|
+
ops.push({ type: 'insert', newIdx: j - 1, line: newLines[j - 1] })
|
|
366
|
+
j--
|
|
367
|
+
} else {
|
|
368
|
+
ops.push({ type: 'delete', oldIdx: i - 1, line: oldLines[i - 1] })
|
|
369
|
+
i--
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
ops.reverse()
|
|
373
|
+
return ops
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Print unified diff with context lines and @@ hunk headers
|
|
377
|
+
function printUnifiedDiff(oldLines, newLines, contextLines) {
|
|
378
|
+
const ctx = contextLines !== undefined ? contextLines : 3
|
|
379
|
+
const ops = computeDiff(oldLines, newLines)
|
|
380
|
+
// Group ops into hunks (runs of changes with context)
|
|
381
|
+
const hunks = []
|
|
382
|
+
let hunk = null
|
|
383
|
+
let lastChangeEnd = -1
|
|
384
|
+
for (let k = 0; k < ops.length; k++) {
|
|
385
|
+
const op = ops[k]
|
|
386
|
+
if (op.type !== 'equal') {
|
|
387
|
+
// Start or extend a hunk
|
|
388
|
+
const contextStart = Math.max(0, k - ctx)
|
|
389
|
+
if (hunk && contextStart <= lastChangeEnd + ctx) {
|
|
390
|
+
// Extend current hunk
|
|
391
|
+
} else {
|
|
392
|
+
// Save previous hunk and start new
|
|
393
|
+
if (hunk) hunks.push(hunk)
|
|
394
|
+
hunk = { startIdx: Math.max(0, k - ctx), endIdx: k }
|
|
395
|
+
}
|
|
396
|
+
hunk.endIdx = k
|
|
397
|
+
lastChangeEnd = k
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (hunk) {
|
|
401
|
+
hunk.endIdx = Math.min(ops.length - 1, hunk.endIdx + ctx)
|
|
402
|
+
hunks.push(hunk)
|
|
403
|
+
}
|
|
404
|
+
// Print each hunk
|
|
405
|
+
for (const h of hunks) {
|
|
406
|
+
const start = h.startIdx
|
|
407
|
+
const end = Math.min(ops.length - 1, h.endIdx)
|
|
408
|
+
// Compute line numbers for header
|
|
409
|
+
let oldStart = 1, newStart = 1
|
|
410
|
+
for (let k = 0; k < start; k++) {
|
|
411
|
+
if (ops[k].type === 'equal' || ops[k].type === 'delete') oldStart++
|
|
412
|
+
if (ops[k].type === 'equal' || ops[k].type === 'insert') newStart++
|
|
413
|
+
}
|
|
414
|
+
let oldCount = 0, newCount = 0
|
|
415
|
+
for (let k = start; k <= end; k++) {
|
|
416
|
+
if (ops[k].type === 'equal' || ops[k].type === 'delete') oldCount++
|
|
417
|
+
if (ops[k].type === 'equal' || ops[k].type === 'insert') newCount++
|
|
418
|
+
}
|
|
419
|
+
process.stdout.write(color(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@\n`, 'cyan'))
|
|
420
|
+
for (let k = start; k <= end; k++) {
|
|
421
|
+
const op = ops[k]
|
|
422
|
+
if (op.type === 'equal') {
|
|
423
|
+
process.stdout.write(` ${op.line}\n`)
|
|
424
|
+
} else if (op.type === 'delete') {
|
|
425
|
+
process.stdout.write(color(`-${op.line}\n`, 'red'))
|
|
426
|
+
} else if (op.type === 'insert') {
|
|
427
|
+
process.stdout.write(color(`+${op.line}\n`, 'green'))
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function loadIgnorePatterns(dir) {
|
|
434
|
+
const patterns = ['.git', '.vcs-next', 'node_modules', '.DS_Store', 'dist', 'build']
|
|
435
|
+
const tryFiles = ['.vcs-ignore', '.gitignore']
|
|
436
|
+
for (const f of tryFiles) {
|
|
437
|
+
try {
|
|
438
|
+
const content = fs.readFileSync(path.join(dir, f), 'utf8')
|
|
439
|
+
const lines = content.split(/\r?\n/).map(l => l.trim()).filter(l => l && !l.startsWith('#'))
|
|
440
|
+
patterns.push(...lines)
|
|
441
|
+
} catch {}
|
|
442
|
+
}
|
|
443
|
+
return [...new Set(patterns)]
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function shouldIgnore(p, patterns) {
|
|
447
|
+
const segments = p.split('/')
|
|
448
|
+
for (const pat of patterns) {
|
|
449
|
+
if (segments.includes(pat)) return true
|
|
450
|
+
if (p === pat || p.startsWith(pat + '/')) return true
|
|
451
|
+
// Basic glob-like support for simple cases
|
|
452
|
+
if (pat.startsWith('**/')) {
|
|
453
|
+
const sub = pat.slice(3)
|
|
454
|
+
if (p.endsWith('/' + sub) || p === sub) return true
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return false
|
|
458
|
+
}
|
|
459
|
+
|
|
342
460
|
async function collectLocal(dir) {
|
|
343
461
|
const out = {}
|
|
344
462
|
const base = path.resolve(dir)
|
|
463
|
+
const patterns = loadIgnorePatterns(base)
|
|
345
464
|
async function walk(cur, rel) {
|
|
346
465
|
const entries = await fs.promises.readdir(cur, { withFileTypes: true })
|
|
347
466
|
for (const e of entries) {
|
|
348
|
-
if (e.name === '.git' || e.name === '.vcs-next') continue
|
|
349
|
-
const abs = path.join(cur, e.name)
|
|
350
467
|
const rp = rel ? rel + '/' + e.name : e.name
|
|
468
|
+
if (shouldIgnore(rp, patterns)) continue
|
|
469
|
+
const abs = path.join(cur, e.name)
|
|
351
470
|
if (e.isDirectory()) {
|
|
352
471
|
await walk(abs, rp)
|
|
353
472
|
} else if (e.isFile()) {
|
|
@@ -392,25 +511,98 @@ async function fetchRemoteFilesMap(server, repo, branch, token) {
|
|
|
392
511
|
async function cmdStatus(opts) {
|
|
393
512
|
const dir = path.resolve(opts.dir || '.')
|
|
394
513
|
const meta = readRemoteMeta(dir)
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
|
|
514
|
+
const metaDir = path.join(dir, '.vcs-next')
|
|
515
|
+
const localPath = path.join(metaDir, 'local.json')
|
|
516
|
+
let localMeta = { baseCommitId: '', baseFiles: {}, pendingCommit: null }
|
|
517
|
+
try {
|
|
518
|
+
const s = fs.readFileSync(localPath, 'utf8')
|
|
519
|
+
localMeta = JSON.parse(s)
|
|
520
|
+
} catch {}
|
|
521
|
+
|
|
398
522
|
const local = await collectLocal(dir)
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
523
|
+
const baseFiles = localMeta.baseFiles || {}
|
|
524
|
+
const pendingFiles = localMeta.pendingCommit ? localMeta.pendingCommit.files : null
|
|
525
|
+
|
|
526
|
+
const untracked = []
|
|
527
|
+
const modifiedUnstaged = []
|
|
528
|
+
const deletedUnstaged = []
|
|
529
|
+
const modifiedStaged = []
|
|
530
|
+
const deletedStaged = []
|
|
531
|
+
const newStaged = []
|
|
532
|
+
|
|
533
|
+
// If there's a pending commit, that's our "staged" area
|
|
534
|
+
if (pendingFiles) {
|
|
535
|
+
// staged changes: pendingFiles vs baseFiles
|
|
536
|
+
const allStagedPaths = new Set([...Object.keys(baseFiles), ...Object.keys(pendingFiles)])
|
|
537
|
+
for (const p of allStagedPaths) {
|
|
538
|
+
const b = baseFiles[p]
|
|
539
|
+
const s = pendingFiles[p]
|
|
540
|
+
if (b === undefined && s !== undefined) newStaged.push(p)
|
|
541
|
+
else if (b !== undefined && s === undefined) deletedStaged.push(p)
|
|
542
|
+
else if (b !== s) modifiedStaged.push(p)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// unstaged changes: local vs pendingFiles
|
|
546
|
+
const localPaths = new Set(Object.keys(local))
|
|
547
|
+
const stagedPaths = new Set(Object.keys(pendingFiles))
|
|
548
|
+
for (const p of localPaths) {
|
|
549
|
+
if (!stagedPaths.has(p)) untracked.push(p)
|
|
550
|
+
else if (pendingFiles[p] !== local[p].content) modifiedUnstaged.push(p)
|
|
551
|
+
}
|
|
552
|
+
for (const p of stagedPaths) {
|
|
553
|
+
if (!localPaths.has(p)) deletedUnstaged.push(p)
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
// No pending commit: just local vs baseFiles
|
|
557
|
+
const localPaths = new Set(Object.keys(local))
|
|
558
|
+
const basePaths = new Set(Object.keys(baseFiles))
|
|
559
|
+
for (const p of localPaths) {
|
|
560
|
+
if (!basePaths.has(p)) untracked.push(p)
|
|
561
|
+
else if (baseFiles[p] !== local[p].content) modifiedUnstaged.push(p)
|
|
562
|
+
}
|
|
563
|
+
for (const p of basePaths) {
|
|
564
|
+
if (!localPaths.has(p)) deletedUnstaged.push(p)
|
|
565
|
+
}
|
|
408
566
|
}
|
|
409
|
-
|
|
410
|
-
|
|
567
|
+
|
|
568
|
+
if (opts.json === 'true') {
|
|
569
|
+
print({
|
|
570
|
+
branch: meta.branch,
|
|
571
|
+
ahead: localMeta.pendingCommit ? 1 : 0,
|
|
572
|
+
staged: { modified: modifiedStaged, deleted: deletedStaged, new: newStaged },
|
|
573
|
+
unstaged: { modified: modifiedUnstaged, deleted: deletedUnstaged },
|
|
574
|
+
untracked
|
|
575
|
+
}, true)
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
process.stdout.write(`On branch ${color(meta.branch, 'cyan')}\n`)
|
|
580
|
+
if (localMeta.pendingCommit) {
|
|
581
|
+
process.stdout.write(`Your branch is ahead of 'origin/${meta.branch}' by 1 commit.\n`)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (modifiedStaged.length > 0 || deletedStaged.length > 0 || newStaged.length > 0) {
|
|
585
|
+
process.stdout.write('\nChanges to be committed:\n')
|
|
586
|
+
for (const p of newStaged) process.stdout.write(color(` new file: ${p}\n`, 'green'))
|
|
587
|
+
for (const p of modifiedStaged) process.stdout.write(color(` modified: ${p}\n`, 'green'))
|
|
588
|
+
for (const p of deletedStaged) process.stdout.write(color(` deleted: ${p}\n`, 'green'))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (modifiedUnstaged.length > 0 || deletedUnstaged.length > 0) {
|
|
592
|
+
process.stdout.write('\nChanges not staged for commit:\n')
|
|
593
|
+
for (const p of modifiedUnstaged) process.stdout.write(color(` modified: ${p}\n`, 'red'))
|
|
594
|
+
for (const p of deletedUnstaged) process.stdout.write(color(` deleted: ${p}\n`, 'red'))
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (untracked.length > 0) {
|
|
598
|
+
process.stdout.write('\nUntracked files:\n')
|
|
599
|
+
for (const p of untracked) process.stdout.write(color(` ${p}\n`, 'red'))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (modifiedStaged.length === 0 && deletedStaged.length === 0 && newStaged.length === 0 &&
|
|
603
|
+
modifiedUnstaged.length === 0 && deletedUnstaged.length === 0 && untracked.length === 0) {
|
|
604
|
+
process.stdout.write('nothing to commit, working tree clean\n')
|
|
411
605
|
}
|
|
412
|
-
const out = { branch: meta.branch, head: remote.headCommitId, added, modified, deleted }
|
|
413
|
-
print(out, opts.json === 'true')
|
|
414
606
|
}
|
|
415
607
|
|
|
416
608
|
async function cmdRestore(opts) {
|
|
@@ -475,7 +667,7 @@ async function cmdDiff(opts) {
|
|
|
475
667
|
const commitSnap = await fetchSnapshotByCommit(server, meta.repoId, commitId, token)
|
|
476
668
|
const parentSnap = parentId ? await fetchSnapshotByCommit(server, meta.repoId, parentId, token) : { files: {}, commitId: '' }
|
|
477
669
|
|
|
478
|
-
const files = filePath ? [filePath] :
|
|
670
|
+
const files = filePath ? [filePath] : [...new Set([...Object.keys(parentSnap.files), ...Object.keys(commitSnap.files)])]
|
|
479
671
|
for (const p of files) {
|
|
480
672
|
const oldContent = parentSnap.files[p] !== undefined ? String(parentSnap.files[p]) : null
|
|
481
673
|
const newContent = commitSnap.files[p] !== undefined ? String(commitSnap.files[p]) : null
|
|
@@ -483,27 +675,17 @@ async function cmdDiff(opts) {
|
|
|
483
675
|
if (opts.json === 'true') {
|
|
484
676
|
print({ path: p, old: oldContent, new: newContent }, true)
|
|
485
677
|
} else {
|
|
486
|
-
process.stdout.write(color(`diff --git a/${p} b/${p}\n`, '
|
|
678
|
+
process.stdout.write(color(`diff --git a/${p} b/${p}\n`, 'bold'))
|
|
487
679
|
if (oldContent === null) {
|
|
488
680
|
process.stdout.write(color(`+++ b/${p}\n`, 'green'))
|
|
489
|
-
|
|
681
|
+
const lines = newContent.split(/\r?\n/)
|
|
682
|
+
for (const line of lines) process.stdout.write(color(`+${line}\n`, 'green'))
|
|
490
683
|
} else if (newContent === null) {
|
|
491
684
|
process.stdout.write(color(`--- a/${p}\n`, 'red'))
|
|
492
|
-
|
|
685
|
+
const lines = oldContent.split(/\r?\n/)
|
|
686
|
+
for (const line of lines) process.stdout.write(color(`-${line}\n`, 'red'))
|
|
493
687
|
} else {
|
|
494
|
-
|
|
495
|
-
const newLines = newContent.split(/\r?\n/)
|
|
496
|
-
const maxLen = Math.max(oldLines.length, newLines.length)
|
|
497
|
-
for (let i = 0; i < maxLen; i++) {
|
|
498
|
-
const oldLine = oldLines[i]
|
|
499
|
-
const newLine = newLines[i]
|
|
500
|
-
if (oldLine !== newLine) {
|
|
501
|
-
if (oldLine !== undefined) process.stdout.write(color(`-${oldLine}\n`, 'red'))
|
|
502
|
-
if (newLine !== undefined) process.stdout.write(color(`+${newLine}\n`, 'green'))
|
|
503
|
-
} else if (oldLine !== undefined) {
|
|
504
|
-
process.stdout.write(` ${oldLine}\n`)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
688
|
+
printUnifiedDiff(oldContent.split(/\r?\n/), newContent.split(/\r?\n/))
|
|
507
689
|
}
|
|
508
690
|
}
|
|
509
691
|
}
|
|
@@ -512,41 +694,37 @@ async function cmdDiff(opts) {
|
|
|
512
694
|
}
|
|
513
695
|
|
|
514
696
|
// Show diff for working directory
|
|
515
|
-
const
|
|
697
|
+
const metaDir = path.join(dir, '.vcs-next')
|
|
698
|
+
const localPath = path.join(metaDir, 'local.json')
|
|
699
|
+
let localMeta = { baseFiles: {} }
|
|
700
|
+
try {
|
|
701
|
+
const s = fs.readFileSync(localPath, 'utf8')
|
|
702
|
+
localMeta = JSON.parse(s)
|
|
703
|
+
} catch {}
|
|
704
|
+
|
|
705
|
+
const baseFiles = localMeta.pendingCommit ? localMeta.pendingCommit.files : localMeta.baseFiles
|
|
516
706
|
const local = await collectLocal(dir)
|
|
517
|
-
const files = filePath ? [filePath] :
|
|
707
|
+
const files = filePath ? [filePath] : [...new Set([...Object.keys(baseFiles), ...Object.keys(local)])]
|
|
518
708
|
|
|
519
709
|
for (const p of files) {
|
|
520
|
-
const
|
|
710
|
+
const baseContent = baseFiles[p] !== undefined ? String(baseFiles[p]) : null
|
|
521
711
|
const localContent = local[p]?.content || null
|
|
522
|
-
const remoteId = remoteSnap.files[p] !== undefined ? hashContent(Buffer.from(String(remoteSnap.files[p]))) : null
|
|
523
|
-
const localId = local[p]?.id
|
|
524
712
|
|
|
525
|
-
if (
|
|
713
|
+
if (baseContent !== localContent) {
|
|
526
714
|
if (opts.json === 'true') {
|
|
527
|
-
print({ path: p,
|
|
715
|
+
print({ path: p, base: baseContent, local: localContent }, true)
|
|
528
716
|
} else {
|
|
529
|
-
process.stdout.write(color(`diff --git a/${p} b/${p}\n`, '
|
|
717
|
+
process.stdout.write(color(`diff --git a/${p} b/${p}\n`, 'bold'))
|
|
530
718
|
if (localContent === null) {
|
|
531
719
|
process.stdout.write(color(`--- a/${p}\n`, 'red'))
|
|
532
|
-
|
|
533
|
-
|
|
720
|
+
const lines = (baseContent || '').split(/\r?\n/)
|
|
721
|
+
for (const line of lines) process.stdout.write(color(`-${line}\n`, 'red'))
|
|
722
|
+
} else if (baseContent === null) {
|
|
534
723
|
process.stdout.write(color(`+++ b/${p}\n`, 'green'))
|
|
535
|
-
|
|
724
|
+
const lines = localContent.split(/\r?\n/)
|
|
725
|
+
for (const line of lines) process.stdout.write(color(`+${line}\n`, 'green'))
|
|
536
726
|
} else {
|
|
537
|
-
|
|
538
|
-
const localLines = String(localContent).split(/\r?\n/)
|
|
539
|
-
const maxLen = Math.max(remoteLines.length, localLines.length)
|
|
540
|
-
for (let i = 0; i < maxLen; i++) {
|
|
541
|
-
const remoteLine = remoteLines[i]
|
|
542
|
-
const localLine = localLines[i]
|
|
543
|
-
if (remoteLine !== localLine) {
|
|
544
|
-
if (remoteLine !== undefined) process.stdout.write(color(`-${remoteLine}\n`, 'red'))
|
|
545
|
-
if (localLine !== undefined) process.stdout.write(color(`+${localLine}\n`, 'green'))
|
|
546
|
-
} else if (remoteLine !== undefined) {
|
|
547
|
-
process.stdout.write(` ${remoteLine}\n`)
|
|
548
|
-
}
|
|
549
|
-
}
|
|
727
|
+
printUnifiedDiff(baseContent.split(/\r?\n/), localContent.split(/\r?\n/))
|
|
550
728
|
}
|
|
551
729
|
}
|
|
552
730
|
}
|