git ssb

30+

cel / git-ssb-web



Tree: fbf36ab9f3e81a1c5b6e0b4c6eed51005e6e07ab

Files: fbf36ab9f3e81a1c5b6e0b4c6eed51005e6e07ab / lib / repos / pulls.js

16125 bytesRaw
1var pull = require('pull-stream')
2var paramap = require('pull-paramap')
3var cat = require('pull-cat')
4var many = require('pull-many')
5var multicb = require('multicb')
6var GitRepo = require('pull-git-repo')
7var u = require('../../lib/util')
8var markdown = require('../../lib/markdown')
9var forms = require('../../lib/forms')
10var ssbRef = require('ssb-ref')
11
12module.exports = function (repoRoutes, web) {
13 return new RepoPullReqRoutes(repoRoutes, web)
14}
15
16function RepoPullReqRoutes(repoRoutes, web) {
17 this.repo = repoRoutes
18 this.web = web
19}
20
21var P = RepoPullReqRoutes.prototype
22
23/* Pull Request */
24
25P.serveRepoPullReq = function (req, repo, pr, path, postId) {
26 var self = this
27 var headRepo, authorLink
28 var page = path[0] || 'activity'
29 var title = u.escape(pr.title) + ' · %{author}/%{repo}'
30 return self.repo.renderRepoPage(req, repo, 'pulls', null, title, cat([
31 pull.once('<div class="pull-request">' +
32 forms.name(req, !self.web.isPublic, pr.id, pr.title,
33 'issue-title', null, req._t('pullRequest.Rename'),
34 '<h3>' + u.link([pr.id], pr.title) + '</h3>') +
35 '<code>' + pr.id + '</code>'),
36 u.readOnce(function (cb) {
37 var done = multicb({ pluck: 1, spread: true })
38 var gotHeadRepo = done()
39 self.web.about.getName(pr.author, done())
40 var sameRepo = (pr.headRepo == pr.baseRepo)
41 self.web.getRepo(pr.headRepo, function (err, headRepo) {
42 if (err) return cb(err)
43 self.web.getRepoName(headRepo.feed, headRepo.id, done())
44 self.web.about.getName(headRepo.feed, done())
45 gotHeadRepo(null, GitRepo(headRepo))
46 })
47
48 done(function (err, _headRepo, issueAuthorName,
49 headRepoName, headRepoAuthorName) {
50 if (err) return cb(err)
51 headRepo = _headRepo
52 authorLink = u.link([pr.author], issueAuthorName)
53 var repoLink = u.link([pr.headRepo], headRepoName)
54 var headRepoAuthorLink = u.link([headRepo.feed], headRepoAuthorName)
55 var headRepoLink = u.link([headRepo.id], headRepoName)
56 var headBranchLink = u.link([headRepo.id, 'tree', pr.headBranch])
57 var baseBranchLink = u.link([repo.id, 'tree', pr.baseBranch])
58 cb(null, '<section class="collapse">' +
59 '<strong class="issue-status ' +
60 (pr.open ? 'open' : 'closed') + '">' +
61 req._t(pr.open ? 'issue.state.Open' : 'issue.state.Closed') +
62 '</strong> ' +
63 req._t('pullRequest.WantToMerge', {
64 name: authorLink,
65 base: '<code>' + baseBranchLink + '</code>',
66 head: (sameRepo ?
67 '<code>' + headBranchLink + '</code>' :
68 '<code class="bgslash">' +
69 headRepoAuthorLink + ' / ' +
70 headRepoLink + ' / ' +
71 headBranchLink + '</code>')
72 }) + '</section>')
73 })
74 }),
75 pull.once(
76 u.nav([
77 [[pr.id], req._t('Discussion'), 'activity'],
78 [[pr.id, 'commits'], req._t('Commits'), 'commits'],
79 [[pr.id, 'files'], req._t('Files'), 'files']
80 ], page)),
81 u.readNext(function (cb) {
82 if (page == 'commits')
83 self.renderPullReqCommits(req, pr, repo, headRepo, cb)
84 else if (page == 'files')
85 self.renderPullReqFiles(req, pr, repo, headRepo, cb)
86 else cb(null,
87 self.renderPullReqActivity(req, pr, repo, headRepo, authorLink, postId))
88 })
89 ]))
90}
91
92P.renderPullReqCommits = function (req, pr, baseRepo, headRepo, cb) {
93 var self = this
94 self.web.pullReqs.getRevs(pr.id, function (err, revs) {
95 if (err) return cb(null, self.web.renderError(err))
96 cb(null, cat([
97 pull.once('<section>'),
98 self.renderCommitLog(req, baseRepo, revs.base, headRepo, revs.head),
99 pull.once('</section>')
100 ]))
101 })
102}
103
104P.renderPullReqFiles = function (req, pr, baseRepo, headRepo, cb) {
105 var self = this
106 self.web.pullReqs.getRevs(pr.id, function (err, revs) {
107 if (err) return cb(null, self.web.renderError(err))
108 cb(null, cat([
109 pull.once('<section>'),
110 self.repo.renderDiffStat(req,
111 [baseRepo, headRepo], [revs.base, revs.head]),
112 pull.once('</section>')
113 ]))
114 })
115}
116
117P.renderPullReqActivity = function (req, pr, repo, headRepo, authorLink, postId) {
118 var self = this
119 var msgTimeLink = u.link([pr.id],
120 new Date(pr.created_at).toLocaleString(req._locale))
121 var newestMsg = {key: pr.id, value: {timestamp: pr.created_at}}
122 var isAuthor = (self.web.myId == pr.author) || (self.web.myId == repo.feed)
123 return cat([
124 u.readOnce(function (cb) {
125 cb(null,
126 '<section class="collapse">' +
127 authorLink + ' &middot; ' + msgTimeLink +
128 markdown(pr.text, repo) + '</section>')
129 }),
130 // render posts, edits, and updates
131 pull(
132 many([
133 self.web.ssb.links({
134 dest: pr.id,
135 values: true
136 }),
137 u.readNext(function (cb) {
138 cb(null, pull(
139 self.web.ssb.links({
140 dest: headRepo.id,
141 source: headRepo.feed,
142 rel: 'repo',
143 values: true,
144 reverse: true
145 }),
146 pull.take(function (link) {
147 return link.value.timestamp > pr.created_at
148 }),
149 pull.filter(function (link) {
150 return link.value.content.type == 'git-update'
151 && ('refs/heads/' + pr.headBranch) in link.value.content.refs
152 })
153 ))
154 })
155 ]),
156 self.web.addAuthorName(),
157 pull.unique('key'),
158 pull.through(function (msg) {
159 if (msg.value.timestamp > newestMsg.value.timestamp)
160 newestMsg = msg
161 }),
162 u.sortMsgs(),
163 pull.map(function (item) {
164 if (item.value.content.type == 'git-update')
165 return self.renderBranchUpdate(req, pr, item)
166 return self.repo.issues.renderIssueActivityMsg(req, repo, pr,
167 req._t('pull request'), postId, item)
168 })
169 ),
170 !self.web.isPublic && isAuthor && pr.open && pull.once(
171 '<section class="merge-instructions">' +
172 '<input type="checkbox" class="toggle" id="merge-instructions"/>' +
173 '<h4><label for="merge-instructions" class="toggle-link"><a>' +
174 req._t('mergeInstructions.MergeViaCmdLine') +
175 '</a></label></h4>' +
176 '<div class="contents">' +
177 '<p>' + req._t('mergeInstructions.CheckOut') + '</p>' +
178 '<pre>' +
179 'git fetch ssb://' + u.escape(pr.headRepo) + ' ' +
180 u.escape(pr.headBranch) + '\n' +
181 'git checkout -b ' + u.escape(pr.headBranch) + ' FETCH_HEAD' +
182 '</pre>' +
183 '<p>' + req._t('mergeInstructions.MergeAndPush') + '</p>' +
184 '<pre>' +
185 'git checkout ' + u.escape(pr.baseBranch) + '\n' +
186 'git merge ' + u.escape(pr.headBranch) + '\n' +
187 'git push ssb ' + u.escape(pr.baseBranch) +
188 '</pre>' +
189 '</div></section>'),
190 !self.web.isPublic && u.readOnce(function (cb) {
191 cb(null, forms.issueComment(req, pr, repo, newestMsg.key,
192 isAuthor, req._t('pull request')))
193 })
194 ])
195}
196
197P.renderBranchUpdate = function (req, pr, msg) {
198 var authorLink = u.link([msg.value.author], msg.authorName)
199 var msgLink = u.link([msg.key],
200 new Date(msg.value.timestamp).toLocaleString(req._locale))
201 var rev = msg.value.content.refs['refs/heads/' + pr.headBranch]
202 if (!rev)
203 return '<section class="collapse">' +
204 req._t('NameDeletedBranch', {
205 name: authorLink,
206 branch: '<code>' + pr.headBranch + '</code>'
207 }) + ' &middot; ' + msgLink +
208 '</section>'
209
210 var revLink = u.link([pr.headRepo, 'commit', rev], rev.substr(0, 8))
211 return '<section class="collapse">' +
212 req._t('NameUpdatedBranch', {
213 name: authorLink,
214 rev: '<code>' + revLink + '</code>'
215 }) + ' &middot; ' + msgLink +
216 '</section>'
217}
218
219/* Compare changes */
220
221P.branchMenu = function (repo, name, currentName) {
222 return cat([
223 pull.once('<select name="' + name + '">'),
224 pull(
225 repo.refs(),
226 pull.map(function (ref) {
227 var m = ref.name.match(/^refs\/([^\/]*)\/(.*)$/) || [,, ref.name]
228 return m[1] == 'heads' && m[2]
229 }),
230 pull.filter(Boolean),
231 u.pullSort(),
232 pull.map(this.repo.formatRevOptions(currentName))
233 ),
234 pull.once('</select>')
235 ])
236}
237
238P.serveRepoCompare = function (req, repo) {
239 var self = this
240 var query = req._u.query
241 var base
242 var count = 0
243 var title = req._t('CompareChanges') + ' · %{author}/%{repo}'
244
245 return self.repo.renderRepoPage(req, repo, 'pulls', null, title, cat([
246 pull.once('<h3>' + req._t('CompareChanges') + '</h3>' +
247 '<form action="' + u.encodeLink(repo.id) + '/comparing" method="get">' +
248 '<section>'),
249 pull.once(req._t('BaseBranch') + ': '),
250 u.readNext(function (cb) {
251 if (query.base) gotBase(null, query.base)
252 else repo.getSymRef('HEAD', true, gotBase)
253 function gotBase(err, ref) {
254 if (err) return cb(err)
255 cb(null, self.branchMenu(repo, 'base', base = ref || 'HEAD'))
256 }
257 }),
258 pull.once('<br/>' + req._t('ComparisonRepoBranch') + ':'),
259 pull(
260 self.repo.getForks(repo, true),
261 pull.asyncMap(function (msg, cb) {
262 self.web.getRepo(msg.key, function (err, repo) {
263 if (err) return cb(err)
264 cb(null, {
265 msg: msg,
266 repo: repo
267 })
268 })
269 }),
270 pull.map(renderFork),
271 pull.flatten()
272 ),
273 pull.once('<div class="bgslash">' +
274 '<input type="radio" name="head" value="other" id="other-radio"> ' +
275 '<label for="other-radio">other</label>: ' +
276 '<input name="other_repo" placeholder="repo id"> / ' +
277 '<input name="other_branch" placeholder="branch"></div>'),
278 pull.once('</section>'),
279 u.readOnce(function (cb) {
280 cb(null,
281 '<button type="submit" class="btn">' +
282 req._t('Compare') + '</button>')
283 }),
284 pull.once('</form>')
285 ]))
286
287 function renderFork(fork) {
288 return pull(
289 fork.repo.refs({inherited: false}),
290 pull.map(function (ref) {
291 var m = /^refs\/([^\/]*)\/(.*)$/.exec(ref.name) || [,ref.name]
292 return {
293 type: m[1],
294 name: m[2],
295 value: ref.value
296 }
297 }),
298 pull.filter(function (ref) {
299 return ref.type == 'heads'
300 && !(ref.name == base && fork.msg.key == repo.id)
301 }),
302 pull.map(function (ref) {
303 var branchLink = u.link([fork.msg.key, 'tree', ref.name], ref.name)
304 var authorLink = u.link([fork.msg.value.author], fork.msg.authorName)
305 var repoLink = u.link([fork.msg.key], fork.msg.repoName)
306 var value = fork.msg.key + ':' + ref.name
307 return '<div class="bgslash">' +
308 '<input type="radio" name="head"' +
309 ' value="' + u.escape(value) + '"' +
310 (query.head == value ? ' checked="checked"' : '') + '> ' +
311 authorLink + ' / ' + repoLink + ' / ' + branchLink + '</div>'
312 })
313 )
314 }
315}
316
317P.serveRepoComparing = function (req, repo) {
318 var self = this
319 var query = req._u.query
320 var baseBranch = query.base
321 var headRepoId, headBranch
322
323 if (query.head === 'other') {
324 headRepoId = String(query.other_repo).replace(/^ssb:\/*/, '')
325 headBranch = String(query.other_branch)
326 } else if (query.head) {
327 var s = String(query.head).split(':')
328 headRepoId = s[0]
329 headBranch = s[1]
330 }
331
332 if (!baseBranch)
333 return self.web.serveRedirect(req, u.encodeLink([repo.id, 'compare']))
334 if (!ssbRef.isMsgId(headRepoId))
335 return self.web.serveError(req, new Error('bad repo id'), 400)
336
337 var baseLink = u.link([repo.id, 'tree', baseBranch])
338 var headBranchLink = u.link([headRepoId, 'tree', headBranch])
339 var backHref = u.encodeLink([repo.id, 'compare']) + req._u.search
340 var title = req._t(query.expand ? 'OpenPullRequest': 'ComparingChanges')
341 var pageTitle = title + ' · %{author}/%{repo}'
342
343 return self.repo.renderRepoPage(req, repo, 'pulls', null, pageTitle, cat([
344 pull.once('<h3>' + title + '</h3>'),
345 u.readNext(function (cb) {
346 self.web.getRepo(headRepoId, function (err, headRepo) {
347 if (err) return cb(err)
348 self.web.getRepoFullName(headRepo.feed, headRepo.id,
349 function (err, repoName, authorName) {
350 if (err) return cb(err)
351 cb(null, renderRepoInfo(GitRepo(headRepo), repoName, authorName))
352 }
353 )
354 })
355 })
356 ]))
357
358 function renderRepoInfo(headRepo, headRepoName, headRepoAuthorName) {
359 var authorLink = u.link([headRepo.feed], headRepoAuthorName)
360 var repoLink = u.link([headRepoId], headRepoName)
361 return cat([
362 pull.once('<section>' +
363 req._t('Base') + ': ' + baseLink + '<br/>' +
364 req._t('Head') + ': ' +
365 '<span class="bgslash">' + authorLink + ' / ' + repoLink +
366 ' / ' + headBranchLink + '</span>' +
367 '</section>' +
368 (query.expand ? '<section><form method="post" action="">' +
369 u.hiddenInputs({
370 action: 'new-pull',
371 branch: baseBranch,
372 head_repo: headRepoId,
373 head_branch: headBranch
374 }) +
375 '<input class="wide-input" name="title"' +
376 ' placeholder="' + req._t('Title') + '" size="77"/>' +
377 forms.post(req, repo, req._t('Description'), 8) +
378 '<button type="submit" class="btn open">' +
379 req._t('Create') + '</button>' +
380 '</form></section>'
381 : self.web.isPublic ? ''
382 : '<section><form method="get" action="">' +
383 u.hiddenInputs({
384 base: baseBranch,
385 head: query.head,
386 other_repo: query.other_repo,
387 other_branch: query.other_branch,
388 }) +
389 '<button class="btn open" type="submit" name="expand" value="1">' +
390 '<i>⎇</i> ' + req._t('CreatePullRequest') + '</button> ' +
391 '<a href="' + backHref + '">' + req._t('Back') + '</a>' +
392 '</form></section>') +
393 '<div id="commits"></div>' +
394 '<div class="tab-links">' +
395 '<a href="#" id="files-link">' + req._t('FilesChanged') + '</a> ' +
396 '<a href="#commits" id="commits-link">' +
397 req._t('Commits') + '</a>' +
398 '</div>'),
399 u.readNext(function (cb) {
400 GitRepo.getMergeBase(repo, baseBranch, headRepo, headBranch,
401 function (err, concestor) {
402 if (err) return cb(err)
403 cb(null, cat([
404 pull.once('<section id="files-tab">'),
405 self.repo.renderDiffStat(req, [repo, headRepo],
406 [concestor, headBranch]),
407 pull.once('</section>' +
408 '<section id="commits-tab">'),
409 self.renderCommitLog(req, repo, concestor, headRepo, headBranch),
410 pull.once('</section>')
411 ]))
412 }
413 )
414 })
415 ])
416 }
417}
418
419P.renderCommitLog = function (req, baseRepo, baseBranch, headRepo, headBranch) {
420 return cat([
421 pull.once('<table class="compare-commits">'),
422 u.readNext(function (cb) {
423 baseRepo.resolveRef(baseBranch, function (err, baseBranchRev) {
424 if (err) return cb(err)
425 var currentDay
426 return cb(null, pull(
427 headRepo.readLog(headBranch),
428 pull.take(function (rev) { return rev != baseBranchRev }),
429 u.pullReverse(),
430 paramap(headRepo.getCommitParsed.bind(headRepo), 8),
431 pull.map(function (commit) {
432 var commitPath = [headRepo.id, 'commit', commit.id]
433 var commitIdShort = '<tt>' + commit.id.substr(0, 8) + '</tt>'
434 var day = Math.floor(commit.author.date / 86400000)
435 var dateRow = day == currentDay ? '' :
436 '<tr><th colspan=3 class="date-info">' +
437 commit.author.date.toLocaleDateString(req._locale) +
438 '</th><tr>'
439 currentDay = day
440 return dateRow + '<tr>' +
441 '<td>' + u.escape(commit.author.name) + '</td>' +
442 '<td>' + u.link(commitPath, commit.title) + '</td>' +
443 '<td>' + u.link(commitPath, commitIdShort, true) + '</td>' +
444 '</tr>'
445 })
446 ))
447 })
448 }),
449 pull.once('</table>')
450 ])
451}
452

Built with git-ssb-web