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