@antora/content-aggregator 3.2.0-alpha.9 → 3.2.0-rc.2

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.
@@ -87,35 +87,41 @@ const URL_PORT_CLEANER_RX = /^([^/]+):[0-9]+(?=\/)/
87
87
  */
88
88
  function aggregateContent (playbook) {
89
89
  const startDir = playbook.dir || '.'
90
- const { branches, editUrl, tags, sources } = playbook.content
91
- const sourceDefaults = { branches, editUrl, tags }
90
+ const { branches, editUrl, tags, sources, worktrees } = playbook.content
91
+ const sourceDefaults = { branches, editUrl, tags, worktrees }
92
92
  const { cacheDir: requestedCacheDir, fetch, quiet } = playbook.runtime
93
- return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
94
- const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
95
- const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
96
- const concurrency = {
97
- fetch: Math.max(gitConfig.fetchConcurrency || Infinity, 1),
98
- read: Math.max(gitConfig.readConcurrency || Infinity, 1),
99
- }
100
- const sourcesByUrl = sources.reduce((accum, source) => {
101
- return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
102
- }, new Map())
103
- const progress = quiet ? undefined : createProgress(sourcesByUrl.keys(), process.stdout)
104
- const refPatternCache = Object.assign(new Map(), { braces: new Map() })
105
- const fetchConfig = { always: fetch, depth: Math.max(0, gitConfig.fetchDepth ?? 1) }
106
- const loadOpts = { cacheDir, fetch: fetchConfig, gitPlugins, progress, startDir, refPatternCache }
107
- return collectFiles(sourcesByUrl, loadOpts, concurrency).then(buildAggregate, (err) => {
108
- progress?.terminate()
109
- throw err
93
+ const READABLE_STREAM_DISABLED = process.env.READABLE_STREAM === 'disable'
94
+ if (!READABLE_STREAM_DISABLED) process.env.READABLE_STREAM = 'disable' // force use of Node.js stream APIs
95
+ return ensureCacheDir(requestedCacheDir, startDir)
96
+ .then((cacheDir) => {
97
+ const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
98
+ const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
99
+ const concurrency = {
100
+ fetch: Math.max(gitConfig.fetchConcurrency || Infinity, 1),
101
+ read: Math.max(gitConfig.readConcurrency || Infinity, 1),
102
+ }
103
+ const sourcesByUrl = sources.reduce((accum, source) => {
104
+ return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
105
+ }, new Map())
106
+ const progress = quiet ? undefined : createProgress(sourcesByUrl.keys(), process.stdout)
107
+ const refPatternCache = Object.assign(new Map(), { braces: new Map() })
108
+ const fetchConfig = { always: fetch, depth: Math.max(0, gitConfig.fetchDepth ?? 1) }
109
+ const loadOpts = { cacheDir, fetch: fetchConfig, gitPlugins, progress, startDir, refPatternCache }
110
+ return collectFiles(sourcesByUrl, loadOpts, concurrency).then(buildAggregate, (err) => {
111
+ progress?.terminate()
112
+ throw err
113
+ })
110
114
  })
111
- })
115
+ .finally(() => READABLE_STREAM_DISABLED || delete process.env.READABLE_STREAM)
112
116
  }
113
117
 
114
118
  async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls = []) {
115
119
  const loadTasks = [...sourcesByUrl.entries()].map(([url, sources]) => {
116
120
  const loadOptsForUrl = Object.assign({}, loadOpts)
117
- if (loadOpts.fetch.always && fetchedUrls.length && fetchedUrls.includes(url)) loadOptsForUrl.fetch.always = false
121
+ if (loadOpts.fetch.always && fetchedUrls.length && ~fetchedUrls.indexOf(url)) loadOptsForUrl.fetch.always = false
118
122
  if (tagsSpecified(sources)) loadOptsForUrl.fetch.tags = true
123
+ const commits = commitsRequested(sources)
124
+ if (commits) loadOptsForUrl.fetch.commits = commits
119
125
  return loadRepository.bind(null, url, loadOptsForUrl, { url, sources })
120
126
  })
121
127
  return gracefulPromiseAllWithLimit(loadTasks, concurrency.fetch).then(([results, rejections]) => {
@@ -140,9 +146,10 @@ async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls =
140
146
  })
141
147
  }
142
148
 
143
- function buildAggregate (componentVersionBuckets) {
144
- const entries = Object.assign(new Map(), { accum: [] })
145
- for (const batchesForOrigin of componentVersionBuckets) {
149
+ function buildAggregate (componentVersionBatches) {
150
+ const contentAggregate = []
151
+ const entries = new Map()
152
+ for (const batchesForOrigin of componentVersionBatches) {
146
153
  for (const batch of batchesForOrigin) {
147
154
  let key, entry
148
155
  if ((entry = entries.get((key = batch.version + '@' + batch.name)))) {
@@ -151,11 +158,12 @@ function buildAggregate (componentVersionBuckets) {
151
158
  ;(batch.origins = entry.origins).push(origins[0])
152
159
  Object.assign(entry, batch)
153
160
  } else {
154
- entries.set(key, batch).accum.push(batch)
161
+ entries.set(key, batch)
162
+ contentAggregate.push(batch)
155
163
  }
156
164
  }
157
165
  }
158
- return entries.accum
166
+ return contentAggregate
159
167
  }
160
168
 
161
169
  async function loadRepository (url, opts, result = {}) {
@@ -177,6 +185,7 @@ async function loadRepository (url, opts, result = {}) {
177
185
  const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'fetch')
178
186
  await git
179
187
  .fetch(fetchOpts)
188
+ .then(() => ensureOids(fetchOpts))
180
189
  .then(() => {
181
190
  authStatus = identifyAuthStatus(credentialManager, credentials, url)
182
191
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
@@ -192,12 +201,13 @@ async function loadRepository (url, opts, result = {}) {
192
201
  authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
193
202
  }
194
203
  } catch (gitErr) {
195
- await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
204
+ await fsp.rm(dir, { recursive: true, force: true })
196
205
  if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
197
206
  const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'clone')
198
207
  await git
199
208
  .clone(fetchOpts)
200
209
  .then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
210
+ .then(() => ensureOids(fetchOpts))
201
211
  .then(() => {
202
212
  authStatus = identifyAuthStatus(credentialManager, credentials, url)
203
213
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
@@ -216,6 +226,7 @@ async function loadRepository (url, opts, result = {}) {
216
226
  if (dotgitStat.isDirectory()) {
217
227
  repo = { cache, dir, fs, gitdir: dotgit }
218
228
  } else if (dotgitStat.isFile()) {
229
+ // NOTE isomorphic-git will discover the gitdir, but we must do it eagerly to process worktree patterns correctly
219
230
  repo = await resolveRepositoryFromWorktree({ cache, dir, fs, gitdir: dotgit })
220
231
  } else {
221
232
  repo = { cache, dir, fs, gitdir: dir, noCheckout: true }
@@ -245,7 +256,7 @@ function extractCredentials (url) {
245
256
  const credentials = username ? { username, password: password || '' } : {}
246
257
  return { displayUrl, url, credentials }
247
258
  }
248
- if (url.startsWith('git@')) return { displayUrl: url, url: 'https://' + url.substr(4).replace(':', '/') }
259
+ if (url.startsWith('git@')) return { displayUrl: url, url: 'https://' + url.substring(4).replace(':', '/') }
249
260
  return { displayUrl: url, url }
250
261
  }
251
262
 
@@ -279,12 +290,12 @@ async function selectStartPathsForRepository (repo, sources) {
279
290
  }
280
291
  }
281
292
  } else {
282
- const { url, branches, tags } = source
293
+ const { url, branches, tags, commits } = source
283
294
  const startPathInfo =
284
295
  'startPaths' in source
285
296
  ? { 'start paths': source.startPaths || undefined }
286
297
  : { 'start path': source.startPath || undefined }
287
- const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
298
+ const sourceInfo = yaml.dump({ url, branches, tags, commits, ...startPathInfo }, { flowLevel: 1 }).trimRight()
288
299
  logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
289
300
  }
290
301
  }
@@ -293,11 +304,12 @@ async function selectStartPathsForRepository (repo, sources) {
293
304
 
294
305
  // QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
295
306
  async function selectReferences (source, repo, remote) {
296
- let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
307
+ let { branches: branchPatterns, tags: tagPatterns, commits, worktrees: worktreePatterns } = source
297
308
  const managed = 'url' in repo
298
309
  const isBare = managed || repo.noCheckout
299
310
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
300
311
  const noWorktree = managed ? undefined : false
312
+ const isLinkedWorktree = repo.worktree?.name
301
313
  const refs = new Map()
302
314
  if (
303
315
  tagPatterns &&
@@ -308,11 +320,20 @@ async function selectReferences (source, repo, remote) {
308
320
  const tags = await git.listTags(repo)
309
321
  if (tags.length) {
310
322
  for (const shortname of filterRefs(tags, tagPatterns, patternCache)) {
311
- // NOTE tags are stored using symbol keys to distinguish them from branches
312
- refs.set(Symbol(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
323
+ // NOTE tags are stored using Buffer keys to distinguish them from commits and branches
324
+ refs.set(Buffer.from(shortname), { shortname, fullname: 'tags/' + shortname, type: 'tag', head: noWorktree })
313
325
  }
314
326
  }
315
327
  }
328
+ if (
329
+ commits &&
330
+ (commits = Array.isArray(commits) ? commits.map((commit) => String(commit)) : commits.split(CSV_RX)).length
331
+ ) {
332
+ for (const oid of commits) {
333
+ // NOTE commits are stored using Symbol keys to distinguish them from tags and branches
334
+ refs.set(Symbol(oid), { oid, shortname: oid, fullname: 'commits/' + oid, type: 'commit' })
335
+ }
336
+ }
316
337
  if (
317
338
  !branchPatterns ||
318
339
  !(branchPatterns = Array.isArray(branchPatterns)
@@ -321,38 +342,54 @@ async function selectReferences (source, repo, remote) {
321
342
  ) {
322
343
  return [...refs.values()]
323
344
  }
324
- const worktreeName = repo.worktreeName // possibly switch to worktree property ({ name, dir}) in future
325
- if (worktreeName) branchPatterns = branchPatterns.map((it) => (it === 'HEAD' ? 'HEAD@' + worktreeName : it))
326
- if (worktreePatterns) {
345
+ let useWorktree = false
346
+ if (!managed && (useWorktree = {})) {
327
347
  if (worktreePatterns === '.') {
328
- worktreePatterns = ['.']
348
+ isLinkedWorktree ? (useWorktree.linked = isLinkedWorktree) : isBare || (useWorktree.main = true)
349
+ worktreePatterns = []
350
+ } else if (!worktreePatterns) {
351
+ worktreePatterns = []
329
352
  } else if (worktreePatterns === true) {
330
- worktreePatterns = ['.', '*']
353
+ if (!isBare) useWorktree.main = true
354
+ // NOTE if we don't start at a linked worktree, linked worktree cannot be current worktree
355
+ if (isLinkedWorktree) useWorktree.linked = isLinkedWorktree
356
+ worktreePatterns = ['*']
357
+ } else if (worktreePatterns === '/.') {
358
+ if (!isBare) useWorktree.main = true
359
+ worktreePatterns = []
331
360
  } else {
332
- worktreePatterns = Array.isArray(worktreePatterns)
333
- ? worktreePatterns.map((pattern) => String(pattern))
334
- : splitRefPatterns(String(worktreePatterns))
335
- if (worktreeName) worktreePatterns = worktreePatterns.map((it) => (it === '@' ? worktreeName : it))
361
+ worktreePatterns = (
362
+ Array.isArray(worktreePatterns)
363
+ ? worktreePatterns.map((pattern) => String(pattern))
364
+ : splitRefPatterns(String(worktreePatterns))
365
+ ).reduce((accum, it) => {
366
+ if (it === '/.') return (isBare || (useWorktree.main = true)) && accum
367
+ if (it === '.') {
368
+ isLinkedWorktree ? (useWorktree.linked = isLinkedWorktree) : isBare || (useWorktree.main = true)
369
+ } else {
370
+ accum.push(it)
371
+ }
372
+ return accum
373
+ }, [])
336
374
  }
337
- } else {
338
- worktreePatterns = worktreePatterns === undefined ? [worktreeName || '.'] : []
375
+ if (!(useWorktree.main || useWorktree.linked)) useWorktree = false
339
376
  }
340
377
  let currentBranch
341
- if (branchPatterns.length === 1 && (branchPatterns[0] === 'HEAD' || branchPatterns[0] === '.')) {
342
- if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
343
- branchPatterns = [currentBranch]
344
- } else if (isBare) {
345
- return [...refs.values()]
346
- } else {
347
- // NOTE current branch is undefined when HEAD is detached
348
- const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
349
- refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
350
- return [...refs.values()]
351
- }
352
- } else {
378
+ if (!isLinkedWorktree) {
353
379
  let headBranchIdx
354
- // NOTE we can assume at least two entries if HEAD or . are present
355
- if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
380
+ if (branchPatterns.length === 1 && (branchPatterns[0] === 'HEAD' || branchPatterns[0] === '.')) {
381
+ if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
382
+ branchPatterns = [currentBranch]
383
+ } else if (isBare) {
384
+ return [...refs.values()]
385
+ } else {
386
+ // NOTE current branch is undefined when HEAD is detached
387
+ const head = useWorktree.main ? repo.dir : noWorktree
388
+ refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
389
+ return [...refs.values()]
390
+ }
391
+ } else if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
392
+ // NOTE we can assume at least two entries if HEAD or . are present
356
393
  if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
357
394
  if (~branchPatterns.indexOf(currentBranch)) {
358
395
  branchPatterns.splice(headBranchIdx, 1)
@@ -362,11 +399,7 @@ async function selectReferences (source, repo, remote) {
362
399
  } else if (isBare) {
363
400
  branchPatterns.splice(headBranchIdx, 1)
364
401
  } else {
365
- let head = noWorktree
366
- if (worktreePatterns[0] === '.') {
367
- worktreePatterns = worktreePatterns.slice(1)
368
- head = repo.dir
369
- }
402
+ const head = useWorktree.main ? repo.dir : noWorktree
370
403
  // NOTE current branch is undefined when HEAD is detached
371
404
  refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
372
405
  branchPatterns.splice(headBranchIdx, 1)
@@ -389,23 +422,37 @@ async function selectReferences (source, repo, remote) {
389
422
  if (currentBranch != null) return [currentBranch]
390
423
  return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : []))
391
424
  })
392
- const worktrees = await findWorktrees(repo, worktreePatterns)
393
- let onMatch
394
- if ((worktreePatterns.join('') || '.') !== '.') {
395
- const symbolicNames = new Map()
396
- worktrees.forEach(({ name, symbolicName = 'HEAD@' + name }, shortname) => {
397
- localBranches.push(symbolicName)
398
- symbolicNames.set(symbolicName, shortname)
425
+ let onMatch, worktrees
426
+ if (
427
+ (useWorktree || worktreePatterns.length) &&
428
+ (worktrees = await findWorktrees(repo, worktreePatterns, useWorktree)).size
429
+ ) {
430
+ const headNames = new Map()
431
+ worktrees.forEach(({ name, symbolicNames }, shortname) => {
432
+ if (name) {
433
+ const headName = 'HEAD@' + name
434
+ localBranches.push(headName)
435
+ headNames.set(headName, shortname)
436
+ }
437
+ if (symbolicNames) {
438
+ for (const symbolicName of symbolicNames) {
439
+ const symbolicHeadName = symbolicName === 'HEAD' ? symbolicName : 'HEAD@' + symbolicName
440
+ localBranches.push(symbolicHeadName)
441
+ headNames.set(symbolicHeadName, shortname)
442
+ }
443
+ }
399
444
  })
400
445
  onMatch = (candidate, { pattern }) => {
401
- const shortname = symbolicNames.get(candidate)
402
- return shortname ? (pattern.startsWith('HEAD@') ? shortname : undefined) : candidate
446
+ const shortname = headNames.get(candidate)
447
+ if (!shortname) return candidate
448
+ if (pattern === 'HEAD' || pattern.startsWith('HEAD@')) return shortname
403
449
  }
404
450
  }
405
451
  if (localBranches.length) {
406
452
  const preferRemote = isBare && remoteBranches.length > 0
407
453
  for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) {
408
454
  if (preferRemote && refs.has(shortname)) continue
455
+ worktrees ??= await findWorktrees(repo, worktreePatterns, useWorktree)
409
456
  const head = (worktrees.get(shortname) || { head: false }).head
410
457
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
411
458
  }
@@ -432,9 +479,11 @@ async function selectStartPaths (source, repo, ref) {
432
479
  const displayUrl = url || repo.dir
433
480
  const worktreePath = ref.head
434
481
  if (!worktreePath) {
435
- ref.oid = await git.resolveRef(
436
- Object.assign(ref.detached ? { ref: 'HEAD', depth: 1 } : { ref: 'refs/' + ref.fullname }, repo)
437
- )
482
+ ref.oid = ref.oid
483
+ ? await git.expandOid(Object.assign({ oid: ref.oid }, repo)).catch(() => ref.oid)
484
+ : await git.resolveRef(
485
+ Object.assign(ref.detached ? { ref: 'HEAD', depth: 1 } : { ref: 'refs/' + ref.fullname }, repo)
486
+ )
438
487
  }
439
488
  if ('startPaths' in source) {
440
489
  let startPaths
@@ -469,7 +518,9 @@ function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl,
469
518
  return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
470
519
  .then((files) => {
471
520
  const batch = deepClone((origin.descriptor = loadComponentDescriptor(files, ref, version)))
472
- if ('nav' in batch && Array.isArray(batch.nav)) batch.nav.origin = origin
521
+ if ('nav' in batch && Array.isArray(batch.nav)) {
522
+ Object.defineProperty(batch.nav, 'origin', { configurable: true, value: origin, writable: true })
523
+ }
473
524
  batch.files = files.map((file) => assignFileProperties(file, origin))
474
525
  batch.origins = [origin]
475
526
  return batch
@@ -656,10 +707,10 @@ function readGitSymlink (repo, root, parent, { oid, path: name }, following) {
656
707
  let target
657
708
  let targetParent = root
658
709
  if (dirname) {
659
- if (!(target = path.join('/', dirname, symlink).substr(1)) || target === dirname) {
710
+ if (!(target = path.join('/', dirname, symlink).substring(1)) || target === dirname) {
660
711
  target = '.'
661
712
  } else if (target.startsWith(dirname + '/')) {
662
- target = target.substr(dirname.length + 1) // join doesn't remove trailing separator
713
+ target = target.substring(dirname.length + 1) // join doesn't remove trailing separator
663
714
  targetParent = parent
664
715
  }
665
716
  } else {
@@ -754,7 +805,7 @@ function loadComponentDescriptor (files, ref, version) {
754
805
  !Object.entries(version).some(([pattern, replacement]) => {
755
806
  const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + (replacement ?? ''))
756
807
  if (result === refname) return false // no match
757
- matched = result.substr(1)
808
+ matched = result.substring(1)
758
809
  return true
759
810
  })
760
811
  ) {
@@ -801,6 +852,7 @@ function buildFetchOptions (repo, progress, displayUrl, credentialsFromUrl, gitP
801
852
  } else if (!fetch.tags) {
802
853
  opts.noTags = true
803
854
  }
855
+ if (fetch.commits) opts.oids = fetch.commits
804
856
  return opts
805
857
  }
806
858
 
@@ -835,14 +887,17 @@ function createProgressListener (progress, progressLabel, operation) {
835
887
  // NOTE leave room for indeterminate progress at end of bar; this isn't strictly needed for a bare clone
836
888
  progressBar.scaleFactor = Math.max(0, (ticks - 1) / ticks)
837
889
  progressBar.tick(0)
838
- return Object.assign(onGitProgress.bind(progressBar), { finish: onGitComplete.bind(progressBar) })
890
+ return Object.assign(onGitProgress.bind(progressBar), {
891
+ finish: onGitComplete.bind(progressBar),
892
+ reset: () => progressBar.update(0),
893
+ })
839
894
  }
840
895
 
841
896
  function formatProgressBar (label, maxLabelWidth, operation) {
842
897
  const paddingSize = maxLabelWidth - label.length
843
898
  let padding = ''
844
899
  if (paddingSize < 0) {
845
- label = '...' + label.substr(-paddingSize + 3)
900
+ label = '...' + label.substring(-paddingSize + 3)
846
901
  } else if (paddingSize) {
847
902
  padding = ' '.repeat(paddingSize)
848
903
  }
@@ -930,9 +985,9 @@ function resolveRemoteUrl (repo, remoteName) {
930
985
  if (url.startsWith('https://') || url.startsWith('http://')) {
931
986
  return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
932
987
  }
933
- if (url.startsWith('git@')) return 'https://' + url.substr(4).replace(':', '/')
988
+ if (url.startsWith('git@')) return 'https://' + url.substring(4).replace(':', '/')
934
989
  if (url.startsWith('ssh://')) {
935
- return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
990
+ return 'https://' + url.substring(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
936
991
  }
937
992
  })
938
993
  }
@@ -951,6 +1006,16 @@ function tagsSpecified (sources) {
951
1006
  return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
952
1007
  }
953
1008
 
1009
+ function commitsRequested (sources) {
1010
+ if (!sources.some(({ commits }) => commits && (Array.isArray(commits) ? commits.length : true))) return
1011
+ const result = new Set()
1012
+ for (const { commits } of sources) {
1013
+ if (!commits) continue
1014
+ for (const commit of Array.isArray(commits) ? commits : commits.split(CSV_RX)) result.add(String(commit))
1015
+ }
1016
+ return [...result]
1017
+ }
1018
+
954
1019
  function loadGitPlugins (gitConfig, networkConfig, startDir) {
955
1020
  const plugins = new Map((git.cores || git.default.cores || new Map()).get(GIT_CORE))
956
1021
  for (const [name, request] of Object.entries(gitConfig.plugins || {})) {
@@ -1028,7 +1093,7 @@ function transformGitCloneError (err, displayUrl, authRequested) {
1028
1093
  }
1029
1094
 
1030
1095
  function splitRefPatterns (str) {
1031
- return ~str.indexOf('{') ? str.split(VENTILATED_CSV_RX) : str.split(CSV_RX)
1096
+ return str.split(~str.indexOf('{') ? VENTILATED_CSV_RX : CSV_RX)
1032
1097
  }
1033
1098
 
1034
1099
  function camelCaseKeys (o, stopPaths = [], p = '') {
@@ -1054,45 +1119,57 @@ function coerceToString (value) {
1054
1119
  function resolveRepositoryFromWorktree (repo) {
1055
1120
  return fsp
1056
1121
  .readFile(repo.gitdir, 'utf8')
1057
- .then((contents) => contents.substr(8).trimEnd())
1122
+ .then((contents) => contents.substring(8).trimEnd())
1058
1123
  .then((worktreeGitdir) =>
1059
1124
  fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
1060
1125
  (contents) => {
1061
1126
  const gitdir = ospath.join(worktreeGitdir, contents.trimEnd())
1062
1127
  const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
1063
- return Object.assign(repo, { dir, gitdir, worktreeName: ospath.basename(worktreeGitdir) })
1128
+ const name = ospath.basename(worktreeGitdir)
1129
+ return Object.assign(repo, { dir, gitdir, worktree: { gitdir: worktreeGitdir, name } })
1064
1130
  },
1065
1131
  () => repo
1066
1132
  )
1067
1133
  )
1068
1134
  }
1069
1135
 
1070
- function findWorktrees (repo, patterns) {
1071
- if (!patterns.length) return new Map()
1072
- const mainWorktree =
1073
- patterns[0] === '.' && (patterns = patterns.slice(1)) && !repo.noCheckout
1074
- ? getCurrentBranchName(repo).then((branch) => branch && [branch, { head: repo.dir, name: '.' }])
1075
- : Promise.resolve()
1076
- if (!patterns.length) return mainWorktree.then((entry) => new Map(entry && [entry]))
1136
+ function findWorktrees (repo, patterns, useWorktree) {
1137
+ const useLinkedWorktree = !!useWorktree.linked
1138
+ const mainWorktree = useWorktree.main
1139
+ ? getCurrentBranchName(repo).then((branch) => {
1140
+ if (!branch) return
1141
+ return [branch, { head: repo.dir, name: undefined, symbolicNames: useLinkedWorktree ? ['/.'] : ['/.', '.'] }]
1142
+ })
1143
+ : Promise.resolve()
1144
+ if (!(useLinkedWorktree || patterns.length)) return mainWorktree.then((entry) => new Map(entry && [entry]))
1077
1145
  const worktreesDir = ospath.join(repo.dir, repo.dir === repo.gitdir ? '' : '.git', 'worktrees')
1078
1146
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
1079
- return fsp
1080
- .readdir(worktreesDir)
1081
- .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1147
+ const scanWorktrees = patterns.length
1148
+ ? fsp
1149
+ .readdir(worktreesDir)
1150
+ .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1151
+ .then((worktreeNames) => {
1152
+ if (useLinkedWorktree && !~worktreeNames.indexOf(useWorktree.linked)) worktreeNames.push(useWorktree.linked)
1153
+ return worktreeNames
1154
+ })
1155
+ : Promise.resolve(useLinkedWorktree ? [useWorktree.linked] : [])
1156
+ return scanWorktrees
1082
1157
  .then((worktreeNames) =>
1083
1158
  Promise.all(
1084
- worktreeNames.map((worktreeName) => {
1085
- const gitdir = ospath.resolve(worktreesDir, worktreeName)
1159
+ worktreeNames.map((name) => {
1160
+ const symbolicNames = useLinkedWorktree && name === useWorktree.linked ? ['.', 'HEAD'] : undefined
1161
+ const gitdir = ospath.resolve(worktreesDir, name)
1086
1162
  // NOTE branch name defaults to worktree name if HEAD is detached
1087
- return getCurrentBranchName(Object.assign({}, repo, { gitdir })).then((branch = worktreeName) =>
1163
+ return getCurrentBranchName(Object.assign({}, repo, { gitdir })).then((branch = name) =>
1088
1164
  fsp
1089
1165
  .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1090
- .then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name: worktreeName }])
1166
+ .then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name, symbolicNames }])
1091
1167
  )
1092
1168
  })
1093
1169
  )
1094
1170
  )
1095
- .then((entries) => mainWorktree.then((main) => (main ? new Map(entries).set(main[0], main[1]) : new Map(entries))))
1171
+ .then((entries) => new Map(entries))
1172
+ .then((worktrees) => mainWorktree.then((result) => (result ? worktrees.set(result[0], result[1]) : worktrees)))
1096
1173
  }
1097
1174
 
1098
1175
  async function gracefulPromiseAllWithLimit (tasks, limit = Infinity) {
@@ -1128,4 +1205,27 @@ async function promiseAllWithLimit (tasks, limit = Infinity) {
1128
1205
  return Promise.all(started)
1129
1206
  }
1130
1207
 
1208
+ async function ensureOids (opts) {
1209
+ if (!opts.oids) return
1210
+ let prevShallowCommits = await getShallowCommits(opts)
1211
+ if (prevShallowCommits == null) return
1212
+ let oids = opts.oids.slice()
1213
+ const deepenOpts = Object.assign({}, opts, { relative: true })
1214
+ const format = 'deflated'
1215
+ while (oids.length) {
1216
+ deepenOpts.onProgress?.reset()
1217
+ await git.fetch(deepenOpts)
1218
+ const shallowCommits = await getShallowCommits(opts)
1219
+ if (shallowCommits == null || shallowCommits === prevShallowCommits) break
1220
+ prevShallowCommits = shallowCommits
1221
+ oids = await Promise.all(
1222
+ oids.map((oid) => git.readObject(Object.assign({ oid, format }, opts)).then(invariably.void, () => oid))
1223
+ ).then((results) => results.filter((it) => it))
1224
+ }
1225
+ }
1226
+
1227
+ function getShallowCommits ({ gitdir }) {
1228
+ return fsp.readFile(ospath.join(gitdir, 'shallow'), 'utf8').catch(invariably.void)
1229
+ }
1230
+
1131
1231
  module.exports = aggregateContent
@@ -42,7 +42,7 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
42
42
  } else if (editUrl) {
43
43
  const vars = {
44
44
  path: () => (startPath ? path.join(startPath, '%s') : '%s'),
45
- ref: () => 'refs/' + (reftype === 'branch' ? 'heads' : reftype) + '/' + refname,
45
+ ref: () => 'refs/' + (reftype === 'branch' ? 'head' : reftype) + 's/' + refname,
46
46
  refhash: () => refhash,
47
47
  reftype: () => reftype,
48
48
  refname: () => refname,
@@ -6,7 +6,7 @@ function compileRx (pattern, opts) {
6
6
  if (pattern === '*' || pattern === '**') return MATCH_ALL_RX
7
7
  const rx =
8
8
  pattern.charAt() === '!' // we handle negate ourselves
9
- ? Object.defineProperty(makeMatcherRx((pattern = pattern.substr(1)), opts), 'negated', { value: true })
9
+ ? Object.defineProperty(makeMatcherRx((pattern = pattern.substring(1)), opts), 'negated', { value: true })
10
10
  : makeMatcherRx(pattern, opts)
11
11
  return Object.defineProperty(rx, 'pattern', { value: pattern })
12
12
  }
@@ -39,6 +39,7 @@ function createMatcher (patterns, cache = Object.assign(new Map(), { braces: new
39
39
  }
40
40
 
41
41
  function filterRefs (candidates, patterns, cache, onMatch) {
42
+ if (!(patterns = patterns.filter(compact)).length) return []
42
43
  const match = createMatcher(patterns, cache)
43
44
  return candidates.reduce((accum, candidate) => {
44
45
  if ((candidate = match(candidate, onMatch))) accum.push(candidate)
@@ -46,4 +47,8 @@ function filterRefs (candidates, patterns, cache, onMatch) {
46
47
  }, [])
47
48
  }
48
49
 
50
+ function compact (str) {
51
+ return str !== ''
52
+ }
53
+
49
54
  module.exports = filterRefs
@@ -14,7 +14,7 @@ function resolvePathGlobs (base, patterns, listDirents, retrievePath, tree = { p
14
14
  if (pattern.charAt() === '!') {
15
15
  return paths.then((resolvedPaths) => {
16
16
  if (resolvedPaths.length) {
17
- const rx = makeMatcherRx(pattern.substr(1), MATCHER_OPTS)
17
+ const rx = makeMatcherRx(pattern.substring(1), MATCHER_OPTS)
18
18
  return resolvedPaths.filter((it) => !rx.test(it))
19
19
  }
20
20
  return resolvedPaths
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.2.0-alpha.9",
3
+ "version": "3.2.0-rc.2",
4
4
  "description": "Fetches and aggregates content from distributed sources for use in an Antora documentation pipeline.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "OpenDevise Inc. (https://opendevise.com)",
@@ -33,14 +33,14 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@antora/expand-path-helper": "~3.0",
36
- "@antora/logger": "3.2.0-alpha.9",
36
+ "@antora/logger": "3.2.0-rc.2",
37
37
  "@antora/user-require-helper": "~3.0",
38
38
  "braces": "~3.0",
39
39
  "cache-directory": "~2.0",
40
40
  "fast-glob": "~3.3",
41
41
  "hpagent": "~1.2",
42
- "isomorphic-git": "~1.25",
43
- "js-yaml": "~4.1",
42
+ "isomorphic-git": "~1.38",
43
+ "js-yaml": "~4.2",
44
44
  "multi-progress": "~4.0",
45
45
  "picomatch": "~4.0",
46
46
  "progress": "~2.0",
@@ -49,7 +49,7 @@
49
49
  "vinyl": "~3.0"
50
50
  },
51
51
  "engines": {
52
- "node": ">=18.0.0"
52
+ "node": ">=20.0.0"
53
53
  },
54
54
  "files": [
55
55
  "lib/"
@@ -65,7 +65,7 @@
65
65
  "web publishing"
66
66
  ],
67
67
  "scripts": {
68
- "test": "_mocha",
68
+ "test": "node --test",
69
69
  "prepublishOnly": "npx -y downdoc@latest --prepublish",
70
70
  "postpublish": "npx -y downdoc@latest --postpublish"
71
71
  }