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