devlyn-cli 4.3.31 → 4.3.33
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 +4 -2
- package/bin/devlyn.js +109 -22
- package/bin/harness-activation.js +4 -1
- package/bin/harness-status.js +15 -8
- package/bin/skill-history.json +10 -0
- package/config/skills/_shared/bring-in.py +10 -0
- package/config/skills/_shared/task-complete.py +787 -87
- package/config/skills/_shared/task-completion.md +6 -6
- package/config/skills/_shared/warm-build-dirs.py +13 -0
- package/config/skills/devlyn-ideate/SKILL.md +3 -2
- package/config/skills/devlyn-ideate/references/loop.md +2 -2
- package/config/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/config/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/native/claude/.claude-plugin/plugin.json +1 -1
- package/native/claude/skills/_shared/bring-in.py +10 -0
- package/native/claude/skills/_shared/task-complete.py +787 -87
- package/native/claude/skills/_shared/task-completion.md +6 -6
- package/native/claude/skills/_shared/warm-build-dirs.py +13 -0
- package/native/claude/skills/devlyn-ideate/SKILL.md +3 -2
- package/native/claude/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/native/codex/.agents/plugins/marketplace.json +1 -1
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +1 -1
- package/native/codex/plugins/devlyn/skills/_shared/bring-in.py +10 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +787 -87
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +6 -6
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +13 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +3 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +2 -2
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +11 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +685 -10
- package/package.json +1 -1
- package/release.json +35 -35
package/README.md
CHANGED
|
@@ -72,7 +72,9 @@ A bare intent is planned and, when your request authorizes the work, added and d
|
|
|
72
72
|
|
|
73
73
|
Direct work edits your current checkout; run parallel sessions in separate worktrees (for example `claude -w`). Completed, verified direct requests with changes, and
|
|
74
74
|
every drained task, are delivered by project policy from their own linked worktrees. A commit request stays local and a PR request stops at the PR; otherwise accepted tasks default to
|
|
75
|
-
scoped commit → push → PR → merge when the repository allows it (otherwise the PR waits for a person
|
|
75
|
+
scoped commit → push → PR → merge when the repository allows it (otherwise the PR waits for a person; while required
|
|
76
|
+
checks or reviews have not passed it stays pending, and running the printed resume after they pass merges it, or a person
|
|
77
|
+
merges it; with GitHub's Allow auto-merge on, delivery requests the merge, so it lands by itself). After the
|
|
76
78
|
PR merges, the worktree and branch its session released are cleaned; anything in use is
|
|
77
79
|
kept. Set `git config --local devlyn.completionMode pr` to stop at the PR; `task-complete.py complete --mode auto|pr`
|
|
78
80
|
overrides one task. Local-only/no-push instructions take precedence: nothing is pushed or merged;
|
|
@@ -253,7 +255,7 @@ On native Windows, use native Node/npm and Python plus Git for Windows Bash for
|
|
|
253
255
|
|
|
254
256
|
Drain worktrees default to `<repo parent>/.devlyn-worktrees/<repo name>/<loop-id>/<task-id>`. On Windows, a newly created shared container is marked hidden, and each worktree disables content indexing for new children; names stay indexed and Microsoft Defender scanning is unaffected. For Defender performance, use a Dev Drive via `--worktree-root` or an administrator's exclusion for the root; see [loop options](config/skills/devlyn-ideate/references/loop.md).
|
|
255
257
|
|
|
256
|
-
Windows completion
|
|
258
|
+
Windows completion keeps a merged task's workspace, with its owned refs and recovery receipt, while a program has a file open in it or runs in it, and removes it once that program lets go; a workspace a program only watches may be removed. A managed workspace is kept the same way and removed by hand as the reason says; scratch that holds files is kept too. Resume guidance and delivery status remain separate from product verification. The portability workflow checks out the repository on native Windows and tests the POSIX-packed npm artifact there. A POSIX pass alone does not establish Windows support: acceptance requires the passing Windows job for the exact source SHA/artifact hashes.
|
|
257
259
|
|
|
258
260
|
## Contributing
|
|
259
261
|
|
package/bin/devlyn.js
CHANGED
|
@@ -616,23 +616,46 @@ function installLocalSkill(skillName, roots, installedSkills = new Map()) {
|
|
|
616
616
|
return true;
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
-
// One skill from `source` into one skill-loader directory. The copy is staged and checked first,
|
|
620
|
-
//
|
|
619
|
+
// One skill from `source` into one skill-loader directory. The copy is staged and checked first, and
|
|
620
|
+
// the older copy is moved aside into the stage before the new one is renamed in, and back if that
|
|
621
|
+
// fails, so a failed copy or swap leaves the older copy in place for a plain rerun. If moving it back
|
|
622
|
+
// or removing it after the swap fails, the error names where it is kept. A copy under its name
|
|
621
623
|
// before the 4.0.0 rename goes only once the new one is complete.
|
|
622
624
|
function installSkillInto(source, target, skillName, installedSkills) {
|
|
623
625
|
const dest = path.join(target, skillName);
|
|
624
626
|
const stage = fs.mkdtempSync(path.join(target, '.devlyn-skill-'));
|
|
625
627
|
const stagedSkill = path.join(stage, skillName);
|
|
628
|
+
const previous = path.join(stage, 'previous');
|
|
629
|
+
let files;
|
|
630
|
+
let moved = false;
|
|
626
631
|
try {
|
|
627
|
-
|
|
632
|
+
files = copyRecursive(path.join(source, skillName), stagedSkill, stage);
|
|
628
633
|
assertCompleteSkillInstall(source, stage, [skillName]);
|
|
629
|
-
fs.
|
|
634
|
+
if (fs.lstatSync(dest, { throwIfNoEntry: false })) {
|
|
635
|
+
fs.renameSync(dest, previous);
|
|
636
|
+
moved = true;
|
|
637
|
+
}
|
|
630
638
|
fs.renameSync(stagedSkill, dest);
|
|
631
|
-
|
|
632
|
-
|
|
639
|
+
} catch (error) {
|
|
640
|
+
if (moved) {
|
|
641
|
+
try { fs.renameSync(previous, dest); }
|
|
642
|
+
catch (rollback) {
|
|
643
|
+
throw new AggregateError([error, rollback], `${error.message}; ${rollback.message}; the previous copy is kept at ${previous}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
try { fs.rmSync(stage, { recursive: true, force: true }); }
|
|
647
|
+
catch (cleanup) {
|
|
648
|
+
throw new AggregateError([error, cleanup], `${error.message}; ${dest} is as it was, but ${stage} could not be removed: ${cleanup.message}`);
|
|
649
|
+
}
|
|
650
|
+
throw new Error(`${error.message}; ${dest} is as it was`, { cause: error });
|
|
651
|
+
}
|
|
652
|
+
installedSkills.set(dest, files.map((file) => path.join(dest, path.relative(stagedSkill, file))));
|
|
653
|
+
try {
|
|
633
654
|
fs.rmSync(stage, { recursive: true, force: true });
|
|
655
|
+
} catch (error) {
|
|
656
|
+
const kept = fs.lstatSync(previous, { throwIfNoEntry: false }) ? `its previous copy at ${previous}` : stage;
|
|
657
|
+
throw new Error(`${dest} is installed, but ${kept} could not be removed: ${error.message}`);
|
|
634
658
|
}
|
|
635
|
-
|
|
636
659
|
}
|
|
637
660
|
|
|
638
661
|
// A 0.6.0-0.7.1 copy is one SKILL.md, the file that identifies it. A failed swap leaves
|
|
@@ -1440,19 +1463,78 @@ function cleanupRealParents(base, file) {
|
|
|
1440
1463
|
return true;
|
|
1441
1464
|
}
|
|
1442
1465
|
|
|
1443
|
-
|
|
1444
|
-
//
|
|
1445
|
-
//
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1466
|
+
// The process list that decides whether a copy is in use (a drain started from a copy runs its queue.py and
|
|
1467
|
+
// acceptance.py by absolute path). A path is compared only with text the lister itself printed for that very
|
|
1468
|
+
// path, in a list whose rows are all rendered the same way. A list that cannot be read clears nothing.
|
|
1469
|
+
// POSIX: under the C locale `ps` renders each byte by itself, alike in every row, and the shell it runs from
|
|
1470
|
+
// holds each path between random tokens in its own row, the witness. Each shell's arguments fit one page,
|
|
1471
|
+
// which every kernel accepts and prints whole.
|
|
1472
|
+
const CLEANUP_LIST_BYTES = 4096;
|
|
1473
|
+
// Windows: each command line travels as its UTF-16 code units in decimal, which no code page changes, and this
|
|
1474
|
+
// marker in PowerShell's own command line must come back intact.
|
|
1475
|
+
const CLEANUP_LIST_MARKER = '✓한';
|
|
1476
|
+
const CLEANUP_LIST_COMMAND = `Get-CimInstance Win32_Process | ForEach-Object { ([int[]][char[]]"$($_.CommandLine)") -join ',' } # ${CLEANUP_LIST_MARKER}`;
|
|
1477
|
+
|
|
1478
|
+
// A Windows list, one command line per row; null when a row is malformed or the marker did not come back in
|
|
1479
|
+
// PowerShell's own row beside the command text before it (Node writes each `"` of the command there as `\"`).
|
|
1480
|
+
function cleanupWindowsRows(text) {
|
|
1481
|
+
const rows = [];
|
|
1482
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1483
|
+
const units = line ? line.split(',') : [];
|
|
1484
|
+
if (!units.every((unit) => /^\d{1,5}$/.test(unit) && Number(unit) <= 0xffff)) return null;
|
|
1485
|
+
rows.push(String.fromCharCode(...units.map(Number)));
|
|
1486
|
+
}
|
|
1487
|
+
const own = CLEANUP_LIST_COMMAND.slice(CLEANUP_LIST_COMMAND.lastIndexOf('"') + 1);
|
|
1488
|
+
return rows.some((row) => row.includes(own)) ? rows : null;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// A drain started from Git Bash may name a copy C:/… or /c/…: compared lower-cased, with slashes.
|
|
1492
|
+
function cleanupWindowsNames(row, dir) {
|
|
1493
|
+
const text = row.toLowerCase().replaceAll('\\', '/');
|
|
1494
|
+
const name = `${dir}\\`.toLowerCase().replaceAll('\\', '/');
|
|
1495
|
+
return text.includes(name) || (/^[a-z]:\//.test(name) && text.includes(`/${name[0]}${name.slice(2)}`));
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Which of `dirs` a running process names on its command line.
|
|
1499
|
+
function cleanupInUse(dirs) {
|
|
1500
|
+
const options = { timeout: 20000, maxBuffer: 64 * 1024 * 1024 };
|
|
1501
|
+
const others = (rows) => rows.filter((row) => !row.includes(' cleanup --'));
|
|
1502
|
+
if (process.platform === 'win32') {
|
|
1503
|
+
const listing = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', CLEANUP_LIST_COMMAND], options);
|
|
1504
|
+
const rows = !listing.error && listing.status === 0 ? cleanupWindowsRows(listing.stdout.toString('latin1')) : null;
|
|
1505
|
+
return new Set(dirs.filter((dir) => !rows || others(rows).some((row) => cleanupWindowsNames(row, dir))));
|
|
1506
|
+
}
|
|
1507
|
+
const script = 'ps -axww -o command=; exit $?';
|
|
1508
|
+
// The shell's arguments with 16-letter tokens around the paths, each argument with its NUL.
|
|
1509
|
+
const size = (batch) => Buffer.byteLength(['sh', '-c', script, ...batch].join('\0')) + 1 + 17 * (batch.length + 1);
|
|
1510
|
+
const used = new Set();
|
|
1511
|
+
const batches = [];
|
|
1512
|
+
for (const name of new Set(dirs.map((dir) => dir + path.sep))) {
|
|
1513
|
+
if (batches.length && size([...batches.at(-1), name]) <= CLEANUP_LIST_BYTES) batches.at(-1).push(name);
|
|
1514
|
+
else if (size([name]) <= CLEANUP_LIST_BYTES) batches.push([name]);
|
|
1515
|
+
else used.add(name);
|
|
1516
|
+
}
|
|
1517
|
+
for (const batch of batches) {
|
|
1518
|
+
let tokens;
|
|
1519
|
+
do {
|
|
1520
|
+
tokens = [...batch, ''].map(() => crypto.randomBytes(8).toString('hex'));
|
|
1521
|
+
} while (new Set(tokens).size < tokens.length || tokens.some((token) => batch.some((name) => name.includes(token))));
|
|
1522
|
+
const listing = spawnSync('sh', ['-c', script, ...batch.flatMap((name, i) => [tokens[i], name]), tokens.at(-1)],
|
|
1523
|
+
{ ...options, env: { ...process.env, LC_ALL: 'C' } });
|
|
1524
|
+
const rows = !listing.error && listing.status === 0 ? listing.stdout.toString('latin1').split('\n') : [];
|
|
1525
|
+
// The witness holds every token once, in order; `ps` joins arguments with one space, dropped on each side.
|
|
1526
|
+
const marks = (row) => tokens.map((token) => (row.split(token).length === 2 ? row.indexOf(token) : -1));
|
|
1527
|
+
const witnesses = rows.filter((row) => marks(row).every((mark, i, all) => mark >= 0 && (i === 0 || all[i - 1] < mark)));
|
|
1528
|
+
const [witness] = witnesses, at = witnesses.length === 1 && marks(witness);
|
|
1529
|
+
const between = at ? batch.map((_, i) => witness.slice(at[i] + tokens[i].length, at[i + 1])) : [];
|
|
1530
|
+
const framed = between.length && between.every((text) => text.length > 2 && text.startsWith(' ') && text.endsWith(' '));
|
|
1531
|
+
// The witness alone is no user: any other row naming a path is one, another cleanup's lister shell included.
|
|
1532
|
+
const listed = others(rows.filter((row) => row !== witness));
|
|
1533
|
+
batch.forEach((name, i) => {
|
|
1534
|
+
if (!framed || listed.some((row) => row.includes(between[i].slice(1, -1)))) used.add(name);
|
|
1535
|
+
});
|
|
1453
1536
|
}
|
|
1454
|
-
|
|
1455
|
-
return cleanupProcesses === null || cleanupProcesses.some((line) => line.includes(dir + path.sep));
|
|
1537
|
+
return new Set(dirs.filter((dir) => used.has(dir + path.sep)));
|
|
1456
1538
|
}
|
|
1457
1539
|
|
|
1458
1540
|
// Markers are generated, not bundled: require the exact writer format, a shipped version and
|
|
@@ -1478,7 +1560,7 @@ function cleanupMarkerOwned(file, current) {
|
|
|
1478
1560
|
function cleanupSkillRoots(roots, current, callerBase = null) {
|
|
1479
1561
|
const resolved = new Map();
|
|
1480
1562
|
const linked = new Set();
|
|
1481
|
-
const
|
|
1563
|
+
const spellings = [];
|
|
1482
1564
|
for (const [base, root] of roots) {
|
|
1483
1565
|
let target = root;
|
|
1484
1566
|
const stat = fs.lstatSync(root, { throwIfNoEntry: false });
|
|
@@ -1498,8 +1580,11 @@ function cleanupSkillRoots(roots, current, callerBase = null) {
|
|
|
1498
1580
|
} else if (parents && stat?.isDirectory()) target = fs.realpathSync.native(root);
|
|
1499
1581
|
if (!resolved.has(target)) resolved.set(target, base);
|
|
1500
1582
|
const callerPath = (file) => callerBase ? path.join(callerBase, path.relative(base, file)) : file;
|
|
1501
|
-
|
|
1583
|
+
spellings.push([target, callerPath(root), callerPath(target)]);
|
|
1502
1584
|
}
|
|
1585
|
+
// Every path is known before the list is taken: the lister prints each of them beside the rows it is sought in.
|
|
1586
|
+
const used = cleanupInUse(spellings.flatMap(([, ...names]) => names));
|
|
1587
|
+
const active = new Set(spellings.filter(([, ...names]) => names.some((name) => used.has(name))).map(([target]) => target));
|
|
1503
1588
|
return [...resolved].flatMap(([root, base]) => {
|
|
1504
1589
|
const items = cleanupSkillRoot(base, root, current);
|
|
1505
1590
|
const callerRoot = callerBase ? path.join(callerBase, path.relative(base, root)) : root;
|
|
@@ -2256,7 +2341,9 @@ function cleanupDeliver(scope, items, current) {
|
|
|
2256
2341
|
if (!prepared.acceptance) return cleanupUpdateCheckout(scope);
|
|
2257
2342
|
const completed = lastJson(run(['complete', '--receipt', allocated.receipt, '--acceptance', prepared.acceptance, '--writers-stopped'], scope.root).stdout);
|
|
2258
2343
|
const status = completed?.delivery_status || completed?.status;
|
|
2259
|
-
|
|
2344
|
+
// A merge whose workspace or scratch the helper retained (CLEANUP_PENDING) is not a clean merge: report its resume.
|
|
2345
|
+
if (status === 'COMPLETE' || status === 'MERGED') return { ...cleanupUpdateCheckout(scope), delivery: completed.status === 'CLEANUP_PENDING'
|
|
2346
|
+
? { state: 'cleanup-pending', url: completed.pr || null, resume: completed.resume } : { state: 'merged', url: completed.pr || null } };
|
|
2260
2347
|
if (completed?.pr) return { delivery: { state: 'pr-open', url: completed.pr } };
|
|
2261
2348
|
const reason = completed?.reason || 'delivery did not complete';
|
|
2262
2349
|
return { error: cleanupReason(failureCode(reason), { detail: reason, ...(completed?.resume ? { resume: completed.resume } : {}) }) };
|
|
@@ -380,7 +380,10 @@ function inspectSkills(context, release, env, config, active) {
|
|
|
380
380
|
for (const name of SKILLS) {
|
|
381
381
|
const dir = path.join(root, name);
|
|
382
382
|
try {
|
|
383
|
-
|
|
383
|
+
// A lookup through a file finds no entry: POSIX says ENOTDIR where Windows says "not found".
|
|
384
|
+
let entry;
|
|
385
|
+
try { entry = fs.lstatSync(dir, { throwIfNoEntry: false }); } catch (error) { if (error.code !== 'ENOTDIR') throw error; }
|
|
386
|
+
if (!entry) continue;
|
|
384
387
|
const hash = fingerprint(dir);
|
|
385
388
|
if (!hashes.has(name)) hashes.set(name, fingerprint(path.join(release.root, 'config/skills', name)));
|
|
386
389
|
found.push({ name, path: path.join(dir, 'SKILL.md'), owned: hash === hashes.get(name) || (history.skills[name] || []).includes(hash) });
|
package/bin/harness-status.js
CHANGED
|
@@ -70,16 +70,23 @@ function requirements() {
|
|
|
70
70
|
const probes = [
|
|
71
71
|
['python', true, 'python3', ['-B', '--version']],
|
|
72
72
|
['git', true, 'git', ['--version']],
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// Git Bash is a bash whose builtin `pwd -W` (an MSYS2 patch) prints a Windows path; its version label no longer
|
|
74
|
+
// tells it from Cygwin's. -p reads no BASH_ENV or ENV file; --norc no ~/.bashrc, which a bash at shell level 1
|
|
75
|
+
// otherwise reads when SSH_CLIENT is set or stdin is a socket (Node's pipes are). Long options come first. macOS
|
|
76
|
+
// and Linux bashes refuse -W, so there this row (never required) is unavailable.
|
|
77
|
+
['git-bash', process.platform === 'win32', 'bash', ['--norc', '-p', '-c', 'echo "$BASH_VERSION"; cd / && pwd -W']],
|
|
78
|
+
// gh 2.91 and later writes a telemetry device id into its state folder on every command unless this is off.
|
|
79
|
+
['gh', false, 'gh', ['--version'], { GH_TELEMETRY: 'false' }],
|
|
75
80
|
];
|
|
76
|
-
return [node, ...probes.map(([name, required, binary, argv]) => {
|
|
77
|
-
const result = spawnSync(binary, argv, { encoding: 'utf8', timeout: 5000, env: { ...process.env, PYTHONDONTWRITEBYTECODE: '1' } });
|
|
81
|
+
return [node, ...probes.map(([name, required, binary, argv, env]) => {
|
|
82
|
+
const result = spawnSync(binary, argv, { encoding: 'utf8', timeout: 5000, env: { ...process.env, PYTHONDONTWRITEBYTECODE: '1', ...env } });
|
|
78
83
|
const output = (result.stdout || result.stderr || '').trim();
|
|
79
|
-
const
|
|
80
|
-
|
|
84
|
+
const bash = name === 'git-bash';
|
|
85
|
+
// A bash's version is its $BASH_VERSION line, never digits in the path line after it.
|
|
86
|
+
const match = output.match(bash ? /^\d+\.\d+\.\d+(?=(?:\(\d+\)-\w+)?\r?$)/m : /\b(\d+)\.(\d+)(?:\.\d+)?/);
|
|
87
|
+
let available = !result.error && result.status === 0
|
|
88
|
+
&& (bash ? /^(?:[A-Za-z]:\/|\/\/)/.test(output.split('\n').pop()) : Boolean(match));
|
|
81
89
|
if (name === 'python' && available) available = Number(match[1]) > 3 || (Number(match[1]) === 3 && Number(match[2]) >= 11);
|
|
82
|
-
if (name === 'git-bash' && required && available) available = /mingw|msys/i.test(output);
|
|
83
90
|
return { name, required, available, version: match?.[0] || null,
|
|
84
91
|
reasons: available ? [] : [{ code: 'requirement-unavailable', params: { name } }] };
|
|
85
92
|
})];
|
|
@@ -168,4 +175,4 @@ function statusCommand(args) {
|
|
|
168
175
|
return status;
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
module.exports = { harnessStatus, statusCommand, repositoryIdentity, inspectInstruction };
|
|
178
|
+
module.exports = { harnessStatus, statusCommand, repositoryIdentity, inspectInstruction, requirements };
|
package/bin/skill-history.json
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"406b982f6ca0b9f5c0faa6cb50749b52aaf23545e06174ec39f831929dc929bd",
|
|
58
58
|
"40f50f3c3c0d030d1efeb6955c0c6f32f518e737d8dd3bec6610ff02badebbdb",
|
|
59
59
|
"430d156eb8c62c7edba3d660050f90b267f928976c3a3688e95d54f00dc34747",
|
|
60
|
+
"446e47fc34c81ed4cb9f4332d446a93025ae3dc1791155a4b80d6f195255958e",
|
|
60
61
|
"450d578ef177a9e8f0b693940ad64b41c1d9719975ba6305f62a7c82ac3a892f",
|
|
61
62
|
"45ef27af9ec27089e2861c13b882521fc6bf073dc6b7a356b326b4a27c5e6e24",
|
|
62
63
|
"4685be6a94ad5587ce53785c14a60cccf9a852d9d23391dba0a5bcd0c64931cf",
|
|
@@ -120,6 +121,7 @@
|
|
|
120
121
|
"8c80926ab03b2477e52264495b35c2116f65fc51b02e8c56e441b5705872392a",
|
|
121
122
|
"8fb73c0d2472b3643ac6e3f0ad87d818694afebaf68fcad596496e703893c43d",
|
|
122
123
|
"9162fc2fed6f4a611d299485be3a397080beb3304865ce59b95576ba7d00650e",
|
|
124
|
+
"92ffe78e156d8f11f666a92b26d21ae52e147ed9cad7eb79efbef334a9373281",
|
|
123
125
|
"94a14a1b87df1d7297d12949a24e47a0d6c16a8fe30495ee759fcabb2e1aa398",
|
|
124
126
|
"978e4210e49b42827c98cf6e40c868c87dc6e3533a2b779ca2fa05abfc16ad0f",
|
|
125
127
|
"97e7ad89c74c609b15dfbf6f9f7ac61df8b50e97e4bc318f72922e058ecffba4",
|
|
@@ -130,6 +132,7 @@
|
|
|
130
132
|
"9e4d06fd466fa896040109bac0018fcfb2f1604d55abb4180ff1f1a0b665bbc8",
|
|
131
133
|
"9f14ca629e41730b1a9fb5d0ad1a39cff558fe34758d6ceccbb9de498f9f384e",
|
|
132
134
|
"a11950a5880346f03257c688fd8bc6f8c29fc0b7c05c90e8f5211d5fb7665774",
|
|
135
|
+
"a1d5a9505c527af88bcac9305ec8bce0ee01d472ec383e7ffacbcccf9137565b",
|
|
133
136
|
"a405a99b988f6edd2eabb0504b36309bb4cf0c3b97eab26bb7ef257e4515dbf3",
|
|
134
137
|
"a463693773934b6de78f5326fb17acac34e8159f459b89c21aa66f3ac0874a57",
|
|
135
138
|
"a66080b0ff25f35e8574a24ee01d87de9f579a59796977f3cc59f1865063f6f6",
|
|
@@ -197,6 +200,7 @@
|
|
|
197
200
|
"f2bcfa0471aba81a10e3d0e6fd43bd6ec670601a4ba3a7d719e595edf9ab032a",
|
|
198
201
|
"f958bfabd2e056fea911526fba2196d2ed5a48828f576e5ce1657100b466db20",
|
|
199
202
|
"f9617e1034543f31ae2a3820301fbac6e4674e5d6fbc0d4b76751fe87b8e013e",
|
|
203
|
+
"fbca65e47854425acd80087e89024fb9d2f44f853d88abe582fbee107a3e8de3",
|
|
200
204
|
"fd68c7a74272929a14e685e447a51058ffcdd4c1d6bef83cbba18045c07aee55",
|
|
201
205
|
"fdb608b34a8600b4ce209d7f645ec5ad4475948be6ad95c941cc7ef02b5959df",
|
|
202
206
|
"fe357defe8ac6efd6bbd15b3bf35f79d79761c0f56d507488d9428536d4fb5f7",
|
|
@@ -350,6 +354,7 @@
|
|
|
350
354
|
"05e824f63a6381e02042afb1df0f1437584e25a80de8e9db0bd9456aaeb7aab8",
|
|
351
355
|
"0601ee535419b7039d5da34bd9c8e1c2b3631307214e4afaf394dd47494090c7",
|
|
352
356
|
"062fde2838c0edc0c20e6716948a2ae9fe9d8a98b01a8e8c544c5801641c7cdb",
|
|
357
|
+
"0c54ebc6c95f04ee0ec2f21aa4ebe34f4d2cfb9697b45a2b02999b963bbbdbca",
|
|
353
358
|
"0cef055580e00f9ed439eacb2dff9d238bb11619457472016095a8915162fa4b",
|
|
354
359
|
"0dc69c20434966015176d83baa6f95b3421f9e13d0b1bd608a22f0560eae1049",
|
|
355
360
|
"0fe9b270d670f70da9d2535969b6117684b7c1517806b3029ebaf9624da8006c",
|
|
@@ -365,6 +370,7 @@
|
|
|
365
370
|
"36b58dbdc26e51abc05a60872c4582faeebd67e9fe5626a3933467ba07c68dc4",
|
|
366
371
|
"40237cd5259dbcf7c490f130e844a9e32eea48a8edabd50800edee5029bf95a4",
|
|
367
372
|
"4213143689f7e7fe62ab8743c256a45492fafffc1757d64901b7b02725aee5c3",
|
|
373
|
+
"437659470cb978840f503c7d4a2af75c78beb2752cc3eed413bb33de346bd561",
|
|
368
374
|
"472d2ad853c96651324aa9a3de00cb61c0cecacb389e850ab1b380d535d22922",
|
|
369
375
|
"49d6e024b343dd77e4c0cea0e513e40fe679a7a8192953190d042b3a21bbf69c",
|
|
370
376
|
"5536f8391ec4442f1b793dba8dd5d7820d45f4b6dadf1111cbac9d54469a01ca",
|
|
@@ -384,6 +390,7 @@
|
|
|
384
390
|
"6e2b54571bfbc1fab571b81564fb0625ed83333fc12277c473fdf5a231f7d40c",
|
|
385
391
|
"6e7228610628fc4bca31dc0b0aaf5eb4445e1f123f2c54bd13921c5cd01129fe",
|
|
386
392
|
"75cecbce4d1f4adbba608cef498ae7e421ea75512c6bf9282b66cd94d821f672",
|
|
393
|
+
"78be1509ca8cf797f8c6e790d0d49ab608e50e0b828916c8c6ee790165ce792b",
|
|
387
394
|
"7f451e48e391643b34f6249244bc47d93d6303875e7b092771e3af0718343bc1",
|
|
388
395
|
"7fb3ceaf835230e1a75e510b046f4fe26e79815760354bec1949fa55515c6316",
|
|
389
396
|
"83e9de10d3c439af17dc4d7a603109d35136c5c8bd468ab2be962e1fad064c3c",
|
|
@@ -407,6 +414,7 @@
|
|
|
407
414
|
"db8d921aabf0d04811b55465a3757c62c4b39631aebaf6eecc994ca241cfbe56",
|
|
408
415
|
"e25da746dc37b2e82c658a28d9481fd69741151045683eee2119582ddaf731cd",
|
|
409
416
|
"e47a2ce766ae81fa0870e0a8dfbfe737311de69ce620e92217abbe4c532e3c8b",
|
|
417
|
+
"e67d3662836adf13c2e7177553bba6e7695484bd40438e8188cb9dd6d956cd2f",
|
|
410
418
|
"e929f76c15349f1b9bc1e84e0a3e9e48e916168d79270e0f4a36ea8eab9b5388",
|
|
411
419
|
"ebc693658da0b260c876f29cd6dfc2b12ae6690e3532622fa9c1cd4481162bd8",
|
|
412
420
|
"f2243cc4150e225961825f6097b0cf38166c291dcf58127516ceee841f7ed4c8",
|
|
@@ -829,6 +837,8 @@
|
|
|
829
837
|
"4.3.3",
|
|
830
838
|
"4.3.30",
|
|
831
839
|
"4.3.31",
|
|
840
|
+
"4.3.32",
|
|
841
|
+
"4.3.33",
|
|
832
842
|
"4.3.4",
|
|
833
843
|
"4.3.5",
|
|
834
844
|
"4.3.6",
|
|
@@ -363,6 +363,8 @@ class BringInTests(unittest.TestCase):
|
|
|
363
363
|
self.env = {key: value for key, value in os.environ.items()
|
|
364
364
|
if not key.startswith("GIT_")}
|
|
365
365
|
self.env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=str(self.root / "no-global"),
|
|
366
|
+
# A commit's detached automatic maintenance would still write into .git while a test copies it.
|
|
367
|
+
GIT_CONFIG_COUNT="1", GIT_CONFIG_KEY_0="maintenance.auto", GIT_CONFIG_VALUE_0="false",
|
|
366
368
|
GIT_AUTHOR_NAME="Fixture", GIT_AUTHOR_EMAIL="fixture@example.invalid",
|
|
367
369
|
GIT_COMMITTER_NAME="Fixture", GIT_COMMITTER_EMAIL="fixture@example.invalid",
|
|
368
370
|
LC_ALL="C", GIT_EDITOR=":", GIT_MERGE_AUTOEDIT="no")
|
|
@@ -415,6 +417,14 @@ class BringInTests(unittest.TestCase):
|
|
|
415
417
|
path.write_bytes(("#!/bin/sh\n" + body).encode("utf-8"))
|
|
416
418
|
path.chmod(0o755)
|
|
417
419
|
|
|
420
|
+
def test_fixture_commit_starts_no_automatic_maintenance(self):
|
|
421
|
+
# Its detached child would still write .git/objects/maintenance.lock while copytree or cleanup reads the tree.
|
|
422
|
+
trace = self.root / "trace.json"
|
|
423
|
+
self.env["GIT_TRACE2_EVENT"] = str(trace)
|
|
424
|
+
self.commit("traced", "traced\n")
|
|
425
|
+
children = [line for line in trace.read_text(encoding="utf-8").splitlines() if '"event":"child_start"' in line]
|
|
426
|
+
self.assertEqual(children, [])
|
|
427
|
+
|
|
418
428
|
def test_fast_forward_and_paths_with_spaces(self):
|
|
419
429
|
self.cli()
|
|
420
430
|
self.assertEqual(self.g("rev-parse", "HEAD").strip(), self.target)
|