Loading CHANGELOG.adoc +2 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ This project utilizes semantic versioning. === Fixed * *content-aggregator*: Discover linked worktree attached to bare repository when worktree is used as content source (#1111) * *content-aggregator*: Discover linked worktree attached to bare repository when bare repository is used as content source (#1111) * *content-aggregator*: Coerce version value to string in refname projection; coerce null value to empty string (#1170) * *page-composer*: Fix crash if `resolvePage` or `resolvePageURL` helpers are called with no arguments * *asciidoc-loader*: Restore support for relative path token for include file in example or partial family (#1173) Loading packages/content-aggregator/lib/aggregate-content.js +30 −29 Original line number Diff line number Diff line Loading @@ -373,24 +373,31 @@ async function selectReferences (source, repo, remote) { } } } let preferRemote = false // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches) const remoteBranches = remote ? (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD') : [] if (remoteBranches.length) { if (isBare) preferRemote = true for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) { const fullname = 'remotes/' + remote + '/' + shortname refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree }) } } // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches if (!isBare) { const localBranches = await git.listBranches(repo).then((branches) => { if (branches.length) return branches if (currentBranch == null) return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : [])) return currentBranch ? [currentBranch] : [] if (branches.length || isBare) return branches if (currentBranch != null) return [currentBranch] return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : [])) }) if (managed) { if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) { if (preferRemote && refs.has(shortname)) continue refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: undefined }) } } } else { const worktrees = await findWorktrees(repo, worktreePatterns) let onMatch if ((worktreePatterns.join('') || '.') !== '.') { Loading @@ -404,19 +411,13 @@ async function selectReferences (source, repo, remote) { return shortname ? (pattern.startsWith('HEAD@') ? shortname : undefined) : candidate } } if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) { const head = (worktrees.get(shortname) || { head: noWorktree }).head if (preferRemote && refs.has(shortname)) continue const head = (worktrees.get(shortname) || { head: false }).head refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head }) } } } else if (!managed || !remoteBranches.length) { const localBranches = await git.listBranches(repo) if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) { if (refs.has(shortname)) continue // NOTE prefer remote branches in bare repository refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree }) } } } return [...refs.values()] } Loading Loading @@ -1077,11 +1078,11 @@ function resolveRepositoryFromWorktree (repo) { function findWorktrees (repo, patterns) { if (!patterns.length) return new Map() const mainWorktree = patterns[0] === '.' && (patterns = patterns.slice(1)) patterns[0] === '.' && (patterns = patterns.slice(1)) && !repo.noCheckout ? getCurrentBranchName(repo).then((branch) => branch && [branch, { head: repo.dir, name: '.' }]) : Promise.resolve() if (!patterns.length) return mainWorktree.then((entry) => new Map(entry && [entry])) const worktreesDir = ospath.join(repo.dir, '.git', 'worktrees') const worktreesDir = ospath.join(repo.dir, repo.dir === repo.gitdir ? '' : '.git', 'worktrees') const patternCache = repo.cache[REF_PATTERN_CACHE_KEY] return fsp .readdir(worktreesDir) Loading packages/content-aggregator/test/aggregate-content-test.js +40 −7 Original line number Diff line number Diff line Loading @@ -4788,16 +4788,20 @@ describe('aggregateContent()', () => { worktreeBranch = 'v1.2.x', worktreeName = worktreeBranch, checkout, bare, } = {}) => { const repoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR) const repoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR, { bare }) const repoName = 'the-component' const version = worktreeBranch.replace(/v?(\d+\.\d+)(?:\.x.*)?/, '$1') const dir = ospath.join(repoBuilder.repoBase, repoName) const linkedWorktreeRepoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR) const linkedWorktreeRepoName = `the-component-${worktreeName}` const linkedWorktreeDir = ospath.join(linkedWorktreeRepoBuilder.repoBase, linkedWorktreeRepoName) const linkedWorktreeDotgit = ospath.join(linkedWorktreeDir, '.git') const linkedWorktreeGitdir = ospath.join(dir, `.git/worktrees/${worktreeName}`) const linkedWorktreeGitdir = bare === 'flat' ? ospath.join(repoBuilder.repoBase, repoName + '.git', 'worktrees', worktreeName) : ospath.join(repoBuilder.repoBase, repoName, '.git', 'worktrees', worktreeName) const worktreesDirFromWorktree = bare === 'flat' ? `../${repoName}.git/worktrees` : '.git/worktrees' const pageOnePath = 'modules/ROOT/pages/page-one.adoc' await initRepoWithFilesAndWorktree(repoBuilder, { version }, () => repoBuilder Loading @@ -4805,12 +4809,17 @@ describe('aggregateContent()', () => { .then((self) => checkout ? (checkout === true ? self.getHeadCommit() : Promise.resolve(`ref: refs/heads/${checkout}`)).then( (ref) => self.addToWorktree(`.git/worktrees/${worktreeName}/HEAD`, ref + '\n') (ref) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/HEAD`, ref + '\n') ) : self.addToWorktree( `${worktreesDirFromWorktree}/${worktreeName}/HEAD`, `ref: refs/heads/${worktreeBranch}\n` ) ) : self.addToWorktree(`.git/worktrees/${worktreeName}/HEAD`, `ref: refs/heads/${worktreeBranch}\n`) .then((self) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/commondir`, '../..\n')) .then((self) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/gitdir`, linkedWorktreeDotgit + '\n') ) .then((self) => self.addToWorktree(`.git/worktrees/${worktreeName}/commondir`, '../..\n')) .then((self) => self.addToWorktree(`.git/worktrees/${worktreeName}/gitdir`, linkedWorktreeDotgit + '\n')) .then((self) => (typeof checkout === 'string' ? self.checkoutBranch(checkout) : self)) .then((self) => self.checkoutBranch('main')) .then((self) => self.addComponentDescriptorToWorktree({ name: 'the-component', version: '2.0' })) Loading Loading @@ -5085,6 +5094,30 @@ describe('aggregateContent()', () => { ) }) it('should resolve worktree from bare repository if content source is linked worktree', async () => { const { linkedWorktreeRepoBuilder } = await initRepoWithFilesAndMultipleWorktrees({ bare: 'flat' }) playbookSpec.content.sources.push({ url: linkedWorktreeRepoBuilder.url, branches: 'HEAD' }) const aggregate = await aggregateContent(playbookSpec) expect(aggregate).to.have.lengthOf(1) expect(aggregate[0]).to.include({ name: 'the-component', version: '1.2' }) const files = aggregate[0].files const pageOne = files.find((it) => it.relative === 'modules/ROOT/pages/page-one.adoc') expect(pageOne.contents.toString()).to.include('= Page One (Linked Worktree)\n') expect(pageOne.src.origin.refname).to.equal('v1.2.x') }) it('should resolve linked worktree if content source is bare repository', async () => { const { repoBuilder } = await initRepoWithFilesAndMultipleWorktrees({ bare: 'flat' }) playbookSpec.content.sources.push({ url: repoBuilder.url, branches: 'v1.2.x', worktrees: true }) const aggregate = await aggregateContent(playbookSpec) expect(aggregate).to.have.lengthOf(1) expect(aggregate[0]).to.include({ name: 'the-component', version: '1.2' }) const files = aggregate[0].files const pageOne = files.find((it) => it.relative === 'modules/ROOT/pages/page-one.adoc') expect(pageOne.contents.toString()).to.include('= Page One (Linked Worktree)\n') expect(pageOne.src.origin.refname).to.equal('v1.2.x') }) it('should not use worktree if content source is linked worktree and worktrees is .', async () => { const { linkedWorktreeRepoBuilder } = await initRepoWithFilesAndMultipleWorktrees() playbookSpec.content.sources.push({ url: linkedWorktreeRepoBuilder.url, branches: 'v1.2.x', worktrees: '.' }) Loading packages/test-harness/lib/repository-builder.js +3 −2 Original line number Diff line number Diff line Loading @@ -41,14 +41,15 @@ class RepositoryBuilder { this.repoPath += '.git' this.url = `${this.gitServerProtocol}//localhost:${this.gitServerPort}/${repoName}.git` } else if (this.bare) { this.url += ospath.sep + '.git' this.url += (this.bare === 'flat' ? '' : ospath.sep) + '.git' } else { this.local = ospath.join(this.repoPath, '.git') } const dir = this.repoPath const gitdir = ospath.join(dir, '.git') const gitdir = this.bare === 'flat' ? dir + '.git' : ospath.join(dir, '.git') this.repository = { cache: {}, defaultBranch: opts.branch || 'main', dir, fs, gitdir, http } await git.init(this.repository) if (this.bare === 'flat') await fsp.mkdir(dir) if (opts.empty) return this await (await this.addToWorktree('.gitignore')).addToWorktree('.gitattributes', '* text=auto eol=lf') // NOTE isomorphic-git doesn't require any commits to set up the default branch, but tests still rely on these files Loading Loading
CHANGELOG.adoc +2 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ This project utilizes semantic versioning. === Fixed * *content-aggregator*: Discover linked worktree attached to bare repository when worktree is used as content source (#1111) * *content-aggregator*: Discover linked worktree attached to bare repository when bare repository is used as content source (#1111) * *content-aggregator*: Coerce version value to string in refname projection; coerce null value to empty string (#1170) * *page-composer*: Fix crash if `resolvePage` or `resolvePageURL` helpers are called with no arguments * *asciidoc-loader*: Restore support for relative path token for include file in example or partial family (#1173) Loading
packages/content-aggregator/lib/aggregate-content.js +30 −29 Original line number Diff line number Diff line Loading @@ -373,24 +373,31 @@ async function selectReferences (source, repo, remote) { } } } let preferRemote = false // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches) const remoteBranches = remote ? (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD') : [] if (remoteBranches.length) { if (isBare) preferRemote = true for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) { const fullname = 'remotes/' + remote + '/' + shortname refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree }) } } // NOTE only consider local branches if repo has a worktree or there are no remote tracking branches if (!isBare) { const localBranches = await git.listBranches(repo).then((branches) => { if (branches.length) return branches if (currentBranch == null) return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : [])) return currentBranch ? [currentBranch] : [] if (branches.length || isBare) return branches if (currentBranch != null) return [currentBranch] return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : [])) }) if (managed) { if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) { if (preferRemote && refs.has(shortname)) continue refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: undefined }) } } } else { const worktrees = await findWorktrees(repo, worktreePatterns) let onMatch if ((worktreePatterns.join('') || '.') !== '.') { Loading @@ -404,19 +411,13 @@ async function selectReferences (source, repo, remote) { return shortname ? (pattern.startsWith('HEAD@') ? shortname : undefined) : candidate } } if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) { const head = (worktrees.get(shortname) || { head: noWorktree }).head if (preferRemote && refs.has(shortname)) continue const head = (worktrees.get(shortname) || { head: false }).head refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head }) } } } else if (!managed || !remoteBranches.length) { const localBranches = await git.listBranches(repo) if (localBranches.length) { for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) { if (refs.has(shortname)) continue // NOTE prefer remote branches in bare repository refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head: noWorktree }) } } } return [...refs.values()] } Loading Loading @@ -1077,11 +1078,11 @@ function resolveRepositoryFromWorktree (repo) { function findWorktrees (repo, patterns) { if (!patterns.length) return new Map() const mainWorktree = patterns[0] === '.' && (patterns = patterns.slice(1)) patterns[0] === '.' && (patterns = patterns.slice(1)) && !repo.noCheckout ? getCurrentBranchName(repo).then((branch) => branch && [branch, { head: repo.dir, name: '.' }]) : Promise.resolve() if (!patterns.length) return mainWorktree.then((entry) => new Map(entry && [entry])) const worktreesDir = ospath.join(repo.dir, '.git', 'worktrees') const worktreesDir = ospath.join(repo.dir, repo.dir === repo.gitdir ? '' : '.git', 'worktrees') const patternCache = repo.cache[REF_PATTERN_CACHE_KEY] return fsp .readdir(worktreesDir) Loading
packages/content-aggregator/test/aggregate-content-test.js +40 −7 Original line number Diff line number Diff line Loading @@ -4788,16 +4788,20 @@ describe('aggregateContent()', () => { worktreeBranch = 'v1.2.x', worktreeName = worktreeBranch, checkout, bare, } = {}) => { const repoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR) const repoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR, { bare }) const repoName = 'the-component' const version = worktreeBranch.replace(/v?(\d+\.\d+)(?:\.x.*)?/, '$1') const dir = ospath.join(repoBuilder.repoBase, repoName) const linkedWorktreeRepoBuilder = new RepositoryBuilder(CONTENT_REPOS_DIR, FIXTURES_DIR) const linkedWorktreeRepoName = `the-component-${worktreeName}` const linkedWorktreeDir = ospath.join(linkedWorktreeRepoBuilder.repoBase, linkedWorktreeRepoName) const linkedWorktreeDotgit = ospath.join(linkedWorktreeDir, '.git') const linkedWorktreeGitdir = ospath.join(dir, `.git/worktrees/${worktreeName}`) const linkedWorktreeGitdir = bare === 'flat' ? ospath.join(repoBuilder.repoBase, repoName + '.git', 'worktrees', worktreeName) : ospath.join(repoBuilder.repoBase, repoName, '.git', 'worktrees', worktreeName) const worktreesDirFromWorktree = bare === 'flat' ? `../${repoName}.git/worktrees` : '.git/worktrees' const pageOnePath = 'modules/ROOT/pages/page-one.adoc' await initRepoWithFilesAndWorktree(repoBuilder, { version }, () => repoBuilder Loading @@ -4805,12 +4809,17 @@ describe('aggregateContent()', () => { .then((self) => checkout ? (checkout === true ? self.getHeadCommit() : Promise.resolve(`ref: refs/heads/${checkout}`)).then( (ref) => self.addToWorktree(`.git/worktrees/${worktreeName}/HEAD`, ref + '\n') (ref) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/HEAD`, ref + '\n') ) : self.addToWorktree( `${worktreesDirFromWorktree}/${worktreeName}/HEAD`, `ref: refs/heads/${worktreeBranch}\n` ) ) : self.addToWorktree(`.git/worktrees/${worktreeName}/HEAD`, `ref: refs/heads/${worktreeBranch}\n`) .then((self) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/commondir`, '../..\n')) .then((self) => self.addToWorktree(`${worktreesDirFromWorktree}/${worktreeName}/gitdir`, linkedWorktreeDotgit + '\n') ) .then((self) => self.addToWorktree(`.git/worktrees/${worktreeName}/commondir`, '../..\n')) .then((self) => self.addToWorktree(`.git/worktrees/${worktreeName}/gitdir`, linkedWorktreeDotgit + '\n')) .then((self) => (typeof checkout === 'string' ? self.checkoutBranch(checkout) : self)) .then((self) => self.checkoutBranch('main')) .then((self) => self.addComponentDescriptorToWorktree({ name: 'the-component', version: '2.0' })) Loading Loading @@ -5085,6 +5094,30 @@ describe('aggregateContent()', () => { ) }) it('should resolve worktree from bare repository if content source is linked worktree', async () => { const { linkedWorktreeRepoBuilder } = await initRepoWithFilesAndMultipleWorktrees({ bare: 'flat' }) playbookSpec.content.sources.push({ url: linkedWorktreeRepoBuilder.url, branches: 'HEAD' }) const aggregate = await aggregateContent(playbookSpec) expect(aggregate).to.have.lengthOf(1) expect(aggregate[0]).to.include({ name: 'the-component', version: '1.2' }) const files = aggregate[0].files const pageOne = files.find((it) => it.relative === 'modules/ROOT/pages/page-one.adoc') expect(pageOne.contents.toString()).to.include('= Page One (Linked Worktree)\n') expect(pageOne.src.origin.refname).to.equal('v1.2.x') }) it('should resolve linked worktree if content source is bare repository', async () => { const { repoBuilder } = await initRepoWithFilesAndMultipleWorktrees({ bare: 'flat' }) playbookSpec.content.sources.push({ url: repoBuilder.url, branches: 'v1.2.x', worktrees: true }) const aggregate = await aggregateContent(playbookSpec) expect(aggregate).to.have.lengthOf(1) expect(aggregate[0]).to.include({ name: 'the-component', version: '1.2' }) const files = aggregate[0].files const pageOne = files.find((it) => it.relative === 'modules/ROOT/pages/page-one.adoc') expect(pageOne.contents.toString()).to.include('= Page One (Linked Worktree)\n') expect(pageOne.src.origin.refname).to.equal('v1.2.x') }) it('should not use worktree if content source is linked worktree and worktrees is .', async () => { const { linkedWorktreeRepoBuilder } = await initRepoWithFilesAndMultipleWorktrees() playbookSpec.content.sources.push({ url: linkedWorktreeRepoBuilder.url, branches: 'v1.2.x', worktrees: '.' }) Loading
packages/test-harness/lib/repository-builder.js +3 −2 Original line number Diff line number Diff line Loading @@ -41,14 +41,15 @@ class RepositoryBuilder { this.repoPath += '.git' this.url = `${this.gitServerProtocol}//localhost:${this.gitServerPort}/${repoName}.git` } else if (this.bare) { this.url += ospath.sep + '.git' this.url += (this.bare === 'flat' ? '' : ospath.sep) + '.git' } else { this.local = ospath.join(this.repoPath, '.git') } const dir = this.repoPath const gitdir = ospath.join(dir, '.git') const gitdir = this.bare === 'flat' ? dir + '.git' : ospath.join(dir, '.git') this.repository = { cache: {}, defaultBranch: opts.branch || 'main', dir, fs, gitdir, http } await git.init(this.repository) if (this.bare === 'flat') await fsp.mkdir(dir) if (opts.empty) return this await (await this.addToWorktree('.gitignore')).addToWorktree('.gitattributes', '* text=auto eol=lf') // NOTE isomorphic-git doesn't require any commits to set up the default branch, but tests still rely on these files Loading