##// END OF EJS Templates
created pull-request overview
marcink -
r2395:b262e349 codereview
parent child Browse files
Show More
@@ -0,0 +1,23 b''
1 ## Changesets table !
2 <div class="container">
3 <table class="compare_view_commits noborder">
4 %if not c.cs_ranges:
5 <tr><td>${_('No changesets')}</td></tr>
6 %else:
7 %for cnt, cs in enumerate(c.cs_ranges):
8 <tr>
9 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
10 <td>
11 %if cs.raw_id in c.statuses:
12 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
13 %endif
14 </td>
15 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
16 <td><div class="author">${h.person(cs.author)}</div></td>
17 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
18 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
19 </tr>
20 %endfor
21 %endif
22 </table>
23 </div> No newline at end of file
@@ -1,142 +1,143 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showoing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 import binascii
29 29
30 30 from webob.exc import HTTPNotFound
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.base import BaseRepoController, render
36 36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 37 from rhodecode.lib import diffs
38 38
39 39 from rhodecode.model.db import Repository
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class CompareController(BaseRepoController):
45 45
46 46 @LoginRequired()
47 47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 48 'repository.admin')
49 49 def __before__(self):
50 50 super(CompareController, self).__before__()
51 51
52 52 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
53 53 from mercurial import discovery
54 54 other = org_repo._repo
55 55 repo = other_repo._repo
56 56 tip = other[org_ref[1]]
57 57 log.debug('Doing discovery for %s@%s vs %s@%s' % (
58 58 org_repo, org_ref, other_repo, other_ref)
59 59 )
60 60 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
61 61 tmp = discovery.findcommonincoming(
62 62 repo=repo, # other_repo we check for incoming
63 63 remote=other, # org_repo source for incoming
64 64 heads=[tip.node()],
65 65 force=False
66 66 )
67 67 return tmp
68 68
69 69 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
70 70 changesets = []
71 71 #case two independent repos
72 72 if org_repo != other_repo:
73 73 common, incoming, rheads = tmp
74 74
75 75 if not incoming:
76 76 revs = []
77 77 else:
78 78 revs = org_repo._repo.changelog.findmissing(common, rheads)
79 79
80 80 for cs in reversed(map(binascii.hexlify, revs)):
81 81 changesets.append(org_repo.get_changeset(cs))
82 82 else:
83 83 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
84 84 other_ref[1])]
85 85 from mercurial import scmutil
86 86 out = scmutil.revrange(org_repo._repo, revs)
87 87 for cs in reversed(out):
88 88 changesets.append(org_repo.get_changeset(cs))
89 89
90 90 return changesets
91 91
92 92 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
93 93
94 94 org_repo = c.rhodecode_db_repo.repo_name
95 95 org_ref = (org_ref_type, org_ref)
96 96 other_ref = (other_ref_type, other_ref)
97 97 other_repo = request.GET.get('repo', org_repo)
98 98
99 99 c.swap_url = h.url('compare_url', repo_name=other_repo,
100 100 org_ref_type=other_ref[0], org_ref=other_ref[1],
101 101 other_ref_type=org_ref[0], other_ref=org_ref[1],
102 102 repo=org_repo)
103 103
104 104 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
105 105 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
106 106
107 107 if c.org_repo is None or c.other_repo is None:
108 108 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
109 109 raise HTTPNotFound
110 110
111 111 discovery_data = self._get_discovery(org_repo.scm_instance,
112 112 org_ref,
113 113 other_repo.scm_instance,
114 114 other_ref)
115 115 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
116 116 org_ref,
117 117 other_repo.scm_instance,
118 118 other_ref,
119 119 discovery_data)
120 120
121 121 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
122 122 c.cs_ranges])
123
123 if request.environ.get('HTTP_X_PARTIAL_XHR'):
124 return render('compare/compare_cs.html')
124 125
125 126 c.org_ref = org_ref[1]
126 127 c.other_ref = other_ref[1]
127 128 # diff needs to have swapped org with other to generate proper diff
128 129 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
129 130 discovery_data)
130 131 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
131 132 _parsed = diff_processor.prepare()
132 133
133 134 c.files = []
134 135 c.changes = {}
135 136
136 137 for f in _parsed:
137 138 fid = h.FID('', f['filename'])
138 139 c.files.append([fid, f['operation'], f['filename'], f['stats']])
139 140 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
140 141 c.changes[fid] = [f['operation'], f['filename'], diff]
141 142
142 143 return render('compare/compare_diff.html')
@@ -1,65 +1,90 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 pylons import request, response, session, tmpl_context as c, url
29 29 from pylons.controllers.util import abort, redirect
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.lib.base import BaseRepoController, render
33 33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from webob.exc import HTTPNotFound
34 from rhodecode.model.db import User
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class PullrequestsController(BaseRepoController):
40 40
41 41 @LoginRequired()
42 42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 43 'repository.admin')
44 44 def __before__(self):
45 45 super(PullrequestsController, self).__before__()
46 46
47 def _get_repo_refs(self,repo):
47 def _get_repo_refs(self, repo):
48 48 hist_l = []
49 49
50 branches_group = ([(k, k) for k in repo.branches.keys()], _("Branches"))
51 bookmarks_group = ([(k, k) for k in repo.bookmarks.keys()], _("Bookmarks"))
52 tags_group = ([(k, k) for k in repo.tags.keys()], _("Tags"))
50 branches_group = ([('branch:' + k, k) for k in repo.branches.keys()],
51 _("Branches"))
52 bookmarks_group = ([('book:' + k, k) for k in repo.bookmarks.keys()],
53 _("Bookmarks"))
54 tags_group = ([('tag:' + k, k) for k in repo.tags.keys()],
55 _("Tags"))
53 56
54 57 hist_l.append(bookmarks_group)
55 58 hist_l.append(branches_group)
56 59 hist_l.append(tags_group)
57 60
58 61 return hist_l
59 62
60 63 def index(self):
64 org_repo = c.rhodecode_db_repo
61 65 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
62 c.sources = []
63 c.sources.append('%s/%s' % (c.rhodecode_db_repo.user.username,
64 c.repo_name))
66 c.org_repos = []
67 c.other_repos = []
68 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
69 org_repo.user.username, c.repo_name))
70 )
71
72 c.other_refs = c.org_refs
73 c.other_repos.extend(c.org_repos)
74
75 #gather forks and add to this list
76 for fork in org_repo.forks:
77 c.other_repos.append((fork.repo_name, '%s/%s' % (
78 fork.user.username, fork.repo_name))
79 )
80 #add parents of this fork also
81 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
82 org_repo.parent.user.username,
83 org_repo.parent.repo_name))
84 )
85
86 #TODO: maybe the owner should be default ?
87 c.review_members = []
88 c.available_members = [(x.user_id, x.username) for x in
89 User.query().filter(User.username != 'default').all()]
65 90 return render('/pullrequests/pullrequest.html')
@@ -1,1448 +1,1462 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
39 39 from pylons.i18n.translation import lazy_ugettext as _
40 40
41 41 from rhodecode.lib.vcs import get_backend
42 42 from rhodecode.lib.vcs.utils.helpers import get_scm
43 43 from rhodecode.lib.vcs.exceptions import VCSError
44 44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45 45
46 46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 47 safe_unicode
48 48 from rhodecode.lib.compat import json
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 from rhodecode.model.meta import Base, Session
52 52
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 ModelSerializer(json.JSONEncoder):
65 65 """
66 66 Simple Serializer for JSON,
67 67
68 68 usage::
69 69
70 70 to make object customized for serialization implement a __json__
71 71 method that will return a dict for serialization into json
72 72
73 73 example::
74 74
75 75 class Task(object):
76 76
77 77 def __init__(self, name, value):
78 78 self.name = name
79 79 self.value = value
80 80
81 81 def __json__(self):
82 82 return dict(name=self.name,
83 83 value=self.value)
84 84
85 85 """
86 86
87 87 def default(self, obj):
88 88
89 89 if hasattr(obj, '__json__'):
90 90 return obj.__json__()
91 91 else:
92 92 return json.JSONEncoder.default(self, obj)
93 93
94 94
95 95 class BaseModel(object):
96 96 """
97 97 Base Model for all classess
98 98 """
99 99
100 100 @classmethod
101 101 def _get_keys(cls):
102 102 """return column names for this model """
103 103 return class_mapper(cls).c.keys()
104 104
105 105 def get_dict(self):
106 106 """
107 107 return dict with keys and values corresponding
108 108 to this model data """
109 109
110 110 d = {}
111 111 for k in self._get_keys():
112 112 d[k] = getattr(self, k)
113 113
114 114 # also use __json__() if present to get additional fields
115 115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 116 d[k] = val
117 117 return d
118 118
119 119 def get_appstruct(self):
120 120 """return list with keys and values tupples corresponding
121 121 to this model data """
122 122
123 123 l = []
124 124 for k in self._get_keys():
125 125 l.append((k, getattr(self, k),))
126 126 return l
127 127
128 128 def populate_obj(self, populate_dict):
129 129 """populate model with data from given populate_dict"""
130 130
131 131 for k in self._get_keys():
132 132 if k in populate_dict:
133 133 setattr(self, k, populate_dict[k])
134 134
135 135 @classmethod
136 136 def query(cls):
137 137 return Session.query(cls)
138 138
139 139 @classmethod
140 140 def get(cls, id_):
141 141 if id_:
142 142 return cls.query().get(id_)
143 143
144 144 @classmethod
145 145 def getAll(cls):
146 146 return cls.query().all()
147 147
148 148 @classmethod
149 149 def delete(cls, id_):
150 150 obj = cls.query().get(id_)
151 151 Session.delete(obj)
152 152
153 153 def __repr__(self):
154 154 if hasattr(self, '__unicode__'):
155 155 # python repr needs to return str
156 156 return safe_str(self.__unicode__())
157 157 return '<DB:%s>' % (self.__class__.__name__)
158 158
159 159
160 160 class RhodeCodeSetting(Base, BaseModel):
161 161 __tablename__ = 'rhodecode_settings'
162 162 __table_args__ = (
163 163 UniqueConstraint('app_settings_name'),
164 164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
165 165 'mysql_charset': 'utf8'}
166 166 )
167 167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
168 168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
170 170
171 171 def __init__(self, k='', v=''):
172 172 self.app_settings_name = k
173 173 self.app_settings_value = v
174 174
175 175 @validates('_app_settings_value')
176 176 def validate_settings_value(self, key, val):
177 177 assert type(val) == unicode
178 178 return val
179 179
180 180 @hybrid_property
181 181 def app_settings_value(self):
182 182 v = self._app_settings_value
183 183 if self.app_settings_name == 'ldap_active':
184 184 v = str2bool(v)
185 185 return v
186 186
187 187 @app_settings_value.setter
188 188 def app_settings_value(self, val):
189 189 """
190 190 Setter that will always make sure we use unicode in app_settings_value
191 191
192 192 :param val:
193 193 """
194 194 self._app_settings_value = safe_unicode(val)
195 195
196 196 def __unicode__(self):
197 197 return u"<%s('%s:%s')>" % (
198 198 self.__class__.__name__,
199 199 self.app_settings_name, self.app_settings_value
200 200 )
201 201
202 202 @classmethod
203 203 def get_by_name(cls, ldap_key):
204 204 return cls.query()\
205 205 .filter(cls.app_settings_name == ldap_key).scalar()
206 206
207 207 @classmethod
208 208 def get_app_settings(cls, cache=False):
209 209
210 210 ret = cls.query()
211 211
212 212 if cache:
213 213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
214 214
215 215 if not ret:
216 216 raise Exception('Could not get application settings !')
217 217 settings = {}
218 218 for each in ret:
219 219 settings['rhodecode_' + each.app_settings_name] = \
220 220 each.app_settings_value
221 221
222 222 return settings
223 223
224 224 @classmethod
225 225 def get_ldap_settings(cls, cache=False):
226 226 ret = cls.query()\
227 227 .filter(cls.app_settings_name.startswith('ldap_')).all()
228 228 fd = {}
229 229 for row in ret:
230 230 fd.update({row.app_settings_name: row.app_settings_value})
231 231
232 232 return fd
233 233
234 234
235 235 class RhodeCodeUi(Base, BaseModel):
236 236 __tablename__ = 'rhodecode_ui'
237 237 __table_args__ = (
238 238 UniqueConstraint('ui_key'),
239 239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
240 240 'mysql_charset': 'utf8'}
241 241 )
242 242
243 243 HOOK_UPDATE = 'changegroup.update'
244 244 HOOK_REPO_SIZE = 'changegroup.repo_size'
245 245 HOOK_PUSH = 'pretxnchangegroup.push_logger'
246 246 HOOK_PULL = 'preoutgoing.pull_logger'
247 247
248 248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
249 249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
253 253
254 254 @classmethod
255 255 def get_by_key(cls, key):
256 256 return cls.query().filter(cls.ui_key == key)
257 257
258 258 @classmethod
259 259 def get_builtin_hooks(cls):
260 260 q = cls.query()
261 261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
262 262 cls.HOOK_REPO_SIZE,
263 263 cls.HOOK_PUSH, cls.HOOK_PULL]))
264 264 return q.all()
265 265
266 266 @classmethod
267 267 def get_custom_hooks(cls):
268 268 q = cls.query()
269 269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
270 270 cls.HOOK_REPO_SIZE,
271 271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 272 q = q.filter(cls.ui_section == 'hooks')
273 273 return q.all()
274 274
275 275 @classmethod
276 276 def create_or_update_hook(cls, key, val):
277 277 new_ui = cls.get_by_key(key).scalar() or cls()
278 278 new_ui.ui_section = 'hooks'
279 279 new_ui.ui_active = True
280 280 new_ui.ui_key = key
281 281 new_ui.ui_value = val
282 282
283 283 Session.add(new_ui)
284 284
285 285
286 286 class User(Base, BaseModel):
287 287 __tablename__ = 'users'
288 288 __table_args__ = (
289 289 UniqueConstraint('username'), UniqueConstraint('email'),
290 290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
291 291 'mysql_charset': 'utf8'}
292 292 )
293 293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 294 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 295 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
297 297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 298 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 300 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 302 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 303 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 304
305 305 user_log = relationship('UserLog', cascade='all')
306 306 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
307 307
308 308 repositories = relationship('Repository')
309 309 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
310 310 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
311 311 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
312 312
313 313 group_member = relationship('UsersGroupMember', cascade='all')
314 314
315 315 notifications = relationship('UserNotification', cascade='all')
316 316 # notifications assigned to this user
317 317 user_created_notifications = relationship('Notification', cascade='all')
318 318 # comments created by this user
319 319 user_comments = relationship('ChangesetComment', cascade='all')
320 320
321 321 @hybrid_property
322 322 def email(self):
323 323 return self._email
324 324
325 325 @email.setter
326 326 def email(self, val):
327 327 self._email = val.lower() if val else None
328 328
329 329 @property
330 330 def full_name(self):
331 331 return '%s %s' % (self.name, self.lastname)
332 332
333 333 @property
334 334 def full_name_or_username(self):
335 335 return ('%s %s' % (self.name, self.lastname)
336 336 if (self.name and self.lastname) else self.username)
337 337
338 338 @property
339 339 def full_contact(self):
340 340 return '%s %s <%s>' % (self.name, self.lastname, self.email)
341 341
342 342 @property
343 343 def short_contact(self):
344 344 return '%s %s' % (self.name, self.lastname)
345 345
346 346 @property
347 347 def is_admin(self):
348 348 return self.admin
349 349
350 350 def __unicode__(self):
351 351 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
352 352 self.user_id, self.username)
353 353
354 354 @classmethod
355 355 def get_by_username(cls, username, case_insensitive=False, cache=False):
356 356 if case_insensitive:
357 357 q = cls.query().filter(cls.username.ilike(username))
358 358 else:
359 359 q = cls.query().filter(cls.username == username)
360 360
361 361 if cache:
362 362 q = q.options(FromCache(
363 363 "sql_cache_short",
364 364 "get_user_%s" % _hash_key(username)
365 365 )
366 366 )
367 367 return q.scalar()
368 368
369 369 @classmethod
370 370 def get_by_api_key(cls, api_key, cache=False):
371 371 q = cls.query().filter(cls.api_key == api_key)
372 372
373 373 if cache:
374 374 q = q.options(FromCache("sql_cache_short",
375 375 "get_api_key_%s" % api_key))
376 376 return q.scalar()
377 377
378 378 @classmethod
379 379 def get_by_email(cls, email, case_insensitive=False, cache=False):
380 380 if case_insensitive:
381 381 q = cls.query().filter(cls.email.ilike(email))
382 382 else:
383 383 q = cls.query().filter(cls.email == email)
384 384
385 385 if cache:
386 386 q = q.options(FromCache("sql_cache_short",
387 387 "get_email_key_%s" % email))
388 388
389 389 ret = q.scalar()
390 390 if ret is None:
391 391 q = UserEmailMap.query()
392 392 # try fetching in alternate email map
393 393 if case_insensitive:
394 394 q = q.filter(UserEmailMap.email.ilike(email))
395 395 else:
396 396 q = q.filter(UserEmailMap.email == email)
397 397 q = q.options(joinedload(UserEmailMap.user))
398 398 if cache:
399 399 q = q.options(FromCache("sql_cache_short",
400 400 "get_email_map_key_%s" % email))
401 401 ret = getattr(q.scalar(), 'user', None)
402 402
403 403 return ret
404 404
405 405 def update_lastlogin(self):
406 406 """Update user lastlogin"""
407 407 self.last_login = datetime.datetime.now()
408 408 Session.add(self)
409 409 log.debug('updated user %s lastlogin' % self.username)
410 410
411 411 def __json__(self):
412 412 return dict(
413 413 user_id=self.user_id,
414 414 first_name=self.name,
415 415 last_name=self.lastname,
416 416 email=self.email,
417 417 full_name=self.full_name,
418 418 full_name_or_username=self.full_name_or_username,
419 419 short_contact=self.short_contact,
420 420 full_contact=self.full_contact
421 421 )
422 422
423 423
424 424 class UserEmailMap(Base, BaseModel):
425 425 __tablename__ = 'user_email_map'
426 426 __table_args__ = (
427 427 UniqueConstraint('email'),
428 428 {'extend_existing': True, 'mysql_engine':'InnoDB',
429 429 'mysql_charset': 'utf8'}
430 430 )
431 431 __mapper_args__ = {}
432 432
433 433 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 434 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
435 435 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
436 436
437 437 user = relationship('User')
438 438
439 439 @validates('_email')
440 440 def validate_email(self, key, email):
441 441 # check if this email is not main one
442 442 main_email = Session.query(User).filter(User.email == email).scalar()
443 443 if main_email is not None:
444 444 raise AttributeError('email %s is present is user table' % email)
445 445 return email
446 446
447 447 @hybrid_property
448 448 def email(self):
449 449 return self._email
450 450
451 451 @email.setter
452 452 def email(self, val):
453 453 self._email = val.lower() if val else None
454 454
455 455
456 456 class UserLog(Base, BaseModel):
457 457 __tablename__ = 'user_logs'
458 458 __table_args__ = (
459 459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
460 460 'mysql_charset': 'utf8'},
461 461 )
462 462 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
463 463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
464 464 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
465 465 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
466 466 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
467 467 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
468 468 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
469 469
470 470 @property
471 471 def action_as_day(self):
472 472 return datetime.date(*self.action_date.timetuple()[:3])
473 473
474 474 user = relationship('User')
475 475 repository = relationship('Repository', cascade='')
476 476
477 477
478 478 class UsersGroup(Base, BaseModel):
479 479 __tablename__ = 'users_groups'
480 480 __table_args__ = (
481 481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
482 482 'mysql_charset': 'utf8'},
483 483 )
484 484
485 485 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
486 486 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
487 487 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
488 488
489 489 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
490 490 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
491 491 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
492 492
493 493 def __unicode__(self):
494 494 return u'<userGroup(%s)>' % (self.users_group_name)
495 495
496 496 @classmethod
497 497 def get_by_group_name(cls, group_name, cache=False,
498 498 case_insensitive=False):
499 499 if case_insensitive:
500 500 q = cls.query().filter(cls.users_group_name.ilike(group_name))
501 501 else:
502 502 q = cls.query().filter(cls.users_group_name == group_name)
503 503 if cache:
504 504 q = q.options(FromCache(
505 505 "sql_cache_short",
506 506 "get_user_%s" % _hash_key(group_name)
507 507 )
508 508 )
509 509 return q.scalar()
510 510
511 511 @classmethod
512 512 def get(cls, users_group_id, cache=False):
513 513 users_group = cls.query()
514 514 if cache:
515 515 users_group = users_group.options(FromCache("sql_cache_short",
516 516 "get_users_group_%s" % users_group_id))
517 517 return users_group.get(users_group_id)
518 518
519 519
520 520 class UsersGroupMember(Base, BaseModel):
521 521 __tablename__ = 'users_groups_members'
522 522 __table_args__ = (
523 523 {'extend_existing': True, 'mysql_engine': 'InnoDB',
524 524 'mysql_charset': 'utf8'},
525 525 )
526 526
527 527 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
528 528 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
529 529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
530 530
531 531 user = relationship('User', lazy='joined')
532 532 users_group = relationship('UsersGroup')
533 533
534 534 def __init__(self, gr_id='', u_id=''):
535 535 self.users_group_id = gr_id
536 536 self.user_id = u_id
537 537
538 538
539 539 class Repository(Base, BaseModel):
540 540 __tablename__ = 'repositories'
541 541 __table_args__ = (
542 542 UniqueConstraint('repo_name'),
543 543 {'extend_existing': True, 'mysql_engine': 'InnoDB',
544 544 'mysql_charset': 'utf8'},
545 545 )
546 546
547 547 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
548 548 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
549 549 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
550 550 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
551 551 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
552 552 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
553 553 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
554 554 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
555 555 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
556 556 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
557 557
558 558 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
559 559 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
560 560
561 561 user = relationship('User')
562 562 fork = relationship('Repository', remote_side=repo_id)
563 563 group = relationship('RepoGroup')
564 564 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
565 565 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
566 566 stats = relationship('Statistics', cascade='all', uselist=False)
567 567
568 568 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
569 569
570 570 logs = relationship('UserLog')
571 571 comments = relationship('ChangesetComment')
572 572
573 573 def __unicode__(self):
574 574 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
575 575 self.repo_name)
576 576
577 577 @classmethod
578 578 def url_sep(cls):
579 579 return URL_SEP
580 580
581 581 @classmethod
582 582 def get_by_repo_name(cls, repo_name):
583 583 q = Session.query(cls).filter(cls.repo_name == repo_name)
584 584 q = q.options(joinedload(Repository.fork))\
585 585 .options(joinedload(Repository.user))\
586 586 .options(joinedload(Repository.group))
587 587 return q.scalar()
588 588
589 589 @classmethod
590 590 def get_repo_forks(cls, repo_id):
591 591 return cls.query().filter(Repository.fork_id == repo_id)
592 592
593 593 @classmethod
594 594 def base_path(cls):
595 595 """
596 596 Returns base path when all repos are stored
597 597
598 598 :param cls:
599 599 """
600 600 q = Session.query(RhodeCodeUi)\
601 601 .filter(RhodeCodeUi.ui_key == cls.url_sep())
602 602 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
603 603 return q.one().ui_value
604 604
605 605 @property
606 def forks(self):
607 """
608 Return forks of this repo
609 """
610 return Repository.get_repo_forks(self.repo_id)
611
612 @property
613 def parent(self):
614 """
615 Returns fork parent
616 """
617 return self.fork
618
619 @property
606 620 def just_name(self):
607 621 return self.repo_name.split(Repository.url_sep())[-1]
608 622
609 623 @property
610 624 def groups_with_parents(self):
611 625 groups = []
612 626 if self.group is None:
613 627 return groups
614 628
615 629 cur_gr = self.group
616 630 groups.insert(0, cur_gr)
617 631 while 1:
618 632 gr = getattr(cur_gr, 'parent_group', None)
619 633 cur_gr = cur_gr.parent_group
620 634 if gr is None:
621 635 break
622 636 groups.insert(0, gr)
623 637
624 638 return groups
625 639
626 640 @property
627 641 def groups_and_repo(self):
628 642 return self.groups_with_parents, self.just_name
629 643
630 644 @LazyProperty
631 645 def repo_path(self):
632 646 """
633 647 Returns base full path for that repository means where it actually
634 648 exists on a filesystem
635 649 """
636 650 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
637 651 Repository.url_sep())
638 652 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
639 653 return q.one().ui_value
640 654
641 655 @property
642 656 def repo_full_path(self):
643 657 p = [self.repo_path]
644 658 # we need to split the name by / since this is how we store the
645 659 # names in the database, but that eventually needs to be converted
646 660 # into a valid system path
647 661 p += self.repo_name.split(Repository.url_sep())
648 662 return os.path.join(*p)
649 663
650 664 def get_new_name(self, repo_name):
651 665 """
652 666 returns new full repository name based on assigned group and new new
653 667
654 668 :param group_name:
655 669 """
656 670 path_prefix = self.group.full_path_splitted if self.group else []
657 671 return Repository.url_sep().join(path_prefix + [repo_name])
658 672
659 673 @property
660 674 def _ui(self):
661 675 """
662 676 Creates an db based ui object for this repository
663 677 """
664 678 from mercurial import ui
665 679 from mercurial import config
666 680 baseui = ui.ui()
667 681
668 682 #clean the baseui object
669 683 baseui._ocfg = config.config()
670 684 baseui._ucfg = config.config()
671 685 baseui._tcfg = config.config()
672 686
673 687 ret = RhodeCodeUi.query()\
674 688 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
675 689
676 690 hg_ui = ret
677 691 for ui_ in hg_ui:
678 692 if ui_.ui_active:
679 693 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
680 694 ui_.ui_key, ui_.ui_value)
681 695 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
682 696
683 697 return baseui
684 698
685 699 @classmethod
686 700 def is_valid(cls, repo_name):
687 701 """
688 702 returns True if given repo name is a valid filesystem repository
689 703
690 704 :param cls:
691 705 :param repo_name:
692 706 """
693 707 from rhodecode.lib.utils import is_valid_repo
694 708
695 709 return is_valid_repo(repo_name, cls.base_path())
696 710
697 711 #==========================================================================
698 712 # SCM PROPERTIES
699 713 #==========================================================================
700 714
701 715 def get_changeset(self, rev=None):
702 716 return get_changeset_safe(self.scm_instance, rev)
703 717
704 718 @property
705 719 def tip(self):
706 720 return self.get_changeset('tip')
707 721
708 722 @property
709 723 def author(self):
710 724 return self.tip.author
711 725
712 726 @property
713 727 def last_change(self):
714 728 return self.scm_instance.last_change
715 729
716 730 def comments(self, revisions=None):
717 731 """
718 732 Returns comments for this repository grouped by revisions
719 733
720 734 :param revisions: filter query by revisions only
721 735 """
722 736 cmts = ChangesetComment.query()\
723 737 .filter(ChangesetComment.repo == self)
724 738 if revisions:
725 739 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
726 740 grouped = defaultdict(list)
727 741 for cmt in cmts.all():
728 742 grouped[cmt.revision].append(cmt)
729 743 return grouped
730 744
731 745 def statuses(self, revisions=None):
732 746 """
733 747 Returns statuses for this repository
734 748
735 749 :param revisions: list of revisions to get statuses for
736 750 :type revisions: list
737 751 """
738 752
739 753 statuses = ChangesetStatus.query()\
740 754 .filter(ChangesetStatus.repo == self)\
741 755 .filter(ChangesetStatus.version == 0)
742 756 if revisions:
743 757 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
744 758 grouped = {}
745 759 for stat in statuses.all():
746 760 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
747 761 return grouped
748 762
749 763 #==========================================================================
750 764 # SCM CACHE INSTANCE
751 765 #==========================================================================
752 766
753 767 @property
754 768 def invalidate(self):
755 769 return CacheInvalidation.invalidate(self.repo_name)
756 770
757 771 def set_invalidate(self):
758 772 """
759 773 set a cache for invalidation for this instance
760 774 """
761 775 CacheInvalidation.set_invalidate(self.repo_name)
762 776
763 777 @LazyProperty
764 778 def scm_instance(self):
765 779 return self.__get_instance()
766 780
767 781 def scm_instance_cached(self, cache_map=None):
768 782 @cache_region('long_term')
769 783 def _c(repo_name):
770 784 return self.__get_instance()
771 785 rn = self.repo_name
772 786 log.debug('Getting cached instance of repo')
773 787
774 788 if cache_map:
775 789 # get using prefilled cache_map
776 790 invalidate_repo = cache_map[self.repo_name]
777 791 if invalidate_repo:
778 792 invalidate_repo = (None if invalidate_repo.cache_active
779 793 else invalidate_repo)
780 794 else:
781 795 # get from invalidate
782 796 invalidate_repo = self.invalidate
783 797
784 798 if invalidate_repo is not None:
785 799 region_invalidate(_c, None, rn)
786 800 # update our cache
787 801 CacheInvalidation.set_valid(invalidate_repo.cache_key)
788 802 return _c(rn)
789 803
790 804 def __get_instance(self):
791 805 repo_full_path = self.repo_full_path
792 806 try:
793 807 alias = get_scm(repo_full_path)[0]
794 808 log.debug('Creating instance of %s repository' % alias)
795 809 backend = get_backend(alias)
796 810 except VCSError:
797 811 log.error(traceback.format_exc())
798 812 log.error('Perhaps this repository is in db and not in '
799 813 'filesystem run rescan repositories with '
800 814 '"destroy old data " option from admin panel')
801 815 return
802 816
803 817 if alias == 'hg':
804 818
805 819 repo = backend(safe_str(repo_full_path), create=False,
806 820 baseui=self._ui)
807 821 # skip hidden web repository
808 822 if repo._get_hidden():
809 823 return
810 824 else:
811 825 repo = backend(repo_full_path, create=False)
812 826
813 827 return repo
814 828
815 829
816 830 class RepoGroup(Base, BaseModel):
817 831 __tablename__ = 'groups'
818 832 __table_args__ = (
819 833 UniqueConstraint('group_name', 'group_parent_id'),
820 834 CheckConstraint('group_id != group_parent_id'),
821 835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
822 836 'mysql_charset': 'utf8'},
823 837 )
824 838 __mapper_args__ = {'order_by': 'group_name'}
825 839
826 840 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
827 841 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
828 842 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
829 843 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
830 844
831 845 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
832 846 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
833 847
834 848 parent_group = relationship('RepoGroup', remote_side=group_id)
835 849
836 850 def __init__(self, group_name='', parent_group=None):
837 851 self.group_name = group_name
838 852 self.parent_group = parent_group
839 853
840 854 def __unicode__(self):
841 855 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
842 856 self.group_name)
843 857
844 858 @classmethod
845 859 def groups_choices(cls):
846 860 from webhelpers.html import literal as _literal
847 861 repo_groups = [('', '')]
848 862 sep = ' &raquo; '
849 863 _name = lambda k: _literal(sep.join(k))
850 864
851 865 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
852 866 for x in cls.query().all()])
853 867
854 868 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
855 869 return repo_groups
856 870
857 871 @classmethod
858 872 def url_sep(cls):
859 873 return URL_SEP
860 874
861 875 @classmethod
862 876 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
863 877 if case_insensitive:
864 878 gr = cls.query()\
865 879 .filter(cls.group_name.ilike(group_name))
866 880 else:
867 881 gr = cls.query()\
868 882 .filter(cls.group_name == group_name)
869 883 if cache:
870 884 gr = gr.options(FromCache(
871 885 "sql_cache_short",
872 886 "get_group_%s" % _hash_key(group_name)
873 887 )
874 888 )
875 889 return gr.scalar()
876 890
877 891 @property
878 892 def parents(self):
879 893 parents_recursion_limit = 5
880 894 groups = []
881 895 if self.parent_group is None:
882 896 return groups
883 897 cur_gr = self.parent_group
884 898 groups.insert(0, cur_gr)
885 899 cnt = 0
886 900 while 1:
887 901 cnt += 1
888 902 gr = getattr(cur_gr, 'parent_group', None)
889 903 cur_gr = cur_gr.parent_group
890 904 if gr is None:
891 905 break
892 906 if cnt == parents_recursion_limit:
893 907 # this will prevent accidental infinit loops
894 908 log.error('group nested more than %s' %
895 909 parents_recursion_limit)
896 910 break
897 911
898 912 groups.insert(0, gr)
899 913 return groups
900 914
901 915 @property
902 916 def children(self):
903 917 return RepoGroup.query().filter(RepoGroup.parent_group == self)
904 918
905 919 @property
906 920 def name(self):
907 921 return self.group_name.split(RepoGroup.url_sep())[-1]
908 922
909 923 @property
910 924 def full_path(self):
911 925 return self.group_name
912 926
913 927 @property
914 928 def full_path_splitted(self):
915 929 return self.group_name.split(RepoGroup.url_sep())
916 930
917 931 @property
918 932 def repositories(self):
919 933 return Repository.query()\
920 934 .filter(Repository.group == self)\
921 935 .order_by(Repository.repo_name)
922 936
923 937 @property
924 938 def repositories_recursive_count(self):
925 939 cnt = self.repositories.count()
926 940
927 941 def children_count(group):
928 942 cnt = 0
929 943 for child in group.children:
930 944 cnt += child.repositories.count()
931 945 cnt += children_count(child)
932 946 return cnt
933 947
934 948 return cnt + children_count(self)
935 949
936 950 def get_new_name(self, group_name):
937 951 """
938 952 returns new full group name based on parent and new name
939 953
940 954 :param group_name:
941 955 """
942 956 path_prefix = (self.parent_group.full_path_splitted if
943 957 self.parent_group else [])
944 958 return RepoGroup.url_sep().join(path_prefix + [group_name])
945 959
946 960
947 961 class Permission(Base, BaseModel):
948 962 __tablename__ = 'permissions'
949 963 __table_args__ = (
950 964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
951 965 'mysql_charset': 'utf8'},
952 966 )
953 967 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
954 968 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
955 969 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
956 970
957 971 def __unicode__(self):
958 972 return u"<%s('%s:%s')>" % (
959 973 self.__class__.__name__, self.permission_id, self.permission_name
960 974 )
961 975
962 976 @classmethod
963 977 def get_by_key(cls, key):
964 978 return cls.query().filter(cls.permission_name == key).scalar()
965 979
966 980 @classmethod
967 981 def get_default_perms(cls, default_user_id):
968 982 q = Session.query(UserRepoToPerm, Repository, cls)\
969 983 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
970 984 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
971 985 .filter(UserRepoToPerm.user_id == default_user_id)
972 986
973 987 return q.all()
974 988
975 989 @classmethod
976 990 def get_default_group_perms(cls, default_user_id):
977 991 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
978 992 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
979 993 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
980 994 .filter(UserRepoGroupToPerm.user_id == default_user_id)
981 995
982 996 return q.all()
983 997
984 998
985 999 class UserRepoToPerm(Base, BaseModel):
986 1000 __tablename__ = 'repo_to_perm'
987 1001 __table_args__ = (
988 1002 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
989 1003 {'extend_existing': True, 'mysql_engine': 'InnoDB',
990 1004 'mysql_charset': 'utf8'}
991 1005 )
992 1006 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
993 1007 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
994 1008 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
995 1009 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
996 1010
997 1011 user = relationship('User')
998 1012 repository = relationship('Repository')
999 1013 permission = relationship('Permission')
1000 1014
1001 1015 @classmethod
1002 1016 def create(cls, user, repository, permission):
1003 1017 n = cls()
1004 1018 n.user = user
1005 1019 n.repository = repository
1006 1020 n.permission = permission
1007 1021 Session.add(n)
1008 1022 return n
1009 1023
1010 1024 def __unicode__(self):
1011 1025 return u'<user:%s => %s >' % (self.user, self.repository)
1012 1026
1013 1027
1014 1028 class UserToPerm(Base, BaseModel):
1015 1029 __tablename__ = 'user_to_perm'
1016 1030 __table_args__ = (
1017 1031 UniqueConstraint('user_id', 'permission_id'),
1018 1032 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1019 1033 'mysql_charset': 'utf8'}
1020 1034 )
1021 1035 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1022 1036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1023 1037 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1024 1038
1025 1039 user = relationship('User')
1026 1040 permission = relationship('Permission', lazy='joined')
1027 1041
1028 1042
1029 1043 class UsersGroupRepoToPerm(Base, BaseModel):
1030 1044 __tablename__ = 'users_group_repo_to_perm'
1031 1045 __table_args__ = (
1032 1046 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1033 1047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 1048 'mysql_charset': 'utf8'}
1035 1049 )
1036 1050 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1037 1051 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1038 1052 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1039 1053 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1040 1054
1041 1055 users_group = relationship('UsersGroup')
1042 1056 permission = relationship('Permission')
1043 1057 repository = relationship('Repository')
1044 1058
1045 1059 @classmethod
1046 1060 def create(cls, users_group, repository, permission):
1047 1061 n = cls()
1048 1062 n.users_group = users_group
1049 1063 n.repository = repository
1050 1064 n.permission = permission
1051 1065 Session.add(n)
1052 1066 return n
1053 1067
1054 1068 def __unicode__(self):
1055 1069 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1056 1070
1057 1071
1058 1072 class UsersGroupToPerm(Base, BaseModel):
1059 1073 __tablename__ = 'users_group_to_perm'
1060 1074 __table_args__ = (
1061 1075 UniqueConstraint('users_group_id', 'permission_id',),
1062 1076 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1063 1077 'mysql_charset': 'utf8'}
1064 1078 )
1065 1079 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1066 1080 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1067 1081 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1068 1082
1069 1083 users_group = relationship('UsersGroup')
1070 1084 permission = relationship('Permission')
1071 1085
1072 1086
1073 1087 class UserRepoGroupToPerm(Base, BaseModel):
1074 1088 __tablename__ = 'user_repo_group_to_perm'
1075 1089 __table_args__ = (
1076 1090 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1077 1091 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1078 1092 'mysql_charset': 'utf8'}
1079 1093 )
1080 1094
1081 1095 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1082 1096 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1083 1097 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1084 1098 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1085 1099
1086 1100 user = relationship('User')
1087 1101 group = relationship('RepoGroup')
1088 1102 permission = relationship('Permission')
1089 1103
1090 1104
1091 1105 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1092 1106 __tablename__ = 'users_group_repo_group_to_perm'
1093 1107 __table_args__ = (
1094 1108 UniqueConstraint('users_group_id', 'group_id'),
1095 1109 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1096 1110 'mysql_charset': 'utf8'}
1097 1111 )
1098 1112
1099 1113 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)
1100 1114 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1101 1115 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1102 1116 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1103 1117
1104 1118 users_group = relationship('UsersGroup')
1105 1119 permission = relationship('Permission')
1106 1120 group = relationship('RepoGroup')
1107 1121
1108 1122
1109 1123 class Statistics(Base, BaseModel):
1110 1124 __tablename__ = 'statistics'
1111 1125 __table_args__ = (
1112 1126 UniqueConstraint('repository_id'),
1113 1127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1114 1128 'mysql_charset': 'utf8'}
1115 1129 )
1116 1130 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1117 1131 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1118 1132 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1119 1133 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1120 1134 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1121 1135 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1122 1136
1123 1137 repository = relationship('Repository', single_parent=True)
1124 1138
1125 1139
1126 1140 class UserFollowing(Base, BaseModel):
1127 1141 __tablename__ = 'user_followings'
1128 1142 __table_args__ = (
1129 1143 UniqueConstraint('user_id', 'follows_repository_id'),
1130 1144 UniqueConstraint('user_id', 'follows_user_id'),
1131 1145 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1132 1146 'mysql_charset': 'utf8'}
1133 1147 )
1134 1148
1135 1149 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1136 1150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1137 1151 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1138 1152 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1139 1153 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1140 1154
1141 1155 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1142 1156
1143 1157 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1144 1158 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1145 1159
1146 1160 @classmethod
1147 1161 def get_repo_followers(cls, repo_id):
1148 1162 return cls.query().filter(cls.follows_repo_id == repo_id)
1149 1163
1150 1164
1151 1165 class CacheInvalidation(Base, BaseModel):
1152 1166 __tablename__ = 'cache_invalidation'
1153 1167 __table_args__ = (
1154 1168 UniqueConstraint('cache_key'),
1155 1169 Index('key_idx', 'cache_key'),
1156 1170 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1157 1171 'mysql_charset': 'utf8'},
1158 1172 )
1159 1173 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1160 1174 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1161 1175 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1162 1176 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1163 1177
1164 1178 def __init__(self, cache_key, cache_args=''):
1165 1179 self.cache_key = cache_key
1166 1180 self.cache_args = cache_args
1167 1181 self.cache_active = False
1168 1182
1169 1183 def __unicode__(self):
1170 1184 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1171 1185 self.cache_id, self.cache_key)
1172 1186
1173 1187 @classmethod
1174 1188 def clear_cache(cls):
1175 1189 cls.query().delete()
1176 1190
1177 1191 @classmethod
1178 1192 def _get_key(cls, key):
1179 1193 """
1180 1194 Wrapper for generating a key, together with a prefix
1181 1195
1182 1196 :param key:
1183 1197 """
1184 1198 import rhodecode
1185 1199 prefix = ''
1186 1200 iid = rhodecode.CONFIG.get('instance_id')
1187 1201 if iid:
1188 1202 prefix = iid
1189 1203 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1190 1204
1191 1205 @classmethod
1192 1206 def get_by_key(cls, key):
1193 1207 return cls.query().filter(cls.cache_key == key).scalar()
1194 1208
1195 1209 @classmethod
1196 1210 def _get_or_create_key(cls, key, prefix, org_key):
1197 1211 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1198 1212 if not inv_obj:
1199 1213 try:
1200 1214 inv_obj = CacheInvalidation(key, org_key)
1201 1215 Session.add(inv_obj)
1202 1216 Session.commit()
1203 1217 except Exception:
1204 1218 log.error(traceback.format_exc())
1205 1219 Session.rollback()
1206 1220 return inv_obj
1207 1221
1208 1222 @classmethod
1209 1223 def invalidate(cls, key):
1210 1224 """
1211 1225 Returns Invalidation object if this given key should be invalidated
1212 1226 None otherwise. `cache_active = False` means that this cache
1213 1227 state is not valid and needs to be invalidated
1214 1228
1215 1229 :param key:
1216 1230 """
1217 1231
1218 1232 key, _prefix, _org_key = cls._get_key(key)
1219 1233 inv = cls._get_or_create_key(key, _prefix, _org_key)
1220 1234
1221 1235 if inv and inv.cache_active is False:
1222 1236 return inv
1223 1237
1224 1238 @classmethod
1225 1239 def set_invalidate(cls, key):
1226 1240 """
1227 1241 Mark this Cache key for invalidation
1228 1242
1229 1243 :param key:
1230 1244 """
1231 1245
1232 1246 key, _prefix, _org_key = cls._get_key(key)
1233 1247 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1234 1248 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1235 1249 _org_key))
1236 1250 try:
1237 1251 for inv_obj in inv_objs:
1238 1252 if inv_obj:
1239 1253 inv_obj.cache_active = False
1240 1254
1241 1255 Session.add(inv_obj)
1242 1256 Session.commit()
1243 1257 except Exception:
1244 1258 log.error(traceback.format_exc())
1245 1259 Session.rollback()
1246 1260
1247 1261 @classmethod
1248 1262 def set_valid(cls, key):
1249 1263 """
1250 1264 Mark this cache key as active and currently cached
1251 1265
1252 1266 :param key:
1253 1267 """
1254 1268 inv_obj = cls.get_by_key(key)
1255 1269 inv_obj.cache_active = True
1256 1270 Session.add(inv_obj)
1257 1271 Session.commit()
1258 1272
1259 1273 @classmethod
1260 1274 def get_cache_map(cls):
1261 1275
1262 1276 class cachemapdict(dict):
1263 1277
1264 1278 def __init__(self, *args, **kwargs):
1265 1279 fixkey = kwargs.get('fixkey')
1266 1280 if fixkey:
1267 1281 del kwargs['fixkey']
1268 1282 self.fixkey = fixkey
1269 1283 super(cachemapdict, self).__init__(*args, **kwargs)
1270 1284
1271 1285 def __getattr__(self, name):
1272 1286 key = name
1273 1287 if self.fixkey:
1274 1288 key, _prefix, _org_key = cls._get_key(key)
1275 1289 if key in self.__dict__:
1276 1290 return self.__dict__[key]
1277 1291 else:
1278 1292 return self[key]
1279 1293
1280 1294 def __getitem__(self, key):
1281 1295 if self.fixkey:
1282 1296 key, _prefix, _org_key = cls._get_key(key)
1283 1297 try:
1284 1298 return super(cachemapdict, self).__getitem__(key)
1285 1299 except KeyError:
1286 1300 return
1287 1301
1288 1302 cache_map = cachemapdict(fixkey=True)
1289 1303 for obj in cls.query().all():
1290 1304 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1291 1305 return cache_map
1292 1306
1293 1307
1294 1308 class ChangesetComment(Base, BaseModel):
1295 1309 __tablename__ = 'changeset_comments'
1296 1310 __table_args__ = (
1297 1311 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1298 1312 'mysql_charset': 'utf8'},
1299 1313 )
1300 1314 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1301 1315 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1302 1316 revision = Column('revision', String(40), nullable=False)
1303 1317 line_no = Column('line_no', Unicode(10), nullable=True)
1304 1318 f_path = Column('f_path', Unicode(1000), nullable=True)
1305 1319 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1306 1320 text = Column('text', Unicode(25000), nullable=False)
1307 1321 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1308 1322
1309 1323 author = relationship('User', lazy='joined')
1310 1324 repo = relationship('Repository')
1311 1325 status_change = relationship('ChangesetStatus', uselist=False)
1312 1326
1313 1327 @classmethod
1314 1328 def get_users(cls, revision):
1315 1329 """
1316 1330 Returns user associated with this changesetComment. ie those
1317 1331 who actually commented
1318 1332
1319 1333 :param cls:
1320 1334 :param revision:
1321 1335 """
1322 1336 return Session.query(User)\
1323 1337 .filter(cls.revision == revision)\
1324 1338 .join(ChangesetComment.author).all()
1325 1339
1326 1340
1327 1341 class ChangesetStatus(Base, BaseModel):
1328 1342 __tablename__ = 'changeset_statuses'
1329 1343 __table_args__ = (
1330 1344 UniqueConstraint('repo_id', 'revision', 'version'),
1331 1345 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1332 1346 'mysql_charset': 'utf8'}
1333 1347 )
1334 1348
1335 1349 STATUSES = [
1336 1350 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1337 1351 ('approved', _("Approved")),
1338 1352 ('rejected', _("Rejected")),
1339 1353 ('under_review', _("Under Review")),
1340 1354 ]
1341 1355 DEFAULT = STATUSES[0][0]
1342 1356
1343 1357 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1344 1358 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1345 1359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1346 1360 revision = Column('revision', String(40), nullable=False)
1347 1361 status = Column('status', String(128), nullable=False, default=DEFAULT)
1348 1362 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1349 1363 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1350 1364 version = Column('version', Integer(), nullable=False, default=0)
1351 1365 author = relationship('User', lazy='joined')
1352 1366 repo = relationship('Repository')
1353 1367 comment = relationship('ChangesetComment', lazy='joined')
1354 1368
1355 1369 @classmethod
1356 1370 def get_status_lbl(cls, value):
1357 1371 return dict(cls.STATUSES).get(value)
1358 1372
1359 1373 @property
1360 1374 def status_lbl(self):
1361 1375 return ChangesetStatus.get_status_lbl(self.status)
1362 1376
1363 1377
1364 1378 class Notification(Base, BaseModel):
1365 1379 __tablename__ = 'notifications'
1366 1380 __table_args__ = (
1367 1381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1368 1382 'mysql_charset': 'utf8'},
1369 1383 )
1370 1384
1371 1385 TYPE_CHANGESET_COMMENT = u'cs_comment'
1372 1386 TYPE_MESSAGE = u'message'
1373 1387 TYPE_MENTION = u'mention'
1374 1388 TYPE_REGISTRATION = u'registration'
1375 1389 TYPE_PULL_REQUEST = u'pull_request'
1376 1390
1377 1391 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1378 1392 subject = Column('subject', Unicode(512), nullable=True)
1379 1393 body = Column('body', Unicode(50000), nullable=True)
1380 1394 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1381 1395 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1382 1396 type_ = Column('type', Unicode(256))
1383 1397
1384 1398 created_by_user = relationship('User')
1385 1399 notifications_to_users = relationship('UserNotification', lazy='joined',
1386 1400 cascade="all, delete, delete-orphan")
1387 1401
1388 1402 @property
1389 1403 def recipients(self):
1390 1404 return [x.user for x in UserNotification.query()\
1391 1405 .filter(UserNotification.notification == self)\
1392 1406 .order_by(UserNotification.user).all()]
1393 1407
1394 1408 @classmethod
1395 1409 def create(cls, created_by, subject, body, recipients, type_=None):
1396 1410 if type_ is None:
1397 1411 type_ = Notification.TYPE_MESSAGE
1398 1412
1399 1413 notification = cls()
1400 1414 notification.created_by_user = created_by
1401 1415 notification.subject = subject
1402 1416 notification.body = body
1403 1417 notification.type_ = type_
1404 1418 notification.created_on = datetime.datetime.now()
1405 1419
1406 1420 for u in recipients:
1407 1421 assoc = UserNotification()
1408 1422 assoc.notification = notification
1409 1423 u.notifications.append(assoc)
1410 1424 Session.add(notification)
1411 1425 return notification
1412 1426
1413 1427 @property
1414 1428 def description(self):
1415 1429 from rhodecode.model.notification import NotificationModel
1416 1430 return NotificationModel().make_description(self)
1417 1431
1418 1432
1419 1433 class UserNotification(Base, BaseModel):
1420 1434 __tablename__ = 'user_to_notification'
1421 1435 __table_args__ = (
1422 1436 UniqueConstraint('user_id', 'notification_id'),
1423 1437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1424 1438 'mysql_charset': 'utf8'}
1425 1439 )
1426 1440 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1427 1441 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1428 1442 read = Column('read', Boolean, default=False)
1429 1443 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1430 1444
1431 1445 user = relationship('User', lazy="joined")
1432 1446 notification = relationship('Notification', lazy="joined",
1433 1447 order_by=lambda: Notification.created_on.desc(),)
1434 1448
1435 1449 def mark_as_read(self):
1436 1450 self.read = True
1437 1451 Session.add(self)
1438 1452
1439 1453
1440 1454 class DbMigrateVersion(Base, BaseModel):
1441 1455 __tablename__ = 'db_migrate_version'
1442 1456 __table_args__ = (
1443 1457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1444 1458 'mysql_charset': 'utf8'},
1445 1459 )
1446 1460 repository_id = Column('repository_id', String(250), primary_key=True)
1447 1461 repository_path = Column('repository_path', Text)
1448 1462 version = Column('version', Integer)
@@ -1,90 +1,77 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 10 &raquo;
11 11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 12 &raquo;
13 13 ${_('Compare')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('changelog')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <div class="table">
27 27 <div id="body" class="diffblock">
28 28 <div class="code-header cv">
29 29 <h3 class="code-header-title">${_('Compare View')}</h3>
30 30 <div>
31 31 ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
32 32 </div>
33 33 </div>
34 34 </div>
35 35 <div id="changeset_compare_view_content">
36 <div class="container">
37 <table class="compare_view_commits noborder">
38 %for cnt, cs in enumerate(c.cs_ranges):
39 <tr>
40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
41 <td>
42 %if cs.raw_id in c.statuses:
43 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
44 %endif
45 </td>
46 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
47 <td><div class="author">${h.person(cs.author)}</div></td>
48 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
49 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
50 </tr>
51 %endfor
52 </table>
53 </div>
36 ##CS
37 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Changesets')}</div>
38 <%include file="compare_cs.html" />
39
40 ## FILES
54 41 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
55 42 <div class="cs_files">
56 43 %for fid, change, f, stat in c.files:
57 44 <div class="cs_${change}">
58 45 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
59 46 <div class="changes">${h.fancy_file_stats(stat)}</div>
60 47 </div>
61 48 %endfor
62 49 </div>
63 50 </div>
64 51 </div>
65 52
66 53 ## diff block
67 54 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
68 55 %for fid, change, f, stat in c.files:
69 56 ${diff_block.diff_block_simple([c.changes[fid]])}
70 57 %endfor
71 58
72 59 <script type="text/javascript">
73 60
74 61 YUE.onDOMReady(function(){
75 62
76 63 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
77 64 var act = e.currentTarget.nextElementSibling;
78 65
79 66 if(YUD.hasClass(act,'active')){
80 67 YUD.removeClass(act,'active');
81 68 YUD.setStyle(act,'display','none');
82 69 }else{
83 70 YUD.addClass(act,'active');
84 71 YUD.setStyle(act,'display','');
85 72 }
86 73 });
87 74 })
88 75 </script>
89 76 </div>
90 77 </%def>
@@ -1,91 +1,185 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Pull request')}
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')}
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 <div style="padding:30px">
22 ${h.form(url('#'),method='put', id='pull_request_form')}
23 <div style="float:left;padding:30px">
23 24 ##ORG
24 25 <div style="float:left">
25 26 <div class="fork_user">
26 27 <div class="gravatar">
27 28 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
28 29 </div>
29 30 <span style="font-size: 20px">
30 ${h.select('other','',['%s/%s' % (c.rhodecode_db_repo.user.username,c.repo_name)])}:${h.select('other_ref','',c.org_refs)}
31 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
31 32 </span>
32 33 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
33 34 </div>
34 35 <div style="clear:both;padding-top: 10px"></div>
35 36 </div>
36 37 <div style="float:left;font-size:24px;padding:0px 20px">
37 <img src="${h.url('/images/arrow_right_64.png')}"/>
38 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
38 39 </div>
39 40
40 41 ##OTHER, most Probably the PARENT OF THIS FORK
41 42 <div style="float:left">
42 43 <div class="fork_user">
43 44 <div class="gravatar">
44 45 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
45 46 </div>
46 47 <span style="font-size: 20px">
47 ${h.select('orther','',c.sources)}:${h.select('other_ref','',c.org_refs)}
48 ${h.select('other_repo','',c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')}
48 49 </span>
49 50 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
50 51 </div>
51 52 <div style="clear:both;padding-top: 10px"></div>
52 53 </div>
54 <div style="float:left;padding:5px 5px 5px 15px">
55 <span>
56 <a id="refresh" href="#">
57 <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
58 ${_('refresh overview')}
59 </a>
60 </span>
61 </div>
62 <div style="clear:both;padding-top: 10px"></div>
63 <div style="float:left" id="pull_request_overview">
64 </div>
53 65 </div>
54
55 <h3>${_('New pull request')} from USER:REF into PARENT:REF</h3>
56 ${h.form(url('#'),method='put')}
66 <div style="float:left; border-left:1px dashed #eee">
67 <h4>${_('Pull request reviewers')}</h4>
68 <div id="reviewers" style="padding:0px 0px 0px 15px">
69 ##TODO: make this nicer :)
70 <table class="table noborder">
71 <tr>
72 <td>
73 <div>
74 <div style="float:left">
75 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen reviewers')}</div>
76 ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")}
77 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
78 ${_('Remove all elements')}
79 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
80 </div>
81 </div>
82 <div style="float:left;width:20px;padding-top:50px">
83 <img alt="add" id="add_element"
84 style="padding:2px;cursor:pointer"
85 src="${h.url('/images/icons/arrow_left.png')}"/>
86 <br />
87 <img alt="remove" id="remove_element"
88 style="padding:2px;cursor:pointer"
89 src="${h.url('/images/icons/arrow_right.png')}"/>
90 </div>
91 <div style="float:left">
92 <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div>
93 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
94 <div id="add_all_elements" style="cursor:pointer;text-align:center">
95 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
96 ${_('Add all elements')}
97 </div>
98 </div>
99 </div>
100 </td>
101 </tr>
102 </table>
103 </div>
104 </div>
105 <h3>${_('Create new pull request')}</h3>
106
57 107 <div class="form">
58 108 <!-- fields -->
59 109
60 110 <div class="fields">
61 111
62 112 <div class="field">
63 113 <div class="label">
64 114 <label for="pullrequest_title">${_('Title')}:</label>
65 115 </div>
66 116 <div class="input">
67 117 ${h.text('pullrequest_title',size=30)}
68 118 </div>
69 119 </div>
70 120
71 121 <div class="field">
72 122 <div class="label label-textarea">
73 123 <label for="pullrequest_desc">${_('description')}:</label>
74 124 </div>
75 125 <div class="textarea text-area editor">
76 126 ${h.textarea('pullrequest_desc',size=30)}
77 127 </div>
78 128 </div>
79 129
80 130 <div class="buttons">
81 131 ${h.submit('save',_('Send pull request'),class_="ui-button")}
82 132 ${h.reset('reset',_('Reset'),class_="ui-button")}
83 133 </div>
84 134 </div>
85 135 </div>
86 136 ${h.end_form()}
87 137
88
89 138 </div>
90 139
140 <script type="text/javascript">
141 MultiSelectWidget('review_members','available_members','pull_request_form');
142
143 var loadPreview = function(){
144 var url = "${h.url('compare_url',
145 repo_name='org_repo',
146 org_ref_type='branch', org_ref='org_ref',
147 other_ref_type='branch', other_ref='other_ref',
148 repo='other_repo')}";
149
150 var select_refs = YUQ('#pull_request_form select.refs')
151
152 for(var i=0;i<select_refs.length;i++){
153 var select_ref = select_refs[i];
154 var select_ref_data = select_ref.value.split(':');
155 var key = null;
156 var val = null;
157 if(select_ref_data.length>1){
158 key = select_ref.name+"_type";
159 val = select_ref_data[0];
160 url = url.replace(key,val);
161
162 key = select_ref.name;
163 val = select_ref_data[1];
164 url = url.replace(key,val);
165
166 }else{
167 key = select_ref.name;
168 val = select_ref.value;
169 url = url.replace(key,val);
170 }
171 }
172
173 ypjax(url,'pull_request_overview', function(data){})
174 }
175 YUE.on('refresh','click',function(e){
176 loadPreview()
177 })
178
179 //lazy load after 0.5
180
181 setTimeout(loadPreview,500)
182
183 </script>
184
91 185 </%def>
General Comments 0
You need to be logged in to leave comments. Login now