##// END OF EJS Templates
Added option to close pull requests, in future that will be close & merge
marcink -
r2608:58c52933 beta
parent child Browse files
Show More
@@ -1,328 +1,342 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27
28 28 from webob.exc import HTTPNotFound, HTTPForbidden
29 29 from collections import defaultdict
30 30 from itertools import groupby
31 31
32 32 from pylons import request, response, session, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35 from pylons.decorators import jsonify
36 36
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 40 from rhodecode.lib import helpers as h
41 41 from rhodecode.lib import diffs
42 42 from rhodecode.lib.utils import action_logger
43 43 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
44 44 ChangesetComment
45 45 from rhodecode.model.pull_request import PullRequestModel
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.repo import RepoModel
48 48 from rhodecode.model.comment import ChangesetCommentsModel
49 49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class PullrequestsController(BaseRepoController):
55 55
56 56 @LoginRequired()
57 57 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
58 58 'repository.admin')
59 59 def __before__(self):
60 60 super(PullrequestsController, self).__before__()
61 61
62 62 def _get_repo_refs(self, repo):
63 63 hist_l = []
64 64
65 65 branches_group = ([('branch:%s:%s' % (k, v), k) for
66 66 k, v in repo.branches.iteritems()], _("Branches"))
67 67 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
68 68 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
69 69 tags_group = ([('tag:%s:%s' % (k, v), k) for
70 70 k, v in repo.tags.iteritems()], _("Tags"))
71 71
72 72 hist_l.append(bookmarks_group)
73 73 hist_l.append(branches_group)
74 74 hist_l.append(tags_group)
75 75
76 76 return hist_l
77 77
78 78 def show_all(self, repo_name):
79 79 c.pull_requests = PullRequestModel().get_all(repo_name)
80 80 c.repo_name = repo_name
81 81 return render('/pullrequests/pullrequest_show_all.html')
82 82
83 83 def index(self):
84 84 org_repo = c.rhodecode_db_repo
85 85
86 86 if org_repo.scm_instance.alias != 'hg':
87 87 log.error('Review not available for GIT REPOS')
88 88 raise HTTPNotFound
89 89
90 90 other_repos_info = {}
91 91
92 92 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
93 93 c.org_repos = []
94 94 c.other_repos = []
95 95 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
96 96 org_repo.user.username, c.repo_name))
97 97 )
98 98
99 99 c.other_refs = c.org_refs
100 100 c.other_repos.extend(c.org_repos)
101 101
102 102 #add orginal repo
103 103 other_repos_info[org_repo.repo_name] = {
104 104 'gravatar': h.gravatar_url(org_repo.user.email, 24),
105 105 'description': org_repo.description
106 106 }
107 107
108 108 c.default_pull_request = org_repo.repo_name
109 109 #gather forks and add to this list
110 110 for fork in org_repo.forks:
111 111 c.other_repos.append((fork.repo_name, '%s/%s' % (
112 112 fork.user.username, fork.repo_name))
113 113 )
114 114 other_repos_info[fork.repo_name] = {
115 115 'gravatar': h.gravatar_url(fork.user.email, 24),
116 116 'description': fork.description
117 117 }
118 118 #add parents of this fork also
119 119 if org_repo.parent:
120 120 c.default_pull_request = org_repo.parent.repo_name
121 121 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
122 122 org_repo.parent.user.username,
123 123 org_repo.parent.repo_name))
124 124 )
125 125 other_repos_info[org_repo.parent.repo_name] = {
126 126 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
127 127 'description': org_repo.parent.description
128 128 }
129 129
130 130 c.other_repos_info = json.dumps(other_repos_info)
131 131 c.review_members = []
132 132 c.available_members = []
133 133 for u in User.query().filter(User.username != 'default').all():
134 134 uname = u.username
135 135 if org_repo.user == u:
136 136 uname = _('%s (owner)') % u.username
137 137 # auto add owner to pull-request recipients
138 138 c.review_members.append([u.user_id, uname])
139 139 c.available_members.append([u.user_id, uname])
140 140 return render('/pullrequests/pullrequest.html')
141 141
142 142 def create(self, repo_name):
143 143 req_p = request.POST
144 144 org_repo = req_p['org_repo']
145 145 org_ref = req_p['org_ref']
146 146 other_repo = req_p['other_repo']
147 147 other_ref = req_p['other_ref']
148 148 revisions = req_p.getall('revisions')
149 149 reviewers = req_p.getall('review_members')
150 150 #TODO: wrap this into a FORM !!!
151 151
152 152 title = req_p['pullrequest_title']
153 153 description = req_p['pullrequest_desc']
154 154
155 155 try:
156 156 pull_request = PullRequestModel().create(
157 157 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
158 158 other_ref, revisions, reviewers, title, description
159 159 )
160 160 Session().commit()
161 161 h.flash(_('Successfully opened new pull request'),
162 162 category='success')
163 163 except Exception:
164 164 h.flash(_('Error occurred during sending pull request'),
165 165 category='error')
166 166 log.error(traceback.format_exc())
167 167 return redirect(url('changelog_home', repo_name=org_repo,))
168 168
169 169 return redirect(url('pullrequest_show', repo_name=other_repo,
170 170 pull_request_id=pull_request.pull_request_id))
171 171
172 def _load_compare_data(self, pull_request):
172 def _load_compare_data(self, pull_request, enable_comments=True):
173 173 """
174 174 Load context data needed for generating compare diff
175 175
176 176 :param pull_request:
177 177 :type pull_request:
178 178 """
179 179
180 180 org_repo = pull_request.org_repo
181 181 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
182 182 other_repo = pull_request.other_repo
183 183 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
184 184
185 185 org_ref = (org_ref_type, org_ref)
186 186 other_ref = (other_ref_type, other_ref)
187 187
188 188 c.org_repo = org_repo
189 189 c.other_repo = other_repo
190 190
191 191 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
192 192 org_repo, org_ref, other_repo, other_ref
193 193 )
194 194
195 195 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
196 196 c.cs_ranges])
197 197 # defines that we need hidden inputs with changesets
198 198 c.as_form = request.GET.get('as_form', False)
199 199
200 200 c.org_ref = org_ref[1]
201 201 c.other_ref = other_ref[1]
202 202 # diff needs to have swapped org with other to generate proper diff
203 203 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
204 204 discovery_data)
205 205 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
206 206 _parsed = diff_processor.prepare()
207 207
208 208 c.files = []
209 209 c.changes = {}
210 210
211 211 for f in _parsed:
212 212 fid = h.FID('', f['filename'])
213 213 c.files.append([fid, f['operation'], f['filename'], f['stats']])
214 diff = diff_processor.as_html(enable_comments=True,
214 diff = diff_processor.as_html(enable_comments=enable_comments,
215 215 diff_lines=[f])
216 216 c.changes[fid] = [f['operation'], f['filename'], diff]
217 217
218 218 def show(self, repo_name, pull_request_id):
219 219 repo_model = RepoModel()
220 220 c.users_array = repo_model.get_users_js()
221 221 c.users_groups_array = repo_model.get_users_groups_js()
222 222 c.pull_request = PullRequest.get_or_404(pull_request_id)
223 223
224 224 cc_model = ChangesetCommentsModel()
225 225 cs_model = ChangesetStatusModel()
226 226 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
227 227 pull_request=c.pull_request,
228 228 with_revisions=True)
229 229
230 230 cs_statuses = defaultdict(list)
231 231 for st in _cs_statuses:
232 232 cs_statuses[st.author.username] += [st]
233 233
234 234 c.pull_request_reviewers = []
235 235 for o in c.pull_request.reviewers:
236 236 st = cs_statuses.get(o.user.username, None)
237 237 if st:
238 238 sorter = lambda k: k.version
239 239 st = [(x, list(y)[0])
240 240 for x, y in (groupby(sorted(st, key=sorter), sorter))]
241 241 c.pull_request_reviewers.append([o.user, st])
242 242
243 243 # pull_requests repo_name we opened it against
244 244 # ie. other_repo must match
245 245 if repo_name != c.pull_request.other_repo.repo_name:
246 246 raise HTTPNotFound
247 247
248 248 # load compare data into template context
249 self._load_compare_data(c.pull_request)
249 enable_comments = not c.pull_request.is_closed()
250 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
250 251
251 252 # inline comments
252 253 c.inline_cnt = 0
253 254 c.inline_comments = cc_model.get_inline_comments(
254 255 c.rhodecode_db_repo.repo_id,
255 256 pull_request=pull_request_id)
256 257 # count inline comments
257 258 for __, lines in c.inline_comments:
258 259 for comments in lines.values():
259 260 c.inline_cnt += len(comments)
260 261 # comments
261 262 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
262 263 pull_request=pull_request_id)
263 264
264 265 # changeset(pull-request) status
265 266 c.current_changeset_status = cs_model.calculate_status(
266 267 c.pull_request_reviewers
267 268 )
268 269 c.changeset_statuses = ChangesetStatus.STATUSES
269 270 c.target_repo = c.pull_request.org_repo.repo_name
270 271 return render('/pullrequests/pullrequest_show.html')
271 272
272 273 @jsonify
273 274 def comment(self, repo_name, pull_request_id):
275 pull_request = PullRequest.get_or_404(pull_request_id)
276 if pull_request.is_closed():
277 raise HTTPForbidden()
274 278
275 279 status = request.POST.get('changeset_status')
276 280 change_status = request.POST.get('change_changeset_status')
277 281
278 282 comm = ChangesetCommentsModel().create(
279 283 text=request.POST.get('text'),
280 284 repo=c.rhodecode_db_repo.repo_id,
281 285 user=c.rhodecode_user.user_id,
282 286 pull_request=pull_request_id,
283 287 f_path=request.POST.get('f_path'),
284 288 line_no=request.POST.get('line'),
285 289 status_change=(ChangesetStatus.get_status_lbl(status)
286 290 if status and change_status else None)
287 291 )
288 292
289 293 # get status if set !
290 294 if status and change_status:
291 295 ChangesetStatusModel().set_status(
292 296 c.rhodecode_db_repo.repo_id,
293 297 status,
294 298 c.rhodecode_user.user_id,
295 299 comm,
296 300 pull_request=pull_request_id
297 301 )
298 302 action_logger(self.rhodecode_user,
299 303 'user_commented_pull_request:%s' % pull_request_id,
300 304 c.rhodecode_db_repo, self.ip_addr, self.sa)
301 305
306 if request.POST.get('save_close'):
307 PullRequestModel().close_pull_request(pull_request_id)
308 action_logger(self.rhodecode_user,
309 'user_closed_pull_request:%s' % pull_request_id,
310 c.rhodecode_db_repo, self.ip_addr, self.sa)
311
302 312 Session().commit()
303 313
304 314 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
305 315 return redirect(h.url('pullrequest_show', repo_name=repo_name,
306 316 pull_request_id=pull_request_id))
307 317
308 318 data = {
309 319 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
310 320 }
311 321 if comm:
312 322 c.co = comm
313 323 data.update(comm.get_dict())
314 324 data.update({'rendered_text':
315 325 render('changeset/changeset_comment_block.html')})
316 326
317 327 return data
318 328
319 329 @jsonify
320 330 def delete_comment(self, repo_name, comment_id):
321 331 co = ChangesetComment.get(comment_id)
332 if co.pull_request.is_closed():
333 #don't allow deleting comments on closed pull request
334 raise HTTPForbidden()
335
322 336 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
323 337 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
324 338 ChangesetCommentsModel().delete(comment=co)
325 339 Session().commit()
326 340 return True
327 341 else:
328 342 raise HTTPForbidden() No newline at end of file
@@ -1,1653 +1,1659 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from sqlalchemy.exc import DatabaseError
37 37 from beaker.cache import cache_region, region_invalidate
38 38 from webob.exc import HTTPNotFound
39 39
40 40 from pylons.i18n.translation import lazy_ugettext as _
41 41
42 42 from rhodecode.lib.vcs import get_backend
43 43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 44 from rhodecode.lib.vcs.exceptions import VCSError
45 45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 46
47 47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 48 safe_unicode
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model.meta import Base, Session
53 53
54 54 URL_SEP = '/'
55 55 log = logging.getLogger(__name__)
56 56
57 57 #==============================================================================
58 58 # BASE CLASSES
59 59 #==============================================================================
60 60
61 61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 62
63 63
64 64 class BaseModel(object):
65 65 """
66 66 Base Model for all classess
67 67 """
68 68
69 69 @classmethod
70 70 def _get_keys(cls):
71 71 """return column names for this model """
72 72 return class_mapper(cls).c.keys()
73 73
74 74 def get_dict(self):
75 75 """
76 76 return dict with keys and values corresponding
77 77 to this model data """
78 78
79 79 d = {}
80 80 for k in self._get_keys():
81 81 d[k] = getattr(self, k)
82 82
83 83 # also use __json__() if present to get additional fields
84 84 _json_attr = getattr(self, '__json__', None)
85 85 if _json_attr:
86 86 # update with attributes from __json__
87 87 if callable(_json_attr):
88 88 _json_attr = _json_attr()
89 89 for k, val in _json_attr.iteritems():
90 90 d[k] = val
91 91 return d
92 92
93 93 def get_appstruct(self):
94 94 """return list with keys and values tupples corresponding
95 95 to this model data """
96 96
97 97 l = []
98 98 for k in self._get_keys():
99 99 l.append((k, getattr(self, k),))
100 100 return l
101 101
102 102 def populate_obj(self, populate_dict):
103 103 """populate model with data from given populate_dict"""
104 104
105 105 for k in self._get_keys():
106 106 if k in populate_dict:
107 107 setattr(self, k, populate_dict[k])
108 108
109 109 @classmethod
110 110 def query(cls):
111 111 return Session().query(cls)
112 112
113 113 @classmethod
114 114 def get(cls, id_):
115 115 if id_:
116 116 return cls.query().get(id_)
117 117
118 118 @classmethod
119 119 def get_or_404(cls, id_):
120 120 if id_:
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 return cls.query().all()
129 129
130 130 @classmethod
131 131 def delete(cls, id_):
132 132 obj = cls.query().get(id_)
133 133 Session().delete(obj)
134 134
135 135 def __repr__(self):
136 136 if hasattr(self, '__unicode__'):
137 137 # python repr needs to return str
138 138 return safe_str(self.__unicode__())
139 139 return '<DB:%s>' % (self.__class__.__name__)
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (
145 145 UniqueConstraint('app_settings_name'),
146 146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 147 'mysql_charset': 'utf8'}
148 148 )
149 149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152
153 153 def __init__(self, k='', v=''):
154 154 self.app_settings_name = k
155 155 self.app_settings_value = v
156 156
157 157 @validates('_app_settings_value')
158 158 def validate_settings_value(self, key, val):
159 159 assert type(val) == unicode
160 160 return val
161 161
162 162 @hybrid_property
163 163 def app_settings_value(self):
164 164 v = self._app_settings_value
165 165 if self.app_settings_name == 'ldap_active':
166 166 v = str2bool(v)
167 167 return v
168 168
169 169 @app_settings_value.setter
170 170 def app_settings_value(self, val):
171 171 """
172 172 Setter that will always make sure we use unicode in app_settings_value
173 173
174 174 :param val:
175 175 """
176 176 self._app_settings_value = safe_unicode(val)
177 177
178 178 def __unicode__(self):
179 179 return u"<%s('%s:%s')>" % (
180 180 self.__class__.__name__,
181 181 self.app_settings_name, self.app_settings_value
182 182 )
183 183
184 184 @classmethod
185 185 def get_by_name(cls, ldap_key):
186 186 return cls.query()\
187 187 .filter(cls.app_settings_name == ldap_key).scalar()
188 188
189 189 @classmethod
190 190 def get_app_settings(cls, cache=False):
191 191
192 192 ret = cls.query()
193 193
194 194 if cache:
195 195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196 196
197 197 if not ret:
198 198 raise Exception('Could not get application settings !')
199 199 settings = {}
200 200 for each in ret:
201 201 settings['rhodecode_' + each.app_settings_name] = \
202 202 each.app_settings_value
203 203
204 204 return settings
205 205
206 206 @classmethod
207 207 def get_ldap_settings(cls, cache=False):
208 208 ret = cls.query()\
209 209 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 210 fd = {}
211 211 for row in ret:
212 212 fd.update({row.app_settings_name: row.app_settings_value})
213 213
214 214 return fd
215 215
216 216
217 217 class RhodeCodeUi(Base, BaseModel):
218 218 __tablename__ = 'rhodecode_ui'
219 219 __table_args__ = (
220 220 UniqueConstraint('ui_key'),
221 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 222 'mysql_charset': 'utf8'}
223 223 )
224 224
225 225 HOOK_UPDATE = 'changegroup.update'
226 226 HOOK_REPO_SIZE = 'changegroup.repo_size'
227 227 HOOK_PUSH = 'changegroup.push_logger'
228 228 HOOK_PULL = 'preoutgoing.pull_logger'
229 229
230 230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
235 235
236 236 @classmethod
237 237 def get_by_key(cls, key):
238 238 return cls.query().filter(cls.ui_key == key)
239 239
240 240 @classmethod
241 241 def get_builtin_hooks(cls):
242 242 q = cls.query()
243 243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
244 244 cls.HOOK_REPO_SIZE,
245 245 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 246 return q.all()
247 247
248 248 @classmethod
249 249 def get_custom_hooks(cls):
250 250 q = cls.query()
251 251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
252 252 cls.HOOK_REPO_SIZE,
253 253 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 254 q = q.filter(cls.ui_section == 'hooks')
255 255 return q.all()
256 256
257 257 @classmethod
258 258 def get_repos_location(cls):
259 259 return cls.get_by_key('/').one().ui_value
260 260
261 261 @classmethod
262 262 def create_or_update_hook(cls, key, val):
263 263 new_ui = cls.get_by_key(key).scalar() or cls()
264 264 new_ui.ui_section = 'hooks'
265 265 new_ui.ui_active = True
266 266 new_ui.ui_key = key
267 267 new_ui.ui_value = val
268 268
269 269 Session().add(new_ui)
270 270
271 271
272 272 class User(Base, BaseModel):
273 273 __tablename__ = 'users'
274 274 __table_args__ = (
275 275 UniqueConstraint('username'), UniqueConstraint('email'),
276 276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
277 277 'mysql_charset': 'utf8'}
278 278 )
279 279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
283 283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 290
291 291 user_log = relationship('UserLog', cascade='all')
292 292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293 293
294 294 repositories = relationship('Repository')
295 295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298 298
299 299 group_member = relationship('UsersGroupMember', cascade='all')
300 300
301 301 notifications = relationship('UserNotification', cascade='all')
302 302 # notifications assigned to this user
303 303 user_created_notifications = relationship('Notification', cascade='all')
304 304 # comments created by this user
305 305 user_comments = relationship('ChangesetComment', cascade='all')
306 306 #extra emails for this user
307 307 user_emails = relationship('UserEmailMap', cascade='all')
308 308
309 309 @hybrid_property
310 310 def email(self):
311 311 return self._email
312 312
313 313 @email.setter
314 314 def email(self, val):
315 315 self._email = val.lower() if val else None
316 316
317 317 @property
318 318 def emails(self):
319 319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
320 320 return [self.email] + [x.email for x in other]
321 321
322 322 @property
323 323 def full_name(self):
324 324 return '%s %s' % (self.name, self.lastname)
325 325
326 326 @property
327 327 def full_name_or_username(self):
328 328 return ('%s %s' % (self.name, self.lastname)
329 329 if (self.name and self.lastname) else self.username)
330 330
331 331 @property
332 332 def full_contact(self):
333 333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
334 334
335 335 @property
336 336 def short_contact(self):
337 337 return '%s %s' % (self.name, self.lastname)
338 338
339 339 @property
340 340 def is_admin(self):
341 341 return self.admin
342 342
343 343 def __unicode__(self):
344 344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
345 345 self.user_id, self.username)
346 346
347 347 @classmethod
348 348 def get_by_username(cls, username, case_insensitive=False, cache=False):
349 349 if case_insensitive:
350 350 q = cls.query().filter(cls.username.ilike(username))
351 351 else:
352 352 q = cls.query().filter(cls.username == username)
353 353
354 354 if cache:
355 355 q = q.options(FromCache(
356 356 "sql_cache_short",
357 357 "get_user_%s" % _hash_key(username)
358 358 )
359 359 )
360 360 return q.scalar()
361 361
362 362 @classmethod
363 363 def get_by_api_key(cls, api_key, cache=False):
364 364 q = cls.query().filter(cls.api_key == api_key)
365 365
366 366 if cache:
367 367 q = q.options(FromCache("sql_cache_short",
368 368 "get_api_key_%s" % api_key))
369 369 return q.scalar()
370 370
371 371 @classmethod
372 372 def get_by_email(cls, email, case_insensitive=False, cache=False):
373 373 if case_insensitive:
374 374 q = cls.query().filter(cls.email.ilike(email))
375 375 else:
376 376 q = cls.query().filter(cls.email == email)
377 377
378 378 if cache:
379 379 q = q.options(FromCache("sql_cache_short",
380 380 "get_email_key_%s" % email))
381 381
382 382 ret = q.scalar()
383 383 if ret is None:
384 384 q = UserEmailMap.query()
385 385 # try fetching in alternate email map
386 386 if case_insensitive:
387 387 q = q.filter(UserEmailMap.email.ilike(email))
388 388 else:
389 389 q = q.filter(UserEmailMap.email == email)
390 390 q = q.options(joinedload(UserEmailMap.user))
391 391 if cache:
392 392 q = q.options(FromCache("sql_cache_short",
393 393 "get_email_map_key_%s" % email))
394 394 ret = getattr(q.scalar(), 'user', None)
395 395
396 396 return ret
397 397
398 398 def update_lastlogin(self):
399 399 """Update user lastlogin"""
400 400 self.last_login = datetime.datetime.now()
401 401 Session().add(self)
402 402 log.debug('updated user %s lastlogin' % self.username)
403 403
404 404 def get_api_data(self):
405 405 """
406 406 Common function for generating user related data for API
407 407 """
408 408 user = self
409 409 data = dict(
410 410 user_id=user.user_id,
411 411 username=user.username,
412 412 firstname=user.name,
413 413 lastname=user.lastname,
414 414 email=user.email,
415 415 emails=user.emails,
416 416 api_key=user.api_key,
417 417 active=user.active,
418 418 admin=user.admin,
419 419 ldap_dn=user.ldap_dn,
420 420 last_login=user.last_login,
421 421 )
422 422 return data
423 423
424 424 def __json__(self):
425 425 data = dict(
426 426 full_name=self.full_name,
427 427 full_name_or_username=self.full_name_or_username,
428 428 short_contact=self.short_contact,
429 429 full_contact=self.full_contact
430 430 )
431 431 data.update(self.get_api_data())
432 432 return data
433 433
434 434
435 435 class UserEmailMap(Base, BaseModel):
436 436 __tablename__ = 'user_email_map'
437 437 __table_args__ = (
438 438 Index('uem_email_idx', 'email'),
439 439 UniqueConstraint('email'),
440 440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
441 441 'mysql_charset': 'utf8'}
442 442 )
443 443 __mapper_args__ = {}
444 444
445 445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
446 446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
447 447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
448 448
449 449 user = relationship('User', lazy='joined')
450 450
451 451 @validates('_email')
452 452 def validate_email(self, key, email):
453 453 # check if this email is not main one
454 454 main_email = Session().query(User).filter(User.email == email).scalar()
455 455 if main_email is not None:
456 456 raise AttributeError('email %s is present is user table' % email)
457 457 return email
458 458
459 459 @hybrid_property
460 460 def email(self):
461 461 return self._email
462 462
463 463 @email.setter
464 464 def email(self, val):
465 465 self._email = val.lower() if val else None
466 466
467 467
468 468 class UserLog(Base, BaseModel):
469 469 __tablename__ = 'user_logs'
470 470 __table_args__ = (
471 471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 472 'mysql_charset': 'utf8'},
473 473 )
474 474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
476 476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
477 477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
480 480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
481 481
482 482 @property
483 483 def action_as_day(self):
484 484 return datetime.date(*self.action_date.timetuple()[:3])
485 485
486 486 user = relationship('User')
487 487 repository = relationship('Repository', cascade='')
488 488
489 489
490 490 class UsersGroup(Base, BaseModel):
491 491 __tablename__ = 'users_groups'
492 492 __table_args__ = (
493 493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 494 'mysql_charset': 'utf8'},
495 495 )
496 496
497 497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
500 500
501 501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
502 502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
503 503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
504 504
505 505 def __unicode__(self):
506 506 return u'<userGroup(%s)>' % (self.users_group_name)
507 507
508 508 @classmethod
509 509 def get_by_group_name(cls, group_name, cache=False,
510 510 case_insensitive=False):
511 511 if case_insensitive:
512 512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
513 513 else:
514 514 q = cls.query().filter(cls.users_group_name == group_name)
515 515 if cache:
516 516 q = q.options(FromCache(
517 517 "sql_cache_short",
518 518 "get_user_%s" % _hash_key(group_name)
519 519 )
520 520 )
521 521 return q.scalar()
522 522
523 523 @classmethod
524 524 def get(cls, users_group_id, cache=False):
525 525 users_group = cls.query()
526 526 if cache:
527 527 users_group = users_group.options(FromCache("sql_cache_short",
528 528 "get_users_group_%s" % users_group_id))
529 529 return users_group.get(users_group_id)
530 530
531 531 def get_api_data(self):
532 532 users_group = self
533 533
534 534 data = dict(
535 535 users_group_id=users_group.users_group_id,
536 536 group_name=users_group.users_group_name,
537 537 active=users_group.users_group_active,
538 538 )
539 539
540 540 return data
541 541
542 542
543 543 class UsersGroupMember(Base, BaseModel):
544 544 __tablename__ = 'users_groups_members'
545 545 __table_args__ = (
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'},
548 548 )
549 549
550 550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
552 552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
553 553
554 554 user = relationship('User', lazy='joined')
555 555 users_group = relationship('UsersGroup')
556 556
557 557 def __init__(self, gr_id='', u_id=''):
558 558 self.users_group_id = gr_id
559 559 self.user_id = u_id
560 560
561 561
562 562 class Repository(Base, BaseModel):
563 563 __tablename__ = 'repositories'
564 564 __table_args__ = (
565 565 UniqueConstraint('repo_name'),
566 566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
567 567 'mysql_charset': 'utf8'},
568 568 )
569 569
570 570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
572 572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
573 573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
574 574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
575 575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
576 576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
577 577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
578 578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
579 579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
580 580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581 581
582 582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
583 583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
584 584
585 585 user = relationship('User')
586 586 fork = relationship('Repository', remote_side=repo_id)
587 587 group = relationship('RepoGroup')
588 588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
589 589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
590 590 stats = relationship('Statistics', cascade='all', uselist=False)
591 591
592 592 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
593 593
594 594 logs = relationship('UserLog')
595 595 comments = relationship('ChangesetComment')
596 596
597 597 def __unicode__(self):
598 598 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
599 599 self.repo_name)
600 600
601 601 @classmethod
602 602 def url_sep(cls):
603 603 return URL_SEP
604 604
605 605 @classmethod
606 606 def get_by_repo_name(cls, repo_name):
607 607 q = Session().query(cls).filter(cls.repo_name == repo_name)
608 608 q = q.options(joinedload(Repository.fork))\
609 609 .options(joinedload(Repository.user))\
610 610 .options(joinedload(Repository.group))
611 611 return q.scalar()
612 612
613 613 @classmethod
614 614 def get_by_full_path(cls, repo_full_path):
615 615 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
616 616 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
617 617
618 618 @classmethod
619 619 def get_repo_forks(cls, repo_id):
620 620 return cls.query().filter(Repository.fork_id == repo_id)
621 621
622 622 @classmethod
623 623 def base_path(cls):
624 624 """
625 625 Returns base path when all repos are stored
626 626
627 627 :param cls:
628 628 """
629 629 q = Session().query(RhodeCodeUi)\
630 630 .filter(RhodeCodeUi.ui_key == cls.url_sep())
631 631 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
632 632 return q.one().ui_value
633 633
634 634 @property
635 635 def forks(self):
636 636 """
637 637 Return forks of this repo
638 638 """
639 639 return Repository.get_repo_forks(self.repo_id)
640 640
641 641 @property
642 642 def parent(self):
643 643 """
644 644 Returns fork parent
645 645 """
646 646 return self.fork
647 647
648 648 @property
649 649 def just_name(self):
650 650 return self.repo_name.split(Repository.url_sep())[-1]
651 651
652 652 @property
653 653 def groups_with_parents(self):
654 654 groups = []
655 655 if self.group is None:
656 656 return groups
657 657
658 658 cur_gr = self.group
659 659 groups.insert(0, cur_gr)
660 660 while 1:
661 661 gr = getattr(cur_gr, 'parent_group', None)
662 662 cur_gr = cur_gr.parent_group
663 663 if gr is None:
664 664 break
665 665 groups.insert(0, gr)
666 666
667 667 return groups
668 668
669 669 @property
670 670 def groups_and_repo(self):
671 671 return self.groups_with_parents, self.just_name
672 672
673 673 @LazyProperty
674 674 def repo_path(self):
675 675 """
676 676 Returns base full path for that repository means where it actually
677 677 exists on a filesystem
678 678 """
679 679 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
680 680 Repository.url_sep())
681 681 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
682 682 return q.one().ui_value
683 683
684 684 @property
685 685 def repo_full_path(self):
686 686 p = [self.repo_path]
687 687 # we need to split the name by / since this is how we store the
688 688 # names in the database, but that eventually needs to be converted
689 689 # into a valid system path
690 690 p += self.repo_name.split(Repository.url_sep())
691 691 return os.path.join(*p)
692 692
693 693 def get_new_name(self, repo_name):
694 694 """
695 695 returns new full repository name based on assigned group and new new
696 696
697 697 :param group_name:
698 698 """
699 699 path_prefix = self.group.full_path_splitted if self.group else []
700 700 return Repository.url_sep().join(path_prefix + [repo_name])
701 701
702 702 @property
703 703 def _ui(self):
704 704 """
705 705 Creates an db based ui object for this repository
706 706 """
707 707 from mercurial import ui
708 708 from mercurial import config
709 709 baseui = ui.ui()
710 710
711 711 #clean the baseui object
712 712 baseui._ocfg = config.config()
713 713 baseui._ucfg = config.config()
714 714 baseui._tcfg = config.config()
715 715
716 716 ret = RhodeCodeUi.query()\
717 717 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
718 718
719 719 hg_ui = ret
720 720 for ui_ in hg_ui:
721 721 if ui_.ui_active:
722 722 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
723 723 ui_.ui_key, ui_.ui_value)
724 724 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
725 725
726 726 return baseui
727 727
728 728 @classmethod
729 729 def inject_ui(cls, repo, extras={}):
730 730 from rhodecode.lib.vcs.backends.hg import MercurialRepository
731 731 from rhodecode.lib.vcs.backends.git import GitRepository
732 732 required = (MercurialRepository, GitRepository)
733 733 if not isinstance(repo, required):
734 734 raise Exception('repo must be instance of %s' % required)
735 735
736 736 # inject ui extra param to log this action via push logger
737 737 for k, v in extras.items():
738 738 repo._repo.ui.setconfig('rhodecode_extras', k, v)
739 739
740 740 @classmethod
741 741 def is_valid(cls, repo_name):
742 742 """
743 743 returns True if given repo name is a valid filesystem repository
744 744
745 745 :param cls:
746 746 :param repo_name:
747 747 """
748 748 from rhodecode.lib.utils import is_valid_repo
749 749
750 750 return is_valid_repo(repo_name, cls.base_path())
751 751
752 752 def get_api_data(self):
753 753 """
754 754 Common function for generating repo api data
755 755
756 756 """
757 757 repo = self
758 758 data = dict(
759 759 repo_id=repo.repo_id,
760 760 repo_name=repo.repo_name,
761 761 repo_type=repo.repo_type,
762 762 clone_uri=repo.clone_uri,
763 763 private=repo.private,
764 764 created_on=repo.created_on,
765 765 description=repo.description,
766 766 landing_rev=repo.landing_rev,
767 767 owner=repo.user.username,
768 768 fork_of=repo.fork.repo_name if repo.fork else None
769 769 )
770 770
771 771 return data
772 772
773 773 #==========================================================================
774 774 # SCM PROPERTIES
775 775 #==========================================================================
776 776
777 777 def get_changeset(self, rev=None):
778 778 return get_changeset_safe(self.scm_instance, rev)
779 779
780 780 def get_landing_changeset(self):
781 781 """
782 782 Returns landing changeset, or if that doesn't exist returns the tip
783 783 """
784 784 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
785 785 return cs
786 786
787 787 @property
788 788 def tip(self):
789 789 return self.get_changeset('tip')
790 790
791 791 @property
792 792 def author(self):
793 793 return self.tip.author
794 794
795 795 @property
796 796 def last_change(self):
797 797 return self.scm_instance.last_change
798 798
799 799 def get_comments(self, revisions=None):
800 800 """
801 801 Returns comments for this repository grouped by revisions
802 802
803 803 :param revisions: filter query by revisions only
804 804 """
805 805 cmts = ChangesetComment.query()\
806 806 .filter(ChangesetComment.repo == self)
807 807 if revisions:
808 808 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
809 809 grouped = defaultdict(list)
810 810 for cmt in cmts.all():
811 811 grouped[cmt.revision].append(cmt)
812 812 return grouped
813 813
814 814 def statuses(self, revisions=None):
815 815 """
816 816 Returns statuses for this repository
817 817
818 818 :param revisions: list of revisions to get statuses for
819 819 :type revisions: list
820 820 """
821 821
822 822 statuses = ChangesetStatus.query()\
823 823 .filter(ChangesetStatus.repo == self)\
824 824 .filter(ChangesetStatus.version == 0)
825 825 if revisions:
826 826 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
827 827 grouped = {}
828 828
829 829 #maybe we have open new pullrequest without a status ?
830 830 stat = ChangesetStatus.STATUS_UNDER_REVIEW
831 831 status_lbl = ChangesetStatus.get_status_lbl(stat)
832 832 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
833 833 for rev in pr.revisions:
834 834 pr_id = pr.pull_request_id
835 835 pr_repo = pr.other_repo.repo_name
836 836 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
837 837
838 838 for stat in statuses.all():
839 839 pr_id = pr_repo = None
840 840 if stat.pull_request:
841 841 pr_id = stat.pull_request.pull_request_id
842 842 pr_repo = stat.pull_request.other_repo.repo_name
843 843 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
844 844 pr_id, pr_repo]
845 845 return grouped
846 846
847 847 #==========================================================================
848 848 # SCM CACHE INSTANCE
849 849 #==========================================================================
850 850
851 851 @property
852 852 def invalidate(self):
853 853 return CacheInvalidation.invalidate(self.repo_name)
854 854
855 855 def set_invalidate(self):
856 856 """
857 857 set a cache for invalidation for this instance
858 858 """
859 859 CacheInvalidation.set_invalidate(self.repo_name)
860 860
861 861 @LazyProperty
862 862 def scm_instance(self):
863 863 return self.__get_instance()
864 864
865 865 def scm_instance_cached(self, cache_map=None):
866 866 @cache_region('long_term')
867 867 def _c(repo_name):
868 868 return self.__get_instance()
869 869 rn = self.repo_name
870 870 log.debug('Getting cached instance of repo')
871 871
872 872 if cache_map:
873 873 # get using prefilled cache_map
874 874 invalidate_repo = cache_map[self.repo_name]
875 875 if invalidate_repo:
876 876 invalidate_repo = (None if invalidate_repo.cache_active
877 877 else invalidate_repo)
878 878 else:
879 879 # get from invalidate
880 880 invalidate_repo = self.invalidate
881 881
882 882 if invalidate_repo is not None:
883 883 region_invalidate(_c, None, rn)
884 884 # update our cache
885 885 CacheInvalidation.set_valid(invalidate_repo.cache_key)
886 886 return _c(rn)
887 887
888 888 def __get_instance(self):
889 889 repo_full_path = self.repo_full_path
890 890 try:
891 891 alias = get_scm(repo_full_path)[0]
892 892 log.debug('Creating instance of %s repository' % alias)
893 893 backend = get_backend(alias)
894 894 except VCSError:
895 895 log.error(traceback.format_exc())
896 896 log.error('Perhaps this repository is in db and not in '
897 897 'filesystem run rescan repositories with '
898 898 '"destroy old data " option from admin panel')
899 899 return
900 900
901 901 if alias == 'hg':
902 902
903 903 repo = backend(safe_str(repo_full_path), create=False,
904 904 baseui=self._ui)
905 905 # skip hidden web repository
906 906 if repo._get_hidden():
907 907 return
908 908 else:
909 909 repo = backend(repo_full_path, create=False)
910 910
911 911 return repo
912 912
913 913
914 914 class RepoGroup(Base, BaseModel):
915 915 __tablename__ = 'groups'
916 916 __table_args__ = (
917 917 UniqueConstraint('group_name', 'group_parent_id'),
918 918 CheckConstraint('group_id != group_parent_id'),
919 919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
920 920 'mysql_charset': 'utf8'},
921 921 )
922 922 __mapper_args__ = {'order_by': 'group_name'}
923 923
924 924 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
925 925 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
926 926 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
927 927 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
928 928
929 929 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
930 930 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
931 931
932 932 parent_group = relationship('RepoGroup', remote_side=group_id)
933 933
934 934 def __init__(self, group_name='', parent_group=None):
935 935 self.group_name = group_name
936 936 self.parent_group = parent_group
937 937
938 938 def __unicode__(self):
939 939 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
940 940 self.group_name)
941 941
942 942 @classmethod
943 943 def groups_choices(cls):
944 944 from webhelpers.html import literal as _literal
945 945 repo_groups = [('', '')]
946 946 sep = ' &raquo; '
947 947 _name = lambda k: _literal(sep.join(k))
948 948
949 949 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
950 950 for x in cls.query().all()])
951 951
952 952 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
953 953 return repo_groups
954 954
955 955 @classmethod
956 956 def url_sep(cls):
957 957 return URL_SEP
958 958
959 959 @classmethod
960 960 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
961 961 if case_insensitive:
962 962 gr = cls.query()\
963 963 .filter(cls.group_name.ilike(group_name))
964 964 else:
965 965 gr = cls.query()\
966 966 .filter(cls.group_name == group_name)
967 967 if cache:
968 968 gr = gr.options(FromCache(
969 969 "sql_cache_short",
970 970 "get_group_%s" % _hash_key(group_name)
971 971 )
972 972 )
973 973 return gr.scalar()
974 974
975 975 @property
976 976 def parents(self):
977 977 parents_recursion_limit = 5
978 978 groups = []
979 979 if self.parent_group is None:
980 980 return groups
981 981 cur_gr = self.parent_group
982 982 groups.insert(0, cur_gr)
983 983 cnt = 0
984 984 while 1:
985 985 cnt += 1
986 986 gr = getattr(cur_gr, 'parent_group', None)
987 987 cur_gr = cur_gr.parent_group
988 988 if gr is None:
989 989 break
990 990 if cnt == parents_recursion_limit:
991 991 # this will prevent accidental infinit loops
992 992 log.error('group nested more than %s' %
993 993 parents_recursion_limit)
994 994 break
995 995
996 996 groups.insert(0, gr)
997 997 return groups
998 998
999 999 @property
1000 1000 def children(self):
1001 1001 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1002 1002
1003 1003 @property
1004 1004 def name(self):
1005 1005 return self.group_name.split(RepoGroup.url_sep())[-1]
1006 1006
1007 1007 @property
1008 1008 def full_path(self):
1009 1009 return self.group_name
1010 1010
1011 1011 @property
1012 1012 def full_path_splitted(self):
1013 1013 return self.group_name.split(RepoGroup.url_sep())
1014 1014
1015 1015 @property
1016 1016 def repositories(self):
1017 1017 return Repository.query()\
1018 1018 .filter(Repository.group == self)\
1019 1019 .order_by(Repository.repo_name)
1020 1020
1021 1021 @property
1022 1022 def repositories_recursive_count(self):
1023 1023 cnt = self.repositories.count()
1024 1024
1025 1025 def children_count(group):
1026 1026 cnt = 0
1027 1027 for child in group.children:
1028 1028 cnt += child.repositories.count()
1029 1029 cnt += children_count(child)
1030 1030 return cnt
1031 1031
1032 1032 return cnt + children_count(self)
1033 1033
1034 1034 def get_new_name(self, group_name):
1035 1035 """
1036 1036 returns new full group name based on parent and new name
1037 1037
1038 1038 :param group_name:
1039 1039 """
1040 1040 path_prefix = (self.parent_group.full_path_splitted if
1041 1041 self.parent_group else [])
1042 1042 return RepoGroup.url_sep().join(path_prefix + [group_name])
1043 1043
1044 1044
1045 1045 class Permission(Base, BaseModel):
1046 1046 __tablename__ = 'permissions'
1047 1047 __table_args__ = (
1048 1048 Index('p_perm_name_idx', 'permission_name'),
1049 1049 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1050 1050 'mysql_charset': 'utf8'},
1051 1051 )
1052 1052 PERMS = [
1053 1053 ('repository.none', _('Repository no access')),
1054 1054 ('repository.read', _('Repository read access')),
1055 1055 ('repository.write', _('Repository write access')),
1056 1056 ('repository.admin', _('Repository admin access')),
1057 1057
1058 1058 ('group.none', _('Repositories Group no access')),
1059 1059 ('group.read', _('Repositories Group read access')),
1060 1060 ('group.write', _('Repositories Group write access')),
1061 1061 ('group.admin', _('Repositories Group admin access')),
1062 1062
1063 1063 ('hg.admin', _('RhodeCode Administrator')),
1064 1064 ('hg.create.none', _('Repository creation disabled')),
1065 1065 ('hg.create.repository', _('Repository creation enabled')),
1066 1066 ('hg.register.none', _('Register disabled')),
1067 1067 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1068 1068 'with manual activation')),
1069 1069
1070 1070 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1071 1071 'with auto activation')),
1072 1072 ]
1073 1073
1074 1074 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 1075 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1076 1076 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1077 1077
1078 1078 def __unicode__(self):
1079 1079 return u"<%s('%s:%s')>" % (
1080 1080 self.__class__.__name__, self.permission_id, self.permission_name
1081 1081 )
1082 1082
1083 1083 @classmethod
1084 1084 def get_by_key(cls, key):
1085 1085 return cls.query().filter(cls.permission_name == key).scalar()
1086 1086
1087 1087 @classmethod
1088 1088 def get_default_perms(cls, default_user_id):
1089 1089 q = Session().query(UserRepoToPerm, Repository, cls)\
1090 1090 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1091 1091 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1092 1092 .filter(UserRepoToPerm.user_id == default_user_id)
1093 1093
1094 1094 return q.all()
1095 1095
1096 1096 @classmethod
1097 1097 def get_default_group_perms(cls, default_user_id):
1098 1098 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1099 1099 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1100 1100 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1101 1101 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1102 1102
1103 1103 return q.all()
1104 1104
1105 1105
1106 1106 class UserRepoToPerm(Base, BaseModel):
1107 1107 __tablename__ = 'repo_to_perm'
1108 1108 __table_args__ = (
1109 1109 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1110 1110 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1111 1111 'mysql_charset': 'utf8'}
1112 1112 )
1113 1113 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1114 1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1115 1115 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1116 1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1117 1117
1118 1118 user = relationship('User')
1119 1119 repository = relationship('Repository')
1120 1120 permission = relationship('Permission')
1121 1121
1122 1122 @classmethod
1123 1123 def create(cls, user, repository, permission):
1124 1124 n = cls()
1125 1125 n.user = user
1126 1126 n.repository = repository
1127 1127 n.permission = permission
1128 1128 Session().add(n)
1129 1129 return n
1130 1130
1131 1131 def __unicode__(self):
1132 1132 return u'<user:%s => %s >' % (self.user, self.repository)
1133 1133
1134 1134
1135 1135 class UserToPerm(Base, BaseModel):
1136 1136 __tablename__ = 'user_to_perm'
1137 1137 __table_args__ = (
1138 1138 UniqueConstraint('user_id', 'permission_id'),
1139 1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 1140 'mysql_charset': 'utf8'}
1141 1141 )
1142 1142 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1143 1143 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1144 1144 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1145 1145
1146 1146 user = relationship('User')
1147 1147 permission = relationship('Permission', lazy='joined')
1148 1148
1149 1149
1150 1150 class UsersGroupRepoToPerm(Base, BaseModel):
1151 1151 __tablename__ = 'users_group_repo_to_perm'
1152 1152 __table_args__ = (
1153 1153 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1154 1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 1155 'mysql_charset': 'utf8'}
1156 1156 )
1157 1157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1158 1158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1159 1159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1160 1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1161 1161
1162 1162 users_group = relationship('UsersGroup')
1163 1163 permission = relationship('Permission')
1164 1164 repository = relationship('Repository')
1165 1165
1166 1166 @classmethod
1167 1167 def create(cls, users_group, repository, permission):
1168 1168 n = cls()
1169 1169 n.users_group = users_group
1170 1170 n.repository = repository
1171 1171 n.permission = permission
1172 1172 Session().add(n)
1173 1173 return n
1174 1174
1175 1175 def __unicode__(self):
1176 1176 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1177 1177
1178 1178
1179 1179 class UsersGroupToPerm(Base, BaseModel):
1180 1180 __tablename__ = 'users_group_to_perm'
1181 1181 __table_args__ = (
1182 1182 UniqueConstraint('users_group_id', 'permission_id',),
1183 1183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1184 1184 'mysql_charset': 'utf8'}
1185 1185 )
1186 1186 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1187 1187 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1188 1188 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1189 1189
1190 1190 users_group = relationship('UsersGroup')
1191 1191 permission = relationship('Permission')
1192 1192
1193 1193
1194 1194 class UserRepoGroupToPerm(Base, BaseModel):
1195 1195 __tablename__ = 'user_repo_group_to_perm'
1196 1196 __table_args__ = (
1197 1197 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1198 1198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1199 1199 'mysql_charset': 'utf8'}
1200 1200 )
1201 1201
1202 1202 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1204 1204 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1205 1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1206 1206
1207 1207 user = relationship('User')
1208 1208 group = relationship('RepoGroup')
1209 1209 permission = relationship('Permission')
1210 1210
1211 1211
1212 1212 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1213 1213 __tablename__ = 'users_group_repo_group_to_perm'
1214 1214 __table_args__ = (
1215 1215 UniqueConstraint('users_group_id', 'group_id'),
1216 1216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1217 1217 'mysql_charset': 'utf8'}
1218 1218 )
1219 1219
1220 1220 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1221 1221 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1222 1222 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1223 1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1224 1224
1225 1225 users_group = relationship('UsersGroup')
1226 1226 permission = relationship('Permission')
1227 1227 group = relationship('RepoGroup')
1228 1228
1229 1229
1230 1230 class Statistics(Base, BaseModel):
1231 1231 __tablename__ = 'statistics'
1232 1232 __table_args__ = (
1233 1233 UniqueConstraint('repository_id'),
1234 1234 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1235 1235 'mysql_charset': 'utf8'}
1236 1236 )
1237 1237 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 1238 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1239 1239 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1240 1240 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1241 1241 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1242 1242 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1243 1243
1244 1244 repository = relationship('Repository', single_parent=True)
1245 1245
1246 1246
1247 1247 class UserFollowing(Base, BaseModel):
1248 1248 __tablename__ = 'user_followings'
1249 1249 __table_args__ = (
1250 1250 UniqueConstraint('user_id', 'follows_repository_id'),
1251 1251 UniqueConstraint('user_id', 'follows_user_id'),
1252 1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 1253 'mysql_charset': 'utf8'}
1254 1254 )
1255 1255
1256 1256 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 1257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1258 1258 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1259 1259 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1260 1260 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1261 1261
1262 1262 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1263 1263
1264 1264 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1265 1265 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1266 1266
1267 1267 @classmethod
1268 1268 def get_repo_followers(cls, repo_id):
1269 1269 return cls.query().filter(cls.follows_repo_id == repo_id)
1270 1270
1271 1271
1272 1272 class CacheInvalidation(Base, BaseModel):
1273 1273 __tablename__ = 'cache_invalidation'
1274 1274 __table_args__ = (
1275 1275 UniqueConstraint('cache_key'),
1276 1276 Index('key_idx', 'cache_key'),
1277 1277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1278 1278 'mysql_charset': 'utf8'},
1279 1279 )
1280 1280 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1281 1281 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1282 1282 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1283 1283 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1284 1284
1285 1285 def __init__(self, cache_key, cache_args=''):
1286 1286 self.cache_key = cache_key
1287 1287 self.cache_args = cache_args
1288 1288 self.cache_active = False
1289 1289
1290 1290 def __unicode__(self):
1291 1291 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1292 1292 self.cache_id, self.cache_key)
1293 1293
1294 1294 @classmethod
1295 1295 def clear_cache(cls):
1296 1296 cls.query().delete()
1297 1297
1298 1298 @classmethod
1299 1299 def _get_key(cls, key):
1300 1300 """
1301 1301 Wrapper for generating a key, together with a prefix
1302 1302
1303 1303 :param key:
1304 1304 """
1305 1305 import rhodecode
1306 1306 prefix = ''
1307 1307 iid = rhodecode.CONFIG.get('instance_id')
1308 1308 if iid:
1309 1309 prefix = iid
1310 1310 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1311 1311
1312 1312 @classmethod
1313 1313 def get_by_key(cls, key):
1314 1314 return cls.query().filter(cls.cache_key == key).scalar()
1315 1315
1316 1316 @classmethod
1317 1317 def _get_or_create_key(cls, key, prefix, org_key):
1318 1318 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1319 1319 if not inv_obj:
1320 1320 try:
1321 1321 inv_obj = CacheInvalidation(key, org_key)
1322 1322 Session().add(inv_obj)
1323 1323 Session().commit()
1324 1324 except Exception:
1325 1325 log.error(traceback.format_exc())
1326 1326 Session().rollback()
1327 1327 return inv_obj
1328 1328
1329 1329 @classmethod
1330 1330 def invalidate(cls, key):
1331 1331 """
1332 1332 Returns Invalidation object if this given key should be invalidated
1333 1333 None otherwise. `cache_active = False` means that this cache
1334 1334 state is not valid and needs to be invalidated
1335 1335
1336 1336 :param key:
1337 1337 """
1338 1338
1339 1339 key, _prefix, _org_key = cls._get_key(key)
1340 1340 inv = cls._get_or_create_key(key, _prefix, _org_key)
1341 1341
1342 1342 if inv and inv.cache_active is False:
1343 1343 return inv
1344 1344
1345 1345 @classmethod
1346 1346 def set_invalidate(cls, key):
1347 1347 """
1348 1348 Mark this Cache key for invalidation
1349 1349
1350 1350 :param key:
1351 1351 """
1352 1352
1353 1353 key, _prefix, _org_key = cls._get_key(key)
1354 1354 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1355 1355 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1356 1356 _org_key))
1357 1357 try:
1358 1358 for inv_obj in inv_objs:
1359 1359 if inv_obj:
1360 1360 inv_obj.cache_active = False
1361 1361
1362 1362 Session().add(inv_obj)
1363 1363 Session().commit()
1364 1364 except Exception:
1365 1365 log.error(traceback.format_exc())
1366 1366 Session().rollback()
1367 1367
1368 1368 @classmethod
1369 1369 def set_valid(cls, key):
1370 1370 """
1371 1371 Mark this cache key as active and currently cached
1372 1372
1373 1373 :param key:
1374 1374 """
1375 1375 inv_obj = cls.get_by_key(key)
1376 1376 inv_obj.cache_active = True
1377 1377 Session().add(inv_obj)
1378 1378 Session().commit()
1379 1379
1380 1380 @classmethod
1381 1381 def get_cache_map(cls):
1382 1382
1383 1383 class cachemapdict(dict):
1384 1384
1385 1385 def __init__(self, *args, **kwargs):
1386 1386 fixkey = kwargs.get('fixkey')
1387 1387 if fixkey:
1388 1388 del kwargs['fixkey']
1389 1389 self.fixkey = fixkey
1390 1390 super(cachemapdict, self).__init__(*args, **kwargs)
1391 1391
1392 1392 def __getattr__(self, name):
1393 1393 key = name
1394 1394 if self.fixkey:
1395 1395 key, _prefix, _org_key = cls._get_key(key)
1396 1396 if key in self.__dict__:
1397 1397 return self.__dict__[key]
1398 1398 else:
1399 1399 return self[key]
1400 1400
1401 1401 def __getitem__(self, key):
1402 1402 if self.fixkey:
1403 1403 key, _prefix, _org_key = cls._get_key(key)
1404 1404 try:
1405 1405 return super(cachemapdict, self).__getitem__(key)
1406 1406 except KeyError:
1407 1407 return
1408 1408
1409 1409 cache_map = cachemapdict(fixkey=True)
1410 1410 for obj in cls.query().all():
1411 1411 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1412 1412 return cache_map
1413 1413
1414 1414
1415 1415 class ChangesetComment(Base, BaseModel):
1416 1416 __tablename__ = 'changeset_comments'
1417 1417 __table_args__ = (
1418 1418 Index('cc_revision_idx', 'revision'),
1419 1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1420 1420 'mysql_charset': 'utf8'},
1421 1421 )
1422 1422 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1423 1423 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1424 1424 revision = Column('revision', String(40), nullable=True)
1425 1425 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1426 1426 line_no = Column('line_no', Unicode(10), nullable=True)
1427 1427 f_path = Column('f_path', Unicode(1000), nullable=True)
1428 1428 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1429 1429 text = Column('text', Unicode(25000), nullable=False)
1430 1430 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1431 1431
1432 1432 author = relationship('User', lazy='joined')
1433 1433 repo = relationship('Repository')
1434 1434 status_change = relationship('ChangesetStatus', uselist=False)
1435 1435 pull_request = relationship('PullRequest', lazy='joined')
1436 1436
1437 1437 @classmethod
1438 1438 def get_users(cls, revision=None, pull_request_id=None):
1439 1439 """
1440 1440 Returns user associated with this ChangesetComment. ie those
1441 1441 who actually commented
1442 1442
1443 1443 :param cls:
1444 1444 :param revision:
1445 1445 """
1446 1446 q = Session().query(User)\
1447 1447 .join(ChangesetComment.author)
1448 1448 if revision:
1449 1449 q = q.filter(cls.revision == revision)
1450 1450 elif pull_request_id:
1451 1451 q = q.filter(cls.pull_request_id == pull_request_id)
1452 1452 return q.all()
1453 1453
1454 1454
1455 1455 class ChangesetStatus(Base, BaseModel):
1456 1456 __tablename__ = 'changeset_statuses'
1457 1457 __table_args__ = (
1458 1458 Index('cs_revision_idx', 'revision'),
1459 1459 Index('cs_version_idx', 'version'),
1460 1460 UniqueConstraint('repo_id', 'revision', 'version'),
1461 1461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1462 1462 'mysql_charset': 'utf8'}
1463 1463 )
1464 1464 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1465 1465 STATUS_APPROVED = 'approved'
1466 1466 STATUS_REJECTED = 'rejected'
1467 1467 STATUS_UNDER_REVIEW = 'under_review'
1468 1468
1469 1469 STATUSES = [
1470 1470 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1471 1471 (STATUS_APPROVED, _("Approved")),
1472 1472 (STATUS_REJECTED, _("Rejected")),
1473 1473 (STATUS_UNDER_REVIEW, _("Under Review")),
1474 1474 ]
1475 1475
1476 1476 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1477 1477 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1478 1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1479 1479 revision = Column('revision', String(40), nullable=False)
1480 1480 status = Column('status', String(128), nullable=False, default=DEFAULT)
1481 1481 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1482 1482 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1483 1483 version = Column('version', Integer(), nullable=False, default=0)
1484 1484 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1485 1485
1486 1486 author = relationship('User', lazy='joined')
1487 1487 repo = relationship('Repository')
1488 1488 comment = relationship('ChangesetComment', lazy='joined')
1489 1489 pull_request = relationship('PullRequest', lazy='joined')
1490 1490
1491 1491 def __unicode__(self):
1492 1492 return u"<%s('%s:%s')>" % (
1493 1493 self.__class__.__name__,
1494 1494 self.status, self.author
1495 1495 )
1496 1496
1497 1497 @classmethod
1498 1498 def get_status_lbl(cls, value):
1499 1499 return dict(cls.STATUSES).get(value)
1500 1500
1501 1501 @property
1502 1502 def status_lbl(self):
1503 1503 return ChangesetStatus.get_status_lbl(self.status)
1504 1504
1505 1505
1506 1506 class PullRequest(Base, BaseModel):
1507 1507 __tablename__ = 'pull_requests'
1508 1508 __table_args__ = (
1509 1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1510 1510 'mysql_charset': 'utf8'},
1511 1511 )
1512 1512
1513 1513 STATUS_NEW = u'new'
1514 1514 STATUS_OPEN = u'open'
1515 1515 STATUS_CLOSED = u'closed'
1516 1516
1517 1517 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1518 1518 title = Column('title', Unicode(256), nullable=True)
1519 1519 description = Column('description', UnicodeText(10240), nullable=True)
1520 1520 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1521 1521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1522 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1522 1523 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1523 1524 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1524 1525 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1525 1526 org_ref = Column('org_ref', Unicode(256), nullable=False)
1526 1527 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1527 1528 other_ref = Column('other_ref', Unicode(256), nullable=False)
1528 1529
1530 statuses = relationship('ChangesetStatus')
1531
1529 1532 @hybrid_property
1530 1533 def revisions(self):
1531 1534 return self._revisions.split(':')
1532 1535
1533 1536 @revisions.setter
1534 1537 def revisions(self, val):
1535 1538 self._revisions = ':'.join(val)
1536 1539
1537 1540 author = relationship('User', lazy='joined')
1538 1541 reviewers = relationship('PullRequestReviewers')
1539 1542 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1540 1543 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1541 1544
1545 def is_closed(self):
1546 return self.status == self.STATUS_CLOSED
1547
1542 1548 def __json__(self):
1543 1549 return dict(
1544 1550 revisions=self.revisions
1545 1551 )
1546 1552
1547 1553
1548 1554 class PullRequestReviewers(Base, BaseModel):
1549 1555 __tablename__ = 'pull_request_reviewers'
1550 1556 __table_args__ = (
1551 1557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 1558 'mysql_charset': 'utf8'},
1553 1559 )
1554 1560
1555 1561 def __init__(self, user=None, pull_request=None):
1556 1562 self.user = user
1557 1563 self.pull_request = pull_request
1558 1564
1559 1565 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1560 1566 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1561 1567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1562 1568
1563 1569 user = relationship('User')
1564 1570 pull_request = relationship('PullRequest')
1565 1571
1566 1572
1567 1573 class Notification(Base, BaseModel):
1568 1574 __tablename__ = 'notifications'
1569 1575 __table_args__ = (
1570 1576 Index('notification_type_idx', 'type'),
1571 1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 1578 'mysql_charset': 'utf8'},
1573 1579 )
1574 1580
1575 1581 TYPE_CHANGESET_COMMENT = u'cs_comment'
1576 1582 TYPE_MESSAGE = u'message'
1577 1583 TYPE_MENTION = u'mention'
1578 1584 TYPE_REGISTRATION = u'registration'
1579 1585 TYPE_PULL_REQUEST = u'pull_request'
1580 1586 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1581 1587
1582 1588 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1583 1589 subject = Column('subject', Unicode(512), nullable=True)
1584 1590 body = Column('body', UnicodeText(50000), nullable=True)
1585 1591 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1586 1592 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1587 1593 type_ = Column('type', Unicode(256))
1588 1594
1589 1595 created_by_user = relationship('User')
1590 1596 notifications_to_users = relationship('UserNotification', lazy='joined',
1591 1597 cascade="all, delete, delete-orphan")
1592 1598
1593 1599 @property
1594 1600 def recipients(self):
1595 1601 return [x.user for x in UserNotification.query()\
1596 1602 .filter(UserNotification.notification == self)\
1597 1603 .order_by(UserNotification.user_id.asc()).all()]
1598 1604
1599 1605 @classmethod
1600 1606 def create(cls, created_by, subject, body, recipients, type_=None):
1601 1607 if type_ is None:
1602 1608 type_ = Notification.TYPE_MESSAGE
1603 1609
1604 1610 notification = cls()
1605 1611 notification.created_by_user = created_by
1606 1612 notification.subject = subject
1607 1613 notification.body = body
1608 1614 notification.type_ = type_
1609 1615 notification.created_on = datetime.datetime.now()
1610 1616
1611 1617 for u in recipients:
1612 1618 assoc = UserNotification()
1613 1619 assoc.notification = notification
1614 1620 u.notifications.append(assoc)
1615 1621 Session().add(notification)
1616 1622 return notification
1617 1623
1618 1624 @property
1619 1625 def description(self):
1620 1626 from rhodecode.model.notification import NotificationModel
1621 1627 return NotificationModel().make_description(self)
1622 1628
1623 1629
1624 1630 class UserNotification(Base, BaseModel):
1625 1631 __tablename__ = 'user_to_notification'
1626 1632 __table_args__ = (
1627 1633 UniqueConstraint('user_id', 'notification_id'),
1628 1634 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1629 1635 'mysql_charset': 'utf8'}
1630 1636 )
1631 1637 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1632 1638 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1633 1639 read = Column('read', Boolean, default=False)
1634 1640 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1635 1641
1636 1642 user = relationship('User', lazy="joined")
1637 1643 notification = relationship('Notification', lazy="joined",
1638 1644 order_by=lambda: Notification.created_on.desc(),)
1639 1645
1640 1646 def mark_as_read(self):
1641 1647 self.read = True
1642 1648 Session().add(self)
1643 1649
1644 1650
1645 1651 class DbMigrateVersion(Base, BaseModel):
1646 1652 __tablename__ = 'db_migrate_version'
1647 1653 __table_args__ = (
1648 1654 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1649 1655 'mysql_charset': 'utf8'},
1650 1656 )
1651 1657 repository_id = Column('repository_id', String(250), primary_key=True)
1652 1658 repository_path = Column('repository_path', Text)
1653 1659 version = Column('version', Integer)
@@ -1,194 +1,204 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 rhodecode.model.pull_reuquest
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull request model for RhodeCode
7 7
8 8 :created_on: Jun 6, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import binascii
28 import datetime
28 29
29 30 from pylons.i18n.translation import _
30 31
31 32 from rhodecode.model.meta import Session
32 33 from rhodecode.lib import helpers as h
33 34 from rhodecode.model import BaseModel
34 35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
35 36 from rhodecode.model.notification import NotificationModel
36 37 from rhodecode.lib.utils2 import safe_unicode
37 38
38 39 from rhodecode.lib.vcs.utils.hgcompat import discovery
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 class PullRequestModel(BaseModel):
44 45
45 46 cls = PullRequest
46 47
48 def __get_pull_request(self, pull_request):
49 return self._get_instance(PullRequest, pull_request)
50
47 51 def get_all(self, repo):
48 52 repo = self._get_repo(repo)
49 53 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
50 54
51 55 def create(self, created_by, org_repo, org_ref, other_repo,
52 56 other_ref, revisions, reviewers, title, description=None):
53 57
54 58 created_by_user = self._get_user(created_by)
55 59 org_repo = self._get_repo(org_repo)
56 60 other_repo = self._get_repo(other_repo)
57 61
58 62 new = PullRequest()
59 63 new.org_repo = org_repo
60 64 new.org_ref = org_ref
61 65 new.other_repo = other_repo
62 66 new.other_ref = other_ref
63 67 new.revisions = revisions
64 68 new.title = title
65 69 new.description = description
66 70 new.author = created_by_user
67 71 self.sa.add(new)
68 72 Session().flush()
69 73 #members
70 74 for member in reviewers:
71 75 _usr = self._get_user(member)
72 76 reviewer = PullRequestReviewers(_usr, new)
73 77 self.sa.add(reviewer)
74 78
75 79 #notification to reviewers
76 80 notif = NotificationModel()
77 81
78 82 subject = safe_unicode(
79 83 h.link_to(
80 84 _('%(user)s wants you to review pull request #%(pr_id)s') % \
81 85 {'user': created_by_user.username,
82 86 'pr_id': new.pull_request_id},
83 87 h.url('pullrequest_show', repo_name=other_repo.repo_name,
84 88 pull_request_id=new.pull_request_id,
85 89 qualified=True,
86 90 )
87 91 )
88 92 )
89 93 body = description
90 94 notif.create(created_by=created_by_user, subject=subject, body=body,
91 95 recipients=reviewers,
92 96 type_=Notification.TYPE_PULL_REQUEST,)
93 97
94 98 return new
95 99
100 def close_pull_request(self, pull_request):
101 pull_request = self.__get_pull_request(pull_request)
102 pull_request.status = PullRequest.STATUS_CLOSED
103 pull_request.updated_on = datetime.datetime.now()
104 self.sa.add(pull_request)
105
96 106 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
97 107 discovery_data):
98 108 """
99 109 Returns a list of changesets that are incoming from org_repo@org_ref
100 110 to other_repo@other_ref
101 111
102 112 :param org_repo:
103 113 :type org_repo:
104 114 :param org_ref:
105 115 :type org_ref:
106 116 :param other_repo:
107 117 :type other_repo:
108 118 :param other_ref:
109 119 :type other_ref:
110 120 :param tmp:
111 121 :type tmp:
112 122 """
113 123 changesets = []
114 124 #case two independent repos
115 125 if org_repo != other_repo:
116 126 common, incoming, rheads = discovery_data
117 127
118 128 if not incoming:
119 129 revs = []
120 130 else:
121 131 revs = org_repo._repo.changelog.findmissing(common, rheads)
122 132
123 133 for cs in reversed(map(binascii.hexlify, revs)):
124 134 changesets.append(org_repo.get_changeset(cs))
125 135 else:
126 136 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
127 137 other_ref[1])]
128 138 from mercurial import scmutil
129 139 out = scmutil.revrange(org_repo._repo, revs)
130 140 for cs in reversed(out):
131 141 changesets.append(org_repo.get_changeset(cs))
132 142
133 143 return changesets
134 144
135 145 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
136 146 """
137 147 Get's mercurial discovery data used to calculate difference between
138 148 repos and refs
139 149
140 150 :param org_repo:
141 151 :type org_repo:
142 152 :param org_ref:
143 153 :type org_ref:
144 154 :param other_repo:
145 155 :type other_repo:
146 156 :param other_ref:
147 157 :type other_ref:
148 158 """
149 159
150 160 other = org_repo._repo
151 161 repo = other_repo._repo
152 162 tip = other[org_ref[1]]
153 163 log.debug('Doing discovery for %s@%s vs %s@%s' % (
154 164 org_repo, org_ref, other_repo, other_ref)
155 165 )
156 166 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
157 167 tmp = discovery.findcommonincoming(
158 168 repo=repo, # other_repo we check for incoming
159 169 remote=other, # org_repo source for incoming
160 170 heads=[tip.node()],
161 171 force=False
162 172 )
163 173 return tmp
164 174
165 175 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
166 176 """
167 177 Returns a tuple of incomming changesets, and discoverydata cache
168 178
169 179 :param org_repo:
170 180 :type org_repo:
171 181 :param org_ref:
172 182 :type org_ref:
173 183 :param other_repo:
174 184 :type other_repo:
175 185 :param other_ref:
176 186 :type other_ref:
177 187 """
178 188
179 189 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
180 190 raise Exception('org_ref must be a two element list/tuple')
181 191
182 192 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
183 193 raise Exception('other_ref must be a two element list/tuple')
184 194
185 195 discovery_data = self._get_discovery(org_repo.scm_instance,
186 196 org_ref,
187 197 other_repo.scm_instance,
188 198 other_ref)
189 199 cs_ranges = self._get_changesets(org_repo.scm_instance,
190 200 org_ref,
191 201 other_repo.scm_instance,
192 202 other_ref,
193 203 discovery_data)
194 204 return cs_ranges, discovery_data
@@ -1,1476 +1,1477 b''
1 1 /**
2 2 RhodeCode JS Files
3 3 **/
4 4
5 5 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 6 console = { log: function() {} }
7 7 }
8 8
9 9
10 10 var str_repeat = function(i, m) {
11 11 for (var o = []; m > 0; o[--m] = i);
12 12 return o.join('');
13 13 };
14 14
15 15 /**
16 16 * INJECT .format function into String
17 17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
18 18 * Return "My name is Johny Bravo"
19 19 * Inspired by https://gist.github.com/1049426
20 20 */
21 21 String.prototype.format = function() {
22 22
23 23 function format() {
24 24 var str = this;
25 25 var len = arguments.length+1;
26 26 var safe = undefined;
27 27 var arg = undefined;
28 28
29 29 // For each {0} {1} {n...} replace with the argument in that position. If
30 30 // the argument is an object or an array it will be stringified to JSON.
31 31 for (var i=0; i < len; arg = arguments[i++]) {
32 32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
33 33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
34 34 }
35 35 return str;
36 36 }
37 37
38 38 // Save a reference of what may already exist under the property native.
39 39 // Allows for doing something like: if("".format.native) { /* use native */ }
40 40 format.native = String.prototype.format;
41 41
42 42 // Replace the prototype property
43 43 return format;
44 44
45 45 }();
46 46
47 47 String.prototype.strip = function(char) {
48 48 if(char === undefined){
49 49 char = '\\s';
50 50 }
51 51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
52 52 }
53 53 String.prototype.lstrip = function(char) {
54 54 if(char === undefined){
55 55 char = '\\s';
56 56 }
57 57 return this.replace(new RegExp('^'+char+'+'),'');
58 58 }
59 59 String.prototype.rstrip = function(char) {
60 60 if(char === undefined){
61 61 char = '\\s';
62 62 }
63 63 return this.replace(new RegExp(''+char+'+$'),'');
64 64 }
65 65
66 66 /**
67 67 * SmartColorGenerator
68 68 *
69 69 *usage::
70 70 * var CG = new ColorGenerator();
71 71 * var col = CG.getColor(key); //returns array of RGB
72 72 * 'rgb({0})'.format(col.join(',')
73 73 *
74 74 * @returns {ColorGenerator}
75 75 */
76 76 var ColorGenerator = function(){
77 77 this.GOLDEN_RATIO = 0.618033988749895;
78 78 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
79 79 this.HSV_1 = 0.75;//saturation
80 80 this.HSV_2 = 0.95;
81 81 this.color;
82 82 this.cacheColorMap = {};
83 83 };
84 84
85 85 ColorGenerator.prototype = {
86 86 getColor:function(key){
87 87 if(this.cacheColorMap[key] !== undefined){
88 88 return this.cacheColorMap[key];
89 89 }
90 90 else{
91 91 this.cacheColorMap[key] = this.generateColor();
92 92 return this.cacheColorMap[key];
93 93 }
94 94 },
95 95 _hsvToRgb:function(h,s,v){
96 96 if (s == 0.0)
97 97 return [v, v, v];
98 98 i = parseInt(h * 6.0)
99 99 f = (h * 6.0) - i
100 100 p = v * (1.0 - s)
101 101 q = v * (1.0 - s * f)
102 102 t = v * (1.0 - s * (1.0 - f))
103 103 i = i % 6
104 104 if (i == 0)
105 105 return [v, t, p]
106 106 if (i == 1)
107 107 return [q, v, p]
108 108 if (i == 2)
109 109 return [p, v, t]
110 110 if (i == 3)
111 111 return [p, q, v]
112 112 if (i == 4)
113 113 return [t, p, v]
114 114 if (i == 5)
115 115 return [v, p, q]
116 116 },
117 117 generateColor:function(){
118 118 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
119 119 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
120 120 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
121 121 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
122 122 function toRgb(v){
123 123 return ""+parseInt(v*256)
124 124 }
125 125 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
126 126
127 127 }
128 128 }
129 129
130 130
131 131
132 132
133 133
134 134 /**
135 135 * GLOBAL YUI Shortcuts
136 136 */
137 137 var YUC = YAHOO.util.Connect;
138 138 var YUD = YAHOO.util.Dom;
139 139 var YUE = YAHOO.util.Event;
140 140 var YUQ = YAHOO.util.Selector.query;
141 141
142 142 // defines if push state is enabled for this browser ?
143 143 var push_state_enabled = Boolean(
144 144 window.history && window.history.pushState && window.history.replaceState
145 145 && !( /* disable for versions of iOS before version 4.3 (8F190) */
146 146 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
147 147 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
148 148 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
149 149 )
150 150 );
151 151
152 152 var _run_callbacks = function(callbacks){
153 153 if (callbacks !== undefined){
154 154 var _l = callbacks.length;
155 155 for (var i=0;i<_l;i++){
156 156 var func = callbacks[i];
157 157 if(typeof(func)=='function'){
158 158 try{
159 159 func();
160 160 }catch (err){};
161 161 }
162 162 }
163 163 }
164 164 }
165 165
166 166 /**
167 167 * Partial Ajax Implementation
168 168 *
169 169 * @param url: defines url to make partial request
170 170 * @param container: defines id of container to input partial result
171 171 * @param s_call: success callback function that takes o as arg
172 172 * o.tId
173 173 * o.status
174 174 * o.statusText
175 175 * o.getResponseHeader[ ]
176 176 * o.getAllResponseHeaders
177 177 * o.responseText
178 178 * o.responseXML
179 179 * o.argument
180 180 * @param f_call: failure callback
181 181 * @param args arguments
182 182 */
183 183 function ypjax(url,container,s_call,f_call,args){
184 184 var method='GET';
185 185 if(args===undefined){
186 186 args=null;
187 187 }
188 188
189 189 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
190 190 YUC.initHeader('X-PARTIAL-XHR',true);
191 191
192 192 // wrapper of passed callback
193 193 var s_wrapper = (function(o){
194 194 return function(o){
195 195 YUD.get(container).innerHTML=o.responseText;
196 196 YUD.setStyle(container,'opacity','1.0');
197 197 //execute the given original callback
198 198 if (s_call !== undefined){
199 199 s_call(o);
200 200 }
201 201 }
202 202 })()
203 203 YUD.setStyle(container,'opacity','0.3');
204 204 YUC.asyncRequest(method,url,{
205 205 success:s_wrapper,
206 206 failure:function(o){
207 207 console.log(o);
208 208 YUD.get(container).innerHTML='ERROR '+o.status;
209 209 YUD.setStyle(container,'opacity','1.0');
210 210 YUD.setStyle(container,'color','red');
211 211 }
212 212 },args);
213 213
214 214 };
215 215
216 216 var ajaxPOST = function(url,postData,success) {
217 217 // Set special header for ajax == HTTP_X_PARTIAL_XHR
218 218 YUC.initHeader('X-PARTIAL-XHR',true);
219 219
220 220 var toQueryString = function(o) {
221 221 if(typeof o !== 'object') {
222 222 return false;
223 223 }
224 224 var _p, _qs = [];
225 225 for(_p in o) {
226 226 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
227 227 }
228 228 return _qs.join('&');
229 229 };
230 230
231 231 var sUrl = url;
232 232 var callback = {
233 233 success: success,
234 234 failure: function (o) {
235 235 alert("error");
236 236 },
237 237 };
238 238 var postData = toQueryString(postData);
239 239 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
240 240 return request;
241 241 };
242 242
243 243
244 244 /**
245 245 * tooltip activate
246 246 */
247 247 var tooltip_activate = function(){
248 248 function toolTipsId(){
249 249 var ids = [];
250 250 var tts = YUQ('.tooltip');
251 251 for (var i = 0; i < tts.length; i++) {
252 252 // if element doesn't not have and id
253 253 // autogenerate one for tooltip
254 254 if (!tts[i].id){
255 255 tts[i].id='tt'+((i*100)+tts.length);
256 256 }
257 257 ids.push(tts[i].id);
258 258 }
259 259 return ids
260 260 };
261 261 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
262 262 context: [[toolTipsId()],"tl","bl",null,[0,5]],
263 263 monitorresize:false,
264 264 xyoffset :[0,0],
265 265 autodismissdelay:300000,
266 266 hidedelay:5,
267 267 showdelay:20,
268 268 });
269 269 };
270 270
271 271 /**
272 272 * show more
273 273 */
274 274 var show_more_event = function(){
275 275 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
276 276 var el = e.target;
277 277 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
278 278 YUD.setStyle(el.parentNode,'display','none');
279 279 });
280 280 };
281 281
282 282
283 283 /**
284 284 * Quick filter widget
285 285 *
286 286 * @param target: filter input target
287 287 * @param nodes: list of nodes in html we want to filter.
288 288 * @param display_element function that takes current node from nodes and
289 289 * does hide or show based on the node
290 290 *
291 291 */
292 292 var q_filter = function(target,nodes,display_element){
293 293
294 294 var nodes = nodes;
295 295 var q_filter_field = YUD.get(target);
296 296 var F = YAHOO.namespace(target);
297 297
298 298 YUE.on(q_filter_field,'click',function(){
299 299 q_filter_field.value = '';
300 300 });
301 301
302 302 YUE.on(q_filter_field,'keyup',function(e){
303 303 clearTimeout(F.filterTimeout);
304 304 F.filterTimeout = setTimeout(F.updateFilter,600);
305 305 });
306 306
307 307 F.filterTimeout = null;
308 308
309 309 var show_node = function(node){
310 310 YUD.setStyle(node,'display','')
311 311 }
312 312 var hide_node = function(node){
313 313 YUD.setStyle(node,'display','none');
314 314 }
315 315
316 316 F.updateFilter = function() {
317 317 // Reset timeout
318 318 F.filterTimeout = null;
319 319
320 320 var obsolete = [];
321 321
322 322 var req = q_filter_field.value.toLowerCase();
323 323
324 324 var l = nodes.length;
325 325 var i;
326 326 var showing = 0;
327 327
328 328 for (i=0;i<l;i++ ){
329 329 var n = nodes[i];
330 330 var target_element = display_element(n)
331 331 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
332 332 hide_node(target_element);
333 333 }
334 334 else{
335 335 show_node(target_element);
336 336 showing+=1;
337 337 }
338 338 }
339 339
340 340 // if repo_count is set update the number
341 341 var cnt = YUD.get('repo_count');
342 342 if(cnt){
343 343 YUD.get('repo_count').innerHTML = showing;
344 344 }
345 345
346 346 }
347 347 };
348 348
349 349 var tableTr = function(cls,body){
350 350 var tr = document.createElement('tr');
351 351 YUD.addClass(tr, cls);
352 352
353 353
354 354 var cont = new YAHOO.util.Element(body);
355 355 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
356 356 tr.id = 'comment-tr-{0}'.format(comment_id);
357 357 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
358 358 '<td class="lineno-inline old-inline"></td>'+
359 359 '<td>{0}</td>'.format(body);
360 360 return tr;
361 361 };
362 362
363 363 /** comments **/
364 364 var removeInlineForm = function(form) {
365 365 form.parentNode.removeChild(form);
366 366 };
367 367
368 368 var createInlineForm = function(parent_tr, f_path, line) {
369 369 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
370 370 tmpl = tmpl.format(f_path, line);
371 371 var form = tableTr('comment-form-inline',tmpl)
372 372
373 373 // create event for hide button
374 374 form = new YAHOO.util.Element(form);
375 375 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
376 376 form_hide_button.on('click', function(e) {
377 377 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
378 378 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
379 379 YUD.setStyle(newtr.nextElementSibling,'display','');
380 380 }
381 381 removeInlineForm(newtr);
382 382 YUD.removeClass(parent_tr, 'form-open');
383 383
384 384 });
385 385
386 386 return form
387 387 };
388 388
389 389 /**
390 390 * Inject inline comment for on given TR this tr should be always an .line
391 391 * tr containing the line. Code will detect comment, and always put the comment
392 392 * block at the very bottom
393 393 */
394 394 var injectInlineForm = function(tr){
395 395 if(!YUD.hasClass(tr, 'line')){
396 396 return
397 397 }
398 398 var submit_url = AJAX_COMMENT_URL;
399 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
399 var _td = tr.getElementsByClassName('code')[0];
400 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
400 401 return
401 402 }
402 403 YUD.addClass(tr,'form-open');
403 404 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
404 405 var f_path = YUD.getAttribute(node,'path');
405 406 var lineno = getLineNo(tr);
406 407 var form = createInlineForm(tr, f_path, lineno, submit_url);
407 408
408 409 var parent = tr;
409 410 while (1){
410 411 var n = parent.nextElementSibling;
411 412 // next element are comments !
412 413 if(YUD.hasClass(n,'inline-comments')){
413 414 parent = n;
414 415 }
415 416 else{
416 417 break;
417 418 }
418 419 }
419 420 YUD.insertAfter(form,parent);
420 421
421 422 var f = YUD.get(form);
422 423
423 424 var overlay = f.getElementsByClassName('overlay')[0];
424 425 var _form = f.getElementsByClassName('inline-form')[0];
425 426
426 427 form.on('submit',function(e){
427 428 YUE.preventDefault(e);
428 429
429 430 //ajax submit
430 431 var text = YUD.get('text_'+lineno).value;
431 432 var postData = {
432 433 'text':text,
433 434 'f_path':f_path,
434 435 'line':lineno
435 436 };
436 437
437 438 if(lineno === undefined){
438 439 alert('missing line !');
439 440 return
440 441 }
441 442 if(f_path === undefined){
442 443 alert('missing file path !');
443 444 return
444 445 }
445 446
446 447 if(text == ""){
447 448 return
448 449 }
449 450
450 451 var success = function(o){
451 452 YUD.removeClass(tr, 'form-open');
452 453 removeInlineForm(f);
453 454 var json_data = JSON.parse(o.responseText);
454 455 renderInlineComment(json_data);
455 456 };
456 457
457 458 if (YUD.hasClass(overlay,'overlay')){
458 459 var w = _form.offsetWidth;
459 460 var h = _form.offsetHeight;
460 461 YUD.setStyle(overlay,'width',w+'px');
461 462 YUD.setStyle(overlay,'height',h+'px');
462 463 }
463 464 YUD.addClass(overlay, 'submitting');
464 465
465 466 ajaxPOST(submit_url, postData, success);
466 467 });
467 468
468 469 setTimeout(function(){
469 470 // callbacks
470 471 tooltip_activate();
471 472 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
472 473 _USERS_AC_DATA, _GROUPS_AC_DATA);
473 474 YUD.get('text_'+lineno).focus();
474 475 },10)
475 476 };
476 477
477 478 var deleteComment = function(comment_id){
478 479 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
479 480 var postData = {'_method':'delete'};
480 481 var success = function(o){
481 482 var n = YUD.get('comment-tr-'+comment_id);
482 483 var root = n.previousElementSibling.previousElementSibling;
483 484 n.parentNode.removeChild(n);
484 485
485 486 // scann nodes, and attach add button to last one
486 487 placeAddButton(root);
487 488 }
488 489 ajaxPOST(url,postData,success);
489 490 }
490 491
491 492
492 493 var createInlineAddButton = function(tr){
493 494
494 495 var label = TRANSLATION_MAP['add another comment'];
495 496
496 497 var html_el = document.createElement('div');
497 498 YUD.addClass(html_el, 'add-comment');
498 499 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
499 500
500 501 var add = new YAHOO.util.Element(html_el);
501 502 add.on('click', function(e) {
502 503 injectInlineForm(tr);
503 504 });
504 505 return add;
505 506 };
506 507
507 508 var getLineNo = function(tr) {
508 509 var line;
509 510 var o = tr.children[0].id.split('_');
510 511 var n = tr.children[1].id.split('_');
511 512
512 513 if (n.length >= 2) {
513 514 line = n[n.length-1];
514 515 } else if (o.length >= 2) {
515 516 line = o[o.length-1];
516 517 }
517 518
518 519 return line
519 520 };
520 521
521 522 var placeAddButton = function(target_tr){
522 523 if(!target_tr){
523 524 return
524 525 }
525 526 var last_node = target_tr;
526 527 //scann
527 528 while (1){
528 529 var n = last_node.nextElementSibling;
529 530 // next element are comments !
530 531 if(YUD.hasClass(n,'inline-comments')){
531 532 last_node = n;
532 533 //also remove the comment button from previos
533 534 var comment_add_buttons = last_node.getElementsByClassName('add-comment');
534 535 for(var i=0;i<comment_add_buttons.length;i++){
535 536 var b = comment_add_buttons[i];
536 537 b.parentNode.removeChild(b);
537 538 }
538 539 }
539 540 else{
540 541 break;
541 542 }
542 543 }
543 544
544 545 var add = createInlineAddButton(target_tr);
545 546 // get the comment div
546 547 var comment_block = last_node.getElementsByClassName('comment')[0];
547 548 // attach add button
548 549 YUD.insertAfter(add,comment_block);
549 550 }
550 551
551 552 /**
552 553 * Places the inline comment into the changeset block in proper line position
553 554 */
554 555 var placeInline = function(target_container,lineno,html){
555 556 var lineid = "{0}_{1}".format(target_container,lineno);
556 557 var target_line = YUD.get(lineid);
557 558 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
558 559
559 560 // check if there are comments already !
560 561 var parent = target_line.parentNode;
561 562 var root_parent = parent;
562 563 while (1){
563 564 var n = parent.nextElementSibling;
564 565 // next element are comments !
565 566 if(YUD.hasClass(n,'inline-comments')){
566 567 parent = n;
567 568 }
568 569 else{
569 570 break;
570 571 }
571 572 }
572 573 // put in the comment at the bottom
573 574 YUD.insertAfter(comment,parent);
574 575
575 576 // scann nodes, and attach add button to last one
576 577 placeAddButton(root_parent);
577 578
578 579 return target_line;
579 580 }
580 581
581 582 /**
582 583 * make a single inline comment and place it inside
583 584 */
584 585 var renderInlineComment = function(json_data){
585 586 try{
586 587 var html = json_data['rendered_text'];
587 588 var lineno = json_data['line_no'];
588 589 var target_id = json_data['target_id'];
589 590 placeInline(target_id, lineno, html);
590 591
591 592 }catch(e){
592 593 console.log(e);
593 594 }
594 595 }
595 596
596 597 /**
597 598 * Iterates over all the inlines, and places them inside proper blocks of data
598 599 */
599 600 var renderInlineComments = function(file_comments){
600 601 for (f in file_comments){
601 602 // holding all comments for a FILE
602 603 var box = file_comments[f];
603 604
604 605 var target_id = YUD.getAttribute(box,'target_id');
605 606 // actually comments with line numbers
606 607 var comments = box.children;
607 608 for(var i=0; i<comments.length; i++){
608 609 var data = {
609 610 'rendered_text': comments[i].outerHTML,
610 611 'line_no': YUD.getAttribute(comments[i],'line'),
611 612 'target_id': target_id
612 613 }
613 614 renderInlineComment(data);
614 615 }
615 616 }
616 617 }
617 618
618 619
619 620 var fileBrowserListeners = function(current_url, node_list_url, url_base){
620 621
621 622 var current_url_branch = +"?branch=__BRANCH__";
622 623 var url = url_base;
623 624 var node_url = node_list_url;
624 625
625 626 YUE.on('stay_at_branch','click',function(e){
626 627 if(e.target.checked){
627 628 var uri = current_url_branch;
628 629 uri = uri.replace('__BRANCH__',e.target.value);
629 630 window.location = uri;
630 631 }
631 632 else{
632 633 window.location = current_url;
633 634 }
634 635 })
635 636
636 637 var n_filter = YUD.get('node_filter');
637 638 var F = YAHOO.namespace('node_filter');
638 639
639 640 F.filterTimeout = null;
640 641 var nodes = null;
641 642
642 643 F.initFilter = function(){
643 644 YUD.setStyle('node_filter_box_loading','display','');
644 645 YUD.setStyle('search_activate_id','display','none');
645 646 YUD.setStyle('add_node_id','display','none');
646 647 YUC.initHeader('X-PARTIAL-XHR',true);
647 648 YUC.asyncRequest('GET',url,{
648 649 success:function(o){
649 650 nodes = JSON.parse(o.responseText).nodes;
650 651 YUD.setStyle('node_filter_box_loading','display','none');
651 652 YUD.setStyle('node_filter_box','display','');
652 653 n_filter.focus();
653 654 if(YUD.hasClass(n_filter,'init')){
654 655 n_filter.value = '';
655 656 YUD.removeClass(n_filter,'init');
656 657 }
657 658 },
658 659 failure:function(o){
659 660 console.log('failed to load');
660 661 }
661 662 },null);
662 663 }
663 664
664 665 F.updateFilter = function(e) {
665 666
666 667 return function(){
667 668 // Reset timeout
668 669 F.filterTimeout = null;
669 670 var query = e.target.value.toLowerCase();
670 671 var match = [];
671 672 var matches = 0;
672 673 var matches_max = 20;
673 674 if (query != ""){
674 675 for(var i=0;i<nodes.length;i++){
675 676
676 677 var pos = nodes[i].name.toLowerCase().indexOf(query)
677 678 if(query && pos != -1){
678 679
679 680 matches++
680 681 //show only certain amount to not kill browser
681 682 if (matches > matches_max){
682 683 break;
683 684 }
684 685
685 686 var n = nodes[i].name;
686 687 var t = nodes[i].type;
687 688 var n_hl = n.substring(0,pos)
688 689 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
689 690 +n.substring(pos+query.length)
690 691 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
691 692 }
692 693 if(match.length >= matches_max){
693 694 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
694 695 }
695 696 }
696 697 }
697 698 if(query != ""){
698 699 YUD.setStyle('tbody','display','none');
699 700 YUD.setStyle('tbody_filtered','display','');
700 701
701 702 if (match.length==0){
702 703 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
703 704 }
704 705
705 706 YUD.get('tbody_filtered').innerHTML = match.join("");
706 707 }
707 708 else{
708 709 YUD.setStyle('tbody','display','');
709 710 YUD.setStyle('tbody_filtered','display','none');
710 711 }
711 712
712 713 }
713 714 };
714 715
715 716 YUE.on(YUD.get('filter_activate'),'click',function(){
716 717 F.initFilter();
717 718 })
718 719 YUE.on(n_filter,'click',function(){
719 720 if(YUD.hasClass(n_filter,'init')){
720 721 n_filter.value = '';
721 722 YUD.removeClass(n_filter,'init');
722 723 }
723 724 });
724 725 YUE.on(n_filter,'keyup',function(e){
725 726 clearTimeout(F.filterTimeout);
726 727 F.filterTimeout = setTimeout(F.updateFilter(e),600);
727 728 });
728 729 };
729 730
730 731
731 732 var initCodeMirror = function(textAreadId,resetUrl){
732 733 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
733 734 mode: "null",
734 735 lineNumbers:true
735 736 });
736 737 YUE.on('reset','click',function(e){
737 738 window.location=resetUrl
738 739 });
739 740
740 741 YUE.on('file_enable','click',function(){
741 742 YUD.setStyle('editor_container','display','');
742 743 YUD.setStyle('upload_file_container','display','none');
743 744 YUD.setStyle('filename_container','display','');
744 745 });
745 746
746 747 YUE.on('upload_file_enable','click',function(){
747 748 YUD.setStyle('editor_container','display','none');
748 749 YUD.setStyle('upload_file_container','display','');
749 750 YUD.setStyle('filename_container','display','none');
750 751 });
751 752 };
752 753
753 754
754 755
755 756 var getIdentNode = function(n){
756 757 //iterate thru nodes untill matched interesting node !
757 758
758 759 if (typeof n == 'undefined'){
759 760 return -1
760 761 }
761 762
762 763 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
763 764 return n
764 765 }
765 766 else{
766 767 return getIdentNode(n.parentNode);
767 768 }
768 769 };
769 770
770 771 var getSelectionLink = function(selection_link_label) {
771 772 return function(){
772 773 //get selection from start/to nodes
773 774 if (typeof window.getSelection != "undefined") {
774 775 s = window.getSelection();
775 776
776 777 from = getIdentNode(s.anchorNode);
777 778 till = getIdentNode(s.focusNode);
778 779
779 780 f_int = parseInt(from.id.replace('L',''));
780 781 t_int = parseInt(till.id.replace('L',''));
781 782
782 783 if (f_int > t_int){
783 784 //highlight from bottom
784 785 offset = -35;
785 786 ranges = [t_int,f_int];
786 787
787 788 }
788 789 else{
789 790 //highligth from top
790 791 offset = 35;
791 792 ranges = [f_int,t_int];
792 793 }
793 794
794 795 if (ranges[0] != ranges[1]){
795 796 if(YUD.get('linktt') == null){
796 797 hl_div = document.createElement('div');
797 798 hl_div.id = 'linktt';
798 799 }
799 800 anchor = '#L'+ranges[0]+'-'+ranges[1];
800 801 hl_div.innerHTML = '';
801 802 l = document.createElement('a');
802 803 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
803 804 l.innerHTML = selection_link_label;
804 805 hl_div.appendChild(l);
805 806
806 807 YUD.get('body').appendChild(hl_div);
807 808
808 809 xy = YUD.getXY(till.id);
809 810
810 811 YUD.addClass('linktt','yui-tt');
811 812 YUD.setStyle('linktt','top',xy[1]+offset+'px');
812 813 YUD.setStyle('linktt','left',xy[0]+'px');
813 814 YUD.setStyle('linktt','visibility','visible');
814 815 }
815 816 else{
816 817 YUD.setStyle('linktt','visibility','hidden');
817 818 }
818 819 }
819 820 }
820 821 };
821 822
822 823 var deleteNotification = function(url, notification_id,callbacks){
823 824 var callback = {
824 825 success:function(o){
825 826 var obj = YUD.get(String("notification_"+notification_id));
826 827 if(obj.parentNode !== undefined){
827 828 obj.parentNode.removeChild(obj);
828 829 }
829 830 _run_callbacks(callbacks);
830 831 },
831 832 failure:function(o){
832 833 alert("error");
833 834 },
834 835 };
835 836 var postData = '_method=delete';
836 837 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
837 838 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
838 839 callback, postData);
839 840 };
840 841
841 842
842 843 /** MEMBERS AUTOCOMPLETE WIDGET **/
843 844
844 845 var MembersAutoComplete = function (users_list, groups_list) {
845 846 var myUsers = users_list;
846 847 var myGroups = groups_list;
847 848
848 849 // Define a custom search function for the DataSource of users
849 850 var matchUsers = function (sQuery) {
850 851 // Case insensitive matching
851 852 var query = sQuery.toLowerCase();
852 853 var i = 0;
853 854 var l = myUsers.length;
854 855 var matches = [];
855 856
856 857 // Match against each name of each contact
857 858 for (; i < l; i++) {
858 859 contact = myUsers[i];
859 860 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
860 861 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
861 862 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
862 863 matches[matches.length] = contact;
863 864 }
864 865 }
865 866 return matches;
866 867 };
867 868
868 869 // Define a custom search function for the DataSource of usersGroups
869 870 var matchGroups = function (sQuery) {
870 871 // Case insensitive matching
871 872 var query = sQuery.toLowerCase();
872 873 var i = 0;
873 874 var l = myGroups.length;
874 875 var matches = [];
875 876
876 877 // Match against each name of each contact
877 878 for (; i < l; i++) {
878 879 matched_group = myGroups[i];
879 880 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
880 881 matches[matches.length] = matched_group;
881 882 }
882 883 }
883 884 return matches;
884 885 };
885 886
886 887 //match all
887 888 var matchAll = function (sQuery) {
888 889 u = matchUsers(sQuery);
889 890 g = matchGroups(sQuery);
890 891 return u.concat(g);
891 892 };
892 893
893 894 // DataScheme for members
894 895 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
895 896 memberDS.responseSchema = {
896 897 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
897 898 };
898 899
899 900 // DataScheme for owner
900 901 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
901 902 ownerDS.responseSchema = {
902 903 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
903 904 };
904 905
905 906 // Instantiate AutoComplete for perms
906 907 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
907 908 membersAC.useShadow = false;
908 909 membersAC.resultTypeList = false;
909 910
910 911 // Instantiate AutoComplete for owner
911 912 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
912 913 ownerAC.useShadow = false;
913 914 ownerAC.resultTypeList = false;
914 915
915 916
916 917 // Helper highlight function for the formatter
917 918 var highlightMatch = function (full, snippet, matchindex) {
918 919 return full.substring(0, matchindex)
919 920 + "<span class='match'>"
920 921 + full.substr(matchindex, snippet.length)
921 922 + "</span>" + full.substring(matchindex + snippet.length);
922 923 };
923 924
924 925 // Custom formatter to highlight the matching letters
925 926 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
926 927 var query = sQuery.toLowerCase();
927 928 var _gravatar = function(res, em, group){
928 929 if (group !== undefined){
929 930 em = '/images/icons/group.png'
930 931 }
931 932 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
932 933 return tmpl.format(em,res)
933 934 }
934 935 // group
935 936 if (oResultData.grname != undefined) {
936 937 var grname = oResultData.grname;
937 938 var grmembers = oResultData.grmembers;
938 939 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
939 940 var grprefix = "{0}: ".format(_TM['Group']);
940 941 var grsuffix = " (" + grmembers + " )";
941 942 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
942 943
943 944 if (grnameMatchIndex > -1) {
944 945 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
945 946 }
946 947 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
947 948 // Users
948 949 } else if (oResultData.nname != undefined) {
949 950 var fname = oResultData.fname || "";
950 951 var lname = oResultData.lname || "";
951 952 var nname = oResultData.nname;
952 953
953 954 // Guard against null value
954 955 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
955 956 lnameMatchIndex = lname.toLowerCase().indexOf(query),
956 957 nnameMatchIndex = nname.toLowerCase().indexOf(query),
957 958 displayfname, displaylname, displaynname;
958 959
959 960 if (fnameMatchIndex > -1) {
960 961 displayfname = highlightMatch(fname, query, fnameMatchIndex);
961 962 } else {
962 963 displayfname = fname;
963 964 }
964 965
965 966 if (lnameMatchIndex > -1) {
966 967 displaylname = highlightMatch(lname, query, lnameMatchIndex);
967 968 } else {
968 969 displaylname = lname;
969 970 }
970 971
971 972 if (nnameMatchIndex > -1) {
972 973 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
973 974 } else {
974 975 displaynname = nname ? "(" + nname + ")" : "";
975 976 }
976 977
977 978 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
978 979 } else {
979 980 return '';
980 981 }
981 982 };
982 983 membersAC.formatResult = custom_formatter;
983 984 ownerAC.formatResult = custom_formatter;
984 985
985 986 var myHandler = function (sType, aArgs) {
986 987
987 988 var myAC = aArgs[0]; // reference back to the AC instance
988 989 var elLI = aArgs[1]; // reference to the selected LI element
989 990 var oData = aArgs[2]; // object literal of selected item's result data
990 991 //fill the autocomplete with value
991 992 if (oData.nname != undefined) {
992 993 //users
993 994 myAC.getInputEl().value = oData.nname;
994 995 YUD.get('perm_new_member_type').value = 'user';
995 996 } else {
996 997 //groups
997 998 myAC.getInputEl().value = oData.grname;
998 999 YUD.get('perm_new_member_type').value = 'users_group';
999 1000 }
1000 1001 };
1001 1002
1002 1003 membersAC.itemSelectEvent.subscribe(myHandler);
1003 1004 if(ownerAC.itemSelectEvent){
1004 1005 ownerAC.itemSelectEvent.subscribe(myHandler);
1005 1006 }
1006 1007
1007 1008 return {
1008 1009 memberDS: memberDS,
1009 1010 ownerDS: ownerDS,
1010 1011 membersAC: membersAC,
1011 1012 ownerAC: ownerAC,
1012 1013 };
1013 1014 }
1014 1015
1015 1016
1016 1017 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1017 1018 var myUsers = users_list;
1018 1019 var myGroups = groups_list;
1019 1020
1020 1021 // Define a custom search function for the DataSource of users
1021 1022 var matchUsers = function (sQuery) {
1022 1023 var org_sQuery = sQuery;
1023 1024 if(this.mentionQuery == null){
1024 1025 return []
1025 1026 }
1026 1027 sQuery = this.mentionQuery;
1027 1028 // Case insensitive matching
1028 1029 var query = sQuery.toLowerCase();
1029 1030 var i = 0;
1030 1031 var l = myUsers.length;
1031 1032 var matches = [];
1032 1033
1033 1034 // Match against each name of each contact
1034 1035 for (; i < l; i++) {
1035 1036 contact = myUsers[i];
1036 1037 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1037 1038 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1038 1039 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1039 1040 matches[matches.length] = contact;
1040 1041 }
1041 1042 }
1042 1043 return matches
1043 1044 };
1044 1045
1045 1046 //match all
1046 1047 var matchAll = function (sQuery) {
1047 1048 u = matchUsers(sQuery);
1048 1049 return u
1049 1050 };
1050 1051
1051 1052 // DataScheme for owner
1052 1053 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1053 1054
1054 1055 ownerDS.responseSchema = {
1055 1056 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1056 1057 };
1057 1058
1058 1059 // Instantiate AutoComplete for mentions
1059 1060 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1060 1061 ownerAC.useShadow = false;
1061 1062 ownerAC.resultTypeList = false;
1062 1063 ownerAC.suppressInputUpdate = true;
1063 1064
1064 1065 // Helper highlight function for the formatter
1065 1066 var highlightMatch = function (full, snippet, matchindex) {
1066 1067 return full.substring(0, matchindex)
1067 1068 + "<span class='match'>"
1068 1069 + full.substr(matchindex, snippet.length)
1069 1070 + "</span>" + full.substring(matchindex + snippet.length);
1070 1071 };
1071 1072
1072 1073 // Custom formatter to highlight the matching letters
1073 1074 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1074 1075 var org_sQuery = sQuery;
1075 1076 if(this.dataSource.mentionQuery != null){
1076 1077 sQuery = this.dataSource.mentionQuery;
1077 1078 }
1078 1079
1079 1080 var query = sQuery.toLowerCase();
1080 1081 var _gravatar = function(res, em, group){
1081 1082 if (group !== undefined){
1082 1083 em = '/images/icons/group.png'
1083 1084 }
1084 1085 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1085 1086 return tmpl.format(em,res)
1086 1087 }
1087 1088 if (oResultData.nname != undefined) {
1088 1089 var fname = oResultData.fname || "";
1089 1090 var lname = oResultData.lname || "";
1090 1091 var nname = oResultData.nname;
1091 1092
1092 1093 // Guard against null value
1093 1094 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1094 1095 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1095 1096 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1096 1097 displayfname, displaylname, displaynname;
1097 1098
1098 1099 if (fnameMatchIndex > -1) {
1099 1100 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1100 1101 } else {
1101 1102 displayfname = fname;
1102 1103 }
1103 1104
1104 1105 if (lnameMatchIndex > -1) {
1105 1106 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1106 1107 } else {
1107 1108 displaylname = lname;
1108 1109 }
1109 1110
1110 1111 if (nnameMatchIndex > -1) {
1111 1112 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1112 1113 } else {
1113 1114 displaynname = nname ? "(" + nname + ")" : "";
1114 1115 }
1115 1116
1116 1117 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1117 1118 } else {
1118 1119 return '';
1119 1120 }
1120 1121 };
1121 1122
1122 1123 if(ownerAC.itemSelectEvent){
1123 1124 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1124 1125
1125 1126 var myAC = aArgs[0]; // reference back to the AC instance
1126 1127 var elLI = aArgs[1]; // reference to the selected LI element
1127 1128 var oData = aArgs[2]; // object literal of selected item's result data
1128 1129 //fill the autocomplete with value
1129 1130 if (oData.nname != undefined) {
1130 1131 //users
1131 1132 //Replace the mention name with replaced
1132 1133 var re = new RegExp();
1133 1134 var org = myAC.getInputEl().value;
1134 1135 var chunks = myAC.dataSource.chunks
1135 1136 // replace middle chunk(the search term) with actuall match
1136 1137 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1137 1138 '@'+oData.nname+' ');
1138 1139 myAC.getInputEl().value = chunks.join('')
1139 1140 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1140 1141 } else {
1141 1142 //groups
1142 1143 myAC.getInputEl().value = oData.grname;
1143 1144 YUD.get('perm_new_member_type').value = 'users_group';
1144 1145 }
1145 1146 });
1146 1147 }
1147 1148
1148 1149 // in this keybuffer we will gather current value of search !
1149 1150 // since we need to get this just when someone does `@` then we do the
1150 1151 // search
1151 1152 ownerAC.dataSource.chunks = [];
1152 1153 ownerAC.dataSource.mentionQuery = null;
1153 1154
1154 1155 ownerAC.get_mention = function(msg, max_pos) {
1155 1156 var org = msg;
1156 1157 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1157 1158 var chunks = [];
1158 1159
1159 1160
1160 1161 // cut first chunk until curret pos
1161 1162 var to_max = msg.substr(0, max_pos);
1162 1163 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1163 1164 var msg2 = to_max.substr(at_pos);
1164 1165
1165 1166 chunks.push(org.substr(0,at_pos))// prefix chunk
1166 1167 chunks.push(msg2) // search chunk
1167 1168 chunks.push(org.substr(max_pos)) // postfix chunk
1168 1169
1169 1170 // clean up msg2 for filtering and regex match
1170 1171 var msg2 = msg2.lstrip(' ').lstrip('\n');
1171 1172
1172 1173 if(re.test(msg2)){
1173 1174 var unam = re.exec(msg2)[1];
1174 1175 return [unam, chunks];
1175 1176 }
1176 1177 return [null, null];
1177 1178 };
1178 1179 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1179 1180
1180 1181 var ac_obj = args[0];
1181 1182 var currentMessage = args[1];
1182 1183 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1183 1184
1184 1185 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1185 1186 var curr_search = null;
1186 1187 if(unam[0]){
1187 1188 curr_search = unam[0];
1188 1189 }
1189 1190
1190 1191 ownerAC.dataSource.chunks = unam[1];
1191 1192 ownerAC.dataSource.mentionQuery = curr_search;
1192 1193
1193 1194 })
1194 1195
1195 1196 return {
1196 1197 ownerDS: ownerDS,
1197 1198 ownerAC: ownerAC,
1198 1199 };
1199 1200 }
1200 1201
1201 1202
1202 1203 /**
1203 1204 * QUICK REPO MENU
1204 1205 */
1205 1206 var quick_repo_menu = function(){
1206 1207 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1207 1208 var menu = e.currentTarget.firstElementChild.firstElementChild;
1208 1209 if(YUD.hasClass(menu,'hidden')){
1209 1210 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1210 1211 YUD.replaceClass(menu, 'hidden', 'active');
1211 1212 }
1212 1213 })
1213 1214 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1214 1215 var menu = e.currentTarget.firstElementChild.firstElementChild;
1215 1216 if(YUD.hasClass(menu,'active')){
1216 1217 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1217 1218 YUD.replaceClass(menu, 'active', 'hidden');
1218 1219 }
1219 1220 })
1220 1221 };
1221 1222
1222 1223
1223 1224 /**
1224 1225 * TABLE SORTING
1225 1226 */
1226 1227
1227 1228 // returns a node from given html;
1228 1229 var fromHTML = function(html){
1229 1230 var _html = document.createElement('element');
1230 1231 _html.innerHTML = html;
1231 1232 return _html;
1232 1233 }
1233 1234 var get_rev = function(node){
1234 1235 var n = node.firstElementChild.firstElementChild;
1235 1236
1236 1237 if (n===null){
1237 1238 return -1
1238 1239 }
1239 1240 else{
1240 1241 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1241 1242 return parseInt(out);
1242 1243 }
1243 1244 }
1244 1245
1245 1246 var get_name = function(node){
1246 1247 var name = node.firstElementChild.children[2].innerHTML;
1247 1248 return name
1248 1249 }
1249 1250 var get_group_name = function(node){
1250 1251 var name = node.firstElementChild.children[1].innerHTML;
1251 1252 return name
1252 1253 }
1253 1254 var get_date = function(node){
1254 1255 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1255 1256 return date_
1256 1257 }
1257 1258
1258 1259 var get_age = function(node){
1259 1260 console.log(node);
1260 1261 return node
1261 1262 }
1262 1263
1263 1264 var revisionSort = function(a, b, desc, field) {
1264 1265
1265 1266 var a_ = fromHTML(a.getData(field));
1266 1267 var b_ = fromHTML(b.getData(field));
1267 1268
1268 1269 // extract revisions from string nodes
1269 1270 a_ = get_rev(a_)
1270 1271 b_ = get_rev(b_)
1271 1272
1272 1273 var comp = YAHOO.util.Sort.compare;
1273 1274 var compState = comp(a_, b_, desc);
1274 1275 return compState;
1275 1276 };
1276 1277 var ageSort = function(a, b, desc, field) {
1277 1278 var a_ = fromHTML(a.getData(field));
1278 1279 var b_ = fromHTML(b.getData(field));
1279 1280
1280 1281 // extract name from table
1281 1282 a_ = get_date(a_)
1282 1283 b_ = get_date(b_)
1283 1284
1284 1285 var comp = YAHOO.util.Sort.compare;
1285 1286 var compState = comp(a_, b_, desc);
1286 1287 return compState;
1287 1288 };
1288 1289
1289 1290 var nameSort = function(a, b, desc, field) {
1290 1291 var a_ = fromHTML(a.getData(field));
1291 1292 var b_ = fromHTML(b.getData(field));
1292 1293
1293 1294 // extract name from table
1294 1295 a_ = get_name(a_)
1295 1296 b_ = get_name(b_)
1296 1297
1297 1298 var comp = YAHOO.util.Sort.compare;
1298 1299 var compState = comp(a_, b_, desc);
1299 1300 return compState;
1300 1301 };
1301 1302
1302 1303 var permNameSort = function(a, b, desc, field) {
1303 1304 var a_ = fromHTML(a.getData(field));
1304 1305 var b_ = fromHTML(b.getData(field));
1305 1306 // extract name from table
1306 1307
1307 1308 a_ = a_.children[0].innerHTML;
1308 1309 b_ = b_.children[0].innerHTML;
1309 1310
1310 1311 var comp = YAHOO.util.Sort.compare;
1311 1312 var compState = comp(a_, b_, desc);
1312 1313 return compState;
1313 1314 };
1314 1315
1315 1316 var groupNameSort = function(a, b, desc, field) {
1316 1317 var a_ = fromHTML(a.getData(field));
1317 1318 var b_ = fromHTML(b.getData(field));
1318 1319
1319 1320 // extract name from table
1320 1321 a_ = get_group_name(a_)
1321 1322 b_ = get_group_name(b_)
1322 1323
1323 1324 var comp = YAHOO.util.Sort.compare;
1324 1325 var compState = comp(a_, b_, desc);
1325 1326 return compState;
1326 1327 };
1327 1328 var dateSort = function(a, b, desc, field) {
1328 1329 var a_ = fromHTML(a.getData(field));
1329 1330 var b_ = fromHTML(b.getData(field));
1330 1331
1331 1332 // extract name from table
1332 1333 a_ = get_date(a_)
1333 1334 b_ = get_date(b_)
1334 1335
1335 1336 var comp = YAHOO.util.Sort.compare;
1336 1337 var compState = comp(a_, b_, desc);
1337 1338 return compState;
1338 1339 };
1339 1340
1340 1341
1341 1342
1342 1343 /* Multi selectors */
1343 1344
1344 1345 var MultiSelectWidget = function(selected_id, available_id, form_id){
1345 1346
1346 1347
1347 1348 //definition of containers ID's
1348 1349 var selected_container = selected_id;
1349 1350 var available_container = available_id;
1350 1351
1351 1352 //temp container for selected storage.
1352 1353 var cache = new Array();
1353 1354 var av_cache = new Array();
1354 1355 var c = YUD.get(selected_container);
1355 1356 var ac = YUD.get(available_container);
1356 1357
1357 1358 //get only selected options for further fullfilment
1358 1359 for(var i = 0;node =c.options[i];i++){
1359 1360 if(node.selected){
1360 1361 //push selected to my temp storage left overs :)
1361 1362 cache.push(node);
1362 1363 }
1363 1364 }
1364 1365
1365 1366 //get all available options to cache
1366 1367 for(var i = 0;node =ac.options[i];i++){
1367 1368 //push selected to my temp storage left overs :)
1368 1369 av_cache.push(node);
1369 1370 }
1370 1371
1371 1372 //fill available only with those not in choosen
1372 1373 ac.options.length=0;
1373 1374 tmp_cache = new Array();
1374 1375
1375 1376 for(var i = 0;node = av_cache[i];i++){
1376 1377 var add = true;
1377 1378 for(var i2 = 0;node_2 = cache[i2];i2++){
1378 1379 if(node.value == node_2.value){
1379 1380 add=false;
1380 1381 break;
1381 1382 }
1382 1383 }
1383 1384 if(add){
1384 1385 tmp_cache.push(new Option(node.text, node.value, false, false));
1385 1386 }
1386 1387 }
1387 1388
1388 1389 for(var i = 0;node = tmp_cache[i];i++){
1389 1390 ac.options[i] = node;
1390 1391 }
1391 1392
1392 1393 function prompts_action_callback(e){
1393 1394
1394 1395 var choosen = YUD.get(selected_container);
1395 1396 var available = YUD.get(available_container);
1396 1397
1397 1398 //get checked and unchecked options from field
1398 1399 function get_checked(from_field){
1399 1400 //temp container for storage.
1400 1401 var sel_cache = new Array();
1401 1402 var oth_cache = new Array();
1402 1403
1403 1404 for(var i = 0;node = from_field.options[i];i++){
1404 1405 if(node.selected){
1405 1406 //push selected fields :)
1406 1407 sel_cache.push(node);
1407 1408 }
1408 1409 else{
1409 1410 oth_cache.push(node)
1410 1411 }
1411 1412 }
1412 1413
1413 1414 return [sel_cache,oth_cache]
1414 1415 }
1415 1416
1416 1417 //fill the field with given options
1417 1418 function fill_with(field,options){
1418 1419 //clear firtst
1419 1420 field.options.length=0;
1420 1421 for(var i = 0;node = options[i];i++){
1421 1422 field.options[i]=new Option(node.text, node.value,
1422 1423 false, false);
1423 1424 }
1424 1425
1425 1426 }
1426 1427 //adds to current field
1427 1428 function add_to(field,options){
1428 1429 for(var i = 0;node = options[i];i++){
1429 1430 field.appendChild(new Option(node.text, node.value,
1430 1431 false, false));
1431 1432 }
1432 1433 }
1433 1434
1434 1435 // add action
1435 1436 if (this.id=='add_element'){
1436 1437 var c = get_checked(available);
1437 1438 add_to(choosen,c[0]);
1438 1439 fill_with(available,c[1]);
1439 1440 }
1440 1441 // remove action
1441 1442 if (this.id=='remove_element'){
1442 1443 var c = get_checked(choosen);
1443 1444 add_to(available,c[0]);
1444 1445 fill_with(choosen,c[1]);
1445 1446 }
1446 1447 // add all elements
1447 1448 if(this.id=='add_all_elements'){
1448 1449 for(var i=0; node = available.options[i];i++){
1449 1450 choosen.appendChild(new Option(node.text,
1450 1451 node.value, false, false));
1451 1452 }
1452 1453 available.options.length = 0;
1453 1454 }
1454 1455 //remove all elements
1455 1456 if(this.id=='remove_all_elements'){
1456 1457 for(var i=0; node = choosen.options[i];i++){
1457 1458 available.appendChild(new Option(node.text,
1458 1459 node.value, false, false));
1459 1460 }
1460 1461 choosen.options.length = 0;
1461 1462 }
1462 1463
1463 1464 }
1464 1465
1465 1466 YUE.addListener(['add_element','remove_element',
1466 1467 'add_all_elements','remove_all_elements'],'click',
1467 1468 prompts_action_callback)
1468 1469 if (form_id !== undefined) {
1469 1470 YUE.addListener(form_id,'submit',function(){
1470 1471 var choosen = YUD.get(selected_container);
1471 1472 for (var i = 0; i < choosen.options.length; i++) {
1472 1473 choosen.options[i].selected = 'selected';
1473 1474 }
1474 1475 });
1475 1476 }
1476 1477 } No newline at end of file
@@ -1,176 +1,179 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_(u'Home'),h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 <div class="diffblock">
29 29 <div class="code-header">
30 30 <div class="hash">
31 31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 32 </div>
33 33 <div class="date">
34 34 ${h.fmt_date(c.changeset.date)}
35 35 </div>
36 36 <div class="changeset-status-container">
37 37 %if c.statuses:
38 38 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
39 39 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
40 40 %endif
41 41 </div>
42 42 <div class="diff-actions">
43 43 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
44 44 <a href="${h.url('raw_changeset_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_white_get.png')}"/></a>
45 45 ${c.ignorews_url(request.GET)}
46 46 ${c.context_url(request.GET)}
47 47 </div>
48 48 <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>
49 49 </div>
50 50 </div>
51 51 <div id="changeset_content">
52 52 <div class="container">
53 53 <div class="left">
54 54 <div class="author">
55 55 <div class="gravatar">
56 56 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
57 57 </div>
58 58 <span>${h.person(c.changeset.author)}</span><br/>
59 59 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
60 60 </div>
61 61 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
62 62 </div>
63 63 <div class="right">
64 64 <div class="changes">
65 65 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
66 66 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
67 67 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
68 68 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
69 69 % else:
70 70 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
71 71 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
72 72 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
73 73 % endif
74 74 </div>
75 75
76 76 %if c.changeset.parents:
77 77 %for p_cs in reversed(c.changeset.parents):
78 78 <div class="parent">${_('Parent')}
79 79 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
80 80 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
81 81 </div>
82 82 %endfor
83 83 %else:
84 84 <div class="parent">${_('No parents')}</div>
85 85 %endif
86 86 <span class="logtags">
87 87 %if len(c.changeset.parents)>1:
88 88 <span class="merge">${_('merge')}</span>
89 89 %endif
90 90 %if c.changeset.branch:
91 91 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
92 92 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
93 93 </span>
94 94 %endif
95 95 %for tag in c.changeset.tags:
96 96 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
97 97 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
98 98 %endfor
99 99 </span>
100 100 </div>
101 101 </div>
102 102 <span>
103 103 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
104 104 </span>
105 105 <div class="cs_files">
106 106 %for change,filenode,diff,cs1,cs2,stat in c.changes:
107 107 <div class="cs_${change}">
108 108 <div class="node">
109 109 %if change != 'removed':
110 110 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
111 111 %else:
112 112 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
113 113 %endif
114 114 </div>
115 115 <div class="changes">${h.fancy_file_stats(stat)}</div>
116 116 </div>
117 117 %endfor
118 118 % if c.cut_off:
119 119 ${_('Changeset was too big and was cut off...')}
120 120 % endif
121 121 </div>
122 122 </div>
123 123
124 124 </div>
125 125 <script>
126 126 var _USERS_AC_DATA = ${c.users_array|n};
127 127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 128 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
129 129 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 130 </script>
131 131 ## diff block
132 132 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
133 133 ${diff_block.diff_block(c.changes)}
134 134
135 135 ## template for inline comment form
136 136 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
137 137 ${comment.comment_inline_form()}
138 138
139 ## render comments main comments form and it status
139 ## render comments and inlines
140 ${comment.generate_comments()}
141
142 ## main comment form and it status
140 143 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
141 144 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
142 145
143 146 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
144 147 <script type="text/javascript">
145 148 YUE.onDOMReady(function(){
146 149 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
147 150 var show = 'none';
148 151 var target = e.currentTarget;
149 152 if(target.checked){
150 153 var show = ''
151 154 }
152 155 var boxid = YUD.getAttribute(target,'id_for');
153 156 var comments = YUQ('#{0} .inline-comments'.format(boxid));
154 157 for(c in comments){
155 158 YUD.setStyle(comments[c],'display',show);
156 159 }
157 160 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
158 161 for(c in btns){
159 162 YUD.setStyle(btns[c],'display',show);
160 163 }
161 164 })
162 165
163 166 YUE.on(YUQ('.line'),'click',function(e){
164 167 var tr = e.currentTarget;
165 168 injectInlineForm(tr);
166 169 });
167 170
168 171 // inject comments into they proper positions
169 172 var file_comments = YUQ('.inline-comment-placeholder');
170 173 renderInlineComments(file_comments);
171 174 })
172 175
173 176 </script>
174 177
175 178 </div>
176 179 </%def>
@@ -1,154 +1,163 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 4 ## ${comment.comment_block(co)}
5 5 ##
6 6 <%def name="comment_block(co)">
7 7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
8 8 <div class="comment-wrapp">
9 9 <div class="meta">
10 10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
11 11 <div class="user">
12 12 ${co.author.username}
13 13 </div>
14 14 <div class="date">
15 15 ${h.age(co.modified_at)}
16 16 </div>
17 17 %if co.status_change:
18 18 <div style="float:left" class="changeset-status-container">
19 19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
20 20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div>
21 21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div>
22 22 </div>
23 23 %endif
24 24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
25 25 <div class="buttons">
26 26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
27 27 </div>
28 28 %endif
29 29 </div>
30 30 <div class="text">
31 31 ${h.rst_w_mentions(co.text)|n}
32 32 </div>
33 33 </div>
34 34 </div>
35 35 </%def>
36 36
37 37
38 38 <%def name="comment_inline_form()">
39 39 <div id='comment-inline-form-template' style="display:none">
40 40 <div class="comment-inline-form ac">
41 41 %if c.rhodecode_user.username != 'default':
42 42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
43 43 ${h.form('#', class_='inline-form')}
44 44 <div class="clearfix">
45 45 <div class="comment-help">${_('Commenting on line {1}.')}
46 46 ${(_('Comments parsed using %s syntax with %s support.') % (
47 47 ('<a href="%s">RST</a>' % h.url('rst_help')),
48 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 50 )|n
51 51 }
52 52 </div>
53 53 <div class="mentions-container" id="mentions_container_{1}"></div>
54 54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
55 55 </div>
56 56 <div class="comment-button">
57 57 <input type="hidden" name="f_path" value="{0}">
58 58 <input type="hidden" name="line" value="{1}">
59 59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
60 60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
61 61 </div>
62 62 ${h.end_form()}
63 63 %else:
64 64 ${h.form('')}
65 65 <div class="clearfix">
66 66 <div class="comment-help">
67 67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
68 68 </div>
69 69 </div>
70 70 <div class="comment-button">
71 71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
72 72 </div>
73 73 ${h.end_form()}
74 74 %endif
75 75 </div>
76 76 </div>
77 77 </%def>
78 78
79 79
80 80 ## generates inlines taken from c.comments var
81 81 <%def name="inlines()">
82 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 83 %for path, lines in c.inline_comments:
84 84 % for line,comments in lines.iteritems():
85 85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
86 86 %for co in comments:
87 87 ${comment_block(co)}
88 88 %endfor
89 89 </div>
90 90 %endfor
91 91 %endfor
92 92
93 93 </%def>
94 94
95 ## MAIN COMMENT FORM
96 <%def name="comments(post_url, cur_status)">
97
95 ## generate inline comments and the main ones
96 <%def name="generate_comments()">
98 97 <div class="comments">
99 98 <div id="inline-comments-container">
100 99 ## generate inlines for this changeset
101 100 ${inlines()}
102 101 </div>
103 102
104 103 %for co in c.comments:
105 104 <div id="comment-tr-${co.comment_id}">
106 105 ${comment_block(co)}
107 106 </div>
108 107 %endfor
108 </div>
109 </%def>
110
111 ## MAIN COMMENT FORM
112 <%def name="comments(post_url, cur_status, close_btn=False)">
113
114 <div class="comments">
109 115 %if c.rhodecode_user.username != 'default':
110 116 <div class="comment-form ac">
111 117 ${h.form(post_url)}
112 118 <strong>${_('Leave a comment')}</strong>
113 119 <div class="clearfix">
114 120 <div class="comment-help">
115 121 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
116 122 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
117 123 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
118 124 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
119 125 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
120 126 </div>
121 127 <div id="status_block_container" class="status-block" style="display:none">
122 128 %for status,lbl in c.changeset_statuses:
123 129 <div class="">
124 130 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label>
125 131 </div>
126 132 %endfor
127 133 </div>
128 134 <div class="mentions-container" id="mentions_container"></div>
129 135 ${h.textarea('text')}
130 136 </div>
131 137 <div class="comment-button">
132 ${h.submit('save', _('Comment'), class_='ui-button')}
138 ${h.submit('save', _('Comment'), class_="ui-btn large")}
139 %if close_btn:
140 ${h.submit('save_close', _('Comment and close'), class_='ui-btn blue large')}
141 %endif
133 142 </div>
134 143 ${h.end_form()}
135 144 </div>
136 145 %endif
137 146 </div>
138 147 <script>
139 148 YUE.onDOMReady(function () {
140 149 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
141 150
142 151 // changeset status box listener
143 152 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
144 153 if(e.currentTarget.checked){
145 154 YUD.setStyle('status_block_container','display','');
146 155 }
147 156 else{
148 157 YUD.setStyle('status_block_container','display','none');
149 158 }
150 159 })
151 160
152 161 });
153 162 </script>
154 163 </%def>
@@ -1,140 +1,146 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22
22 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
24 %endif
23 25 <h3>${_('Title')}: ${c.pull_request.title}
24 26 <div class="changeset-status-container" style="float:none">
25 27 %if c.current_changeset_status:
26 28 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
29 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 30 %endif
29 31 </div>
30 32 </h3>
31 33 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
32 34 <div style="padding:4px 4px 10px 20px">
33 35 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
34 36 </div>
35 37
36 38 ## REVIEWERS
37 39 <div>
38 40 <div class="table" style="float:right;width:46%;clear:none">
39 41 <div id="body" class="diffblock">
40 42 <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div>
41 43 </div>
42 44 <div style="border: 1px solid #CCC">
43 45 <div class="group_members_wrap">
44 46 <ul class="group_members">
45 47 %for user,status in c.pull_request_reviewers:
46 48 <li>
47 49 <div class="group_member">
48 50 <div style="float:left;padding:3px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
49 51 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
50 52 </div>
51 53 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div>
52 54 <div style="float:left">${user.username}</div>
53 55 </div>
54 56 </li>
55 57 %endfor
56 58 </ul>
57 59 </div>
58 60 </div>
59 61 </div>
60 62 ##DIFF
61 63 <div class="table" style="float:left;width:46%;clear:none">
62 64 <div id="body" class="diffblock">
63 65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
64 66 </div>
65 67 <div id="changeset_compare_view_content">
66 68 ##CS
67 69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
68 70 <%include file="/compare/compare_cs.html" />
69 71
70 72 ## FILES
71 73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
72 74 <div class="cs_files">
73 75 %for fid, change, f, stat in c.files:
74 76 <div class="cs_${change}">
75 77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
76 78 <div class="changes">${h.fancy_file_stats(stat)}</div>
77 79 </div>
78 80 %endfor
79 81 </div>
80 82 </div>
81 83 </div>
82 84 </div>
83 85 <script>
84 86 var _USERS_AC_DATA = ${c.users_array|n};
85 87 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
86 88 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
87 89 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
88 90 </script>
89 91
90 92 ## diff block
91 93 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
92 94 %for fid, change, f, stat in c.files:
93 95 ${diff_block.diff_block_simple([c.changes[fid]])}
94 96 %endfor
95 97
96 98 ## template for inline comment form
97 99 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
98 100 ${comment.comment_inline_form()}
99
100 ## render comments main comments form and it status
101 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
102 c.current_changeset_status)}
103
101
102 ## render comments and inlines
103 ${comment.generate_comments()}
104
105 % if not c.pull_request.is_closed():
106 ## main comment form and it status
107 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
108 pull_request_id=c.pull_request.pull_request_id),
109 c.current_changeset_status,
110 close_btn=True)}
111 %endif
104 112
105 113 <script type="text/javascript">
106 114 YUE.onDOMReady(function(){
107 115
108 116 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
109 117 var show = 'none';
110 118 var target = e.currentTarget;
111 119 if(target.checked){
112 120 var show = ''
113 121 }
114 122 var boxid = YUD.getAttribute(target,'id_for');
115 123 var comments = YUQ('#{0} .inline-comments'.format(boxid));
116 124 for(c in comments){
117 125 YUD.setStyle(comments[c],'display',show);
118 126 }
119 127 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
120 128 for(c in btns){
121 129 YUD.setStyle(btns[c],'display',show);
122 130 }
123 131 })
124 132
125 133 YUE.on(YUQ('.line'),'click',function(e){
126 134 var tr = e.currentTarget;
127 135 injectInlineForm(tr);
128 136 });
129 137
130 138 // inject comments into they proper positions
131 139 var file_comments = YUQ('.inline-comment-placeholder');
132 140 renderInlineComments(file_comments);
133 141 })
134
135 142 </script>
136 143
137
138 144 </div>
139 145
140 146 </%def>
@@ -1,38 +1,42 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 ${c.repo_name} ${_('All pull requests')}
4 ${c.repo_name} ${_('all pull requests')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('All pull requests')}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22
23 23 %for pr in c.pull_requests:
24 24 <div>
25 <h4><a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
25 <h4>
26 %if pr.is_closed():
27 <img src="${h.url('/images/icons/tick.png')}" alt="${_('Closed')}" />
28 %endif
29 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
26 30 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
27 31 </a>
28 32 </h4>
29 33 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
30 34 <div style="padding:0px 24px">${pr.description}</div>
31 35 </div>
32 36 %endfor
33 37
34 38 </div>
35 39
36 40 <script type="text/javascript"></script>
37 41
38 42 </%def>
General Comments 0
You need to be logged in to leave comments. Login now