@antora/content-aggregator 3.1.2 → 3.2.0-alpha.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.
package/README.md CHANGED
@@ -9,4 +9,4 @@ Its site generator aggregates documents from versioned content repositories and
9
9
 
10
10
  Copyright (C) 2017-present [OpenDevise Inc.](https://opendevise.com) and the [Antora Project](https://antora.org).
11
11
 
12
- Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
12
+ Use of this software is granted under the terms of the [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) (MPL-2.0).
@@ -188,8 +188,7 @@ async function loadRepository (url, opts) {
188
188
  await git
189
189
  .fetch(fetchOpts)
190
190
  .then(() => {
191
- const credentialManager = gitPlugins.credentialManager
192
- authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
191
+ authStatus = identifyAuthStatus(gitPlugins.credentialManager, credentials, url)
193
192
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
194
193
  })
195
194
  .catch((fetchErr) => {
@@ -210,8 +209,7 @@ async function loadRepository (url, opts) {
210
209
  .clone(fetchOpts)
211
210
  .then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
212
211
  .then(() => {
213
- const credentialManager = gitPlugins.credentialManager
214
- authStatus = credentials ? 'auth-embedded' : credentialManager.status({ url }) ? 'auth-required' : undefined
212
+ authStatus = identifyAuthStatus(gitPlugins.credentialManager, credentials, url)
215
213
  return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
216
214
  })
217
215
  .catch((cloneErr) => {
@@ -223,8 +221,15 @@ async function loadRepository (url, opts) {
223
221
  .then(() => fetchOpts.onProgress && fetchOpts.onProgress.finish())
224
222
  }
225
223
  } else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
226
- const gitdir = ospath.join(dir, '.git')
227
- repo = (await isDirectory(gitdir)) ? { cache, dir, fs, gitdir } : { cache, dir, fs, gitdir: dir, noCheckout: true }
224
+ const dotgit = ospath.join(dir, '.git')
225
+ const dotgitStat = await fsp.stat(dotgit).catch(() => ({ isFile: invariably.false, isDirectory: invariably.false }))
226
+ if (dotgitStat.isDirectory()) {
227
+ repo = { cache, dir, fs, gitdir: dotgit }
228
+ } else if (dotgitStat.isFile()) {
229
+ repo = await resolveRepositoryFromWorktree({ cache, dir, fs, gitdir: dotgit })
230
+ } else {
231
+ repo = { cache, dir, fs, gitdir: dir, noCheckout: true }
232
+ }
228
233
  try {
229
234
  await git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo))
230
235
  } catch {
@@ -273,7 +278,7 @@ async function collectFilesFromSource (source, repo, remoteName, authStatus) {
273
278
 
274
279
  // QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
275
280
  async function selectReferences (source, repo, remote) {
276
- let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns = '.' } = source
281
+ let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
277
282
  const isBare = repo.noCheckout
278
283
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
279
284
  const noWorktree = repo.url ? undefined : false
@@ -292,7 +297,16 @@ async function selectReferences (source, repo, remote) {
292
297
  }
293
298
  }
294
299
  }
295
- if (!branchPatterns) return [...refs.values()]
300
+ if (
301
+ !branchPatterns ||
302
+ !(branchPatterns = Array.isArray(branchPatterns)
303
+ ? branchPatterns.map((pattern) => String(pattern))
304
+ : splitRefPatterns(String(branchPatterns))).length
305
+ ) {
306
+ return [...refs.values()]
307
+ }
308
+ const worktreeName = repo.worktreeName // possibly switch to worktree property ({ name, dir}) in future
309
+ if (worktreeName) branchPatterns = branchPatterns.map((it) => (it === 'HEAD' ? 'HEAD@' + worktreeName : it))
296
310
  if (worktreePatterns) {
297
311
  if (worktreePatterns === '.') {
298
312
  worktreePatterns = ['.']
@@ -302,12 +316,12 @@ async function selectReferences (source, repo, remote) {
302
316
  worktreePatterns = Array.isArray(worktreePatterns)
303
317
  ? worktreePatterns.map((pattern) => String(pattern))
304
318
  : splitRefPatterns(String(worktreePatterns))
319
+ if (worktreeName) worktreePatterns = worktreePatterns.map((it) => (it === '@' ? worktreeName : it))
305
320
  }
306
321
  } else {
307
- worktreePatterns = []
322
+ worktreePatterns = worktreePatterns === undefined ? [worktreeName || '.'] : []
308
323
  }
309
- const branchPatternsString = String(branchPatterns)
310
- if (branchPatternsString === 'HEAD' || branchPatternsString === '.') {
324
+ if (branchPatterns.length === 1 && (branchPatterns[0] === 'HEAD' || branchPatterns[0] === '.')) {
311
325
  const currentBranch = await getCurrentBranchName(repo, remote)
312
326
  if (currentBranch) {
313
327
  branchPatterns = [currentBranch]
@@ -319,11 +333,7 @@ async function selectReferences (source, repo, remote) {
319
333
  refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
320
334
  return [...refs.values()]
321
335
  }
322
- } else if (
323
- (branchPatterns = Array.isArray(branchPatterns)
324
- ? branchPatterns.map((pattern) => String(pattern))
325
- : splitRefPatterns(branchPatternsString)).length
326
- ) {
336
+ } else {
327
337
  let headBranchIdx
328
338
  // NOTE we can assume at least two entries if HEAD or . are present
329
339
  if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
@@ -348,8 +358,6 @@ async function selectReferences (source, repo, remote) {
348
358
  branchPatterns.splice(headBranchIdx, 1)
349
359
  }
350
360
  }
351
- } else {
352
- return [...refs.values()]
353
361
  }
354
362
  // NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
355
363
  const remoteBranches = (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
@@ -364,8 +372,20 @@ async function selectReferences (source, repo, remote) {
364
372
  const localBranches = await git.listBranches(repo)
365
373
  if (localBranches.length) {
366
374
  const worktrees = await findWorktrees(repo, worktreePatterns)
367
- for (const shortname of filterRefs(localBranches, branchPatterns, patternCache)) {
368
- const head = worktrees.get(shortname) || noWorktree
375
+ let onMatch
376
+ if ((worktreePatterns.join('') || '.') !== '.') {
377
+ const symbolicNames = new Map()
378
+ worktrees.forEach(({ name, symbolicName = 'HEAD@' + name }, shortname) => {
379
+ localBranches.push(symbolicName)
380
+ symbolicNames.set(symbolicName, shortname)
381
+ })
382
+ onMatch = (candidate, { pattern }) => {
383
+ const shortname = symbolicNames.get(candidate)
384
+ return shortname ? (pattern.startsWith('HEAD@') ? shortname : undefined) : candidate
385
+ }
386
+ }
387
+ for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) {
388
+ const head = (worktrees.get(shortname) || { head: noWorktree }).head
369
389
  refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
370
390
  }
371
391
  }
@@ -384,15 +404,13 @@ async function selectReferences (source, repo, remote) {
384
404
  * Returns the current branch name unless the HEAD is detached.
385
405
  */
386
406
  function getCurrentBranchName (repo, remote) {
387
- let refPromise
388
- if (repo.noCheckout) {
389
- refPromise = git
390
- .resolveRef(Object.assign({ ref: 'refs/remotes/' + remote + '/HEAD', depth: 2 }, repo))
391
- .catch(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo)))
392
- } else {
393
- refPromise = git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo))
394
- }
395
- return refPromise.then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
407
+ return (
408
+ repo.noCheckout && remote
409
+ ? git
410
+ .resolveRef(Object.assign({ ref: 'refs/remotes/' + remote + '/HEAD', depth: 2 }, repo))
411
+ .catch(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo)))
412
+ : git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo))
413
+ ).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
396
414
  }
397
415
 
398
416
  async function collectFilesFromReference (source, repo, remoteName, authStatus, ref, originUrl) {
@@ -826,9 +844,12 @@ function onGitComplete (err) {
826
844
 
827
845
  function resolveCredentials (credentialsFromUrlHolder, url, auth) {
828
846
  const credentialsFromUrl = credentialsFromUrlHolder.get() || {}
829
- credentialsFromUrlHolder.clear()
830
847
  if ('Authorization' in auth.headers) {
831
- if (!('username' in credentialsFromUrl)) return this.rejected({ url, auth })
848
+ if ('username' in credentialsFromUrl) {
849
+ credentialsFromUrlHolder.clear()
850
+ } else {
851
+ return this.rejected({ url, auth })
852
+ }
832
853
  } else if ('username' in credentialsFromUrl) {
833
854
  return credentialsFromUrl
834
855
  } else {
@@ -841,6 +862,15 @@ function resolveCredentials (credentialsFromUrlHolder, url, auth) {
841
862
  )
842
863
  }
843
864
 
865
+ function identifyAuthStatus (credentialManager, credentials, url) {
866
+ const status = credentialManager.status({ url })
867
+ if (credentials) {
868
+ return typeof status === 'string' && status.startsWith('requested,') ? 'auth-required' : 'auth-embedded'
869
+ } else if (status != null) {
870
+ return 'auth-required'
871
+ }
872
+ }
873
+
844
874
  /**
845
875
  * Generates a safe, unique folder name for a git URL.
846
876
  *
@@ -1008,39 +1038,52 @@ function coerceToString (value) {
1008
1038
  return value == null ? '' : String(value)
1009
1039
  }
1010
1040
 
1041
+ function resolveRepositoryFromWorktree (repo) {
1042
+ return fsp
1043
+ .readFile(repo.gitdir, 'utf8')
1044
+ .then((contents) => contents.trimRight().substr(8))
1045
+ .then((worktreeGitdir) =>
1046
+ fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
1047
+ (contents) => {
1048
+ const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
1049
+ const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
1050
+ return Object.assign(repo, { dir, gitdir, worktreeName: ospath.basename(worktreeGitdir) })
1051
+ },
1052
+ () => repo
1053
+ )
1054
+ )
1055
+ }
1056
+
1011
1057
  function findWorktrees (repo, patterns) {
1012
1058
  if (!patterns.length) return new Map()
1013
- const linkedOnly = patterns[0] === '.' ? !(patterns = patterns.slice(1)) : true
1014
- let worktreesDir
1059
+ const mainWorktree =
1060
+ patterns[0] === '.' && (patterns = patterns.slice(1))
1061
+ ? getCurrentBranchName(repo).then((branch) => (branch ? [branch, { head: repo.dir, name: '.' }] : undefined))
1062
+ : Promise.resolve()
1063
+ const worktreesDir = patterns.length ? ospath.join(repo.dir, '.git', 'worktrees') : undefined
1015
1064
  const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
1016
1065
  return (
1017
- patterns.length
1066
+ worktreesDir
1018
1067
  ? fsp
1019
- .readdir((worktreesDir = ospath.join(repo.dir, '.git', 'worktrees')))
1068
+ .readdir(worktreesDir)
1020
1069
  .then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
1021
1070
  .then((worktreeNames) =>
1022
- worktreeNames.length
1023
- ? Promise.all(
1024
- worktreeNames.map((worktreeName) => {
1025
- const gitdir = ospath.resolve(worktreesDir, worktreeName)
1026
- // NOTE uses name of worktree as branch name if HEAD is detached
1027
- return git
1028
- .currentBranch(Object.assign({}, repo, { gitdir }))
1029
- .then((branch = worktreeName) =>
1030
- fsp
1031
- .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1032
- .then((contents) => ({ branch, dir: ospath.dirname(contents.trimRight()) }))
1033
- )
1034
- })
1035
- ).then((entries) => entries.reduce((accum, it) => accum.set(it.branch, it.dir), new Map()))
1036
- : new Map()
1071
+ Promise.all(
1072
+ worktreeNames.map((worktreeName) => {
1073
+ const gitdir = ospath.resolve(worktreesDir, worktreeName)
1074
+ // NOTE branch name defaults to worktree name if HEAD is detached
1075
+ return git
1076
+ .currentBranch(Object.assign({}, repo, { gitdir }))
1077
+ .then((branch = worktreeName) =>
1078
+ fsp
1079
+ .readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
1080
+ .then((contents) => [branch, { head: ospath.dirname(contents.trimRight()), name: worktreeName }])
1081
+ )
1082
+ })
1083
+ )
1037
1084
  )
1038
- : Promise.resolve(new Map())
1039
- ).then((worktrees) =>
1040
- linkedOnly
1041
- ? worktrees
1042
- : git.currentBranch(repo).then((branch) => (branch ? worktrees.set(branch, repo.dir) : worktrees))
1043
- )
1085
+ : Promise.resolve()
1086
+ ).then((entries = []) => mainWorktree.then((entry) => new Map(entry ? entries.push(entry) && entries : entries)))
1044
1087
  }
1045
1088
 
1046
1089
  module.exports = aggregateContent
@@ -4,7 +4,7 @@ const { posix: path } = require('path')
4
4
  const posixify = require('./posixify')
5
5
  const removeGitSuffix = require('./remove-git-suffix')
6
6
 
7
- const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name|type)|path)\}/g
7
+ const EDIT_URL_TEMPLATE_VAR_RX = /\{(web_url|ref(?:hash|name|type)?|path)\}/g
8
8
  const HOSTED_GIT_REPO_RX = /^(?:https?:\/\/|.+@)(git(?:hub|lab)\.com|bitbucket\.org|pagure\.io)[/:](.+?)(?:\.git)?$/
9
9
 
10
10
  function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath = undefined, editUrl = true) {
@@ -41,6 +41,7 @@ function computeOrigin (url, authStatus, gitdir, ref, startPath, worktreePath =
41
41
  } else if (editUrl) {
42
42
  const vars = {
43
43
  path: () => (startPath ? path.join(startPath, '%s') : '%s'),
44
+ ref: () => 'refs/' + (reftype === 'branch' ? 'heads' : reftype) + '/' + refname,
44
45
  refhash: () => refhash,
45
46
  reftype: () => reftype,
46
47
  refname: () => refname,
@@ -4,9 +4,11 @@ const { makeMatcherRx, refMatcherOpts: getMatcherOpts, MATCH_ALL_RX } = require(
4
4
 
5
5
  function compileRx (pattern, opts) {
6
6
  if (pattern === '*' || pattern === '**') return MATCH_ALL_RX
7
- return pattern.charAt() === '!' // do our own negate
8
- ? Object.defineProperty(makeMatcherRx(pattern.substr(1), opts), 'negated', { value: true })
9
- : makeMatcherRx(pattern, opts)
7
+ const rx =
8
+ pattern.charAt() === '!' // we handle negate ourselves
9
+ ? Object.defineProperty(makeMatcherRx((pattern = pattern.substr(1)), opts), 'negated', { value: true })
10
+ : makeMatcherRx(pattern, opts)
11
+ return Object.defineProperty(rx, 'pattern', { value: pattern })
10
12
  }
11
13
 
12
14
  function createMatcher (patterns, cache, opts) {
@@ -15,8 +17,8 @@ function createMatcher (patterns, cache, opts) {
15
17
  cache.get(pattern) || cache.set(pattern, compileRx(pattern, opts || (opts = getMatcherOpts(cache)))).get(pattern)
16
18
  )
17
19
  if (rxs[0].negated) rxs.unshift(MATCH_ALL_RX)
18
- return (candidate) => {
19
- let matched
20
+ return (candidate, onMatch) => {
21
+ let matched, symbolic
20
22
  for (const rx of rxs) {
21
23
  let voteIfMatched = true
22
24
  if (matched) {
@@ -25,16 +27,22 @@ function createMatcher (patterns, cache, opts) {
25
27
  } else if (rx.negated) {
26
28
  continue
27
29
  }
28
- if (rx.test(candidate)) matched = voteIfMatched
30
+ if (rx.test(candidate) || (symbolic && rx.test(symbolic) && (candidate = symbolic))) {
31
+ if (onMatch) {
32
+ if (!(matched = onMatch(candidate, rx))) continue
33
+ ;[symbolic, candidate] = [candidate, matched]
34
+ }
35
+ matched = voteIfMatched && candidate
36
+ }
29
37
  }
30
38
  return matched
31
39
  }
32
40
  }
33
41
 
34
- function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() })) {
35
- const isMatch = createMatcher(patterns, cache)
42
+ function filterRefs (candidates, patterns, cache = Object.assign(new Map(), { braces: new Map() }), onMatch) {
43
+ const match = createMatcher(patterns, cache)
36
44
  return candidates.reduce((accum, candidate) => {
37
- if (isMatch(candidate)) accum.push(candidate)
45
+ if ((candidate = match(candidate, onMatch))) accum.push(candidate)
38
46
  return accum
39
47
  }, [])
40
48
  }
@@ -86,11 +86,11 @@ class GitCredentialManagerStore {
86
86
  }
87
87
 
88
88
  async approved ({ url }) {
89
- this.urls[url] = 'approved'
89
+ this.urls[url] = (url in this.urls ? this.urls[url] + ',' : '') + 'approved'
90
90
  }
91
91
 
92
92
  async rejected ({ url, auth }) {
93
- this.urls[url] = 'rejected'
93
+ this.urls[url] = (url in this.urls ? this.urls[url] + ',' : '') + 'rejected'
94
94
  const statusCode = 401
95
95
  const statusMessage = 'HTTP Basic: Access Denied'
96
96
  const err = new Error(`HTTP Error: ${statusCode} ${statusMessage}`)
package/lib/matcher.js CHANGED
@@ -22,7 +22,7 @@ function makeMatcherRx (input, opts) {
22
22
  }
23
23
 
24
24
  module.exports = {
25
- MATCH_ALL_RX: { test: () => true },
25
+ MATCH_ALL_RX: Object.defineProperty({ test: () => true }, 'pattern', { value: '*' }),
26
26
  expandBraces,
27
27
  makeMatcherRx,
28
28
  pathMatcherOpts: Object.assign({}, BASE_OPTS, { dot: false }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/content-aggregator",
3
- "version": "3.1.2",
3
+ "version": "3.2.0-alpha.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)",
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@antora/expand-path-helper": "~2.0",
32
- "@antora/logger": "3.1.2",
32
+ "@antora/logger": "3.2.0-alpha.2",
33
33
  "@antora/user-require-helper": "~2.0",
34
34
  "braces": "~3.0",
35
35
  "cache-directory": "~2.0",
36
36
  "glob-stream": "~7.0",
37
- "hpagent": "~1.1",
37
+ "hpagent": "~1.2",
38
38
  "isomorphic-git": "~1.21",
39
39
  "js-yaml": "~4.1",
40
40
  "multi-progress": "~4.0",