@antora/content-aggregator 3.2.0-alpha.1 → 3.2.0-alpha.10
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/lib/aggregate-content.js +389 -310
- package/lib/compute-origin.js +5 -4
- package/lib/constants.js +2 -2
- package/lib/decode-uint8-array.js +1 -1
- package/lib/filter-refs.js +8 -4
- package/lib/git-credential-manager-store.js +3 -3
- package/lib/git-plugin-http.js +5 -4
- package/lib/git.js +8 -1
- package/lib/matcher.js +1 -6
- package/lib/posixify.js +1 -1
- package/lib/resolve-path-globs.js +25 -24
- package/package.json +17 -13
package/lib/aggregate-content.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const computeOrigin = require('./compute-origin')
|
|
4
|
-
const { createHash } = require('crypto')
|
|
4
|
+
const { createHash } = require('node:crypto')
|
|
5
5
|
const createGitHttpPlugin = require('./git-plugin-http')
|
|
6
6
|
const decodeUint8Array = require('./decode-uint8-array')
|
|
7
7
|
const deepClone = require('./deep-clone')
|
|
8
|
-
const
|
|
9
|
-
const EventEmitter = require('events')
|
|
8
|
+
const EventEmitter = require('node:events')
|
|
10
9
|
const expandPath = require('@antora/expand-path-helper')
|
|
11
10
|
const File = require('./file')
|
|
12
11
|
const filterRefs = require('./filter-refs')
|
|
13
|
-
const fs = require('fs')
|
|
12
|
+
const fs = require('node:fs')
|
|
14
13
|
const { promises: fsp } = fs
|
|
15
14
|
const getCacheDir = require('cache-directory')
|
|
16
15
|
const GitCredentialManagerStore = require('./git-credential-manager-store')
|
|
17
16
|
const git = require('./git')
|
|
18
17
|
const { NotFoundError, ObjectTypeError, UnknownTransportError, UrlParseError } = git.Errors
|
|
19
|
-
const globStream = require('glob
|
|
18
|
+
const { globStream } = require('fast-glob')
|
|
19
|
+
const { inspect } = require('node:util')
|
|
20
20
|
const invariably = require('./invariably')
|
|
21
21
|
const logger = require('./logger')
|
|
22
22
|
const { makeMatcherRx, versionMatcherOpts: VERSION_MATCHER_OPTS } = require('./matcher')
|
|
23
23
|
const MultiProgress = require('multi-progress') // calls require('progress') as a peer dependencies
|
|
24
|
-
const ospath = require('path')
|
|
24
|
+
const ospath = require('node:path')
|
|
25
25
|
const { posix: path } = ospath
|
|
26
26
|
const posixify = require('./posixify')
|
|
27
27
|
const removeGitSuffix = require('./remove-git-suffix')
|
|
28
28
|
const { fs: resolvePathGlobsFs, git: resolvePathGlobsGit } = require('./resolve-path-globs')
|
|
29
|
-
const { pipeline, Writable } = require('stream')
|
|
29
|
+
const { pipeline, Writable } = require('node:stream')
|
|
30
30
|
const forEach = (write) => new Writable({ objectMode: true, write })
|
|
31
31
|
const userRequire = require('@antora/user-require-helper')
|
|
32
32
|
const yaml = require('js-yaml')
|
|
@@ -87,138 +87,128 @@ 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
93
|
return ensureCacheDir(requestedCacheDir, startDir).then((cacheDir) => {
|
|
94
94
|
const gitConfig = Object.assign({ ensureGitSuffix: true }, playbook.git)
|
|
95
95
|
const gitPlugins = loadGitPlugins(gitConfig, playbook.network || {}, startDir)
|
|
96
|
-
const
|
|
96
|
+
const concurrency = {
|
|
97
|
+
fetch: Math.max(gitConfig.fetchConcurrency || Infinity, 1),
|
|
98
|
+
read: Math.max(gitConfig.readConcurrency || Infinity, 1),
|
|
99
|
+
}
|
|
97
100
|
const sourcesByUrl = sources.reduce((accum, source) => {
|
|
98
101
|
return accum.set(source.url, [...(accum.get(source.url) || []), Object.assign({}, sourceDefaults, source)])
|
|
99
102
|
}, new Map())
|
|
100
|
-
const progress =
|
|
103
|
+
const progress = quiet ? undefined : createProgress(sourcesByUrl.keys(), process.stdout)
|
|
101
104
|
const refPatternCache = Object.assign(new Map(), { braces: new Map() })
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
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()
|
|
105
109
|
throw err
|
|
106
110
|
})
|
|
107
111
|
})
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
async function collectFiles (sourcesByUrl, loadOpts, concurrency) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const runTask = (primary, continuation, idx) =>
|
|
129
|
-
primary().then((value) => {
|
|
130
|
-
if (!rejection) startedContinuations[idx] = continuation(value).catch(recordRejection)
|
|
131
|
-
}, recordRejection)
|
|
132
|
-
if (tasks.length > concurrency) {
|
|
133
|
-
started = []
|
|
134
|
-
const pending = []
|
|
135
|
-
for (const [primary, continuation] of tasks) {
|
|
136
|
-
const current = runTask(primary, continuation, started.length).finally(() =>
|
|
137
|
-
pending.splice(pending.indexOf(current), 1)
|
|
138
|
-
)
|
|
139
|
-
started.push(current)
|
|
140
|
-
if (pending.push(current) < concurrency) continue
|
|
141
|
-
await Promise.race(pending)
|
|
142
|
-
if (rejection) break
|
|
114
|
+
async function collectFiles (sourcesByUrl, loadOpts, concurrency, fetchedUrls = []) {
|
|
115
|
+
const loadTasks = [...sourcesByUrl.entries()].map(([url, sources]) => {
|
|
116
|
+
const loadOptsForUrl = Object.assign({}, loadOpts)
|
|
117
|
+
if (loadOpts.fetch.always && fetchedUrls.length && ~fetchedUrls.indexOf(url)) loadOptsForUrl.fetch.always = false
|
|
118
|
+
if (tagsSpecified(sources)) loadOptsForUrl.fetch.tags = true
|
|
119
|
+
return loadRepository.bind(null, url, loadOptsForUrl, { url, sources })
|
|
120
|
+
})
|
|
121
|
+
return gracefulPromiseAllWithLimit(loadTasks, concurrency.fetch).then(([results, rejections]) => {
|
|
122
|
+
if (rejections.length) {
|
|
123
|
+
if (concurrency.fetch > 1 && results.length > 1 && rejections.every(({ recoverable }) => recoverable)) {
|
|
124
|
+
if (loadOpts.progress) loadOpts.progress.terminate() // reset cursor position and allow it be reused
|
|
125
|
+
const msg0 = 'An unexpected error occurred while fetching content sources concurrently.'
|
|
126
|
+
const msg1 = 'Retrying with git.fetch_concurrency value of 1.'
|
|
127
|
+
logger.warn(rejections[0], msg0 + ' ' + msg1)
|
|
128
|
+
const fulfilledUrls = results.filter((it) => it?.repo.url).map((it) => it.url)
|
|
129
|
+
return collectFiles(sourcesByUrl, loadOpts, Object.assign(concurrency, { fetch: 1 }), fulfilledUrls)
|
|
130
|
+
}
|
|
131
|
+
throw rejections[0]
|
|
143
132
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
133
|
+
return Promise.all(
|
|
134
|
+
results.map(({ repo, authStatus, sources }) =>
|
|
135
|
+
selectStartPathsForRepository(repo, sources).then((startPaths) =>
|
|
136
|
+
collectFilesFromStartPaths.bind(null, startPaths, repo, authStatus)
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
).then((collectTasks) => promiseAllWithLimit(collectTasks, concurrency.read))
|
|
140
|
+
})
|
|
153
141
|
}
|
|
154
142
|
|
|
155
143
|
function buildAggregate (componentVersionBuckets) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!entry) return accum.set(key, batch)
|
|
144
|
+
const entries = Object.assign(new Map(), { accum: [] })
|
|
145
|
+
for (const batchesForOrigin of componentVersionBuckets) {
|
|
146
|
+
for (const batch of batchesForOrigin) {
|
|
147
|
+
let key, entry
|
|
148
|
+
if ((entry = entries.get((key = batch.version + '@' + batch.name)))) {
|
|
162
149
|
const { files, origins } = batch
|
|
163
150
|
;(batch.files = entry.files).push(...files)
|
|
164
151
|
;(batch.origins = entry.origins).push(origins[0])
|
|
165
152
|
Object.assign(entry, batch)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
153
|
+
} else {
|
|
154
|
+
entries.set(key, batch).accum.push(batch)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return entries.accum
|
|
170
159
|
}
|
|
171
160
|
|
|
172
|
-
async function loadRepository (url, opts) {
|
|
161
|
+
async function loadRepository (url, opts, result = {}) {
|
|
173
162
|
let authStatus, dir, repo
|
|
174
163
|
const cache = { [REF_PATTERN_CACHE_KEY]: opts.refPatternCache }
|
|
175
164
|
if (~url.indexOf(':') && GIT_URI_DETECTOR_RX.test(url)) {
|
|
176
165
|
let credentials, displayUrl
|
|
177
166
|
;({ displayUrl, url, credentials } = extractCredentials(url))
|
|
178
|
-
const { cacheDir, fetch,
|
|
167
|
+
const { cacheDir, fetch, gitPlugins, progress } = opts
|
|
179
168
|
dir = ospath.join(cacheDir, generateCloneFolderName(displayUrl))
|
|
180
169
|
// NOTE the presence of the url property on the repo object implies the repository is remote
|
|
181
170
|
repo = { cache, dir, fs, gitdir: dir, noCheckout: true, url }
|
|
171
|
+
const { credentialManager } = gitPlugins
|
|
182
172
|
const validStateFile = ospath.join(dir, VALID_STATE_FILENAME)
|
|
183
173
|
try {
|
|
184
174
|
await fsp.access(validStateFile)
|
|
185
|
-
if (fetch) {
|
|
175
|
+
if (fetch.always) {
|
|
186
176
|
await fsp.unlink(validStateFile)
|
|
187
|
-
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins,
|
|
177
|
+
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'fetch')
|
|
188
178
|
await git
|
|
189
179
|
.fetch(fetchOpts)
|
|
190
180
|
.then(() => {
|
|
191
|
-
authStatus = identifyAuthStatus(
|
|
181
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
192
182
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
193
183
|
})
|
|
194
184
|
.catch((fetchErr) => {
|
|
195
|
-
|
|
185
|
+
fetchOpts.onProgress?.finish(fetchErr)
|
|
196
186
|
if (HTTP_ERROR_CODE_RX.test(fetchErr.code) && fetchErr.data.statusCode === 401) fetchErr.rethrow = true
|
|
197
187
|
throw fetchErr
|
|
198
188
|
})
|
|
199
189
|
.then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
|
|
200
|
-
.then(() => fetchOpts.onProgress
|
|
190
|
+
.then(() => fetchOpts.onProgress?.finish())
|
|
201
191
|
} else {
|
|
202
192
|
authStatus = await git.getConfig(Object.assign({ path: 'remote.origin.private' }, repo))
|
|
203
193
|
}
|
|
204
194
|
} catch (gitErr) {
|
|
205
195
|
await fsp['rm' in fsp ? 'rm' : 'rmdir'](dir, { recursive: true, force: true })
|
|
206
196
|
if (gitErr.rethrow) throw transformGitCloneError(gitErr, displayUrl)
|
|
207
|
-
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins,
|
|
197
|
+
const fetchOpts = buildFetchOptions(repo, progress, displayUrl, credentials, gitPlugins, fetch, 'clone')
|
|
208
198
|
await git
|
|
209
199
|
.clone(fetchOpts)
|
|
210
200
|
.then(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 1 }, repo)))
|
|
211
201
|
.then(() => {
|
|
212
|
-
authStatus = identifyAuthStatus(
|
|
202
|
+
authStatus = identifyAuthStatus(credentialManager, credentials, url)
|
|
213
203
|
return git.setConfig(Object.assign({ path: 'remote.origin.private', value: authStatus }, repo))
|
|
214
204
|
})
|
|
215
205
|
.catch((cloneErr) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
throw transformGitCloneError(cloneErr, displayUrl)
|
|
206
|
+
fetchOpts.onProgress?.finish(cloneErr)
|
|
207
|
+
const authRequested = credentialManager.status({ url }) === 'requested'
|
|
208
|
+
throw transformGitCloneError(cloneErr, displayUrl, authRequested)
|
|
219
209
|
})
|
|
220
210
|
.then(() => fsp.writeFile(validStateFile, '').catch(invariably.void))
|
|
221
|
-
.then(() => fetchOpts.onProgress
|
|
211
|
+
.then(() => fetchOpts.onProgress?.finish())
|
|
222
212
|
}
|
|
223
213
|
} else if (await isDirectory((dir = expandPath(url, { dot: opts.startDir })))) {
|
|
224
214
|
const dotgit = ospath.join(dir, '.git')
|
|
@@ -239,7 +229,7 @@ async function loadRepository (url, opts) {
|
|
|
239
229
|
} else {
|
|
240
230
|
throw new Error(`Local content source does not exist: ${dir}${url !== dir ? ' (url: ' + url + ')' : ''}`)
|
|
241
231
|
}
|
|
242
|
-
return { repo, authStatus }
|
|
232
|
+
return Object.assign(result, { repo, authStatus })
|
|
243
233
|
}
|
|
244
234
|
|
|
245
235
|
function extractCredentials (url) {
|
|
@@ -254,34 +244,61 @@ function extractCredentials (url) {
|
|
|
254
244
|
// NOTE if only username is present, assume it's an oauth token and set password to empty string
|
|
255
245
|
const credentials = username ? { username, password: password || '' } : {}
|
|
256
246
|
return { displayUrl, url, credentials }
|
|
257
|
-
} else if (url.startsWith('git@')) {
|
|
258
|
-
return { displayUrl: url, url: 'https://' + url.substr(4).replace(':', '/') }
|
|
259
|
-
} else {
|
|
260
|
-
return { displayUrl: url, url }
|
|
261
247
|
}
|
|
248
|
+
if (url.startsWith('git@')) return { displayUrl: url, url: 'https://' + url.substr(4).replace(':', '/') }
|
|
249
|
+
return { displayUrl: url, url }
|
|
262
250
|
}
|
|
263
251
|
|
|
264
|
-
async function
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
252
|
+
async function selectStartPathsForRepository (repo, sources) {
|
|
253
|
+
const startPaths = []
|
|
254
|
+
const originUrls = {}
|
|
255
|
+
for (const source of sources) {
|
|
256
|
+
const { version, editUrl } = source
|
|
257
|
+
let remoteName, originUrl
|
|
258
|
+
if (repo.url) {
|
|
259
|
+
remoteName = 'origin' // NOTE if repository is managed (has url property), we can assume remote name is origin
|
|
260
|
+
originUrl = repo.url
|
|
261
|
+
} else {
|
|
262
|
+
remoteName = source.remote || 'origin'
|
|
263
|
+
originUrl =
|
|
264
|
+
remoteName in originUrls
|
|
265
|
+
? originUrls[remoteName]
|
|
266
|
+
: (originUrls[remoteName] = await resolveRemoteUrl(repo, remoteName))
|
|
267
|
+
if (!originUrl) {
|
|
268
|
+
remoteName = undefined
|
|
269
|
+
if ((originUrl = posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir).indexOf(' ')) {
|
|
270
|
+
originUrl = originUrl.replace(SPACE_RX, '%20')
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const refs = await selectReferences(source, repo, remoteName)
|
|
275
|
+
if (refs.length) {
|
|
276
|
+
for (const ref of refs) {
|
|
277
|
+
for (const startPath of await selectStartPaths(source, repo, ref)) {
|
|
278
|
+
startPaths.push({ startPath, ref, originUrl, editUrl, version })
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
const { url, branches, tags } = source
|
|
269
283
|
const startPathInfo =
|
|
270
|
-
'startPaths' in source
|
|
284
|
+
'startPaths' in source
|
|
285
|
+
? { 'start paths': source.startPaths || undefined }
|
|
286
|
+
: { 'start path': source.startPath || undefined }
|
|
271
287
|
const sourceInfo = yaml.dump({ url, branches, tags, ...startPathInfo }, { flowLevel: 1 }).trimRight()
|
|
272
288
|
logger.info(`No matching references found for content source entry (${sourceInfo.replace(NEWLINE_RX, ' | ')})`)
|
|
273
|
-
return []
|
|
274
289
|
}
|
|
275
|
-
|
|
276
|
-
|
|
290
|
+
}
|
|
291
|
+
return startPaths
|
|
277
292
|
}
|
|
278
293
|
|
|
279
294
|
// QUESTION should we resolve HEAD to a ref eagerly to avoid having to do a match on it?
|
|
280
295
|
async function selectReferences (source, repo, remote) {
|
|
281
296
|
let { branches: branchPatterns, tags: tagPatterns, worktrees: worktreePatterns } = source
|
|
282
|
-
const
|
|
297
|
+
const managed = 'url' in repo
|
|
298
|
+
const isBare = managed || repo.noCheckout
|
|
283
299
|
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
284
|
-
const noWorktree =
|
|
300
|
+
const noWorktree = managed ? undefined : false
|
|
301
|
+
const isLinkedWorktree = repo.worktree?.name
|
|
285
302
|
const refs = new Map()
|
|
286
303
|
if (
|
|
287
304
|
tagPatterns &&
|
|
@@ -297,46 +314,63 @@ async function selectReferences (source, repo, remote) {
|
|
|
297
314
|
}
|
|
298
315
|
}
|
|
299
316
|
}
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
if (
|
|
318
|
+
!branchPatterns ||
|
|
319
|
+
!(branchPatterns = Array.isArray(branchPatterns)
|
|
320
|
+
? branchPatterns.map((pattern) => String(pattern))
|
|
321
|
+
: splitRefPatterns(String(branchPatterns))).length
|
|
322
|
+
) {
|
|
323
|
+
return [...refs.values()]
|
|
324
|
+
}
|
|
325
|
+
let useWorktree = false
|
|
326
|
+
if (!managed && (useWorktree = {})) {
|
|
308
327
|
if (worktreePatterns === '.') {
|
|
309
|
-
|
|
328
|
+
isLinkedWorktree ? (useWorktree.linked = isLinkedWorktree) : isBare || (useWorktree.main = true)
|
|
329
|
+
worktreePatterns = []
|
|
330
|
+
} else if (!worktreePatterns) {
|
|
331
|
+
worktreePatterns = []
|
|
310
332
|
} else if (worktreePatterns === true) {
|
|
311
|
-
|
|
333
|
+
if (!isBare) useWorktree.main = true
|
|
334
|
+
// NOTE if we don't start at a linked worktree, linked worktree cannot be current worktree
|
|
335
|
+
if (isLinkedWorktree) useWorktree.linked = isLinkedWorktree
|
|
336
|
+
worktreePatterns = ['*']
|
|
337
|
+
} else if (worktreePatterns === '/.') {
|
|
338
|
+
if (!isBare) useWorktree.main = true
|
|
339
|
+
worktreePatterns = []
|
|
312
340
|
} else {
|
|
313
|
-
worktreePatterns =
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
341
|
+
worktreePatterns = (
|
|
342
|
+
Array.isArray(worktreePatterns)
|
|
343
|
+
? worktreePatterns.map((pattern) => String(pattern))
|
|
344
|
+
: splitRefPatterns(String(worktreePatterns))
|
|
345
|
+
).reduce((accum, it) => {
|
|
346
|
+
if (it === '/.') return (isBare || (useWorktree.main = true)) && accum
|
|
347
|
+
if (it === '.') {
|
|
348
|
+
isLinkedWorktree ? (useWorktree.linked = isLinkedWorktree) : isBare || (useWorktree.main = true)
|
|
349
|
+
} else {
|
|
350
|
+
accum.push(it)
|
|
351
|
+
}
|
|
352
|
+
return accum
|
|
353
|
+
}, [])
|
|
317
354
|
}
|
|
318
|
-
|
|
319
|
-
worktreePatterns = worktreePatterns === undefined ? [worktreeName || '.'] : []
|
|
355
|
+
if (!(useWorktree.main || useWorktree.linked)) useWorktree = false
|
|
320
356
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (currentBranch) {
|
|
324
|
-
branchPatterns = [currentBranch]
|
|
325
|
-
} else if (isBare) {
|
|
326
|
-
return [...refs.values()]
|
|
327
|
-
} else {
|
|
328
|
-
// NOTE current branch is undefined when HEAD is detached
|
|
329
|
-
const head = worktreePatterns[0] === '.' ? repo.dir : noWorktree
|
|
330
|
-
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
331
|
-
return [...refs.values()]
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
357
|
+
let currentBranch
|
|
358
|
+
if (!isLinkedWorktree) {
|
|
334
359
|
let headBranchIdx
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
360
|
+
if (branchPatterns.length === 1 && (branchPatterns[0] === 'HEAD' || branchPatterns[0] === '.')) {
|
|
361
|
+
if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
|
|
362
|
+
branchPatterns = [currentBranch]
|
|
363
|
+
} else if (isBare) {
|
|
364
|
+
return [...refs.values()]
|
|
365
|
+
} else {
|
|
366
|
+
// NOTE current branch is undefined when HEAD is detached
|
|
367
|
+
const head = useWorktree.main ? repo.dir : noWorktree
|
|
368
|
+
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
369
|
+
return [...refs.values()]
|
|
370
|
+
}
|
|
371
|
+
} else if (~(headBranchIdx = branchPatterns.indexOf('HEAD')) || ~(headBranchIdx = branchPatterns.indexOf('.'))) {
|
|
372
|
+
// NOTE we can assume at least two entries if HEAD or . are present
|
|
373
|
+
if ((currentBranch = await getCurrentBranchName(repo, remote).then((branch) => branch ?? false))) {
|
|
340
374
|
if (~branchPatterns.indexOf(currentBranch)) {
|
|
341
375
|
branchPatterns.splice(headBranchIdx, 1)
|
|
342
376
|
} else {
|
|
@@ -345,11 +379,7 @@ async function selectReferences (source, repo, remote) {
|
|
|
345
379
|
} else if (isBare) {
|
|
346
380
|
branchPatterns.splice(headBranchIdx, 1)
|
|
347
381
|
} else {
|
|
348
|
-
|
|
349
|
-
if (worktreePatterns[0] === '.') {
|
|
350
|
-
worktreePatterns = worktreePatterns.slice(1)
|
|
351
|
-
head = repo.dir
|
|
352
|
-
}
|
|
382
|
+
const head = useWorktree.main ? repo.dir : noWorktree
|
|
353
383
|
// NOTE current branch is undefined when HEAD is detached
|
|
354
384
|
refs.set('HEAD', { shortname: 'HEAD', fullname: 'HEAD', type: 'branch', detached: true, head })
|
|
355
385
|
branchPatterns.splice(headBranchIdx, 1)
|
|
@@ -357,40 +387,51 @@ async function selectReferences (source, repo, remote) {
|
|
|
357
387
|
}
|
|
358
388
|
}
|
|
359
389
|
// NOTE isomorphic-git includes HEAD in list of remote branches (see https://isomorphic-git.org/docs/listBranches)
|
|
360
|
-
const remoteBranches =
|
|
390
|
+
const remoteBranches = remote
|
|
391
|
+
? (await git.listBranches(Object.assign({ remote }, repo))).filter((it) => it !== 'HEAD')
|
|
392
|
+
: []
|
|
361
393
|
if (remoteBranches.length) {
|
|
362
394
|
for (const shortname of filterRefs(remoteBranches, branchPatterns, patternCache)) {
|
|
363
395
|
const fullname = 'remotes/' + remote + '/' + shortname
|
|
364
396
|
refs.set(shortname, { shortname, fullname, type: 'branch', remote, head: noWorktree })
|
|
365
397
|
}
|
|
366
398
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
399
|
+
if (!managed) {
|
|
400
|
+
const localBranches = await git.listBranches(repo).then((branches) => {
|
|
401
|
+
if (branches.length || isBare) return branches
|
|
402
|
+
if (currentBranch != null) return [currentBranch]
|
|
403
|
+
return getCurrentBranchName(repo).then((branch) => (branch ? [branch] : []))
|
|
404
|
+
})
|
|
405
|
+
const worktrees = await findWorktrees(repo, worktreePatterns, useWorktree)
|
|
406
|
+
let onMatch
|
|
407
|
+
if ((useWorktree || worktreePatterns.length) && worktrees.size) {
|
|
408
|
+
const headNames = new Map()
|
|
409
|
+
worktrees.forEach(({ name, symbolicNames }, shortname) => {
|
|
410
|
+
if (name) {
|
|
411
|
+
const headName = 'HEAD@' + name
|
|
412
|
+
localBranches.push(headName)
|
|
413
|
+
headNames.set(headName, shortname)
|
|
382
414
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
415
|
+
if (symbolicNames) {
|
|
416
|
+
for (const symbolicName of symbolicNames) {
|
|
417
|
+
const symbolicHeadName = symbolicName === 'HEAD' ? symbolicName : 'HEAD@' + symbolicName
|
|
418
|
+
localBranches.push(symbolicHeadName)
|
|
419
|
+
headNames.set(symbolicHeadName, shortname)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
onMatch = (candidate, { pattern }) => {
|
|
424
|
+
const shortname = headNames.get(candidate)
|
|
425
|
+
if (!shortname) return candidate
|
|
426
|
+
if (pattern === 'HEAD' || pattern.startsWith('HEAD@')) return shortname
|
|
387
427
|
}
|
|
388
428
|
}
|
|
389
|
-
} else if (!remoteBranches.length) {
|
|
390
|
-
const localBranches = await git.listBranches(repo)
|
|
391
429
|
if (localBranches.length) {
|
|
392
|
-
|
|
393
|
-
|
|
430
|
+
const preferRemote = isBare && remoteBranches.length > 0
|
|
431
|
+
for (const shortname of filterRefs(localBranches, branchPatterns, patternCache, onMatch)) {
|
|
432
|
+
if (preferRemote && refs.has(shortname)) continue
|
|
433
|
+
const head = (worktrees.get(shortname) || { head: false }).head
|
|
434
|
+
refs.set(shortname, { shortname, fullname: 'heads/' + shortname, type: 'branch', head })
|
|
394
435
|
}
|
|
395
436
|
}
|
|
396
437
|
}
|
|
@@ -398,22 +439,21 @@ async function selectReferences (source, repo, remote) {
|
|
|
398
439
|
}
|
|
399
440
|
|
|
400
441
|
/**
|
|
401
|
-
* Returns the current branch name
|
|
442
|
+
* Returns the current branch name or undefined if the HEAD is detached.
|
|
402
443
|
*/
|
|
403
444
|
function getCurrentBranchName (repo, remote) {
|
|
404
445
|
return (
|
|
405
|
-
repo.noCheckout
|
|
446
|
+
remote && repo.noCheckout
|
|
406
447
|
? git
|
|
407
|
-
|
|
408
|
-
|
|
448
|
+
.resolveRef(Object.assign({ ref: 'refs/remotes/' + remote + '/HEAD', depth: 2 }, repo))
|
|
449
|
+
.catch(() => git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo)))
|
|
409
450
|
: git.resolveRef(Object.assign({ ref: 'HEAD', depth: 2 }, repo))
|
|
410
451
|
).then((ref) => (ref.startsWith('refs/') ? ref.replace(SHORTEN_REF_RX, '') : undefined))
|
|
411
452
|
}
|
|
412
453
|
|
|
413
|
-
async function
|
|
454
|
+
async function selectStartPaths (source, repo, ref) {
|
|
414
455
|
const url = repo.url
|
|
415
456
|
const displayUrl = url || repo.dir
|
|
416
|
-
const { version, editUrl } = source
|
|
417
457
|
const worktreePath = ref.head
|
|
418
458
|
if (!worktreePath) {
|
|
419
459
|
ref.oid = await git.resolveRef(
|
|
@@ -433,25 +473,31 @@ async function collectFilesFromReference (source, repo, remoteName, authStatus,
|
|
|
433
473
|
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
434
474
|
throw new Error(`no start paths found in ${where} (${ref.type}: ${ref.shortname}${flag})`)
|
|
435
475
|
}
|
|
436
|
-
return
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
476
|
+
return startPaths
|
|
477
|
+
}
|
|
478
|
+
return [cleanStartPath(coerceToString(source.startPath))]
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function collectFilesFromStartPaths (startPaths, repo, authStatus) {
|
|
482
|
+
const buckets = []
|
|
483
|
+
for (const { startPath, ref, originUrl, editUrl, version } of startPaths) {
|
|
484
|
+
buckets.push(await collectFilesFromStartPath(startPath, repo, authStatus, ref, originUrl, editUrl, version))
|
|
441
485
|
}
|
|
442
|
-
|
|
443
|
-
return
|
|
486
|
+
repo.cache = undefined
|
|
487
|
+
return buckets
|
|
444
488
|
}
|
|
445
489
|
|
|
446
|
-
function collectFilesFromStartPath (startPath, repo, authStatus, ref,
|
|
490
|
+
function collectFilesFromStartPath (startPath, repo, authStatus, ref, originUrl, editUrl, version) {
|
|
491
|
+
const worktreePath = ref.head
|
|
447
492
|
const origin = computeOrigin(originUrl, authStatus, repo.gitdir, ref, startPath, worktreePath, editUrl)
|
|
448
493
|
return (worktreePath ? readFilesFromWorktree(origin) : readFilesFromGitTree(repo, ref.oid, startPath))
|
|
449
|
-
.then((files) =>
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
494
|
+
.then((files) => {
|
|
495
|
+
const batch = deepClone((origin.descriptor = loadComponentDescriptor(files, ref, version)))
|
|
496
|
+
if ('nav' in batch && Array.isArray(batch.nav)) batch.nav.origin = origin
|
|
497
|
+
batch.files = files.map((file) => assignFileProperties(file, origin))
|
|
498
|
+
batch.origins = [origin]
|
|
499
|
+
return batch
|
|
500
|
+
})
|
|
455
501
|
.catch((err) => {
|
|
456
502
|
const where = worktreePath || (worktreePath === false ? repo.gitdir : repo.url || repo.dir)
|
|
457
503
|
const flag = worktreePath ? ' <worktree>' : ref.remote && worktreePath === false ? ` <remotes/${ref.remote}>` : ''
|
|
@@ -476,19 +522,18 @@ function readFilesFromWorktree (origin) {
|
|
|
476
522
|
}
|
|
477
523
|
|
|
478
524
|
function srcFs (cwd, origin) {
|
|
479
|
-
return new Promise((resolve, reject,
|
|
525
|
+
return new Promise((resolve, reject, files = []) =>
|
|
480
526
|
pipeline(
|
|
481
|
-
globStream(CONTENT_SRC_GLOB, Object.assign({
|
|
482
|
-
forEach(({ path:
|
|
483
|
-
if ((
|
|
484
|
-
const
|
|
485
|
-
const relpath =
|
|
486
|
-
|
|
487
|
-
(stat) =>
|
|
488
|
-
if (stat.isDirectory()) return done() // detects directories that slipped through cache check
|
|
527
|
+
globStream(CONTENT_SRC_GLOB, Object.assign({ cwd }, CONTENT_SRC_OPTS)),
|
|
528
|
+
forEach(({ path: relpath, dirent }, _, done) => {
|
|
529
|
+
if (dirent.isDirectory()) return done()
|
|
530
|
+
const relpathPosix = relpath
|
|
531
|
+
const abspath = posixify ? ospath.join(cwd, (relpath = ospath.normalize(relpath))) : cwd + '/' + relpath
|
|
532
|
+
fsp.stat(abspath).then(
|
|
533
|
+
(stat) =>
|
|
489
534
|
fsp.readFile(abspath).then(
|
|
490
535
|
(contents) => {
|
|
491
|
-
files.push(new File({ path:
|
|
536
|
+
files.push(new File({ path: relpathPosix, contents, stat, src: { abspath } }))
|
|
492
537
|
done()
|
|
493
538
|
},
|
|
494
539
|
(readErr) => {
|
|
@@ -498,22 +543,28 @@ function srcFs (cwd, origin) {
|
|
|
498
543
|
: logger.error(logObject, readErr.message.replace(`'${abspath}'`, relpath))
|
|
499
544
|
done()
|
|
500
545
|
}
|
|
501
|
-
)
|
|
502
|
-
},
|
|
546
|
+
),
|
|
503
547
|
(statErr) => {
|
|
504
548
|
const logObject = { file: { abspath, origin } }
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
(
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
549
|
+
if (dirent.isSymbolicLink()) {
|
|
550
|
+
fsp
|
|
551
|
+
.readlink(abspath)
|
|
552
|
+
.then(
|
|
553
|
+
(symlink) =>
|
|
554
|
+
(statErr.code === 'ELOOP' ? 'ELOOP: symbolic link cycle, ' : 'ENOENT: broken symbolic link, ') +
|
|
555
|
+
`${relpath} -> ${symlink}`,
|
|
556
|
+
() => statErr.message.replace(`'${abspath}'`, relpath)
|
|
557
|
+
)
|
|
558
|
+
.then((message) => {
|
|
559
|
+
logger.error(logObject, message)
|
|
560
|
+
done()
|
|
561
|
+
})
|
|
513
562
|
} else {
|
|
514
|
-
|
|
563
|
+
statErr.code === 'ENOENT'
|
|
564
|
+
? logger.warn(logObject, `ENOENT: file or directory disappeared, ${statErr.syscall} ${relpath}`)
|
|
565
|
+
: logger.error(logObject, statErr.message.replace(`'${abspath}'`, relpath))
|
|
566
|
+
done()
|
|
515
567
|
}
|
|
516
|
-
done()
|
|
517
568
|
}
|
|
518
569
|
)
|
|
519
570
|
}),
|
|
@@ -523,26 +574,25 @@ function srcFs (cwd, origin) {
|
|
|
523
574
|
}
|
|
524
575
|
|
|
525
576
|
function readFilesFromGitTree (repo, oid, startPath) {
|
|
526
|
-
return git
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
getGitTreeAtStartPath(repo, oid, startPath).then((start) =>
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
577
|
+
return git.readTree(Object.assign({ oid }, repo)).then((root) => {
|
|
578
|
+
Object.assign(root, { dirname: '' })
|
|
579
|
+
return startPath
|
|
580
|
+
? getGitTreeAtStartPath(repo, oid, startPath).then((start) => {
|
|
581
|
+
Object.assign(start, { dirname: startPath })
|
|
582
|
+
return srcGitTree(repo, root, start)
|
|
583
|
+
})
|
|
584
|
+
: srcGitTree(repo, root)
|
|
585
|
+
})
|
|
533
586
|
}
|
|
534
587
|
|
|
535
588
|
function getGitTreeAtStartPath (repo, oid, startPath) {
|
|
536
|
-
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).
|
|
537
|
-
|
|
538
|
-
(
|
|
539
|
-
|
|
540
|
-
throw new Error(`the start path '${startPath}' ${m}`)
|
|
541
|
-
}
|
|
542
|
-
)
|
|
589
|
+
return git.readTree(Object.assign({ oid, filepath: startPath }, repo)).catch((err) => {
|
|
590
|
+
const m = err instanceof ObjectTypeError && err.data.expected === 'tree' ? 'is not a directory' : 'does not exist'
|
|
591
|
+
throw new Error(`the start path '${startPath}' ${m}`)
|
|
592
|
+
})
|
|
543
593
|
}
|
|
544
594
|
|
|
545
|
-
function srcGitTree (repo, root, start) {
|
|
595
|
+
function srcGitTree (repo, root, start = root) {
|
|
546
596
|
return new Promise((resolve, reject) => {
|
|
547
597
|
const files = []
|
|
548
598
|
createGitTreeWalker(repo, root, filterGitEntry, gitEntryToFile)
|
|
@@ -585,7 +635,8 @@ function visitGitTree (emitter, repo, root, filter, convert, parent, dirname = '
|
|
|
585
635
|
(target) => {
|
|
586
636
|
if (target.type === 'tree') {
|
|
587
637
|
return visitGitTree(emitter, repo, root, filter, convert, target, vfilePath, target.following)
|
|
588
|
-
}
|
|
638
|
+
}
|
|
639
|
+
if (target.type === 'blob' && filterVerdict === true && (mode = FILE_MODES[target.mode])) {
|
|
589
640
|
return convert(Object.assign({ mode, oid: target.oid, path: vfilePath }, repo)).then((result) =>
|
|
590
641
|
emitter.emit('entry', result)
|
|
591
642
|
)
|
|
@@ -657,11 +708,11 @@ function readGitObjectAtPath (repo, root, parent, pathSegments, following) {
|
|
|
657
708
|
if (entry.path === firstPathSegment) {
|
|
658
709
|
return entry.type === 'tree'
|
|
659
710
|
? git.readTree(Object.assign({ oid: entry.oid }, repo)).then((subtree) => {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
711
|
+
Object.assign(subtree, { dirname: path.join(parent.dirname, entry.path) })
|
|
712
|
+
return (pathSegments = pathSegments.slice(1)).length
|
|
713
|
+
? readGitObjectAtPath(repo, root, subtree, pathSegments, following)
|
|
714
|
+
: Object.assign(subtree, { type: 'tree', following }) // Q: should this create copy?
|
|
715
|
+
})
|
|
665
716
|
: entry.mode === SYMLINK_FILE_MODE
|
|
666
717
|
? readGitSymlink(repo, root, parent, entry, following)
|
|
667
718
|
: Promise.resolve(entry)
|
|
@@ -684,8 +735,13 @@ function filterGitEntry (entry) {
|
|
|
684
735
|
|
|
685
736
|
function gitEntryToFile (entry) {
|
|
686
737
|
return git.readBlob(entry).then(({ blob: contents }) => {
|
|
687
|
-
|
|
688
|
-
|
|
738
|
+
const stat = {
|
|
739
|
+
mode: entry.mode,
|
|
740
|
+
size: (contents = Buffer.from(contents.buffer)).byteLength,
|
|
741
|
+
isDirectory: invariably.false,
|
|
742
|
+
isFile: invariably.true,
|
|
743
|
+
isSymbolicLink: invariably.false,
|
|
744
|
+
}
|
|
689
745
|
return new File({ path: entry.path, contents, stat })
|
|
690
746
|
})
|
|
691
747
|
}
|
|
@@ -697,7 +753,7 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
697
753
|
files.splice(descriptorFileIdx, 1)
|
|
698
754
|
let data
|
|
699
755
|
try {
|
|
700
|
-
data = yaml.load(descriptorFile.contents.toString(), { schema: yaml.CORE_SCHEMA })
|
|
756
|
+
data = Object(yaml.load(descriptorFile.contents.toString(), { schema: yaml.CORE_SCHEMA }))
|
|
701
757
|
} catch (err) {
|
|
702
758
|
throw Object.assign(err, { message: `${COMPONENT_DESC_FILENAME} has invalid syntax; ${err.message}` })
|
|
703
759
|
}
|
|
@@ -710,18 +766,18 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
710
766
|
if (!version) {
|
|
711
767
|
if (version === undefined) throw new Error(`${COMPONENT_DESC_FILENAME} is missing a version`)
|
|
712
768
|
if (version === false) throw new Error(`${COMPONENT_DESC_FILENAME} has an invalid version`)
|
|
713
|
-
version =
|
|
769
|
+
version = typeof version === 'number' ? '' + version : ''
|
|
714
770
|
} else if (version === true) {
|
|
715
771
|
version = ref.shortname.replace(PATH_SEPARATOR_RX, '-')
|
|
716
772
|
} else if (version.constructor === Object) {
|
|
717
773
|
const refname = ref.shortname
|
|
718
774
|
let matched
|
|
719
775
|
if (refname in version) {
|
|
720
|
-
matched = version[refname]
|
|
776
|
+
matched = '' + (version[refname] ?? '')
|
|
721
777
|
} else if (
|
|
722
778
|
!Object.entries(version).some(([pattern, replacement]) => {
|
|
723
|
-
const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + replacement)
|
|
724
|
-
if (result === refname) return false
|
|
779
|
+
const result = refname.replace(makeMatcherRx(pattern, VERSION_MATCHER_OPTS), '\0' + (replacement ?? ''))
|
|
780
|
+
if (result === refname) return false // no match
|
|
725
781
|
matched = result.substr(1)
|
|
726
782
|
return true
|
|
727
783
|
})
|
|
@@ -736,7 +792,7 @@ function loadComponentDescriptor (files, ref, version) {
|
|
|
736
792
|
throw new Error(`version in ${COMPONENT_DESC_FILENAME} cannot have path segments: ${version}`)
|
|
737
793
|
}
|
|
738
794
|
data.version = version
|
|
739
|
-
return camelCaseKeys(data, ['asciidoc'])
|
|
795
|
+
return camelCaseKeys(data, ['asciidoc', 'ext'])
|
|
740
796
|
}
|
|
741
797
|
|
|
742
798
|
function assignFileProperties (file, origin) {
|
|
@@ -753,18 +809,20 @@ function assignFileProperties (file, origin) {
|
|
|
753
809
|
return file
|
|
754
810
|
}
|
|
755
811
|
|
|
756
|
-
function buildFetchOptions (repo, progress, displayUrl, credentialsFromUrl, gitPlugins,
|
|
812
|
+
function buildFetchOptions (repo, progress, displayUrl, credentialsFromUrl, gitPlugins, fetch, operation) {
|
|
757
813
|
const { credentialManager, http, urlRouter } = gitPlugins
|
|
814
|
+
const corsProxy = false
|
|
815
|
+
const depth = fetch.depth || undefined
|
|
758
816
|
const onAuth = resolveCredentials.bind(credentialManager, new Map().set(undefined, credentialsFromUrl))
|
|
759
817
|
const onAuthFailure = onAuth
|
|
760
818
|
const onAuthSuccess = (url) => credentialManager.approved({ url })
|
|
761
|
-
const opts = Object.assign({ corsProxy
|
|
819
|
+
const opts = Object.assign({ corsProxy, depth, http, onAuth, onAuthFailure, onAuthSuccess }, repo)
|
|
762
820
|
if (urlRouter) opts.url = urlRouter.ensureGitSuffix(opts.url)
|
|
763
821
|
if (progress) opts.onProgress = createProgressListener(progress, displayUrl, operation)
|
|
764
822
|
if (operation === 'fetch') {
|
|
765
823
|
opts.prune = true
|
|
766
|
-
if (
|
|
767
|
-
} else if (!
|
|
824
|
+
if (fetch.tags) opts.tags = opts.pruneTags = true
|
|
825
|
+
} else if (!fetch.tags) {
|
|
768
826
|
opts.noTags = true
|
|
769
827
|
}
|
|
770
828
|
return opts
|
|
@@ -829,7 +887,6 @@ function onGitProgress ({ phase, loaded, total }) {
|
|
|
829
887
|
|
|
830
888
|
function onGitComplete (err) {
|
|
831
889
|
if (err) {
|
|
832
|
-
// TODO could use progressBar.interrupt() to replace bar with message instead
|
|
833
890
|
this.chars.incomplete = '?'
|
|
834
891
|
this.update(0)
|
|
835
892
|
// NOTE force progress bar to update regardless of throttle setting
|
|
@@ -863,9 +920,8 @@ function identifyAuthStatus (credentialManager, credentials, url) {
|
|
|
863
920
|
const status = credentialManager.status({ url })
|
|
864
921
|
if (credentials) {
|
|
865
922
|
return typeof status === 'string' && status.startsWith('requested,') ? 'auth-required' : 'auth-embedded'
|
|
866
|
-
} else if (status != null) {
|
|
867
|
-
return 'auth-required'
|
|
868
923
|
}
|
|
924
|
+
if (status != null) return 'auth-required'
|
|
869
925
|
}
|
|
870
926
|
|
|
871
927
|
/**
|
|
@@ -874,7 +930,7 @@ function identifyAuthStatus (credentialManager, credentials, url) {
|
|
|
874
930
|
* The purpose of this function is generate a safe, unique folder name for the cloned
|
|
875
931
|
* repository that gets stored in the cache directory.
|
|
876
932
|
*
|
|
877
|
-
* The generated folder name follows the pattern: <basename>-<sha1
|
|
933
|
+
* The generated folder name follows the pattern: <basename>-<sha1-of-normalized-url>.git
|
|
878
934
|
*
|
|
879
935
|
* @param {String} url - The repository URL to convert.
|
|
880
936
|
* @returns {String} The generated folder name.
|
|
@@ -890,21 +946,18 @@ function generateCloneFolderName (url) {
|
|
|
890
946
|
*
|
|
891
947
|
* @param {Repository} repo - The repository on which to operate.
|
|
892
948
|
* @param {String} remoteName - The name of the remote to resolve.
|
|
893
|
-
* @returns {String} The URL of the specified remote, if defined
|
|
949
|
+
* @returns {String} The URL of the specified remote, if defined
|
|
894
950
|
*/
|
|
895
951
|
function resolveRemoteUrl (repo, remoteName) {
|
|
896
952
|
return git.getConfig(Object.assign({ path: 'remote.' + remoteName + '.url' }, repo)).then((url) => {
|
|
897
|
-
if (url)
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
953
|
+
if (!url) return
|
|
954
|
+
if (url.startsWith('https://') || url.startsWith('http://')) {
|
|
955
|
+
return ~url.indexOf('@') ? url.replace(URL_AUTH_CLEANER_RX, '$1') : url
|
|
956
|
+
}
|
|
957
|
+
if (url.startsWith('git@')) return 'https://' + url.substr(4).replace(':', '/')
|
|
958
|
+
if (url.startsWith('ssh://')) {
|
|
959
|
+
return 'https://' + url.substr(url.indexOf('@') + 1 || 6).replace(URL_PORT_CLEANER_RX, '$1')
|
|
905
960
|
}
|
|
906
|
-
url = posixify ? 'file:///' + posixify(repo.dir) : 'file://' + repo.dir
|
|
907
|
-
return ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
|
|
908
961
|
})
|
|
909
962
|
}
|
|
910
963
|
|
|
@@ -918,20 +971,6 @@ function isDirectory (url) {
|
|
|
918
971
|
return fsp.stat(url).then((stat) => stat.isDirectory(), invariably.false)
|
|
919
972
|
}
|
|
920
973
|
|
|
921
|
-
function symlinkAwareStat (path_) {
|
|
922
|
-
return fsp.lstat(path_).then((lstat) => {
|
|
923
|
-
if (!lstat.isSymbolicLink()) return lstat
|
|
924
|
-
return fsp.stat(path_).catch((statErr) =>
|
|
925
|
-
fsp
|
|
926
|
-
.readlink(path_)
|
|
927
|
-
.catch(invariably.void)
|
|
928
|
-
.then((symlink) => {
|
|
929
|
-
throw Object.assign(statErr, { symlink })
|
|
930
|
-
})
|
|
931
|
-
)
|
|
932
|
-
})
|
|
933
|
-
}
|
|
934
|
-
|
|
935
974
|
function tagsSpecified (sources) {
|
|
936
975
|
return sources.some(({ tags }) => tags && (Array.isArray(tags) ? tags.length : true))
|
|
937
976
|
}
|
|
@@ -946,7 +985,7 @@ function loadGitPlugins (gitConfig, networkConfig, startDir) {
|
|
|
946
985
|
if (typeof credentialManager.configure === 'function') {
|
|
947
986
|
credentialManager.configure({ config: gitConfig.credentials, startDir })
|
|
948
987
|
}
|
|
949
|
-
if (typeof credentialManager.status !== 'function') Object.assign(credentialManager, { status
|
|
988
|
+
if (typeof credentialManager.status !== 'function') Object.assign(credentialManager, { status: invariably.void })
|
|
950
989
|
} else {
|
|
951
990
|
credentialManager = new GitCredentialManagerStore().configure({ config: gitConfig.credentials, startDir })
|
|
952
991
|
}
|
|
@@ -979,8 +1018,8 @@ function ensureCacheDir (preferredCacheDir, startDir) {
|
|
|
979
1018
|
)
|
|
980
1019
|
}
|
|
981
1020
|
|
|
982
|
-
function transformGitCloneError (err, displayUrl) {
|
|
983
|
-
let wrappedMsg, trimMessage
|
|
1021
|
+
function transformGitCloneError (err, displayUrl, authRequested) {
|
|
1022
|
+
let wrappedMsg, recoverable, trimMessage
|
|
984
1023
|
if (HTTP_ERROR_CODE_RX.test(err.code)) {
|
|
985
1024
|
switch (err.data.statusCode) {
|
|
986
1025
|
case 401:
|
|
@@ -989,30 +1028,31 @@ function transformGitCloneError (err, displayUrl) {
|
|
|
989
1028
|
: 'Content repository not found or requires credentials'
|
|
990
1029
|
break
|
|
991
1030
|
case 404:
|
|
992
|
-
wrappedMsg =
|
|
1031
|
+
wrappedMsg = authRequested
|
|
1032
|
+
? 'Content repository not found or credentials were rejected'
|
|
1033
|
+
: 'Content repository not found'
|
|
993
1034
|
break
|
|
994
1035
|
default:
|
|
995
1036
|
wrappedMsg = err.message
|
|
996
|
-
trimMessage = true
|
|
1037
|
+
recoverable = trimMessage = true
|
|
997
1038
|
}
|
|
998
1039
|
} else if (err instanceof UrlParseError || err instanceof UnknownTransportError) {
|
|
999
1040
|
wrappedMsg = 'Content source uses an unsupported transport protocol'
|
|
1000
1041
|
} else if (err.code === 'ENOTFOUND') {
|
|
1001
1042
|
wrappedMsg = `Content repository host could not be resolved: ${err.hostname}`
|
|
1002
1043
|
} else {
|
|
1003
|
-
wrappedMsg =
|
|
1004
|
-
trimMessage = true
|
|
1005
|
-
}
|
|
1006
|
-
if (trimMessage) {
|
|
1007
|
-
wrappedMsg = ~(wrappedMsg = wrappedMsg.trimRight()).indexOf('. ') ? wrappedMsg : wrappedMsg.replace(/\.$/, '')
|
|
1044
|
+
wrappedMsg = err.message || String(err)
|
|
1045
|
+
recoverable = trimMessage = true
|
|
1008
1046
|
}
|
|
1047
|
+
if (trimMessage && !~(wrappedMsg = wrappedMsg.trimEnd()).indexOf('. ')) wrappedMsg = wrappedMsg.replace(/\.$/, '')
|
|
1009
1048
|
const errWrapper = new Error(`${wrappedMsg} (url: ${displayUrl})`)
|
|
1010
|
-
errWrapper.stack += `\nCaused by: ${err.stack
|
|
1049
|
+
errWrapper.stack += `\nCaused by: ${err.stack ? inspect(err).replace(/^Error \[(.+?)\](?=: )/, '$1') : err}`
|
|
1050
|
+
if (recoverable) Object.defineProperty(errWrapper, 'recoverable', { value: true })
|
|
1011
1051
|
return errWrapper
|
|
1012
1052
|
}
|
|
1013
1053
|
|
|
1014
1054
|
function splitRefPatterns (str) {
|
|
1015
|
-
return ~str.indexOf('{') ?
|
|
1055
|
+
return str.split(~str.indexOf('{') ? VENTILATED_CSV_RX : CSV_RX)
|
|
1016
1056
|
}
|
|
1017
1057
|
|
|
1018
1058
|
function camelCaseKeys (o, stopPaths = [], p = '') {
|
|
@@ -1035,54 +1075,93 @@ function coerceToString (value) {
|
|
|
1035
1075
|
return value == null ? '' : String(value)
|
|
1036
1076
|
}
|
|
1037
1077
|
|
|
1038
|
-
|
|
1078
|
+
function resolveRepositoryFromWorktree (repo) {
|
|
1039
1079
|
return fsp
|
|
1040
1080
|
.readFile(repo.gitdir, 'utf8')
|
|
1041
|
-
.then((contents) => contents.
|
|
1042
|
-
.then((worktreeGitdir) =>
|
|
1043
|
-
|
|
1044
|
-
return fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
|
|
1081
|
+
.then((contents) => contents.substr(8).trimEnd())
|
|
1082
|
+
.then((worktreeGitdir) =>
|
|
1083
|
+
fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8').then(
|
|
1045
1084
|
(contents) => {
|
|
1046
|
-
const gitdir = ospath.join(worktreeGitdir, contents.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1085
|
+
const gitdir = ospath.join(worktreeGitdir, contents.trimEnd())
|
|
1086
|
+
const dir = ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
|
|
1087
|
+
const name = ospath.basename(worktreeGitdir)
|
|
1088
|
+
return Object.assign(repo, { dir, gitdir, worktree: { gitdir: worktreeGitdir, name } })
|
|
1050
1089
|
},
|
|
1051
1090
|
() => repo
|
|
1052
1091
|
)
|
|
1053
|
-
|
|
1092
|
+
)
|
|
1054
1093
|
}
|
|
1055
1094
|
|
|
1056
|
-
function findWorktrees (repo, patterns) {
|
|
1057
|
-
|
|
1058
|
-
const mainWorktree =
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1095
|
+
function findWorktrees (repo, patterns, useWorktree) {
|
|
1096
|
+
const useLinkedWorktree = !!useWorktree.linked
|
|
1097
|
+
const mainWorktree = useWorktree.main
|
|
1098
|
+
? getCurrentBranchName(repo).then((branch) => {
|
|
1099
|
+
if (!branch) return
|
|
1100
|
+
return [branch, { head: repo.dir, name: undefined, symbolicNames: useLinkedWorktree ? ['/.'] : ['/.', '.'] }]
|
|
1101
|
+
})
|
|
1102
|
+
: Promise.resolve()
|
|
1103
|
+
if (!(useLinkedWorktree || patterns.length)) return mainWorktree.then((entry) => new Map(entry && [entry]))
|
|
1104
|
+
const worktreesDir = ospath.join(repo.dir, repo.dir === repo.gitdir ? '' : '.git', 'worktrees')
|
|
1063
1105
|
const patternCache = repo.cache[REF_PATTERN_CACHE_KEY]
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
? fsp
|
|
1106
|
+
const scanWorktrees = patterns.length
|
|
1107
|
+
? fsp
|
|
1067
1108
|
.readdir(worktreesDir)
|
|
1068
1109
|
.then((worktreeNames) => filterRefs(worktreeNames, patterns, patternCache), invariably.emptyArray)
|
|
1069
|
-
.then((worktreeNames) =>
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1110
|
+
.then((worktreeNames) => {
|
|
1111
|
+
if (useLinkedWorktree && !~worktreeNames.indexOf(useWorktree.linked)) worktreeNames.push(useWorktree.linked)
|
|
1112
|
+
return worktreeNames
|
|
1113
|
+
})
|
|
1114
|
+
: Promise.resolve(useLinkedWorktree ? [useWorktree.linked] : [])
|
|
1115
|
+
return scanWorktrees
|
|
1116
|
+
.then((worktreeNames) =>
|
|
1117
|
+
Promise.all(
|
|
1118
|
+
worktreeNames.map((name) => {
|
|
1119
|
+
const symbolicNames = useLinkedWorktree && name === useWorktree.linked ? ['.', 'HEAD'] : undefined
|
|
1120
|
+
const gitdir = ospath.resolve(worktreesDir, name)
|
|
1121
|
+
// NOTE branch name defaults to worktree name if HEAD is detached
|
|
1122
|
+
return getCurrentBranchName(Object.assign({}, repo, { gitdir })).then((branch = name) =>
|
|
1123
|
+
fsp
|
|
1124
|
+
.readFile(ospath.join(gitdir, 'gitdir'), 'utf8')
|
|
1125
|
+
.then((contents) => [branch, { head: ospath.dirname(contents.trimEnd()), name, symbolicNames }])
|
|
1082
1126
|
)
|
|
1083
|
-
)
|
|
1084
|
-
|
|
1085
|
-
|
|
1127
|
+
})
|
|
1128
|
+
)
|
|
1129
|
+
)
|
|
1130
|
+
.then((entries) => new Map(entries))
|
|
1131
|
+
.then((worktrees) => mainWorktree.then((result) => (result ? worktrees.set(result[0], result[1]) : worktrees)))
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async function gracefulPromiseAllWithLimit (tasks, limit = Infinity) {
|
|
1135
|
+
const rejections = []
|
|
1136
|
+
const recordRejection = (err) => rejections.push(err) && undefined
|
|
1137
|
+
const started = []
|
|
1138
|
+
if (tasks.length <= limit) {
|
|
1139
|
+
for (const task of tasks) started.push(task().catch(recordRejection))
|
|
1140
|
+
} else {
|
|
1141
|
+
const pending = []
|
|
1142
|
+
for (const task of tasks) {
|
|
1143
|
+
const current = task()
|
|
1144
|
+
.catch(recordRejection)
|
|
1145
|
+
.finally(() => pending.splice(pending.indexOf(current), 1))
|
|
1146
|
+
started.push(current)
|
|
1147
|
+
if (pending.push(current) < limit) continue
|
|
1148
|
+
await Promise.race(pending)
|
|
1149
|
+
if (rejections.length) break
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return Promise.all(started).then((results) => [results, rejections])
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function promiseAllWithLimit (tasks, limit = Infinity) {
|
|
1156
|
+
if (tasks.length <= limit) return Promise.all(tasks.map((task) => task()))
|
|
1157
|
+
const started = []
|
|
1158
|
+
const pending = []
|
|
1159
|
+
for (const task of tasks) {
|
|
1160
|
+
const current = task().finally(() => pending.splice(pending.indexOf(current), 1))
|
|
1161
|
+
started.push(current)
|
|
1162
|
+
if (pending.push(current) >= limit) await Promise.race(pending)
|
|
1163
|
+
}
|
|
1164
|
+
return Promise.all(started)
|
|
1086
1165
|
}
|
|
1087
1166
|
|
|
1088
1167
|
module.exports = aggregateContent
|