git ssb

16+

Dominic / patchbay



Tree: 0764edcc78e29358a627ab3614462f998cfdc47b

Files: 0764edcc78e29358a627ab3614462f998cfdc47b / modules / git.js

8498 bytesRaw
1var h = require('hyperscript')
2var pull = require('pull-stream')
3var paramap = require('pull-paramap')
4var human = require('human-time')
5
6var plugs = require('../plugs')
7var message_link = plugs.first(exports.message_link = [])
8var message_confirm = plugs.first(exports.message_confirm = [])
9var sbot_links = plugs.first(exports.sbot_links = [])
10var sbot_links2 = plugs.first(exports.sbot_links2 = [])
11var sbot_get = plugs.first(exports.sbot_get = [])
12var getAvatar = require('ssb-avatar')
13var avatar_name = plugs.first(exports.avatar_name = [])
14var markdown = plugs.first(exports.markdown = [])
15
16var self_id = require('../keys').id
17
18function shortRefName(ref) {
19 return ref.replace(/^refs\/(heads|tags)\//, '')
20}
21
22function repoLink(id) {
23 var el = h('a', {href: '#'+id}, id.substr(0, 10) + '…')
24 getAvatar({links: sbot_links}, self_id, id, function (err, avatar) {
25 if(err) return console.error(err)
26 el.textContent = avatar.name
27 })
28 return el
29}
30
31function getIssueState(id, cb) {
32 pull(
33 sbot_links({dest: id, rel: 'issues', values: true, reverse: true}),
34 pull.map(function (msg) {
35 return msg.value.content.issues
36 }),
37 pull.flatten(),
38 pull.filter(function (issue) {
39 return issue.link === id
40 }),
41 pull.map(function (issue) {
42 return issue.merged ? 'merged' : issue.open ? 'open' : 'closed'
43 }),
44 pull.take(1),
45 pull.collect(function (err, updates) {
46 cb(err, updates && updates[0] || 'open')
47 })
48 )
49}
50
51//todo:
52function messageTimestampLink(msg) {
53 return h('a.timestamp', {
54 timestamp: msg.value.timestamp,
55 title: new Date(msg.value.timestamp),
56 href: '#'+msg.key
57 }, human(msg.value.timestamp))
58}
59
60function tableRows(headerRow) {
61 var thead = h('thead'), tbody = h('tbody')
62 var first = true
63 var t = [thead, tbody]
64 t.append = function (row) {
65 if (first) {
66 first = false
67 thead.appendChild(headerRow)
68 }
69 tbody.appendChild(row)
70 }
71 return t
72}
73
74function repoName(id, link) {
75 var el = link
76 ? h('a', {href: '#'+id}, id.substr(0, 8) + '…')
77 : h('ins', id.substr(0, 8) + '…')
78 getAvatar({links: sbot_links}, self_id, id, function (err, avatar) {
79 if(err) return console.error(err)
80 el.textContent = avatar.name
81 })
82 return el
83}
84
85exports.message_content = function (msg, sbot) {
86 var c = msg.value.content
87
88 if(c.type === 'git-repo') {
89 var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT
90 var forksT
91 var div = h('div',
92 h('p', 'git repo ', repoName(msg.key)),
93 c.upstream ? h('p', 'fork of ', repoName(c.upstream, true)) : '',
94 h('p', h('code', 'ssb://' + msg.key)),
95 h('div.git-table-wrapper', {style: {'max-height': '12em'}},
96 h('table',
97 branchesT = tableRows(h('tr',
98 h('th', 'branch'),
99 h('th', 'commit'),
100 h('th', 'last update'))),
101 tagsT = tableRows(h('tr',
102 h('th', 'tag'),
103 h('th', 'commit'),
104 h('th', 'last update'))))),
105 h('div.git-table-wrapper', {style: {'max-height': '16em'}},
106 h('table',
107 openIssuesT = tableRows(h('tr',
108 h('th', 'open issues'))),
109 closedIssuesT = tableRows(h('tr',
110 h('th', 'closed issues'))))),
111 h('div.git-table-wrapper', {style: {'max-height': '16em'}},
112 h('table',
113 openPRsT = tableRows(h('tr',
114 h('th', 'open pull requests'))),
115 closedPRsT = tableRows(h('tr',
116 h('th', 'closed pull requests'))))),
117 h('div.git-table-wrapper',
118 h('table',
119 forksT = tableRows(h('tr',
120 h('th', 'forks'))))))
121
122 // compute refs
123 var refs = {}
124 pull(
125 sbot_links({
126 reverse: true,
127 source: msg.value.author,
128 dest: msg.key,
129 rel: 'repo',
130 values: true
131 }),
132 pull.drain(function (link) {
133 var refUpdates = link.value.content.refs || {}
134 Object.keys(refUpdates).reverse().filter(function (ref) {
135 if (refs[ref]) return
136 refs[ref] = true
137 var rev = refUpdates[ref]
138 if (!rev) return
139 var parts = /^refs\/(heads|tags)\/(.*)$/.exec(ref) || []
140 var t
141 if (parts[1] === 'heads') t = branchesT
142 else if (parts[1] === 'tags') t = tagsT
143 if (t) t.append(h('tr',
144 h('td', parts[2]),
145 h('td', h('code', rev)),
146 h('td', messageTimestampLink(link))))
147 })
148 }, function (err) {
149 if (err) console.error(err)
150 })
151 )
152
153 // list issues and pull requests
154 pull(
155 sbot_links({
156 reverse: true,
157 dest: msg.key,
158 rel: 'project',
159 values: true
160 }),
161 paramap(function (link, cb) {
162 getIssueState(link.key, function (err, state) {
163 if(err) return cb(err)
164 link.state = state
165 cb(null, link)
166 })
167 }),
168 pull.drain(function (link) {
169 var c = link.value.content
170 var title = c.title || (c.text ? c.text.length > 70
171 ? c.text.substr(0, 70) + '…'
172 : c.text : link.key)
173 var author = link.value.author
174 var t = c.type === 'pull-request'
175 ? link.state === 'open' ? openPRsT : closedPRsT
176 : link.state === 'open' ? openIssuesT : closedIssuesT
177 t.append(h('tr',
178 h('td',
179 h('a', {href: '#'+link.key}, title), h('br'),
180 h('small',
181 'opened ', messageTimestampLink(link),
182 ' by ', h('a', {href: '#'+author}, avatar_name(author))))))
183 }, function (err) {
184 if (err) console.error(err)
185 })
186 )
187
188 // list forks
189 pull(
190 sbot_links({
191 reverse: true,
192 dest: msg.key,
193 rel: 'upstream'
194 }),
195 pull.drain(function (link) {
196 forksT.append(h('tr', h('td',
197 repoName(link.key, true),
198 ' by ', h('a', {href: '#'+link.source}, avatar_name(link.source)))))
199 }, function (err) {
200 if (err) console.error(err)
201 })
202 )
203
204 return div
205 }
206
207 if(c.type === 'git-update') {
208 return h('p',
209 'pushed to ',
210 repoLink(c.repo),
211 c.refs ? h('ul', Object.keys(c.refs).map(function (ref) {
212 var rev = c.refs[ref]
213 return h('li',
214 shortRefName(ref) + ': ',
215 rev ? h('code', rev) : h('em', 'deleted'))
216 })) : null,
217 Array.isArray(c.issues) ? c.issues.map(function (issue) {
218 if (issue.merged === true)
219 return ['Merged ', message_link(issue.link), ' in ',
220 h('code', issue.object), ' ', h('q', issue.label)]
221 if (issue.open === false)
222 return ['Closed ', message_link(issue.link), ' in ',
223 h('code', issue.object), ' ', h('q', issue.label)]
224 }) : null
225 )
226 }
227
228 if(c.type === 'issue-edit') {
229 return h('div',
230 0, false, null, undefined, '', 'ok',
231 c.title ? h('p', 'renamed issue ', message_link(c.issue),
232 ' to ', h('ins', c.title)) : null,
233 c.open === false ? h('p', 'closed issue ', message_link(c.issue)) : null,
234 c.open === true ? h('p', 'reopened issue ', message_link(c.issue)) : '',
235 c.issues ? c.issues : null
236 )
237 }
238
239 if (c.type === 'issue') {
240 return h('div',
241 h('p', 'opened issue on ', repoLink(c.project)),
242 c.title ? h('h4', c.title) : '',
243 markdown(c)
244 )
245 }
246
247 if (c.type === 'pull-request') {
248 return h('div',
249 h('p', 'opened pull-request ',
250 'to ', repoLink(c.repo), ':', c.branch, ' ',
251 'from ', repoLink(c.head_repo), ':', c.head_branch),
252 c.title ? h('h4', c.title) : '',
253 markdown(c)
254 )
255 }
256}
257
258exports.message_meta = function (msg, sbot) {
259 var type = msg.value.content.type
260 if (type == 'issue' || type == 'pull-request') {
261 var el = h('em', '...')
262 getIssueState(msg.key, function (err, state) {
263 if (err) return console.error(err)
264 el.textContent = state
265 })
266 return el
267 }
268}
269
270exports.message_action = function (msg, sbot) {
271 var c = msg.value.content
272 if(c.type === 'issue' || c.type === 'pull-request') {
273 var isOpen
274 var a = h('a', {href: '#', onclick: function () {
275 message_confirm({
276 type: 'issue-edit',
277 issues: [{
278 link: msg.key,
279 open: !isOpen
280 }]
281 })
282 }})
283 getIssueState(msg.key, function (err, state) {
284 if (err) return console.error(err)
285 isOpen = state === 'open'
286 a.textContent = isOpen ? 'Close' : 'Reopen'
287 })
288 return a
289 }
290}
291
292

Built with git-ssb-web