##// END OF EJS Templates
templateContext: improve the context object idea
ergo -
r395:52964495 default
parent child Browse files
Show More
@@ -1,849 +1,849 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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
24
25 import formencode
25 import formencode
26 import logging
26 import logging
27
27
28 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
28 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
29 from pylons import request, tmpl_context as c, url
29 from pylons import request, tmpl_context as c, url
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from sqlalchemy.sql import func
32 from sqlalchemy.sql import func
33 from sqlalchemy.sql.expression import or_
33 from sqlalchemy.sql.expression import or_
34
34
35 from rhodecode.lib import auth, diffs, helpers as h
35 from rhodecode.lib import auth, diffs, helpers as h
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.base import (
37 from rhodecode.lib.base import (
38 BaseRepoController, render, vcs_operation_context)
38 BaseRepoController, render, vcs_operation_context)
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
40 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
41 HasAcceptedRepoType, XHRRequired)
41 HasAcceptedRepoType, XHRRequired)
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
43 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError)
46 EmptyRepositoryError, CommitDoesNotExistError, RepositoryRequirementError)
47 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import ChangesetCommentsModel
49 from rhodecode.model.comment import ChangesetCommentsModel
50 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
50 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment, \
51 Repository
51 Repository
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel
54 from rhodecode.model.pull_request import PullRequestModel
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
60 def __before__(self):
60 def __before__(self):
61 super(PullrequestsController, self).__before__()
61 super(PullrequestsController, self).__before__()
62
62
63 def _load_compare_data(self, pull_request, enable_comments=True):
63 def _load_compare_data(self, pull_request, enable_comments=True):
64 """
64 """
65 Load context data needed for generating compare diff
65 Load context data needed for generating compare diff
66
66
67 :param pull_request: object related to the request
67 :param pull_request: object related to the request
68 :param enable_comments: flag to determine if comments are included
68 :param enable_comments: flag to determine if comments are included
69 """
69 """
70 source_repo = pull_request.source_repo
70 source_repo = pull_request.source_repo
71 source_ref_id = pull_request.source_ref_parts.commit_id
71 source_ref_id = pull_request.source_ref_parts.commit_id
72
72
73 target_repo = pull_request.target_repo
73 target_repo = pull_request.target_repo
74 target_ref_id = pull_request.target_ref_parts.commit_id
74 target_ref_id = pull_request.target_ref_parts.commit_id
75
75
76 # despite opening commits for bookmarks/branches/tags, we always
76 # despite opening commits for bookmarks/branches/tags, we always
77 # convert this to rev to prevent changes after bookmark or branch change
77 # convert this to rev to prevent changes after bookmark or branch change
78 c.source_ref_type = 'rev'
78 c.source_ref_type = 'rev'
79 c.source_ref = source_ref_id
79 c.source_ref = source_ref_id
80
80
81 c.target_ref_type = 'rev'
81 c.target_ref_type = 'rev'
82 c.target_ref = target_ref_id
82 c.target_ref = target_ref_id
83
83
84 c.source_repo = source_repo
84 c.source_repo = source_repo
85 c.target_repo = target_repo
85 c.target_repo = target_repo
86
86
87 c.fulldiff = bool(request.GET.get('fulldiff'))
87 c.fulldiff = bool(request.GET.get('fulldiff'))
88
88
89 # diff_limit is the old behavior, will cut off the whole diff
89 # diff_limit is the old behavior, will cut off the whole diff
90 # if the limit is applied otherwise will just hide the
90 # if the limit is applied otherwise will just hide the
91 # big files from the front-end
91 # big files from the front-end
92 diff_limit = self.cut_off_limit_diff
92 diff_limit = self.cut_off_limit_diff
93 file_limit = self.cut_off_limit_file
93 file_limit = self.cut_off_limit_file
94
94
95 pre_load = ["author", "branch", "date", "message"]
95 pre_load = ["author", "branch", "date", "message"]
96
96
97 c.commit_ranges = []
97 c.commit_ranges = []
98 source_commit = EmptyCommit()
98 source_commit = EmptyCommit()
99 target_commit = EmptyCommit()
99 target_commit = EmptyCommit()
100 c.missing_requirements = False
100 c.missing_requirements = False
101 try:
101 try:
102 c.commit_ranges = [
102 c.commit_ranges = [
103 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
103 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
104 for rev in pull_request.revisions]
104 for rev in pull_request.revisions]
105
105
106 c.statuses = source_repo.statuses(
106 c.statuses = source_repo.statuses(
107 [x.raw_id for x in c.commit_ranges])
107 [x.raw_id for x in c.commit_ranges])
108
108
109 target_commit = source_repo.get_commit(
109 target_commit = source_repo.get_commit(
110 commit_id=safe_str(target_ref_id))
110 commit_id=safe_str(target_ref_id))
111 source_commit = source_repo.get_commit(
111 source_commit = source_repo.get_commit(
112 commit_id=safe_str(source_ref_id))
112 commit_id=safe_str(source_ref_id))
113 except RepositoryRequirementError:
113 except RepositoryRequirementError:
114 c.missing_requirements = True
114 c.missing_requirements = True
115
115
116 c.missing_commits = False
116 c.missing_commits = False
117 if (c.missing_requirements or
117 if (c.missing_requirements or
118 isinstance(source_commit, EmptyCommit) or
118 isinstance(source_commit, EmptyCommit) or
119 source_commit == target_commit):
119 source_commit == target_commit):
120 _parsed = []
120 _parsed = []
121 c.missing_commits = True
121 c.missing_commits = True
122 else:
122 else:
123 vcs_diff = PullRequestModel().get_diff(pull_request)
123 vcs_diff = PullRequestModel().get_diff(pull_request)
124 diff_processor = diffs.DiffProcessor(
124 diff_processor = diffs.DiffProcessor(
125 vcs_diff, format='gitdiff', diff_limit=diff_limit,
125 vcs_diff, format='gitdiff', diff_limit=diff_limit,
126 file_limit=file_limit, show_full_diff=c.fulldiff)
126 file_limit=file_limit, show_full_diff=c.fulldiff)
127 _parsed = diff_processor.prepare()
127 _parsed = diff_processor.prepare()
128
128
129 c.limited_diff = isinstance(_parsed, LimitedDiffContainer)
129 c.limited_diff = isinstance(_parsed, LimitedDiffContainer)
130
130
131 c.files = []
131 c.files = []
132 c.changes = {}
132 c.changes = {}
133 c.lines_added = 0
133 c.lines_added = 0
134 c.lines_deleted = 0
134 c.lines_deleted = 0
135 c.included_files = []
135 c.included_files = []
136 c.deleted_files = []
136 c.deleted_files = []
137
137
138 for f in _parsed:
138 for f in _parsed:
139 st = f['stats']
139 st = f['stats']
140 c.lines_added += st['added']
140 c.lines_added += st['added']
141 c.lines_deleted += st['deleted']
141 c.lines_deleted += st['deleted']
142
142
143 fid = h.FID('', f['filename'])
143 fid = h.FID('', f['filename'])
144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
145 c.included_files.append(f['filename'])
145 c.included_files.append(f['filename'])
146 html_diff = diff_processor.as_html(enable_comments=enable_comments,
146 html_diff = diff_processor.as_html(enable_comments=enable_comments,
147 parsed_lines=[f])
147 parsed_lines=[f])
148 c.changes[fid] = [f['operation'], f['filename'], html_diff, f]
148 c.changes[fid] = [f['operation'], f['filename'], html_diff, f]
149
149
150 def _extract_ordering(self, request):
150 def _extract_ordering(self, request):
151 column_index = safe_int(request.GET.get('order[0][column]'))
151 column_index = safe_int(request.GET.get('order[0][column]'))
152 order_dir = request.GET.get('order[0][dir]', 'desc')
152 order_dir = request.GET.get('order[0][dir]', 'desc')
153 order_by = request.GET.get(
153 order_by = request.GET.get(
154 'columns[%s][data][sort]' % column_index, 'name_raw')
154 'columns[%s][data][sort]' % column_index, 'name_raw')
155 return order_by, order_dir
155 return order_by, order_dir
156
156
157 @LoginRequired()
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
159 'repository.admin')
159 'repository.admin')
160 @HasAcceptedRepoType('git', 'hg')
160 @HasAcceptedRepoType('git', 'hg')
161 def show_all(self, repo_name):
161 def show_all(self, repo_name):
162 # filter types
162 # filter types
163 c.active = 'open'
163 c.active = 'open'
164 c.source = str2bool(request.GET.get('source'))
164 c.source = str2bool(request.GET.get('source'))
165 c.closed = str2bool(request.GET.get('closed'))
165 c.closed = str2bool(request.GET.get('closed'))
166 c.my = str2bool(request.GET.get('my'))
166 c.my = str2bool(request.GET.get('my'))
167 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
167 c.awaiting_review = str2bool(request.GET.get('awaiting_review'))
168 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
168 c.awaiting_my_review = str2bool(request.GET.get('awaiting_my_review'))
169 c.repo_name = repo_name
169 c.repo_name = repo_name
170
170
171 opened_by = None
171 opened_by = None
172 if c.my:
172 if c.my:
173 c.active = 'my'
173 c.active = 'my'
174 opened_by = [c.rhodecode_user.user_id]
174 opened_by = [c.rhodecode_user.user_id]
175
175
176 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
176 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
177 if c.closed:
177 if c.closed:
178 c.active = 'closed'
178 c.active = 'closed'
179 statuses = [PullRequest.STATUS_CLOSED]
179 statuses = [PullRequest.STATUS_CLOSED]
180
180
181 if c.awaiting_review and not c.source:
181 if c.awaiting_review and not c.source:
182 c.active = 'awaiting'
182 c.active = 'awaiting'
183 if c.source and not c.awaiting_review:
183 if c.source and not c.awaiting_review:
184 c.active = 'source'
184 c.active = 'source'
185 if c.awaiting_my_review:
185 if c.awaiting_my_review:
186 c.active = 'awaiting_my'
186 c.active = 'awaiting_my'
187
187
188 data = self._get_pull_requests_list(
188 data = self._get_pull_requests_list(
189 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
189 repo_name=repo_name, opened_by=opened_by, statuses=statuses)
190 if not request.is_xhr:
190 if not request.is_xhr:
191 c.data = json.dumps(data['data'])
191 c.data = json.dumps(data['data'])
192 c.records_total = data['recordsTotal']
192 c.records_total = data['recordsTotal']
193 return render('/pullrequests/pullrequests.html')
193 return render('/pullrequests/pullrequests.html')
194 else:
194 else:
195 return json.dumps(data)
195 return json.dumps(data)
196
196
197 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
197 def _get_pull_requests_list(self, repo_name, opened_by, statuses):
198 # pagination
198 # pagination
199 start = safe_int(request.GET.get('start'), 0)
199 start = safe_int(request.GET.get('start'), 0)
200 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
200 length = safe_int(request.GET.get('length'), c.visual.dashboard_items)
201 order_by, order_dir = self._extract_ordering(request)
201 order_by, order_dir = self._extract_ordering(request)
202
202
203 if c.awaiting_review:
203 if c.awaiting_review:
204 pull_requests = PullRequestModel().get_awaiting_review(
204 pull_requests = PullRequestModel().get_awaiting_review(
205 repo_name, source=c.source, opened_by=opened_by,
205 repo_name, source=c.source, opened_by=opened_by,
206 statuses=statuses, offset=start, length=length,
206 statuses=statuses, offset=start, length=length,
207 order_by=order_by, order_dir=order_dir)
207 order_by=order_by, order_dir=order_dir)
208 pull_requests_total_count = PullRequestModel(
208 pull_requests_total_count = PullRequestModel(
209 ).count_awaiting_review(
209 ).count_awaiting_review(
210 repo_name, source=c.source, statuses=statuses,
210 repo_name, source=c.source, statuses=statuses,
211 opened_by=opened_by)
211 opened_by=opened_by)
212 elif c.awaiting_my_review:
212 elif c.awaiting_my_review:
213 pull_requests = PullRequestModel().get_awaiting_my_review(
213 pull_requests = PullRequestModel().get_awaiting_my_review(
214 repo_name, source=c.source, opened_by=opened_by,
214 repo_name, source=c.source, opened_by=opened_by,
215 user_id=c.rhodecode_user.user_id, statuses=statuses,
215 user_id=c.rhodecode_user.user_id, statuses=statuses,
216 offset=start, length=length, order_by=order_by,
216 offset=start, length=length, order_by=order_by,
217 order_dir=order_dir)
217 order_dir=order_dir)
218 pull_requests_total_count = PullRequestModel(
218 pull_requests_total_count = PullRequestModel(
219 ).count_awaiting_my_review(
219 ).count_awaiting_my_review(
220 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
220 repo_name, source=c.source, user_id=c.rhodecode_user.user_id,
221 statuses=statuses, opened_by=opened_by)
221 statuses=statuses, opened_by=opened_by)
222 else:
222 else:
223 pull_requests = PullRequestModel().get_all(
223 pull_requests = PullRequestModel().get_all(
224 repo_name, source=c.source, opened_by=opened_by,
224 repo_name, source=c.source, opened_by=opened_by,
225 statuses=statuses, offset=start, length=length,
225 statuses=statuses, offset=start, length=length,
226 order_by=order_by, order_dir=order_dir)
226 order_by=order_by, order_dir=order_dir)
227 pull_requests_total_count = PullRequestModel().count_all(
227 pull_requests_total_count = PullRequestModel().count_all(
228 repo_name, source=c.source, statuses=statuses,
228 repo_name, source=c.source, statuses=statuses,
229 opened_by=opened_by)
229 opened_by=opened_by)
230
230
231 from rhodecode.lib.utils import PartialRenderer
231 from rhodecode.lib.utils import PartialRenderer
232 _render = PartialRenderer('data_table/_dt_elements.html')
232 _render = PartialRenderer('data_table/_dt_elements.html')
233 data = []
233 data = []
234 for pr in pull_requests:
234 for pr in pull_requests:
235 comments = ChangesetCommentsModel().get_all_comments(
235 comments = ChangesetCommentsModel().get_all_comments(
236 c.rhodecode_db_repo.repo_id, pull_request=pr)
236 c.rhodecode_db_repo.repo_id, pull_request=pr)
237
237
238 data.append({
238 data.append({
239 'name': _render('pullrequest_name',
239 'name': _render('pullrequest_name',
240 pr.pull_request_id, pr.target_repo.repo_name),
240 pr.pull_request_id, pr.target_repo.repo_name),
241 'name_raw': pr.pull_request_id,
241 'name_raw': pr.pull_request_id,
242 'status': _render('pullrequest_status',
242 'status': _render('pullrequest_status',
243 pr.calculated_review_status()),
243 pr.calculated_review_status()),
244 'title': _render(
244 'title': _render(
245 'pullrequest_title', pr.title, pr.description),
245 'pullrequest_title', pr.title, pr.description),
246 'description': h.escape(pr.description),
246 'description': h.escape(pr.description),
247 'updated_on': _render('pullrequest_updated_on',
247 'updated_on': _render('pullrequest_updated_on',
248 h.datetime_to_time(pr.updated_on)),
248 h.datetime_to_time(pr.updated_on)),
249 'updated_on_raw': h.datetime_to_time(pr.updated_on),
249 'updated_on_raw': h.datetime_to_time(pr.updated_on),
250 'created_on': _render('pullrequest_updated_on',
250 'created_on': _render('pullrequest_updated_on',
251 h.datetime_to_time(pr.created_on)),
251 h.datetime_to_time(pr.created_on)),
252 'created_on_raw': h.datetime_to_time(pr.created_on),
252 'created_on_raw': h.datetime_to_time(pr.created_on),
253 'author': _render('pullrequest_author',
253 'author': _render('pullrequest_author',
254 pr.author.full_contact, ),
254 pr.author.full_contact, ),
255 'author_raw': pr.author.full_name,
255 'author_raw': pr.author.full_name,
256 'comments': _render('pullrequest_comments', len(comments)),
256 'comments': _render('pullrequest_comments', len(comments)),
257 'comments_raw': len(comments),
257 'comments_raw': len(comments),
258 'closed': pr.is_closed(),
258 'closed': pr.is_closed(),
259 })
259 })
260 # json used to render the grid
260 # json used to render the grid
261 data = ({
261 data = ({
262 'data': data,
262 'data': data,
263 'recordsTotal': pull_requests_total_count,
263 'recordsTotal': pull_requests_total_count,
264 'recordsFiltered': pull_requests_total_count,
264 'recordsFiltered': pull_requests_total_count,
265 })
265 })
266 return data
266 return data
267
267
268 @LoginRequired()
268 @LoginRequired()
269 @NotAnonymous()
269 @NotAnonymous()
270 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
270 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
271 'repository.admin')
271 'repository.admin')
272 @HasAcceptedRepoType('git', 'hg')
272 @HasAcceptedRepoType('git', 'hg')
273 def index(self):
273 def index(self):
274 source_repo = c.rhodecode_db_repo
274 source_repo = c.rhodecode_db_repo
275
275
276 try:
276 try:
277 source_repo.scm_instance().get_commit()
277 source_repo.scm_instance().get_commit()
278 except EmptyRepositoryError:
278 except EmptyRepositoryError:
279 h.flash(h.literal(_('There are no commits yet')),
279 h.flash(h.literal(_('There are no commits yet')),
280 category='warning')
280 category='warning')
281 redirect(url('summary_home', repo_name=source_repo.repo_name))
281 redirect(url('summary_home', repo_name=source_repo.repo_name))
282
282
283 commit_id = request.GET.get('commit')
283 commit_id = request.GET.get('commit')
284 branch_ref = request.GET.get('branch')
284 branch_ref = request.GET.get('branch')
285 bookmark_ref = request.GET.get('bookmark')
285 bookmark_ref = request.GET.get('bookmark')
286
286
287 try:
287 try:
288 source_repo_data = PullRequestModel().generate_repo_data(
288 source_repo_data = PullRequestModel().generate_repo_data(
289 source_repo, commit_id=commit_id,
289 source_repo, commit_id=commit_id,
290 branch=branch_ref, bookmark=bookmark_ref)
290 branch=branch_ref, bookmark=bookmark_ref)
291 except CommitDoesNotExistError as e:
291 except CommitDoesNotExistError as e:
292 log.exception(e)
292 log.exception(e)
293 h.flash(_('Commit does not exist'), 'error')
293 h.flash(_('Commit does not exist'), 'error')
294 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
294 redirect(url('pullrequest_home', repo_name=source_repo.repo_name))
295
295
296 default_target_repo = source_repo
296 default_target_repo = source_repo
297 if (source_repo.parent and
297 if (source_repo.parent and
298 not source_repo.parent.scm_instance().is_empty()):
298 not source_repo.parent.scm_instance().is_empty()):
299 # change default if we have a parent repo
299 # change default if we have a parent repo
300 default_target_repo = source_repo.parent
300 default_target_repo = source_repo.parent
301
301
302 target_repo_data = PullRequestModel().generate_repo_data(
302 target_repo_data = PullRequestModel().generate_repo_data(
303 default_target_repo)
303 default_target_repo)
304
304
305 selected_source_ref = source_repo_data['refs']['selected_ref']
305 selected_source_ref = source_repo_data['refs']['selected_ref']
306
306
307 title_source_ref = selected_source_ref.split(':', 2)[1]
307 title_source_ref = selected_source_ref.split(':', 2)[1]
308 c.default_title = PullRequestModel().generate_pullrequest_title(
308 c.default_title = PullRequestModel().generate_pullrequest_title(
309 source=source_repo.repo_name,
309 source=source_repo.repo_name,
310 source_ref=title_source_ref,
310 source_ref=title_source_ref,
311 target=default_target_repo.repo_name
311 target=default_target_repo.repo_name
312 )
312 )
313
313
314 c.default_repo_data = {
314 c.default_repo_data = {
315 'source_repo_name': source_repo.repo_name,
315 'source_repo_name': source_repo.repo_name,
316 'source_refs_json': json.dumps(source_repo_data),
316 'source_refs_json': json.dumps(source_repo_data),
317 'target_repo_name': default_target_repo.repo_name,
317 'target_repo_name': default_target_repo.repo_name,
318 'target_refs_json': json.dumps(target_repo_data),
318 'target_refs_json': json.dumps(target_repo_data),
319 }
319 }
320 c.default_source_ref = selected_source_ref
320 c.default_source_ref = selected_source_ref
321
321
322 return render('/pullrequests/pullrequest.html')
322 return render('/pullrequests/pullrequest.html')
323
323
324 @LoginRequired()
324 @LoginRequired()
325 @NotAnonymous()
325 @NotAnonymous()
326 @XHRRequired()
326 @XHRRequired()
327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
327 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
328 'repository.admin')
328 'repository.admin')
329 @jsonify
329 @jsonify
330 def get_repo_refs(self, repo_name, target_repo_name):
330 def get_repo_refs(self, repo_name, target_repo_name):
331 repo = Repository.get_by_repo_name(target_repo_name)
331 repo = Repository.get_by_repo_name(target_repo_name)
332 if not repo:
332 if not repo:
333 raise HTTPNotFound
333 raise HTTPNotFound
334 return PullRequestModel().generate_repo_data(repo)
334 return PullRequestModel().generate_repo_data(repo)
335
335
336 @LoginRequired()
336 @LoginRequired()
337 @NotAnonymous()
337 @NotAnonymous()
338 @XHRRequired()
338 @XHRRequired()
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
339 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
340 'repository.admin')
340 'repository.admin')
341 @jsonify
341 @jsonify
342 def get_repo_destinations(self, repo_name):
342 def get_repo_destinations(self, repo_name):
343 repo = Repository.get_by_repo_name(repo_name)
343 repo = Repository.get_by_repo_name(repo_name)
344 if not repo:
344 if not repo:
345 raise HTTPNotFound
345 raise HTTPNotFound
346 filter_query = request.GET.get('query')
346 filter_query = request.GET.get('query')
347
347
348 query = Repository.query() \
348 query = Repository.query() \
349 .order_by(func.length(Repository.repo_name)) \
349 .order_by(func.length(Repository.repo_name)) \
350 .filter(or_(
350 .filter(or_(
351 Repository.repo_name == repo.repo_name,
351 Repository.repo_name == repo.repo_name,
352 Repository.fork_id == repo.repo_id))
352 Repository.fork_id == repo.repo_id))
353
353
354 if filter_query:
354 if filter_query:
355 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
355 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
356 query = query.filter(
356 query = query.filter(
357 Repository.repo_name.ilike(ilike_expression))
357 Repository.repo_name.ilike(ilike_expression))
358
358
359 add_parent = False
359 add_parent = False
360 if repo.parent:
360 if repo.parent:
361 if filter_query in repo.parent.repo_name:
361 if filter_query in repo.parent.repo_name:
362 if not repo.parent.scm_instance().is_empty():
362 if not repo.parent.scm_instance().is_empty():
363 add_parent = True
363 add_parent = True
364
364
365 limit = 20 - 1 if add_parent else 20
365 limit = 20 - 1 if add_parent else 20
366 all_repos = query.limit(limit).all()
366 all_repos = query.limit(limit).all()
367 if add_parent:
367 if add_parent:
368 all_repos += [repo.parent]
368 all_repos += [repo.parent]
369
369
370 repos = []
370 repos = []
371 for obj in self.scm_model.get_repos(all_repos):
371 for obj in self.scm_model.get_repos(all_repos):
372 repos.append({
372 repos.append({
373 'id': obj['name'],
373 'id': obj['name'],
374 'text': obj['name'],
374 'text': obj['name'],
375 'type': 'repo',
375 'type': 'repo',
376 'obj': obj['dbrepo']
376 'obj': obj['dbrepo']
377 })
377 })
378
378
379 data = {
379 data = {
380 'more': False,
380 'more': False,
381 'results': [{
381 'results': [{
382 'text': _('Repositories'),
382 'text': _('Repositories'),
383 'children': repos
383 'children': repos
384 }] if repos else []
384 }] if repos else []
385 }
385 }
386 return data
386 return data
387
387
388 @LoginRequired()
388 @LoginRequired()
389 @NotAnonymous()
389 @NotAnonymous()
390 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
390 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
391 'repository.admin')
391 'repository.admin')
392 @HasAcceptedRepoType('git', 'hg')
392 @HasAcceptedRepoType('git', 'hg')
393 @auth.CSRFRequired()
393 @auth.CSRFRequired()
394 def create(self, repo_name):
394 def create(self, repo_name):
395 repo = Repository.get_by_repo_name(repo_name)
395 repo = Repository.get_by_repo_name(repo_name)
396 if not repo:
396 if not repo:
397 raise HTTPNotFound
397 raise HTTPNotFound
398
398
399 try:
399 try:
400 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
400 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
401 except formencode.Invalid as errors:
401 except formencode.Invalid as errors:
402 if errors.error_dict.get('revisions'):
402 if errors.error_dict.get('revisions'):
403 msg = 'Revisions: %s' % errors.error_dict['revisions']
403 msg = 'Revisions: %s' % errors.error_dict['revisions']
404 elif errors.error_dict.get('pullrequest_title'):
404 elif errors.error_dict.get('pullrequest_title'):
405 msg = _('Pull request requires a title with min. 3 chars')
405 msg = _('Pull request requires a title with min. 3 chars')
406 else:
406 else:
407 msg = _('Error creating pull request: {}').format(errors)
407 msg = _('Error creating pull request: {}').format(errors)
408 log.exception(msg)
408 log.exception(msg)
409 h.flash(msg, 'error')
409 h.flash(msg, 'error')
410
410
411 # would rather just go back to form ...
411 # would rather just go back to form ...
412 return redirect(url('pullrequest_home', repo_name=repo_name))
412 return redirect(url('pullrequest_home', repo_name=repo_name))
413
413
414 source_repo = _form['source_repo']
414 source_repo = _form['source_repo']
415 source_ref = _form['source_ref']
415 source_ref = _form['source_ref']
416 target_repo = _form['target_repo']
416 target_repo = _form['target_repo']
417 target_ref = _form['target_ref']
417 target_ref = _form['target_ref']
418 commit_ids = _form['revisions'][::-1]
418 commit_ids = _form['revisions'][::-1]
419 reviewers = _form['review_members']
419 reviewers = _form['review_members']
420
420
421 # find the ancestor for this pr
421 # find the ancestor for this pr
422 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
422 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
423 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
423 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
424
424
425 source_scm = source_db_repo.scm_instance()
425 source_scm = source_db_repo.scm_instance()
426 target_scm = target_db_repo.scm_instance()
426 target_scm = target_db_repo.scm_instance()
427
427
428 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
428 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
429 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
429 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
430
430
431 ancestor = source_scm.get_common_ancestor(
431 ancestor = source_scm.get_common_ancestor(
432 source_commit.raw_id, target_commit.raw_id, target_scm)
432 source_commit.raw_id, target_commit.raw_id, target_scm)
433
433
434 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
434 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
435 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
435 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
436
436
437 pullrequest_title = _form['pullrequest_title']
437 pullrequest_title = _form['pullrequest_title']
438 title_source_ref = source_ref.split(':', 2)[1]
438 title_source_ref = source_ref.split(':', 2)[1]
439 if not pullrequest_title:
439 if not pullrequest_title:
440 pullrequest_title = PullRequestModel().generate_pullrequest_title(
440 pullrequest_title = PullRequestModel().generate_pullrequest_title(
441 source=source_repo,
441 source=source_repo,
442 source_ref=title_source_ref,
442 source_ref=title_source_ref,
443 target=target_repo
443 target=target_repo
444 )
444 )
445
445
446 description = _form['pullrequest_desc']
446 description = _form['pullrequest_desc']
447 try:
447 try:
448 pull_request = PullRequestModel().create(
448 pull_request = PullRequestModel().create(
449 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
449 c.rhodecode_user.user_id, source_repo, source_ref, target_repo,
450 target_ref, commit_ids, reviewers, pullrequest_title,
450 target_ref, commit_ids, reviewers, pullrequest_title,
451 description
451 description
452 )
452 )
453 Session().commit()
453 Session().commit()
454 h.flash(_('Successfully opened new pull request'),
454 h.flash(_('Successfully opened new pull request'),
455 category='success')
455 category='success')
456 except Exception as e:
456 except Exception as e:
457 msg = _('Error occurred during sending pull request')
457 msg = _('Error occurred during sending pull request')
458 log.exception(msg)
458 log.exception(msg)
459 h.flash(msg, category='error')
459 h.flash(msg, category='error')
460 return redirect(url('pullrequest_home', repo_name=repo_name))
460 return redirect(url('pullrequest_home', repo_name=repo_name))
461
461
462 return redirect(url('pullrequest_show', repo_name=target_repo,
462 return redirect(url('pullrequest_show', repo_name=target_repo,
463 pull_request_id=pull_request.pull_request_id))
463 pull_request_id=pull_request.pull_request_id))
464
464
465 @LoginRequired()
465 @LoginRequired()
466 @NotAnonymous()
466 @NotAnonymous()
467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
468 'repository.admin')
468 'repository.admin')
469 @auth.CSRFRequired()
469 @auth.CSRFRequired()
470 @jsonify
470 @jsonify
471 def update(self, repo_name, pull_request_id):
471 def update(self, repo_name, pull_request_id):
472 pull_request_id = safe_int(pull_request_id)
472 pull_request_id = safe_int(pull_request_id)
473 pull_request = PullRequest.get_or_404(pull_request_id)
473 pull_request = PullRequest.get_or_404(pull_request_id)
474 # only owner or admin can update it
474 # only owner or admin can update it
475 allowed_to_update = PullRequestModel().check_user_update(
475 allowed_to_update = PullRequestModel().check_user_update(
476 pull_request, c.rhodecode_user)
476 pull_request, c.rhodecode_user)
477 if allowed_to_update:
477 if allowed_to_update:
478 if 'reviewers_ids' in request.POST:
478 if 'reviewers_ids' in request.POST:
479 self._update_reviewers(pull_request_id)
479 self._update_reviewers(pull_request_id)
480 elif str2bool(request.POST.get('update_commits', 'false')):
480 elif str2bool(request.POST.get('update_commits', 'false')):
481 self._update_commits(pull_request)
481 self._update_commits(pull_request)
482 elif str2bool(request.POST.get('close_pull_request', 'false')):
482 elif str2bool(request.POST.get('close_pull_request', 'false')):
483 self._reject_close(pull_request)
483 self._reject_close(pull_request)
484 elif str2bool(request.POST.get('edit_pull_request', 'false')):
484 elif str2bool(request.POST.get('edit_pull_request', 'false')):
485 self._edit_pull_request(pull_request)
485 self._edit_pull_request(pull_request)
486 else:
486 else:
487 raise HTTPBadRequest()
487 raise HTTPBadRequest()
488 return True
488 return True
489 raise HTTPForbidden()
489 raise HTTPForbidden()
490
490
491 def _edit_pull_request(self, pull_request):
491 def _edit_pull_request(self, pull_request):
492 try:
492 try:
493 PullRequestModel().edit(
493 PullRequestModel().edit(
494 pull_request, request.POST.get('title'),
494 pull_request, request.POST.get('title'),
495 request.POST.get('description'))
495 request.POST.get('description'))
496 except ValueError:
496 except ValueError:
497 msg = _(u'Cannot update closed pull requests.')
497 msg = _(u'Cannot update closed pull requests.')
498 h.flash(msg, category='error')
498 h.flash(msg, category='error')
499 return
499 return
500 else:
500 else:
501 Session().commit()
501 Session().commit()
502
502
503 msg = _(u'Pull request title & description updated.')
503 msg = _(u'Pull request title & description updated.')
504 h.flash(msg, category='success')
504 h.flash(msg, category='success')
505 return
505 return
506
506
507 def _update_commits(self, pull_request):
507 def _update_commits(self, pull_request):
508 try:
508 try:
509 if PullRequestModel().has_valid_update_type(pull_request):
509 if PullRequestModel().has_valid_update_type(pull_request):
510 updated_version, changes = PullRequestModel().update_commits(
510 updated_version, changes = PullRequestModel().update_commits(
511 pull_request)
511 pull_request)
512 if updated_version:
512 if updated_version:
513 msg = _(
513 msg = _(
514 u'Pull request updated to "{source_commit_id}" with '
514 u'Pull request updated to "{source_commit_id}" with '
515 u'{count_added} added, {count_removed} removed '
515 u'{count_added} added, {count_removed} removed '
516 u'commits.'
516 u'commits.'
517 ).format(
517 ).format(
518 source_commit_id=pull_request.source_ref_parts.commit_id,
518 source_commit_id=pull_request.source_ref_parts.commit_id,
519 count_added=len(changes.added),
519 count_added=len(changes.added),
520 count_removed=len(changes.removed))
520 count_removed=len(changes.removed))
521 h.flash(msg, category='success')
521 h.flash(msg, category='success')
522 else:
522 else:
523 h.flash(_("Nothing changed in pull request."),
523 h.flash(_("Nothing changed in pull request."),
524 category='warning')
524 category='warning')
525 else:
525 else:
526 msg = _(
526 msg = _(
527 u"Skipping update of pull request due to reference "
527 u"Skipping update of pull request due to reference "
528 u"type: {reference_type}"
528 u"type: {reference_type}"
529 ).format(reference_type=pull_request.source_ref_parts.type)
529 ).format(reference_type=pull_request.source_ref_parts.type)
530 h.flash(msg, category='warning')
530 h.flash(msg, category='warning')
531 except CommitDoesNotExistError:
531 except CommitDoesNotExistError:
532 h.flash(
532 h.flash(
533 _(u'Update failed due to missing commits.'), category='error')
533 _(u'Update failed due to missing commits.'), category='error')
534
534
535 @auth.CSRFRequired()
535 @auth.CSRFRequired()
536 @LoginRequired()
536 @LoginRequired()
537 @NotAnonymous()
537 @NotAnonymous()
538 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
538 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
539 'repository.admin')
539 'repository.admin')
540 def merge(self, repo_name, pull_request_id):
540 def merge(self, repo_name, pull_request_id):
541 """
541 """
542 POST /{repo_name}/pull-request/{pull_request_id}
542 POST /{repo_name}/pull-request/{pull_request_id}
543
543
544 Merge will perform a server-side merge of the specified
544 Merge will perform a server-side merge of the specified
545 pull request, if the pull request is approved and mergeable.
545 pull request, if the pull request is approved and mergeable.
546 After succesfull merging, the pull request is automatically
546 After succesfull merging, the pull request is automatically
547 closed, with a relevant comment.
547 closed, with a relevant comment.
548 """
548 """
549 pull_request_id = safe_int(pull_request_id)
549 pull_request_id = safe_int(pull_request_id)
550 pull_request = PullRequest.get_or_404(pull_request_id)
550 pull_request = PullRequest.get_or_404(pull_request_id)
551 user = c.rhodecode_user
551 user = c.rhodecode_user
552
552
553 if self._meets_merge_pre_conditions(pull_request, user):
553 if self._meets_merge_pre_conditions(pull_request, user):
554 log.debug("Pre-conditions checked, trying to merge.")
554 log.debug("Pre-conditions checked, trying to merge.")
555 extras = vcs_operation_context(
555 extras = vcs_operation_context(
556 request.environ, repo_name=pull_request.target_repo.repo_name,
556 request.environ, repo_name=pull_request.target_repo.repo_name,
557 username=user.username, action='push',
557 username=user.username, action='push',
558 scm=pull_request.target_repo.repo_type)
558 scm=pull_request.target_repo.repo_type)
559 self._merge_pull_request(pull_request, user, extras)
559 self._merge_pull_request(pull_request, user, extras)
560
560
561 return redirect(url(
561 return redirect(url(
562 'pullrequest_show',
562 'pullrequest_show',
563 repo_name=pull_request.target_repo.repo_name,
563 repo_name=pull_request.target_repo.repo_name,
564 pull_request_id=pull_request.pull_request_id))
564 pull_request_id=pull_request.pull_request_id))
565
565
566 def _meets_merge_pre_conditions(self, pull_request, user):
566 def _meets_merge_pre_conditions(self, pull_request, user):
567 if not PullRequestModel().check_user_merge(pull_request, user):
567 if not PullRequestModel().check_user_merge(pull_request, user):
568 raise HTTPForbidden()
568 raise HTTPForbidden()
569
569
570 merge_status, msg = PullRequestModel().merge_status(pull_request)
570 merge_status, msg = PullRequestModel().merge_status(pull_request)
571 if not merge_status:
571 if not merge_status:
572 log.debug("Cannot merge, not mergeable.")
572 log.debug("Cannot merge, not mergeable.")
573 h.flash(msg, category='error')
573 h.flash(msg, category='error')
574 return False
574 return False
575
575
576 if (pull_request.calculated_review_status()
576 if (pull_request.calculated_review_status()
577 is not ChangesetStatus.STATUS_APPROVED):
577 is not ChangesetStatus.STATUS_APPROVED):
578 log.debug("Cannot merge, approval is pending.")
578 log.debug("Cannot merge, approval is pending.")
579 msg = _('Pull request reviewer approval is pending.')
579 msg = _('Pull request reviewer approval is pending.')
580 h.flash(msg, category='error')
580 h.flash(msg, category='error')
581 return False
581 return False
582 return True
582 return True
583
583
584 def _merge_pull_request(self, pull_request, user, extras):
584 def _merge_pull_request(self, pull_request, user, extras):
585 merge_resp = PullRequestModel().merge(
585 merge_resp = PullRequestModel().merge(
586 pull_request, user, extras=extras)
586 pull_request, user, extras=extras)
587
587
588 if merge_resp.executed:
588 if merge_resp.executed:
589 log.debug("The merge was successful, closing the pull request.")
589 log.debug("The merge was successful, closing the pull request.")
590 PullRequestModel().close_pull_request(
590 PullRequestModel().close_pull_request(
591 pull_request.pull_request_id, user)
591 pull_request.pull_request_id, user)
592 Session().commit()
592 Session().commit()
593 msg = _('Pull request was successfully merged and closed.')
593 msg = _('Pull request was successfully merged and closed.')
594 h.flash(msg, category='success')
594 h.flash(msg, category='success')
595 else:
595 else:
596 log.debug(
596 log.debug(
597 "The merge was not successful. Merge response: %s",
597 "The merge was not successful. Merge response: %s",
598 merge_resp)
598 merge_resp)
599 msg = PullRequestModel().merge_status_message(
599 msg = PullRequestModel().merge_status_message(
600 merge_resp.failure_reason)
600 merge_resp.failure_reason)
601 h.flash(msg, category='error')
601 h.flash(msg, category='error')
602
602
603 def _update_reviewers(self, pull_request_id):
603 def _update_reviewers(self, pull_request_id):
604 reviewers_ids = map(int, filter(
604 reviewers_ids = map(int, filter(
605 lambda v: v not in [None, ''],
605 lambda v: v not in [None, ''],
606 request.POST.get('reviewers_ids', '').split(',')))
606 request.POST.get('reviewers_ids', '').split(',')))
607 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
607 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
608 Session().commit()
608 Session().commit()
609
609
610 def _reject_close(self, pull_request):
610 def _reject_close(self, pull_request):
611 if pull_request.is_closed():
611 if pull_request.is_closed():
612 raise HTTPForbidden()
612 raise HTTPForbidden()
613
613
614 PullRequestModel().close_pull_request_with_comment(
614 PullRequestModel().close_pull_request_with_comment(
615 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
615 pull_request, c.rhodecode_user, c.rhodecode_db_repo)
616 Session().commit()
616 Session().commit()
617
617
618 @LoginRequired()
618 @LoginRequired()
619 @NotAnonymous()
619 @NotAnonymous()
620 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
620 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
621 'repository.admin')
621 'repository.admin')
622 @auth.CSRFRequired()
622 @auth.CSRFRequired()
623 @jsonify
623 @jsonify
624 def delete(self, repo_name, pull_request_id):
624 def delete(self, repo_name, pull_request_id):
625 pull_request_id = safe_int(pull_request_id)
625 pull_request_id = safe_int(pull_request_id)
626 pull_request = PullRequest.get_or_404(pull_request_id)
626 pull_request = PullRequest.get_or_404(pull_request_id)
627 # only owner can delete it !
627 # only owner can delete it !
628 if pull_request.author.user_id == c.rhodecode_user.user_id:
628 if pull_request.author.user_id == c.rhodecode_user.user_id:
629 PullRequestModel().delete(pull_request)
629 PullRequestModel().delete(pull_request)
630 Session().commit()
630 Session().commit()
631 h.flash(_('Successfully deleted pull request'),
631 h.flash(_('Successfully deleted pull request'),
632 category='success')
632 category='success')
633 return redirect(url('my_account_pullrequests'))
633 return redirect(url('my_account_pullrequests'))
634 raise HTTPForbidden()
634 raise HTTPForbidden()
635
635
636 @LoginRequired()
636 @LoginRequired()
637 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
637 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
638 'repository.admin')
638 'repository.admin')
639 def show(self, repo_name, pull_request_id):
639 def show(self, repo_name, pull_request_id):
640 pull_request_id = safe_int(pull_request_id)
640 pull_request_id = safe_int(pull_request_id)
641 c.pull_request = PullRequest.get_or_404(pull_request_id)
641 c.pull_request = PullRequest.get_or_404(pull_request_id)
642
642
643 if hasattr(c, 'pylons_dispatch_info'):
643 c.template_context['pull_request_data']['pull_request_id'] = \
644 c.pylons_dispatch_info['extra']['pull_request'] = pull_request_id
644 pull_request_id
645
645
646 # pull_requests repo_name we opened it against
646 # pull_requests repo_name we opened it against
647 # ie. target_repo must match
647 # ie. target_repo must match
648 if repo_name != c.pull_request.target_repo.repo_name:
648 if repo_name != c.pull_request.target_repo.repo_name:
649 raise HTTPNotFound
649 raise HTTPNotFound
650
650
651 c.allowed_to_change_status = PullRequestModel(). \
651 c.allowed_to_change_status = PullRequestModel(). \
652 check_user_change_status(c.pull_request, c.rhodecode_user)
652 check_user_change_status(c.pull_request, c.rhodecode_user)
653 c.allowed_to_update = PullRequestModel().check_user_update(
653 c.allowed_to_update = PullRequestModel().check_user_update(
654 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
654 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
655 c.allowed_to_merge = PullRequestModel().check_user_merge(
655 c.allowed_to_merge = PullRequestModel().check_user_merge(
656 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
656 c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed()
657
657
658 cc_model = ChangesetCommentsModel()
658 cc_model = ChangesetCommentsModel()
659
659
660 c.pull_request_reviewers = c.pull_request.reviewers_statuses()
660 c.pull_request_reviewers = c.pull_request.reviewers_statuses()
661
661
662 c.pull_request_review_status = c.pull_request.calculated_review_status()
662 c.pull_request_review_status = c.pull_request.calculated_review_status()
663 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
663 c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status(
664 c.pull_request)
664 c.pull_request)
665 c.approval_msg = None
665 c.approval_msg = None
666 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
666 if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED:
667 c.approval_msg = _('Reviewer approval is pending.')
667 c.approval_msg = _('Reviewer approval is pending.')
668 c.pr_merge_status = False
668 c.pr_merge_status = False
669 # load compare data into template context
669 # load compare data into template context
670 enable_comments = not c.pull_request.is_closed()
670 enable_comments = not c.pull_request.is_closed()
671 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
671 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
672
672
673 # this is a hack to properly display links, when creating PR, the
673 # this is a hack to properly display links, when creating PR, the
674 # compare view and others uses different notation, and
674 # compare view and others uses different notation, and
675 # compare_commits.html renders links based on the target_repo.
675 # compare_commits.html renders links based on the target_repo.
676 # We need to swap that here to generate it properly on the html side
676 # We need to swap that here to generate it properly on the html side
677 c.target_repo = c.source_repo
677 c.target_repo = c.source_repo
678
678
679 # inline comments
679 # inline comments
680 c.inline_cnt = 0
680 c.inline_cnt = 0
681 c.inline_comments = cc_model.get_inline_comments(
681 c.inline_comments = cc_model.get_inline_comments(
682 c.rhodecode_db_repo.repo_id,
682 c.rhodecode_db_repo.repo_id,
683 pull_request=pull_request_id).items()
683 pull_request=pull_request_id).items()
684 # count inline comments
684 # count inline comments
685 for __, lines in c.inline_comments:
685 for __, lines in c.inline_comments:
686 for comments in lines.values():
686 for comments in lines.values():
687 c.inline_cnt += len(comments)
687 c.inline_cnt += len(comments)
688
688
689 # outdated comments
689 # outdated comments
690 c.outdated_cnt = 0
690 c.outdated_cnt = 0
691 if ChangesetCommentsModel.use_outdated_comments(c.pull_request):
691 if ChangesetCommentsModel.use_outdated_comments(c.pull_request):
692 c.outdated_comments = cc_model.get_outdated_comments(
692 c.outdated_comments = cc_model.get_outdated_comments(
693 c.rhodecode_db_repo.repo_id,
693 c.rhodecode_db_repo.repo_id,
694 pull_request=c.pull_request)
694 pull_request=c.pull_request)
695 # Count outdated comments and check for deleted files
695 # Count outdated comments and check for deleted files
696 for file_name, lines in c.outdated_comments.iteritems():
696 for file_name, lines in c.outdated_comments.iteritems():
697 for comments in lines.values():
697 for comments in lines.values():
698 c.outdated_cnt += len(comments)
698 c.outdated_cnt += len(comments)
699 if file_name not in c.included_files:
699 if file_name not in c.included_files:
700 c.deleted_files.append(file_name)
700 c.deleted_files.append(file_name)
701 else:
701 else:
702 c.outdated_comments = {}
702 c.outdated_comments = {}
703
703
704 # comments
704 # comments
705 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
705 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
706 pull_request=pull_request_id)
706 pull_request=pull_request_id)
707
707
708 if c.allowed_to_update:
708 if c.allowed_to_update:
709 force_close = ('forced_closed', _('Close Pull Request'))
709 force_close = ('forced_closed', _('Close Pull Request'))
710 statuses = ChangesetStatus.STATUSES + [force_close]
710 statuses = ChangesetStatus.STATUSES + [force_close]
711 else:
711 else:
712 statuses = ChangesetStatus.STATUSES
712 statuses = ChangesetStatus.STATUSES
713 c.commit_statuses = statuses
713 c.commit_statuses = statuses
714
714
715 c.ancestor = None # TODO: add ancestor here
715 c.ancestor = None # TODO: add ancestor here
716
716
717 return render('/pullrequests/pullrequest_show.html')
717 return render('/pullrequests/pullrequest_show.html')
718
718
719 @LoginRequired()
719 @LoginRequired()
720 @NotAnonymous()
720 @NotAnonymous()
721 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
721 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
722 'repository.admin')
722 'repository.admin')
723 @auth.CSRFRequired()
723 @auth.CSRFRequired()
724 @jsonify
724 @jsonify
725 def comment(self, repo_name, pull_request_id):
725 def comment(self, repo_name, pull_request_id):
726 pull_request_id = safe_int(pull_request_id)
726 pull_request_id = safe_int(pull_request_id)
727 pull_request = PullRequest.get_or_404(pull_request_id)
727 pull_request = PullRequest.get_or_404(pull_request_id)
728 if pull_request.is_closed():
728 if pull_request.is_closed():
729 raise HTTPForbidden()
729 raise HTTPForbidden()
730
730
731 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
731 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
732 # as a changeset status, still we want to send it in one value.
732 # as a changeset status, still we want to send it in one value.
733 status = request.POST.get('changeset_status', None)
733 status = request.POST.get('changeset_status', None)
734 text = request.POST.get('text')
734 text = request.POST.get('text')
735 if status and '_closed' in status:
735 if status and '_closed' in status:
736 close_pr = True
736 close_pr = True
737 status = status.replace('_closed', '')
737 status = status.replace('_closed', '')
738 else:
738 else:
739 close_pr = False
739 close_pr = False
740
740
741 forced = (status == 'forced')
741 forced = (status == 'forced')
742 if forced:
742 if forced:
743 status = 'rejected'
743 status = 'rejected'
744
744
745 allowed_to_change_status = PullRequestModel().check_user_change_status(
745 allowed_to_change_status = PullRequestModel().check_user_change_status(
746 pull_request, c.rhodecode_user)
746 pull_request, c.rhodecode_user)
747
747
748 if status and allowed_to_change_status:
748 if status and allowed_to_change_status:
749 message = (_('Status change %(transition_icon)s %(status)s')
749 message = (_('Status change %(transition_icon)s %(status)s')
750 % {'transition_icon': '>',
750 % {'transition_icon': '>',
751 'status': ChangesetStatus.get_status_lbl(status)})
751 'status': ChangesetStatus.get_status_lbl(status)})
752 if close_pr:
752 if close_pr:
753 message = _('Closing with') + ' ' + message
753 message = _('Closing with') + ' ' + message
754 text = text or message
754 text = text or message
755 comm = ChangesetCommentsModel().create(
755 comm = ChangesetCommentsModel().create(
756 text=text,
756 text=text,
757 repo=c.rhodecode_db_repo.repo_id,
757 repo=c.rhodecode_db_repo.repo_id,
758 user=c.rhodecode_user.user_id,
758 user=c.rhodecode_user.user_id,
759 pull_request=pull_request_id,
759 pull_request=pull_request_id,
760 f_path=request.POST.get('f_path'),
760 f_path=request.POST.get('f_path'),
761 line_no=request.POST.get('line'),
761 line_no=request.POST.get('line'),
762 status_change=(ChangesetStatus.get_status_lbl(status)
762 status_change=(ChangesetStatus.get_status_lbl(status)
763 if status and allowed_to_change_status else None),
763 if status and allowed_to_change_status else None),
764 closing_pr=close_pr
764 closing_pr=close_pr
765 )
765 )
766
766
767 if allowed_to_change_status:
767 if allowed_to_change_status:
768 old_calculated_status = pull_request.calculated_review_status()
768 old_calculated_status = pull_request.calculated_review_status()
769 # get status if set !
769 # get status if set !
770 if status:
770 if status:
771 ChangesetStatusModel().set_status(
771 ChangesetStatusModel().set_status(
772 c.rhodecode_db_repo.repo_id,
772 c.rhodecode_db_repo.repo_id,
773 status,
773 status,
774 c.rhodecode_user.user_id,
774 c.rhodecode_user.user_id,
775 comm,
775 comm,
776 pull_request=pull_request_id
776 pull_request=pull_request_id
777 )
777 )
778
778
779 Session().flush()
779 Session().flush()
780 # we now calculate the status of pull request, and based on that
780 # we now calculate the status of pull request, and based on that
781 # calculation we set the commits status
781 # calculation we set the commits status
782 calculated_status = pull_request.calculated_review_status()
782 calculated_status = pull_request.calculated_review_status()
783 if old_calculated_status != calculated_status:
783 if old_calculated_status != calculated_status:
784 PullRequestModel()._trigger_pull_request_hook(
784 PullRequestModel()._trigger_pull_request_hook(
785 pull_request, c.rhodecode_user, 'review_status_change')
785 pull_request, c.rhodecode_user, 'review_status_change')
786
786
787 calculated_status_lbl = ChangesetStatus.get_status_lbl(
787 calculated_status_lbl = ChangesetStatus.get_status_lbl(
788 calculated_status)
788 calculated_status)
789
789
790 if close_pr:
790 if close_pr:
791 status_completed = (
791 status_completed = (
792 calculated_status in [ChangesetStatus.STATUS_APPROVED,
792 calculated_status in [ChangesetStatus.STATUS_APPROVED,
793 ChangesetStatus.STATUS_REJECTED])
793 ChangesetStatus.STATUS_REJECTED])
794 if forced or status_completed:
794 if forced or status_completed:
795 PullRequestModel().close_pull_request(
795 PullRequestModel().close_pull_request(
796 pull_request_id, c.rhodecode_user)
796 pull_request_id, c.rhodecode_user)
797 else:
797 else:
798 h.flash(_('Closing pull request on other statuses than '
798 h.flash(_('Closing pull request on other statuses than '
799 'rejected or approved is forbidden. '
799 'rejected or approved is forbidden. '
800 'Calculated status from all reviewers '
800 'Calculated status from all reviewers '
801 'is currently: %s') % calculated_status_lbl,
801 'is currently: %s') % calculated_status_lbl,
802 category='warning')
802 category='warning')
803
803
804 Session().commit()
804 Session().commit()
805
805
806 if not request.is_xhr:
806 if not request.is_xhr:
807 return redirect(h.url('pullrequest_show', repo_name=repo_name,
807 return redirect(h.url('pullrequest_show', repo_name=repo_name,
808 pull_request_id=pull_request_id))
808 pull_request_id=pull_request_id))
809
809
810 data = {
810 data = {
811 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
811 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
812 }
812 }
813 if comm:
813 if comm:
814 c.co = comm
814 c.co = comm
815 data.update(comm.get_dict())
815 data.update(comm.get_dict())
816 data.update({'rendered_text':
816 data.update({'rendered_text':
817 render('changeset/changeset_comment_block.html')})
817 render('changeset/changeset_comment_block.html')})
818
818
819 return data
819 return data
820
820
821 @LoginRequired()
821 @LoginRequired()
822 @NotAnonymous()
822 @NotAnonymous()
823 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
823 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
824 'repository.admin')
824 'repository.admin')
825 @auth.CSRFRequired()
825 @auth.CSRFRequired()
826 @jsonify
826 @jsonify
827 def delete_comment(self, repo_name, comment_id):
827 def delete_comment(self, repo_name, comment_id):
828 return self._delete_comment(comment_id)
828 return self._delete_comment(comment_id)
829
829
830 def _delete_comment(self, comment_id):
830 def _delete_comment(self, comment_id):
831 comment_id = safe_int(comment_id)
831 comment_id = safe_int(comment_id)
832 co = ChangesetComment.get_or_404(comment_id)
832 co = ChangesetComment.get_or_404(comment_id)
833 if co.pull_request.is_closed():
833 if co.pull_request.is_closed():
834 # don't allow deleting comments on closed pull request
834 # don't allow deleting comments on closed pull request
835 raise HTTPForbidden()
835 raise HTTPForbidden()
836
836
837 is_owner = co.author.user_id == c.rhodecode_user.user_id
837 is_owner = co.author.user_id == c.rhodecode_user.user_id
838 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
838 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
839 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
839 if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner:
840 old_calculated_status = co.pull_request.calculated_review_status()
840 old_calculated_status = co.pull_request.calculated_review_status()
841 ChangesetCommentsModel().delete(comment=co)
841 ChangesetCommentsModel().delete(comment=co)
842 Session().commit()
842 Session().commit()
843 calculated_status = co.pull_request.calculated_review_status()
843 calculated_status = co.pull_request.calculated_review_status()
844 if old_calculated_status != calculated_status:
844 if old_calculated_status != calculated_status:
845 PullRequestModel()._trigger_pull_request_hook(
845 PullRequestModel()._trigger_pull_request_hook(
846 co.pull_request, c.rhodecode_user, 'review_status_change')
846 co.pull_request, c.rhodecode_user, 'review_status_change')
847 return True
847 return True
848 else:
848 else:
849 raise HTTPForbidden()
849 raise HTTPForbidden()
@@ -1,557 +1,577 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31
31
32 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from pylons import config, tmpl_context as c, request, session, url
35 from pylons import config, tmpl_context as c, request, session, url
36 from pylons.controllers import WSGIController
36 from pylons.controllers import WSGIController
37 from pylons.controllers.util import redirect
37 from pylons.controllers.util import redirect
38 from pylons.i18n import translation
38 from pylons.i18n import translation
39 # marcink: don't remove this import
39 # marcink: don't remove this import
40 from pylons.templating import render_mako as render # noqa
40 from pylons.templating import render_mako as render # noqa
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42 from webob.exc import HTTPFound
42 from webob.exc import HTTPFound
43
43
44
44
45 import rhodecode
45 import rhodecode
46 from rhodecode.authentication.base import VCS_TYPE
46 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.lib import auth, utils2
47 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import helpers as h
48 from rhodecode.lib import helpers as h
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.exceptions import UserCreationError
50 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.utils import (
51 from rhodecode.lib.utils import (
52 get_repo_slug, set_rhodecode_config, password_changed,
52 get_repo_slug, set_rhodecode_config, password_changed,
53 get_enabled_hook_classes)
53 get_enabled_hook_classes)
54 from rhodecode.lib.utils2 import (
54 from rhodecode.lib.utils2 import (
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.model import meta
57 from rhodecode.model import meta
58 from rhodecode.model.db import Repository, User
58 from rhodecode.model.db import Repository, User
59 from rhodecode.model.notification import NotificationModel
59 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62
62
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 def _filter_proxy(ip):
67 def _filter_proxy(ip):
68 """
68 """
69 Passed in IP addresses in HEADERS can be in a special format of multiple
69 Passed in IP addresses in HEADERS can be in a special format of multiple
70 ips. Those comma separated IPs are passed from various proxies in the
70 ips. Those comma separated IPs are passed from various proxies in the
71 chain of request processing. The left-most being the original client.
71 chain of request processing. The left-most being the original client.
72 We only care about the first IP which came from the org. client.
72 We only care about the first IP which came from the org. client.
73
73
74 :param ip: ip string from headers
74 :param ip: ip string from headers
75 """
75 """
76 if ',' in ip:
76 if ',' in ip:
77 _ips = ip.split(',')
77 _ips = ip.split(',')
78 _first_ip = _ips[0].strip()
78 _first_ip = _ips[0].strip()
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 return _first_ip
80 return _first_ip
81 return ip
81 return ip
82
82
83
83
84 def _filter_port(ip):
84 def _filter_port(ip):
85 """
85 """
86 Removes a port from ip, there are 4 main cases to handle here.
86 Removes a port from ip, there are 4 main cases to handle here.
87 - ipv4 eg. 127.0.0.1
87 - ipv4 eg. 127.0.0.1
88 - ipv6 eg. ::1
88 - ipv6 eg. ::1
89 - ipv4+port eg. 127.0.0.1:8080
89 - ipv4+port eg. 127.0.0.1:8080
90 - ipv6+port eg. [::1]:8080
90 - ipv6+port eg. [::1]:8080
91
91
92 :param ip:
92 :param ip:
93 """
93 """
94 def is_ipv6(ip_addr):
94 def is_ipv6(ip_addr):
95 if hasattr(socket, 'inet_pton'):
95 if hasattr(socket, 'inet_pton'):
96 try:
96 try:
97 socket.inet_pton(socket.AF_INET6, ip_addr)
97 socket.inet_pton(socket.AF_INET6, ip_addr)
98 except socket.error:
98 except socket.error:
99 return False
99 return False
100 else:
100 else:
101 # fallback to ipaddress
101 # fallback to ipaddress
102 try:
102 try:
103 ipaddress.IPv6Address(ip_addr)
103 ipaddress.IPv6Address(ip_addr)
104 except Exception:
104 except Exception:
105 return False
105 return False
106 return True
106 return True
107
107
108 if ':' not in ip: # must be ipv4 pure ip
108 if ':' not in ip: # must be ipv4 pure ip
109 return ip
109 return ip
110
110
111 if '[' in ip and ']' in ip: # ipv6 with port
111 if '[' in ip and ']' in ip: # ipv6 with port
112 return ip.split(']')[0][1:].lower()
112 return ip.split(']')[0][1:].lower()
113
113
114 # must be ipv6 or ipv4 with port
114 # must be ipv6 or ipv4 with port
115 if is_ipv6(ip):
115 if is_ipv6(ip):
116 return ip
116 return ip
117 else:
117 else:
118 ip, _port = ip.split(':')[:2] # means ipv4+port
118 ip, _port = ip.split(':')[:2] # means ipv4+port
119 return ip
119 return ip
120
120
121
121
122 def get_ip_addr(environ):
122 def get_ip_addr(environ):
123 proxy_key = 'HTTP_X_REAL_IP'
123 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 def_key = 'REMOTE_ADDR'
125 def_key = 'REMOTE_ADDR'
126 _filters = lambda x: _filter_port(_filter_proxy(x))
126 _filters = lambda x: _filter_port(_filter_proxy(x))
127
127
128 ip = environ.get(proxy_key)
128 ip = environ.get(proxy_key)
129 if ip:
129 if ip:
130 return _filters(ip)
130 return _filters(ip)
131
131
132 ip = environ.get(proxy_key2)
132 ip = environ.get(proxy_key2)
133 if ip:
133 if ip:
134 return _filters(ip)
134 return _filters(ip)
135
135
136 ip = environ.get(def_key, '0.0.0.0')
136 ip = environ.get(def_key, '0.0.0.0')
137 return _filters(ip)
137 return _filters(ip)
138
138
139
139
140 def get_server_ip_addr(environ, log_errors=True):
140 def get_server_ip_addr(environ, log_errors=True):
141 hostname = environ.get('SERVER_NAME')
141 hostname = environ.get('SERVER_NAME')
142 try:
142 try:
143 return socket.gethostbyname(hostname)
143 return socket.gethostbyname(hostname)
144 except Exception as e:
144 except Exception as e:
145 if log_errors:
145 if log_errors:
146 # in some cases this lookup is not possible, and we don't want to
146 # in some cases this lookup is not possible, and we don't want to
147 # make it an exception in logs
147 # make it an exception in logs
148 log.exception('Could not retrieve server ip address: %s', e)
148 log.exception('Could not retrieve server ip address: %s', e)
149 return hostname
149 return hostname
150
150
151
151
152 def get_server_port(environ):
152 def get_server_port(environ):
153 return environ.get('SERVER_PORT')
153 return environ.get('SERVER_PORT')
154
154
155
155
156 def get_access_path(environ):
156 def get_access_path(environ):
157 path = environ.get('PATH_INFO')
157 path = environ.get('PATH_INFO')
158 org_req = environ.get('pylons.original_request')
158 org_req = environ.get('pylons.original_request')
159 if org_req:
159 if org_req:
160 path = org_req.environ.get('PATH_INFO')
160 path = org_req.environ.get('PATH_INFO')
161 return path
161 return path
162
162
163
163
164 def vcs_operation_context(
164 def vcs_operation_context(
165 environ, repo_name, username, action, scm, check_locking=True):
165 environ, repo_name, username, action, scm, check_locking=True):
166 """
166 """
167 Generate the context for a vcs operation, e.g. push or pull.
167 Generate the context for a vcs operation, e.g. push or pull.
168
168
169 This context is passed over the layers so that hooks triggered by the
169 This context is passed over the layers so that hooks triggered by the
170 vcs operation know details like the user, the user's IP address etc.
170 vcs operation know details like the user, the user's IP address etc.
171
171
172 :param check_locking: Allows to switch of the computation of the locking
172 :param check_locking: Allows to switch of the computation of the locking
173 data. This serves mainly the need of the simplevcs middleware to be
173 data. This serves mainly the need of the simplevcs middleware to be
174 able to disable this for certain operations.
174 able to disable this for certain operations.
175
175
176 """
176 """
177 # Tri-state value: False: unlock, None: nothing, True: lock
177 # Tri-state value: False: unlock, None: nothing, True: lock
178 make_lock = None
178 make_lock = None
179 locked_by = [None, None, None]
179 locked_by = [None, None, None]
180 is_anonymous = username == User.DEFAULT_USER
180 is_anonymous = username == User.DEFAULT_USER
181 if not is_anonymous and check_locking:
181 if not is_anonymous and check_locking:
182 log.debug('Checking locking on repository "%s"', repo_name)
182 log.debug('Checking locking on repository "%s"', repo_name)
183 user = User.get_by_username(username)
183 user = User.get_by_username(username)
184 repo = Repository.get_by_repo_name(repo_name)
184 repo = Repository.get_by_repo_name(repo_name)
185 make_lock, __, locked_by = repo.get_locking_state(
185 make_lock, __, locked_by = repo.get_locking_state(
186 action, user.user_id)
186 action, user.user_id)
187
187
188 settings_model = VcsSettingsModel(repo=repo_name)
188 settings_model = VcsSettingsModel(repo=repo_name)
189 ui_settings = settings_model.get_ui_settings()
189 ui_settings = settings_model.get_ui_settings()
190
190
191 extras = {
191 extras = {
192 'ip': get_ip_addr(environ),
192 'ip': get_ip_addr(environ),
193 'username': username,
193 'username': username,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'make_lock': make_lock,
198 'make_lock': make_lock,
199 'locked_by': locked_by,
199 'locked_by': locked_by,
200 'server_url': utils2.get_server_url(environ),
200 'server_url': utils2.get_server_url(environ),
201 'hooks': get_enabled_hook_classes(ui_settings),
201 'hooks': get_enabled_hook_classes(ui_settings),
202 }
202 }
203 return extras
203 return extras
204
204
205
205
206 class BasicAuth(AuthBasicAuthenticator):
206 class BasicAuth(AuthBasicAuthenticator):
207
207
208 def __init__(self, realm, authfunc, auth_http_code=None,
208 def __init__(self, realm, authfunc, auth_http_code=None,
209 initial_call_detection=False):
209 initial_call_detection=False):
210 self.realm = realm
210 self.realm = realm
211 self.initial_call = initial_call_detection
211 self.initial_call = initial_call_detection
212 self.authfunc = authfunc
212 self.authfunc = authfunc
213 self._rc_auth_http_code = auth_http_code
213 self._rc_auth_http_code = auth_http_code
214
214
215 def _get_response_from_code(self, http_code):
215 def _get_response_from_code(self, http_code):
216 try:
216 try:
217 return get_exception(safe_int(http_code))
217 return get_exception(safe_int(http_code))
218 except Exception:
218 except Exception:
219 log.exception('Failed to fetch response for code %s' % http_code)
219 log.exception('Failed to fetch response for code %s' % http_code)
220 return HTTPForbidden
220 return HTTPForbidden
221
221
222 def build_authentication(self):
222 def build_authentication(self):
223 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
224 if self._rc_auth_http_code and not self.initial_call:
224 if self._rc_auth_http_code and not self.initial_call:
225 # return alternative HTTP code if alternative http return code
225 # return alternative HTTP code if alternative http return code
226 # is specified in RhodeCode config, but ONLY if it's not the
226 # is specified in RhodeCode config, but ONLY if it's not the
227 # FIRST call
227 # FIRST call
228 custom_response_klass = self._get_response_from_code(
228 custom_response_klass = self._get_response_from_code(
229 self._rc_auth_http_code)
229 self._rc_auth_http_code)
230 return custom_response_klass(headers=head)
230 return custom_response_klass(headers=head)
231 return HTTPUnauthorized(headers=head)
231 return HTTPUnauthorized(headers=head)
232
232
233 def authenticate(self, environ):
233 def authenticate(self, environ):
234 authorization = AUTHORIZATION(environ)
234 authorization = AUTHORIZATION(environ)
235 if not authorization:
235 if not authorization:
236 return self.build_authentication()
236 return self.build_authentication()
237 (authmeth, auth) = authorization.split(' ', 1)
237 (authmeth, auth) = authorization.split(' ', 1)
238 if 'basic' != authmeth.lower():
238 if 'basic' != authmeth.lower():
239 return self.build_authentication()
239 return self.build_authentication()
240 auth = auth.strip().decode('base64')
240 auth = auth.strip().decode('base64')
241 _parts = auth.split(':', 1)
241 _parts = auth.split(':', 1)
242 if len(_parts) == 2:
242 if len(_parts) == 2:
243 username, password = _parts
243 username, password = _parts
244 if self.authfunc(
244 if self.authfunc(
245 username, password, environ, VCS_TYPE):
245 username, password, environ, VCS_TYPE):
246 return username
246 return username
247 if username and password:
247 if username and password:
248 # we mark that we actually executed authentication once, at
248 # we mark that we actually executed authentication once, at
249 # that point we can use the alternative auth code
249 # that point we can use the alternative auth code
250 self.initial_call = False
250 self.initial_call = False
251
251
252 return self.build_authentication()
252 return self.build_authentication()
253
253
254 __call__ = authenticate
254 __call__ = authenticate
255
255
256
256
257 def attach_context_attributes(context):
257 def attach_context_attributes(context):
258 rc_config = SettingsModel().get_all_settings(cache=True)
258 rc_config = SettingsModel().get_all_settings(cache=True)
259
259
260 context.rhodecode_version = rhodecode.__version__
260 context.rhodecode_version = rhodecode.__version__
261 context.rhodecode_edition = config.get('rhodecode.edition')
261 context.rhodecode_edition = config.get('rhodecode.edition')
262 # unique secret + version does not leak the version but keep consistency
262 # unique secret + version does not leak the version but keep consistency
263 context.rhodecode_version_hash = md5(
263 context.rhodecode_version_hash = md5(
264 config.get('beaker.session.secret', '') +
264 config.get('beaker.session.secret', '') +
265 rhodecode.__version__)[:8]
265 rhodecode.__version__)[:8]
266
266
267 # Default language set for the incoming request
267 # Default language set for the incoming request
268 context.language = translation.get_lang()[0]
268 context.language = translation.get_lang()[0]
269
269
270 # Visual options
270 # Visual options
271 context.visual = AttributeDict({})
271 context.visual = AttributeDict({})
272
272
273 # DB store
273 # DB store
274 context.visual.show_public_icon = str2bool(
274 context.visual.show_public_icon = str2bool(
275 rc_config.get('rhodecode_show_public_icon'))
275 rc_config.get('rhodecode_show_public_icon'))
276 context.visual.show_private_icon = str2bool(
276 context.visual.show_private_icon = str2bool(
277 rc_config.get('rhodecode_show_private_icon'))
277 rc_config.get('rhodecode_show_private_icon'))
278 context.visual.stylify_metatags = str2bool(
278 context.visual.stylify_metatags = str2bool(
279 rc_config.get('rhodecode_stylify_metatags'))
279 rc_config.get('rhodecode_stylify_metatags'))
280 context.visual.dashboard_items = safe_int(
280 context.visual.dashboard_items = safe_int(
281 rc_config.get('rhodecode_dashboard_items', 100))
281 rc_config.get('rhodecode_dashboard_items', 100))
282 context.visual.admin_grid_items = safe_int(
282 context.visual.admin_grid_items = safe_int(
283 rc_config.get('rhodecode_admin_grid_items', 100))
283 rc_config.get('rhodecode_admin_grid_items', 100))
284 context.visual.repository_fields = str2bool(
284 context.visual.repository_fields = str2bool(
285 rc_config.get('rhodecode_repository_fields'))
285 rc_config.get('rhodecode_repository_fields'))
286 context.visual.show_version = str2bool(
286 context.visual.show_version = str2bool(
287 rc_config.get('rhodecode_show_version'))
287 rc_config.get('rhodecode_show_version'))
288 context.visual.use_gravatar = str2bool(
288 context.visual.use_gravatar = str2bool(
289 rc_config.get('rhodecode_use_gravatar'))
289 rc_config.get('rhodecode_use_gravatar'))
290 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
290 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
291 context.visual.default_renderer = rc_config.get(
291 context.visual.default_renderer = rc_config.get(
292 'rhodecode_markup_renderer', 'rst')
292 'rhodecode_markup_renderer', 'rst')
293 context.visual.rhodecode_support_url = \
293 context.visual.rhodecode_support_url = \
294 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
294 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
295
295
296 context.pre_code = rc_config.get('rhodecode_pre_code')
296 context.pre_code = rc_config.get('rhodecode_pre_code')
297 context.post_code = rc_config.get('rhodecode_post_code')
297 context.post_code = rc_config.get('rhodecode_post_code')
298 context.rhodecode_name = rc_config.get('rhodecode_title')
298 context.rhodecode_name = rc_config.get('rhodecode_title')
299 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
299 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
300 # if we have specified default_encoding in the request, it has more
300 # if we have specified default_encoding in the request, it has more
301 # priority
301 # priority
302 if request.GET.get('default_encoding'):
302 if request.GET.get('default_encoding'):
303 context.default_encodings.insert(0, request.GET.get('default_encoding'))
303 context.default_encodings.insert(0, request.GET.get('default_encoding'))
304 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
304 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
305
305
306 # INI stored
306 # INI stored
307 context.labs_active = str2bool(
307 context.labs_active = str2bool(
308 config.get('labs_settings_active', 'false'))
308 config.get('labs_settings_active', 'false'))
309 context.visual.allow_repo_location_change = str2bool(
309 context.visual.allow_repo_location_change = str2bool(
310 config.get('allow_repo_location_change', True))
310 config.get('allow_repo_location_change', True))
311 context.visual.allow_custom_hooks_settings = str2bool(
311 context.visual.allow_custom_hooks_settings = str2bool(
312 config.get('allow_custom_hooks_settings', True))
312 config.get('allow_custom_hooks_settings', True))
313 context.debug_style = str2bool(config.get('debug_style', False))
313 context.debug_style = str2bool(config.get('debug_style', False))
314
314
315 context.rhodecode_instanceid = config.get('instance_id')
315 context.rhodecode_instanceid = config.get('instance_id')
316
316
317 # AppEnlight
317 # AppEnlight
318 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
318 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
319 context.appenlight_api_public_key = config.get(
319 context.appenlight_api_public_key = config.get(
320 'appenlight.api_public_key', '')
320 'appenlight.api_public_key', '')
321 context.appenlight_server_url = config.get('appenlight.server_url', '')
321 context.appenlight_server_url = config.get('appenlight.server_url', '')
322
322
323 # END CONFIG VARS
323 # END CONFIG VARS
324
324
325 # TODO: This dosn't work when called from pylons compatibility tween.
325 # TODO: This dosn't work when called from pylons compatibility tween.
326 # Fix this and remove it from base controller.
326 # Fix this and remove it from base controller.
327 # context.repo_name = get_repo_slug(request) # can be empty
327 # context.repo_name = get_repo_slug(request) # can be empty
328
328
329 context.csrf_token = auth.get_csrf_token()
329 context.csrf_token = auth.get_csrf_token()
330 context.backends = rhodecode.BACKENDS.keys()
330 context.backends = rhodecode.BACKENDS.keys()
331 context.backends.sort()
331 context.backends.sort()
332 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
332 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
333 context.rhodecode_user.user_id)
333 context.rhodecode_user.user_id)
334
334
335
335
336 def get_auth_user(environ):
336 def get_auth_user(environ):
337 ip_addr = get_ip_addr(environ)
337 ip_addr = get_ip_addr(environ)
338 # make sure that we update permissions each time we call controller
338 # make sure that we update permissions each time we call controller
339 _auth_token = (request.GET.get('auth_token', '') or
339 _auth_token = (request.GET.get('auth_token', '') or
340 request.GET.get('api_key', ''))
340 request.GET.get('api_key', ''))
341
341
342 if _auth_token:
342 if _auth_token:
343 # when using API_KEY we are sure user exists.
343 # when using API_KEY we are sure user exists.
344 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
344 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
345 authenticated = False
345 authenticated = False
346 else:
346 else:
347 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
347 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
348 try:
348 try:
349 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
349 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
350 ip_addr=ip_addr)
350 ip_addr=ip_addr)
351 except UserCreationError as e:
351 except UserCreationError as e:
352 h.flash(e, 'error')
352 h.flash(e, 'error')
353 # container auth or other auth functions that create users
353 # container auth or other auth functions that create users
354 # on the fly can throw this exception signaling that there's
354 # on the fly can throw this exception signaling that there's
355 # issue with user creation, explanation should be provided
355 # issue with user creation, explanation should be provided
356 # in Exception itself. We then create a simple blank
356 # in Exception itself. We then create a simple blank
357 # AuthUser
357 # AuthUser
358 auth_user = AuthUser(ip_addr=ip_addr)
358 auth_user = AuthUser(ip_addr=ip_addr)
359
359
360 if password_changed(auth_user, session):
360 if password_changed(auth_user, session):
361 session.invalidate()
361 session.invalidate()
362 cookie_store = CookieStoreWrapper(
362 cookie_store = CookieStoreWrapper(
363 session.get('rhodecode_user'))
363 session.get('rhodecode_user'))
364 auth_user = AuthUser(ip_addr=ip_addr)
364 auth_user = AuthUser(ip_addr=ip_addr)
365
365
366 authenticated = cookie_store.get('is_authenticated')
366 authenticated = cookie_store.get('is_authenticated')
367
367
368 if not auth_user.is_authenticated and auth_user.is_user_object:
368 if not auth_user.is_authenticated and auth_user.is_user_object:
369 # user is not authenticated and not empty
369 # user is not authenticated and not empty
370 auth_user.set_authenticated(authenticated)
370 auth_user.set_authenticated(authenticated)
371
371
372 return auth_user
372 return auth_user
373
373
374
374
375 class BaseController(WSGIController):
375 class BaseController(WSGIController):
376
376
377 def __before__(self):
377 def __before__(self):
378 """
378 """
379 __before__ is called before controller methods and after __call__
379 __before__ is called before controller methods and after __call__
380 """
380 """
381 # on each call propagate settings calls into global settings.
381 # on each call propagate settings calls into global settings.
382 set_rhodecode_config(config)
382 set_rhodecode_config(config)
383 attach_context_attributes(c)
383 attach_context_attributes(c)
384
384
385 # TODO: Remove this when fixed in attach_context_attributes()
385 # TODO: Remove this when fixed in attach_context_attributes()
386 c.repo_name = get_repo_slug(request) # can be empty
386 c.repo_name = get_repo_slug(request) # can be empty
387
387
388 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
388 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
389 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
389 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
390 self.sa = meta.Session
390 self.sa = meta.Session
391 self.scm_model = ScmModel(self.sa)
391 self.scm_model = ScmModel(self.sa)
392
392
393 default_lang = c.language
393 default_lang = c.language
394 user_lang = c.language
394 user_lang = c.language
395 try:
395 try:
396 user_obj = self._rhodecode_user.get_instance()
396 user_obj = self._rhodecode_user.get_instance()
397 if user_obj:
397 if user_obj:
398 user_lang = user_obj.user_data.get('language')
398 user_lang = user_obj.user_data.get('language')
399 except Exception:
399 except Exception:
400 log.exception('Failed to fetch user language for user %s',
400 log.exception('Failed to fetch user language for user %s',
401 self._rhodecode_user)
401 self._rhodecode_user)
402
402
403 if user_lang and user_lang != default_lang:
403 if user_lang and user_lang != default_lang:
404 log.debug('set language to %s for user %s', user_lang,
404 log.debug('set language to %s for user %s', user_lang,
405 self._rhodecode_user)
405 self._rhodecode_user)
406 translation.set_lang(user_lang)
406 translation.set_lang(user_lang)
407
407
408 def _dispatch_redirect(self, with_url, environ, start_response):
408 def _dispatch_redirect(self, with_url, environ, start_response):
409 resp = HTTPFound(with_url)
409 resp = HTTPFound(with_url)
410 environ['SCRIPT_NAME'] = '' # handle prefix middleware
410 environ['SCRIPT_NAME'] = '' # handle prefix middleware
411 environ['PATH_INFO'] = with_url
411 environ['PATH_INFO'] = with_url
412 return resp(environ, start_response)
412 return resp(environ, start_response)
413
413
414 def __call__(self, environ, start_response):
414 def __call__(self, environ, start_response):
415 """Invoke the Controller"""
415 """Invoke the Controller"""
416 # WSGIController.__call__ dispatches to the Controller method
416 # WSGIController.__call__ dispatches to the Controller method
417 # the request is routed to. This routing information is
417 # the request is routed to. This routing information is
418 # available in environ['pylons.routes_dict']
418 # available in environ['pylons.routes_dict']
419 from rhodecode.lib import helpers as h
419 from rhodecode.lib import helpers as h
420
420
421 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
421 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
422 if environ.get('debugtoolbar.wants_pylons_context', False):
422 if environ.get('debugtoolbar.wants_pylons_context', False):
423 environ['debugtoolbar.pylons_context'] = c._current_obj()
423 environ['debugtoolbar.pylons_context'] = c._current_obj()
424
424
425 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
425 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
426 environ['pylons.routes_dict']['action']])
426 environ['pylons.routes_dict']['action']])
427
427
428 c.pylons_dispatch_info = {
428 c.template_context = {
429 'controller': environ['pylons.routes_dict']['controller'],
429 'repo_name': None,
430 'action': environ['pylons.routes_dict']['action'],
430 'repo_type': None,
431 'repo_landing_commit': None,
432 'rhodecode_user': {
433 'username': None,
434 'email': None,
435 },
436 'visual': {
437 'default_renderer': None
438 },
439 'commit_data': {
440 'commit_id': None
441 },
442 'pull_request_data': {'pull_request_id': None},
443 'timeago': {
444 'refresh_time': 120 * 1000,
445 'cutoff_limit': 1000*60*60*24*7
446 },
447 'pylons_dispatch':{
448 'controller': environ['pylons.routes_dict']['controller'],
449 'action': environ['pylons.routes_dict']['action'],
450 },
431 'extra': {'plugins': {}}
451 'extra': {'plugins': {}}
432 }
452 }
433
453
434 self.rc_config = SettingsModel().get_all_settings(cache=True)
454 self.rc_config = SettingsModel().get_all_settings(cache=True)
435 self.ip_addr = get_ip_addr(environ)
455 self.ip_addr = get_ip_addr(environ)
436
456
437 # The rhodecode auth user is looked up and passed through the
457 # The rhodecode auth user is looked up and passed through the
438 # environ by the pylons compatibility tween in pyramid.
458 # environ by the pylons compatibility tween in pyramid.
439 # So we can just grab it from there.
459 # So we can just grab it from there.
440 auth_user = environ['rc_auth_user']
460 auth_user = environ['rc_auth_user']
441
461
442 # set globals for auth user
462 # set globals for auth user
443 request.user = auth_user
463 request.user = auth_user
444 c.rhodecode_user = self._rhodecode_user = auth_user
464 c.rhodecode_user = self._rhodecode_user = auth_user
445
465
446 log.info('IP: %s User: %s accessed %s [%s]' % (
466 log.info('IP: %s User: %s accessed %s [%s]' % (
447 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
467 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
448 _route_name)
468 _route_name)
449 )
469 )
450
470
451 # TODO: Maybe this should be move to pyramid to cover all views.
471 # TODO: Maybe this should be move to pyramid to cover all views.
452 # check user attributes for password change flag
472 # check user attributes for password change flag
453 user_obj = auth_user.get_instance()
473 user_obj = auth_user.get_instance()
454 if user_obj and user_obj.user_data.get('force_password_change'):
474 if user_obj and user_obj.user_data.get('force_password_change'):
455 h.flash('You are required to change your password', 'warning',
475 h.flash('You are required to change your password', 'warning',
456 ignore_duplicate=True)
476 ignore_duplicate=True)
457
477
458 skip_user_check_urls = [
478 skip_user_check_urls = [
459 'error.document', 'login.logout', 'login.index',
479 'error.document', 'login.logout', 'login.index',
460 'admin/my_account.my_account_password',
480 'admin/my_account.my_account_password',
461 'admin/my_account.my_account_password_update'
481 'admin/my_account.my_account_password_update'
462 ]
482 ]
463 if _route_name not in skip_user_check_urls:
483 if _route_name not in skip_user_check_urls:
464 return self._dispatch_redirect(
484 return self._dispatch_redirect(
465 url('my_account_password'), environ, start_response)
485 url('my_account_password'), environ, start_response)
466
486
467 return WSGIController.__call__(self, environ, start_response)
487 return WSGIController.__call__(self, environ, start_response)
468
488
469
489
470 class BaseRepoController(BaseController):
490 class BaseRepoController(BaseController):
471 """
491 """
472 Base class for controllers responsible for loading all needed data for
492 Base class for controllers responsible for loading all needed data for
473 repository loaded items are
493 repository loaded items are
474
494
475 c.rhodecode_repo: instance of scm repository
495 c.rhodecode_repo: instance of scm repository
476 c.rhodecode_db_repo: instance of db
496 c.rhodecode_db_repo: instance of db
477 c.repository_requirements_missing: shows that repository specific data
497 c.repository_requirements_missing: shows that repository specific data
478 could not be displayed due to the missing requirements
498 could not be displayed due to the missing requirements
479 c.repository_pull_requests: show number of open pull requests
499 c.repository_pull_requests: show number of open pull requests
480 """
500 """
481
501
482 def __before__(self):
502 def __before__(self):
483 super(BaseRepoController, self).__before__()
503 super(BaseRepoController, self).__before__()
484 if c.repo_name: # extracted from routes
504 if c.repo_name: # extracted from routes
485 db_repo = Repository.get_by_repo_name(c.repo_name)
505 db_repo = Repository.get_by_repo_name(c.repo_name)
486 if not db_repo:
506 if not db_repo:
487 return
507 return
488
508
489 log.debug(
509 log.debug(
490 'Found repository in database %s with state `%s`',
510 'Found repository in database %s with state `%s`',
491 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
511 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
492 route = getattr(request.environ.get('routes.route'), 'name', '')
512 route = getattr(request.environ.get('routes.route'), 'name', '')
493
513
494 # allow to delete repos that are somehow damages in filesystem
514 # allow to delete repos that are somehow damages in filesystem
495 if route in ['delete_repo']:
515 if route in ['delete_repo']:
496 return
516 return
497
517
498 if db_repo.repo_state in [Repository.STATE_PENDING]:
518 if db_repo.repo_state in [Repository.STATE_PENDING]:
499 if route in ['repo_creating_home']:
519 if route in ['repo_creating_home']:
500 return
520 return
501 check_url = url('repo_creating_home', repo_name=c.repo_name)
521 check_url = url('repo_creating_home', repo_name=c.repo_name)
502 return redirect(check_url)
522 return redirect(check_url)
503
523
504 self.rhodecode_db_repo = db_repo
524 self.rhodecode_db_repo = db_repo
505
525
506 missing_requirements = False
526 missing_requirements = False
507 try:
527 try:
508 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
528 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
509 except RepositoryRequirementError as e:
529 except RepositoryRequirementError as e:
510 missing_requirements = True
530 missing_requirements = True
511 self._handle_missing_requirements(e)
531 self._handle_missing_requirements(e)
512
532
513 if self.rhodecode_repo is None and not missing_requirements:
533 if self.rhodecode_repo is None and not missing_requirements:
514 log.error('%s this repository is present in database but it '
534 log.error('%s this repository is present in database but it '
515 'cannot be created as an scm instance', c.repo_name)
535 'cannot be created as an scm instance', c.repo_name)
516
536
517 h.flash(_(
537 h.flash(_(
518 "The repository at %(repo_name)s cannot be located.") %
538 "The repository at %(repo_name)s cannot be located.") %
519 {'repo_name': c.repo_name},
539 {'repo_name': c.repo_name},
520 category='error', ignore_duplicate=True)
540 category='error', ignore_duplicate=True)
521 redirect(url('home'))
541 redirect(url('home'))
522
542
523 # update last change according to VCS data
543 # update last change according to VCS data
524 if not missing_requirements:
544 if not missing_requirements:
525 commit = db_repo.get_commit(
545 commit = db_repo.get_commit(
526 pre_load=["author", "date", "message", "parents"])
546 pre_load=["author", "date", "message", "parents"])
527 db_repo.update_commit_cache(commit)
547 db_repo.update_commit_cache(commit)
528
548
529 # Prepare context
549 # Prepare context
530 c.rhodecode_db_repo = db_repo
550 c.rhodecode_db_repo = db_repo
531 c.rhodecode_repo = self.rhodecode_repo
551 c.rhodecode_repo = self.rhodecode_repo
532 c.repository_requirements_missing = missing_requirements
552 c.repository_requirements_missing = missing_requirements
533
553
534 self._update_global_counters(self.scm_model, db_repo)
554 self._update_global_counters(self.scm_model, db_repo)
535
555
536 def _update_global_counters(self, scm_model, db_repo):
556 def _update_global_counters(self, scm_model, db_repo):
537 """
557 """
538 Base variables that are exposed to every page of repository
558 Base variables that are exposed to every page of repository
539 """
559 """
540 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
560 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
541
561
542 def _handle_missing_requirements(self, error):
562 def _handle_missing_requirements(self, error):
543 self.rhodecode_repo = None
563 self.rhodecode_repo = None
544 log.error(
564 log.error(
545 'Requirements are missing for repository %s: %s',
565 'Requirements are missing for repository %s: %s',
546 c.repo_name, error.message)
566 c.repo_name, error.message)
547
567
548 summary_url = url('summary_home', repo_name=c.repo_name)
568 summary_url = url('summary_home', repo_name=c.repo_name)
549 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
569 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
550 settings_update_url = url('repo', repo_name=c.repo_name)
570 settings_update_url = url('repo', repo_name=c.repo_name)
551 path = request.path
571 path = request.path
552 should_redirect = (
572 should_redirect = (
553 path not in (summary_url, settings_update_url)
573 path not in (summary_url, settings_update_url)
554 and '/settings' not in path or path == statistics_url
574 and '/settings' not in path or path == statistics_url
555 )
575 )
556 if should_redirect:
576 if should_redirect:
557 redirect(summary_url)
577 redirect(summary_url)
@@ -1,171 +1,135 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%def name="get_template_context()" filter="n, trim">{
4 <%
5
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 ## repo data
7 repo_name: "${getattr(c, 'repo_name', '')}",
8 % if hasattr(c, 'rhodecode_db_repo'):
9 repo_type: "${c.rhodecode_db_repo.repo_type}",
10 repo_landing_commit: "${c.rhodecode_db_repo.landing_rev[1]}",
11 % else:
12 repo_type: null,
13 repo_landing_commit: null,
14 % endif
15
6
16 ## user data
7 if hasattr(c, 'rhodecode_db_repo'):
17 % if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
18 rhodecode_user: {
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
19 username: "${c.rhodecode_user.username}",
20 email: "${c.rhodecode_user.email}",
21 },
22 % else:
23 rhodecode_user: {
24 username: null,
25 email: null,
26 },
27 % endif
28
10
29 ## visual settings
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
30 visual: {
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
31 default_renderer: "${h.get_visual_attr(c, 'default_renderer')}"
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
32 },
33
34 ## current commit context, filled inside templates that expose that
35 commit_data: {
36 commit_id: null,
37 },
38
14
39 ## current pr context, filled inside templates that expose that
15 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
40 pull_request_data: {
16 %>
41 pull_request_id: null,
42 },
43
44 ## timeago settings, can be overwritten by custom user settings later
45 timeago: {
46 refresh_time: ${120 * 1000},
47 cutoff_limit: ${1000*60*60*24*7}
48 },
49 dispatch_info: ${h.json.dumps(getattr(c, 'pylons_dispatch_info', {}))|n}
50 }
51
52 </%def>
53
17
54 <html xmlns="http://www.w3.org/1999/xhtml">
18 <html xmlns="http://www.w3.org/1999/xhtml">
55 <head>
19 <head>
56 <title>${self.title()}</title>
20 <title>${self.title()}</title>
57 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
21 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
58 <%def name="robots()">
22 <%def name="robots()">
59 <meta name="robots" content="index, nofollow"/>
23 <meta name="robots" content="index, nofollow"/>
60 </%def>
24 </%def>
61 ${self.robots()}
25 ${self.robots()}
62 <link rel="icon" href="${h.url('/images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
26 <link rel="icon" href="${h.url('/images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
63
27
64 ## CSS definitions
28 ## CSS definitions
65 <%def name="css()">
29 <%def name="css()">
66 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
30 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
67 <!--[if lt IE 9]>
31 <!--[if lt IE 9]>
68 <link rel="stylesheet" type="text/css" href="${h.url('/css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
32 <link rel="stylesheet" type="text/css" href="${h.url('/css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
69 <![endif]-->
33 <![endif]-->
70 ## EXTRA FOR CSS
34 ## EXTRA FOR CSS
71 ${self.css_extra()}
35 ${self.css_extra()}
72 </%def>
36 </%def>
73 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
37 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
74 <%def name="css_extra()">
38 <%def name="css_extra()">
75 </%def>
39 </%def>
76
40
77 ${self.css()}
41 ${self.css()}
78
42
79 ## JAVASCRIPT
43 ## JAVASCRIPT
80 <%def name="js()">
44 <%def name="js()">
81 <script src="${h.url('/js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
45 <script src="${h.url('/js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
82 <script type="text/javascript">
46 <script type="text/javascript">
83 // register templateContext to pass template variables to JS
47 // register templateContext to pass template variables to JS
84 var templateContext = ${get_template_context()};
48 var templateContext = ${h.json.dumps(c.template_context)|n};
85
49
86 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
50 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
87 %if hasattr(c, 'rhodecode_db_repo'):
51 %if hasattr(c, 'rhodecode_db_repo'):
88 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
52 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
89 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
53 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
90 %else:
54 %else:
91 var REPO_LANDING_REV = '';
55 var REPO_LANDING_REV = '';
92 var REPO_TYPE = '';
56 var REPO_TYPE = '';
93 %endif
57 %endif
94 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
58 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
95 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
59 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
96 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
60 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
97 % if getattr(c, 'rhodecode_user', None):
61 % if getattr(c, 'rhodecode_user', None):
98 var USER = {name:'${c.rhodecode_user.username}'};
62 var USER = {name:'${c.rhodecode_user.username}'};
99 % else:
63 % else:
100 var USER = {name:null};
64 var USER = {name:null};
101 % endif
65 % endif
102
66
103 var APPENLIGHT = {
67 var APPENLIGHT = {
104 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
68 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
105 key: '${getattr(c, "appenlight_api_public_key", "")}',
69 key: '${getattr(c, "appenlight_api_public_key", "")}',
106 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
70 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
107 requestInfo: {
71 requestInfo: {
108 % if getattr(c, 'rhodecode_user', None):
72 % if getattr(c, 'rhodecode_user', None):
109 ip: '${c.rhodecode_user.ip_addr}',
73 ip: '${c.rhodecode_user.ip_addr}',
110 username: '${c.rhodecode_user.username}'
74 username: '${c.rhodecode_user.username}'
111 % endif
75 % endif
112 }
76 }
113 };
77 };
114 </script>
78 </script>
115
79
116 <!--[if lt IE 9]>
80 <!--[if lt IE 9]>
117 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
81 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
118 <![endif]-->
82 <![endif]-->
119 <script language="javascript" type="text/javascript" src="${h.url('/js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
83 <script language="javascript" type="text/javascript" src="${h.url('/js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
120 <script language="javascript" type="text/javascript" src="${h.url('/js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
84 <script language="javascript" type="text/javascript" src="${h.url('/js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
121 <script>CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";</script>
85 <script>CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";</script>
122
86
123 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
87 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
124 ${self.js_extra()}
88 ${self.js_extra()}
125
89
126 <script type="text/javascript">
90 <script type="text/javascript">
127 $(document).ready(function(){
91 $(document).ready(function(){
128 show_more_event();
92 show_more_event();
129 timeagoActivate();
93 timeagoActivate();
130 })
94 })
131 </script>
95 </script>
132
96
133 </%def>
97 </%def>
134
98
135 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
99 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
136 <%def name="js_extra()"></%def>
100 <%def name="js_extra()"></%def>
137 ${self.js()}
101 ${self.js()}
138
102
139 <%def name="head_extra()"></%def>
103 <%def name="head_extra()"></%def>
140 ${self.head_extra()}
104 ${self.head_extra()}
141
105
142 <%include file="/base/plugins_base.html"/>
106 <%include file="/base/plugins_base.html"/>
143
107
144 ## extra stuff
108 ## extra stuff
145 %if c.pre_code:
109 %if c.pre_code:
146 ${c.pre_code|n}
110 ${c.pre_code|n}
147 %endif
111 %endif
148 </head>
112 </head>
149 <body id="body">
113 <body id="body">
150 <noscript>
114 <noscript>
151 <div class="noscript-error">
115 <div class="noscript-error">
152 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
116 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
153 </div>
117 </div>
154 </noscript>
118 </noscript>
155 ## IE hacks
119 ## IE hacks
156 <!--[if IE 7]>
120 <!--[if IE 7]>
157 <script>$(document.body).addClass('ie7')</script>
121 <script>$(document.body).addClass('ie7')</script>
158 <![endif]-->
122 <![endif]-->
159 <!--[if IE 8]>
123 <!--[if IE 8]>
160 <script>$(document.body).addClass('ie8')</script>
124 <script>$(document.body).addClass('ie8')</script>
161 <![endif]-->
125 <![endif]-->
162 <!--[if IE 9]>
126 <!--[if IE 9]>
163 <script>$(document.body).addClass('ie9')</script>
127 <script>$(document.body).addClass('ie9')</script>
164 <![endif]-->
128 <![endif]-->
165
129
166 ${next.body()}
130 ${next.body()}
167 %if c.post_code:
131 %if c.post_code:
168 ${c.post_code|n}
132 ${c.post_code|n}
169 %endif
133 %endif
170 </body>
134 </body>
171 </html>
135 </html>
General Comments 0
You need to be logged in to leave comments. Login now