[6a3a178] | 1 | // turn an array of lines from `git ls-remote` into a thing
|
---|
| 2 | // vaguely resembling a packument, where docs are a resolved ref
|
---|
| 3 |
|
---|
| 4 | const semver = require('semver')
|
---|
| 5 |
|
---|
| 6 | module.exports = lines => finish(lines.reduce(linesToRevsReducer, {
|
---|
| 7 | versions: {},
|
---|
| 8 | 'dist-tags': {},
|
---|
| 9 | refs: {},
|
---|
| 10 | shas: {}
|
---|
| 11 | }))
|
---|
| 12 |
|
---|
| 13 | const finish = revs => distTags(shaList(peelTags(revs)))
|
---|
| 14 |
|
---|
| 15 | // We can check out shallow clones on specific SHAs if we have a ref
|
---|
| 16 | const shaList = revs => {
|
---|
| 17 | Object.keys(revs.refs).forEach(ref => {
|
---|
| 18 | const doc = revs.refs[ref]
|
---|
| 19 | if (!revs.shas[doc.sha]) {
|
---|
| 20 | revs.shas[doc.sha] = [ref]
|
---|
| 21 | } else {
|
---|
| 22 | revs.shas[doc.sha].push(ref)
|
---|
| 23 | }
|
---|
| 24 | })
|
---|
| 25 | return revs
|
---|
| 26 | }
|
---|
| 27 |
|
---|
| 28 | // Replace any tags with their ^{} counterparts, if those exist
|
---|
| 29 | const peelTags = revs => {
|
---|
| 30 | Object.keys(revs.refs).filter(ref => ref.endsWith('^{}')).forEach(ref => {
|
---|
| 31 | const peeled = revs.refs[ref]
|
---|
| 32 | const unpeeled = revs.refs[ref.replace(/\^\{\}$/, '')]
|
---|
| 33 | if (unpeeled) {
|
---|
| 34 | unpeeled.sha = peeled.sha
|
---|
| 35 | delete revs.refs[ref]
|
---|
| 36 | }
|
---|
| 37 | })
|
---|
| 38 | return revs
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | const distTags = revs => {
|
---|
| 42 | // not entirely sure what situations would result in an
|
---|
| 43 | // ichabod repo, but best to be careful in Sleepy Hollow anyway
|
---|
| 44 | const HEAD = revs.refs.HEAD || /* istanbul ignore next */ {}
|
---|
| 45 | const versions = Object.keys(revs.versions)
|
---|
| 46 | versions.forEach(v => {
|
---|
| 47 | // simulate a dist-tags with latest pointing at the
|
---|
| 48 | // 'latest' branch if one exists and is a version,
|
---|
| 49 | // or HEAD if not.
|
---|
| 50 | const ver = revs.versions[v]
|
---|
| 51 | if (revs.refs.latest && ver.sha === revs.refs.latest.sha) {
|
---|
| 52 | revs['dist-tags'].latest = v
|
---|
| 53 | } else if (ver.sha === HEAD.sha) {
|
---|
| 54 | revs['dist-tags'].HEAD = v
|
---|
| 55 | if (!revs.refs.latest) { revs['dist-tags'].latest = v }
|
---|
| 56 | }
|
---|
| 57 | })
|
---|
| 58 | return revs
|
---|
| 59 | }
|
---|
| 60 |
|
---|
| 61 | const refType = ref => {
|
---|
| 62 | if (ref.startsWith('refs/tags/')) {
|
---|
| 63 | return 'tag'
|
---|
| 64 | }
|
---|
| 65 | if (ref.startsWith('refs/heads/')) {
|
---|
| 66 | return 'branch'
|
---|
| 67 | }
|
---|
| 68 | if (ref.startsWith('refs/pull/')) {
|
---|
| 69 | return 'pull'
|
---|
| 70 | }
|
---|
| 71 | if (ref === 'HEAD') {
|
---|
| 72 | return 'head'
|
---|
| 73 | }
|
---|
| 74 | // Could be anything, ignore for now
|
---|
| 75 | /* istanbul ignore next */
|
---|
| 76 | return 'other'
|
---|
| 77 | }
|
---|
| 78 |
|
---|
| 79 | // return the doc, or null if we should ignore it.
|
---|
| 80 | const lineToRevDoc = line => {
|
---|
| 81 | const split = line.trim().split(/\s+/, 2)
|
---|
| 82 | if (split.length < 2) { return null }
|
---|
| 83 |
|
---|
| 84 | const sha = split[0].trim()
|
---|
| 85 | const rawRef = split[1].trim()
|
---|
| 86 | const type = refType(rawRef)
|
---|
| 87 |
|
---|
| 88 | if (type === 'tag') {
|
---|
| 89 | // refs/tags/foo^{} is the 'peeled tag', ie the commit
|
---|
| 90 | // that is tagged by refs/tags/foo they resolve to the same
|
---|
| 91 | // content, just different objects in git's data structure.
|
---|
| 92 | // But, we care about the thing the tag POINTS to, not the tag
|
---|
| 93 | // object itself, so we only look at the peeled tag refs, and
|
---|
| 94 | // ignore the pointer.
|
---|
| 95 | // For now, though, we have to save both, because some tags
|
---|
| 96 | // don't have peels, if they were not annotated.
|
---|
| 97 | const ref = rawRef.substr('refs/tags/'.length)
|
---|
| 98 | return { sha, ref, rawRef, type }
|
---|
| 99 | }
|
---|
| 100 |
|
---|
| 101 | if (type === 'branch') {
|
---|
| 102 | const ref = rawRef.substr('refs/heads/'.length)
|
---|
| 103 | return { sha, ref, rawRef, type }
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | if (type === 'pull') {
|
---|
| 107 | // NB: merged pull requests installable with #pull/123/merge
|
---|
| 108 | // for the merged pr, or #pull/123 for the PR head
|
---|
| 109 | const ref = rawRef.substr('refs/'.length).replace(/\/head$/, '')
|
---|
| 110 | return { sha, ref, rawRef, type }
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | if (type === 'head') {
|
---|
| 114 | const ref = 'HEAD'
|
---|
| 115 | return { sha, ref, rawRef, type }
|
---|
| 116 | }
|
---|
| 117 |
|
---|
| 118 | // at this point, all we can do is leave the ref un-munged
|
---|
| 119 | return { sha, ref: rawRef, rawRef, type }
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | const linesToRevsReducer = (revs, line) => {
|
---|
| 123 | const doc = lineToRevDoc(line)
|
---|
| 124 |
|
---|
| 125 | if (!doc) { return revs }
|
---|
| 126 |
|
---|
| 127 | revs.refs[doc.ref] = doc
|
---|
| 128 | revs.refs[doc.rawRef] = doc
|
---|
| 129 |
|
---|
| 130 | if (doc.type === 'tag') {
|
---|
| 131 | // try to pull a semver value out of tags like `release-v1.2.3`
|
---|
| 132 | // which is a pretty common pattern.
|
---|
| 133 | const match = !doc.ref.endsWith('^{}') &&
|
---|
| 134 | doc.ref.match(/v?(\d+\.\d+\.\d+(?:[-+].+)?)$/)
|
---|
| 135 | if (match && semver.valid(match[1], true)) {
|
---|
| 136 | revs.versions[semver.clean(match[1], true)] = doc
|
---|
| 137 | }
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | return revs
|
---|
| 141 | }
|
---|