##// END OF EJS Templates
review-rules: extend code to support the forbid commit authors.
marcink -
r1787:bb077306 default
parent child Browse files
Show More
@@ -0,0 +1,33 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
17
18 repo_review_rule_table = db.RepoReviewRule.__table__
19
20 forbid_commit_author_to_review = Column(
21 "forbid_commit_author_to_review", Boolean(), nullable=True, default=False)
22 forbid_commit_author_to_review.create(table=repo_review_rule_table)
23
24 fixups(db, meta.Session)
25
26
27 def downgrade(migrate_engine):
28 meta = MetaData()
29 meta.bind = migrate_engine
30
31
32 def fixups(models, _SESSION):
33 pass
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 77 # defines current db version for migrations
54 __dbversion__ = 78 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,1018 +1,1023 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 pull requests controller for rhodecode for initializing pull requests
22 pull requests controller for rhodecode for initializing pull requests
23 """
23 """
24 import types
24 import types
25
25
26 import peppercorn
26 import peppercorn
27 import formencode
27 import formencode
28 import logging
28 import logging
29 import collections
29 import collections
30
30
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
31 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
35 from pyramid.threadlocal import get_current_registry
36 from sqlalchemy.sql import func
36 from sqlalchemy.sql import func
37 from sqlalchemy.sql.expression import or_
37 from sqlalchemy.sql.expression import or_
38
38
39 from rhodecode import events
39 from rhodecode import events
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
40 from rhodecode.lib import auth, diffs, helpers as h, codeblocks
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.base import (
42 from rhodecode.lib.base import (
43 BaseRepoController, render, vcs_operation_context)
43 BaseRepoController, render, vcs_operation_context)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
45 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
46 HasAcceptedRepoType, XHRRequired)
46 HasAcceptedRepoType, XHRRequired)
47 from rhodecode.lib.channelstream import channelstream_request
47 from rhodecode.lib.channelstream import channelstream_request
48 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils import jsonify
49 from rhodecode.lib.utils2 import (
49 from rhodecode.lib.utils2 import (
50 safe_int, safe_str, str2bool, safe_unicode)
50 safe_int, safe_str, str2bool, safe_unicode)
51 from rhodecode.lib.vcs.backends.base import (
51 from rhodecode.lib.vcs.backends.base import (
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
52 EmptyCommit, UpdateFailureReason, EmptyRepository)
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
54 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError,
55 NodeDoesNotExistError)
55 NodeDoesNotExistError)
56
56
57 from rhodecode.model.changeset_status import ChangesetStatusModel
57 from rhodecode.model.changeset_status import ChangesetStatusModel
58 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
59 from rhodecode.model.db import (PullRequest, ChangesetStatus, ChangesetComment,
60 Repository, PullRequestVersion)
60 Repository, PullRequestVersion)
61 from rhodecode.model.forms import PullRequestForm
61 from rhodecode.model.forms import PullRequestForm
62 from rhodecode.model.meta import Session
62 from rhodecode.model.meta import Session
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
63 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 class PullrequestsController(BaseRepoController):
68 class PullrequestsController(BaseRepoController):
69
69
70 def __before__(self):
70 def __before__(self):
71 super(PullrequestsController, self).__before__()
71 super(PullrequestsController, self).__before__()
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @NotAnonymous()
76 @NotAnonymous()
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
78 'repository.admin')
78 'repository.admin')
79 @HasAcceptedRepoType('git', 'hg')
79 @HasAcceptedRepoType('git', 'hg')
80 def index(self):
80 def index(self):
81 source_repo = c.rhodecode_db_repo
81 source_repo = c.rhodecode_db_repo
82
82
83 try:
83 try:
84 source_repo.scm_instance().get_commit()
84 source_repo.scm_instance().get_commit()
85 except EmptyRepositoryError:
85 except EmptyRepositoryError:
86 h.flash(h.literal(_('There are no commits yet')),
86 h.flash(h.literal(_('There are no commits yet')),
87 category='warning')
87 category='warning')
88 redirect(h.route_path('repo_summary', repo_name=source_repo.repo_name))
88 redirect(h.route_path('repo_summary', repo_name=source_repo.repo_name))
89
89
90 commit_id = request.GET.get('commit')
90 commit_id = request.GET.get('commit')
91 branch_ref = request.GET.get('branch')
91 branch_ref = request.GET.get('branch')
92 bookmark_ref = request.GET.get('bookmark')
92 bookmark_ref = request.GET.get('bookmark')
93
93
94 try:
94 try:
95 source_repo_data = PullRequestModel().generate_repo_data(
95 source_repo_data = PullRequestModel().generate_repo_data(
96 source_repo, commit_id=commit_id,
96 source_repo, commit_id=commit_id,
97 branch=branch_ref, bookmark=bookmark_ref)
97 branch=branch_ref, bookmark=bookmark_ref)
98 except CommitDoesNotExistError as e:
98 except CommitDoesNotExistError as e:
99 log.exception(e)
99 log.exception(e)
100 h.flash(_('Commit does not exist'), 'error')
100 h.flash(_('Commit does not exist'), 'error')
101 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
101 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
102
102
103 default_target_repo = source_repo
103 default_target_repo = source_repo
104
104
105 if source_repo.parent:
105 if source_repo.parent:
106 parent_vcs_obj = source_repo.parent.scm_instance()
106 parent_vcs_obj = source_repo.parent.scm_instance()
107 if parent_vcs_obj and not parent_vcs_obj.is_empty():
107 if parent_vcs_obj and not parent_vcs_obj.is_empty():
108 # change default if we have a parent repo
108 # change default if we have a parent repo
109 default_target_repo = source_repo.parent
109 default_target_repo = source_repo.parent
110
110
111 target_repo_data = PullRequestModel().generate_repo_data(
111 target_repo_data = PullRequestModel().generate_repo_data(
112 default_target_repo)
112 default_target_repo)
113
113
114 selected_source_ref = source_repo_data['refs']['selected_ref']
114 selected_source_ref = source_repo_data['refs']['selected_ref']
115
115
116 title_source_ref = selected_source_ref.split(':', 2)[1]
116 title_source_ref = selected_source_ref.split(':', 2)[1]
117 c.default_title = PullRequestModel().generate_pullrequest_title(
117 c.default_title = PullRequestModel().generate_pullrequest_title(
118 source=source_repo.repo_name,
118 source=source_repo.repo_name,
119 source_ref=title_source_ref,
119 source_ref=title_source_ref,
120 target=default_target_repo.repo_name
120 target=default_target_repo.repo_name
121 )
121 )
122
122
123 c.default_repo_data = {
123 c.default_repo_data = {
124 'source_repo_name': source_repo.repo_name,
124 'source_repo_name': source_repo.repo_name,
125 'source_refs_json': json.dumps(source_repo_data),
125 'source_refs_json': json.dumps(source_repo_data),
126 'target_repo_name': default_target_repo.repo_name,
126 'target_repo_name': default_target_repo.repo_name,
127 'target_refs_json': json.dumps(target_repo_data),
127 'target_refs_json': json.dumps(target_repo_data),
128 }
128 }
129 c.default_source_ref = selected_source_ref
129 c.default_source_ref = selected_source_ref
130
130
131 return render('/pullrequests/pullrequest.mako')
131 return render('/pullrequests/pullrequest.mako')
132
132
133 @LoginRequired()
133 @LoginRequired()
134 @NotAnonymous()
134 @NotAnonymous()
135 @XHRRequired()
135 @XHRRequired()
136 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
136 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
137 'repository.admin')
137 'repository.admin')
138 @jsonify
138 @jsonify
139 def get_repo_refs(self, repo_name, target_repo_name):
139 def get_repo_refs(self, repo_name, target_repo_name):
140 repo = Repository.get_by_repo_name(target_repo_name)
140 repo = Repository.get_by_repo_name(target_repo_name)
141 if not repo:
141 if not repo:
142 raise HTTPNotFound
142 raise HTTPNotFound
143 return PullRequestModel().generate_repo_data(repo)
143 return PullRequestModel().generate_repo_data(repo)
144
144
145 @LoginRequired()
145 @LoginRequired()
146 @NotAnonymous()
146 @NotAnonymous()
147 @XHRRequired()
147 @XHRRequired()
148 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
148 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
149 'repository.admin')
149 'repository.admin')
150 @jsonify
150 @jsonify
151 def get_repo_destinations(self, repo_name):
151 def get_repo_destinations(self, repo_name):
152 repo = Repository.get_by_repo_name(repo_name)
152 repo = Repository.get_by_repo_name(repo_name)
153 if not repo:
153 if not repo:
154 raise HTTPNotFound
154 raise HTTPNotFound
155 filter_query = request.GET.get('query')
155 filter_query = request.GET.get('query')
156
156
157 query = Repository.query() \
157 query = Repository.query() \
158 .order_by(func.length(Repository.repo_name)) \
158 .order_by(func.length(Repository.repo_name)) \
159 .filter(or_(
159 .filter(or_(
160 Repository.repo_name == repo.repo_name,
160 Repository.repo_name == repo.repo_name,
161 Repository.fork_id == repo.repo_id))
161 Repository.fork_id == repo.repo_id))
162
162
163 if filter_query:
163 if filter_query:
164 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
164 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
165 query = query.filter(
165 query = query.filter(
166 Repository.repo_name.ilike(ilike_expression))
166 Repository.repo_name.ilike(ilike_expression))
167
167
168 add_parent = False
168 add_parent = False
169 if repo.parent:
169 if repo.parent:
170 if filter_query in repo.parent.repo_name:
170 if filter_query in repo.parent.repo_name:
171 parent_vcs_obj = repo.parent.scm_instance()
171 parent_vcs_obj = repo.parent.scm_instance()
172 if parent_vcs_obj and not parent_vcs_obj.is_empty():
172 if parent_vcs_obj and not parent_vcs_obj.is_empty():
173 add_parent = True
173 add_parent = True
174
174
175 limit = 20 - 1 if add_parent else 20
175 limit = 20 - 1 if add_parent else 20
176 all_repos = query.limit(limit).all()
176 all_repos = query.limit(limit).all()
177 if add_parent:
177 if add_parent:
178 all_repos += [repo.parent]
178 all_repos += [repo.parent]
179
179
180 repos = []
180 repos = []
181 for obj in self.scm_model.get_repos(all_repos):
181 for obj in self.scm_model.get_repos(all_repos):
182 repos.append({
182 repos.append({
183 'id': obj['name'],
183 'id': obj['name'],
184 'text': obj['name'],
184 'text': obj['name'],
185 'type': 'repo',
185 'type': 'repo',
186 'obj': obj['dbrepo']
186 'obj': obj['dbrepo']
187 })
187 })
188
188
189 data = {
189 data = {
190 'more': False,
190 'more': False,
191 'results': [{
191 'results': [{
192 'text': _('Repositories'),
192 'text': _('Repositories'),
193 'children': repos
193 'children': repos
194 }] if repos else []
194 }] if repos else []
195 }
195 }
196 return data
196 return data
197
197
198 @LoginRequired()
198 @LoginRequired()
199 @NotAnonymous()
199 @NotAnonymous()
200 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
200 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
201 'repository.admin')
201 'repository.admin')
202 @HasAcceptedRepoType('git', 'hg')
202 @HasAcceptedRepoType('git', 'hg')
203 @auth.CSRFRequired()
203 @auth.CSRFRequired()
204 def create(self, repo_name):
204 def create(self, repo_name):
205 repo = Repository.get_by_repo_name(repo_name)
205 repo = Repository.get_by_repo_name(repo_name)
206 if not repo:
206 if not repo:
207 raise HTTPNotFound
207 raise HTTPNotFound
208
208
209 controls = peppercorn.parse(request.POST.items())
209 controls = peppercorn.parse(request.POST.items())
210
210
211 try:
211 try:
212 _form = PullRequestForm(repo.repo_id)().to_python(controls)
212 _form = PullRequestForm(repo.repo_id)().to_python(controls)
213 except formencode.Invalid as errors:
213 except formencode.Invalid as errors:
214 if errors.error_dict.get('revisions'):
214 if errors.error_dict.get('revisions'):
215 msg = 'Revisions: %s' % errors.error_dict['revisions']
215 msg = 'Revisions: %s' % errors.error_dict['revisions']
216 elif errors.error_dict.get('pullrequest_title'):
216 elif errors.error_dict.get('pullrequest_title'):
217 msg = _('Pull request requires a title with min. 3 chars')
217 msg = _('Pull request requires a title with min. 3 chars')
218 else:
218 else:
219 msg = _('Error creating pull request: {}').format(errors)
219 msg = _('Error creating pull request: {}').format(errors)
220 log.exception(msg)
220 log.exception(msg)
221 h.flash(msg, 'error')
221 h.flash(msg, 'error')
222
222
223 # would rather just go back to form ...
223 # would rather just go back to form ...
224 return redirect(url('pullrequest_home', repo_name=repo_name))
224 return redirect(url('pullrequest_home', repo_name=repo_name))
225
225
226 source_repo = _form['source_repo']
226 source_repo = _form['source_repo']
227 source_ref = _form['source_ref']
227 source_ref = _form['source_ref']
228 target_repo = _form['target_repo']
228 target_repo = _form['target_repo']
229 target_ref = _form['target_ref']
229 target_ref = _form['target_ref']
230 commit_ids = _form['revisions'][::-1]
230 commit_ids = _form['revisions'][::-1]
231
231
232 # find the ancestor for this pr
232 # find the ancestor for this pr
233 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
233 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
234 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
234 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
235
235
236 source_scm = source_db_repo.scm_instance()
236 source_scm = source_db_repo.scm_instance()
237 target_scm = target_db_repo.scm_instance()
237 target_scm = target_db_repo.scm_instance()
238
238
239 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
239 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
240 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
240 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
241
241
242 ancestor = source_scm.get_common_ancestor(
242 ancestor = source_scm.get_common_ancestor(
243 source_commit.raw_id, target_commit.raw_id, target_scm)
243 source_commit.raw_id, target_commit.raw_id, target_scm)
244
244
245 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
245 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
246 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
246 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
247
247
248 pullrequest_title = _form['pullrequest_title']
248 pullrequest_title = _form['pullrequest_title']
249 title_source_ref = source_ref.split(':', 2)[1]
249 title_source_ref = source_ref.split(':', 2)[1]
250 if not pullrequest_title:
250 if not pullrequest_title:
251 pullrequest_title = PullRequestModel().generate_pullrequest_title(
251 pullrequest_title = PullRequestModel().generate_pullrequest_title(
252 source=source_repo,
252 source=source_repo,
253 source_ref=title_source_ref,
253 source_ref=title_source_ref,
254 target=target_repo
254 target=target_repo
255 )
255 )
256
256
257 description = _form['pullrequest_desc']
257 description = _form['pullrequest_desc']
258
258
259 get_default_reviewers_data, validate_default_reviewers = \
259 get_default_reviewers_data, validate_default_reviewers = \
260 PullRequestModel().get_reviewer_functions()
260 PullRequestModel().get_reviewer_functions()
261
261
262 # recalculate reviewers logic, to make sure we can validate this
262 # recalculate reviewers logic, to make sure we can validate this
263 reviewer_rules = get_default_reviewers_data(
263 reviewer_rules = get_default_reviewers_data(
264 c.rhodecode_user, source_db_repo, source_commit, target_db_repo,
264 c.rhodecode_user.get_instance(), source_db_repo,
265 target_commit)
265 source_commit, target_db_repo, target_commit)
266
266
267 reviewers = validate_default_reviewers(
267 reviewers = validate_default_reviewers(
268 _form['review_members'], reviewer_rules)
268 _form['review_members'], reviewer_rules)
269
269
270 try:
270 try:
271 pull_request = PullRequestModel().create(
271 pull_request = PullRequestModel().create(
272 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
272 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
273 target_ref, commit_ids, reviewers, pullrequest_title,
273 target_ref, commit_ids, reviewers, pullrequest_title,
274 description, reviewer_rules
274 description, reviewer_rules
275 )
275 )
276 Session().commit()
276 Session().commit()
277 h.flash(_('Successfully opened new pull request'),
277 h.flash(_('Successfully opened new pull request'),
278 category='success')
278 category='success')
279 except Exception as e:
279 except Exception as e:
280 msg = _('Error occurred during creation of this pull request.')
280 msg = _('Error occurred during creation of this pull request.')
281 log.exception(msg)
281 log.exception(msg)
282 h.flash(msg, category='error')
282 h.flash(msg, category='error')
283 return redirect(url('pullrequest_home', repo_name=repo_name))
283 return redirect(url('pullrequest_home', repo_name=repo_name))
284
284
285 return redirect(url('pullrequest_show', repo_name=target_repo,
285 return redirect(url('pullrequest_show', repo_name=target_repo,
286 pull_request_id=pull_request.pull_request_id))
286 pull_request_id=pull_request.pull_request_id))
287
287
288 @LoginRequired()
288 @LoginRequired()
289 @NotAnonymous()
289 @NotAnonymous()
290 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
290 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
291 'repository.admin')
291 'repository.admin')
292 @auth.CSRFRequired()
292 @auth.CSRFRequired()
293 @jsonify
293 @jsonify
294 def update(self, repo_name, pull_request_id):
294 def update(self, repo_name, pull_request_id):
295 pull_request_id = safe_int(pull_request_id)
295 pull_request_id = safe_int(pull_request_id)
296 pull_request = PullRequest.get_or_404(pull_request_id)
296 pull_request = PullRequest.get_or_404(pull_request_id)
297 # only owner or admin can update it
297 # only owner or admin can update it
298 allowed_to_update = PullRequestModel().check_user_update(
298 allowed_to_update = PullRequestModel().check_user_update(
299 pull_request, c.rhodecode_user)
299 pull_request, c.rhodecode_user)
300 if allowed_to_update:
300 if allowed_to_update:
301 controls = peppercorn.parse(request.POST.items())
301 controls = peppercorn.parse(request.POST.items())
302
302
303 if 'review_members' in controls:
303 if 'review_members' in controls:
304 self._update_reviewers(
304 self._update_reviewers(
305 pull_request_id, controls['review_members'],
305 pull_request_id, controls['review_members'],
306 pull_request.reviewer_data)
306 pull_request.reviewer_data)
307 elif str2bool(request.POST.get('update_commits', 'false')):
307 elif str2bool(request.POST.get('update_commits', 'false')):
308 self._update_commits(pull_request)
308 self._update_commits(pull_request)
309 elif str2bool(request.POST.get('close_pull_request', 'false')):
309 elif str2bool(request.POST.get('close_pull_request', 'false')):
310 self._reject_close(pull_request)
310 self._reject_close(pull_request)
311 elif str2bool(request.POST.get('edit_pull_request', 'false')):
311 elif str2bool(request.POST.get('edit_pull_request', 'false')):
312 self._edit_pull_request(pull_request)
312 self._edit_pull_request(pull_request)
313 else:
313 else:
314 raise HTTPBadRequest()
314 raise HTTPBadRequest()
315 return True
315 return True
316 raise HTTPForbidden()
316 raise HTTPForbidden()
317
317
318 def _edit_pull_request(self, pull_request):
318 def _edit_pull_request(self, pull_request):
319 try:
319 try:
320 PullRequestModel().edit(
320 PullRequestModel().edit(
321 pull_request, request.POST.get('title'),
321 pull_request, request.POST.get('title'),
322 request.POST.get('description'))
322 request.POST.get('description'))
323 except ValueError:
323 except ValueError:
324 msg = _(u'Cannot update closed pull requests.')
324 msg = _(u'Cannot update closed pull requests.')
325 h.flash(msg, category='error')
325 h.flash(msg, category='error')
326 return
326 return
327 else:
327 else:
328 Session().commit()
328 Session().commit()
329
329
330 msg = _(u'Pull request title & description updated.')
330 msg = _(u'Pull request title & description updated.')
331 h.flash(msg, category='success')
331 h.flash(msg, category='success')
332 return
332 return
333
333
334 def _update_commits(self, pull_request):
334 def _update_commits(self, pull_request):
335 resp = PullRequestModel().update_commits(pull_request)
335 resp = PullRequestModel().update_commits(pull_request)
336
336
337 if resp.executed:
337 if resp.executed:
338
338
339 if resp.target_changed and resp.source_changed:
339 if resp.target_changed and resp.source_changed:
340 changed = 'target and source repositories'
340 changed = 'target and source repositories'
341 elif resp.target_changed and not resp.source_changed:
341 elif resp.target_changed and not resp.source_changed:
342 changed = 'target repository'
342 changed = 'target repository'
343 elif not resp.target_changed and resp.source_changed:
343 elif not resp.target_changed and resp.source_changed:
344 changed = 'source repository'
344 changed = 'source repository'
345 else:
345 else:
346 changed = 'nothing'
346 changed = 'nothing'
347
347
348 msg = _(
348 msg = _(
349 u'Pull request updated to "{source_commit_id}" with '
349 u'Pull request updated to "{source_commit_id}" with '
350 u'{count_added} added, {count_removed} removed commits. '
350 u'{count_added} added, {count_removed} removed commits. '
351 u'Source of changes: {change_source}')
351 u'Source of changes: {change_source}')
352 msg = msg.format(
352 msg = msg.format(
353 source_commit_id=pull_request.source_ref_parts.commit_id,
353 source_commit_id=pull_request.source_ref_parts.commit_id,
354 count_added=len(resp.changes.added),
354 count_added=len(resp.changes.added),
355 count_removed=len(resp.changes.removed),
355 count_removed=len(resp.changes.removed),
356 change_source=changed)
356 change_source=changed)
357 h.flash(msg, category='success')
357 h.flash(msg, category='success')
358
358
359 registry = get_current_registry()
359 registry = get_current_registry()
360 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
360 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
361 channelstream_config = rhodecode_plugins.get('channelstream', {})
361 channelstream_config = rhodecode_plugins.get('channelstream', {})
362 if channelstream_config.get('enabled'):
362 if channelstream_config.get('enabled'):
363 message = msg + (
363 message = msg + (
364 ' - <a onclick="window.location.reload()">'
364 ' - <a onclick="window.location.reload()">'
365 '<strong>{}</strong></a>'.format(_('Reload page')))
365 '<strong>{}</strong></a>'.format(_('Reload page')))
366 channel = '/repo${}$/pr/{}'.format(
366 channel = '/repo${}$/pr/{}'.format(
367 pull_request.target_repo.repo_name,
367 pull_request.target_repo.repo_name,
368 pull_request.pull_request_id
368 pull_request.pull_request_id
369 )
369 )
370 payload = {
370 payload = {
371 'type': 'message',
371 'type': 'message',
372 'user': 'system',
372 'user': 'system',
373 'exclude_users': [request.user.username],
373 'exclude_users': [request.user.username],
374 'channel': channel,
374 'channel': channel,
375 'message': {
375 'message': {
376 'message': message,
376 'message': message,
377 'level': 'success',
377 'level': 'success',
378 'topic': '/notifications'
378 'topic': '/notifications'
379 }
379 }
380 }
380 }
381 channelstream_request(
381 channelstream_request(
382 channelstream_config, [payload], '/message',
382 channelstream_config, [payload], '/message',
383 raise_exc=False)
383 raise_exc=False)
384 else:
384 else:
385 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
385 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
386 warning_reasons = [
386 warning_reasons = [
387 UpdateFailureReason.NO_CHANGE,
387 UpdateFailureReason.NO_CHANGE,
388 UpdateFailureReason.WRONG_REF_TYPE,
388 UpdateFailureReason.WRONG_REF_TYPE,
389 ]
389 ]
390 category = 'warning' if resp.reason in warning_reasons else 'error'
390 category = 'warning' if resp.reason in warning_reasons else 'error'
391 h.flash(msg, category=category)
391 h.flash(msg, category=category)
392
392
393 @auth.CSRFRequired()
393 @auth.CSRFRequired()
394 @LoginRequired()
394 @LoginRequired()
395 @NotAnonymous()
395 @NotAnonymous()
396 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
396 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
397 'repository.admin')
397 'repository.admin')
398 def merge(self, repo_name, pull_request_id):
398 def merge(self, repo_name, pull_request_id):
399 """
399 """
400 POST /{repo_name}/pull-request/{pull_request_id}
400 POST /{repo_name}/pull-request/{pull_request_id}
401
401
402 Merge will perform a server-side merge of the specified
402 Merge will perform a server-side merge of the specified
403 pull request, if the pull request is approved and mergeable.
403 pull request, if the pull request is approved and mergeable.
404 After successful merging, the pull request is automatically
404 After successful merging, the pull request is automatically
405 closed, with a relevant comment.
405 closed, with a relevant comment.
406 """
406 """
407 pull_request_id = safe_int(pull_request_id)
407 pull_request_id = safe_int(pull_request_id)
408 pull_request = PullRequest.get_or_404(pull_request_id)
408 pull_request = PullRequest.get_or_404(pull_request_id)
409 user = c.rhodecode_user
409 user = c.rhodecode_user
410
410
411 check = MergeCheck.validate(pull_request, user)
411 check = MergeCheck.validate(pull_request, user)
412 merge_possible = not check.failed
412 merge_possible = not check.failed
413
413
414 for err_type, error_msg in check.errors:
414 for err_type, error_msg in check.errors:
415 h.flash(error_msg, category=err_type)
415 h.flash(error_msg, category=err_type)
416
416
417 if merge_possible:
417 if merge_possible:
418 log.debug("Pre-conditions checked, trying to merge.")
418 log.debug("Pre-conditions checked, trying to merge.")
419 extras = vcs_operation_context(
419 extras = vcs_operation_context(
420 request.environ, repo_name=pull_request.target_repo.repo_name,
420 request.environ, repo_name=pull_request.target_repo.repo_name,
421 username=user.username, action='push',
421 username=user.username, action='push',
422 scm=pull_request.target_repo.repo_type)
422 scm=pull_request.target_repo.repo_type)
423 self._merge_pull_request(pull_request, user, extras)
423 self._merge_pull_request(pull_request, user, extras)
424
424
425 return redirect(url(
425 return redirect(url(
426 'pullrequest_show',
426 'pullrequest_show',
427 repo_name=pull_request.target_repo.repo_name,
427 repo_name=pull_request.target_repo.repo_name,
428 pull_request_id=pull_request.pull_request_id))
428 pull_request_id=pull_request.pull_request_id))
429
429
430 def _merge_pull_request(self, pull_request, user, extras):
430 def _merge_pull_request(self, pull_request, user, extras):
431 merge_resp = PullRequestModel().merge(
431 merge_resp = PullRequestModel().merge(
432 pull_request, user, extras=extras)
432 pull_request, user, extras=extras)
433
433
434 if merge_resp.executed:
434 if merge_resp.executed:
435 log.debug("The merge was successful, closing the pull request.")
435 log.debug("The merge was successful, closing the pull request.")
436 PullRequestModel().close_pull_request(
436 PullRequestModel().close_pull_request(
437 pull_request.pull_request_id, user)
437 pull_request.pull_request_id, user)
438 Session().commit()
438 Session().commit()
439 msg = _('Pull request was successfully merged and closed.')
439 msg = _('Pull request was successfully merged and closed.')
440 h.flash(msg, category='success')
440 h.flash(msg, category='success')
441 else:
441 else:
442 log.debug(
442 log.debug(
443 "The merge was not successful. Merge response: %s",
443 "The merge was not successful. Merge response: %s",
444 merge_resp)
444 merge_resp)
445 msg = PullRequestModel().merge_status_message(
445 msg = PullRequestModel().merge_status_message(
446 merge_resp.failure_reason)
446 merge_resp.failure_reason)
447 h.flash(msg, category='error')
447 h.flash(msg, category='error')
448
448
449 def _update_reviewers(self, pull_request_id, review_members, reviewer_rules):
449 def _update_reviewers(self, pull_request_id, review_members, reviewer_rules):
450
450
451 get_default_reviewers_data, validate_default_reviewers = \
451 get_default_reviewers_data, validate_default_reviewers = \
452 PullRequestModel().get_reviewer_functions()
452 PullRequestModel().get_reviewer_functions()
453
453
454 try:
454 try:
455 reviewers = validate_default_reviewers(review_members, reviewer_rules)
455 reviewers = validate_default_reviewers(review_members, reviewer_rules)
456 except ValueError as e:
456 except ValueError as e:
457 log.error('Reviewers Validation:{}'.format(e))
457 log.error('Reviewers Validation:{}'.format(e))
458 h.flash(e, category='error')
458 h.flash(e, category='error')
459 return
459 return
460
460
461 PullRequestModel().update_reviewers(pull_request_id, reviewers)
461 PullRequestModel().update_reviewers(pull_request_id, reviewers)
462 h.flash(_('Pull request reviewers updated.'), category='success')
462 h.flash(_('Pull request reviewers updated.'), category='success')
463 Session().commit()
463 Session().commit()
464
464
465 def _reject_close(self, pull_request):
465 def _reject_close(self, pull_request):
466 if pull_request.is_closed():
466 if pull_request.is_closed():
467 raise HTTPForbidden()
467 raise HTTPForbidden()
468
468
469 PullRequestModel().close_pull_request_with_comment(
469 PullRequestModel().close_pull_request_with_comment(
470 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
470 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
471 Session().commit()
471 Session().commit()
472
472
473 @LoginRequired()
473 @LoginRequired()
474 @NotAnonymous()
474 @NotAnonymous()
475 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
475 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
476 'repository.admin')
476 'repository.admin')
477 @auth.CSRFRequired()
477 @auth.CSRFRequired()
478 @jsonify
478 @jsonify
479 def delete(self, repo_name, pull_request_id):
479 def delete(self, repo_name, pull_request_id):
480 pull_request_id = safe_int(pull_request_id)
480 pull_request_id = safe_int(pull_request_id)
481 pull_request = PullRequest.get_or_404(pull_request_id)
481 pull_request = PullRequest.get_or_404(pull_request_id)
482
482
483 pr_closed = pull_request.is_closed()
483 pr_closed = pull_request.is_closed()
484 allowed_to_delete = PullRequestModel().check_user_delete(
484 allowed_to_delete = PullRequestModel().check_user_delete(
485 pull_request, c.rhodecode_user) and not pr_closed
485 pull_request, c.rhodecode_user) and not pr_closed
486
486
487 # only owner can delete it !
487 # only owner can delete it !
488 if allowed_to_delete:
488 if allowed_to_delete:
489 PullRequestModel().delete(pull_request)
489 PullRequestModel().delete(pull_request)
490 Session().commit()
490 Session().commit()
491 h.flash(_('Successfully deleted pull request'),
491 h.flash(_('Successfully deleted pull request'),
492 category='success')
492 category='success')
493 return redirect(url('my_account_pullrequests'))
493 return redirect(url('my_account_pullrequests'))
494
494
495 h.flash(_('Your are not allowed to delete this pull request'),
495 h.flash(_('Your are not allowed to delete this pull request'),
496 category='error')
496 category='error')
497 raise HTTPForbidden()
497 raise HTTPForbidden()
498
498
499 def _get_pr_version(self, pull_request_id, version=None):
499 def _get_pr_version(self, pull_request_id, version=None):
500 pull_request_id = safe_int(pull_request_id)
500 pull_request_id = safe_int(pull_request_id)
501 at_version = None
501 at_version = None
502
502
503 if version and version == 'latest':
503 if version and version == 'latest':
504 pull_request_ver = PullRequest.get(pull_request_id)
504 pull_request_ver = PullRequest.get(pull_request_id)
505 pull_request_obj = pull_request_ver
505 pull_request_obj = pull_request_ver
506 _org_pull_request_obj = pull_request_obj
506 _org_pull_request_obj = pull_request_obj
507 at_version = 'latest'
507 at_version = 'latest'
508 elif version:
508 elif version:
509 pull_request_ver = PullRequestVersion.get_or_404(version)
509 pull_request_ver = PullRequestVersion.get_or_404(version)
510 pull_request_obj = pull_request_ver
510 pull_request_obj = pull_request_ver
511 _org_pull_request_obj = pull_request_ver.pull_request
511 _org_pull_request_obj = pull_request_ver.pull_request
512 at_version = pull_request_ver.pull_request_version_id
512 at_version = pull_request_ver.pull_request_version_id
513 else:
513 else:
514 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
514 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
515 pull_request_id)
515 pull_request_id)
516
516
517 pull_request_display_obj = PullRequest.get_pr_display_object(
517 pull_request_display_obj = PullRequest.get_pr_display_object(
518 pull_request_obj, _org_pull_request_obj)
518 pull_request_obj, _org_pull_request_obj)
519
519
520 return _org_pull_request_obj, pull_request_obj, \
520 return _org_pull_request_obj, pull_request_obj, \
521 pull_request_display_obj, at_version
521 pull_request_display_obj, at_version
522
522
523 def _get_diffset(
523 def _get_diffset(
524 self, source_repo, source_ref_id, target_ref_id, target_commit,
524 self, source_repo, source_ref_id, target_ref_id, target_commit,
525 source_commit, diff_limit, file_limit, display_inline_comments):
525 source_commit, diff_limit, file_limit, display_inline_comments):
526 vcs_diff = PullRequestModel().get_diff(
526 vcs_diff = PullRequestModel().get_diff(
527 source_repo, source_ref_id, target_ref_id)
527 source_repo, source_ref_id, target_ref_id)
528
528
529 diff_processor = diffs.DiffProcessor(
529 diff_processor = diffs.DiffProcessor(
530 vcs_diff, format='newdiff', diff_limit=diff_limit,
530 vcs_diff, format='newdiff', diff_limit=diff_limit,
531 file_limit=file_limit, show_full_diff=c.fulldiff)
531 file_limit=file_limit, show_full_diff=c.fulldiff)
532
532
533 _parsed = diff_processor.prepare()
533 _parsed = diff_processor.prepare()
534
534
535 def _node_getter(commit):
535 def _node_getter(commit):
536 def get_node(fname):
536 def get_node(fname):
537 try:
537 try:
538 return commit.get_node(fname)
538 return commit.get_node(fname)
539 except NodeDoesNotExistError:
539 except NodeDoesNotExistError:
540 return None
540 return None
541
541
542 return get_node
542 return get_node
543
543
544 diffset = codeblocks.DiffSet(
544 diffset = codeblocks.DiffSet(
545 repo_name=c.repo_name,
545 repo_name=c.repo_name,
546 source_repo_name=c.source_repo.repo_name,
546 source_repo_name=c.source_repo.repo_name,
547 source_node_getter=_node_getter(target_commit),
547 source_node_getter=_node_getter(target_commit),
548 target_node_getter=_node_getter(source_commit),
548 target_node_getter=_node_getter(source_commit),
549 comments=display_inline_comments
549 comments=display_inline_comments
550 )
550 )
551 diffset = diffset.render_patchset(
551 diffset = diffset.render_patchset(
552 _parsed, target_commit.raw_id, source_commit.raw_id)
552 _parsed, target_commit.raw_id, source_commit.raw_id)
553
553
554 return diffset
554 return diffset
555
555
556 @LoginRequired()
556 @LoginRequired()
557 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
557 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
558 'repository.admin')
558 'repository.admin')
559 def show(self, repo_name, pull_request_id):
559 def show(self, repo_name, pull_request_id):
560 pull_request_id = safe_int(pull_request_id)
560 pull_request_id = safe_int(pull_request_id)
561 version = request.GET.get('version')
561 version = request.GET.get('version')
562 from_version = request.GET.get('from_version') or version
562 from_version = request.GET.get('from_version') or version
563 merge_checks = request.GET.get('merge_checks')
563 merge_checks = request.GET.get('merge_checks')
564 c.fulldiff = str2bool(request.GET.get('fulldiff'))
564 c.fulldiff = str2bool(request.GET.get('fulldiff'))
565
565
566 (pull_request_latest,
566 (pull_request_latest,
567 pull_request_at_ver,
567 pull_request_at_ver,
568 pull_request_display_obj,
568 pull_request_display_obj,
569 at_version) = self._get_pr_version(
569 at_version) = self._get_pr_version(
570 pull_request_id, version=version)
570 pull_request_id, version=version)
571 pr_closed = pull_request_latest.is_closed()
571 pr_closed = pull_request_latest.is_closed()
572
572
573 if pr_closed and (version or from_version):
573 if pr_closed and (version or from_version):
574 # not allow to browse versions
574 # not allow to browse versions
575 return redirect(h.url('pullrequest_show', repo_name=repo_name,
575 return redirect(h.url('pullrequest_show', repo_name=repo_name,
576 pull_request_id=pull_request_id))
576 pull_request_id=pull_request_id))
577
577
578 versions = pull_request_display_obj.versions()
578 versions = pull_request_display_obj.versions()
579
579
580 c.at_version = at_version
580 c.at_version = at_version
581 c.at_version_num = (at_version
581 c.at_version_num = (at_version
582 if at_version and at_version != 'latest'
582 if at_version and at_version != 'latest'
583 else None)
583 else None)
584 c.at_version_pos = ChangesetComment.get_index_from_version(
584 c.at_version_pos = ChangesetComment.get_index_from_version(
585 c.at_version_num, versions)
585 c.at_version_num, versions)
586
586
587 (prev_pull_request_latest,
587 (prev_pull_request_latest,
588 prev_pull_request_at_ver,
588 prev_pull_request_at_ver,
589 prev_pull_request_display_obj,
589 prev_pull_request_display_obj,
590 prev_at_version) = self._get_pr_version(
590 prev_at_version) = self._get_pr_version(
591 pull_request_id, version=from_version)
591 pull_request_id, version=from_version)
592
592
593 c.from_version = prev_at_version
593 c.from_version = prev_at_version
594 c.from_version_num = (prev_at_version
594 c.from_version_num = (prev_at_version
595 if prev_at_version and prev_at_version != 'latest'
595 if prev_at_version and prev_at_version != 'latest'
596 else None)
596 else None)
597 c.from_version_pos = ChangesetComment.get_index_from_version(
597 c.from_version_pos = ChangesetComment.get_index_from_version(
598 c.from_version_num, versions)
598 c.from_version_num, versions)
599
599
600 # define if we're in COMPARE mode or VIEW at version mode
600 # define if we're in COMPARE mode or VIEW at version mode
601 compare = at_version != prev_at_version
601 compare = at_version != prev_at_version
602
602
603 # pull_requests repo_name we opened it against
603 # pull_requests repo_name we opened it against
604 # ie. target_repo must match
604 # ie. target_repo must match
605 if repo_name != pull_request_at_ver.target_repo.repo_name:
605 if repo_name != pull_request_at_ver.target_repo.repo_name:
606 raise HTTPNotFound
606 raise HTTPNotFound
607
607
608 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
608 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
609 pull_request_at_ver)
609 pull_request_at_ver)
610
610
611 c.pull_request = pull_request_display_obj
611 c.pull_request = pull_request_display_obj
612 c.pull_request_latest = pull_request_latest
612 c.pull_request_latest = pull_request_latest
613
613
614 if compare or (at_version and not at_version == 'latest'):
614 if compare or (at_version and not at_version == 'latest'):
615 c.allowed_to_change_status = False
615 c.allowed_to_change_status = False
616 c.allowed_to_update = False
616 c.allowed_to_update = False
617 c.allowed_to_merge = False
617 c.allowed_to_merge = False
618 c.allowed_to_delete = False
618 c.allowed_to_delete = False
619 c.allowed_to_comment = False
619 c.allowed_to_comment = False
620 c.allowed_to_close = False
620 c.allowed_to_close = False
621 else:
621 else:
622 can_change_status = PullRequestModel().check_user_change_status(
622 can_change_status = PullRequestModel().check_user_change_status(
623 pull_request_at_ver, c.rhodecode_user)
623 pull_request_at_ver, c.rhodecode_user)
624 c.allowed_to_change_status = can_change_status and not pr_closed
624 c.allowed_to_change_status = can_change_status and not pr_closed
625
625
626 c.allowed_to_update = PullRequestModel().check_user_update(
626 c.allowed_to_update = PullRequestModel().check_user_update(
627 pull_request_latest, c.rhodecode_user) and not pr_closed
627 pull_request_latest, c.rhodecode_user) and not pr_closed
628 c.allowed_to_merge = PullRequestModel().check_user_merge(
628 c.allowed_to_merge = PullRequestModel().check_user_merge(
629 pull_request_latest, c.rhodecode_user) and not pr_closed
629 pull_request_latest, c.rhodecode_user) and not pr_closed
630 c.allowed_to_delete = PullRequestModel().check_user_delete(
630 c.allowed_to_delete = PullRequestModel().check_user_delete(
631 pull_request_latest, c.rhodecode_user) and not pr_closed
631 pull_request_latest, c.rhodecode_user) and not pr_closed
632 c.allowed_to_comment = not pr_closed
632 c.allowed_to_comment = not pr_closed
633 c.allowed_to_close = c.allowed_to_merge and not pr_closed
633 c.allowed_to_close = c.allowed_to_merge and not pr_closed
634
634
635 c.forbid_adding_reviewers = False
635 c.forbid_adding_reviewers = False
636 c.forbid_author_to_review = False
636 c.forbid_author_to_review = False
637 c.forbid_commit_author_to_review = False
637
638
638 if pull_request_latest.reviewer_data and \
639 if pull_request_latest.reviewer_data and \
639 'rules' in pull_request_latest.reviewer_data:
640 'rules' in pull_request_latest.reviewer_data:
640 rules = pull_request_latest.reviewer_data['rules'] or {}
641 rules = pull_request_latest.reviewer_data['rules'] or {}
641 try:
642 try:
642 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
643 c.forbid_adding_reviewers = rules.get(
643 c.forbid_author_to_review = rules.get('forbid_author_to_review')
644 'forbid_adding_reviewers')
645 c.forbid_author_to_review = rules.get(
646 'forbid_author_to_review')
647 c.forbid_commit_author_to_review = rules.get(
648 'forbid_commit_author_to_review')
644 except Exception:
649 except Exception:
645 pass
650 pass
646
651
647 # check merge capabilities
652 # check merge capabilities
648 _merge_check = MergeCheck.validate(
653 _merge_check = MergeCheck.validate(
649 pull_request_latest, user=c.rhodecode_user)
654 pull_request_latest, user=c.rhodecode_user)
650 c.pr_merge_errors = _merge_check.error_details
655 c.pr_merge_errors = _merge_check.error_details
651 c.pr_merge_possible = not _merge_check.failed
656 c.pr_merge_possible = not _merge_check.failed
652 c.pr_merge_message = _merge_check.merge_msg
657 c.pr_merge_message = _merge_check.merge_msg
653
658
654 c.pull_request_review_status = _merge_check.review_status
659 c.pull_request_review_status = _merge_check.review_status
655 if merge_checks:
660 if merge_checks:
656 return render('/pullrequests/pullrequest_merge_checks.mako')
661 return render('/pullrequests/pullrequest_merge_checks.mako')
657
662
658 comments_model = CommentsModel()
663 comments_model = CommentsModel()
659
664
660 # reviewers and statuses
665 # reviewers and statuses
661 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
666 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
662 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
667 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
663
668
664 # GENERAL COMMENTS with versions #
669 # GENERAL COMMENTS with versions #
665 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
670 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
666 q = q.order_by(ChangesetComment.comment_id.asc())
671 q = q.order_by(ChangesetComment.comment_id.asc())
667 general_comments = q
672 general_comments = q
668
673
669 # pick comments we want to render at current version
674 # pick comments we want to render at current version
670 c.comment_versions = comments_model.aggregate_comments(
675 c.comment_versions = comments_model.aggregate_comments(
671 general_comments, versions, c.at_version_num)
676 general_comments, versions, c.at_version_num)
672 c.comments = c.comment_versions[c.at_version_num]['until']
677 c.comments = c.comment_versions[c.at_version_num]['until']
673
678
674 # INLINE COMMENTS with versions #
679 # INLINE COMMENTS with versions #
675 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
680 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
676 q = q.order_by(ChangesetComment.comment_id.asc())
681 q = q.order_by(ChangesetComment.comment_id.asc())
677 inline_comments = q
682 inline_comments = q
678
683
679 c.inline_versions = comments_model.aggregate_comments(
684 c.inline_versions = comments_model.aggregate_comments(
680 inline_comments, versions, c.at_version_num, inline=True)
685 inline_comments, versions, c.at_version_num, inline=True)
681
686
682 # inject latest version
687 # inject latest version
683 latest_ver = PullRequest.get_pr_display_object(
688 latest_ver = PullRequest.get_pr_display_object(
684 pull_request_latest, pull_request_latest)
689 pull_request_latest, pull_request_latest)
685
690
686 c.versions = versions + [latest_ver]
691 c.versions = versions + [latest_ver]
687
692
688 # if we use version, then do not show later comments
693 # if we use version, then do not show later comments
689 # than current version
694 # than current version
690 display_inline_comments = collections.defaultdict(
695 display_inline_comments = collections.defaultdict(
691 lambda: collections.defaultdict(list))
696 lambda: collections.defaultdict(list))
692 for co in inline_comments:
697 for co in inline_comments:
693 if c.at_version_num:
698 if c.at_version_num:
694 # pick comments that are at least UPTO given version, so we
699 # pick comments that are at least UPTO given version, so we
695 # don't render comments for higher version
700 # don't render comments for higher version
696 should_render = co.pull_request_version_id and \
701 should_render = co.pull_request_version_id and \
697 co.pull_request_version_id <= c.at_version_num
702 co.pull_request_version_id <= c.at_version_num
698 else:
703 else:
699 # showing all, for 'latest'
704 # showing all, for 'latest'
700 should_render = True
705 should_render = True
701
706
702 if should_render:
707 if should_render:
703 display_inline_comments[co.f_path][co.line_no].append(co)
708 display_inline_comments[co.f_path][co.line_no].append(co)
704
709
705 # load diff data into template context, if we use compare mode then
710 # load diff data into template context, if we use compare mode then
706 # diff is calculated based on changes between versions of PR
711 # diff is calculated based on changes between versions of PR
707
712
708 source_repo = pull_request_at_ver.source_repo
713 source_repo = pull_request_at_ver.source_repo
709 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
714 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
710
715
711 target_repo = pull_request_at_ver.target_repo
716 target_repo = pull_request_at_ver.target_repo
712 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
717 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
713
718
714 if compare:
719 if compare:
715 # in compare switch the diff base to latest commit from prev version
720 # in compare switch the diff base to latest commit from prev version
716 target_ref_id = prev_pull_request_display_obj.revisions[0]
721 target_ref_id = prev_pull_request_display_obj.revisions[0]
717
722
718 # despite opening commits for bookmarks/branches/tags, we always
723 # despite opening commits for bookmarks/branches/tags, we always
719 # convert this to rev to prevent changes after bookmark or branch change
724 # convert this to rev to prevent changes after bookmark or branch change
720 c.source_ref_type = 'rev'
725 c.source_ref_type = 'rev'
721 c.source_ref = source_ref_id
726 c.source_ref = source_ref_id
722
727
723 c.target_ref_type = 'rev'
728 c.target_ref_type = 'rev'
724 c.target_ref = target_ref_id
729 c.target_ref = target_ref_id
725
730
726 c.source_repo = source_repo
731 c.source_repo = source_repo
727 c.target_repo = target_repo
732 c.target_repo = target_repo
728
733
729 # diff_limit is the old behavior, will cut off the whole diff
734 # diff_limit is the old behavior, will cut off the whole diff
730 # if the limit is applied otherwise will just hide the
735 # if the limit is applied otherwise will just hide the
731 # big files from the front-end
736 # big files from the front-end
732 diff_limit = self.cut_off_limit_diff
737 diff_limit = self.cut_off_limit_diff
733 file_limit = self.cut_off_limit_file
738 file_limit = self.cut_off_limit_file
734
739
735 c.commit_ranges = []
740 c.commit_ranges = []
736 source_commit = EmptyCommit()
741 source_commit = EmptyCommit()
737 target_commit = EmptyCommit()
742 target_commit = EmptyCommit()
738 c.missing_requirements = False
743 c.missing_requirements = False
739
744
740 source_scm = source_repo.scm_instance()
745 source_scm = source_repo.scm_instance()
741 target_scm = target_repo.scm_instance()
746 target_scm = target_repo.scm_instance()
742
747
743 # try first shadow repo, fallback to regular repo
748 # try first shadow repo, fallback to regular repo
744 try:
749 try:
745 commits_source_repo = pull_request_latest.get_shadow_repo()
750 commits_source_repo = pull_request_latest.get_shadow_repo()
746 except Exception:
751 except Exception:
747 log.debug('Failed to get shadow repo', exc_info=True)
752 log.debug('Failed to get shadow repo', exc_info=True)
748 commits_source_repo = source_scm
753 commits_source_repo = source_scm
749
754
750 c.commits_source_repo = commits_source_repo
755 c.commits_source_repo = commits_source_repo
751 commit_cache = {}
756 commit_cache = {}
752 try:
757 try:
753 pre_load = ["author", "branch", "date", "message"]
758 pre_load = ["author", "branch", "date", "message"]
754 show_revs = pull_request_at_ver.revisions
759 show_revs = pull_request_at_ver.revisions
755 for rev in show_revs:
760 for rev in show_revs:
756 comm = commits_source_repo.get_commit(
761 comm = commits_source_repo.get_commit(
757 commit_id=rev, pre_load=pre_load)
762 commit_id=rev, pre_load=pre_load)
758 c.commit_ranges.append(comm)
763 c.commit_ranges.append(comm)
759 commit_cache[comm.raw_id] = comm
764 commit_cache[comm.raw_id] = comm
760
765
761 # Order here matters, we first need to get target, and then
766 # Order here matters, we first need to get target, and then
762 # the source
767 # the source
763 target_commit = commits_source_repo.get_commit(
768 target_commit = commits_source_repo.get_commit(
764 commit_id=safe_str(target_ref_id))
769 commit_id=safe_str(target_ref_id))
765
770
766 source_commit = commits_source_repo.get_commit(
771 source_commit = commits_source_repo.get_commit(
767 commit_id=safe_str(source_ref_id))
772 commit_id=safe_str(source_ref_id))
768
773
769 except CommitDoesNotExistError:
774 except CommitDoesNotExistError:
770 log.warning(
775 log.warning(
771 'Failed to get commit from `{}` repo'.format(
776 'Failed to get commit from `{}` repo'.format(
772 commits_source_repo), exc_info=True)
777 commits_source_repo), exc_info=True)
773 except RepositoryRequirementError:
778 except RepositoryRequirementError:
774 log.warning(
779 log.warning(
775 'Failed to get all required data from repo', exc_info=True)
780 'Failed to get all required data from repo', exc_info=True)
776 c.missing_requirements = True
781 c.missing_requirements = True
777
782
778 c.ancestor = None # set it to None, to hide it from PR view
783 c.ancestor = None # set it to None, to hide it from PR view
779
784
780 try:
785 try:
781 ancestor_id = source_scm.get_common_ancestor(
786 ancestor_id = source_scm.get_common_ancestor(
782 source_commit.raw_id, target_commit.raw_id, target_scm)
787 source_commit.raw_id, target_commit.raw_id, target_scm)
783 c.ancestor_commit = source_scm.get_commit(ancestor_id)
788 c.ancestor_commit = source_scm.get_commit(ancestor_id)
784 except Exception:
789 except Exception:
785 c.ancestor_commit = None
790 c.ancestor_commit = None
786
791
787 c.statuses = source_repo.statuses(
792 c.statuses = source_repo.statuses(
788 [x.raw_id for x in c.commit_ranges])
793 [x.raw_id for x in c.commit_ranges])
789
794
790 # auto collapse if we have more than limit
795 # auto collapse if we have more than limit
791 collapse_limit = diffs.DiffProcessor._collapse_commits_over
796 collapse_limit = diffs.DiffProcessor._collapse_commits_over
792 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
797 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
793 c.compare_mode = compare
798 c.compare_mode = compare
794
799
795 c.missing_commits = False
800 c.missing_commits = False
796 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
801 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
797 or source_commit == target_commit):
802 or source_commit == target_commit):
798
803
799 c.missing_commits = True
804 c.missing_commits = True
800 else:
805 else:
801
806
802 c.diffset = self._get_diffset(
807 c.diffset = self._get_diffset(
803 commits_source_repo, source_ref_id, target_ref_id,
808 commits_source_repo, source_ref_id, target_ref_id,
804 target_commit, source_commit,
809 target_commit, source_commit,
805 diff_limit, file_limit, display_inline_comments)
810 diff_limit, file_limit, display_inline_comments)
806
811
807 c.limited_diff = c.diffset.limited_diff
812 c.limited_diff = c.diffset.limited_diff
808
813
809 # calculate removed files that are bound to comments
814 # calculate removed files that are bound to comments
810 comment_deleted_files = [
815 comment_deleted_files = [
811 fname for fname in display_inline_comments
816 fname for fname in display_inline_comments
812 if fname not in c.diffset.file_stats]
817 if fname not in c.diffset.file_stats]
813
818
814 c.deleted_files_comments = collections.defaultdict(dict)
819 c.deleted_files_comments = collections.defaultdict(dict)
815 for fname, per_line_comments in display_inline_comments.items():
820 for fname, per_line_comments in display_inline_comments.items():
816 if fname in comment_deleted_files:
821 if fname in comment_deleted_files:
817 c.deleted_files_comments[fname]['stats'] = 0
822 c.deleted_files_comments[fname]['stats'] = 0
818 c.deleted_files_comments[fname]['comments'] = list()
823 c.deleted_files_comments[fname]['comments'] = list()
819 for lno, comments in per_line_comments.items():
824 for lno, comments in per_line_comments.items():
820 c.deleted_files_comments[fname]['comments'].extend(
825 c.deleted_files_comments[fname]['comments'].extend(
821 comments)
826 comments)
822
827
823 # this is a hack to properly display links, when creating PR, the
828 # this is a hack to properly display links, when creating PR, the
824 # compare view and others uses different notation, and
829 # compare view and others uses different notation, and
825 # compare_commits.mako renders links based on the target_repo.
830 # compare_commits.mako renders links based on the target_repo.
826 # We need to swap that here to generate it properly on the html side
831 # We need to swap that here to generate it properly on the html side
827 c.target_repo = c.source_repo
832 c.target_repo = c.source_repo
828
833
829 c.commit_statuses = ChangesetStatus.STATUSES
834 c.commit_statuses = ChangesetStatus.STATUSES
830
835
831 c.show_version_changes = not pr_closed
836 c.show_version_changes = not pr_closed
832 if c.show_version_changes:
837 if c.show_version_changes:
833 cur_obj = pull_request_at_ver
838 cur_obj = pull_request_at_ver
834 prev_obj = prev_pull_request_at_ver
839 prev_obj = prev_pull_request_at_ver
835
840
836 old_commit_ids = prev_obj.revisions
841 old_commit_ids = prev_obj.revisions
837 new_commit_ids = cur_obj.revisions
842 new_commit_ids = cur_obj.revisions
838 commit_changes = PullRequestModel()._calculate_commit_id_changes(
843 commit_changes = PullRequestModel()._calculate_commit_id_changes(
839 old_commit_ids, new_commit_ids)
844 old_commit_ids, new_commit_ids)
840 c.commit_changes_summary = commit_changes
845 c.commit_changes_summary = commit_changes
841
846
842 # calculate the diff for commits between versions
847 # calculate the diff for commits between versions
843 c.commit_changes = []
848 c.commit_changes = []
844 mark = lambda cs, fw: list(
849 mark = lambda cs, fw: list(
845 h.itertools.izip_longest([], cs, fillvalue=fw))
850 h.itertools.izip_longest([], cs, fillvalue=fw))
846 for c_type, raw_id in mark(commit_changes.added, 'a') \
851 for c_type, raw_id in mark(commit_changes.added, 'a') \
847 + mark(commit_changes.removed, 'r') \
852 + mark(commit_changes.removed, 'r') \
848 + mark(commit_changes.common, 'c'):
853 + mark(commit_changes.common, 'c'):
849
854
850 if raw_id in commit_cache:
855 if raw_id in commit_cache:
851 commit = commit_cache[raw_id]
856 commit = commit_cache[raw_id]
852 else:
857 else:
853 try:
858 try:
854 commit = commits_source_repo.get_commit(raw_id)
859 commit = commits_source_repo.get_commit(raw_id)
855 except CommitDoesNotExistError:
860 except CommitDoesNotExistError:
856 # in case we fail extracting still use "dummy" commit
861 # in case we fail extracting still use "dummy" commit
857 # for display in commit diff
862 # for display in commit diff
858 commit = h.AttributeDict(
863 commit = h.AttributeDict(
859 {'raw_id': raw_id,
864 {'raw_id': raw_id,
860 'message': 'EMPTY or MISSING COMMIT'})
865 'message': 'EMPTY or MISSING COMMIT'})
861 c.commit_changes.append([c_type, commit])
866 c.commit_changes.append([c_type, commit])
862
867
863 # current user review statuses for each version
868 # current user review statuses for each version
864 c.review_versions = {}
869 c.review_versions = {}
865 if c.rhodecode_user.user_id in allowed_reviewers:
870 if c.rhodecode_user.user_id in allowed_reviewers:
866 for co in general_comments:
871 for co in general_comments:
867 if co.author.user_id == c.rhodecode_user.user_id:
872 if co.author.user_id == c.rhodecode_user.user_id:
868 # each comment has a status change
873 # each comment has a status change
869 status = co.status_change
874 status = co.status_change
870 if status:
875 if status:
871 _ver_pr = status[0].comment.pull_request_version_id
876 _ver_pr = status[0].comment.pull_request_version_id
872 c.review_versions[_ver_pr] = status[0]
877 c.review_versions[_ver_pr] = status[0]
873
878
874 return render('/pullrequests/pullrequest_show.mako')
879 return render('/pullrequests/pullrequest_show.mako')
875
880
876 @LoginRequired()
881 @LoginRequired()
877 @NotAnonymous()
882 @NotAnonymous()
878 @HasRepoPermissionAnyDecorator(
883 @HasRepoPermissionAnyDecorator(
879 'repository.read', 'repository.write', 'repository.admin')
884 'repository.read', 'repository.write', 'repository.admin')
880 @auth.CSRFRequired()
885 @auth.CSRFRequired()
881 @jsonify
886 @jsonify
882 def comment(self, repo_name, pull_request_id):
887 def comment(self, repo_name, pull_request_id):
883 pull_request_id = safe_int(pull_request_id)
888 pull_request_id = safe_int(pull_request_id)
884 pull_request = PullRequest.get_or_404(pull_request_id)
889 pull_request = PullRequest.get_or_404(pull_request_id)
885 if pull_request.is_closed():
890 if pull_request.is_closed():
886 raise HTTPForbidden()
891 raise HTTPForbidden()
887
892
888 status = request.POST.get('changeset_status', None)
893 status = request.POST.get('changeset_status', None)
889 text = request.POST.get('text')
894 text = request.POST.get('text')
890 comment_type = request.POST.get('comment_type')
895 comment_type = request.POST.get('comment_type')
891 resolves_comment_id = request.POST.get('resolves_comment_id', None)
896 resolves_comment_id = request.POST.get('resolves_comment_id', None)
892 close_pull_request = request.POST.get('close_pull_request')
897 close_pull_request = request.POST.get('close_pull_request')
893
898
894 close_pr = False
899 close_pr = False
895 # only owner or admin or person with write permissions
900 # only owner or admin or person with write permissions
896 allowed_to_close = PullRequestModel().check_user_update(
901 allowed_to_close = PullRequestModel().check_user_update(
897 pull_request, c.rhodecode_user)
902 pull_request, c.rhodecode_user)
898
903
899 if close_pull_request and allowed_to_close:
904 if close_pull_request and allowed_to_close:
900 close_pr = True
905 close_pr = True
901 pull_request_review_status = pull_request.calculated_review_status()
906 pull_request_review_status = pull_request.calculated_review_status()
902 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
907 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
903 # approved only if we have voting consent
908 # approved only if we have voting consent
904 status = ChangesetStatus.STATUS_APPROVED
909 status = ChangesetStatus.STATUS_APPROVED
905 else:
910 else:
906 status = ChangesetStatus.STATUS_REJECTED
911 status = ChangesetStatus.STATUS_REJECTED
907
912
908 allowed_to_change_status = PullRequestModel().check_user_change_status(
913 allowed_to_change_status = PullRequestModel().check_user_change_status(
909 pull_request, c.rhodecode_user)
914 pull_request, c.rhodecode_user)
910
915
911 if status and allowed_to_change_status:
916 if status and allowed_to_change_status:
912 message = (_('Status change %(transition_icon)s %(status)s')
917 message = (_('Status change %(transition_icon)s %(status)s')
913 % {'transition_icon': '>',
918 % {'transition_icon': '>',
914 'status': ChangesetStatus.get_status_lbl(status)})
919 'status': ChangesetStatus.get_status_lbl(status)})
915 if close_pr:
920 if close_pr:
916 message = _('Closing with') + ' ' + message
921 message = _('Closing with') + ' ' + message
917 text = text or message
922 text = text or message
918 comm = CommentsModel().create(
923 comm = CommentsModel().create(
919 text=text,
924 text=text,
920 repo=c.rhodecode_db_repo.repo_id,
925 repo=c.rhodecode_db_repo.repo_id,
921 user=c.rhodecode_user.user_id,
926 user=c.rhodecode_user.user_id,
922 pull_request=pull_request_id,
927 pull_request=pull_request_id,
923 f_path=request.POST.get('f_path'),
928 f_path=request.POST.get('f_path'),
924 line_no=request.POST.get('line'),
929 line_no=request.POST.get('line'),
925 status_change=(ChangesetStatus.get_status_lbl(status)
930 status_change=(ChangesetStatus.get_status_lbl(status)
926 if status and allowed_to_change_status else None),
931 if status and allowed_to_change_status else None),
927 status_change_type=(status
932 status_change_type=(status
928 if status and allowed_to_change_status else None),
933 if status and allowed_to_change_status else None),
929 closing_pr=close_pr,
934 closing_pr=close_pr,
930 comment_type=comment_type,
935 comment_type=comment_type,
931 resolves_comment_id=resolves_comment_id
936 resolves_comment_id=resolves_comment_id
932 )
937 )
933
938
934 if allowed_to_change_status:
939 if allowed_to_change_status:
935 old_calculated_status = pull_request.calculated_review_status()
940 old_calculated_status = pull_request.calculated_review_status()
936 # get status if set !
941 # get status if set !
937 if status:
942 if status:
938 ChangesetStatusModel().set_status(
943 ChangesetStatusModel().set_status(
939 c.rhodecode_db_repo.repo_id,
944 c.rhodecode_db_repo.repo_id,
940 status,
945 status,
941 c.rhodecode_user.user_id,
946 c.rhodecode_user.user_id,
942 comm,
947 comm,
943 pull_request=pull_request_id
948 pull_request=pull_request_id
944 )
949 )
945
950
946 Session().flush()
951 Session().flush()
947 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
952 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
948 # we now calculate the status of pull request, and based on that
953 # we now calculate the status of pull request, and based on that
949 # calculation we set the commits status
954 # calculation we set the commits status
950 calculated_status = pull_request.calculated_review_status()
955 calculated_status = pull_request.calculated_review_status()
951 if old_calculated_status != calculated_status:
956 if old_calculated_status != calculated_status:
952 PullRequestModel()._trigger_pull_request_hook(
957 PullRequestModel()._trigger_pull_request_hook(
953 pull_request, c.rhodecode_user, 'review_status_change')
958 pull_request, c.rhodecode_user, 'review_status_change')
954
959
955 calculated_status_lbl = ChangesetStatus.get_status_lbl(
960 calculated_status_lbl = ChangesetStatus.get_status_lbl(
956 calculated_status)
961 calculated_status)
957
962
958 if close_pr:
963 if close_pr:
959 status_completed = (
964 status_completed = (
960 calculated_status in [ChangesetStatus.STATUS_APPROVED,
965 calculated_status in [ChangesetStatus.STATUS_APPROVED,
961 ChangesetStatus.STATUS_REJECTED])
966 ChangesetStatus.STATUS_REJECTED])
962 if close_pull_request or status_completed:
967 if close_pull_request or status_completed:
963 PullRequestModel().close_pull_request(
968 PullRequestModel().close_pull_request(
964 pull_request_id, c.rhodecode_user)
969 pull_request_id, c.rhodecode_user)
965 else:
970 else:
966 h.flash(_('Closing pull request on other statuses than '
971 h.flash(_('Closing pull request on other statuses than '
967 'rejected or approved is forbidden. '
972 'rejected or approved is forbidden. '
968 'Calculated status from all reviewers '
973 'Calculated status from all reviewers '
969 'is currently: %s') % calculated_status_lbl,
974 'is currently: %s') % calculated_status_lbl,
970 category='warning')
975 category='warning')
971
976
972 Session().commit()
977 Session().commit()
973
978
974 if not request.is_xhr:
979 if not request.is_xhr:
975 return redirect(h.url('pullrequest_show', repo_name=repo_name,
980 return redirect(h.url('pullrequest_show', repo_name=repo_name,
976 pull_request_id=pull_request_id))
981 pull_request_id=pull_request_id))
977
982
978 data = {
983 data = {
979 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
984 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
980 }
985 }
981 if comm:
986 if comm:
982 c.co = comm
987 c.co = comm
983 c.inline_comment = True if comm.line_no else False
988 c.inline_comment = True if comm.line_no else False
984 data.update(comm.get_dict())
989 data.update(comm.get_dict())
985 data.update({'rendered_text':
990 data.update({'rendered_text':
986 render('changeset/changeset_comment_block.mako')})
991 render('changeset/changeset_comment_block.mako')})
987
992
988 return data
993 return data
989
994
990 @LoginRequired()
995 @LoginRequired()
991 @NotAnonymous()
996 @NotAnonymous()
992 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
997 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
993 'repository.admin')
998 'repository.admin')
994 @auth.CSRFRequired()
999 @auth.CSRFRequired()
995 @jsonify
1000 @jsonify
996 def delete_comment(self, repo_name, comment_id):
1001 def delete_comment(self, repo_name, comment_id):
997 return self._delete_comment(comment_id)
1002 return self._delete_comment(comment_id)
998
1003
999 def _delete_comment(self, comment_id):
1004 def _delete_comment(self, comment_id):
1000 comment_id = safe_int(comment_id)
1005 comment_id = safe_int(comment_id)
1001 co = ChangesetComment.get_or_404(comment_id)
1006 co = ChangesetComment.get_or_404(comment_id)
1002 if co.pull_request.is_closed():
1007 if co.pull_request.is_closed():
1003 # don't allow deleting comments on closed pull request
1008 # don't allow deleting comments on closed pull request
1004 raise HTTPForbidden()
1009 raise HTTPForbidden()
1005
1010
1006 is_owner = co.author.user_id == c.rhodecode_user.user_id
1011 is_owner = co.author.user_id == c.rhodecode_user.user_id
1007 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1012 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
1008 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1013 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
1009 old_calculated_status = co.pull_request.calculated_review_status()
1014 old_calculated_status = co.pull_request.calculated_review_status()
1010 CommentsModel().delete(comment=co)
1015 CommentsModel().delete(comment=co)
1011 Session().commit()
1016 Session().commit()
1012 calculated_status = co.pull_request.calculated_review_status()
1017 calculated_status = co.pull_request.calculated_review_status()
1013 if old_calculated_status != calculated_status:
1018 if old_calculated_status != calculated_status:
1014 PullRequestModel()._trigger_pull_request_hook(
1019 PullRequestModel()._trigger_pull_request_hook(
1015 co.pull_request, c.rhodecode_user, 'review_status_change')
1020 co.pull_request, c.rhodecode_user, 'review_status_change')
1016 return True
1021 return True
1017 else:
1022 else:
1018 raise HTTPForbidden()
1023 raise HTTPForbidden()
@@ -1,4025 +1,4026 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons.i18n.translation import lazy_ugettext as _
47 from pylons.i18n.translation import lazy_ugettext as _
48 from pyramid.threadlocal import get_current_request
48 from pyramid.threadlocal import get_current_request
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
362
362
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
364 # git part is currently hardcoded.
364 # git part is currently hardcoded.
365
365
366 # SVN PATTERNS
366 # SVN PATTERNS
367 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_BRANCH_ID = 'vcs_svn_branch'
368 SVN_TAG_ID = 'vcs_svn_tag'
368 SVN_TAG_ID = 'vcs_svn_tag'
369
369
370 ui_id = Column(
370 ui_id = Column(
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
372 primary_key=True)
372 primary_key=True)
373 ui_section = Column(
373 ui_section = Column(
374 "ui_section", String(255), nullable=True, unique=None, default=None)
374 "ui_section", String(255), nullable=True, unique=None, default=None)
375 ui_key = Column(
375 ui_key = Column(
376 "ui_key", String(255), nullable=True, unique=None, default=None)
376 "ui_key", String(255), nullable=True, unique=None, default=None)
377 ui_value = Column(
377 ui_value = Column(
378 "ui_value", String(255), nullable=True, unique=None, default=None)
378 "ui_value", String(255), nullable=True, unique=None, default=None)
379 ui_active = Column(
379 ui_active = Column(
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
384 self.ui_key, self.ui_value)
384 self.ui_key, self.ui_value)
385
385
386
386
387 class RepoRhodeCodeSetting(Base, BaseModel):
387 class RepoRhodeCodeSetting(Base, BaseModel):
388 __tablename__ = 'repo_rhodecode_settings'
388 __tablename__ = 'repo_rhodecode_settings'
389 __table_args__ = (
389 __table_args__ = (
390 UniqueConstraint(
390 UniqueConstraint(
391 'app_settings_name', 'repository_id',
391 'app_settings_name', 'repository_id',
392 name='uq_repo_rhodecode_setting_name_repo_id'),
392 name='uq_repo_rhodecode_setting_name_repo_id'),
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
395 )
395 )
396
396
397 repository_id = Column(
397 repository_id = Column(
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
399 nullable=False)
399 nullable=False)
400 app_settings_id = Column(
400 app_settings_id = Column(
401 "app_settings_id", Integer(), nullable=False, unique=True,
401 "app_settings_id", Integer(), nullable=False, unique=True,
402 default=None, primary_key=True)
402 default=None, primary_key=True)
403 app_settings_name = Column(
403 app_settings_name = Column(
404 "app_settings_name", String(255), nullable=True, unique=None,
404 "app_settings_name", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406 _app_settings_value = Column(
406 _app_settings_value = Column(
407 "app_settings_value", String(4096), nullable=True, unique=None,
407 "app_settings_value", String(4096), nullable=True, unique=None,
408 default=None)
408 default=None)
409 _app_settings_type = Column(
409 _app_settings_type = Column(
410 "app_settings_type", String(255), nullable=True, unique=None,
410 "app_settings_type", String(255), nullable=True, unique=None,
411 default=None)
411 default=None)
412
412
413 repository = relationship('Repository')
413 repository = relationship('Repository')
414
414
415 def __init__(self, repository_id, key='', val='', type='unicode'):
415 def __init__(self, repository_id, key='', val='', type='unicode'):
416 self.repository_id = repository_id
416 self.repository_id = repository_id
417 self.app_settings_name = key
417 self.app_settings_name = key
418 self.app_settings_type = type
418 self.app_settings_type = type
419 self.app_settings_value = val
419 self.app_settings_value = val
420
420
421 @validates('_app_settings_value')
421 @validates('_app_settings_value')
422 def validate_settings_value(self, key, val):
422 def validate_settings_value(self, key, val):
423 assert type(val) == unicode
423 assert type(val) == unicode
424 return val
424 return val
425
425
426 @hybrid_property
426 @hybrid_property
427 def app_settings_value(self):
427 def app_settings_value(self):
428 v = self._app_settings_value
428 v = self._app_settings_value
429 type_ = self.app_settings_type
429 type_ = self.app_settings_type
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
432 return converter(v)
432 return converter(v)
433
433
434 @app_settings_value.setter
434 @app_settings_value.setter
435 def app_settings_value(self, val):
435 def app_settings_value(self, val):
436 """
436 """
437 Setter that will always make sure we use unicode in app_settings_value
437 Setter that will always make sure we use unicode in app_settings_value
438
438
439 :param val:
439 :param val:
440 """
440 """
441 self._app_settings_value = safe_unicode(val)
441 self._app_settings_value = safe_unicode(val)
442
442
443 @hybrid_property
443 @hybrid_property
444 def app_settings_type(self):
444 def app_settings_type(self):
445 return self._app_settings_type
445 return self._app_settings_type
446
446
447 @app_settings_type.setter
447 @app_settings_type.setter
448 def app_settings_type(self, val):
448 def app_settings_type(self, val):
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
450 if val not in SETTINGS_TYPES:
450 if val not in SETTINGS_TYPES:
451 raise Exception('type must be one of %s got %s'
451 raise Exception('type must be one of %s got %s'
452 % (SETTINGS_TYPES.keys(), val))
452 % (SETTINGS_TYPES.keys(), val))
453 self._app_settings_type = val
453 self._app_settings_type = val
454
454
455 def __unicode__(self):
455 def __unicode__(self):
456 return u"<%s('%s:%s:%s[%s]')>" % (
456 return u"<%s('%s:%s:%s[%s]')>" % (
457 self.__class__.__name__, self.repository.repo_name,
457 self.__class__.__name__, self.repository.repo_name,
458 self.app_settings_name, self.app_settings_value,
458 self.app_settings_name, self.app_settings_value,
459 self.app_settings_type
459 self.app_settings_type
460 )
460 )
461
461
462
462
463 class RepoRhodeCodeUi(Base, BaseModel):
463 class RepoRhodeCodeUi(Base, BaseModel):
464 __tablename__ = 'repo_rhodecode_ui'
464 __tablename__ = 'repo_rhodecode_ui'
465 __table_args__ = (
465 __table_args__ = (
466 UniqueConstraint(
466 UniqueConstraint(
467 'repository_id', 'ui_section', 'ui_key',
467 'repository_id', 'ui_section', 'ui_key',
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
471 )
471 )
472
472
473 repository_id = Column(
473 repository_id = Column(
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
475 nullable=False)
475 nullable=False)
476 ui_id = Column(
476 ui_id = Column(
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
478 primary_key=True)
478 primary_key=True)
479 ui_section = Column(
479 ui_section = Column(
480 "ui_section", String(255), nullable=True, unique=None, default=None)
480 "ui_section", String(255), nullable=True, unique=None, default=None)
481 ui_key = Column(
481 ui_key = Column(
482 "ui_key", String(255), nullable=True, unique=None, default=None)
482 "ui_key", String(255), nullable=True, unique=None, default=None)
483 ui_value = Column(
483 ui_value = Column(
484 "ui_value", String(255), nullable=True, unique=None, default=None)
484 "ui_value", String(255), nullable=True, unique=None, default=None)
485 ui_active = Column(
485 ui_active = Column(
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
487
487
488 repository = relationship('Repository')
488 repository = relationship('Repository')
489
489
490 def __repr__(self):
490 def __repr__(self):
491 return '<%s[%s:%s]%s=>%s]>' % (
491 return '<%s[%s:%s]%s=>%s]>' % (
492 self.__class__.__name__, self.repository.repo_name,
492 self.__class__.__name__, self.repository.repo_name,
493 self.ui_section, self.ui_key, self.ui_value)
493 self.ui_section, self.ui_key, self.ui_value)
494
494
495
495
496 class User(Base, BaseModel):
496 class User(Base, BaseModel):
497 __tablename__ = 'users'
497 __tablename__ = 'users'
498 __table_args__ = (
498 __table_args__ = (
499 UniqueConstraint('username'), UniqueConstraint('email'),
499 UniqueConstraint('username'), UniqueConstraint('email'),
500 Index('u_username_idx', 'username'),
500 Index('u_username_idx', 'username'),
501 Index('u_email_idx', 'email'),
501 Index('u_email_idx', 'email'),
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
504 )
504 )
505 DEFAULT_USER = 'default'
505 DEFAULT_USER = 'default'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
508
508
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
519
519
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
526
526
527 user_log = relationship('UserLog')
527 user_log = relationship('UserLog')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
529
529
530 repositories = relationship('Repository')
530 repositories = relationship('Repository')
531 repository_groups = relationship('RepoGroup')
531 repository_groups = relationship('RepoGroup')
532 user_groups = relationship('UserGroup')
532 user_groups = relationship('UserGroup')
533
533
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
536
536
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
540
540
541 group_member = relationship('UserGroupMember', cascade='all')
541 group_member = relationship('UserGroupMember', cascade='all')
542
542
543 notifications = relationship('UserNotification', cascade='all')
543 notifications = relationship('UserNotification', cascade='all')
544 # notifications assigned to this user
544 # notifications assigned to this user
545 user_created_notifications = relationship('Notification', cascade='all')
545 user_created_notifications = relationship('Notification', cascade='all')
546 # comments created by this user
546 # comments created by this user
547 user_comments = relationship('ChangesetComment', cascade='all')
547 user_comments = relationship('ChangesetComment', cascade='all')
548 # user profile extra info
548 # user profile extra info
549 user_emails = relationship('UserEmailMap', cascade='all')
549 user_emails = relationship('UserEmailMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
552 # gists
552 # gists
553 user_gists = relationship('Gist', cascade='all')
553 user_gists = relationship('Gist', cascade='all')
554 # user pull requests
554 # user pull requests
555 user_pull_requests = relationship('PullRequest', cascade='all')
555 user_pull_requests = relationship('PullRequest', cascade='all')
556 # external identities
556 # external identities
557 extenal_identities = relationship(
557 extenal_identities = relationship(
558 'ExternalIdentity',
558 'ExternalIdentity',
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
560 cascade='all')
560 cascade='all')
561
561
562 def __unicode__(self):
562 def __unicode__(self):
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
564 self.user_id, self.username)
564 self.user_id, self.username)
565
565
566 @hybrid_property
566 @hybrid_property
567 def email(self):
567 def email(self):
568 return self._email
568 return self._email
569
569
570 @email.setter
570 @email.setter
571 def email(self, val):
571 def email(self, val):
572 self._email = val.lower() if val else None
572 self._email = val.lower() if val else None
573
573
574 @hybrid_property
574 @hybrid_property
575 def api_key(self):
575 def api_key(self):
576 """
576 """
577 Fetch if exist an auth-token with role ALL connected to this user
577 Fetch if exist an auth-token with role ALL connected to this user
578 """
578 """
579 user_auth_token = UserApiKeys.query()\
579 user_auth_token = UserApiKeys.query()\
580 .filter(UserApiKeys.user_id == self.user_id)\
580 .filter(UserApiKeys.user_id == self.user_id)\
581 .filter(or_(UserApiKeys.expires == -1,
581 .filter(or_(UserApiKeys.expires == -1,
582 UserApiKeys.expires >= time.time()))\
582 UserApiKeys.expires >= time.time()))\
583 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
583 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
584 if user_auth_token:
584 if user_auth_token:
585 user_auth_token = user_auth_token.api_key
585 user_auth_token = user_auth_token.api_key
586
586
587 return user_auth_token
587 return user_auth_token
588
588
589 @api_key.setter
589 @api_key.setter
590 def api_key(self, val):
590 def api_key(self, val):
591 # don't allow to set API key this is deprecated for now
591 # don't allow to set API key this is deprecated for now
592 self._api_key = None
592 self._api_key = None
593
593
594 @property
594 @property
595 def firstname(self):
595 def firstname(self):
596 # alias for future
596 # alias for future
597 return self.name
597 return self.name
598
598
599 @property
599 @property
600 def emails(self):
600 def emails(self):
601 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
601 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
602 return [self.email] + [x.email for x in other]
602 return [self.email] + [x.email for x in other]
603
603
604 @property
604 @property
605 def auth_tokens(self):
605 def auth_tokens(self):
606 return [x.api_key for x in self.extra_auth_tokens]
606 return [x.api_key for x in self.extra_auth_tokens]
607
607
608 @property
608 @property
609 def extra_auth_tokens(self):
609 def extra_auth_tokens(self):
610 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
610 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
611
611
612 @property
612 @property
613 def feed_token(self):
613 def feed_token(self):
614 return self.get_feed_token()
614 return self.get_feed_token()
615
615
616 def get_feed_token(self):
616 def get_feed_token(self):
617 feed_tokens = UserApiKeys.query()\
617 feed_tokens = UserApiKeys.query()\
618 .filter(UserApiKeys.user == self)\
618 .filter(UserApiKeys.user == self)\
619 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
619 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
620 .all()
620 .all()
621 if feed_tokens:
621 if feed_tokens:
622 return feed_tokens[0].api_key
622 return feed_tokens[0].api_key
623 return 'NO_FEED_TOKEN_AVAILABLE'
623 return 'NO_FEED_TOKEN_AVAILABLE'
624
624
625 @classmethod
625 @classmethod
626 def extra_valid_auth_tokens(cls, user, role=None):
626 def extra_valid_auth_tokens(cls, user, role=None):
627 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
627 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
628 .filter(or_(UserApiKeys.expires == -1,
628 .filter(or_(UserApiKeys.expires == -1,
629 UserApiKeys.expires >= time.time()))
629 UserApiKeys.expires >= time.time()))
630 if role:
630 if role:
631 tokens = tokens.filter(or_(UserApiKeys.role == role,
631 tokens = tokens.filter(or_(UserApiKeys.role == role,
632 UserApiKeys.role == UserApiKeys.ROLE_ALL))
632 UserApiKeys.role == UserApiKeys.ROLE_ALL))
633 return tokens.all()
633 return tokens.all()
634
634
635 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
635 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
636 from rhodecode.lib import auth
636 from rhodecode.lib import auth
637
637
638 log.debug('Trying to authenticate user: %s via auth-token, '
638 log.debug('Trying to authenticate user: %s via auth-token, '
639 'and roles: %s', self, roles)
639 'and roles: %s', self, roles)
640
640
641 if not auth_token:
641 if not auth_token:
642 return False
642 return False
643
643
644 crypto_backend = auth.crypto_backend()
644 crypto_backend = auth.crypto_backend()
645
645
646 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
646 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
647 tokens_q = UserApiKeys.query()\
647 tokens_q = UserApiKeys.query()\
648 .filter(UserApiKeys.user_id == self.user_id)\
648 .filter(UserApiKeys.user_id == self.user_id)\
649 .filter(or_(UserApiKeys.expires == -1,
649 .filter(or_(UserApiKeys.expires == -1,
650 UserApiKeys.expires >= time.time()))
650 UserApiKeys.expires >= time.time()))
651
651
652 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
652 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
653
653
654 plain_tokens = []
654 plain_tokens = []
655 hash_tokens = []
655 hash_tokens = []
656
656
657 for token in tokens_q.all():
657 for token in tokens_q.all():
658 # verify scope first
658 # verify scope first
659 if token.repo_id:
659 if token.repo_id:
660 # token has a scope, we need to verify it
660 # token has a scope, we need to verify it
661 if scope_repo_id != token.repo_id:
661 if scope_repo_id != token.repo_id:
662 log.debug(
662 log.debug(
663 'Scope mismatch: token has a set repo scope: %s, '
663 'Scope mismatch: token has a set repo scope: %s, '
664 'and calling scope is:%s, skipping further checks',
664 'and calling scope is:%s, skipping further checks',
665 token.repo, scope_repo_id)
665 token.repo, scope_repo_id)
666 # token has a scope, and it doesn't match, skip token
666 # token has a scope, and it doesn't match, skip token
667 continue
667 continue
668
668
669 if token.api_key.startswith(crypto_backend.ENC_PREF):
669 if token.api_key.startswith(crypto_backend.ENC_PREF):
670 hash_tokens.append(token.api_key)
670 hash_tokens.append(token.api_key)
671 else:
671 else:
672 plain_tokens.append(token.api_key)
672 plain_tokens.append(token.api_key)
673
673
674 is_plain_match = auth_token in plain_tokens
674 is_plain_match = auth_token in plain_tokens
675 if is_plain_match:
675 if is_plain_match:
676 return True
676 return True
677
677
678 for hashed in hash_tokens:
678 for hashed in hash_tokens:
679 # TODO(marcink): this is expensive to calculate, but most secure
679 # TODO(marcink): this is expensive to calculate, but most secure
680 match = crypto_backend.hash_check(auth_token, hashed)
680 match = crypto_backend.hash_check(auth_token, hashed)
681 if match:
681 if match:
682 return True
682 return True
683
683
684 return False
684 return False
685
685
686 @property
686 @property
687 def ip_addresses(self):
687 def ip_addresses(self):
688 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
688 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
689 return [x.ip_addr for x in ret]
689 return [x.ip_addr for x in ret]
690
690
691 @property
691 @property
692 def username_and_name(self):
692 def username_and_name(self):
693 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
693 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
694
694
695 @property
695 @property
696 def username_or_name_or_email(self):
696 def username_or_name_or_email(self):
697 full_name = self.full_name if self.full_name is not ' ' else None
697 full_name = self.full_name if self.full_name is not ' ' else None
698 return self.username or full_name or self.email
698 return self.username or full_name or self.email
699
699
700 @property
700 @property
701 def full_name(self):
701 def full_name(self):
702 return '%s %s' % (self.firstname, self.lastname)
702 return '%s %s' % (self.firstname, self.lastname)
703
703
704 @property
704 @property
705 def full_name_or_username(self):
705 def full_name_or_username(self):
706 return ('%s %s' % (self.firstname, self.lastname)
706 return ('%s %s' % (self.firstname, self.lastname)
707 if (self.firstname and self.lastname) else self.username)
707 if (self.firstname and self.lastname) else self.username)
708
708
709 @property
709 @property
710 def full_contact(self):
710 def full_contact(self):
711 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
711 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
712
712
713 @property
713 @property
714 def short_contact(self):
714 def short_contact(self):
715 return '%s %s' % (self.firstname, self.lastname)
715 return '%s %s' % (self.firstname, self.lastname)
716
716
717 @property
717 @property
718 def is_admin(self):
718 def is_admin(self):
719 return self.admin
719 return self.admin
720
720
721 @property
721 @property
722 def AuthUser(self):
722 def AuthUser(self):
723 """
723 """
724 Returns instance of AuthUser for this user
724 Returns instance of AuthUser for this user
725 """
725 """
726 from rhodecode.lib.auth import AuthUser
726 from rhodecode.lib.auth import AuthUser
727 return AuthUser(user_id=self.user_id, username=self.username)
727 return AuthUser(user_id=self.user_id, username=self.username)
728
728
729 @hybrid_property
729 @hybrid_property
730 def user_data(self):
730 def user_data(self):
731 if not self._user_data:
731 if not self._user_data:
732 return {}
732 return {}
733
733
734 try:
734 try:
735 return json.loads(self._user_data)
735 return json.loads(self._user_data)
736 except TypeError:
736 except TypeError:
737 return {}
737 return {}
738
738
739 @user_data.setter
739 @user_data.setter
740 def user_data(self, val):
740 def user_data(self, val):
741 if not isinstance(val, dict):
741 if not isinstance(val, dict):
742 raise Exception('user_data must be dict, got %s' % type(val))
742 raise Exception('user_data must be dict, got %s' % type(val))
743 try:
743 try:
744 self._user_data = json.dumps(val)
744 self._user_data = json.dumps(val)
745 except Exception:
745 except Exception:
746 log.error(traceback.format_exc())
746 log.error(traceback.format_exc())
747
747
748 @classmethod
748 @classmethod
749 def get_by_username(cls, username, case_insensitive=False,
749 def get_by_username(cls, username, case_insensitive=False,
750 cache=False, identity_cache=False):
750 cache=False, identity_cache=False):
751 session = Session()
751 session = Session()
752
752
753 if case_insensitive:
753 if case_insensitive:
754 q = cls.query().filter(
754 q = cls.query().filter(
755 func.lower(cls.username) == func.lower(username))
755 func.lower(cls.username) == func.lower(username))
756 else:
756 else:
757 q = cls.query().filter(cls.username == username)
757 q = cls.query().filter(cls.username == username)
758
758
759 if cache:
759 if cache:
760 if identity_cache:
760 if identity_cache:
761 val = cls.identity_cache(session, 'username', username)
761 val = cls.identity_cache(session, 'username', username)
762 if val:
762 if val:
763 return val
763 return val
764 else:
764 else:
765 cache_key = "get_user_by_name_%s" % _hash_key(username)
765 cache_key = "get_user_by_name_%s" % _hash_key(username)
766 q = q.options(
766 q = q.options(
767 FromCache("sql_cache_short", cache_key))
767 FromCache("sql_cache_short", cache_key))
768
768
769 return q.scalar()
769 return q.scalar()
770
770
771 @classmethod
771 @classmethod
772 def get_by_auth_token(cls, auth_token, cache=False):
772 def get_by_auth_token(cls, auth_token, cache=False):
773 q = UserApiKeys.query()\
773 q = UserApiKeys.query()\
774 .filter(UserApiKeys.api_key == auth_token)\
774 .filter(UserApiKeys.api_key == auth_token)\
775 .filter(or_(UserApiKeys.expires == -1,
775 .filter(or_(UserApiKeys.expires == -1,
776 UserApiKeys.expires >= time.time()))
776 UserApiKeys.expires >= time.time()))
777 if cache:
777 if cache:
778 q = q.options(
778 q = q.options(
779 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
779 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
780
780
781 match = q.first()
781 match = q.first()
782 if match:
782 if match:
783 return match.user
783 return match.user
784
784
785 @classmethod
785 @classmethod
786 def get_by_email(cls, email, case_insensitive=False, cache=False):
786 def get_by_email(cls, email, case_insensitive=False, cache=False):
787
787
788 if case_insensitive:
788 if case_insensitive:
789 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
789 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
790
790
791 else:
791 else:
792 q = cls.query().filter(cls.email == email)
792 q = cls.query().filter(cls.email == email)
793
793
794 email_key = _hash_key(email)
794 email_key = _hash_key(email)
795 if cache:
795 if cache:
796 q = q.options(
796 q = q.options(
797 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
797 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
798
798
799 ret = q.scalar()
799 ret = q.scalar()
800 if ret is None:
800 if ret is None:
801 q = UserEmailMap.query()
801 q = UserEmailMap.query()
802 # try fetching in alternate email map
802 # try fetching in alternate email map
803 if case_insensitive:
803 if case_insensitive:
804 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
804 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
805 else:
805 else:
806 q = q.filter(UserEmailMap.email == email)
806 q = q.filter(UserEmailMap.email == email)
807 q = q.options(joinedload(UserEmailMap.user))
807 q = q.options(joinedload(UserEmailMap.user))
808 if cache:
808 if cache:
809 q = q.options(
809 q = q.options(
810 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
810 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
811 ret = getattr(q.scalar(), 'user', None)
811 ret = getattr(q.scalar(), 'user', None)
812
812
813 return ret
813 return ret
814
814
815 @classmethod
815 @classmethod
816 def get_from_cs_author(cls, author):
816 def get_from_cs_author(cls, author):
817 """
817 """
818 Tries to get User objects out of commit author string
818 Tries to get User objects out of commit author string
819
819
820 :param author:
820 :param author:
821 """
821 """
822 from rhodecode.lib.helpers import email, author_name
822 from rhodecode.lib.helpers import email, author_name
823 # Valid email in the attribute passed, see if they're in the system
823 # Valid email in the attribute passed, see if they're in the system
824 _email = email(author)
824 _email = email(author)
825 if _email:
825 if _email:
826 user = cls.get_by_email(_email, case_insensitive=True)
826 user = cls.get_by_email(_email, case_insensitive=True)
827 if user:
827 if user:
828 return user
828 return user
829 # Maybe we can match by username?
829 # Maybe we can match by username?
830 _author = author_name(author)
830 _author = author_name(author)
831 user = cls.get_by_username(_author, case_insensitive=True)
831 user = cls.get_by_username(_author, case_insensitive=True)
832 if user:
832 if user:
833 return user
833 return user
834
834
835 def update_userdata(self, **kwargs):
835 def update_userdata(self, **kwargs):
836 usr = self
836 usr = self
837 old = usr.user_data
837 old = usr.user_data
838 old.update(**kwargs)
838 old.update(**kwargs)
839 usr.user_data = old
839 usr.user_data = old
840 Session().add(usr)
840 Session().add(usr)
841 log.debug('updated userdata with ', kwargs)
841 log.debug('updated userdata with ', kwargs)
842
842
843 def update_lastlogin(self):
843 def update_lastlogin(self):
844 """Update user lastlogin"""
844 """Update user lastlogin"""
845 self.last_login = datetime.datetime.now()
845 self.last_login = datetime.datetime.now()
846 Session().add(self)
846 Session().add(self)
847 log.debug('updated user %s lastlogin', self.username)
847 log.debug('updated user %s lastlogin', self.username)
848
848
849 def update_lastactivity(self):
849 def update_lastactivity(self):
850 """Update user lastactivity"""
850 """Update user lastactivity"""
851 self.last_activity = datetime.datetime.now()
851 self.last_activity = datetime.datetime.now()
852 Session().add(self)
852 Session().add(self)
853 log.debug('updated user %s lastactivity', self.username)
853 log.debug('updated user %s lastactivity', self.username)
854
854
855 def update_password(self, new_password):
855 def update_password(self, new_password):
856 from rhodecode.lib.auth import get_crypt_password
856 from rhodecode.lib.auth import get_crypt_password
857
857
858 self.password = get_crypt_password(new_password)
858 self.password = get_crypt_password(new_password)
859 Session().add(self)
859 Session().add(self)
860
860
861 @classmethod
861 @classmethod
862 def get_first_super_admin(cls):
862 def get_first_super_admin(cls):
863 user = User.query().filter(User.admin == true()).first()
863 user = User.query().filter(User.admin == true()).first()
864 if user is None:
864 if user is None:
865 raise Exception('FATAL: Missing administrative account!')
865 raise Exception('FATAL: Missing administrative account!')
866 return user
866 return user
867
867
868 @classmethod
868 @classmethod
869 def get_all_super_admins(cls):
869 def get_all_super_admins(cls):
870 """
870 """
871 Returns all admin accounts sorted by username
871 Returns all admin accounts sorted by username
872 """
872 """
873 return User.query().filter(User.admin == true())\
873 return User.query().filter(User.admin == true())\
874 .order_by(User.username.asc()).all()
874 .order_by(User.username.asc()).all()
875
875
876 @classmethod
876 @classmethod
877 def get_default_user(cls, cache=False, refresh=False):
877 def get_default_user(cls, cache=False, refresh=False):
878 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
878 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
879 if user is None:
879 if user is None:
880 raise Exception('FATAL: Missing default account!')
880 raise Exception('FATAL: Missing default account!')
881 if refresh:
881 if refresh:
882 # The default user might be based on outdated state which
882 # The default user might be based on outdated state which
883 # has been loaded from the cache.
883 # has been loaded from the cache.
884 # A call to refresh() ensures that the
884 # A call to refresh() ensures that the
885 # latest state from the database is used.
885 # latest state from the database is used.
886 Session().refresh(user)
886 Session().refresh(user)
887 return user
887 return user
888
888
889 def _get_default_perms(self, user, suffix=''):
889 def _get_default_perms(self, user, suffix=''):
890 from rhodecode.model.permission import PermissionModel
890 from rhodecode.model.permission import PermissionModel
891 return PermissionModel().get_default_perms(user.user_perms, suffix)
891 return PermissionModel().get_default_perms(user.user_perms, suffix)
892
892
893 def get_default_perms(self, suffix=''):
893 def get_default_perms(self, suffix=''):
894 return self._get_default_perms(self, suffix)
894 return self._get_default_perms(self, suffix)
895
895
896 def get_api_data(self, include_secrets=False, details='full'):
896 def get_api_data(self, include_secrets=False, details='full'):
897 """
897 """
898 Common function for generating user related data for API
898 Common function for generating user related data for API
899
899
900 :param include_secrets: By default secrets in the API data will be replaced
900 :param include_secrets: By default secrets in the API data will be replaced
901 by a placeholder value to prevent exposing this data by accident. In case
901 by a placeholder value to prevent exposing this data by accident. In case
902 this data shall be exposed, set this flag to ``True``.
902 this data shall be exposed, set this flag to ``True``.
903
903
904 :param details: details can be 'basic|full' basic gives only a subset of
904 :param details: details can be 'basic|full' basic gives only a subset of
905 the available user information that includes user_id, name and emails.
905 the available user information that includes user_id, name and emails.
906 """
906 """
907 user = self
907 user = self
908 user_data = self.user_data
908 user_data = self.user_data
909 data = {
909 data = {
910 'user_id': user.user_id,
910 'user_id': user.user_id,
911 'username': user.username,
911 'username': user.username,
912 'firstname': user.name,
912 'firstname': user.name,
913 'lastname': user.lastname,
913 'lastname': user.lastname,
914 'email': user.email,
914 'email': user.email,
915 'emails': user.emails,
915 'emails': user.emails,
916 }
916 }
917 if details == 'basic':
917 if details == 'basic':
918 return data
918 return data
919
919
920 api_key_length = 40
920 api_key_length = 40
921 api_key_replacement = '*' * api_key_length
921 api_key_replacement = '*' * api_key_length
922
922
923 extras = {
923 extras = {
924 'api_keys': [api_key_replacement],
924 'api_keys': [api_key_replacement],
925 'auth_tokens': [api_key_replacement],
925 'auth_tokens': [api_key_replacement],
926 'active': user.active,
926 'active': user.active,
927 'admin': user.admin,
927 'admin': user.admin,
928 'extern_type': user.extern_type,
928 'extern_type': user.extern_type,
929 'extern_name': user.extern_name,
929 'extern_name': user.extern_name,
930 'last_login': user.last_login,
930 'last_login': user.last_login,
931 'last_activity': user.last_activity,
931 'last_activity': user.last_activity,
932 'ip_addresses': user.ip_addresses,
932 'ip_addresses': user.ip_addresses,
933 'language': user_data.get('language')
933 'language': user_data.get('language')
934 }
934 }
935 data.update(extras)
935 data.update(extras)
936
936
937 if include_secrets:
937 if include_secrets:
938 data['api_keys'] = user.auth_tokens
938 data['api_keys'] = user.auth_tokens
939 data['auth_tokens'] = user.extra_auth_tokens
939 data['auth_tokens'] = user.extra_auth_tokens
940 return data
940 return data
941
941
942 def __json__(self):
942 def __json__(self):
943 data = {
943 data = {
944 'full_name': self.full_name,
944 'full_name': self.full_name,
945 'full_name_or_username': self.full_name_or_username,
945 'full_name_or_username': self.full_name_or_username,
946 'short_contact': self.short_contact,
946 'short_contact': self.short_contact,
947 'full_contact': self.full_contact,
947 'full_contact': self.full_contact,
948 }
948 }
949 data.update(self.get_api_data())
949 data.update(self.get_api_data())
950 return data
950 return data
951
951
952
952
953 class UserApiKeys(Base, BaseModel):
953 class UserApiKeys(Base, BaseModel):
954 __tablename__ = 'user_api_keys'
954 __tablename__ = 'user_api_keys'
955 __table_args__ = (
955 __table_args__ = (
956 Index('uak_api_key_idx', 'api_key'),
956 Index('uak_api_key_idx', 'api_key'),
957 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
957 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
958 UniqueConstraint('api_key'),
958 UniqueConstraint('api_key'),
959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
961 )
961 )
962 __mapper_args__ = {}
962 __mapper_args__ = {}
963
963
964 # ApiKey role
964 # ApiKey role
965 ROLE_ALL = 'token_role_all'
965 ROLE_ALL = 'token_role_all'
966 ROLE_HTTP = 'token_role_http'
966 ROLE_HTTP = 'token_role_http'
967 ROLE_VCS = 'token_role_vcs'
967 ROLE_VCS = 'token_role_vcs'
968 ROLE_API = 'token_role_api'
968 ROLE_API = 'token_role_api'
969 ROLE_FEED = 'token_role_feed'
969 ROLE_FEED = 'token_role_feed'
970 ROLE_PASSWORD_RESET = 'token_password_reset'
970 ROLE_PASSWORD_RESET = 'token_password_reset'
971
971
972 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
972 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
973
973
974 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
974 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
975 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
975 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
976 api_key = Column("api_key", String(255), nullable=False, unique=True)
976 api_key = Column("api_key", String(255), nullable=False, unique=True)
977 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
977 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
978 expires = Column('expires', Float(53), nullable=False)
978 expires = Column('expires', Float(53), nullable=False)
979 role = Column('role', String(255), nullable=True)
979 role = Column('role', String(255), nullable=True)
980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
981
981
982 # scope columns
982 # scope columns
983 repo_id = Column(
983 repo_id = Column(
984 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
984 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
985 nullable=True, unique=None, default=None)
985 nullable=True, unique=None, default=None)
986 repo = relationship('Repository', lazy='joined')
986 repo = relationship('Repository', lazy='joined')
987
987
988 repo_group_id = Column(
988 repo_group_id = Column(
989 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
989 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
990 nullable=True, unique=None, default=None)
990 nullable=True, unique=None, default=None)
991 repo_group = relationship('RepoGroup', lazy='joined')
991 repo_group = relationship('RepoGroup', lazy='joined')
992
992
993 user = relationship('User', lazy='joined')
993 user = relationship('User', lazy='joined')
994
994
995 def __unicode__(self):
995 def __unicode__(self):
996 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
996 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
997
997
998 def __json__(self):
998 def __json__(self):
999 data = {
999 data = {
1000 'auth_token': self.api_key,
1000 'auth_token': self.api_key,
1001 'role': self.role,
1001 'role': self.role,
1002 'scope': self.scope_humanized,
1002 'scope': self.scope_humanized,
1003 'expired': self.expired
1003 'expired': self.expired
1004 }
1004 }
1005 return data
1005 return data
1006
1006
1007 @property
1007 @property
1008 def expired(self):
1008 def expired(self):
1009 if self.expires == -1:
1009 if self.expires == -1:
1010 return False
1010 return False
1011 return time.time() > self.expires
1011 return time.time() > self.expires
1012
1012
1013 @classmethod
1013 @classmethod
1014 def _get_role_name(cls, role):
1014 def _get_role_name(cls, role):
1015 return {
1015 return {
1016 cls.ROLE_ALL: _('all'),
1016 cls.ROLE_ALL: _('all'),
1017 cls.ROLE_HTTP: _('http/web interface'),
1017 cls.ROLE_HTTP: _('http/web interface'),
1018 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1018 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1019 cls.ROLE_API: _('api calls'),
1019 cls.ROLE_API: _('api calls'),
1020 cls.ROLE_FEED: _('feed access'),
1020 cls.ROLE_FEED: _('feed access'),
1021 }.get(role, role)
1021 }.get(role, role)
1022
1022
1023 @property
1023 @property
1024 def role_humanized(self):
1024 def role_humanized(self):
1025 return self._get_role_name(self.role)
1025 return self._get_role_name(self.role)
1026
1026
1027 def _get_scope(self):
1027 def _get_scope(self):
1028 if self.repo:
1028 if self.repo:
1029 return repr(self.repo)
1029 return repr(self.repo)
1030 if self.repo_group:
1030 if self.repo_group:
1031 return repr(self.repo_group) + ' (recursive)'
1031 return repr(self.repo_group) + ' (recursive)'
1032 return 'global'
1032 return 'global'
1033
1033
1034 @property
1034 @property
1035 def scope_humanized(self):
1035 def scope_humanized(self):
1036 return self._get_scope()
1036 return self._get_scope()
1037
1037
1038
1038
1039 class UserEmailMap(Base, BaseModel):
1039 class UserEmailMap(Base, BaseModel):
1040 __tablename__ = 'user_email_map'
1040 __tablename__ = 'user_email_map'
1041 __table_args__ = (
1041 __table_args__ = (
1042 Index('uem_email_idx', 'email'),
1042 Index('uem_email_idx', 'email'),
1043 UniqueConstraint('email'),
1043 UniqueConstraint('email'),
1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1046 )
1046 )
1047 __mapper_args__ = {}
1047 __mapper_args__ = {}
1048
1048
1049 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1051 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1051 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1052 user = relationship('User', lazy='joined')
1052 user = relationship('User', lazy='joined')
1053
1053
1054 @validates('_email')
1054 @validates('_email')
1055 def validate_email(self, key, email):
1055 def validate_email(self, key, email):
1056 # check if this email is not main one
1056 # check if this email is not main one
1057 main_email = Session().query(User).filter(User.email == email).scalar()
1057 main_email = Session().query(User).filter(User.email == email).scalar()
1058 if main_email is not None:
1058 if main_email is not None:
1059 raise AttributeError('email %s is present is user table' % email)
1059 raise AttributeError('email %s is present is user table' % email)
1060 return email
1060 return email
1061
1061
1062 @hybrid_property
1062 @hybrid_property
1063 def email(self):
1063 def email(self):
1064 return self._email
1064 return self._email
1065
1065
1066 @email.setter
1066 @email.setter
1067 def email(self, val):
1067 def email(self, val):
1068 self._email = val.lower() if val else None
1068 self._email = val.lower() if val else None
1069
1069
1070
1070
1071 class UserIpMap(Base, BaseModel):
1071 class UserIpMap(Base, BaseModel):
1072 __tablename__ = 'user_ip_map'
1072 __tablename__ = 'user_ip_map'
1073 __table_args__ = (
1073 __table_args__ = (
1074 UniqueConstraint('user_id', 'ip_addr'),
1074 UniqueConstraint('user_id', 'ip_addr'),
1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1077 )
1077 )
1078 __mapper_args__ = {}
1078 __mapper_args__ = {}
1079
1079
1080 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1082 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1083 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1083 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1084 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1084 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1085 user = relationship('User', lazy='joined')
1085 user = relationship('User', lazy='joined')
1086
1086
1087 @classmethod
1087 @classmethod
1088 def _get_ip_range(cls, ip_addr):
1088 def _get_ip_range(cls, ip_addr):
1089 net = ipaddress.ip_network(ip_addr, strict=False)
1089 net = ipaddress.ip_network(ip_addr, strict=False)
1090 return [str(net.network_address), str(net.broadcast_address)]
1090 return [str(net.network_address), str(net.broadcast_address)]
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 return {
1093 return {
1094 'ip_addr': self.ip_addr,
1094 'ip_addr': self.ip_addr,
1095 'ip_range': self._get_ip_range(self.ip_addr),
1095 'ip_range': self._get_ip_range(self.ip_addr),
1096 }
1096 }
1097
1097
1098 def __unicode__(self):
1098 def __unicode__(self):
1099 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1099 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1100 self.user_id, self.ip_addr)
1100 self.user_id, self.ip_addr)
1101
1101
1102
1102
1103 class UserLog(Base, BaseModel):
1103 class UserLog(Base, BaseModel):
1104 __tablename__ = 'user_logs'
1104 __tablename__ = 'user_logs'
1105 __table_args__ = (
1105 __table_args__ = (
1106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1108 )
1108 )
1109 VERSION_1 = 'v1'
1109 VERSION_1 = 'v1'
1110 VERSION_2 = 'v2'
1110 VERSION_2 = 'v2'
1111 VERSIONS = [VERSION_1, VERSION_2]
1111 VERSIONS = [VERSION_1, VERSION_2]
1112
1112
1113 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1113 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1115 username = Column("username", String(255), nullable=True, unique=None, default=None)
1115 username = Column("username", String(255), nullable=True, unique=None, default=None)
1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1117 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1117 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1118 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1118 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1119 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1119 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1120 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1120 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1121
1121
1122 version = Column("version", String(255), nullable=True, default=VERSION_1)
1122 version = Column("version", String(255), nullable=True, default=VERSION_1)
1123 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1123 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1124 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1124 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1125
1125
1126 def __unicode__(self):
1126 def __unicode__(self):
1127 return u"<%s('id:%s:%s')>" % (
1127 return u"<%s('id:%s:%s')>" % (
1128 self.__class__.__name__, self.repository_name, self.action)
1128 self.__class__.__name__, self.repository_name, self.action)
1129
1129
1130 def __json__(self):
1130 def __json__(self):
1131 return {
1131 return {
1132 'user_id': self.user_id,
1132 'user_id': self.user_id,
1133 'username': self.username,
1133 'username': self.username,
1134 'repository_id': self.repository_id,
1134 'repository_id': self.repository_id,
1135 'repository_name': self.repository_name,
1135 'repository_name': self.repository_name,
1136 'user_ip': self.user_ip,
1136 'user_ip': self.user_ip,
1137 'action_date': self.action_date,
1137 'action_date': self.action_date,
1138 'action': self.action,
1138 'action': self.action,
1139 }
1139 }
1140
1140
1141 @property
1141 @property
1142 def action_as_day(self):
1142 def action_as_day(self):
1143 return datetime.date(*self.action_date.timetuple()[:3])
1143 return datetime.date(*self.action_date.timetuple()[:3])
1144
1144
1145 user = relationship('User')
1145 user = relationship('User')
1146 repository = relationship('Repository', cascade='')
1146 repository = relationship('Repository', cascade='')
1147
1147
1148
1148
1149 class UserGroup(Base, BaseModel):
1149 class UserGroup(Base, BaseModel):
1150 __tablename__ = 'users_groups'
1150 __tablename__ = 'users_groups'
1151 __table_args__ = (
1151 __table_args__ = (
1152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1153 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1153 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1154 )
1154 )
1155
1155
1156 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1156 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1157 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1158 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1158 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1159 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1159 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1160 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1160 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1162 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1162 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1163 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1163 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1164
1164
1165 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1165 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1166 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1166 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1167 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1167 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1168 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1168 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1169 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1169 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1170 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1170 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1171
1171
1172 user = relationship('User')
1172 user = relationship('User')
1173
1173
1174 @hybrid_property
1174 @hybrid_property
1175 def group_data(self):
1175 def group_data(self):
1176 if not self._group_data:
1176 if not self._group_data:
1177 return {}
1177 return {}
1178
1178
1179 try:
1179 try:
1180 return json.loads(self._group_data)
1180 return json.loads(self._group_data)
1181 except TypeError:
1181 except TypeError:
1182 return {}
1182 return {}
1183
1183
1184 @group_data.setter
1184 @group_data.setter
1185 def group_data(self, val):
1185 def group_data(self, val):
1186 try:
1186 try:
1187 self._group_data = json.dumps(val)
1187 self._group_data = json.dumps(val)
1188 except Exception:
1188 except Exception:
1189 log.error(traceback.format_exc())
1189 log.error(traceback.format_exc())
1190
1190
1191 def __unicode__(self):
1191 def __unicode__(self):
1192 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1192 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1193 self.users_group_id,
1193 self.users_group_id,
1194 self.users_group_name)
1194 self.users_group_name)
1195
1195
1196 @classmethod
1196 @classmethod
1197 def get_by_group_name(cls, group_name, cache=False,
1197 def get_by_group_name(cls, group_name, cache=False,
1198 case_insensitive=False):
1198 case_insensitive=False):
1199 if case_insensitive:
1199 if case_insensitive:
1200 q = cls.query().filter(func.lower(cls.users_group_name) ==
1200 q = cls.query().filter(func.lower(cls.users_group_name) ==
1201 func.lower(group_name))
1201 func.lower(group_name))
1202
1202
1203 else:
1203 else:
1204 q = cls.query().filter(cls.users_group_name == group_name)
1204 q = cls.query().filter(cls.users_group_name == group_name)
1205 if cache:
1205 if cache:
1206 q = q.options(
1206 q = q.options(
1207 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1207 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1208 return q.scalar()
1208 return q.scalar()
1209
1209
1210 @classmethod
1210 @classmethod
1211 def get(cls, user_group_id, cache=False):
1211 def get(cls, user_group_id, cache=False):
1212 user_group = cls.query()
1212 user_group = cls.query()
1213 if cache:
1213 if cache:
1214 user_group = user_group.options(
1214 user_group = user_group.options(
1215 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1215 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1216 return user_group.get(user_group_id)
1216 return user_group.get(user_group_id)
1217
1217
1218 def permissions(self, with_admins=True, with_owner=True):
1218 def permissions(self, with_admins=True, with_owner=True):
1219 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1219 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1220 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1220 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1221 joinedload(UserUserGroupToPerm.user),
1221 joinedload(UserUserGroupToPerm.user),
1222 joinedload(UserUserGroupToPerm.permission),)
1222 joinedload(UserUserGroupToPerm.permission),)
1223
1223
1224 # get owners and admins and permissions. We do a trick of re-writing
1224 # get owners and admins and permissions. We do a trick of re-writing
1225 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1225 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1226 # has a global reference and changing one object propagates to all
1226 # has a global reference and changing one object propagates to all
1227 # others. This means if admin is also an owner admin_row that change
1227 # others. This means if admin is also an owner admin_row that change
1228 # would propagate to both objects
1228 # would propagate to both objects
1229 perm_rows = []
1229 perm_rows = []
1230 for _usr in q.all():
1230 for _usr in q.all():
1231 usr = AttributeDict(_usr.user.get_dict())
1231 usr = AttributeDict(_usr.user.get_dict())
1232 usr.permission = _usr.permission.permission_name
1232 usr.permission = _usr.permission.permission_name
1233 perm_rows.append(usr)
1233 perm_rows.append(usr)
1234
1234
1235 # filter the perm rows by 'default' first and then sort them by
1235 # filter the perm rows by 'default' first and then sort them by
1236 # admin,write,read,none permissions sorted again alphabetically in
1236 # admin,write,read,none permissions sorted again alphabetically in
1237 # each group
1237 # each group
1238 perm_rows = sorted(perm_rows, key=display_sort)
1238 perm_rows = sorted(perm_rows, key=display_sort)
1239
1239
1240 _admin_perm = 'usergroup.admin'
1240 _admin_perm = 'usergroup.admin'
1241 owner_row = []
1241 owner_row = []
1242 if with_owner:
1242 if with_owner:
1243 usr = AttributeDict(self.user.get_dict())
1243 usr = AttributeDict(self.user.get_dict())
1244 usr.owner_row = True
1244 usr.owner_row = True
1245 usr.permission = _admin_perm
1245 usr.permission = _admin_perm
1246 owner_row.append(usr)
1246 owner_row.append(usr)
1247
1247
1248 super_admin_rows = []
1248 super_admin_rows = []
1249 if with_admins:
1249 if with_admins:
1250 for usr in User.get_all_super_admins():
1250 for usr in User.get_all_super_admins():
1251 # if this admin is also owner, don't double the record
1251 # if this admin is also owner, don't double the record
1252 if usr.user_id == owner_row[0].user_id:
1252 if usr.user_id == owner_row[0].user_id:
1253 owner_row[0].admin_row = True
1253 owner_row[0].admin_row = True
1254 else:
1254 else:
1255 usr = AttributeDict(usr.get_dict())
1255 usr = AttributeDict(usr.get_dict())
1256 usr.admin_row = True
1256 usr.admin_row = True
1257 usr.permission = _admin_perm
1257 usr.permission = _admin_perm
1258 super_admin_rows.append(usr)
1258 super_admin_rows.append(usr)
1259
1259
1260 return super_admin_rows + owner_row + perm_rows
1260 return super_admin_rows + owner_row + perm_rows
1261
1261
1262 def permission_user_groups(self):
1262 def permission_user_groups(self):
1263 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1263 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1264 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1264 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1265 joinedload(UserGroupUserGroupToPerm.target_user_group),
1265 joinedload(UserGroupUserGroupToPerm.target_user_group),
1266 joinedload(UserGroupUserGroupToPerm.permission),)
1266 joinedload(UserGroupUserGroupToPerm.permission),)
1267
1267
1268 perm_rows = []
1268 perm_rows = []
1269 for _user_group in q.all():
1269 for _user_group in q.all():
1270 usr = AttributeDict(_user_group.user_group.get_dict())
1270 usr = AttributeDict(_user_group.user_group.get_dict())
1271 usr.permission = _user_group.permission.permission_name
1271 usr.permission = _user_group.permission.permission_name
1272 perm_rows.append(usr)
1272 perm_rows.append(usr)
1273
1273
1274 return perm_rows
1274 return perm_rows
1275
1275
1276 def _get_default_perms(self, user_group, suffix=''):
1276 def _get_default_perms(self, user_group, suffix=''):
1277 from rhodecode.model.permission import PermissionModel
1277 from rhodecode.model.permission import PermissionModel
1278 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1278 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1279
1279
1280 def get_default_perms(self, suffix=''):
1280 def get_default_perms(self, suffix=''):
1281 return self._get_default_perms(self, suffix)
1281 return self._get_default_perms(self, suffix)
1282
1282
1283 def get_api_data(self, with_group_members=True, include_secrets=False):
1283 def get_api_data(self, with_group_members=True, include_secrets=False):
1284 """
1284 """
1285 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1285 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1286 basically forwarded.
1286 basically forwarded.
1287
1287
1288 """
1288 """
1289 user_group = self
1289 user_group = self
1290 data = {
1290 data = {
1291 'users_group_id': user_group.users_group_id,
1291 'users_group_id': user_group.users_group_id,
1292 'group_name': user_group.users_group_name,
1292 'group_name': user_group.users_group_name,
1293 'group_description': user_group.user_group_description,
1293 'group_description': user_group.user_group_description,
1294 'active': user_group.users_group_active,
1294 'active': user_group.users_group_active,
1295 'owner': user_group.user.username,
1295 'owner': user_group.user.username,
1296 'owner_email': user_group.user.email,
1296 'owner_email': user_group.user.email,
1297 }
1297 }
1298
1298
1299 if with_group_members:
1299 if with_group_members:
1300 users = []
1300 users = []
1301 for user in user_group.members:
1301 for user in user_group.members:
1302 user = user.user
1302 user = user.user
1303 users.append(user.get_api_data(include_secrets=include_secrets))
1303 users.append(user.get_api_data(include_secrets=include_secrets))
1304 data['users'] = users
1304 data['users'] = users
1305
1305
1306 return data
1306 return data
1307
1307
1308
1308
1309 class UserGroupMember(Base, BaseModel):
1309 class UserGroupMember(Base, BaseModel):
1310 __tablename__ = 'users_groups_members'
1310 __tablename__ = 'users_groups_members'
1311 __table_args__ = (
1311 __table_args__ = (
1312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1314 )
1314 )
1315
1315
1316 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1316 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1319
1319
1320 user = relationship('User', lazy='joined')
1320 user = relationship('User', lazy='joined')
1321 users_group = relationship('UserGroup')
1321 users_group = relationship('UserGroup')
1322
1322
1323 def __init__(self, gr_id='', u_id=''):
1323 def __init__(self, gr_id='', u_id=''):
1324 self.users_group_id = gr_id
1324 self.users_group_id = gr_id
1325 self.user_id = u_id
1325 self.user_id = u_id
1326
1326
1327
1327
1328 class RepositoryField(Base, BaseModel):
1328 class RepositoryField(Base, BaseModel):
1329 __tablename__ = 'repositories_fields'
1329 __tablename__ = 'repositories_fields'
1330 __table_args__ = (
1330 __table_args__ = (
1331 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1331 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1334 )
1334 )
1335 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1335 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1336
1336
1337 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1337 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1338 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1338 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1339 field_key = Column("field_key", String(250))
1339 field_key = Column("field_key", String(250))
1340 field_label = Column("field_label", String(1024), nullable=False)
1340 field_label = Column("field_label", String(1024), nullable=False)
1341 field_value = Column("field_value", String(10000), nullable=False)
1341 field_value = Column("field_value", String(10000), nullable=False)
1342 field_desc = Column("field_desc", String(1024), nullable=False)
1342 field_desc = Column("field_desc", String(1024), nullable=False)
1343 field_type = Column("field_type", String(255), nullable=False, unique=None)
1343 field_type = Column("field_type", String(255), nullable=False, unique=None)
1344 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1344 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1345
1345
1346 repository = relationship('Repository')
1346 repository = relationship('Repository')
1347
1347
1348 @property
1348 @property
1349 def field_key_prefixed(self):
1349 def field_key_prefixed(self):
1350 return 'ex_%s' % self.field_key
1350 return 'ex_%s' % self.field_key
1351
1351
1352 @classmethod
1352 @classmethod
1353 def un_prefix_key(cls, key):
1353 def un_prefix_key(cls, key):
1354 if key.startswith(cls.PREFIX):
1354 if key.startswith(cls.PREFIX):
1355 return key[len(cls.PREFIX):]
1355 return key[len(cls.PREFIX):]
1356 return key
1356 return key
1357
1357
1358 @classmethod
1358 @classmethod
1359 def get_by_key_name(cls, key, repo):
1359 def get_by_key_name(cls, key, repo):
1360 row = cls.query()\
1360 row = cls.query()\
1361 .filter(cls.repository == repo)\
1361 .filter(cls.repository == repo)\
1362 .filter(cls.field_key == key).scalar()
1362 .filter(cls.field_key == key).scalar()
1363 return row
1363 return row
1364
1364
1365
1365
1366 class Repository(Base, BaseModel):
1366 class Repository(Base, BaseModel):
1367 __tablename__ = 'repositories'
1367 __tablename__ = 'repositories'
1368 __table_args__ = (
1368 __table_args__ = (
1369 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1369 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1372 )
1372 )
1373 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1373 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1374 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1374 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1375
1375
1376 STATE_CREATED = 'repo_state_created'
1376 STATE_CREATED = 'repo_state_created'
1377 STATE_PENDING = 'repo_state_pending'
1377 STATE_PENDING = 'repo_state_pending'
1378 STATE_ERROR = 'repo_state_error'
1378 STATE_ERROR = 'repo_state_error'
1379
1379
1380 LOCK_AUTOMATIC = 'lock_auto'
1380 LOCK_AUTOMATIC = 'lock_auto'
1381 LOCK_API = 'lock_api'
1381 LOCK_API = 'lock_api'
1382 LOCK_WEB = 'lock_web'
1382 LOCK_WEB = 'lock_web'
1383 LOCK_PULL = 'lock_pull'
1383 LOCK_PULL = 'lock_pull'
1384
1384
1385 NAME_SEP = URL_SEP
1385 NAME_SEP = URL_SEP
1386
1386
1387 repo_id = Column(
1387 repo_id = Column(
1388 "repo_id", Integer(), nullable=False, unique=True, default=None,
1388 "repo_id", Integer(), nullable=False, unique=True, default=None,
1389 primary_key=True)
1389 primary_key=True)
1390 _repo_name = Column(
1390 _repo_name = Column(
1391 "repo_name", Text(), nullable=False, default=None)
1391 "repo_name", Text(), nullable=False, default=None)
1392 _repo_name_hash = Column(
1392 _repo_name_hash = Column(
1393 "repo_name_hash", String(255), nullable=False, unique=True)
1393 "repo_name_hash", String(255), nullable=False, unique=True)
1394 repo_state = Column("repo_state", String(255), nullable=True)
1394 repo_state = Column("repo_state", String(255), nullable=True)
1395
1395
1396 clone_uri = Column(
1396 clone_uri = Column(
1397 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1397 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1398 default=None)
1398 default=None)
1399 repo_type = Column(
1399 repo_type = Column(
1400 "repo_type", String(255), nullable=False, unique=False, default=None)
1400 "repo_type", String(255), nullable=False, unique=False, default=None)
1401 user_id = Column(
1401 user_id = Column(
1402 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1402 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1403 unique=False, default=None)
1403 unique=False, default=None)
1404 private = Column(
1404 private = Column(
1405 "private", Boolean(), nullable=True, unique=None, default=None)
1405 "private", Boolean(), nullable=True, unique=None, default=None)
1406 enable_statistics = Column(
1406 enable_statistics = Column(
1407 "statistics", Boolean(), nullable=True, unique=None, default=True)
1407 "statistics", Boolean(), nullable=True, unique=None, default=True)
1408 enable_downloads = Column(
1408 enable_downloads = Column(
1409 "downloads", Boolean(), nullable=True, unique=None, default=True)
1409 "downloads", Boolean(), nullable=True, unique=None, default=True)
1410 description = Column(
1410 description = Column(
1411 "description", String(10000), nullable=True, unique=None, default=None)
1411 "description", String(10000), nullable=True, unique=None, default=None)
1412 created_on = Column(
1412 created_on = Column(
1413 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1413 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1414 default=datetime.datetime.now)
1414 default=datetime.datetime.now)
1415 updated_on = Column(
1415 updated_on = Column(
1416 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1416 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1417 default=datetime.datetime.now)
1417 default=datetime.datetime.now)
1418 _landing_revision = Column(
1418 _landing_revision = Column(
1419 "landing_revision", String(255), nullable=False, unique=False,
1419 "landing_revision", String(255), nullable=False, unique=False,
1420 default=None)
1420 default=None)
1421 enable_locking = Column(
1421 enable_locking = Column(
1422 "enable_locking", Boolean(), nullable=False, unique=None,
1422 "enable_locking", Boolean(), nullable=False, unique=None,
1423 default=False)
1423 default=False)
1424 _locked = Column(
1424 _locked = Column(
1425 "locked", String(255), nullable=True, unique=False, default=None)
1425 "locked", String(255), nullable=True, unique=False, default=None)
1426 _changeset_cache = Column(
1426 _changeset_cache = Column(
1427 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1427 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1428
1428
1429 fork_id = Column(
1429 fork_id = Column(
1430 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1430 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1431 nullable=True, unique=False, default=None)
1431 nullable=True, unique=False, default=None)
1432 group_id = Column(
1432 group_id = Column(
1433 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1433 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1434 unique=False, default=None)
1434 unique=False, default=None)
1435
1435
1436 user = relationship('User', lazy='joined')
1436 user = relationship('User', lazy='joined')
1437 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1437 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1438 group = relationship('RepoGroup', lazy='joined')
1438 group = relationship('RepoGroup', lazy='joined')
1439 repo_to_perm = relationship(
1439 repo_to_perm = relationship(
1440 'UserRepoToPerm', cascade='all',
1440 'UserRepoToPerm', cascade='all',
1441 order_by='UserRepoToPerm.repo_to_perm_id')
1441 order_by='UserRepoToPerm.repo_to_perm_id')
1442 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1442 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1443 stats = relationship('Statistics', cascade='all', uselist=False)
1443 stats = relationship('Statistics', cascade='all', uselist=False)
1444
1444
1445 followers = relationship(
1445 followers = relationship(
1446 'UserFollowing',
1446 'UserFollowing',
1447 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1447 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1448 cascade='all')
1448 cascade='all')
1449 extra_fields = relationship(
1449 extra_fields = relationship(
1450 'RepositoryField', cascade="all, delete, delete-orphan")
1450 'RepositoryField', cascade="all, delete, delete-orphan")
1451 logs = relationship('UserLog')
1451 logs = relationship('UserLog')
1452 comments = relationship(
1452 comments = relationship(
1453 'ChangesetComment', cascade="all, delete, delete-orphan")
1453 'ChangesetComment', cascade="all, delete, delete-orphan")
1454 pull_requests_source = relationship(
1454 pull_requests_source = relationship(
1455 'PullRequest',
1455 'PullRequest',
1456 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1456 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1457 cascade="all, delete, delete-orphan")
1457 cascade="all, delete, delete-orphan")
1458 pull_requests_target = relationship(
1458 pull_requests_target = relationship(
1459 'PullRequest',
1459 'PullRequest',
1460 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1460 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1461 cascade="all, delete, delete-orphan")
1461 cascade="all, delete, delete-orphan")
1462 ui = relationship('RepoRhodeCodeUi', cascade="all")
1462 ui = relationship('RepoRhodeCodeUi', cascade="all")
1463 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1463 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1464 integrations = relationship('Integration',
1464 integrations = relationship('Integration',
1465 cascade="all, delete, delete-orphan")
1465 cascade="all, delete, delete-orphan")
1466
1466
1467 def __unicode__(self):
1467 def __unicode__(self):
1468 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1468 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1469 safe_unicode(self.repo_name))
1469 safe_unicode(self.repo_name))
1470
1470
1471 @hybrid_property
1471 @hybrid_property
1472 def landing_rev(self):
1472 def landing_rev(self):
1473 # always should return [rev_type, rev]
1473 # always should return [rev_type, rev]
1474 if self._landing_revision:
1474 if self._landing_revision:
1475 _rev_info = self._landing_revision.split(':')
1475 _rev_info = self._landing_revision.split(':')
1476 if len(_rev_info) < 2:
1476 if len(_rev_info) < 2:
1477 _rev_info.insert(0, 'rev')
1477 _rev_info.insert(0, 'rev')
1478 return [_rev_info[0], _rev_info[1]]
1478 return [_rev_info[0], _rev_info[1]]
1479 return [None, None]
1479 return [None, None]
1480
1480
1481 @landing_rev.setter
1481 @landing_rev.setter
1482 def landing_rev(self, val):
1482 def landing_rev(self, val):
1483 if ':' not in val:
1483 if ':' not in val:
1484 raise ValueError('value must be delimited with `:` and consist '
1484 raise ValueError('value must be delimited with `:` and consist '
1485 'of <rev_type>:<rev>, got %s instead' % val)
1485 'of <rev_type>:<rev>, got %s instead' % val)
1486 self._landing_revision = val
1486 self._landing_revision = val
1487
1487
1488 @hybrid_property
1488 @hybrid_property
1489 def locked(self):
1489 def locked(self):
1490 if self._locked:
1490 if self._locked:
1491 user_id, timelocked, reason = self._locked.split(':')
1491 user_id, timelocked, reason = self._locked.split(':')
1492 lock_values = int(user_id), timelocked, reason
1492 lock_values = int(user_id), timelocked, reason
1493 else:
1493 else:
1494 lock_values = [None, None, None]
1494 lock_values = [None, None, None]
1495 return lock_values
1495 return lock_values
1496
1496
1497 @locked.setter
1497 @locked.setter
1498 def locked(self, val):
1498 def locked(self, val):
1499 if val and isinstance(val, (list, tuple)):
1499 if val and isinstance(val, (list, tuple)):
1500 self._locked = ':'.join(map(str, val))
1500 self._locked = ':'.join(map(str, val))
1501 else:
1501 else:
1502 self._locked = None
1502 self._locked = None
1503
1503
1504 @hybrid_property
1504 @hybrid_property
1505 def changeset_cache(self):
1505 def changeset_cache(self):
1506 from rhodecode.lib.vcs.backends.base import EmptyCommit
1506 from rhodecode.lib.vcs.backends.base import EmptyCommit
1507 dummy = EmptyCommit().__json__()
1507 dummy = EmptyCommit().__json__()
1508 if not self._changeset_cache:
1508 if not self._changeset_cache:
1509 return dummy
1509 return dummy
1510 try:
1510 try:
1511 return json.loads(self._changeset_cache)
1511 return json.loads(self._changeset_cache)
1512 except TypeError:
1512 except TypeError:
1513 return dummy
1513 return dummy
1514 except Exception:
1514 except Exception:
1515 log.error(traceback.format_exc())
1515 log.error(traceback.format_exc())
1516 return dummy
1516 return dummy
1517
1517
1518 @changeset_cache.setter
1518 @changeset_cache.setter
1519 def changeset_cache(self, val):
1519 def changeset_cache(self, val):
1520 try:
1520 try:
1521 self._changeset_cache = json.dumps(val)
1521 self._changeset_cache = json.dumps(val)
1522 except Exception:
1522 except Exception:
1523 log.error(traceback.format_exc())
1523 log.error(traceback.format_exc())
1524
1524
1525 @hybrid_property
1525 @hybrid_property
1526 def repo_name(self):
1526 def repo_name(self):
1527 return self._repo_name
1527 return self._repo_name
1528
1528
1529 @repo_name.setter
1529 @repo_name.setter
1530 def repo_name(self, value):
1530 def repo_name(self, value):
1531 self._repo_name = value
1531 self._repo_name = value
1532 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1532 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1533
1533
1534 @classmethod
1534 @classmethod
1535 def normalize_repo_name(cls, repo_name):
1535 def normalize_repo_name(cls, repo_name):
1536 """
1536 """
1537 Normalizes os specific repo_name to the format internally stored inside
1537 Normalizes os specific repo_name to the format internally stored inside
1538 database using URL_SEP
1538 database using URL_SEP
1539
1539
1540 :param cls:
1540 :param cls:
1541 :param repo_name:
1541 :param repo_name:
1542 """
1542 """
1543 return cls.NAME_SEP.join(repo_name.split(os.sep))
1543 return cls.NAME_SEP.join(repo_name.split(os.sep))
1544
1544
1545 @classmethod
1545 @classmethod
1546 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1546 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1547 session = Session()
1547 session = Session()
1548 q = session.query(cls).filter(cls.repo_name == repo_name)
1548 q = session.query(cls).filter(cls.repo_name == repo_name)
1549
1549
1550 if cache:
1550 if cache:
1551 if identity_cache:
1551 if identity_cache:
1552 val = cls.identity_cache(session, 'repo_name', repo_name)
1552 val = cls.identity_cache(session, 'repo_name', repo_name)
1553 if val:
1553 if val:
1554 return val
1554 return val
1555 else:
1555 else:
1556 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1556 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1557 q = q.options(
1557 q = q.options(
1558 FromCache("sql_cache_short", cache_key))
1558 FromCache("sql_cache_short", cache_key))
1559
1559
1560 return q.scalar()
1560 return q.scalar()
1561
1561
1562 @classmethod
1562 @classmethod
1563 def get_by_full_path(cls, repo_full_path):
1563 def get_by_full_path(cls, repo_full_path):
1564 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1564 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1565 repo_name = cls.normalize_repo_name(repo_name)
1565 repo_name = cls.normalize_repo_name(repo_name)
1566 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1566 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1567
1567
1568 @classmethod
1568 @classmethod
1569 def get_repo_forks(cls, repo_id):
1569 def get_repo_forks(cls, repo_id):
1570 return cls.query().filter(Repository.fork_id == repo_id)
1570 return cls.query().filter(Repository.fork_id == repo_id)
1571
1571
1572 @classmethod
1572 @classmethod
1573 def base_path(cls):
1573 def base_path(cls):
1574 """
1574 """
1575 Returns base path when all repos are stored
1575 Returns base path when all repos are stored
1576
1576
1577 :param cls:
1577 :param cls:
1578 """
1578 """
1579 q = Session().query(RhodeCodeUi)\
1579 q = Session().query(RhodeCodeUi)\
1580 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1580 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1581 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1582 return q.one().ui_value
1582 return q.one().ui_value
1583
1583
1584 @classmethod
1584 @classmethod
1585 def is_valid(cls, repo_name):
1585 def is_valid(cls, repo_name):
1586 """
1586 """
1587 returns True if given repo name is a valid filesystem repository
1587 returns True if given repo name is a valid filesystem repository
1588
1588
1589 :param cls:
1589 :param cls:
1590 :param repo_name:
1590 :param repo_name:
1591 """
1591 """
1592 from rhodecode.lib.utils import is_valid_repo
1592 from rhodecode.lib.utils import is_valid_repo
1593
1593
1594 return is_valid_repo(repo_name, cls.base_path())
1594 return is_valid_repo(repo_name, cls.base_path())
1595
1595
1596 @classmethod
1596 @classmethod
1597 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1597 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1598 case_insensitive=True):
1598 case_insensitive=True):
1599 q = Repository.query()
1599 q = Repository.query()
1600
1600
1601 if not isinstance(user_id, Optional):
1601 if not isinstance(user_id, Optional):
1602 q = q.filter(Repository.user_id == user_id)
1602 q = q.filter(Repository.user_id == user_id)
1603
1603
1604 if not isinstance(group_id, Optional):
1604 if not isinstance(group_id, Optional):
1605 q = q.filter(Repository.group_id == group_id)
1605 q = q.filter(Repository.group_id == group_id)
1606
1606
1607 if case_insensitive:
1607 if case_insensitive:
1608 q = q.order_by(func.lower(Repository.repo_name))
1608 q = q.order_by(func.lower(Repository.repo_name))
1609 else:
1609 else:
1610 q = q.order_by(Repository.repo_name)
1610 q = q.order_by(Repository.repo_name)
1611 return q.all()
1611 return q.all()
1612
1612
1613 @property
1613 @property
1614 def forks(self):
1614 def forks(self):
1615 """
1615 """
1616 Return forks of this repo
1616 Return forks of this repo
1617 """
1617 """
1618 return Repository.get_repo_forks(self.repo_id)
1618 return Repository.get_repo_forks(self.repo_id)
1619
1619
1620 @property
1620 @property
1621 def parent(self):
1621 def parent(self):
1622 """
1622 """
1623 Returns fork parent
1623 Returns fork parent
1624 """
1624 """
1625 return self.fork
1625 return self.fork
1626
1626
1627 @property
1627 @property
1628 def just_name(self):
1628 def just_name(self):
1629 return self.repo_name.split(self.NAME_SEP)[-1]
1629 return self.repo_name.split(self.NAME_SEP)[-1]
1630
1630
1631 @property
1631 @property
1632 def groups_with_parents(self):
1632 def groups_with_parents(self):
1633 groups = []
1633 groups = []
1634 if self.group is None:
1634 if self.group is None:
1635 return groups
1635 return groups
1636
1636
1637 cur_gr = self.group
1637 cur_gr = self.group
1638 groups.insert(0, cur_gr)
1638 groups.insert(0, cur_gr)
1639 while 1:
1639 while 1:
1640 gr = getattr(cur_gr, 'parent_group', None)
1640 gr = getattr(cur_gr, 'parent_group', None)
1641 cur_gr = cur_gr.parent_group
1641 cur_gr = cur_gr.parent_group
1642 if gr is None:
1642 if gr is None:
1643 break
1643 break
1644 groups.insert(0, gr)
1644 groups.insert(0, gr)
1645
1645
1646 return groups
1646 return groups
1647
1647
1648 @property
1648 @property
1649 def groups_and_repo(self):
1649 def groups_and_repo(self):
1650 return self.groups_with_parents, self
1650 return self.groups_with_parents, self
1651
1651
1652 @LazyProperty
1652 @LazyProperty
1653 def repo_path(self):
1653 def repo_path(self):
1654 """
1654 """
1655 Returns base full path for that repository means where it actually
1655 Returns base full path for that repository means where it actually
1656 exists on a filesystem
1656 exists on a filesystem
1657 """
1657 """
1658 q = Session().query(RhodeCodeUi).filter(
1658 q = Session().query(RhodeCodeUi).filter(
1659 RhodeCodeUi.ui_key == self.NAME_SEP)
1659 RhodeCodeUi.ui_key == self.NAME_SEP)
1660 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1660 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1661 return q.one().ui_value
1661 return q.one().ui_value
1662
1662
1663 @property
1663 @property
1664 def repo_full_path(self):
1664 def repo_full_path(self):
1665 p = [self.repo_path]
1665 p = [self.repo_path]
1666 # we need to split the name by / since this is how we store the
1666 # we need to split the name by / since this is how we store the
1667 # names in the database, but that eventually needs to be converted
1667 # names in the database, but that eventually needs to be converted
1668 # into a valid system path
1668 # into a valid system path
1669 p += self.repo_name.split(self.NAME_SEP)
1669 p += self.repo_name.split(self.NAME_SEP)
1670 return os.path.join(*map(safe_unicode, p))
1670 return os.path.join(*map(safe_unicode, p))
1671
1671
1672 @property
1672 @property
1673 def cache_keys(self):
1673 def cache_keys(self):
1674 """
1674 """
1675 Returns associated cache keys for that repo
1675 Returns associated cache keys for that repo
1676 """
1676 """
1677 return CacheKey.query()\
1677 return CacheKey.query()\
1678 .filter(CacheKey.cache_args == self.repo_name)\
1678 .filter(CacheKey.cache_args == self.repo_name)\
1679 .order_by(CacheKey.cache_key)\
1679 .order_by(CacheKey.cache_key)\
1680 .all()
1680 .all()
1681
1681
1682 def get_new_name(self, repo_name):
1682 def get_new_name(self, repo_name):
1683 """
1683 """
1684 returns new full repository name based on assigned group and new new
1684 returns new full repository name based on assigned group and new new
1685
1685
1686 :param group_name:
1686 :param group_name:
1687 """
1687 """
1688 path_prefix = self.group.full_path_splitted if self.group else []
1688 path_prefix = self.group.full_path_splitted if self.group else []
1689 return self.NAME_SEP.join(path_prefix + [repo_name])
1689 return self.NAME_SEP.join(path_prefix + [repo_name])
1690
1690
1691 @property
1691 @property
1692 def _config(self):
1692 def _config(self):
1693 """
1693 """
1694 Returns db based config object.
1694 Returns db based config object.
1695 """
1695 """
1696 from rhodecode.lib.utils import make_db_config
1696 from rhodecode.lib.utils import make_db_config
1697 return make_db_config(clear_session=False, repo=self)
1697 return make_db_config(clear_session=False, repo=self)
1698
1698
1699 def permissions(self, with_admins=True, with_owner=True):
1699 def permissions(self, with_admins=True, with_owner=True):
1700 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1700 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1701 q = q.options(joinedload(UserRepoToPerm.repository),
1701 q = q.options(joinedload(UserRepoToPerm.repository),
1702 joinedload(UserRepoToPerm.user),
1702 joinedload(UserRepoToPerm.user),
1703 joinedload(UserRepoToPerm.permission),)
1703 joinedload(UserRepoToPerm.permission),)
1704
1704
1705 # get owners and admins and permissions. We do a trick of re-writing
1705 # get owners and admins and permissions. We do a trick of re-writing
1706 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1706 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1707 # has a global reference and changing one object propagates to all
1707 # has a global reference and changing one object propagates to all
1708 # others. This means if admin is also an owner admin_row that change
1708 # others. This means if admin is also an owner admin_row that change
1709 # would propagate to both objects
1709 # would propagate to both objects
1710 perm_rows = []
1710 perm_rows = []
1711 for _usr in q.all():
1711 for _usr in q.all():
1712 usr = AttributeDict(_usr.user.get_dict())
1712 usr = AttributeDict(_usr.user.get_dict())
1713 usr.permission = _usr.permission.permission_name
1713 usr.permission = _usr.permission.permission_name
1714 perm_rows.append(usr)
1714 perm_rows.append(usr)
1715
1715
1716 # filter the perm rows by 'default' first and then sort them by
1716 # filter the perm rows by 'default' first and then sort them by
1717 # admin,write,read,none permissions sorted again alphabetically in
1717 # admin,write,read,none permissions sorted again alphabetically in
1718 # each group
1718 # each group
1719 perm_rows = sorted(perm_rows, key=display_sort)
1719 perm_rows = sorted(perm_rows, key=display_sort)
1720
1720
1721 _admin_perm = 'repository.admin'
1721 _admin_perm = 'repository.admin'
1722 owner_row = []
1722 owner_row = []
1723 if with_owner:
1723 if with_owner:
1724 usr = AttributeDict(self.user.get_dict())
1724 usr = AttributeDict(self.user.get_dict())
1725 usr.owner_row = True
1725 usr.owner_row = True
1726 usr.permission = _admin_perm
1726 usr.permission = _admin_perm
1727 owner_row.append(usr)
1727 owner_row.append(usr)
1728
1728
1729 super_admin_rows = []
1729 super_admin_rows = []
1730 if with_admins:
1730 if with_admins:
1731 for usr in User.get_all_super_admins():
1731 for usr in User.get_all_super_admins():
1732 # if this admin is also owner, don't double the record
1732 # if this admin is also owner, don't double the record
1733 if usr.user_id == owner_row[0].user_id:
1733 if usr.user_id == owner_row[0].user_id:
1734 owner_row[0].admin_row = True
1734 owner_row[0].admin_row = True
1735 else:
1735 else:
1736 usr = AttributeDict(usr.get_dict())
1736 usr = AttributeDict(usr.get_dict())
1737 usr.admin_row = True
1737 usr.admin_row = True
1738 usr.permission = _admin_perm
1738 usr.permission = _admin_perm
1739 super_admin_rows.append(usr)
1739 super_admin_rows.append(usr)
1740
1740
1741 return super_admin_rows + owner_row + perm_rows
1741 return super_admin_rows + owner_row + perm_rows
1742
1742
1743 def permission_user_groups(self):
1743 def permission_user_groups(self):
1744 q = UserGroupRepoToPerm.query().filter(
1744 q = UserGroupRepoToPerm.query().filter(
1745 UserGroupRepoToPerm.repository == self)
1745 UserGroupRepoToPerm.repository == self)
1746 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1746 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1747 joinedload(UserGroupRepoToPerm.users_group),
1747 joinedload(UserGroupRepoToPerm.users_group),
1748 joinedload(UserGroupRepoToPerm.permission),)
1748 joinedload(UserGroupRepoToPerm.permission),)
1749
1749
1750 perm_rows = []
1750 perm_rows = []
1751 for _user_group in q.all():
1751 for _user_group in q.all():
1752 usr = AttributeDict(_user_group.users_group.get_dict())
1752 usr = AttributeDict(_user_group.users_group.get_dict())
1753 usr.permission = _user_group.permission.permission_name
1753 usr.permission = _user_group.permission.permission_name
1754 perm_rows.append(usr)
1754 perm_rows.append(usr)
1755
1755
1756 return perm_rows
1756 return perm_rows
1757
1757
1758 def get_api_data(self, include_secrets=False):
1758 def get_api_data(self, include_secrets=False):
1759 """
1759 """
1760 Common function for generating repo api data
1760 Common function for generating repo api data
1761
1761
1762 :param include_secrets: See :meth:`User.get_api_data`.
1762 :param include_secrets: See :meth:`User.get_api_data`.
1763
1763
1764 """
1764 """
1765 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1765 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1766 # move this methods on models level.
1766 # move this methods on models level.
1767 from rhodecode.model.settings import SettingsModel
1767 from rhodecode.model.settings import SettingsModel
1768 from rhodecode.model.repo import RepoModel
1768 from rhodecode.model.repo import RepoModel
1769
1769
1770 repo = self
1770 repo = self
1771 _user_id, _time, _reason = self.locked
1771 _user_id, _time, _reason = self.locked
1772
1772
1773 data = {
1773 data = {
1774 'repo_id': repo.repo_id,
1774 'repo_id': repo.repo_id,
1775 'repo_name': repo.repo_name,
1775 'repo_name': repo.repo_name,
1776 'repo_type': repo.repo_type,
1776 'repo_type': repo.repo_type,
1777 'clone_uri': repo.clone_uri or '',
1777 'clone_uri': repo.clone_uri or '',
1778 'url': RepoModel().get_url(self),
1778 'url': RepoModel().get_url(self),
1779 'private': repo.private,
1779 'private': repo.private,
1780 'created_on': repo.created_on,
1780 'created_on': repo.created_on,
1781 'description': repo.description,
1781 'description': repo.description,
1782 'landing_rev': repo.landing_rev,
1782 'landing_rev': repo.landing_rev,
1783 'owner': repo.user.username,
1783 'owner': repo.user.username,
1784 'fork_of': repo.fork.repo_name if repo.fork else None,
1784 'fork_of': repo.fork.repo_name if repo.fork else None,
1785 'enable_statistics': repo.enable_statistics,
1785 'enable_statistics': repo.enable_statistics,
1786 'enable_locking': repo.enable_locking,
1786 'enable_locking': repo.enable_locking,
1787 'enable_downloads': repo.enable_downloads,
1787 'enable_downloads': repo.enable_downloads,
1788 'last_changeset': repo.changeset_cache,
1788 'last_changeset': repo.changeset_cache,
1789 'locked_by': User.get(_user_id).get_api_data(
1789 'locked_by': User.get(_user_id).get_api_data(
1790 include_secrets=include_secrets) if _user_id else None,
1790 include_secrets=include_secrets) if _user_id else None,
1791 'locked_date': time_to_datetime(_time) if _time else None,
1791 'locked_date': time_to_datetime(_time) if _time else None,
1792 'lock_reason': _reason if _reason else None,
1792 'lock_reason': _reason if _reason else None,
1793 }
1793 }
1794
1794
1795 # TODO: mikhail: should be per-repo settings here
1795 # TODO: mikhail: should be per-repo settings here
1796 rc_config = SettingsModel().get_all_settings()
1796 rc_config = SettingsModel().get_all_settings()
1797 repository_fields = str2bool(
1797 repository_fields = str2bool(
1798 rc_config.get('rhodecode_repository_fields'))
1798 rc_config.get('rhodecode_repository_fields'))
1799 if repository_fields:
1799 if repository_fields:
1800 for f in self.extra_fields:
1800 for f in self.extra_fields:
1801 data[f.field_key_prefixed] = f.field_value
1801 data[f.field_key_prefixed] = f.field_value
1802
1802
1803 return data
1803 return data
1804
1804
1805 @classmethod
1805 @classmethod
1806 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1806 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1807 if not lock_time:
1807 if not lock_time:
1808 lock_time = time.time()
1808 lock_time = time.time()
1809 if not lock_reason:
1809 if not lock_reason:
1810 lock_reason = cls.LOCK_AUTOMATIC
1810 lock_reason = cls.LOCK_AUTOMATIC
1811 repo.locked = [user_id, lock_time, lock_reason]
1811 repo.locked = [user_id, lock_time, lock_reason]
1812 Session().add(repo)
1812 Session().add(repo)
1813 Session().commit()
1813 Session().commit()
1814
1814
1815 @classmethod
1815 @classmethod
1816 def unlock(cls, repo):
1816 def unlock(cls, repo):
1817 repo.locked = None
1817 repo.locked = None
1818 Session().add(repo)
1818 Session().add(repo)
1819 Session().commit()
1819 Session().commit()
1820
1820
1821 @classmethod
1821 @classmethod
1822 def getlock(cls, repo):
1822 def getlock(cls, repo):
1823 return repo.locked
1823 return repo.locked
1824
1824
1825 def is_user_lock(self, user_id):
1825 def is_user_lock(self, user_id):
1826 if self.lock[0]:
1826 if self.lock[0]:
1827 lock_user_id = safe_int(self.lock[0])
1827 lock_user_id = safe_int(self.lock[0])
1828 user_id = safe_int(user_id)
1828 user_id = safe_int(user_id)
1829 # both are ints, and they are equal
1829 # both are ints, and they are equal
1830 return all([lock_user_id, user_id]) and lock_user_id == user_id
1830 return all([lock_user_id, user_id]) and lock_user_id == user_id
1831
1831
1832 return False
1832 return False
1833
1833
1834 def get_locking_state(self, action, user_id, only_when_enabled=True):
1834 def get_locking_state(self, action, user_id, only_when_enabled=True):
1835 """
1835 """
1836 Checks locking on this repository, if locking is enabled and lock is
1836 Checks locking on this repository, if locking is enabled and lock is
1837 present returns a tuple of make_lock, locked, locked_by.
1837 present returns a tuple of make_lock, locked, locked_by.
1838 make_lock can have 3 states None (do nothing) True, make lock
1838 make_lock can have 3 states None (do nothing) True, make lock
1839 False release lock, This value is later propagated to hooks, which
1839 False release lock, This value is later propagated to hooks, which
1840 do the locking. Think about this as signals passed to hooks what to do.
1840 do the locking. Think about this as signals passed to hooks what to do.
1841
1841
1842 """
1842 """
1843 # TODO: johbo: This is part of the business logic and should be moved
1843 # TODO: johbo: This is part of the business logic and should be moved
1844 # into the RepositoryModel.
1844 # into the RepositoryModel.
1845
1845
1846 if action not in ('push', 'pull'):
1846 if action not in ('push', 'pull'):
1847 raise ValueError("Invalid action value: %s" % repr(action))
1847 raise ValueError("Invalid action value: %s" % repr(action))
1848
1848
1849 # defines if locked error should be thrown to user
1849 # defines if locked error should be thrown to user
1850 currently_locked = False
1850 currently_locked = False
1851 # defines if new lock should be made, tri-state
1851 # defines if new lock should be made, tri-state
1852 make_lock = None
1852 make_lock = None
1853 repo = self
1853 repo = self
1854 user = User.get(user_id)
1854 user = User.get(user_id)
1855
1855
1856 lock_info = repo.locked
1856 lock_info = repo.locked
1857
1857
1858 if repo and (repo.enable_locking or not only_when_enabled):
1858 if repo and (repo.enable_locking or not only_when_enabled):
1859 if action == 'push':
1859 if action == 'push':
1860 # check if it's already locked !, if it is compare users
1860 # check if it's already locked !, if it is compare users
1861 locked_by_user_id = lock_info[0]
1861 locked_by_user_id = lock_info[0]
1862 if user.user_id == locked_by_user_id:
1862 if user.user_id == locked_by_user_id:
1863 log.debug(
1863 log.debug(
1864 'Got `push` action from user %s, now unlocking', user)
1864 'Got `push` action from user %s, now unlocking', user)
1865 # unlock if we have push from user who locked
1865 # unlock if we have push from user who locked
1866 make_lock = False
1866 make_lock = False
1867 else:
1867 else:
1868 # we're not the same user who locked, ban with
1868 # we're not the same user who locked, ban with
1869 # code defined in settings (default is 423 HTTP Locked) !
1869 # code defined in settings (default is 423 HTTP Locked) !
1870 log.debug('Repo %s is currently locked by %s', repo, user)
1870 log.debug('Repo %s is currently locked by %s', repo, user)
1871 currently_locked = True
1871 currently_locked = True
1872 elif action == 'pull':
1872 elif action == 'pull':
1873 # [0] user [1] date
1873 # [0] user [1] date
1874 if lock_info[0] and lock_info[1]:
1874 if lock_info[0] and lock_info[1]:
1875 log.debug('Repo %s is currently locked by %s', repo, user)
1875 log.debug('Repo %s is currently locked by %s', repo, user)
1876 currently_locked = True
1876 currently_locked = True
1877 else:
1877 else:
1878 log.debug('Setting lock on repo %s by %s', repo, user)
1878 log.debug('Setting lock on repo %s by %s', repo, user)
1879 make_lock = True
1879 make_lock = True
1880
1880
1881 else:
1881 else:
1882 log.debug('Repository %s do not have locking enabled', repo)
1882 log.debug('Repository %s do not have locking enabled', repo)
1883
1883
1884 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1884 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1885 make_lock, currently_locked, lock_info)
1885 make_lock, currently_locked, lock_info)
1886
1886
1887 from rhodecode.lib.auth import HasRepoPermissionAny
1887 from rhodecode.lib.auth import HasRepoPermissionAny
1888 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1888 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1889 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1889 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1890 # if we don't have at least write permission we cannot make a lock
1890 # if we don't have at least write permission we cannot make a lock
1891 log.debug('lock state reset back to FALSE due to lack '
1891 log.debug('lock state reset back to FALSE due to lack '
1892 'of at least read permission')
1892 'of at least read permission')
1893 make_lock = False
1893 make_lock = False
1894
1894
1895 return make_lock, currently_locked, lock_info
1895 return make_lock, currently_locked, lock_info
1896
1896
1897 @property
1897 @property
1898 def last_db_change(self):
1898 def last_db_change(self):
1899 return self.updated_on
1899 return self.updated_on
1900
1900
1901 @property
1901 @property
1902 def clone_uri_hidden(self):
1902 def clone_uri_hidden(self):
1903 clone_uri = self.clone_uri
1903 clone_uri = self.clone_uri
1904 if clone_uri:
1904 if clone_uri:
1905 import urlobject
1905 import urlobject
1906 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1906 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1907 if url_obj.password:
1907 if url_obj.password:
1908 clone_uri = url_obj.with_password('*****')
1908 clone_uri = url_obj.with_password('*****')
1909 return clone_uri
1909 return clone_uri
1910
1910
1911 def clone_url(self, **override):
1911 def clone_url(self, **override):
1912
1912
1913 uri_tmpl = None
1913 uri_tmpl = None
1914 if 'with_id' in override:
1914 if 'with_id' in override:
1915 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1915 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1916 del override['with_id']
1916 del override['with_id']
1917
1917
1918 if 'uri_tmpl' in override:
1918 if 'uri_tmpl' in override:
1919 uri_tmpl = override['uri_tmpl']
1919 uri_tmpl = override['uri_tmpl']
1920 del override['uri_tmpl']
1920 del override['uri_tmpl']
1921
1921
1922 # we didn't override our tmpl from **overrides
1922 # we didn't override our tmpl from **overrides
1923 if not uri_tmpl:
1923 if not uri_tmpl:
1924 uri_tmpl = self.DEFAULT_CLONE_URI
1924 uri_tmpl = self.DEFAULT_CLONE_URI
1925 try:
1925 try:
1926 from pylons import tmpl_context as c
1926 from pylons import tmpl_context as c
1927 uri_tmpl = c.clone_uri_tmpl
1927 uri_tmpl = c.clone_uri_tmpl
1928 except Exception:
1928 except Exception:
1929 # in any case if we call this outside of request context,
1929 # in any case if we call this outside of request context,
1930 # ie, not having tmpl_context set up
1930 # ie, not having tmpl_context set up
1931 pass
1931 pass
1932
1932
1933 request = get_current_request()
1933 request = get_current_request()
1934 return get_clone_url(request=request,
1934 return get_clone_url(request=request,
1935 uri_tmpl=uri_tmpl,
1935 uri_tmpl=uri_tmpl,
1936 repo_name=self.repo_name,
1936 repo_name=self.repo_name,
1937 repo_id=self.repo_id, **override)
1937 repo_id=self.repo_id, **override)
1938
1938
1939 def set_state(self, state):
1939 def set_state(self, state):
1940 self.repo_state = state
1940 self.repo_state = state
1941 Session().add(self)
1941 Session().add(self)
1942 #==========================================================================
1942 #==========================================================================
1943 # SCM PROPERTIES
1943 # SCM PROPERTIES
1944 #==========================================================================
1944 #==========================================================================
1945
1945
1946 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1946 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1947 return get_commit_safe(
1947 return get_commit_safe(
1948 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1948 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1949
1949
1950 def get_changeset(self, rev=None, pre_load=None):
1950 def get_changeset(self, rev=None, pre_load=None):
1951 warnings.warn("Use get_commit", DeprecationWarning)
1951 warnings.warn("Use get_commit", DeprecationWarning)
1952 commit_id = None
1952 commit_id = None
1953 commit_idx = None
1953 commit_idx = None
1954 if isinstance(rev, basestring):
1954 if isinstance(rev, basestring):
1955 commit_id = rev
1955 commit_id = rev
1956 else:
1956 else:
1957 commit_idx = rev
1957 commit_idx = rev
1958 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1958 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1959 pre_load=pre_load)
1959 pre_load=pre_load)
1960
1960
1961 def get_landing_commit(self):
1961 def get_landing_commit(self):
1962 """
1962 """
1963 Returns landing commit, or if that doesn't exist returns the tip
1963 Returns landing commit, or if that doesn't exist returns the tip
1964 """
1964 """
1965 _rev_type, _rev = self.landing_rev
1965 _rev_type, _rev = self.landing_rev
1966 commit = self.get_commit(_rev)
1966 commit = self.get_commit(_rev)
1967 if isinstance(commit, EmptyCommit):
1967 if isinstance(commit, EmptyCommit):
1968 return self.get_commit()
1968 return self.get_commit()
1969 return commit
1969 return commit
1970
1970
1971 def update_commit_cache(self, cs_cache=None, config=None):
1971 def update_commit_cache(self, cs_cache=None, config=None):
1972 """
1972 """
1973 Update cache of last changeset for repository, keys should be::
1973 Update cache of last changeset for repository, keys should be::
1974
1974
1975 short_id
1975 short_id
1976 raw_id
1976 raw_id
1977 revision
1977 revision
1978 parents
1978 parents
1979 message
1979 message
1980 date
1980 date
1981 author
1981 author
1982
1982
1983 :param cs_cache:
1983 :param cs_cache:
1984 """
1984 """
1985 from rhodecode.lib.vcs.backends.base import BaseChangeset
1985 from rhodecode.lib.vcs.backends.base import BaseChangeset
1986 if cs_cache is None:
1986 if cs_cache is None:
1987 # use no-cache version here
1987 # use no-cache version here
1988 scm_repo = self.scm_instance(cache=False, config=config)
1988 scm_repo = self.scm_instance(cache=False, config=config)
1989 if scm_repo:
1989 if scm_repo:
1990 cs_cache = scm_repo.get_commit(
1990 cs_cache = scm_repo.get_commit(
1991 pre_load=["author", "date", "message", "parents"])
1991 pre_load=["author", "date", "message", "parents"])
1992 else:
1992 else:
1993 cs_cache = EmptyCommit()
1993 cs_cache = EmptyCommit()
1994
1994
1995 if isinstance(cs_cache, BaseChangeset):
1995 if isinstance(cs_cache, BaseChangeset):
1996 cs_cache = cs_cache.__json__()
1996 cs_cache = cs_cache.__json__()
1997
1997
1998 def is_outdated(new_cs_cache):
1998 def is_outdated(new_cs_cache):
1999 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1999 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2000 new_cs_cache['revision'] != self.changeset_cache['revision']):
2000 new_cs_cache['revision'] != self.changeset_cache['revision']):
2001 return True
2001 return True
2002 return False
2002 return False
2003
2003
2004 # check if we have maybe already latest cached revision
2004 # check if we have maybe already latest cached revision
2005 if is_outdated(cs_cache) or not self.changeset_cache:
2005 if is_outdated(cs_cache) or not self.changeset_cache:
2006 _default = datetime.datetime.fromtimestamp(0)
2006 _default = datetime.datetime.fromtimestamp(0)
2007 last_change = cs_cache.get('date') or _default
2007 last_change = cs_cache.get('date') or _default
2008 log.debug('updated repo %s with new cs cache %s',
2008 log.debug('updated repo %s with new cs cache %s',
2009 self.repo_name, cs_cache)
2009 self.repo_name, cs_cache)
2010 self.updated_on = last_change
2010 self.updated_on = last_change
2011 self.changeset_cache = cs_cache
2011 self.changeset_cache = cs_cache
2012 Session().add(self)
2012 Session().add(self)
2013 Session().commit()
2013 Session().commit()
2014 else:
2014 else:
2015 log.debug('Skipping update_commit_cache for repo:`%s` '
2015 log.debug('Skipping update_commit_cache for repo:`%s` '
2016 'commit already with latest changes', self.repo_name)
2016 'commit already with latest changes', self.repo_name)
2017
2017
2018 @property
2018 @property
2019 def tip(self):
2019 def tip(self):
2020 return self.get_commit('tip')
2020 return self.get_commit('tip')
2021
2021
2022 @property
2022 @property
2023 def author(self):
2023 def author(self):
2024 return self.tip.author
2024 return self.tip.author
2025
2025
2026 @property
2026 @property
2027 def last_change(self):
2027 def last_change(self):
2028 return self.scm_instance().last_change
2028 return self.scm_instance().last_change
2029
2029
2030 def get_comments(self, revisions=None):
2030 def get_comments(self, revisions=None):
2031 """
2031 """
2032 Returns comments for this repository grouped by revisions
2032 Returns comments for this repository grouped by revisions
2033
2033
2034 :param revisions: filter query by revisions only
2034 :param revisions: filter query by revisions only
2035 """
2035 """
2036 cmts = ChangesetComment.query()\
2036 cmts = ChangesetComment.query()\
2037 .filter(ChangesetComment.repo == self)
2037 .filter(ChangesetComment.repo == self)
2038 if revisions:
2038 if revisions:
2039 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2039 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2040 grouped = collections.defaultdict(list)
2040 grouped = collections.defaultdict(list)
2041 for cmt in cmts.all():
2041 for cmt in cmts.all():
2042 grouped[cmt.revision].append(cmt)
2042 grouped[cmt.revision].append(cmt)
2043 return grouped
2043 return grouped
2044
2044
2045 def statuses(self, revisions=None):
2045 def statuses(self, revisions=None):
2046 """
2046 """
2047 Returns statuses for this repository
2047 Returns statuses for this repository
2048
2048
2049 :param revisions: list of revisions to get statuses for
2049 :param revisions: list of revisions to get statuses for
2050 """
2050 """
2051 statuses = ChangesetStatus.query()\
2051 statuses = ChangesetStatus.query()\
2052 .filter(ChangesetStatus.repo == self)\
2052 .filter(ChangesetStatus.repo == self)\
2053 .filter(ChangesetStatus.version == 0)
2053 .filter(ChangesetStatus.version == 0)
2054
2054
2055 if revisions:
2055 if revisions:
2056 # Try doing the filtering in chunks to avoid hitting limits
2056 # Try doing the filtering in chunks to avoid hitting limits
2057 size = 500
2057 size = 500
2058 status_results = []
2058 status_results = []
2059 for chunk in xrange(0, len(revisions), size):
2059 for chunk in xrange(0, len(revisions), size):
2060 status_results += statuses.filter(
2060 status_results += statuses.filter(
2061 ChangesetStatus.revision.in_(
2061 ChangesetStatus.revision.in_(
2062 revisions[chunk: chunk+size])
2062 revisions[chunk: chunk+size])
2063 ).all()
2063 ).all()
2064 else:
2064 else:
2065 status_results = statuses.all()
2065 status_results = statuses.all()
2066
2066
2067 grouped = {}
2067 grouped = {}
2068
2068
2069 # maybe we have open new pullrequest without a status?
2069 # maybe we have open new pullrequest without a status?
2070 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2070 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2071 status_lbl = ChangesetStatus.get_status_lbl(stat)
2071 status_lbl = ChangesetStatus.get_status_lbl(stat)
2072 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2072 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2073 for rev in pr.revisions:
2073 for rev in pr.revisions:
2074 pr_id = pr.pull_request_id
2074 pr_id = pr.pull_request_id
2075 pr_repo = pr.target_repo.repo_name
2075 pr_repo = pr.target_repo.repo_name
2076 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2076 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2077
2077
2078 for stat in status_results:
2078 for stat in status_results:
2079 pr_id = pr_repo = None
2079 pr_id = pr_repo = None
2080 if stat.pull_request:
2080 if stat.pull_request:
2081 pr_id = stat.pull_request.pull_request_id
2081 pr_id = stat.pull_request.pull_request_id
2082 pr_repo = stat.pull_request.target_repo.repo_name
2082 pr_repo = stat.pull_request.target_repo.repo_name
2083 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2083 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2084 pr_id, pr_repo]
2084 pr_id, pr_repo]
2085 return grouped
2085 return grouped
2086
2086
2087 # ==========================================================================
2087 # ==========================================================================
2088 # SCM CACHE INSTANCE
2088 # SCM CACHE INSTANCE
2089 # ==========================================================================
2089 # ==========================================================================
2090
2090
2091 def scm_instance(self, **kwargs):
2091 def scm_instance(self, **kwargs):
2092 import rhodecode
2092 import rhodecode
2093
2093
2094 # Passing a config will not hit the cache currently only used
2094 # Passing a config will not hit the cache currently only used
2095 # for repo2dbmapper
2095 # for repo2dbmapper
2096 config = kwargs.pop('config', None)
2096 config = kwargs.pop('config', None)
2097 cache = kwargs.pop('cache', None)
2097 cache = kwargs.pop('cache', None)
2098 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2098 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2099 # if cache is NOT defined use default global, else we have a full
2099 # if cache is NOT defined use default global, else we have a full
2100 # control over cache behaviour
2100 # control over cache behaviour
2101 if cache is None and full_cache and not config:
2101 if cache is None and full_cache and not config:
2102 return self._get_instance_cached()
2102 return self._get_instance_cached()
2103 return self._get_instance(cache=bool(cache), config=config)
2103 return self._get_instance(cache=bool(cache), config=config)
2104
2104
2105 def _get_instance_cached(self):
2105 def _get_instance_cached(self):
2106 @cache_region('long_term')
2106 @cache_region('long_term')
2107 def _get_repo(cache_key):
2107 def _get_repo(cache_key):
2108 return self._get_instance()
2108 return self._get_instance()
2109
2109
2110 invalidator_context = CacheKey.repo_context_cache(
2110 invalidator_context = CacheKey.repo_context_cache(
2111 _get_repo, self.repo_name, None, thread_scoped=True)
2111 _get_repo, self.repo_name, None, thread_scoped=True)
2112
2112
2113 with invalidator_context as context:
2113 with invalidator_context as context:
2114 context.invalidate()
2114 context.invalidate()
2115 repo = context.compute()
2115 repo = context.compute()
2116
2116
2117 return repo
2117 return repo
2118
2118
2119 def _get_instance(self, cache=True, config=None):
2119 def _get_instance(self, cache=True, config=None):
2120 config = config or self._config
2120 config = config or self._config
2121 custom_wire = {
2121 custom_wire = {
2122 'cache': cache # controls the vcs.remote cache
2122 'cache': cache # controls the vcs.remote cache
2123 }
2123 }
2124 repo = get_vcs_instance(
2124 repo = get_vcs_instance(
2125 repo_path=safe_str(self.repo_full_path),
2125 repo_path=safe_str(self.repo_full_path),
2126 config=config,
2126 config=config,
2127 with_wire=custom_wire,
2127 with_wire=custom_wire,
2128 create=False,
2128 create=False,
2129 _vcs_alias=self.repo_type)
2129 _vcs_alias=self.repo_type)
2130
2130
2131 return repo
2131 return repo
2132
2132
2133 def __json__(self):
2133 def __json__(self):
2134 return {'landing_rev': self.landing_rev}
2134 return {'landing_rev': self.landing_rev}
2135
2135
2136 def get_dict(self):
2136 def get_dict(self):
2137
2137
2138 # Since we transformed `repo_name` to a hybrid property, we need to
2138 # Since we transformed `repo_name` to a hybrid property, we need to
2139 # keep compatibility with the code which uses `repo_name` field.
2139 # keep compatibility with the code which uses `repo_name` field.
2140
2140
2141 result = super(Repository, self).get_dict()
2141 result = super(Repository, self).get_dict()
2142 result['repo_name'] = result.pop('_repo_name', None)
2142 result['repo_name'] = result.pop('_repo_name', None)
2143 return result
2143 return result
2144
2144
2145
2145
2146 class RepoGroup(Base, BaseModel):
2146 class RepoGroup(Base, BaseModel):
2147 __tablename__ = 'groups'
2147 __tablename__ = 'groups'
2148 __table_args__ = (
2148 __table_args__ = (
2149 UniqueConstraint('group_name', 'group_parent_id'),
2149 UniqueConstraint('group_name', 'group_parent_id'),
2150 CheckConstraint('group_id != group_parent_id'),
2150 CheckConstraint('group_id != group_parent_id'),
2151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2153 )
2153 )
2154 __mapper_args__ = {'order_by': 'group_name'}
2154 __mapper_args__ = {'order_by': 'group_name'}
2155
2155
2156 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2156 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2157
2157
2158 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2158 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2159 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2159 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2160 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2160 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2161 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2161 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2162 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2162 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2163 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2163 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2164 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2164 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2165 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2165 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2166
2166
2167 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2167 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2168 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2168 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2169 parent_group = relationship('RepoGroup', remote_side=group_id)
2169 parent_group = relationship('RepoGroup', remote_side=group_id)
2170 user = relationship('User')
2170 user = relationship('User')
2171 integrations = relationship('Integration',
2171 integrations = relationship('Integration',
2172 cascade="all, delete, delete-orphan")
2172 cascade="all, delete, delete-orphan")
2173
2173
2174 def __init__(self, group_name='', parent_group=None):
2174 def __init__(self, group_name='', parent_group=None):
2175 self.group_name = group_name
2175 self.group_name = group_name
2176 self.parent_group = parent_group
2176 self.parent_group = parent_group
2177
2177
2178 def __unicode__(self):
2178 def __unicode__(self):
2179 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2179 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2180 self.group_name)
2180 self.group_name)
2181
2181
2182 @classmethod
2182 @classmethod
2183 def _generate_choice(cls, repo_group):
2183 def _generate_choice(cls, repo_group):
2184 from webhelpers.html import literal as _literal
2184 from webhelpers.html import literal as _literal
2185 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2185 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2186 return repo_group.group_id, _name(repo_group.full_path_splitted)
2186 return repo_group.group_id, _name(repo_group.full_path_splitted)
2187
2187
2188 @classmethod
2188 @classmethod
2189 def groups_choices(cls, groups=None, show_empty_group=True):
2189 def groups_choices(cls, groups=None, show_empty_group=True):
2190 if not groups:
2190 if not groups:
2191 groups = cls.query().all()
2191 groups = cls.query().all()
2192
2192
2193 repo_groups = []
2193 repo_groups = []
2194 if show_empty_group:
2194 if show_empty_group:
2195 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2195 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2196
2196
2197 repo_groups.extend([cls._generate_choice(x) for x in groups])
2197 repo_groups.extend([cls._generate_choice(x) for x in groups])
2198
2198
2199 repo_groups = sorted(
2199 repo_groups = sorted(
2200 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2200 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2201 return repo_groups
2201 return repo_groups
2202
2202
2203 @classmethod
2203 @classmethod
2204 def url_sep(cls):
2204 def url_sep(cls):
2205 return URL_SEP
2205 return URL_SEP
2206
2206
2207 @classmethod
2207 @classmethod
2208 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2208 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2209 if case_insensitive:
2209 if case_insensitive:
2210 gr = cls.query().filter(func.lower(cls.group_name)
2210 gr = cls.query().filter(func.lower(cls.group_name)
2211 == func.lower(group_name))
2211 == func.lower(group_name))
2212 else:
2212 else:
2213 gr = cls.query().filter(cls.group_name == group_name)
2213 gr = cls.query().filter(cls.group_name == group_name)
2214 if cache:
2214 if cache:
2215 name_key = _hash_key(group_name)
2215 name_key = _hash_key(group_name)
2216 gr = gr.options(
2216 gr = gr.options(
2217 FromCache("sql_cache_short", "get_group_%s" % name_key))
2217 FromCache("sql_cache_short", "get_group_%s" % name_key))
2218 return gr.scalar()
2218 return gr.scalar()
2219
2219
2220 @classmethod
2220 @classmethod
2221 def get_user_personal_repo_group(cls, user_id):
2221 def get_user_personal_repo_group(cls, user_id):
2222 user = User.get(user_id)
2222 user = User.get(user_id)
2223 if user.username == User.DEFAULT_USER:
2223 if user.username == User.DEFAULT_USER:
2224 return None
2224 return None
2225
2225
2226 return cls.query()\
2226 return cls.query()\
2227 .filter(cls.personal == true()) \
2227 .filter(cls.personal == true()) \
2228 .filter(cls.user == user).scalar()
2228 .filter(cls.user == user).scalar()
2229
2229
2230 @classmethod
2230 @classmethod
2231 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2231 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2232 case_insensitive=True):
2232 case_insensitive=True):
2233 q = RepoGroup.query()
2233 q = RepoGroup.query()
2234
2234
2235 if not isinstance(user_id, Optional):
2235 if not isinstance(user_id, Optional):
2236 q = q.filter(RepoGroup.user_id == user_id)
2236 q = q.filter(RepoGroup.user_id == user_id)
2237
2237
2238 if not isinstance(group_id, Optional):
2238 if not isinstance(group_id, Optional):
2239 q = q.filter(RepoGroup.group_parent_id == group_id)
2239 q = q.filter(RepoGroup.group_parent_id == group_id)
2240
2240
2241 if case_insensitive:
2241 if case_insensitive:
2242 q = q.order_by(func.lower(RepoGroup.group_name))
2242 q = q.order_by(func.lower(RepoGroup.group_name))
2243 else:
2243 else:
2244 q = q.order_by(RepoGroup.group_name)
2244 q = q.order_by(RepoGroup.group_name)
2245 return q.all()
2245 return q.all()
2246
2246
2247 @property
2247 @property
2248 def parents(self):
2248 def parents(self):
2249 parents_recursion_limit = 10
2249 parents_recursion_limit = 10
2250 groups = []
2250 groups = []
2251 if self.parent_group is None:
2251 if self.parent_group is None:
2252 return groups
2252 return groups
2253 cur_gr = self.parent_group
2253 cur_gr = self.parent_group
2254 groups.insert(0, cur_gr)
2254 groups.insert(0, cur_gr)
2255 cnt = 0
2255 cnt = 0
2256 while 1:
2256 while 1:
2257 cnt += 1
2257 cnt += 1
2258 gr = getattr(cur_gr, 'parent_group', None)
2258 gr = getattr(cur_gr, 'parent_group', None)
2259 cur_gr = cur_gr.parent_group
2259 cur_gr = cur_gr.parent_group
2260 if gr is None:
2260 if gr is None:
2261 break
2261 break
2262 if cnt == parents_recursion_limit:
2262 if cnt == parents_recursion_limit:
2263 # this will prevent accidental infinit loops
2263 # this will prevent accidental infinit loops
2264 log.error(('more than %s parents found for group %s, stopping '
2264 log.error(('more than %s parents found for group %s, stopping '
2265 'recursive parent fetching' % (parents_recursion_limit, self)))
2265 'recursive parent fetching' % (parents_recursion_limit, self)))
2266 break
2266 break
2267
2267
2268 groups.insert(0, gr)
2268 groups.insert(0, gr)
2269 return groups
2269 return groups
2270
2270
2271 @property
2271 @property
2272 def children(self):
2272 def children(self):
2273 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2273 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2274
2274
2275 @property
2275 @property
2276 def name(self):
2276 def name(self):
2277 return self.group_name.split(RepoGroup.url_sep())[-1]
2277 return self.group_name.split(RepoGroup.url_sep())[-1]
2278
2278
2279 @property
2279 @property
2280 def full_path(self):
2280 def full_path(self):
2281 return self.group_name
2281 return self.group_name
2282
2282
2283 @property
2283 @property
2284 def full_path_splitted(self):
2284 def full_path_splitted(self):
2285 return self.group_name.split(RepoGroup.url_sep())
2285 return self.group_name.split(RepoGroup.url_sep())
2286
2286
2287 @property
2287 @property
2288 def repositories(self):
2288 def repositories(self):
2289 return Repository.query()\
2289 return Repository.query()\
2290 .filter(Repository.group == self)\
2290 .filter(Repository.group == self)\
2291 .order_by(Repository.repo_name)
2291 .order_by(Repository.repo_name)
2292
2292
2293 @property
2293 @property
2294 def repositories_recursive_count(self):
2294 def repositories_recursive_count(self):
2295 cnt = self.repositories.count()
2295 cnt = self.repositories.count()
2296
2296
2297 def children_count(group):
2297 def children_count(group):
2298 cnt = 0
2298 cnt = 0
2299 for child in group.children:
2299 for child in group.children:
2300 cnt += child.repositories.count()
2300 cnt += child.repositories.count()
2301 cnt += children_count(child)
2301 cnt += children_count(child)
2302 return cnt
2302 return cnt
2303
2303
2304 return cnt + children_count(self)
2304 return cnt + children_count(self)
2305
2305
2306 def _recursive_objects(self, include_repos=True):
2306 def _recursive_objects(self, include_repos=True):
2307 all_ = []
2307 all_ = []
2308
2308
2309 def _get_members(root_gr):
2309 def _get_members(root_gr):
2310 if include_repos:
2310 if include_repos:
2311 for r in root_gr.repositories:
2311 for r in root_gr.repositories:
2312 all_.append(r)
2312 all_.append(r)
2313 childs = root_gr.children.all()
2313 childs = root_gr.children.all()
2314 if childs:
2314 if childs:
2315 for gr in childs:
2315 for gr in childs:
2316 all_.append(gr)
2316 all_.append(gr)
2317 _get_members(gr)
2317 _get_members(gr)
2318
2318
2319 _get_members(self)
2319 _get_members(self)
2320 return [self] + all_
2320 return [self] + all_
2321
2321
2322 def recursive_groups_and_repos(self):
2322 def recursive_groups_and_repos(self):
2323 """
2323 """
2324 Recursive return all groups, with repositories in those groups
2324 Recursive return all groups, with repositories in those groups
2325 """
2325 """
2326 return self._recursive_objects()
2326 return self._recursive_objects()
2327
2327
2328 def recursive_groups(self):
2328 def recursive_groups(self):
2329 """
2329 """
2330 Returns all children groups for this group including children of children
2330 Returns all children groups for this group including children of children
2331 """
2331 """
2332 return self._recursive_objects(include_repos=False)
2332 return self._recursive_objects(include_repos=False)
2333
2333
2334 def get_new_name(self, group_name):
2334 def get_new_name(self, group_name):
2335 """
2335 """
2336 returns new full group name based on parent and new name
2336 returns new full group name based on parent and new name
2337
2337
2338 :param group_name:
2338 :param group_name:
2339 """
2339 """
2340 path_prefix = (self.parent_group.full_path_splitted if
2340 path_prefix = (self.parent_group.full_path_splitted if
2341 self.parent_group else [])
2341 self.parent_group else [])
2342 return RepoGroup.url_sep().join(path_prefix + [group_name])
2342 return RepoGroup.url_sep().join(path_prefix + [group_name])
2343
2343
2344 def permissions(self, with_admins=True, with_owner=True):
2344 def permissions(self, with_admins=True, with_owner=True):
2345 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2345 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2346 q = q.options(joinedload(UserRepoGroupToPerm.group),
2346 q = q.options(joinedload(UserRepoGroupToPerm.group),
2347 joinedload(UserRepoGroupToPerm.user),
2347 joinedload(UserRepoGroupToPerm.user),
2348 joinedload(UserRepoGroupToPerm.permission),)
2348 joinedload(UserRepoGroupToPerm.permission),)
2349
2349
2350 # get owners and admins and permissions. We do a trick of re-writing
2350 # get owners and admins and permissions. We do a trick of re-writing
2351 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2351 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2352 # has a global reference and changing one object propagates to all
2352 # has a global reference and changing one object propagates to all
2353 # others. This means if admin is also an owner admin_row that change
2353 # others. This means if admin is also an owner admin_row that change
2354 # would propagate to both objects
2354 # would propagate to both objects
2355 perm_rows = []
2355 perm_rows = []
2356 for _usr in q.all():
2356 for _usr in q.all():
2357 usr = AttributeDict(_usr.user.get_dict())
2357 usr = AttributeDict(_usr.user.get_dict())
2358 usr.permission = _usr.permission.permission_name
2358 usr.permission = _usr.permission.permission_name
2359 perm_rows.append(usr)
2359 perm_rows.append(usr)
2360
2360
2361 # filter the perm rows by 'default' first and then sort them by
2361 # filter the perm rows by 'default' first and then sort them by
2362 # admin,write,read,none permissions sorted again alphabetically in
2362 # admin,write,read,none permissions sorted again alphabetically in
2363 # each group
2363 # each group
2364 perm_rows = sorted(perm_rows, key=display_sort)
2364 perm_rows = sorted(perm_rows, key=display_sort)
2365
2365
2366 _admin_perm = 'group.admin'
2366 _admin_perm = 'group.admin'
2367 owner_row = []
2367 owner_row = []
2368 if with_owner:
2368 if with_owner:
2369 usr = AttributeDict(self.user.get_dict())
2369 usr = AttributeDict(self.user.get_dict())
2370 usr.owner_row = True
2370 usr.owner_row = True
2371 usr.permission = _admin_perm
2371 usr.permission = _admin_perm
2372 owner_row.append(usr)
2372 owner_row.append(usr)
2373
2373
2374 super_admin_rows = []
2374 super_admin_rows = []
2375 if with_admins:
2375 if with_admins:
2376 for usr in User.get_all_super_admins():
2376 for usr in User.get_all_super_admins():
2377 # if this admin is also owner, don't double the record
2377 # if this admin is also owner, don't double the record
2378 if usr.user_id == owner_row[0].user_id:
2378 if usr.user_id == owner_row[0].user_id:
2379 owner_row[0].admin_row = True
2379 owner_row[0].admin_row = True
2380 else:
2380 else:
2381 usr = AttributeDict(usr.get_dict())
2381 usr = AttributeDict(usr.get_dict())
2382 usr.admin_row = True
2382 usr.admin_row = True
2383 usr.permission = _admin_perm
2383 usr.permission = _admin_perm
2384 super_admin_rows.append(usr)
2384 super_admin_rows.append(usr)
2385
2385
2386 return super_admin_rows + owner_row + perm_rows
2386 return super_admin_rows + owner_row + perm_rows
2387
2387
2388 def permission_user_groups(self):
2388 def permission_user_groups(self):
2389 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2389 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2390 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2390 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2391 joinedload(UserGroupRepoGroupToPerm.users_group),
2391 joinedload(UserGroupRepoGroupToPerm.users_group),
2392 joinedload(UserGroupRepoGroupToPerm.permission),)
2392 joinedload(UserGroupRepoGroupToPerm.permission),)
2393
2393
2394 perm_rows = []
2394 perm_rows = []
2395 for _user_group in q.all():
2395 for _user_group in q.all():
2396 usr = AttributeDict(_user_group.users_group.get_dict())
2396 usr = AttributeDict(_user_group.users_group.get_dict())
2397 usr.permission = _user_group.permission.permission_name
2397 usr.permission = _user_group.permission.permission_name
2398 perm_rows.append(usr)
2398 perm_rows.append(usr)
2399
2399
2400 return perm_rows
2400 return perm_rows
2401
2401
2402 def get_api_data(self):
2402 def get_api_data(self):
2403 """
2403 """
2404 Common function for generating api data
2404 Common function for generating api data
2405
2405
2406 """
2406 """
2407 group = self
2407 group = self
2408 data = {
2408 data = {
2409 'group_id': group.group_id,
2409 'group_id': group.group_id,
2410 'group_name': group.group_name,
2410 'group_name': group.group_name,
2411 'group_description': group.group_description,
2411 'group_description': group.group_description,
2412 'parent_group': group.parent_group.group_name if group.parent_group else None,
2412 'parent_group': group.parent_group.group_name if group.parent_group else None,
2413 'repositories': [x.repo_name for x in group.repositories],
2413 'repositories': [x.repo_name for x in group.repositories],
2414 'owner': group.user.username,
2414 'owner': group.user.username,
2415 }
2415 }
2416 return data
2416 return data
2417
2417
2418
2418
2419 class Permission(Base, BaseModel):
2419 class Permission(Base, BaseModel):
2420 __tablename__ = 'permissions'
2420 __tablename__ = 'permissions'
2421 __table_args__ = (
2421 __table_args__ = (
2422 Index('p_perm_name_idx', 'permission_name'),
2422 Index('p_perm_name_idx', 'permission_name'),
2423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2424 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2424 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2425 )
2425 )
2426 PERMS = [
2426 PERMS = [
2427 ('hg.admin', _('RhodeCode Super Administrator')),
2427 ('hg.admin', _('RhodeCode Super Administrator')),
2428
2428
2429 ('repository.none', _('Repository no access')),
2429 ('repository.none', _('Repository no access')),
2430 ('repository.read', _('Repository read access')),
2430 ('repository.read', _('Repository read access')),
2431 ('repository.write', _('Repository write access')),
2431 ('repository.write', _('Repository write access')),
2432 ('repository.admin', _('Repository admin access')),
2432 ('repository.admin', _('Repository admin access')),
2433
2433
2434 ('group.none', _('Repository group no access')),
2434 ('group.none', _('Repository group no access')),
2435 ('group.read', _('Repository group read access')),
2435 ('group.read', _('Repository group read access')),
2436 ('group.write', _('Repository group write access')),
2436 ('group.write', _('Repository group write access')),
2437 ('group.admin', _('Repository group admin access')),
2437 ('group.admin', _('Repository group admin access')),
2438
2438
2439 ('usergroup.none', _('User group no access')),
2439 ('usergroup.none', _('User group no access')),
2440 ('usergroup.read', _('User group read access')),
2440 ('usergroup.read', _('User group read access')),
2441 ('usergroup.write', _('User group write access')),
2441 ('usergroup.write', _('User group write access')),
2442 ('usergroup.admin', _('User group admin access')),
2442 ('usergroup.admin', _('User group admin access')),
2443
2443
2444 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2444 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2445 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2445 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2446
2446
2447 ('hg.usergroup.create.false', _('User Group creation disabled')),
2447 ('hg.usergroup.create.false', _('User Group creation disabled')),
2448 ('hg.usergroup.create.true', _('User Group creation enabled')),
2448 ('hg.usergroup.create.true', _('User Group creation enabled')),
2449
2449
2450 ('hg.create.none', _('Repository creation disabled')),
2450 ('hg.create.none', _('Repository creation disabled')),
2451 ('hg.create.repository', _('Repository creation enabled')),
2451 ('hg.create.repository', _('Repository creation enabled')),
2452 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2452 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2453 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2453 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2454
2454
2455 ('hg.fork.none', _('Repository forking disabled')),
2455 ('hg.fork.none', _('Repository forking disabled')),
2456 ('hg.fork.repository', _('Repository forking enabled')),
2456 ('hg.fork.repository', _('Repository forking enabled')),
2457
2457
2458 ('hg.register.none', _('Registration disabled')),
2458 ('hg.register.none', _('Registration disabled')),
2459 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2459 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2460 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2460 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2461
2461
2462 ('hg.password_reset.enabled', _('Password reset enabled')),
2462 ('hg.password_reset.enabled', _('Password reset enabled')),
2463 ('hg.password_reset.hidden', _('Password reset hidden')),
2463 ('hg.password_reset.hidden', _('Password reset hidden')),
2464 ('hg.password_reset.disabled', _('Password reset disabled')),
2464 ('hg.password_reset.disabled', _('Password reset disabled')),
2465
2465
2466 ('hg.extern_activate.manual', _('Manual activation of external account')),
2466 ('hg.extern_activate.manual', _('Manual activation of external account')),
2467 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2467 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2468
2468
2469 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2469 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2470 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2470 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2471 ]
2471 ]
2472
2472
2473 # definition of system default permissions for DEFAULT user
2473 # definition of system default permissions for DEFAULT user
2474 DEFAULT_USER_PERMISSIONS = [
2474 DEFAULT_USER_PERMISSIONS = [
2475 'repository.read',
2475 'repository.read',
2476 'group.read',
2476 'group.read',
2477 'usergroup.read',
2477 'usergroup.read',
2478 'hg.create.repository',
2478 'hg.create.repository',
2479 'hg.repogroup.create.false',
2479 'hg.repogroup.create.false',
2480 'hg.usergroup.create.false',
2480 'hg.usergroup.create.false',
2481 'hg.create.write_on_repogroup.true',
2481 'hg.create.write_on_repogroup.true',
2482 'hg.fork.repository',
2482 'hg.fork.repository',
2483 'hg.register.manual_activate',
2483 'hg.register.manual_activate',
2484 'hg.password_reset.enabled',
2484 'hg.password_reset.enabled',
2485 'hg.extern_activate.auto',
2485 'hg.extern_activate.auto',
2486 'hg.inherit_default_perms.true',
2486 'hg.inherit_default_perms.true',
2487 ]
2487 ]
2488
2488
2489 # defines which permissions are more important higher the more important
2489 # defines which permissions are more important higher the more important
2490 # Weight defines which permissions are more important.
2490 # Weight defines which permissions are more important.
2491 # The higher number the more important.
2491 # The higher number the more important.
2492 PERM_WEIGHTS = {
2492 PERM_WEIGHTS = {
2493 'repository.none': 0,
2493 'repository.none': 0,
2494 'repository.read': 1,
2494 'repository.read': 1,
2495 'repository.write': 3,
2495 'repository.write': 3,
2496 'repository.admin': 4,
2496 'repository.admin': 4,
2497
2497
2498 'group.none': 0,
2498 'group.none': 0,
2499 'group.read': 1,
2499 'group.read': 1,
2500 'group.write': 3,
2500 'group.write': 3,
2501 'group.admin': 4,
2501 'group.admin': 4,
2502
2502
2503 'usergroup.none': 0,
2503 'usergroup.none': 0,
2504 'usergroup.read': 1,
2504 'usergroup.read': 1,
2505 'usergroup.write': 3,
2505 'usergroup.write': 3,
2506 'usergroup.admin': 4,
2506 'usergroup.admin': 4,
2507
2507
2508 'hg.repogroup.create.false': 0,
2508 'hg.repogroup.create.false': 0,
2509 'hg.repogroup.create.true': 1,
2509 'hg.repogroup.create.true': 1,
2510
2510
2511 'hg.usergroup.create.false': 0,
2511 'hg.usergroup.create.false': 0,
2512 'hg.usergroup.create.true': 1,
2512 'hg.usergroup.create.true': 1,
2513
2513
2514 'hg.fork.none': 0,
2514 'hg.fork.none': 0,
2515 'hg.fork.repository': 1,
2515 'hg.fork.repository': 1,
2516 'hg.create.none': 0,
2516 'hg.create.none': 0,
2517 'hg.create.repository': 1
2517 'hg.create.repository': 1
2518 }
2518 }
2519
2519
2520 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2520 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2521 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2521 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2522 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2522 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2523
2523
2524 def __unicode__(self):
2524 def __unicode__(self):
2525 return u"<%s('%s:%s')>" % (
2525 return u"<%s('%s:%s')>" % (
2526 self.__class__.__name__, self.permission_id, self.permission_name
2526 self.__class__.__name__, self.permission_id, self.permission_name
2527 )
2527 )
2528
2528
2529 @classmethod
2529 @classmethod
2530 def get_by_key(cls, key):
2530 def get_by_key(cls, key):
2531 return cls.query().filter(cls.permission_name == key).scalar()
2531 return cls.query().filter(cls.permission_name == key).scalar()
2532
2532
2533 @classmethod
2533 @classmethod
2534 def get_default_repo_perms(cls, user_id, repo_id=None):
2534 def get_default_repo_perms(cls, user_id, repo_id=None):
2535 q = Session().query(UserRepoToPerm, Repository, Permission)\
2535 q = Session().query(UserRepoToPerm, Repository, Permission)\
2536 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2536 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2537 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2537 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2538 .filter(UserRepoToPerm.user_id == user_id)
2538 .filter(UserRepoToPerm.user_id == user_id)
2539 if repo_id:
2539 if repo_id:
2540 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2540 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2541 return q.all()
2541 return q.all()
2542
2542
2543 @classmethod
2543 @classmethod
2544 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2544 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2545 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2545 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2546 .join(
2546 .join(
2547 Permission,
2547 Permission,
2548 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2548 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2549 .join(
2549 .join(
2550 Repository,
2550 Repository,
2551 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2551 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2552 .join(
2552 .join(
2553 UserGroup,
2553 UserGroup,
2554 UserGroupRepoToPerm.users_group_id ==
2554 UserGroupRepoToPerm.users_group_id ==
2555 UserGroup.users_group_id)\
2555 UserGroup.users_group_id)\
2556 .join(
2556 .join(
2557 UserGroupMember,
2557 UserGroupMember,
2558 UserGroupRepoToPerm.users_group_id ==
2558 UserGroupRepoToPerm.users_group_id ==
2559 UserGroupMember.users_group_id)\
2559 UserGroupMember.users_group_id)\
2560 .filter(
2560 .filter(
2561 UserGroupMember.user_id == user_id,
2561 UserGroupMember.user_id == user_id,
2562 UserGroup.users_group_active == true())
2562 UserGroup.users_group_active == true())
2563 if repo_id:
2563 if repo_id:
2564 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2564 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2565 return q.all()
2565 return q.all()
2566
2566
2567 @classmethod
2567 @classmethod
2568 def get_default_group_perms(cls, user_id, repo_group_id=None):
2568 def get_default_group_perms(cls, user_id, repo_group_id=None):
2569 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2569 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2570 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2570 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2571 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2571 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2572 .filter(UserRepoGroupToPerm.user_id == user_id)
2572 .filter(UserRepoGroupToPerm.user_id == user_id)
2573 if repo_group_id:
2573 if repo_group_id:
2574 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2574 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2575 return q.all()
2575 return q.all()
2576
2576
2577 @classmethod
2577 @classmethod
2578 def get_default_group_perms_from_user_group(
2578 def get_default_group_perms_from_user_group(
2579 cls, user_id, repo_group_id=None):
2579 cls, user_id, repo_group_id=None):
2580 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2580 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2581 .join(
2581 .join(
2582 Permission,
2582 Permission,
2583 UserGroupRepoGroupToPerm.permission_id ==
2583 UserGroupRepoGroupToPerm.permission_id ==
2584 Permission.permission_id)\
2584 Permission.permission_id)\
2585 .join(
2585 .join(
2586 RepoGroup,
2586 RepoGroup,
2587 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2587 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2588 .join(
2588 .join(
2589 UserGroup,
2589 UserGroup,
2590 UserGroupRepoGroupToPerm.users_group_id ==
2590 UserGroupRepoGroupToPerm.users_group_id ==
2591 UserGroup.users_group_id)\
2591 UserGroup.users_group_id)\
2592 .join(
2592 .join(
2593 UserGroupMember,
2593 UserGroupMember,
2594 UserGroupRepoGroupToPerm.users_group_id ==
2594 UserGroupRepoGroupToPerm.users_group_id ==
2595 UserGroupMember.users_group_id)\
2595 UserGroupMember.users_group_id)\
2596 .filter(
2596 .filter(
2597 UserGroupMember.user_id == user_id,
2597 UserGroupMember.user_id == user_id,
2598 UserGroup.users_group_active == true())
2598 UserGroup.users_group_active == true())
2599 if repo_group_id:
2599 if repo_group_id:
2600 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2600 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2601 return q.all()
2601 return q.all()
2602
2602
2603 @classmethod
2603 @classmethod
2604 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2604 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2605 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2605 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2606 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2606 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2607 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2607 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2608 .filter(UserUserGroupToPerm.user_id == user_id)
2608 .filter(UserUserGroupToPerm.user_id == user_id)
2609 if user_group_id:
2609 if user_group_id:
2610 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2610 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2611 return q.all()
2611 return q.all()
2612
2612
2613 @classmethod
2613 @classmethod
2614 def get_default_user_group_perms_from_user_group(
2614 def get_default_user_group_perms_from_user_group(
2615 cls, user_id, user_group_id=None):
2615 cls, user_id, user_group_id=None):
2616 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2616 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2617 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2617 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2618 .join(
2618 .join(
2619 Permission,
2619 Permission,
2620 UserGroupUserGroupToPerm.permission_id ==
2620 UserGroupUserGroupToPerm.permission_id ==
2621 Permission.permission_id)\
2621 Permission.permission_id)\
2622 .join(
2622 .join(
2623 TargetUserGroup,
2623 TargetUserGroup,
2624 UserGroupUserGroupToPerm.target_user_group_id ==
2624 UserGroupUserGroupToPerm.target_user_group_id ==
2625 TargetUserGroup.users_group_id)\
2625 TargetUserGroup.users_group_id)\
2626 .join(
2626 .join(
2627 UserGroup,
2627 UserGroup,
2628 UserGroupUserGroupToPerm.user_group_id ==
2628 UserGroupUserGroupToPerm.user_group_id ==
2629 UserGroup.users_group_id)\
2629 UserGroup.users_group_id)\
2630 .join(
2630 .join(
2631 UserGroupMember,
2631 UserGroupMember,
2632 UserGroupUserGroupToPerm.user_group_id ==
2632 UserGroupUserGroupToPerm.user_group_id ==
2633 UserGroupMember.users_group_id)\
2633 UserGroupMember.users_group_id)\
2634 .filter(
2634 .filter(
2635 UserGroupMember.user_id == user_id,
2635 UserGroupMember.user_id == user_id,
2636 UserGroup.users_group_active == true())
2636 UserGroup.users_group_active == true())
2637 if user_group_id:
2637 if user_group_id:
2638 q = q.filter(
2638 q = q.filter(
2639 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2639 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2640
2640
2641 return q.all()
2641 return q.all()
2642
2642
2643
2643
2644 class UserRepoToPerm(Base, BaseModel):
2644 class UserRepoToPerm(Base, BaseModel):
2645 __tablename__ = 'repo_to_perm'
2645 __tablename__ = 'repo_to_perm'
2646 __table_args__ = (
2646 __table_args__ = (
2647 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2647 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2650 )
2650 )
2651 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2651 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2652 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2653 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2653 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2654 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2654 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2655
2655
2656 user = relationship('User')
2656 user = relationship('User')
2657 repository = relationship('Repository')
2657 repository = relationship('Repository')
2658 permission = relationship('Permission')
2658 permission = relationship('Permission')
2659
2659
2660 @classmethod
2660 @classmethod
2661 def create(cls, user, repository, permission):
2661 def create(cls, user, repository, permission):
2662 n = cls()
2662 n = cls()
2663 n.user = user
2663 n.user = user
2664 n.repository = repository
2664 n.repository = repository
2665 n.permission = permission
2665 n.permission = permission
2666 Session().add(n)
2666 Session().add(n)
2667 return n
2667 return n
2668
2668
2669 def __unicode__(self):
2669 def __unicode__(self):
2670 return u'<%s => %s >' % (self.user, self.repository)
2670 return u'<%s => %s >' % (self.user, self.repository)
2671
2671
2672
2672
2673 class UserUserGroupToPerm(Base, BaseModel):
2673 class UserUserGroupToPerm(Base, BaseModel):
2674 __tablename__ = 'user_user_group_to_perm'
2674 __tablename__ = 'user_user_group_to_perm'
2675 __table_args__ = (
2675 __table_args__ = (
2676 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2676 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2679 )
2679 )
2680 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2680 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2681 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2681 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2683 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2683 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2684
2684
2685 user = relationship('User')
2685 user = relationship('User')
2686 user_group = relationship('UserGroup')
2686 user_group = relationship('UserGroup')
2687 permission = relationship('Permission')
2687 permission = relationship('Permission')
2688
2688
2689 @classmethod
2689 @classmethod
2690 def create(cls, user, user_group, permission):
2690 def create(cls, user, user_group, permission):
2691 n = cls()
2691 n = cls()
2692 n.user = user
2692 n.user = user
2693 n.user_group = user_group
2693 n.user_group = user_group
2694 n.permission = permission
2694 n.permission = permission
2695 Session().add(n)
2695 Session().add(n)
2696 return n
2696 return n
2697
2697
2698 def __unicode__(self):
2698 def __unicode__(self):
2699 return u'<%s => %s >' % (self.user, self.user_group)
2699 return u'<%s => %s >' % (self.user, self.user_group)
2700
2700
2701
2701
2702 class UserToPerm(Base, BaseModel):
2702 class UserToPerm(Base, BaseModel):
2703 __tablename__ = 'user_to_perm'
2703 __tablename__ = 'user_to_perm'
2704 __table_args__ = (
2704 __table_args__ = (
2705 UniqueConstraint('user_id', 'permission_id'),
2705 UniqueConstraint('user_id', 'permission_id'),
2706 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2706 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2707 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2707 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2708 )
2708 )
2709 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2709 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2710 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2710 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2711 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2711 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2712
2712
2713 user = relationship('User')
2713 user = relationship('User')
2714 permission = relationship('Permission', lazy='joined')
2714 permission = relationship('Permission', lazy='joined')
2715
2715
2716 def __unicode__(self):
2716 def __unicode__(self):
2717 return u'<%s => %s >' % (self.user, self.permission)
2717 return u'<%s => %s >' % (self.user, self.permission)
2718
2718
2719
2719
2720 class UserGroupRepoToPerm(Base, BaseModel):
2720 class UserGroupRepoToPerm(Base, BaseModel):
2721 __tablename__ = 'users_group_repo_to_perm'
2721 __tablename__ = 'users_group_repo_to_perm'
2722 __table_args__ = (
2722 __table_args__ = (
2723 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2723 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2726 )
2726 )
2727 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2727 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2728 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2729 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2729 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2730 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2730 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2731
2731
2732 users_group = relationship('UserGroup')
2732 users_group = relationship('UserGroup')
2733 permission = relationship('Permission')
2733 permission = relationship('Permission')
2734 repository = relationship('Repository')
2734 repository = relationship('Repository')
2735
2735
2736 @classmethod
2736 @classmethod
2737 def create(cls, users_group, repository, permission):
2737 def create(cls, users_group, repository, permission):
2738 n = cls()
2738 n = cls()
2739 n.users_group = users_group
2739 n.users_group = users_group
2740 n.repository = repository
2740 n.repository = repository
2741 n.permission = permission
2741 n.permission = permission
2742 Session().add(n)
2742 Session().add(n)
2743 return n
2743 return n
2744
2744
2745 def __unicode__(self):
2745 def __unicode__(self):
2746 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2746 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2747
2747
2748
2748
2749 class UserGroupUserGroupToPerm(Base, BaseModel):
2749 class UserGroupUserGroupToPerm(Base, BaseModel):
2750 __tablename__ = 'user_group_user_group_to_perm'
2750 __tablename__ = 'user_group_user_group_to_perm'
2751 __table_args__ = (
2751 __table_args__ = (
2752 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2752 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2753 CheckConstraint('target_user_group_id != user_group_id'),
2753 CheckConstraint('target_user_group_id != user_group_id'),
2754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2755 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2755 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2756 )
2756 )
2757 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2757 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2758 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2758 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2759 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2759 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2760 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2760 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2761
2761
2762 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2762 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2763 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2763 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2764 permission = relationship('Permission')
2764 permission = relationship('Permission')
2765
2765
2766 @classmethod
2766 @classmethod
2767 def create(cls, target_user_group, user_group, permission):
2767 def create(cls, target_user_group, user_group, permission):
2768 n = cls()
2768 n = cls()
2769 n.target_user_group = target_user_group
2769 n.target_user_group = target_user_group
2770 n.user_group = user_group
2770 n.user_group = user_group
2771 n.permission = permission
2771 n.permission = permission
2772 Session().add(n)
2772 Session().add(n)
2773 return n
2773 return n
2774
2774
2775 def __unicode__(self):
2775 def __unicode__(self):
2776 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2776 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2777
2777
2778
2778
2779 class UserGroupToPerm(Base, BaseModel):
2779 class UserGroupToPerm(Base, BaseModel):
2780 __tablename__ = 'users_group_to_perm'
2780 __tablename__ = 'users_group_to_perm'
2781 __table_args__ = (
2781 __table_args__ = (
2782 UniqueConstraint('users_group_id', 'permission_id',),
2782 UniqueConstraint('users_group_id', 'permission_id',),
2783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2785 )
2785 )
2786 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2786 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2787 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2787 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2788 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2788 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2789
2789
2790 users_group = relationship('UserGroup')
2790 users_group = relationship('UserGroup')
2791 permission = relationship('Permission')
2791 permission = relationship('Permission')
2792
2792
2793
2793
2794 class UserRepoGroupToPerm(Base, BaseModel):
2794 class UserRepoGroupToPerm(Base, BaseModel):
2795 __tablename__ = 'user_repo_group_to_perm'
2795 __tablename__ = 'user_repo_group_to_perm'
2796 __table_args__ = (
2796 __table_args__ = (
2797 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2797 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2800 )
2800 )
2801
2801
2802 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2802 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2803 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2803 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2804 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2804 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2805 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2805 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2806
2806
2807 user = relationship('User')
2807 user = relationship('User')
2808 group = relationship('RepoGroup')
2808 group = relationship('RepoGroup')
2809 permission = relationship('Permission')
2809 permission = relationship('Permission')
2810
2810
2811 @classmethod
2811 @classmethod
2812 def create(cls, user, repository_group, permission):
2812 def create(cls, user, repository_group, permission):
2813 n = cls()
2813 n = cls()
2814 n.user = user
2814 n.user = user
2815 n.group = repository_group
2815 n.group = repository_group
2816 n.permission = permission
2816 n.permission = permission
2817 Session().add(n)
2817 Session().add(n)
2818 return n
2818 return n
2819
2819
2820
2820
2821 class UserGroupRepoGroupToPerm(Base, BaseModel):
2821 class UserGroupRepoGroupToPerm(Base, BaseModel):
2822 __tablename__ = 'users_group_repo_group_to_perm'
2822 __tablename__ = 'users_group_repo_group_to_perm'
2823 __table_args__ = (
2823 __table_args__ = (
2824 UniqueConstraint('users_group_id', 'group_id'),
2824 UniqueConstraint('users_group_id', 'group_id'),
2825 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2825 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2826 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2826 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2827 )
2827 )
2828
2828
2829 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)
2829 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)
2830 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2830 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2831 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2831 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2832 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2832 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2833
2833
2834 users_group = relationship('UserGroup')
2834 users_group = relationship('UserGroup')
2835 permission = relationship('Permission')
2835 permission = relationship('Permission')
2836 group = relationship('RepoGroup')
2836 group = relationship('RepoGroup')
2837
2837
2838 @classmethod
2838 @classmethod
2839 def create(cls, user_group, repository_group, permission):
2839 def create(cls, user_group, repository_group, permission):
2840 n = cls()
2840 n = cls()
2841 n.users_group = user_group
2841 n.users_group = user_group
2842 n.group = repository_group
2842 n.group = repository_group
2843 n.permission = permission
2843 n.permission = permission
2844 Session().add(n)
2844 Session().add(n)
2845 return n
2845 return n
2846
2846
2847 def __unicode__(self):
2847 def __unicode__(self):
2848 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2848 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2849
2849
2850
2850
2851 class Statistics(Base, BaseModel):
2851 class Statistics(Base, BaseModel):
2852 __tablename__ = 'statistics'
2852 __tablename__ = 'statistics'
2853 __table_args__ = (
2853 __table_args__ = (
2854 UniqueConstraint('repository_id'),
2854 UniqueConstraint('repository_id'),
2855 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2855 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2856 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2856 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2857 )
2857 )
2858 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2858 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2859 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2859 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2860 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2860 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2861 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2861 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2862 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2862 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2863 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2863 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2864
2864
2865 repository = relationship('Repository', single_parent=True)
2865 repository = relationship('Repository', single_parent=True)
2866
2866
2867
2867
2868 class UserFollowing(Base, BaseModel):
2868 class UserFollowing(Base, BaseModel):
2869 __tablename__ = 'user_followings'
2869 __tablename__ = 'user_followings'
2870 __table_args__ = (
2870 __table_args__ = (
2871 UniqueConstraint('user_id', 'follows_repository_id'),
2871 UniqueConstraint('user_id', 'follows_repository_id'),
2872 UniqueConstraint('user_id', 'follows_user_id'),
2872 UniqueConstraint('user_id', 'follows_user_id'),
2873 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2873 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2874 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2874 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2875 )
2875 )
2876
2876
2877 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2877 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2878 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2878 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2879 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2879 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2880 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2880 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2881 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2881 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2882
2882
2883 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2883 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2884
2884
2885 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2885 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2886 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2886 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2887
2887
2888 @classmethod
2888 @classmethod
2889 def get_repo_followers(cls, repo_id):
2889 def get_repo_followers(cls, repo_id):
2890 return cls.query().filter(cls.follows_repo_id == repo_id)
2890 return cls.query().filter(cls.follows_repo_id == repo_id)
2891
2891
2892
2892
2893 class CacheKey(Base, BaseModel):
2893 class CacheKey(Base, BaseModel):
2894 __tablename__ = 'cache_invalidation'
2894 __tablename__ = 'cache_invalidation'
2895 __table_args__ = (
2895 __table_args__ = (
2896 UniqueConstraint('cache_key'),
2896 UniqueConstraint('cache_key'),
2897 Index('key_idx', 'cache_key'),
2897 Index('key_idx', 'cache_key'),
2898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2900 )
2900 )
2901 CACHE_TYPE_ATOM = 'ATOM'
2901 CACHE_TYPE_ATOM = 'ATOM'
2902 CACHE_TYPE_RSS = 'RSS'
2902 CACHE_TYPE_RSS = 'RSS'
2903 CACHE_TYPE_README = 'README'
2903 CACHE_TYPE_README = 'README'
2904
2904
2905 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2905 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2906 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2906 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2907 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2907 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2908 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2908 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2909
2909
2910 def __init__(self, cache_key, cache_args=''):
2910 def __init__(self, cache_key, cache_args=''):
2911 self.cache_key = cache_key
2911 self.cache_key = cache_key
2912 self.cache_args = cache_args
2912 self.cache_args = cache_args
2913 self.cache_active = False
2913 self.cache_active = False
2914
2914
2915 def __unicode__(self):
2915 def __unicode__(self):
2916 return u"<%s('%s:%s[%s]')>" % (
2916 return u"<%s('%s:%s[%s]')>" % (
2917 self.__class__.__name__,
2917 self.__class__.__name__,
2918 self.cache_id, self.cache_key, self.cache_active)
2918 self.cache_id, self.cache_key, self.cache_active)
2919
2919
2920 def _cache_key_partition(self):
2920 def _cache_key_partition(self):
2921 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2921 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2922 return prefix, repo_name, suffix
2922 return prefix, repo_name, suffix
2923
2923
2924 def get_prefix(self):
2924 def get_prefix(self):
2925 """
2925 """
2926 Try to extract prefix from existing cache key. The key could consist
2926 Try to extract prefix from existing cache key. The key could consist
2927 of prefix, repo_name, suffix
2927 of prefix, repo_name, suffix
2928 """
2928 """
2929 # this returns prefix, repo_name, suffix
2929 # this returns prefix, repo_name, suffix
2930 return self._cache_key_partition()[0]
2930 return self._cache_key_partition()[0]
2931
2931
2932 def get_suffix(self):
2932 def get_suffix(self):
2933 """
2933 """
2934 get suffix that might have been used in _get_cache_key to
2934 get suffix that might have been used in _get_cache_key to
2935 generate self.cache_key. Only used for informational purposes
2935 generate self.cache_key. Only used for informational purposes
2936 in repo_edit.mako.
2936 in repo_edit.mako.
2937 """
2937 """
2938 # prefix, repo_name, suffix
2938 # prefix, repo_name, suffix
2939 return self._cache_key_partition()[2]
2939 return self._cache_key_partition()[2]
2940
2940
2941 @classmethod
2941 @classmethod
2942 def delete_all_cache(cls):
2942 def delete_all_cache(cls):
2943 """
2943 """
2944 Delete all cache keys from database.
2944 Delete all cache keys from database.
2945 Should only be run when all instances are down and all entries
2945 Should only be run when all instances are down and all entries
2946 thus stale.
2946 thus stale.
2947 """
2947 """
2948 cls.query().delete()
2948 cls.query().delete()
2949 Session().commit()
2949 Session().commit()
2950
2950
2951 @classmethod
2951 @classmethod
2952 def get_cache_key(cls, repo_name, cache_type):
2952 def get_cache_key(cls, repo_name, cache_type):
2953 """
2953 """
2954
2954
2955 Generate a cache key for this process of RhodeCode instance.
2955 Generate a cache key for this process of RhodeCode instance.
2956 Prefix most likely will be process id or maybe explicitly set
2956 Prefix most likely will be process id or maybe explicitly set
2957 instance_id from .ini file.
2957 instance_id from .ini file.
2958 """
2958 """
2959 import rhodecode
2959 import rhodecode
2960 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2960 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2961
2961
2962 repo_as_unicode = safe_unicode(repo_name)
2962 repo_as_unicode = safe_unicode(repo_name)
2963 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2963 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2964 if cache_type else repo_as_unicode
2964 if cache_type else repo_as_unicode
2965
2965
2966 return u'{}{}'.format(prefix, key)
2966 return u'{}{}'.format(prefix, key)
2967
2967
2968 @classmethod
2968 @classmethod
2969 def set_invalidate(cls, repo_name, delete=False):
2969 def set_invalidate(cls, repo_name, delete=False):
2970 """
2970 """
2971 Mark all caches of a repo as invalid in the database.
2971 Mark all caches of a repo as invalid in the database.
2972 """
2972 """
2973
2973
2974 try:
2974 try:
2975 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2975 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2976 if delete:
2976 if delete:
2977 log.debug('cache objects deleted for repo %s',
2977 log.debug('cache objects deleted for repo %s',
2978 safe_str(repo_name))
2978 safe_str(repo_name))
2979 qry.delete()
2979 qry.delete()
2980 else:
2980 else:
2981 log.debug('cache objects marked as invalid for repo %s',
2981 log.debug('cache objects marked as invalid for repo %s',
2982 safe_str(repo_name))
2982 safe_str(repo_name))
2983 qry.update({"cache_active": False})
2983 qry.update({"cache_active": False})
2984
2984
2985 Session().commit()
2985 Session().commit()
2986 except Exception:
2986 except Exception:
2987 log.exception(
2987 log.exception(
2988 'Cache key invalidation failed for repository %s',
2988 'Cache key invalidation failed for repository %s',
2989 safe_str(repo_name))
2989 safe_str(repo_name))
2990 Session().rollback()
2990 Session().rollback()
2991
2991
2992 @classmethod
2992 @classmethod
2993 def get_active_cache(cls, cache_key):
2993 def get_active_cache(cls, cache_key):
2994 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2994 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2995 if inv_obj:
2995 if inv_obj:
2996 return inv_obj
2996 return inv_obj
2997 return None
2997 return None
2998
2998
2999 @classmethod
2999 @classmethod
3000 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3000 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3001 thread_scoped=False):
3001 thread_scoped=False):
3002 """
3002 """
3003 @cache_region('long_term')
3003 @cache_region('long_term')
3004 def _heavy_calculation(cache_key):
3004 def _heavy_calculation(cache_key):
3005 return 'result'
3005 return 'result'
3006
3006
3007 cache_context = CacheKey.repo_context_cache(
3007 cache_context = CacheKey.repo_context_cache(
3008 _heavy_calculation, repo_name, cache_type)
3008 _heavy_calculation, repo_name, cache_type)
3009
3009
3010 with cache_context as context:
3010 with cache_context as context:
3011 context.invalidate()
3011 context.invalidate()
3012 computed = context.compute()
3012 computed = context.compute()
3013
3013
3014 assert computed == 'result'
3014 assert computed == 'result'
3015 """
3015 """
3016 from rhodecode.lib import caches
3016 from rhodecode.lib import caches
3017 return caches.InvalidationContext(
3017 return caches.InvalidationContext(
3018 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3018 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3019
3019
3020
3020
3021 class ChangesetComment(Base, BaseModel):
3021 class ChangesetComment(Base, BaseModel):
3022 __tablename__ = 'changeset_comments'
3022 __tablename__ = 'changeset_comments'
3023 __table_args__ = (
3023 __table_args__ = (
3024 Index('cc_revision_idx', 'revision'),
3024 Index('cc_revision_idx', 'revision'),
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3027 )
3027 )
3028
3028
3029 COMMENT_OUTDATED = u'comment_outdated'
3029 COMMENT_OUTDATED = u'comment_outdated'
3030 COMMENT_TYPE_NOTE = u'note'
3030 COMMENT_TYPE_NOTE = u'note'
3031 COMMENT_TYPE_TODO = u'todo'
3031 COMMENT_TYPE_TODO = u'todo'
3032 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3032 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3033
3033
3034 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3034 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3035 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3035 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3036 revision = Column('revision', String(40), nullable=True)
3036 revision = Column('revision', String(40), nullable=True)
3037 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3037 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3038 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3038 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3039 line_no = Column('line_no', Unicode(10), nullable=True)
3039 line_no = Column('line_no', Unicode(10), nullable=True)
3040 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3040 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3041 f_path = Column('f_path', Unicode(1000), nullable=True)
3041 f_path = Column('f_path', Unicode(1000), nullable=True)
3042 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3042 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3043 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3043 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3044 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3044 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3045 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3045 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3046 renderer = Column('renderer', Unicode(64), nullable=True)
3046 renderer = Column('renderer', Unicode(64), nullable=True)
3047 display_state = Column('display_state', Unicode(128), nullable=True)
3047 display_state = Column('display_state', Unicode(128), nullable=True)
3048
3048
3049 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3049 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3050 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3050 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3051 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3051 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3052 author = relationship('User', lazy='joined')
3052 author = relationship('User', lazy='joined')
3053 repo = relationship('Repository')
3053 repo = relationship('Repository')
3054 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3054 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3055 pull_request = relationship('PullRequest', lazy='joined')
3055 pull_request = relationship('PullRequest', lazy='joined')
3056 pull_request_version = relationship('PullRequestVersion')
3056 pull_request_version = relationship('PullRequestVersion')
3057
3057
3058 @classmethod
3058 @classmethod
3059 def get_users(cls, revision=None, pull_request_id=None):
3059 def get_users(cls, revision=None, pull_request_id=None):
3060 """
3060 """
3061 Returns user associated with this ChangesetComment. ie those
3061 Returns user associated with this ChangesetComment. ie those
3062 who actually commented
3062 who actually commented
3063
3063
3064 :param cls:
3064 :param cls:
3065 :param revision:
3065 :param revision:
3066 """
3066 """
3067 q = Session().query(User)\
3067 q = Session().query(User)\
3068 .join(ChangesetComment.author)
3068 .join(ChangesetComment.author)
3069 if revision:
3069 if revision:
3070 q = q.filter(cls.revision == revision)
3070 q = q.filter(cls.revision == revision)
3071 elif pull_request_id:
3071 elif pull_request_id:
3072 q = q.filter(cls.pull_request_id == pull_request_id)
3072 q = q.filter(cls.pull_request_id == pull_request_id)
3073 return q.all()
3073 return q.all()
3074
3074
3075 @classmethod
3075 @classmethod
3076 def get_index_from_version(cls, pr_version, versions):
3076 def get_index_from_version(cls, pr_version, versions):
3077 num_versions = [x.pull_request_version_id for x in versions]
3077 num_versions = [x.pull_request_version_id for x in versions]
3078 try:
3078 try:
3079 return num_versions.index(pr_version) +1
3079 return num_versions.index(pr_version) +1
3080 except (IndexError, ValueError):
3080 except (IndexError, ValueError):
3081 return
3081 return
3082
3082
3083 @property
3083 @property
3084 def outdated(self):
3084 def outdated(self):
3085 return self.display_state == self.COMMENT_OUTDATED
3085 return self.display_state == self.COMMENT_OUTDATED
3086
3086
3087 def outdated_at_version(self, version):
3087 def outdated_at_version(self, version):
3088 """
3088 """
3089 Checks if comment is outdated for given pull request version
3089 Checks if comment is outdated for given pull request version
3090 """
3090 """
3091 return self.outdated and self.pull_request_version_id != version
3091 return self.outdated and self.pull_request_version_id != version
3092
3092
3093 def older_than_version(self, version):
3093 def older_than_version(self, version):
3094 """
3094 """
3095 Checks if comment is made from previous version than given
3095 Checks if comment is made from previous version than given
3096 """
3096 """
3097 if version is None:
3097 if version is None:
3098 return self.pull_request_version_id is not None
3098 return self.pull_request_version_id is not None
3099
3099
3100 return self.pull_request_version_id < version
3100 return self.pull_request_version_id < version
3101
3101
3102 @property
3102 @property
3103 def resolved(self):
3103 def resolved(self):
3104 return self.resolved_by[0] if self.resolved_by else None
3104 return self.resolved_by[0] if self.resolved_by else None
3105
3105
3106 @property
3106 @property
3107 def is_todo(self):
3107 def is_todo(self):
3108 return self.comment_type == self.COMMENT_TYPE_TODO
3108 return self.comment_type == self.COMMENT_TYPE_TODO
3109
3109
3110 def get_index_version(self, versions):
3110 def get_index_version(self, versions):
3111 return self.get_index_from_version(
3111 return self.get_index_from_version(
3112 self.pull_request_version_id, versions)
3112 self.pull_request_version_id, versions)
3113
3113
3114 def __repr__(self):
3114 def __repr__(self):
3115 if self.comment_id:
3115 if self.comment_id:
3116 return '<DB:Comment #%s>' % self.comment_id
3116 return '<DB:Comment #%s>' % self.comment_id
3117 else:
3117 else:
3118 return '<DB:Comment at %#x>' % id(self)
3118 return '<DB:Comment at %#x>' % id(self)
3119
3119
3120
3120
3121 class ChangesetStatus(Base, BaseModel):
3121 class ChangesetStatus(Base, BaseModel):
3122 __tablename__ = 'changeset_statuses'
3122 __tablename__ = 'changeset_statuses'
3123 __table_args__ = (
3123 __table_args__ = (
3124 Index('cs_revision_idx', 'revision'),
3124 Index('cs_revision_idx', 'revision'),
3125 Index('cs_version_idx', 'version'),
3125 Index('cs_version_idx', 'version'),
3126 UniqueConstraint('repo_id', 'revision', 'version'),
3126 UniqueConstraint('repo_id', 'revision', 'version'),
3127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3128 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3128 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3129 )
3129 )
3130 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3130 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3131 STATUS_APPROVED = 'approved'
3131 STATUS_APPROVED = 'approved'
3132 STATUS_REJECTED = 'rejected'
3132 STATUS_REJECTED = 'rejected'
3133 STATUS_UNDER_REVIEW = 'under_review'
3133 STATUS_UNDER_REVIEW = 'under_review'
3134
3134
3135 STATUSES = [
3135 STATUSES = [
3136 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3136 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3137 (STATUS_APPROVED, _("Approved")),
3137 (STATUS_APPROVED, _("Approved")),
3138 (STATUS_REJECTED, _("Rejected")),
3138 (STATUS_REJECTED, _("Rejected")),
3139 (STATUS_UNDER_REVIEW, _("Under Review")),
3139 (STATUS_UNDER_REVIEW, _("Under Review")),
3140 ]
3140 ]
3141
3141
3142 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3142 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3143 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3143 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3144 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3144 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3145 revision = Column('revision', String(40), nullable=False)
3145 revision = Column('revision', String(40), nullable=False)
3146 status = Column('status', String(128), nullable=False, default=DEFAULT)
3146 status = Column('status', String(128), nullable=False, default=DEFAULT)
3147 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3147 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3148 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3148 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3149 version = Column('version', Integer(), nullable=False, default=0)
3149 version = Column('version', Integer(), nullable=False, default=0)
3150 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3150 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3151
3151
3152 author = relationship('User', lazy='joined')
3152 author = relationship('User', lazy='joined')
3153 repo = relationship('Repository')
3153 repo = relationship('Repository')
3154 comment = relationship('ChangesetComment', lazy='joined')
3154 comment = relationship('ChangesetComment', lazy='joined')
3155 pull_request = relationship('PullRequest', lazy='joined')
3155 pull_request = relationship('PullRequest', lazy='joined')
3156
3156
3157 def __unicode__(self):
3157 def __unicode__(self):
3158 return u"<%s('%s[v%s]:%s')>" % (
3158 return u"<%s('%s[v%s]:%s')>" % (
3159 self.__class__.__name__,
3159 self.__class__.__name__,
3160 self.status, self.version, self.author
3160 self.status, self.version, self.author
3161 )
3161 )
3162
3162
3163 @classmethod
3163 @classmethod
3164 def get_status_lbl(cls, value):
3164 def get_status_lbl(cls, value):
3165 return dict(cls.STATUSES).get(value)
3165 return dict(cls.STATUSES).get(value)
3166
3166
3167 @property
3167 @property
3168 def status_lbl(self):
3168 def status_lbl(self):
3169 return ChangesetStatus.get_status_lbl(self.status)
3169 return ChangesetStatus.get_status_lbl(self.status)
3170
3170
3171
3171
3172 class _PullRequestBase(BaseModel):
3172 class _PullRequestBase(BaseModel):
3173 """
3173 """
3174 Common attributes of pull request and version entries.
3174 Common attributes of pull request and version entries.
3175 """
3175 """
3176
3176
3177 # .status values
3177 # .status values
3178 STATUS_NEW = u'new'
3178 STATUS_NEW = u'new'
3179 STATUS_OPEN = u'open'
3179 STATUS_OPEN = u'open'
3180 STATUS_CLOSED = u'closed'
3180 STATUS_CLOSED = u'closed'
3181
3181
3182 title = Column('title', Unicode(255), nullable=True)
3182 title = Column('title', Unicode(255), nullable=True)
3183 description = Column(
3183 description = Column(
3184 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3184 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3185 nullable=True)
3185 nullable=True)
3186 # new/open/closed status of pull request (not approve/reject/etc)
3186 # new/open/closed status of pull request (not approve/reject/etc)
3187 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3187 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3188 created_on = Column(
3188 created_on = Column(
3189 'created_on', DateTime(timezone=False), nullable=False,
3189 'created_on', DateTime(timezone=False), nullable=False,
3190 default=datetime.datetime.now)
3190 default=datetime.datetime.now)
3191 updated_on = Column(
3191 updated_on = Column(
3192 'updated_on', DateTime(timezone=False), nullable=False,
3192 'updated_on', DateTime(timezone=False), nullable=False,
3193 default=datetime.datetime.now)
3193 default=datetime.datetime.now)
3194
3194
3195 @declared_attr
3195 @declared_attr
3196 def user_id(cls):
3196 def user_id(cls):
3197 return Column(
3197 return Column(
3198 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3198 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3199 unique=None)
3199 unique=None)
3200
3200
3201 # 500 revisions max
3201 # 500 revisions max
3202 _revisions = Column(
3202 _revisions = Column(
3203 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3203 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3204
3204
3205 @declared_attr
3205 @declared_attr
3206 def source_repo_id(cls):
3206 def source_repo_id(cls):
3207 # TODO: dan: rename column to source_repo_id
3207 # TODO: dan: rename column to source_repo_id
3208 return Column(
3208 return Column(
3209 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3209 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3210 nullable=False)
3210 nullable=False)
3211
3211
3212 source_ref = Column('org_ref', Unicode(255), nullable=False)
3212 source_ref = Column('org_ref', Unicode(255), nullable=False)
3213
3213
3214 @declared_attr
3214 @declared_attr
3215 def target_repo_id(cls):
3215 def target_repo_id(cls):
3216 # TODO: dan: rename column to target_repo_id
3216 # TODO: dan: rename column to target_repo_id
3217 return Column(
3217 return Column(
3218 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3218 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3219 nullable=False)
3219 nullable=False)
3220
3220
3221 target_ref = Column('other_ref', Unicode(255), nullable=False)
3221 target_ref = Column('other_ref', Unicode(255), nullable=False)
3222 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3222 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3223
3223
3224 # TODO: dan: rename column to last_merge_source_rev
3224 # TODO: dan: rename column to last_merge_source_rev
3225 _last_merge_source_rev = Column(
3225 _last_merge_source_rev = Column(
3226 'last_merge_org_rev', String(40), nullable=True)
3226 'last_merge_org_rev', String(40), nullable=True)
3227 # TODO: dan: rename column to last_merge_target_rev
3227 # TODO: dan: rename column to last_merge_target_rev
3228 _last_merge_target_rev = Column(
3228 _last_merge_target_rev = Column(
3229 'last_merge_other_rev', String(40), nullable=True)
3229 'last_merge_other_rev', String(40), nullable=True)
3230 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3230 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3231 merge_rev = Column('merge_rev', String(40), nullable=True)
3231 merge_rev = Column('merge_rev', String(40), nullable=True)
3232
3232
3233 reviewer_data = Column(
3233 reviewer_data = Column(
3234 'reviewer_data_json', MutationObj.as_mutable(
3234 'reviewer_data_json', MutationObj.as_mutable(
3235 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3235 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3236
3236
3237 @property
3237 @property
3238 def reviewer_data_json(self):
3238 def reviewer_data_json(self):
3239 return json.dumps(self.reviewer_data)
3239 return json.dumps(self.reviewer_data)
3240
3240
3241 @hybrid_property
3241 @hybrid_property
3242 def revisions(self):
3242 def revisions(self):
3243 return self._revisions.split(':') if self._revisions else []
3243 return self._revisions.split(':') if self._revisions else []
3244
3244
3245 @revisions.setter
3245 @revisions.setter
3246 def revisions(self, val):
3246 def revisions(self, val):
3247 self._revisions = ':'.join(val)
3247 self._revisions = ':'.join(val)
3248
3248
3249 @declared_attr
3249 @declared_attr
3250 def author(cls):
3250 def author(cls):
3251 return relationship('User', lazy='joined')
3251 return relationship('User', lazy='joined')
3252
3252
3253 @declared_attr
3253 @declared_attr
3254 def source_repo(cls):
3254 def source_repo(cls):
3255 return relationship(
3255 return relationship(
3256 'Repository',
3256 'Repository',
3257 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3257 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3258
3258
3259 @property
3259 @property
3260 def source_ref_parts(self):
3260 def source_ref_parts(self):
3261 return self.unicode_to_reference(self.source_ref)
3261 return self.unicode_to_reference(self.source_ref)
3262
3262
3263 @declared_attr
3263 @declared_attr
3264 def target_repo(cls):
3264 def target_repo(cls):
3265 return relationship(
3265 return relationship(
3266 'Repository',
3266 'Repository',
3267 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3267 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3268
3268
3269 @property
3269 @property
3270 def target_ref_parts(self):
3270 def target_ref_parts(self):
3271 return self.unicode_to_reference(self.target_ref)
3271 return self.unicode_to_reference(self.target_ref)
3272
3272
3273 @property
3273 @property
3274 def shadow_merge_ref(self):
3274 def shadow_merge_ref(self):
3275 return self.unicode_to_reference(self._shadow_merge_ref)
3275 return self.unicode_to_reference(self._shadow_merge_ref)
3276
3276
3277 @shadow_merge_ref.setter
3277 @shadow_merge_ref.setter
3278 def shadow_merge_ref(self, ref):
3278 def shadow_merge_ref(self, ref):
3279 self._shadow_merge_ref = self.reference_to_unicode(ref)
3279 self._shadow_merge_ref = self.reference_to_unicode(ref)
3280
3280
3281 def unicode_to_reference(self, raw):
3281 def unicode_to_reference(self, raw):
3282 """
3282 """
3283 Convert a unicode (or string) to a reference object.
3283 Convert a unicode (or string) to a reference object.
3284 If unicode evaluates to False it returns None.
3284 If unicode evaluates to False it returns None.
3285 """
3285 """
3286 if raw:
3286 if raw:
3287 refs = raw.split(':')
3287 refs = raw.split(':')
3288 return Reference(*refs)
3288 return Reference(*refs)
3289 else:
3289 else:
3290 return None
3290 return None
3291
3291
3292 def reference_to_unicode(self, ref):
3292 def reference_to_unicode(self, ref):
3293 """
3293 """
3294 Convert a reference object to unicode.
3294 Convert a reference object to unicode.
3295 If reference is None it returns None.
3295 If reference is None it returns None.
3296 """
3296 """
3297 if ref:
3297 if ref:
3298 return u':'.join(ref)
3298 return u':'.join(ref)
3299 else:
3299 else:
3300 return None
3300 return None
3301
3301
3302 def get_api_data(self):
3302 def get_api_data(self):
3303 from pylons import url
3303 from pylons import url
3304 from rhodecode.model.pull_request import PullRequestModel
3304 from rhodecode.model.pull_request import PullRequestModel
3305 pull_request = self
3305 pull_request = self
3306 merge_status = PullRequestModel().merge_status(pull_request)
3306 merge_status = PullRequestModel().merge_status(pull_request)
3307
3307
3308 pull_request_url = url(
3308 pull_request_url = url(
3309 'pullrequest_show', repo_name=self.target_repo.repo_name,
3309 'pullrequest_show', repo_name=self.target_repo.repo_name,
3310 pull_request_id=self.pull_request_id, qualified=True)
3310 pull_request_id=self.pull_request_id, qualified=True)
3311
3311
3312 merge_data = {
3312 merge_data = {
3313 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3313 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3314 'reference': (
3314 'reference': (
3315 pull_request.shadow_merge_ref._asdict()
3315 pull_request.shadow_merge_ref._asdict()
3316 if pull_request.shadow_merge_ref else None),
3316 if pull_request.shadow_merge_ref else None),
3317 }
3317 }
3318
3318
3319 data = {
3319 data = {
3320 'pull_request_id': pull_request.pull_request_id,
3320 'pull_request_id': pull_request.pull_request_id,
3321 'url': pull_request_url,
3321 'url': pull_request_url,
3322 'title': pull_request.title,
3322 'title': pull_request.title,
3323 'description': pull_request.description,
3323 'description': pull_request.description,
3324 'status': pull_request.status,
3324 'status': pull_request.status,
3325 'created_on': pull_request.created_on,
3325 'created_on': pull_request.created_on,
3326 'updated_on': pull_request.updated_on,
3326 'updated_on': pull_request.updated_on,
3327 'commit_ids': pull_request.revisions,
3327 'commit_ids': pull_request.revisions,
3328 'review_status': pull_request.calculated_review_status(),
3328 'review_status': pull_request.calculated_review_status(),
3329 'mergeable': {
3329 'mergeable': {
3330 'status': merge_status[0],
3330 'status': merge_status[0],
3331 'message': unicode(merge_status[1]),
3331 'message': unicode(merge_status[1]),
3332 },
3332 },
3333 'source': {
3333 'source': {
3334 'clone_url': pull_request.source_repo.clone_url(),
3334 'clone_url': pull_request.source_repo.clone_url(),
3335 'repository': pull_request.source_repo.repo_name,
3335 'repository': pull_request.source_repo.repo_name,
3336 'reference': {
3336 'reference': {
3337 'name': pull_request.source_ref_parts.name,
3337 'name': pull_request.source_ref_parts.name,
3338 'type': pull_request.source_ref_parts.type,
3338 'type': pull_request.source_ref_parts.type,
3339 'commit_id': pull_request.source_ref_parts.commit_id,
3339 'commit_id': pull_request.source_ref_parts.commit_id,
3340 },
3340 },
3341 },
3341 },
3342 'target': {
3342 'target': {
3343 'clone_url': pull_request.target_repo.clone_url(),
3343 'clone_url': pull_request.target_repo.clone_url(),
3344 'repository': pull_request.target_repo.repo_name,
3344 'repository': pull_request.target_repo.repo_name,
3345 'reference': {
3345 'reference': {
3346 'name': pull_request.target_ref_parts.name,
3346 'name': pull_request.target_ref_parts.name,
3347 'type': pull_request.target_ref_parts.type,
3347 'type': pull_request.target_ref_parts.type,
3348 'commit_id': pull_request.target_ref_parts.commit_id,
3348 'commit_id': pull_request.target_ref_parts.commit_id,
3349 },
3349 },
3350 },
3350 },
3351 'merge': merge_data,
3351 'merge': merge_data,
3352 'author': pull_request.author.get_api_data(include_secrets=False,
3352 'author': pull_request.author.get_api_data(include_secrets=False,
3353 details='basic'),
3353 details='basic'),
3354 'reviewers': [
3354 'reviewers': [
3355 {
3355 {
3356 'user': reviewer.get_api_data(include_secrets=False,
3356 'user': reviewer.get_api_data(include_secrets=False,
3357 details='basic'),
3357 details='basic'),
3358 'reasons': reasons,
3358 'reasons': reasons,
3359 'review_status': st[0][1].status if st else 'not_reviewed',
3359 'review_status': st[0][1].status if st else 'not_reviewed',
3360 }
3360 }
3361 for reviewer, reasons, mandatory, st in
3361 for reviewer, reasons, mandatory, st in
3362 pull_request.reviewers_statuses()
3362 pull_request.reviewers_statuses()
3363 ]
3363 ]
3364 }
3364 }
3365
3365
3366 return data
3366 return data
3367
3367
3368
3368
3369 class PullRequest(Base, _PullRequestBase):
3369 class PullRequest(Base, _PullRequestBase):
3370 __tablename__ = 'pull_requests'
3370 __tablename__ = 'pull_requests'
3371 __table_args__ = (
3371 __table_args__ = (
3372 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3372 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3373 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3373 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3374 )
3374 )
3375
3375
3376 pull_request_id = Column(
3376 pull_request_id = Column(
3377 'pull_request_id', Integer(), nullable=False, primary_key=True)
3377 'pull_request_id', Integer(), nullable=False, primary_key=True)
3378
3378
3379 def __repr__(self):
3379 def __repr__(self):
3380 if self.pull_request_id:
3380 if self.pull_request_id:
3381 return '<DB:PullRequest #%s>' % self.pull_request_id
3381 return '<DB:PullRequest #%s>' % self.pull_request_id
3382 else:
3382 else:
3383 return '<DB:PullRequest at %#x>' % id(self)
3383 return '<DB:PullRequest at %#x>' % id(self)
3384
3384
3385 reviewers = relationship('PullRequestReviewers',
3385 reviewers = relationship('PullRequestReviewers',
3386 cascade="all, delete, delete-orphan")
3386 cascade="all, delete, delete-orphan")
3387 statuses = relationship('ChangesetStatus')
3387 statuses = relationship('ChangesetStatus')
3388 comments = relationship('ChangesetComment',
3388 comments = relationship('ChangesetComment',
3389 cascade="all, delete, delete-orphan")
3389 cascade="all, delete, delete-orphan")
3390 versions = relationship('PullRequestVersion',
3390 versions = relationship('PullRequestVersion',
3391 cascade="all, delete, delete-orphan",
3391 cascade="all, delete, delete-orphan",
3392 lazy='dynamic')
3392 lazy='dynamic')
3393
3393
3394 @classmethod
3394 @classmethod
3395 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3395 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3396 internal_methods=None):
3396 internal_methods=None):
3397
3397
3398 class PullRequestDisplay(object):
3398 class PullRequestDisplay(object):
3399 """
3399 """
3400 Special object wrapper for showing PullRequest data via Versions
3400 Special object wrapper for showing PullRequest data via Versions
3401 It mimics PR object as close as possible. This is read only object
3401 It mimics PR object as close as possible. This is read only object
3402 just for display
3402 just for display
3403 """
3403 """
3404
3404
3405 def __init__(self, attrs, internal=None):
3405 def __init__(self, attrs, internal=None):
3406 self.attrs = attrs
3406 self.attrs = attrs
3407 # internal have priority over the given ones via attrs
3407 # internal have priority over the given ones via attrs
3408 self.internal = internal or ['versions']
3408 self.internal = internal or ['versions']
3409
3409
3410 def __getattr__(self, item):
3410 def __getattr__(self, item):
3411 if item in self.internal:
3411 if item in self.internal:
3412 return getattr(self, item)
3412 return getattr(self, item)
3413 try:
3413 try:
3414 return self.attrs[item]
3414 return self.attrs[item]
3415 except KeyError:
3415 except KeyError:
3416 raise AttributeError(
3416 raise AttributeError(
3417 '%s object has no attribute %s' % (self, item))
3417 '%s object has no attribute %s' % (self, item))
3418
3418
3419 def __repr__(self):
3419 def __repr__(self):
3420 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3420 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3421
3421
3422 def versions(self):
3422 def versions(self):
3423 return pull_request_obj.versions.order_by(
3423 return pull_request_obj.versions.order_by(
3424 PullRequestVersion.pull_request_version_id).all()
3424 PullRequestVersion.pull_request_version_id).all()
3425
3425
3426 def is_closed(self):
3426 def is_closed(self):
3427 return pull_request_obj.is_closed()
3427 return pull_request_obj.is_closed()
3428
3428
3429 @property
3429 @property
3430 def pull_request_version_id(self):
3430 def pull_request_version_id(self):
3431 return getattr(pull_request_obj, 'pull_request_version_id', None)
3431 return getattr(pull_request_obj, 'pull_request_version_id', None)
3432
3432
3433 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3433 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3434
3434
3435 attrs.author = StrictAttributeDict(
3435 attrs.author = StrictAttributeDict(
3436 pull_request_obj.author.get_api_data())
3436 pull_request_obj.author.get_api_data())
3437 if pull_request_obj.target_repo:
3437 if pull_request_obj.target_repo:
3438 attrs.target_repo = StrictAttributeDict(
3438 attrs.target_repo = StrictAttributeDict(
3439 pull_request_obj.target_repo.get_api_data())
3439 pull_request_obj.target_repo.get_api_data())
3440 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3440 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3441
3441
3442 if pull_request_obj.source_repo:
3442 if pull_request_obj.source_repo:
3443 attrs.source_repo = StrictAttributeDict(
3443 attrs.source_repo = StrictAttributeDict(
3444 pull_request_obj.source_repo.get_api_data())
3444 pull_request_obj.source_repo.get_api_data())
3445 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3445 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3446
3446
3447 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3447 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3448 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3448 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3449 attrs.revisions = pull_request_obj.revisions
3449 attrs.revisions = pull_request_obj.revisions
3450
3450
3451 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3451 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3452 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3452 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3453 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3453 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3454
3454
3455 return PullRequestDisplay(attrs, internal=internal_methods)
3455 return PullRequestDisplay(attrs, internal=internal_methods)
3456
3456
3457 def is_closed(self):
3457 def is_closed(self):
3458 return self.status == self.STATUS_CLOSED
3458 return self.status == self.STATUS_CLOSED
3459
3459
3460 def __json__(self):
3460 def __json__(self):
3461 return {
3461 return {
3462 'revisions': self.revisions,
3462 'revisions': self.revisions,
3463 }
3463 }
3464
3464
3465 def calculated_review_status(self):
3465 def calculated_review_status(self):
3466 from rhodecode.model.changeset_status import ChangesetStatusModel
3466 from rhodecode.model.changeset_status import ChangesetStatusModel
3467 return ChangesetStatusModel().calculated_review_status(self)
3467 return ChangesetStatusModel().calculated_review_status(self)
3468
3468
3469 def reviewers_statuses(self):
3469 def reviewers_statuses(self):
3470 from rhodecode.model.changeset_status import ChangesetStatusModel
3470 from rhodecode.model.changeset_status import ChangesetStatusModel
3471 return ChangesetStatusModel().reviewers_statuses(self)
3471 return ChangesetStatusModel().reviewers_statuses(self)
3472
3472
3473 @property
3473 @property
3474 def workspace_id(self):
3474 def workspace_id(self):
3475 from rhodecode.model.pull_request import PullRequestModel
3475 from rhodecode.model.pull_request import PullRequestModel
3476 return PullRequestModel()._workspace_id(self)
3476 return PullRequestModel()._workspace_id(self)
3477
3477
3478 def get_shadow_repo(self):
3478 def get_shadow_repo(self):
3479 workspace_id = self.workspace_id
3479 workspace_id = self.workspace_id
3480 vcs_obj = self.target_repo.scm_instance()
3480 vcs_obj = self.target_repo.scm_instance()
3481 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3481 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3482 workspace_id)
3482 workspace_id)
3483 return vcs_obj._get_shadow_instance(shadow_repository_path)
3483 return vcs_obj._get_shadow_instance(shadow_repository_path)
3484
3484
3485
3485
3486 class PullRequestVersion(Base, _PullRequestBase):
3486 class PullRequestVersion(Base, _PullRequestBase):
3487 __tablename__ = 'pull_request_versions'
3487 __tablename__ = 'pull_request_versions'
3488 __table_args__ = (
3488 __table_args__ = (
3489 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3489 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3490 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3490 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3491 )
3491 )
3492
3492
3493 pull_request_version_id = Column(
3493 pull_request_version_id = Column(
3494 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3494 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3495 pull_request_id = Column(
3495 pull_request_id = Column(
3496 'pull_request_id', Integer(),
3496 'pull_request_id', Integer(),
3497 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3497 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3498 pull_request = relationship('PullRequest')
3498 pull_request = relationship('PullRequest')
3499
3499
3500 def __repr__(self):
3500 def __repr__(self):
3501 if self.pull_request_version_id:
3501 if self.pull_request_version_id:
3502 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3502 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3503 else:
3503 else:
3504 return '<DB:PullRequestVersion at %#x>' % id(self)
3504 return '<DB:PullRequestVersion at %#x>' % id(self)
3505
3505
3506 @property
3506 @property
3507 def reviewers(self):
3507 def reviewers(self):
3508 return self.pull_request.reviewers
3508 return self.pull_request.reviewers
3509
3509
3510 @property
3510 @property
3511 def versions(self):
3511 def versions(self):
3512 return self.pull_request.versions
3512 return self.pull_request.versions
3513
3513
3514 def is_closed(self):
3514 def is_closed(self):
3515 # calculate from original
3515 # calculate from original
3516 return self.pull_request.status == self.STATUS_CLOSED
3516 return self.pull_request.status == self.STATUS_CLOSED
3517
3517
3518 def calculated_review_status(self):
3518 def calculated_review_status(self):
3519 return self.pull_request.calculated_review_status()
3519 return self.pull_request.calculated_review_status()
3520
3520
3521 def reviewers_statuses(self):
3521 def reviewers_statuses(self):
3522 return self.pull_request.reviewers_statuses()
3522 return self.pull_request.reviewers_statuses()
3523
3523
3524
3524
3525 class PullRequestReviewers(Base, BaseModel):
3525 class PullRequestReviewers(Base, BaseModel):
3526 __tablename__ = 'pull_request_reviewers'
3526 __tablename__ = 'pull_request_reviewers'
3527 __table_args__ = (
3527 __table_args__ = (
3528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3529 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3529 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3530 )
3530 )
3531
3531
3532 @hybrid_property
3532 @hybrid_property
3533 def reasons(self):
3533 def reasons(self):
3534 if not self._reasons:
3534 if not self._reasons:
3535 return []
3535 return []
3536 return self._reasons
3536 return self._reasons
3537
3537
3538 @reasons.setter
3538 @reasons.setter
3539 def reasons(self, val):
3539 def reasons(self, val):
3540 val = val or []
3540 val = val or []
3541 if any(not isinstance(x, basestring) for x in val):
3541 if any(not isinstance(x, basestring) for x in val):
3542 raise Exception('invalid reasons type, must be list of strings')
3542 raise Exception('invalid reasons type, must be list of strings')
3543 self._reasons = val
3543 self._reasons = val
3544
3544
3545 pull_requests_reviewers_id = Column(
3545 pull_requests_reviewers_id = Column(
3546 'pull_requests_reviewers_id', Integer(), nullable=False,
3546 'pull_requests_reviewers_id', Integer(), nullable=False,
3547 primary_key=True)
3547 primary_key=True)
3548 pull_request_id = Column(
3548 pull_request_id = Column(
3549 "pull_request_id", Integer(),
3549 "pull_request_id", Integer(),
3550 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3550 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3551 user_id = Column(
3551 user_id = Column(
3552 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3552 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3553 _reasons = Column(
3553 _reasons = Column(
3554 'reason', MutationList.as_mutable(
3554 'reason', MutationList.as_mutable(
3555 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3555 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3556 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3556 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3557 user = relationship('User')
3557 user = relationship('User')
3558 pull_request = relationship('PullRequest')
3558 pull_request = relationship('PullRequest')
3559
3559
3560
3560
3561 class Notification(Base, BaseModel):
3561 class Notification(Base, BaseModel):
3562 __tablename__ = 'notifications'
3562 __tablename__ = 'notifications'
3563 __table_args__ = (
3563 __table_args__ = (
3564 Index('notification_type_idx', 'type'),
3564 Index('notification_type_idx', 'type'),
3565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3567 )
3567 )
3568
3568
3569 TYPE_CHANGESET_COMMENT = u'cs_comment'
3569 TYPE_CHANGESET_COMMENT = u'cs_comment'
3570 TYPE_MESSAGE = u'message'
3570 TYPE_MESSAGE = u'message'
3571 TYPE_MENTION = u'mention'
3571 TYPE_MENTION = u'mention'
3572 TYPE_REGISTRATION = u'registration'
3572 TYPE_REGISTRATION = u'registration'
3573 TYPE_PULL_REQUEST = u'pull_request'
3573 TYPE_PULL_REQUEST = u'pull_request'
3574 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3574 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3575
3575
3576 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3576 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3577 subject = Column('subject', Unicode(512), nullable=True)
3577 subject = Column('subject', Unicode(512), nullable=True)
3578 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3578 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3579 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3579 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3581 type_ = Column('type', Unicode(255))
3581 type_ = Column('type', Unicode(255))
3582
3582
3583 created_by_user = relationship('User')
3583 created_by_user = relationship('User')
3584 notifications_to_users = relationship('UserNotification', lazy='joined',
3584 notifications_to_users = relationship('UserNotification', lazy='joined',
3585 cascade="all, delete, delete-orphan")
3585 cascade="all, delete, delete-orphan")
3586
3586
3587 @property
3587 @property
3588 def recipients(self):
3588 def recipients(self):
3589 return [x.user for x in UserNotification.query()\
3589 return [x.user for x in UserNotification.query()\
3590 .filter(UserNotification.notification == self)\
3590 .filter(UserNotification.notification == self)\
3591 .order_by(UserNotification.user_id.asc()).all()]
3591 .order_by(UserNotification.user_id.asc()).all()]
3592
3592
3593 @classmethod
3593 @classmethod
3594 def create(cls, created_by, subject, body, recipients, type_=None):
3594 def create(cls, created_by, subject, body, recipients, type_=None):
3595 if type_ is None:
3595 if type_ is None:
3596 type_ = Notification.TYPE_MESSAGE
3596 type_ = Notification.TYPE_MESSAGE
3597
3597
3598 notification = cls()
3598 notification = cls()
3599 notification.created_by_user = created_by
3599 notification.created_by_user = created_by
3600 notification.subject = subject
3600 notification.subject = subject
3601 notification.body = body
3601 notification.body = body
3602 notification.type_ = type_
3602 notification.type_ = type_
3603 notification.created_on = datetime.datetime.now()
3603 notification.created_on = datetime.datetime.now()
3604
3604
3605 for u in recipients:
3605 for u in recipients:
3606 assoc = UserNotification()
3606 assoc = UserNotification()
3607 assoc.notification = notification
3607 assoc.notification = notification
3608
3608
3609 # if created_by is inside recipients mark his notification
3609 # if created_by is inside recipients mark his notification
3610 # as read
3610 # as read
3611 if u.user_id == created_by.user_id:
3611 if u.user_id == created_by.user_id:
3612 assoc.read = True
3612 assoc.read = True
3613
3613
3614 u.notifications.append(assoc)
3614 u.notifications.append(assoc)
3615 Session().add(notification)
3615 Session().add(notification)
3616
3616
3617 return notification
3617 return notification
3618
3618
3619 @property
3619 @property
3620 def description(self):
3620 def description(self):
3621 from rhodecode.model.notification import NotificationModel
3621 from rhodecode.model.notification import NotificationModel
3622 return NotificationModel().make_description(self)
3622 return NotificationModel().make_description(self)
3623
3623
3624
3624
3625 class UserNotification(Base, BaseModel):
3625 class UserNotification(Base, BaseModel):
3626 __tablename__ = 'user_to_notification'
3626 __tablename__ = 'user_to_notification'
3627 __table_args__ = (
3627 __table_args__ = (
3628 UniqueConstraint('user_id', 'notification_id'),
3628 UniqueConstraint('user_id', 'notification_id'),
3629 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3629 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3630 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3630 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3631 )
3631 )
3632 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3632 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3633 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3633 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3634 read = Column('read', Boolean, default=False)
3634 read = Column('read', Boolean, default=False)
3635 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3635 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3636
3636
3637 user = relationship('User', lazy="joined")
3637 user = relationship('User', lazy="joined")
3638 notification = relationship('Notification', lazy="joined",
3638 notification = relationship('Notification', lazy="joined",
3639 order_by=lambda: Notification.created_on.desc(),)
3639 order_by=lambda: Notification.created_on.desc(),)
3640
3640
3641 def mark_as_read(self):
3641 def mark_as_read(self):
3642 self.read = True
3642 self.read = True
3643 Session().add(self)
3643 Session().add(self)
3644
3644
3645
3645
3646 class Gist(Base, BaseModel):
3646 class Gist(Base, BaseModel):
3647 __tablename__ = 'gists'
3647 __tablename__ = 'gists'
3648 __table_args__ = (
3648 __table_args__ = (
3649 Index('g_gist_access_id_idx', 'gist_access_id'),
3649 Index('g_gist_access_id_idx', 'gist_access_id'),
3650 Index('g_created_on_idx', 'created_on'),
3650 Index('g_created_on_idx', 'created_on'),
3651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3652 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3652 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3653 )
3653 )
3654 GIST_PUBLIC = u'public'
3654 GIST_PUBLIC = u'public'
3655 GIST_PRIVATE = u'private'
3655 GIST_PRIVATE = u'private'
3656 DEFAULT_FILENAME = u'gistfile1.txt'
3656 DEFAULT_FILENAME = u'gistfile1.txt'
3657
3657
3658 ACL_LEVEL_PUBLIC = u'acl_public'
3658 ACL_LEVEL_PUBLIC = u'acl_public'
3659 ACL_LEVEL_PRIVATE = u'acl_private'
3659 ACL_LEVEL_PRIVATE = u'acl_private'
3660
3660
3661 gist_id = Column('gist_id', Integer(), primary_key=True)
3661 gist_id = Column('gist_id', Integer(), primary_key=True)
3662 gist_access_id = Column('gist_access_id', Unicode(250))
3662 gist_access_id = Column('gist_access_id', Unicode(250))
3663 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3663 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3664 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3664 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3665 gist_expires = Column('gist_expires', Float(53), nullable=False)
3665 gist_expires = Column('gist_expires', Float(53), nullable=False)
3666 gist_type = Column('gist_type', Unicode(128), nullable=False)
3666 gist_type = Column('gist_type', Unicode(128), nullable=False)
3667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3668 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3668 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3669 acl_level = Column('acl_level', Unicode(128), nullable=True)
3669 acl_level = Column('acl_level', Unicode(128), nullable=True)
3670
3670
3671 owner = relationship('User')
3671 owner = relationship('User')
3672
3672
3673 def __repr__(self):
3673 def __repr__(self):
3674 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3674 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3675
3675
3676 @classmethod
3676 @classmethod
3677 def get_or_404(cls, id_, pyramid_exc=False):
3677 def get_or_404(cls, id_, pyramid_exc=False):
3678
3678
3679 if pyramid_exc:
3679 if pyramid_exc:
3680 from pyramid.httpexceptions import HTTPNotFound
3680 from pyramid.httpexceptions import HTTPNotFound
3681 else:
3681 else:
3682 from webob.exc import HTTPNotFound
3682 from webob.exc import HTTPNotFound
3683
3683
3684 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3684 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3685 if not res:
3685 if not res:
3686 raise HTTPNotFound
3686 raise HTTPNotFound
3687 return res
3687 return res
3688
3688
3689 @classmethod
3689 @classmethod
3690 def get_by_access_id(cls, gist_access_id):
3690 def get_by_access_id(cls, gist_access_id):
3691 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3691 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3692
3692
3693 def gist_url(self):
3693 def gist_url(self):
3694 import rhodecode
3694 import rhodecode
3695 from pylons import url
3695 from pylons import url
3696
3696
3697 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3697 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3698 if alias_url:
3698 if alias_url:
3699 return alias_url.replace('{gistid}', self.gist_access_id)
3699 return alias_url.replace('{gistid}', self.gist_access_id)
3700
3700
3701 return url('gist', gist_id=self.gist_access_id, qualified=True)
3701 return url('gist', gist_id=self.gist_access_id, qualified=True)
3702
3702
3703 @classmethod
3703 @classmethod
3704 def base_path(cls):
3704 def base_path(cls):
3705 """
3705 """
3706 Returns base path when all gists are stored
3706 Returns base path when all gists are stored
3707
3707
3708 :param cls:
3708 :param cls:
3709 """
3709 """
3710 from rhodecode.model.gist import GIST_STORE_LOC
3710 from rhodecode.model.gist import GIST_STORE_LOC
3711 q = Session().query(RhodeCodeUi)\
3711 q = Session().query(RhodeCodeUi)\
3712 .filter(RhodeCodeUi.ui_key == URL_SEP)
3712 .filter(RhodeCodeUi.ui_key == URL_SEP)
3713 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3713 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3714 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3714 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3715
3715
3716 def get_api_data(self):
3716 def get_api_data(self):
3717 """
3717 """
3718 Common function for generating gist related data for API
3718 Common function for generating gist related data for API
3719 """
3719 """
3720 gist = self
3720 gist = self
3721 data = {
3721 data = {
3722 'gist_id': gist.gist_id,
3722 'gist_id': gist.gist_id,
3723 'type': gist.gist_type,
3723 'type': gist.gist_type,
3724 'access_id': gist.gist_access_id,
3724 'access_id': gist.gist_access_id,
3725 'description': gist.gist_description,
3725 'description': gist.gist_description,
3726 'url': gist.gist_url(),
3726 'url': gist.gist_url(),
3727 'expires': gist.gist_expires,
3727 'expires': gist.gist_expires,
3728 'created_on': gist.created_on,
3728 'created_on': gist.created_on,
3729 'modified_at': gist.modified_at,
3729 'modified_at': gist.modified_at,
3730 'content': None,
3730 'content': None,
3731 'acl_level': gist.acl_level,
3731 'acl_level': gist.acl_level,
3732 }
3732 }
3733 return data
3733 return data
3734
3734
3735 def __json__(self):
3735 def __json__(self):
3736 data = dict(
3736 data = dict(
3737 )
3737 )
3738 data.update(self.get_api_data())
3738 data.update(self.get_api_data())
3739 return data
3739 return data
3740 # SCM functions
3740 # SCM functions
3741
3741
3742 def scm_instance(self, **kwargs):
3742 def scm_instance(self, **kwargs):
3743 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3743 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3744 return get_vcs_instance(
3744 return get_vcs_instance(
3745 repo_path=safe_str(full_repo_path), create=False)
3745 repo_path=safe_str(full_repo_path), create=False)
3746
3746
3747
3747
3748 class ExternalIdentity(Base, BaseModel):
3748 class ExternalIdentity(Base, BaseModel):
3749 __tablename__ = 'external_identities'
3749 __tablename__ = 'external_identities'
3750 __table_args__ = (
3750 __table_args__ = (
3751 Index('local_user_id_idx', 'local_user_id'),
3751 Index('local_user_id_idx', 'local_user_id'),
3752 Index('external_id_idx', 'external_id'),
3752 Index('external_id_idx', 'external_id'),
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3754 'mysql_charset': 'utf8'})
3754 'mysql_charset': 'utf8'})
3755
3755
3756 external_id = Column('external_id', Unicode(255), default=u'',
3756 external_id = Column('external_id', Unicode(255), default=u'',
3757 primary_key=True)
3757 primary_key=True)
3758 external_username = Column('external_username', Unicode(1024), default=u'')
3758 external_username = Column('external_username', Unicode(1024), default=u'')
3759 local_user_id = Column('local_user_id', Integer(),
3759 local_user_id = Column('local_user_id', Integer(),
3760 ForeignKey('users.user_id'), primary_key=True)
3760 ForeignKey('users.user_id'), primary_key=True)
3761 provider_name = Column('provider_name', Unicode(255), default=u'',
3761 provider_name = Column('provider_name', Unicode(255), default=u'',
3762 primary_key=True)
3762 primary_key=True)
3763 access_token = Column('access_token', String(1024), default=u'')
3763 access_token = Column('access_token', String(1024), default=u'')
3764 alt_token = Column('alt_token', String(1024), default=u'')
3764 alt_token = Column('alt_token', String(1024), default=u'')
3765 token_secret = Column('token_secret', String(1024), default=u'')
3765 token_secret = Column('token_secret', String(1024), default=u'')
3766
3766
3767 @classmethod
3767 @classmethod
3768 def by_external_id_and_provider(cls, external_id, provider_name,
3768 def by_external_id_and_provider(cls, external_id, provider_name,
3769 local_user_id=None):
3769 local_user_id=None):
3770 """
3770 """
3771 Returns ExternalIdentity instance based on search params
3771 Returns ExternalIdentity instance based on search params
3772
3772
3773 :param external_id:
3773 :param external_id:
3774 :param provider_name:
3774 :param provider_name:
3775 :return: ExternalIdentity
3775 :return: ExternalIdentity
3776 """
3776 """
3777 query = cls.query()
3777 query = cls.query()
3778 query = query.filter(cls.external_id == external_id)
3778 query = query.filter(cls.external_id == external_id)
3779 query = query.filter(cls.provider_name == provider_name)
3779 query = query.filter(cls.provider_name == provider_name)
3780 if local_user_id:
3780 if local_user_id:
3781 query = query.filter(cls.local_user_id == local_user_id)
3781 query = query.filter(cls.local_user_id == local_user_id)
3782 return query.first()
3782 return query.first()
3783
3783
3784 @classmethod
3784 @classmethod
3785 def user_by_external_id_and_provider(cls, external_id, provider_name):
3785 def user_by_external_id_and_provider(cls, external_id, provider_name):
3786 """
3786 """
3787 Returns User instance based on search params
3787 Returns User instance based on search params
3788
3788
3789 :param external_id:
3789 :param external_id:
3790 :param provider_name:
3790 :param provider_name:
3791 :return: User
3791 :return: User
3792 """
3792 """
3793 query = User.query()
3793 query = User.query()
3794 query = query.filter(cls.external_id == external_id)
3794 query = query.filter(cls.external_id == external_id)
3795 query = query.filter(cls.provider_name == provider_name)
3795 query = query.filter(cls.provider_name == provider_name)
3796 query = query.filter(User.user_id == cls.local_user_id)
3796 query = query.filter(User.user_id == cls.local_user_id)
3797 return query.first()
3797 return query.first()
3798
3798
3799 @classmethod
3799 @classmethod
3800 def by_local_user_id(cls, local_user_id):
3800 def by_local_user_id(cls, local_user_id):
3801 """
3801 """
3802 Returns all tokens for user
3802 Returns all tokens for user
3803
3803
3804 :param local_user_id:
3804 :param local_user_id:
3805 :return: ExternalIdentity
3805 :return: ExternalIdentity
3806 """
3806 """
3807 query = cls.query()
3807 query = cls.query()
3808 query = query.filter(cls.local_user_id == local_user_id)
3808 query = query.filter(cls.local_user_id == local_user_id)
3809 return query
3809 return query
3810
3810
3811
3811
3812 class Integration(Base, BaseModel):
3812 class Integration(Base, BaseModel):
3813 __tablename__ = 'integrations'
3813 __tablename__ = 'integrations'
3814 __table_args__ = (
3814 __table_args__ = (
3815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3817 )
3817 )
3818
3818
3819 integration_id = Column('integration_id', Integer(), primary_key=True)
3819 integration_id = Column('integration_id', Integer(), primary_key=True)
3820 integration_type = Column('integration_type', String(255))
3820 integration_type = Column('integration_type', String(255))
3821 enabled = Column('enabled', Boolean(), nullable=False)
3821 enabled = Column('enabled', Boolean(), nullable=False)
3822 name = Column('name', String(255), nullable=False)
3822 name = Column('name', String(255), nullable=False)
3823 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3823 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3824 default=False)
3824 default=False)
3825
3825
3826 settings = Column(
3826 settings = Column(
3827 'settings_json', MutationObj.as_mutable(
3827 'settings_json', MutationObj.as_mutable(
3828 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3828 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3829 repo_id = Column(
3829 repo_id = Column(
3830 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3830 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3831 nullable=True, unique=None, default=None)
3831 nullable=True, unique=None, default=None)
3832 repo = relationship('Repository', lazy='joined')
3832 repo = relationship('Repository', lazy='joined')
3833
3833
3834 repo_group_id = Column(
3834 repo_group_id = Column(
3835 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3835 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3836 nullable=True, unique=None, default=None)
3836 nullable=True, unique=None, default=None)
3837 repo_group = relationship('RepoGroup', lazy='joined')
3837 repo_group = relationship('RepoGroup', lazy='joined')
3838
3838
3839 @property
3839 @property
3840 def scope(self):
3840 def scope(self):
3841 if self.repo:
3841 if self.repo:
3842 return repr(self.repo)
3842 return repr(self.repo)
3843 if self.repo_group:
3843 if self.repo_group:
3844 if self.child_repos_only:
3844 if self.child_repos_only:
3845 return repr(self.repo_group) + ' (child repos only)'
3845 return repr(self.repo_group) + ' (child repos only)'
3846 else:
3846 else:
3847 return repr(self.repo_group) + ' (recursive)'
3847 return repr(self.repo_group) + ' (recursive)'
3848 if self.child_repos_only:
3848 if self.child_repos_only:
3849 return 'root_repos'
3849 return 'root_repos'
3850 return 'global'
3850 return 'global'
3851
3851
3852 def __repr__(self):
3852 def __repr__(self):
3853 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3853 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3854
3854
3855
3855
3856 class RepoReviewRuleUser(Base, BaseModel):
3856 class RepoReviewRuleUser(Base, BaseModel):
3857 __tablename__ = 'repo_review_rules_users'
3857 __tablename__ = 'repo_review_rules_users'
3858 __table_args__ = (
3858 __table_args__ = (
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3861 )
3861 )
3862 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3862 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3863 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3863 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3864 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3864 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3865 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3865 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3866 user = relationship('User')
3866 user = relationship('User')
3867
3867
3868 def rule_data(self):
3868 def rule_data(self):
3869 return {
3869 return {
3870 'mandatory': self.mandatory
3870 'mandatory': self.mandatory
3871 }
3871 }
3872
3872
3873
3873
3874 class RepoReviewRuleUserGroup(Base, BaseModel):
3874 class RepoReviewRuleUserGroup(Base, BaseModel):
3875 __tablename__ = 'repo_review_rules_users_groups'
3875 __tablename__ = 'repo_review_rules_users_groups'
3876 __table_args__ = (
3876 __table_args__ = (
3877 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3877 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3878 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3878 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3879 )
3879 )
3880 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3880 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3881 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3881 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3882 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3882 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3883 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3883 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3884 users_group = relationship('UserGroup')
3884 users_group = relationship('UserGroup')
3885
3885
3886 def rule_data(self):
3886 def rule_data(self):
3887 return {
3887 return {
3888 'mandatory': self.mandatory
3888 'mandatory': self.mandatory
3889 }
3889 }
3890
3890
3891
3891
3892 class RepoReviewRule(Base, BaseModel):
3892 class RepoReviewRule(Base, BaseModel):
3893 __tablename__ = 'repo_review_rules'
3893 __tablename__ = 'repo_review_rules'
3894 __table_args__ = (
3894 __table_args__ = (
3895 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3895 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3896 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3896 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3897 )
3897 )
3898
3898
3899 repo_review_rule_id = Column(
3899 repo_review_rule_id = Column(
3900 'repo_review_rule_id', Integer(), primary_key=True)
3900 'repo_review_rule_id', Integer(), primary_key=True)
3901 repo_id = Column(
3901 repo_id = Column(
3902 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3902 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3903 repo = relationship('Repository', backref='review_rules')
3903 repo = relationship('Repository', backref='review_rules')
3904
3904
3905 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3905 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3906 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3906 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3907
3907
3908 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3908 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
3909 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3909 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
3910 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
3910 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
3911 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
3911
3912
3912 rule_users = relationship('RepoReviewRuleUser')
3913 rule_users = relationship('RepoReviewRuleUser')
3913 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3914 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3914
3915
3915 @hybrid_property
3916 @hybrid_property
3916 def branch_pattern(self):
3917 def branch_pattern(self):
3917 return self._branch_pattern or '*'
3918 return self._branch_pattern or '*'
3918
3919
3919 def _validate_glob(self, value):
3920 def _validate_glob(self, value):
3920 re.compile('^' + glob2re(value) + '$')
3921 re.compile('^' + glob2re(value) + '$')
3921
3922
3922 @branch_pattern.setter
3923 @branch_pattern.setter
3923 def branch_pattern(self, value):
3924 def branch_pattern(self, value):
3924 self._validate_glob(value)
3925 self._validate_glob(value)
3925 self._branch_pattern = value or '*'
3926 self._branch_pattern = value or '*'
3926
3927
3927 @hybrid_property
3928 @hybrid_property
3928 def file_pattern(self):
3929 def file_pattern(self):
3929 return self._file_pattern or '*'
3930 return self._file_pattern or '*'
3930
3931
3931 @file_pattern.setter
3932 @file_pattern.setter
3932 def file_pattern(self, value):
3933 def file_pattern(self, value):
3933 self._validate_glob(value)
3934 self._validate_glob(value)
3934 self._file_pattern = value or '*'
3935 self._file_pattern = value or '*'
3935
3936
3936 def matches(self, branch, files_changed):
3937 def matches(self, branch, files_changed):
3937 """
3938 """
3938 Check if this review rule matches a branch/files in a pull request
3939 Check if this review rule matches a branch/files in a pull request
3939
3940
3940 :param branch: branch name for the commit
3941 :param branch: branch name for the commit
3941 :param files_changed: list of file paths changed in the pull request
3942 :param files_changed: list of file paths changed in the pull request
3942 """
3943 """
3943
3944
3944 branch = branch or ''
3945 branch = branch or ''
3945 files_changed = files_changed or []
3946 files_changed = files_changed or []
3946
3947
3947 branch_matches = True
3948 branch_matches = True
3948 if branch:
3949 if branch:
3949 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3950 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3950 branch_matches = bool(branch_regex.search(branch))
3951 branch_matches = bool(branch_regex.search(branch))
3951
3952
3952 files_matches = True
3953 files_matches = True
3953 if self.file_pattern != '*':
3954 if self.file_pattern != '*':
3954 files_matches = False
3955 files_matches = False
3955 file_regex = re.compile(glob2re(self.file_pattern))
3956 file_regex = re.compile(glob2re(self.file_pattern))
3956 for filename in files_changed:
3957 for filename in files_changed:
3957 if file_regex.search(filename):
3958 if file_regex.search(filename):
3958 files_matches = True
3959 files_matches = True
3959 break
3960 break
3960
3961
3961 return branch_matches and files_matches
3962 return branch_matches and files_matches
3962
3963
3963 @property
3964 @property
3964 def review_users(self):
3965 def review_users(self):
3965 """ Returns the users which this rule applies to """
3966 """ Returns the users which this rule applies to """
3966
3967
3967 users = collections.OrderedDict()
3968 users = collections.OrderedDict()
3968
3969
3969 for rule_user in self.rule_users:
3970 for rule_user in self.rule_users:
3970 if rule_user.user.active:
3971 if rule_user.user.active:
3971 if rule_user.user not in users:
3972 if rule_user.user not in users:
3972 users[rule_user.user.username] = {
3973 users[rule_user.user.username] = {
3973 'user': rule_user.user,
3974 'user': rule_user.user,
3974 'source': 'user',
3975 'source': 'user',
3975 'source_data': {},
3976 'source_data': {},
3976 'data': rule_user.rule_data()
3977 'data': rule_user.rule_data()
3977 }
3978 }
3978
3979
3979 for rule_user_group in self.rule_user_groups:
3980 for rule_user_group in self.rule_user_groups:
3980 source_data = {
3981 source_data = {
3981 'name': rule_user_group.users_group.users_group_name,
3982 'name': rule_user_group.users_group.users_group_name,
3982 'members': len(rule_user_group.users_group.members)
3983 'members': len(rule_user_group.users_group.members)
3983 }
3984 }
3984 for member in rule_user_group.users_group.members:
3985 for member in rule_user_group.users_group.members:
3985 if member.user.active:
3986 if member.user.active:
3986 users[member.user.username] = {
3987 users[member.user.username] = {
3987 'user': member.user,
3988 'user': member.user,
3988 'source': 'user_group',
3989 'source': 'user_group',
3989 'source_data': source_data,
3990 'source_data': source_data,
3990 'data': rule_user_group.rule_data()
3991 'data': rule_user_group.rule_data()
3991 }
3992 }
3992
3993
3993 return users
3994 return users
3994
3995
3995 def __repr__(self):
3996 def __repr__(self):
3996 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3997 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3997 self.repo_review_rule_id, self.repo)
3998 self.repo_review_rule_id, self.repo)
3998
3999
3999
4000
4000 class DbMigrateVersion(Base, BaseModel):
4001 class DbMigrateVersion(Base, BaseModel):
4001 __tablename__ = 'db_migrate_version'
4002 __tablename__ = 'db_migrate_version'
4002 __table_args__ = (
4003 __table_args__ = (
4003 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4004 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4004 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4005 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4005 )
4006 )
4006 repository_id = Column('repository_id', String(250), primary_key=True)
4007 repository_id = Column('repository_id', String(250), primary_key=True)
4007 repository_path = Column('repository_path', Text)
4008 repository_path = Column('repository_path', Text)
4008 version = Column('version', Integer)
4009 version = Column('version', Integer)
4009
4010
4010
4011
4011 class DbSession(Base, BaseModel):
4012 class DbSession(Base, BaseModel):
4012 __tablename__ = 'db_session'
4013 __tablename__ = 'db_session'
4013 __table_args__ = (
4014 __table_args__ = (
4014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4015 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4016 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4016 )
4017 )
4017
4018
4018 def __repr__(self):
4019 def __repr__(self):
4019 return '<DB:DbSession({})>'.format(self.id)
4020 return '<DB:DbSession({})>'.format(self.id)
4020
4021
4021 id = Column('id', Integer())
4022 id = Column('id', Integer())
4022 namespace = Column('namespace', String(255), primary_key=True)
4023 namespace = Column('namespace', String(255), primary_key=True)
4023 accessed = Column('accessed', DateTime, nullable=False)
4024 accessed = Column('accessed', DateTime, nullable=False)
4024 created = Column('created', DateTime, nullable=False)
4025 created = Column('created', DateTime, nullable=False)
4025 data = Column('data', PickleType, nullable=False)
4026 data = Column('data', PickleType, nullable=False)
@@ -1,600 +1,615 b''
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#save').attr('disabled', 'disabled');
44 $('#save').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#save').removeAttr('disabled');
47 $('#save').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 }
84 }
85 else {
85 else {
86 // use reference name
86 // use reference name
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 }
88 }
89
89
90 return [title, desc]
90 return [title, desc]
91 };
91 };
92
92
93
93
94
94
95 ReviewersController = function () {
95 ReviewersController = function () {
96 var self = this;
96 var self = this;
97 this.$reviewRulesContainer = $('#review_rules');
97 this.$reviewRulesContainer = $('#review_rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 this.forbidReviewUsers = undefined;
99 this.forbidReviewUsers = undefined;
100 this.$reviewMembers = $('#review_members');
100 this.$reviewMembers = $('#review_members');
101 this.currentRequest = null;
101 this.currentRequest = null;
102
102
103 this.defaultForbidReviewUsers = function() {
103 this.defaultForbidReviewUsers = function() {
104 return [
104 return [
105 {'username': 'default',
105 {'username': 'default',
106 'user_id': templateContext.default_user.user_id}
106 'user_id': templateContext.default_user.user_id}
107 ];
107 ];
108 };
108 };
109
109
110 this.hideReviewRules = function() {
110 this.hideReviewRules = function() {
111 self.$reviewRulesContainer.hide();
111 self.$reviewRulesContainer.hide();
112 };
112 };
113
113
114 this.showReviewRules = function() {
114 this.showReviewRules = function() {
115 self.$reviewRulesContainer.show();
115 self.$reviewRulesContainer.show();
116 };
116 };
117
117
118 this.addRule = function(ruleText) {
118 this.addRule = function(ruleText) {
119 self.showReviewRules();
119 self.showReviewRules();
120 return '<div>- {0}</div>'.format(ruleText)
120 return '<div>- {0}</div>'.format(ruleText)
121 };
121 };
122
122
123 this.loadReviewRules = function(data) {
123 this.loadReviewRules = function(data) {
124 // reset forbidden Users
124 // reset forbidden Users
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126
126
127 // reset state of review rules
127 // reset state of review rules
128 self.$rulesList.html('');
128 self.$rulesList.html('');
129
129
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 // default rule, case for older repo that don't have any rules stored
131 // default rule, case for older repo that don't have any rules stored
132 self.$rulesList.append(
132 self.$rulesList.append(
133 self.addRule(
133 self.addRule(
134 _gettext('All reviewers must vote.'))
134 _gettext('All reviewers must vote.'))
135 );
135 );
136 return self.forbidReviewUsers
136 return self.forbidReviewUsers
137 }
137 }
138
138
139 if (data.rules.voting !== undefined) {
139 if (data.rules.voting !== undefined) {
140 if (data.rules.voting < 0){
140 if (data.rules.voting < 0){
141 self.$rulesList.append(
141 self.$rulesList.append(
142 self.addRule(
142 self.addRule(
143 _gettext('All reviewers must vote.'))
143 _gettext('All reviewers must vote.'))
144 )
144 )
145 } else if (data.rules.voting === 1) {
145 } else if (data.rules.voting === 1) {
146 self.$rulesList.append(
146 self.$rulesList.append(
147 self.addRule(
147 self.addRule(
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 )
149 )
150
150
151 } else {
151 } else {
152 self.$rulesList.append(
152 self.$rulesList.append(
153 self.addRule(
153 self.addRule(
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 )
155 )
156 }
156 }
157 }
157 }
158 if (data.rules.use_code_authors_for_review) {
158 if (data.rules.use_code_authors_for_review) {
159 self.$rulesList.append(
159 self.$rulesList.append(
160 self.addRule(
160 self.addRule(
161 _gettext('Reviewers picked from source code changes.'))
161 _gettext('Reviewers picked from source code changes.'))
162 )
162 )
163 }
163 }
164 if (data.rules.forbid_adding_reviewers) {
164 if (data.rules.forbid_adding_reviewers) {
165 $('#add_reviewer_input').remove();
165 $('#add_reviewer_input').remove();
166 self.$rulesList.append(
166 self.$rulesList.append(
167 self.addRule(
167 self.addRule(
168 _gettext('Adding new reviewers is forbidden.'))
168 _gettext('Adding new reviewers is forbidden.'))
169 )
169 )
170 }
170 }
171 if (data.rules.forbid_author_to_review) {
171 if (data.rules.forbid_author_to_review) {
172 self.forbidReviewUsers.push(data.rules_data.pr_author);
172 self.forbidReviewUsers.push(data.rules_data.pr_author);
173 self.$rulesList.append(
173 self.$rulesList.append(
174 self.addRule(
174 self.addRule(
175 _gettext('Author is not allowed to be a reviewer.'))
175 _gettext('Author is not allowed to be a reviewer.'))
176 )
176 )
177 }
177 }
178 if (data.rules.forbid_commit_author_to_review) {
179
180 if (data.rules_data.forbidden_users) {
181 $.each(data.rules_data.forbidden_users, function(index, member_data) {
182 self.forbidReviewUsers.push(member_data)
183 });
184
185 }
186
187 self.$rulesList.append(
188 self.addRule(
189 _gettext('Commit Authors are not allowed to be a reviewer.'))
190 )
191 }
192
178 return self.forbidReviewUsers
193 return self.forbidReviewUsers
179 };
194 };
180
195
181 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
196 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
182
197
183 if (self.currentRequest) {
198 if (self.currentRequest) {
184 // make sure we cleanup old running requests before triggering this
199 // make sure we cleanup old running requests before triggering this
185 // again
200 // again
186 self.currentRequest.abort();
201 self.currentRequest.abort();
187 }
202 }
188
203
189 $('.calculate-reviewers').show();
204 $('.calculate-reviewers').show();
190 // reset reviewer members
205 // reset reviewer members
191 self.$reviewMembers.empty();
206 self.$reviewMembers.empty();
192
207
193 prButtonLock(true, null, 'reviewers');
208 prButtonLock(true, null, 'reviewers');
194 $('#user').hide(); // hide user autocomplete before load
209 $('#user').hide(); // hide user autocomplete before load
195
210
196 var url = pyroutes.url('repo_default_reviewers_data',
211 var url = pyroutes.url('repo_default_reviewers_data',
197 {
212 {
198 'repo_name': templateContext.repo_name,
213 'repo_name': templateContext.repo_name,
199 'source_repo': sourceRepo,
214 'source_repo': sourceRepo,
200 'source_ref': sourceRef[2],
215 'source_ref': sourceRef[2],
201 'target_repo': targetRepo,
216 'target_repo': targetRepo,
202 'target_ref': targetRef[2]
217 'target_ref': targetRef[2]
203 });
218 });
204
219
205 self.currentRequest = $.get(url)
220 self.currentRequest = $.get(url)
206 .done(function(data) {
221 .done(function(data) {
207 self.currentRequest = null;
222 self.currentRequest = null;
208
223
209 // review rules
224 // review rules
210 self.loadReviewRules(data);
225 self.loadReviewRules(data);
211
226
212 for (var i = 0; i < data.reviewers.length; i++) {
227 for (var i = 0; i < data.reviewers.length; i++) {
213 var reviewer = data.reviewers[i];
228 var reviewer = data.reviewers[i];
214 self.addReviewMember(
229 self.addReviewMember(
215 reviewer.user_id, reviewer.firstname,
230 reviewer.user_id, reviewer.firstname,
216 reviewer.lastname, reviewer.username,
231 reviewer.lastname, reviewer.username,
217 reviewer.gravatar_link, reviewer.reasons,
232 reviewer.gravatar_link, reviewer.reasons,
218 reviewer.mandatory);
233 reviewer.mandatory);
219 }
234 }
220 $('.calculate-reviewers').hide();
235 $('.calculate-reviewers').hide();
221 prButtonLock(false, null, 'reviewers');
236 prButtonLock(false, null, 'reviewers');
222 $('#user').show(); // show user autocomplete after load
237 $('#user').show(); // show user autocomplete after load
223 });
238 });
224 };
239 };
225
240
226 // check those, refactor
241 // check those, refactor
227 this.removeReviewMember = function(reviewer_id, mark_delete) {
242 this.removeReviewMember = function(reviewer_id, mark_delete) {
228 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
243 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
229
244
230 if(typeof(mark_delete) === undefined){
245 if(typeof(mark_delete) === undefined){
231 mark_delete = false;
246 mark_delete = false;
232 }
247 }
233
248
234 if(mark_delete === true){
249 if(mark_delete === true){
235 if (reviewer){
250 if (reviewer){
236 // now delete the input
251 // now delete the input
237 $('#reviewer_{0} input'.format(reviewer_id)).remove();
252 $('#reviewer_{0} input'.format(reviewer_id)).remove();
238 // mark as to-delete
253 // mark as to-delete
239 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
254 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
240 obj.addClass('to-delete');
255 obj.addClass('to-delete');
241 obj.css({"text-decoration":"line-through", "opacity": 0.5});
256 obj.css({"text-decoration":"line-through", "opacity": 0.5});
242 }
257 }
243 }
258 }
244 else{
259 else{
245 $('#reviewer_{0}'.format(reviewer_id)).remove();
260 $('#reviewer_{0}'.format(reviewer_id)).remove();
246 }
261 }
247 };
262 };
248
263
249 this.addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons, mandatory) {
264 this.addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons, mandatory) {
250 var members = self.$reviewMembers.get(0);
265 var members = self.$reviewMembers.get(0);
251 var reasons_html = '';
266 var reasons_html = '';
252 var reasons_inputs = '';
267 var reasons_inputs = '';
253 var reasons = reasons || [];
268 var reasons = reasons || [];
254 var mandatory = mandatory || false;
269 var mandatory = mandatory || false;
255
270
256 if (reasons) {
271 if (reasons) {
257 for (var i = 0; i < reasons.length; i++) {
272 for (var i = 0; i < reasons.length; i++) {
258 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
273 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
259 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
274 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
260 }
275 }
261 }
276 }
262 var tmpl = '' +
277 var tmpl = '' +
263 '<li id="reviewer_{2}" class="reviewer_entry">'+
278 '<li id="reviewer_{2}" class="reviewer_entry">'+
264 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
279 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
265 '<div class="reviewer_status">'+
280 '<div class="reviewer_status">'+
266 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
281 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
267 '</div>'+
282 '</div>'+
268 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
283 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
269 '<span class="reviewer_name user">{1}</span>'+
284 '<span class="reviewer_name user">{1}</span>'+
270 reasons_html +
285 reasons_html +
271 '<input type="hidden" name="user_id" value="{2}">'+
286 '<input type="hidden" name="user_id" value="{2}">'+
272 '<input type="hidden" name="__start__" value="reasons:sequence">'+
287 '<input type="hidden" name="__start__" value="reasons:sequence">'+
273 '{3}'+
288 '{3}'+
274 '<input type="hidden" name="__end__" value="reasons:sequence">';
289 '<input type="hidden" name="__end__" value="reasons:sequence">';
275
290
276 if (mandatory) {
291 if (mandatory) {
277 tmpl += ''+
292 tmpl += ''+
278 '<div class="reviewer_member_mandatory_remove">' +
293 '<div class="reviewer_member_mandatory_remove">' +
279 '<i class="icon-remove-sign"></i>'+
294 '<i class="icon-remove-sign"></i>'+
280 '</div>' +
295 '</div>' +
281 '<input type="hidden" name="mandatory" value="true">'+
296 '<input type="hidden" name="mandatory" value="true">'+
282 '<div class="reviewer_member_mandatory">' +
297 '<div class="reviewer_member_mandatory">' +
283 '<i class="icon-lock" title="Mandatory reviewer"></i>'+
298 '<i class="icon-lock" title="Mandatory reviewer"></i>'+
284 '</div>';
299 '</div>';
285
300
286 } else {
301 } else {
287 tmpl += ''+
302 tmpl += ''+
288 '<input type="hidden" name="mandatory" value="false">'+
303 '<input type="hidden" name="mandatory" value="false">'+
289 '<div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember({2})">' +
304 '<div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember({2})">' +
290 '<i class="icon-remove-sign"></i>'+
305 '<i class="icon-remove-sign"></i>'+
291 '</div>';
306 '</div>';
292 }
307 }
293 // continue template
308 // continue template
294 tmpl += ''+
309 tmpl += ''+
295 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
310 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
296 '</li>' ;
311 '</li>' ;
297
312
298 var displayname = "{0} ({1} {2})".format(
313 var displayname = "{0} ({1} {2})".format(
299 nname, escapeHtml(fname), escapeHtml(lname));
314 nname, escapeHtml(fname), escapeHtml(lname));
300 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
315 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
301 // check if we don't have this ID already in
316 // check if we don't have this ID already in
302 var ids = [];
317 var ids = [];
303 var _els = self.$reviewMembers.find('li').toArray();
318 var _els = self.$reviewMembers.find('li').toArray();
304 for (el in _els){
319 for (el in _els){
305 ids.push(_els[el].id)
320 ids.push(_els[el].id)
306 }
321 }
307
322
308 var userAllowedReview = function(userId) {
323 var userAllowedReview = function(userId) {
309 var allowed = true;
324 var allowed = true;
310 $.each(self.forbidReviewUsers, function(index, member_data) {
325 $.each(self.forbidReviewUsers, function(index, member_data) {
311 if (parseInt(userId) === member_data['user_id']) {
326 if (parseInt(userId) === member_data['user_id']) {
312 allowed = false;
327 allowed = false;
313 return false // breaks the loop
328 return false // breaks the loop
314 }
329 }
315 });
330 });
316 return allowed
331 return allowed
317 };
332 };
318
333
319 var userAllowed = userAllowedReview(id);
334 var userAllowed = userAllowedReview(id);
320 if (!userAllowed){
335 if (!userAllowed){
321 alert(_gettext('User `{0}` not allowed to be a reviewer').format(nname));
336 alert(_gettext('User `{0}` not allowed to be a reviewer').format(nname));
322 }
337 }
323 var shouldAdd = userAllowed && ids.indexOf('reviewer_'+id) == -1;
338 var shouldAdd = userAllowed && ids.indexOf('reviewer_'+id) == -1;
324
339
325 if(shouldAdd) {
340 if(shouldAdd) {
326 // only add if it's not there
341 // only add if it's not there
327 members.innerHTML += element;
342 members.innerHTML += element;
328 }
343 }
329
344
330 };
345 };
331
346
332 this.updateReviewers = function(repo_name, pull_request_id){
347 this.updateReviewers = function(repo_name, pull_request_id){
333 var postData = '_method=put&' + $('#reviewers input').serialize();
348 var postData = '_method=put&' + $('#reviewers input').serialize();
334 _updatePullRequest(repo_name, pull_request_id, postData);
349 _updatePullRequest(repo_name, pull_request_id, postData);
335 };
350 };
336
351
337 };
352 };
338
353
339
354
340 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
355 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
341 var url = pyroutes.url(
356 var url = pyroutes.url(
342 'pullrequest_update',
357 'pullrequest_update',
343 {"repo_name": repo_name, "pull_request_id": pull_request_id});
358 {"repo_name": repo_name, "pull_request_id": pull_request_id});
344 if (typeof postData === 'string' ) {
359 if (typeof postData === 'string' ) {
345 postData += '&csrf_token=' + CSRF_TOKEN;
360 postData += '&csrf_token=' + CSRF_TOKEN;
346 } else {
361 } else {
347 postData.csrf_token = CSRF_TOKEN;
362 postData.csrf_token = CSRF_TOKEN;
348 }
363 }
349 var success = function(o) {
364 var success = function(o) {
350 window.location.reload();
365 window.location.reload();
351 };
366 };
352 ajaxPOST(url, postData, success);
367 ajaxPOST(url, postData, success);
353 };
368 };
354
369
355 /**
370 /**
356 * PULL REQUEST reject & close
371 * PULL REQUEST reject & close
357 */
372 */
358 var closePullRequest = function(repo_name, pull_request_id) {
373 var closePullRequest = function(repo_name, pull_request_id) {
359 var postData = {
374 var postData = {
360 '_method': 'put',
375 '_method': 'put',
361 'close_pull_request': true};
376 'close_pull_request': true};
362 _updatePullRequest(repo_name, pull_request_id, postData);
377 _updatePullRequest(repo_name, pull_request_id, postData);
363 };
378 };
364
379
365 /**
380 /**
366 * PULL REQUEST update commits
381 * PULL REQUEST update commits
367 */
382 */
368 var updateCommits = function(repo_name, pull_request_id) {
383 var updateCommits = function(repo_name, pull_request_id) {
369 var postData = {
384 var postData = {
370 '_method': 'put',
385 '_method': 'put',
371 'update_commits': true};
386 'update_commits': true};
372 _updatePullRequest(repo_name, pull_request_id, postData);
387 _updatePullRequest(repo_name, pull_request_id, postData);
373 };
388 };
374
389
375
390
376 /**
391 /**
377 * PULL REQUEST edit info
392 * PULL REQUEST edit info
378 */
393 */
379 var editPullRequest = function(repo_name, pull_request_id, title, description) {
394 var editPullRequest = function(repo_name, pull_request_id, title, description) {
380 var url = pyroutes.url(
395 var url = pyroutes.url(
381 'pullrequest_update',
396 'pullrequest_update',
382 {"repo_name": repo_name, "pull_request_id": pull_request_id});
397 {"repo_name": repo_name, "pull_request_id": pull_request_id});
383
398
384 var postData = {
399 var postData = {
385 '_method': 'put',
400 '_method': 'put',
386 'title': title,
401 'title': title,
387 'description': description,
402 'description': description,
388 'edit_pull_request': true,
403 'edit_pull_request': true,
389 'csrf_token': CSRF_TOKEN
404 'csrf_token': CSRF_TOKEN
390 };
405 };
391 var success = function(o) {
406 var success = function(o) {
392 window.location.reload();
407 window.location.reload();
393 };
408 };
394 ajaxPOST(url, postData, success);
409 ajaxPOST(url, postData, success);
395 };
410 };
396
411
397 var initPullRequestsCodeMirror = function (textAreaId) {
412 var initPullRequestsCodeMirror = function (textAreaId) {
398 var ta = $(textAreaId).get(0);
413 var ta = $(textAreaId).get(0);
399 var initialHeight = '100px';
414 var initialHeight = '100px';
400
415
401 // default options
416 // default options
402 var codeMirrorOptions = {
417 var codeMirrorOptions = {
403 mode: "text",
418 mode: "text",
404 lineNumbers: false,
419 lineNumbers: false,
405 indentUnit: 4,
420 indentUnit: 4,
406 theme: 'rc-input'
421 theme: 'rc-input'
407 };
422 };
408
423
409 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
424 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
410 // marker for manually set description
425 // marker for manually set description
411 codeMirrorInstance._userDefinedDesc = false;
426 codeMirrorInstance._userDefinedDesc = false;
412 codeMirrorInstance.setSize(null, initialHeight);
427 codeMirrorInstance.setSize(null, initialHeight);
413 codeMirrorInstance.on("change", function(instance, changeObj) {
428 codeMirrorInstance.on("change", function(instance, changeObj) {
414 var height = initialHeight;
429 var height = initialHeight;
415 var lines = instance.lineCount();
430 var lines = instance.lineCount();
416 if (lines > 6 && lines < 20) {
431 if (lines > 6 && lines < 20) {
417 height = "auto"
432 height = "auto"
418 }
433 }
419 else if (lines >= 20) {
434 else if (lines >= 20) {
420 height = 20 * 15;
435 height = 20 * 15;
421 }
436 }
422 instance.setSize(null, height);
437 instance.setSize(null, height);
423
438
424 // detect if the change was trigger by auto desc, or user input
439 // detect if the change was trigger by auto desc, or user input
425 changeOrigin = changeObj.origin;
440 changeOrigin = changeObj.origin;
426
441
427 if (changeOrigin === "setValue") {
442 if (changeOrigin === "setValue") {
428 cmLog.debug('Change triggered by setValue');
443 cmLog.debug('Change triggered by setValue');
429 }
444 }
430 else {
445 else {
431 cmLog.debug('user triggered change !');
446 cmLog.debug('user triggered change !');
432 // set special marker to indicate user has created an input.
447 // set special marker to indicate user has created an input.
433 instance._userDefinedDesc = true;
448 instance._userDefinedDesc = true;
434 }
449 }
435
450
436 });
451 });
437
452
438 return codeMirrorInstance
453 return codeMirrorInstance
439 };
454 };
440
455
441 /**
456 /**
442 * Reviewer autocomplete
457 * Reviewer autocomplete
443 */
458 */
444 var ReviewerAutoComplete = function(inputId) {
459 var ReviewerAutoComplete = function(inputId) {
445 $(inputId).autocomplete({
460 $(inputId).autocomplete({
446 serviceUrl: pyroutes.url('user_autocomplete_data'),
461 serviceUrl: pyroutes.url('user_autocomplete_data'),
447 minChars:2,
462 minChars:2,
448 maxHeight:400,
463 maxHeight:400,
449 deferRequestBy: 300, //miliseconds
464 deferRequestBy: 300, //miliseconds
450 showNoSuggestionNotice: true,
465 showNoSuggestionNotice: true,
451 tabDisabled: true,
466 tabDisabled: true,
452 autoSelectFirst: true,
467 autoSelectFirst: true,
453 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
468 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
454 formatResult: autocompleteFormatResult,
469 formatResult: autocompleteFormatResult,
455 lookupFilter: autocompleteFilterResult,
470 lookupFilter: autocompleteFilterResult,
456 onSelect: function(element, data) {
471 onSelect: function(element, data) {
457
472
458 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
473 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
459 if (data.value_type == 'user_group') {
474 if (data.value_type == 'user_group') {
460 reasons.push(_gettext('member of "{0}"').format(data.value_display));
475 reasons.push(_gettext('member of "{0}"').format(data.value_display));
461
476
462 $.each(data.members, function(index, member_data) {
477 $.each(data.members, function(index, member_data) {
463 reviewersController.addReviewMember(
478 reviewersController.addReviewMember(
464 member_data.id, member_data.first_name, member_data.last_name,
479 member_data.id, member_data.first_name, member_data.last_name,
465 member_data.username, member_data.icon_link, reasons);
480 member_data.username, member_data.icon_link, reasons);
466 })
481 })
467
482
468 } else {
483 } else {
469 reviewersController.addReviewMember(
484 reviewersController.addReviewMember(
470 data.id, data.first_name, data.last_name,
485 data.id, data.first_name, data.last_name,
471 data.username, data.icon_link, reasons);
486 data.username, data.icon_link, reasons);
472 }
487 }
473
488
474 $(inputId).val('');
489 $(inputId).val('');
475 }
490 }
476 });
491 });
477 };
492 };
478
493
479
494
480 VersionController = function () {
495 VersionController = function () {
481 var self = this;
496 var self = this;
482 this.$verSource = $('input[name=ver_source]');
497 this.$verSource = $('input[name=ver_source]');
483 this.$verTarget = $('input[name=ver_target]');
498 this.$verTarget = $('input[name=ver_target]');
484 this.$showVersionDiff = $('#show-version-diff');
499 this.$showVersionDiff = $('#show-version-diff');
485
500
486 this.adjustRadioSelectors = function (curNode) {
501 this.adjustRadioSelectors = function (curNode) {
487 var getVal = function (item) {
502 var getVal = function (item) {
488 if (item == 'latest') {
503 if (item == 'latest') {
489 return Number.MAX_SAFE_INTEGER
504 return Number.MAX_SAFE_INTEGER
490 }
505 }
491 else {
506 else {
492 return parseInt(item)
507 return parseInt(item)
493 }
508 }
494 };
509 };
495
510
496 var curVal = getVal($(curNode).val());
511 var curVal = getVal($(curNode).val());
497 var cleared = false;
512 var cleared = false;
498
513
499 $.each(self.$verSource, function (index, value) {
514 $.each(self.$verSource, function (index, value) {
500 var elVal = getVal($(value).val());
515 var elVal = getVal($(value).val());
501
516
502 if (elVal > curVal) {
517 if (elVal > curVal) {
503 if ($(value).is(':checked')) {
518 if ($(value).is(':checked')) {
504 cleared = true;
519 cleared = true;
505 }
520 }
506 $(value).attr('disabled', 'disabled');
521 $(value).attr('disabled', 'disabled');
507 $(value).removeAttr('checked');
522 $(value).removeAttr('checked');
508 $(value).css({'opacity': 0.1});
523 $(value).css({'opacity': 0.1});
509 }
524 }
510 else {
525 else {
511 $(value).css({'opacity': 1});
526 $(value).css({'opacity': 1});
512 $(value).removeAttr('disabled');
527 $(value).removeAttr('disabled');
513 }
528 }
514 });
529 });
515
530
516 if (cleared) {
531 if (cleared) {
517 // if we unchecked an active, set the next one to same loc.
532 // if we unchecked an active, set the next one to same loc.
518 $(this.$verSource).filter('[value={0}]'.format(
533 $(this.$verSource).filter('[value={0}]'.format(
519 curVal)).attr('checked', 'checked');
534 curVal)).attr('checked', 'checked');
520 }
535 }
521
536
522 self.setLockAction(false,
537 self.setLockAction(false,
523 $(curNode).data('verPos'),
538 $(curNode).data('verPos'),
524 $(this.$verSource).filter(':checked').data('verPos')
539 $(this.$verSource).filter(':checked').data('verPos')
525 );
540 );
526 };
541 };
527
542
528
543
529 this.attachVersionListener = function () {
544 this.attachVersionListener = function () {
530 self.$verTarget.change(function (e) {
545 self.$verTarget.change(function (e) {
531 self.adjustRadioSelectors(this)
546 self.adjustRadioSelectors(this)
532 });
547 });
533 self.$verSource.change(function (e) {
548 self.$verSource.change(function (e) {
534 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
549 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
535 });
550 });
536 };
551 };
537
552
538 this.init = function () {
553 this.init = function () {
539
554
540 var curNode = self.$verTarget.filter(':checked');
555 var curNode = self.$verTarget.filter(':checked');
541 self.adjustRadioSelectors(curNode);
556 self.adjustRadioSelectors(curNode);
542 self.setLockAction(true);
557 self.setLockAction(true);
543 self.attachVersionListener();
558 self.attachVersionListener();
544
559
545 };
560 };
546
561
547 this.setLockAction = function (state, selectedVersion, otherVersion) {
562 this.setLockAction = function (state, selectedVersion, otherVersion) {
548 var $showVersionDiff = this.$showVersionDiff;
563 var $showVersionDiff = this.$showVersionDiff;
549
564
550 if (state) {
565 if (state) {
551 $showVersionDiff.attr('disabled', 'disabled');
566 $showVersionDiff.attr('disabled', 'disabled');
552 $showVersionDiff.addClass('disabled');
567 $showVersionDiff.addClass('disabled');
553 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
568 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
554 }
569 }
555 else {
570 else {
556 $showVersionDiff.removeAttr('disabled');
571 $showVersionDiff.removeAttr('disabled');
557 $showVersionDiff.removeClass('disabled');
572 $showVersionDiff.removeClass('disabled');
558
573
559 if (selectedVersion == otherVersion) {
574 if (selectedVersion == otherVersion) {
560 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
575 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
561 } else {
576 } else {
562 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
577 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
563 }
578 }
564 }
579 }
565
580
566 };
581 };
567
582
568 this.showVersionDiff = function () {
583 this.showVersionDiff = function () {
569 var target = self.$verTarget.filter(':checked');
584 var target = self.$verTarget.filter(':checked');
570 var source = self.$verSource.filter(':checked');
585 var source = self.$verSource.filter(':checked');
571
586
572 if (target.val() && source.val()) {
587 if (target.val() && source.val()) {
573 var params = {
588 var params = {
574 'pull_request_id': templateContext.pull_request_data.pull_request_id,
589 'pull_request_id': templateContext.pull_request_data.pull_request_id,
575 'repo_name': templateContext.repo_name,
590 'repo_name': templateContext.repo_name,
576 'version': target.val(),
591 'version': target.val(),
577 'from_version': source.val()
592 'from_version': source.val()
578 };
593 };
579 window.location = pyroutes.url('pullrequest_show', params)
594 window.location = pyroutes.url('pullrequest_show', params)
580 }
595 }
581
596
582 return false;
597 return false;
583 };
598 };
584
599
585 this.toggleVersionView = function (elem) {
600 this.toggleVersionView = function (elem) {
586
601
587 if (this.$showVersionDiff.is(':visible')) {
602 if (this.$showVersionDiff.is(':visible')) {
588 $('.version-pr').hide();
603 $('.version-pr').hide();
589 this.$showVersionDiff.hide();
604 this.$showVersionDiff.hide();
590 $(elem).html($(elem).data('toggleOn'))
605 $(elem).html($(elem).data('toggleOn'))
591 } else {
606 } else {
592 $('.version-pr').show();
607 $('.version-pr').show();
593 this.$showVersionDiff.show();
608 this.$showVersionDiff.show();
594 $(elem).html($(elem).data('toggleOff'))
609 $(elem).html($(elem).data('toggleOff'))
595 }
610 }
596
611
597 return false
612 return false
598 }
613 }
599
614
600 }; No newline at end of file
615 };
General Comments 0
You need to be logged in to leave comments. Login now