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 4const semver = require('semver') 5 6module.exports = lines => finish(lines.reduce(linesToRevsReducer, { 7 versions: {}, 8 'dist-tags': {}, 9 refs: {}, 10 shas: {}, 11})) 12 13const finish = revs => distTags(shaList(peelTags(revs))) 14 15// We can check out shallow clones on specific SHAs if we have a ref 16const 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 29const 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 41const 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) { 56 revs['dist-tags'].latest = v 57 } 58 } 59 }) 60 return revs 61} 62 63const refType = ref => { 64 if (ref.startsWith('refs/tags/')) { 65 return 'tag' 66 } 67 if (ref.startsWith('refs/heads/')) { 68 return 'branch' 69 } 70 if (ref.startsWith('refs/pull/')) { 71 return 'pull' 72 } 73 if (ref === 'HEAD') { 74 return 'head' 75 } 76 // Could be anything, ignore for now 77 /* istanbul ignore next */ 78 return 'other' 79} 80 81// return the doc, or null if we should ignore it. 82const lineToRevDoc = line => { 83 const split = line.trim().split(/\s+/, 2) 84 if (split.length < 2) { 85 return null 86 } 87 88 const sha = split[0].trim() 89 const rawRef = split[1].trim() 90 const type = refType(rawRef) 91 92 if (type === 'tag') { 93 // refs/tags/foo^{} is the 'peeled tag', ie the commit 94 // that is tagged by refs/tags/foo they resolve to the same 95 // content, just different objects in git's data structure. 96 // But, we care about the thing the tag POINTS to, not the tag 97 // object itself, so we only look at the peeled tag refs, and 98 // ignore the pointer. 99 // For now, though, we have to save both, because some tags 100 // don't have peels, if they were not annotated. 101 const ref = rawRef.slice('refs/tags/'.length) 102 return { sha, ref, rawRef, type } 103 } 104 105 if (type === 'branch') { 106 const ref = rawRef.slice('refs/heads/'.length) 107 return { sha, ref, rawRef, type } 108 } 109 110 if (type === 'pull') { 111 // NB: merged pull requests installable with #pull/123/merge 112 // for the merged pr, or #pull/123 for the PR head 113 const ref = rawRef.slice('refs/'.length).replace(/\/head$/, '') 114 return { sha, ref, rawRef, type } 115 } 116 117 if (type === 'head') { 118 const ref = 'HEAD' 119 return { sha, ref, rawRef, type } 120 } 121 122 // at this point, all we can do is leave the ref un-munged 123 return { sha, ref: rawRef, rawRef, type } 124} 125 126const linesToRevsReducer = (revs, line) => { 127 const doc = lineToRevDoc(line) 128 129 if (!doc) { 130 return revs 131 } 132 133 revs.refs[doc.ref] = doc 134 revs.refs[doc.rawRef] = doc 135 136 if (doc.type === 'tag') { 137 // try to pull a semver value out of tags like `release-v1.2.3` 138 // which is a pretty common pattern. 139 const match = !doc.ref.endsWith('^{}') && 140 doc.ref.match(/v?(\d+\.\d+\.\d+(?:[-+].+)?)$/) 141 if (match && semver.valid(match[1], true)) { 142 revs.versions[semver.clean(match[1], true)] = doc 143 } 144 } 145 146 return revs 147} 148