diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c29f09c1228d055568557c8d908b4ddab060d676..cf14e5a85acf066f12fb612e7ad26b7dd183c69f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -10,7 +10,8 @@ For a detailed view of what's changed, refer to the {url-repo}/commits[commit hi * allow target bucket to be specified using `into` key on scan operation (#3) * allow different target bucket to be specified in scanned antora.yml when `create` key is true (#3) -* allow checkout of worktree to be bypassed by setting `worktree.false` on the collector config (#8) +* allow checkout of worktree to be bypassed by setting `worktree.checkout` on the collector config (#8) +* allow keep worktree behavior to be configured per origin by setting `worktree.keep` on the collector config (#22) == 1.0.0-alpha.6 (2024-07-17) diff --git a/docs/modules/ROOT/pages/configuration-keys.adoc b/docs/modules/ROOT/pages/configuration-keys.adoc index 2191c5672aab3a0e4341729a87a2dc7a7641bf69..ccf685e081bbd550e9d184683fcf67beecd6d6dd 100644 --- a/docs/modules/ROOT/pages/configuration-keys.adoc +++ b/docs/modules/ROOT/pages/configuration-keys.adoc @@ -56,7 +56,7 @@ The `worktree` key must be nested under the `collector` key. If the `collector` key is an array, the `worktree` key must be specified in the first entry of the array. The `worktree` key accepts map. -Currently, the only key recognized in that map is `checkout`. +Acceptable keys are `checkout` and `keep`. By default, Collector will checkout the git reference into a temporary worktree if the content source does not already have a worktree. To turn off the default behavior and run collector in an empty directory, set the value of this key to `false`. @@ -82,6 +82,26 @@ When the worktree checkout behavior is turned off, the command to run must be av The command cannot be a file in the git repository (unless the command does its own checkout). The purpose of this option is to avoid the overhead of checking out the worktree when the Collector run does not need any of those files. +The decision to keep the worktree can be configured per origin by setting `worktree.keep` on the collector configuration. + +.antora.yml +[,yaml] +---- +name: colorado +title: Colorado +version: '5.6.0' +ext: + collector: + worktree: + keep: until:exit + run: + - command: generate-files + scan: + dir: generated +---- + +This `worktree.keep` setting overrides the global setting on the extension. + [#clean-key] == clean key diff --git a/packages/collector-extension/lib/index.js b/packages/collector-extension/lib/index.js index b5b6f176da3521f2c3c29a97349d4f39fbea1ca4..71aed8e84536955910ae0ed830c382d925807568 100644 --- a/packages/collector-extension/lib/index.js +++ b/packages/collector-extension/lib/index.js @@ -36,15 +36,16 @@ module.exports.register = function ({ config: { keepWorktrees = false } }) { let worktreeDir = worktree let checkoutWorktree if (!worktree) { - worktreeDir = ospath.join(cacheDir, generateWorktreeFolderName(origin, keepWorktrees)) const worktreeConfig = collectorConfig[0].worktree || {} checkoutWorktree = worktreeConfig.checkout !== false + const keepWorktree = 'keep' in worktreeConfig ? worktreeConfig.keep : keepWorktrees + worktreeDir = ospath.join(cacheDir, generateWorktreeFolderName(origin, keepWorktree)) if (managedWorktrees.has(worktreeDir)) { - managedWorktrees.get(worktreeDir).add(origin) + managedWorktrees.get(worktreeDir).origins.add(origin) if (!checkoutWorktree) await fsp.rm(worktreeDir, { force: true, recursive: true }) } else { - managedWorktrees.set(worktreeDir, new Set([origin])) - if (!checkoutWorktree || keepWorktrees !== true) await fsp.rm(worktreeDir, { force: true, recursive: true }) + managedWorktrees.set(worktreeDir, { origins: new Set([origin]), keep: keepWorktree }) + if (!checkoutWorktree || keepWorktree !== true) await fsp.rm(worktreeDir, { force: true, recursive: true }) } origin.collectorWorktree = worktreeDir } @@ -167,29 +168,24 @@ module.exports.register = function ({ config: { keepWorktrees = false } }) { } } } - if (!keepWorktrees) { - for (const [collectorWorktree, origins] of managedWorktrees) { - for (const origin of origins) delete origin.collectorWorktree - await fsp.rm(collectorWorktree, { recursive: true }) + const deferredWorktreeRemovals = new Map() + for (const [worktreeDir, { origins, keep }] of managedWorktrees) { + if (keep === true) continue + if (typeof keep === 'string' && keep.startsWith('until:')) { + const eventName = keep === 'until:exit' ? 'contextClosed' : keep.slice(6) + const removal = { worktreeDir, origins } + const removals = deferredWorktreeRemovals.get(eventName) + removals ? removals.push(removal) : deferredWorktreeRemovals.set(eventName, [removal]) + continue } + await removeWorktree(worktreeDir, origins) + } + for (const [eventName, removals] of deferredWorktreeRemovals) { + this.once(eventName, () => + Promise.all(removals.map(({ worktreeDir, origins }) => removeWorktree(worktreeDir, origins))) + ) } }) - - if (typeof keepWorktrees === 'string' && keepWorktrees.startsWith('until:')) { - const eventName = keepWorktrees.slice(6) - this.once(eventName === 'exit' ? 'contextClosed' : eventName, async ({ contentCatalog }) => { - // TODO get origins from component version once implemented in Antora core - const collectorWorktrees = contentCatalog.getFiles().reduce((accum, { src }) => { - const collectorWorktree = src.origin?.collectorWorktree - if (collectorWorktree) { - accum.add(collectorWorktree) - delete src.origin.collectorWorktree - } - return accum - }, new Set()) - for (const collectorWorktree of collectorWorktrees) await fsp.rm(collectorWorktree, { recursive: true }) - }) - } } /** @@ -237,6 +233,11 @@ async function prepareWorktree (repo) { } } +async function removeWorktree (worktreeDir, origins) { + for (const origin of origins) delete origin.collectorWorktree + await fsp.rm(worktreeDir, { recursive: true }) +} + function generateWorktreeFolderName ({ url, gitdir, refname, worktree }, keepWorktrees) { const refnameQualifier = keepWorktrees ? '@' + refname.replace(/[/]/g, '-') : undefined if (worktree === undefined) { diff --git a/packages/collector-extension/test/collector-extension-test.js b/packages/collector-extension/test/collector-extension-test.js index 82ac333970cb2936065bb531390792ce0cc48fba..74083e66404c55fe85200069a1a36c095fecf884 100644 --- a/packages/collector-extension/test/collector-extension-test.js +++ b/packages/collector-extension/test/collector-extension-test.js @@ -166,17 +166,7 @@ describe('collector extension', () => { ext.register.call(generatorContext, registerVars) await generatorContext.contentAggregated({ playbook, contentAggregate }) if (after) isAsync(after) ? await after(contentAggregate) : after(contentAggregate) - if (typeof generatorContext.contextClosed === 'function') { - const contentCatalog = { - getFiles () { - return contentAggregate.reduce((accum, { origins = [] }) => { - origins.forEach((origin) => accum.push({ src: { origin } })) - return accum - }, []) - }, - } - await generatorContext.contextClosed({ contentCatalog }) - } + if (typeof generatorContext.contextClosed === 'function') await generatorContext.contextClosed() } it('should not allocate worktree for reference in git tree if collector is not configured', async () => { @@ -264,6 +254,30 @@ describe('collector extension', () => { expect(collectorWorktree).to.not.be.a.path() }) + it('should keep temporary worktree until specified event when specified on collector config', async () => { + const collectorConfig = { + worktree: { keep: 'until:exit' }, + run: { command: 'node .gen-start-page.js' }, + scan: { dir: 'build' }, + } + let collectorWorktree + await runScenario({ + repoName: 'test-at-root', + collectorConfig, + before: (contentAggregate) => { + expect(contentAggregate).to.have.lengthOf(1) + expect(contentAggregate[0].files).to.be.empty() + }, + after: (contentAggregate) => { + expect(contentAggregate[0].files).to.have.lengthOf(1) + collectorWorktree = contentAggregate[0].files[0].src.origin.collectorWorktree + expect(collectorWorktree).to.be.a.directory() + expect(ospath.basename(collectorWorktree)).to.include('@main') + }, + }) + expect(collectorWorktree).to.not.be.a.path() + }) + it('should populate properties of file collected from temporary worktree', async () => { const collectorConfig = { run: { command: 'node .gen-start-page.js' },