##// END OF EJS Templates
pull requests: use branch name when creating PRs from a changelog with branch filter
Mads Kiilerich -
r3813:dca89d57 beta
parent child Browse files
Show More
@@ -1,546 +1,560
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27 import formencode
28 28
29 29 from webob.exc import HTTPNotFound, HTTPForbidden
30 30 from collections import defaultdict
31 31 from itertools import groupby
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 40 NotAnonymous
41 41 from rhodecode.lib.helpers import Page
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib import diffs
44 44 from rhodecode.lib.utils import action_logger, jsonify
45 45 from rhodecode.lib.vcs.utils import safe_str
46 46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48 from rhodecode.lib.diffs import LimitedDiffContainer
49 49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 50 ChangesetComment
51 51 from rhodecode.model.pull_request import PullRequestModel
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.repo import RepoModel
54 54 from rhodecode.model.comment import ChangesetCommentsModel
55 55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 56 from rhodecode.model.forms import PullRequestForm
57 57 from mercurial import scmutil
58 58 from rhodecode.lib.utils2 import safe_int
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class PullrequestsController(BaseRepoController):
64 64
65 65 def __before__(self):
66 66 super(PullrequestsController, self).__before__()
67 67 repo_model = RepoModel()
68 68 c.users_array = repo_model.get_users_js()
69 69 c.users_groups_array = repo_model.get_users_groups_js()
70 70
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
72 72 """return a structure with repo's interesting changesets, suitable for
73 73 the selectors in pullrequest.html
74 74
75 rev: a revision that must be in the list and selected by default
75 rev: a revision that must be in the list somehow and selected by default
76 branch: a branch that must be in the list and selected by default - even if closed
76 77 branch_rev: a revision of which peers should be preferred and available."""
77 78 # list named branches that has been merged to this named branch - it should probably merge back
78 79 peers = []
79 80
80 81 if rev:
81 82 rev = safe_str(rev)
82 83
84 if branch:
85 branch = safe_str(branch)
86
83 87 if branch_rev:
84 88 branch_rev = safe_str(branch_rev)
85 89 # not restricting to merge() would also get branch point and be better
86 90 # (especially because it would get the branch point) ... but is currently too expensive
87 91 otherbranches = {}
88 92 for i in repo._repo.revs(
89 93 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
90 94 branch_rev, branch_rev):
91 95 cs = repo.get_changeset(i)
92 96 otherbranches[cs.branch] = cs.raw_id
93 97 for abranch, node in otherbranches.iteritems():
94 98 selected = 'branch:%s:%s' % (abranch, node)
95 99 peers.append((selected, abranch))
96 100
97 101 selected = None
98 102
99 103 branches = []
100 104 for abranch, branchrev in repo.branches.iteritems():
101 105 n = 'branch:%s:%s' % (abranch, branchrev)
102 106 branches.append((n, abranch))
103 107 if rev == branchrev:
104 108 selected = n
109 if branch == abranch:
110 selected = n
111 branch = None
112 if branch: # branch not in list - it is probably closed
113 revs = repo._repo.revs('max(branch(%s))', branch)
114 if revs:
115 cs = repo.get_changeset(revs[0])
116 selected = 'branch:%s:%s' % (branch, cs.raw_id)
117 branches.append((selected, branch))
105 118
106 119 bookmarks = []
107 120 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
108 121 n = 'book:%s:%s' % (bookmark, bookmarkrev)
109 122 bookmarks.append((n, bookmark))
110 123 if rev == bookmarkrev:
111 124 selected = n
112 125
113 126 tags = []
114 127 for tag, tagrev in repo.tags.iteritems():
115 128 n = 'tag:%s:%s' % (tag, tagrev)
116 129 tags.append((n, tag))
117 130 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
118 131 selected = n
119 132
120 133 # prio 1: rev was selected as existing entry above
121 134
122 135 # prio 2: create special entry for rev; rev _must_ be used
123 136 specials = []
124 137 if rev and selected is None:
125 138 selected = 'rev:%s:%s' % (rev, rev)
126 139 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
127 140
128 141 # prio 3: most recent peer branch
129 142 if peers and not selected:
130 143 selected = peers[0][0][0]
131 144
132 145 # prio 4: tip revision
133 146 if not selected:
134 147 selected = 'tag:tip:%s' % repo.tags['tip']
135 148
136 149 groups = [(specials, _("Special")),
137 150 (peers, _("Peer branches")),
138 151 (bookmarks, _("Bookmarks")),
139 152 (branches, _("Branches")),
140 153 (tags, _("Tags")),
141 154 ]
142 155 return [g for g in groups if g[0]], selected
143 156
144 157 def _get_is_allowed_change_status(self, pull_request):
145 158 owner = self.rhodecode_user.user_id == pull_request.user_id
146 159 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
147 160 pull_request.reviewers]
148 161 return (self.rhodecode_user.admin or owner or reviewer)
149 162
150 163 def _load_compare_data(self, pull_request, enable_comments=True):
151 164 """
152 165 Load context data needed for generating compare diff
153 166
154 167 :param pull_request:
155 168 :type pull_request:
156 169 """
157 170 org_repo = pull_request.org_repo
158 171 (org_ref_type,
159 172 org_ref_name,
160 173 org_ref_rev) = pull_request.org_ref.split(':')
161 174
162 175 other_repo = org_repo
163 176 (other_ref_type,
164 177 other_ref_name,
165 178 other_ref_rev) = pull_request.other_ref.split(':')
166 179
167 180 # despite opening revisions for bookmarks/branches/tags, we always
168 181 # convert this to rev to prevent changes after bookmark or branch change
169 182 org_ref = ('rev', org_ref_rev)
170 183 other_ref = ('rev', other_ref_rev)
171 184
172 185 c.org_repo = org_repo
173 186 c.other_repo = other_repo
174 187
175 188 c.fulldiff = fulldiff = request.GET.get('fulldiff')
176 189
177 190 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
178 191
179 192 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
180 193
181 194 c.org_ref = org_ref[1]
182 195 c.org_ref_type = org_ref[0]
183 196 c.other_ref = other_ref[1]
184 197 c.other_ref_type = other_ref[0]
185 198
186 199 diff_limit = self.cut_off_limit if not fulldiff else None
187 200
188 201 # we swap org/other ref since we run a simple diff on one repo
189 202 log.debug('running diff between %s and %s in %s'
190 203 % (other_ref, org_ref, org_repo.scm_instance.path))
191 204 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
192 205
193 206 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
194 207 diff_limit=diff_limit)
195 208 _parsed = diff_processor.prepare()
196 209
197 210 c.limited_diff = False
198 211 if isinstance(_parsed, LimitedDiffContainer):
199 212 c.limited_diff = True
200 213
201 214 c.files = []
202 215 c.changes = {}
203 216 c.lines_added = 0
204 217 c.lines_deleted = 0
205 218 for f in _parsed:
206 219 st = f['stats']
207 220 if st[0] != 'b':
208 221 c.lines_added += st[0]
209 222 c.lines_deleted += st[1]
210 223 fid = h.FID('', f['filename'])
211 224 c.files.append([fid, f['operation'], f['filename'], f['stats']])
212 225 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
213 226 parsed_lines=[f])
214 227 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
215 228
216 229 @LoginRequired()
217 230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
218 231 'repository.admin')
219 232 def show_all(self, repo_name):
220 233 c.pull_requests = PullRequestModel().get_all(repo_name)
221 234 c.repo_name = repo_name
222 235 p = safe_int(request.GET.get('page', 1), 1)
223 236
224 237 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
225 238
226 239 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
227 240
228 241 if request.environ.get('HTTP_X_PARTIAL_XHR'):
229 242 return c.pullrequest_data
230 243
231 244 return render('/pullrequests/pullrequest_show_all.html')
232 245
233 246 @LoginRequired()
234 247 @NotAnonymous()
235 248 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
236 249 'repository.admin')
237 250 def index(self):
238 251 org_repo = c.rhodecode_db_repo
239 252
240 253 if org_repo.scm_instance.alias != 'hg':
241 254 log.error('Review not available for GIT REPOS')
242 255 raise HTTPNotFound
243 256
244 257 try:
245 258 org_repo.scm_instance.get_changeset()
246 259 except EmptyRepositoryError, e:
247 260 h.flash(h.literal(_('There are no changesets yet')),
248 261 category='warning')
249 262 redirect(url('summary_home', repo_name=org_repo.repo_name))
250 263
251 264 org_rev = request.GET.get('rev_end')
252 265 # rev_start is not directly useful - its parent could however be used
253 266 # as default for other and thus give a simple compare view
254 267 #other_rev = request.POST.get('rev_start')
268 branch = request.GET.get('branch')
255 269
256 270 c.org_repos = []
257 271 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
258 272 c.default_org_repo = org_repo.repo_name
259 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
273 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
260 274
261 275 c.other_repos = []
262 276 other_repos_info = {}
263 277
264 278 def add_other_repo(repo, branch_rev=None):
265 279 if repo.repo_name in other_repos_info: # shouldn't happen
266 280 return
267 281 c.other_repos.append((repo.repo_name, repo.repo_name))
268 282 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
269 283 other_repos_info[repo.repo_name] = {
270 284 'user': dict(user_id=repo.user.user_id,
271 285 username=repo.user.username,
272 286 firstname=repo.user.firstname,
273 287 lastname=repo.user.lastname,
274 288 gravatar_link=h.gravatar_url(repo.user.email, 14)),
275 289 'description': repo.description.split('\n', 1)[0],
276 290 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
277 291 }
278 292
279 293 # add org repo to other so we can open pull request against peer branches on itself
280 294 add_other_repo(org_repo, branch_rev=org_rev)
281 295 c.default_other_repo = org_repo.repo_name
282 296
283 297 # gather forks and add to this list ... even though it is rare to
284 298 # request forks to pull from their parent
285 299 for fork in org_repo.forks:
286 300 add_other_repo(fork)
287 301
288 302 # add parents of this fork also, but only if it's not empty
289 303 if org_repo.parent and org_repo.parent.scm_instance.revisions:
290 304 add_other_repo(org_repo.parent)
291 305 c.default_other_repo = org_repo.parent.repo_name
292 306
293 307 c.default_other_repo_info = other_repos_info[c.default_other_repo]
294 308 c.other_repos_info = json.dumps(other_repos_info)
295 309
296 310 return render('/pullrequests/pullrequest.html')
297 311
298 312 @LoginRequired()
299 313 @NotAnonymous()
300 314 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
301 315 'repository.admin')
302 316 def create(self, repo_name):
303 317 repo = RepoModel()._get_repo(repo_name)
304 318 try:
305 319 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
306 320 except formencode.Invalid, errors:
307 321 log.error(traceback.format_exc())
308 322 if errors.error_dict.get('revisions'):
309 323 msg = 'Revisions: %s' % errors.error_dict['revisions']
310 324 elif errors.error_dict.get('pullrequest_title'):
311 325 msg = _('Pull request requires a title with min. 3 chars')
312 326 else:
313 327 msg = _('Error creating pull request')
314 328
315 329 h.flash(msg, 'error')
316 330 return redirect(url('pullrequest_home', repo_name=repo_name))
317 331
318 332 org_repo = _form['org_repo']
319 333 org_ref = 'rev:merge:%s' % _form['merge_rev']
320 334 other_repo = _form['other_repo']
321 335 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
322 336 revisions = [x for x in reversed(_form['revisions'])]
323 337 reviewers = _form['review_members']
324 338
325 339 title = _form['pullrequest_title']
326 340 description = _form['pullrequest_desc']
327 341 try:
328 342 pull_request = PullRequestModel().create(
329 343 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
330 344 other_ref, revisions, reviewers, title, description
331 345 )
332 346 Session().commit()
333 347 h.flash(_('Successfully opened new pull request'),
334 348 category='success')
335 349 except Exception:
336 350 h.flash(_('Error occurred during sending pull request'),
337 351 category='error')
338 352 log.error(traceback.format_exc())
339 353 return redirect(url('pullrequest_home', repo_name=repo_name))
340 354
341 355 return redirect(url('pullrequest_show', repo_name=other_repo,
342 356 pull_request_id=pull_request.pull_request_id))
343 357
344 358 @LoginRequired()
345 359 @NotAnonymous()
346 360 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
347 361 'repository.admin')
348 362 @jsonify
349 363 def update(self, repo_name, pull_request_id):
350 364 pull_request = PullRequest.get_or_404(pull_request_id)
351 365 if pull_request.is_closed():
352 366 raise HTTPForbidden()
353 367 #only owner or admin can update it
354 368 owner = pull_request.author.user_id == c.rhodecode_user.user_id
355 369 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
356 370 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
357 371 request.POST.get('reviewers_ids', '').split(',')))
358 372
359 373 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
360 374 Session().commit()
361 375 return True
362 376 raise HTTPForbidden()
363 377
364 378 @LoginRequired()
365 379 @NotAnonymous()
366 380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
367 381 'repository.admin')
368 382 @jsonify
369 383 def delete(self, repo_name, pull_request_id):
370 384 pull_request = PullRequest.get_or_404(pull_request_id)
371 385 #only owner can delete it !
372 386 if pull_request.author.user_id == c.rhodecode_user.user_id:
373 387 PullRequestModel().delete(pull_request)
374 388 Session().commit()
375 389 h.flash(_('Successfully deleted pull request'),
376 390 category='success')
377 391 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
378 392 raise HTTPForbidden()
379 393
380 394 @LoginRequired()
381 395 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
382 396 'repository.admin')
383 397 def show(self, repo_name, pull_request_id):
384 398 repo_model = RepoModel()
385 399 c.users_array = repo_model.get_users_js()
386 400 c.users_groups_array = repo_model.get_users_groups_js()
387 401 c.pull_request = PullRequest.get_or_404(pull_request_id)
388 402 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
389 403 cc_model = ChangesetCommentsModel()
390 404 cs_model = ChangesetStatusModel()
391 405 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
392 406 pull_request=c.pull_request,
393 407 with_revisions=True)
394 408
395 409 cs_statuses = defaultdict(list)
396 410 for st in _cs_statuses:
397 411 cs_statuses[st.author.username] += [st]
398 412
399 413 c.pull_request_reviewers = []
400 414 c.pull_request_pending_reviewers = []
401 415 for o in c.pull_request.reviewers:
402 416 st = cs_statuses.get(o.user.username, None)
403 417 if st:
404 418 sorter = lambda k: k.version
405 419 st = [(x, list(y)[0])
406 420 for x, y in (groupby(sorted(st, key=sorter), sorter))]
407 421 else:
408 422 c.pull_request_pending_reviewers.append(o.user)
409 423 c.pull_request_reviewers.append([o.user, st])
410 424
411 425 # pull_requests repo_name we opened it against
412 426 # ie. other_repo must match
413 427 if repo_name != c.pull_request.other_repo.repo_name:
414 428 raise HTTPNotFound
415 429
416 430 # load compare data into template context
417 431 enable_comments = not c.pull_request.is_closed()
418 432 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
419 433
420 434 # inline comments
421 435 c.inline_cnt = 0
422 436 c.inline_comments = cc_model.get_inline_comments(
423 437 c.rhodecode_db_repo.repo_id,
424 438 pull_request=pull_request_id)
425 439 # count inline comments
426 440 for __, lines in c.inline_comments:
427 441 for comments in lines.values():
428 442 c.inline_cnt += len(comments)
429 443 # comments
430 444 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
431 445 pull_request=pull_request_id)
432 446
433 447 try:
434 448 cur_status = c.statuses[c.pull_request.revisions[0]][0]
435 449 except Exception:
436 450 log.error(traceback.format_exc())
437 451 cur_status = 'undefined'
438 452 if c.pull_request.is_closed() and 0:
439 453 c.current_changeset_status = cur_status
440 454 else:
441 455 # changeset(pull-request) status calulation based on reviewers
442 456 c.current_changeset_status = cs_model.calculate_status(
443 457 c.pull_request_reviewers,
444 458 )
445 459 c.changeset_statuses = ChangesetStatus.STATUSES
446 460
447 461 c.as_form = False
448 462 c.ancestor = None # there is one - but right here we don't know which
449 463 return render('/pullrequests/pullrequest_show.html')
450 464
451 465 @LoginRequired()
452 466 @NotAnonymous()
453 467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
454 468 'repository.admin')
455 469 @jsonify
456 470 def comment(self, repo_name, pull_request_id):
457 471 pull_request = PullRequest.get_or_404(pull_request_id)
458 472 if pull_request.is_closed():
459 473 raise HTTPForbidden()
460 474
461 475 status = request.POST.get('changeset_status')
462 476 change_status = request.POST.get('change_changeset_status')
463 477 text = request.POST.get('text')
464 478 close_pr = request.POST.get('save_close')
465 479
466 480 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
467 481 if status and change_status and allowed_to_change_status:
468 482 _def = (_('Status change -> %s')
469 483 % ChangesetStatus.get_status_lbl(status))
470 484 if close_pr:
471 485 _def = _('Closing with') + ' ' + _def
472 486 text = text or _def
473 487 comm = ChangesetCommentsModel().create(
474 488 text=text,
475 489 repo=c.rhodecode_db_repo.repo_id,
476 490 user=c.rhodecode_user.user_id,
477 491 pull_request=pull_request_id,
478 492 f_path=request.POST.get('f_path'),
479 493 line_no=request.POST.get('line'),
480 494 status_change=(ChangesetStatus.get_status_lbl(status)
481 495 if status and change_status
482 496 and allowed_to_change_status else None),
483 497 closing_pr=close_pr
484 498 )
485 499
486 500 action_logger(self.rhodecode_user,
487 501 'user_commented_pull_request:%s' % pull_request_id,
488 502 c.rhodecode_db_repo, self.ip_addr, self.sa)
489 503
490 504 if allowed_to_change_status:
491 505 # get status if set !
492 506 if status and change_status:
493 507 ChangesetStatusModel().set_status(
494 508 c.rhodecode_db_repo.repo_id,
495 509 status,
496 510 c.rhodecode_user.user_id,
497 511 comm,
498 512 pull_request=pull_request_id
499 513 )
500 514
501 515 if close_pr:
502 516 if status in ['rejected', 'approved']:
503 517 PullRequestModel().close_pull_request(pull_request_id)
504 518 action_logger(self.rhodecode_user,
505 519 'user_closed_pull_request:%s' % pull_request_id,
506 520 c.rhodecode_db_repo, self.ip_addr, self.sa)
507 521 else:
508 522 h.flash(_('Closing pull request on other statuses than '
509 523 'rejected or approved forbidden'),
510 524 category='warning')
511 525
512 526 Session().commit()
513 527
514 528 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
515 529 return redirect(h.url('pullrequest_show', repo_name=repo_name,
516 530 pull_request_id=pull_request_id))
517 531
518 532 data = {
519 533 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
520 534 }
521 535 if comm:
522 536 c.co = comm
523 537 data.update(comm.get_dict())
524 538 data.update({'rendered_text':
525 539 render('changeset/changeset_comment_block.html')})
526 540
527 541 return data
528 542
529 543 @LoginRequired()
530 544 @NotAnonymous()
531 545 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
532 546 'repository.admin')
533 547 @jsonify
534 548 def delete_comment(self, repo_name, comment_id):
535 549 co = ChangesetComment.get(comment_id)
536 550 if co.pull_request.is_closed():
537 551 #don't allow deleting comments on closed pull request
538 552 raise HTTPForbidden()
539 553
540 554 owner = co.author.user_id == c.rhodecode_user.user_id
541 555 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
542 556 ChangesetCommentsModel().delete(comment=co)
543 557 Session().commit()
544 558 return True
545 559 else:
546 560 raise HTTPForbidden()
@@ -1,288 +1,290
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name} &middot;
7 7 %if c.changelog_for_path:
8 8 /${c.changelog_for_path} &middot;
9 9 %endif
10 10 ${c.rhodecode_name}
11 11 </%def>
12 12
13 13 <%def name="breadcrumbs_links()">
14 14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 15 ${_('Changelog')}
16 16 %if c.changelog_for_path:
17 17 - /${c.changelog_for_path}
18 18 %endif
19 19 - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
20 20 </%def>
21 21
22 22 <%def name="page_nav()">
23 23 ${self.menu('repositories')}
24 24 </%def>
25 25
26 26 <%def name="main()">
27 27 ${self.context_bar('changelog')}
28 28 <div class="box">
29 29 <!-- box / title -->
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 </div>
33 33 <div class="table">
34 34 % if c.pagination:
35 35 <div id="graph">
36 36 <div style="display:${'none' if c.changelog_for_path else ''}">
37 37 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
38 38 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
39 39 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
40 40
41 41 %if c.rhodecode_db_repo.fork:
42 42 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default',merge=1)}" class="ui-btn small">${_('Compare fork with parent')}</a>
43 43 %endif
44 44 %if h.is_hg(c.rhodecode_repo):
45 45 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
46 46 %endif
47 47 </div>
48 48 <div class="container_header">
49 49 ${h.form(h.url.current(),method='get')}
50 50 <div style="float:left">
51 51 ${h.submit('set',_('Show'),class_="ui-btn")}
52 52 ${h.text('size',size=1,value=c.size)}
53 53 ${_('revisions')}
54 54 </div>
55 55 ${h.end_form()}
56 56 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
57 57 </div>
58 58 </div>
59 59 <div id="graph_nodes">
60 60 <canvas id="graph_canvas"></canvas>
61 61 </div>
62 62 <div id="graph_content">
63 63
64 64 <table id="changesets">
65 65 <tbody>
66 66 %for cnt,cs in enumerate(c.pagination):
67 67 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
68 68 <td class="checkbox">
69 69 %if c.changelog_for_path:
70 70 ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
71 71 %else:
72 72 ${h.checkbox(cs.raw_id,class_="changeset_range")}
73 73 %endif
74 74 <td class="status">
75 75 %if c.statuses.get(cs.raw_id):
76 76 <div class="changeset-status-ico">
77 77 %if c.statuses.get(cs.raw_id)[2]:
78 78 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
79 79 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
80 80 </a>
81 81 %else:
82 82 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
83 83 %endif
84 84 </div>
85 85 %endif
86 86 </td>
87 87 <td class="author">
88 88 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
89 89 <span title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</span>
90 90 </td>
91 91 <td class="hash" style="width:${len(h.show_id(cs))*6.5}px">
92 92 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
93 93 <span class="changeset_hash">${h.show_id(cs)}</span>
94 94 </a>
95 95 </td>
96 96 <td class="date">
97 97 <div class="date">${h.age(cs.date,True)}</div>
98 98 </td>
99 99 <td class="mid">
100 100 <div class="log-container">
101 101 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
102 102 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
103 103 <div class="extra-container">
104 104 %if c.comments.get(cs.raw_id):
105 105 <div class="comments-container">
106 106 <div class="comments-cnt" title="${('comments')}">
107 107 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
108 108 ${len(c.comments[cs.raw_id])}
109 109 </a>
110 110 </div>
111 111 </div>
112 112 %endif
113 113 %if h.is_hg(c.rhodecode_repo):
114 114 %for book in cs.bookmarks:
115 115 <div class="booktag" title="${_('Bookmark %s') % book}">
116 116 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
117 117 </div>
118 118 %endfor
119 119 %endif
120 120 %for tag in cs.tags:
121 121 <div class="tagtag" title="${_('Tag %s') % tag}">
122 122 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
123 123 </div>
124 124 %endfor
125 125 %if (not c.branch_name) and cs.branch:
126 126 <div class="branchtag" title="${_('Branch %s' % cs.branch)}">
127 127 ${h.link_to(h.shorter(cs.branch),h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
128 128 </div>
129 129 %endif
130 130 </div>
131 131 </div>
132 132 </td>
133 133 </tr>
134 134 %endfor
135 135 </tbody>
136 136 </table>
137 137
138 138 <div class="pagination-wh pagination-left">
139 139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 140 </div>
141 141 </div>
142 142 </div>
143 143
144 144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 145 <script type="text/javascript">
146 146 YAHOO.util.Event.onDOMReady(function(){
147 147
148 148 //Monitor range checkboxes and build a link to changesets
149 149 //ranges
150 150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
153 152
154 153 var checkbox_checker = function(e){
155 154 var checked_checkboxes = [];
156 155 for (pos in checkboxes){
157 156 if(checkboxes[pos].checked){
158 157 checked_checkboxes.push(checkboxes[pos]);
159 158 }
160 159 }
161 160 if(YUD.get('open_new_pr')){
162 161 if(checked_checkboxes.length>1){
163 162 YUD.setStyle('open_new_pr','display','none');
164 163 } else {
165 164 YUD.setStyle('open_new_pr','display','');
166 165 if(checked_checkboxes.length>0){
167 166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
168 167 }else{
169 168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
170 169 }
171 170 }
172 171 }
173 172
174 173 if(checked_checkboxes.length>0){
175 174 var rev_end = checked_checkboxes[0].name;
176 175 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
177 176 var url = url_tmpl.replace('__REVRANGE__',
178 177 rev_start+'...'+rev_end);
179 178
180 179 var link = (rev_start == rev_end)
181 180 ? _TM['Show selected changeset __S']
182 181 : _TM['Show selected changesets __S -> __E'];
183 182
184 183 link = link.replace('__S',rev_start.substr(0,6));
185 184 link = link.replace('__E',rev_end.substr(0,6));
186 185 YUD.get('rev_range_container').href = url;
187 186 YUD.get('rev_range_container').innerHTML = link;
188 187 YUD.setStyle('rev_range_container','display','');
189 188 YUD.setStyle('rev_range_clear','display','');
190 189
191 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
190 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name,rev_start='{0}',rev_end='{1}')}";
191 YUD.get('open_new_pr').href = pr_tmpl.format(rev_start,rev_end);
192 192 YUD.setStyle('compare_fork','display','none');
193 193 }else{
194 194 YUD.setStyle('rev_range_container','display','none');
195 195 YUD.setStyle('rev_range_clear','display','none');
196 if (checkboxes){
197 YUD.get('open_new_pr').href = pr_tmpl + '?rev_end={0}'.format(checkboxes[0].name);
198 }
196 %if c.branch_name:
197 YUD.get('open_new_pr').href = "${h.url('pullrequest_home',repo_name=c.repo_name,branch=c.branch_name)}";
198 %else:
199 YUD.get('open_new_pr').href = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
200 %endif
199 201 YUD.setStyle('compare_fork','display','');
200 202 }
201 203 };
202 204 YUE.onDOMReady(checkbox_checker);
203 205 YUE.on(checkboxes,'click', checkbox_checker);
204 206
205 207 YUE.on('rev_range_clear','click',function(e){
206 208 for (var i=0; i<checkboxes.length; i++){
207 209 var cb = checkboxes[i];
208 210 cb.checked = false;
209 211 }
210 212 checkbox_checker();
211 213 YUE.preventDefault(e);
212 214 });
213 215
214 216 var msgs = YUQ('.message');
215 217 // get first element height
216 218 var el = YUQ('#graph_content .container')[0];
217 219 var row_h = el.clientHeight;
218 220 for(var i=0;i<msgs.length;i++){
219 221 var m = msgs[i];
220 222
221 223 var h = m.clientHeight;
222 224 var pad = YUD.getStyle(m,'padding');
223 225 if(h > row_h){
224 226 var offset = row_h - (h+12);
225 227 YUD.setStyle(m.nextElementSibling,'display','block');
226 228 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
227 229 };
228 230 }
229 231 YUE.on(YUQ('.expand'),'click',function(e){
230 232 var elem = e.currentTarget.parentNode.parentNode;
231 233 YUD.setStyle(e.currentTarget,'display','none');
232 234 YUD.setStyle(elem,'height','auto');
233 235
234 236 //redraw the graph, line_count and jsdata are global vars
235 237 set_canvas(100);
236 238
237 239 var r = new BranchRenderer();
238 240 r.render(jsdata,100,line_count);
239 241
240 242 });
241 243
242 244 // change branch filter
243 245 YUE.on(YUD.get('branch_filter'),'change',function(e){
244 246 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
245 247 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
246 248 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
247 249 var url = url.replace('__BRANCH__', encodeURIComponent(selected_branch));
248 250 if(selected_branch != ''){
249 251 window.location = url;
250 252 }else{
251 253 window.location = url_main;
252 254 }
253 255
254 256 });
255 257
256 258 function set_canvas(width) {
257 259 var c = document.getElementById('graph_nodes');
258 260 var t = document.getElementById('graph_content');
259 261 canvas = document.getElementById('graph_canvas');
260 262 var div_h = t.clientHeight;
261 263 canvas.setAttribute('height',div_h);
262 264 canvas.setAttribute('width',width);
263 265 };
264 266 var heads = 1;
265 267 var line_count = 0;
266 268 var jsdata = ${c.jsdata|n};
267 269
268 270 for (var i=0;i<jsdata.length;i++) {
269 271 var in_l = jsdata[i][2];
270 272 for (var j in in_l) {
271 273 var m = in_l[j][1];
272 274 if (m > line_count)
273 275 line_count = m;
274 276 }
275 277 }
276 278 set_canvas(100);
277 279
278 280 var r = new BranchRenderer();
279 281 r.render(jsdata,100,line_count);
280 282
281 283 });
282 284 </script>
283 285 %else:
284 286 ${_('There are no changes yet')}
285 287 %endif
286 288 </div>
287 289 </div>
288 290 </%def>
General Comments 0
You need to be logged in to leave comments. Login now