amalgm 0.1.243 → 0.1.245
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/lib/supervisor.js
CHANGED
|
@@ -174,6 +174,8 @@ const SAFE_PROCESS_ENV_KEYS = [
|
|
|
174
174
|
'LANG',
|
|
175
175
|
'LC_ALL',
|
|
176
176
|
'LOGNAME',
|
|
177
|
+
'AMALGM_AUTO_UPDATE',
|
|
178
|
+
'AMALGM_DISABLE_AUTO_UPDATE',
|
|
177
179
|
'AMALGM_NATIVE_NODE_MODULES',
|
|
178
180
|
'AMALGM_SKIP_NATIVE_INSTALL',
|
|
179
181
|
'ELECTRON_RUN_AS_NODE',
|
|
@@ -1343,5 +1345,6 @@ module.exports = {
|
|
|
1343
1345
|
liveDeclaredRuntimePorts,
|
|
1344
1346
|
runtimeStateFilesUnder,
|
|
1345
1347
|
runtimeStateSearchRoots,
|
|
1348
|
+
safeBaseProcessEnv,
|
|
1346
1349
|
},
|
|
1347
1350
|
};
|
package/package.json
CHANGED
|
@@ -363,6 +363,77 @@ test('an official checkpoint with a pending payload is terminal corruption', asy
|
|
|
363
363
|
);
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
+
test('hydration reimports official objects when its checkout is replaced before recovery', async () => {
|
|
367
|
+
const { repo: source, oid } = await makeRepo('hydrate-replaced-source');
|
|
368
|
+
const bundle = path.join(tempRoot, 'hydrate-replaced.bundle');
|
|
369
|
+
await git(source, 'bundle', 'create', bundle, 'main');
|
|
370
|
+
const bytes = fs.readFileSync(bundle);
|
|
371
|
+
const digest = require('crypto').createHash('sha256').update(bytes).digest('hex');
|
|
372
|
+
const locatorChunks = [{ sha256: digest, bytes: bytes.length }];
|
|
373
|
+
const chunks = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects', 'chunks');
|
|
374
|
+
fs.mkdirSync(chunks, { recursive: true });
|
|
375
|
+
fs.writeFileSync(path.join(chunks, `${digest}.bin`), bytes);
|
|
376
|
+
|
|
377
|
+
const target = path.join(tempRoot, 'hydrate-replaced-target');
|
|
378
|
+
fs.mkdirSync(target, { recursive: true });
|
|
379
|
+
const resourceId = '018f0000-0000-7000-8000-000000000022';
|
|
380
|
+
const project = { path: target, resourceId };
|
|
381
|
+
const winning = card('/remote', oid, 1);
|
|
382
|
+
winning.objects = {
|
|
383
|
+
kind: 'bundle',
|
|
384
|
+
contract: 'git-bundle-chunks@2',
|
|
385
|
+
resourceId,
|
|
386
|
+
oid,
|
|
387
|
+
ref: `refs/amalgm/exports/${oid}`,
|
|
388
|
+
checksum: locatorChecksum(locatorChunks),
|
|
389
|
+
bytes: bytes.length,
|
|
390
|
+
chunks: locatorChunks,
|
|
391
|
+
};
|
|
392
|
+
const plan = {
|
|
393
|
+
repoKey: `code-folder:${resourceId}`,
|
|
394
|
+
card: winning,
|
|
395
|
+
branches: [],
|
|
396
|
+
transitions: [],
|
|
397
|
+
};
|
|
398
|
+
require('../workspace/wall').bindProject(
|
|
399
|
+
resourceId,
|
|
400
|
+
'11111111-1111-4111-8111-111111111111',
|
|
401
|
+
target,
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const transitions = require('../workspace/transition');
|
|
405
|
+
const updateBranchRefs = transitions.updateBranchRefs;
|
|
406
|
+
let interrupt = true;
|
|
407
|
+
transitions.updateBranchRefs = async (...args) => {
|
|
408
|
+
await updateBranchRefs(...args);
|
|
409
|
+
if (interrupt) {
|
|
410
|
+
interrupt = false;
|
|
411
|
+
throw new Error('simulated process death before hydration execution');
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
try {
|
|
415
|
+
await assert.rejects(
|
|
416
|
+
hydrateRepo(project, plan),
|
|
417
|
+
/simulated process death before hydration execution/,
|
|
418
|
+
);
|
|
419
|
+
} finally {
|
|
420
|
+
transitions.updateBranchRefs = updateBranchRefs;
|
|
421
|
+
}
|
|
422
|
+
assert.equal(readRepoState(plan.repoKey, resourceId).hydrating.contract, 'repo-hydrate@5');
|
|
423
|
+
|
|
424
|
+
const displaced = `${target}.displaced`;
|
|
425
|
+
fs.renameSync(target, displaced);
|
|
426
|
+
fs.mkdirSync(target);
|
|
427
|
+
|
|
428
|
+
const result = await hydrateRepo(project, plan);
|
|
429
|
+
assert.equal(result.state, 'hydrated', JSON.stringify(result));
|
|
430
|
+
assert.equal((await git(target, 'rev-parse', 'HEAD')).trim(), oid);
|
|
431
|
+
await git(target, 'cat-file', '-e', 'HEAD^{commit}');
|
|
432
|
+
await git(target, 'status', '--porcelain=v2', '--branch');
|
|
433
|
+
await git(target, 'fsck', '--connectivity-only', '--no-dangling');
|
|
434
|
+
assert.equal(readRepoState(plan.repoKey, resourceId).materialized, true);
|
|
435
|
+
});
|
|
436
|
+
|
|
366
437
|
test('hydration preserves unknown files and a branch committed while recovery was down', async () => {
|
|
367
438
|
const sourceRepo = await makeRepo('hydrate-source');
|
|
368
439
|
const source = sourceRepo.repo;
|
|
@@ -440,19 +440,17 @@ async function hydrateRepo(project, plan) {
|
|
|
440
440
|
const targetCard = activePlan.card;
|
|
441
441
|
if (!targetCard?.head?.oid) throw new Error('official card has no commit');
|
|
442
442
|
await transition.initializeRepo(project.path, plan.card.repo?.remote || null);
|
|
443
|
+
// Object readiness belongs to the physical checkout, not to the pathname or
|
|
444
|
+
// the durable hydration record. The directory at a registered spelling can
|
|
445
|
+
// be replaced while the runtime is down, leaving a valid recovery plan but
|
|
446
|
+
// a fresh object database. Re-prove the complete current plan on every
|
|
447
|
+
// attempt before recovery is allowed to move HEAD, refs, index, or files.
|
|
448
|
+
const prepared = await preparePlan({ path: project.path }, plan);
|
|
449
|
+
if (prepared.state !== 'ready') return prepared;
|
|
443
450
|
let reduced = pending?.reduced || null;
|
|
444
451
|
let through = pending?.through || null;
|
|
445
452
|
if (!pending) {
|
|
446
|
-
|
|
447
|
-
throw new Error('official card has no complete bundle');
|
|
448
|
-
}
|
|
449
|
-
const bundle = require('./objects').prepareBundleImport(plan.card.objects);
|
|
450
|
-
if (bundle.state !== 'ready') {
|
|
451
|
-
return { state: 'waiting', reason: `objects-${bundle.state}` };
|
|
452
|
-
}
|
|
453
|
-
await transition.importBundle(project.path, bundle.file);
|
|
454
|
-
reduced = await preparePlan({ path: project.path }, plan);
|
|
455
|
-
if (reduced.state !== 'ready') return reduced;
|
|
453
|
+
reduced = prepared;
|
|
456
454
|
through = planThrough(plan);
|
|
457
455
|
}
|
|
458
456
|
const requestDigest = applyingRequestDigest(through, activePlan);
|