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