resulgit 1.0.13 → 1.0.15
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/package.json +1 -1
- package/resulgit.js +96 -6
package/package.json
CHANGED
package/resulgit.js
CHANGED
|
@@ -100,6 +100,43 @@ async function request(method, url, body, token) {
|
|
|
100
100
|
return res.text()
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Upload files as blobs via multipart form data
|
|
105
|
+
* @param {string} server - Server URL
|
|
106
|
+
* @param {string} repoId - Repository ID
|
|
107
|
+
* @param {Record<string, string>} files - Map of file paths to content
|
|
108
|
+
* @param {string} token - Auth token
|
|
109
|
+
* @returns {Promise<Record<string, string>>} Map of file paths to blob IDs
|
|
110
|
+
*/
|
|
111
|
+
async function uploadBlobs(server, repoId, files, token) {
|
|
112
|
+
const FormData = (await import('node:buffer')).File ? globalThis.FormData : (await import('undici')).FormData
|
|
113
|
+
const formData = new FormData()
|
|
114
|
+
|
|
115
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
116
|
+
// Create a Blob/File from the content
|
|
117
|
+
const blob = new Blob([content], { type: 'text/plain' })
|
|
118
|
+
formData.append('files', blob, filePath)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const url = new URL(`/api/repositories/${repoId}/blobs`, server).toString()
|
|
122
|
+
const headers = {}
|
|
123
|
+
if (token) headers['Authorization'] = `Bearer ${token}`
|
|
124
|
+
|
|
125
|
+
const res = await fetch(url, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers,
|
|
128
|
+
body: formData
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
if (!res.ok) {
|
|
132
|
+
const text = await res.text()
|
|
133
|
+
throw new Error(`Blob upload failed: ${res.status} ${res.statusText} ${text}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const data = await res.json()
|
|
137
|
+
return data.blobs || {}
|
|
138
|
+
}
|
|
139
|
+
|
|
103
140
|
function print(obj, json) {
|
|
104
141
|
if (json) {
|
|
105
142
|
process.stdout.write(JSON.stringify(obj, null, 2) + '\n')
|
|
@@ -1393,14 +1430,44 @@ async function cmdPush(opts) {
|
|
|
1393
1430
|
}
|
|
1394
1431
|
if (conflicts.length > 0) {
|
|
1395
1432
|
spinnerFail(spinner, 'Push blocked by conflicts')
|
|
1433
|
+
|
|
1434
|
+
// Write conflict markers to local files
|
|
1435
|
+
for (const c of conflicts) {
|
|
1436
|
+
const localContent = local[c.path]?.content || ''
|
|
1437
|
+
const remoteContent = remote.files[c.path] || ''
|
|
1438
|
+
|
|
1439
|
+
// Create conflict-marked content
|
|
1440
|
+
const conflictContent = [
|
|
1441
|
+
'<<<<<<< LOCAL (your changes)',
|
|
1442
|
+
localContent,
|
|
1443
|
+
'=======',
|
|
1444
|
+
remoteContent,
|
|
1445
|
+
'>>>>>>> REMOTE (server changes)'
|
|
1446
|
+
].join('\n')
|
|
1447
|
+
|
|
1448
|
+
// Write to local file
|
|
1449
|
+
const conflictPath = path.join(dir, c.path)
|
|
1450
|
+
await fs.promises.mkdir(path.dirname(conflictPath), { recursive: true })
|
|
1451
|
+
await fs.promises.writeFile(conflictPath, conflictContent, 'utf8')
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1396
1454
|
if (opts.json === 'true') {
|
|
1397
|
-
print({ conflicts }, true)
|
|
1455
|
+
print({ conflicts, message: 'Conflict markers written to files. Resolve them and try again.' }, true)
|
|
1398
1456
|
} else {
|
|
1399
|
-
process.stderr.write(color('
|
|
1400
|
-
process.stderr.write(color('Conflicts detected:\\n', 'yellow'))
|
|
1457
|
+
process.stderr.write(color('\nConflicts detected! The following files have been updated with conflict markers:\n', 'red'))
|
|
1401
1458
|
for (const c of conflicts) {
|
|
1402
|
-
process.stderr.write(color(` ${c.path}
|
|
1459
|
+
process.stderr.write(color(` ${c.path}\n`, 'yellow'))
|
|
1403
1460
|
}
|
|
1461
|
+
process.stderr.write(color('\nTo resolve:\n', 'cyan'))
|
|
1462
|
+
process.stderr.write(color(' 1. Open the files above and look for conflict markers:\n', 'dim'))
|
|
1463
|
+
process.stderr.write(color(' <<<<<<< LOCAL (your changes)\n', 'dim'))
|
|
1464
|
+
process.stderr.write(color(' ... your version ...\n', 'dim'))
|
|
1465
|
+
process.stderr.write(color(' =======\n', 'dim'))
|
|
1466
|
+
process.stderr.write(color(' ... server version ...\n', 'dim'))
|
|
1467
|
+
process.stderr.write(color(' >>>>>>> REMOTE (server changes)\n', 'dim'))
|
|
1468
|
+
process.stderr.write(color(' 2. Edit the files to keep the changes you want\n', 'dim'))
|
|
1469
|
+
process.stderr.write(color(' 3. Remove the conflict markers\n', 'dim'))
|
|
1470
|
+
process.stderr.write(color(' 4. Run: resulgit add . && resulgit commit -m "Resolved conflicts" && resulgit push\n\n', 'dim'))
|
|
1404
1471
|
}
|
|
1405
1472
|
return
|
|
1406
1473
|
}
|
|
@@ -1429,8 +1496,31 @@ async function cmdPush(opts) {
|
|
|
1429
1496
|
// Hook doesn't exist or other error, continue
|
|
1430
1497
|
}
|
|
1431
1498
|
|
|
1432
|
-
|
|
1433
|
-
spinnerUpdate(spinner, `
|
|
1499
|
+
// Step 1: Upload files as blobs
|
|
1500
|
+
spinnerUpdate(spinner, `Uploading ${Object.keys(merged).length} file(s)...`)
|
|
1501
|
+
let blobMap = {}
|
|
1502
|
+
try {
|
|
1503
|
+
blobMap = await uploadBlobs(server, remoteMeta.repoId, merged, token)
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
// If blob upload fails, fall back to sending content directly
|
|
1506
|
+
// (This handles servers that accept content in commits endpoint)
|
|
1507
|
+
spinnerUpdate(spinner, 'Blob upload not available, sending files directly...')
|
|
1508
|
+
blobMap = merged // Use content directly
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// Step 2: Create commit with blob IDs (or content if blob upload failed)
|
|
1512
|
+
const commitFiles = {}
|
|
1513
|
+
for (const [filePath, content] of Object.entries(merged)) {
|
|
1514
|
+
// Use blob ID if available, otherwise use content
|
|
1515
|
+
commitFiles[filePath] = blobMap[filePath] || content
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const body = {
|
|
1519
|
+
message: localMeta.pendingCommit?.message || (opts.message || 'Push'),
|
|
1520
|
+
files: commitFiles,
|
|
1521
|
+
branchName: remoteMeta.branch
|
|
1522
|
+
}
|
|
1523
|
+
spinnerUpdate(spinner, `Creating commit on '${remoteMeta.branch}'...`)
|
|
1434
1524
|
const url = new URL(`/api/repositories/${remoteMeta.repoId}/commits`, server).toString()
|
|
1435
1525
|
const data = await request('POST', url, body, token)
|
|
1436
1526
|
localMeta.baseCommitId = data.id || remote.commitId || ''
|