git ssb

16+

Dominic / patchbay



Tree: a2b4868abcd60a593d913223d3af542f4c8e95b2

Files: a2b4868abcd60a593d913223d3af542f4c8e95b2 / modules / git.js

9010 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 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
62function 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
76function 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
87function 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
96exports.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.issues) ? c.issues.map(function (issue) {
229 if (issue.merged === true)
230 return ['Merged ', message_link(issue.link), ' in ',
231 h('code', issue.object), ' ', h('q', issue.label)]
232 if (issue.open === false)
233 return ['Closed ', message_link(issue.link), ' in ',
234 h('code', issue.object), ' ', h('q', issue.label)]
235 }) : null
236 )
237 }
238
239 if(c.type === 'issue-edit') {
240 return h('div',
241 c.issue ? renderIssueEdit(c) : null,
242 c.issues ? c.issues.map(renderIssueEdit) : null)
243 }
244
245 if(c.type === 'issue') {
246 return h('div',
247 h('p', 'opened issue on ', repoLink(c.project)),
248 c.title ? h('h4', c.title) : '',
249 markdown(c)
250 )
251 }
252
253 if(c.type === 'pull-request') {
254 return h('div',
255 h('p', 'opened pull-request ',
256 'to ', repoLink(c.repo), ':', c.branch, ' ',
257 'from ', repoLink(c.head_repo), ':', c.head_branch),
258 c.title ? h('h4', c.title) : '',
259 markdown(c)
260 )
261 }
262}
263
264exports.message_meta = function (msg, sbot) {
265 var type = msg.value.content.type
266 if (type === 'issue' || type === 'pull-request') {
267 var el = h('em', '...')
268 // TODO: update if issue is changed
269 getIssueState(msg.key, function (err, state) {
270 if (err) return console.error(err)
271 el.textContent = state
272 })
273 return el
274 }
275}
276
277exports.message_action = function (msg, sbot) {
278 var c = msg.value.content
279 if(c.type === 'issue' || c.type === 'pull-request') {
280 var isOpen
281 var a = h('a', {href: '#', onclick: function () {
282 message_confirm({
283 type: 'issue-edit',
284 root: msg.key,
285 issues: [{
286 link: msg.key,
287 open: !isOpen
288 }]
289 }, function (err, msg) {
290 if(err) return alert(err)
291 if(!msg) return
292 isOpen = msg.value.content.open
293 update()
294 })
295 }})
296 getIssueState(msg.key, function (err, state) {
297 if (err) return console.error(err)
298 isOpen = state === 'open'
299 update()
300 })
301 function update() {
302 a.textContent = c.type === 'pull-request'
303 ? isOpen ? 'Close Pull Request' : 'Reopen Pull Request'
304 : isOpen ? 'Close Issue' : 'Reopen Issue'
305 }
306 return a
307 }
308}
309
310

Built with git-ssb-web