##// END OF EJS Templates
white space cleanup
marcink -
r3149:68f9c216 beta
parent child Browse files
Show More
@@ -1,486 +1,486 b''
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()], _("Tags"))
78 k, v in repo.tags.iteritems()], _("Tags"))
79
79
80 hist_l.append(bookmarks_group)
80 hist_l.append(bookmarks_group)
81 hist_l.append(branches_group)
81 hist_l.append(branches_group)
82 hist_l.append(tags_group)
82 hist_l.append(tags_group)
83
83
84 return hist_l
84 return hist_l
85
85
86 def _get_default_rev(self, repo):
86 def _get_default_rev(self, repo):
87 """
87 """
88 Get's default revision to do compare on pull request
88 Get's default revision to do compare on pull request
89
89
90 :param repo:
90 :param repo:
91 """
91 """
92 repo = repo.scm_instance
92 repo = repo.scm_instance
93 if 'default' in repo.branches:
93 if 'default' in repo.branches:
94 return 'default'
94 return 'default'
95 else:
95 else:
96 #if repo doesn't have default branch return first found
96 #if repo doesn't have default branch return first found
97 return repo.branches.keys()[0]
97 return repo.branches.keys()[0]
98
98
99 def _get_is_allowed_change_status(self, pull_request):
99 def _get_is_allowed_change_status(self, pull_request):
100 owner = self.rhodecode_user.user_id == pull_request.user_id
100 owner = self.rhodecode_user.user_id == pull_request.user_id
101 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
101 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
102 pull_request.reviewers]
102 pull_request.reviewers]
103 return (self.rhodecode_user.admin or owner or reviewer)
103 return (self.rhodecode_user.admin or owner or reviewer)
104
104
105 def show_all(self, repo_name):
105 def show_all(self, repo_name):
106 c.pull_requests = PullRequestModel().get_all(repo_name)
106 c.pull_requests = PullRequestModel().get_all(repo_name)
107 c.repo_name = repo_name
107 c.repo_name = repo_name
108 return render('/pullrequests/pullrequest_show_all.html')
108 return render('/pullrequests/pullrequest_show_all.html')
109
109
110 @NotAnonymous()
110 @NotAnonymous()
111 def index(self):
111 def index(self):
112 org_repo = c.rhodecode_db_repo
112 org_repo = c.rhodecode_db_repo
113
113
114 if org_repo.scm_instance.alias != 'hg':
114 if org_repo.scm_instance.alias != 'hg':
115 log.error('Review not available for GIT REPOS')
115 log.error('Review not available for GIT REPOS')
116 raise HTTPNotFound
116 raise HTTPNotFound
117
117
118 try:
118 try:
119 org_repo.scm_instance.get_changeset()
119 org_repo.scm_instance.get_changeset()
120 except EmptyRepositoryError, e:
120 except EmptyRepositoryError, e:
121 h.flash(h.literal(_('There are no changesets yet')),
121 h.flash(h.literal(_('There are no changesets yet')),
122 category='warning')
122 category='warning')
123 redirect(url('summary_home', repo_name=org_repo.repo_name))
123 redirect(url('summary_home', repo_name=org_repo.repo_name))
124
124
125 other_repos_info = {}
125 other_repos_info = {}
126
126
127 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
127 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
128 c.org_repos = []
128 c.org_repos = []
129 c.other_repos = []
129 c.other_repos = []
130 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
130 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
131 org_repo.user.username, c.repo_name))
131 org_repo.user.username, c.repo_name))
132 )
132 )
133
133
134 # add org repo to other so we can open pull request agains itself
134 # add org repo to other so we can open pull request agains itself
135 c.other_repos.extend(c.org_repos)
135 c.other_repos.extend(c.org_repos)
136
136
137 c.default_pull_request = org_repo.repo_name # repo name pre-selected
137 c.default_pull_request = org_repo.repo_name # repo name pre-selected
138 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
138 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
139 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
139 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
140 #add orginal repo
140 #add orginal repo
141 other_repos_info[org_repo.repo_name] = {
141 other_repos_info[org_repo.repo_name] = {
142 'gravatar': h.gravatar_url(org_repo.user.email, 24),
142 'gravatar': h.gravatar_url(org_repo.user.email, 24),
143 'description': org_repo.description,
143 'description': org_repo.description,
144 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
144 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
145 }
145 }
146
146
147 #gather forks and add to this list
147 #gather forks and add to this list
148 for fork in org_repo.forks:
148 for fork in org_repo.forks:
149 c.other_repos.append((fork.repo_name, '%s/%s' % (
149 c.other_repos.append((fork.repo_name, '%s/%s' % (
150 fork.user.username, fork.repo_name))
150 fork.user.username, fork.repo_name))
151 )
151 )
152 other_repos_info[fork.repo_name] = {
152 other_repos_info[fork.repo_name] = {
153 'gravatar': h.gravatar_url(fork.user.email, 24),
153 'gravatar': h.gravatar_url(fork.user.email, 24),
154 'description': fork.description,
154 'description': fork.description,
155 'revs': h.select('other_ref', '',
155 'revs': h.select('other_ref', '',
156 self._get_repo_refs(fork.scm_instance),
156 self._get_repo_refs(fork.scm_instance),
157 class_='refs')
157 class_='refs')
158 }
158 }
159 #add parents of this fork also, but only if it's not empty
159 #add parents of this fork also, but only if it's not empty
160 if org_repo.parent and org_repo.parent.scm_instance.revisions:
160 if org_repo.parent and org_repo.parent.scm_instance.revisions:
161 c.default_pull_request = org_repo.parent.repo_name
161 c.default_pull_request = org_repo.parent.repo_name
162 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
162 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
163 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
163 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
164 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
164 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
165 org_repo.parent.user.username,
165 org_repo.parent.user.username,
166 org_repo.parent.repo_name))
166 org_repo.parent.repo_name))
167 )
167 )
168 other_repos_info[org_repo.parent.repo_name] = {
168 other_repos_info[org_repo.parent.repo_name] = {
169 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
169 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
170 'description': org_repo.parent.description,
170 'description': org_repo.parent.description,
171 'revs': h.select('other_ref', '',
171 'revs': h.select('other_ref', '',
172 self._get_repo_refs(org_repo.parent.scm_instance),
172 self._get_repo_refs(org_repo.parent.scm_instance),
173 class_='refs')
173 class_='refs')
174 }
174 }
175
175
176 c.other_repos_info = json.dumps(other_repos_info)
176 c.other_repos_info = json.dumps(other_repos_info)
177 c.review_members = [org_repo.user]
177 c.review_members = [org_repo.user]
178 return render('/pullrequests/pullrequest.html')
178 return render('/pullrequests/pullrequest.html')
179
179
180 @NotAnonymous()
180 @NotAnonymous()
181 def create(self, repo_name):
181 def create(self, repo_name):
182 repo = RepoModel()._get_repo(repo_name)
182 repo = RepoModel()._get_repo(repo_name)
183 try:
183 try:
184 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
184 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
185 except formencode.Invalid, errors:
185 except formencode.Invalid, errors:
186 log.error(traceback.format_exc())
186 log.error(traceback.format_exc())
187 if errors.error_dict.get('revisions'):
187 if errors.error_dict.get('revisions'):
188 msg = 'Revisions: %s' % errors.error_dict['revisions']
188 msg = 'Revisions: %s' % errors.error_dict['revisions']
189 elif errors.error_dict.get('pullrequest_title'):
189 elif errors.error_dict.get('pullrequest_title'):
190 msg = _('Pull request requires a title with min. 3 chars')
190 msg = _('Pull request requires a title with min. 3 chars')
191 else:
191 else:
192 msg = _('error during creation of pull request')
192 msg = _('error during creation of pull request')
193
193
194 h.flash(msg, 'error')
194 h.flash(msg, 'error')
195 return redirect(url('pullrequest_home', repo_name=repo_name))
195 return redirect(url('pullrequest_home', repo_name=repo_name))
196
196
197 org_repo = _form['org_repo']
197 org_repo = _form['org_repo']
198 org_ref = _form['org_ref']
198 org_ref = _form['org_ref']
199 other_repo = _form['other_repo']
199 other_repo = _form['other_repo']
200 other_ref = _form['other_ref']
200 other_ref = _form['other_ref']
201 revisions = _form['revisions']
201 revisions = _form['revisions']
202 reviewers = _form['review_members']
202 reviewers = _form['review_members']
203
203
204 # if we have cherry picked pull request we don't care what is in
204 # if we have cherry picked pull request we don't care what is in
205 # org_ref/other_ref
205 # org_ref/other_ref
206 rev_start = request.POST.get('rev_start')
206 rev_start = request.POST.get('rev_start')
207 rev_end = request.POST.get('rev_end')
207 rev_end = request.POST.get('rev_end')
208
208
209 if rev_start and rev_end:
209 if rev_start and rev_end:
210 # this is swapped to simulate that rev_end is a revision from
210 # this is swapped to simulate that rev_end is a revision from
211 # parent of the fork
211 # parent of the fork
212 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
212 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
213 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
213 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
214
214
215 title = _form['pullrequest_title']
215 title = _form['pullrequest_title']
216 description = _form['pullrequest_desc']
216 description = _form['pullrequest_desc']
217
217
218 try:
218 try:
219 pull_request = PullRequestModel().create(
219 pull_request = PullRequestModel().create(
220 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
220 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
221 other_ref, revisions, reviewers, title, description
221 other_ref, revisions, reviewers, title, description
222 )
222 )
223 Session().commit()
223 Session().commit()
224 h.flash(_('Successfully opened new pull request'),
224 h.flash(_('Successfully opened new pull request'),
225 category='success')
225 category='success')
226 except Exception:
226 except Exception:
227 h.flash(_('Error occurred during sending pull request'),
227 h.flash(_('Error occurred during sending pull request'),
228 category='error')
228 category='error')
229 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
230 return redirect(url('pullrequest_home', repo_name=repo_name))
230 return redirect(url('pullrequest_home', repo_name=repo_name))
231
231
232 return redirect(url('pullrequest_show', repo_name=other_repo,
232 return redirect(url('pullrequest_show', repo_name=other_repo,
233 pull_request_id=pull_request.pull_request_id))
233 pull_request_id=pull_request.pull_request_id))
234
234
235 @NotAnonymous()
235 @NotAnonymous()
236 @jsonify
236 @jsonify
237 def update(self, repo_name, pull_request_id):
237 def update(self, repo_name, pull_request_id):
238 pull_request = PullRequest.get_or_404(pull_request_id)
238 pull_request = PullRequest.get_or_404(pull_request_id)
239 if pull_request.is_closed():
239 if pull_request.is_closed():
240 raise HTTPForbidden()
240 raise HTTPForbidden()
241 #only owner or admin can update it
241 #only owner or admin can update it
242 owner = pull_request.author.user_id == c.rhodecode_user.user_id
242 owner = pull_request.author.user_id == c.rhodecode_user.user_id
243 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
243 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
244 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
244 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
245 request.POST.get('reviewers_ids', '').split(',')))
245 request.POST.get('reviewers_ids', '').split(',')))
246
246
247 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
247 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
248 Session().commit()
248 Session().commit()
249 return True
249 return True
250 raise HTTPForbidden()
250 raise HTTPForbidden()
251
251
252 @NotAnonymous()
252 @NotAnonymous()
253 @jsonify
253 @jsonify
254 def delete(self, repo_name, pull_request_id):
254 def delete(self, repo_name, pull_request_id):
255 pull_request = PullRequest.get_or_404(pull_request_id)
255 pull_request = PullRequest.get_or_404(pull_request_id)
256 #only owner can delete it !
256 #only owner can delete it !
257 if pull_request.author.user_id == c.rhodecode_user.user_id:
257 if pull_request.author.user_id == c.rhodecode_user.user_id:
258 PullRequestModel().delete(pull_request)
258 PullRequestModel().delete(pull_request)
259 Session().commit()
259 Session().commit()
260 h.flash(_('Successfully deleted pull request'),
260 h.flash(_('Successfully deleted pull request'),
261 category='success')
261 category='success')
262 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
262 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
263 raise HTTPForbidden()
263 raise HTTPForbidden()
264
264
265 def _load_compare_data(self, pull_request, enable_comments=True):
265 def _load_compare_data(self, pull_request, enable_comments=True):
266 """
266 """
267 Load context data needed for generating compare diff
267 Load context data needed for generating compare diff
268
268
269 :param pull_request:
269 :param pull_request:
270 :type pull_request:
270 :type pull_request:
271 """
271 """
272 rev_start = request.GET.get('rev_start')
272 rev_start = request.GET.get('rev_start')
273 rev_end = request.GET.get('rev_end')
273 rev_end = request.GET.get('rev_end')
274
274
275 org_repo = pull_request.org_repo
275 org_repo = pull_request.org_repo
276 (org_ref_type,
276 (org_ref_type,
277 org_ref_name,
277 org_ref_name,
278 org_ref_rev) = pull_request.org_ref.split(':')
278 org_ref_rev) = pull_request.org_ref.split(':')
279
279
280 other_repo = org_repo
280 other_repo = org_repo
281 (other_ref_type,
281 (other_ref_type,
282 other_ref_name,
282 other_ref_name,
283 other_ref_rev) = pull_request.other_ref.split(':')
283 other_ref_rev) = pull_request.other_ref.split(':')
284
284
285 # despite opening revisions for bookmarks/branches/tags, we always
285 # despite opening revisions for bookmarks/branches/tags, we always
286 # convert this to rev to prevent changes after book or branch change
286 # convert this to rev to prevent changes after book or branch change
287 org_ref = ('rev', org_ref_rev)
287 org_ref = ('rev', org_ref_rev)
288 other_ref = ('rev', other_ref_rev)
288 other_ref = ('rev', other_ref_rev)
289
289
290 c.org_repo = org_repo
290 c.org_repo = org_repo
291 c.other_repo = other_repo
291 c.other_repo = other_repo
292
292
293 c.fulldiff = fulldiff = request.GET.get('fulldiff')
293 c.fulldiff = fulldiff = request.GET.get('fulldiff')
294
294
295 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
295 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
296
296
297 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
297 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
298 if c.cs_ranges[0].parents
298 if c.cs_ranges[0].parents
299 else EmptyChangeset(), 'raw_id'))
299 else EmptyChangeset(), 'raw_id'))
300
300
301 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
301 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
302 c.target_repo = other_repo.repo_name
302 c.target_repo = other_repo.repo_name
303 # defines that we need hidden inputs with changesets
303 # defines that we need hidden inputs with changesets
304 c.as_form = request.GET.get('as_form', False)
304 c.as_form = request.GET.get('as_form', False)
305
305
306 c.org_ref = org_ref[1]
306 c.org_ref = org_ref[1]
307 c.other_ref = other_ref[1]
307 c.other_ref = other_ref[1]
308
308
309 diff_limit = self.cut_off_limit if not fulldiff else None
309 diff_limit = self.cut_off_limit if not fulldiff else None
310
310
311 #we swap org/other ref since we run a simple diff on one repo
311 #we swap org/other ref since we run a simple diff on one repo
312 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
312 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
313
313
314 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
314 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
315 diff_limit=diff_limit)
315 diff_limit=diff_limit)
316 _parsed = diff_processor.prepare()
316 _parsed = diff_processor.prepare()
317
317
318 c.limited_diff = False
318 c.limited_diff = False
319 if isinstance(_parsed, LimitedDiffContainer):
319 if isinstance(_parsed, LimitedDiffContainer):
320 c.limited_diff = True
320 c.limited_diff = True
321
321
322 c.files = []
322 c.files = []
323 c.changes = {}
323 c.changes = {}
324 c.lines_added = 0
324 c.lines_added = 0
325 c.lines_deleted = 0
325 c.lines_deleted = 0
326 for f in _parsed:
326 for f in _parsed:
327 st = f['stats']
327 st = f['stats']
328 if st[0] != 'b':
328 if st[0] != 'b':
329 c.lines_added += st[0]
329 c.lines_added += st[0]
330 c.lines_deleted += st[1]
330 c.lines_deleted += st[1]
331 fid = h.FID('', f['filename'])
331 fid = h.FID('', f['filename'])
332 c.files.append([fid, f['operation'], f['filename'], f['stats']])
332 c.files.append([fid, f['operation'], f['filename'], f['stats']])
333 diff = diff_processor.as_html(enable_comments=enable_comments,
333 diff = diff_processor.as_html(enable_comments=enable_comments,
334 parsed_lines=[f])
334 parsed_lines=[f])
335 c.changes[fid] = [f['operation'], f['filename'], diff]
335 c.changes[fid] = [f['operation'], f['filename'], diff]
336
336
337 def show(self, repo_name, pull_request_id):
337 def show(self, repo_name, pull_request_id):
338 repo_model = RepoModel()
338 repo_model = RepoModel()
339 c.users_array = repo_model.get_users_js()
339 c.users_array = repo_model.get_users_js()
340 c.users_groups_array = repo_model.get_users_groups_js()
340 c.users_groups_array = repo_model.get_users_groups_js()
341 c.pull_request = PullRequest.get_or_404(pull_request_id)
341 c.pull_request = PullRequest.get_or_404(pull_request_id)
342 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
342 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
343 cc_model = ChangesetCommentsModel()
343 cc_model = ChangesetCommentsModel()
344 cs_model = ChangesetStatusModel()
344 cs_model = ChangesetStatusModel()
345 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
345 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
346 pull_request=c.pull_request,
346 pull_request=c.pull_request,
347 with_revisions=True)
347 with_revisions=True)
348
348
349 cs_statuses = defaultdict(list)
349 cs_statuses = defaultdict(list)
350 for st in _cs_statuses:
350 for st in _cs_statuses:
351 cs_statuses[st.author.username] += [st]
351 cs_statuses[st.author.username] += [st]
352
352
353 c.pull_request_reviewers = []
353 c.pull_request_reviewers = []
354 c.pull_request_pending_reviewers = []
354 c.pull_request_pending_reviewers = []
355 for o in c.pull_request.reviewers:
355 for o in c.pull_request.reviewers:
356 st = cs_statuses.get(o.user.username, None)
356 st = cs_statuses.get(o.user.username, None)
357 if st:
357 if st:
358 sorter = lambda k: k.version
358 sorter = lambda k: k.version
359 st = [(x, list(y)[0])
359 st = [(x, list(y)[0])
360 for x, y in (groupby(sorted(st, key=sorter), sorter))]
360 for x, y in (groupby(sorted(st, key=sorter), sorter))]
361 else:
361 else:
362 c.pull_request_pending_reviewers.append(o.user)
362 c.pull_request_pending_reviewers.append(o.user)
363 c.pull_request_reviewers.append([o.user, st])
363 c.pull_request_reviewers.append([o.user, st])
364
364
365 # pull_requests repo_name we opened it against
365 # pull_requests repo_name we opened it against
366 # ie. other_repo must match
366 # ie. other_repo must match
367 if repo_name != c.pull_request.other_repo.repo_name:
367 if repo_name != c.pull_request.other_repo.repo_name:
368 raise HTTPNotFound
368 raise HTTPNotFound
369
369
370 # load compare data into template context
370 # load compare data into template context
371 enable_comments = not c.pull_request.is_closed()
371 enable_comments = not c.pull_request.is_closed()
372 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
372 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
373
373
374 # inline comments
374 # inline comments
375 c.inline_cnt = 0
375 c.inline_cnt = 0
376 c.inline_comments = cc_model.get_inline_comments(
376 c.inline_comments = cc_model.get_inline_comments(
377 c.rhodecode_db_repo.repo_id,
377 c.rhodecode_db_repo.repo_id,
378 pull_request=pull_request_id)
378 pull_request=pull_request_id)
379 # count inline comments
379 # count inline comments
380 for __, lines in c.inline_comments:
380 for __, lines in c.inline_comments:
381 for comments in lines.values():
381 for comments in lines.values():
382 c.inline_cnt += len(comments)
382 c.inline_cnt += len(comments)
383 # comments
383 # comments
384 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
384 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
385 pull_request=pull_request_id)
385 pull_request=pull_request_id)
386
386
387 try:
387 try:
388 cur_status = c.statuses[c.pull_request.revisions[0]][0]
388 cur_status = c.statuses[c.pull_request.revisions[0]][0]
389 except:
389 except:
390 log.error(traceback.format_exc())
390 log.error(traceback.format_exc())
391 cur_status = 'undefined'
391 cur_status = 'undefined'
392 if c.pull_request.is_closed() and 0:
392 if c.pull_request.is_closed() and 0:
393 c.current_changeset_status = cur_status
393 c.current_changeset_status = cur_status
394 else:
394 else:
395 # changeset(pull-request) status calulation based on reviewers
395 # changeset(pull-request) status calulation based on reviewers
396 c.current_changeset_status = cs_model.calculate_status(
396 c.current_changeset_status = cs_model.calculate_status(
397 c.pull_request_reviewers,
397 c.pull_request_reviewers,
398 )
398 )
399 c.changeset_statuses = ChangesetStatus.STATUSES
399 c.changeset_statuses = ChangesetStatus.STATUSES
400
400
401 return render('/pullrequests/pullrequest_show.html')
401 return render('/pullrequests/pullrequest_show.html')
402
402
403 @NotAnonymous()
403 @NotAnonymous()
404 @jsonify
404 @jsonify
405 def comment(self, repo_name, pull_request_id):
405 def comment(self, repo_name, pull_request_id):
406 pull_request = PullRequest.get_or_404(pull_request_id)
406 pull_request = PullRequest.get_or_404(pull_request_id)
407 if pull_request.is_closed():
407 if pull_request.is_closed():
408 raise HTTPForbidden()
408 raise HTTPForbidden()
409
409
410 status = request.POST.get('changeset_status')
410 status = request.POST.get('changeset_status')
411 change_status = request.POST.get('change_changeset_status')
411 change_status = request.POST.get('change_changeset_status')
412 text = request.POST.get('text')
412 text = request.POST.get('text')
413
413
414 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
414 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
415 if status and change_status and allowed_to_change_status:
415 if status and change_status and allowed_to_change_status:
416 text = text or (_('Status change -> %s')
416 text = text or (_('Status change -> %s')
417 % ChangesetStatus.get_status_lbl(status))
417 % ChangesetStatus.get_status_lbl(status))
418 comm = ChangesetCommentsModel().create(
418 comm = ChangesetCommentsModel().create(
419 text=text,
419 text=text,
420 repo=c.rhodecode_db_repo.repo_id,
420 repo=c.rhodecode_db_repo.repo_id,
421 user=c.rhodecode_user.user_id,
421 user=c.rhodecode_user.user_id,
422 pull_request=pull_request_id,
422 pull_request=pull_request_id,
423 f_path=request.POST.get('f_path'),
423 f_path=request.POST.get('f_path'),
424 line_no=request.POST.get('line'),
424 line_no=request.POST.get('line'),
425 status_change=(ChangesetStatus.get_status_lbl(status)
425 status_change=(ChangesetStatus.get_status_lbl(status)
426 if status and change_status and allowed_to_change_status else None)
426 if status and change_status and allowed_to_change_status else None)
427 )
427 )
428
428
429 action_logger(self.rhodecode_user,
429 action_logger(self.rhodecode_user,
430 'user_commented_pull_request:%s' % pull_request_id,
430 'user_commented_pull_request:%s' % pull_request_id,
431 c.rhodecode_db_repo, self.ip_addr, self.sa)
431 c.rhodecode_db_repo, self.ip_addr, self.sa)
432
432
433 if allowed_to_change_status:
433 if allowed_to_change_status:
434 # get status if set !
434 # get status if set !
435 if status and change_status:
435 if status and change_status:
436 ChangesetStatusModel().set_status(
436 ChangesetStatusModel().set_status(
437 c.rhodecode_db_repo.repo_id,
437 c.rhodecode_db_repo.repo_id,
438 status,
438 status,
439 c.rhodecode_user.user_id,
439 c.rhodecode_user.user_id,
440 comm,
440 comm,
441 pull_request=pull_request_id
441 pull_request=pull_request_id
442 )
442 )
443
443
444 if request.POST.get('save_close'):
444 if request.POST.get('save_close'):
445 if status in ['rejected', 'approved']:
445 if status in ['rejected', 'approved']:
446 PullRequestModel().close_pull_request(pull_request_id)
446 PullRequestModel().close_pull_request(pull_request_id)
447 action_logger(self.rhodecode_user,
447 action_logger(self.rhodecode_user,
448 'user_closed_pull_request:%s' % pull_request_id,
448 'user_closed_pull_request:%s' % pull_request_id,
449 c.rhodecode_db_repo, self.ip_addr, self.sa)
449 c.rhodecode_db_repo, self.ip_addr, self.sa)
450 else:
450 else:
451 h.flash(_('Closing pull request on other statuses than '
451 h.flash(_('Closing pull request on other statuses than '
452 'rejected or approved forbidden'),
452 'rejected or approved forbidden'),
453 category='warning')
453 category='warning')
454
454
455 Session().commit()
455 Session().commit()
456
456
457 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
457 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
458 return redirect(h.url('pullrequest_show', repo_name=repo_name,
458 return redirect(h.url('pullrequest_show', repo_name=repo_name,
459 pull_request_id=pull_request_id))
459 pull_request_id=pull_request_id))
460
460
461 data = {
461 data = {
462 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
462 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
463 }
463 }
464 if comm:
464 if comm:
465 c.co = comm
465 c.co = comm
466 data.update(comm.get_dict())
466 data.update(comm.get_dict())
467 data.update({'rendered_text':
467 data.update({'rendered_text':
468 render('changeset/changeset_comment_block.html')})
468 render('changeset/changeset_comment_block.html')})
469
469
470 return data
470 return data
471
471
472 @NotAnonymous()
472 @NotAnonymous()
473 @jsonify
473 @jsonify
474 def delete_comment(self, repo_name, comment_id):
474 def delete_comment(self, repo_name, comment_id):
475 co = ChangesetComment.get(comment_id)
475 co = ChangesetComment.get(comment_id)
476 if co.pull_request.is_closed():
476 if co.pull_request.is_closed():
477 #don't allow deleting comments on closed pull request
477 #don't allow deleting comments on closed pull request
478 raise HTTPForbidden()
478 raise HTTPForbidden()
479
479
480 owner = co.author.user_id == c.rhodecode_user.user_id
480 owner = co.author.user_id == c.rhodecode_user.user_id
481 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
481 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
482 ChangesetCommentsModel().delete(comment=co)
482 ChangesetCommentsModel().delete(comment=co)
483 Session().commit()
483 Session().commit()
484 return True
484 return True
485 else:
485 else:
486 raise HTTPForbidden()
486 raise HTTPForbidden()
@@ -1,1173 +1,1172 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils import repo_name_slug
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
47 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.db import URL_SEP, Permission
52 from rhodecode.model.db import URL_SEP, Permission
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 html_escape_table = {
57 html_escape_table = {
58 "&": "&amp;",
58 "&": "&amp;",
59 '"': "&quot;",
59 '"': "&quot;",
60 "'": "&apos;",
60 "'": "&apos;",
61 ">": "&gt;",
61 ">": "&gt;",
62 "<": "&lt;",
62 "<": "&lt;",
63 }
63 }
64
64
65
65
66 def html_escape(text):
66 def html_escape(text):
67 """Produce entities within text."""
67 """Produce entities within text."""
68 return "".join(html_escape_table.get(c, c) for c in text)
68 return "".join(html_escape_table.get(c, c) for c in text)
69
69
70
70
71 def shorter(text, size=20):
71 def shorter(text, size=20):
72 postfix = '...'
72 postfix = '...'
73 if len(text) > size:
73 if len(text) > size:
74 return text[:size - len(postfix)] + postfix
74 return text[:size - len(postfix)] + postfix
75 return text
75 return text
76
76
77
77
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 """
79 """
80 Reset button
80 Reset button
81 """
81 """
82 _set_input_attrs(attrs, type, name, value)
82 _set_input_attrs(attrs, type, name, value)
83 _set_id_attr(attrs, id, name)
83 _set_id_attr(attrs, id, name)
84 convert_boolean_attrs(attrs, ["disabled"])
84 convert_boolean_attrs(attrs, ["disabled"])
85 return HTML.input(**attrs)
85 return HTML.input(**attrs)
86
86
87 reset = _reset
87 reset = _reset
88 safeid = _make_safe_id_component
88 safeid = _make_safe_id_component
89
89
90
90
91 def FID(raw_id, path):
91 def FID(raw_id, path):
92 """
92 """
93 Creates a uniqe ID for filenode based on it's hash of path and revision
93 Creates a uniqe ID for filenode based on it's hash of path and revision
94 it's safe to use in urls
94 it's safe to use in urls
95
95
96 :param raw_id:
96 :param raw_id:
97 :param path:
97 :param path:
98 """
98 """
99
99
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101
101
102
102
103 def get_token():
103 def get_token():
104 """Return the current authentication token, creating one if one doesn't
104 """Return the current authentication token, creating one if one doesn't
105 already exist.
105 already exist.
106 """
106 """
107 token_key = "_authentication_token"
107 token_key = "_authentication_token"
108 from pylons import session
108 from pylons import session
109 if not token_key in session:
109 if not token_key in session:
110 try:
110 try:
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 except AttributeError: # Python < 2.4
112 except AttributeError: # Python < 2.4
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 session[token_key] = token
114 session[token_key] = token
115 if hasattr(session, 'save'):
115 if hasattr(session, 'save'):
116 session.save()
116 session.save()
117 return session[token_key]
117 return session[token_key]
118
118
119
119
120 class _GetError(object):
120 class _GetError(object):
121 """Get error from form_errors, and represent it as span wrapped error
121 """Get error from form_errors, and represent it as span wrapped error
122 message
122 message
123
123
124 :param field_name: field to fetch errors for
124 :param field_name: field to fetch errors for
125 :param form_errors: form errors dict
125 :param form_errors: form errors dict
126 """
126 """
127
127
128 def __call__(self, field_name, form_errors):
128 def __call__(self, field_name, form_errors):
129 tmpl = """<span class="error_msg">%s</span>"""
129 tmpl = """<span class="error_msg">%s</span>"""
130 if form_errors and field_name in form_errors:
130 if form_errors and field_name in form_errors:
131 return literal(tmpl % form_errors.get(field_name))
131 return literal(tmpl % form_errors.get(field_name))
132
132
133 get_error = _GetError()
133 get_error = _GetError()
134
134
135
135
136 class _ToolTip(object):
136 class _ToolTip(object):
137
137
138 def __call__(self, tooltip_title, trim_at=50):
138 def __call__(self, tooltip_title, trim_at=50):
139 """
139 """
140 Special function just to wrap our text into nice formatted
140 Special function just to wrap our text into nice formatted
141 autowrapped text
141 autowrapped text
142
142
143 :param tooltip_title:
143 :param tooltip_title:
144 """
144 """
145 tooltip_title = escape(tooltip_title)
145 tooltip_title = escape(tooltip_title)
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 return tooltip_title
147 return tooltip_title
148 tooltip = _ToolTip()
148 tooltip = _ToolTip()
149
149
150
150
151 class _FilesBreadCrumbs(object):
151 class _FilesBreadCrumbs(object):
152
152
153 def __call__(self, repo_name, rev, paths):
153 def __call__(self, repo_name, rev, paths):
154 if isinstance(paths, str):
154 if isinstance(paths, str):
155 paths = safe_unicode(paths)
155 paths = safe_unicode(paths)
156 url_l = [link_to(repo_name, url('files_home',
156 url_l = [link_to(repo_name, url('files_home',
157 repo_name=repo_name,
157 repo_name=repo_name,
158 revision=rev, f_path=''),
158 revision=rev, f_path=''),
159 class_='ypjax-link')]
159 class_='ypjax-link')]
160 paths_l = paths.split('/')
160 paths_l = paths.split('/')
161 for cnt, p in enumerate(paths_l):
161 for cnt, p in enumerate(paths_l):
162 if p != '':
162 if p != '':
163 url_l.append(link_to(p,
163 url_l.append(link_to(p,
164 url('files_home',
164 url('files_home',
165 repo_name=repo_name,
165 repo_name=repo_name,
166 revision=rev,
166 revision=rev,
167 f_path='/'.join(paths_l[:cnt + 1])
167 f_path='/'.join(paths_l[:cnt + 1])
168 ),
168 ),
169 class_='ypjax-link'
169 class_='ypjax-link'
170 )
170 )
171 )
171 )
172
172
173 return literal('/'.join(url_l))
173 return literal('/'.join(url_l))
174
174
175 files_breadcrumbs = _FilesBreadCrumbs()
175 files_breadcrumbs = _FilesBreadCrumbs()
176
176
177
177
178 class CodeHtmlFormatter(HtmlFormatter):
178 class CodeHtmlFormatter(HtmlFormatter):
179 """
179 """
180 My code Html Formatter for source codes
180 My code Html Formatter for source codes
181 """
181 """
182
182
183 def wrap(self, source, outfile):
183 def wrap(self, source, outfile):
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185
185
186 def _wrap_code(self, source):
186 def _wrap_code(self, source):
187 for cnt, it in enumerate(source):
187 for cnt, it in enumerate(source):
188 i, t = it
188 i, t = it
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 yield i, t
190 yield i, t
191
191
192 def _wrap_tablelinenos(self, inner):
192 def _wrap_tablelinenos(self, inner):
193 dummyoutfile = StringIO.StringIO()
193 dummyoutfile = StringIO.StringIO()
194 lncount = 0
194 lncount = 0
195 for t, line in inner:
195 for t, line in inner:
196 if t:
196 if t:
197 lncount += 1
197 lncount += 1
198 dummyoutfile.write(line)
198 dummyoutfile.write(line)
199
199
200 fl = self.linenostart
200 fl = self.linenostart
201 mw = len(str(lncount + fl - 1))
201 mw = len(str(lncount + fl - 1))
202 sp = self.linenospecial
202 sp = self.linenospecial
203 st = self.linenostep
203 st = self.linenostep
204 la = self.lineanchors
204 la = self.lineanchors
205 aln = self.anchorlinenos
205 aln = self.anchorlinenos
206 nocls = self.noclasses
206 nocls = self.noclasses
207 if sp:
207 if sp:
208 lines = []
208 lines = []
209
209
210 for i in range(fl, fl + lncount):
210 for i in range(fl, fl + lncount):
211 if i % st == 0:
211 if i % st == 0:
212 if i % sp == 0:
212 if i % sp == 0:
213 if aln:
213 if aln:
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 (la, i, mw, i))
215 (la, i, mw, i))
216 else:
216 else:
217 lines.append('<span class="special">%*d</span>' % (mw, i))
217 lines.append('<span class="special">%*d</span>' % (mw, i))
218 else:
218 else:
219 if aln:
219 if aln:
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 else:
221 else:
222 lines.append('%*d' % (mw, i))
222 lines.append('%*d' % (mw, i))
223 else:
223 else:
224 lines.append('')
224 lines.append('')
225 ls = '\n'.join(lines)
225 ls = '\n'.join(lines)
226 else:
226 else:
227 lines = []
227 lines = []
228 for i in range(fl, fl + lncount):
228 for i in range(fl, fl + lncount):
229 if i % st == 0:
229 if i % st == 0:
230 if aln:
230 if aln:
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 else:
232 else:
233 lines.append('%*d' % (mw, i))
233 lines.append('%*d' % (mw, i))
234 else:
234 else:
235 lines.append('')
235 lines.append('')
236 ls = '\n'.join(lines)
236 ls = '\n'.join(lines)
237
237
238 # in case you wonder about the seemingly redundant <div> here: since the
238 # in case you wonder about the seemingly redundant <div> here: since the
239 # content in the other cell also is wrapped in a div, some browsers in
239 # content in the other cell also is wrapped in a div, some browsers in
240 # some configurations seem to mess up the formatting...
240 # some configurations seem to mess up the formatting...
241 if nocls:
241 if nocls:
242 yield 0, ('<table class="%stable">' % self.cssclass +
242 yield 0, ('<table class="%stable">' % self.cssclass +
243 '<tr><td><div class="linenodiv" '
243 '<tr><td><div class="linenodiv" '
244 'style="background-color: #f0f0f0; padding-right: 10px">'
244 'style="background-color: #f0f0f0; padding-right: 10px">'
245 '<pre style="line-height: 125%">' +
245 '<pre style="line-height: 125%">' +
246 ls + '</pre></div></td><td id="hlcode" class="code">')
246 ls + '</pre></div></td><td id="hlcode" class="code">')
247 else:
247 else:
248 yield 0, ('<table class="%stable">' % self.cssclass +
248 yield 0, ('<table class="%stable">' % self.cssclass +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 ls + '</pre></div></td><td id="hlcode" class="code">')
250 ls + '</pre></div></td><td id="hlcode" class="code">')
251 yield 0, dummyoutfile.getvalue()
251 yield 0, dummyoutfile.getvalue()
252 yield 0, '</td></tr></table>'
252 yield 0, '</td></tr></table>'
253
253
254
254
255 def pygmentize(filenode, **kwargs):
255 def pygmentize(filenode, **kwargs):
256 """pygmentize function using pygments
256 """pygmentize function using pygments
257
257
258 :param filenode:
258 :param filenode:
259 """
259 """
260
260
261 return literal(code_highlight(filenode.content,
261 return literal(code_highlight(filenode.content,
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
263
263
264
264
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
266 """
266 """
267 pygmentize function for annotation
267 pygmentize function for annotation
268
268
269 :param filenode:
269 :param filenode:
270 """
270 """
271
271
272 color_dict = {}
272 color_dict = {}
273
273
274 def gen_color(n=10000):
274 def gen_color(n=10000):
275 """generator for getting n of evenly distributed colors using
275 """generator for getting n of evenly distributed colors using
276 hsv color and golden ratio. It always return same order of colors
276 hsv color and golden ratio. It always return same order of colors
277
277
278 :returns: RGB tuple
278 :returns: RGB tuple
279 """
279 """
280
280
281 def hsv_to_rgb(h, s, v):
281 def hsv_to_rgb(h, s, v):
282 if s == 0.0:
282 if s == 0.0:
283 return v, v, v
283 return v, v, v
284 i = int(h * 6.0) # XXX assume int() truncates!
284 i = int(h * 6.0) # XXX assume int() truncates!
285 f = (h * 6.0) - i
285 f = (h * 6.0) - i
286 p = v * (1.0 - s)
286 p = v * (1.0 - s)
287 q = v * (1.0 - s * f)
287 q = v * (1.0 - s * f)
288 t = v * (1.0 - s * (1.0 - f))
288 t = v * (1.0 - s * (1.0 - f))
289 i = i % 6
289 i = i % 6
290 if i == 0:
290 if i == 0:
291 return v, t, p
291 return v, t, p
292 if i == 1:
292 if i == 1:
293 return q, v, p
293 return q, v, p
294 if i == 2:
294 if i == 2:
295 return p, v, t
295 return p, v, t
296 if i == 3:
296 if i == 3:
297 return p, q, v
297 return p, q, v
298 if i == 4:
298 if i == 4:
299 return t, p, v
299 return t, p, v
300 if i == 5:
300 if i == 5:
301 return v, p, q
301 return v, p, q
302
302
303 golden_ratio = 0.618033988749895
303 golden_ratio = 0.618033988749895
304 h = 0.22717784590367374
304 h = 0.22717784590367374
305
305
306 for _ in xrange(n):
306 for _ in xrange(n):
307 h += golden_ratio
307 h += golden_ratio
308 h %= 1
308 h %= 1
309 HSV_tuple = [h, 0.95, 0.95]
309 HSV_tuple = [h, 0.95, 0.95]
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
312
312
313 cgenerator = gen_color()
313 cgenerator = gen_color()
314
314
315 def get_color_string(cs):
315 def get_color_string(cs):
316 if cs in color_dict:
316 if cs in color_dict:
317 col = color_dict[cs]
317 col = color_dict[cs]
318 else:
318 else:
319 col = color_dict[cs] = cgenerator.next()
319 col = color_dict[cs] = cgenerator.next()
320 return "color: rgb(%s)! important;" % (', '.join(col))
320 return "color: rgb(%s)! important;" % (', '.join(col))
321
321
322 def url_func(repo_name):
322 def url_func(repo_name):
323
323
324 def _url_func(changeset):
324 def _url_func(changeset):
325 author = changeset.author
325 author = changeset.author
326 date = changeset.date
326 date = changeset.date
327 message = tooltip(changeset.message)
327 message = tooltip(changeset.message)
328
328
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
331 "</b> %s<br/></div>")
331 "</b> %s<br/></div>")
332
332
333 tooltip_html = tooltip_html % (author, date, message)
333 tooltip_html = tooltip_html % (author, date, message)
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
335 short_id(changeset.raw_id))
335 short_id(changeset.raw_id))
336 uri = link_to(
336 uri = link_to(
337 lnk_format,
337 lnk_format,
338 url('changeset_home', repo_name=repo_name,
338 url('changeset_home', repo_name=repo_name,
339 revision=changeset.raw_id),
339 revision=changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
341 class_='tooltip',
341 class_='tooltip',
342 title=tooltip_html
342 title=tooltip_html
343 )
343 )
344
344
345 uri += '\n'
345 uri += '\n'
346 return uri
346 return uri
347 return _url_func
347 return _url_func
348
348
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350
350
351
351
352 def is_following_repo(repo_name, user_id):
352 def is_following_repo(repo_name, user_id):
353 from rhodecode.model.scm import ScmModel
353 from rhodecode.model.scm import ScmModel
354 return ScmModel().is_following_repo(repo_name, user_id)
354 return ScmModel().is_following_repo(repo_name, user_id)
355
355
356 flash = _Flash()
356 flash = _Flash()
357
357
358 #==============================================================================
358 #==============================================================================
359 # SCM FILTERS available via h.
359 # SCM FILTERS available via h.
360 #==============================================================================
360 #==============================================================================
361 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.vcs.utils import author_name, author_email
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
363 from rhodecode.model.db import User, ChangesetStatus
363 from rhodecode.model.db import User, ChangesetStatus
364
364
365 age = lambda x: _age(x)
365 age = lambda x: _age(x)
366 capitalize = lambda x: x.capitalize()
366 capitalize = lambda x: x.capitalize()
367 email = author_email
367 email = author_email
368 short_id = lambda x: x[:12]
368 short_id = lambda x: x[:12]
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
370
370
371
371
372 def fmt_date(date):
372 def fmt_date(date):
373 if date:
373 if date:
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
375 return date.strftime(_fmt).decode('utf8')
375 return date.strftime(_fmt).decode('utf8')
376
376
377 return ""
377 return ""
378
378
379
379
380 def is_git(repository):
380 def is_git(repository):
381 if hasattr(repository, 'alias'):
381 if hasattr(repository, 'alias'):
382 _type = repository.alias
382 _type = repository.alias
383 elif hasattr(repository, 'repo_type'):
383 elif hasattr(repository, 'repo_type'):
384 _type = repository.repo_type
384 _type = repository.repo_type
385 else:
385 else:
386 _type = repository
386 _type = repository
387 return _type == 'git'
387 return _type == 'git'
388
388
389
389
390 def is_hg(repository):
390 def is_hg(repository):
391 if hasattr(repository, 'alias'):
391 if hasattr(repository, 'alias'):
392 _type = repository.alias
392 _type = repository.alias
393 elif hasattr(repository, 'repo_type'):
393 elif hasattr(repository, 'repo_type'):
394 _type = repository.repo_type
394 _type = repository.repo_type
395 else:
395 else:
396 _type = repository
396 _type = repository
397 return _type == 'hg'
397 return _type == 'hg'
398
398
399
399
400 def email_or_none(author):
400 def email_or_none(author):
401 # extract email from the commit string
401 # extract email from the commit string
402 _email = email(author)
402 _email = email(author)
403 if _email != '':
403 if _email != '':
404 # check it against RhodeCode database, and use the MAIN email for this
404 # check it against RhodeCode database, and use the MAIN email for this
405 # user
405 # user
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
407 if user is not None:
407 if user is not None:
408 return user.email
408 return user.email
409 return _email
409 return _email
410
410
411 # See if it contains a username we can get an email from
411 # See if it contains a username we can get an email from
412 user = User.get_by_username(author_name(author), case_insensitive=True,
412 user = User.get_by_username(author_name(author), case_insensitive=True,
413 cache=True)
413 cache=True)
414 if user is not None:
414 if user is not None:
415 return user.email
415 return user.email
416
416
417 # No valid email, not a valid user in the system, none!
417 # No valid email, not a valid user in the system, none!
418 return None
418 return None
419
419
420
420
421 def person(author, show_attr="username_and_name"):
421 def person(author, show_attr="username_and_name"):
422 # attr to return from fetched user
422 # attr to return from fetched user
423 person_getter = lambda usr: getattr(usr, show_attr)
423 person_getter = lambda usr: getattr(usr, show_attr)
424
424
425 # Valid email in the attribute passed, see if they're in the system
425 # Valid email in the attribute passed, see if they're in the system
426 _email = email(author)
426 _email = email(author)
427 if _email != '':
427 if _email != '':
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
429 if user is not None:
429 if user is not None:
430 return person_getter(user)
430 return person_getter(user)
431 return _email
431 return _email
432
432
433 # Maybe it's a username?
433 # Maybe it's a username?
434 _author = author_name(author)
434 _author = author_name(author)
435 user = User.get_by_username(_author, case_insensitive=True,
435 user = User.get_by_username(_author, case_insensitive=True,
436 cache=True)
436 cache=True)
437 if user is not None:
437 if user is not None:
438 return person_getter(user)
438 return person_getter(user)
439
439
440 # Still nothing? Just pass back the author name then
440 # Still nothing? Just pass back the author name then
441 return _author
441 return _author
442
442
443
443
444 def person_by_id(id_, show_attr="username_and_name"):
444 def person_by_id(id_, show_attr="username_and_name"):
445 # attr to return from fetched user
445 # attr to return from fetched user
446 person_getter = lambda usr: getattr(usr, show_attr)
446 person_getter = lambda usr: getattr(usr, show_attr)
447
447
448 #maybe it's an ID ?
448 #maybe it's an ID ?
449 if str(id_).isdigit() or isinstance(id_, int):
449 if str(id_).isdigit() or isinstance(id_, int):
450 id_ = int(id_)
450 id_ = int(id_)
451 user = User.get(id_)
451 user = User.get(id_)
452 if user is not None:
452 if user is not None:
453 return person_getter(user)
453 return person_getter(user)
454 return id_
454 return id_
455
455
456
456
457 def desc_stylize(value):
457 def desc_stylize(value):
458 """
458 """
459 converts tags from value into html equivalent
459 converts tags from value into html equivalent
460
460
461 :param value:
461 :param value:
462 """
462 """
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
470 '<div class="metatag" tag="lang">\\2</div>', value)
470 '<div class="metatag" tag="lang">\\2</div>', value)
471 value = re.sub(r'\[([a-z]+)\]',
471 value = re.sub(r'\[([a-z]+)\]',
472 '<div class="metatag" tag="\\1">\\1</div>', value)
472 '<div class="metatag" tag="\\1">\\1</div>', value)
473
473
474 return value
474 return value
475
475
476
476
477 def bool2icon(value):
477 def bool2icon(value):
478 """Returns True/False values represented as small html image of true/false
478 """Returns True/False values represented as small html image of true/false
479 icons
479 icons
480
480
481 :param value: bool value
481 :param value: bool value
482 """
482 """
483
483
484 if value is True:
484 if value is True:
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
486 alt=_('True'))
486 alt=_('True'))
487
487
488 if value is False:
488 if value is False:
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
490 alt=_('False'))
490 alt=_('False'))
491
491
492 return value
492 return value
493
493
494
494
495 def action_parser(user_log, feed=False, parse_cs=False):
495 def action_parser(user_log, feed=False, parse_cs=False):
496 """
496 """
497 This helper will action_map the specified string action into translated
497 This helper will action_map the specified string action into translated
498 fancy names with icons and links
498 fancy names with icons and links
499
499
500 :param user_log: user log instance
500 :param user_log: user log instance
501 :param feed: use output for feeds (no html and fancy icons)
501 :param feed: use output for feeds (no html and fancy icons)
502 :param parse_cs: parse Changesets into VCS instances
502 :param parse_cs: parse Changesets into VCS instances
503 """
503 """
504
504
505 action = user_log.action
505 action = user_log.action
506 action_params = ' '
506 action_params = ' '
507
507
508 x = action.split(':')
508 x = action.split(':')
509
509
510 if len(x) > 1:
510 if len(x) > 1:
511 action, action_params = x
511 action, action_params = x
512
512
513 def get_cs_links():
513 def get_cs_links():
514 revs_limit = 3 # display this amount always
514 revs_limit = 3 # display this amount always
515 revs_top_limit = 50 # show upto this amount of changesets hidden
515 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_ids = action_params.split(',')
516 revs_ids = action_params.split(',')
517 deleted = user_log.repository is None
517 deleted = user_log.repository is None
518 if deleted:
518 if deleted:
519 return ','.join(revs_ids)
519 return ','.join(revs_ids)
520
520
521 repo_name = user_log.repository.repo_name
521 repo_name = user_log.repository.repo_name
522
522
523 def lnk(rev, repo_name):
523 def lnk(rev, repo_name):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
525 lazy_cs = True
525 lazy_cs = True
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
527 lazy_cs = False
527 lazy_cs = False
528 lbl = '?'
528 lbl = '?'
529 if rev.op == 'delete_branch':
529 if rev.op == 'delete_branch':
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
531 title = ''
531 title = ''
532 elif rev.op == 'tag':
532 elif rev.op == 'tag':
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
534 title = ''
534 title = ''
535 _url = '#'
535 _url = '#'
536
536
537 else:
537 else:
538 lbl = '%s' % (rev.short_id[:8])
538 lbl = '%s' % (rev.short_id[:8])
539 _url = url('changeset_home', repo_name=repo_name,
539 _url = url('changeset_home', repo_name=repo_name,
540 revision=rev.raw_id)
540 revision=rev.raw_id)
541 title = tooltip(rev.message)
541 title = tooltip(rev.message)
542 else:
542 else:
543 ## changeset cannot be found/striped/removed etc.
543 ## changeset cannot be found/striped/removed etc.
544 lbl = ('%s' % rev)[:12]
544 lbl = ('%s' % rev)[:12]
545 _url = '#'
545 _url = '#'
546 title = _('Changeset not found')
546 title = _('Changeset not found')
547 if parse_cs:
547 if parse_cs:
548 return link_to(lbl, _url, title=title, class_='tooltip')
548 return link_to(lbl, _url, title=title, class_='tooltip')
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
550 class_='lazy-cs' if lazy_cs else '')
550 class_='lazy-cs' if lazy_cs else '')
551
551
552 revs = []
552 revs = []
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
554 repo = None
554 repo = None
555 for rev in revs_ids[:revs_top_limit]:
555 for rev in revs_ids[:revs_top_limit]:
556 _op = _name = None
556 _op = _name = None
557 if len(rev.split('=>')) == 2:
557 if len(rev.split('=>')) == 2:
558 _op, _name = rev.split('=>')
558 _op, _name = rev.split('=>')
559
559
560 # we want parsed changesets, or new log store format is bad
560 # we want parsed changesets, or new log store format is bad
561 if parse_cs:
561 if parse_cs:
562 try:
562 try:
563 if repo is None:
563 if repo is None:
564 repo = user_log.repository.scm_instance
564 repo = user_log.repository.scm_instance
565 _rev = repo.get_changeset(rev)
565 _rev = repo.get_changeset(rev)
566 revs.append(_rev)
566 revs.append(_rev)
567 except ChangesetDoesNotExistError:
567 except ChangesetDoesNotExistError:
568 log.error('cannot find revision %s in this repo' % rev)
568 log.error('cannot find revision %s in this repo' % rev)
569 revs.append(rev)
569 revs.append(rev)
570 continue
570 continue
571 else:
571 else:
572 _rev = AttributeDict({
572 _rev = AttributeDict({
573 'short_id': rev[:12],
573 'short_id': rev[:12],
574 'raw_id': rev,
574 'raw_id': rev,
575 'message': '',
575 'message': '',
576 'op': _op,
576 'op': _op,
577 'ref_name': _name
577 'ref_name': _name
578 })
578 })
579 revs.append(_rev)
579 revs.append(_rev)
580 cs_links = []
580 cs_links = []
581 cs_links.append(" " + ', '.join(
581 cs_links.append(" " + ', '.join(
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
583 )
583 )
584 )
584 )
585
585
586 compare_view = (
586 compare_view = (
587 ' <div class="compare_view tooltip" title="%s">'
587 ' <div class="compare_view tooltip" title="%s">'
588 '<a href="%s">%s</a> </div>' % (
588 '<a href="%s">%s</a> </div>' % (
589 _('Show all combined changesets %s->%s') % (
589 _('Show all combined changesets %s->%s') % (
590 revs_ids[0][:12], revs_ids[-1][:12]
590 revs_ids[0][:12], revs_ids[-1][:12]
591 ),
591 ),
592 url('changeset_home', repo_name=repo_name,
592 url('changeset_home', repo_name=repo_name,
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
594 ),
594 ),
595 _('compare view')
595 _('compare view')
596 )
596 )
597 )
597 )
598
598
599 # if we have exactly one more than normally displayed
599 # if we have exactly one more than normally displayed
600 # just display it, takes less space than displaying
600 # just display it, takes less space than displaying
601 # "and 1 more revisions"
601 # "and 1 more revisions"
602 if len(revs_ids) == revs_limit + 1:
602 if len(revs_ids) == revs_limit + 1:
603 rev = revs[revs_limit]
603 rev = revs[revs_limit]
604 cs_links.append(", " + lnk(rev, repo_name))
604 cs_links.append(", " + lnk(rev, repo_name))
605
605
606 # hidden-by-default ones
606 # hidden-by-default ones
607 if len(revs_ids) > revs_limit + 1:
607 if len(revs_ids) > revs_limit + 1:
608 uniq_id = revs_ids[0]
608 uniq_id = revs_ids[0]
609 html_tmpl = (
609 html_tmpl = (
610 '<span> %s <a class="show_more" id="_%s" '
610 '<span> %s <a class="show_more" id="_%s" '
611 'href="#more">%s</a> %s</span>'
611 'href="#more">%s</a> %s</span>'
612 )
612 )
613 if not feed:
613 if not feed:
614 cs_links.append(html_tmpl % (
614 cs_links.append(html_tmpl % (
615 _('and'),
615 _('and'),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
617 _('revisions')
617 _('revisions')
618 )
618 )
619 )
619 )
620
620
621 if not feed:
621 if not feed:
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
623 else:
623 else:
624 html_tmpl = '<span id="%s"> %s </span>'
624 html_tmpl = '<span id="%s"> %s </span>'
625
625
626 morelinks = ', '.join(
626 morelinks = ', '.join(
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
628 )
628 )
629
629
630 if len(revs_ids) > revs_top_limit:
630 if len(revs_ids) > revs_top_limit:
631 morelinks += ', ...'
631 morelinks += ', ...'
632
632
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
634 if len(revs) > 1:
634 if len(revs) > 1:
635 cs_links.append(compare_view)
635 cs_links.append(compare_view)
636 return ''.join(cs_links)
636 return ''.join(cs_links)
637
637
638 def get_fork_name():
638 def get_fork_name():
639 repo_name = action_params
639 repo_name = action_params
640 _url = url('summary_home', repo_name=repo_name)
640 _url = url('summary_home', repo_name=repo_name)
641 return _('fork name %s') % link_to(action_params, _url)
641 return _('fork name %s') % link_to(action_params, _url)
642
642
643 def get_user_name():
643 def get_user_name():
644 user_name = action_params
644 user_name = action_params
645 return user_name
645 return user_name
646
646
647 def get_users_group():
647 def get_users_group():
648 group_name = action_params
648 group_name = action_params
649 return group_name
649 return group_name
650
650
651 def get_pull_request():
651 def get_pull_request():
652 pull_request_id = action_params
652 pull_request_id = action_params
653 deleted = user_log.repository is None
653 deleted = user_log.repository is None
654 if deleted:
654 if deleted:
655 repo_name = user_log.repository_name
655 repo_name = user_log.repository_name
656 else:
656 else:
657 repo_name = user_log.repository.repo_name
657 repo_name = user_log.repository.repo_name
658 return link_to(_('Pull request #%s') % pull_request_id,
658 return link_to(_('Pull request #%s') % pull_request_id,
659 url('pullrequest_show', repo_name=repo_name,
659 url('pullrequest_show', repo_name=repo_name,
660 pull_request_id=pull_request_id))
660 pull_request_id=pull_request_id))
661
661
662 # action : translated str, callback(extractor), icon
662 # action : translated str, callback(extractor), icon
663 action_map = {
663 action_map = {
664 'user_deleted_repo': (_('[deleted] repository'),
664 'user_deleted_repo': (_('[deleted] repository'),
665 None, 'database_delete.png'),
665 None, 'database_delete.png'),
666 'user_created_repo': (_('[created] repository'),
666 'user_created_repo': (_('[created] repository'),
667 None, 'database_add.png'),
667 None, 'database_add.png'),
668 'user_created_fork': (_('[created] repository as fork'),
668 'user_created_fork': (_('[created] repository as fork'),
669 None, 'arrow_divide.png'),
669 None, 'arrow_divide.png'),
670 'user_forked_repo': (_('[forked] repository'),
670 'user_forked_repo': (_('[forked] repository'),
671 get_fork_name, 'arrow_divide.png'),
671 get_fork_name, 'arrow_divide.png'),
672 'user_updated_repo': (_('[updated] repository'),
672 'user_updated_repo': (_('[updated] repository'),
673 None, 'database_edit.png'),
673 None, 'database_edit.png'),
674 'admin_deleted_repo': (_('[delete] repository'),
674 'admin_deleted_repo': (_('[delete] repository'),
675 None, 'database_delete.png'),
675 None, 'database_delete.png'),
676 'admin_created_repo': (_('[created] repository'),
676 'admin_created_repo': (_('[created] repository'),
677 None, 'database_add.png'),
677 None, 'database_add.png'),
678 'admin_forked_repo': (_('[forked] repository'),
678 'admin_forked_repo': (_('[forked] repository'),
679 None, 'arrow_divide.png'),
679 None, 'arrow_divide.png'),
680 'admin_updated_repo': (_('[updated] repository'),
680 'admin_updated_repo': (_('[updated] repository'),
681 None, 'database_edit.png'),
681 None, 'database_edit.png'),
682 'admin_created_user': (_('[created] user'),
682 'admin_created_user': (_('[created] user'),
683 get_user_name, 'user_add.png'),
683 get_user_name, 'user_add.png'),
684 'admin_updated_user': (_('[updated] user'),
684 'admin_updated_user': (_('[updated] user'),
685 get_user_name, 'user_edit.png'),
685 get_user_name, 'user_edit.png'),
686 'admin_created_users_group': (_('[created] users group'),
686 'admin_created_users_group': (_('[created] users group'),
687 get_users_group, 'group_add.png'),
687 get_users_group, 'group_add.png'),
688 'admin_updated_users_group': (_('[updated] users group'),
688 'admin_updated_users_group': (_('[updated] users group'),
689 get_users_group, 'group_edit.png'),
689 get_users_group, 'group_edit.png'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
691 get_cs_links, 'comment_add.png'),
691 get_cs_links, 'comment_add.png'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
693 get_pull_request, 'comment_add.png'),
693 get_pull_request, 'comment_add.png'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
695 get_pull_request, 'tick.png'),
695 get_pull_request, 'tick.png'),
696 'push': (_('[pushed] into'),
696 'push': (_('[pushed] into'),
697 get_cs_links, 'script_add.png'),
697 get_cs_links, 'script_add.png'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
699 get_cs_links, 'script_edit.png'),
699 get_cs_links, 'script_edit.png'),
700 'push_remote': (_('[pulled from remote] into repository'),
700 'push_remote': (_('[pulled from remote] into repository'),
701 get_cs_links, 'connect.png'),
701 get_cs_links, 'connect.png'),
702 'pull': (_('[pulled] from'),
702 'pull': (_('[pulled] from'),
703 None, 'down_16.png'),
703 None, 'down_16.png'),
704 'started_following_repo': (_('[started following] repository'),
704 'started_following_repo': (_('[started following] repository'),
705 None, 'heart_add.png'),
705 None, 'heart_add.png'),
706 'stopped_following_repo': (_('[stopped following] repository'),
706 'stopped_following_repo': (_('[stopped following] repository'),
707 None, 'heart_delete.png'),
707 None, 'heart_delete.png'),
708 }
708 }
709
709
710 action_str = action_map.get(action, action)
710 action_str = action_map.get(action, action)
711 if feed:
711 if feed:
712 action = action_str[0].replace('[', '').replace(']', '')
712 action = action_str[0].replace('[', '').replace(']', '')
713 else:
713 else:
714 action = action_str[0]\
714 action = action_str[0]\
715 .replace('[', '<span class="journal_highlight">')\
715 .replace('[', '<span class="journal_highlight">')\
716 .replace(']', '</span>')
716 .replace(']', '</span>')
717
717
718 action_params_func = lambda: ""
718 action_params_func = lambda: ""
719
719
720 if callable(action_str[1]):
720 if callable(action_str[1]):
721 action_params_func = action_str[1]
721 action_params_func = action_str[1]
722
722
723 def action_parser_icon():
723 def action_parser_icon():
724 action = user_log.action
724 action = user_log.action
725 action_params = None
725 action_params = None
726 x = action.split(':')
726 x = action.split(':')
727
727
728 if len(x) > 1:
728 if len(x) > 1:
729 action, action_params = x
729 action, action_params = x
730
730
731 tmpl = """<img src="%s%s" alt="%s"/>"""
731 tmpl = """<img src="%s%s" alt="%s"/>"""
732 ico = action_map.get(action, ['', '', ''])[2]
732 ico = action_map.get(action, ['', '', ''])[2]
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
734
734
735 # returned callbacks we need to call to get
735 # returned callbacks we need to call to get
736 return [lambda: literal(action), action_params_func, action_parser_icon]
736 return [lambda: literal(action), action_params_func, action_parser_icon]
737
737
738
738
739
739
740 #==============================================================================
740 #==============================================================================
741 # PERMS
741 # PERMS
742 #==============================================================================
742 #==============================================================================
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
744 HasRepoPermissionAny, HasRepoPermissionAll
744 HasRepoPermissionAny, HasRepoPermissionAll
745
745
746
746
747 #==============================================================================
747 #==============================================================================
748 # GRAVATAR URL
748 # GRAVATAR URL
749 #==============================================================================
749 #==============================================================================
750
750
751 def gravatar_url(email_address, size=30):
751 def gravatar_url(email_address, size=30):
752 from pylons import url # doh, we need to re-import url to mock it later
752 from pylons import url # doh, we need to re-import url to mock it later
753
753
754 if (not str2bool(config['app_conf'].get('use_gravatar')) or
754 if (not str2bool(config['app_conf'].get('use_gravatar')) or
755 not email_address or email_address == 'anonymous@rhodecode.org'):
755 not email_address or email_address == 'anonymous@rhodecode.org'):
756 f = lambda a, l: min(l, key=lambda x: abs(x - a))
756 f = lambda a, l: min(l, key=lambda x: abs(x - a))
757 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
757 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
758
758
759 if(str2bool(config['app_conf'].get('use_gravatar')) and
759 if(str2bool(config['app_conf'].get('use_gravatar')) and
760 config['app_conf'].get('alternative_gravatar_url')):
760 config['app_conf'].get('alternative_gravatar_url')):
761 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
761 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
762 parsed_url = urlparse.urlparse(url.current(qualified=True))
762 parsed_url = urlparse.urlparse(url.current(qualified=True))
763 tmpl = tmpl.replace('{email}', email_address)\
763 tmpl = tmpl.replace('{email}', email_address)\
764 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
764 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
765 .replace('{netloc}', parsed_url.netloc)\
765 .replace('{netloc}', parsed_url.netloc)\
766 .replace('{scheme}', parsed_url.scheme)\
766 .replace('{scheme}', parsed_url.scheme)\
767 .replace('{size}', str(size))
767 .replace('{size}', str(size))
768 return tmpl
768 return tmpl
769
769
770 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
770 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
771 default = 'identicon'
771 default = 'identicon'
772 baseurl_nossl = "http://www.gravatar.com/avatar/"
772 baseurl_nossl = "http://www.gravatar.com/avatar/"
773 baseurl_ssl = "https://secure.gravatar.com/avatar/"
773 baseurl_ssl = "https://secure.gravatar.com/avatar/"
774 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
774 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
775
775
776 if isinstance(email_address, unicode):
776 if isinstance(email_address, unicode):
777 #hashlib crashes on unicode items
777 #hashlib crashes on unicode items
778 email_address = safe_str(email_address)
778 email_address = safe_str(email_address)
779 # construct the url
779 # construct the url
780 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
780 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
781 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
781 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
782
782
783 return gravatar_url
783 return gravatar_url
784
784
785
785
786 #==============================================================================
786 #==============================================================================
787 # REPO PAGER, PAGER FOR REPOSITORY
787 # REPO PAGER, PAGER FOR REPOSITORY
788 #==============================================================================
788 #==============================================================================
789 class RepoPage(Page):
789 class RepoPage(Page):
790
790
791 def __init__(self, collection, page=1, items_per_page=20,
791 def __init__(self, collection, page=1, items_per_page=20,
792 item_count=None, url=None, **kwargs):
792 item_count=None, url=None, **kwargs):
793
793
794 """Create a "RepoPage" instance. special pager for paging
794 """Create a "RepoPage" instance. special pager for paging
795 repository
795 repository
796 """
796 """
797 self._url_generator = url
797 self._url_generator = url
798
798
799 # Safe the kwargs class-wide so they can be used in the pager() method
799 # Safe the kwargs class-wide so they can be used in the pager() method
800 self.kwargs = kwargs
800 self.kwargs = kwargs
801
801
802 # Save a reference to the collection
802 # Save a reference to the collection
803 self.original_collection = collection
803 self.original_collection = collection
804
804
805 self.collection = collection
805 self.collection = collection
806
806
807 # The self.page is the number of the current page.
807 # The self.page is the number of the current page.
808 # The first page has the number 1!
808 # The first page has the number 1!
809 try:
809 try:
810 self.page = int(page) # make it int() if we get it as a string
810 self.page = int(page) # make it int() if we get it as a string
811 except (ValueError, TypeError):
811 except (ValueError, TypeError):
812 self.page = 1
812 self.page = 1
813
813
814 self.items_per_page = items_per_page
814 self.items_per_page = items_per_page
815
815
816 # Unless the user tells us how many items the collections has
816 # Unless the user tells us how many items the collections has
817 # we calculate that ourselves.
817 # we calculate that ourselves.
818 if item_count is not None:
818 if item_count is not None:
819 self.item_count = item_count
819 self.item_count = item_count
820 else:
820 else:
821 self.item_count = len(self.collection)
821 self.item_count = len(self.collection)
822
822
823 # Compute the number of the first and last available page
823 # Compute the number of the first and last available page
824 if self.item_count > 0:
824 if self.item_count > 0:
825 self.first_page = 1
825 self.first_page = 1
826 self.page_count = int(math.ceil(float(self.item_count) /
826 self.page_count = int(math.ceil(float(self.item_count) /
827 self.items_per_page))
827 self.items_per_page))
828 self.last_page = self.first_page + self.page_count - 1
828 self.last_page = self.first_page + self.page_count - 1
829
829
830 # Make sure that the requested page number is the range of
830 # Make sure that the requested page number is the range of
831 # valid pages
831 # valid pages
832 if self.page > self.last_page:
832 if self.page > self.last_page:
833 self.page = self.last_page
833 self.page = self.last_page
834 elif self.page < self.first_page:
834 elif self.page < self.first_page:
835 self.page = self.first_page
835 self.page = self.first_page
836
836
837 # Note: the number of items on this page can be less than
837 # Note: the number of items on this page can be less than
838 # items_per_page if the last page is not full
838 # items_per_page if the last page is not full
839 self.first_item = max(0, (self.item_count) - (self.page *
839 self.first_item = max(0, (self.item_count) - (self.page *
840 items_per_page))
840 items_per_page))
841 self.last_item = ((self.item_count - 1) - items_per_page *
841 self.last_item = ((self.item_count - 1) - items_per_page *
842 (self.page - 1))
842 (self.page - 1))
843
843
844 self.items = list(self.collection[self.first_item:self.last_item + 1])
844 self.items = list(self.collection[self.first_item:self.last_item + 1])
845
845
846 # Links to previous and next page
846 # Links to previous and next page
847 if self.page > self.first_page:
847 if self.page > self.first_page:
848 self.previous_page = self.page - 1
848 self.previous_page = self.page - 1
849 else:
849 else:
850 self.previous_page = None
850 self.previous_page = None
851
851
852 if self.page < self.last_page:
852 if self.page < self.last_page:
853 self.next_page = self.page + 1
853 self.next_page = self.page + 1
854 else:
854 else:
855 self.next_page = None
855 self.next_page = None
856
856
857 # No items available
857 # No items available
858 else:
858 else:
859 self.first_page = None
859 self.first_page = None
860 self.page_count = 0
860 self.page_count = 0
861 self.last_page = None
861 self.last_page = None
862 self.first_item = None
862 self.first_item = None
863 self.last_item = None
863 self.last_item = None
864 self.previous_page = None
864 self.previous_page = None
865 self.next_page = None
865 self.next_page = None
866 self.items = []
866 self.items = []
867
867
868 # This is a subclass of the 'list' type. Initialise the list now.
868 # This is a subclass of the 'list' type. Initialise the list now.
869 list.__init__(self, reversed(self.items))
869 list.__init__(self, reversed(self.items))
870
870
871
871
872 def changed_tooltip(nodes):
872 def changed_tooltip(nodes):
873 """
873 """
874 Generates a html string for changed nodes in changeset page.
874 Generates a html string for changed nodes in changeset page.
875 It limits the output to 30 entries
875 It limits the output to 30 entries
876
876
877 :param nodes: LazyNodesGenerator
877 :param nodes: LazyNodesGenerator
878 """
878 """
879 if nodes:
879 if nodes:
880 pref = ': <br/> '
880 pref = ': <br/> '
881 suf = ''
881 suf = ''
882 if len(nodes) > 30:
882 if len(nodes) > 30:
883 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
883 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
884 return literal(pref + '<br/> '.join([safe_unicode(x.path)
884 return literal(pref + '<br/> '.join([safe_unicode(x.path)
885 for x in nodes[:30]]) + suf)
885 for x in nodes[:30]]) + suf)
886 else:
886 else:
887 return ': ' + _('No Files')
887 return ': ' + _('No Files')
888
888
889
889
890 def repo_link(groups_and_repos, last_url=None):
890 def repo_link(groups_and_repos, last_url=None):
891 """
891 """
892 Makes a breadcrumbs link to repo within a group
892 Makes a breadcrumbs link to repo within a group
893 joins &raquo; on each group to create a fancy link
893 joins &raquo; on each group to create a fancy link
894
894
895 ex::
895 ex::
896 group >> subgroup >> repo
896 group >> subgroup >> repo
897
897
898 :param groups_and_repos:
898 :param groups_and_repos:
899 :param last_url:
899 :param last_url:
900 """
900 """
901 groups, repo_name = groups_and_repos
901 groups, repo_name = groups_and_repos
902 last_link = link_to(repo_name, last_url) if last_url else repo_name
902 last_link = link_to(repo_name, last_url) if last_url else repo_name
903
903
904 if not groups:
904 if not groups:
905 if last_url:
905 if last_url:
906 return last_link
906 return last_link
907 return repo_name
907 return repo_name
908 else:
908 else:
909 def make_link(group):
909 def make_link(group):
910 return link_to(group.name,
910 return link_to(group.name,
911 url('repos_group_home', group_name=group.group_name))
911 url('repos_group_home', group_name=group.group_name))
912 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
912 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
913
913
914
914
915 def fancy_file_stats(stats):
915 def fancy_file_stats(stats):
916 """
916 """
917 Displays a fancy two colored bar for number of added/deleted
917 Displays a fancy two colored bar for number of added/deleted
918 lines of code on file
918 lines of code on file
919
919
920 :param stats: two element list of added/deleted lines of code
920 :param stats: two element list of added/deleted lines of code
921 """
921 """
922 def cgen(l_type, a_v, d_v):
922 def cgen(l_type, a_v, d_v):
923 mapping = {'tr': 'top-right-rounded-corner-mid',
923 mapping = {'tr': 'top-right-rounded-corner-mid',
924 'tl': 'top-left-rounded-corner-mid',
924 'tl': 'top-left-rounded-corner-mid',
925 'br': 'bottom-right-rounded-corner-mid',
925 'br': 'bottom-right-rounded-corner-mid',
926 'bl': 'bottom-left-rounded-corner-mid'}
926 'bl': 'bottom-left-rounded-corner-mid'}
927 map_getter = lambda x: mapping[x]
927 map_getter = lambda x: mapping[x]
928
928
929 if l_type == 'a' and d_v:
929 if l_type == 'a' and d_v:
930 #case when added and deleted are present
930 #case when added and deleted are present
931 return ' '.join(map(map_getter, ['tl', 'bl']))
931 return ' '.join(map(map_getter, ['tl', 'bl']))
932
932
933 if l_type == 'a' and not d_v:
933 if l_type == 'a' and not d_v:
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
935
935
936 if l_type == 'd' and a_v:
936 if l_type == 'd' and a_v:
937 return ' '.join(map(map_getter, ['tr', 'br']))
937 return ' '.join(map(map_getter, ['tr', 'br']))
938
938
939 if l_type == 'd' and not a_v:
939 if l_type == 'd' and not a_v:
940 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
940 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
941
941
942 a, d = stats[0], stats[1]
942 a, d = stats[0], stats[1]
943 width = 100
943 width = 100
944
944
945 if a == 'b':
945 if a == 'b':
946 #binary mode
946 #binary mode
947 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
947 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
948 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
948 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
949 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
949 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
950
950
951 t = stats[0] + stats[1]
951 t = stats[0] + stats[1]
952 unit = float(width) / (t or 1)
952 unit = float(width) / (t or 1)
953
953
954 # needs > 9% of width to be visible or 0 to be hidden
954 # needs > 9% of width to be visible or 0 to be hidden
955 a_p = max(9, unit * a) if a > 0 else 0
955 a_p = max(9, unit * a) if a > 0 else 0
956 d_p = max(9, unit * d) if d > 0 else 0
956 d_p = max(9, unit * d) if d > 0 else 0
957 p_sum = a_p + d_p
957 p_sum = a_p + d_p
958
958
959 if p_sum > width:
959 if p_sum > width:
960 #adjust the percentage to be == 100% since we adjusted to 9
960 #adjust the percentage to be == 100% since we adjusted to 9
961 if a_p > d_p:
961 if a_p > d_p:
962 a_p = a_p - (p_sum - width)
962 a_p = a_p - (p_sum - width)
963 else:
963 else:
964 d_p = d_p - (p_sum - width)
964 d_p = d_p - (p_sum - width)
965
965
966 a_v = a if a > 0 else ''
966 a_v = a if a > 0 else ''
967 d_v = d if d > 0 else ''
967 d_v = d if d > 0 else ''
968
968
969 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
969 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
970 cgen('a', a_v, d_v), a_p, a_v
970 cgen('a', a_v, d_v), a_p, a_v
971 )
971 )
972 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
972 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
973 cgen('d', a_v, d_v), d_p, d_v
973 cgen('d', a_v, d_v), d_p, d_v
974 )
974 )
975 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
975 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
976
976
977
977
978 def urlify_text(text_):
978 def urlify_text(text_):
979
979
980 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
980 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
981 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
981 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
982
982
983 def url_func(match_obj):
983 def url_func(match_obj):
984 url_full = match_obj.groups()[0]
984 url_full = match_obj.groups()[0]
985 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
985 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
986
986
987 return literal(url_pat.sub(url_func, text_))
987 return literal(url_pat.sub(url_func, text_))
988
988
989
989
990 def urlify_changesets(text_, repository):
990 def urlify_changesets(text_, repository):
991 """
991 """
992 Extract revision ids from changeset and make link from them
992 Extract revision ids from changeset and make link from them
993
993
994 :param text_:
994 :param text_:
995 :param repository:
995 :param repository:
996 """
996 """
997
997
998 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
998 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
999
999
1000 def url_func(match_obj):
1000 def url_func(match_obj):
1001 rev = match_obj.groups()[0]
1001 rev = match_obj.groups()[0]
1002 pref = ''
1002 pref = ''
1003 if match_obj.group().startswith(' '):
1003 if match_obj.group().startswith(' '):
1004 pref = ' '
1004 pref = ' '
1005 tmpl = (
1005 tmpl = (
1006 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1006 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1007 '%(rev)s'
1007 '%(rev)s'
1008 '</a>'
1008 '</a>'
1009 )
1009 )
1010 return tmpl % {
1010 return tmpl % {
1011 'pref': pref,
1011 'pref': pref,
1012 'cls': 'revision-link',
1012 'cls': 'revision-link',
1013 'url': url('changeset_home', repo_name=repository, revision=rev),
1013 'url': url('changeset_home', repo_name=repository, revision=rev),
1014 'rev': rev,
1014 'rev': rev,
1015 }
1015 }
1016
1016
1017 newtext = URL_PAT.sub(url_func, text_)
1017 newtext = URL_PAT.sub(url_func, text_)
1018
1018
1019 return newtext
1019 return newtext
1020
1020
1021
1021
1022 def urlify_commit(text_, repository=None, link_=None):
1022 def urlify_commit(text_, repository=None, link_=None):
1023 """
1023 """
1024 Parses given text message and makes proper links.
1024 Parses given text message and makes proper links.
1025 issues are linked to given issue-server, and rest is a changeset link
1025 issues are linked to given issue-server, and rest is a changeset link
1026 if link_ is given, in other case it's a plain text
1026 if link_ is given, in other case it's a plain text
1027
1027
1028 :param text_:
1028 :param text_:
1029 :param repository:
1029 :param repository:
1030 :param link_: changeset link
1030 :param link_: changeset link
1031 """
1031 """
1032 import traceback
1032 import traceback
1033
1033
1034 def escaper(string):
1034 def escaper(string):
1035 return string.replace('<', '&lt;').replace('>', '&gt;')
1035 return string.replace('<', '&lt;').replace('>', '&gt;')
1036
1036
1037 def linkify_others(t, l):
1037 def linkify_others(t, l):
1038 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1038 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1039 links = []
1039 links = []
1040 for e in urls.split(t):
1040 for e in urls.split(t):
1041 if not urls.match(e):
1041 if not urls.match(e):
1042 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1042 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1043 else:
1043 else:
1044 links.append(e)
1044 links.append(e)
1045
1045
1046 return ''.join(links)
1046 return ''.join(links)
1047
1047
1048 # urlify changesets - extrac revisions and make link out of them
1048 # urlify changesets - extrac revisions and make link out of them
1049 newtext = urlify_changesets(escaper(text_), repository)
1049 newtext = urlify_changesets(escaper(text_), repository)
1050
1050
1051 try:
1051 try:
1052 conf = config['app_conf']
1052 conf = config['app_conf']
1053
1053
1054 # allow multiple issue servers to be used
1054 # allow multiple issue servers to be used
1055 valid_indices = [
1055 valid_indices = [
1056 x.group(1)
1056 x.group(1)
1057 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1057 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1058 if x and 'issue_server_link%s' % x.group(1) in conf
1058 if x and 'issue_server_link%s' % x.group(1) in conf
1059 and 'issue_prefix%s' % x.group(1) in conf
1059 and 'issue_prefix%s' % x.group(1) in conf
1060 ]
1060 ]
1061
1061
1062 log.debug('found issue server suffixes `%s` during valuation of: %s'
1062 log.debug('found issue server suffixes `%s` during valuation of: %s'
1063 % (','.join(valid_indices), newtext))
1063 % (','.join(valid_indices), newtext))
1064
1064
1065 for pattern_index in valid_indices:
1065 for pattern_index in valid_indices:
1066 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1066 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1067 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1067 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1068 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1068 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1069
1069
1070 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1070 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1071 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1071 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1072 ISSUE_PREFIX))
1072 ISSUE_PREFIX))
1073
1073
1074 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1074 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1075
1075
1076 def url_func(match_obj):
1076 def url_func(match_obj):
1077 pref = ''
1077 pref = ''
1078 if match_obj.group().startswith(' '):
1078 if match_obj.group().startswith(' '):
1079 pref = ' '
1079 pref = ' '
1080
1080
1081 issue_id = ''.join(match_obj.groups())
1081 issue_id = ''.join(match_obj.groups())
1082 tmpl = (
1082 tmpl = (
1083 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1083 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1084 '%(issue-prefix)s%(id-repr)s'
1084 '%(issue-prefix)s%(id-repr)s'
1085 '</a>'
1085 '</a>'
1086 )
1086 )
1087 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1087 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1088 if repository:
1088 if repository:
1089 url = url.replace('{repo}', repository)
1089 url = url.replace('{repo}', repository)
1090 repo_name = repository.split(URL_SEP)[-1]
1090 repo_name = repository.split(URL_SEP)[-1]
1091 url = url.replace('{repo_name}', repo_name)
1091 url = url.replace('{repo_name}', repo_name)
1092
1092
1093 return tmpl % {
1093 return tmpl % {
1094 'pref': pref,
1094 'pref': pref,
1095 'cls': 'issue-tracker-link',
1095 'cls': 'issue-tracker-link',
1096 'url': url,
1096 'url': url,
1097 'id-repr': issue_id,
1097 'id-repr': issue_id,
1098 'issue-prefix': ISSUE_PREFIX,
1098 'issue-prefix': ISSUE_PREFIX,
1099 'serv': ISSUE_SERVER_LNK,
1099 'serv': ISSUE_SERVER_LNK,
1100 }
1100 }
1101 newtext = URL_PAT.sub(url_func, newtext)
1101 newtext = URL_PAT.sub(url_func, newtext)
1102 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1102 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1103
1103
1104 # if we actually did something above
1104 # if we actually did something above
1105 if link_:
1105 if link_:
1106 # wrap not links into final link => link_
1106 # wrap not links into final link => link_
1107 newtext = linkify_others(newtext, link_)
1107 newtext = linkify_others(newtext, link_)
1108 except:
1108 except:
1109 log.error(traceback.format_exc())
1109 log.error(traceback.format_exc())
1110 pass
1110 pass
1111
1111
1112 return literal(newtext)
1112 return literal(newtext)
1113
1113
1114
1114
1115 def rst(source):
1115 def rst(source):
1116 return literal('<div class="rst-block">%s</div>' %
1116 return literal('<div class="rst-block">%s</div>' %
1117 MarkupRenderer.rst(source))
1117 MarkupRenderer.rst(source))
1118
1118
1119
1119
1120 def rst_w_mentions(source):
1120 def rst_w_mentions(source):
1121 """
1121 """
1122 Wrapped rst renderer with @mention highlighting
1122 Wrapped rst renderer with @mention highlighting
1123
1123
1124 :param source:
1124 :param source:
1125 """
1125 """
1126 return literal('<div class="rst-block">%s</div>' %
1126 return literal('<div class="rst-block">%s</div>' %
1127 MarkupRenderer.rst_with_mentions(source))
1127 MarkupRenderer.rst_with_mentions(source))
1128
1128
1129
1129
1130 def changeset_status(repo, revision):
1130 def changeset_status(repo, revision):
1131 return ChangesetStatusModel().get_status(repo, revision)
1131 return ChangesetStatusModel().get_status(repo, revision)
1132
1132
1133
1133
1134 def changeset_status_lbl(changeset_status):
1134 def changeset_status_lbl(changeset_status):
1135 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1135 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1136
1136
1137
1137
1138 def get_permission_name(key):
1138 def get_permission_name(key):
1139 return dict(Permission.PERMS).get(key)
1139 return dict(Permission.PERMS).get(key)
1140
1140
1141
1141
1142 def journal_filter_help():
1142 def journal_filter_help():
1143 return _(textwrap.dedent('''
1143 return _(textwrap.dedent('''
1144 Example filter terms:
1144 Example filter terms:
1145 repository:vcs
1145 repository:vcs
1146 username:marcin
1146 username:marcin
1147 action:*push*
1147 action:*push*
1148 ip:127.0.0.1
1148 ip:127.0.0.1
1149 date:20120101
1149 date:20120101
1150 date:[20120101100000 TO 20120102]
1150 date:[20120101100000 TO 20120102]
1151
1151
1152 Generate wildcards using '*' character:
1152 Generate wildcards using '*' character:
1153 "repositroy:vcs*" - search everything starting with 'vcs'
1153 "repositroy:vcs*" - search everything starting with 'vcs'
1154 "repository:*vcs*" - search for repository containing 'vcs'
1154 "repository:*vcs*" - search for repository containing 'vcs'
1155
1155
1156 Optional AND / OR operators in queries
1156 Optional AND / OR operators in queries
1157 "repository:vcs OR repository:test"
1157 "repository:vcs OR repository:test"
1158 "username:test AND repository:test*"
1158 "username:test AND repository:test*"
1159 '''))
1159 '''))
1160
1160
1161
1161
1162 def not_mapped_error(repo_name):
1162 def not_mapped_error(repo_name):
1163 flash(_('%s repository is not mapped to db perhaps'
1163 flash(_('%s repository is not mapped to db perhaps'
1164 ' it was created or renamed from the filesystem'
1164 ' it was created or renamed from the filesystem'
1165 ' please run the application again'
1165 ' please run the application again'
1166 ' in order to rescan repositories') % repo_name, category='error')
1166 ' in order to rescan repositories') % repo_name, category='error')
1167
1167
1168
1168
1169 def ip_range(ip_addr):
1169 def ip_range(ip_addr):
1170 from rhodecode.model.db import UserIpMap
1170 from rhodecode.model.db import UserIpMap
1171 s, e = UserIpMap._get_ip_range(ip_addr)
1171 s, e = UserIpMap._get_ip_range(ip_addr)
1172 return '%s - %s' % (s, e)
1172 return '%s - %s' % (s, e)
1173
@@ -1,754 +1,754 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
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
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info('Logging action %s on %s by %s' %
165 log.info('Logging action %s on %s by %s' %
166 (action, safe_unicode(repo), user_obj))
166 (action, safe_unicode(repo), user_obj))
167 if commit:
167 if commit:
168 sa.commit()
168 sa.commit()
169 except:
169 except:
170 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
171 raise
171 raise
172
172
173
173
174 def get_repos(path, recursive=False):
174 def get_repos(path, recursive=False):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184
184
185 def _get_repos(p):
185 def _get_repos(p):
186 if not os.access(p, os.W_OK):
186 if not os.access(p, os.W_OK):
187 return
187 return
188 for dirpath in os.listdir(p):
188 for dirpath in os.listdir(p):
189 if os.path.isfile(os.path.join(p, dirpath)):
189 if os.path.isfile(os.path.join(p, dirpath)):
190 continue
190 continue
191 cur_path = os.path.join(p, dirpath)
191 cur_path = os.path.join(p, dirpath)
192 try:
192 try:
193 scm_info = get_scm(cur_path)
193 scm_info = get_scm(cur_path)
194 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
195 except VCSError:
195 except VCSError:
196 if not recursive:
196 if not recursive:
197 continue
197 continue
198 #check if this dir containts other repos for recursive scan
198 #check if this dir containts other repos for recursive scan
199 rec_path = os.path.join(p, dirpath)
199 rec_path = os.path.join(p, dirpath)
200 if os.path.isdir(rec_path):
200 if os.path.isdir(rec_path):
201 for inner_scm in _get_repos(rec_path):
201 for inner_scm in _get_repos(rec_path):
202 yield inner_scm
202 yield inner_scm
203
203
204 return _get_repos(path)
204 return _get_repos(path)
205
205
206
206
207 def is_valid_repo(repo_name, base_path, scm=None):
207 def is_valid_repo(repo_name, base_path, scm=None):
208 """
208 """
209 Returns True if given path is a valid repository False otherwise.
209 Returns True if given path is a valid repository False otherwise.
210 If scm param is given also compare if given scm is the same as expected
210 If scm param is given also compare if given scm is the same as expected
211 from scm parameter
211 from scm parameter
212
212
213 :param repo_name:
213 :param repo_name:
214 :param base_path:
214 :param base_path:
215 :param scm:
215 :param scm:
216
216
217 :return True: if given path is a valid repository
217 :return True: if given path is a valid repository
218 """
218 """
219 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
220
220
221 try:
221 try:
222 scm_ = get_scm(full_path)
222 scm_ = get_scm(full_path)
223 if scm:
223 if scm:
224 return scm_[0] == scm
224 return scm_[0] == scm
225 return True
225 return True
226 except VCSError:
226 except VCSError:
227 return False
227 return False
228
228
229
229
230 def is_valid_repos_group(repos_group_name, base_path):
230 def is_valid_repos_group(repos_group_name, base_path):
231 """
231 """
232 Returns True if given path is a repos group False otherwise
232 Returns True if given path is a repos group False otherwise
233
233
234 :param repo_name:
234 :param repo_name:
235 :param base_path:
235 :param base_path:
236 """
236 """
237 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
238
238
239 # check if it's not a repo
239 # check if it's not a repo
240 if is_valid_repo(repos_group_name, base_path):
240 if is_valid_repo(repos_group_name, base_path):
241 return False
241 return False
242
242
243 try:
243 try:
244 # we need to check bare git repos at higher level
244 # we need to check bare git repos at higher level
245 # since we might match branches/hooks/info/objects or possible
245 # since we might match branches/hooks/info/objects or possible
246 # other things inside bare git repo
246 # other things inside bare git repo
247 get_scm(os.path.dirname(full_path))
247 get_scm(os.path.dirname(full_path))
248 return False
248 return False
249 except VCSError:
249 except VCSError:
250 pass
250 pass
251
251
252 # check if it's a valid path
252 # check if it's a valid path
253 if os.path.isdir(full_path):
253 if os.path.isdir(full_path):
254 return True
254 return True
255
255
256 return False
256 return False
257
257
258
258
259 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
259 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
260 while True:
260 while True:
261 ok = raw_input(prompt)
261 ok = raw_input(prompt)
262 if ok in ('y', 'ye', 'yes'):
262 if ok in ('y', 'ye', 'yes'):
263 return True
263 return True
264 if ok in ('n', 'no', 'nop', 'nope'):
264 if ok in ('n', 'no', 'nop', 'nope'):
265 return False
265 return False
266 retries = retries - 1
266 retries = retries - 1
267 if retries < 0:
267 if retries < 0:
268 raise IOError
268 raise IOError
269 print complaint
269 print complaint
270
270
271 #propagated from mercurial documentation
271 #propagated from mercurial documentation
272 ui_sections = ['alias', 'auth',
272 ui_sections = ['alias', 'auth',
273 'decode/encode', 'defaults',
273 'decode/encode', 'defaults',
274 'diff', 'email',
274 'diff', 'email',
275 'extensions', 'format',
275 'extensions', 'format',
276 'merge-patterns', 'merge-tools',
276 'merge-patterns', 'merge-tools',
277 'hooks', 'http_proxy',
277 'hooks', 'http_proxy',
278 'smtp', 'patch',
278 'smtp', 'patch',
279 'paths', 'profiling',
279 'paths', 'profiling',
280 'server', 'trusted',
280 'server', 'trusted',
281 'ui', 'web', ]
281 'ui', 'web', ]
282
282
283
283
284 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
285 """
285 """
286 A function that will read python rc files or database
286 A function that will read python rc files or database
287 and make an mercurial ui object from read options
287 and make an mercurial ui object from read options
288
288
289 :param path: path to mercurial config file
289 :param path: path to mercurial config file
290 :param checkpaths: check the path
290 :param checkpaths: check the path
291 :param read_from: read from 'file' or 'db'
291 :param read_from: read from 'file' or 'db'
292 """
292 """
293
293
294 baseui = ui.ui()
294 baseui = ui.ui()
295
295
296 # clean the baseui object
296 # clean the baseui object
297 baseui._ocfg = config.config()
297 baseui._ocfg = config.config()
298 baseui._ucfg = config.config()
298 baseui._ucfg = config.config()
299 baseui._tcfg = config.config()
299 baseui._tcfg = config.config()
300
300
301 if read_from == 'file':
301 if read_from == 'file':
302 if not os.path.isfile(path):
302 if not os.path.isfile(path):
303 log.debug('hgrc file is not present at %s, skipping...' % path)
303 log.debug('hgrc file is not present at %s, skipping...' % path)
304 return False
304 return False
305 log.debug('reading hgrc from %s' % path)
305 log.debug('reading hgrc from %s' % path)
306 cfg = config.config()
306 cfg = config.config()
307 cfg.read(path)
307 cfg.read(path)
308 for section in ui_sections:
308 for section in ui_sections:
309 for k, v in cfg.items(section):
309 for k, v in cfg.items(section):
310 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
310 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
311 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
311 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
312
312
313 elif read_from == 'db':
313 elif read_from == 'db':
314 sa = meta.Session()
314 sa = meta.Session()
315 ret = sa.query(RhodeCodeUi)\
315 ret = sa.query(RhodeCodeUi)\
316 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
317 .all()
317 .all()
318
318
319 hg_ui = ret
319 hg_ui = ret
320 for ui_ in hg_ui:
320 for ui_ in hg_ui:
321 if ui_.ui_active:
321 if ui_.ui_active:
322 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
322 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
323 ui_.ui_key, ui_.ui_value)
323 ui_.ui_key, ui_.ui_value)
324 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
324 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
325 safe_str(ui_.ui_value))
325 safe_str(ui_.ui_value))
326 if ui_.ui_key == 'push_ssl':
326 if ui_.ui_key == 'push_ssl':
327 # force set push_ssl requirement to False, rhodecode
327 # force set push_ssl requirement to False, rhodecode
328 # handles that
328 # handles that
329 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
329 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
330 False)
330 False)
331 if clear_session:
331 if clear_session:
332 meta.Session.remove()
332 meta.Session.remove()
333 return baseui
333 return baseui
334
334
335
335
336 def set_rhodecode_config(config):
336 def set_rhodecode_config(config):
337 """
337 """
338 Updates pylons config with new settings from database
338 Updates pylons config with new settings from database
339
339
340 :param config:
340 :param config:
341 """
341 """
342 hgsettings = RhodeCodeSetting.get_app_settings()
342 hgsettings = RhodeCodeSetting.get_app_settings()
343
343
344 for k, v in hgsettings.items():
344 for k, v in hgsettings.items():
345 config[k] = v
345 config[k] = v
346
346
347
347
348 def invalidate_cache(cache_key, *args):
348 def invalidate_cache(cache_key, *args):
349 """
349 """
350 Puts cache invalidation task into db for
350 Puts cache invalidation task into db for
351 further global cache invalidation
351 further global cache invalidation
352 """
352 """
353
353
354 from rhodecode.model.scm import ScmModel
354 from rhodecode.model.scm import ScmModel
355
355
356 if cache_key.startswith('get_repo_cached_'):
356 if cache_key.startswith('get_repo_cached_'):
357 name = cache_key.split('get_repo_cached_')[-1]
357 name = cache_key.split('get_repo_cached_')[-1]
358 ScmModel().mark_for_invalidation(name)
358 ScmModel().mark_for_invalidation(name)
359
359
360
360
361 def map_groups(path):
361 def map_groups(path):
362 """
362 """
363 Given a full path to a repository, create all nested groups that this
363 Given a full path to a repository, create all nested groups that this
364 repo is inside. This function creates parent-child relationships between
364 repo is inside. This function creates parent-child relationships between
365 groups and creates default perms for all new groups.
365 groups and creates default perms for all new groups.
366
366
367 :param paths: full path to repository
367 :param paths: full path to repository
368 """
368 """
369 sa = meta.Session()
369 sa = meta.Session()
370 groups = path.split(Repository.url_sep())
370 groups = path.split(Repository.url_sep())
371 parent = None
371 parent = None
372 group = None
372 group = None
373
373
374 # last element is repo in nested groups structure
374 # last element is repo in nested groups structure
375 groups = groups[:-1]
375 groups = groups[:-1]
376 rgm = ReposGroupModel(sa)
376 rgm = ReposGroupModel(sa)
377 for lvl, group_name in enumerate(groups):
377 for lvl, group_name in enumerate(groups):
378 group_name = '/'.join(groups[:lvl] + [group_name])
378 group_name = '/'.join(groups[:lvl] + [group_name])
379 group = RepoGroup.get_by_group_name(group_name)
379 group = RepoGroup.get_by_group_name(group_name)
380 desc = '%s group' % group_name
380 desc = '%s group' % group_name
381
381
382 # skip folders that are now removed repos
382 # skip folders that are now removed repos
383 if REMOVED_REPO_PAT.match(group_name):
383 if REMOVED_REPO_PAT.match(group_name):
384 break
384 break
385
385
386 if group is None:
386 if group is None:
387 log.debug('creating group level: %s group_name: %s' % (lvl,
387 log.debug('creating group level: %s group_name: %s' % (lvl,
388 group_name))
388 group_name))
389 group = RepoGroup(group_name, parent)
389 group = RepoGroup(group_name, parent)
390 group.group_description = desc
390 group.group_description = desc
391 sa.add(group)
391 sa.add(group)
392 rgm._create_default_perms(group)
392 rgm._create_default_perms(group)
393 sa.flush()
393 sa.flush()
394 parent = group
394 parent = group
395 return group
395 return group
396
396
397
397
398 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
398 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
399 install_git_hook=False):
399 install_git_hook=False):
400 """
400 """
401 maps all repos given in initial_repo_list, non existing repositories
401 maps all repos given in initial_repo_list, non existing repositories
402 are created, if remove_obsolete is True it also check for db entries
402 are created, if remove_obsolete is True it also check for db entries
403 that are not in initial_repo_list and removes them.
403 that are not in initial_repo_list and removes them.
404
404
405 :param initial_repo_list: list of repositories found by scanning methods
405 :param initial_repo_list: list of repositories found by scanning methods
406 :param remove_obsolete: check for obsolete entries in database
406 :param remove_obsolete: check for obsolete entries in database
407 :param install_git_hook: if this is True, also check and install githook
407 :param install_git_hook: if this is True, also check and install githook
408 for a repo if missing
408 for a repo if missing
409 """
409 """
410 from rhodecode.model.repo import RepoModel
410 from rhodecode.model.repo import RepoModel
411 from rhodecode.model.scm import ScmModel
411 from rhodecode.model.scm import ScmModel
412 sa = meta.Session()
412 sa = meta.Session()
413 rm = RepoModel()
413 rm = RepoModel()
414 user = sa.query(User).filter(User.admin == True).first()
414 user = sa.query(User).filter(User.admin == True).first()
415 if user is None:
415 if user is None:
416 raise Exception('Missing administrative account!')
416 raise Exception('Missing administrative account!')
417 added = []
417 added = []
418
418
419 # # clear cache keys
419 # # clear cache keys
420 # log.debug("Clearing cache keys now...")
420 # log.debug("Clearing cache keys now...")
421 # CacheInvalidation.clear_cache()
421 # CacheInvalidation.clear_cache()
422 # sa.commit()
422 # sa.commit()
423
423
424 ##creation defaults
424 ##creation defaults
425 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
425 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
426 enable_statistics = defs.get('repo_enable_statistics')
426 enable_statistics = defs.get('repo_enable_statistics')
427 enable_locking = defs.get('repo_enable_locking')
427 enable_locking = defs.get('repo_enable_locking')
428 enable_downloads = defs.get('repo_enable_downloads')
428 enable_downloads = defs.get('repo_enable_downloads')
429 private = defs.get('repo_private')
429 private = defs.get('repo_private')
430
430
431 for name, repo in initial_repo_list.items():
431 for name, repo in initial_repo_list.items():
432 group = map_groups(name)
432 group = map_groups(name)
433 db_repo = rm.get_by_repo_name(name)
433 db_repo = rm.get_by_repo_name(name)
434 # found repo that is on filesystem not in RhodeCode database
434 # found repo that is on filesystem not in RhodeCode database
435 if not db_repo:
435 if not db_repo:
436 log.info('repository %s not found, creating now' % name)
436 log.info('repository %s not found, creating now' % name)
437 added.append(name)
437 added.append(name)
438 desc = (repo.description
438 desc = (repo.description
439 if repo.description != 'unknown'
439 if repo.description != 'unknown'
440 else '%s repository' % name)
440 else '%s repository' % name)
441
441
442 new_repo = rm.create_repo(
442 new_repo = rm.create_repo(
443 repo_name=name,
443 repo_name=name,
444 repo_type=repo.alias,
444 repo_type=repo.alias,
445 description=desc,
445 description=desc,
446 repos_group=getattr(group, 'group_id', None),
446 repos_group=getattr(group, 'group_id', None),
447 owner=user,
447 owner=user,
448 just_db=True,
448 just_db=True,
449 enable_locking=enable_locking,
449 enable_locking=enable_locking,
450 enable_downloads=enable_downloads,
450 enable_downloads=enable_downloads,
451 enable_statistics=enable_statistics,
451 enable_statistics=enable_statistics,
452 private=private
452 private=private
453 )
453 )
454 # we added that repo just now, and make sure it has githook
454 # we added that repo just now, and make sure it has githook
455 # installed
455 # installed
456 if new_repo.repo_type == 'git':
456 if new_repo.repo_type == 'git':
457 ScmModel().install_git_hook(new_repo.scm_instance)
457 ScmModel().install_git_hook(new_repo.scm_instance)
458 new_repo.update_changeset_cache()
458 new_repo.update_changeset_cache()
459 elif install_git_hook:
459 elif install_git_hook:
460 if db_repo.repo_type == 'git':
460 if db_repo.repo_type == 'git':
461 ScmModel().install_git_hook(db_repo.scm_instance)
461 ScmModel().install_git_hook(db_repo.scm_instance)
462 # during starting install all cache keys for all repositories in the
462 # during starting install all cache keys for all repositories in the
463 # system, this will register all repos and multiple instances
463 # system, this will register all repos and multiple instances
464 key, _prefix, _org_key = CacheInvalidation._get_key(name)
464 key, _prefix, _org_key = CacheInvalidation._get_key(name)
465 CacheInvalidation.invalidate(name)
465 CacheInvalidation.invalidate(name)
466 log.debug("Creating a cache key for %s, instance_id %s"
466 log.debug("Creating a cache key for %s, instance_id %s"
467 % (name, _prefix or 'unknown'))
467 % (name, _prefix or 'unknown'))
468
468
469 sa.commit()
469 sa.commit()
470 removed = []
470 removed = []
471 if remove_obsolete:
471 if remove_obsolete:
472 # remove from database those repositories that are not in the filesystem
472 # remove from database those repositories that are not in the filesystem
473 for repo in sa.query(Repository).all():
473 for repo in sa.query(Repository).all():
474 if repo.repo_name not in initial_repo_list.keys():
474 if repo.repo_name not in initial_repo_list.keys():
475 log.debug("Removing non-existing repository found in db `%s`" %
475 log.debug("Removing non-existing repository found in db `%s`" %
476 repo.repo_name)
476 repo.repo_name)
477 try:
477 try:
478 sa.delete(repo)
478 sa.delete(repo)
479 sa.commit()
479 sa.commit()
480 removed.append(repo.repo_name)
480 removed.append(repo.repo_name)
481 except:
481 except:
482 #don't hold further removals on error
482 #don't hold further removals on error
483 log.error(traceback.format_exc())
483 log.error(traceback.format_exc())
484 sa.rollback()
484 sa.rollback()
485
485
486 return added, removed
486 return added, removed
487
487
488
488
489 # set cache regions for beaker so celery can utilise it
489 # set cache regions for beaker so celery can utilise it
490 def add_cache(settings):
490 def add_cache(settings):
491 cache_settings = {'regions': None}
491 cache_settings = {'regions': None}
492 for key in settings.keys():
492 for key in settings.keys():
493 for prefix in ['beaker.cache.', 'cache.']:
493 for prefix in ['beaker.cache.', 'cache.']:
494 if key.startswith(prefix):
494 if key.startswith(prefix):
495 name = key.split(prefix)[1].strip()
495 name = key.split(prefix)[1].strip()
496 cache_settings[name] = settings[key].strip()
496 cache_settings[name] = settings[key].strip()
497 if cache_settings['regions']:
497 if cache_settings['regions']:
498 for region in cache_settings['regions'].split(','):
498 for region in cache_settings['regions'].split(','):
499 region = region.strip()
499 region = region.strip()
500 region_settings = {}
500 region_settings = {}
501 for key, value in cache_settings.items():
501 for key, value in cache_settings.items():
502 if key.startswith(region):
502 if key.startswith(region):
503 region_settings[key.split('.')[1]] = value
503 region_settings[key.split('.')[1]] = value
504 region_settings['expire'] = int(region_settings.get('expire',
504 region_settings['expire'] = int(region_settings.get('expire',
505 60))
505 60))
506 region_settings.setdefault('lock_dir',
506 region_settings.setdefault('lock_dir',
507 cache_settings.get('lock_dir'))
507 cache_settings.get('lock_dir'))
508 region_settings.setdefault('data_dir',
508 region_settings.setdefault('data_dir',
509 cache_settings.get('data_dir'))
509 cache_settings.get('data_dir'))
510
510
511 if 'type' not in region_settings:
511 if 'type' not in region_settings:
512 region_settings['type'] = cache_settings.get('type',
512 region_settings['type'] = cache_settings.get('type',
513 'memory')
513 'memory')
514 beaker.cache.cache_regions[region] = region_settings
514 beaker.cache.cache_regions[region] = region_settings
515
515
516
516
517 def load_rcextensions(root_path):
517 def load_rcextensions(root_path):
518 import rhodecode
518 import rhodecode
519 from rhodecode.config import conf
519 from rhodecode.config import conf
520
520
521 path = os.path.join(root_path, 'rcextensions', '__init__.py')
521 path = os.path.join(root_path, 'rcextensions', '__init__.py')
522 if os.path.isfile(path):
522 if os.path.isfile(path):
523 rcext = create_module('rc', path)
523 rcext = create_module('rc', path)
524 EXT = rhodecode.EXTENSIONS = rcext
524 EXT = rhodecode.EXTENSIONS = rcext
525 log.debug('Found rcextensions now loading %s...' % rcext)
525 log.debug('Found rcextensions now loading %s...' % rcext)
526
526
527 # Additional mappings that are not present in the pygments lexers
527 # Additional mappings that are not present in the pygments lexers
528 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
528 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
529
529
530 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
530 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
531
531
532 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
532 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
533 log.debug('settings custom INDEX_EXTENSIONS')
533 log.debug('settings custom INDEX_EXTENSIONS')
534 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
534 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
535
535
536 #ADDITIONAL MAPPINGS
536 #ADDITIONAL MAPPINGS
537 log.debug('adding extra into INDEX_EXTENSIONS')
537 log.debug('adding extra into INDEX_EXTENSIONS')
538 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
538 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
539
539
540
540
541 #==============================================================================
541 #==============================================================================
542 # TEST FUNCTIONS AND CREATORS
542 # TEST FUNCTIONS AND CREATORS
543 #==============================================================================
543 #==============================================================================
544 def create_test_index(repo_location, config, full_index):
544 def create_test_index(repo_location, config, full_index):
545 """
545 """
546 Makes default test index
546 Makes default test index
547
547
548 :param config: test config
548 :param config: test config
549 :param full_index:
549 :param full_index:
550 """
550 """
551
551
552 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
552 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
553 from rhodecode.lib.pidlock import DaemonLock, LockHeld
553 from rhodecode.lib.pidlock import DaemonLock, LockHeld
554
554
555 repo_location = repo_location
555 repo_location = repo_location
556
556
557 index_location = os.path.join(config['app_conf']['index_dir'])
557 index_location = os.path.join(config['app_conf']['index_dir'])
558 if not os.path.exists(index_location):
558 if not os.path.exists(index_location):
559 os.makedirs(index_location)
559 os.makedirs(index_location)
560
560
561 try:
561 try:
562 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
562 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
563 WhooshIndexingDaemon(index_location=index_location,
563 WhooshIndexingDaemon(index_location=index_location,
564 repo_location=repo_location)\
564 repo_location=repo_location)\
565 .run(full_index=full_index)
565 .run(full_index=full_index)
566 l.release()
566 l.release()
567 except LockHeld:
567 except LockHeld:
568 pass
568 pass
569
569
570
570
571 def create_test_env(repos_test_path, config):
571 def create_test_env(repos_test_path, config):
572 """
572 """
573 Makes a fresh database and
573 Makes a fresh database and
574 install test repository into tmp dir
574 install test repository into tmp dir
575 """
575 """
576 from rhodecode.lib.db_manage import DbManage
576 from rhodecode.lib.db_manage import DbManage
577 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
577 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
578
578
579 # PART ONE create db
579 # PART ONE create db
580 dbconf = config['sqlalchemy.db1.url']
580 dbconf = config['sqlalchemy.db1.url']
581 log.debug('making test db %s' % dbconf)
581 log.debug('making test db %s' % dbconf)
582
582
583 # create test dir if it doesn't exist
583 # create test dir if it doesn't exist
584 if not os.path.isdir(repos_test_path):
584 if not os.path.isdir(repos_test_path):
585 log.debug('Creating testdir %s' % repos_test_path)
585 log.debug('Creating testdir %s' % repos_test_path)
586 os.makedirs(repos_test_path)
586 os.makedirs(repos_test_path)
587
587
588 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
588 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
589 tests=True)
589 tests=True)
590 dbmanage.create_tables(override=True)
590 dbmanage.create_tables(override=True)
591 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
591 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
592 dbmanage.create_default_user()
592 dbmanage.create_default_user()
593 dbmanage.admin_prompt()
593 dbmanage.admin_prompt()
594 dbmanage.create_permissions()
594 dbmanage.create_permissions()
595 dbmanage.populate_default_permissions()
595 dbmanage.populate_default_permissions()
596 Session().commit()
596 Session().commit()
597 # PART TWO make test repo
597 # PART TWO make test repo
598 log.debug('making test vcs repositories')
598 log.debug('making test vcs repositories')
599
599
600 idx_path = config['app_conf']['index_dir']
600 idx_path = config['app_conf']['index_dir']
601 data_path = config['app_conf']['cache_dir']
601 data_path = config['app_conf']['cache_dir']
602
602
603 #clean index and data
603 #clean index and data
604 if idx_path and os.path.exists(idx_path):
604 if idx_path and os.path.exists(idx_path):
605 log.debug('remove %s' % idx_path)
605 log.debug('remove %s' % idx_path)
606 shutil.rmtree(idx_path)
606 shutil.rmtree(idx_path)
607
607
608 if data_path and os.path.exists(data_path):
608 if data_path and os.path.exists(data_path):
609 log.debug('remove %s' % data_path)
609 log.debug('remove %s' % data_path)
610 shutil.rmtree(data_path)
610 shutil.rmtree(data_path)
611
611
612 #CREATE DEFAULT TEST REPOS
612 #CREATE DEFAULT TEST REPOS
613 cur_dir = dn(dn(abspath(__file__)))
613 cur_dir = dn(dn(abspath(__file__)))
614 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
614 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
615 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
615 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
616 tar.close()
616 tar.close()
617
617
618 cur_dir = dn(dn(abspath(__file__)))
618 cur_dir = dn(dn(abspath(__file__)))
619 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
619 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
620 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
620 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
621 tar.close()
621 tar.close()
622
622
623 #LOAD VCS test stuff
623 #LOAD VCS test stuff
624 from rhodecode.tests.vcs import setup_package
624 from rhodecode.tests.vcs import setup_package
625 setup_package()
625 setup_package()
626
626
627
627
628 #==============================================================================
628 #==============================================================================
629 # PASTER COMMANDS
629 # PASTER COMMANDS
630 #==============================================================================
630 #==============================================================================
631 class BasePasterCommand(Command):
631 class BasePasterCommand(Command):
632 """
632 """
633 Abstract Base Class for paster commands.
633 Abstract Base Class for paster commands.
634
634
635 The celery commands are somewhat aggressive about loading
635 The celery commands are somewhat aggressive about loading
636 celery.conf, and since our module sets the `CELERY_LOADER`
636 celery.conf, and since our module sets the `CELERY_LOADER`
637 environment variable to our loader, we have to bootstrap a bit and
637 environment variable to our loader, we have to bootstrap a bit and
638 make sure we've had a chance to load the pylons config off of the
638 make sure we've had a chance to load the pylons config off of the
639 command line, otherwise everything fails.
639 command line, otherwise everything fails.
640 """
640 """
641 min_args = 1
641 min_args = 1
642 min_args_error = "Please provide a paster config file as an argument."
642 min_args_error = "Please provide a paster config file as an argument."
643 takes_config_file = 1
643 takes_config_file = 1
644 requires_config_file = True
644 requires_config_file = True
645
645
646 def notify_msg(self, msg, log=False):
646 def notify_msg(self, msg, log=False):
647 """Make a notification to user, additionally if logger is passed
647 """Make a notification to user, additionally if logger is passed
648 it logs this action using given logger
648 it logs this action using given logger
649
649
650 :param msg: message that will be printed to user
650 :param msg: message that will be printed to user
651 :param log: logging instance, to use to additionally log this message
651 :param log: logging instance, to use to additionally log this message
652
652
653 """
653 """
654 if log and isinstance(log, logging):
654 if log and isinstance(log, logging):
655 log(msg)
655 log(msg)
656
656
657 def run(self, args):
657 def run(self, args):
658 """
658 """
659 Overrides Command.run
659 Overrides Command.run
660
660
661 Checks for a config file argument and loads it.
661 Checks for a config file argument and loads it.
662 """
662 """
663 if len(args) < self.min_args:
663 if len(args) < self.min_args:
664 raise BadCommand(
664 raise BadCommand(
665 self.min_args_error % {'min_args': self.min_args,
665 self.min_args_error % {'min_args': self.min_args,
666 'actual_args': len(args)})
666 'actual_args': len(args)})
667
667
668 # Decrement because we're going to lob off the first argument.
668 # Decrement because we're going to lob off the first argument.
669 # @@ This is hacky
669 # @@ This is hacky
670 self.min_args -= 1
670 self.min_args -= 1
671 self.bootstrap_config(args[0])
671 self.bootstrap_config(args[0])
672 self.update_parser()
672 self.update_parser()
673 return super(BasePasterCommand, self).run(args[1:])
673 return super(BasePasterCommand, self).run(args[1:])
674
674
675 def update_parser(self):
675 def update_parser(self):
676 """
676 """
677 Abstract method. Allows for the class's parser to be updated
677 Abstract method. Allows for the class's parser to be updated
678 before the superclass's `run` method is called. Necessary to
678 before the superclass's `run` method is called. Necessary to
679 allow options/arguments to be passed through to the underlying
679 allow options/arguments to be passed through to the underlying
680 celery command.
680 celery command.
681 """
681 """
682 raise NotImplementedError("Abstract Method.")
682 raise NotImplementedError("Abstract Method.")
683
683
684 def bootstrap_config(self, conf):
684 def bootstrap_config(self, conf):
685 """
685 """
686 Loads the pylons configuration.
686 Loads the pylons configuration.
687 """
687 """
688 from pylons import config as pylonsconfig
688 from pylons import config as pylonsconfig
689
689
690 self.path_to_ini_file = os.path.realpath(conf)
690 self.path_to_ini_file = os.path.realpath(conf)
691 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
691 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
692 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
692 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
693
693
694
694
695 def check_git_version():
695 def check_git_version():
696 """
696 """
697 Checks what version of git is installed in system, and issues a warning
697 Checks what version of git is installed in system, and issues a warning
698 if it's too old for RhodeCode to properly work.
698 if it's too old for RhodeCode to properly work.
699 """
699 """
700 import subprocess
700 import subprocess
701 from distutils.version import StrictVersion
701 from distutils.version import StrictVersion
702 from rhodecode import BACKENDS
702 from rhodecode import BACKENDS
703
703
704 p = subprocess.Popen('git --version', shell=True,
704 p = subprocess.Popen('git --version', shell=True,
705 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
705 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
706 stdout, stderr = p.communicate()
706 stdout, stderr = p.communicate()
707 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
707 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
708 if len(ver.split('.')) > 3:
708 if len(ver.split('.')) > 3:
709 #StrictVersion needs to be only 3 element type
709 #StrictVersion needs to be only 3 element type
710 ver = '.'.join(ver.split('.')[:3])
710 ver = '.'.join(ver.split('.')[:3])
711 try:
711 try:
712 _ver = StrictVersion(ver)
712 _ver = StrictVersion(ver)
713 except:
713 except:
714 _ver = StrictVersion('0.0.0')
714 _ver = StrictVersion('0.0.0')
715 stderr = traceback.format_exc()
715 stderr = traceback.format_exc()
716
716
717 req_ver = '1.7.4'
717 req_ver = '1.7.4'
718 to_old_git = False
718 to_old_git = False
719 if _ver < StrictVersion(req_ver):
719 if _ver < StrictVersion(req_ver):
720 to_old_git = True
720 to_old_git = True
721
721
722 if 'git' in BACKENDS:
722 if 'git' in BACKENDS:
723 log.debug('GIT version detected: %s' % stdout)
723 log.debug('GIT version detected: %s' % stdout)
724 if stderr:
724 if stderr:
725 log.warning('Unable to detect git version org error was:%r' % stderr)
725 log.warning('Unable to detect git version org error was:%r' % stderr)
726 elif to_old_git:
726 elif to_old_git:
727 log.warning('RhodeCode detected git version %s, which is too old '
727 log.warning('RhodeCode detected git version %s, which is too old '
728 'for the system to function properly. Make sure '
728 'for the system to function properly. Make sure '
729 'its version is at least %s' % (ver, req_ver))
729 'its version is at least %s' % (ver, req_ver))
730 return _ver
730 return _ver
731
731
732
732
733 @decorator.decorator
733 @decorator.decorator
734 def jsonify(func, *args, **kwargs):
734 def jsonify(func, *args, **kwargs):
735 """Action decorator that formats output for JSON
735 """Action decorator that formats output for JSON
736
736
737 Given a function that will return content, this decorator will turn
737 Given a function that will return content, this decorator will turn
738 the result into JSON, with a content-type of 'application/json' and
738 the result into JSON, with a content-type of 'application/json' and
739 output it.
739 output it.
740
740
741 """
741 """
742 from pylons.decorators.util import get_pylons
742 from pylons.decorators.util import get_pylons
743 from rhodecode.lib.ext_json import json
743 from rhodecode.lib.ext_json import json
744 pylons = get_pylons(args)
744 pylons = get_pylons(args)
745 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
745 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
746 data = func(*args, **kwargs)
746 data = func(*args, **kwargs)
747 if isinstance(data, (list, tuple)):
747 if isinstance(data, (list, tuple)):
748 msg = "JSON responses with Array envelopes are susceptible to " \
748 msg = "JSON responses with Array envelopes are susceptible to " \
749 "cross-site data leak attacks, see " \
749 "cross-site data leak attacks, see " \
750 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
750 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
751 warnings.warn(msg, Warning, 2)
751 warnings.warn(msg, Warning, 2)
752 log.warning(msg)
752 log.warning(msg)
753 log.debug("Returning JSON wrapped action output")
753 log.debug("Returning JSON wrapped action output")
754 return json.dumps(data, encoding='utf-8') No newline at end of file
754 return json.dumps(data, encoding='utf-8')
@@ -1,745 +1,745 b''
1 """
1 """
2 Set of generic validators
2 Set of generic validators
3 """
3 """
4 import os
4 import os
5 import re
5 import re
6 import formencode
6 import formencode
7 import logging
7 import logging
8 from collections import defaultdict
8 from collections import defaultdict
9 from pylons.i18n.translation import _
9 from pylons.i18n.translation import _
10 from webhelpers.pylonslib.secure_form import authentication_token
10 from webhelpers.pylonslib.secure_form import authentication_token
11
11
12 from formencode.validators import (
12 from formencode.validators import (
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
14 NotEmpty, IPAddress, CIDR
14 NotEmpty, IPAddress, CIDR
15 )
15 )
16 from rhodecode.lib.compat import OrderedSet
16 from rhodecode.lib.compat import OrderedSet
17 from rhodecode.lib.utils import repo_name_slug
17 from rhodecode.lib.utils import repo_name_slug
18 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
18 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
19 ChangesetStatus
19 ChangesetStatus
20 from rhodecode.lib.exceptions import LdapImportError
20 from rhodecode.lib.exceptions import LdapImportError
21 from rhodecode.config.routing import ADMIN_PREFIX
21 from rhodecode.config.routing import ADMIN_PREFIX
22 from rhodecode.lib.auth import HasReposGroupPermissionAny
22 from rhodecode.lib.auth import HasReposGroupPermissionAny
23
23
24 # silence warnings and pylint
24 # silence warnings and pylint
25 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
25 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
26 NotEmpty, IPAddress, CIDR
26 NotEmpty, IPAddress, CIDR
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 class UniqueList(formencode.FancyValidator):
31 class UniqueList(formencode.FancyValidator):
32 """
32 """
33 Unique List !
33 Unique List !
34 """
34 """
35 messages = dict(
35 messages = dict(
36 empty=_('Value cannot be an empty list'),
36 empty=_('Value cannot be an empty list'),
37 missing_value=_('Value cannot be an empty list'),
37 missing_value=_('Value cannot be an empty list'),
38 )
38 )
39
39
40 def _to_python(self, value, state):
40 def _to_python(self, value, state):
41 if isinstance(value, list):
41 if isinstance(value, list):
42 return value
42 return value
43 elif isinstance(value, set):
43 elif isinstance(value, set):
44 return list(value)
44 return list(value)
45 elif isinstance(value, tuple):
45 elif isinstance(value, tuple):
46 return list(value)
46 return list(value)
47 elif value is None:
47 elif value is None:
48 return []
48 return []
49 else:
49 else:
50 return [value]
50 return [value]
51
51
52 def empty_value(self, value):
52 def empty_value(self, value):
53 return []
53 return []
54
54
55
55
56 class StateObj(object):
56 class StateObj(object):
57 """
57 """
58 this is needed to translate the messages using _() in validators
58 this is needed to translate the messages using _() in validators
59 """
59 """
60 _ = staticmethod(_)
60 _ = staticmethod(_)
61
61
62
62
63 def M(self, key, state=None, **kwargs):
63 def M(self, key, state=None, **kwargs):
64 """
64 """
65 returns string from self.message based on given key,
65 returns string from self.message based on given key,
66 passed kw params are used to substitute %(named)s params inside
66 passed kw params are used to substitute %(named)s params inside
67 translated strings
67 translated strings
68
68
69 :param msg:
69 :param msg:
70 :param state:
70 :param state:
71 """
71 """
72 if state is None:
72 if state is None:
73 state = StateObj()
73 state = StateObj()
74 else:
74 else:
75 state._ = staticmethod(_)
75 state._ = staticmethod(_)
76 #inject validator into state object
76 #inject validator into state object
77 return self.message(key, state, **kwargs)
77 return self.message(key, state, **kwargs)
78
78
79
79
80 def ValidUsername(edit=False, old_data={}):
80 def ValidUsername(edit=False, old_data={}):
81 class _validator(formencode.validators.FancyValidator):
81 class _validator(formencode.validators.FancyValidator):
82 messages = {
82 messages = {
83 'username_exists': _(u'Username "%(username)s" already exists'),
83 'username_exists': _(u'Username "%(username)s" already exists'),
84 'system_invalid_username':
84 'system_invalid_username':
85 _(u'Username "%(username)s" is forbidden'),
85 _(u'Username "%(username)s" is forbidden'),
86 'invalid_username':
86 'invalid_username':
87 _(u'Username may only contain alphanumeric characters '
87 _(u'Username may only contain alphanumeric characters '
88 'underscores, periods or dashes and must begin with '
88 'underscores, periods or dashes and must begin with '
89 'alphanumeric character')
89 'alphanumeric character')
90 }
90 }
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default', 'new_user']:
93 if value in ['default', 'new_user']:
94 msg = M(self, 'system_invalid_username', state, username=value)
94 msg = M(self, 'system_invalid_username', state, username=value)
95 raise formencode.Invalid(msg, value, state)
95 raise formencode.Invalid(msg, value, state)
96 #check if user is unique
96 #check if user is unique
97 old_un = None
97 old_un = None
98 if edit:
98 if edit:
99 old_un = User.get(old_data.get('user_id')).username
99 old_un = User.get(old_data.get('user_id')).username
100
100
101 if old_un != value or not edit:
101 if old_un != value or not edit:
102 if User.get_by_username(value, case_insensitive=True):
102 if User.get_by_username(value, case_insensitive=True):
103 msg = M(self, 'username_exists', state, username=value)
103 msg = M(self, 'username_exists', state, username=value)
104 raise formencode.Invalid(msg, value, state)
104 raise formencode.Invalid(msg, value, state)
105
105
106 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
106 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
107 msg = M(self, 'invalid_username', state)
107 msg = M(self, 'invalid_username', state)
108 raise formencode.Invalid(msg, value, state)
108 raise formencode.Invalid(msg, value, state)
109 return _validator
109 return _validator
110
110
111
111
112 def ValidRepoUser():
112 def ValidRepoUser():
113 class _validator(formencode.validators.FancyValidator):
113 class _validator(formencode.validators.FancyValidator):
114 messages = {
114 messages = {
115 'invalid_username': _(u'Username %(username)s is not valid')
115 'invalid_username': _(u'Username %(username)s is not valid')
116 }
116 }
117
117
118 def validate_python(self, value, state):
118 def validate_python(self, value, state):
119 try:
119 try:
120 User.query().filter(User.active == True)\
120 User.query().filter(User.active == True)\
121 .filter(User.username == value).one()
121 .filter(User.username == value).one()
122 except Exception:
122 except Exception:
123 msg = M(self, 'invalid_username', state, username=value)
123 msg = M(self, 'invalid_username', state, username=value)
124 raise formencode.Invalid(msg, value, state,
124 raise formencode.Invalid(msg, value, state,
125 error_dict=dict(username=msg)
125 error_dict=dict(username=msg)
126 )
126 )
127
127
128 return _validator
128 return _validator
129
129
130
130
131 def ValidUsersGroup(edit=False, old_data={}):
131 def ValidUsersGroup(edit=False, old_data={}):
132 class _validator(formencode.validators.FancyValidator):
132 class _validator(formencode.validators.FancyValidator):
133 messages = {
133 messages = {
134 'invalid_group': _(u'Invalid users group name'),
134 'invalid_group': _(u'Invalid users group name'),
135 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
135 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
136 'invalid_usersgroup_name':
136 'invalid_usersgroup_name':
137 _(u'users group name may only contain alphanumeric '
137 _(u'users group name may only contain alphanumeric '
138 'characters underscores, periods or dashes and must begin '
138 'characters underscores, periods or dashes and must begin '
139 'with alphanumeric character')
139 'with alphanumeric character')
140 }
140 }
141
141
142 def validate_python(self, value, state):
142 def validate_python(self, value, state):
143 if value in ['default']:
143 if value in ['default']:
144 msg = M(self, 'invalid_group', state)
144 msg = M(self, 'invalid_group', state)
145 raise formencode.Invalid(msg, value, state,
145 raise formencode.Invalid(msg, value, state,
146 error_dict=dict(users_group_name=msg)
146 error_dict=dict(users_group_name=msg)
147 )
147 )
148 #check if group is unique
148 #check if group is unique
149 old_ugname = None
149 old_ugname = None
150 if edit:
150 if edit:
151 old_id = old_data.get('users_group_id')
151 old_id = old_data.get('users_group_id')
152 old_ugname = UsersGroup.get(old_id).users_group_name
152 old_ugname = UsersGroup.get(old_id).users_group_name
153
153
154 if old_ugname != value or not edit:
154 if old_ugname != value or not edit:
155 is_existing_group = UsersGroup.get_by_group_name(value,
155 is_existing_group = UsersGroup.get_by_group_name(value,
156 case_insensitive=True)
156 case_insensitive=True)
157 if is_existing_group:
157 if is_existing_group:
158 msg = M(self, 'group_exist', state, usersgroup=value)
158 msg = M(self, 'group_exist', state, usersgroup=value)
159 raise formencode.Invalid(msg, value, state,
159 raise formencode.Invalid(msg, value, state,
160 error_dict=dict(users_group_name=msg)
160 error_dict=dict(users_group_name=msg)
161 )
161 )
162
162
163 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
163 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
164 msg = M(self, 'invalid_usersgroup_name', state)
164 msg = M(self, 'invalid_usersgroup_name', state)
165 raise formencode.Invalid(msg, value, state,
165 raise formencode.Invalid(msg, value, state,
166 error_dict=dict(users_group_name=msg)
166 error_dict=dict(users_group_name=msg)
167 )
167 )
168
168
169 return _validator
169 return _validator
170
170
171
171
172 def ValidReposGroup(edit=False, old_data={}):
172 def ValidReposGroup(edit=False, old_data={}):
173 class _validator(formencode.validators.FancyValidator):
173 class _validator(formencode.validators.FancyValidator):
174 messages = {
174 messages = {
175 'group_parent_id': _(u'Cannot assign this group as parent'),
175 'group_parent_id': _(u'Cannot assign this group as parent'),
176 'group_exists': _(u'Group "%(group_name)s" already exists'),
176 'group_exists': _(u'Group "%(group_name)s" already exists'),
177 'repo_exists':
177 'repo_exists':
178 _(u'Repository with name "%(group_name)s" already exists')
178 _(u'Repository with name "%(group_name)s" already exists')
179 }
179 }
180
180
181 def validate_python(self, value, state):
181 def validate_python(self, value, state):
182 # TODO WRITE VALIDATIONS
182 # TODO WRITE VALIDATIONS
183 group_name = value.get('group_name')
183 group_name = value.get('group_name')
184 group_parent_id = value.get('group_parent_id')
184 group_parent_id = value.get('group_parent_id')
185
185
186 # slugify repo group just in case :)
186 # slugify repo group just in case :)
187 slug = repo_name_slug(group_name)
187 slug = repo_name_slug(group_name)
188
188
189 # check for parent of self
189 # check for parent of self
190 parent_of_self = lambda: (
190 parent_of_self = lambda: (
191 old_data['group_id'] == int(group_parent_id)
191 old_data['group_id'] == int(group_parent_id)
192 if group_parent_id else False
192 if group_parent_id else False
193 )
193 )
194 if edit and parent_of_self():
194 if edit and parent_of_self():
195 msg = M(self, 'group_parent_id', state)
195 msg = M(self, 'group_parent_id', state)
196 raise formencode.Invalid(msg, value, state,
196 raise formencode.Invalid(msg, value, state,
197 error_dict=dict(group_parent_id=msg)
197 error_dict=dict(group_parent_id=msg)
198 )
198 )
199
199
200 old_gname = None
200 old_gname = None
201 if edit:
201 if edit:
202 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
202 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
203
203
204 if old_gname != group_name or not edit:
204 if old_gname != group_name or not edit:
205
205
206 # check group
206 # check group
207 gr = RepoGroup.query()\
207 gr = RepoGroup.query()\
208 .filter(RepoGroup.group_name == slug)\
208 .filter(RepoGroup.group_name == slug)\
209 .filter(RepoGroup.group_parent_id == group_parent_id)\
209 .filter(RepoGroup.group_parent_id == group_parent_id)\
210 .scalar()
210 .scalar()
211
211
212 if gr:
212 if gr:
213 msg = M(self, 'group_exists', state, group_name=slug)
213 msg = M(self, 'group_exists', state, group_name=slug)
214 raise formencode.Invalid(msg, value, state,
214 raise formencode.Invalid(msg, value, state,
215 error_dict=dict(group_name=msg)
215 error_dict=dict(group_name=msg)
216 )
216 )
217
217
218 # check for same repo
218 # check for same repo
219 repo = Repository.query()\
219 repo = Repository.query()\
220 .filter(Repository.repo_name == slug)\
220 .filter(Repository.repo_name == slug)\
221 .scalar()
221 .scalar()
222
222
223 if repo:
223 if repo:
224 msg = M(self, 'repo_exists', state, group_name=slug)
224 msg = M(self, 'repo_exists', state, group_name=slug)
225 raise formencode.Invalid(msg, value, state,
225 raise formencode.Invalid(msg, value, state,
226 error_dict=dict(group_name=msg)
226 error_dict=dict(group_name=msg)
227 )
227 )
228
228
229 return _validator
229 return _validator
230
230
231
231
232 def ValidPassword():
232 def ValidPassword():
233 class _validator(formencode.validators.FancyValidator):
233 class _validator(formencode.validators.FancyValidator):
234 messages = {
234 messages = {
235 'invalid_password':
235 'invalid_password':
236 _(u'Invalid characters (non-ascii) in password')
236 _(u'Invalid characters (non-ascii) in password')
237 }
237 }
238
238
239 def validate_python(self, value, state):
239 def validate_python(self, value, state):
240 try:
240 try:
241 (value or '').decode('ascii')
241 (value or '').decode('ascii')
242 except UnicodeError:
242 except UnicodeError:
243 msg = M(self, 'invalid_password', state)
243 msg = M(self, 'invalid_password', state)
244 raise formencode.Invalid(msg, value, state,)
244 raise formencode.Invalid(msg, value, state,)
245 return _validator
245 return _validator
246
246
247
247
248 def ValidPasswordsMatch():
248 def ValidPasswordsMatch():
249 class _validator(formencode.validators.FancyValidator):
249 class _validator(formencode.validators.FancyValidator):
250 messages = {
250 messages = {
251 'password_mismatch': _(u'Passwords do not match'),
251 'password_mismatch': _(u'Passwords do not match'),
252 }
252 }
253
253
254 def validate_python(self, value, state):
254 def validate_python(self, value, state):
255
255
256 pass_val = value.get('password') or value.get('new_password')
256 pass_val = value.get('password') or value.get('new_password')
257 if pass_val != value['password_confirmation']:
257 if pass_val != value['password_confirmation']:
258 msg = M(self, 'password_mismatch', state)
258 msg = M(self, 'password_mismatch', state)
259 raise formencode.Invalid(msg, value, state,
259 raise formencode.Invalid(msg, value, state,
260 error_dict=dict(password_confirmation=msg)
260 error_dict=dict(password_confirmation=msg)
261 )
261 )
262 return _validator
262 return _validator
263
263
264
264
265 def ValidAuth():
265 def ValidAuth():
266 class _validator(formencode.validators.FancyValidator):
266 class _validator(formencode.validators.FancyValidator):
267 messages = {
267 messages = {
268 'invalid_password': _(u'invalid password'),
268 'invalid_password': _(u'invalid password'),
269 'invalid_username': _(u'invalid user name'),
269 'invalid_username': _(u'invalid user name'),
270 'disabled_account': _(u'Your account is disabled')
270 'disabled_account': _(u'Your account is disabled')
271 }
271 }
272
272
273 def validate_python(self, value, state):
273 def validate_python(self, value, state):
274 from rhodecode.lib.auth import authenticate
274 from rhodecode.lib.auth import authenticate
275
275
276 password = value['password']
276 password = value['password']
277 username = value['username']
277 username = value['username']
278
278
279 if not authenticate(username, password):
279 if not authenticate(username, password):
280 user = User.get_by_username(username)
280 user = User.get_by_username(username)
281 if user and user.active is False:
281 if user and user.active is False:
282 log.warning('user %s is disabled' % username)
282 log.warning('user %s is disabled' % username)
283 msg = M(self, 'disabled_account', state)
283 msg = M(self, 'disabled_account', state)
284 raise formencode.Invalid(msg, value, state,
284 raise formencode.Invalid(msg, value, state,
285 error_dict=dict(username=msg)
285 error_dict=dict(username=msg)
286 )
286 )
287 else:
287 else:
288 log.warning('user %s failed to authenticate' % username)
288 log.warning('user %s failed to authenticate' % username)
289 msg = M(self, 'invalid_username', state)
289 msg = M(self, 'invalid_username', state)
290 msg2 = M(self, 'invalid_password', state)
290 msg2 = M(self, 'invalid_password', state)
291 raise formencode.Invalid(msg, value, state,
291 raise formencode.Invalid(msg, value, state,
292 error_dict=dict(username=msg, password=msg2)
292 error_dict=dict(username=msg, password=msg2)
293 )
293 )
294 return _validator
294 return _validator
295
295
296
296
297 def ValidAuthToken():
297 def ValidAuthToken():
298 class _validator(formencode.validators.FancyValidator):
298 class _validator(formencode.validators.FancyValidator):
299 messages = {
299 messages = {
300 'invalid_token': _(u'Token mismatch')
300 'invalid_token': _(u'Token mismatch')
301 }
301 }
302
302
303 def validate_python(self, value, state):
303 def validate_python(self, value, state):
304 if value != authentication_token():
304 if value != authentication_token():
305 msg = M(self, 'invalid_token', state)
305 msg = M(self, 'invalid_token', state)
306 raise formencode.Invalid(msg, value, state)
306 raise formencode.Invalid(msg, value, state)
307 return _validator
307 return _validator
308
308
309
309
310 def ValidRepoName(edit=False, old_data={}):
310 def ValidRepoName(edit=False, old_data={}):
311 class _validator(formencode.validators.FancyValidator):
311 class _validator(formencode.validators.FancyValidator):
312 messages = {
312 messages = {
313 'invalid_repo_name':
313 'invalid_repo_name':
314 _(u'Repository name %(repo)s is disallowed'),
314 _(u'Repository name %(repo)s is disallowed'),
315 'repository_exists':
315 'repository_exists':
316 _(u'Repository named %(repo)s already exists'),
316 _(u'Repository named %(repo)s already exists'),
317 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
317 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
318 'exists in group "%(group)s"'),
318 'exists in group "%(group)s"'),
319 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
319 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
320 'already exists')
320 'already exists')
321 }
321 }
322
322
323 def _to_python(self, value, state):
323 def _to_python(self, value, state):
324 repo_name = repo_name_slug(value.get('repo_name', ''))
324 repo_name = repo_name_slug(value.get('repo_name', ''))
325 repo_group = value.get('repo_group')
325 repo_group = value.get('repo_group')
326 if repo_group:
326 if repo_group:
327 gr = RepoGroup.get(repo_group)
327 gr = RepoGroup.get(repo_group)
328 group_path = gr.full_path
328 group_path = gr.full_path
329 group_name = gr.group_name
329 group_name = gr.group_name
330 # value needs to be aware of group name in order to check
330 # value needs to be aware of group name in order to check
331 # db key This is an actual just the name to store in the
331 # db key This is an actual just the name to store in the
332 # database
332 # database
333 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
333 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
334 else:
334 else:
335 group_name = group_path = ''
335 group_name = group_path = ''
336 repo_name_full = repo_name
336 repo_name_full = repo_name
337
337
338 value['repo_name'] = repo_name
338 value['repo_name'] = repo_name
339 value['repo_name_full'] = repo_name_full
339 value['repo_name_full'] = repo_name_full
340 value['group_path'] = group_path
340 value['group_path'] = group_path
341 value['group_name'] = group_name
341 value['group_name'] = group_name
342 return value
342 return value
343
343
344 def validate_python(self, value, state):
344 def validate_python(self, value, state):
345
345
346 repo_name = value.get('repo_name')
346 repo_name = value.get('repo_name')
347 repo_name_full = value.get('repo_name_full')
347 repo_name_full = value.get('repo_name_full')
348 group_path = value.get('group_path')
348 group_path = value.get('group_path')
349 group_name = value.get('group_name')
349 group_name = value.get('group_name')
350
350
351 if repo_name in [ADMIN_PREFIX, '']:
351 if repo_name in [ADMIN_PREFIX, '']:
352 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
352 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
353 raise formencode.Invalid(msg, value, state,
353 raise formencode.Invalid(msg, value, state,
354 error_dict=dict(repo_name=msg)
354 error_dict=dict(repo_name=msg)
355 )
355 )
356
356
357 rename = old_data.get('repo_name') != repo_name_full
357 rename = old_data.get('repo_name') != repo_name_full
358 create = not edit
358 create = not edit
359 if rename or create:
359 if rename or create:
360
360
361 if group_path != '':
361 if group_path != '':
362 if Repository.get_by_repo_name(repo_name_full):
362 if Repository.get_by_repo_name(repo_name_full):
363 msg = M(self, 'repository_in_group_exists', state,
363 msg = M(self, 'repository_in_group_exists', state,
364 repo=repo_name, group=group_name)
364 repo=repo_name, group=group_name)
365 raise formencode.Invalid(msg, value, state,
365 raise formencode.Invalid(msg, value, state,
366 error_dict=dict(repo_name=msg)
366 error_dict=dict(repo_name=msg)
367 )
367 )
368 elif RepoGroup.get_by_group_name(repo_name_full):
368 elif RepoGroup.get_by_group_name(repo_name_full):
369 msg = M(self, 'same_group_exists', state,
369 msg = M(self, 'same_group_exists', state,
370 repo=repo_name)
370 repo=repo_name)
371 raise formencode.Invalid(msg, value, state,
371 raise formencode.Invalid(msg, value, state,
372 error_dict=dict(repo_name=msg)
372 error_dict=dict(repo_name=msg)
373 )
373 )
374
374
375 elif Repository.get_by_repo_name(repo_name_full):
375 elif Repository.get_by_repo_name(repo_name_full):
376 msg = M(self, 'repository_exists', state,
376 msg = M(self, 'repository_exists', state,
377 repo=repo_name)
377 repo=repo_name)
378 raise formencode.Invalid(msg, value, state,
378 raise formencode.Invalid(msg, value, state,
379 error_dict=dict(repo_name=msg)
379 error_dict=dict(repo_name=msg)
380 )
380 )
381 return value
381 return value
382 return _validator
382 return _validator
383
383
384
384
385 def ValidForkName(*args, **kwargs):
385 def ValidForkName(*args, **kwargs):
386 return ValidRepoName(*args, **kwargs)
386 return ValidRepoName(*args, **kwargs)
387
387
388
388
389 def SlugifyName():
389 def SlugifyName():
390 class _validator(formencode.validators.FancyValidator):
390 class _validator(formencode.validators.FancyValidator):
391
391
392 def _to_python(self, value, state):
392 def _to_python(self, value, state):
393 return repo_name_slug(value)
393 return repo_name_slug(value)
394
394
395 def validate_python(self, value, state):
395 def validate_python(self, value, state):
396 pass
396 pass
397
397
398 return _validator
398 return _validator
399
399
400
400
401 def ValidCloneUri():
401 def ValidCloneUri():
402 from rhodecode.lib.utils import make_ui
402 from rhodecode.lib.utils import make_ui
403
403
404 def url_handler(repo_type, url, ui=None):
404 def url_handler(repo_type, url, ui=None):
405 if repo_type == 'hg':
405 if repo_type == 'hg':
406 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
406 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
407 from mercurial.httppeer import httppeer
407 from mercurial.httppeer import httppeer
408 if url.startswith('http'):
408 if url.startswith('http'):
409 ## initially check if it's at least the proper URL
409 ## initially check if it's at least the proper URL
410 ## or does it pass basic auth
410 ## or does it pass basic auth
411 MercurialRepository._check_url(url)
411 MercurialRepository._check_url(url)
412 httppeer(ui, url)._capabilities()
412 httppeer(ui, url)._capabilities()
413 elif url.startswith('svn+http'):
413 elif url.startswith('svn+http'):
414 from hgsubversion.svnrepo import svnremoterepo
414 from hgsubversion.svnrepo import svnremoterepo
415 svnremoterepo(ui, url).capabilities
415 svnremoterepo(ui, url).capabilities
416 elif url.startswith('git+http'):
416 elif url.startswith('git+http'):
417 raise NotImplementedError()
417 raise NotImplementedError()
418
418
419 elif repo_type == 'git':
419 elif repo_type == 'git':
420 from rhodecode.lib.vcs.backends.git.repository import GitRepository
420 from rhodecode.lib.vcs.backends.git.repository import GitRepository
421 if url.startswith('http'):
421 if url.startswith('http'):
422 ## initially check if it's at least the proper URL
422 ## initially check if it's at least the proper URL
423 ## or does it pass basic auth
423 ## or does it pass basic auth
424 GitRepository._check_url(url)
424 GitRepository._check_url(url)
425 elif url.startswith('svn+http'):
425 elif url.startswith('svn+http'):
426 raise NotImplementedError()
426 raise NotImplementedError()
427 elif url.startswith('hg+http'):
427 elif url.startswith('hg+http'):
428 raise NotImplementedError()
428 raise NotImplementedError()
429
429
430 class _validator(formencode.validators.FancyValidator):
430 class _validator(formencode.validators.FancyValidator):
431 messages = {
431 messages = {
432 'clone_uri': _(u'invalid clone url'),
432 'clone_uri': _(u'invalid clone url'),
433 'invalid_clone_uri': _(u'Invalid clone url, provide a '
433 'invalid_clone_uri': _(u'Invalid clone url, provide a '
434 'valid clone http(s)/svn+http(s) url')
434 'valid clone http(s)/svn+http(s) url')
435 }
435 }
436
436
437 def validate_python(self, value, state):
437 def validate_python(self, value, state):
438 repo_type = value.get('repo_type')
438 repo_type = value.get('repo_type')
439 url = value.get('clone_uri')
439 url = value.get('clone_uri')
440
440
441 if not url:
441 if not url:
442 pass
442 pass
443 else:
443 else:
444 try:
444 try:
445 url_handler(repo_type, url, make_ui('db', clear_session=False))
445 url_handler(repo_type, url, make_ui('db', clear_session=False))
446 except Exception:
446 except Exception:
447 log.exception('Url validation failed')
447 log.exception('Url validation failed')
448 msg = M(self, 'clone_uri')
448 msg = M(self, 'clone_uri')
449 raise formencode.Invalid(msg, value, state,
449 raise formencode.Invalid(msg, value, state,
450 error_dict=dict(clone_uri=msg)
450 error_dict=dict(clone_uri=msg)
451 )
451 )
452 return _validator
452 return _validator
453
453
454
454
455 def ValidForkType(old_data={}):
455 def ValidForkType(old_data={}):
456 class _validator(formencode.validators.FancyValidator):
456 class _validator(formencode.validators.FancyValidator):
457 messages = {
457 messages = {
458 'invalid_fork_type': _(u'Fork have to be the same type as parent')
458 'invalid_fork_type': _(u'Fork have to be the same type as parent')
459 }
459 }
460
460
461 def validate_python(self, value, state):
461 def validate_python(self, value, state):
462 if old_data['repo_type'] != value:
462 if old_data['repo_type'] != value:
463 msg = M(self, 'invalid_fork_type', state)
463 msg = M(self, 'invalid_fork_type', state)
464 raise formencode.Invalid(msg, value, state,
464 raise formencode.Invalid(msg, value, state,
465 error_dict=dict(repo_type=msg)
465 error_dict=dict(repo_type=msg)
466 )
466 )
467 return _validator
467 return _validator
468
468
469
469
470 def CanWriteGroup():
470 def CanWriteGroup():
471 class _validator(formencode.validators.FancyValidator):
471 class _validator(formencode.validators.FancyValidator):
472 messages = {
472 messages = {
473 'permission_denied': _(u"You don't have permissions "
473 'permission_denied': _(u"You don't have permissions "
474 "to create repository in this group")
474 "to create repository in this group")
475 }
475 }
476
476
477 def validate_python(self, value, state):
477 def validate_python(self, value, state):
478 gr = RepoGroup.get(value)
478 gr = RepoGroup.get(value)
479 if not HasReposGroupPermissionAny(
479 if not HasReposGroupPermissionAny(
480 'group.write', 'group.admin'
480 'group.write', 'group.admin'
481 )(gr.group_name, 'get group of repo form'):
481 )(gr.group_name, 'get group of repo form'):
482 msg = M(self, 'permission_denied', state)
482 msg = M(self, 'permission_denied', state)
483 raise formencode.Invalid(msg, value, state,
483 raise formencode.Invalid(msg, value, state,
484 error_dict=dict(repo_type=msg)
484 error_dict=dict(repo_type=msg)
485 )
485 )
486 return _validator
486 return _validator
487
487
488
488
489 def ValidPerms(type_='repo'):
489 def ValidPerms(type_='repo'):
490 if type_ == 'group':
490 if type_ == 'group':
491 EMPTY_PERM = 'group.none'
491 EMPTY_PERM = 'group.none'
492 elif type_ == 'repo':
492 elif type_ == 'repo':
493 EMPTY_PERM = 'repository.none'
493 EMPTY_PERM = 'repository.none'
494
494
495 class _validator(formencode.validators.FancyValidator):
495 class _validator(formencode.validators.FancyValidator):
496 messages = {
496 messages = {
497 'perm_new_member_name':
497 'perm_new_member_name':
498 _(u'This username or users group name is not valid')
498 _(u'This username or users group name is not valid')
499 }
499 }
500
500
501 def to_python(self, value, state):
501 def to_python(self, value, state):
502 perms_update = OrderedSet()
502 perms_update = OrderedSet()
503 perms_new = OrderedSet()
503 perms_new = OrderedSet()
504 # build a list of permission to update and new permission to create
504 # build a list of permission to update and new permission to create
505
505
506 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
506 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
507 new_perms_group = defaultdict(dict)
507 new_perms_group = defaultdict(dict)
508 for k, v in value.copy().iteritems():
508 for k, v in value.copy().iteritems():
509 if k.startswith('perm_new_member'):
509 if k.startswith('perm_new_member'):
510 del value[k]
510 del value[k]
511 _type, part = k.split('perm_new_member_')
511 _type, part = k.split('perm_new_member_')
512 args = part.split('_')
512 args = part.split('_')
513 if len(args) == 1:
513 if len(args) == 1:
514 new_perms_group[args[0]]['perm'] = v
514 new_perms_group[args[0]]['perm'] = v
515 elif len(args) == 2:
515 elif len(args) == 2:
516 _key, pos = args
516 _key, pos = args
517 new_perms_group[pos][_key] = v
517 new_perms_group[pos][_key] = v
518
518
519 # fill new permissions in order of how they were added
519 # fill new permissions in order of how they were added
520 for k in sorted(map(int, new_perms_group.keys())):
520 for k in sorted(map(int, new_perms_group.keys())):
521 perm_dict = new_perms_group[str(k)]
521 perm_dict = new_perms_group[str(k)]
522 new_member = perm_dict.get('name')
522 new_member = perm_dict.get('name')
523 new_perm = perm_dict.get('perm')
523 new_perm = perm_dict.get('perm')
524 new_type = perm_dict.get('type')
524 new_type = perm_dict.get('type')
525 if new_member and new_perm and new_type:
525 if new_member and new_perm and new_type:
526 perms_new.add((new_member, new_perm, new_type))
526 perms_new.add((new_member, new_perm, new_type))
527
527
528 for k, v in value.iteritems():
528 for k, v in value.iteritems():
529 if k.startswith('u_perm_') or k.startswith('g_perm_'):
529 if k.startswith('u_perm_') or k.startswith('g_perm_'):
530 member = k[7:]
530 member = k[7:]
531 t = {'u': 'user',
531 t = {'u': 'user',
532 'g': 'users_group'
532 'g': 'users_group'
533 }[k[0]]
533 }[k[0]]
534 if member == 'default':
534 if member == 'default':
535 if value.get('private'):
535 if value.get('private'):
536 # set none for default when updating to
536 # set none for default when updating to
537 # private repo
537 # private repo
538 v = EMPTY_PERM
538 v = EMPTY_PERM
539 perms_update.add((member, v, t))
539 perms_update.add((member, v, t))
540
540
541 value['perms_updates'] = list(perms_update)
541 value['perms_updates'] = list(perms_update)
542 value['perms_new'] = list(perms_new)
542 value['perms_new'] = list(perms_new)
543
543
544 # update permissions
544 # update permissions
545 for k, v, t in perms_new:
545 for k, v, t in perms_new:
546 try:
546 try:
547 if t is 'user':
547 if t is 'user':
548 self.user_db = User.query()\
548 self.user_db = User.query()\
549 .filter(User.active == True)\
549 .filter(User.active == True)\
550 .filter(User.username == k).one()
550 .filter(User.username == k).one()
551 if t is 'users_group':
551 if t is 'users_group':
552 self.user_db = UsersGroup.query()\
552 self.user_db = UsersGroup.query()\
553 .filter(UsersGroup.users_group_active == True)\
553 .filter(UsersGroup.users_group_active == True)\
554 .filter(UsersGroup.users_group_name == k).one()
554 .filter(UsersGroup.users_group_name == k).one()
555
555
556 except Exception:
556 except Exception:
557 log.exception('Updated permission failed')
557 log.exception('Updated permission failed')
558 msg = M(self, 'perm_new_member_type', state)
558 msg = M(self, 'perm_new_member_type', state)
559 raise formencode.Invalid(msg, value, state,
559 raise formencode.Invalid(msg, value, state,
560 error_dict=dict(perm_new_member_name=msg)
560 error_dict=dict(perm_new_member_name=msg)
561 )
561 )
562 return value
562 return value
563 return _validator
563 return _validator
564
564
565
565
566 def ValidSettings():
566 def ValidSettings():
567 class _validator(formencode.validators.FancyValidator):
567 class _validator(formencode.validators.FancyValidator):
568 def _to_python(self, value, state):
568 def _to_python(self, value, state):
569 # settings form for users that are not admin
569 # settings form for users that are not admin
570 # can't edit certain parameters, it's extra backup if they mangle
570 # can't edit certain parameters, it's extra backup if they mangle
571 # with forms
571 # with forms
572
572
573 forbidden_params = [
573 forbidden_params = [
574 'user', 'repo_type', 'repo_enable_locking',
574 'user', 'repo_type', 'repo_enable_locking',
575 'repo_enable_downloads', 'repo_enable_statistics'
575 'repo_enable_downloads', 'repo_enable_statistics'
576 ]
576 ]
577
577
578 for param in forbidden_params:
578 for param in forbidden_params:
579 if param in value:
579 if param in value:
580 del value[param]
580 del value[param]
581 return value
581 return value
582
582
583 def validate_python(self, value, state):
583 def validate_python(self, value, state):
584 pass
584 pass
585 return _validator
585 return _validator
586
586
587
587
588 def ValidPath():
588 def ValidPath():
589 class _validator(formencode.validators.FancyValidator):
589 class _validator(formencode.validators.FancyValidator):
590 messages = {
590 messages = {
591 'invalid_path': _(u'This is not a valid path')
591 'invalid_path': _(u'This is not a valid path')
592 }
592 }
593
593
594 def validate_python(self, value, state):
594 def validate_python(self, value, state):
595 if not os.path.isdir(value):
595 if not os.path.isdir(value):
596 msg = M(self, 'invalid_path', state)
596 msg = M(self, 'invalid_path', state)
597 raise formencode.Invalid(msg, value, state,
597 raise formencode.Invalid(msg, value, state,
598 error_dict=dict(paths_root_path=msg)
598 error_dict=dict(paths_root_path=msg)
599 )
599 )
600 return _validator
600 return _validator
601
601
602
602
603 def UniqSystemEmail(old_data={}):
603 def UniqSystemEmail(old_data={}):
604 class _validator(formencode.validators.FancyValidator):
604 class _validator(formencode.validators.FancyValidator):
605 messages = {
605 messages = {
606 'email_taken': _(u'This e-mail address is already taken')
606 'email_taken': _(u'This e-mail address is already taken')
607 }
607 }
608
608
609 def _to_python(self, value, state):
609 def _to_python(self, value, state):
610 return value.lower()
610 return value.lower()
611
611
612 def validate_python(self, value, state):
612 def validate_python(self, value, state):
613 if (old_data.get('email') or '').lower() != value:
613 if (old_data.get('email') or '').lower() != value:
614 user = User.get_by_email(value, case_insensitive=True)
614 user = User.get_by_email(value, case_insensitive=True)
615 if user:
615 if user:
616 msg = M(self, 'email_taken', state)
616 msg = M(self, 'email_taken', state)
617 raise formencode.Invalid(msg, value, state,
617 raise formencode.Invalid(msg, value, state,
618 error_dict=dict(email=msg)
618 error_dict=dict(email=msg)
619 )
619 )
620 return _validator
620 return _validator
621
621
622
622
623 def ValidSystemEmail():
623 def ValidSystemEmail():
624 class _validator(formencode.validators.FancyValidator):
624 class _validator(formencode.validators.FancyValidator):
625 messages = {
625 messages = {
626 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
626 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
627 }
627 }
628
628
629 def _to_python(self, value, state):
629 def _to_python(self, value, state):
630 return value.lower()
630 return value.lower()
631
631
632 def validate_python(self, value, state):
632 def validate_python(self, value, state):
633 user = User.get_by_email(value, case_insensitive=True)
633 user = User.get_by_email(value, case_insensitive=True)
634 if user is None:
634 if user is None:
635 msg = M(self, 'non_existing_email', state, email=value)
635 msg = M(self, 'non_existing_email', state, email=value)
636 raise formencode.Invalid(msg, value, state,
636 raise formencode.Invalid(msg, value, state,
637 error_dict=dict(email=msg)
637 error_dict=dict(email=msg)
638 )
638 )
639
639
640 return _validator
640 return _validator
641
641
642
642
643 def LdapLibValidator():
643 def LdapLibValidator():
644 class _validator(formencode.validators.FancyValidator):
644 class _validator(formencode.validators.FancyValidator):
645 messages = {
645 messages = {
646
646
647 }
647 }
648
648
649 def validate_python(self, value, state):
649 def validate_python(self, value, state):
650 try:
650 try:
651 import ldap
651 import ldap
652 ldap # pyflakes silence !
652 ldap # pyflakes silence !
653 except ImportError:
653 except ImportError:
654 raise LdapImportError()
654 raise LdapImportError()
655
655
656 return _validator
656 return _validator
657
657
658
658
659 def AttrLoginValidator():
659 def AttrLoginValidator():
660 class _validator(formencode.validators.FancyValidator):
660 class _validator(formencode.validators.FancyValidator):
661 messages = {
661 messages = {
662 'invalid_cn':
662 'invalid_cn':
663 _(u'The LDAP Login attribute of the CN must be specified - '
663 _(u'The LDAP Login attribute of the CN must be specified - '
664 'this is the name of the attribute that is equivalent '
664 'this is the name of the attribute that is equivalent '
665 'to "username"')
665 'to "username"')
666 }
666 }
667
667
668 def validate_python(self, value, state):
668 def validate_python(self, value, state):
669 if not value or not isinstance(value, (str, unicode)):
669 if not value or not isinstance(value, (str, unicode)):
670 msg = M(self, 'invalid_cn', state)
670 msg = M(self, 'invalid_cn', state)
671 raise formencode.Invalid(msg, value, state,
671 raise formencode.Invalid(msg, value, state,
672 error_dict=dict(ldap_attr_login=msg)
672 error_dict=dict(ldap_attr_login=msg)
673 )
673 )
674
674
675 return _validator
675 return _validator
676
676
677
677
678 def NotReviewedRevisions(repo_id):
678 def NotReviewedRevisions(repo_id):
679 class _validator(formencode.validators.FancyValidator):
679 class _validator(formencode.validators.FancyValidator):
680 messages = {
680 messages = {
681 'rev_already_reviewed':
681 'rev_already_reviewed':
682 _(u'Revisions %(revs)s are already part of pull request '
682 _(u'Revisions %(revs)s are already part of pull request '
683 'or have set status')
683 'or have set status')
684 }
684 }
685
685
686 def validate_python(self, value, state):
686 def validate_python(self, value, state):
687 # check revisions if they are not reviewed, or a part of another
687 # check revisions if they are not reviewed, or a part of another
688 # pull request
688 # pull request
689 statuses = ChangesetStatus.query()\
689 statuses = ChangesetStatus.query()\
690 .filter(ChangesetStatus.revision.in_(value))\
690 .filter(ChangesetStatus.revision.in_(value))\
691 .filter(ChangesetStatus.repo_id == repo_id)\
691 .filter(ChangesetStatus.repo_id == repo_id)\
692 .all()
692 .all()
693
693
694 errors = []
694 errors = []
695 for cs in statuses:
695 for cs in statuses:
696 if cs.pull_request_id:
696 if cs.pull_request_id:
697 errors.append(['pull_req', cs.revision[:12]])
697 errors.append(['pull_req', cs.revision[:12]])
698 elif cs.status:
698 elif cs.status:
699 errors.append(['status', cs.revision[:12]])
699 errors.append(['status', cs.revision[:12]])
700
700
701 if errors:
701 if errors:
702 revs = ','.join([x[1] for x in errors])
702 revs = ','.join([x[1] for x in errors])
703 msg = M(self, 'rev_already_reviewed', state, revs=revs)
703 msg = M(self, 'rev_already_reviewed', state, revs=revs)
704 raise formencode.Invalid(msg, value, state,
704 raise formencode.Invalid(msg, value, state,
705 error_dict=dict(revisions=revs)
705 error_dict=dict(revisions=revs)
706 )
706 )
707
707
708 return _validator
708 return _validator
709
709
710
710
711 def ValidIp():
711 def ValidIp():
712 class _validator(CIDR):
712 class _validator(CIDR):
713 messages = dict(
713 messages = dict(
714 badFormat=_('Please enter a valid IP address (a.b.c.d)'),
714 badFormat=_('Please enter a valid IP address (a.b.c.d)'),
715 illegalOctets=_('The octets must be within the range of 0-255'
715 illegalOctets=_('The octets must be within the range of 0-255'
716 ' (not %(octet)r)'),
716 ' (not %(octet)r)'),
717 illegalBits=_('The network size (bits) must be within the range'
717 illegalBits=_('The network size (bits) must be within the range'
718 ' of 0-32 (not %(bits)r)'))
718 ' of 0-32 (not %(bits)r)'))
719
719
720 def validate_python(self, value, state):
720 def validate_python(self, value, state):
721 try:
721 try:
722 # Split into octets and bits
722 # Split into octets and bits
723 if '/' in value: # a.b.c.d/e
723 if '/' in value: # a.b.c.d/e
724 addr, bits = value.split('/')
724 addr, bits = value.split('/')
725 else: # a.b.c.d
725 else: # a.b.c.d
726 addr, bits = value, 32
726 addr, bits = value, 32
727 # Use IPAddress validator to validate the IP part
727 # Use IPAddress validator to validate the IP part
728 IPAddress.validate_python(self, addr, state)
728 IPAddress.validate_python(self, addr, state)
729 # Bits (netmask) correct?
729 # Bits (netmask) correct?
730 if not 0 <= int(bits) <= 32:
730 if not 0 <= int(bits) <= 32:
731 raise formencode.Invalid(
731 raise formencode.Invalid(
732 self.message('illegalBits', state, bits=bits),
732 self.message('illegalBits', state, bits=bits),
733 value, state)
733 value, state)
734 # Splitting faild: wrong syntax
734 # Splitting faild: wrong syntax
735 except ValueError:
735 except ValueError:
736 raise formencode.Invalid(self.message('badFormat', state),
736 raise formencode.Invalid(self.message('badFormat', state),
737 value, state)
737 value, state)
738
738
739 def to_python(self, value, state):
739 def to_python(self, value, state):
740 v = super(_validator, self).to_python(value, state)
740 v = super(_validator, self).to_python(value, state)
741 #if IP doesn't end with a mask, add /32
741 #if IP doesn't end with a mask, add /32
742 if '/' not in value:
742 if '/' not in value:
743 v += '/32'
743 v += '/32'
744 return v
744 return v
745 return _validator
745 return _validator
@@ -1,56 +1,55 b''
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 ${_('Admin journal')} - ${c.rhodecode_name}
5 ${_('Admin journal')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 <form id="filter_form">
9 <form id="filter_form">
10 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
10 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
11 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
11 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
12 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
12 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
13 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
13 ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
14 </form>
14 </form>
15 ${h.end_form()}
15 ${h.end_form()}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('admin')}
19 ${self.menu('admin')}
20 </%def>
20 </%def>
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <!-- end box / title -->
27 <!-- end box / title -->
28 <div class="table">
28 <div class="table">
29 <div id="user_log">
29 <div id="user_log">
30 ${c.log_data}
30 ${c.log_data}
31 </div>
31 </div>
32 </div>
32 </div>
33 </div>
33 </div>
34
34
35 <script>
35 <script>
36 YUE.on('j_filter','click',function(){
36 YUE.on('j_filter','click',function(){
37 var jfilter = YUD.get('j_filter');
37 var jfilter = YUD.get('j_filter');
38 if(YUD.hasClass(jfilter, 'initial')){
38 if(YUD.hasClass(jfilter, 'initial')){
39 jfilter.value = '';
39 jfilter.value = '';
40 }
40 }
41 });
41 });
42 var fix_j_filter_width = function(len){
42 var fix_j_filter_width = function(len){
43 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
43 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
44 }
44 }
45 YUE.on('j_filter','keyup',function(){
45 YUE.on('j_filter','keyup',function(){
46 fix_j_filter_width(YUD.get('j_filter').value.length);
46 fix_j_filter_width(YUD.get('j_filter').value.length);
47 });
47 });
48 YUE.on('filter_form','submit',function(e){
48 YUE.on('filter_form','submit',function(e){
49 YUE.preventDefault(e)
49 YUE.preventDefault(e)
50 var val = YUD.get('j_filter').value;
50 var val = YUD.get('j_filter').value;
51 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
51 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
52 });
52 });
53 fix_j_filter_width(YUD.get('j_filter').value.length);
53 fix_j_filter_width(YUD.get('j_filter').value.length);
54 </script>
54 </script>
55 </%def>
55 </%def>
56
@@ -1,64 +1,64 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %if c.users_log:
2 %if c.users_log:
3 <table>
3 <table>
4 <tr>
4 <tr>
5 <th class="left">${_('Username')}</th>
5 <th class="left">${_('Username')}</th>
6 <th class="left">${_('Action')}</th>
6 <th class="left">${_('Action')}</th>
7 <th class="left">${_('Repository')}</th>
7 <th class="left">${_('Repository')}</th>
8 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Date')}</th>
9 <th class="left">${_('From IP')}</th>
9 <th class="left">${_('From IP')}</th>
10 </tr>
10 </tr>
11
11
12 %for cnt,l in enumerate(c.users_log):
12 %for cnt,l in enumerate(c.users_log):
13 <tr class="parity${cnt%2}">
13 <tr class="parity${cnt%2}">
14 <td>
14 <td>
15 %if l.user is not None:
15 %if l.user is not None:
16 ${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}
16 ${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}
17 %else:
17 %else:
18 ${l.username}
18 ${l.username}
19 %endif
19 %endif
20 </td>
20 </td>
21 <td>${h.action_parser(l)[0]()}
21 <td>${h.action_parser(l)[0]()}
22 <div class="journal_action_params">
22 <div class="journal_action_params">
23 ${h.literal(h.action_parser(l)[1]())}
23 ${h.literal(h.action_parser(l)[1]())}
24 </div>
24 </div>
25 </td>
25 </td>
26 <td>
26 <td>
27 %if l.repository is not None:
27 %if l.repository is not None:
28 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
28 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
29 %else:
29 %else:
30 ${l.repository_name}
30 ${l.repository_name}
31 %endif
31 %endif
32 </td>
32 </td>
33
33
34 <td>${h.fmt_date(l.action_date)}</td>
34 <td>${h.fmt_date(l.action_date)}</td>
35 <td>${l.user_ip}</td>
35 <td>${l.user_ip}</td>
36 </tr>
36 </tr>
37 %endfor
37 %endfor
38 </table>
38 </table>
39
39
40 <script type="text/javascript">
40 <script type="text/javascript">
41 YUE.onDOMReady(function(){
41 YUE.onDOMReady(function(){
42 YUE.delegate("user_log","click",function(e, matchedEl, container){
42 YUE.delegate("user_log","click",function(e, matchedEl, container){
43 ypjax(e.target.href,"user_log",function(){
43 ypjax(e.target.href,"user_log",function(){
44 show_more_event();
44 show_more_event();
45 tooltip_activate();
45 tooltip_activate();
46 show_changeset_tooltip();
46 show_changeset_tooltip();
47 });
47 });
48 YUE.preventDefault(e);
48 YUE.preventDefault(e);
49 },'.pager_link');
49 },'.pager_link');
50
50
51 YUE.delegate("user_log","click",function(e,matchedEl,container){
51 YUE.delegate("user_log","click",function(e,matchedEl,container){
52 var el = e.target;
52 var el = e.target;
53 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
53 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
54 YUD.setStyle(el.parentNode,'display','none');
54 YUD.setStyle(el.parentNode,'display','none');
55 },'.show_more');
55 },'.show_more');
56 });
56 });
57 </script>
57 </script>
58
58
59 <div class="pagination-wh pagination-left">
59 <div class="pagination-wh pagination-left">
60 ${c.users_log.pager('$link_previous ~2~ $link_next')}
60 ${c.users_log.pager('$link_previous ~2~ $link_next')}
61 </div>
61 </div>
62 %else:
62 %else:
63 ${_('No actions yet')}
63 ${_('No actions yet')}
64 %endif
64 %endif
@@ -1,122 +1,122 b''
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 var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}";
56 var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}";
57 </script>
57 </script>
58 <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script>
58 <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script>
59 <!--[if lt IE 9]>
59 <!--[if lt IE 9]>
60 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
60 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
61 <![endif]-->
61 <![endif]-->
62 <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.rhodecode_version)}"></script>
62 <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.rhodecode_version)}"></script>
63 <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.rhodecode_version)}"></script>
63 <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.rhodecode_version)}"></script>
64 <script type="text/javascript" src="${h.url('/js/rhodecode.js', ver=c.rhodecode_version)}"></script>
64 <script type="text/javascript" src="${h.url('/js/rhodecode.js', ver=c.rhodecode_version)}"></script>
65 ## EXTRA FOR JS
65 ## EXTRA FOR JS
66 ${self.js_extra()}
66 ${self.js_extra()}
67 <script type="text/javascript">
67 <script type="text/javascript">
68 (function(window,undefined){
68 (function(window,undefined){
69 // Prepare
69 // Prepare
70 var History = window.History; // Note: We are using a capital H instead of a lower h
70 var History = window.History; // Note: We are using a capital H instead of a lower h
71 if ( !History.enabled ) {
71 if ( !History.enabled ) {
72 // History.js is disabled for this browser.
72 // History.js is disabled for this browser.
73 // This is because we can optionally choose to support HTML4 browsers or not.
73 // This is because we can optionally choose to support HTML4 browsers or not.
74 return false;
74 return false;
75 }
75 }
76 })(window);
76 })(window);
77
77
78 YUE.onDOMReady(function(){
78 YUE.onDOMReady(function(){
79 tooltip_activate();
79 tooltip_activate();
80 show_more_event();
80 show_more_event();
81 show_changeset_tooltip();
81 show_changeset_tooltip();
82
82
83 YUE.on('quick_login_link','click',function(e){
83 YUE.on('quick_login_link','click',function(e){
84 // make sure we don't redirect
84 // make sure we don't redirect
85 YUE.preventDefault(e);
85 YUE.preventDefault(e);
86
86
87 if(YUD.hasClass('quick_login_link','enabled')){
87 if(YUD.hasClass('quick_login_link','enabled')){
88 YUD.setStyle('quick_login','display','none');
88 YUD.setStyle('quick_login','display','none');
89 YUD.removeClass('quick_login_link','enabled');
89 YUD.removeClass('quick_login_link','enabled');
90 }
90 }
91 else{
91 else{
92 YUD.setStyle('quick_login','display','');
92 YUD.setStyle('quick_login','display','');
93 YUD.addClass('quick_login_link','enabled');
93 YUD.addClass('quick_login_link','enabled');
94 var usr = YUD.get('username');
94 var usr = YUD.get('username');
95 if(usr){
95 if(usr){
96 usr.focus();
96 usr.focus();
97 }
97 }
98 }
98 }
99 });
99 });
100 })
100 })
101 </script>
101 </script>
102 </%def>
102 </%def>
103 <%def name="js_extra()"></%def>
103 <%def name="js_extra()"></%def>
104 ${self.js()}
104 ${self.js()}
105 <%def name="head_extra()"></%def>
105 <%def name="head_extra()"></%def>
106 ${self.head_extra()}
106 ${self.head_extra()}
107 </head>
107 </head>
108 <body id="body">
108 <body id="body">
109 ## IE hacks
109 ## IE hacks
110 <!--[if IE 7]>
110 <!--[if IE 7]>
111 <script>YUD.addClass(document.body,'ie7')</script>
111 <script>YUD.addClass(document.body,'ie7')</script>
112 <![endif]-->
112 <![endif]-->
113 <!--[if IE 8]>
113 <!--[if IE 8]>
114 <script>YUD.addClass(document.body,'ie8')</script>
114 <script>YUD.addClass(document.body,'ie8')</script>
115 <![endif]-->
115 <![endif]-->
116 <!--[if IE 9]>
116 <!--[if IE 9]>
117 <script>YUD.addClass(document.body,'ie9')</script>
117 <script>YUD.addClass(document.body,'ie9')</script>
118 <![endif]-->
118 <![endif]-->
119
119
120 ${next.body()}
120 ${next.body()}
121 </body>
121 </body>
122 </html>
122 </html>
@@ -1,202 +1,202 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <script>
27 <script>
28 var _USERS_AC_DATA = ${c.users_array|n};
28 var _USERS_AC_DATA = ${c.users_array|n};
29 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
29 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
30 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
30 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
31 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
31 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
32 </script>
32 </script>
33 <div class="table">
33 <div class="table">
34 <div class="diffblock">
34 <div class="diffblock">
35 <div class="parents">
35 <div class="parents">
36 %if c.changeset.parents:
36 %if c.changeset.parents:
37 %for n, p_cs in enumerate(reversed(c.changeset.parents)):
37 %for n, p_cs in enumerate(reversed(c.changeset.parents)):
38 <span class="changeset_hash">&laquo; ${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span>
38 <span class="changeset_hash">&laquo; ${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span>
39 <br>
39 <br>
40 %endfor
40 %endfor
41 %else:
41 %else:
42 <span>${_('No parents')}</span>
42 <span>${_('No parents')}</span>
43 %endif
43 %endif
44 </div>
44 </div>
45 <div class="children">
45 <div class="children">
46 %if c.changeset.children:
46 %if c.changeset.children:
47 %for n, p_cs in enumerate(reversed(c.changeset.children)):
47 %for n, p_cs in enumerate(reversed(c.changeset.children)):
48 <span class="changeset_hash">${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)} &raquo;</span>
48 <span class="changeset_hash">${h.link_to('%s:%s' % (p_cs.revision,p_cs.raw_id[:6]),h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)} &raquo;</span>
49 <br>
49 <br>
50 %endfor
50 %endfor
51 %else:
51 %else:
52 <span>${_('No children')}</span>
52 <span>${_('No children')}</span>
53 %endif
53 %endif
54 </div>
54 </div>
55 <div class="code-header banner">
55 <div class="code-header banner">
56
56
57 <div class="hash">
57 <div class="hash">
58 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
58 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
59 </div>
59 </div>
60 <div class="date">
60 <div class="date">
61 ${h.fmt_date(c.changeset.date)}
61 ${h.fmt_date(c.changeset.date)}
62 </div>
62 </div>
63 <div class="changeset-status-container">
63 <div class="changeset-status-container">
64 %if c.statuses:
64 %if c.statuses:
65 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
65 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
66 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
66 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
67 %endif
67 %endif
68 </div>
68 </div>
69 <div class="diff-actions">
69 <div class="diff-actions">
70 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
70 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
71 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('patch diff'))}"><img class="icon" src="${h.url('/images/icons/page_add.png')}"/></a>
71 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('patch diff'))}"><img class="icon" src="${h.url('/images/icons/page_add.png')}"/></a>
72 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
72 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
73 ${c.ignorews_url(request.GET)}
73 ${c.ignorews_url(request.GET)}
74 ${c.context_url(request.GET)}
74 ${c.context_url(request.GET)}
75 </div>
75 </div>
76 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
76 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
77 </div>
77 </div>
78 </div>
78 </div>
79 <div id="changeset_content">
79 <div id="changeset_content">
80 <div class="container">
80 <div class="container">
81 <div class="left">
81 <div class="left">
82 <div class="author">
82 <div class="author">
83 <div class="gravatar">
83 <div class="gravatar">
84 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
84 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
85 </div>
85 </div>
86 <span>${h.person(c.changeset.author)}</span><br/>
86 <span>${h.person(c.changeset.author)}</span><br/>
87 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
87 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
88 </div>
88 </div>
89 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
89 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
90 </div>
90 </div>
91 <div class="right">
91 <div class="right">
92 <div class="changes">
92 <div class="changes">
93 % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
93 % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
94 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
94 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
95 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
95 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
96 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
96 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
97 % else:
97 % else:
98 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
98 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
99 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
99 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
100 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
100 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
101 % endif
101 % endif
102 </div>
102 </div>
103
103
104 <span class="logtags">
104 <span class="logtags">
105 %if len(c.changeset.parents)>1:
105 %if len(c.changeset.parents)>1:
106 <span class="merge">${_('merge')}</span>
106 <span class="merge">${_('merge')}</span>
107 %endif
107 %endif
108 %if c.changeset.branch:
108 %if c.changeset.branch:
109 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
109 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
110 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
110 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
111 </span>
111 </span>
112 %endif
112 %endif
113 %for tag in c.changeset.tags:
113 %for tag in c.changeset.tags:
114 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
114 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
115 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
115 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
116 %endfor
116 %endfor
117 </span>
117 </span>
118 </div>
118 </div>
119 </div>
119 </div>
120 <span>
120 <span>
121 % if c.limited_diff:
121 % if c.limited_diff:
122 ${ungettext('%s file changed','%s files changed',len(c.changeset.affected_files)) % (len(c.changeset.affected_files))}:
122 ${ungettext('%s file changed','%s files changed',len(c.changeset.affected_files)) % (len(c.changeset.affected_files))}:
123 % else:
123 % else:
124 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.changeset.affected_files)) % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}:
124 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.changeset.affected_files)) % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}:
125 %endif
125 %endif
126 </span>
126 </span>
127 <div class="cs_files">
127 <div class="cs_files">
128 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
128 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
129 <div class="cs_${change}">
129 <div class="cs_${change}">
130 <div class="node">
130 <div class="node">
131 <a href="#${FID}">${h.safe_unicode(path)}</a>
131 <a href="#${FID}">${h.safe_unicode(path)}</a>
132 </div>
132 </div>
133 <div class="changes">${h.fancy_file_stats(stats)}</div>
133 <div class="changes">${h.fancy_file_stats(stats)}</div>
134 </div>
134 </div>
135 %endfor
135 %endfor
136 % if c.limited_diff:
136 % if c.limited_diff:
137 <h5>${_('Changeset was too big and was cut off...')}</h5>
137 <h5>${_('Changeset was too big and was cut off...')}</h5>
138 % endif
138 % endif
139 </div>
139 </div>
140 </div>
140 </div>
141
141
142 </div>
142 </div>
143
143
144 ## diff block
144 ## diff block
145 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
145 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
146 ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
146 ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
147
147
148 % if c.limited_diff:
148 % if c.limited_diff:
149 <h4>${_('Changeset was too big and was cut off...')}</h4>
149 <h4>${_('Changeset was too big and was cut off...')}</h4>
150 % endif
150 % endif
151
151
152 ## template for inline comment form
152 ## template for inline comment form
153 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
153 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
154 ${comment.comment_inline_form()}
154 ${comment.comment_inline_form()}
155
155
156 ## render comments and inlines
156 ## render comments and inlines
157 ${comment.generate_comments()}
157 ${comment.generate_comments()}
158
158
159 ## main comment form and it status
159 ## main comment form and it status
160 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
160 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
161 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
161 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
162
162
163 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
163 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
164 <script type="text/javascript">
164 <script type="text/javascript">
165 YUE.onDOMReady(function(){
165 YUE.onDOMReady(function(){
166 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
166 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
167 var show = 'none';
167 var show = 'none';
168 var target = e.currentTarget;
168 var target = e.currentTarget;
169 if(target == null){
169 if(target == null){
170 target = this;
170 target = this;
171 }
171 }
172 if(target.checked){
172 if(target.checked){
173 var show = ''
173 var show = ''
174 }
174 }
175 var boxid = YUD.getAttribute(target,'id_for');
175 var boxid = YUD.getAttribute(target,'id_for');
176 var comments = YUQ('#{0} .inline-comments'.format(boxid));
176 var comments = YUQ('#{0} .inline-comments'.format(boxid));
177 for(c in comments){
177 for(c in comments){
178 YUD.setStyle(comments[c],'display',show);
178 YUD.setStyle(comments[c],'display',show);
179 }
179 }
180 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
180 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
181 for(c in btns){
181 for(c in btns){
182 YUD.setStyle(btns[c],'display',show);
182 YUD.setStyle(btns[c],'display',show);
183 }
183 }
184 })
184 })
185
185
186 YUE.on(YUQ('.line'),'click',function(e){
186 YUE.on(YUQ('.line'),'click',function(e){
187 var tr = e.currentTarget;
187 var tr = e.currentTarget;
188 if(tr == null){
188 if(tr == null){
189 tr = this;
189 tr = this;
190 }
190 }
191 injectInlineForm(tr);
191 injectInlineForm(tr);
192 });
192 });
193
193
194 // inject comments into they proper positions
194 // inject comments into they proper positions
195 var file_comments = YUQ('.inline-comment-placeholder');
195 var file_comments = YUQ('.inline-comment-placeholder');
196 renderInlineComments(file_comments);
196 renderInlineComments(file_comments);
197 })
197 })
198
198
199 </script>
199 </script>
200
200
201 </div>
201 </div>
202 </%def>
202 </%def>
@@ -1,176 +1,176 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 ## ${comment.comment_block(co)}
4 ## ${comment.comment_block(co)}
5 ##
5 ##
6 <%def name="comment_block(co)">
6 <%def name="comment_block(co)">
7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
8 <div class="comment-wrapp">
8 <div class="comment-wrapp">
9 <div class="meta">
9 <div class="meta">
10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
11 <div class="user">
11 <div class="user">
12 ${co.author.username}
12 ${co.author.username}
13 </div>
13 </div>
14 <div class="date">
14 <div class="date">
15 ${h.age(co.modified_at)} <a class="permalink" href="#comment-${co.comment_id}">&para;</a>
15 ${h.age(co.modified_at)} <a class="permalink" href="#comment-${co.comment_id}">&para;</a>
16 </div>
16 </div>
17 %if co.status_change:
17 %if co.status_change:
18 <div style="float:left" class="changeset-status-container">
18 <div style="float:left" class="changeset-status-container">
19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div>
20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div>
21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div>
21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div>
22 </div>
22 </div>
23 %endif
23 %endif
24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
25 <div class="buttons">
25 <div class="buttons">
26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
27 </div>
27 </div>
28 %endif
28 %endif
29 </div>
29 </div>
30 <div class="text">
30 <div class="text">
31 ${h.rst_w_mentions(co.text)|n}
31 ${h.rst_w_mentions(co.text)|n}
32 </div>
32 </div>
33 </div>
33 </div>
34 </div>
34 </div>
35 </%def>
35 </%def>
36
36
37
37
38 <%def name="comment_inline_form()">
38 <%def name="comment_inline_form()">
39 <div id='comment-inline-form-template' style="display:none">
39 <div id='comment-inline-form-template' style="display:none">
40 <div class="comment-inline-form ac">
40 <div class="comment-inline-form ac">
41 %if c.rhodecode_user.username != 'default':
41 %if c.rhodecode_user.username != 'default':
42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
43 ${h.form('#', class_='inline-form')}
43 ${h.form('#', class_='inline-form')}
44 <div class="clearfix">
44 <div class="clearfix">
45 <div class="comment-help">${_('Commenting on line {1}.')}
45 <div class="comment-help">${_('Commenting on line {1}.')}
46 ${(_('Comments parsed using %s syntax with %s support.') % (
46 ${(_('Comments parsed using %s syntax with %s support.') % (
47 ('<a href="%s">RST</a>' % h.url('rst_help')),
47 ('<a href="%s">RST</a>' % h.url('rst_help')),
48 ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
48 ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
49 )
49 )
50 )|n
50 )|n
51 }
51 }
52 </div>
52 </div>
53 <div class="mentions-container" id="mentions_container_{1}"></div>
53 <div class="mentions-container" id="mentions_container_{1}"></div>
54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
55 </div>
55 </div>
56 <div class="comment-button">
56 <div class="comment-button">
57 <input type="hidden" name="f_path" value="{0}">
57 <input type="hidden" name="f_path" value="{0}">
58 <input type="hidden" name="line" value="{1}">
58 <input type="hidden" name="line" value="{1}">
59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
61 </div>
61 </div>
62 ${h.end_form()}
62 ${h.end_form()}
63 %else:
63 %else:
64 ${h.form('')}
64 ${h.form('')}
65 <div class="clearfix">
65 <div class="clearfix">
66 <div class="comment-help">
66 <div class="comment-help">
67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
68 </div>
68 </div>
69 </div>
69 </div>
70 <div class="comment-button">
70 <div class="comment-button">
71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
72 </div>
72 </div>
73 ${h.end_form()}
73 ${h.end_form()}
74 %endif
74 %endif
75 </div>
75 </div>
76 </div>
76 </div>
77 </%def>
77 </%def>
78
78
79
79
80 ## generates inlines taken from c.comments var
80 ## generates inlines taken from c.comments var
81 <%def name="inlines()">
81 <%def name="inlines()">
82 <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
82 <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
83 %for path, lines in c.inline_comments:
83 %for path, lines in c.inline_comments:
84 % for line,comments in lines.iteritems():
84 % for line,comments in lines.iteritems():
85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
86 %for co in comments:
86 %for co in comments:
87 ${comment_block(co)}
87 ${comment_block(co)}
88 %endfor
88 %endfor
89 </div>
89 </div>
90 %endfor
90 %endfor
91 %endfor
91 %endfor
92
92
93 </%def>
93 </%def>
94
94
95 ## generate inline comments and the main ones
95 ## generate inline comments and the main ones
96 <%def name="generate_comments()">
96 <%def name="generate_comments()">
97 <div class="comments">
97 <div class="comments">
98 <div id="inline-comments-container">
98 <div id="inline-comments-container">
99 ## generate inlines for this changeset
99 ## generate inlines for this changeset
100 ${inlines()}
100 ${inlines()}
101 </div>
101 </div>
102
102
103 %for co in c.comments:
103 %for co in c.comments:
104 <div id="comment-tr-${co.comment_id}">
104 <div id="comment-tr-${co.comment_id}">
105 ${comment_block(co)}
105 ${comment_block(co)}
106 </div>
106 </div>
107 %endfor
107 %endfor
108 </div>
108 </div>
109 </%def>
109 </%def>
110
110
111 ## MAIN COMMENT FORM
111 ## MAIN COMMENT FORM
112 <%def name="comments(post_url, cur_status, close_btn=False, change_status=True)">
112 <%def name="comments(post_url, cur_status, close_btn=False, change_status=True)">
113
113
114 <div class="comments">
114 <div class="comments">
115 %if c.rhodecode_user.username != 'default':
115 %if c.rhodecode_user.username != 'default':
116 <div class="comment-form ac">
116 <div class="comment-form ac">
117 ${h.form(post_url)}
117 ${h.form(post_url)}
118 <strong>${_('Leave a comment')}</strong>
118 <strong>${_('Leave a comment')}</strong>
119 <div class="clearfix">
119 <div class="clearfix">
120 <div class="comment-help">
120 <div class="comment-help">
121 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
121 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
122 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
122 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
123 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
123 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
124 %if change_status:
124 %if change_status:
125 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
125 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
126 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
126 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
127 %endif
127 %endif
128 </div>
128 </div>
129 %if change_status:
129 %if change_status:
130 <div id="status_block_container" class="status-block" style="display:none">
130 <div id="status_block_container" class="status-block" style="display:none">
131 %for status,lbl in c.changeset_statuses:
131 %for status,lbl in c.changeset_statuses:
132 <div class="">
132 <div class="">
133 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
133 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
134 <label for="${status}">${lbl}</label>
134 <label for="${status}">${lbl}</label>
135 </div>
135 </div>
136 %endfor
136 %endfor
137 </div>
137 </div>
138 %endif
138 %endif
139 <div class="mentions-container" id="mentions_container"></div>
139 <div class="mentions-container" id="mentions_container"></div>
140 ${h.textarea('text')}
140 ${h.textarea('text')}
141 </div>
141 </div>
142 <div class="comment-button">
142 <div class="comment-button">
143 ${h.submit('save', _('Comment'), class_="ui-btn large")}
143 ${h.submit('save', _('Comment'), class_="ui-btn large")}
144 %if close_btn and change_status:
144 %if close_btn and change_status:
145 ${h.submit('save_close', _('Comment and close'), class_='ui-btn blue large %s' % ('hidden' if cur_status in ['not_reviewed','under_review'] else ''))}
145 ${h.submit('save_close', _('Comment and close'), class_='ui-btn blue large %s' % ('hidden' if cur_status in ['not_reviewed','under_review'] else ''))}
146 %endif
146 %endif
147 </div>
147 </div>
148 ${h.end_form()}
148 ${h.end_form()}
149 </div>
149 </div>
150 %endif
150 %endif
151 </div>
151 </div>
152 <script>
152 <script>
153 YUE.onDOMReady(function () {
153 YUE.onDOMReady(function () {
154 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
154 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
155
155
156 // changeset status box listener
156 // changeset status box listener
157 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
157 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
158 if(e.currentTarget.checked){
158 if(e.currentTarget.checked){
159 YUD.setStyle('status_block_container','display','');
159 YUD.setStyle('status_block_container','display','');
160 }
160 }
161 else{
161 else{
162 YUD.setStyle('status_block_container','display','none');
162 YUD.setStyle('status_block_container','display','none');
163 }
163 }
164 })
164 })
165 YUE.on(YUQ('.status_change_radio'), 'change',function(e){
165 YUE.on(YUQ('.status_change_radio'), 'change',function(e){
166 var val = e.currentTarget.value;
166 var val = e.currentTarget.value;
167 if (val == 'approved' || val == 'rejected') {
167 if (val == 'approved' || val == 'rejected') {
168 YUD.removeClass('save_close', 'hidden');
168 YUD.removeClass('save_close', 'hidden');
169 }else{
169 }else{
170 YUD.addClass('save_close', 'hidden');
170 YUD.addClass('save_close', 'hidden');
171 }
171 }
172 })
172 })
173
173
174 });
174 });
175 </script>
175 </script>
176 </%def>
176 </%def>
@@ -1,19 +1,17 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3
3
4 ${_('User %s opened pull request for repository %s and wants you to review changes.') % (('<b>%s</b>' % pr_user_created),pr_repo_url) |n}
4 ${_('User %s opened pull request for repository %s and wants you to review changes.') % (('<b>%s</b>' % pr_user_created),pr_repo_url) |n}
5 <div>${_('title')}: ${pr_title}</div>
5 <div>${_('title')}: ${pr_title}</div>
6 <div>${_('description')}:</div>
6 <div>${_('description')}:</div>
7 <div>${_('View this pull request here')}: ${pr_url}</div>
7 <div>${_('View this pull request here')}: ${pr_url}</div>
8 <p>
8 <p>
9 ${body}
9 ${body}
10 </p>
10 </p>
11
11
12 <div>${_('revisions for reviewing')}</div>
12 <div>${_('revisions for reviewing')}</div>
13 <ul>
13 <ul>
14 %for r in pr_revisions:
14 %for r in pr_revisions:
15 <li>${r}</li>
15 <li>${r}</li>
16 %endfor
16 %endfor
17 </ul>
17 </ul>
18
19
@@ -1,334 +1,334 b''
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 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
10 <ul class="links">
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 %else:
14 %else:
15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 %endif
16 %endif
17 </li>
17 </li>
18 </ul>
18 </ul>
19 %endif
19 %endif
20 %endif
20 %endif
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23 <div class="table">
23 <div class="table">
24 % if c.groups:
24 % if c.groups:
25 <div id='groups_list_wrap' class="yui-skin-sam">
25 <div id='groups_list_wrap' class="yui-skin-sam">
26 <table id="groups_list">
26 <table id="groups_list">
27 <thead>
27 <thead>
28 <tr>
28 <tr>
29 <th class="left"><a href="#">${_('Group name')}</a></th>
29 <th class="left"><a href="#">${_('Group name')}</a></th>
30 <th class="left"><a href="#">${_('Description')}</a></th>
30 <th class="left"><a href="#">${_('Description')}</a></th>
31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 </tr>
32 </tr>
33 </thead>
33 </thead>
34
34
35 ## REPO GROUPS
35 ## REPO GROUPS
36 % for gr in c.groups:
36 % for gr in c.groups:
37 <tr>
37 <tr>
38 <td>
38 <td>
39 <div style="white-space: nowrap">
39 <div style="white-space: nowrap">
40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 </div>
42 </div>
43 </td>
43 </td>
44 %if c.visual.stylify_metatags:
44 %if c.visual.stylify_metatags:
45 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
45 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
46 %else:
46 %else:
47 <td>${gr.group_description}</td>
47 <td>${gr.group_description}</td>
48 %endif
48 %endif
49 ## this is commented out since for multi nested repos can be HEAVY!
49 ## this is commented out since for multi nested repos can be HEAVY!
50 ## in number of executed queries during traversing uncomment at will
50 ## in number of executed queries during traversing uncomment at will
51 ##<td><b>${gr.repositories_recursive_count}</b></td>
51 ##<td><b>${gr.repositories_recursive_count}</b></td>
52 </tr>
52 </tr>
53 % endfor
53 % endfor
54
54
55 </table>
55 </table>
56 </div>
56 </div>
57 <div style="height: 20px"></div>
57 <div style="height: 20px"></div>
58 % endif
58 % endif
59 <div id="welcome" style="display:none;text-align:center">
59 <div id="welcome" style="display:none;text-align:center">
60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
61 </div>
61 </div>
62 <%cnt=0%>
62 <%cnt=0%>
63 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
63 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
64 % if c.visual.lightweight_dashboard is False:
64 % if c.visual.lightweight_dashboard is False:
65 ## old full detailed version
65 ## old full detailed version
66 <div id='repos_list_wrap' class="yui-skin-sam">
66 <div id='repos_list_wrap' class="yui-skin-sam">
67 <table id="repos_list">
67 <table id="repos_list">
68 <thead>
68 <thead>
69 <tr>
69 <tr>
70 <th class="left"></th>
70 <th class="left"></th>
71 <th class="left">${_('Name')}</th>
71 <th class="left">${_('Name')}</th>
72 <th class="left">${_('Description')}</th>
72 <th class="left">${_('Description')}</th>
73 <th class="left">${_('Last change')}</th>
73 <th class="left">${_('Last change')}</th>
74 <th class="left">${_('Tip')}</th>
74 <th class="left">${_('Tip')}</th>
75 <th class="left">${_('Owner')}</th>
75 <th class="left">${_('Owner')}</th>
76 <th class="left">${_('RSS')}</th>
76 <th class="left">${_('RSS')}</th>
77 <th class="left">${_('Atom')}</th>
77 <th class="left">${_('Atom')}</th>
78 </tr>
78 </tr>
79 </thead>
79 </thead>
80 <tbody>
80 <tbody>
81 %for cnt,repo in enumerate(c.repos_list):
81 %for cnt,repo in enumerate(c.repos_list):
82 <tr class="parity${(cnt+1)%2}">
82 <tr class="parity${(cnt+1)%2}">
83 ##QUICK MENU
83 ##QUICK MENU
84 <td class="quick_repo_menu">
84 <td class="quick_repo_menu">
85 ${dt.quick_menu(repo['name'])}
85 ${dt.quick_menu(repo['name'])}
86 </td>
86 </td>
87 ##REPO NAME AND ICONS
87 ##REPO NAME AND ICONS
88 <td class="reponame">
88 <td class="reponame">
89 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
89 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
90 </td>
90 </td>
91 ##DESCRIPTION
91 ##DESCRIPTION
92 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
92 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
93 %if c.visual.stylify_metatags:
93 %if c.visual.stylify_metatags:
94 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
94 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
95 %else:
95 %else:
96 ${h.truncate(repo['description'],60)}</span>
96 ${h.truncate(repo['description'],60)}</span>
97 %endif
97 %endif
98 </td>
98 </td>
99 ##LAST CHANGE DATE
99 ##LAST CHANGE DATE
100 <td>
100 <td>
101 ${dt.last_change(repo['last_change'])}
101 ${dt.last_change(repo['last_change'])}
102 </td>
102 </td>
103 ##LAST REVISION
103 ##LAST REVISION
104 <td>
104 <td>
105 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
105 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
106 </td>
106 </td>
107 ##
107 ##
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
109 <td>
109 <td>
110 ${dt.rss(repo['name'])}
110 ${dt.rss(repo['name'])}
111 </td>
111 </td>
112 <td>
112 <td>
113 ${dt.atom(repo['name'])}
113 ${dt.atom(repo['name'])}
114 </td>
114 </td>
115 </tr>
115 </tr>
116 %endfor
116 %endfor
117 </tbody>
117 </tbody>
118 </table>
118 </table>
119 </div>
119 </div>
120 % else:
120 % else:
121 ## lightweight version
121 ## lightweight version
122 <div class="yui-skin-sam" id="repos_list_wrap"></div>
122 <div class="yui-skin-sam" id="repos_list_wrap"></div>
123 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
123 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
124 % endif
124 % endif
125 </div>
125 </div>
126 </div>
126 </div>
127 % if c.visual.lightweight_dashboard is False:
127 % if c.visual.lightweight_dashboard is False:
128 <script>
128 <script>
129 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
129 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
130 var func = function(node){
130 var func = function(node){
131 return node.parentNode.parentNode.parentNode.parentNode;
131 return node.parentNode.parentNode.parentNode.parentNode;
132 }
132 }
133
133
134 // groups table sorting
134 // groups table sorting
135 var myColumnDefs = [
135 var myColumnDefs = [
136 {key:"name",label:"${_('Group name')}",sortable:true,
136 {key:"name",label:"${_('Group name')}",sortable:true,
137 sortOptions: { sortFunction: groupNameSort }},
137 sortOptions: { sortFunction: groupNameSort }},
138 {key:"desc",label:"${_('Description')}",sortable:true},
138 {key:"desc",label:"${_('Description')}",sortable:true},
139 ];
139 ];
140
140
141 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
141 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
142
142
143 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
143 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
144 myDataSource.responseSchema = {
144 myDataSource.responseSchema = {
145 fields: [
145 fields: [
146 {key:"name"},
146 {key:"name"},
147 {key:"desc"},
147 {key:"desc"},
148 ]
148 ]
149 };
149 };
150
150
151 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
151 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
152 sortedBy:{key:"name",dir:"asc"},
152 sortedBy:{key:"name",dir:"asc"},
153 paginator: new YAHOO.widget.Paginator({
153 paginator: new YAHOO.widget.Paginator({
154 rowsPerPage: 5,
154 rowsPerPage: 5,
155 alwaysVisible: false,
155 alwaysVisible: false,
156 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
156 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
157 pageLinks: 5,
157 pageLinks: 5,
158 containerClass: 'pagination-wh',
158 containerClass: 'pagination-wh',
159 currentPageClass: 'pager_curpage',
159 currentPageClass: 'pager_curpage',
160 pageLinkClass: 'pager_link',
160 pageLinkClass: 'pager_link',
161 nextPageLinkLabel: '&gt;',
161 nextPageLinkLabel: '&gt;',
162 previousPageLinkLabel: '&lt;',
162 previousPageLinkLabel: '&lt;',
163 firstPageLinkLabel: '&lt;&lt;',
163 firstPageLinkLabel: '&lt;&lt;',
164 lastPageLinkLabel: '&gt;&gt;',
164 lastPageLinkLabel: '&gt;&gt;',
165 containers:['user-paginator']
165 containers:['user-paginator']
166 }),
166 }),
167 MSG_SORTASC:"${_('Click to sort ascending')}",
167 MSG_SORTASC:"${_('Click to sort ascending')}",
168 MSG_SORTDESC:"${_('Click to sort descending')}"
168 MSG_SORTDESC:"${_('Click to sort descending')}"
169 });
169 });
170
170
171 // main table sorting
171 // main table sorting
172 var myColumnDefs = [
172 var myColumnDefs = [
173 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
173 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
174 {key:"name",label:"${_('Name')}",sortable:true,
174 {key:"name",label:"${_('Name')}",sortable:true,
175 sortOptions: { sortFunction: nameSort }},
175 sortOptions: { sortFunction: nameSort }},
176 {key:"desc",label:"${_('Description')}",sortable:true},
176 {key:"desc",label:"${_('Description')}",sortable:true},
177 {key:"last_change",label:"${_('Last Change')}",sortable:true,
177 {key:"last_change",label:"${_('Last Change')}",sortable:true,
178 sortOptions: { sortFunction: ageSort }},
178 sortOptions: { sortFunction: ageSort }},
179 {key:"tip",label:"${_('Tip')}",sortable:true,
179 {key:"tip",label:"${_('Tip')}",sortable:true,
180 sortOptions: { sortFunction: revisionSort }},
180 sortOptions: { sortFunction: revisionSort }},
181 {key:"owner",label:"${_('Owner')}",sortable:true},
181 {key:"owner",label:"${_('Owner')}",sortable:true},
182 {key:"rss",label:"",sortable:false},
182 {key:"rss",label:"",sortable:false},
183 {key:"atom",label:"",sortable:false},
183 {key:"atom",label:"",sortable:false},
184 ];
184 ];
185
185
186 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
186 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
187
187
188 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
188 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
189
189
190 myDataSource.responseSchema = {
190 myDataSource.responseSchema = {
191 fields: [
191 fields: [
192 {key:"menu"},
192 {key:"menu"},
193 //{key:"raw_name"},
193 //{key:"raw_name"},
194 {key:"name"},
194 {key:"name"},
195 {key:"desc"},
195 {key:"desc"},
196 {key:"last_change"},
196 {key:"last_change"},
197 {key:"tip"},
197 {key:"tip"},
198 {key:"owner"},
198 {key:"owner"},
199 {key:"rss"},
199 {key:"rss"},
200 {key:"atom"},
200 {key:"atom"},
201 ]
201 ]
202 };
202 };
203
203
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
205 {
205 {
206 sortedBy:{key:"name",dir:"asc"},
206 sortedBy:{key:"name",dir:"asc"},
207 MSG_SORTASC:"${_('Click to sort ascending')}",
207 MSG_SORTASC:"${_('Click to sort ascending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
209 MSG_EMPTY:"${_('No records found.')}",
209 MSG_EMPTY:"${_('No records found.')}",
210 MSG_ERROR:"${_('Data error.')}",
210 MSG_ERROR:"${_('Data error.')}",
211 MSG_LOADING:"${_('Loading...')}",
211 MSG_LOADING:"${_('Loading...')}",
212 }
212 }
213 );
213 );
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
215 tooltip_activate();
215 tooltip_activate();
216 quick_repo_menu();
216 quick_repo_menu();
217 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
217 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
218 });
218 });
219
219
220 </script>
220 </script>
221 % else:
221 % else:
222 <script>
222 <script>
223 //var url = "${h.url('formatted_users', format='json')}";
223 //var url = "${h.url('formatted_users', format='json')}";
224 var data = ${c.data|n};
224 var data = ${c.data|n};
225 var myDataSource = new YAHOO.util.DataSource(data);
225 var myDataSource = new YAHOO.util.DataSource(data);
226 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
226 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
227
227
228 myDataSource.responseSchema = {
228 myDataSource.responseSchema = {
229 resultsList: "records",
229 resultsList: "records",
230 fields: [
230 fields: [
231 {key:"menu"},
231 {key:"menu"},
232 {key:"raw_name"},
232 {key:"raw_name"},
233 {key:"name"},
233 {key:"name"},
234 {key:"desc"},
234 {key:"desc"},
235 {key:"last_change"},
235 {key:"last_change"},
236 {key: "tip"},
236 {key: "tip"},
237 {key:"owner"},
237 {key:"owner"},
238 {key:"rss"},
238 {key:"rss"},
239 {key:"atom"},
239 {key:"atom"},
240 ]
240 ]
241 };
241 };
242 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
242 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
243 // This is the filter function
243 // This is the filter function
244 var data = res.results || [],
244 var data = res.results || [],
245 filtered = [],
245 filtered = [],
246 i,l;
246 i,l;
247
247
248 if (req) {
248 if (req) {
249 req = req.toLowerCase();
249 req = req.toLowerCase();
250 for (i = 0; i<data.length; i++) {
250 for (i = 0; i<data.length; i++) {
251 var pos = data[i].raw_name.toLowerCase().indexOf(req)
251 var pos = data[i].raw_name.toLowerCase().indexOf(req)
252 if (pos != -1) {
252 if (pos != -1) {
253 filtered.push(data[i]);
253 filtered.push(data[i]);
254 }
254 }
255 }
255 }
256 res.results = filtered;
256 res.results = filtered;
257 }
257 }
258 YUD.get('repo_count').innerHTML = res.results.length;
258 YUD.get('repo_count').innerHTML = res.results.length;
259 return res;
259 return res;
260 }
260 }
261
261
262 // main table sorting
262 // main table sorting
263 var myColumnDefs = [
263 var myColumnDefs = [
264 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
264 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
265 {key:"name",label:"${_('Name')}",sortable:true,
265 {key:"name",label:"${_('Name')}",sortable:true,
266 sortOptions: { sortFunction: nameSort }},
266 sortOptions: { sortFunction: nameSort }},
267 {key:"desc",label:"${_('Description')}",sortable:true},
267 {key:"desc",label:"${_('Description')}",sortable:true},
268 {key:"last_change",label:"${_('Last Change')}",sortable:true,
268 {key:"last_change",label:"${_('Last Change')}",sortable:true,
269 sortOptions: { sortFunction: ageSort }},
269 sortOptions: { sortFunction: ageSort }},
270 {key:"tip",label:"${_('Tip')}",sortable:true,
270 {key:"tip",label:"${_('Tip')}",sortable:true,
271 sortOptions: { sortFunction: revisionSort }},
271 sortOptions: { sortFunction: revisionSort }},
272 {key:"owner",label:"${_('Owner')}",sortable:true},
272 {key:"owner",label:"${_('Owner')}",sortable:true},
273 {key:"rss",label:"",sortable:false},
273 {key:"rss",label:"",sortable:false},
274 {key:"atom",label:"",sortable:false},
274 {key:"atom",label:"",sortable:false},
275 ];
275 ];
276
276
277 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
277 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
278 sortedBy:{key:"name",dir:"asc"},
278 sortedBy:{key:"name",dir:"asc"},
279 paginator: new YAHOO.widget.Paginator({
279 paginator: new YAHOO.widget.Paginator({
280 rowsPerPage: ${c.visual.lightweight_dashboard_items},
280 rowsPerPage: ${c.visual.lightweight_dashboard_items},
281 alwaysVisible: false,
281 alwaysVisible: false,
282 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
282 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
283 pageLinks: 5,
283 pageLinks: 5,
284 containerClass: 'pagination-wh',
284 containerClass: 'pagination-wh',
285 currentPageClass: 'pager_curpage',
285 currentPageClass: 'pager_curpage',
286 pageLinkClass: 'pager_link',
286 pageLinkClass: 'pager_link',
287 nextPageLinkLabel: '&gt;',
287 nextPageLinkLabel: '&gt;',
288 previousPageLinkLabel: '&lt;',
288 previousPageLinkLabel: '&lt;',
289 firstPageLinkLabel: '&lt;&lt;',
289 firstPageLinkLabel: '&lt;&lt;',
290 lastPageLinkLabel: '&gt;&gt;',
290 lastPageLinkLabel: '&gt;&gt;',
291 containers:['user-paginator']
291 containers:['user-paginator']
292 }),
292 }),
293
293
294 MSG_SORTASC:"${_('Click to sort ascending')}",
294 MSG_SORTASC:"${_('Click to sort ascending')}",
295 MSG_SORTDESC:"${_('Click to sort descending')}",
295 MSG_SORTDESC:"${_('Click to sort descending')}",
296 MSG_EMPTY:"${_('No records found.')}",
296 MSG_EMPTY:"${_('No records found.')}",
297 MSG_ERROR:"${_('Data error.')}",
297 MSG_ERROR:"${_('Data error.')}",
298 MSG_LOADING:"${_('Loading...')}",
298 MSG_LOADING:"${_('Loading...')}",
299 }
299 }
300 );
300 );
301 myDataTable.subscribe('postRenderEvent',function(oArgs) {
301 myDataTable.subscribe('postRenderEvent',function(oArgs) {
302 tooltip_activate();
302 tooltip_activate();
303 quick_repo_menu();
303 quick_repo_menu();
304 });
304 });
305
305
306 var filterTimeout = null;
306 var filterTimeout = null;
307
307
308 updateFilter = function () {
308 updateFilter = function () {
309 // Reset timeout
309 // Reset timeout
310 filterTimeout = null;
310 filterTimeout = null;
311
311
312 // Reset sort
312 // Reset sort
313 var state = myDataTable.getState();
313 var state = myDataTable.getState();
314 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
314 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
315
315
316 // Get filtered data
316 // Get filtered data
317 myDataSource.sendRequest(YUD.get('q_filter').value,{
317 myDataSource.sendRequest(YUD.get('q_filter').value,{
318 success : myDataTable.onDataReturnInitializeTable,
318 success : myDataTable.onDataReturnInitializeTable,
319 failure : myDataTable.onDataReturnInitializeTable,
319 failure : myDataTable.onDataReturnInitializeTable,
320 scope : myDataTable,
320 scope : myDataTable,
321 argument: state
321 argument: state
322 });
322 });
323
323
324 };
324 };
325 YUE.on('q_filter','click',function(){
325 YUE.on('q_filter','click',function(){
326 YUD.get('q_filter').value = '';
326 YUD.get('q_filter').value = '';
327 });
327 });
328
328
329 YUE.on('q_filter','keyup',function (e) {
329 YUE.on('q_filter','keyup',function (e) {
330 clearTimeout(filterTimeout);
330 clearTimeout(filterTimeout);
331 filterTimeout = setTimeout(updateFilter,600);
331 filterTimeout = setTimeout(updateFilter,600);
332 });
332 });
333 </script>
333 </script>
334 % endif
334 % endif
@@ -1,241 +1,241 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Journal')} - ${c.rhodecode_name}
4 ${_('Journal')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 <h5>
7 <h5>
8 <form id="filter_form">
8 <form id="filter_form">
9 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
9 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
10 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
10 <span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
11 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
11 <input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
12 ${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
12 ${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
13 </form>
13 </form>
14 ${h.end_form()}
14 ${h.end_form()}
15 </h5>
15 </h5>
16 </%def>
16 </%def>
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('home')}
18 ${self.menu('home')}
19 </%def>
19 </%def>
20 <%def name="head_extra()">
20 <%def name="head_extra()">
21 <link href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
21 <link href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
22 <link href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
22 <link href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
23 </%def>
23 </%def>
24 <%def name="main()">
24 <%def name="main()">
25
25
26 <div class="box box-left">
26 <div class="box box-left">
27 <!-- box / title -->
27 <!-- box / title -->
28 <div class="title">
28 <div class="title">
29 ${self.breadcrumbs()}
29 ${self.breadcrumbs()}
30 <ul class="links">
30 <ul class="links">
31 <li>
31 <li>
32 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
32 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
33 </li>
33 </li>
34 <li>
34 <li>
35 <span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
35 <span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
36 </li>
36 </li>
37 <li>
37 <li>
38 <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
38 <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
39 </li>
39 </li>
40 </ul>
40 </ul>
41 </div>
41 </div>
42 <div id="journal">${c.journal_data}</div>
42 <div id="journal">${c.journal_data}</div>
43 </div>
43 </div>
44 <div class="box box-right">
44 <div class="box box-right">
45 <!-- box / title -->
45 <!-- box / title -->
46 <div class="title">
46 <div class="title">
47 <h5>
47 <h5>
48 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
48 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
49 <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a> / <a id="show_my" class="link-white" href="#my">${_('My repos')}</a>
49 <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a> / <a id="show_my" class="link-white" href="#my">${_('My repos')}</a>
50 </h5>
50 </h5>
51 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
51 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
52 <ul class="links">
52 <ul class="links">
53 <li>
53 <li>
54 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
54 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
55 </li>
55 </li>
56 </ul>
56 </ul>
57 %endif
57 %endif
58 </div>
58 </div>
59 <!-- end box / title -->
59 <!-- end box / title -->
60 <div id="my" class="table" style="display:none">
60 <div id="my" class="table" style="display:none">
61 ## loaded via AJAX
61 ## loaded via AJAX
62 ${_('Loading...')}
62 ${_('Loading...')}
63 </div>
63 </div>
64
64
65 <div id="watched" class="table">
65 <div id="watched" class="table">
66 %if c.following:
66 %if c.following:
67 <table>
67 <table>
68 <thead>
68 <thead>
69 <tr>
69 <tr>
70 <th class="left">${_('Name')}</th>
70 <th class="left">${_('Name')}</th>
71 </thead>
71 </thead>
72 <tbody>
72 <tbody>
73 %for entry in c.following:
73 %for entry in c.following:
74 <tr>
74 <tr>
75 <td>
75 <td>
76 %if entry.follows_user_id:
76 %if entry.follows_user_id:
77 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
77 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
78 ${entry.follows_user.full_contact}
78 ${entry.follows_user.full_contact}
79 %endif
79 %endif
80
80
81 %if entry.follows_repo_id:
81 %if entry.follows_repo_id:
82 <div style="float:right;padding-right:5px">
82 <div style="float:right;padding-right:5px">
83 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
83 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
84 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
84 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
85 </span>
85 </span>
86 </div>
86 </div>
87
87
88 %if h.is_hg(entry.follows_repository):
88 %if h.is_hg(entry.follows_repository):
89 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
89 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
90 %elif h.is_git(entry.follows_repository):
90 %elif h.is_git(entry.follows_repository):
91 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
91 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
92 %endif
92 %endif
93
93
94 %if entry.follows_repository.private and c.visual.show_private_icon:
94 %if entry.follows_repository.private and c.visual.show_private_icon:
95 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
95 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
96 %elif not entry.follows_repository.private and c.visual.show_public_icon:
96 %elif not entry.follows_repository.private and c.visual.show_public_icon:
97 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
97 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
98 %endif
98 %endif
99 <span class="watched_repo">
99 <span class="watched_repo">
100 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
100 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
101 </span>
101 </span>
102 %endif
102 %endif
103 </td>
103 </td>
104 </tr>
104 </tr>
105 %endfor
105 %endfor
106 </tbody>
106 </tbody>
107 </table>
107 </table>
108 %else:
108 %else:
109 <div style="padding:5px 0px 10px 0px;">
109 <div style="padding:5px 0px 10px 0px;">
110 ${_('You are not following any users or repositories')}
110 ${_('You are not following any users or repositories')}
111 </div>
111 </div>
112 %endif
112 %endif
113 </div>
113 </div>
114 </div>
114 </div>
115
115
116 <script type="text/javascript">
116 <script type="text/javascript">
117
117
118 YUE.on('j_filter','click',function(){
118 YUE.on('j_filter','click',function(){
119 var jfilter = YUD.get('j_filter');
119 var jfilter = YUD.get('j_filter');
120 if(YUD.hasClass(jfilter, 'initial')){
120 if(YUD.hasClass(jfilter, 'initial')){
121 jfilter.value = '';
121 jfilter.value = '';
122 }
122 }
123 });
123 });
124 var fix_j_filter_width = function(len){
124 var fix_j_filter_width = function(len){
125 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
125 YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
126 }
126 }
127 YUE.on('j_filter','keyup',function(){
127 YUE.on('j_filter','keyup',function(){
128 fix_j_filter_width(YUD.get('j_filter').value.length);
128 fix_j_filter_width(YUD.get('j_filter').value.length);
129 });
129 });
130 YUE.on('filter_form','submit',function(e){
130 YUE.on('filter_form','submit',function(e){
131 YUE.preventDefault(e)
131 YUE.preventDefault(e)
132 var val = YUD.get('j_filter').value;
132 var val = YUD.get('j_filter').value;
133 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
133 window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
134 });
134 });
135 fix_j_filter_width(YUD.get('j_filter').value.length);
135 fix_j_filter_width(YUD.get('j_filter').value.length);
136
136
137 var show_my = function(e){
137 var show_my = function(e){
138 YUD.setStyle('watched','display','none');
138 YUD.setStyle('watched','display','none');
139 YUD.setStyle('my','display','');
139 YUD.setStyle('my','display','');
140
140
141 var url = "${h.url('admin_settings_my_repos')}";
141 var url = "${h.url('admin_settings_my_repos')}";
142 ypjax(url, 'my', function(){
142 ypjax(url, 'my', function(){
143 tooltip_activate();
143 tooltip_activate();
144 quick_repo_menu();
144 quick_repo_menu();
145 var nodes = YUQ('#my tr td a.repo_name');
145 var nodes = YUQ('#my tr td a.repo_name');
146 var func = function(node){
146 var func = function(node){
147 return node.parentNode.parentNode.parentNode;
147 return node.parentNode.parentNode.parentNode;
148 }
148 }
149 q_filter('q_filter',nodes,func);
149 q_filter('q_filter',nodes,func);
150 });
150 });
151
151
152 }
152 }
153 YUE.on('show_my','click',function(e){
153 YUE.on('show_my','click',function(e){
154 show_my(e);
154 show_my(e);
155 })
155 })
156 var show_watched = function(e){
156 var show_watched = function(e){
157 YUD.setStyle('my','display','none');
157 YUD.setStyle('my','display','none');
158 YUD.setStyle('watched','display','');
158 YUD.setStyle('watched','display','');
159 var nodes = YUQ('#watched .watched_repo a');
159 var nodes = YUQ('#watched .watched_repo a');
160 var target = 'q_filter';
160 var target = 'q_filter';
161 var func = function(node){
161 var func = function(node){
162 return node.parentNode.parentNode;
162 return node.parentNode.parentNode;
163 }
163 }
164 q_filter(target,nodes,func);
164 q_filter(target,nodes,func);
165 }
165 }
166 YUE.on('show_watched','click',function(e){
166 YUE.on('show_watched','click',function(e){
167 show_watched(e);
167 show_watched(e);
168 })
168 })
169 //init watched
169 //init watched
170 show_watched();
170 show_watched();
171
171
172 var tabs = {
172 var tabs = {
173 'watched': show_watched,
173 'watched': show_watched,
174 'my': show_my,
174 'my': show_my,
175 }
175 }
176 var url = location.href.split('#');
176 var url = location.href.split('#');
177 if (url[1]) {
177 if (url[1]) {
178 //We have a hash
178 //We have a hash
179 var tabHash = url[1];
179 var tabHash = url[1];
180 var func = tabs[tabHash]
180 var func = tabs[tabHash]
181 if (func){
181 if (func){
182 func();
182 func();
183 }
183 }
184 }
184 }
185
185
186 YUE.on('refresh','click',function(e){
186 YUE.on('refresh','click',function(e){
187 ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
187 ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
188 show_more_event();
188 show_more_event();
189 tooltip_activate();
189 tooltip_activate();
190 show_changeset_tooltip();
190 show_changeset_tooltip();
191 });
191 });
192 YUE.preventDefault(e);
192 YUE.preventDefault(e);
193 });
193 });
194
194
195
195
196 // main table sorting
196 // main table sorting
197 var myColumnDefs = [
197 var myColumnDefs = [
198 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
198 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
199 {key:"name",label:"${_('Name')}",sortable:true,
199 {key:"name",label:"${_('Name')}",sortable:true,
200 sortOptions: { sortFunction: nameSort }},
200 sortOptions: { sortFunction: nameSort }},
201 {key:"tip",label:"${_('Tip')}",sortable:true,
201 {key:"tip",label:"${_('Tip')}",sortable:true,
202 sortOptions: { sortFunction: revisionSort }},
202 sortOptions: { sortFunction: revisionSort }},
203 {key:"action1",label:"",sortable:false},
203 {key:"action1",label:"",sortable:false},
204 {key:"action2",label:"",sortable:false},
204 {key:"action2",label:"",sortable:false},
205 ];
205 ];
206
206
207 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
207 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
208
208
209 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
209 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
210
210
211 myDataSource.responseSchema = {
211 myDataSource.responseSchema = {
212 fields: [
212 fields: [
213 {key:"menu"},
213 {key:"menu"},
214 {key:"name"},
214 {key:"name"},
215 {key:"tip"},
215 {key:"tip"},
216 {key:"action1"},
216 {key:"action1"},
217 {key:"action2"}
217 {key:"action2"}
218 ]
218 ]
219 };
219 };
220
220
221 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
221 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
222 {
222 {
223 sortedBy:{key:"name",dir:"asc"},
223 sortedBy:{key:"name",dir:"asc"},
224 MSG_SORTASC:"${_('Click to sort ascending')}",
224 MSG_SORTASC:"${_('Click to sort ascending')}",
225 MSG_SORTDESC:"${_('Click to sort descending')}",
225 MSG_SORTDESC:"${_('Click to sort descending')}",
226 MSG_EMPTY:"${_('No records found.')}",
226 MSG_EMPTY:"${_('No records found.')}",
227 MSG_ERROR:"${_('Data error.')}",
227 MSG_ERROR:"${_('Data error.')}",
228 MSG_LOADING:"${_('Loading...')}",
228 MSG_LOADING:"${_('Loading...')}",
229 }
229 }
230 );
230 );
231 myDataTable.subscribe('postRenderEvent',function(oArgs) {
231 myDataTable.subscribe('postRenderEvent',function(oArgs) {
232 tooltip_activate();
232 tooltip_activate();
233 quick_repo_menu();
233 quick_repo_menu();
234 var func = function(node){
234 var func = function(node){
235 return node.parentNode.parentNode.parentNode.parentNode;
235 return node.parentNode.parentNode.parentNode.parentNode;
236 }
236 }
237 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
237 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
238 });
238 });
239
239
240 </script>
240 </script>
241 </%def>
241 </%def>
@@ -1,210 +1,210 b''
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>${_('Status')}:</label>
31 <label>${_('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>
54 </div>
55 </div>
55 </div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
57 <div style="padding:4px 4px 10px 20px">
57 <div style="padding:4px 4px 10px 20px">
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
59 </div>
59 </div>
60
60
61 <div style="overflow: auto;">
61 <div style="overflow: auto;">
62 ##DIFF
62 ##DIFF
63 <div class="table" style="float:left;clear:none">
63 <div class="table" style="float:left;clear:none">
64 <div id="body" class="diffblock">
64 <div id="body" class="diffblock">
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
66 </div>
66 </div>
67 <div id="changeset_compare_view_content">
67 <div id="changeset_compare_view_content">
68 ##CS
68 ##CS
69 <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>
69 <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>
70 <%include file="/compare/compare_cs.html" />
70 <%include file="/compare/compare_cs.html" />
71
71
72 ## FILES
72 ## FILES
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
74
74
75 % if c.limited_diff:
75 % if c.limited_diff:
76 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
76 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
77 % else:
77 % else:
78 ${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)}:
78 ${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)}:
79 %endif
79 %endif
80
80
81 </div>
81 </div>
82 <div class="cs_files">
82 <div class="cs_files">
83 %if not c.files:
83 %if not c.files:
84 <span class="empty_data">${_('No files')}</span>
84 <span class="empty_data">${_('No files')}</span>
85 %endif
85 %endif
86 %for fid, change, f, stat in c.files:
86 %for fid, change, f, stat in c.files:
87 <div class="cs_${change}">
87 <div class="cs_${change}">
88 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
88 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
89 <div class="changes">${h.fancy_file_stats(stat)}</div>
89 <div class="changes">${h.fancy_file_stats(stat)}</div>
90 </div>
90 </div>
91 %endfor
91 %endfor
92 </div>
92 </div>
93 % if c.limited_diff:
93 % if c.limited_diff:
94 <h5>${_('Changeset was too big and was cut off...')}</h5>
94 <h5>${_('Changeset was too big and was cut off...')}</h5>
95 % endif
95 % endif
96 </div>
96 </div>
97 </div>
97 </div>
98 ## REVIEWERS
98 ## REVIEWERS
99 <div style="float:left; border-left:1px dashed #eee">
99 <div style="float:left; border-left:1px dashed #eee">
100 <h4>${_('Pull request reviewers')}</h4>
100 <h4>${_('Pull request reviewers')}</h4>
101 <div id="reviewers" style="padding:0px 0px 5px 10px">
101 <div id="reviewers" style="padding:0px 0px 5px 10px">
102 ## members goes here !
102 ## members goes here !
103 <div class="group_members_wrap" style="min-height:45px">
103 <div class="group_members_wrap" style="min-height:45px">
104 <ul id="review_members" class="group_members">
104 <ul id="review_members" class="group_members">
105 %for member,status in c.pull_request_reviewers:
105 %for member,status in c.pull_request_reviewers:
106 <li id="reviewer_${member.user_id}">
106 <li id="reviewer_${member.user_id}">
107 <div class="reviewers_member">
107 <div class="reviewers_member">
108 <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'))}">
108 <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'))}">
109 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
109 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
110 </div>
110 </div>
111 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
111 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
112 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
112 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
113 <input type="hidden" value="${member.user_id}" name="review_members" />
113 <input type="hidden" value="${member.user_id}" name="review_members" />
114 %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):
114 %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):
115 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
115 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
116 %endif
116 %endif
117 </div>
117 </div>
118 </li>
118 </li>
119 %endfor
119 %endfor
120 </ul>
120 </ul>
121 </div>
121 </div>
122 %if not c.pull_request.is_closed():
122 %if not c.pull_request.is_closed():
123 <div class='ac'>
123 <div class='ac'>
124 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
124 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
125 <div class="reviewer_ac">
125 <div class="reviewer_ac">
126 ${h.text('user', class_='yui-ac-input')}
126 ${h.text('user', class_='yui-ac-input')}
127 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
127 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
128 <div id="reviewers_container"></div>
128 <div id="reviewers_container"></div>
129 </div>
129 </div>
130 <div style="padding:0px 10px">
130 <div style="padding:0px 10px">
131 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
131 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
132 </div>
132 </div>
133 %endif
133 %endif
134 </div>
134 </div>
135 %endif
135 %endif
136 </div>
136 </div>
137 </div>
137 </div>
138 </div>
138 </div>
139 <script>
139 <script>
140 var _USERS_AC_DATA = ${c.users_array|n};
140 var _USERS_AC_DATA = ${c.users_array|n};
141 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
141 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
142 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
142 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
143 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
143 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
144 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
144 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
145 </script>
145 </script>
146
146
147 ## diff block
147 ## diff block
148 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
148 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
149 %for fid, change, f, stat in c.files:
149 %for fid, change, f, stat in c.files:
150 ${diff_block.diff_block_simple([c.changes[fid]])}
150 ${diff_block.diff_block_simple([c.changes[fid]])}
151 %endfor
151 %endfor
152 % if c.limited_diff:
152 % if c.limited_diff:
153 <h4>${_('Changeset was too big and was cut off...')}</h4>
153 <h4>${_('Changeset was too big and was cut off...')}</h4>
154 % endif
154 % endif
155
155
156
156
157 ## template for inline comment form
157 ## template for inline comment form
158 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
158 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
159 ${comment.comment_inline_form()}
159 ${comment.comment_inline_form()}
160
160
161 ## render comments and inlines
161 ## render comments and inlines
162 ${comment.generate_comments()}
162 ${comment.generate_comments()}
163
163
164 % if not c.pull_request.is_closed():
164 % if not c.pull_request.is_closed():
165 ## main comment form and it status
165 ## main comment form and it status
166 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
166 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
167 pull_request_id=c.pull_request.pull_request_id),
167 pull_request_id=c.pull_request.pull_request_id),
168 c.current_changeset_status,
168 c.current_changeset_status,
169 close_btn=True, change_status=c.allowed_to_change_status)}
169 close_btn=True, change_status=c.allowed_to_change_status)}
170 %endif
170 %endif
171
171
172 <script type="text/javascript">
172 <script type="text/javascript">
173 YUE.onDOMReady(function(){
173 YUE.onDOMReady(function(){
174 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
174 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
175
175
176 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
176 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
177 var show = 'none';
177 var show = 'none';
178 var target = e.currentTarget;
178 var target = e.currentTarget;
179 if(target.checked){
179 if(target.checked){
180 var show = ''
180 var show = ''
181 }
181 }
182 var boxid = YUD.getAttribute(target,'id_for');
182 var boxid = YUD.getAttribute(target,'id_for');
183 var comments = YUQ('#{0} .inline-comments'.format(boxid));
183 var comments = YUQ('#{0} .inline-comments'.format(boxid));
184 for(c in comments){
184 for(c in comments){
185 YUD.setStyle(comments[c],'display',show);
185 YUD.setStyle(comments[c],'display',show);
186 }
186 }
187 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
187 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
188 for(c in btns){
188 for(c in btns){
189 YUD.setStyle(btns[c],'display',show);
189 YUD.setStyle(btns[c],'display',show);
190 }
190 }
191 })
191 })
192
192
193 YUE.on(YUQ('.line'),'click',function(e){
193 YUE.on(YUQ('.line'),'click',function(e){
194 var tr = e.currentTarget;
194 var tr = e.currentTarget;
195 injectInlineForm(tr);
195 injectInlineForm(tr);
196 });
196 });
197
197
198 // inject comments into they proper positions
198 // inject comments into they proper positions
199 var file_comments = YUQ('.inline-comment-placeholder');
199 var file_comments = YUQ('.inline-comment-placeholder');
200 renderInlineComments(file_comments);
200 renderInlineComments(file_comments);
201
201
202 YUE.on(YUD.get('update_pull_request'),'click',function(e){
202 YUE.on(YUD.get('update_pull_request'),'click',function(e){
203 updateReviewers();
203 updateReviewers();
204 })
204 })
205 })
205 })
206 </script>
206 </script>
207
207
208 </div>
208 </div>
209
209
210 </%def>
210 </%def>
@@ -1,130 +1,130 b''
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 ${_('%s Settings') % c.repo_name} - ${c.rhodecode_name}
5 ${_('%s Settings') % c.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.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 &raquo;
12 &raquo;
13 ${_('Settings')}
13 ${_('Settings')}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('settings')}
17 ${self.menu('settings')}
18 </%def>
18 </%def>
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
25 ${h.form(url('repo_settings_update', repo_name=c.repo_info.repo_name),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28 <div class="fields">
28 <div class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label for="repo_name">${_('Name')}:</label>
31 <label for="repo_name">${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input input-medium">
33 <div class="input input-medium">
34 ${h.text('repo_name',class_="small")}
34 ${h.text('repo_name',class_="small")}
35 </div>
35 </div>
36 </div>
36 </div>
37 <div class="field">
37 <div class="field">
38 <div class="label">
38 <div class="label">
39 <label for="clone_uri">${_('Clone uri')}:</label>
39 <label for="clone_uri">${_('Clone uri')}:</label>
40 </div>
40 </div>
41 <div class="input">
41 <div class="input">
42 ${h.text('clone_uri',class_="medium")}
42 ${h.text('clone_uri',class_="medium")}
43 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
43 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label for="repo_group">${_('Repository group')}:</label>
48 <label for="repo_group">${_('Repository group')}:</label>
49 </div>
49 </div>
50 <div class="input">
50 <div class="input">
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
52 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="landing_rev">${_('Landing revision')}:</label>
57 <label for="landing_rev">${_('Landing revision')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
60 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
61 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
61 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="field">
64 <div class="field">
65 <div class="label label-textarea">
65 <div class="label label-textarea">
66 <label for="repo_description">${_('Description')}:</label>
66 <label for="repo_description">${_('Description')}:</label>
67 </div>
67 </div>
68 <div class="textarea text-area editor">
68 <div class="textarea text-area editor">
69 ${h.textarea('repo_description')}
69 ${h.textarea('repo_description')}
70 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
70 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="field">
74 <div class="field">
75 <div class="label label-checkbox">
75 <div class="label label-checkbox">
76 <label for="repo_private">${_('Private repository')}:</label>
76 <label for="repo_private">${_('Private repository')}:</label>
77 </div>
77 </div>
78 <div class="checkboxes">
78 <div class="checkboxes">
79 ${h.checkbox('repo_private',value="True")}
79 ${h.checkbox('repo_private',value="True")}
80 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
80 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="">${_('Permissions')}:</label>
86 <label for="">${_('Permissions')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 <%include file="../admin/repos/repo_edit_perms.html"/>
89 <%include file="../admin/repos/repo_edit_perms.html"/>
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="buttons">
93 <div class="buttons">
94 ${h.submit('save',_('Save'),class_="ui-btn large")}
94 ${h.submit('save',_('Save'),class_="ui-btn large")}
95 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
95 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
96 </div>
96 </div>
97
97
98 </div>
98 </div>
99 ${h.end_form()}
99 ${h.end_form()}
100 </div>
100 </div>
101
101
102 <h3>${_('Delete repository')}</h3>
102 <h3>${_('Delete repository')}</h3>
103 <div class="form">
103 <div class="form">
104 <!-- fields -->
104 <!-- fields -->
105 <div class="fields">
105 <div class="fields">
106
106
107 <div class="field">
107 <div class="field">
108 <div class="label">
108 <div class="label">
109 <label for="">${_('Remove repo')}:</label>
109 <label for="">${_('Remove repo')}:</label>
110 </div>
110 </div>
111 <div class="checkboxes">
111 <div class="checkboxes">
112 ${h.form(url('repo_settings_delete', repo_name=c.repo_info.repo_name),method='delete')}
112 ${h.form(url('repo_settings_delete', repo_name=c.repo_info.repo_name),method='delete')}
113 <div class="">
113 <div class="">
114 <div class="fields">
114 <div class="fields">
115 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
115 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
116 </div>
116 </div>
117 <div class="field" style="border:none;color:#888">
117 <div class="field" style="border:none;color:#888">
118 <ul>
118 <ul>
119 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need fully delete it from file system please do it manually')}</li>
119 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need fully delete it from file system please do it manually')}</li>
120 </ul>
120 </ul>
121 </div>
121 </div>
122 </div>
122 </div>
123 ${h.end_form()}
123 ${h.end_form()}
124 </div>
124 </div>
125 </div>
125 </div>
126 </div>
126 </div>
127 </div>
127 </div>
128
128
129 </div>
129 </div>
130 </%def>
130 </%def>
@@ -1,1 +1,1 b''
1 #TODO; write tests when we activate algo for permissions. No newline at end of file
1 #TODO; write tests when we activate algo for permissions.
General Comments 0
You need to be logged in to leave comments. Login now