gip-remote 1.2.7 → 1.2.8
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/index.js +145 -15
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -210,35 +210,76 @@ class Remote extends ReadyResource {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// 4b.
|
|
214
|
-
// actually changed
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
213
|
+
// 4b. Compute, for each file in HEAD's tree, the most recent commit IN
|
|
214
|
+
// THIS PUSH that actually changed its blob — that's the commit we
|
|
215
|
+
// want to credit on the @gip/files row. If we just stamped every
|
|
216
|
+
// file with HEAD's metadata (the previous behaviour), a fresh push
|
|
217
|
+
// of a multi-commit history would make every file look like it was
|
|
218
|
+
// last edited by HEAD, which is useless for a tree view.
|
|
219
219
|
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
220
|
+
// Algorithm: walk the commit chain HEAD → first-parent through the
|
|
221
|
+
// commits we have in the pack, flatten each commit's tree into a
|
|
222
|
+
// path → oid map, and for each path record the most recent commit
|
|
223
|
+
// whose tree differs at that path from its first-parent's tree.
|
|
224
|
+
// We don't follow merge parents — same heuristic GitHub's "blame"
|
|
225
|
+
// uses; it keeps cost predictable and matches user expectation.
|
|
226
|
+
//
|
|
227
|
+
// Cases the fallback below handles:
|
|
228
|
+
// - Pack is shallow and the path was already at HEAD's blob
|
|
229
|
+
// before our oldest commit — keep the existing row as-is.
|
|
230
|
+
// - First push containing a root commit — files appearing in the
|
|
231
|
+
// root take the root commit's metadata.
|
|
232
|
+
const fileLastTouch = computeFileLastTouch(objects, resolvedOid, commit)
|
|
233
|
+
const oldestInPack = fileLastTouch.oldest
|
|
234
|
+
|
|
223
235
|
for (const file of files) {
|
|
224
236
|
const existing = await this._db.get('@gip/files', {
|
|
225
237
|
branch: refName,
|
|
226
238
|
path: file.path
|
|
227
239
|
})
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
|
|
241
|
+
let meta = fileLastTouch.byPath.get(file.path)
|
|
242
|
+
|
|
243
|
+
if (!meta) {
|
|
244
|
+
// No commit in our pack changed this file. Either it's been at this
|
|
245
|
+
// blob since before our window, or the pack is shallow.
|
|
246
|
+
if (existing && existing.oid === file.oid && existing.mode === file.mode) {
|
|
247
|
+
// Genuinely unchanged from prior push — leave row alone.
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
// Fall back to the oldest commit we have. Best approximation when a
|
|
251
|
+
// shallow clone is being seeded (we don't have the real introducing
|
|
252
|
+
// commit, but the oldest commit in the pack is a strict upper bound
|
|
253
|
+
// on "when it could have last changed" given what we know).
|
|
254
|
+
meta = {
|
|
255
|
+
author: oldestInPack.author,
|
|
256
|
+
message: oldestInPack.message,
|
|
257
|
+
timestamp: oldestInPack.timestamp
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Idempotency: if the existing row already reflects the same blob,
|
|
262
|
+
// mode, and metadata we'd write, skip the insert. Saves a write
|
|
263
|
+
// round-trip on no-op pushes (e.g. retries).
|
|
264
|
+
if (
|
|
265
|
+
existing &&
|
|
266
|
+
existing.oid === file.oid &&
|
|
267
|
+
existing.mode === file.mode &&
|
|
268
|
+
existing.message === meta.message &&
|
|
269
|
+
existing.timestamp === meta.timestamp
|
|
270
|
+
) {
|
|
231
271
|
continue
|
|
232
272
|
}
|
|
273
|
+
|
|
233
274
|
await this._db.insert('@gip/files', {
|
|
234
275
|
branch: refName,
|
|
235
276
|
path: file.path,
|
|
236
277
|
oid: file.oid,
|
|
237
278
|
mode: file.mode,
|
|
238
279
|
size: file.size,
|
|
239
|
-
author:
|
|
240
|
-
message:
|
|
241
|
-
timestamp:
|
|
280
|
+
author: meta.author,
|
|
281
|
+
message: meta.message,
|
|
282
|
+
timestamp: meta.timestamp
|
|
242
283
|
})
|
|
243
284
|
}
|
|
244
285
|
|
|
@@ -382,6 +423,95 @@ class Remote extends ReadyResource {
|
|
|
382
423
|
}
|
|
383
424
|
}
|
|
384
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Walk the first-parent commit chain (within `objects`) starting at HEAD,
|
|
428
|
+
* and for each path in HEAD's tree return the metadata of the most recent
|
|
429
|
+
* commit whose tree differs at that path from its parent's tree.
|
|
430
|
+
*
|
|
431
|
+
* Returns:
|
|
432
|
+
* - byPath: Map<path, { author, message, timestamp }> for paths we
|
|
433
|
+
* could attribute within the pack.
|
|
434
|
+
* - oldest: the oldest commit reached (used as a fallback by callers
|
|
435
|
+
* when the pack is shallow and a path was already at its
|
|
436
|
+
* current blob before the pack window).
|
|
437
|
+
*
|
|
438
|
+
* Pure function — does no IO, just inspects the in-memory `objects` map.
|
|
439
|
+
*/
|
|
440
|
+
function computeFileLastTouch(objects, headOid, headCommit) {
|
|
441
|
+
// 1. Walk the chain through first-parent edges, stopping at the first
|
|
442
|
+
// parent we don't have. Stash both the parsed commit and the OID.
|
|
443
|
+
const chain = []
|
|
444
|
+
let curOid = headOid
|
|
445
|
+
let cur = headCommit
|
|
446
|
+
for (;;) {
|
|
447
|
+
chain.push({ oid: curOid, commit: cur })
|
|
448
|
+
if (!cur.parents || cur.parents.length === 0) break
|
|
449
|
+
const parentOid = cur.parents[0]
|
|
450
|
+
const parentObj = objects.get(parentOid)
|
|
451
|
+
if (!parentObj || parentObj.type !== 'commit') break
|
|
452
|
+
cur = parseCommit(parentObj.data)
|
|
453
|
+
curOid = parentOid
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 2. Flatten each commit's tree into a path → oid map. walkTree handles
|
|
457
|
+
// nested trees and skips missing sub-trees gracefully (returns []),
|
|
458
|
+
// which is what we want for incremental packs that don't re-send
|
|
459
|
+
// unchanged sub-trees.
|
|
460
|
+
const treeMaps = chain.map(({ commit }) => {
|
|
461
|
+
const map = new Map()
|
|
462
|
+
for (const f of walkTree(objects, commit.tree, '')) {
|
|
463
|
+
map.set(f.path, f.oid)
|
|
464
|
+
}
|
|
465
|
+
return map
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// 3. Walk newest-first; record the change point for each HEAD path.
|
|
469
|
+
const byPath = new Map()
|
|
470
|
+
const remaining = new Set(treeMaps[0].keys())
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < chain.length && remaining.size > 0; i++) {
|
|
473
|
+
const cur = treeMaps[i]
|
|
474
|
+
const isLast = i === chain.length - 1
|
|
475
|
+
const par = isLast ? null : treeMaps[i + 1]
|
|
476
|
+
// True root commit (no parents at all) — files appearing here are new,
|
|
477
|
+
// so they're attributed to this commit. We must NOT do the same when
|
|
478
|
+
// we ran out of pack (parents exist, just not in pack), or we'd make
|
|
479
|
+
// up an attribution that isn't real.
|
|
480
|
+
const isRoot = isLast && (!chain[i].commit.parents || chain[i].commit.parents.length === 0)
|
|
481
|
+
|
|
482
|
+
for (const path of remaining) {
|
|
483
|
+
const curOidAtPath = cur.get(path)
|
|
484
|
+
if (curOidAtPath === undefined) continue
|
|
485
|
+
|
|
486
|
+
if (par) {
|
|
487
|
+
const parOidAtPath = par.get(path)
|
|
488
|
+
if (parOidAtPath !== curOidAtPath) {
|
|
489
|
+
byPath.set(path, {
|
|
490
|
+
author: chain[i].commit.author,
|
|
491
|
+
message: chain[i].commit.message,
|
|
492
|
+
timestamp: chain[i].commit.timestamp
|
|
493
|
+
})
|
|
494
|
+
remaining.delete(path)
|
|
495
|
+
}
|
|
496
|
+
} else if (isRoot) {
|
|
497
|
+
byPath.set(path, {
|
|
498
|
+
author: chain[i].commit.author,
|
|
499
|
+
message: chain[i].commit.message,
|
|
500
|
+
timestamp: chain[i].commit.timestamp
|
|
501
|
+
})
|
|
502
|
+
remaining.delete(path)
|
|
503
|
+
}
|
|
504
|
+
// else: shallow boundary — caller falls back to existing row /
|
|
505
|
+
// oldest-commit metadata.
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
byPath,
|
|
511
|
+
oldest: chain[chain.length - 1].commit
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
385
515
|
module.exports = {
|
|
386
516
|
Remote,
|
|
387
517
|
RemoteDrive,
|