Show More
@@ -1,479 +1,479 | |||
|
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 import helpers as h |
|
42 | 42 | from rhodecode.lib import diffs |
|
43 | 43 | from rhodecode.lib.utils import action_logger, jsonify |
|
44 | 44 | from rhodecode.lib.vcs.exceptions import EmptyRepositoryError |
|
45 | 45 | from rhodecode.lib.vcs.backends.base import EmptyChangeset |
|
46 | 46 | from rhodecode.lib.diffs import LimitedDiffContainer |
|
47 | 47 | from rhodecode.model.db import User, PullRequest, ChangesetStatus,\ |
|
48 | 48 | ChangesetComment |
|
49 | 49 | from rhodecode.model.pull_request import PullRequestModel |
|
50 | 50 | from rhodecode.model.meta import Session |
|
51 | 51 | from rhodecode.model.repo import RepoModel |
|
52 | 52 | from rhodecode.model.comment import ChangesetCommentsModel |
|
53 | 53 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
54 | 54 | from rhodecode.model.forms import PullRequestForm |
|
55 | 55 | |
|
56 | 56 | log = logging.getLogger(__name__) |
|
57 | 57 | |
|
58 | 58 | |
|
59 | 59 | class PullrequestsController(BaseRepoController): |
|
60 | 60 | |
|
61 | 61 | @LoginRequired() |
|
62 | 62 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
63 | 63 | 'repository.admin') |
|
64 | 64 | def __before__(self): |
|
65 | 65 | super(PullrequestsController, self).__before__() |
|
66 | 66 | repo_model = RepoModel() |
|
67 | 67 | c.users_array = repo_model.get_users_js() |
|
68 | 68 | c.users_groups_array = repo_model.get_users_groups_js() |
|
69 | 69 | |
|
70 | 70 | def _get_repo_refs(self, repo): |
|
71 | 71 | hist_l = [] |
|
72 | 72 | |
|
73 | 73 | branches_group = ([('branch:%s:%s' % (k, v), k) for |
|
74 | 74 | k, v in repo.branches.iteritems()], _("Branches")) |
|
75 | 75 | bookmarks_group = ([('book:%s:%s' % (k, v), k) for |
|
76 | 76 | k, v in repo.bookmarks.iteritems()], _("Bookmarks")) |
|
77 | 77 | tags_group = ([('tag:%s:%s' % (k, v), k) for |
|
78 | 78 | k, v in repo.tags.iteritems() |
|
79 | 79 | if k != 'tip'], _("Tags")) |
|
80 | 80 | |
|
81 | 81 | tip = repo.tags['tip'] |
|
82 | 82 | tipref = 'tag:tip:%s' % tip |
|
83 | 83 | colontip = ':' + tip |
|
84 | 84 | tips = [x[1] for x in branches_group[0] + bookmarks_group[0] + tags_group[0] |
|
85 | 85 | if x[0].endswith(colontip)] |
|
86 | 86 | tags_group[0].append((tipref, 'tip (%s)' % ', '.join(tips))) |
|
87 | 87 | |
|
88 | 88 | hist_l.append(bookmarks_group) |
|
89 | 89 | hist_l.append(branches_group) |
|
90 | 90 | hist_l.append(tags_group) |
|
91 | 91 | |
|
92 | 92 | return hist_l, tipref |
|
93 | 93 | |
|
94 | 94 | def _get_is_allowed_change_status(self, pull_request): |
|
95 | 95 | owner = self.rhodecode_user.user_id == pull_request.user_id |
|
96 | 96 | reviewer = self.rhodecode_user.user_id in [x.user_id for x in |
|
97 | 97 | pull_request.reviewers] |
|
98 | 98 | return (self.rhodecode_user.admin or owner or reviewer) |
|
99 | 99 | |
|
100 | 100 | def show_all(self, repo_name): |
|
101 | 101 | c.pull_requests = PullRequestModel().get_all(repo_name) |
|
102 | 102 | c.repo_name = repo_name |
|
103 | 103 | return render('/pullrequests/pullrequest_show_all.html') |
|
104 | 104 | |
|
105 | 105 | @NotAnonymous() |
|
106 | 106 | def index(self): |
|
107 | 107 | org_repo = c.rhodecode_db_repo |
|
108 | 108 | |
|
109 | 109 | if org_repo.scm_instance.alias != 'hg': |
|
110 | 110 | log.error('Review not available for GIT REPOS') |
|
111 | 111 | raise HTTPNotFound |
|
112 | 112 | |
|
113 | 113 | try: |
|
114 | 114 | org_repo.scm_instance.get_changeset() |
|
115 | 115 | except EmptyRepositoryError, e: |
|
116 | 116 | h.flash(h.literal(_('There are no changesets yet')), |
|
117 | 117 | category='warning') |
|
118 | 118 | redirect(url('summary_home', repo_name=org_repo.repo_name)) |
|
119 | 119 | |
|
120 | 120 | other_repos_info = {} |
|
121 | 121 | |
|
122 | 122 | c.org_repos = [] |
|
123 | 123 | c.org_repos.append((org_repo.repo_name, org_repo.repo_name)) |
|
124 | 124 | c.default_org_repo = org_repo.repo_name |
|
125 | 125 | c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance) |
|
126 | 126 | |
|
127 | 127 | c.other_repos = [] |
|
128 | 128 | # add org repo to other so we can open pull request against itself |
|
129 | 129 | c.other_repos.extend(c.org_repos) |
|
130 | 130 | c.default_other_repo = org_repo.repo_name |
|
131 | 131 | c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance) |
|
132 | 132 | usr_data = lambda usr: dict(user_id=usr.user_id, |
|
133 | 133 | username=usr.username, |
|
134 | 134 | firstname=usr.firstname, |
|
135 | 135 | lastname=usr.lastname, |
|
136 | 136 | gravatar_link=h.gravatar_url(usr.email, 14)) |
|
137 | 137 | other_repos_info[org_repo.repo_name] = { |
|
138 | 138 | 'user': usr_data(org_repo.user), |
|
139 | 139 | 'description': org_repo.description, |
|
140 | 140 | 'revs': h.select('other_ref', c.default_other_ref, |
|
141 | 141 | c.default_other_refs, class_='refs') |
|
142 | 142 | } |
|
143 | 143 | |
|
144 |
# gather forks and add to this list ... even though it is rare to |
|
|
144 | # gather forks and add to this list ... even though it is rare to | |
|
145 | 145 | # request forks to pull their parent |
|
146 | 146 | for fork in org_repo.forks: |
|
147 | 147 | c.other_repos.append((fork.repo_name, fork.repo_name)) |
|
148 | 148 | refs, default_ref = self._get_repo_refs(fork.scm_instance) |
|
149 | 149 | other_repos_info[fork.repo_name] = { |
|
150 | 150 | 'user': usr_data(fork.user), |
|
151 | 151 | 'description': fork.description, |
|
152 | 152 | 'revs': h.select('other_ref', default_ref, refs, class_='refs') |
|
153 | 153 | } |
|
154 | 154 | |
|
155 | 155 | # add parents of this fork also, but only if it's not empty |
|
156 | 156 | if org_repo.parent and org_repo.parent.scm_instance.revisions: |
|
157 | 157 | c.default_other_repo = org_repo.parent.repo_name |
|
158 | 158 | c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance) |
|
159 | 159 | c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name)) |
|
160 | 160 | other_repos_info[org_repo.parent.repo_name] = { |
|
161 | 161 | 'user': usr_data(org_repo.parent.user), |
|
162 | 162 | 'description': org_repo.parent.description, |
|
163 | 163 | 'revs': h.select('other_ref', c.default_other_ref, |
|
164 | 164 | c.default_other_refs, class_='refs') |
|
165 | 165 | } |
|
166 | 166 | |
|
167 | 167 | c.other_repos_info = json.dumps(other_repos_info) |
|
168 | 168 | # other repo owner |
|
169 | 169 | c.review_members = [] |
|
170 | 170 | return render('/pullrequests/pullrequest.html') |
|
171 | 171 | |
|
172 | 172 | @NotAnonymous() |
|
173 | 173 | def create(self, repo_name): |
|
174 | 174 | repo = RepoModel()._get_repo(repo_name) |
|
175 | 175 | try: |
|
176 | 176 | _form = PullRequestForm(repo.repo_id)().to_python(request.POST) |
|
177 | 177 | except formencode.Invalid, errors: |
|
178 | 178 | log.error(traceback.format_exc()) |
|
179 | 179 | if errors.error_dict.get('revisions'): |
|
180 | 180 | msg = 'Revisions: %s' % errors.error_dict['revisions'] |
|
181 | 181 | elif errors.error_dict.get('pullrequest_title'): |
|
182 | 182 | msg = _('Pull request requires a title with min. 3 chars') |
|
183 | 183 | else: |
|
184 | 184 | msg = _('error during creation of pull request') |
|
185 | 185 | |
|
186 | 186 | h.flash(msg, 'error') |
|
187 | 187 | return redirect(url('pullrequest_home', repo_name=repo_name)) |
|
188 | 188 | |
|
189 | 189 | org_repo = _form['org_repo'] |
|
190 | 190 | org_ref = _form['org_ref'] |
|
191 | 191 | other_repo = _form['other_repo'] |
|
192 | 192 | other_ref = _form['other_ref'] |
|
193 | 193 | revisions = _form['revisions'] |
|
194 | 194 | reviewers = _form['review_members'] |
|
195 | 195 | |
|
196 | 196 | # if we have cherry picked pull request we don't care what is in |
|
197 | 197 | # org_ref/other_ref |
|
198 | 198 | rev_start = request.POST.get('rev_start') |
|
199 | 199 | rev_end = request.POST.get('rev_end') |
|
200 | 200 | |
|
201 | 201 | if rev_start and rev_end: |
|
202 | 202 | # this is swapped to simulate that rev_end is a revision from |
|
203 | 203 | # parent of the fork |
|
204 | 204 | org_ref = 'rev:%s:%s' % (rev_end, rev_end) |
|
205 | 205 | other_ref = 'rev:%s:%s' % (rev_start, rev_start) |
|
206 | 206 | |
|
207 | 207 | title = _form['pullrequest_title'] |
|
208 | 208 | description = _form['pullrequest_desc'] |
|
209 | 209 | |
|
210 | 210 | try: |
|
211 | 211 | pull_request = PullRequestModel().create( |
|
212 | 212 | self.rhodecode_user.user_id, org_repo, org_ref, other_repo, |
|
213 | 213 | other_ref, revisions, reviewers, title, description |
|
214 | 214 | ) |
|
215 | 215 | Session().commit() |
|
216 | 216 | h.flash(_('Successfully opened new pull request'), |
|
217 | 217 | category='success') |
|
218 | 218 | except Exception: |
|
219 | 219 | h.flash(_('Error occurred during sending pull request'), |
|
220 | 220 | category='error') |
|
221 | 221 | log.error(traceback.format_exc()) |
|
222 | 222 | return redirect(url('pullrequest_home', repo_name=repo_name)) |
|
223 | 223 | |
|
224 | 224 | return redirect(url('pullrequest_show', repo_name=other_repo, |
|
225 | 225 | pull_request_id=pull_request.pull_request_id)) |
|
226 | 226 | |
|
227 | 227 | @NotAnonymous() |
|
228 | 228 | @jsonify |
|
229 | 229 | def update(self, repo_name, pull_request_id): |
|
230 | 230 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
231 | 231 | if pull_request.is_closed(): |
|
232 | 232 | raise HTTPForbidden() |
|
233 | 233 | #only owner or admin can update it |
|
234 | 234 | owner = pull_request.author.user_id == c.rhodecode_user.user_id |
|
235 | 235 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: |
|
236 | 236 | reviewers_ids = map(int, filter(lambda v: v not in [None, ''], |
|
237 | 237 | request.POST.get('reviewers_ids', '').split(','))) |
|
238 | 238 | |
|
239 | 239 | PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) |
|
240 | 240 | Session().commit() |
|
241 | 241 | return True |
|
242 | 242 | raise HTTPForbidden() |
|
243 | 243 | |
|
244 | 244 | @NotAnonymous() |
|
245 | 245 | @jsonify |
|
246 | 246 | def delete(self, repo_name, pull_request_id): |
|
247 | 247 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
248 | 248 | #only owner can delete it ! |
|
249 | 249 | if pull_request.author.user_id == c.rhodecode_user.user_id: |
|
250 | 250 | PullRequestModel().delete(pull_request) |
|
251 | 251 | Session().commit() |
|
252 | 252 | h.flash(_('Successfully deleted pull request'), |
|
253 | 253 | category='success') |
|
254 | 254 | return redirect(url('admin_settings_my_account', anchor='pullrequests')) |
|
255 | 255 | raise HTTPForbidden() |
|
256 | 256 | |
|
257 | 257 | def _load_compare_data(self, pull_request, enable_comments=True): |
|
258 | 258 | """ |
|
259 | 259 | Load context data needed for generating compare diff |
|
260 | 260 | |
|
261 | 261 | :param pull_request: |
|
262 | 262 | :type pull_request: |
|
263 | 263 | """ |
|
264 | 264 | rev_start = request.GET.get('rev_start') |
|
265 | 265 | rev_end = request.GET.get('rev_end') |
|
266 | 266 | |
|
267 | 267 | org_repo = pull_request.org_repo |
|
268 | 268 | (org_ref_type, |
|
269 | 269 | org_ref_name, |
|
270 | 270 | org_ref_rev) = pull_request.org_ref.split(':') |
|
271 | 271 | |
|
272 | 272 | other_repo = org_repo |
|
273 | 273 | (other_ref_type, |
|
274 | 274 | other_ref_name, |
|
275 | 275 | other_ref_rev) = pull_request.other_ref.split(':') |
|
276 | 276 | |
|
277 | 277 | # despite opening revisions for bookmarks/branches/tags, we always |
|
278 | 278 | # convert this to rev to prevent changes after book or branch change |
|
279 | 279 | org_ref = ('rev', org_ref_rev) |
|
280 | 280 | other_ref = ('rev', other_ref_rev) |
|
281 | 281 | |
|
282 | 282 | c.org_repo = org_repo |
|
283 | 283 | c.other_repo = other_repo |
|
284 | 284 | |
|
285 | 285 | c.fulldiff = fulldiff = request.GET.get('fulldiff') |
|
286 | 286 | |
|
287 | 287 | c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions] |
|
288 | 288 | |
|
289 | 289 | other_ref = ('rev', getattr(c.cs_ranges[0].parents[0] |
|
290 | 290 | if c.cs_ranges[0].parents |
|
291 | 291 | else EmptyChangeset(), 'raw_id')) |
|
292 | 292 | |
|
293 | 293 | c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) |
|
294 | 294 | # defines that we need hidden inputs with changesets |
|
295 | 295 | c.as_form = request.GET.get('as_form', False) |
|
296 | 296 | |
|
297 | 297 | c.org_ref = org_ref[1] |
|
298 | 298 | c.org_ref_type = org_ref[0] |
|
299 | 299 | c.other_ref = other_ref[1] |
|
300 | 300 | c.other_ref_type = other_ref[0] |
|
301 | 301 | |
|
302 | 302 | diff_limit = self.cut_off_limit if not fulldiff else None |
|
303 | 303 | |
|
304 | 304 | #we swap org/other ref since we run a simple diff on one repo |
|
305 | 305 | _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref) |
|
306 | 306 | |
|
307 | 307 | diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff', |
|
308 | 308 | diff_limit=diff_limit) |
|
309 | 309 | _parsed = diff_processor.prepare() |
|
310 | 310 | |
|
311 | 311 | c.limited_diff = False |
|
312 | 312 | if isinstance(_parsed, LimitedDiffContainer): |
|
313 | 313 | c.limited_diff = True |
|
314 | 314 | |
|
315 | 315 | c.files = [] |
|
316 | 316 | c.changes = {} |
|
317 | 317 | c.lines_added = 0 |
|
318 | 318 | c.lines_deleted = 0 |
|
319 | 319 | for f in _parsed: |
|
320 | 320 | st = f['stats'] |
|
321 | 321 | if st[0] != 'b': |
|
322 | 322 | c.lines_added += st[0] |
|
323 | 323 | c.lines_deleted += st[1] |
|
324 | 324 | fid = h.FID('', f['filename']) |
|
325 | 325 | c.files.append([fid, f['operation'], f['filename'], f['stats']]) |
|
326 | 326 | diff = diff_processor.as_html(enable_comments=enable_comments, |
|
327 | 327 | parsed_lines=[f]) |
|
328 | 328 | c.changes[fid] = [f['operation'], f['filename'], diff] |
|
329 | 329 | |
|
330 | 330 | def show(self, repo_name, pull_request_id): |
|
331 | 331 | repo_model = RepoModel() |
|
332 | 332 | c.users_array = repo_model.get_users_js() |
|
333 | 333 | c.users_groups_array = repo_model.get_users_groups_js() |
|
334 | 334 | c.pull_request = PullRequest.get_or_404(pull_request_id) |
|
335 | 335 | c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) |
|
336 | 336 | cc_model = ChangesetCommentsModel() |
|
337 | 337 | cs_model = ChangesetStatusModel() |
|
338 | 338 | _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, |
|
339 | 339 | pull_request=c.pull_request, |
|
340 | 340 | with_revisions=True) |
|
341 | 341 | |
|
342 | 342 | cs_statuses = defaultdict(list) |
|
343 | 343 | for st in _cs_statuses: |
|
344 | 344 | cs_statuses[st.author.username] += [st] |
|
345 | 345 | |
|
346 | 346 | c.pull_request_reviewers = [] |
|
347 | 347 | c.pull_request_pending_reviewers = [] |
|
348 | 348 | for o in c.pull_request.reviewers: |
|
349 | 349 | st = cs_statuses.get(o.user.username, None) |
|
350 | 350 | if st: |
|
351 | 351 | sorter = lambda k: k.version |
|
352 | 352 | st = [(x, list(y)[0]) |
|
353 | 353 | for x, y in (groupby(sorted(st, key=sorter), sorter))] |
|
354 | 354 | else: |
|
355 | 355 | c.pull_request_pending_reviewers.append(o.user) |
|
356 | 356 | c.pull_request_reviewers.append([o.user, st]) |
|
357 | 357 | |
|
358 | 358 | # pull_requests repo_name we opened it against |
|
359 | 359 | # ie. other_repo must match |
|
360 | 360 | if repo_name != c.pull_request.other_repo.repo_name: |
|
361 | 361 | raise HTTPNotFound |
|
362 | 362 | |
|
363 | 363 | # load compare data into template context |
|
364 | 364 | enable_comments = not c.pull_request.is_closed() |
|
365 | 365 | self._load_compare_data(c.pull_request, enable_comments=enable_comments) |
|
366 | 366 | |
|
367 | 367 | # inline comments |
|
368 | 368 | c.inline_cnt = 0 |
|
369 | 369 | c.inline_comments = cc_model.get_inline_comments( |
|
370 | 370 | c.rhodecode_db_repo.repo_id, |
|
371 | 371 | pull_request=pull_request_id) |
|
372 | 372 | # count inline comments |
|
373 | 373 | for __, lines in c.inline_comments: |
|
374 | 374 | for comments in lines.values(): |
|
375 | 375 | c.inline_cnt += len(comments) |
|
376 | 376 | # comments |
|
377 | 377 | c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, |
|
378 | 378 | pull_request=pull_request_id) |
|
379 | 379 | |
|
380 | 380 | try: |
|
381 | 381 | cur_status = c.statuses[c.pull_request.revisions[0]][0] |
|
382 | 382 | except: |
|
383 | 383 | log.error(traceback.format_exc()) |
|
384 | 384 | cur_status = 'undefined' |
|
385 | 385 | if c.pull_request.is_closed() and 0: |
|
386 | 386 | c.current_changeset_status = cur_status |
|
387 | 387 | else: |
|
388 | 388 | # changeset(pull-request) status calulation based on reviewers |
|
389 | 389 | c.current_changeset_status = cs_model.calculate_status( |
|
390 | 390 | c.pull_request_reviewers, |
|
391 | 391 | ) |
|
392 | 392 | c.changeset_statuses = ChangesetStatus.STATUSES |
|
393 | 393 | |
|
394 | 394 | return render('/pullrequests/pullrequest_show.html') |
|
395 | 395 | |
|
396 | 396 | @NotAnonymous() |
|
397 | 397 | @jsonify |
|
398 | 398 | def comment(self, repo_name, pull_request_id): |
|
399 | 399 | pull_request = PullRequest.get_or_404(pull_request_id) |
|
400 | 400 | if pull_request.is_closed(): |
|
401 | 401 | raise HTTPForbidden() |
|
402 | 402 | |
|
403 | 403 | status = request.POST.get('changeset_status') |
|
404 | 404 | change_status = request.POST.get('change_changeset_status') |
|
405 | 405 | text = request.POST.get('text') |
|
406 | 406 | |
|
407 | 407 | allowed_to_change_status = self._get_is_allowed_change_status(pull_request) |
|
408 | 408 | if status and change_status and allowed_to_change_status: |
|
409 | 409 | text = text or (_('Status change -> %s') |
|
410 | 410 | % ChangesetStatus.get_status_lbl(status)) |
|
411 | 411 | comm = ChangesetCommentsModel().create( |
|
412 | 412 | text=text, |
|
413 | 413 | repo=c.rhodecode_db_repo.repo_id, |
|
414 | 414 | user=c.rhodecode_user.user_id, |
|
415 | 415 | pull_request=pull_request_id, |
|
416 | 416 | f_path=request.POST.get('f_path'), |
|
417 | 417 | line_no=request.POST.get('line'), |
|
418 | 418 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
419 | 419 | if status and change_status and allowed_to_change_status else None) |
|
420 | 420 | ) |
|
421 | 421 | |
|
422 | 422 | action_logger(self.rhodecode_user, |
|
423 | 423 | 'user_commented_pull_request:%s' % pull_request_id, |
|
424 | 424 | c.rhodecode_db_repo, self.ip_addr, self.sa) |
|
425 | 425 | |
|
426 | 426 | if allowed_to_change_status: |
|
427 | 427 | # get status if set ! |
|
428 | 428 | if status and change_status: |
|
429 | 429 | ChangesetStatusModel().set_status( |
|
430 | 430 | c.rhodecode_db_repo.repo_id, |
|
431 | 431 | status, |
|
432 | 432 | c.rhodecode_user.user_id, |
|
433 | 433 | comm, |
|
434 | 434 | pull_request=pull_request_id |
|
435 | 435 | ) |
|
436 | 436 | |
|
437 | 437 | if request.POST.get('save_close'): |
|
438 | 438 | if status in ['rejected', 'approved']: |
|
439 | 439 | PullRequestModel().close_pull_request(pull_request_id) |
|
440 | 440 | action_logger(self.rhodecode_user, |
|
441 | 441 | 'user_closed_pull_request:%s' % pull_request_id, |
|
442 | 442 | c.rhodecode_db_repo, self.ip_addr, self.sa) |
|
443 | 443 | else: |
|
444 | 444 | h.flash(_('Closing pull request on other statuses than ' |
|
445 | 445 | 'rejected or approved forbidden'), |
|
446 | 446 | category='warning') |
|
447 | 447 | |
|
448 | 448 | Session().commit() |
|
449 | 449 | |
|
450 | 450 | if not request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
451 | 451 | return redirect(h.url('pullrequest_show', repo_name=repo_name, |
|
452 | 452 | pull_request_id=pull_request_id)) |
|
453 | 453 | |
|
454 | 454 | data = { |
|
455 | 455 | 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), |
|
456 | 456 | } |
|
457 | 457 | if comm: |
|
458 | 458 | c.co = comm |
|
459 | 459 | data.update(comm.get_dict()) |
|
460 | 460 | data.update({'rendered_text': |
|
461 | 461 | render('changeset/changeset_comment_block.html')}) |
|
462 | 462 | |
|
463 | 463 | return data |
|
464 | 464 | |
|
465 | 465 | @NotAnonymous() |
|
466 | 466 | @jsonify |
|
467 | 467 | def delete_comment(self, repo_name, comment_id): |
|
468 | 468 | co = ChangesetComment.get(comment_id) |
|
469 | 469 | if co.pull_request.is_closed(): |
|
470 | 470 | #don't allow deleting comments on closed pull request |
|
471 | 471 | raise HTTPForbidden() |
|
472 | 472 | |
|
473 | 473 | owner = co.author.user_id == c.rhodecode_user.user_id |
|
474 | 474 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: |
|
475 | 475 | ChangesetCommentsModel().delete(comment=co) |
|
476 | 476 | Session().commit() |
|
477 | 477 | return True |
|
478 | 478 | else: |
|
479 | 479 | raise HTTPForbidden() |
@@ -1,544 +1,544 | |||
|
1 | 1 | import re |
|
2 | 2 | from itertools import chain |
|
3 | 3 | from dulwich import objects |
|
4 | 4 | from subprocess import Popen, PIPE |
|
5 | 5 | import rhodecode |
|
6 | 6 | from rhodecode.lib.vcs.conf import settings |
|
7 | 7 | from rhodecode.lib.vcs.exceptions import RepositoryError |
|
8 | 8 | from rhodecode.lib.vcs.exceptions import ChangesetError |
|
9 | 9 | from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError |
|
10 | 10 | from rhodecode.lib.vcs.exceptions import VCSError |
|
11 | 11 | from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError |
|
12 | 12 | from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError |
|
13 | 13 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset |
|
14 | 14 | from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \ |
|
15 | 15 | RemovedFileNode, SubModuleNode, ChangedFileNodesGenerator,\ |
|
16 | 16 | AddedFileNodesGenerator, RemovedFileNodesGenerator |
|
17 | 17 | from rhodecode.lib.vcs.utils import safe_unicode |
|
18 | 18 | from rhodecode.lib.vcs.utils import date_fromtimestamp |
|
19 | 19 | from rhodecode.lib.vcs.utils.lazy import LazyProperty |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | class GitChangeset(BaseChangeset): |
|
23 | 23 | """ |
|
24 | 24 | Represents state of the repository at single revision. |
|
25 | 25 | """ |
|
26 | 26 | |
|
27 | 27 | def __init__(self, repository, revision): |
|
28 | 28 | self._stat_modes = {} |
|
29 | 29 | self.repository = repository |
|
30 | 30 | |
|
31 | 31 | try: |
|
32 | 32 | commit = self.repository._repo.get_object(revision) |
|
33 | 33 | if isinstance(commit, objects.Tag): |
|
34 | 34 | revision = commit.object[1] |
|
35 | 35 | commit = self.repository._repo.get_object(commit.object[1]) |
|
36 | 36 | except KeyError: |
|
37 | 37 | raise RepositoryError("Cannot get object with id %s" % revision) |
|
38 | 38 | self.raw_id = revision |
|
39 | 39 | self.id = self.raw_id |
|
40 | 40 | self.short_id = self.raw_id[:12] |
|
41 | 41 | self._commit = commit |
|
42 | 42 | |
|
43 | 43 | self._tree_id = commit.tree |
|
44 | 44 | self._commiter_property = 'committer' |
|
45 | 45 | self._author_property = 'author' |
|
46 | 46 | self._date_property = 'commit_time' |
|
47 | 47 | self._date_tz_property = 'commit_timezone' |
|
48 | 48 | self.revision = repository.revisions.index(revision) |
|
49 | 49 | |
|
50 | 50 | self.message = safe_unicode(commit.message) |
|
51 | 51 | |
|
52 | 52 | self.nodes = {} |
|
53 | 53 | self._paths = {} |
|
54 | 54 | |
|
55 | 55 | @LazyProperty |
|
56 | 56 | def commiter(self): |
|
57 | 57 | return safe_unicode(getattr(self._commit, self._commiter_property)) |
|
58 | 58 | |
|
59 | 59 | @LazyProperty |
|
60 | 60 | def author(self): |
|
61 | 61 | return safe_unicode(getattr(self._commit, self._author_property)) |
|
62 | 62 | |
|
63 | 63 | @LazyProperty |
|
64 | 64 | def date(self): |
|
65 | 65 | return date_fromtimestamp(getattr(self._commit, self._date_property), |
|
66 | 66 | getattr(self._commit, self._date_tz_property)) |
|
67 | 67 | |
|
68 | 68 | @LazyProperty |
|
69 | 69 | def _timestamp(self): |
|
70 | 70 | return getattr(self._commit, self._date_property) |
|
71 | 71 | |
|
72 | 72 | @LazyProperty |
|
73 | 73 | def status(self): |
|
74 | 74 | """ |
|
75 | 75 | Returns modified, added, removed, deleted files for current changeset |
|
76 | 76 | """ |
|
77 | 77 | return self.changed, self.added, self.removed |
|
78 | 78 | |
|
79 | 79 | @LazyProperty |
|
80 | 80 | def tags(self): |
|
81 | 81 | _tags = [] |
|
82 | 82 | for tname, tsha in self.repository.tags.iteritems(): |
|
83 | 83 | if tsha == self.raw_id: |
|
84 | 84 | _tags.append(tname) |
|
85 | 85 | return _tags |
|
86 | 86 | |
|
87 | 87 | @LazyProperty |
|
88 | 88 | def branch(self): |
|
89 | 89 | |
|
90 | 90 | heads = self.repository._heads(reverse=False) |
|
91 | 91 | |
|
92 | 92 | ref = heads.get(self.raw_id) |
|
93 | 93 | if ref: |
|
94 | 94 | return safe_unicode(ref) |
|
95 | 95 | |
|
96 | 96 | def _fix_path(self, path): |
|
97 | 97 | """ |
|
98 | 98 | Paths are stored without trailing slash so we need to get rid off it if |
|
99 | 99 | needed. |
|
100 | 100 | """ |
|
101 | 101 | if path.endswith('/'): |
|
102 | 102 | path = path.rstrip('/') |
|
103 | 103 | return path |
|
104 | 104 | |
|
105 | 105 | def _get_id_for_path(self, path): |
|
106 | 106 | |
|
107 | 107 | # FIXME: Please, spare a couple of minutes and make those codes cleaner; |
|
108 | 108 | if not path in self._paths: |
|
109 | 109 | path = path.strip('/') |
|
110 | 110 | # set root tree |
|
111 | 111 | tree = self.repository._repo[self._tree_id] |
|
112 | 112 | if path == '': |
|
113 | 113 | self._paths[''] = tree.id |
|
114 | 114 | return tree.id |
|
115 | 115 | splitted = path.split('/') |
|
116 | 116 | dirs, name = splitted[:-1], splitted[-1] |
|
117 | 117 | curdir = '' |
|
118 | 118 | |
|
119 | 119 | # initially extract things from root dir |
|
120 | 120 | for item, stat, id in tree.iteritems(): |
|
121 | 121 | if curdir: |
|
122 | 122 | name = '/'.join((curdir, item)) |
|
123 | 123 | else: |
|
124 | 124 | name = item |
|
125 | 125 | self._paths[name] = id |
|
126 | 126 | self._stat_modes[name] = stat |
|
127 | 127 | |
|
128 | 128 | for dir in dirs: |
|
129 | 129 | if curdir: |
|
130 | 130 | curdir = '/'.join((curdir, dir)) |
|
131 | 131 | else: |
|
132 | 132 | curdir = dir |
|
133 | 133 | dir_id = None |
|
134 | 134 | for item, stat, id in tree.iteritems(): |
|
135 | 135 | if dir == item: |
|
136 | 136 | dir_id = id |
|
137 | 137 | if dir_id: |
|
138 | 138 | # Update tree |
|
139 | 139 | tree = self.repository._repo[dir_id] |
|
140 | 140 | if not isinstance(tree, objects.Tree): |
|
141 | 141 | raise ChangesetError('%s is not a directory' % curdir) |
|
142 | 142 | else: |
|
143 | 143 | raise ChangesetError('%s have not been found' % curdir) |
|
144 | 144 | |
|
145 | 145 | # cache all items from the given traversed tree |
|
146 | 146 | for item, stat, id in tree.iteritems(): |
|
147 | 147 | if curdir: |
|
148 | 148 | name = '/'.join((curdir, item)) |
|
149 | 149 | else: |
|
150 | 150 | name = item |
|
151 | 151 | self._paths[name] = id |
|
152 | 152 | self._stat_modes[name] = stat |
|
153 | 153 | if not path in self._paths: |
|
154 | 154 | raise NodeDoesNotExistError("There is no file nor directory " |
|
155 | 155 | "at the given path %r at revision %r" |
|
156 | 156 | % (path, self.short_id)) |
|
157 | 157 | return self._paths[path] |
|
158 | 158 | |
|
159 | 159 | def _get_kind(self, path): |
|
160 | 160 | obj = self.repository._repo[self._get_id_for_path(path)] |
|
161 | 161 | if isinstance(obj, objects.Blob): |
|
162 | 162 | return NodeKind.FILE |
|
163 | 163 | elif isinstance(obj, objects.Tree): |
|
164 | 164 | return NodeKind.DIR |
|
165 | 165 | |
|
166 | 166 | def _get_filectx(self, path): |
|
167 | 167 | path = self._fix_path(path) |
|
168 | 168 | if self._get_kind(path) != NodeKind.FILE: |
|
169 | 169 | raise ChangesetError("File does not exist for revision %r at " |
|
170 | 170 | " %r" % (self.raw_id, path)) |
|
171 | 171 | return path |
|
172 | 172 | |
|
173 | 173 | def _get_file_nodes(self): |
|
174 | 174 | return chain(*(t[2] for t in self.walk())) |
|
175 | 175 | |
|
176 | 176 | @LazyProperty |
|
177 | 177 | def parents(self): |
|
178 | 178 | """ |
|
179 | 179 | Returns list of parents changesets. |
|
180 | 180 | """ |
|
181 | 181 | return [self.repository.get_changeset(parent) |
|
182 | 182 | for parent in self._commit.parents] |
|
183 | 183 | |
|
184 | 184 | @LazyProperty |
|
185 | 185 | def children(self): |
|
186 | 186 | """ |
|
187 | 187 | Returns list of children changesets. |
|
188 | 188 | """ |
|
189 | 189 | so, se = self.repository.run_git_command( |
|
190 | 190 | "rev-list --all --children | grep '^%s'" % self.raw_id |
|
191 | 191 | ) |
|
192 | 192 | |
|
193 | 193 | children = [] |
|
194 | 194 | for l in so.splitlines(): |
|
195 | 195 | childs = l.split(' ')[1:] |
|
196 | 196 | children.extend(childs) |
|
197 | 197 | return [self.repository.get_changeset(cs) for cs in children] |
|
198 | 198 | |
|
199 | 199 | def next(self, branch=None): |
|
200 | 200 | |
|
201 | 201 | if branch and self.branch != branch: |
|
202 | 202 | raise VCSError('Branch option used on changeset not belonging ' |
|
203 | 203 | 'to that branch') |
|
204 | 204 | |
|
205 | 205 | def _next(changeset, branch): |
|
206 | 206 | try: |
|
207 | 207 | next_ = changeset.revision + 1 |
|
208 | 208 | next_rev = changeset.repository.revisions[next_] |
|
209 | 209 | except IndexError: |
|
210 | 210 | raise ChangesetDoesNotExistError |
|
211 | 211 | cs = changeset.repository.get_changeset(next_rev) |
|
212 | 212 | |
|
213 | 213 | if branch and branch != cs.branch: |
|
214 | 214 | return _next(cs, branch) |
|
215 | 215 | |
|
216 | 216 | return cs |
|
217 | 217 | |
|
218 | 218 | return _next(self, branch) |
|
219 | 219 | |
|
220 | 220 | def prev(self, branch=None): |
|
221 | 221 | if branch and self.branch != branch: |
|
222 | 222 | raise VCSError('Branch option used on changeset not belonging ' |
|
223 | 223 | 'to that branch') |
|
224 | 224 | |
|
225 | 225 | def _prev(changeset, branch): |
|
226 | 226 | try: |
|
227 | 227 | prev_ = changeset.revision - 1 |
|
228 | 228 | if prev_ < 0: |
|
229 | 229 | raise IndexError |
|
230 | 230 | prev_rev = changeset.repository.revisions[prev_] |
|
231 | 231 | except IndexError: |
|
232 | 232 | raise ChangesetDoesNotExistError |
|
233 | 233 | |
|
234 | 234 | cs = changeset.repository.get_changeset(prev_rev) |
|
235 | 235 | |
|
236 | 236 | if branch and branch != cs.branch: |
|
237 | 237 | return _prev(cs, branch) |
|
238 | 238 | |
|
239 | 239 | return cs |
|
240 | 240 | |
|
241 | 241 | return _prev(self, branch) |
|
242 | 242 | |
|
243 | 243 | def diff(self, ignore_whitespace=True, context=3): |
|
244 | 244 | rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET |
|
245 | 245 | rev2 = self |
|
246 | 246 | return ''.join(self.repository.get_diff(rev1, rev2, |
|
247 | 247 | ignore_whitespace=ignore_whitespace, |
|
248 | 248 | context=context)) |
|
249 | 249 | |
|
250 | 250 | def get_file_mode(self, path): |
|
251 | 251 | """ |
|
252 | 252 | Returns stat mode of the file at the given ``path``. |
|
253 | 253 | """ |
|
254 | 254 | # ensure path is traversed |
|
255 | 255 | self._get_id_for_path(path) |
|
256 | 256 | return self._stat_modes[path] |
|
257 | 257 | |
|
258 | 258 | def get_file_content(self, path): |
|
259 | 259 | """ |
|
260 | 260 | Returns content of the file at given ``path``. |
|
261 | 261 | """ |
|
262 | 262 | id = self._get_id_for_path(path) |
|
263 | 263 | blob = self.repository._repo[id] |
|
264 | 264 | return blob.as_pretty_string() |
|
265 | 265 | |
|
266 | 266 | def get_file_size(self, path): |
|
267 | 267 | """ |
|
268 | 268 | Returns size of the file at given ``path``. |
|
269 | 269 | """ |
|
270 | 270 | id = self._get_id_for_path(path) |
|
271 | 271 | blob = self.repository._repo[id] |
|
272 | 272 | return blob.raw_length() |
|
273 | 273 | |
|
274 | 274 | def get_file_changeset(self, path): |
|
275 | 275 | """ |
|
276 | 276 | Returns last commit of the file at the given ``path``. |
|
277 | 277 | """ |
|
278 | 278 | node = self.get_node(path) |
|
279 | 279 | return node.history[0] |
|
280 | 280 | |
|
281 | 281 | def get_file_history(self, path): |
|
282 | 282 | """ |
|
283 | 283 | Returns history of file as reversed list of ``Changeset`` objects for |
|
284 | 284 | which file at given ``path`` has been modified. |
|
285 | 285 | |
|
286 | 286 | TODO: This function now uses os underlying 'git' and 'grep' commands |
|
287 | 287 | which is generally not good. Should be replaced with algorithm |
|
288 | 288 | iterating commits. |
|
289 | 289 | """ |
|
290 | 290 | self._get_filectx(path) |
|
291 | 291 | |
|
292 | 292 | cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % ( |
|
293 | 293 | self.id, path |
|
294 | 294 | ) |
|
295 | 295 | so, se = self.repository.run_git_command(cmd) |
|
296 | 296 | ids = re.findall(r'[0-9a-fA-F]{40}', so) |
|
297 | 297 | return [self.repository.get_changeset(id) for id in ids] |
|
298 | 298 | |
|
299 | 299 | def get_file_history_2(self, path): |
|
300 | 300 | """ |
|
301 | 301 | Returns history of file as reversed list of ``Changeset`` objects for |
|
302 | 302 | which file at given ``path`` has been modified. |
|
303 | 303 | |
|
304 | 304 | """ |
|
305 | 305 | self._get_filectx(path) |
|
306 | 306 | from dulwich.walk import Walker |
|
307 | 307 | include = [self.id] |
|
308 | 308 | walker = Walker(self.repository._repo.object_store, include, |
|
309 | 309 | paths=[path], max_entries=1) |
|
310 | 310 | return [self.repository.get_changeset(sha) |
|
311 | 311 | for sha in (x.commit.id for x in walker)] |
|
312 | 312 | |
|
313 | 313 | def get_file_annotate(self, path): |
|
314 | 314 | """ |
|
315 | 315 | Returns a generator of four element tuples with |
|
316 | 316 | lineno, sha, changeset lazy loader and line |
|
317 | 317 | |
|
318 | 318 | TODO: This function now uses os underlying 'git' command which is |
|
319 | 319 | generally not good. Should be replaced with algorithm iterating |
|
320 | 320 | commits. |
|
321 | 321 | """ |
|
322 | 322 | cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path) |
|
323 | 323 | # -l ==> outputs long shas (and we need all 40 characters) |
|
324 | 324 | # --root ==> doesn't put '^' character for bounderies |
|
325 | 325 | # -r sha ==> blames for the given revision |
|
326 | 326 | so, se = self.repository.run_git_command(cmd) |
|
327 | 327 | |
|
328 | 328 | for i, blame_line in enumerate(so.split('\n')[:-1]): |
|
329 | 329 | ln_no = i + 1 |
|
330 | 330 | sha, line = re.split(r' ', blame_line, 1) |
|
331 | 331 | yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line) |
|
332 | 332 | |
|
333 | 333 | def fill_archive(self, stream=None, kind='tgz', prefix=None, |
|
334 | 334 | subrepos=False): |
|
335 | 335 | """ |
|
336 | 336 | Fills up given stream. |
|
337 | 337 | |
|
338 | 338 | :param stream: file like object. |
|
339 | 339 | :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``. |
|
340 | 340 | Default: ``tgz``. |
|
341 | 341 | :param prefix: name of root directory in archive. |
|
342 | 342 | Default is repository name and changeset's raw_id joined with dash |
|
343 | 343 | (``repo-tip.<KIND>``). |
|
344 | 344 | :param subrepos: include subrepos in this archive. |
|
345 | 345 | |
|
346 | 346 | :raise ImproperArchiveTypeError: If given kind is wrong. |
|
347 | 347 | :raise VcsError: If given stream is None |
|
348 | 348 | |
|
349 | 349 | """ |
|
350 | 350 | allowed_kinds = settings.ARCHIVE_SPECS.keys() |
|
351 | 351 | if kind not in allowed_kinds: |
|
352 | 352 | raise ImproperArchiveTypeError('Archive kind not supported use one' |
|
353 | 353 | 'of %s', allowed_kinds) |
|
354 | 354 | |
|
355 | 355 | if prefix is None: |
|
356 | 356 | prefix = '%s-%s' % (self.repository.name, self.short_id) |
|
357 | 357 | elif prefix.startswith('/'): |
|
358 | 358 | raise VCSError("Prefix cannot start with leading slash") |
|
359 | 359 | elif prefix.strip() == '': |
|
360 | 360 | raise VCSError("Prefix cannot be empty") |
|
361 | 361 | |
|
362 | 362 | if kind == 'zip': |
|
363 | 363 | frmt = 'zip' |
|
364 | 364 | else: |
|
365 | 365 | frmt = 'tar' |
|
366 | 366 | _git_path = rhodecode.CONFIG.get('git_path', 'git') |
|
367 |
cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path, |
|
|
367 | cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path, | |
|
368 | 368 | frmt, prefix, self.raw_id) |
|
369 | 369 | if kind == 'tgz': |
|
370 | 370 | cmd += ' | gzip -9' |
|
371 | 371 | elif kind == 'tbz2': |
|
372 | 372 | cmd += ' | bzip2 -9' |
|
373 | 373 | |
|
374 | 374 | if stream is None: |
|
375 | 375 | raise VCSError('You need to pass in a valid stream for filling' |
|
376 | 376 | ' with archival data') |
|
377 | 377 | popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, |
|
378 | 378 | cwd=self.repository.path) |
|
379 | 379 | |
|
380 | 380 | buffer_size = 1024 * 8 |
|
381 | 381 | chunk = popen.stdout.read(buffer_size) |
|
382 | 382 | while chunk: |
|
383 | 383 | stream.write(chunk) |
|
384 | 384 | chunk = popen.stdout.read(buffer_size) |
|
385 | 385 | # Make sure all descriptors would be read |
|
386 | 386 | popen.communicate() |
|
387 | 387 | |
|
388 | 388 | def get_nodes(self, path): |
|
389 | 389 | if self._get_kind(path) != NodeKind.DIR: |
|
390 | 390 | raise ChangesetError("Directory does not exist for revision %r at " |
|
391 | 391 | " %r" % (self.revision, path)) |
|
392 | 392 | path = self._fix_path(path) |
|
393 | 393 | id = self._get_id_for_path(path) |
|
394 | 394 | tree = self.repository._repo[id] |
|
395 | 395 | dirnodes = [] |
|
396 | 396 | filenodes = [] |
|
397 | 397 | als = self.repository.alias |
|
398 | 398 | for name, stat, id in tree.iteritems(): |
|
399 | 399 | if objects.S_ISGITLINK(stat): |
|
400 | 400 | dirnodes.append(SubModuleNode(name, url=None, changeset=id, |
|
401 | 401 | alias=als)) |
|
402 | 402 | continue |
|
403 | 403 | |
|
404 | 404 | obj = self.repository._repo.get_object(id) |
|
405 | 405 | if path != '': |
|
406 | 406 | obj_path = '/'.join((path, name)) |
|
407 | 407 | else: |
|
408 | 408 | obj_path = name |
|
409 | 409 | if obj_path not in self._stat_modes: |
|
410 | 410 | self._stat_modes[obj_path] = stat |
|
411 | 411 | if isinstance(obj, objects.Tree): |
|
412 | 412 | dirnodes.append(DirNode(obj_path, changeset=self)) |
|
413 | 413 | elif isinstance(obj, objects.Blob): |
|
414 | 414 | filenodes.append(FileNode(obj_path, changeset=self, mode=stat)) |
|
415 | 415 | else: |
|
416 | 416 | raise ChangesetError("Requested object should be Tree " |
|
417 | 417 | "or Blob, is %r" % type(obj)) |
|
418 | 418 | nodes = dirnodes + filenodes |
|
419 | 419 | for node in nodes: |
|
420 | 420 | if not node.path in self.nodes: |
|
421 | 421 | self.nodes[node.path] = node |
|
422 | 422 | nodes.sort() |
|
423 | 423 | return nodes |
|
424 | 424 | |
|
425 | 425 | def get_node(self, path): |
|
426 | 426 | if isinstance(path, unicode): |
|
427 | 427 | path = path.encode('utf-8') |
|
428 | 428 | path = self._fix_path(path) |
|
429 | 429 | if not path in self.nodes: |
|
430 | 430 | try: |
|
431 | 431 | id_ = self._get_id_for_path(path) |
|
432 | 432 | except ChangesetError: |
|
433 | 433 | raise NodeDoesNotExistError("Cannot find one of parents' " |
|
434 | 434 | "directories for a given path: %s" % path) |
|
435 | 435 | |
|
436 | 436 | _GL = lambda m: m and objects.S_ISGITLINK(m) |
|
437 | 437 | if _GL(self._stat_modes.get(path)): |
|
438 | 438 | node = SubModuleNode(path, url=None, changeset=id_, |
|
439 | 439 | alias=self.repository.alias) |
|
440 | 440 | else: |
|
441 | 441 | obj = self.repository._repo.get_object(id_) |
|
442 | 442 | |
|
443 | 443 | if isinstance(obj, objects.Tree): |
|
444 | 444 | if path == '': |
|
445 | 445 | node = RootNode(changeset=self) |
|
446 | 446 | else: |
|
447 | 447 | node = DirNode(path, changeset=self) |
|
448 | 448 | node._tree = obj |
|
449 | 449 | elif isinstance(obj, objects.Blob): |
|
450 | 450 | node = FileNode(path, changeset=self) |
|
451 | 451 | node._blob = obj |
|
452 | 452 | else: |
|
453 | 453 | raise NodeDoesNotExistError("There is no file nor directory " |
|
454 | 454 | "at the given path %r at revision %r" |
|
455 | 455 | % (path, self.short_id)) |
|
456 | 456 | # cache node |
|
457 | 457 | self.nodes[path] = node |
|
458 | 458 | return self.nodes[path] |
|
459 | 459 | |
|
460 | 460 | @LazyProperty |
|
461 | 461 | def affected_files(self): |
|
462 | 462 | """ |
|
463 | 463 | Get's a fast accessible file changes for given changeset |
|
464 | 464 | """ |
|
465 | 465 | a, m, d = self._changes_cache |
|
466 | 466 | return list(a.union(m).union(d)) |
|
467 | 467 | |
|
468 | 468 | @LazyProperty |
|
469 | 469 | def _diff_name_status(self): |
|
470 | 470 | output = [] |
|
471 | 471 | for parent in self.parents: |
|
472 | 472 | cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, |
|
473 | 473 | self.raw_id) |
|
474 | 474 | so, se = self.repository.run_git_command(cmd) |
|
475 | 475 | output.append(so.strip()) |
|
476 | 476 | return '\n'.join(output) |
|
477 | 477 | |
|
478 | 478 | @LazyProperty |
|
479 | 479 | def _changes_cache(self): |
|
480 | 480 | added = set() |
|
481 | 481 | modified = set() |
|
482 | 482 | deleted = set() |
|
483 | 483 | _r = self.repository._repo |
|
484 | 484 | |
|
485 | 485 | parents = self.parents |
|
486 | 486 | if not self.parents: |
|
487 | 487 | parents = [EmptyChangeset()] |
|
488 | 488 | for parent in parents: |
|
489 | 489 | if isinstance(parent, EmptyChangeset): |
|
490 | 490 | oid = None |
|
491 | 491 | else: |
|
492 | 492 | oid = _r[parent.raw_id].tree |
|
493 | 493 | changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree) |
|
494 | 494 | for (oldpath, newpath), (_, _), (_, _) in changes: |
|
495 | 495 | if newpath and oldpath: |
|
496 | 496 | modified.add(newpath) |
|
497 | 497 | elif newpath and not oldpath: |
|
498 | 498 | added.add(newpath) |
|
499 | 499 | elif not newpath and oldpath: |
|
500 | 500 | deleted.add(oldpath) |
|
501 | 501 | return added, modified, deleted |
|
502 | 502 | |
|
503 | 503 | def _get_paths_for_status(self, status): |
|
504 | 504 | """ |
|
505 | 505 | Returns sorted list of paths for given ``status``. |
|
506 | 506 | |
|
507 | 507 | :param status: one of: *added*, *modified* or *deleted* |
|
508 | 508 | """ |
|
509 | 509 | a, m, d = self._changes_cache |
|
510 | 510 | return sorted({ |
|
511 | 511 | 'added': list(a), |
|
512 | 512 | 'modified': list(m), |
|
513 | 513 | 'deleted': list(d)}[status] |
|
514 | 514 | ) |
|
515 | 515 | |
|
516 | 516 | @LazyProperty |
|
517 | 517 | def added(self): |
|
518 | 518 | """ |
|
519 | 519 | Returns list of added ``FileNode`` objects. |
|
520 | 520 | """ |
|
521 | 521 | if not self.parents: |
|
522 | 522 | return list(self._get_file_nodes()) |
|
523 | 523 | return AddedFileNodesGenerator([n for n in |
|
524 | 524 | self._get_paths_for_status('added')], self) |
|
525 | 525 | |
|
526 | 526 | @LazyProperty |
|
527 | 527 | def changed(self): |
|
528 | 528 | """ |
|
529 | 529 | Returns list of modified ``FileNode`` objects. |
|
530 | 530 | """ |
|
531 | 531 | if not self.parents: |
|
532 | 532 | return [] |
|
533 | 533 | return ChangedFileNodesGenerator([n for n in |
|
534 | 534 | self._get_paths_for_status('modified')], self) |
|
535 | 535 | |
|
536 | 536 | @LazyProperty |
|
537 | 537 | def removed(self): |
|
538 | 538 | """ |
|
539 | 539 | Returns list of removed ``FileNode`` objects. |
|
540 | 540 | """ |
|
541 | 541 | if not self.parents: |
|
542 | 542 | return [] |
|
543 | 543 | return RemovedFileNodesGenerator([n for n in |
|
544 | 544 | self._get_paths_for_status('deleted')], self) |
@@ -1,373 +1,373 | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.html"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name} |
|
6 | 6 | </%def> |
|
7 | 7 | |
|
8 | 8 | <%def name="breadcrumbs_links()"> |
|
9 | 9 | ${h.link_to(_(u'Home'),h.url('/'))} |
|
10 | 10 | » |
|
11 | 11 | ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)} |
|
12 | 12 | » |
|
13 | 13 | ${_('edit')} |
|
14 | 14 | </%def> |
|
15 | 15 | |
|
16 | 16 | <%def name="page_nav()"> |
|
17 | 17 | ${self.menu('options')} |
|
18 | 18 | </%def> |
|
19 | 19 | |
|
20 | 20 | <%def name="main()"> |
|
21 | 21 | <div class="box box-left"> |
|
22 | 22 | <!-- box / title --> |
|
23 | 23 | <div class="title"> |
|
24 | 24 | ${self.breadcrumbs()} |
|
25 | 25 | </div> |
|
26 | 26 | ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')} |
|
27 | 27 | <div class="form"> |
|
28 | 28 | <!-- fields --> |
|
29 | 29 | <div class="fields"> |
|
30 | 30 | <div class="field"> |
|
31 | 31 | <div class="label"> |
|
32 | 32 | <label for="repo_name">${_('Name')}:</label> |
|
33 | 33 | </div> |
|
34 | 34 | <div class="input"> |
|
35 | 35 | ${h.text('repo_name',class_="medium")} |
|
36 | 36 | </div> |
|
37 | 37 | </div> |
|
38 | 38 | <div class="field"> |
|
39 | 39 | <div class="label"> |
|
40 | 40 | <label for="clone_uri">${_('Clone uri')}:</label> |
|
41 | 41 | </div> |
|
42 | 42 | <div class="input"> |
|
43 | 43 | ${h.text('clone_uri',class_="medium")} |
|
44 | 44 | <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span> |
|
45 | 45 | </div> |
|
46 | 46 | </div> |
|
47 | 47 | <div class="field"> |
|
48 | 48 | <div class="label"> |
|
49 | 49 | <label for="repo_group">${_('Repository group')}:</label> |
|
50 | 50 | </div> |
|
51 | 51 | <div class="input"> |
|
52 | 52 | ${h.select('repo_group','',c.repo_groups,class_="medium")} |
|
53 | 53 | <span class="help-block">${_('Optional select a group to put this repository into.')}</span> |
|
54 | 54 | </div> |
|
55 | 55 | </div> |
|
56 | 56 | <div class="field"> |
|
57 | 57 | <div class="label"> |
|
58 | 58 | <label for="repo_type">${_('Type')}:</label> |
|
59 | 59 | </div> |
|
60 | 60 | <div class="input"> |
|
61 | 61 | ${h.select('repo_type','hg',c.backends,class_="medium")} |
|
62 | 62 | </div> |
|
63 | 63 | </div> |
|
64 | 64 | <div class="field"> |
|
65 | 65 | <div class="label"> |
|
66 | 66 | <label for="repo_landing_rev">${_('Landing revision')}:</label> |
|
67 | 67 | </div> |
|
68 | 68 | <div class="input"> |
|
69 | 69 | ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")} |
|
70 | 70 | <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span> |
|
71 | 71 | </div> |
|
72 | 72 | </div> |
|
73 | 73 | <div class="field"> |
|
74 | 74 | <div class="label label-textarea"> |
|
75 | 75 | <label for="repo_description">${_('Description')}:</label> |
|
76 | 76 | </div> |
|
77 | 77 | <div class="textarea text-area editor"> |
|
78 | 78 | ${h.textarea('repo_description')} |
|
79 | 79 | <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span> |
|
80 | 80 | </div> |
|
81 | 81 | </div> |
|
82 | 82 | |
|
83 | 83 | <div class="field"> |
|
84 | 84 | <div class="label label-checkbox"> |
|
85 | 85 | <label for="repo_private">${_('Private repository')}:</label> |
|
86 | 86 | </div> |
|
87 | 87 | <div class="checkboxes"> |
|
88 | 88 | ${h.checkbox('repo_private',value="True")} |
|
89 | 89 | <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span> |
|
90 | 90 | </div> |
|
91 | 91 | </div> |
|
92 | 92 | <div class="field"> |
|
93 | 93 | <div class="label label-checkbox"> |
|
94 | 94 | <label for="repo_enable_statistics">${_('Enable statistics')}:</label> |
|
95 | 95 | </div> |
|
96 | 96 | <div class="checkboxes"> |
|
97 | 97 | ${h.checkbox('repo_enable_statistics',value="True")} |
|
98 | 98 | <span class="help-block">${_('Enable statistics window on summary page.')}</span> |
|
99 | 99 | </div> |
|
100 | 100 | </div> |
|
101 | 101 | <div class="field"> |
|
102 | 102 | <div class="label label-checkbox"> |
|
103 | 103 | <label for="repo_enable_downloads">${_('Enable downloads')}:</label> |
|
104 | 104 | </div> |
|
105 | 105 | <div class="checkboxes"> |
|
106 | 106 | ${h.checkbox('repo_enable_downloads',value="True")} |
|
107 | 107 | <span class="help-block">${_('Enable download menu on summary page.')}</span> |
|
108 | 108 | </div> |
|
109 | 109 | </div> |
|
110 | 110 | <div class="field"> |
|
111 | 111 | <div class="label label-checkbox"> |
|
112 | 112 | <label for="repo_enable_locking">${_('Enable locking')}:</label> |
|
113 | 113 | </div> |
|
114 | 114 | <div class="checkboxes"> |
|
115 | 115 | ${h.checkbox('repo_enable_locking',value="True")} |
|
116 | 116 | <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span> |
|
117 | 117 | </div> |
|
118 | 118 | </div> |
|
119 | 119 | <div class="field"> |
|
120 | 120 | <div class="label"> |
|
121 | 121 | <label for="user">${_('Owner')}:</label> |
|
122 | 122 | </div> |
|
123 | 123 | <div class="input input-medium ac"> |
|
124 | 124 | <div class="perm_ac"> |
|
125 | 125 | ${h.text('user',class_='yui-ac-input')} |
|
126 | 126 | <span class="help-block">${_('Change owner of this repository.')}</span> |
|
127 | 127 | <div id="owner_container"></div> |
|
128 | 128 | </div> |
|
129 | 129 | </div> |
|
130 | 130 | </div> |
|
131 | 131 | %if c.visual.repository_fields: |
|
132 | 132 | ## EXTRA FIELDS |
|
133 | 133 | %for field in c.repo_fields: |
|
134 | 134 | <div class="field"> |
|
135 | 135 | <div class="label"> |
|
136 | 136 | <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label> |
|
137 | 137 | </div> |
|
138 | 138 | <div class="input input-medium"> |
|
139 | 139 | ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} |
|
140 | 140 | %if field.field_desc: |
|
141 | 141 | <span class="help-block">${field.field_desc}</span> |
|
142 | 142 | %endif |
|
143 | 143 | </div> |
|
144 | 144 | </div> |
|
145 | 145 | %endfor |
|
146 | 146 | %endif |
|
147 | 147 | <div class="field"> |
|
148 | 148 | <div class="label"> |
|
149 | 149 | <label for="input">${_('Permissions')}:</label> |
|
150 | 150 | </div> |
|
151 | 151 | <div class="input"> |
|
152 | 152 | <%include file="repo_edit_perms.html"/> |
|
153 | 153 | </div> |
|
154 | 154 | |
|
155 | 155 | <div class="buttons"> |
|
156 | 156 | ${h.submit('save',_('Save'),class_="ui-btn large")} |
|
157 | 157 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} |
|
158 | 158 | </div> |
|
159 | 159 | </div> |
|
160 | 160 | </div> |
|
161 | 161 | </div> |
|
162 | 162 | ${h.end_form()} |
|
163 | 163 | </div> |
|
164 | 164 | |
|
165 | 165 | <div class="box box-right"> |
|
166 | 166 | <div class="title"> |
|
167 | 167 | <h5>${_('Administration')}</h5> |
|
168 | 168 | </div> |
|
169 | 169 | |
|
170 | 170 | <h3>${_('Statistics')}</h3> |
|
171 | 171 | ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')} |
|
172 | 172 | <div class="form"> |
|
173 | 173 | <div class="fields"> |
|
174 | 174 | ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")} |
|
175 | 175 | <div class="field" style="border:none;color:#888"> |
|
176 | 176 | <ul> |
|
177 | 177 | <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li> |
|
178 | 178 | <li>${_('Stats gathered')}: ${c.stats_percentage}%</li> |
|
179 | 179 | </ul> |
|
180 | 180 | </div> |
|
181 | 181 | </div> |
|
182 | 182 | </div> |
|
183 | 183 | ${h.end_form()} |
|
184 | 184 | |
|
185 | 185 | %if c.repo_info.clone_uri: |
|
186 | 186 | <h3>${_('Remote')}</h3> |
|
187 | 187 | ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')} |
|
188 | 188 | <div class="form"> |
|
189 | 189 | <div class="fields"> |
|
190 | 190 | ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")} |
|
191 | 191 | <div class="field" style="border:none"> |
|
192 | 192 | <ul> |
|
193 | 193 | <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li> |
|
194 | 194 | </ul> |
|
195 | 195 | </div> |
|
196 | 196 | </div> |
|
197 | 197 | </div> |
|
198 | 198 | ${h.end_form()} |
|
199 | 199 | %endif |
|
200 | 200 | |
|
201 | 201 | <h3>${_('Cache')}</h3> |
|
202 | 202 | ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')} |
|
203 | 203 | <div class="form"> |
|
204 | 204 | <div class="fields"> |
|
205 | 205 | ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")} |
|
206 | 206 | <div class="field" style="border:none;color:#888"> |
|
207 | 207 | <ul> |
|
208 | 208 | <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')} |
|
209 | 209 | </li> |
|
210 | 210 | </ul> |
|
211 | 211 | </div> |
|
212 | 212 | <div class="field" style="border:none;"> |
|
213 | 213 | ${_('List of cached values')} |
|
214 | 214 | <table> |
|
215 | 215 | <tr> |
|
216 | 216 | <th>${_('Prefix')}</th> |
|
217 | 217 | <th>${_('Key')}</th> |
|
218 | 218 | <th>${_('Active')}</th> |
|
219 | 219 | </tr> |
|
220 | 220 | %for cache in c.repo_info.cache_keys: |
|
221 | 221 | <tr> |
|
222 | 222 | <td>${cache.prefix or '-'}</td> |
|
223 | 223 | <td>${cache.cache_key}</td> |
|
224 | 224 | <td>${h.bool2icon(cache.cache_active)}</td> |
|
225 | 225 | </tr> |
|
226 | 226 | %endfor |
|
227 | 227 | </table> |
|
228 | 228 | </div> |
|
229 | 229 | </div> |
|
230 | 230 | </div> |
|
231 | 231 | ${h.end_form()} |
|
232 | 232 | |
|
233 | 233 | <h3>${_('Public journal')}</h3> |
|
234 | 234 | ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')} |
|
235 | 235 | <div class="form"> |
|
236 | 236 | ${h.hidden('auth_token',str(h.get_token()))} |
|
237 | 237 | <div class="field"> |
|
238 | 238 | %if c.in_public_journal: |
|
239 | 239 | ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")} |
|
240 | 240 | %else: |
|
241 | 241 | ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")} |
|
242 | 242 | %endif |
|
243 | 243 | </div> |
|
244 | 244 | <div class="field" style="border:none;color:#888"> |
|
245 | 245 | <ul> |
|
246 | 246 | <li>${_('All actions made on this repository will be accessible to everyone in public journal')} |
|
247 | 247 | </li> |
|
248 | 248 | </ul> |
|
249 | 249 | </div> |
|
250 | 250 | </div> |
|
251 | 251 | ${h.end_form()} |
|
252 | 252 | |
|
253 | 253 | <h3>${_('Locking')}</h3> |
|
254 | 254 | ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')} |
|
255 | 255 | <div class="form"> |
|
256 | 256 | <div class="fields"> |
|
257 | 257 | %if c.repo_info.locked[0]: |
|
258 | 258 | ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")} |
|
259 | 259 | ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))} |
|
260 | 260 | %else: |
|
261 | 261 | ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")} |
|
262 | 262 | ${_('Repository is not locked')} |
|
263 | 263 | %endif |
|
264 | 264 | </div> |
|
265 | 265 | <div class="field" style="border:none;color:#888"> |
|
266 | 266 | <ul> |
|
267 | 267 | <li>${_('Force locking on repository. Works only when anonymous access is disabled')} |
|
268 | 268 | </li> |
|
269 | 269 | </ul> |
|
270 | 270 | </div> |
|
271 | 271 | </div> |
|
272 | 272 | ${h.end_form()} |
|
273 | 273 | |
|
274 | 274 | <h3>${_('Set as fork of')}</h3> |
|
275 | 275 | ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')} |
|
276 | 276 | <div class="form"> |
|
277 | 277 | <div class="fields"> |
|
278 | 278 | ${h.select('id_fork_of','',c.repos_list,class_="medium")} |
|
279 | 279 | ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)} |
|
280 | 280 | </div> |
|
281 | 281 | <div class="field" style="border:none;color:#888"> |
|
282 | 282 | <ul> |
|
283 | 283 | <li>${_('''Manually set this repository as a fork of another from the list''')}</li> |
|
284 | 284 | </ul> |
|
285 | 285 | </div> |
|
286 | 286 | </div> |
|
287 | 287 | ${h.end_form()} |
|
288 | 288 | |
|
289 | 289 | <h3>${_('Delete')}</h3> |
|
290 | 290 | ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')} |
|
291 | 291 | <div class="form"> |
|
292 | 292 | <div class="fields"> |
|
293 | 293 | ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")} |
|
294 | 294 | %if c.repo_info.forks.count(): |
|
295 |
- ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()} |
|
|
295 | - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()} | |
|
296 | 296 | <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label> |
|
297 | 297 | <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label> |
|
298 |
%endif |
|
|
298 | %endif | |
|
299 | 299 | </div> |
|
300 | 300 | <div class="field" style="border:none;color:#888"> |
|
301 | 301 | <ul> |
|
302 | 302 | <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li> |
|
303 | 303 | </ul> |
|
304 | 304 | </div> |
|
305 | 305 | </div> |
|
306 | 306 | ${h.end_form()} |
|
307 | 307 | </div> |
|
308 | 308 | |
|
309 | 309 | ##TODO: this should be controlled by the VISUAL setting |
|
310 | 310 | %if c.visual.repository_fields: |
|
311 | 311 | <div class="box box-left" style="clear:left"> |
|
312 | 312 | <!-- box / title --> |
|
313 | 313 | <div class="title"> |
|
314 | 314 | <h5>${_('Extra fields')}</h5> |
|
315 | 315 | </div> |
|
316 | 316 | |
|
317 | 317 | <div class="emails_wrap"> |
|
318 | 318 | <table class="noborder"> |
|
319 | 319 | %for field in c.repo_fields: |
|
320 | 320 | <tr> |
|
321 | 321 | <td>${field.field_label} (${field.field_key})</td> |
|
322 | 322 | <td>${field.field_type}</td> |
|
323 | 323 | <td> |
|
324 | 324 | ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')} |
|
325 | 325 | ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id, |
|
326 | 326 | class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")} |
|
327 | 327 | ${h.end_form()} |
|
328 | 328 | </td> |
|
329 | 329 | </tr> |
|
330 | 330 | %endfor |
|
331 | 331 | </table> |
|
332 | 332 | </div> |
|
333 | 333 | |
|
334 | 334 | ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')} |
|
335 | 335 | <div class="form"> |
|
336 | 336 | <!-- fields --> |
|
337 | 337 | <div class="fields"> |
|
338 | 338 | <div class="field"> |
|
339 | 339 | <div class="label"> |
|
340 | 340 | <label for="new_field_key">${_('New field key')}:</label> |
|
341 | 341 | </div> |
|
342 | 342 | <div class="input"> |
|
343 | 343 | ${h.text('new_field_key', class_='small')} |
|
344 | 344 | </div> |
|
345 | 345 | </div> |
|
346 | 346 | <div class="field"> |
|
347 | 347 | <div class="label"> |
|
348 | 348 | <label for="new_field_label">${_('New field label')}:</label> |
|
349 | 349 | </div> |
|
350 | 350 | <div class="input"> |
|
351 | 351 | ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))} |
|
352 | 352 | </div> |
|
353 | 353 | </div> |
|
354 | 354 | |
|
355 | 355 | <div class="field"> |
|
356 | 356 | <div class="label"> |
|
357 | 357 | <label for="new_field_desc">${_('New field description')}:</label> |
|
358 | 358 | </div> |
|
359 | 359 | <div class="input"> |
|
360 | 360 | ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))} |
|
361 | 361 | </div> |
|
362 | 362 | </div> |
|
363 | 363 | |
|
364 | 364 | <div class="buttons"> |
|
365 | 365 | ${h.submit('save',_('Add'),class_="ui-btn large")} |
|
366 | 366 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} |
|
367 | 367 | </div> |
|
368 | 368 | </div> |
|
369 | 369 | </div> |
|
370 | 370 | ${h.end_form()} |
|
371 | 371 | </div> |
|
372 | 372 | %endif |
|
373 | 373 | </%def> |
@@ -1,49 +1,49 | |||
|
1 | 1 | |
|
2 | 2 | <div class="pullrequests_section_head">${_('Opened by me')}</div> |
|
3 | 3 | <ul> |
|
4 | 4 | %if c.my_pull_requests: |
|
5 | 5 | %for pull_request in c.my_pull_requests: |
|
6 | 6 | <li> |
|
7 | 7 | <div style="height: 12px"> |
|
8 | 8 | <div style="float:left"> |
|
9 | 9 | %if pull_request.is_closed(): |
|
10 | 10 | <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/> |
|
11 | 11 | %endif |
|
12 |
<img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" /> |
|
|
12 | <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" /> | |
|
13 | 13 | <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}"> |
|
14 | 14 | ${_('Pull request #%s opened on %s') % (pull_request.pull_request_id, h.fmt_date(pull_request.created_on))} |
|
15 | 15 | </a> |
|
16 | 16 | </div> |
|
17 | 17 | <div style="float:left;margin-top: -5px"> |
|
18 | 18 | ${h.form(url('pullrequest_delete', repo_name=pull_request.other_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')} |
|
19 | 19 | ${h.submit('remove_%s' % pull_request.pull_request_id,'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")} |
|
20 | 20 | ${h.end_form()} |
|
21 | 21 | </div> |
|
22 | 22 | </div> |
|
23 | 23 | </li> |
|
24 | 24 | %endfor |
|
25 | 25 | %else: |
|
26 | 26 | <li><span class="empty_data">${_('Nothing here yet')}</span></li> |
|
27 | 27 | %endif |
|
28 | 28 | </ul> |
|
29 | 29 | |
|
30 | 30 | <div class="pullrequests_section_head" style="clear:both">${_('I participate in')}</div> |
|
31 | 31 | <ul> |
|
32 | 32 | %if c.participate_in_pull_requests: |
|
33 | 33 | %for pull_request in c.participate_in_pull_requests: |
|
34 | 34 | <li> |
|
35 | 35 | <div style="height: 12px"> |
|
36 | 36 | %if pull_request.is_closed(): |
|
37 | 37 | <img src="${h.url('/images/icons/lock_go.png')}" title="${_('Closed')}"/> |
|
38 | 38 | %endif |
|
39 |
<img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" /> |
|
|
39 | <img src="${h.url('/images/icons/flag_status_%s.png' % str(pull_request.last_review_status))}" /> | |
|
40 | 40 | <a href="${h.url('pullrequest_show',repo_name=pull_request.other_repo.repo_name,pull_request_id=pull_request.pull_request_id)}"> |
|
41 | 41 | ${_('Pull request #%s opened by %s on %s') % (pull_request.pull_request_id, pull_request.author.full_name, h.fmt_date(pull_request.created_on))} |
|
42 | 42 | </a> |
|
43 | 43 | </div> |
|
44 | 44 | </li> |
|
45 | 45 | %endfor |
|
46 | 46 | %else: |
|
47 | 47 | <li><span class="empty_data">${_('Nothing here yet')}</span></li> |
|
48 | 48 | %endif |
|
49 | 49 | </ul> |
@@ -1,390 +1,390 | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="root.html"/> |
|
3 | 3 | |
|
4 | 4 | <!-- HEADER --> |
|
5 | 5 | <div id="header-dd"></div> |
|
6 | 6 | <div id="header"> |
|
7 | 7 | <div id="header-inner" class="title"> |
|
8 | 8 | <div id="logo"> |
|
9 | 9 | <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1> |
|
10 | 10 | </div> |
|
11 | 11 | <!-- MENU --> |
|
12 | 12 | ${self.page_nav()} |
|
13 | 13 | <!-- END MENU --> |
|
14 | 14 | ${self.body()} |
|
15 | 15 | </div> |
|
16 | 16 | </div> |
|
17 | 17 | <!-- END HEADER --> |
|
18 | 18 | |
|
19 | 19 | <!-- CONTENT --> |
|
20 | 20 | <div id="content"> |
|
21 | 21 | <div class="flash_msg"> |
|
22 | 22 | <% messages = h.flash.pop_messages() %> |
|
23 | 23 | % if messages: |
|
24 | 24 | <ul id="flash-messages"> |
|
25 | 25 | % for message in messages: |
|
26 | 26 | <li class="${message.category}_msg">${message}</li> |
|
27 | 27 | % endfor |
|
28 | 28 | </ul> |
|
29 | 29 | % endif |
|
30 | 30 | </div> |
|
31 | 31 | <div id="main"> |
|
32 | 32 | ${next.main()} |
|
33 | 33 | </div> |
|
34 | 34 | </div> |
|
35 | 35 | <!-- END CONTENT --> |
|
36 | 36 | |
|
37 | 37 | <!-- FOOTER --> |
|
38 | 38 | <div id="footer"> |
|
39 | 39 | <div id="footer-inner" class="title"> |
|
40 | 40 | <div> |
|
41 | 41 | <p class="footer-link"> |
|
42 | 42 | <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a> |
|
43 | 43 | </p> |
|
44 | 44 | <p class="footer-link-right"> |
|
45 | 45 | <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a> |
|
46 | 46 | ${c.rhodecode_version} © 2010-${h.datetime.today().year} by Marcin Kuzminski |
|
47 | 47 | </p> |
|
48 | 48 | </div> |
|
49 | 49 | </div> |
|
50 | 50 | </div> |
|
51 | 51 | <!-- END FOOTER --> |
|
52 | 52 | |
|
53 | 53 | ### MAKO DEFS ### |
|
54 | 54 | <%def name="page_nav()"> |
|
55 | 55 | ${self.menu()} |
|
56 | 56 | </%def> |
|
57 | 57 | |
|
58 | 58 | <%def name="breadcrumbs()"> |
|
59 | 59 | <div class="breadcrumbs"> |
|
60 | 60 | ${self.breadcrumbs_links()} |
|
61 | 61 | </div> |
|
62 | 62 | </%def> |
|
63 | 63 | |
|
64 | 64 | <%def name="usermenu()"> |
|
65 | 65 | ## USER MENU |
|
66 | 66 | <li> |
|
67 | 67 | <a class="menu_link" id="quick_login_link"> |
|
68 | 68 | <span class="icon" style="padding:5px 5px 0px 5px"> |
|
69 | 69 | <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar"> |
|
70 | 70 | </span> |
|
71 | 71 | %if c.rhodecode_user.username != 'default': |
|
72 | 72 | <span class="menu_link_user">${c.rhodecode_user.username}</span> |
|
73 | 73 | %if c.unread_notifications != 0: |
|
74 | 74 | <span class="menu_link_notifications">${c.unread_notifications}</span> |
|
75 | 75 | %endif |
|
76 | 76 | %else: |
|
77 | 77 | <span>${_('Not logged in')}</span> |
|
78 | 78 | %endif |
|
79 | 79 | </a> |
|
80 | 80 | |
|
81 | 81 | <div class="user-menu"> |
|
82 | 82 | <div id="quick_login"> |
|
83 | 83 | %if c.rhodecode_user.username == 'default': |
|
84 | 84 | <h4>${_('Login to your account')}</h4> |
|
85 | 85 | ${h.form(h.url('login_home',came_from=h.url.current()))} |
|
86 | 86 | <div class="form"> |
|
87 | 87 | <div class="fields"> |
|
88 | 88 | <div class="field"> |
|
89 | 89 | <div class="label"> |
|
90 | 90 | <label for="username">${_('Username')}:</label> |
|
91 | 91 | </div> |
|
92 | 92 | <div class="input"> |
|
93 | 93 | ${h.text('username',class_='focus',size=40)} |
|
94 | 94 | </div> |
|
95 | 95 | |
|
96 | 96 | </div> |
|
97 | 97 | <div class="field"> |
|
98 | 98 | <div class="label"> |
|
99 | 99 | <label for="password">${_('Password')}:</label> |
|
100 | 100 | </div> |
|
101 | 101 | <div class="input"> |
|
102 | 102 | ${h.password('password',class_='focus',size=40)} |
|
103 | 103 | </div> |
|
104 | 104 | |
|
105 | 105 | </div> |
|
106 | 106 | <div class="buttons"> |
|
107 | 107 | <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div> |
|
108 | 108 | <div class="register"> |
|
109 | 109 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): |
|
110 | 110 | ${h.link_to(_("Don't have an account ?"),h.url('register'))} |
|
111 | 111 | %endif |
|
112 | 112 | </div> |
|
113 | 113 | <div class="submit"> |
|
114 | 114 | ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")} |
|
115 | 115 | </div> |
|
116 | 116 | </div> |
|
117 | 117 | </div> |
|
118 | 118 | </div> |
|
119 | 119 | ${h.end_form()} |
|
120 | 120 | %else: |
|
121 | 121 | <div class="links_left"> |
|
122 | 122 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> |
|
123 | 123 | <div class="email">${c.rhodecode_user.email}</div> |
|
124 | 124 | <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div> |
|
125 | 125 | <div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div> |
|
126 | 126 | <div class="unread"><a href="${h.url('notifications')}">${_('Unread')}: ${c.unread_notifications}</a></div> |
|
127 | 127 | </div> |
|
128 | 128 | <div class="links_right"> |
|
129 | 129 | <ol class="links"> |
|
130 | 130 | <li>${h.link_to(_(u'Home'),h.url('home'))}</li> |
|
131 | 131 | <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li> |
|
132 | 132 | <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li> |
|
133 | 133 | <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li> |
|
134 | 134 | </ol> |
|
135 | 135 | </div> |
|
136 | 136 | %endif |
|
137 | 137 | </div> |
|
138 | 138 | </div> |
|
139 | 139 | |
|
140 | 140 | </li> |
|
141 | 141 | </%def> |
|
142 | 142 | |
|
143 | 143 | <%def name="menu(current=None)"> |
|
144 | 144 | <% |
|
145 | 145 | def is_current(selected): |
|
146 | 146 | if selected == current: |
|
147 | 147 | return h.literal('class="current"') |
|
148 | 148 | %> |
|
149 | 149 | <ul id="quick"> |
|
150 | 150 | <!-- repo switcher --> |
|
151 | 151 | <li ${is_current('home')}> |
|
152 | 152 | <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}"> |
|
153 | 153 | <span class="icon"> |
|
154 | 154 | <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" /> |
|
155 | 155 | </span> |
|
156 | 156 | <span>${_('Repositories')}</span> |
|
157 | 157 | </a> |
|
158 | 158 | <ul id="repo_switcher_list" class="repo_switcher"> |
|
159 | 159 | <li> |
|
160 | 160 | <a href="#">${_('loading...')}</a> |
|
161 | 161 | </li> |
|
162 | 162 | </ul> |
|
163 | 163 | </li> |
|
164 | 164 | ## we render this menu only not for those pages |
|
165 | 165 | %if current not in ['home','admin', 'search', 'journal']: |
|
166 | 166 | ##REGULAR MENU |
|
167 | 167 | <li ${is_current('summary')}> |
|
168 | 168 | <a class="menu_link" title="${_('Summary page')}" href="${h.url('summary_home',repo_name=c.repo_name)}"> |
|
169 | 169 | <span class="icon"> |
|
170 | 170 | <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" /> |
|
171 | 171 | </span> |
|
172 | 172 | <span>${_('Summary')}</span> |
|
173 | 173 | </a> |
|
174 | 174 | </li> |
|
175 | 175 | <li ${is_current('changelog')}> |
|
176 | 176 | <a class="menu_link" title="${_('Changeset list')}" href="${h.url('changelog_home',repo_name=c.repo_name)}"> |
|
177 | 177 | <span class="icon"> |
|
178 | 178 | <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" /> |
|
179 | 179 | </span> |
|
180 | 180 | <span>${_('Changelog')}</span> |
|
181 | 181 | </a> |
|
182 | 182 | </li> |
|
183 | 183 | <li ${is_current('switch_to')}> |
|
184 | 184 | <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#"> |
|
185 | 185 | <span class="icon"> |
|
186 | 186 | <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" /> |
|
187 | 187 | </span> |
|
188 | 188 | <span>${_('Switch to')}</span> |
|
189 | 189 | </a> |
|
190 | 190 | <ul id="switch_to_list" class="switch_to"> |
|
191 | 191 | <li><a href="#">${_('loading...')}</a></li> |
|
192 | 192 | </ul> |
|
193 | 193 | </li> |
|
194 | 194 | <li ${is_current('files')}> |
|
195 | 195 | <a class="menu_link" title="${_('Show repository content')}" href="${h.url('files_home',repo_name=c.repo_name)}"> |
|
196 | 196 | <span class="icon"> |
|
197 | 197 | <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" /> |
|
198 | 198 | </span> |
|
199 | 199 | <span>${_('Files')}</span> |
|
200 | 200 | </a> |
|
201 | 201 | </li> |
|
202 | 202 | <li ${is_current('options')}> |
|
203 | 203 | <a class="menu_link" title="${_('Options')}" href="#"> |
|
204 | 204 | <span class="icon"> |
|
205 | 205 | <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" /> |
|
206 | 206 | </span> |
|
207 | 207 | <span>${_('Options')}</span> |
|
208 | 208 | </a> |
|
209 | 209 | <ul> |
|
210 | 210 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
211 | 211 | %if h.HasPermissionAll('hg.admin')('access settings on repository'): |
|
212 | 212 | <li>${h.link_to(_('repository settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li> |
|
213 | 213 | %else: |
|
214 | 214 | <li>${h.link_to(_('repository settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li> |
|
215 | 215 | %endif |
|
216 | 216 | %endif |
|
217 | 217 | |
|
218 | 218 | <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li> |
|
219 | 219 | %if h.is_hg(c.rhodecode_repo): |
|
220 | 220 | <li>${h.link_to(_('open new pull request'),h.url('pullrequest_home',repo_name=c.repo_name),class_='pull_request')}</li> |
|
221 | 221 | %endif |
|
222 | 222 | %if c.rhodecode_db_repo.fork: |
|
223 | 223 | <li>${h.link_to(_('compare fork'),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'),class_='compare_request')}</li> |
|
224 | 224 | %endif |
|
225 | 225 | <li>${h.link_to(_('lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li> |
|
226 | 226 | <li>${h.link_to(_('search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li> |
|
227 | 227 | |
|
228 | 228 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking: |
|
229 | 229 | %if c.rhodecode_db_repo.locked[0]: |
|
230 | 230 | <li>${h.link_to(_('unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li> |
|
231 | 231 | %else: |
|
232 | 232 | <li>${h.link_to(_('lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li> |
|
233 | 233 | %endif |
|
234 | 234 | %endif |
|
235 | 235 | |
|
236 | 236 | % if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
237 | 237 | <li> |
|
238 | 238 | ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')} |
|
239 | 239 | <%def name="admin_menu()"> |
|
240 | 240 | <ul> |
|
241 | 241 | <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal')}</li> |
|
242 | 242 | <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li> |
|
243 | 243 | <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li> |
|
244 | 244 | <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li> |
|
245 | 245 | <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li> |
|
246 | 246 | <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li> |
|
247 | 247 | <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li> |
|
248 | 248 | <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li> |
|
249 | 249 | <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li> |
|
250 | 250 | </ul> |
|
251 | 251 | </%def> |
|
252 | 252 | ## ADMIN MENU |
|
253 | 253 | ${admin_menu()} |
|
254 | 254 | </li> |
|
255 | 255 | ## if you're a admin of any groups, show admin menu for it |
|
256 | 256 | % elif c.rhodecode_user.groups_admin: |
|
257 | 257 | <li> |
|
258 | 258 | ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')} |
|
259 | 259 | <%def name="admin_menu_simple()"> |
|
260 | 260 | <ul> |
|
261 | 261 | <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li> |
|
262 | 262 | </ul> |
|
263 | 263 | </%def> |
|
264 | 264 | ## ADMIN MENU |
|
265 | 265 | ${admin_menu_simple()} |
|
266 | 266 | </li> |
|
267 | 267 | % endif |
|
268 | 268 | </ul> |
|
269 | 269 | </li> |
|
270 | 270 | <li> |
|
271 | 271 | <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}"> |
|
272 | 272 | <span class="icon_short"> |
|
273 | 273 | <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" /> |
|
274 | 274 | </span> |
|
275 | 275 | <span id="current_followers_count" class="short">${c.repository_followers}</span> |
|
276 | 276 | </a> |
|
277 | 277 | </li> |
|
278 | 278 | <li> |
|
279 | 279 | <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}"> |
|
280 | 280 | <span class="icon_short"> |
|
281 | 281 | <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" /> |
|
282 | 282 | </span> |
|
283 | 283 | <span class="short">${c.repository_forks}</span> |
|
284 | 284 | </a> |
|
285 | 285 | </li> |
|
286 | 286 | <li> |
|
287 | 287 | <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}"> |
|
288 | 288 | <span class="icon_short"> |
|
289 | 289 | <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" /> |
|
290 | 290 | </span> |
|
291 | 291 | <span class="short">${c.repository_pull_requests}</span> |
|
292 | 292 | </a> |
|
293 | 293 | </li> |
|
294 | 294 | ${usermenu()} |
|
295 | 295 | <script type="text/javascript"> |
|
296 | 296 | YUE.on('branch_tag_switcher','mouseover',function(){ |
|
297 | 297 | var loaded = YUD.hasClass('branch_tag_switcher','loaded'); |
|
298 | 298 | if(!loaded){ |
|
299 | 299 | YUD.addClass('branch_tag_switcher','loaded'); |
|
300 | 300 | ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list', |
|
301 | 301 | function(o){}, |
|
302 | 302 | function(o){YUD.removeClass('branch_tag_switcher','loaded');} |
|
303 | 303 | ,null); |
|
304 | 304 | } |
|
305 | 305 | return false; |
|
306 | 306 | }); |
|
307 | 307 | </script> |
|
308 | 308 | %else: |
|
309 | 309 | ##ROOT MENU |
|
310 | 310 | %if c.rhodecode_user.username != 'default': |
|
311 | 311 | <li ${is_current('journal')}> |
|
312 | 312 | <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}"> |
|
313 | 313 | <span class="icon"> |
|
314 | 314 | <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" /> |
|
315 | 315 | </span> |
|
316 | 316 | <span>${_('Journal')}</span> |
|
317 | 317 | </a> |
|
318 | 318 | </li> |
|
319 | 319 | %else: |
|
320 | 320 | <li ${is_current('journal')}> |
|
321 | 321 | <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}"> |
|
322 | 322 | <span class="icon"> |
|
323 | 323 | <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" /> |
|
324 | 324 | </span> |
|
325 | 325 | <span>${_('Public journal')}</span> |
|
326 | 326 | </a> |
|
327 | 327 | </li> |
|
328 | 328 | %endif |
|
329 | 329 | <li ${is_current('search')}> |
|
330 | 330 | <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}"> |
|
331 | 331 | <span class="icon"> |
|
332 | 332 | <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" /> |
|
333 | 333 | </span> |
|
334 | 334 | <span>${_('Search')}</span> |
|
335 | 335 | </a> |
|
336 | 336 | </li> |
|
337 | 337 | % if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
338 | 338 | <li ${is_current('admin')}> |
|
339 | 339 | <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}"> |
|
340 | 340 | <span class="icon"> |
|
341 | 341 | <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" /> |
|
342 | 342 | </span> |
|
343 | 343 | <span>${_('Admin')}</span> |
|
344 | 344 | </a> |
|
345 | 345 | ${admin_menu()} |
|
346 | 346 | </li> |
|
347 | 347 | % elif c.rhodecode_user.groups_admin: |
|
348 | 348 | <li ${is_current('admin')}> |
|
349 | 349 | <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}"> |
|
350 | 350 | <span class="icon"> |
|
351 | 351 | <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" /> |
|
352 | 352 | </span> |
|
353 | 353 | <span>${_('Admin')}</span> |
|
354 | 354 | </a> |
|
355 | 355 | ${admin_menu_simple()} |
|
356 |
</li> |
|
|
356 | </li> | |
|
357 | 357 | % endif |
|
358 | 358 | ${usermenu()} |
|
359 | 359 | %endif |
|
360 | 360 | <script type="text/javascript"> |
|
361 | 361 | YUE.on('repo_switcher','mouseover',function(){ |
|
362 | 362 | var target = 'q_filter_rs'; |
|
363 | 363 | var qfilter_activate = function(){ |
|
364 | 364 | var nodes = YUQ('ul#repo_switcher_list li a.repo_name'); |
|
365 | 365 | var func = function(node){ |
|
366 | 366 | return node.parentNode; |
|
367 | 367 | } |
|
368 | 368 | q_filter(target,nodes,func); |
|
369 | 369 | } |
|
370 | 370 | |
|
371 | 371 | var loaded = YUD.hasClass('repo_switcher','loaded'); |
|
372 | 372 | if(!loaded){ |
|
373 | 373 | YUD.addClass('repo_switcher','loaded'); |
|
374 | 374 | ypjax("${h.url('repo_switcher')}",'repo_switcher_list', |
|
375 | 375 | function(o){qfilter_activate();YUD.get(target).focus()}, |
|
376 | 376 | function(o){YUD.removeClass('repo_switcher','loaded');} |
|
377 | 377 | ,null); |
|
378 | 378 | }else{ |
|
379 | 379 | YUD.get(target).focus(); |
|
380 | 380 | } |
|
381 | 381 | return false; |
|
382 | 382 | }); |
|
383 | 383 | |
|
384 | 384 | YUE.on('header-dd', 'click',function(e){ |
|
385 | 385 | YUD.addClass('header-inner', 'hover'); |
|
386 | 386 | YUD.addClass('content', 'hover'); |
|
387 | 387 | }); |
|
388 | 388 | |
|
389 | 389 | </script> |
|
390 | 390 | </%def> |
@@ -1,111 +1,111 | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <!DOCTYPE html> |
|
3 | 3 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
4 | 4 | <head> |
|
5 | 5 | <title>${self.title()}</title> |
|
6 | 6 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
7 | 7 | <meta name="robots" content="index, nofollow"/> |
|
8 | 8 | <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" /> |
|
9 | 9 | |
|
10 | 10 | ## CSS ### |
|
11 | 11 | <%def name="css()"> |
|
12 | 12 | <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version)}" media="screen"/> |
|
13 | 13 | <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.rhodecode_version)}"/> |
|
14 | 14 | ## EXTRA FOR CSS |
|
15 | 15 | ${self.css_extra()} |
|
16 | 16 | </%def> |
|
17 | 17 | <%def name="css_extra()"> |
|
18 | 18 | </%def> |
|
19 | 19 | |
|
20 | 20 | ${self.css()} |
|
21 | 21 | |
|
22 | 22 | %if c.ga_code: |
|
23 | 23 | <!-- Analytics --> |
|
24 | 24 | <script type="text/javascript"> |
|
25 | 25 | var _gaq = _gaq || []; |
|
26 | 26 | _gaq.push(['_setAccount', '${c.ga_code}']); |
|
27 | 27 | _gaq.push(['_trackPageview']); |
|
28 | 28 | |
|
29 | 29 | (function() { |
|
30 | 30 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; |
|
31 | 31 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; |
|
32 | 32 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); |
|
33 | 33 | })(); |
|
34 | 34 | </script> |
|
35 | 35 | %endif |
|
36 | 36 | |
|
37 | 37 | ## JAVASCRIPT ## |
|
38 | 38 | <%def name="js()"> |
|
39 | 39 | <script type="text/javascript"> |
|
40 | 40 | //JS translations map |
|
41 | 41 | var TRANSLATION_MAP = { |
|
42 | 42 | 'add another comment':'${_("add another comment")}', |
|
43 | 43 | 'Stop following this repository':"${_('Stop following this repository')}", |
|
44 | 44 | 'Start following this repository':"${_('Start following this repository')}", |
|
45 | 45 | 'Group':"${_('Group')}", |
|
46 | 46 | 'members':"${_('members')}", |
|
47 | 47 | 'loading...':"${_('loading...')}", |
|
48 | 48 | 'search truncated': "${_('search truncated')}", |
|
49 | 49 | 'no matching files': "${_('no matching files')}", |
|
50 | 50 | 'Open new pull request': "${_('Open new pull request')}", |
|
51 | 51 | 'Open new pull request for selected changesets': "${_('Open new pull request for selected changesets')}", |
|
52 | 52 | 'Show selected changes __S -> __E': "${_('Show selected changes __S -> __E')}", |
|
53 | 53 | 'Selection link': "${_('Selection link')}", |
|
54 | 54 | }; |
|
55 | 55 | var _TM = TRANSLATION_MAP; |
|
56 | 56 | |
|
57 | 57 | var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}"; |
|
58 | 58 | |
|
59 | 59 | </script> |
|
60 | 60 | <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script> |
|
61 | 61 | <!--[if lt IE 9]> |
|
62 | 62 | <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script> |
|
63 | 63 | <![endif]--> |
|
64 | 64 | <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.rhodecode_version)}"></script> |
|
65 | 65 | <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.rhodecode_version)}"></script> |
|
66 | 66 | <script type="text/javascript" src="${h.url('/js/pyroutes_map.js', ver=c.rhodecode_version)}"></script> |
|
67 | 67 | <script type="text/javascript" src="${h.url('/js/rhodecode.js', ver=c.rhodecode_version)}"></script> |
|
68 | 68 | ## EXTRA FOR JS |
|
69 | 69 | ${self.js_extra()} |
|
70 | 70 | <script type="text/javascript"> |
|
71 | 71 | (function(window,undefined){ |
|
72 | 72 | // Prepare |
|
73 | 73 | var History = window.History; // Note: We are using a capital H instead of a lower h |
|
74 | 74 | if ( !History.enabled ) { |
|
75 | 75 | // History.js is disabled for this browser. |
|
76 | 76 | // This is because we can optionally choose to support HTML4 browsers or not. |
|
77 | 77 | return false; |
|
78 | 78 | } |
|
79 | 79 | })(window); |
|
80 | 80 | |
|
81 | 81 | YUE.onDOMReady(function(){ |
|
82 | 82 | tooltip_activate(); |
|
83 | 83 | show_more_event(); |
|
84 | 84 | show_changeset_tooltip(); |
|
85 | 85 | // routes registration |
|
86 | 86 | pyroutes.register('toggle_following', "${h.url('toggle_following')}"); |
|
87 | 87 | pyroutes.register('changeset_info', "${h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']); |
|
88 |
pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']); |
|
|
88 | pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']); | |
|
89 | 89 | }) |
|
90 | 90 | </script> |
|
91 | 91 | </%def> |
|
92 | 92 | <%def name="js_extra()"></%def> |
|
93 | 93 | ${self.js()} |
|
94 | 94 | <%def name="head_extra()"></%def> |
|
95 | 95 | ${self.head_extra()} |
|
96 | 96 | </head> |
|
97 | 97 | <body id="body"> |
|
98 | 98 | ## IE hacks |
|
99 | 99 | <!--[if IE 7]> |
|
100 | 100 | <script>YUD.addClass(document.body,'ie7')</script> |
|
101 | 101 | <![endif]--> |
|
102 | 102 | <!--[if IE 8]> |
|
103 | 103 | <script>YUD.addClass(document.body,'ie8')</script> |
|
104 | 104 | <![endif]--> |
|
105 | 105 | <!--[if IE 9]> |
|
106 | 106 | <script>YUD.addClass(document.body,'ie9')</script> |
|
107 | 107 | <![endif]--> |
|
108 | 108 | |
|
109 | 109 | ${next.body()} |
|
110 | 110 | </body> |
|
111 | 111 | </html> |
@@ -1,340 +1,340 | |||
|
1 | 1 | <%page args="parent" /> |
|
2 | 2 | <div class="box"> |
|
3 | 3 | <!-- box / title --> |
|
4 | 4 | <div class="title"> |
|
5 | 5 | <h5> |
|
6 | 6 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')} |
|
7 | 7 | </h5> |
|
8 | 8 | %if c.rhodecode_user.username != 'default': |
|
9 | 9 | <ul class="links"> |
|
10 | 10 | %if h.HasPermissionAny('hg.admin','hg.create.repository')() or h.HasReposGroupPermissionAny('group.write', 'group.admin')(c.group.group_name if c.group else None): |
|
11 | 11 | <li> |
|
12 | 12 | %if c.group: |
|
13 | 13 | <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span> |
|
14 | 14 | %if h.HasPermissionAny('hg.admin')() or h.HasReposGroupPermissionAny('group.admin')(c.group.group_name): |
|
15 | 15 | <span>${h.link_to(_(u'Add group'),h.url('new_repos_group', parent_group=c.group.group_id))}</span> |
|
16 |
%endif |
|
|
16 | %endif | |
|
17 | 17 | %else: |
|
18 | 18 | <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository'))}</span> |
|
19 | 19 | %if h.HasPermissionAny('hg.admin')(): |
|
20 | 20 | <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span> |
|
21 | 21 | %endif |
|
22 | 22 | %endif |
|
23 | 23 | </li> |
|
24 | 24 | %endif |
|
25 | 25 | %if c.group and h.HasReposGroupPermissionAny('group.admin')(c.group.group_name): |
|
26 | 26 | <li> |
|
27 | 27 | <span>${h.link_to(_('Edit group'),h.url('edit_repos_group',group_name=c.group.group_name), title=_('You have admin right to this group, and can edit it'))}</span> |
|
28 | 28 | </li> |
|
29 | 29 | %endif |
|
30 | 30 | </ul> |
|
31 | 31 | %endif |
|
32 | 32 | </div> |
|
33 | 33 | <!-- end box / title --> |
|
34 | 34 | <div class="table"> |
|
35 | 35 | % if c.groups: |
|
36 | 36 | <div id='groups_list_wrap' class="yui-skin-sam"> |
|
37 | 37 | <table id="groups_list"> |
|
38 | 38 | <thead> |
|
39 | 39 | <tr> |
|
40 | 40 | <th class="left"><a href="#">${_('Group name')}</a></th> |
|
41 | 41 | <th class="left"><a href="#">${_('Description')}</a></th> |
|
42 | 42 | ##<th class="left"><a href="#">${_('Number of repositories')}</a></th> |
|
43 | 43 | </tr> |
|
44 | 44 | </thead> |
|
45 | 45 | |
|
46 | 46 | ## REPO GROUPS |
|
47 | 47 | % for gr in c.groups: |
|
48 | 48 | <tr> |
|
49 | 49 | <td> |
|
50 | 50 | <div style="white-space: nowrap"> |
|
51 | 51 | <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/> |
|
52 | 52 | ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))} |
|
53 | 53 | </div> |
|
54 | 54 | </td> |
|
55 | 55 | %if c.visual.stylify_metatags: |
|
56 | 56 | <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td> |
|
57 | 57 | %else: |
|
58 | 58 | <td>${gr.group_description}</td> |
|
59 | 59 | %endif |
|
60 | 60 | ## this is commented out since for multi nested repos can be HEAVY! |
|
61 | 61 | ## in number of executed queries during traversing uncomment at will |
|
62 | 62 | ##<td><b>${gr.repositories_recursive_count}</b></td> |
|
63 | 63 | </tr> |
|
64 | 64 | % endfor |
|
65 | 65 | </table> |
|
66 | 66 | </div> |
|
67 | 67 | <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div> |
|
68 | 68 | <div style="height: 20px"></div> |
|
69 | 69 | % endif |
|
70 | 70 | <div id="welcome" style="display:none;text-align:center"> |
|
71 | 71 | <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1> |
|
72 | 72 | </div> |
|
73 | 73 | <%cnt=0%> |
|
74 | 74 | <%namespace name="dt" file="/data_table/_dt_elements.html"/> |
|
75 | 75 | % if c.visual.lightweight_dashboard is False: |
|
76 | 76 | ## old full detailed version |
|
77 | 77 | <div id='repos_list_wrap' class="yui-skin-sam"> |
|
78 | 78 | <table id="repos_list"> |
|
79 | 79 | <thead> |
|
80 | 80 | <tr> |
|
81 | 81 | <th class="left"></th> |
|
82 | 82 | <th class="left">${_('Name')}</th> |
|
83 | 83 | <th class="left">${_('Description')}</th> |
|
84 | 84 | <th class="left">${_('Last change')}</th> |
|
85 | 85 | <th class="left">${_('Tip')}</th> |
|
86 | 86 | <th class="left">${_('Owner')}</th> |
|
87 | 87 | <th class="left">${_('Atom')}</th> |
|
88 | 88 | </tr> |
|
89 | 89 | </thead> |
|
90 | 90 | <tbody> |
|
91 | 91 | %for cnt,repo in enumerate(c.repos_list): |
|
92 | 92 | <tr class="parity${(cnt+1)%2}"> |
|
93 | 93 | ##QUICK MENU |
|
94 | 94 | <td class="quick_repo_menu"> |
|
95 | 95 | ${dt.quick_menu(repo['name'])} |
|
96 | 96 | </td> |
|
97 | 97 | ##REPO NAME AND ICONS |
|
98 | 98 | <td class="reponame"> |
|
99 | 99 | ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))} |
|
100 | 100 | </td> |
|
101 | 101 | ##DESCRIPTION |
|
102 | 102 | <td><span class="tooltip" title="${h.tooltip(repo['description'])}"> |
|
103 | 103 | %if c.visual.stylify_metatags: |
|
104 | 104 | ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span> |
|
105 | 105 | %else: |
|
106 | 106 | ${h.truncate(repo['description'],60)}</span> |
|
107 | 107 | %endif |
|
108 | 108 | </td> |
|
109 | 109 | ##LAST CHANGE DATE |
|
110 | 110 | <td> |
|
111 | 111 | ${dt.last_change(repo['last_change'])} |
|
112 | 112 | </td> |
|
113 | 113 | ##LAST REVISION |
|
114 | 114 | <td> |
|
115 | 115 | ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} |
|
116 | 116 | </td> |
|
117 | 117 | ## |
|
118 | 118 | <td title="${repo['contact']}">${h.person(repo['contact'])}</td> |
|
119 | 119 | <td> |
|
120 | 120 | ${dt.atom(repo['name'])} |
|
121 | 121 | </td> |
|
122 | 122 | </tr> |
|
123 | 123 | %endfor |
|
124 | 124 | </tbody> |
|
125 | 125 | </table> |
|
126 | 126 | </div> |
|
127 | 127 | % else: |
|
128 | 128 | ## lightweight version |
|
129 | 129 | <div class="yui-skin-sam" id="repos_list_wrap"></div> |
|
130 | 130 | <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div> |
|
131 | 131 | % endif |
|
132 | 132 | </div> |
|
133 | 133 | </div> |
|
134 | 134 | % if c.visual.lightweight_dashboard is False: |
|
135 | 135 | <script> |
|
136 | 136 | YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0}; |
|
137 | 137 | |
|
138 | 138 | // groups table sorting |
|
139 | 139 | var myColumnDefs = [ |
|
140 | 140 | {key:"name",label:"${_('Group name')}",sortable:true, |
|
141 | 141 | sortOptions: { sortFunction: groupNameSort }}, |
|
142 | 142 | {key:"desc",label:"${_('Description')}",sortable:true}, |
|
143 | 143 | ]; |
|
144 | 144 | |
|
145 | 145 | var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list")); |
|
146 | 146 | |
|
147 | 147 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
148 | 148 | myDataSource.responseSchema = { |
|
149 | 149 | fields: [ |
|
150 | 150 | {key:"name"}, |
|
151 | 151 | {key:"desc"}, |
|
152 | 152 | ] |
|
153 | 153 | }; |
|
154 | 154 | |
|
155 | 155 | var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{ |
|
156 | 156 | sortedBy:{key:"name",dir:"asc"}, |
|
157 | 157 | paginator: new YAHOO.widget.Paginator({ |
|
158 | 158 | rowsPerPage: 50, |
|
159 | 159 | alwaysVisible: false, |
|
160 | 160 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", |
|
161 | 161 | pageLinks: 5, |
|
162 | 162 | containerClass: 'pagination-wh', |
|
163 | 163 | currentPageClass: 'pager_curpage', |
|
164 | 164 | pageLinkClass: 'pager_link', |
|
165 | 165 | nextPageLinkLabel: '>', |
|
166 | 166 | previousPageLinkLabel: '<', |
|
167 | 167 | firstPageLinkLabel: '<<', |
|
168 | 168 | lastPageLinkLabel: '>>', |
|
169 | 169 | containers:['group-user-paginator'] |
|
170 | 170 | }), |
|
171 | 171 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
172 | 172 | MSG_SORTDESC:"${_('Click to sort descending')}" |
|
173 | 173 | }); |
|
174 | 174 | |
|
175 | 175 | // main table sorting |
|
176 | 176 | var myColumnDefs = [ |
|
177 | 177 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, |
|
178 | 178 | {key:"name",label:"${_('Name')}",sortable:true, |
|
179 | 179 | sortOptions: { sortFunction: nameSort }}, |
|
180 | 180 | {key:"desc",label:"${_('Description')}",sortable:true}, |
|
181 | 181 | {key:"last_change",label:"${_('Last Change')}",sortable:true, |
|
182 | 182 | sortOptions: { sortFunction: ageSort }}, |
|
183 | 183 | {key:"tip",label:"${_('Tip')}",sortable:true, |
|
184 | 184 | sortOptions: { sortFunction: revisionSort }}, |
|
185 | 185 | {key:"owner",label:"${_('Owner')}",sortable:true}, |
|
186 | 186 | {key:"atom",label:"",sortable:false}, |
|
187 | 187 | ]; |
|
188 | 188 | |
|
189 | 189 | var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list")); |
|
190 | 190 | |
|
191 | 191 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
192 | 192 | |
|
193 | 193 | myDataSource.responseSchema = { |
|
194 | 194 | fields: [ |
|
195 | 195 | {key:"menu"}, |
|
196 | 196 | //{key:"raw_name"}, |
|
197 | 197 | {key:"name"}, |
|
198 | 198 | {key:"desc"}, |
|
199 | 199 | {key:"last_change"}, |
|
200 | 200 | {key:"tip"}, |
|
201 | 201 | {key:"owner"}, |
|
202 | 202 | {key:"atom"}, |
|
203 | 203 | ] |
|
204 | 204 | }; |
|
205 | 205 | |
|
206 | 206 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource, |
|
207 | 207 | { |
|
208 | 208 | sortedBy:{key:"name",dir:"asc"}, |
|
209 | 209 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
210 | 210 | MSG_SORTDESC:"${_('Click to sort descending')}", |
|
211 | 211 | MSG_EMPTY:"${_('No records found.')}", |
|
212 | 212 | MSG_ERROR:"${_('Data error.')}", |
|
213 | 213 | MSG_LOADING:"${_('Loading...')}", |
|
214 | 214 | } |
|
215 | 215 | ); |
|
216 | 216 | myDataTable.subscribe('postRenderEvent',function(oArgs) { |
|
217 | 217 | tooltip_activate(); |
|
218 | 218 | quick_repo_menu(); |
|
219 | 219 | var func = function(node){ |
|
220 | 220 | return node.parentNode.parentNode.parentNode.parentNode; |
|
221 | 221 | } |
|
222 | 222 | q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func); |
|
223 | 223 | }); |
|
224 | 224 | |
|
225 | 225 | </script> |
|
226 | 226 | % else: |
|
227 | 227 | <script> |
|
228 | 228 | var data = ${c.data|n}; |
|
229 | 229 | var myDataSource = new YAHOO.util.DataSource(data); |
|
230 | 230 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; |
|
231 | 231 | |
|
232 | 232 | myDataSource.responseSchema = { |
|
233 | 233 | resultsList: "records", |
|
234 | 234 | fields: [ |
|
235 | 235 | {key:"menu"}, |
|
236 | 236 | {key:"raw_name"}, |
|
237 | 237 | {key:"name"}, |
|
238 | 238 | {key:"desc"}, |
|
239 | 239 | {key:"last_change"}, |
|
240 | 240 | {key:"last_changeset"}, |
|
241 | 241 | {key:"owner"}, |
|
242 | 242 | {key:"atom"}, |
|
243 | 243 | ] |
|
244 | 244 | }; |
|
245 | 245 | myDataSource.doBeforeCallback = function(req,raw,res,cb) { |
|
246 | 246 | // This is the filter function |
|
247 | 247 | var data = res.results || [], |
|
248 | 248 | filtered = [], |
|
249 | 249 | i,l; |
|
250 | 250 | |
|
251 | 251 | if (req) { |
|
252 | 252 | req = req.toLowerCase(); |
|
253 | 253 | for (i = 0; i<data.length; i++) { |
|
254 | 254 | var pos = data[i].raw_name.toLowerCase().indexOf(req) |
|
255 | 255 | if (pos != -1) { |
|
256 | 256 | filtered.push(data[i]); |
|
257 | 257 | } |
|
258 | 258 | } |
|
259 | 259 | res.results = filtered; |
|
260 | 260 | } |
|
261 | 261 | YUD.get('repo_count').innerHTML = res.results.length; |
|
262 | 262 | return res; |
|
263 | 263 | } |
|
264 | 264 | |
|
265 | 265 | // main table sorting |
|
266 | 266 | var myColumnDefs = [ |
|
267 | 267 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, |
|
268 | 268 | {key:"name",label:"${_('Name')}",sortable:true, |
|
269 | 269 | sortOptions: { sortFunction: nameSort }}, |
|
270 | 270 | {key:"desc",label:"${_('Description')}",sortable:true}, |
|
271 | 271 | {key:"last_change",label:"${_('Last Change')}",sortable:true, |
|
272 | 272 | sortOptions: { sortFunction: ageSort }}, |
|
273 | 273 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, |
|
274 | 274 | sortOptions: { sortFunction: revisionSort }}, |
|
275 | 275 | {key:"owner",label:"${_('Owner')}",sortable:true}, |
|
276 | 276 | {key:"atom",label:"",sortable:false}, |
|
277 | 277 | ]; |
|
278 | 278 | |
|
279 | 279 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ |
|
280 | 280 | sortedBy:{key:"name",dir:"asc"}, |
|
281 | 281 | paginator: new YAHOO.widget.Paginator({ |
|
282 | 282 | rowsPerPage: ${c.visual.lightweight_dashboard_items}, |
|
283 | 283 | alwaysVisible: false, |
|
284 | 284 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", |
|
285 | 285 | pageLinks: 5, |
|
286 | 286 | containerClass: 'pagination-wh', |
|
287 | 287 | currentPageClass: 'pager_curpage', |
|
288 | 288 | pageLinkClass: 'pager_link', |
|
289 | 289 | nextPageLinkLabel: '>', |
|
290 | 290 | previousPageLinkLabel: '<', |
|
291 | 291 | firstPageLinkLabel: '<<', |
|
292 | 292 | lastPageLinkLabel: '>>', |
|
293 | 293 | containers:['user-paginator'] |
|
294 | 294 | }), |
|
295 | 295 | |
|
296 | 296 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
297 | 297 | MSG_SORTDESC:"${_('Click to sort descending')}", |
|
298 | 298 | MSG_EMPTY:"${_('No records found.')}", |
|
299 | 299 | MSG_ERROR:"${_('Data error.')}", |
|
300 | 300 | MSG_LOADING:"${_('Loading...')}", |
|
301 | 301 | } |
|
302 | 302 | ); |
|
303 | 303 | myDataTable.subscribe('postRenderEvent',function(oArgs) { |
|
304 | 304 | tooltip_activate(); |
|
305 | 305 | quick_repo_menu(); |
|
306 | 306 | }); |
|
307 | 307 | |
|
308 | 308 | var filterTimeout = null; |
|
309 | 309 | |
|
310 | 310 | updateFilter = function () { |
|
311 | 311 | // Reset timeout |
|
312 | 312 | filterTimeout = null; |
|
313 | 313 | |
|
314 | 314 | // Reset sort |
|
315 | 315 | var state = myDataTable.getState(); |
|
316 | 316 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; |
|
317 | 317 | |
|
318 | 318 | // Get filtered data |
|
319 | 319 | myDataSource.sendRequest(YUD.get('q_filter').value,{ |
|
320 | 320 | success : myDataTable.onDataReturnInitializeTable, |
|
321 | 321 | failure : myDataTable.onDataReturnInitializeTable, |
|
322 | 322 | scope : myDataTable, |
|
323 | 323 | argument: state |
|
324 | 324 | }); |
|
325 | 325 | |
|
326 | 326 | }; |
|
327 | 327 | YUE.on('q_filter','click',function(){ |
|
328 | 328 | if(!YUD.hasClass('q_filter', 'loaded')){ |
|
329 | 329 | YUD.get('q_filter').value = ''; |
|
330 | 330 | //TODO: load here full list later to do search within groups |
|
331 | 331 | YUD.addClass('q_filter', 'loaded'); |
|
332 | 332 | } |
|
333 | 333 | }); |
|
334 | 334 | |
|
335 | 335 | YUE.on('q_filter','keyup',function (e) { |
|
336 | 336 | clearTimeout(filterTimeout); |
|
337 | 337 | filterTimeout = setTimeout(updateFilter,600); |
|
338 | 338 | }); |
|
339 | 339 | </script> |
|
340 | 340 | % endif |
@@ -1,243 +1,243 | |||
|
1 | 1 | <%inherit file="/base/base.html"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id} |
|
5 | 5 | </%def> |
|
6 | 6 | |
|
7 | 7 | <%def name="breadcrumbs_links()"> |
|
8 | 8 | ${h.link_to(_(u'Home'),h.url('/'))} |
|
9 | 9 | » |
|
10 | 10 | ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} |
|
11 | 11 | » |
|
12 | 12 | ${_('Pull request #%s') % c.pull_request.pull_request_id} |
|
13 | 13 | </%def> |
|
14 | 14 | |
|
15 | 15 | <%def name="main()"> |
|
16 | 16 | |
|
17 | 17 | <div class="box"> |
|
18 | 18 | <!-- box / title --> |
|
19 | 19 | <div class="title"> |
|
20 | 20 | ${self.breadcrumbs()} |
|
21 | 21 | </div> |
|
22 | 22 | %if c.pull_request.is_closed(): |
|
23 | 23 | <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div> |
|
24 | 24 | %endif |
|
25 | 25 | <h3>${_('Title')}: ${c.pull_request.title}</h3> |
|
26 | 26 | |
|
27 | 27 | <div class="form"> |
|
28 | 28 | <div id="summary" class="fields"> |
|
29 | 29 | <div class="field"> |
|
30 | 30 | <div class="label-summary"> |
|
31 | 31 | <label>${_('Review status')}:</label> |
|
32 | 32 | </div> |
|
33 | 33 | <div class="input"> |
|
34 | 34 | <div class="changeset-status-container" style="float:none;clear:both"> |
|
35 | 35 | %if c.current_changeset_status: |
|
36 | 36 | <div title="${_('Pull request status')}" class="changeset-status-lbl">${h.changeset_status_lbl(c.current_changeset_status)}</div> |
|
37 | 37 | <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div> |
|
38 | 38 | %endif |
|
39 | 39 | </div> |
|
40 | 40 | </div> |
|
41 | 41 | </div> |
|
42 | 42 | <div class="field"> |
|
43 | 43 | <div class="label-summary"> |
|
44 | 44 | <label>${_('Still not reviewed by')}:</label> |
|
45 | 45 | </div> |
|
46 | 46 | <div class="input"> |
|
47 | 47 | % if len(c.pull_request_pending_reviewers) > 0: |
|
48 | 48 | <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div> |
|
49 | 49 | %else: |
|
50 | 50 | <div>${_('pull request was reviewed by all reviewers')}</div> |
|
51 | 51 | %endif |
|
52 | 52 | </div> |
|
53 | 53 | </div> |
|
54 | 54 | <div class="field"> |
|
55 | 55 | <div class="label-summary"> |
|
56 | 56 | <label>${_('Origin repository')}:</label> |
|
57 | 57 | </div> |
|
58 | 58 | <div class="input"> |
|
59 | 59 | <div> |
|
60 | 60 | ##%if h.is_hg(c.pull_request.org_repo): |
|
61 | 61 | ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/> |
|
62 | 62 | ##%elif h.is_git(c.pull_request.org_repo): |
|
63 | 63 | ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/> |
|
64 | 64 | ##%endif |
|
65 | 65 | <span class="spantag">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</span> |
|
66 | 66 | <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span> |
|
67 | 67 | </div> |
|
68 | 68 | </div> |
|
69 | 69 | </div> |
|
70 | 70 | <div class="field"> |
|
71 | 71 | <div class="label-summary"> |
|
72 | 72 | <label>${_('Summary')}:</label> |
|
73 | 73 | </div> |
|
74 | 74 | <div class="input"> |
|
75 | 75 | <div style="white-space:pre-wrap">${h.literal(c.pull_request.description)}</div> |
|
76 | 76 | </div> |
|
77 | 77 | </div> |
|
78 | 78 | <div class="field"> |
|
79 | 79 | <div class="label-summary"> |
|
80 | 80 | <label>${_('Created on')}:</label> |
|
81 | 81 | </div> |
|
82 | 82 | <div class="input"> |
|
83 | 83 | <div>${h.fmt_date(c.pull_request.created_on)}</div> |
|
84 | 84 | </div> |
|
85 | 85 | </div> |
|
86 | 86 | </div> |
|
87 | 87 | </div> |
|
88 | 88 | |
|
89 | 89 | <div style="overflow: auto;"> |
|
90 | 90 | ##DIFF |
|
91 | 91 | <div class="table" style="float:left;clear:none"> |
|
92 | 92 | <div id="body" class="diffblock"> |
|
93 | 93 | <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div> |
|
94 | 94 | </div> |
|
95 | 95 | <div id="changeset_compare_view_content"> |
|
96 | 96 | ##CS |
|
97 | 97 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div> |
|
98 | 98 | <%include file="/compare/compare_cs.html" /> |
|
99 | 99 | |
|
100 | 100 | ## FILES |
|
101 | 101 | <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px"> |
|
102 | 102 | |
|
103 | 103 | % if c.limited_diff: |
|
104 | 104 | ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)} |
|
105 | 105 | % else: |
|
106 | 106 | ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}: |
|
107 | 107 | %endif |
|
108 | 108 | |
|
109 | 109 | </div> |
|
110 | 110 | <div class="cs_files"> |
|
111 | 111 | %if not c.files: |
|
112 | 112 | <span class="empty_data">${_('No files')}</span> |
|
113 | 113 | %endif |
|
114 | 114 | %for fid, change, f, stat in c.files: |
|
115 | 115 | <div class="cs_${change}"> |
|
116 | 116 | <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div> |
|
117 | 117 | <div class="changes">${h.fancy_file_stats(stat)}</div> |
|
118 | 118 | </div> |
|
119 | 119 | %endfor |
|
120 | 120 | </div> |
|
121 | 121 | % if c.limited_diff: |
|
122 | 122 | <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h5> |
|
123 | 123 | % endif |
|
124 | 124 | </div> |
|
125 | 125 | </div> |
|
126 | 126 | ## REVIEWERS |
|
127 | 127 | <div style="float:left; border-left:1px dashed #eee"> |
|
128 | 128 | <h4>${_('Pull request reviewers')}</h4> |
|
129 | 129 | <div id="reviewers" style="padding:0px 0px 5px 10px"> |
|
130 | 130 | ## members goes here ! |
|
131 | 131 | <div class="group_members_wrap" style="min-height:45px"> |
|
132 | 132 | <ul id="review_members" class="group_members"> |
|
133 | 133 | %for member,status in c.pull_request_reviewers: |
|
134 | 134 | <li id="reviewer_${member.user_id}"> |
|
135 | 135 | <div class="reviewers_member"> |
|
136 | 136 | <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}"> |
|
137 | 137 | <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/> |
|
138 | 138 | </div> |
|
139 | 139 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div> |
|
140 | 140 | <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div> |
|
141 | 141 | <input type="hidden" value="${member.user_id}" name="review_members" /> |
|
142 | 142 | %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id): |
|
143 | 143 | <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span> |
|
144 | 144 | %endif |
|
145 | 145 | </div> |
|
146 | 146 | </li> |
|
147 | 147 | %endfor |
|
148 | 148 | </ul> |
|
149 | 149 | </div> |
|
150 | 150 | %if not c.pull_request.is_closed(): |
|
151 | 151 | <div class='ac'> |
|
152 | 152 | %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id: |
|
153 | 153 | <div class="reviewer_ac"> |
|
154 | 154 | ${h.text('user', class_='yui-ac-input')} |
|
155 | 155 | <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span> |
|
156 | 156 | <div id="reviewers_container"></div> |
|
157 | 157 | </div> |
|
158 | 158 | <div style="padding:0px 10px"> |
|
159 | 159 | <span id="update_pull_request" class="ui-btn xsmall">${_('save changes')}</span> |
|
160 | 160 | </div> |
|
161 | 161 | %endif |
|
162 | 162 | </div> |
|
163 | 163 | %endif |
|
164 | 164 | </div> |
|
165 | 165 | </div> |
|
166 | 166 | </div> |
|
167 | 167 | <script> |
|
168 | 168 | var _USERS_AC_DATA = ${c.users_array|n}; |
|
169 | 169 | var _GROUPS_AC_DATA = ${c.users_groups_array|n}; |
|
170 | 170 | // TODO: switch this to pyroutes |
|
171 | 171 | AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"; |
|
172 | 172 | AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"; |
|
173 | 173 | |
|
174 | 174 | pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); |
|
175 | 175 | pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']); |
|
176 | 176 | pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']); |
|
177 | ||
|
177 | ||
|
178 | 178 | </script> |
|
179 | 179 | |
|
180 | 180 | ## diff block |
|
181 | 181 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
182 | 182 | %for fid, change, f, stat in c.files: |
|
183 | 183 | ${diff_block.diff_block_simple([c.changes[fid]])} |
|
184 | 184 | %endfor |
|
185 | 185 | % if c.limited_diff: |
|
186 | 186 | <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("confirm to show potentially huge diff")}')">${_('Show full diff')}</a></h4> |
|
187 | 187 | % endif |
|
188 | 188 | |
|
189 | 189 | |
|
190 | 190 | ## template for inline comment form |
|
191 | 191 | <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> |
|
192 | 192 | ${comment.comment_inline_form()} |
|
193 | 193 | |
|
194 | 194 | ## render comments and inlines |
|
195 | 195 | ${comment.generate_comments(include_pr=True)} |
|
196 | 196 | |
|
197 | 197 | % if not c.pull_request.is_closed(): |
|
198 | 198 | ## main comment form and it status |
|
199 | 199 | ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, |
|
200 | 200 | pull_request_id=c.pull_request.pull_request_id), |
|
201 | 201 | c.current_changeset_status, |
|
202 | 202 | close_btn=True, change_status=c.allowed_to_change_status)} |
|
203 | 203 | %endif |
|
204 | 204 | |
|
205 | 205 | <script type="text/javascript"> |
|
206 | 206 | YUE.onDOMReady(function(){ |
|
207 | 207 | PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA); |
|
208 | 208 | |
|
209 | 209 | YUE.on(YUQ('.show-inline-comments'),'change',function(e){ |
|
210 | 210 | var show = 'none'; |
|
211 | 211 | var target = e.currentTarget; |
|
212 | 212 | if(target.checked){ |
|
213 | 213 | var show = '' |
|
214 | 214 | } |
|
215 | 215 | var boxid = YUD.getAttribute(target,'id_for'); |
|
216 | 216 | var comments = YUQ('#{0} .inline-comments'.format(boxid)); |
|
217 | 217 | for(c in comments){ |
|
218 | 218 | YUD.setStyle(comments[c],'display',show); |
|
219 | 219 | } |
|
220 | 220 | var btns = YUQ('#{0} .inline-comments-button'.format(boxid)); |
|
221 | 221 | for(c in btns){ |
|
222 | 222 | YUD.setStyle(btns[c],'display',show); |
|
223 | 223 | } |
|
224 | 224 | }) |
|
225 | 225 | |
|
226 | 226 | YUE.on(YUQ('.line'),'click',function(e){ |
|
227 | 227 | var tr = e.currentTarget; |
|
228 | 228 | injectInlineForm(tr); |
|
229 | 229 | }); |
|
230 | 230 | |
|
231 | 231 | // inject comments into they proper positions |
|
232 | 232 | var file_comments = YUQ('.inline-comment-placeholder'); |
|
233 | 233 | renderInlineComments(file_comments); |
|
234 | 234 | |
|
235 | 235 | YUE.on(YUD.get('update_pull_request'),'click',function(e){ |
|
236 | 236 | updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}"); |
|
237 | 237 | }) |
|
238 | 238 | }) |
|
239 | 239 | </script> |
|
240 | 240 | |
|
241 | 241 | </div> |
|
242 | 242 | |
|
243 | 243 | </%def> |
@@ -1,254 +1,254 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | rhodecode.tests.test_libs |
|
4 | 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
5 | 5 | |
|
6 | 6 | |
|
7 | 7 | Package for testing various lib/helper functions in rhodecode |
|
8 | 8 | |
|
9 | 9 | :created_on: Jun 9, 2011 |
|
10 | 10 | :copyright: (C) 2011-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 | from __future__ import with_statement |
|
26 | 26 | import unittest |
|
27 | 27 | import datetime |
|
28 | 28 | import hashlib |
|
29 | 29 | import mock |
|
30 | 30 | from rhodecode.tests import * |
|
31 | 31 | |
|
32 | 32 | proto = 'http' |
|
33 | 33 | TEST_URLS = [ |
|
34 | 34 | ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
35 | 35 | '%s://127.0.0.1' % proto), |
|
36 | 36 | ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
37 | 37 | '%s://127.0.0.1' % proto), |
|
38 | 38 | ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
39 | 39 | '%s://127.0.0.1' % proto), |
|
40 | 40 | ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'], |
|
41 | 41 | '%s://127.0.0.1:8080' % proto), |
|
42 | 42 | ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'], |
|
43 | 43 | '%s://domain.org' % proto), |
|
44 | 44 | ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org', |
|
45 | 45 | '8080'], |
|
46 | 46 | '%s://domain.org:8080' % proto), |
|
47 | 47 | ] |
|
48 | 48 | |
|
49 | 49 | proto = 'https' |
|
50 | 50 | TEST_URLS += [ |
|
51 | 51 | ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
52 | 52 | '%s://127.0.0.1' % proto), |
|
53 | 53 | ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
54 | 54 | '%s://127.0.0.1' % proto), |
|
55 | 55 | ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'], |
|
56 | 56 | '%s://127.0.0.1' % proto), |
|
57 | 57 | ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'], |
|
58 | 58 | '%s://127.0.0.1:8080' % proto), |
|
59 | 59 | ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'], |
|
60 | 60 | '%s://domain.org' % proto), |
|
61 | 61 | ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org', |
|
62 | 62 | '8080'], |
|
63 | 63 | '%s://domain.org:8080' % proto), |
|
64 | 64 | ] |
|
65 | 65 | |
|
66 | 66 | |
|
67 | 67 | class TestLibs(unittest.TestCase): |
|
68 | 68 | |
|
69 | 69 | @parameterized.expand(TEST_URLS) |
|
70 | 70 | def test_uri_filter(self, test_url, expected, expected_creds): |
|
71 | 71 | from rhodecode.lib.utils2 import uri_filter |
|
72 | 72 | self.assertEqual(uri_filter(test_url), expected) |
|
73 | 73 | |
|
74 | 74 | @parameterized.expand(TEST_URLS) |
|
75 | 75 | def test_credentials_filter(self, test_url, expected, expected_creds): |
|
76 | 76 | from rhodecode.lib.utils2 import credentials_filter |
|
77 | 77 | self.assertEqual(credentials_filter(test_url), expected_creds) |
|
78 | 78 | |
|
79 | 79 | @parameterized.expand([('t', True), |
|
80 | 80 | ('true', True), |
|
81 | 81 | ('y', True), |
|
82 | 82 | ('yes', True), |
|
83 | 83 | ('on', True), |
|
84 | 84 | ('1', True), |
|
85 | 85 | ('Y', True), |
|
86 | 86 | ('yeS', True), |
|
87 | 87 | ('Y', True), |
|
88 | 88 | ('TRUE', True), |
|
89 | 89 | ('T', True), |
|
90 | 90 | ('False', False), |
|
91 | 91 | ('F', False), |
|
92 | 92 | ('FALSE', False), |
|
93 | 93 | ('0', False), |
|
94 | 94 | ('-1', False), |
|
95 | 95 | ('', False) |
|
96 | 96 | ]) |
|
97 | 97 | def test_str2bool(self, str_bool, expected): |
|
98 | 98 | from rhodecode.lib.utils2 import str2bool |
|
99 | 99 | self.assertEqual(str2bool(str_bool), expected) |
|
100 | 100 | |
|
101 | 101 | def test_mention_extractor(self): |
|
102 | 102 | from rhodecode.lib.utils2 import extract_mentioned_users |
|
103 | 103 | sample = ( |
|
104 | 104 | "@first hi there @marcink here's my email marcin@email.com " |
|
105 | 105 | "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three " |
|
106 | 106 | "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl " |
|
107 | 107 | "@marian.user just do it @marco-polo and next extract @marco_polo " |
|
108 | 108 | "user.dot hej ! not-needed maril@domain.org" |
|
109 | 109 | ) |
|
110 | 110 | |
|
111 | 111 | s = sorted([ |
|
112 | 112 | 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john', |
|
113 | 113 | 'marian.user', 'marco-polo', 'marco_polo' |
|
114 | 114 | ], key=lambda k: k.lower()) |
|
115 | 115 | self.assertEqual(s, extract_mentioned_users(sample)) |
|
116 | 116 | |
|
117 | 117 | def test_age(self): |
|
118 | 118 | from rhodecode.lib.utils2 import age |
|
119 | 119 | from dateutil import relativedelta |
|
120 | 120 | n = datetime.datetime.now() |
|
121 | 121 | delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs) |
|
122 | 122 | |
|
123 | 123 | self.assertEqual(age(n), u'just now') |
|
124 | 124 | self.assertEqual(age(n + delt(seconds=-1)), u'1 second ago') |
|
125 | 125 | self.assertEqual(age(n + delt(seconds=-60 * 2)), u'2 minutes ago') |
|
126 | 126 | self.assertEqual(age(n + delt(hours=-1)), u'1 hour ago') |
|
127 | 127 | self.assertEqual(age(n + delt(hours=-24)), u'1 day ago') |
|
128 | 128 | self.assertEqual(age(n + delt(hours=-24 * 5)), u'5 days ago') |
|
129 | 129 | self.assertEqual(age(n + delt(months=-1)), u'1 month ago') |
|
130 | 130 | self.assertEqual(age(n + delt(months=-1, days=-2)), u'1 month and 2 days ago') |
|
131 | 131 | self.assertEqual(age(n + delt(years=-1, months=-1)), u'1 year and 1 month ago') |
|
132 | 132 | |
|
133 | 133 | def test_age_in_future(self): |
|
134 | 134 | from rhodecode.lib.utils2 import age |
|
135 | 135 | from dateutil import relativedelta |
|
136 | 136 | n = datetime.datetime.now() |
|
137 | 137 | delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs) |
|
138 | 138 | |
|
139 | 139 | self.assertEqual(age(n), u'just now') |
|
140 | 140 | self.assertEqual(age(n + delt(seconds=1)), u'in 1 second') |
|
141 | 141 | self.assertEqual(age(n + delt(seconds=60 * 2)), u'in 2 minutes') |
|
142 | 142 | self.assertEqual(age(n + delt(hours=1)), u'in 1 hour') |
|
143 | 143 | self.assertEqual(age(n + delt(hours=24)), u'in 1 day') |
|
144 | 144 | self.assertEqual(age(n + delt(hours=24 * 5)), u'in 5 days') |
|
145 | 145 | self.assertEqual(age(n + delt(months=1)), u'in 1 month') |
|
146 | 146 | self.assertEqual(age(n + delt(months=1, days=1)), u'in 1 month and 1 day') |
|
147 | 147 | self.assertEqual(age(n + delt(years=1, months=1)), u'in 1 year and 1 month') |
|
148 | 148 | |
|
149 | 149 | def test_tag_exctrator(self): |
|
150 | 150 | sample = ( |
|
151 | 151 | "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]" |
|
152 | 152 | "[requires] [stale] [see<>=>] [see => http://url.com]" |
|
153 | 153 | "[requires => url] [lang => python] [just a tag]" |
|
154 | 154 | "[,d] [ => ULR ] [obsolete] [desc]]" |
|
155 | 155 | ) |
|
156 | 156 | from rhodecode.lib.helpers import desc_stylize |
|
157 | 157 | res = desc_stylize(sample) |
|
158 | 158 | self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res) |
|
159 | 159 | self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res) |
|
160 | 160 | self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res) |
|
161 | 161 | self.assertTrue('<div class="metatag" tag="lang">python</div>' in res) |
|
162 | 162 | self.assertTrue('<div class="metatag" tag="requires">requires => <a href="/url">url</a></div>' in res) |
|
163 | 163 | self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res) |
|
164 | 164 | |
|
165 | 165 | def test_alternative_gravatar(self): |
|
166 | 166 | from rhodecode.lib.helpers import gravatar_url |
|
167 | 167 | _md5 = lambda s: hashlib.md5(s).hexdigest() |
|
168 | 168 | |
|
169 | 169 | def fake_conf(**kwargs): |
|
170 | 170 | from pylons import config |
|
171 | 171 | config['app_conf'] = {} |
|
172 | 172 | config['app_conf']['use_gravatar'] = True |
|
173 | 173 | config['app_conf'].update(kwargs) |
|
174 | 174 | return config |
|
175 | 175 | |
|
176 | 176 | class fake_url(): |
|
177 | 177 | @classmethod |
|
178 | 178 | def current(cls, *args, **kwargs): |
|
179 | 179 | return 'https://server.com' |
|
180 | 180 | |
|
181 | 181 | with mock.patch('pylons.url', fake_url): |
|
182 | 182 | fake = fake_conf(alternative_gravatar_url='http://test.com/{email}') |
|
183 | 183 | with mock.patch('pylons.config', fake): |
|
184 | 184 | from pylons import url |
|
185 | 185 | assert url.current() == 'https://server.com' |
|
186 | 186 | grav = gravatar_url(email_address='test@foo.com', size=24) |
|
187 | 187 | assert grav == 'http://test.com/test@foo.com' |
|
188 | 188 | |
|
189 | 189 | fake = fake_conf(alternative_gravatar_url='http://test.com/{email}') |
|
190 | 190 | with mock.patch('pylons.config', fake): |
|
191 | 191 | grav = gravatar_url(email_address='test@foo.com', size=24) |
|
192 | 192 | assert grav == 'http://test.com/test@foo.com' |
|
193 | 193 | |
|
194 | 194 | fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}') |
|
195 | 195 | with mock.patch('pylons.config', fake): |
|
196 | 196 | em = 'test@foo.com' |
|
197 | 197 | grav = gravatar_url(email_address=em, size=24) |
|
198 | 198 | assert grav == 'http://test.com/%s' % (_md5(em)) |
|
199 | 199 | |
|
200 | 200 | fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}') |
|
201 | 201 | with mock.patch('pylons.config', fake): |
|
202 | 202 | em = 'test@foo.com' |
|
203 | 203 | grav = gravatar_url(email_address=em, size=24) |
|
204 | 204 | assert grav == 'http://test.com/%s/%s' % (_md5(em), 24) |
|
205 | 205 | |
|
206 | 206 | fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}') |
|
207 | 207 | with mock.patch('pylons.config', fake): |
|
208 | 208 | em = 'test@foo.com' |
|
209 | 209 | grav = gravatar_url(email_address=em, size=24) |
|
210 | 210 | assert grav == 'https://server.com/%s/%s' % (_md5(em), 24) |
|
211 | 211 | |
|
212 | 212 | @parameterized.expand([ |
|
213 | 213 | ("", |
|
214 | 214 | ""), |
|
215 | 215 | ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68", |
|
216 | 216 | "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"), |
|
217 | 217 | ("from rev 000000000000", |
|
218 | 218 | "from rev url[000000000000]"), |
|
219 | 219 | ("from rev 000000000000123123 also rev 000000000000", |
|
220 | 220 | "from rev url[000000000000123123] also rev url[000000000000]"), |
|
221 | 221 | ("this should-000 00", |
|
222 | 222 | "this should-000 00"), |
|
223 | 223 | ("longtextffffffffff rev 123123123123", |
|
224 | 224 | "longtextffffffffff rev url[123123123123]"), |
|
225 | 225 | ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff", |
|
226 | 226 | "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"), |
|
227 | 227 | ("ffffffffffff some text traalaa", |
|
228 | 228 | "url[ffffffffffff] some text traalaa"), |
|
229 | 229 | ("""Multi line |
|
230 |
123123123123 |
|
|
230 | 123123123123 | |
|
231 | 231 | some text 123123123123""", |
|
232 | 232 | """Multi line |
|
233 |
url[123123123123] |
|
|
233 | url[123123123123] | |
|
234 | 234 | some text url[123123123123]""") |
|
235 | 235 | ]) |
|
236 | 236 | def test_urlify_changesets(self, sample, expected): |
|
237 | 237 | import re |
|
238 | 238 | |
|
239 | 239 | def fake_url(self, *args, **kwargs): |
|
240 | 240 | return '/some-url' |
|
241 | 241 | |
|
242 | 242 | #quickly change expected url[] into a link |
|
243 | 243 | URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])') |
|
244 | 244 | |
|
245 | 245 | def url_func(match_obj): |
|
246 | 246 | _url = match_obj.groups()[0] |
|
247 | 247 | tmpl = """<a class="revision-link" href="/some-url">%s</a>""" |
|
248 | 248 | return tmpl % _url |
|
249 | 249 | |
|
250 | 250 | expected = URL_PAT.sub(url_func, expected) |
|
251 | 251 | |
|
252 | 252 | with mock.patch('pylons.url', fake_url): |
|
253 | 253 | from rhodecode.lib.helpers import urlify_changesets |
|
254 | 254 | self.assertEqual(urlify_changesets(sample, 'repo_name'), expected) |
General Comments 0
You need to be logged in to leave comments.
Login now