##// END OF EJS Templates
pull-request: lock button when updating reviewers to forbid multi-submit....
marcink -
r1578:3793854d default
parent child Browse files
Show More
@@ -1,1423 +1,1425 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 pull request model for RhodeCode
23 pull request model for RhodeCode
24 """
24 """
25
25
26 from collections import namedtuple
26 from collections import namedtuple
27 import json
27 import json
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import urllib
30 import urllib
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.i18n.translation import lazy_ugettext
33 from pylons.i18n.translation import lazy_ugettext
34 from sqlalchemy import or_
34 from sqlalchemy import or_
35
35
36 from rhodecode.lib import helpers as h, hooks_utils, diffs
36 from rhodecode.lib import helpers as h, hooks_utils, diffs
37 from rhodecode.lib.compat import OrderedDict
37 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
38 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
39 from rhodecode.lib.markup_renderer import (
39 from rhodecode.lib.markup_renderer import (
40 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
40 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
41 from rhodecode.lib.utils import action_logger
41 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
42 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
43 from rhodecode.lib.vcs.backends.base import (
43 from rhodecode.lib.vcs.backends.base import (
44 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason)
44 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason)
45 from rhodecode.lib.vcs.conf import settings as vcs_settings
45 from rhodecode.lib.vcs.conf import settings as vcs_settings
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 CommitDoesNotExistError, EmptyRepositoryError)
47 CommitDoesNotExistError, EmptyRepositoryError)
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 PullRequest, PullRequestReviewers, ChangesetStatus,
52 PullRequest, PullRequestReviewers, ChangesetStatus,
53 PullRequestVersion, ChangesetComment, Repository)
53 PullRequestVersion, ChangesetComment, Repository)
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.notification import NotificationModel, \
55 from rhodecode.model.notification import NotificationModel, \
56 EmailNotificationModel
56 EmailNotificationModel
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.settings import VcsSettingsModel
58 from rhodecode.model.settings import VcsSettingsModel
59
59
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 # Data structure to hold the response data when updating commits during a pull
64 # Data structure to hold the response data when updating commits during a pull
65 # request update.
65 # request update.
66 UpdateResponse = namedtuple(
66 UpdateResponse = namedtuple(
67 'UpdateResponse', 'executed, reason, new, old, changes')
67 'UpdateResponse', 'executed, reason, new, old, changes')
68
68
69
69
70 class PullRequestModel(BaseModel):
70 class PullRequestModel(BaseModel):
71
71
72 cls = PullRequest
72 cls = PullRequest
73
73
74 DIFF_CONTEXT = 3
74 DIFF_CONTEXT = 3
75
75
76 MERGE_STATUS_MESSAGES = {
76 MERGE_STATUS_MESSAGES = {
77 MergeFailureReason.NONE: lazy_ugettext(
77 MergeFailureReason.NONE: lazy_ugettext(
78 'This pull request can be automatically merged.'),
78 'This pull request can be automatically merged.'),
79 MergeFailureReason.UNKNOWN: lazy_ugettext(
79 MergeFailureReason.UNKNOWN: lazy_ugettext(
80 'This pull request cannot be merged because of an unhandled'
80 'This pull request cannot be merged because of an unhandled'
81 ' exception.'),
81 ' exception.'),
82 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
82 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
83 'This pull request cannot be merged because of merge conflicts.'),
83 'This pull request cannot be merged because of merge conflicts.'),
84 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
84 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
85 'This pull request could not be merged because push to target'
85 'This pull request could not be merged because push to target'
86 ' failed.'),
86 ' failed.'),
87 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
87 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
88 'This pull request cannot be merged because the target is not a'
88 'This pull request cannot be merged because the target is not a'
89 ' head.'),
89 ' head.'),
90 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
90 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
91 'This pull request cannot be merged because the source contains'
91 'This pull request cannot be merged because the source contains'
92 ' more branches than the target.'),
92 ' more branches than the target.'),
93 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
93 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
94 'This pull request cannot be merged because the target has'
94 'This pull request cannot be merged because the target has'
95 ' multiple heads.'),
95 ' multiple heads.'),
96 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
96 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
97 'This pull request cannot be merged because the target repository'
97 'This pull request cannot be merged because the target repository'
98 ' is locked.'),
98 ' is locked.'),
99 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
99 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
100 'This pull request cannot be merged because the target or the '
100 'This pull request cannot be merged because the target or the '
101 'source reference is missing.'),
101 'source reference is missing.'),
102 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
102 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
103 'This pull request cannot be merged because the target '
103 'This pull request cannot be merged because the target '
104 'reference is missing.'),
104 'reference is missing.'),
105 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
105 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
106 'This pull request cannot be merged because the source '
106 'This pull request cannot be merged because the source '
107 'reference is missing.'),
107 'reference is missing.'),
108 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
108 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
109 'This pull request cannot be merged because of conflicts related '
109 'This pull request cannot be merged because of conflicts related '
110 'to sub repositories.'),
110 'to sub repositories.'),
111 }
111 }
112
112
113 UPDATE_STATUS_MESSAGES = {
113 UPDATE_STATUS_MESSAGES = {
114 UpdateFailureReason.NONE: lazy_ugettext(
114 UpdateFailureReason.NONE: lazy_ugettext(
115 'Pull request update successful.'),
115 'Pull request update successful.'),
116 UpdateFailureReason.UNKNOWN: lazy_ugettext(
116 UpdateFailureReason.UNKNOWN: lazy_ugettext(
117 'Pull request update failed because of an unknown error.'),
117 'Pull request update failed because of an unknown error.'),
118 UpdateFailureReason.NO_CHANGE: lazy_ugettext(
118 UpdateFailureReason.NO_CHANGE: lazy_ugettext(
119 'No update needed because the source reference is already '
119 'No update needed because the source reference is already '
120 'up to date.'),
120 'up to date.'),
121 UpdateFailureReason.WRONG_REF_TPYE: lazy_ugettext(
121 UpdateFailureReason.WRONG_REF_TPYE: lazy_ugettext(
122 'Pull request cannot be updated because the reference type is '
122 'Pull request cannot be updated because the reference type is '
123 'not supported for an update.'),
123 'not supported for an update.'),
124 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
124 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
125 'This pull request cannot be updated because the target '
125 'This pull request cannot be updated because the target '
126 'reference is missing.'),
126 'reference is missing.'),
127 UpdateFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
127 UpdateFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
128 'This pull request cannot be updated because the source '
128 'This pull request cannot be updated because the source '
129 'reference is missing.'),
129 'reference is missing.'),
130 }
130 }
131
131
132 def __get_pull_request(self, pull_request):
132 def __get_pull_request(self, pull_request):
133 return self._get_instance((
133 return self._get_instance((
134 PullRequest, PullRequestVersion), pull_request)
134 PullRequest, PullRequestVersion), pull_request)
135
135
136 def _check_perms(self, perms, pull_request, user, api=False):
136 def _check_perms(self, perms, pull_request, user, api=False):
137 if not api:
137 if not api:
138 return h.HasRepoPermissionAny(*perms)(
138 return h.HasRepoPermissionAny(*perms)(
139 user=user, repo_name=pull_request.target_repo.repo_name)
139 user=user, repo_name=pull_request.target_repo.repo_name)
140 else:
140 else:
141 return h.HasRepoPermissionAnyApi(*perms)(
141 return h.HasRepoPermissionAnyApi(*perms)(
142 user=user, repo_name=pull_request.target_repo.repo_name)
142 user=user, repo_name=pull_request.target_repo.repo_name)
143
143
144 def check_user_read(self, pull_request, user, api=False):
144 def check_user_read(self, pull_request, user, api=False):
145 _perms = ('repository.admin', 'repository.write', 'repository.read',)
145 _perms = ('repository.admin', 'repository.write', 'repository.read',)
146 return self._check_perms(_perms, pull_request, user, api)
146 return self._check_perms(_perms, pull_request, user, api)
147
147
148 def check_user_merge(self, pull_request, user, api=False):
148 def check_user_merge(self, pull_request, user, api=False):
149 _perms = ('repository.admin', 'repository.write', 'hg.admin',)
149 _perms = ('repository.admin', 'repository.write', 'hg.admin',)
150 return self._check_perms(_perms, pull_request, user, api)
150 return self._check_perms(_perms, pull_request, user, api)
151
151
152 def check_user_update(self, pull_request, user, api=False):
152 def check_user_update(self, pull_request, user, api=False):
153 owner = user.user_id == pull_request.user_id
153 owner = user.user_id == pull_request.user_id
154 return self.check_user_merge(pull_request, user, api) or owner
154 return self.check_user_merge(pull_request, user, api) or owner
155
155
156 def check_user_delete(self, pull_request, user):
156 def check_user_delete(self, pull_request, user):
157 owner = user.user_id == pull_request.user_id
157 owner = user.user_id == pull_request.user_id
158 _perms = ('repository.admin',)
158 _perms = ('repository.admin',)
159 return self._check_perms(_perms, pull_request, user) or owner
159 return self._check_perms(_perms, pull_request, user) or owner
160
160
161 def check_user_change_status(self, pull_request, user, api=False):
161 def check_user_change_status(self, pull_request, user, api=False):
162 reviewer = user.user_id in [x.user_id for x in
162 reviewer = user.user_id in [x.user_id for x in
163 pull_request.reviewers]
163 pull_request.reviewers]
164 return self.check_user_update(pull_request, user, api) or reviewer
164 return self.check_user_update(pull_request, user, api) or reviewer
165
165
166 def get(self, pull_request):
166 def get(self, pull_request):
167 return self.__get_pull_request(pull_request)
167 return self.__get_pull_request(pull_request)
168
168
169 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
169 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
170 opened_by=None, order_by=None,
170 opened_by=None, order_by=None,
171 order_dir='desc'):
171 order_dir='desc'):
172 repo = None
172 repo = None
173 if repo_name:
173 if repo_name:
174 repo = self._get_repo(repo_name)
174 repo = self._get_repo(repo_name)
175
175
176 q = PullRequest.query()
176 q = PullRequest.query()
177
177
178 # source or target
178 # source or target
179 if repo and source:
179 if repo and source:
180 q = q.filter(PullRequest.source_repo == repo)
180 q = q.filter(PullRequest.source_repo == repo)
181 elif repo:
181 elif repo:
182 q = q.filter(PullRequest.target_repo == repo)
182 q = q.filter(PullRequest.target_repo == repo)
183
183
184 # closed,opened
184 # closed,opened
185 if statuses:
185 if statuses:
186 q = q.filter(PullRequest.status.in_(statuses))
186 q = q.filter(PullRequest.status.in_(statuses))
187
187
188 # opened by filter
188 # opened by filter
189 if opened_by:
189 if opened_by:
190 q = q.filter(PullRequest.user_id.in_(opened_by))
190 q = q.filter(PullRequest.user_id.in_(opened_by))
191
191
192 if order_by:
192 if order_by:
193 order_map = {
193 order_map = {
194 'name_raw': PullRequest.pull_request_id,
194 'name_raw': PullRequest.pull_request_id,
195 'title': PullRequest.title,
195 'title': PullRequest.title,
196 'updated_on_raw': PullRequest.updated_on,
196 'updated_on_raw': PullRequest.updated_on,
197 'target_repo': PullRequest.target_repo_id
197 'target_repo': PullRequest.target_repo_id
198 }
198 }
199 if order_dir == 'asc':
199 if order_dir == 'asc':
200 q = q.order_by(order_map[order_by].asc())
200 q = q.order_by(order_map[order_by].asc())
201 else:
201 else:
202 q = q.order_by(order_map[order_by].desc())
202 q = q.order_by(order_map[order_by].desc())
203
203
204 return q
204 return q
205
205
206 def count_all(self, repo_name, source=False, statuses=None,
206 def count_all(self, repo_name, source=False, statuses=None,
207 opened_by=None):
207 opened_by=None):
208 """
208 """
209 Count the number of pull requests for a specific repository.
209 Count the number of pull requests for a specific repository.
210
210
211 :param repo_name: target or source repo
211 :param repo_name: target or source repo
212 :param source: boolean flag to specify if repo_name refers to source
212 :param source: boolean flag to specify if repo_name refers to source
213 :param statuses: list of pull request statuses
213 :param statuses: list of pull request statuses
214 :param opened_by: author user of the pull request
214 :param opened_by: author user of the pull request
215 :returns: int number of pull requests
215 :returns: int number of pull requests
216 """
216 """
217 q = self._prepare_get_all_query(
217 q = self._prepare_get_all_query(
218 repo_name, source=source, statuses=statuses, opened_by=opened_by)
218 repo_name, source=source, statuses=statuses, opened_by=opened_by)
219
219
220 return q.count()
220 return q.count()
221
221
222 def get_all(self, repo_name, source=False, statuses=None, opened_by=None,
222 def get_all(self, repo_name, source=False, statuses=None, opened_by=None,
223 offset=0, length=None, order_by=None, order_dir='desc'):
223 offset=0, length=None, order_by=None, order_dir='desc'):
224 """
224 """
225 Get all pull requests for a specific repository.
225 Get all pull requests for a specific repository.
226
226
227 :param repo_name: target or source repo
227 :param repo_name: target or source repo
228 :param source: boolean flag to specify if repo_name refers to source
228 :param source: boolean flag to specify if repo_name refers to source
229 :param statuses: list of pull request statuses
229 :param statuses: list of pull request statuses
230 :param opened_by: author user of the pull request
230 :param opened_by: author user of the pull request
231 :param offset: pagination offset
231 :param offset: pagination offset
232 :param length: length of returned list
232 :param length: length of returned list
233 :param order_by: order of the returned list
233 :param order_by: order of the returned list
234 :param order_dir: 'asc' or 'desc' ordering direction
234 :param order_dir: 'asc' or 'desc' ordering direction
235 :returns: list of pull requests
235 :returns: list of pull requests
236 """
236 """
237 q = self._prepare_get_all_query(
237 q = self._prepare_get_all_query(
238 repo_name, source=source, statuses=statuses, opened_by=opened_by,
238 repo_name, source=source, statuses=statuses, opened_by=opened_by,
239 order_by=order_by, order_dir=order_dir)
239 order_by=order_by, order_dir=order_dir)
240
240
241 if length:
241 if length:
242 pull_requests = q.limit(length).offset(offset).all()
242 pull_requests = q.limit(length).offset(offset).all()
243 else:
243 else:
244 pull_requests = q.all()
244 pull_requests = q.all()
245
245
246 return pull_requests
246 return pull_requests
247
247
248 def count_awaiting_review(self, repo_name, source=False, statuses=None,
248 def count_awaiting_review(self, repo_name, source=False, statuses=None,
249 opened_by=None):
249 opened_by=None):
250 """
250 """
251 Count the number of pull requests for a specific repository that are
251 Count the number of pull requests for a specific repository that are
252 awaiting review.
252 awaiting review.
253
253
254 :param repo_name: target or source repo
254 :param repo_name: target or source repo
255 :param source: boolean flag to specify if repo_name refers to source
255 :param source: boolean flag to specify if repo_name refers to source
256 :param statuses: list of pull request statuses
256 :param statuses: list of pull request statuses
257 :param opened_by: author user of the pull request
257 :param opened_by: author user of the pull request
258 :returns: int number of pull requests
258 :returns: int number of pull requests
259 """
259 """
260 pull_requests = self.get_awaiting_review(
260 pull_requests = self.get_awaiting_review(
261 repo_name, source=source, statuses=statuses, opened_by=opened_by)
261 repo_name, source=source, statuses=statuses, opened_by=opened_by)
262
262
263 return len(pull_requests)
263 return len(pull_requests)
264
264
265 def get_awaiting_review(self, repo_name, source=False, statuses=None,
265 def get_awaiting_review(self, repo_name, source=False, statuses=None,
266 opened_by=None, offset=0, length=None,
266 opened_by=None, offset=0, length=None,
267 order_by=None, order_dir='desc'):
267 order_by=None, order_dir='desc'):
268 """
268 """
269 Get all pull requests for a specific repository that are awaiting
269 Get all pull requests for a specific repository that are awaiting
270 review.
270 review.
271
271
272 :param repo_name: target or source repo
272 :param repo_name: target or source repo
273 :param source: boolean flag to specify if repo_name refers to source
273 :param source: boolean flag to specify if repo_name refers to source
274 :param statuses: list of pull request statuses
274 :param statuses: list of pull request statuses
275 :param opened_by: author user of the pull request
275 :param opened_by: author user of the pull request
276 :param offset: pagination offset
276 :param offset: pagination offset
277 :param length: length of returned list
277 :param length: length of returned list
278 :param order_by: order of the returned list
278 :param order_by: order of the returned list
279 :param order_dir: 'asc' or 'desc' ordering direction
279 :param order_dir: 'asc' or 'desc' ordering direction
280 :returns: list of pull requests
280 :returns: list of pull requests
281 """
281 """
282 pull_requests = self.get_all(
282 pull_requests = self.get_all(
283 repo_name, source=source, statuses=statuses, opened_by=opened_by,
283 repo_name, source=source, statuses=statuses, opened_by=opened_by,
284 order_by=order_by, order_dir=order_dir)
284 order_by=order_by, order_dir=order_dir)
285
285
286 _filtered_pull_requests = []
286 _filtered_pull_requests = []
287 for pr in pull_requests:
287 for pr in pull_requests:
288 status = pr.calculated_review_status()
288 status = pr.calculated_review_status()
289 if status in [ChangesetStatus.STATUS_NOT_REVIEWED,
289 if status in [ChangesetStatus.STATUS_NOT_REVIEWED,
290 ChangesetStatus.STATUS_UNDER_REVIEW]:
290 ChangesetStatus.STATUS_UNDER_REVIEW]:
291 _filtered_pull_requests.append(pr)
291 _filtered_pull_requests.append(pr)
292 if length:
292 if length:
293 return _filtered_pull_requests[offset:offset+length]
293 return _filtered_pull_requests[offset:offset+length]
294 else:
294 else:
295 return _filtered_pull_requests
295 return _filtered_pull_requests
296
296
297 def count_awaiting_my_review(self, repo_name, source=False, statuses=None,
297 def count_awaiting_my_review(self, repo_name, source=False, statuses=None,
298 opened_by=None, user_id=None):
298 opened_by=None, user_id=None):
299 """
299 """
300 Count the number of pull requests for a specific repository that are
300 Count the number of pull requests for a specific repository that are
301 awaiting review from a specific user.
301 awaiting review from a specific user.
302
302
303 :param repo_name: target or source repo
303 :param repo_name: target or source repo
304 :param source: boolean flag to specify if repo_name refers to source
304 :param source: boolean flag to specify if repo_name refers to source
305 :param statuses: list of pull request statuses
305 :param statuses: list of pull request statuses
306 :param opened_by: author user of the pull request
306 :param opened_by: author user of the pull request
307 :param user_id: reviewer user of the pull request
307 :param user_id: reviewer user of the pull request
308 :returns: int number of pull requests
308 :returns: int number of pull requests
309 """
309 """
310 pull_requests = self.get_awaiting_my_review(
310 pull_requests = self.get_awaiting_my_review(
311 repo_name, source=source, statuses=statuses, opened_by=opened_by,
311 repo_name, source=source, statuses=statuses, opened_by=opened_by,
312 user_id=user_id)
312 user_id=user_id)
313
313
314 return len(pull_requests)
314 return len(pull_requests)
315
315
316 def get_awaiting_my_review(self, repo_name, source=False, statuses=None,
316 def get_awaiting_my_review(self, repo_name, source=False, statuses=None,
317 opened_by=None, user_id=None, offset=0,
317 opened_by=None, user_id=None, offset=0,
318 length=None, order_by=None, order_dir='desc'):
318 length=None, order_by=None, order_dir='desc'):
319 """
319 """
320 Get all pull requests for a specific repository that are awaiting
320 Get all pull requests for a specific repository that are awaiting
321 review from a specific user.
321 review from a specific user.
322
322
323 :param repo_name: target or source repo
323 :param repo_name: target or source repo
324 :param source: boolean flag to specify if repo_name refers to source
324 :param source: boolean flag to specify if repo_name refers to source
325 :param statuses: list of pull request statuses
325 :param statuses: list of pull request statuses
326 :param opened_by: author user of the pull request
326 :param opened_by: author user of the pull request
327 :param user_id: reviewer user of the pull request
327 :param user_id: reviewer user of the pull request
328 :param offset: pagination offset
328 :param offset: pagination offset
329 :param length: length of returned list
329 :param length: length of returned list
330 :param order_by: order of the returned list
330 :param order_by: order of the returned list
331 :param order_dir: 'asc' or 'desc' ordering direction
331 :param order_dir: 'asc' or 'desc' ordering direction
332 :returns: list of pull requests
332 :returns: list of pull requests
333 """
333 """
334 pull_requests = self.get_all(
334 pull_requests = self.get_all(
335 repo_name, source=source, statuses=statuses, opened_by=opened_by,
335 repo_name, source=source, statuses=statuses, opened_by=opened_by,
336 order_by=order_by, order_dir=order_dir)
336 order_by=order_by, order_dir=order_dir)
337
337
338 _my = PullRequestModel().get_not_reviewed(user_id)
338 _my = PullRequestModel().get_not_reviewed(user_id)
339 my_participation = []
339 my_participation = []
340 for pr in pull_requests:
340 for pr in pull_requests:
341 if pr in _my:
341 if pr in _my:
342 my_participation.append(pr)
342 my_participation.append(pr)
343 _filtered_pull_requests = my_participation
343 _filtered_pull_requests = my_participation
344 if length:
344 if length:
345 return _filtered_pull_requests[offset:offset+length]
345 return _filtered_pull_requests[offset:offset+length]
346 else:
346 else:
347 return _filtered_pull_requests
347 return _filtered_pull_requests
348
348
349 def get_not_reviewed(self, user_id):
349 def get_not_reviewed(self, user_id):
350 return [
350 return [
351 x.pull_request for x in PullRequestReviewers.query().filter(
351 x.pull_request for x in PullRequestReviewers.query().filter(
352 PullRequestReviewers.user_id == user_id).all()
352 PullRequestReviewers.user_id == user_id).all()
353 ]
353 ]
354
354
355 def _prepare_participating_query(self, user_id=None, statuses=None,
355 def _prepare_participating_query(self, user_id=None, statuses=None,
356 order_by=None, order_dir='desc'):
356 order_by=None, order_dir='desc'):
357 q = PullRequest.query()
357 q = PullRequest.query()
358 if user_id:
358 if user_id:
359 reviewers_subquery = Session().query(
359 reviewers_subquery = Session().query(
360 PullRequestReviewers.pull_request_id).filter(
360 PullRequestReviewers.pull_request_id).filter(
361 PullRequestReviewers.user_id == user_id).subquery()
361 PullRequestReviewers.user_id == user_id).subquery()
362 user_filter= or_(
362 user_filter= or_(
363 PullRequest.user_id == user_id,
363 PullRequest.user_id == user_id,
364 PullRequest.pull_request_id.in_(reviewers_subquery)
364 PullRequest.pull_request_id.in_(reviewers_subquery)
365 )
365 )
366 q = PullRequest.query().filter(user_filter)
366 q = PullRequest.query().filter(user_filter)
367
367
368 # closed,opened
368 # closed,opened
369 if statuses:
369 if statuses:
370 q = q.filter(PullRequest.status.in_(statuses))
370 q = q.filter(PullRequest.status.in_(statuses))
371
371
372 if order_by:
372 if order_by:
373 order_map = {
373 order_map = {
374 'name_raw': PullRequest.pull_request_id,
374 'name_raw': PullRequest.pull_request_id,
375 'title': PullRequest.title,
375 'title': PullRequest.title,
376 'updated_on_raw': PullRequest.updated_on,
376 'updated_on_raw': PullRequest.updated_on,
377 'target_repo': PullRequest.target_repo_id
377 'target_repo': PullRequest.target_repo_id
378 }
378 }
379 if order_dir == 'asc':
379 if order_dir == 'asc':
380 q = q.order_by(order_map[order_by].asc())
380 q = q.order_by(order_map[order_by].asc())
381 else:
381 else:
382 q = q.order_by(order_map[order_by].desc())
382 q = q.order_by(order_map[order_by].desc())
383
383
384 return q
384 return q
385
385
386 def count_im_participating_in(self, user_id=None, statuses=None):
386 def count_im_participating_in(self, user_id=None, statuses=None):
387 q = self._prepare_participating_query(user_id, statuses=statuses)
387 q = self._prepare_participating_query(user_id, statuses=statuses)
388 return q.count()
388 return q.count()
389
389
390 def get_im_participating_in(
390 def get_im_participating_in(
391 self, user_id=None, statuses=None, offset=0,
391 self, user_id=None, statuses=None, offset=0,
392 length=None, order_by=None, order_dir='desc'):
392 length=None, order_by=None, order_dir='desc'):
393 """
393 """
394 Get all Pull requests that i'm participating in, or i have opened
394 Get all Pull requests that i'm participating in, or i have opened
395 """
395 """
396
396
397 q = self._prepare_participating_query(
397 q = self._prepare_participating_query(
398 user_id, statuses=statuses, order_by=order_by,
398 user_id, statuses=statuses, order_by=order_by,
399 order_dir=order_dir)
399 order_dir=order_dir)
400
400
401 if length:
401 if length:
402 pull_requests = q.limit(length).offset(offset).all()
402 pull_requests = q.limit(length).offset(offset).all()
403 else:
403 else:
404 pull_requests = q.all()
404 pull_requests = q.all()
405
405
406 return pull_requests
406 return pull_requests
407
407
408 def get_versions(self, pull_request):
408 def get_versions(self, pull_request):
409 """
409 """
410 returns version of pull request sorted by ID descending
410 returns version of pull request sorted by ID descending
411 """
411 """
412 return PullRequestVersion.query()\
412 return PullRequestVersion.query()\
413 .filter(PullRequestVersion.pull_request == pull_request)\
413 .filter(PullRequestVersion.pull_request == pull_request)\
414 .order_by(PullRequestVersion.pull_request_version_id.asc())\
414 .order_by(PullRequestVersion.pull_request_version_id.asc())\
415 .all()
415 .all()
416
416
417 def create(self, created_by, source_repo, source_ref, target_repo,
417 def create(self, created_by, source_repo, source_ref, target_repo,
418 target_ref, revisions, reviewers, title, description=None):
418 target_ref, revisions, reviewers, title, description=None):
419 created_by_user = self._get_user(created_by)
419 created_by_user = self._get_user(created_by)
420 source_repo = self._get_repo(source_repo)
420 source_repo = self._get_repo(source_repo)
421 target_repo = self._get_repo(target_repo)
421 target_repo = self._get_repo(target_repo)
422
422
423 pull_request = PullRequest()
423 pull_request = PullRequest()
424 pull_request.source_repo = source_repo
424 pull_request.source_repo = source_repo
425 pull_request.source_ref = source_ref
425 pull_request.source_ref = source_ref
426 pull_request.target_repo = target_repo
426 pull_request.target_repo = target_repo
427 pull_request.target_ref = target_ref
427 pull_request.target_ref = target_ref
428 pull_request.revisions = revisions
428 pull_request.revisions = revisions
429 pull_request.title = title
429 pull_request.title = title
430 pull_request.description = description
430 pull_request.description = description
431 pull_request.author = created_by_user
431 pull_request.author = created_by_user
432
432
433 Session().add(pull_request)
433 Session().add(pull_request)
434 Session().flush()
434 Session().flush()
435
435
436 reviewer_ids = set()
436 reviewer_ids = set()
437 # members / reviewers
437 # members / reviewers
438 for reviewer_object in reviewers:
438 for reviewer_object in reviewers:
439 if isinstance(reviewer_object, tuple):
439 if isinstance(reviewer_object, tuple):
440 user_id, reasons = reviewer_object
440 user_id, reasons = reviewer_object
441 else:
441 else:
442 user_id, reasons = reviewer_object, []
442 user_id, reasons = reviewer_object, []
443
443
444 user = self._get_user(user_id)
444 user = self._get_user(user_id)
445 reviewer_ids.add(user.user_id)
445 reviewer_ids.add(user.user_id)
446
446
447 reviewer = PullRequestReviewers(user, pull_request, reasons)
447 reviewer = PullRequestReviewers(user, pull_request, reasons)
448 Session().add(reviewer)
448 Session().add(reviewer)
449
449
450 # Set approval status to "Under Review" for all commits which are
450 # Set approval status to "Under Review" for all commits which are
451 # part of this pull request.
451 # part of this pull request.
452 ChangesetStatusModel().set_status(
452 ChangesetStatusModel().set_status(
453 repo=target_repo,
453 repo=target_repo,
454 status=ChangesetStatus.STATUS_UNDER_REVIEW,
454 status=ChangesetStatus.STATUS_UNDER_REVIEW,
455 user=created_by_user,
455 user=created_by_user,
456 pull_request=pull_request
456 pull_request=pull_request
457 )
457 )
458
458
459 self.notify_reviewers(pull_request, reviewer_ids)
459 self.notify_reviewers(pull_request, reviewer_ids)
460 self._trigger_pull_request_hook(
460 self._trigger_pull_request_hook(
461 pull_request, created_by_user, 'create')
461 pull_request, created_by_user, 'create')
462
462
463 return pull_request
463 return pull_request
464
464
465 def _trigger_pull_request_hook(self, pull_request, user, action):
465 def _trigger_pull_request_hook(self, pull_request, user, action):
466 pull_request = self.__get_pull_request(pull_request)
466 pull_request = self.__get_pull_request(pull_request)
467 target_scm = pull_request.target_repo.scm_instance()
467 target_scm = pull_request.target_repo.scm_instance()
468 if action == 'create':
468 if action == 'create':
469 trigger_hook = hooks_utils.trigger_log_create_pull_request_hook
469 trigger_hook = hooks_utils.trigger_log_create_pull_request_hook
470 elif action == 'merge':
470 elif action == 'merge':
471 trigger_hook = hooks_utils.trigger_log_merge_pull_request_hook
471 trigger_hook = hooks_utils.trigger_log_merge_pull_request_hook
472 elif action == 'close':
472 elif action == 'close':
473 trigger_hook = hooks_utils.trigger_log_close_pull_request_hook
473 trigger_hook = hooks_utils.trigger_log_close_pull_request_hook
474 elif action == 'review_status_change':
474 elif action == 'review_status_change':
475 trigger_hook = hooks_utils.trigger_log_review_pull_request_hook
475 trigger_hook = hooks_utils.trigger_log_review_pull_request_hook
476 elif action == 'update':
476 elif action == 'update':
477 trigger_hook = hooks_utils.trigger_log_update_pull_request_hook
477 trigger_hook = hooks_utils.trigger_log_update_pull_request_hook
478 else:
478 else:
479 return
479 return
480
480
481 trigger_hook(
481 trigger_hook(
482 username=user.username,
482 username=user.username,
483 repo_name=pull_request.target_repo.repo_name,
483 repo_name=pull_request.target_repo.repo_name,
484 repo_alias=target_scm.alias,
484 repo_alias=target_scm.alias,
485 pull_request=pull_request)
485 pull_request=pull_request)
486
486
487 def _get_commit_ids(self, pull_request):
487 def _get_commit_ids(self, pull_request):
488 """
488 """
489 Return the commit ids of the merged pull request.
489 Return the commit ids of the merged pull request.
490
490
491 This method is not dealing correctly yet with the lack of autoupdates
491 This method is not dealing correctly yet with the lack of autoupdates
492 nor with the implicit target updates.
492 nor with the implicit target updates.
493 For example: if a commit in the source repo is already in the target it
493 For example: if a commit in the source repo is already in the target it
494 will be reported anyways.
494 will be reported anyways.
495 """
495 """
496 merge_rev = pull_request.merge_rev
496 merge_rev = pull_request.merge_rev
497 if merge_rev is None:
497 if merge_rev is None:
498 raise ValueError('This pull request was not merged yet')
498 raise ValueError('This pull request was not merged yet')
499
499
500 commit_ids = list(pull_request.revisions)
500 commit_ids = list(pull_request.revisions)
501 if merge_rev not in commit_ids:
501 if merge_rev not in commit_ids:
502 commit_ids.append(merge_rev)
502 commit_ids.append(merge_rev)
503
503
504 return commit_ids
504 return commit_ids
505
505
506 def merge(self, pull_request, user, extras):
506 def merge(self, pull_request, user, extras):
507 log.debug("Merging pull request %s", pull_request.pull_request_id)
507 log.debug("Merging pull request %s", pull_request.pull_request_id)
508 merge_state = self._merge_pull_request(pull_request, user, extras)
508 merge_state = self._merge_pull_request(pull_request, user, extras)
509 if merge_state.executed:
509 if merge_state.executed:
510 log.debug(
510 log.debug(
511 "Merge was successful, updating the pull request comments.")
511 "Merge was successful, updating the pull request comments.")
512 self._comment_and_close_pr(pull_request, user, merge_state)
512 self._comment_and_close_pr(pull_request, user, merge_state)
513 self._log_action('user_merged_pull_request', user, pull_request)
513 self._log_action('user_merged_pull_request', user, pull_request)
514 else:
514 else:
515 log.warn("Merge failed, not updating the pull request.")
515 log.warn("Merge failed, not updating the pull request.")
516 return merge_state
516 return merge_state
517
517
518 def _merge_pull_request(self, pull_request, user, extras):
518 def _merge_pull_request(self, pull_request, user, extras):
519 target_vcs = pull_request.target_repo.scm_instance()
519 target_vcs = pull_request.target_repo.scm_instance()
520 source_vcs = pull_request.source_repo.scm_instance()
520 source_vcs = pull_request.source_repo.scm_instance()
521 target_ref = self._refresh_reference(
521 target_ref = self._refresh_reference(
522 pull_request.target_ref_parts, target_vcs)
522 pull_request.target_ref_parts, target_vcs)
523
523
524 message = _(
524 message = _(
525 'Merge pull request #%(pr_id)s from '
525 'Merge pull request #%(pr_id)s from '
526 '%(source_repo)s %(source_ref_name)s\n\n %(pr_title)s') % {
526 '%(source_repo)s %(source_ref_name)s\n\n %(pr_title)s') % {
527 'pr_id': pull_request.pull_request_id,
527 'pr_id': pull_request.pull_request_id,
528 'source_repo': source_vcs.name,
528 'source_repo': source_vcs.name,
529 'source_ref_name': pull_request.source_ref_parts.name,
529 'source_ref_name': pull_request.source_ref_parts.name,
530 'pr_title': pull_request.title
530 'pr_title': pull_request.title
531 }
531 }
532
532
533 workspace_id = self._workspace_id(pull_request)
533 workspace_id = self._workspace_id(pull_request)
534 use_rebase = self._use_rebase_for_merging(pull_request)
534 use_rebase = self._use_rebase_for_merging(pull_request)
535
535
536 callback_daemon, extras = prepare_callback_daemon(
536 callback_daemon, extras = prepare_callback_daemon(
537 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
537 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
538 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
538 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
539
539
540 with callback_daemon:
540 with callback_daemon:
541 # TODO: johbo: Implement a clean way to run a config_override
541 # TODO: johbo: Implement a clean way to run a config_override
542 # for a single call.
542 # for a single call.
543 target_vcs.config.set(
543 target_vcs.config.set(
544 'rhodecode', 'RC_SCM_DATA', json.dumps(extras))
544 'rhodecode', 'RC_SCM_DATA', json.dumps(extras))
545 merge_state = target_vcs.merge(
545 merge_state = target_vcs.merge(
546 target_ref, source_vcs, pull_request.source_ref_parts,
546 target_ref, source_vcs, pull_request.source_ref_parts,
547 workspace_id, user_name=user.username,
547 workspace_id, user_name=user.username,
548 user_email=user.email, message=message, use_rebase=use_rebase)
548 user_email=user.email, message=message, use_rebase=use_rebase)
549 return merge_state
549 return merge_state
550
550
551 def _comment_and_close_pr(self, pull_request, user, merge_state):
551 def _comment_and_close_pr(self, pull_request, user, merge_state):
552 pull_request.merge_rev = merge_state.merge_ref.commit_id
552 pull_request.merge_rev = merge_state.merge_ref.commit_id
553 pull_request.updated_on = datetime.datetime.now()
553 pull_request.updated_on = datetime.datetime.now()
554
554
555 CommentsModel().create(
555 CommentsModel().create(
556 text=unicode(_('Pull request merged and closed')),
556 text=unicode(_('Pull request merged and closed')),
557 repo=pull_request.target_repo.repo_id,
557 repo=pull_request.target_repo.repo_id,
558 user=user.user_id,
558 user=user.user_id,
559 pull_request=pull_request.pull_request_id,
559 pull_request=pull_request.pull_request_id,
560 f_path=None,
560 f_path=None,
561 line_no=None,
561 line_no=None,
562 closing_pr=True
562 closing_pr=True
563 )
563 )
564
564
565 Session().add(pull_request)
565 Session().add(pull_request)
566 Session().flush()
566 Session().flush()
567 # TODO: paris: replace invalidation with less radical solution
567 # TODO: paris: replace invalidation with less radical solution
568 ScmModel().mark_for_invalidation(
568 ScmModel().mark_for_invalidation(
569 pull_request.target_repo.repo_name)
569 pull_request.target_repo.repo_name)
570 self._trigger_pull_request_hook(pull_request, user, 'merge')
570 self._trigger_pull_request_hook(pull_request, user, 'merge')
571
571
572 def has_valid_update_type(self, pull_request):
572 def has_valid_update_type(self, pull_request):
573 source_ref_type = pull_request.source_ref_parts.type
573 source_ref_type = pull_request.source_ref_parts.type
574 return source_ref_type in ['book', 'branch', 'tag']
574 return source_ref_type in ['book', 'branch', 'tag']
575
575
576 def update_commits(self, pull_request):
576 def update_commits(self, pull_request):
577 """
577 """
578 Get the updated list of commits for the pull request
578 Get the updated list of commits for the pull request
579 and return the new pull request version and the list
579 and return the new pull request version and the list
580 of commits processed by this update action
580 of commits processed by this update action
581 """
581 """
582 pull_request = self.__get_pull_request(pull_request)
582 pull_request = self.__get_pull_request(pull_request)
583 source_ref_type = pull_request.source_ref_parts.type
583 source_ref_type = pull_request.source_ref_parts.type
584 source_ref_name = pull_request.source_ref_parts.name
584 source_ref_name = pull_request.source_ref_parts.name
585 source_ref_id = pull_request.source_ref_parts.commit_id
585 source_ref_id = pull_request.source_ref_parts.commit_id
586
586
587 if not self.has_valid_update_type(pull_request):
587 if not self.has_valid_update_type(pull_request):
588 log.debug(
588 log.debug(
589 "Skipping update of pull request %s due to ref type: %s",
589 "Skipping update of pull request %s due to ref type: %s",
590 pull_request, source_ref_type)
590 pull_request, source_ref_type)
591 return UpdateResponse(
591 return UpdateResponse(
592 executed=False,
592 executed=False,
593 reason=UpdateFailureReason.WRONG_REF_TPYE,
593 reason=UpdateFailureReason.WRONG_REF_TPYE,
594 old=pull_request, new=None, changes=None)
594 old=pull_request, new=None, changes=None)
595
595
596 source_repo = pull_request.source_repo.scm_instance()
596 source_repo = pull_request.source_repo.scm_instance()
597 try:
597 try:
598 source_commit = source_repo.get_commit(commit_id=source_ref_name)
598 source_commit = source_repo.get_commit(commit_id=source_ref_name)
599 except CommitDoesNotExistError:
599 except CommitDoesNotExistError:
600 return UpdateResponse(
600 return UpdateResponse(
601 executed=False,
601 executed=False,
602 reason=UpdateFailureReason.MISSING_SOURCE_REF,
602 reason=UpdateFailureReason.MISSING_SOURCE_REF,
603 old=pull_request, new=None, changes=None)
603 old=pull_request, new=None, changes=None)
604
604
605 if source_ref_id == source_commit.raw_id:
605 if source_ref_id == source_commit.raw_id:
606 log.debug("Nothing changed in pull request %s", pull_request)
606 log.debug("Nothing changed in pull request %s", pull_request)
607 return UpdateResponse(
607 return UpdateResponse(
608 executed=False,
608 executed=False,
609 reason=UpdateFailureReason.NO_CHANGE,
609 reason=UpdateFailureReason.NO_CHANGE,
610 old=pull_request, new=None, changes=None)
610 old=pull_request, new=None, changes=None)
611
611
612 # Finally there is a need for an update
612 # Finally there is a need for an update
613 pull_request_version = self._create_version_from_snapshot(pull_request)
613 pull_request_version = self._create_version_from_snapshot(pull_request)
614 self._link_comments_to_version(pull_request_version)
614 self._link_comments_to_version(pull_request_version)
615
615
616 target_ref_type = pull_request.target_ref_parts.type
616 target_ref_type = pull_request.target_ref_parts.type
617 target_ref_name = pull_request.target_ref_parts.name
617 target_ref_name = pull_request.target_ref_parts.name
618 target_ref_id = pull_request.target_ref_parts.commit_id
618 target_ref_id = pull_request.target_ref_parts.commit_id
619 target_repo = pull_request.target_repo.scm_instance()
619 target_repo = pull_request.target_repo.scm_instance()
620
620
621 try:
621 try:
622 if target_ref_type in ('tag', 'branch', 'book'):
622 if target_ref_type in ('tag', 'branch', 'book'):
623 target_commit = target_repo.get_commit(target_ref_name)
623 target_commit = target_repo.get_commit(target_ref_name)
624 else:
624 else:
625 target_commit = target_repo.get_commit(target_ref_id)
625 target_commit = target_repo.get_commit(target_ref_id)
626 except CommitDoesNotExistError:
626 except CommitDoesNotExistError:
627 return UpdateResponse(
627 return UpdateResponse(
628 executed=False,
628 executed=False,
629 reason=UpdateFailureReason.MISSING_TARGET_REF,
629 reason=UpdateFailureReason.MISSING_TARGET_REF,
630 old=pull_request, new=None, changes=None)
630 old=pull_request, new=None, changes=None)
631
631
632 # re-compute commit ids
632 # re-compute commit ids
633 old_commit_ids = pull_request.revisions
633 old_commit_ids = pull_request.revisions
634 pre_load = ["author", "branch", "date", "message"]
634 pre_load = ["author", "branch", "date", "message"]
635 commit_ranges = target_repo.compare(
635 commit_ranges = target_repo.compare(
636 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
636 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
637 pre_load=pre_load)
637 pre_load=pre_load)
638
638
639 ancestor = target_repo.get_common_ancestor(
639 ancestor = target_repo.get_common_ancestor(
640 target_commit.raw_id, source_commit.raw_id, source_repo)
640 target_commit.raw_id, source_commit.raw_id, source_repo)
641
641
642 pull_request.source_ref = '%s:%s:%s' % (
642 pull_request.source_ref = '%s:%s:%s' % (
643 source_ref_type, source_ref_name, source_commit.raw_id)
643 source_ref_type, source_ref_name, source_commit.raw_id)
644 pull_request.target_ref = '%s:%s:%s' % (
644 pull_request.target_ref = '%s:%s:%s' % (
645 target_ref_type, target_ref_name, ancestor)
645 target_ref_type, target_ref_name, ancestor)
646 pull_request.revisions = [
646 pull_request.revisions = [
647 commit.raw_id for commit in reversed(commit_ranges)]
647 commit.raw_id for commit in reversed(commit_ranges)]
648 pull_request.updated_on = datetime.datetime.now()
648 pull_request.updated_on = datetime.datetime.now()
649 Session().add(pull_request)
649 Session().add(pull_request)
650 new_commit_ids = pull_request.revisions
650 new_commit_ids = pull_request.revisions
651
651
652 changes = self._calculate_commit_id_changes(
652 changes = self._calculate_commit_id_changes(
653 old_commit_ids, new_commit_ids)
653 old_commit_ids, new_commit_ids)
654
654
655 old_diff_data, new_diff_data = self._generate_update_diffs(
655 old_diff_data, new_diff_data = self._generate_update_diffs(
656 pull_request, pull_request_version)
656 pull_request, pull_request_version)
657
657
658 CommentsModel().outdate_comments(
658 CommentsModel().outdate_comments(
659 pull_request, old_diff_data=old_diff_data,
659 pull_request, old_diff_data=old_diff_data,
660 new_diff_data=new_diff_data)
660 new_diff_data=new_diff_data)
661
661
662 file_changes = self._calculate_file_changes(
662 file_changes = self._calculate_file_changes(
663 old_diff_data, new_diff_data)
663 old_diff_data, new_diff_data)
664
664
665 # Add an automatic comment to the pull request
665 # Add an automatic comment to the pull request
666 update_comment = CommentsModel().create(
666 update_comment = CommentsModel().create(
667 text=self._render_update_message(changes, file_changes),
667 text=self._render_update_message(changes, file_changes),
668 repo=pull_request.target_repo,
668 repo=pull_request.target_repo,
669 user=pull_request.author,
669 user=pull_request.author,
670 pull_request=pull_request,
670 pull_request=pull_request,
671 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
671 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
672
672
673 # Update status to "Under Review" for added commits
673 # Update status to "Under Review" for added commits
674 for commit_id in changes.added:
674 for commit_id in changes.added:
675 ChangesetStatusModel().set_status(
675 ChangesetStatusModel().set_status(
676 repo=pull_request.source_repo,
676 repo=pull_request.source_repo,
677 status=ChangesetStatus.STATUS_UNDER_REVIEW,
677 status=ChangesetStatus.STATUS_UNDER_REVIEW,
678 comment=update_comment,
678 comment=update_comment,
679 user=pull_request.author,
679 user=pull_request.author,
680 pull_request=pull_request,
680 pull_request=pull_request,
681 revision=commit_id)
681 revision=commit_id)
682
682
683 log.debug(
683 log.debug(
684 'Updated pull request %s, added_ids: %s, common_ids: %s, '
684 'Updated pull request %s, added_ids: %s, common_ids: %s, '
685 'removed_ids: %s', pull_request.pull_request_id,
685 'removed_ids: %s', pull_request.pull_request_id,
686 changes.added, changes.common, changes.removed)
686 changes.added, changes.common, changes.removed)
687 log.debug('Updated pull request with the following file changes: %s',
687 log.debug('Updated pull request with the following file changes: %s',
688 file_changes)
688 file_changes)
689
689
690 log.info(
690 log.info(
691 "Updated pull request %s from commit %s to commit %s, "
691 "Updated pull request %s from commit %s to commit %s, "
692 "stored new version %s of this pull request.",
692 "stored new version %s of this pull request.",
693 pull_request.pull_request_id, source_ref_id,
693 pull_request.pull_request_id, source_ref_id,
694 pull_request.source_ref_parts.commit_id,
694 pull_request.source_ref_parts.commit_id,
695 pull_request_version.pull_request_version_id)
695 pull_request_version.pull_request_version_id)
696 Session().commit()
696 Session().commit()
697 self._trigger_pull_request_hook(pull_request, pull_request.author,
697 self._trigger_pull_request_hook(pull_request, pull_request.author,
698 'update')
698 'update')
699
699
700 return UpdateResponse(
700 return UpdateResponse(
701 executed=True, reason=UpdateFailureReason.NONE,
701 executed=True, reason=UpdateFailureReason.NONE,
702 old=pull_request, new=pull_request_version, changes=changes)
702 old=pull_request, new=pull_request_version, changes=changes)
703
703
704 def _create_version_from_snapshot(self, pull_request):
704 def _create_version_from_snapshot(self, pull_request):
705 version = PullRequestVersion()
705 version = PullRequestVersion()
706 version.title = pull_request.title
706 version.title = pull_request.title
707 version.description = pull_request.description
707 version.description = pull_request.description
708 version.status = pull_request.status
708 version.status = pull_request.status
709 version.created_on = datetime.datetime.now()
709 version.created_on = datetime.datetime.now()
710 version.updated_on = pull_request.updated_on
710 version.updated_on = pull_request.updated_on
711 version.user_id = pull_request.user_id
711 version.user_id = pull_request.user_id
712 version.source_repo = pull_request.source_repo
712 version.source_repo = pull_request.source_repo
713 version.source_ref = pull_request.source_ref
713 version.source_ref = pull_request.source_ref
714 version.target_repo = pull_request.target_repo
714 version.target_repo = pull_request.target_repo
715 version.target_ref = pull_request.target_ref
715 version.target_ref = pull_request.target_ref
716
716
717 version._last_merge_source_rev = pull_request._last_merge_source_rev
717 version._last_merge_source_rev = pull_request._last_merge_source_rev
718 version._last_merge_target_rev = pull_request._last_merge_target_rev
718 version._last_merge_target_rev = pull_request._last_merge_target_rev
719 version._last_merge_status = pull_request._last_merge_status
719 version._last_merge_status = pull_request._last_merge_status
720 version.shadow_merge_ref = pull_request.shadow_merge_ref
720 version.shadow_merge_ref = pull_request.shadow_merge_ref
721 version.merge_rev = pull_request.merge_rev
721 version.merge_rev = pull_request.merge_rev
722
722
723 version.revisions = pull_request.revisions
723 version.revisions = pull_request.revisions
724 version.pull_request = pull_request
724 version.pull_request = pull_request
725 Session().add(version)
725 Session().add(version)
726 Session().flush()
726 Session().flush()
727
727
728 return version
728 return version
729
729
730 def _generate_update_diffs(self, pull_request, pull_request_version):
730 def _generate_update_diffs(self, pull_request, pull_request_version):
731
731
732 diff_context = (
732 diff_context = (
733 self.DIFF_CONTEXT +
733 self.DIFF_CONTEXT +
734 CommentsModel.needed_extra_diff_context())
734 CommentsModel.needed_extra_diff_context())
735
735
736 source_repo = pull_request_version.source_repo
736 source_repo = pull_request_version.source_repo
737 source_ref_id = pull_request_version.source_ref_parts.commit_id
737 source_ref_id = pull_request_version.source_ref_parts.commit_id
738 target_ref_id = pull_request_version.target_ref_parts.commit_id
738 target_ref_id = pull_request_version.target_ref_parts.commit_id
739 old_diff = self._get_diff_from_pr_or_version(
739 old_diff = self._get_diff_from_pr_or_version(
740 source_repo, source_ref_id, target_ref_id, context=diff_context)
740 source_repo, source_ref_id, target_ref_id, context=diff_context)
741
741
742 source_repo = pull_request.source_repo
742 source_repo = pull_request.source_repo
743 source_ref_id = pull_request.source_ref_parts.commit_id
743 source_ref_id = pull_request.source_ref_parts.commit_id
744 target_ref_id = pull_request.target_ref_parts.commit_id
744 target_ref_id = pull_request.target_ref_parts.commit_id
745
745
746 new_diff = self._get_diff_from_pr_or_version(
746 new_diff = self._get_diff_from_pr_or_version(
747 source_repo, source_ref_id, target_ref_id, context=diff_context)
747 source_repo, source_ref_id, target_ref_id, context=diff_context)
748
748
749 old_diff_data = diffs.DiffProcessor(old_diff)
749 old_diff_data = diffs.DiffProcessor(old_diff)
750 old_diff_data.prepare()
750 old_diff_data.prepare()
751 new_diff_data = diffs.DiffProcessor(new_diff)
751 new_diff_data = diffs.DiffProcessor(new_diff)
752 new_diff_data.prepare()
752 new_diff_data.prepare()
753
753
754 return old_diff_data, new_diff_data
754 return old_diff_data, new_diff_data
755
755
756 def _link_comments_to_version(self, pull_request_version):
756 def _link_comments_to_version(self, pull_request_version):
757 """
757 """
758 Link all unlinked comments of this pull request to the given version.
758 Link all unlinked comments of this pull request to the given version.
759
759
760 :param pull_request_version: The `PullRequestVersion` to which
760 :param pull_request_version: The `PullRequestVersion` to which
761 the comments shall be linked.
761 the comments shall be linked.
762
762
763 """
763 """
764 pull_request = pull_request_version.pull_request
764 pull_request = pull_request_version.pull_request
765 comments = ChangesetComment.query().filter(
765 comments = ChangesetComment.query().filter(
766 # TODO: johbo: Should we query for the repo at all here?
766 # TODO: johbo: Should we query for the repo at all here?
767 # Pending decision on how comments of PRs are to be related
767 # Pending decision on how comments of PRs are to be related
768 # to either the source repo, the target repo or no repo at all.
768 # to either the source repo, the target repo or no repo at all.
769 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
769 ChangesetComment.repo_id == pull_request.target_repo.repo_id,
770 ChangesetComment.pull_request == pull_request,
770 ChangesetComment.pull_request == pull_request,
771 ChangesetComment.pull_request_version == None)
771 ChangesetComment.pull_request_version == None)
772
772
773 # TODO: johbo: Find out why this breaks if it is done in a bulk
773 # TODO: johbo: Find out why this breaks if it is done in a bulk
774 # operation.
774 # operation.
775 for comment in comments:
775 for comment in comments:
776 comment.pull_request_version_id = (
776 comment.pull_request_version_id = (
777 pull_request_version.pull_request_version_id)
777 pull_request_version.pull_request_version_id)
778 Session().add(comment)
778 Session().add(comment)
779
779
780 def _calculate_commit_id_changes(self, old_ids, new_ids):
780 def _calculate_commit_id_changes(self, old_ids, new_ids):
781 added = [x for x in new_ids if x not in old_ids]
781 added = [x for x in new_ids if x not in old_ids]
782 common = [x for x in new_ids if x in old_ids]
782 common = [x for x in new_ids if x in old_ids]
783 removed = [x for x in old_ids if x not in new_ids]
783 removed = [x for x in old_ids if x not in new_ids]
784 total = new_ids
784 total = new_ids
785 return ChangeTuple(added, common, removed, total)
785 return ChangeTuple(added, common, removed, total)
786
786
787 def _calculate_file_changes(self, old_diff_data, new_diff_data):
787 def _calculate_file_changes(self, old_diff_data, new_diff_data):
788
788
789 old_files = OrderedDict()
789 old_files = OrderedDict()
790 for diff_data in old_diff_data.parsed_diff:
790 for diff_data in old_diff_data.parsed_diff:
791 old_files[diff_data['filename']] = md5_safe(diff_data['raw_diff'])
791 old_files[diff_data['filename']] = md5_safe(diff_data['raw_diff'])
792
792
793 added_files = []
793 added_files = []
794 modified_files = []
794 modified_files = []
795 removed_files = []
795 removed_files = []
796 for diff_data in new_diff_data.parsed_diff:
796 for diff_data in new_diff_data.parsed_diff:
797 new_filename = diff_data['filename']
797 new_filename = diff_data['filename']
798 new_hash = md5_safe(diff_data['raw_diff'])
798 new_hash = md5_safe(diff_data['raw_diff'])
799
799
800 old_hash = old_files.get(new_filename)
800 old_hash = old_files.get(new_filename)
801 if not old_hash:
801 if not old_hash:
802 # file is not present in old diff, means it's added
802 # file is not present in old diff, means it's added
803 added_files.append(new_filename)
803 added_files.append(new_filename)
804 else:
804 else:
805 if new_hash != old_hash:
805 if new_hash != old_hash:
806 modified_files.append(new_filename)
806 modified_files.append(new_filename)
807 # now remove a file from old, since we have seen it already
807 # now remove a file from old, since we have seen it already
808 del old_files[new_filename]
808 del old_files[new_filename]
809
809
810 # removed files is when there are present in old, but not in NEW,
810 # removed files is when there are present in old, but not in NEW,
811 # since we remove old files that are present in new diff, left-overs
811 # since we remove old files that are present in new diff, left-overs
812 # if any should be the removed files
812 # if any should be the removed files
813 removed_files.extend(old_files.keys())
813 removed_files.extend(old_files.keys())
814
814
815 return FileChangeTuple(added_files, modified_files, removed_files)
815 return FileChangeTuple(added_files, modified_files, removed_files)
816
816
817 def _render_update_message(self, changes, file_changes):
817 def _render_update_message(self, changes, file_changes):
818 """
818 """
819 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
819 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
820 so it's always looking the same disregarding on which default
820 so it's always looking the same disregarding on which default
821 renderer system is using.
821 renderer system is using.
822
822
823 :param changes: changes named tuple
823 :param changes: changes named tuple
824 :param file_changes: file changes named tuple
824 :param file_changes: file changes named tuple
825
825
826 """
826 """
827 new_status = ChangesetStatus.get_status_lbl(
827 new_status = ChangesetStatus.get_status_lbl(
828 ChangesetStatus.STATUS_UNDER_REVIEW)
828 ChangesetStatus.STATUS_UNDER_REVIEW)
829
829
830 changed_files = (
830 changed_files = (
831 file_changes.added + file_changes.modified + file_changes.removed)
831 file_changes.added + file_changes.modified + file_changes.removed)
832
832
833 params = {
833 params = {
834 'under_review_label': new_status,
834 'under_review_label': new_status,
835 'added_commits': changes.added,
835 'added_commits': changes.added,
836 'removed_commits': changes.removed,
836 'removed_commits': changes.removed,
837 'changed_files': changed_files,
837 'changed_files': changed_files,
838 'added_files': file_changes.added,
838 'added_files': file_changes.added,
839 'modified_files': file_changes.modified,
839 'modified_files': file_changes.modified,
840 'removed_files': file_changes.removed,
840 'removed_files': file_changes.removed,
841 }
841 }
842 renderer = RstTemplateRenderer()
842 renderer = RstTemplateRenderer()
843 return renderer.render('pull_request_update.mako', **params)
843 return renderer.render('pull_request_update.mako', **params)
844
844
845 def edit(self, pull_request, title, description):
845 def edit(self, pull_request, title, description):
846 pull_request = self.__get_pull_request(pull_request)
846 pull_request = self.__get_pull_request(pull_request)
847 if pull_request.is_closed():
847 if pull_request.is_closed():
848 raise ValueError('This pull request is closed')
848 raise ValueError('This pull request is closed')
849 if title:
849 if title:
850 pull_request.title = title
850 pull_request.title = title
851 pull_request.description = description
851 pull_request.description = description
852 pull_request.updated_on = datetime.datetime.now()
852 pull_request.updated_on = datetime.datetime.now()
853 Session().add(pull_request)
853 Session().add(pull_request)
854
854
855 def update_reviewers(self, pull_request, reviewer_data):
855 def update_reviewers(self, pull_request, reviewer_data):
856 """
856 """
857 Update the reviewers in the pull request
857 Update the reviewers in the pull request
858
858
859 :param pull_request: the pr to update
859 :param pull_request: the pr to update
860 :param reviewer_data: list of tuples [(user, ['reason1', 'reason2'])]
860 :param reviewer_data: list of tuples [(user, ['reason1', 'reason2'])]
861 """
861 """
862
862
863 reviewers_reasons = {}
863 reviewers_reasons = {}
864 for user_id, reasons in reviewer_data:
864 for user_id, reasons in reviewer_data:
865 if isinstance(user_id, (int, basestring)):
865 if isinstance(user_id, (int, basestring)):
866 user_id = self._get_user(user_id).user_id
866 user_id = self._get_user(user_id).user_id
867 reviewers_reasons[user_id] = reasons
867 reviewers_reasons[user_id] = reasons
868
868
869 reviewers_ids = set(reviewers_reasons.keys())
869 reviewers_ids = set(reviewers_reasons.keys())
870 pull_request = self.__get_pull_request(pull_request)
870 pull_request = self.__get_pull_request(pull_request)
871 current_reviewers = PullRequestReviewers.query()\
871 current_reviewers = PullRequestReviewers.query()\
872 .filter(PullRequestReviewers.pull_request ==
872 .filter(PullRequestReviewers.pull_request ==
873 pull_request).all()
873 pull_request).all()
874 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
874 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
875
875
876 ids_to_add = reviewers_ids.difference(current_reviewers_ids)
876 ids_to_add = reviewers_ids.difference(current_reviewers_ids)
877 ids_to_remove = current_reviewers_ids.difference(reviewers_ids)
877 ids_to_remove = current_reviewers_ids.difference(reviewers_ids)
878
878
879 log.debug("Adding %s reviewers", ids_to_add)
879 log.debug("Adding %s reviewers", ids_to_add)
880 log.debug("Removing %s reviewers", ids_to_remove)
880 log.debug("Removing %s reviewers", ids_to_remove)
881 changed = False
881 changed = False
882 for uid in ids_to_add:
882 for uid in ids_to_add:
883 changed = True
883 changed = True
884 _usr = self._get_user(uid)
884 _usr = self._get_user(uid)
885 reasons = reviewers_reasons[uid]
885 reasons = reviewers_reasons[uid]
886 reviewer = PullRequestReviewers(_usr, pull_request, reasons)
886 reviewer = PullRequestReviewers(_usr, pull_request, reasons)
887 Session().add(reviewer)
887 Session().add(reviewer)
888
888
889 self.notify_reviewers(pull_request, ids_to_add)
890
891 for uid in ids_to_remove:
889 for uid in ids_to_remove:
892 changed = True
890 changed = True
893 reviewer = PullRequestReviewers.query()\
891 reviewers = PullRequestReviewers.query()\
894 .filter(PullRequestReviewers.user_id == uid,
892 .filter(PullRequestReviewers.user_id == uid,
895 PullRequestReviewers.pull_request == pull_request)\
893 PullRequestReviewers.pull_request == pull_request)\
896 .scalar()
894 .all()
897 if reviewer:
895 # use .all() in case we accidentally added the same person twice
898 Session().delete(reviewer)
896 # this CAN happen due to the lack of DB checks
897 for obj in reviewers:
898 Session().delete(obj)
899
899 if changed:
900 if changed:
900 pull_request.updated_on = datetime.datetime.now()
901 pull_request.updated_on = datetime.datetime.now()
901 Session().add(pull_request)
902 Session().add(pull_request)
902
903
904 self.notify_reviewers(pull_request, ids_to_add)
903 return ids_to_add, ids_to_remove
905 return ids_to_add, ids_to_remove
904
906
905 def get_url(self, pull_request):
907 def get_url(self, pull_request):
906 return h.url('pullrequest_show',
908 return h.url('pullrequest_show',
907 repo_name=safe_str(pull_request.target_repo.repo_name),
909 repo_name=safe_str(pull_request.target_repo.repo_name),
908 pull_request_id=pull_request.pull_request_id,
910 pull_request_id=pull_request.pull_request_id,
909 qualified=True)
911 qualified=True)
910
912
911 def get_shadow_clone_url(self, pull_request):
913 def get_shadow_clone_url(self, pull_request):
912 """
914 """
913 Returns qualified url pointing to the shadow repository. If this pull
915 Returns qualified url pointing to the shadow repository. If this pull
914 request is closed there is no shadow repository and ``None`` will be
916 request is closed there is no shadow repository and ``None`` will be
915 returned.
917 returned.
916 """
918 """
917 if pull_request.is_closed():
919 if pull_request.is_closed():
918 return None
920 return None
919 else:
921 else:
920 pr_url = urllib.unquote(self.get_url(pull_request))
922 pr_url = urllib.unquote(self.get_url(pull_request))
921 return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url))
923 return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url))
922
924
923 def notify_reviewers(self, pull_request, reviewers_ids):
925 def notify_reviewers(self, pull_request, reviewers_ids):
924 # notification to reviewers
926 # notification to reviewers
925 if not reviewers_ids:
927 if not reviewers_ids:
926 return
928 return
927
929
928 pull_request_obj = pull_request
930 pull_request_obj = pull_request
929 # get the current participants of this pull request
931 # get the current participants of this pull request
930 recipients = reviewers_ids
932 recipients = reviewers_ids
931 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST
933 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST
932
934
933 pr_source_repo = pull_request_obj.source_repo
935 pr_source_repo = pull_request_obj.source_repo
934 pr_target_repo = pull_request_obj.target_repo
936 pr_target_repo = pull_request_obj.target_repo
935
937
936 pr_url = h.url(
938 pr_url = h.url(
937 'pullrequest_show',
939 'pullrequest_show',
938 repo_name=pr_target_repo.repo_name,
940 repo_name=pr_target_repo.repo_name,
939 pull_request_id=pull_request_obj.pull_request_id,
941 pull_request_id=pull_request_obj.pull_request_id,
940 qualified=True,)
942 qualified=True,)
941
943
942 # set some variables for email notification
944 # set some variables for email notification
943 pr_target_repo_url = h.url(
945 pr_target_repo_url = h.url(
944 'summary_home',
946 'summary_home',
945 repo_name=pr_target_repo.repo_name,
947 repo_name=pr_target_repo.repo_name,
946 qualified=True)
948 qualified=True)
947
949
948 pr_source_repo_url = h.url(
950 pr_source_repo_url = h.url(
949 'summary_home',
951 'summary_home',
950 repo_name=pr_source_repo.repo_name,
952 repo_name=pr_source_repo.repo_name,
951 qualified=True)
953 qualified=True)
952
954
953 # pull request specifics
955 # pull request specifics
954 pull_request_commits = [
956 pull_request_commits = [
955 (x.raw_id, x.message)
957 (x.raw_id, x.message)
956 for x in map(pr_source_repo.get_commit, pull_request.revisions)]
958 for x in map(pr_source_repo.get_commit, pull_request.revisions)]
957
959
958 kwargs = {
960 kwargs = {
959 'user': pull_request.author,
961 'user': pull_request.author,
960 'pull_request': pull_request_obj,
962 'pull_request': pull_request_obj,
961 'pull_request_commits': pull_request_commits,
963 'pull_request_commits': pull_request_commits,
962
964
963 'pull_request_target_repo': pr_target_repo,
965 'pull_request_target_repo': pr_target_repo,
964 'pull_request_target_repo_url': pr_target_repo_url,
966 'pull_request_target_repo_url': pr_target_repo_url,
965
967
966 'pull_request_source_repo': pr_source_repo,
968 'pull_request_source_repo': pr_source_repo,
967 'pull_request_source_repo_url': pr_source_repo_url,
969 'pull_request_source_repo_url': pr_source_repo_url,
968
970
969 'pull_request_url': pr_url,
971 'pull_request_url': pr_url,
970 }
972 }
971
973
972 # pre-generate the subject for notification itself
974 # pre-generate the subject for notification itself
973 (subject,
975 (subject,
974 _h, _e, # we don't care about those
976 _h, _e, # we don't care about those
975 body_plaintext) = EmailNotificationModel().render_email(
977 body_plaintext) = EmailNotificationModel().render_email(
976 notification_type, **kwargs)
978 notification_type, **kwargs)
977
979
978 # create notification objects, and emails
980 # create notification objects, and emails
979 NotificationModel().create(
981 NotificationModel().create(
980 created_by=pull_request.author,
982 created_by=pull_request.author,
981 notification_subject=subject,
983 notification_subject=subject,
982 notification_body=body_plaintext,
984 notification_body=body_plaintext,
983 notification_type=notification_type,
985 notification_type=notification_type,
984 recipients=recipients,
986 recipients=recipients,
985 email_kwargs=kwargs,
987 email_kwargs=kwargs,
986 )
988 )
987
989
988 def delete(self, pull_request):
990 def delete(self, pull_request):
989 pull_request = self.__get_pull_request(pull_request)
991 pull_request = self.__get_pull_request(pull_request)
990 self._cleanup_merge_workspace(pull_request)
992 self._cleanup_merge_workspace(pull_request)
991 Session().delete(pull_request)
993 Session().delete(pull_request)
992
994
993 def close_pull_request(self, pull_request, user):
995 def close_pull_request(self, pull_request, user):
994 pull_request = self.__get_pull_request(pull_request)
996 pull_request = self.__get_pull_request(pull_request)
995 self._cleanup_merge_workspace(pull_request)
997 self._cleanup_merge_workspace(pull_request)
996 pull_request.status = PullRequest.STATUS_CLOSED
998 pull_request.status = PullRequest.STATUS_CLOSED
997 pull_request.updated_on = datetime.datetime.now()
999 pull_request.updated_on = datetime.datetime.now()
998 Session().add(pull_request)
1000 Session().add(pull_request)
999 self._trigger_pull_request_hook(
1001 self._trigger_pull_request_hook(
1000 pull_request, pull_request.author, 'close')
1002 pull_request, pull_request.author, 'close')
1001 self._log_action('user_closed_pull_request', user, pull_request)
1003 self._log_action('user_closed_pull_request', user, pull_request)
1002
1004
1003 def close_pull_request_with_comment(self, pull_request, user, repo,
1005 def close_pull_request_with_comment(self, pull_request, user, repo,
1004 message=None):
1006 message=None):
1005 status = ChangesetStatus.STATUS_REJECTED
1007 status = ChangesetStatus.STATUS_REJECTED
1006
1008
1007 if not message:
1009 if not message:
1008 message = (
1010 message = (
1009 _('Status change %(transition_icon)s %(status)s') % {
1011 _('Status change %(transition_icon)s %(status)s') % {
1010 'transition_icon': '>',
1012 'transition_icon': '>',
1011 'status': ChangesetStatus.get_status_lbl(status)})
1013 'status': ChangesetStatus.get_status_lbl(status)})
1012
1014
1013 internal_message = _('Closing with') + ' ' + message
1015 internal_message = _('Closing with') + ' ' + message
1014
1016
1015 comm = CommentsModel().create(
1017 comm = CommentsModel().create(
1016 text=internal_message,
1018 text=internal_message,
1017 repo=repo.repo_id,
1019 repo=repo.repo_id,
1018 user=user.user_id,
1020 user=user.user_id,
1019 pull_request=pull_request.pull_request_id,
1021 pull_request=pull_request.pull_request_id,
1020 f_path=None,
1022 f_path=None,
1021 line_no=None,
1023 line_no=None,
1022 status_change=ChangesetStatus.get_status_lbl(status),
1024 status_change=ChangesetStatus.get_status_lbl(status),
1023 status_change_type=status,
1025 status_change_type=status,
1024 closing_pr=True
1026 closing_pr=True
1025 )
1027 )
1026
1028
1027 ChangesetStatusModel().set_status(
1029 ChangesetStatusModel().set_status(
1028 repo.repo_id,
1030 repo.repo_id,
1029 status,
1031 status,
1030 user.user_id,
1032 user.user_id,
1031 comm,
1033 comm,
1032 pull_request=pull_request.pull_request_id
1034 pull_request=pull_request.pull_request_id
1033 )
1035 )
1034 Session().flush()
1036 Session().flush()
1035
1037
1036 PullRequestModel().close_pull_request(
1038 PullRequestModel().close_pull_request(
1037 pull_request.pull_request_id, user)
1039 pull_request.pull_request_id, user)
1038
1040
1039 def merge_status(self, pull_request):
1041 def merge_status(self, pull_request):
1040 if not self._is_merge_enabled(pull_request):
1042 if not self._is_merge_enabled(pull_request):
1041 return False, _('Server-side pull request merging is disabled.')
1043 return False, _('Server-side pull request merging is disabled.')
1042 if pull_request.is_closed():
1044 if pull_request.is_closed():
1043 return False, _('This pull request is closed.')
1045 return False, _('This pull request is closed.')
1044 merge_possible, msg = self._check_repo_requirements(
1046 merge_possible, msg = self._check_repo_requirements(
1045 target=pull_request.target_repo, source=pull_request.source_repo)
1047 target=pull_request.target_repo, source=pull_request.source_repo)
1046 if not merge_possible:
1048 if not merge_possible:
1047 return merge_possible, msg
1049 return merge_possible, msg
1048
1050
1049 try:
1051 try:
1050 resp = self._try_merge(pull_request)
1052 resp = self._try_merge(pull_request)
1051 log.debug("Merge response: %s", resp)
1053 log.debug("Merge response: %s", resp)
1052 status = resp.possible, self.merge_status_message(
1054 status = resp.possible, self.merge_status_message(
1053 resp.failure_reason)
1055 resp.failure_reason)
1054 except NotImplementedError:
1056 except NotImplementedError:
1055 status = False, _('Pull request merging is not supported.')
1057 status = False, _('Pull request merging is not supported.')
1056
1058
1057 return status
1059 return status
1058
1060
1059 def _check_repo_requirements(self, target, source):
1061 def _check_repo_requirements(self, target, source):
1060 """
1062 """
1061 Check if `target` and `source` have compatible requirements.
1063 Check if `target` and `source` have compatible requirements.
1062
1064
1063 Currently this is just checking for largefiles.
1065 Currently this is just checking for largefiles.
1064 """
1066 """
1065 target_has_largefiles = self._has_largefiles(target)
1067 target_has_largefiles = self._has_largefiles(target)
1066 source_has_largefiles = self._has_largefiles(source)
1068 source_has_largefiles = self._has_largefiles(source)
1067 merge_possible = True
1069 merge_possible = True
1068 message = u''
1070 message = u''
1069
1071
1070 if target_has_largefiles != source_has_largefiles:
1072 if target_has_largefiles != source_has_largefiles:
1071 merge_possible = False
1073 merge_possible = False
1072 if source_has_largefiles:
1074 if source_has_largefiles:
1073 message = _(
1075 message = _(
1074 'Target repository large files support is disabled.')
1076 'Target repository large files support is disabled.')
1075 else:
1077 else:
1076 message = _(
1078 message = _(
1077 'Source repository large files support is disabled.')
1079 'Source repository large files support is disabled.')
1078
1080
1079 return merge_possible, message
1081 return merge_possible, message
1080
1082
1081 def _has_largefiles(self, repo):
1083 def _has_largefiles(self, repo):
1082 largefiles_ui = VcsSettingsModel(repo=repo).get_ui_settings(
1084 largefiles_ui = VcsSettingsModel(repo=repo).get_ui_settings(
1083 'extensions', 'largefiles')
1085 'extensions', 'largefiles')
1084 return largefiles_ui and largefiles_ui[0].active
1086 return largefiles_ui and largefiles_ui[0].active
1085
1087
1086 def _try_merge(self, pull_request):
1088 def _try_merge(self, pull_request):
1087 """
1089 """
1088 Try to merge the pull request and return the merge status.
1090 Try to merge the pull request and return the merge status.
1089 """
1091 """
1090 log.debug(
1092 log.debug(
1091 "Trying out if the pull request %s can be merged.",
1093 "Trying out if the pull request %s can be merged.",
1092 pull_request.pull_request_id)
1094 pull_request.pull_request_id)
1093 target_vcs = pull_request.target_repo.scm_instance()
1095 target_vcs = pull_request.target_repo.scm_instance()
1094
1096
1095 # Refresh the target reference.
1097 # Refresh the target reference.
1096 try:
1098 try:
1097 target_ref = self._refresh_reference(
1099 target_ref = self._refresh_reference(
1098 pull_request.target_ref_parts, target_vcs)
1100 pull_request.target_ref_parts, target_vcs)
1099 except CommitDoesNotExistError:
1101 except CommitDoesNotExistError:
1100 merge_state = MergeResponse(
1102 merge_state = MergeResponse(
1101 False, False, None, MergeFailureReason.MISSING_TARGET_REF)
1103 False, False, None, MergeFailureReason.MISSING_TARGET_REF)
1102 return merge_state
1104 return merge_state
1103
1105
1104 target_locked = pull_request.target_repo.locked
1106 target_locked = pull_request.target_repo.locked
1105 if target_locked and target_locked[0]:
1107 if target_locked and target_locked[0]:
1106 log.debug("The target repository is locked.")
1108 log.debug("The target repository is locked.")
1107 merge_state = MergeResponse(
1109 merge_state = MergeResponse(
1108 False, False, None, MergeFailureReason.TARGET_IS_LOCKED)
1110 False, False, None, MergeFailureReason.TARGET_IS_LOCKED)
1109 elif self._needs_merge_state_refresh(pull_request, target_ref):
1111 elif self._needs_merge_state_refresh(pull_request, target_ref):
1110 log.debug("Refreshing the merge status of the repository.")
1112 log.debug("Refreshing the merge status of the repository.")
1111 merge_state = self._refresh_merge_state(
1113 merge_state = self._refresh_merge_state(
1112 pull_request, target_vcs, target_ref)
1114 pull_request, target_vcs, target_ref)
1113 else:
1115 else:
1114 possible = pull_request.\
1116 possible = pull_request.\
1115 _last_merge_status == MergeFailureReason.NONE
1117 _last_merge_status == MergeFailureReason.NONE
1116 merge_state = MergeResponse(
1118 merge_state = MergeResponse(
1117 possible, False, None, pull_request._last_merge_status)
1119 possible, False, None, pull_request._last_merge_status)
1118
1120
1119 return merge_state
1121 return merge_state
1120
1122
1121 def _refresh_reference(self, reference, vcs_repository):
1123 def _refresh_reference(self, reference, vcs_repository):
1122 if reference.type in ('branch', 'book'):
1124 if reference.type in ('branch', 'book'):
1123 name_or_id = reference.name
1125 name_or_id = reference.name
1124 else:
1126 else:
1125 name_or_id = reference.commit_id
1127 name_or_id = reference.commit_id
1126 refreshed_commit = vcs_repository.get_commit(name_or_id)
1128 refreshed_commit = vcs_repository.get_commit(name_or_id)
1127 refreshed_reference = Reference(
1129 refreshed_reference = Reference(
1128 reference.type, reference.name, refreshed_commit.raw_id)
1130 reference.type, reference.name, refreshed_commit.raw_id)
1129 return refreshed_reference
1131 return refreshed_reference
1130
1132
1131 def _needs_merge_state_refresh(self, pull_request, target_reference):
1133 def _needs_merge_state_refresh(self, pull_request, target_reference):
1132 return not(
1134 return not(
1133 pull_request.revisions and
1135 pull_request.revisions and
1134 pull_request.revisions[0] == pull_request._last_merge_source_rev and
1136 pull_request.revisions[0] == pull_request._last_merge_source_rev and
1135 target_reference.commit_id == pull_request._last_merge_target_rev)
1137 target_reference.commit_id == pull_request._last_merge_target_rev)
1136
1138
1137 def _refresh_merge_state(self, pull_request, target_vcs, target_reference):
1139 def _refresh_merge_state(self, pull_request, target_vcs, target_reference):
1138 workspace_id = self._workspace_id(pull_request)
1140 workspace_id = self._workspace_id(pull_request)
1139 source_vcs = pull_request.source_repo.scm_instance()
1141 source_vcs = pull_request.source_repo.scm_instance()
1140 use_rebase = self._use_rebase_for_merging(pull_request)
1142 use_rebase = self._use_rebase_for_merging(pull_request)
1141 merge_state = target_vcs.merge(
1143 merge_state = target_vcs.merge(
1142 target_reference, source_vcs, pull_request.source_ref_parts,
1144 target_reference, source_vcs, pull_request.source_ref_parts,
1143 workspace_id, dry_run=True, use_rebase=use_rebase)
1145 workspace_id, dry_run=True, use_rebase=use_rebase)
1144
1146
1145 # Do not store the response if there was an unknown error.
1147 # Do not store the response if there was an unknown error.
1146 if merge_state.failure_reason != MergeFailureReason.UNKNOWN:
1148 if merge_state.failure_reason != MergeFailureReason.UNKNOWN:
1147 pull_request._last_merge_source_rev = \
1149 pull_request._last_merge_source_rev = \
1148 pull_request.source_ref_parts.commit_id
1150 pull_request.source_ref_parts.commit_id
1149 pull_request._last_merge_target_rev = target_reference.commit_id
1151 pull_request._last_merge_target_rev = target_reference.commit_id
1150 pull_request._last_merge_status = merge_state.failure_reason
1152 pull_request._last_merge_status = merge_state.failure_reason
1151 pull_request.shadow_merge_ref = merge_state.merge_ref
1153 pull_request.shadow_merge_ref = merge_state.merge_ref
1152 Session().add(pull_request)
1154 Session().add(pull_request)
1153 Session().commit()
1155 Session().commit()
1154
1156
1155 return merge_state
1157 return merge_state
1156
1158
1157 def _workspace_id(self, pull_request):
1159 def _workspace_id(self, pull_request):
1158 workspace_id = 'pr-%s' % pull_request.pull_request_id
1160 workspace_id = 'pr-%s' % pull_request.pull_request_id
1159 return workspace_id
1161 return workspace_id
1160
1162
1161 def merge_status_message(self, status_code):
1163 def merge_status_message(self, status_code):
1162 """
1164 """
1163 Return a human friendly error message for the given merge status code.
1165 Return a human friendly error message for the given merge status code.
1164 """
1166 """
1165 return self.MERGE_STATUS_MESSAGES[status_code]
1167 return self.MERGE_STATUS_MESSAGES[status_code]
1166
1168
1167 def generate_repo_data(self, repo, commit_id=None, branch=None,
1169 def generate_repo_data(self, repo, commit_id=None, branch=None,
1168 bookmark=None):
1170 bookmark=None):
1169 all_refs, selected_ref = \
1171 all_refs, selected_ref = \
1170 self._get_repo_pullrequest_sources(
1172 self._get_repo_pullrequest_sources(
1171 repo.scm_instance(), commit_id=commit_id,
1173 repo.scm_instance(), commit_id=commit_id,
1172 branch=branch, bookmark=bookmark)
1174 branch=branch, bookmark=bookmark)
1173
1175
1174 refs_select2 = []
1176 refs_select2 = []
1175 for element in all_refs:
1177 for element in all_refs:
1176 children = [{'id': x[0], 'text': x[1]} for x in element[0]]
1178 children = [{'id': x[0], 'text': x[1]} for x in element[0]]
1177 refs_select2.append({'text': element[1], 'children': children})
1179 refs_select2.append({'text': element[1], 'children': children})
1178
1180
1179 return {
1181 return {
1180 'user': {
1182 'user': {
1181 'user_id': repo.user.user_id,
1183 'user_id': repo.user.user_id,
1182 'username': repo.user.username,
1184 'username': repo.user.username,
1183 'firstname': repo.user.firstname,
1185 'firstname': repo.user.firstname,
1184 'lastname': repo.user.lastname,
1186 'lastname': repo.user.lastname,
1185 'gravatar_link': h.gravatar_url(repo.user.email, 14),
1187 'gravatar_link': h.gravatar_url(repo.user.email, 14),
1186 },
1188 },
1187 'description': h.chop_at_smart(repo.description, '\n'),
1189 'description': h.chop_at_smart(repo.description, '\n'),
1188 'refs': {
1190 'refs': {
1189 'all_refs': all_refs,
1191 'all_refs': all_refs,
1190 'selected_ref': selected_ref,
1192 'selected_ref': selected_ref,
1191 'select2_refs': refs_select2
1193 'select2_refs': refs_select2
1192 }
1194 }
1193 }
1195 }
1194
1196
1195 def generate_pullrequest_title(self, source, source_ref, target):
1197 def generate_pullrequest_title(self, source, source_ref, target):
1196 return u'{source}#{at_ref} to {target}'.format(
1198 return u'{source}#{at_ref} to {target}'.format(
1197 source=source,
1199 source=source,
1198 at_ref=source_ref,
1200 at_ref=source_ref,
1199 target=target,
1201 target=target,
1200 )
1202 )
1201
1203
1202 def _cleanup_merge_workspace(self, pull_request):
1204 def _cleanup_merge_workspace(self, pull_request):
1203 # Merging related cleanup
1205 # Merging related cleanup
1204 target_scm = pull_request.target_repo.scm_instance()
1206 target_scm = pull_request.target_repo.scm_instance()
1205 workspace_id = 'pr-%s' % pull_request.pull_request_id
1207 workspace_id = 'pr-%s' % pull_request.pull_request_id
1206
1208
1207 try:
1209 try:
1208 target_scm.cleanup_merge_workspace(workspace_id)
1210 target_scm.cleanup_merge_workspace(workspace_id)
1209 except NotImplementedError:
1211 except NotImplementedError:
1210 pass
1212 pass
1211
1213
1212 def _get_repo_pullrequest_sources(
1214 def _get_repo_pullrequest_sources(
1213 self, repo, commit_id=None, branch=None, bookmark=None):
1215 self, repo, commit_id=None, branch=None, bookmark=None):
1214 """
1216 """
1215 Return a structure with repo's interesting commits, suitable for
1217 Return a structure with repo's interesting commits, suitable for
1216 the selectors in pullrequest controller
1218 the selectors in pullrequest controller
1217
1219
1218 :param commit_id: a commit that must be in the list somehow
1220 :param commit_id: a commit that must be in the list somehow
1219 and selected by default
1221 and selected by default
1220 :param branch: a branch that must be in the list and selected
1222 :param branch: a branch that must be in the list and selected
1221 by default - even if closed
1223 by default - even if closed
1222 :param bookmark: a bookmark that must be in the list and selected
1224 :param bookmark: a bookmark that must be in the list and selected
1223 """
1225 """
1224
1226
1225 commit_id = safe_str(commit_id) if commit_id else None
1227 commit_id = safe_str(commit_id) if commit_id else None
1226 branch = safe_str(branch) if branch else None
1228 branch = safe_str(branch) if branch else None
1227 bookmark = safe_str(bookmark) if bookmark else None
1229 bookmark = safe_str(bookmark) if bookmark else None
1228
1230
1229 selected = None
1231 selected = None
1230
1232
1231 # order matters: first source that has commit_id in it will be selected
1233 # order matters: first source that has commit_id in it will be selected
1232 sources = []
1234 sources = []
1233 sources.append(('book', repo.bookmarks.items(), _('Bookmarks'), bookmark))
1235 sources.append(('book', repo.bookmarks.items(), _('Bookmarks'), bookmark))
1234 sources.append(('branch', repo.branches.items(), _('Branches'), branch))
1236 sources.append(('branch', repo.branches.items(), _('Branches'), branch))
1235
1237
1236 if commit_id:
1238 if commit_id:
1237 ref_commit = (h.short_id(commit_id), commit_id)
1239 ref_commit = (h.short_id(commit_id), commit_id)
1238 sources.append(('rev', [ref_commit], _('Commit IDs'), commit_id))
1240 sources.append(('rev', [ref_commit], _('Commit IDs'), commit_id))
1239
1241
1240 sources.append(
1242 sources.append(
1241 ('branch', repo.branches_closed.items(), _('Closed Branches'), branch),
1243 ('branch', repo.branches_closed.items(), _('Closed Branches'), branch),
1242 )
1244 )
1243
1245
1244 groups = []
1246 groups = []
1245 for group_key, ref_list, group_name, match in sources:
1247 for group_key, ref_list, group_name, match in sources:
1246 group_refs = []
1248 group_refs = []
1247 for ref_name, ref_id in ref_list:
1249 for ref_name, ref_id in ref_list:
1248 ref_key = '%s:%s:%s' % (group_key, ref_name, ref_id)
1250 ref_key = '%s:%s:%s' % (group_key, ref_name, ref_id)
1249 group_refs.append((ref_key, ref_name))
1251 group_refs.append((ref_key, ref_name))
1250
1252
1251 if not selected:
1253 if not selected:
1252 if set([commit_id, match]) & set([ref_id, ref_name]):
1254 if set([commit_id, match]) & set([ref_id, ref_name]):
1253 selected = ref_key
1255 selected = ref_key
1254
1256
1255 if group_refs:
1257 if group_refs:
1256 groups.append((group_refs, group_name))
1258 groups.append((group_refs, group_name))
1257
1259
1258 if not selected:
1260 if not selected:
1259 ref = commit_id or branch or bookmark
1261 ref = commit_id or branch or bookmark
1260 if ref:
1262 if ref:
1261 raise CommitDoesNotExistError(
1263 raise CommitDoesNotExistError(
1262 'No commit refs could be found matching: %s' % ref)
1264 'No commit refs could be found matching: %s' % ref)
1263 elif repo.DEFAULT_BRANCH_NAME in repo.branches:
1265 elif repo.DEFAULT_BRANCH_NAME in repo.branches:
1264 selected = 'branch:%s:%s' % (
1266 selected = 'branch:%s:%s' % (
1265 repo.DEFAULT_BRANCH_NAME,
1267 repo.DEFAULT_BRANCH_NAME,
1266 repo.branches[repo.DEFAULT_BRANCH_NAME]
1268 repo.branches[repo.DEFAULT_BRANCH_NAME]
1267 )
1269 )
1268 elif repo.commit_ids:
1270 elif repo.commit_ids:
1269 rev = repo.commit_ids[0]
1271 rev = repo.commit_ids[0]
1270 selected = 'rev:%s:%s' % (rev, rev)
1272 selected = 'rev:%s:%s' % (rev, rev)
1271 else:
1273 else:
1272 raise EmptyRepositoryError()
1274 raise EmptyRepositoryError()
1273 return groups, selected
1275 return groups, selected
1274
1276
1275 def get_diff(self, source_repo, source_ref_id, target_ref_id, context=DIFF_CONTEXT):
1277 def get_diff(self, source_repo, source_ref_id, target_ref_id, context=DIFF_CONTEXT):
1276 return self._get_diff_from_pr_or_version(
1278 return self._get_diff_from_pr_or_version(
1277 source_repo, source_ref_id, target_ref_id, context=context)
1279 source_repo, source_ref_id, target_ref_id, context=context)
1278
1280
1279 def _get_diff_from_pr_or_version(
1281 def _get_diff_from_pr_or_version(
1280 self, source_repo, source_ref_id, target_ref_id, context):
1282 self, source_repo, source_ref_id, target_ref_id, context):
1281 target_commit = source_repo.get_commit(
1283 target_commit = source_repo.get_commit(
1282 commit_id=safe_str(target_ref_id))
1284 commit_id=safe_str(target_ref_id))
1283 source_commit = source_repo.get_commit(
1285 source_commit = source_repo.get_commit(
1284 commit_id=safe_str(source_ref_id))
1286 commit_id=safe_str(source_ref_id))
1285 if isinstance(source_repo, Repository):
1287 if isinstance(source_repo, Repository):
1286 vcs_repo = source_repo.scm_instance()
1288 vcs_repo = source_repo.scm_instance()
1287 else:
1289 else:
1288 vcs_repo = source_repo
1290 vcs_repo = source_repo
1289
1291
1290 # TODO: johbo: In the context of an update, we cannot reach
1292 # TODO: johbo: In the context of an update, we cannot reach
1291 # the old commit anymore with our normal mechanisms. It needs
1293 # the old commit anymore with our normal mechanisms. It needs
1292 # some sort of special support in the vcs layer to avoid this
1294 # some sort of special support in the vcs layer to avoid this
1293 # workaround.
1295 # workaround.
1294 if (source_commit.raw_id == vcs_repo.EMPTY_COMMIT_ID and
1296 if (source_commit.raw_id == vcs_repo.EMPTY_COMMIT_ID and
1295 vcs_repo.alias == 'git'):
1297 vcs_repo.alias == 'git'):
1296 source_commit.raw_id = safe_str(source_ref_id)
1298 source_commit.raw_id = safe_str(source_ref_id)
1297
1299
1298 log.debug('calculating diff between '
1300 log.debug('calculating diff between '
1299 'source_ref:%s and target_ref:%s for repo `%s`',
1301 'source_ref:%s and target_ref:%s for repo `%s`',
1300 target_ref_id, source_ref_id,
1302 target_ref_id, source_ref_id,
1301 safe_unicode(vcs_repo.path))
1303 safe_unicode(vcs_repo.path))
1302
1304
1303 vcs_diff = vcs_repo.get_diff(
1305 vcs_diff = vcs_repo.get_diff(
1304 commit1=target_commit, commit2=source_commit, context=context)
1306 commit1=target_commit, commit2=source_commit, context=context)
1305 return vcs_diff
1307 return vcs_diff
1306
1308
1307 def _is_merge_enabled(self, pull_request):
1309 def _is_merge_enabled(self, pull_request):
1308 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1310 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1309 settings = settings_model.get_general_settings()
1311 settings = settings_model.get_general_settings()
1310 return settings.get('rhodecode_pr_merge_enabled', False)
1312 return settings.get('rhodecode_pr_merge_enabled', False)
1311
1313
1312 def _use_rebase_for_merging(self, pull_request):
1314 def _use_rebase_for_merging(self, pull_request):
1313 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1315 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1314 settings = settings_model.get_general_settings()
1316 settings = settings_model.get_general_settings()
1315 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1317 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1316
1318
1317 def _log_action(self, action, user, pull_request):
1319 def _log_action(self, action, user, pull_request):
1318 action_logger(
1320 action_logger(
1319 user,
1321 user,
1320 '{action}:{pr_id}'.format(
1322 '{action}:{pr_id}'.format(
1321 action=action, pr_id=pull_request.pull_request_id),
1323 action=action, pr_id=pull_request.pull_request_id),
1322 pull_request.target_repo)
1324 pull_request.target_repo)
1323
1325
1324
1326
1325 class MergeCheck(object):
1327 class MergeCheck(object):
1326 """
1328 """
1327 Perform Merge Checks and returns a check object which stores information
1329 Perform Merge Checks and returns a check object which stores information
1328 about merge errors, and merge conditions
1330 about merge errors, and merge conditions
1329 """
1331 """
1330 TODO_CHECK = 'todo'
1332 TODO_CHECK = 'todo'
1331 PERM_CHECK = 'perm'
1333 PERM_CHECK = 'perm'
1332 REVIEW_CHECK = 'review'
1334 REVIEW_CHECK = 'review'
1333 MERGE_CHECK = 'merge'
1335 MERGE_CHECK = 'merge'
1334
1336
1335 def __init__(self):
1337 def __init__(self):
1336 self.review_status = None
1338 self.review_status = None
1337 self.merge_possible = None
1339 self.merge_possible = None
1338 self.merge_msg = ''
1340 self.merge_msg = ''
1339 self.failed = None
1341 self.failed = None
1340 self.errors = []
1342 self.errors = []
1341 self.error_details = OrderedDict()
1343 self.error_details = OrderedDict()
1342
1344
1343 def push_error(self, error_type, message, error_key, details):
1345 def push_error(self, error_type, message, error_key, details):
1344 self.failed = True
1346 self.failed = True
1345 self.errors.append([error_type, message])
1347 self.errors.append([error_type, message])
1346 self.error_details[error_key] = dict(
1348 self.error_details[error_key] = dict(
1347 details=details,
1349 details=details,
1348 error_type=error_type,
1350 error_type=error_type,
1349 message=message
1351 message=message
1350 )
1352 )
1351
1353
1352 @classmethod
1354 @classmethod
1353 def validate(cls, pull_request, user, fail_early=False, translator=None):
1355 def validate(cls, pull_request, user, fail_early=False, translator=None):
1354 # if migrated to pyramid...
1356 # if migrated to pyramid...
1355 # _ = lambda: translator or _ # use passed in translator if any
1357 # _ = lambda: translator or _ # use passed in translator if any
1356
1358
1357 merge_check = cls()
1359 merge_check = cls()
1358
1360
1359 # permissions to merge
1361 # permissions to merge
1360 user_allowed_to_merge = PullRequestModel().check_user_merge(
1362 user_allowed_to_merge = PullRequestModel().check_user_merge(
1361 pull_request, user)
1363 pull_request, user)
1362 if not user_allowed_to_merge:
1364 if not user_allowed_to_merge:
1363 log.debug("MergeCheck: cannot merge, approval is pending.")
1365 log.debug("MergeCheck: cannot merge, approval is pending.")
1364
1366
1365 msg = _('User `{}` not allowed to perform merge.').format(user.username)
1367 msg = _('User `{}` not allowed to perform merge.').format(user.username)
1366 merge_check.push_error('error', msg, cls.PERM_CHECK, user.username)
1368 merge_check.push_error('error', msg, cls.PERM_CHECK, user.username)
1367 if fail_early:
1369 if fail_early:
1368 return merge_check
1370 return merge_check
1369
1371
1370 # review status, must be always present
1372 # review status, must be always present
1371 review_status = pull_request.calculated_review_status()
1373 review_status = pull_request.calculated_review_status()
1372 merge_check.review_status = review_status
1374 merge_check.review_status = review_status
1373
1375
1374 status_approved = review_status == ChangesetStatus.STATUS_APPROVED
1376 status_approved = review_status == ChangesetStatus.STATUS_APPROVED
1375 if not status_approved:
1377 if not status_approved:
1376 log.debug("MergeCheck: cannot merge, approval is pending.")
1378 log.debug("MergeCheck: cannot merge, approval is pending.")
1377
1379
1378 msg = _('Pull request reviewer approval is pending.')
1380 msg = _('Pull request reviewer approval is pending.')
1379
1381
1380 merge_check.push_error(
1382 merge_check.push_error(
1381 'warning', msg, cls.REVIEW_CHECK, review_status)
1383 'warning', msg, cls.REVIEW_CHECK, review_status)
1382
1384
1383 if fail_early:
1385 if fail_early:
1384 return merge_check
1386 return merge_check
1385
1387
1386 # left over TODOs
1388 # left over TODOs
1387 todos = CommentsModel().get_unresolved_todos(pull_request)
1389 todos = CommentsModel().get_unresolved_todos(pull_request)
1388 if todos:
1390 if todos:
1389 log.debug("MergeCheck: cannot merge, {} "
1391 log.debug("MergeCheck: cannot merge, {} "
1390 "unresolved todos left.".format(len(todos)))
1392 "unresolved todos left.".format(len(todos)))
1391
1393
1392 if len(todos) == 1:
1394 if len(todos) == 1:
1393 msg = _('Cannot merge, {} TODO still not resolved.').format(
1395 msg = _('Cannot merge, {} TODO still not resolved.').format(
1394 len(todos))
1396 len(todos))
1395 else:
1397 else:
1396 msg = _('Cannot merge, {} TODOs still not resolved.').format(
1398 msg = _('Cannot merge, {} TODOs still not resolved.').format(
1397 len(todos))
1399 len(todos))
1398
1400
1399 merge_check.push_error('warning', msg, cls.TODO_CHECK, todos)
1401 merge_check.push_error('warning', msg, cls.TODO_CHECK, todos)
1400
1402
1401 if fail_early:
1403 if fail_early:
1402 return merge_check
1404 return merge_check
1403
1405
1404 # merge possible
1406 # merge possible
1405 merge_status, msg = PullRequestModel().merge_status(pull_request)
1407 merge_status, msg = PullRequestModel().merge_status(pull_request)
1406 merge_check.merge_possible = merge_status
1408 merge_check.merge_possible = merge_status
1407 merge_check.merge_msg = msg
1409 merge_check.merge_msg = msg
1408 if not merge_status:
1410 if not merge_status:
1409 log.debug(
1411 log.debug(
1410 "MergeCheck: cannot merge, pull request merge not possible.")
1412 "MergeCheck: cannot merge, pull request merge not possible.")
1411 merge_check.push_error('warning', msg, cls.MERGE_CHECK, None)
1413 merge_check.push_error('warning', msg, cls.MERGE_CHECK, None)
1412
1414
1413 if fail_early:
1415 if fail_early:
1414 return merge_check
1416 return merge_check
1415
1417
1416 return merge_check
1418 return merge_check
1417
1419
1418
1420
1419 ChangeTuple = namedtuple('ChangeTuple',
1421 ChangeTuple = namedtuple('ChangeTuple',
1420 ['added', 'common', 'removed', 'total'])
1422 ['added', 'common', 'removed', 'total'])
1421
1423
1422 FileChangeTuple = namedtuple('FileChangeTuple',
1424 FileChangeTuple = namedtuple('FileChangeTuple',
1423 ['added', 'modified', 'removed'])
1425 ['added', 'modified', 'removed'])
@@ -1,2349 +1,2342 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-family: @text-semibold;
37 font-family: @text-semibold;
38 font-size: 120%;
38 font-size: 120%;
39 color: white;
39 color: white;
40 background-color: @alert2;
40 background-color: @alert2;
41 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
42 }
42 }
43
43
44 html {
44 html {
45 display: table;
45 display: table;
46 height: 100%;
46 height: 100%;
47 width: 100%;
47 width: 100%;
48 }
48 }
49
49
50 body {
50 body {
51 display: table-cell;
51 display: table-cell;
52 width: 100%;
52 width: 100%;
53 }
53 }
54
54
55 //--- LAYOUT ------------------//
55 //--- LAYOUT ------------------//
56
56
57 .hidden{
57 .hidden{
58 display: none !important;
58 display: none !important;
59 }
59 }
60
60
61 .box{
61 .box{
62 float: left;
62 float: left;
63 width: 100%;
63 width: 100%;
64 }
64 }
65
65
66 .browser-header {
66 .browser-header {
67 clear: both;
67 clear: both;
68 }
68 }
69 .main {
69 .main {
70 clear: both;
70 clear: both;
71 padding:0 0 @pagepadding;
71 padding:0 0 @pagepadding;
72 height: auto;
72 height: auto;
73
73
74 &:after { //clearfix
74 &:after { //clearfix
75 content:"";
75 content:"";
76 clear:both;
76 clear:both;
77 width:100%;
77 width:100%;
78 display:block;
78 display:block;
79 }
79 }
80 }
80 }
81
81
82 .action-link{
82 .action-link{
83 margin-left: @padding;
83 margin-left: @padding;
84 padding-left: @padding;
84 padding-left: @padding;
85 border-left: @border-thickness solid @border-default-color;
85 border-left: @border-thickness solid @border-default-color;
86 }
86 }
87
87
88 input + .action-link, .action-link.first{
88 input + .action-link, .action-link.first{
89 border-left: none;
89 border-left: none;
90 }
90 }
91
91
92 .action-link.last{
92 .action-link.last{
93 margin-right: @padding;
93 margin-right: @padding;
94 padding-right: @padding;
94 padding-right: @padding;
95 }
95 }
96
96
97 .action-link.active,
97 .action-link.active,
98 .action-link.active a{
98 .action-link.active a{
99 color: @grey4;
99 color: @grey4;
100 }
100 }
101
101
102 ul.simple-list{
102 ul.simple-list{
103 list-style: none;
103 list-style: none;
104 margin: 0;
104 margin: 0;
105 padding: 0;
105 padding: 0;
106 }
106 }
107
107
108 .main-content {
108 .main-content {
109 padding-bottom: @pagepadding;
109 padding-bottom: @pagepadding;
110 }
110 }
111
111
112 .wide-mode-wrapper {
112 .wide-mode-wrapper {
113 max-width:4000px !important;
113 max-width:4000px !important;
114 }
114 }
115
115
116 .wrapper {
116 .wrapper {
117 position: relative;
117 position: relative;
118 max-width: @wrapper-maxwidth;
118 max-width: @wrapper-maxwidth;
119 margin: 0 auto;
119 margin: 0 auto;
120 }
120 }
121
121
122 #content {
122 #content {
123 clear: both;
123 clear: both;
124 padding: 0 @contentpadding;
124 padding: 0 @contentpadding;
125 }
125 }
126
126
127 .advanced-settings-fields{
127 .advanced-settings-fields{
128 input{
128 input{
129 margin-left: @textmargin;
129 margin-left: @textmargin;
130 margin-right: @padding/2;
130 margin-right: @padding/2;
131 }
131 }
132 }
132 }
133
133
134 .cs_files_title {
134 .cs_files_title {
135 margin: @pagepadding 0 0;
135 margin: @pagepadding 0 0;
136 }
136 }
137
137
138 input.inline[type="file"] {
138 input.inline[type="file"] {
139 display: inline;
139 display: inline;
140 }
140 }
141
141
142 .error_page {
142 .error_page {
143 margin: 10% auto;
143 margin: 10% auto;
144
144
145 h1 {
145 h1 {
146 color: @grey2;
146 color: @grey2;
147 }
147 }
148
148
149 .alert {
149 .alert {
150 margin: @padding 0;
150 margin: @padding 0;
151 }
151 }
152
152
153 .error-branding {
153 .error-branding {
154 font-family: @text-semibold;
154 font-family: @text-semibold;
155 color: @grey4;
155 color: @grey4;
156 }
156 }
157
157
158 .error_message {
158 .error_message {
159 font-family: @text-regular;
159 font-family: @text-regular;
160 }
160 }
161
161
162 .sidebar {
162 .sidebar {
163 min-height: 275px;
163 min-height: 275px;
164 margin: 0;
164 margin: 0;
165 padding: 0 0 @sidebarpadding @sidebarpadding;
165 padding: 0 0 @sidebarpadding @sidebarpadding;
166 border: none;
166 border: none;
167 }
167 }
168
168
169 .main-content {
169 .main-content {
170 position: relative;
170 position: relative;
171 margin: 0 @sidebarpadding @sidebarpadding;
171 margin: 0 @sidebarpadding @sidebarpadding;
172 padding: 0 0 0 @sidebarpadding;
172 padding: 0 0 0 @sidebarpadding;
173 border-left: @border-thickness solid @grey5;
173 border-left: @border-thickness solid @grey5;
174
174
175 @media (max-width:767px) {
175 @media (max-width:767px) {
176 clear: both;
176 clear: both;
177 width: 100%;
177 width: 100%;
178 margin: 0;
178 margin: 0;
179 border: none;
179 border: none;
180 }
180 }
181 }
181 }
182
182
183 .inner-column {
183 .inner-column {
184 float: left;
184 float: left;
185 width: 29.75%;
185 width: 29.75%;
186 min-height: 150px;
186 min-height: 150px;
187 margin: @sidebarpadding 2% 0 0;
187 margin: @sidebarpadding 2% 0 0;
188 padding: 0 2% 0 0;
188 padding: 0 2% 0 0;
189 border-right: @border-thickness solid @grey5;
189 border-right: @border-thickness solid @grey5;
190
190
191 @media (max-width:767px) {
191 @media (max-width:767px) {
192 clear: both;
192 clear: both;
193 width: 100%;
193 width: 100%;
194 border: none;
194 border: none;
195 }
195 }
196
196
197 ul {
197 ul {
198 padding-left: 1.25em;
198 padding-left: 1.25em;
199 }
199 }
200
200
201 &:last-child {
201 &:last-child {
202 margin: @sidebarpadding 0 0;
202 margin: @sidebarpadding 0 0;
203 border: none;
203 border: none;
204 }
204 }
205
205
206 h4 {
206 h4 {
207 margin: 0 0 @padding;
207 margin: 0 0 @padding;
208 font-family: @text-semibold;
208 font-family: @text-semibold;
209 }
209 }
210 }
210 }
211 }
211 }
212 .error-page-logo {
212 .error-page-logo {
213 width: 130px;
213 width: 130px;
214 height: 160px;
214 height: 160px;
215 }
215 }
216
216
217 // HEADER
217 // HEADER
218 .header {
218 .header {
219
219
220 // TODO: johbo: Fix login pages, so that they work without a min-height
220 // TODO: johbo: Fix login pages, so that they work without a min-height
221 // for the header and then remove the min-height. I chose a smaller value
221 // for the header and then remove the min-height. I chose a smaller value
222 // intentionally here to avoid rendering issues in the main navigation.
222 // intentionally here to avoid rendering issues in the main navigation.
223 min-height: 49px;
223 min-height: 49px;
224
224
225 position: relative;
225 position: relative;
226 vertical-align: bottom;
226 vertical-align: bottom;
227 padding: 0 @header-padding;
227 padding: 0 @header-padding;
228 background-color: @grey2;
228 background-color: @grey2;
229 color: @grey5;
229 color: @grey5;
230
230
231 .title {
231 .title {
232 overflow: visible;
232 overflow: visible;
233 }
233 }
234
234
235 &:before,
235 &:before,
236 &:after {
236 &:after {
237 content: "";
237 content: "";
238 clear: both;
238 clear: both;
239 width: 100%;
239 width: 100%;
240 }
240 }
241
241
242 // TODO: johbo: Avoids breaking "Repositories" chooser
242 // TODO: johbo: Avoids breaking "Repositories" chooser
243 .select2-container .select2-choice .select2-arrow {
243 .select2-container .select2-choice .select2-arrow {
244 display: none;
244 display: none;
245 }
245 }
246 }
246 }
247
247
248 #header-inner {
248 #header-inner {
249 &.title {
249 &.title {
250 margin: 0;
250 margin: 0;
251 }
251 }
252 &:before,
252 &:before,
253 &:after {
253 &:after {
254 content: "";
254 content: "";
255 clear: both;
255 clear: both;
256 }
256 }
257 }
257 }
258
258
259 // Gists
259 // Gists
260 #files_data {
260 #files_data {
261 clear: both; //for firefox
261 clear: both; //for firefox
262 }
262 }
263 #gistid {
263 #gistid {
264 margin-right: @padding;
264 margin-right: @padding;
265 }
265 }
266
266
267 // Global Settings Editor
267 // Global Settings Editor
268 .textarea.editor {
268 .textarea.editor {
269 float: left;
269 float: left;
270 position: relative;
270 position: relative;
271 max-width: @texteditor-width;
271 max-width: @texteditor-width;
272
272
273 select {
273 select {
274 position: absolute;
274 position: absolute;
275 top:10px;
275 top:10px;
276 right:0;
276 right:0;
277 }
277 }
278
278
279 .CodeMirror {
279 .CodeMirror {
280 margin: 0;
280 margin: 0;
281 }
281 }
282
282
283 .help-block {
283 .help-block {
284 margin: 0 0 @padding;
284 margin: 0 0 @padding;
285 padding:.5em;
285 padding:.5em;
286 background-color: @grey6;
286 background-color: @grey6;
287 }
287 }
288 }
288 }
289
289
290 ul.auth_plugins {
290 ul.auth_plugins {
291 margin: @padding 0 @padding @legend-width;
291 margin: @padding 0 @padding @legend-width;
292 padding: 0;
292 padding: 0;
293
293
294 li {
294 li {
295 margin-bottom: @padding;
295 margin-bottom: @padding;
296 line-height: 1em;
296 line-height: 1em;
297 list-style-type: none;
297 list-style-type: none;
298
298
299 .auth_buttons .btn {
299 .auth_buttons .btn {
300 margin-right: @padding;
300 margin-right: @padding;
301 }
301 }
302
302
303 &:before { content: none; }
303 &:before { content: none; }
304 }
304 }
305 }
305 }
306
306
307
307
308 // My Account PR list
308 // My Account PR list
309
309
310 #show_closed {
310 #show_closed {
311 margin: 0 1em 0 0;
311 margin: 0 1em 0 0;
312 }
312 }
313
313
314 .pullrequestlist {
314 .pullrequestlist {
315 .closed {
315 .closed {
316 background-color: @grey6;
316 background-color: @grey6;
317 }
317 }
318 .td-status {
318 .td-status {
319 padding-left: .5em;
319 padding-left: .5em;
320 }
320 }
321 .log-container .truncate {
321 .log-container .truncate {
322 height: 2.75em;
322 height: 2.75em;
323 white-space: pre-line;
323 white-space: pre-line;
324 }
324 }
325 table.rctable .user {
325 table.rctable .user {
326 padding-left: 0;
326 padding-left: 0;
327 }
327 }
328 table.rctable {
328 table.rctable {
329 td.td-description,
329 td.td-description,
330 .rc-user {
330 .rc-user {
331 min-width: auto;
331 min-width: auto;
332 }
332 }
333 }
333 }
334 }
334 }
335
335
336 // Pull Requests
336 // Pull Requests
337
337
338 .pullrequests_section_head {
338 .pullrequests_section_head {
339 display: block;
339 display: block;
340 clear: both;
340 clear: both;
341 margin: @padding 0;
341 margin: @padding 0;
342 font-family: @text-bold;
342 font-family: @text-bold;
343 }
343 }
344
344
345 .pr-origininfo, .pr-targetinfo {
345 .pr-origininfo, .pr-targetinfo {
346 position: relative;
346 position: relative;
347
347
348 .tag {
348 .tag {
349 display: inline-block;
349 display: inline-block;
350 margin: 0 1em .5em 0;
350 margin: 0 1em .5em 0;
351 }
351 }
352
352
353 .clone-url {
353 .clone-url {
354 display: inline-block;
354 display: inline-block;
355 margin: 0 0 .5em 0;
355 margin: 0 0 .5em 0;
356 padding: 0;
356 padding: 0;
357 line-height: 1.2em;
357 line-height: 1.2em;
358 }
358 }
359 }
359 }
360
360
361 .pr-pullinfo {
361 .pr-pullinfo {
362 clear: both;
362 clear: both;
363 margin: .5em 0;
363 margin: .5em 0;
364 }
364 }
365
365
366 #pr-title-input {
366 #pr-title-input {
367 width: 72%;
367 width: 72%;
368 font-size: 1em;
368 font-size: 1em;
369 font-family: @text-bold;
369 font-family: @text-bold;
370 margin: 0;
370 margin: 0;
371 padding: 0 0 0 @padding/4;
371 padding: 0 0 0 @padding/4;
372 line-height: 1.7em;
372 line-height: 1.7em;
373 color: @text-color;
373 color: @text-color;
374 letter-spacing: .02em;
374 letter-spacing: .02em;
375 }
375 }
376
376
377 #pullrequest_title {
377 #pullrequest_title {
378 width: 100%;
378 width: 100%;
379 box-sizing: border-box;
379 box-sizing: border-box;
380 }
380 }
381
381
382 #pr_open_message {
382 #pr_open_message {
383 border: @border-thickness solid #fff;
383 border: @border-thickness solid #fff;
384 border-radius: @border-radius;
384 border-radius: @border-radius;
385 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
385 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
386 text-align: left;
386 text-align: left;
387 overflow: hidden;
387 overflow: hidden;
388 }
388 }
389
389
390 .pr-submit-button {
390 .pr-submit-button {
391 float: right;
391 float: right;
392 margin: 0 0 0 5px;
392 margin: 0 0 0 5px;
393 }
393 }
394
394
395 .pr-spacing-container {
395 .pr-spacing-container {
396 padding: 20px;
396 padding: 20px;
397 clear: both
397 clear: both
398 }
398 }
399
399
400 #pr-description-input {
400 #pr-description-input {
401 margin-bottom: 0;
401 margin-bottom: 0;
402 }
402 }
403
403
404 .pr-description-label {
404 .pr-description-label {
405 vertical-align: top;
405 vertical-align: top;
406 }
406 }
407
407
408 .perms_section_head {
408 .perms_section_head {
409 min-width: 625px;
409 min-width: 625px;
410
410
411 h2 {
411 h2 {
412 margin-bottom: 0;
412 margin-bottom: 0;
413 }
413 }
414
414
415 .label-checkbox {
415 .label-checkbox {
416 float: left;
416 float: left;
417 }
417 }
418
418
419 &.field {
419 &.field {
420 margin: @space 0 @padding;
420 margin: @space 0 @padding;
421 }
421 }
422
422
423 &:first-child.field {
423 &:first-child.field {
424 margin-top: 0;
424 margin-top: 0;
425
425
426 .label {
426 .label {
427 margin-top: 0;
427 margin-top: 0;
428 padding-top: 0;
428 padding-top: 0;
429 }
429 }
430
430
431 .radios {
431 .radios {
432 padding-top: 0;
432 padding-top: 0;
433 }
433 }
434 }
434 }
435
435
436 .radios {
436 .radios {
437 float: right;
437 float: right;
438 position: relative;
438 position: relative;
439 width: 405px;
439 width: 405px;
440 }
440 }
441 }
441 }
442
442
443 //--- MODULES ------------------//
443 //--- MODULES ------------------//
444
444
445
445
446 // Server Announcement
446 // Server Announcement
447 #server-announcement {
447 #server-announcement {
448 width: 95%;
448 width: 95%;
449 margin: @padding auto;
449 margin: @padding auto;
450 padding: @padding;
450 padding: @padding;
451 border-width: 2px;
451 border-width: 2px;
452 border-style: solid;
452 border-style: solid;
453 .border-radius(2px);
453 .border-radius(2px);
454 font-family: @text-bold;
454 font-family: @text-bold;
455
455
456 &.info { border-color: @alert4; background-color: @alert4-inner; }
456 &.info { border-color: @alert4; background-color: @alert4-inner; }
457 &.warning { border-color: @alert3; background-color: @alert3-inner; }
457 &.warning { border-color: @alert3; background-color: @alert3-inner; }
458 &.error { border-color: @alert2; background-color: @alert2-inner; }
458 &.error { border-color: @alert2; background-color: @alert2-inner; }
459 &.success { border-color: @alert1; background-color: @alert1-inner; }
459 &.success { border-color: @alert1; background-color: @alert1-inner; }
460 &.neutral { border-color: @grey3; background-color: @grey6; }
460 &.neutral { border-color: @grey3; background-color: @grey6; }
461 }
461 }
462
462
463 // Fixed Sidebar Column
463 // Fixed Sidebar Column
464 .sidebar-col-wrapper {
464 .sidebar-col-wrapper {
465 padding-left: @sidebar-all-width;
465 padding-left: @sidebar-all-width;
466
466
467 .sidebar {
467 .sidebar {
468 width: @sidebar-width;
468 width: @sidebar-width;
469 margin-left: -@sidebar-all-width;
469 margin-left: -@sidebar-all-width;
470 }
470 }
471 }
471 }
472
472
473 .sidebar-col-wrapper.scw-small {
473 .sidebar-col-wrapper.scw-small {
474 padding-left: @sidebar-small-all-width;
474 padding-left: @sidebar-small-all-width;
475
475
476 .sidebar {
476 .sidebar {
477 width: @sidebar-small-width;
477 width: @sidebar-small-width;
478 margin-left: -@sidebar-small-all-width;
478 margin-left: -@sidebar-small-all-width;
479 }
479 }
480 }
480 }
481
481
482
482
483 // FOOTER
483 // FOOTER
484 #footer {
484 #footer {
485 padding: 0;
485 padding: 0;
486 text-align: center;
486 text-align: center;
487 vertical-align: middle;
487 vertical-align: middle;
488 color: @grey2;
488 color: @grey2;
489 background-color: @grey6;
489 background-color: @grey6;
490
490
491 p {
491 p {
492 margin: 0;
492 margin: 0;
493 padding: 1em;
493 padding: 1em;
494 line-height: 1em;
494 line-height: 1em;
495 }
495 }
496
496
497 .server-instance { //server instance
497 .server-instance { //server instance
498 display: none;
498 display: none;
499 }
499 }
500
500
501 .title {
501 .title {
502 float: none;
502 float: none;
503 margin: 0 auto;
503 margin: 0 auto;
504 }
504 }
505 }
505 }
506
506
507 button.close {
507 button.close {
508 padding: 0;
508 padding: 0;
509 cursor: pointer;
509 cursor: pointer;
510 background: transparent;
510 background: transparent;
511 border: 0;
511 border: 0;
512 .box-shadow(none);
512 .box-shadow(none);
513 -webkit-appearance: none;
513 -webkit-appearance: none;
514 }
514 }
515
515
516 .close {
516 .close {
517 float: right;
517 float: right;
518 font-size: 21px;
518 font-size: 21px;
519 font-family: @text-bootstrap;
519 font-family: @text-bootstrap;
520 line-height: 1em;
520 line-height: 1em;
521 font-weight: bold;
521 font-weight: bold;
522 color: @grey2;
522 color: @grey2;
523
523
524 &:hover,
524 &:hover,
525 &:focus {
525 &:focus {
526 color: @grey1;
526 color: @grey1;
527 text-decoration: none;
527 text-decoration: none;
528 cursor: pointer;
528 cursor: pointer;
529 }
529 }
530 }
530 }
531
531
532 // GRID
532 // GRID
533 .sorting,
533 .sorting,
534 .sorting_desc,
534 .sorting_desc,
535 .sorting_asc {
535 .sorting_asc {
536 cursor: pointer;
536 cursor: pointer;
537 }
537 }
538 .sorting_desc:after {
538 .sorting_desc:after {
539 content: "\00A0\25B2";
539 content: "\00A0\25B2";
540 font-size: .75em;
540 font-size: .75em;
541 }
541 }
542 .sorting_asc:after {
542 .sorting_asc:after {
543 content: "\00A0\25BC";
543 content: "\00A0\25BC";
544 font-size: .68em;
544 font-size: .68em;
545 }
545 }
546
546
547
547
548 .user_auth_tokens {
548 .user_auth_tokens {
549
549
550 &.truncate {
550 &.truncate {
551 white-space: nowrap;
551 white-space: nowrap;
552 overflow: hidden;
552 overflow: hidden;
553 text-overflow: ellipsis;
553 text-overflow: ellipsis;
554 }
554 }
555
555
556 .fields .field .input {
556 .fields .field .input {
557 margin: 0;
557 margin: 0;
558 }
558 }
559
559
560 input#description {
560 input#description {
561 width: 100px;
561 width: 100px;
562 margin: 0;
562 margin: 0;
563 }
563 }
564
564
565 .drop-menu {
565 .drop-menu {
566 // TODO: johbo: Remove this, should work out of the box when
566 // TODO: johbo: Remove this, should work out of the box when
567 // having multiple inputs inline
567 // having multiple inputs inline
568 margin: 0 0 0 5px;
568 margin: 0 0 0 5px;
569 }
569 }
570 }
570 }
571 #user_list_table {
571 #user_list_table {
572 .closed {
572 .closed {
573 background-color: @grey6;
573 background-color: @grey6;
574 }
574 }
575 }
575 }
576
576
577
577
578 input {
578 input {
579 &.disabled {
579 &.disabled {
580 opacity: .5;
580 opacity: .5;
581 }
581 }
582 }
582 }
583
583
584 // remove extra padding in firefox
584 // remove extra padding in firefox
585 input::-moz-focus-inner { border:0; padding:0 }
585 input::-moz-focus-inner { border:0; padding:0 }
586
586
587 .adjacent input {
587 .adjacent input {
588 margin-bottom: @padding;
588 margin-bottom: @padding;
589 }
589 }
590
590
591 .permissions_boxes {
591 .permissions_boxes {
592 display: block;
592 display: block;
593 }
593 }
594
594
595 //TODO: lisa: this should be in tables
595 //TODO: lisa: this should be in tables
596 .show_more_col {
596 .show_more_col {
597 width: 20px;
597 width: 20px;
598 }
598 }
599
599
600 //FORMS
600 //FORMS
601
601
602 .medium-inline,
602 .medium-inline,
603 input#description.medium-inline {
603 input#description.medium-inline {
604 display: inline;
604 display: inline;
605 width: @medium-inline-input-width;
605 width: @medium-inline-input-width;
606 min-width: 100px;
606 min-width: 100px;
607 }
607 }
608
608
609 select {
609 select {
610 //reset
610 //reset
611 -webkit-appearance: none;
611 -webkit-appearance: none;
612 -moz-appearance: none;
612 -moz-appearance: none;
613
613
614 display: inline-block;
614 display: inline-block;
615 height: 28px;
615 height: 28px;
616 width: auto;
616 width: auto;
617 margin: 0 @padding @padding 0;
617 margin: 0 @padding @padding 0;
618 padding: 0 18px 0 8px;
618 padding: 0 18px 0 8px;
619 line-height:1em;
619 line-height:1em;
620 font-size: @basefontsize;
620 font-size: @basefontsize;
621 border: @border-thickness solid @rcblue;
621 border: @border-thickness solid @rcblue;
622 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
622 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
623 color: @rcblue;
623 color: @rcblue;
624
624
625 &:after {
625 &:after {
626 content: "\00A0\25BE";
626 content: "\00A0\25BE";
627 }
627 }
628
628
629 &:focus {
629 &:focus {
630 outline: none;
630 outline: none;
631 }
631 }
632 }
632 }
633
633
634 option {
634 option {
635 &:focus {
635 &:focus {
636 outline: none;
636 outline: none;
637 }
637 }
638 }
638 }
639
639
640 input,
640 input,
641 textarea {
641 textarea {
642 padding: @input-padding;
642 padding: @input-padding;
643 border: @input-border-thickness solid @border-highlight-color;
643 border: @input-border-thickness solid @border-highlight-color;
644 .border-radius (@border-radius);
644 .border-radius (@border-radius);
645 font-family: @text-light;
645 font-family: @text-light;
646 font-size: @basefontsize;
646 font-size: @basefontsize;
647
647
648 &.input-sm {
648 &.input-sm {
649 padding: 5px;
649 padding: 5px;
650 }
650 }
651
651
652 &#description {
652 &#description {
653 min-width: @input-description-minwidth;
653 min-width: @input-description-minwidth;
654 min-height: 1em;
654 min-height: 1em;
655 padding: 10px;
655 padding: 10px;
656 }
656 }
657 }
657 }
658
658
659 .field-sm {
659 .field-sm {
660 input,
660 input,
661 textarea {
661 textarea {
662 padding: 5px;
662 padding: 5px;
663 }
663 }
664 }
664 }
665
665
666 textarea {
666 textarea {
667 display: block;
667 display: block;
668 clear: both;
668 clear: both;
669 width: 100%;
669 width: 100%;
670 min-height: 100px;
670 min-height: 100px;
671 margin-bottom: @padding;
671 margin-bottom: @padding;
672 .box-sizing(border-box);
672 .box-sizing(border-box);
673 overflow: auto;
673 overflow: auto;
674 }
674 }
675
675
676 label {
676 label {
677 font-family: @text-light;
677 font-family: @text-light;
678 }
678 }
679
679
680 // GRAVATARS
680 // GRAVATARS
681 // centers gravatar on username to the right
681 // centers gravatar on username to the right
682
682
683 .gravatar {
683 .gravatar {
684 display: inline;
684 display: inline;
685 min-width: 16px;
685 min-width: 16px;
686 min-height: 16px;
686 min-height: 16px;
687 margin: -5px 0;
687 margin: -5px 0;
688 padding: 0;
688 padding: 0;
689 line-height: 1em;
689 line-height: 1em;
690 border: 1px solid @grey4;
690 border: 1px solid @grey4;
691 box-sizing: content-box;
691 box-sizing: content-box;
692
692
693 &.gravatar-large {
693 &.gravatar-large {
694 margin: -0.5em .25em -0.5em 0;
694 margin: -0.5em .25em -0.5em 0;
695 }
695 }
696
696
697 & + .user {
697 & + .user {
698 display: inline;
698 display: inline;
699 margin: 0;
699 margin: 0;
700 padding: 0 0 0 .17em;
700 padding: 0 0 0 .17em;
701 line-height: 1em;
701 line-height: 1em;
702 }
702 }
703 }
703 }
704
704
705 .user-inline-data {
705 .user-inline-data {
706 display: inline-block;
706 display: inline-block;
707 float: left;
707 float: left;
708 padding-left: .5em;
708 padding-left: .5em;
709 line-height: 1.3em;
709 line-height: 1.3em;
710 }
710 }
711
711
712 .rc-user { // gravatar + user wrapper
712 .rc-user { // gravatar + user wrapper
713 float: left;
713 float: left;
714 position: relative;
714 position: relative;
715 min-width: 100px;
715 min-width: 100px;
716 max-width: 200px;
716 max-width: 200px;
717 min-height: (@gravatar-size + @border-thickness * 2); // account for border
717 min-height: (@gravatar-size + @border-thickness * 2); // account for border
718 display: block;
718 display: block;
719 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
719 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
720
720
721
721
722 .gravatar {
722 .gravatar {
723 display: block;
723 display: block;
724 position: absolute;
724 position: absolute;
725 top: 0;
725 top: 0;
726 left: 0;
726 left: 0;
727 min-width: @gravatar-size;
727 min-width: @gravatar-size;
728 min-height: @gravatar-size;
728 min-height: @gravatar-size;
729 margin: 0;
729 margin: 0;
730 }
730 }
731
731
732 .user {
732 .user {
733 display: block;
733 display: block;
734 max-width: 175px;
734 max-width: 175px;
735 padding-top: 2px;
735 padding-top: 2px;
736 overflow: hidden;
736 overflow: hidden;
737 text-overflow: ellipsis;
737 text-overflow: ellipsis;
738 }
738 }
739 }
739 }
740
740
741 .gist-gravatar,
741 .gist-gravatar,
742 .journal_container {
742 .journal_container {
743 .gravatar-large {
743 .gravatar-large {
744 margin: 0 .5em -10px 0;
744 margin: 0 .5em -10px 0;
745 }
745 }
746 }
746 }
747
747
748
748
749 // ADMIN SETTINGS
749 // ADMIN SETTINGS
750
750
751 // Tag Patterns
751 // Tag Patterns
752 .tag_patterns {
752 .tag_patterns {
753 .tag_input {
753 .tag_input {
754 margin-bottom: @padding;
754 margin-bottom: @padding;
755 }
755 }
756 }
756 }
757
757
758 .locked_input {
758 .locked_input {
759 position: relative;
759 position: relative;
760
760
761 input {
761 input {
762 display: inline;
762 display: inline;
763 margin-top: 3px;
763 margin-top: 3px;
764 }
764 }
765
765
766 br {
766 br {
767 display: none;
767 display: none;
768 }
768 }
769
769
770 .error-message {
770 .error-message {
771 float: left;
771 float: left;
772 width: 100%;
772 width: 100%;
773 }
773 }
774
774
775 .lock_input_button {
775 .lock_input_button {
776 display: inline;
776 display: inline;
777 }
777 }
778
778
779 .help-block {
779 .help-block {
780 clear: both;
780 clear: both;
781 }
781 }
782 }
782 }
783
783
784 // Notifications
784 // Notifications
785
785
786 .notifications_buttons {
786 .notifications_buttons {
787 margin: 0 0 @space 0;
787 margin: 0 0 @space 0;
788 padding: 0;
788 padding: 0;
789
789
790 .btn {
790 .btn {
791 display: inline-block;
791 display: inline-block;
792 }
792 }
793 }
793 }
794
794
795 .notification-list {
795 .notification-list {
796
796
797 div {
797 div {
798 display: inline-block;
798 display: inline-block;
799 vertical-align: middle;
799 vertical-align: middle;
800 }
800 }
801
801
802 .container {
802 .container {
803 display: block;
803 display: block;
804 margin: 0 0 @padding 0;
804 margin: 0 0 @padding 0;
805 }
805 }
806
806
807 .delete-notifications {
807 .delete-notifications {
808 margin-left: @padding;
808 margin-left: @padding;
809 text-align: right;
809 text-align: right;
810 cursor: pointer;
810 cursor: pointer;
811 }
811 }
812
812
813 .read-notifications {
813 .read-notifications {
814 margin-left: @padding/2;
814 margin-left: @padding/2;
815 text-align: right;
815 text-align: right;
816 width: 35px;
816 width: 35px;
817 cursor: pointer;
817 cursor: pointer;
818 }
818 }
819
819
820 .icon-minus-sign {
820 .icon-minus-sign {
821 color: @alert2;
821 color: @alert2;
822 }
822 }
823
823
824 .icon-ok-sign {
824 .icon-ok-sign {
825 color: @alert1;
825 color: @alert1;
826 }
826 }
827 }
827 }
828
828
829 .user_settings {
829 .user_settings {
830 float: left;
830 float: left;
831 clear: both;
831 clear: both;
832 display: block;
832 display: block;
833 width: 100%;
833 width: 100%;
834
834
835 .gravatar_box {
835 .gravatar_box {
836 margin-bottom: @padding;
836 margin-bottom: @padding;
837
837
838 &:after {
838 &:after {
839 content: " ";
839 content: " ";
840 clear: both;
840 clear: both;
841 width: 100%;
841 width: 100%;
842 }
842 }
843 }
843 }
844
844
845 .fields .field {
845 .fields .field {
846 clear: both;
846 clear: both;
847 }
847 }
848 }
848 }
849
849
850 .advanced_settings {
850 .advanced_settings {
851 margin-bottom: @space;
851 margin-bottom: @space;
852
852
853 .help-block {
853 .help-block {
854 margin-left: 0;
854 margin-left: 0;
855 }
855 }
856
856
857 button + .help-block {
857 button + .help-block {
858 margin-top: @padding;
858 margin-top: @padding;
859 }
859 }
860 }
860 }
861
861
862 // admin settings radio buttons and labels
862 // admin settings radio buttons and labels
863 .label-2 {
863 .label-2 {
864 float: left;
864 float: left;
865 width: @label2-width;
865 width: @label2-width;
866
866
867 label {
867 label {
868 color: @grey1;
868 color: @grey1;
869 }
869 }
870 }
870 }
871 .checkboxes {
871 .checkboxes {
872 float: left;
872 float: left;
873 width: @checkboxes-width;
873 width: @checkboxes-width;
874 margin-bottom: @padding;
874 margin-bottom: @padding;
875
875
876 .checkbox {
876 .checkbox {
877 width: 100%;
877 width: 100%;
878
878
879 label {
879 label {
880 margin: 0;
880 margin: 0;
881 padding: 0;
881 padding: 0;
882 }
882 }
883 }
883 }
884
884
885 .checkbox + .checkbox {
885 .checkbox + .checkbox {
886 display: inline-block;
886 display: inline-block;
887 }
887 }
888
888
889 label {
889 label {
890 margin-right: 1em;
890 margin-right: 1em;
891 }
891 }
892 }
892 }
893
893
894 // CHANGELOG
894 // CHANGELOG
895 .container_header {
895 .container_header {
896 float: left;
896 float: left;
897 display: block;
897 display: block;
898 width: 100%;
898 width: 100%;
899 margin: @padding 0 @padding;
899 margin: @padding 0 @padding;
900
900
901 #filter_changelog {
901 #filter_changelog {
902 float: left;
902 float: left;
903 margin-right: @padding;
903 margin-right: @padding;
904 }
904 }
905
905
906 .breadcrumbs_light {
906 .breadcrumbs_light {
907 display: inline-block;
907 display: inline-block;
908 }
908 }
909 }
909 }
910
910
911 .info_box {
911 .info_box {
912 float: right;
912 float: right;
913 }
913 }
914
914
915
915
916 #graph_nodes {
916 #graph_nodes {
917 padding-top: 43px;
917 padding-top: 43px;
918 }
918 }
919
919
920 #graph_content{
920 #graph_content{
921
921
922 // adjust for table headers so that graph renders properly
922 // adjust for table headers so that graph renders properly
923 // #graph_nodes padding - table cell padding
923 // #graph_nodes padding - table cell padding
924 padding-top: (@space - (@basefontsize * 2.4));
924 padding-top: (@space - (@basefontsize * 2.4));
925
925
926 &.graph_full_width {
926 &.graph_full_width {
927 width: 100%;
927 width: 100%;
928 max-width: 100%;
928 max-width: 100%;
929 }
929 }
930 }
930 }
931
931
932 #graph {
932 #graph {
933 .flag_status {
933 .flag_status {
934 margin: 0;
934 margin: 0;
935 }
935 }
936
936
937 .pagination-left {
937 .pagination-left {
938 float: left;
938 float: left;
939 clear: both;
939 clear: both;
940 }
940 }
941
941
942 .log-container {
942 .log-container {
943 max-width: 345px;
943 max-width: 345px;
944
944
945 .message{
945 .message{
946 max-width: 340px;
946 max-width: 340px;
947 }
947 }
948 }
948 }
949
949
950 .graph-col-wrapper {
950 .graph-col-wrapper {
951 padding-left: 110px;
951 padding-left: 110px;
952
952
953 #graph_nodes {
953 #graph_nodes {
954 width: 100px;
954 width: 100px;
955 margin-left: -110px;
955 margin-left: -110px;
956 float: left;
956 float: left;
957 clear: left;
957 clear: left;
958 }
958 }
959 }
959 }
960
960
961 .load-more-commits {
961 .load-more-commits {
962 text-align: center;
962 text-align: center;
963 }
963 }
964 .load-more-commits:hover {
964 .load-more-commits:hover {
965 background-color: @grey7;
965 background-color: @grey7;
966 }
966 }
967 .load-more-commits {
967 .load-more-commits {
968 a {
968 a {
969 display: block;
969 display: block;
970 }
970 }
971 }
971 }
972 }
972 }
973
973
974 #filter_changelog {
974 #filter_changelog {
975 float: left;
975 float: left;
976 }
976 }
977
977
978
978
979 //--- THEME ------------------//
979 //--- THEME ------------------//
980
980
981 #logo {
981 #logo {
982 float: left;
982 float: left;
983 margin: 9px 0 0 0;
983 margin: 9px 0 0 0;
984
984
985 .header {
985 .header {
986 background-color: transparent;
986 background-color: transparent;
987 }
987 }
988
988
989 a {
989 a {
990 display: inline-block;
990 display: inline-block;
991 }
991 }
992
992
993 img {
993 img {
994 height:30px;
994 height:30px;
995 }
995 }
996 }
996 }
997
997
998 .logo-wrapper {
998 .logo-wrapper {
999 float:left;
999 float:left;
1000 }
1000 }
1001
1001
1002 .branding{
1002 .branding{
1003 float: left;
1003 float: left;
1004 padding: 9px 2px;
1004 padding: 9px 2px;
1005 line-height: 1em;
1005 line-height: 1em;
1006 font-size: @navigation-fontsize;
1006 font-size: @navigation-fontsize;
1007 }
1007 }
1008
1008
1009 img {
1009 img {
1010 border: none;
1010 border: none;
1011 outline: none;
1011 outline: none;
1012 }
1012 }
1013 user-profile-header
1013 user-profile-header
1014 label {
1014 label {
1015
1015
1016 input[type="checkbox"] {
1016 input[type="checkbox"] {
1017 margin-right: 1em;
1017 margin-right: 1em;
1018 }
1018 }
1019 input[type="radio"] {
1019 input[type="radio"] {
1020 margin-right: 1em;
1020 margin-right: 1em;
1021 }
1021 }
1022 }
1022 }
1023
1023
1024 .flag_status {
1024 .flag_status {
1025 margin: 2px 8px 6px 2px;
1025 margin: 2px 8px 6px 2px;
1026 &.under_review {
1026 &.under_review {
1027 .circle(5px, @alert3);
1027 .circle(5px, @alert3);
1028 }
1028 }
1029 &.approved {
1029 &.approved {
1030 .circle(5px, @alert1);
1030 .circle(5px, @alert1);
1031 }
1031 }
1032 &.rejected,
1032 &.rejected,
1033 &.forced_closed{
1033 &.forced_closed{
1034 .circle(5px, @alert2);
1034 .circle(5px, @alert2);
1035 }
1035 }
1036 &.not_reviewed {
1036 &.not_reviewed {
1037 .circle(5px, @grey5);
1037 .circle(5px, @grey5);
1038 }
1038 }
1039 }
1039 }
1040
1040
1041 .flag_status_comment_box {
1041 .flag_status_comment_box {
1042 margin: 5px 6px 0px 2px;
1042 margin: 5px 6px 0px 2px;
1043 }
1043 }
1044 .test_pattern_preview {
1044 .test_pattern_preview {
1045 margin: @space 0;
1045 margin: @space 0;
1046
1046
1047 p {
1047 p {
1048 margin-bottom: 0;
1048 margin-bottom: 0;
1049 border-bottom: @border-thickness solid @border-default-color;
1049 border-bottom: @border-thickness solid @border-default-color;
1050 color: @grey3;
1050 color: @grey3;
1051 }
1051 }
1052
1052
1053 .btn {
1053 .btn {
1054 margin-bottom: @padding;
1054 margin-bottom: @padding;
1055 }
1055 }
1056 }
1056 }
1057 #test_pattern_result {
1057 #test_pattern_result {
1058 display: none;
1058 display: none;
1059 &:extend(pre);
1059 &:extend(pre);
1060 padding: .9em;
1060 padding: .9em;
1061 color: @grey3;
1061 color: @grey3;
1062 background-color: @grey7;
1062 background-color: @grey7;
1063 border-right: @border-thickness solid @border-default-color;
1063 border-right: @border-thickness solid @border-default-color;
1064 border-bottom: @border-thickness solid @border-default-color;
1064 border-bottom: @border-thickness solid @border-default-color;
1065 border-left: @border-thickness solid @border-default-color;
1065 border-left: @border-thickness solid @border-default-color;
1066 }
1066 }
1067
1067
1068 #repo_vcs_settings {
1068 #repo_vcs_settings {
1069 #inherit_overlay_vcs_default {
1069 #inherit_overlay_vcs_default {
1070 display: none;
1070 display: none;
1071 }
1071 }
1072 #inherit_overlay_vcs_custom {
1072 #inherit_overlay_vcs_custom {
1073 display: custom;
1073 display: custom;
1074 }
1074 }
1075 &.inherited {
1075 &.inherited {
1076 #inherit_overlay_vcs_default {
1076 #inherit_overlay_vcs_default {
1077 display: block;
1077 display: block;
1078 }
1078 }
1079 #inherit_overlay_vcs_custom {
1079 #inherit_overlay_vcs_custom {
1080 display: none;
1080 display: none;
1081 }
1081 }
1082 }
1082 }
1083 }
1083 }
1084
1084
1085 .issue-tracker-link {
1085 .issue-tracker-link {
1086 color: @rcblue;
1086 color: @rcblue;
1087 }
1087 }
1088
1088
1089 // Issue Tracker Table Show/Hide
1089 // Issue Tracker Table Show/Hide
1090 #repo_issue_tracker {
1090 #repo_issue_tracker {
1091 #inherit_overlay {
1091 #inherit_overlay {
1092 display: none;
1092 display: none;
1093 }
1093 }
1094 #custom_overlay {
1094 #custom_overlay {
1095 display: custom;
1095 display: custom;
1096 }
1096 }
1097 &.inherited {
1097 &.inherited {
1098 #inherit_overlay {
1098 #inherit_overlay {
1099 display: block;
1099 display: block;
1100 }
1100 }
1101 #custom_overlay {
1101 #custom_overlay {
1102 display: none;
1102 display: none;
1103 }
1103 }
1104 }
1104 }
1105 }
1105 }
1106 table.issuetracker {
1106 table.issuetracker {
1107 &.readonly {
1107 &.readonly {
1108 tr, td {
1108 tr, td {
1109 color: @grey3;
1109 color: @grey3;
1110 }
1110 }
1111 }
1111 }
1112 .edit {
1112 .edit {
1113 display: none;
1113 display: none;
1114 }
1114 }
1115 .editopen {
1115 .editopen {
1116 .edit {
1116 .edit {
1117 display: inline;
1117 display: inline;
1118 }
1118 }
1119 .entry {
1119 .entry {
1120 display: none;
1120 display: none;
1121 }
1121 }
1122 }
1122 }
1123 tr td.td-action {
1123 tr td.td-action {
1124 min-width: 117px;
1124 min-width: 117px;
1125 }
1125 }
1126 td input {
1126 td input {
1127 max-width: none;
1127 max-width: none;
1128 min-width: 30px;
1128 min-width: 30px;
1129 width: 80%;
1129 width: 80%;
1130 }
1130 }
1131 .issuetracker_pref input {
1131 .issuetracker_pref input {
1132 width: 40%;
1132 width: 40%;
1133 }
1133 }
1134 input.edit_issuetracker_update {
1134 input.edit_issuetracker_update {
1135 margin-right: 0;
1135 margin-right: 0;
1136 width: auto;
1136 width: auto;
1137 }
1137 }
1138 }
1138 }
1139
1139
1140 table.integrations {
1140 table.integrations {
1141 .td-icon {
1141 .td-icon {
1142 width: 20px;
1142 width: 20px;
1143 .integration-icon {
1143 .integration-icon {
1144 height: 20px;
1144 height: 20px;
1145 width: 20px;
1145 width: 20px;
1146 }
1146 }
1147 }
1147 }
1148 }
1148 }
1149
1149
1150 .integrations {
1150 .integrations {
1151 a.integration-box {
1151 a.integration-box {
1152 color: @text-color;
1152 color: @text-color;
1153 &:hover {
1153 &:hover {
1154 .panel {
1154 .panel {
1155 background: #fbfbfb;
1155 background: #fbfbfb;
1156 }
1156 }
1157 }
1157 }
1158 .integration-icon {
1158 .integration-icon {
1159 width: 30px;
1159 width: 30px;
1160 height: 30px;
1160 height: 30px;
1161 margin-right: 20px;
1161 margin-right: 20px;
1162 float: left;
1162 float: left;
1163 }
1163 }
1164
1164
1165 .panel-body {
1165 .panel-body {
1166 padding: 10px;
1166 padding: 10px;
1167 }
1167 }
1168 .panel {
1168 .panel {
1169 margin-bottom: 10px;
1169 margin-bottom: 10px;
1170 }
1170 }
1171 h2 {
1171 h2 {
1172 display: inline-block;
1172 display: inline-block;
1173 margin: 0;
1173 margin: 0;
1174 min-width: 140px;
1174 min-width: 140px;
1175 }
1175 }
1176 }
1176 }
1177 }
1177 }
1178
1178
1179 //Permissions Settings
1179 //Permissions Settings
1180 #add_perm {
1180 #add_perm {
1181 margin: 0 0 @padding;
1181 margin: 0 0 @padding;
1182 cursor: pointer;
1182 cursor: pointer;
1183 }
1183 }
1184
1184
1185 .perm_ac {
1185 .perm_ac {
1186 input {
1186 input {
1187 width: 95%;
1187 width: 95%;
1188 }
1188 }
1189 }
1189 }
1190
1190
1191 .autocomplete-suggestions {
1191 .autocomplete-suggestions {
1192 width: auto !important; // overrides autocomplete.js
1192 width: auto !important; // overrides autocomplete.js
1193 margin: 0;
1193 margin: 0;
1194 border: @border-thickness solid @rcblue;
1194 border: @border-thickness solid @rcblue;
1195 border-radius: @border-radius;
1195 border-radius: @border-radius;
1196 color: @rcblue;
1196 color: @rcblue;
1197 background-color: white;
1197 background-color: white;
1198 }
1198 }
1199 .autocomplete-selected {
1199 .autocomplete-selected {
1200 background: #F0F0F0;
1200 background: #F0F0F0;
1201 }
1201 }
1202 .ac-container-wrap {
1202 .ac-container-wrap {
1203 margin: 0;
1203 margin: 0;
1204 padding: 8px;
1204 padding: 8px;
1205 border-bottom: @border-thickness solid @rclightblue;
1205 border-bottom: @border-thickness solid @rclightblue;
1206 list-style-type: none;
1206 list-style-type: none;
1207 cursor: pointer;
1207 cursor: pointer;
1208
1208
1209 &:hover {
1209 &:hover {
1210 background-color: @rclightblue;
1210 background-color: @rclightblue;
1211 }
1211 }
1212
1212
1213 img {
1213 img {
1214 height: @gravatar-size;
1214 height: @gravatar-size;
1215 width: @gravatar-size;
1215 width: @gravatar-size;
1216 margin-right: 1em;
1216 margin-right: 1em;
1217 }
1217 }
1218
1218
1219 strong {
1219 strong {
1220 font-weight: normal;
1220 font-weight: normal;
1221 }
1221 }
1222 }
1222 }
1223
1223
1224 // Settings Dropdown
1224 // Settings Dropdown
1225 .user-menu .container {
1225 .user-menu .container {
1226 padding: 0 4px;
1226 padding: 0 4px;
1227 margin: 0;
1227 margin: 0;
1228 }
1228 }
1229
1229
1230 .user-menu .gravatar {
1230 .user-menu .gravatar {
1231 cursor: pointer;
1231 cursor: pointer;
1232 }
1232 }
1233
1233
1234 .codeblock {
1234 .codeblock {
1235 margin-bottom: @padding;
1235 margin-bottom: @padding;
1236 clear: both;
1236 clear: both;
1237
1237
1238 .stats{
1238 .stats{
1239 overflow: hidden;
1239 overflow: hidden;
1240 }
1240 }
1241
1241
1242 .message{
1242 .message{
1243 textarea{
1243 textarea{
1244 margin: 0;
1244 margin: 0;
1245 }
1245 }
1246 }
1246 }
1247
1247
1248 .code-header {
1248 .code-header {
1249 .stats {
1249 .stats {
1250 line-height: 2em;
1250 line-height: 2em;
1251
1251
1252 .revision_id {
1252 .revision_id {
1253 margin-left: 0;
1253 margin-left: 0;
1254 }
1254 }
1255 .buttons {
1255 .buttons {
1256 padding-right: 0;
1256 padding-right: 0;
1257 }
1257 }
1258 }
1258 }
1259
1259
1260 .item{
1260 .item{
1261 margin-right: 0.5em;
1261 margin-right: 0.5em;
1262 }
1262 }
1263 }
1263 }
1264
1264
1265 #editor_container{
1265 #editor_container{
1266 position: relative;
1266 position: relative;
1267 margin: @padding;
1267 margin: @padding;
1268 }
1268 }
1269 }
1269 }
1270
1270
1271 #file_history_container {
1271 #file_history_container {
1272 display: none;
1272 display: none;
1273 }
1273 }
1274
1274
1275 .file-history-inner {
1275 .file-history-inner {
1276 margin-bottom: 10px;
1276 margin-bottom: 10px;
1277 }
1277 }
1278
1278
1279 // Pull Requests
1279 // Pull Requests
1280 .summary-details {
1280 .summary-details {
1281 width: 72%;
1281 width: 72%;
1282 }
1282 }
1283 .pr-summary {
1283 .pr-summary {
1284 border-bottom: @border-thickness solid @grey5;
1284 border-bottom: @border-thickness solid @grey5;
1285 margin-bottom: @space;
1285 margin-bottom: @space;
1286 }
1286 }
1287 .reviewers-title {
1287 .reviewers-title {
1288 width: 25%;
1288 width: 25%;
1289 min-width: 200px;
1289 min-width: 200px;
1290 }
1290 }
1291 .reviewers {
1291 .reviewers {
1292 width: 25%;
1292 width: 25%;
1293 min-width: 200px;
1293 min-width: 200px;
1294 }
1294 }
1295 .reviewers ul li {
1295 .reviewers ul li {
1296 position: relative;
1296 position: relative;
1297 width: 100%;
1297 width: 100%;
1298 margin-bottom: 8px;
1298 margin-bottom: 8px;
1299 }
1299 }
1300 .reviewers_member {
1300 .reviewers_member {
1301 width: 100%;
1301 width: 100%;
1302 overflow: auto;
1302 overflow: auto;
1303 }
1303 }
1304 .reviewer_reason {
1304 .reviewer_reason {
1305 padding-left: 20px;
1305 padding-left: 20px;
1306 }
1306 }
1307 .reviewer_status {
1307 .reviewer_status {
1308 display: inline-block;
1308 display: inline-block;
1309 vertical-align: top;
1309 vertical-align: top;
1310 width: 7%;
1310 width: 7%;
1311 min-width: 20px;
1311 min-width: 20px;
1312 height: 1.2em;
1312 height: 1.2em;
1313 margin-top: 3px;
1313 margin-top: 3px;
1314 line-height: 1em;
1314 line-height: 1em;
1315 }
1315 }
1316
1316
1317 .reviewer_name {
1317 .reviewer_name {
1318 display: inline-block;
1318 display: inline-block;
1319 max-width: 83%;
1319 max-width: 83%;
1320 padding-right: 20px;
1320 padding-right: 20px;
1321 vertical-align: middle;
1321 vertical-align: middle;
1322 line-height: 1;
1322 line-height: 1;
1323
1323
1324 .rc-user {
1324 .rc-user {
1325 min-width: 0;
1325 min-width: 0;
1326 margin: -2px 1em 0 0;
1326 margin: -2px 1em 0 0;
1327 }
1327 }
1328
1328
1329 .reviewer {
1329 .reviewer {
1330 float: left;
1330 float: left;
1331 }
1331 }
1332
1333 &.to-delete {
1334 .user,
1335 .reviewer {
1336 text-decoration: line-through;
1337 }
1338 }
1339 }
1332 }
1340
1333
1341 .reviewer_member_remove {
1334 .reviewer_member_remove {
1342 position: absolute;
1335 position: absolute;
1343 right: 0;
1336 right: 0;
1344 top: 0;
1337 top: 0;
1345 width: 16px;
1338 width: 16px;
1346 margin-bottom: 10px;
1339 margin-bottom: 10px;
1347 padding: 0;
1340 padding: 0;
1348 color: black;
1341 color: black;
1349 }
1342 }
1350 .reviewer_member_status {
1343 .reviewer_member_status {
1351 margin-top: 5px;
1344 margin-top: 5px;
1352 }
1345 }
1353 .pr-summary #summary{
1346 .pr-summary #summary{
1354 width: 100%;
1347 width: 100%;
1355 }
1348 }
1356 .pr-summary .action_button:hover {
1349 .pr-summary .action_button:hover {
1357 border: 0;
1350 border: 0;
1358 cursor: pointer;
1351 cursor: pointer;
1359 }
1352 }
1360 .pr-details-title {
1353 .pr-details-title {
1361 padding-bottom: 8px;
1354 padding-bottom: 8px;
1362 border-bottom: @border-thickness solid @grey5;
1355 border-bottom: @border-thickness solid @grey5;
1363
1356
1364 .action_button.disabled {
1357 .action_button.disabled {
1365 color: @grey4;
1358 color: @grey4;
1366 cursor: inherit;
1359 cursor: inherit;
1367 }
1360 }
1368 .action_button {
1361 .action_button {
1369 color: @rcblue;
1362 color: @rcblue;
1370 }
1363 }
1371 }
1364 }
1372 .pr-details-content {
1365 .pr-details-content {
1373 margin-top: @textmargin;
1366 margin-top: @textmargin;
1374 margin-bottom: @textmargin;
1367 margin-bottom: @textmargin;
1375 }
1368 }
1376 .pr-description {
1369 .pr-description {
1377 white-space:pre-wrap;
1370 white-space:pre-wrap;
1378 }
1371 }
1379 .group_members {
1372 .group_members {
1380 margin-top: 0;
1373 margin-top: 0;
1381 padding: 0;
1374 padding: 0;
1382 list-style: outside none none;
1375 list-style: outside none none;
1383
1376
1384 img {
1377 img {
1385 height: @gravatar-size;
1378 height: @gravatar-size;
1386 width: @gravatar-size;
1379 width: @gravatar-size;
1387 margin-right: .5em;
1380 margin-right: .5em;
1388 margin-left: 3px;
1381 margin-left: 3px;
1389 }
1382 }
1390
1383
1391 .to-delete {
1384 .to-delete {
1392 .user {
1385 .user {
1393 text-decoration: line-through;
1386 text-decoration: line-through;
1394 }
1387 }
1395 }
1388 }
1396 }
1389 }
1397
1390
1398 .compare_view_commits_title {
1391 .compare_view_commits_title {
1399 .disabled {
1392 .disabled {
1400 cursor: inherit;
1393 cursor: inherit;
1401 &:hover{
1394 &:hover{
1402 background-color: inherit;
1395 background-color: inherit;
1403 color: inherit;
1396 color: inherit;
1404 }
1397 }
1405 }
1398 }
1406 }
1399 }
1407
1400
1408 .subtitle-compare {
1401 .subtitle-compare {
1409 margin: -15px 0px 0px 0px;
1402 margin: -15px 0px 0px 0px;
1410 }
1403 }
1411
1404
1412 .comments-summary-td {
1405 .comments-summary-td {
1413 border-top: 1px dashed @grey5;
1406 border-top: 1px dashed @grey5;
1414 }
1407 }
1415
1408
1416 // new entry in group_members
1409 // new entry in group_members
1417 .td-author-new-entry {
1410 .td-author-new-entry {
1418 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1411 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1419 }
1412 }
1420
1413
1421 .usergroup_member_remove {
1414 .usergroup_member_remove {
1422 width: 16px;
1415 width: 16px;
1423 margin-bottom: 10px;
1416 margin-bottom: 10px;
1424 padding: 0;
1417 padding: 0;
1425 color: black !important;
1418 color: black !important;
1426 cursor: pointer;
1419 cursor: pointer;
1427 }
1420 }
1428
1421
1429 .reviewer_ac .ac-input {
1422 .reviewer_ac .ac-input {
1430 width: 92%;
1423 width: 92%;
1431 margin-bottom: 1em;
1424 margin-bottom: 1em;
1432 }
1425 }
1433
1426
1434 .compare_view_commits tr{
1427 .compare_view_commits tr{
1435 height: 20px;
1428 height: 20px;
1436 }
1429 }
1437 .compare_view_commits td {
1430 .compare_view_commits td {
1438 vertical-align: top;
1431 vertical-align: top;
1439 padding-top: 10px;
1432 padding-top: 10px;
1440 }
1433 }
1441 .compare_view_commits .author {
1434 .compare_view_commits .author {
1442 margin-left: 5px;
1435 margin-left: 5px;
1443 }
1436 }
1444
1437
1445 .compare_view_commits {
1438 .compare_view_commits {
1446 .color-a {
1439 .color-a {
1447 color: @alert1;
1440 color: @alert1;
1448 }
1441 }
1449
1442
1450 .color-c {
1443 .color-c {
1451 color: @color3;
1444 color: @color3;
1452 }
1445 }
1453
1446
1454 .color-r {
1447 .color-r {
1455 color: @color5;
1448 color: @color5;
1456 }
1449 }
1457
1450
1458 .color-a-bg {
1451 .color-a-bg {
1459 background-color: @alert1;
1452 background-color: @alert1;
1460 }
1453 }
1461
1454
1462 .color-c-bg {
1455 .color-c-bg {
1463 background-color: @alert3;
1456 background-color: @alert3;
1464 }
1457 }
1465
1458
1466 .color-r-bg {
1459 .color-r-bg {
1467 background-color: @alert2;
1460 background-color: @alert2;
1468 }
1461 }
1469
1462
1470 .color-a-border {
1463 .color-a-border {
1471 border: 1px solid @alert1;
1464 border: 1px solid @alert1;
1472 }
1465 }
1473
1466
1474 .color-c-border {
1467 .color-c-border {
1475 border: 1px solid @alert3;
1468 border: 1px solid @alert3;
1476 }
1469 }
1477
1470
1478 .color-r-border {
1471 .color-r-border {
1479 border: 1px solid @alert2;
1472 border: 1px solid @alert2;
1480 }
1473 }
1481
1474
1482 .commit-change-indicator {
1475 .commit-change-indicator {
1483 width: 15px;
1476 width: 15px;
1484 height: 15px;
1477 height: 15px;
1485 position: relative;
1478 position: relative;
1486 left: 15px;
1479 left: 15px;
1487 }
1480 }
1488
1481
1489 .commit-change-content {
1482 .commit-change-content {
1490 text-align: center;
1483 text-align: center;
1491 vertical-align: middle;
1484 vertical-align: middle;
1492 line-height: 15px;
1485 line-height: 15px;
1493 }
1486 }
1494 }
1487 }
1495
1488
1496 .compare_view_files {
1489 .compare_view_files {
1497 width: 100%;
1490 width: 100%;
1498
1491
1499 td {
1492 td {
1500 vertical-align: middle;
1493 vertical-align: middle;
1501 }
1494 }
1502 }
1495 }
1503
1496
1504 .compare_view_filepath {
1497 .compare_view_filepath {
1505 color: @grey1;
1498 color: @grey1;
1506 }
1499 }
1507
1500
1508 .show_more {
1501 .show_more {
1509 display: inline-block;
1502 display: inline-block;
1510 position: relative;
1503 position: relative;
1511 vertical-align: middle;
1504 vertical-align: middle;
1512 width: 4px;
1505 width: 4px;
1513 height: @basefontsize;
1506 height: @basefontsize;
1514
1507
1515 &:after {
1508 &:after {
1516 content: "\00A0\25BE";
1509 content: "\00A0\25BE";
1517 display: inline-block;
1510 display: inline-block;
1518 width:10px;
1511 width:10px;
1519 line-height: 5px;
1512 line-height: 5px;
1520 font-size: 12px;
1513 font-size: 12px;
1521 cursor: pointer;
1514 cursor: pointer;
1522 }
1515 }
1523 }
1516 }
1524
1517
1525 .journal_more .show_more {
1518 .journal_more .show_more {
1526 display: inline;
1519 display: inline;
1527
1520
1528 &:after {
1521 &:after {
1529 content: none;
1522 content: none;
1530 }
1523 }
1531 }
1524 }
1532
1525
1533 .open .show_more:after,
1526 .open .show_more:after,
1534 .select2-dropdown-open .show_more:after {
1527 .select2-dropdown-open .show_more:after {
1535 .rotate(180deg);
1528 .rotate(180deg);
1536 margin-left: 4px;
1529 margin-left: 4px;
1537 }
1530 }
1538
1531
1539
1532
1540 .compare_view_commits .collapse_commit:after {
1533 .compare_view_commits .collapse_commit:after {
1541 cursor: pointer;
1534 cursor: pointer;
1542 content: "\00A0\25B4";
1535 content: "\00A0\25B4";
1543 margin-left: -3px;
1536 margin-left: -3px;
1544 font-size: 17px;
1537 font-size: 17px;
1545 color: @grey4;
1538 color: @grey4;
1546 }
1539 }
1547
1540
1548 .diff_links {
1541 .diff_links {
1549 margin-left: 8px;
1542 margin-left: 8px;
1550 }
1543 }
1551
1544
1552 div.ancestor {
1545 div.ancestor {
1553 margin: -30px 0px;
1546 margin: -30px 0px;
1554 }
1547 }
1555
1548
1556 .cs_icon_td input[type="checkbox"] {
1549 .cs_icon_td input[type="checkbox"] {
1557 display: none;
1550 display: none;
1558 }
1551 }
1559
1552
1560 .cs_icon_td .expand_file_icon:after {
1553 .cs_icon_td .expand_file_icon:after {
1561 cursor: pointer;
1554 cursor: pointer;
1562 content: "\00A0\25B6";
1555 content: "\00A0\25B6";
1563 font-size: 12px;
1556 font-size: 12px;
1564 color: @grey4;
1557 color: @grey4;
1565 }
1558 }
1566
1559
1567 .cs_icon_td .collapse_file_icon:after {
1560 .cs_icon_td .collapse_file_icon:after {
1568 cursor: pointer;
1561 cursor: pointer;
1569 content: "\00A0\25BC";
1562 content: "\00A0\25BC";
1570 font-size: 12px;
1563 font-size: 12px;
1571 color: @grey4;
1564 color: @grey4;
1572 }
1565 }
1573
1566
1574 /*new binary
1567 /*new binary
1575 NEW_FILENODE = 1
1568 NEW_FILENODE = 1
1576 DEL_FILENODE = 2
1569 DEL_FILENODE = 2
1577 MOD_FILENODE = 3
1570 MOD_FILENODE = 3
1578 RENAMED_FILENODE = 4
1571 RENAMED_FILENODE = 4
1579 COPIED_FILENODE = 5
1572 COPIED_FILENODE = 5
1580 CHMOD_FILENODE = 6
1573 CHMOD_FILENODE = 6
1581 BIN_FILENODE = 7
1574 BIN_FILENODE = 7
1582 */
1575 */
1583 .cs_files_expand {
1576 .cs_files_expand {
1584 font-size: @basefontsize + 5px;
1577 font-size: @basefontsize + 5px;
1585 line-height: 1.8em;
1578 line-height: 1.8em;
1586 float: right;
1579 float: right;
1587 }
1580 }
1588
1581
1589 .cs_files_expand span{
1582 .cs_files_expand span{
1590 color: @rcblue;
1583 color: @rcblue;
1591 cursor: pointer;
1584 cursor: pointer;
1592 }
1585 }
1593 .cs_files {
1586 .cs_files {
1594 clear: both;
1587 clear: both;
1595 padding-bottom: @padding;
1588 padding-bottom: @padding;
1596
1589
1597 .cur_cs {
1590 .cur_cs {
1598 margin: 10px 2px;
1591 margin: 10px 2px;
1599 font-weight: bold;
1592 font-weight: bold;
1600 }
1593 }
1601
1594
1602 .node {
1595 .node {
1603 float: left;
1596 float: left;
1604 }
1597 }
1605
1598
1606 .changes {
1599 .changes {
1607 float: right;
1600 float: right;
1608 color: white;
1601 color: white;
1609 font-size: @basefontsize - 4px;
1602 font-size: @basefontsize - 4px;
1610 margin-top: 4px;
1603 margin-top: 4px;
1611 opacity: 0.6;
1604 opacity: 0.6;
1612 filter: Alpha(opacity=60); /* IE8 and earlier */
1605 filter: Alpha(opacity=60); /* IE8 and earlier */
1613
1606
1614 .added {
1607 .added {
1615 background-color: @alert1;
1608 background-color: @alert1;
1616 float: left;
1609 float: left;
1617 text-align: center;
1610 text-align: center;
1618 }
1611 }
1619
1612
1620 .deleted {
1613 .deleted {
1621 background-color: @alert2;
1614 background-color: @alert2;
1622 float: left;
1615 float: left;
1623 text-align: center;
1616 text-align: center;
1624 }
1617 }
1625
1618
1626 .bin {
1619 .bin {
1627 background-color: @alert1;
1620 background-color: @alert1;
1628 text-align: center;
1621 text-align: center;
1629 }
1622 }
1630
1623
1631 /*new binary*/
1624 /*new binary*/
1632 .bin.bin1 {
1625 .bin.bin1 {
1633 background-color: @alert1;
1626 background-color: @alert1;
1634 text-align: center;
1627 text-align: center;
1635 }
1628 }
1636
1629
1637 /*deleted binary*/
1630 /*deleted binary*/
1638 .bin.bin2 {
1631 .bin.bin2 {
1639 background-color: @alert2;
1632 background-color: @alert2;
1640 text-align: center;
1633 text-align: center;
1641 }
1634 }
1642
1635
1643 /*mod binary*/
1636 /*mod binary*/
1644 .bin.bin3 {
1637 .bin.bin3 {
1645 background-color: @grey2;
1638 background-color: @grey2;
1646 text-align: center;
1639 text-align: center;
1647 }
1640 }
1648
1641
1649 /*rename file*/
1642 /*rename file*/
1650 .bin.bin4 {
1643 .bin.bin4 {
1651 background-color: @alert4;
1644 background-color: @alert4;
1652 text-align: center;
1645 text-align: center;
1653 }
1646 }
1654
1647
1655 /*copied file*/
1648 /*copied file*/
1656 .bin.bin5 {
1649 .bin.bin5 {
1657 background-color: @alert4;
1650 background-color: @alert4;
1658 text-align: center;
1651 text-align: center;
1659 }
1652 }
1660
1653
1661 /*chmod file*/
1654 /*chmod file*/
1662 .bin.bin6 {
1655 .bin.bin6 {
1663 background-color: @grey2;
1656 background-color: @grey2;
1664 text-align: center;
1657 text-align: center;
1665 }
1658 }
1666 }
1659 }
1667 }
1660 }
1668
1661
1669 .cs_files .cs_added, .cs_files .cs_A,
1662 .cs_files .cs_added, .cs_files .cs_A,
1670 .cs_files .cs_added, .cs_files .cs_M,
1663 .cs_files .cs_added, .cs_files .cs_M,
1671 .cs_files .cs_added, .cs_files .cs_D {
1664 .cs_files .cs_added, .cs_files .cs_D {
1672 height: 16px;
1665 height: 16px;
1673 padding-right: 10px;
1666 padding-right: 10px;
1674 margin-top: 7px;
1667 margin-top: 7px;
1675 text-align: left;
1668 text-align: left;
1676 }
1669 }
1677
1670
1678 .cs_icon_td {
1671 .cs_icon_td {
1679 min-width: 16px;
1672 min-width: 16px;
1680 width: 16px;
1673 width: 16px;
1681 }
1674 }
1682
1675
1683 .pull-request-merge {
1676 .pull-request-merge {
1684 border: 1px solid @grey5;
1677 border: 1px solid @grey5;
1685 padding: 10px 0px 20px;
1678 padding: 10px 0px 20px;
1686 margin-top: 10px;
1679 margin-top: 10px;
1687 margin-bottom: 20px;
1680 margin-bottom: 20px;
1688 }
1681 }
1689
1682
1690 .pull-request-merge ul {
1683 .pull-request-merge ul {
1691 padding: 0px 0px;
1684 padding: 0px 0px;
1692 }
1685 }
1693
1686
1694 .pull-request-merge li:before{
1687 .pull-request-merge li:before{
1695 content:none;
1688 content:none;
1696 }
1689 }
1697
1690
1698 .pull-request-merge .pull-request-wrap {
1691 .pull-request-merge .pull-request-wrap {
1699 height: auto;
1692 height: auto;
1700 padding: 0px 0px;
1693 padding: 0px 0px;
1701 text-align: right;
1694 text-align: right;
1702 }
1695 }
1703
1696
1704 .pull-request-merge span {
1697 .pull-request-merge span {
1705 margin-right: 5px;
1698 margin-right: 5px;
1706 }
1699 }
1707
1700
1708 .pull-request-merge-actions {
1701 .pull-request-merge-actions {
1709 height: 30px;
1702 height: 30px;
1710 padding: 0px 0px;
1703 padding: 0px 0px;
1711 }
1704 }
1712
1705
1713 .merge-status {
1706 .merge-status {
1714 margin-right: 5px;
1707 margin-right: 5px;
1715 }
1708 }
1716
1709
1717 .merge-message {
1710 .merge-message {
1718 font-size: 1.2em
1711 font-size: 1.2em
1719 }
1712 }
1720
1713
1721 .merge-message.success i,
1714 .merge-message.success i,
1722 .merge-icon.success i {
1715 .merge-icon.success i {
1723 color:@alert1;
1716 color:@alert1;
1724 }
1717 }
1725
1718
1726 .merge-message.warning i,
1719 .merge-message.warning i,
1727 .merge-icon.warning i {
1720 .merge-icon.warning i {
1728 color: @alert3;
1721 color: @alert3;
1729 }
1722 }
1730
1723
1731 .merge-message.error i,
1724 .merge-message.error i,
1732 .merge-icon.error i {
1725 .merge-icon.error i {
1733 color:@alert2;
1726 color:@alert2;
1734 }
1727 }
1735
1728
1736 .pr-versions {
1729 .pr-versions {
1737 font-size: 1.1em;
1730 font-size: 1.1em;
1738
1731
1739 table {
1732 table {
1740 padding: 0px 5px;
1733 padding: 0px 5px;
1741 }
1734 }
1742
1735
1743 td {
1736 td {
1744 line-height: 15px;
1737 line-height: 15px;
1745 }
1738 }
1746
1739
1747 .flag_status {
1740 .flag_status {
1748 margin: 0;
1741 margin: 0;
1749 }
1742 }
1750
1743
1751 .compare-radio-button {
1744 .compare-radio-button {
1752 position: relative;
1745 position: relative;
1753 top: -3px;
1746 top: -3px;
1754 }
1747 }
1755 }
1748 }
1756
1749
1757
1750
1758 #close_pull_request {
1751 #close_pull_request {
1759 margin-right: 0px;
1752 margin-right: 0px;
1760 }
1753 }
1761
1754
1762 .empty_data {
1755 .empty_data {
1763 color: @grey4;
1756 color: @grey4;
1764 }
1757 }
1765
1758
1766 #changeset_compare_view_content {
1759 #changeset_compare_view_content {
1767 margin-bottom: @space;
1760 margin-bottom: @space;
1768 clear: both;
1761 clear: both;
1769 width: 100%;
1762 width: 100%;
1770 box-sizing: border-box;
1763 box-sizing: border-box;
1771 .border-radius(@border-radius);
1764 .border-radius(@border-radius);
1772
1765
1773 .help-block {
1766 .help-block {
1774 margin: @padding 0;
1767 margin: @padding 0;
1775 color: @text-color;
1768 color: @text-color;
1776 }
1769 }
1777
1770
1778 .empty_data {
1771 .empty_data {
1779 margin: @padding 0;
1772 margin: @padding 0;
1780 }
1773 }
1781
1774
1782 .alert {
1775 .alert {
1783 margin-bottom: @space;
1776 margin-bottom: @space;
1784 }
1777 }
1785 }
1778 }
1786
1779
1787 .table_disp {
1780 .table_disp {
1788 .status {
1781 .status {
1789 width: auto;
1782 width: auto;
1790
1783
1791 .flag_status {
1784 .flag_status {
1792 float: left;
1785 float: left;
1793 }
1786 }
1794 }
1787 }
1795 }
1788 }
1796
1789
1797 .status_box_menu {
1790 .status_box_menu {
1798 margin: 0;
1791 margin: 0;
1799 }
1792 }
1800
1793
1801 .notification-table{
1794 .notification-table{
1802 margin-bottom: @space;
1795 margin-bottom: @space;
1803 display: table;
1796 display: table;
1804 width: 100%;
1797 width: 100%;
1805
1798
1806 .container{
1799 .container{
1807 display: table-row;
1800 display: table-row;
1808
1801
1809 .notification-header{
1802 .notification-header{
1810 border-bottom: @border-thickness solid @border-default-color;
1803 border-bottom: @border-thickness solid @border-default-color;
1811 }
1804 }
1812
1805
1813 .notification-subject{
1806 .notification-subject{
1814 display: table-cell;
1807 display: table-cell;
1815 }
1808 }
1816 }
1809 }
1817 }
1810 }
1818
1811
1819 // Notifications
1812 // Notifications
1820 .notification-header{
1813 .notification-header{
1821 display: table;
1814 display: table;
1822 width: 100%;
1815 width: 100%;
1823 padding: floor(@basefontsize/2) 0;
1816 padding: floor(@basefontsize/2) 0;
1824 line-height: 1em;
1817 line-height: 1em;
1825
1818
1826 .desc, .delete-notifications, .read-notifications{
1819 .desc, .delete-notifications, .read-notifications{
1827 display: table-cell;
1820 display: table-cell;
1828 text-align: left;
1821 text-align: left;
1829 }
1822 }
1830
1823
1831 .desc{
1824 .desc{
1832 width: 1163px;
1825 width: 1163px;
1833 }
1826 }
1834
1827
1835 .delete-notifications, .read-notifications{
1828 .delete-notifications, .read-notifications{
1836 width: 35px;
1829 width: 35px;
1837 min-width: 35px; //fixes when only one button is displayed
1830 min-width: 35px; //fixes when only one button is displayed
1838 }
1831 }
1839 }
1832 }
1840
1833
1841 .notification-body {
1834 .notification-body {
1842 .markdown-block,
1835 .markdown-block,
1843 .rst-block {
1836 .rst-block {
1844 padding: @padding 0;
1837 padding: @padding 0;
1845 }
1838 }
1846
1839
1847 .notification-subject {
1840 .notification-subject {
1848 padding: @textmargin 0;
1841 padding: @textmargin 0;
1849 border-bottom: @border-thickness solid @border-default-color;
1842 border-bottom: @border-thickness solid @border-default-color;
1850 }
1843 }
1851 }
1844 }
1852
1845
1853
1846
1854 .notifications_buttons{
1847 .notifications_buttons{
1855 float: right;
1848 float: right;
1856 }
1849 }
1857
1850
1858 #notification-status{
1851 #notification-status{
1859 display: inline;
1852 display: inline;
1860 }
1853 }
1861
1854
1862 // Repositories
1855 // Repositories
1863
1856
1864 #summary.fields{
1857 #summary.fields{
1865 display: table;
1858 display: table;
1866
1859
1867 .field{
1860 .field{
1868 display: table-row;
1861 display: table-row;
1869
1862
1870 .label-summary{
1863 .label-summary{
1871 display: table-cell;
1864 display: table-cell;
1872 min-width: @label-summary-minwidth;
1865 min-width: @label-summary-minwidth;
1873 padding-top: @padding/2;
1866 padding-top: @padding/2;
1874 padding-bottom: @padding/2;
1867 padding-bottom: @padding/2;
1875 padding-right: @padding/2;
1868 padding-right: @padding/2;
1876 }
1869 }
1877
1870
1878 .input{
1871 .input{
1879 display: table-cell;
1872 display: table-cell;
1880 padding: @padding/2;
1873 padding: @padding/2;
1881
1874
1882 input{
1875 input{
1883 min-width: 29em;
1876 min-width: 29em;
1884 padding: @padding/4;
1877 padding: @padding/4;
1885 }
1878 }
1886 }
1879 }
1887 .statistics, .downloads{
1880 .statistics, .downloads{
1888 .disabled{
1881 .disabled{
1889 color: @grey4;
1882 color: @grey4;
1890 }
1883 }
1891 }
1884 }
1892 }
1885 }
1893 }
1886 }
1894
1887
1895 #summary{
1888 #summary{
1896 width: 70%;
1889 width: 70%;
1897 }
1890 }
1898
1891
1899
1892
1900 // Journal
1893 // Journal
1901 .journal.title {
1894 .journal.title {
1902 h5 {
1895 h5 {
1903 float: left;
1896 float: left;
1904 margin: 0;
1897 margin: 0;
1905 width: 70%;
1898 width: 70%;
1906 }
1899 }
1907
1900
1908 ul {
1901 ul {
1909 float: right;
1902 float: right;
1910 display: inline-block;
1903 display: inline-block;
1911 margin: 0;
1904 margin: 0;
1912 width: 30%;
1905 width: 30%;
1913 text-align: right;
1906 text-align: right;
1914
1907
1915 li {
1908 li {
1916 display: inline;
1909 display: inline;
1917 font-size: @journal-fontsize;
1910 font-size: @journal-fontsize;
1918 line-height: 1em;
1911 line-height: 1em;
1919
1912
1920 &:before { content: none; }
1913 &:before { content: none; }
1921 }
1914 }
1922 }
1915 }
1923 }
1916 }
1924
1917
1925 .filterexample {
1918 .filterexample {
1926 position: absolute;
1919 position: absolute;
1927 top: 95px;
1920 top: 95px;
1928 left: @contentpadding;
1921 left: @contentpadding;
1929 color: @rcblue;
1922 color: @rcblue;
1930 font-size: 11px;
1923 font-size: 11px;
1931 font-family: @text-regular;
1924 font-family: @text-regular;
1932 cursor: help;
1925 cursor: help;
1933
1926
1934 &:hover {
1927 &:hover {
1935 color: @rcdarkblue;
1928 color: @rcdarkblue;
1936 }
1929 }
1937
1930
1938 @media (max-width:768px) {
1931 @media (max-width:768px) {
1939 position: relative;
1932 position: relative;
1940 top: auto;
1933 top: auto;
1941 left: auto;
1934 left: auto;
1942 display: block;
1935 display: block;
1943 }
1936 }
1944 }
1937 }
1945
1938
1946
1939
1947 #journal{
1940 #journal{
1948 margin-bottom: @space;
1941 margin-bottom: @space;
1949
1942
1950 .journal_day{
1943 .journal_day{
1951 margin-bottom: @textmargin/2;
1944 margin-bottom: @textmargin/2;
1952 padding-bottom: @textmargin/2;
1945 padding-bottom: @textmargin/2;
1953 font-size: @journal-fontsize;
1946 font-size: @journal-fontsize;
1954 border-bottom: @border-thickness solid @border-default-color;
1947 border-bottom: @border-thickness solid @border-default-color;
1955 }
1948 }
1956
1949
1957 .journal_container{
1950 .journal_container{
1958 margin-bottom: @space;
1951 margin-bottom: @space;
1959
1952
1960 .journal_user{
1953 .journal_user{
1961 display: inline-block;
1954 display: inline-block;
1962 }
1955 }
1963 .journal_action_container{
1956 .journal_action_container{
1964 display: block;
1957 display: block;
1965 margin-top: @textmargin;
1958 margin-top: @textmargin;
1966
1959
1967 div{
1960 div{
1968 display: inline;
1961 display: inline;
1969 }
1962 }
1970
1963
1971 div.journal_action_params{
1964 div.journal_action_params{
1972 display: block;
1965 display: block;
1973 }
1966 }
1974
1967
1975 div.journal_repo:after{
1968 div.journal_repo:after{
1976 content: "\A";
1969 content: "\A";
1977 white-space: pre;
1970 white-space: pre;
1978 }
1971 }
1979
1972
1980 div.date{
1973 div.date{
1981 display: block;
1974 display: block;
1982 margin-bottom: @textmargin;
1975 margin-bottom: @textmargin;
1983 }
1976 }
1984 }
1977 }
1985 }
1978 }
1986 }
1979 }
1987
1980
1988 // Files
1981 // Files
1989 .edit-file-title {
1982 .edit-file-title {
1990 border-bottom: @border-thickness solid @border-default-color;
1983 border-bottom: @border-thickness solid @border-default-color;
1991
1984
1992 .breadcrumbs {
1985 .breadcrumbs {
1993 margin-bottom: 0;
1986 margin-bottom: 0;
1994 }
1987 }
1995 }
1988 }
1996
1989
1997 .edit-file-fieldset {
1990 .edit-file-fieldset {
1998 margin-top: @sidebarpadding;
1991 margin-top: @sidebarpadding;
1999
1992
2000 .fieldset {
1993 .fieldset {
2001 .left-label {
1994 .left-label {
2002 width: 13%;
1995 width: 13%;
2003 }
1996 }
2004 .right-content {
1997 .right-content {
2005 width: 87%;
1998 width: 87%;
2006 max-width: 100%;
1999 max-width: 100%;
2007 }
2000 }
2008 .filename-label {
2001 .filename-label {
2009 margin-top: 13px;
2002 margin-top: 13px;
2010 }
2003 }
2011 .commit-message-label {
2004 .commit-message-label {
2012 margin-top: 4px;
2005 margin-top: 4px;
2013 }
2006 }
2014 .file-upload-input {
2007 .file-upload-input {
2015 input {
2008 input {
2016 display: none;
2009 display: none;
2017 }
2010 }
2018 }
2011 }
2019 p {
2012 p {
2020 margin-top: 5px;
2013 margin-top: 5px;
2021 }
2014 }
2022
2015
2023 }
2016 }
2024 .custom-path-link {
2017 .custom-path-link {
2025 margin-left: 5px;
2018 margin-left: 5px;
2026 }
2019 }
2027 #commit {
2020 #commit {
2028 resize: vertical;
2021 resize: vertical;
2029 }
2022 }
2030 }
2023 }
2031
2024
2032 .delete-file-preview {
2025 .delete-file-preview {
2033 max-height: 250px;
2026 max-height: 250px;
2034 }
2027 }
2035
2028
2036 .new-file,
2029 .new-file,
2037 #filter_activate,
2030 #filter_activate,
2038 #filter_deactivate {
2031 #filter_deactivate {
2039 float: left;
2032 float: left;
2040 margin: 0 0 0 15px;
2033 margin: 0 0 0 15px;
2041 }
2034 }
2042
2035
2043 h3.files_location{
2036 h3.files_location{
2044 line-height: 2.4em;
2037 line-height: 2.4em;
2045 }
2038 }
2046
2039
2047 .browser-nav {
2040 .browser-nav {
2048 display: table;
2041 display: table;
2049 margin-bottom: @space;
2042 margin-bottom: @space;
2050
2043
2051
2044
2052 .info_box {
2045 .info_box {
2053 display: inline-table;
2046 display: inline-table;
2054 height: 2.5em;
2047 height: 2.5em;
2055
2048
2056 .browser-cur-rev, .info_box_elem {
2049 .browser-cur-rev, .info_box_elem {
2057 display: table-cell;
2050 display: table-cell;
2058 vertical-align: middle;
2051 vertical-align: middle;
2059 }
2052 }
2060
2053
2061 .info_box_elem {
2054 .info_box_elem {
2062 border-top: @border-thickness solid @rcblue;
2055 border-top: @border-thickness solid @rcblue;
2063 border-bottom: @border-thickness solid @rcblue;
2056 border-bottom: @border-thickness solid @rcblue;
2064
2057
2065 #at_rev, a {
2058 #at_rev, a {
2066 padding: 0.6em 0.9em;
2059 padding: 0.6em 0.9em;
2067 margin: 0;
2060 margin: 0;
2068 .box-shadow(none);
2061 .box-shadow(none);
2069 border: 0;
2062 border: 0;
2070 height: 12px;
2063 height: 12px;
2071 }
2064 }
2072
2065
2073 input#at_rev {
2066 input#at_rev {
2074 max-width: 50px;
2067 max-width: 50px;
2075 text-align: right;
2068 text-align: right;
2076 }
2069 }
2077
2070
2078 &.previous {
2071 &.previous {
2079 border: @border-thickness solid @rcblue;
2072 border: @border-thickness solid @rcblue;
2080 .disabled {
2073 .disabled {
2081 color: @grey4;
2074 color: @grey4;
2082 cursor: not-allowed;
2075 cursor: not-allowed;
2083 }
2076 }
2084 }
2077 }
2085
2078
2086 &.next {
2079 &.next {
2087 border: @border-thickness solid @rcblue;
2080 border: @border-thickness solid @rcblue;
2088 .disabled {
2081 .disabled {
2089 color: @grey4;
2082 color: @grey4;
2090 cursor: not-allowed;
2083 cursor: not-allowed;
2091 }
2084 }
2092 }
2085 }
2093 }
2086 }
2094
2087
2095 .browser-cur-rev {
2088 .browser-cur-rev {
2096
2089
2097 span{
2090 span{
2098 margin: 0;
2091 margin: 0;
2099 color: @rcblue;
2092 color: @rcblue;
2100 height: 12px;
2093 height: 12px;
2101 display: inline-block;
2094 display: inline-block;
2102 padding: 0.7em 1em ;
2095 padding: 0.7em 1em ;
2103 border: @border-thickness solid @rcblue;
2096 border: @border-thickness solid @rcblue;
2104 margin-right: @padding;
2097 margin-right: @padding;
2105 }
2098 }
2106 }
2099 }
2107 }
2100 }
2108
2101
2109 .search_activate {
2102 .search_activate {
2110 display: table-cell;
2103 display: table-cell;
2111 vertical-align: middle;
2104 vertical-align: middle;
2112
2105
2113 input, label{
2106 input, label{
2114 margin: 0;
2107 margin: 0;
2115 padding: 0;
2108 padding: 0;
2116 }
2109 }
2117
2110
2118 input{
2111 input{
2119 margin-left: @textmargin;
2112 margin-left: @textmargin;
2120 }
2113 }
2121
2114
2122 }
2115 }
2123 }
2116 }
2124
2117
2125 .browser-cur-rev{
2118 .browser-cur-rev{
2126 margin-bottom: @textmargin;
2119 margin-bottom: @textmargin;
2127 }
2120 }
2128
2121
2129 #node_filter_box_loading{
2122 #node_filter_box_loading{
2130 .info_text;
2123 .info_text;
2131 }
2124 }
2132
2125
2133 .browser-search {
2126 .browser-search {
2134 margin: -25px 0px 5px 0px;
2127 margin: -25px 0px 5px 0px;
2135 }
2128 }
2136
2129
2137 .node-filter {
2130 .node-filter {
2138 font-size: @repo-title-fontsize;
2131 font-size: @repo-title-fontsize;
2139 padding: 4px 0px 0px 0px;
2132 padding: 4px 0px 0px 0px;
2140
2133
2141 .node-filter-path {
2134 .node-filter-path {
2142 float: left;
2135 float: left;
2143 color: @grey4;
2136 color: @grey4;
2144 }
2137 }
2145 .node-filter-input {
2138 .node-filter-input {
2146 float: left;
2139 float: left;
2147 margin: -2px 0px 0px 2px;
2140 margin: -2px 0px 0px 2px;
2148 input {
2141 input {
2149 padding: 2px;
2142 padding: 2px;
2150 border: none;
2143 border: none;
2151 font-size: @repo-title-fontsize;
2144 font-size: @repo-title-fontsize;
2152 }
2145 }
2153 }
2146 }
2154 }
2147 }
2155
2148
2156
2149
2157 .browser-result{
2150 .browser-result{
2158 td a{
2151 td a{
2159 margin-left: 0.5em;
2152 margin-left: 0.5em;
2160 display: inline-block;
2153 display: inline-block;
2161
2154
2162 em{
2155 em{
2163 font-family: @text-bold;
2156 font-family: @text-bold;
2164 }
2157 }
2165 }
2158 }
2166 }
2159 }
2167
2160
2168 .browser-highlight{
2161 .browser-highlight{
2169 background-color: @grey5-alpha;
2162 background-color: @grey5-alpha;
2170 }
2163 }
2171
2164
2172
2165
2173 // Search
2166 // Search
2174
2167
2175 .search-form{
2168 .search-form{
2176 #q {
2169 #q {
2177 width: @search-form-width;
2170 width: @search-form-width;
2178 }
2171 }
2179 .fields{
2172 .fields{
2180 margin: 0 0 @space;
2173 margin: 0 0 @space;
2181 }
2174 }
2182
2175
2183 label{
2176 label{
2184 display: inline-block;
2177 display: inline-block;
2185 margin-right: @textmargin;
2178 margin-right: @textmargin;
2186 padding-top: 0.25em;
2179 padding-top: 0.25em;
2187 }
2180 }
2188
2181
2189
2182
2190 .results{
2183 .results{
2191 clear: both;
2184 clear: both;
2192 margin: 0 0 @padding;
2185 margin: 0 0 @padding;
2193 }
2186 }
2194 }
2187 }
2195
2188
2196 div.search-feedback-items {
2189 div.search-feedback-items {
2197 display: inline-block;
2190 display: inline-block;
2198 padding:0px 0px 0px 96px;
2191 padding:0px 0px 0px 96px;
2199 }
2192 }
2200
2193
2201 div.search-code-body {
2194 div.search-code-body {
2202 background-color: #ffffff; padding: 5px 0 5px 10px;
2195 background-color: #ffffff; padding: 5px 0 5px 10px;
2203 pre {
2196 pre {
2204 .match { background-color: #faffa6;}
2197 .match { background-color: #faffa6;}
2205 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2198 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2206 }
2199 }
2207 }
2200 }
2208
2201
2209 .expand_commit.search {
2202 .expand_commit.search {
2210 .show_more.open {
2203 .show_more.open {
2211 height: auto;
2204 height: auto;
2212 max-height: none;
2205 max-height: none;
2213 }
2206 }
2214 }
2207 }
2215
2208
2216 .search-results {
2209 .search-results {
2217
2210
2218 h2 {
2211 h2 {
2219 margin-bottom: 0;
2212 margin-bottom: 0;
2220 }
2213 }
2221 .codeblock {
2214 .codeblock {
2222 border: none;
2215 border: none;
2223 background: transparent;
2216 background: transparent;
2224 }
2217 }
2225
2218
2226 .codeblock-header {
2219 .codeblock-header {
2227 border: none;
2220 border: none;
2228 background: transparent;
2221 background: transparent;
2229 }
2222 }
2230
2223
2231 .code-body {
2224 .code-body {
2232 border: @border-thickness solid @border-default-color;
2225 border: @border-thickness solid @border-default-color;
2233 .border-radius(@border-radius);
2226 .border-radius(@border-radius);
2234 }
2227 }
2235
2228
2236 .td-commit {
2229 .td-commit {
2237 &:extend(pre);
2230 &:extend(pre);
2238 border-bottom: @border-thickness solid @border-default-color;
2231 border-bottom: @border-thickness solid @border-default-color;
2239 }
2232 }
2240
2233
2241 .message {
2234 .message {
2242 height: auto;
2235 height: auto;
2243 max-width: 350px;
2236 max-width: 350px;
2244 white-space: normal;
2237 white-space: normal;
2245 text-overflow: initial;
2238 text-overflow: initial;
2246 overflow: visible;
2239 overflow: visible;
2247
2240
2248 .match { background-color: #faffa6;}
2241 .match { background-color: #faffa6;}
2249 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2242 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2250 }
2243 }
2251
2244
2252 }
2245 }
2253
2246
2254 table.rctable td.td-search-results div {
2247 table.rctable td.td-search-results div {
2255 max-width: 100%;
2248 max-width: 100%;
2256 }
2249 }
2257
2250
2258 #tip-box, .tip-box{
2251 #tip-box, .tip-box{
2259 padding: @menupadding/2;
2252 padding: @menupadding/2;
2260 display: block;
2253 display: block;
2261 border: @border-thickness solid @border-highlight-color;
2254 border: @border-thickness solid @border-highlight-color;
2262 .border-radius(@border-radius);
2255 .border-radius(@border-radius);
2263 background-color: white;
2256 background-color: white;
2264 z-index: 99;
2257 z-index: 99;
2265 white-space: pre-wrap;
2258 white-space: pre-wrap;
2266 }
2259 }
2267
2260
2268 #linktt {
2261 #linktt {
2269 width: 79px;
2262 width: 79px;
2270 }
2263 }
2271
2264
2272 #help_kb .modal-content{
2265 #help_kb .modal-content{
2273 max-width: 750px;
2266 max-width: 750px;
2274 margin: 10% auto;
2267 margin: 10% auto;
2275
2268
2276 table{
2269 table{
2277 td,th{
2270 td,th{
2278 border-bottom: none;
2271 border-bottom: none;
2279 line-height: 2.5em;
2272 line-height: 2.5em;
2280 }
2273 }
2281 th{
2274 th{
2282 padding-bottom: @textmargin/2;
2275 padding-bottom: @textmargin/2;
2283 }
2276 }
2284 td.keys{
2277 td.keys{
2285 text-align: center;
2278 text-align: center;
2286 }
2279 }
2287 }
2280 }
2288
2281
2289 .block-left{
2282 .block-left{
2290 width: 45%;
2283 width: 45%;
2291 margin-right: 5%;
2284 margin-right: 5%;
2292 }
2285 }
2293 .modal-footer{
2286 .modal-footer{
2294 clear: both;
2287 clear: both;
2295 }
2288 }
2296 .key.tag{
2289 .key.tag{
2297 padding: 0.5em;
2290 padding: 0.5em;
2298 background-color: @rcblue;
2291 background-color: @rcblue;
2299 color: white;
2292 color: white;
2300 border-color: @rcblue;
2293 border-color: @rcblue;
2301 .box-shadow(none);
2294 .box-shadow(none);
2302 }
2295 }
2303 }
2296 }
2304
2297
2305
2298
2306
2299
2307 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2300 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2308
2301
2309 @import 'statistics-graph';
2302 @import 'statistics-graph';
2310 @import 'tables';
2303 @import 'tables';
2311 @import 'forms';
2304 @import 'forms';
2312 @import 'diff';
2305 @import 'diff';
2313 @import 'summary';
2306 @import 'summary';
2314 @import 'navigation';
2307 @import 'navigation';
2315
2308
2316 //--- SHOW/HIDE SECTIONS --//
2309 //--- SHOW/HIDE SECTIONS --//
2317
2310
2318 .btn-collapse {
2311 .btn-collapse {
2319 float: right;
2312 float: right;
2320 text-align: right;
2313 text-align: right;
2321 font-family: @text-light;
2314 font-family: @text-light;
2322 font-size: @basefontsize;
2315 font-size: @basefontsize;
2323 cursor: pointer;
2316 cursor: pointer;
2324 border: none;
2317 border: none;
2325 color: @rcblue;
2318 color: @rcblue;
2326 }
2319 }
2327
2320
2328 table.rctable,
2321 table.rctable,
2329 table.dataTable {
2322 table.dataTable {
2330 .btn-collapse {
2323 .btn-collapse {
2331 float: right;
2324 float: right;
2332 text-align: right;
2325 text-align: right;
2333 }
2326 }
2334 }
2327 }
2335
2328
2336
2329
2337 // TODO: johbo: Fix for IE10, this avoids that we see a border
2330 // TODO: johbo: Fix for IE10, this avoids that we see a border
2338 // and padding around checkboxes and radio boxes. Move to the right place,
2331 // and padding around checkboxes and radio boxes. Move to the right place,
2339 // or better: Remove this once we did the form refactoring.
2332 // or better: Remove this once we did the form refactoring.
2340 input[type=checkbox],
2333 input[type=checkbox],
2341 input[type=radio] {
2334 input[type=radio] {
2342 padding: 0;
2335 padding: 0;
2343 border: none;
2336 border: none;
2344 }
2337 }
2345
2338
2346 .toggle-ajax-spinner{
2339 .toggle-ajax-spinner{
2347 height: 16px;
2340 height: 16px;
2348 width: 16px;
2341 width: 16px;
2349 }
2342 }
@@ -1,342 +1,343 b''
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
1 // # Copyright (C) 2010-2017 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Pull request reviewers
20 * Pull request reviewers
21 */
21 */
22 var removeReviewMember = function(reviewer_id, mark_delete){
22 var removeReviewMember = function(reviewer_id, mark_delete){
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
23 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
24
24
25 if(typeof(mark_delete) === undefined){
25 if(typeof(mark_delete) === undefined){
26 mark_delete = false;
26 mark_delete = false;
27 }
27 }
28
28
29 if(mark_delete === true){
29 if(mark_delete === true){
30 if (reviewer){
30 if (reviewer){
31 // mark as to-remove
31 // now delete the input
32 $('#reviewer_{0} input'.format(reviewer_id)).remove();
33 // mark as to-delete
32 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
34 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
33 obj.addClass('to-delete');
35 obj.addClass('to-delete');
34 // now delete the input
36 obj.css({"text-decoration":"line-through", "opacity": 0.5});
35 $('#reviewer_{0} input'.format(reviewer_id)).remove();
36 }
37 }
37 }
38 }
38 else{
39 else{
39 $('#reviewer_{0}'.format(reviewer_id)).remove();
40 $('#reviewer_{0}'.format(reviewer_id)).remove();
40 }
41 }
41 };
42 };
42
43
43 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
44 var addReviewMember = function(id, fname, lname, nname, gravatar_link, reasons) {
44 var members = $('#review_members').get(0);
45 var members = $('#review_members').get(0);
45 var reasons_html = '';
46 var reasons_html = '';
46 var reasons_inputs = '';
47 var reasons_inputs = '';
47 var reasons = reasons || [];
48 var reasons = reasons || [];
48 if (reasons) {
49 if (reasons) {
49 for (var i = 0; i < reasons.length; i++) {
50 for (var i = 0; i < reasons.length; i++) {
50 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
51 reasons_html += '<div class="reviewer_reason">- {0}</div>'.format(reasons[i]);
51 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
52 reasons_inputs += '<input type="hidden" name="reason" value="' + escapeHtml(reasons[i]) + '">';
52 }
53 }
53 }
54 }
54 var tmpl = '<li id="reviewer_{2}">'+
55 var tmpl = '<li id="reviewer_{2}">'+
55 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
56 '<input type="hidden" name="__start__" value="reviewer:mapping">'+
56 '<div class="reviewer_status">'+
57 '<div class="reviewer_status">'+
57 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
58 '<div class="flag_status not_reviewed pull-left reviewer_member_status"></div>'+
58 '</div>'+
59 '</div>'+
59 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
60 '<img alt="gravatar" class="gravatar" src="{0}"/>'+
60 '<span class="reviewer_name user">{1}</span>'+
61 '<span class="reviewer_name user">{1}</span>'+
61 reasons_html +
62 reasons_html +
62 '<input type="hidden" name="user_id" value="{2}">'+
63 '<input type="hidden" name="user_id" value="{2}">'+
63 '<input type="hidden" name="__start__" value="reasons:sequence">'+
64 '<input type="hidden" name="__start__" value="reasons:sequence">'+
64 '{3}'+
65 '{3}'+
65 '<input type="hidden" name="__end__" value="reasons:sequence">'+
66 '<input type="hidden" name="__end__" value="reasons:sequence">'+
66 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
67 '<div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">' +
67 '<i class="icon-remove-sign"></i>'+
68 '<i class="icon-remove-sign"></i>'+
68 '</div>'+
69 '</div>'+
69 '</div>'+
70 '</div>'+
70 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
71 '<input type="hidden" name="__end__" value="reviewer:mapping">'+
71 '</li>' ;
72 '</li>' ;
72
73
73 var displayname = "{0} ({1} {2})".format(
74 var displayname = "{0} ({1} {2})".format(
74 nname, escapeHtml(fname), escapeHtml(lname));
75 nname, escapeHtml(fname), escapeHtml(lname));
75 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
76 var element = tmpl.format(gravatar_link,displayname,id,reasons_inputs);
76 // check if we don't have this ID already in
77 // check if we don't have this ID already in
77 var ids = [];
78 var ids = [];
78 var _els = $('#review_members li').toArray();
79 var _els = $('#review_members li').toArray();
79 for (el in _els){
80 for (el in _els){
80 ids.push(_els[el].id)
81 ids.push(_els[el].id)
81 }
82 }
82 if(ids.indexOf('reviewer_'+id) == -1){
83 if(ids.indexOf('reviewer_'+id) == -1){
83 // only add if it's not there
84 // only add if it's not there
84 members.innerHTML += element;
85 members.innerHTML += element;
85 }
86 }
86
87
87 };
88 };
88
89
89 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
90 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
90 var url = pyroutes.url(
91 var url = pyroutes.url(
91 'pullrequest_update',
92 'pullrequest_update',
92 {"repo_name": repo_name, "pull_request_id": pull_request_id});
93 {"repo_name": repo_name, "pull_request_id": pull_request_id});
93 if (typeof postData === 'string' ) {
94 if (typeof postData === 'string' ) {
94 postData += '&csrf_token=' + CSRF_TOKEN;
95 postData += '&csrf_token=' + CSRF_TOKEN;
95 } else {
96 } else {
96 postData.csrf_token = CSRF_TOKEN;
97 postData.csrf_token = CSRF_TOKEN;
97 }
98 }
98 var success = function(o) {
99 var success = function(o) {
99 window.location.reload();
100 window.location.reload();
100 };
101 };
101 ajaxPOST(url, postData, success);
102 ajaxPOST(url, postData, success);
102 };
103 };
103
104
104 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
105 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
105 if (reviewers_ids === undefined){
106 if (reviewers_ids === undefined){
106 var postData = '_method=put&' + $('#reviewers input').serialize();
107 var postData = '_method=put&' + $('#reviewers input').serialize();
107 _updatePullRequest(repo_name, pull_request_id, postData);
108 _updatePullRequest(repo_name, pull_request_id, postData);
108 }
109 }
109 };
110 };
110
111
111 /**
112 /**
112 * PULL REQUEST reject & close
113 * PULL REQUEST reject & close
113 */
114 */
114 var closePullRequest = function(repo_name, pull_request_id) {
115 var closePullRequest = function(repo_name, pull_request_id) {
115 var postData = {
116 var postData = {
116 '_method': 'put',
117 '_method': 'put',
117 'close_pull_request': true};
118 'close_pull_request': true};
118 _updatePullRequest(repo_name, pull_request_id, postData);
119 _updatePullRequest(repo_name, pull_request_id, postData);
119 };
120 };
120
121
121 /**
122 /**
122 * PULL REQUEST update commits
123 * PULL REQUEST update commits
123 */
124 */
124 var updateCommits = function(repo_name, pull_request_id) {
125 var updateCommits = function(repo_name, pull_request_id) {
125 var postData = {
126 var postData = {
126 '_method': 'put',
127 '_method': 'put',
127 'update_commits': true};
128 'update_commits': true};
128 _updatePullRequest(repo_name, pull_request_id, postData);
129 _updatePullRequest(repo_name, pull_request_id, postData);
129 };
130 };
130
131
131
132
132 /**
133 /**
133 * PULL REQUEST edit info
134 * PULL REQUEST edit info
134 */
135 */
135 var editPullRequest = function(repo_name, pull_request_id, title, description) {
136 var editPullRequest = function(repo_name, pull_request_id, title, description) {
136 var url = pyroutes.url(
137 var url = pyroutes.url(
137 'pullrequest_update',
138 'pullrequest_update',
138 {"repo_name": repo_name, "pull_request_id": pull_request_id});
139 {"repo_name": repo_name, "pull_request_id": pull_request_id});
139
140
140 var postData = {
141 var postData = {
141 '_method': 'put',
142 '_method': 'put',
142 'title': title,
143 'title': title,
143 'description': description,
144 'description': description,
144 'edit_pull_request': true,
145 'edit_pull_request': true,
145 'csrf_token': CSRF_TOKEN
146 'csrf_token': CSRF_TOKEN
146 };
147 };
147 var success = function(o) {
148 var success = function(o) {
148 window.location.reload();
149 window.location.reload();
149 };
150 };
150 ajaxPOST(url, postData, success);
151 ajaxPOST(url, postData, success);
151 };
152 };
152
153
153 var initPullRequestsCodeMirror = function (textAreaId) {
154 var initPullRequestsCodeMirror = function (textAreaId) {
154 var ta = $(textAreaId).get(0);
155 var ta = $(textAreaId).get(0);
155 var initialHeight = '100px';
156 var initialHeight = '100px';
156
157
157 // default options
158 // default options
158 var codeMirrorOptions = {
159 var codeMirrorOptions = {
159 mode: "text",
160 mode: "text",
160 lineNumbers: false,
161 lineNumbers: false,
161 indentUnit: 4,
162 indentUnit: 4,
162 theme: 'rc-input'
163 theme: 'rc-input'
163 };
164 };
164
165
165 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
166 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
166 // marker for manually set description
167 // marker for manually set description
167 codeMirrorInstance._userDefinedDesc = false;
168 codeMirrorInstance._userDefinedDesc = false;
168 codeMirrorInstance.setSize(null, initialHeight);
169 codeMirrorInstance.setSize(null, initialHeight);
169 codeMirrorInstance.on("change", function(instance, changeObj) {
170 codeMirrorInstance.on("change", function(instance, changeObj) {
170 var height = initialHeight;
171 var height = initialHeight;
171 var lines = instance.lineCount();
172 var lines = instance.lineCount();
172 if (lines > 6 && lines < 20) {
173 if (lines > 6 && lines < 20) {
173 height = "auto"
174 height = "auto"
174 }
175 }
175 else if (lines >= 20) {
176 else if (lines >= 20) {
176 height = 20 * 15;
177 height = 20 * 15;
177 }
178 }
178 instance.setSize(null, height);
179 instance.setSize(null, height);
179
180
180 // detect if the change was trigger by auto desc, or user input
181 // detect if the change was trigger by auto desc, or user input
181 changeOrigin = changeObj.origin;
182 changeOrigin = changeObj.origin;
182
183
183 if (changeOrigin === "setValue") {
184 if (changeOrigin === "setValue") {
184 cmLog.debug('Change triggered by setValue');
185 cmLog.debug('Change triggered by setValue');
185 }
186 }
186 else {
187 else {
187 cmLog.debug('user triggered change !');
188 cmLog.debug('user triggered change !');
188 // set special marker to indicate user has created an input.
189 // set special marker to indicate user has created an input.
189 instance._userDefinedDesc = true;
190 instance._userDefinedDesc = true;
190 }
191 }
191
192
192 });
193 });
193
194
194 return codeMirrorInstance
195 return codeMirrorInstance
195 };
196 };
196
197
197 /**
198 /**
198 * Reviewer autocomplete
199 * Reviewer autocomplete
199 */
200 */
200 var ReviewerAutoComplete = function(input_id) {
201 var ReviewerAutoComplete = function(input_id) {
201 $('#'+input_id).autocomplete({
202 $('#'+input_id).autocomplete({
202 serviceUrl: pyroutes.url('user_autocomplete_data'),
203 serviceUrl: pyroutes.url('user_autocomplete_data'),
203 minChars:2,
204 minChars:2,
204 maxHeight:400,
205 maxHeight:400,
205 deferRequestBy: 300, //miliseconds
206 deferRequestBy: 300, //miliseconds
206 showNoSuggestionNotice: true,
207 showNoSuggestionNotice: true,
207 tabDisabled: true,
208 tabDisabled: true,
208 autoSelectFirst: true,
209 autoSelectFirst: true,
209 formatResult: autocompleteFormatResult,
210 formatResult: autocompleteFormatResult,
210 lookupFilter: autocompleteFilterResult,
211 lookupFilter: autocompleteFilterResult,
211 onSelect: function(suggestion, data){
212 onSelect: function(suggestion, data){
212 var msg = _gettext('added manually by "{0}"');
213 var msg = _gettext('added manually by "{0}"');
213 var reasons = [msg.format(templateContext.rhodecode_user.username)];
214 var reasons = [msg.format(templateContext.rhodecode_user.username)];
214 addReviewMember(data.id, data.first_name, data.last_name,
215 addReviewMember(data.id, data.first_name, data.last_name,
215 data.username, data.icon_link, reasons);
216 data.username, data.icon_link, reasons);
216 $('#'+input_id).val('');
217 $('#'+input_id).val('');
217 }
218 }
218 });
219 });
219 };
220 };
220
221
221
222
222 VersionController = function () {
223 VersionController = function () {
223 var self = this;
224 var self = this;
224 this.$verSource = $('input[name=ver_source]');
225 this.$verSource = $('input[name=ver_source]');
225 this.$verTarget = $('input[name=ver_target]');
226 this.$verTarget = $('input[name=ver_target]');
226 this.$showVersionDiff = $('#show-version-diff');
227 this.$showVersionDiff = $('#show-version-diff');
227
228
228 this.adjustRadioSelectors = function (curNode) {
229 this.adjustRadioSelectors = function (curNode) {
229 var getVal = function (item) {
230 var getVal = function (item) {
230 if (item == 'latest') {
231 if (item == 'latest') {
231 return Number.MAX_SAFE_INTEGER
232 return Number.MAX_SAFE_INTEGER
232 }
233 }
233 else {
234 else {
234 return parseInt(item)
235 return parseInt(item)
235 }
236 }
236 };
237 };
237
238
238 var curVal = getVal($(curNode).val());
239 var curVal = getVal($(curNode).val());
239 var cleared = false;
240 var cleared = false;
240
241
241 $.each(self.$verSource, function (index, value) {
242 $.each(self.$verSource, function (index, value) {
242 var elVal = getVal($(value).val());
243 var elVal = getVal($(value).val());
243
244
244 if (elVal > curVal) {
245 if (elVal > curVal) {
245 if ($(value).is(':checked')) {
246 if ($(value).is(':checked')) {
246 cleared = true;
247 cleared = true;
247 }
248 }
248 $(value).attr('disabled', 'disabled');
249 $(value).attr('disabled', 'disabled');
249 $(value).removeAttr('checked');
250 $(value).removeAttr('checked');
250 $(value).css({'opacity': 0.1});
251 $(value).css({'opacity': 0.1});
251 }
252 }
252 else {
253 else {
253 $(value).css({'opacity': 1});
254 $(value).css({'opacity': 1});
254 $(value).removeAttr('disabled');
255 $(value).removeAttr('disabled');
255 }
256 }
256 });
257 });
257
258
258 if (cleared) {
259 if (cleared) {
259 // if we unchecked an active, set the next one to same loc.
260 // if we unchecked an active, set the next one to same loc.
260 $(this.$verSource).filter('[value={0}]'.format(
261 $(this.$verSource).filter('[value={0}]'.format(
261 curVal)).attr('checked', 'checked');
262 curVal)).attr('checked', 'checked');
262 }
263 }
263
264
264 self.setLockAction(false,
265 self.setLockAction(false,
265 $(curNode).data('verPos'),
266 $(curNode).data('verPos'),
266 $(this.$verSource).filter(':checked').data('verPos')
267 $(this.$verSource).filter(':checked').data('verPos')
267 );
268 );
268 };
269 };
269
270
270
271
271 this.attachVersionListener = function () {
272 this.attachVersionListener = function () {
272 self.$verTarget.change(function (e) {
273 self.$verTarget.change(function (e) {
273 self.adjustRadioSelectors(this)
274 self.adjustRadioSelectors(this)
274 });
275 });
275 self.$verSource.change(function (e) {
276 self.$verSource.change(function (e) {
276 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
277 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
277 });
278 });
278 };
279 };
279
280
280 this.init = function () {
281 this.init = function () {
281
282
282 var curNode = self.$verTarget.filter(':checked');
283 var curNode = self.$verTarget.filter(':checked');
283 self.adjustRadioSelectors(curNode);
284 self.adjustRadioSelectors(curNode);
284 self.setLockAction(true);
285 self.setLockAction(true);
285 self.attachVersionListener();
286 self.attachVersionListener();
286
287
287 };
288 };
288
289
289 this.setLockAction = function (state, selectedVersion, otherVersion) {
290 this.setLockAction = function (state, selectedVersion, otherVersion) {
290 var $showVersionDiff = this.$showVersionDiff;
291 var $showVersionDiff = this.$showVersionDiff;
291
292
292 if (state) {
293 if (state) {
293 $showVersionDiff.attr('disabled', 'disabled');
294 $showVersionDiff.attr('disabled', 'disabled');
294 $showVersionDiff.addClass('disabled');
295 $showVersionDiff.addClass('disabled');
295 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
296 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
296 }
297 }
297 else {
298 else {
298 $showVersionDiff.removeAttr('disabled');
299 $showVersionDiff.removeAttr('disabled');
299 $showVersionDiff.removeClass('disabled');
300 $showVersionDiff.removeClass('disabled');
300
301
301 if (selectedVersion == otherVersion) {
302 if (selectedVersion == otherVersion) {
302 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
303 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
303 } else {
304 } else {
304 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
305 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
305 }
306 }
306 }
307 }
307
308
308 };
309 };
309
310
310 this.showVersionDiff = function () {
311 this.showVersionDiff = function () {
311 var target = self.$verTarget.filter(':checked');
312 var target = self.$verTarget.filter(':checked');
312 var source = self.$verSource.filter(':checked');
313 var source = self.$verSource.filter(':checked');
313
314
314 if (target.val() && source.val()) {
315 if (target.val() && source.val()) {
315 var params = {
316 var params = {
316 'pull_request_id': templateContext.pull_request_data.pull_request_id,
317 'pull_request_id': templateContext.pull_request_data.pull_request_id,
317 'repo_name': templateContext.repo_name,
318 'repo_name': templateContext.repo_name,
318 'version': target.val(),
319 'version': target.val(),
319 'from_version': source.val()
320 'from_version': source.val()
320 };
321 };
321 window.location = pyroutes.url('pullrequest_show', params)
322 window.location = pyroutes.url('pullrequest_show', params)
322 }
323 }
323
324
324 return false;
325 return false;
325 };
326 };
326
327
327 this.toggleVersionView = function (elem) {
328 this.toggleVersionView = function (elem) {
328
329
329 if (this.$showVersionDiff.is(':visible')) {
330 if (this.$showVersionDiff.is(':visible')) {
330 $('.version-pr').hide();
331 $('.version-pr').hide();
331 this.$showVersionDiff.hide();
332 this.$showVersionDiff.hide();
332 $(elem).html($(elem).data('toggleOn'))
333 $(elem).html($(elem).data('toggleOn'))
333 } else {
334 } else {
334 $('.version-pr').show();
335 $('.version-pr').show();
335 this.$showVersionDiff.show();
336 this.$showVersionDiff.show();
336 $(elem).html($(elem).data('toggleOff'))
337 $(elem).html($(elem).data('toggleOff'))
337 }
338 }
338
339
339 return false
340 return false
340 }
341 }
341
342
342 }; No newline at end of file
343 };
@@ -1,818 +1,821 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <span id="pr-title">
12 <span id="pr-title">
13 ${c.pull_request.title}
13 ${c.pull_request.title}
14 %if c.pull_request.is_closed():
14 %if c.pull_request.is_closed():
15 (${_('Closed')})
15 (${_('Closed')})
16 %endif
16 %endif
17 </span>
17 </span>
18 <div id="pr-title-edit" class="input" style="display: none;">
18 <div id="pr-title-edit" class="input" style="display: none;">
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 </div>
20 </div>
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='showpullrequest')}
28 ${self.repo_menu(active='showpullrequest')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32
32
33 <script type="text/javascript">
33 <script type="text/javascript">
34 // TODO: marcink switch this to pyroutes
34 // TODO: marcink switch this to pyroutes
35 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
35 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 </script>
37 </script>
38 <div class="box">
38 <div class="box">
39
39
40 <div class="title">
40 <div class="title">
41 ${self.repo_page_title(c.rhodecode_db_repo)}
41 ${self.repo_page_title(c.rhodecode_db_repo)}
42 </div>
42 </div>
43
43
44 ${self.breadcrumbs()}
44 ${self.breadcrumbs()}
45
45
46 <div class="box pr-summary">
46 <div class="box pr-summary">
47
47
48 <div class="summary-details block-left">
48 <div class="summary-details block-left">
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 <div class="pr-details-title">
50 <div class="pr-details-title">
51 <a href="${h.url('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
51 <a href="${h.url('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 %if c.allowed_to_update:
52 %if c.allowed_to_update:
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 % if c.allowed_to_delete:
54 % if c.allowed_to_delete:
55 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
55 ${h.secure_form(url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
57 class_="btn btn-link btn-danger",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 ${h.end_form()}
58 ${h.end_form()}
59 % else:
59 % else:
60 ${_('Delete')}
60 ${_('Delete')}
61 % endif
61 % endif
62 </div>
62 </div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 %endif
65 %endif
66 </div>
66 </div>
67
67
68 <div id="summary" class="fields pr-details-content">
68 <div id="summary" class="fields pr-details-content">
69 <div class="field">
69 <div class="field">
70 <div class="label-summary">
70 <div class="label-summary">
71 <label>${_('Origin')}:</label>
71 <label>${_('Origin')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 <div class="pr-origininfo">
74 <div class="pr-origininfo">
75 ## branch link is only valid if it is a branch
75 ## branch link is only valid if it is a branch
76 <span class="tag">
76 <span class="tag">
77 %if c.pull_request.source_ref_parts.type == 'branch':
77 %if c.pull_request.source_ref_parts.type == 'branch':
78 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
78 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
79 %else:
79 %else:
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 %endif
81 %endif
82 </span>
82 </span>
83 <span class="clone-url">
83 <span class="clone-url">
84 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
84 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 </span>
85 </span>
86 </div>
86 </div>
87 <div class="pr-pullinfo">
87 <div class="pr-pullinfo">
88 %if h.is_hg(c.pull_request.source_repo):
88 %if h.is_hg(c.pull_request.source_repo):
89 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
89 <input type="text" class="input-monospace" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
90 %elif h.is_git(c.pull_request.source_repo):
90 %elif h.is_git(c.pull_request.source_repo):
91 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
91 <input type="text" class="input-monospace" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
92 %endif
92 %endif
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96 <div class="field">
96 <div class="field">
97 <div class="label-summary">
97 <div class="label-summary">
98 <label>${_('Target')}:</label>
98 <label>${_('Target')}:</label>
99 </div>
99 </div>
100 <div class="input">
100 <div class="input">
101 <div class="pr-targetinfo">
101 <div class="pr-targetinfo">
102 ## branch link is only valid if it is a branch
102 ## branch link is only valid if it is a branch
103 <span class="tag">
103 <span class="tag">
104 %if c.pull_request.target_ref_parts.type == 'branch':
104 %if c.pull_request.target_ref_parts.type == 'branch':
105 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
105 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
106 %else:
106 %else:
107 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
107 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
108 %endif
108 %endif
109 </span>
109 </span>
110 <span class="clone-url">
110 <span class="clone-url">
111 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
111 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
112 </span>
112 </span>
113 </div>
113 </div>
114 </div>
114 </div>
115 </div>
115 </div>
116
116
117 ## Link to the shadow repository.
117 ## Link to the shadow repository.
118 <div class="field">
118 <div class="field">
119 <div class="label-summary">
119 <div class="label-summary">
120 <label>${_('Merge')}:</label>
120 <label>${_('Merge')}:</label>
121 </div>
121 </div>
122 <div class="input">
122 <div class="input">
123 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
123 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
124 <div class="pr-mergeinfo">
124 <div class="pr-mergeinfo">
125 %if h.is_hg(c.pull_request.target_repo):
125 %if h.is_hg(c.pull_request.target_repo):
126 <input type="text" class="input-monospace" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
126 <input type="text" class="input-monospace" value="hg clone -u ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
127 %elif h.is_git(c.pull_request.target_repo):
127 %elif h.is_git(c.pull_request.target_repo):
128 <input type="text" class="input-monospace" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
128 <input type="text" class="input-monospace" value="git clone --branch ${c.pull_request.shadow_merge_ref.name} ${c.shadow_clone_url} pull-request-${c.pull_request.pull_request_id}" readonly="readonly">
129 %endif
129 %endif
130 </div>
130 </div>
131 % else:
131 % else:
132 <div class="">
132 <div class="">
133 ${_('Shadow repository data not available')}.
133 ${_('Shadow repository data not available')}.
134 </div>
134 </div>
135 % endif
135 % endif
136 </div>
136 </div>
137 </div>
137 </div>
138
138
139 <div class="field">
139 <div class="field">
140 <div class="label-summary">
140 <div class="label-summary">
141 <label>${_('Review')}:</label>
141 <label>${_('Review')}:</label>
142 </div>
142 </div>
143 <div class="input">
143 <div class="input">
144 %if c.pull_request_review_status:
144 %if c.pull_request_review_status:
145 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
145 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
146 <span class="changeset-status-lbl tooltip">
146 <span class="changeset-status-lbl tooltip">
147 %if c.pull_request.is_closed():
147 %if c.pull_request.is_closed():
148 ${_('Closed')},
148 ${_('Closed')},
149 %endif
149 %endif
150 ${h.commit_status_lbl(c.pull_request_review_status)}
150 ${h.commit_status_lbl(c.pull_request_review_status)}
151 </span>
151 </span>
152 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
152 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
153 %endif
153 %endif
154 </div>
154 </div>
155 </div>
155 </div>
156 <div class="field">
156 <div class="field">
157 <div class="pr-description-label label-summary">
157 <div class="pr-description-label label-summary">
158 <label>${_('Description')}:</label>
158 <label>${_('Description')}:</label>
159 </div>
159 </div>
160 <div id="pr-desc" class="input">
160 <div id="pr-desc" class="input">
161 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
161 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
162 </div>
162 </div>
163 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
163 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
164 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
164 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
165 </div>
165 </div>
166 </div>
166 </div>
167
167
168 <div class="field">
168 <div class="field">
169 <div class="label-summary">
169 <div class="label-summary">
170 <label>${_('Versions')}:</label>
170 <label>${_('Versions')}:</label>
171 </div>
171 </div>
172
172
173 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
173 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
174 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
174 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
175
175
176 <div class="pr-versions">
176 <div class="pr-versions">
177 % if c.show_version_changes:
177 % if c.show_version_changes:
178 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
178 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
180 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
180 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
181 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
181 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
182 data-toggle-off="${_('Hide all versions of this pull request')}">
182 data-toggle-off="${_('Hide all versions of this pull request')}">
183 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
183 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
184 </a>
184 </a>
185 <table>
185 <table>
186 ## SHOW ALL VERSIONS OF PR
186 ## SHOW ALL VERSIONS OF PR
187 <% ver_pr = None %>
187 <% ver_pr = None %>
188
188
189 % for data in reversed(list(enumerate(c.versions, 1))):
189 % for data in reversed(list(enumerate(c.versions, 1))):
190 <% ver_pos = data[0] %>
190 <% ver_pos = data[0] %>
191 <% ver = data[1] %>
191 <% ver = data[1] %>
192 <% ver_pr = ver.pull_request_version_id %>
192 <% ver_pr = ver.pull_request_version_id %>
193 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
193 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
194
194
195 <tr class="version-pr" style="display: ${display_row}">
195 <tr class="version-pr" style="display: ${display_row}">
196 <td>
196 <td>
197 <code>
197 <code>
198 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
198 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
199 </code>
199 </code>
200 </td>
200 </td>
201 <td>
201 <td>
202 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
202 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
203 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
203 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
204 </td>
204 </td>
205 <td>
205 <td>
206 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
206 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
207 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
207 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
208 </div>
208 </div>
209 </td>
209 </td>
210 <td>
210 <td>
211 % if c.at_version_num != ver_pr:
211 % if c.at_version_num != ver_pr:
212 <i class="icon-comment"></i>
212 <i class="icon-comment"></i>
213 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
213 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
214 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
214 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
215 </code>
215 </code>
216 % endif
216 % endif
217 </td>
217 </td>
218 <td>
218 <td>
219 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
219 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
220 </td>
220 </td>
221 <td>
221 <td>
222 ${h.age_component(ver.updated_on, time_is_local=True)}
222 ${h.age_component(ver.updated_on, time_is_local=True)}
223 </td>
223 </td>
224 </tr>
224 </tr>
225 % endfor
225 % endfor
226
226
227 <tr>
227 <tr>
228 <td colspan="6">
228 <td colspan="6">
229 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
229 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
230 data-label-text-locked="${_('select versions to show changes')}"
230 data-label-text-locked="${_('select versions to show changes')}"
231 data-label-text-diff="${_('show changes between versions')}"
231 data-label-text-diff="${_('show changes between versions')}"
232 data-label-text-show="${_('show pull request for this version')}"
232 data-label-text-show="${_('show pull request for this version')}"
233 >
233 >
234 ${_('select versions to show changes')}
234 ${_('select versions to show changes')}
235 </button>
235 </button>
236 </td>
236 </td>
237 </tr>
237 </tr>
238
238
239 ## show comment/inline comments summary
239 ## show comment/inline comments summary
240 <%def name="comments_summary()">
240 <%def name="comments_summary()">
241 <tr>
241 <tr>
242 <td colspan="6" class="comments-summary-td">
242 <td colspan="6" class="comments-summary-td">
243
243
244 % if c.at_version:
244 % if c.at_version:
245 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
245 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
246 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
246 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
247 ${_('Comments at this version')}:
247 ${_('Comments at this version')}:
248 % else:
248 % else:
249 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
249 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
250 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
250 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
251 ${_('Comments for this pull request')}:
251 ${_('Comments for this pull request')}:
252 % endif
252 % endif
253
253
254
254
255 %if general_comm_count_ver:
255 %if general_comm_count_ver:
256 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
256 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
257 %else:
257 %else:
258 ${_("%d General ") % general_comm_count_ver}
258 ${_("%d General ") % general_comm_count_ver}
259 %endif
259 %endif
260
260
261 %if inline_comm_count_ver:
261 %if inline_comm_count_ver:
262 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
262 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
263 %else:
263 %else:
264 , ${_("%d Inline") % inline_comm_count_ver}
264 , ${_("%d Inline") % inline_comm_count_ver}
265 %endif
265 %endif
266
266
267 %if outdated_comm_count_ver:
267 %if outdated_comm_count_ver:
268 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
268 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
269 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
269 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
270 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
270 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
271 %else:
271 %else:
272 , ${_("%d Outdated") % outdated_comm_count_ver}
272 , ${_("%d Outdated") % outdated_comm_count_ver}
273 %endif
273 %endif
274 </td>
274 </td>
275 </tr>
275 </tr>
276 </%def>
276 </%def>
277 ${comments_summary()}
277 ${comments_summary()}
278 </table>
278 </table>
279 % else:
279 % else:
280 <div class="input">
280 <div class="input">
281 ${_('Pull request versions not available')}.
281 ${_('Pull request versions not available')}.
282 </div>
282 </div>
283 <div>
283 <div>
284 <table>
284 <table>
285 ${comments_summary()}
285 ${comments_summary()}
286 </table>
286 </table>
287 </div>
287 </div>
288 % endif
288 % endif
289 </div>
289 </div>
290 </div>
290 </div>
291
291
292 <div id="pr-save" class="field" style="display: none;">
292 <div id="pr-save" class="field" style="display: none;">
293 <div class="label-summary"></div>
293 <div class="label-summary"></div>
294 <div class="input">
294 <div class="input">
295 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
295 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
296 </div>
296 </div>
297 </div>
297 </div>
298 </div>
298 </div>
299 </div>
299 </div>
300 <div>
300 <div>
301 ## AUTHOR
301 ## AUTHOR
302 <div class="reviewers-title block-right">
302 <div class="reviewers-title block-right">
303 <div class="pr-details-title">
303 <div class="pr-details-title">
304 ${_('Author')}
304 ${_('Author')}
305 </div>
305 </div>
306 </div>
306 </div>
307 <div class="block-right pr-details-content reviewers">
307 <div class="block-right pr-details-content reviewers">
308 <ul class="group_members">
308 <ul class="group_members">
309 <li>
309 <li>
310 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
310 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
311 </li>
311 </li>
312 </ul>
312 </ul>
313 </div>
313 </div>
314 ## REVIEWERS
314 ## REVIEWERS
315 <div class="reviewers-title block-right">
315 <div class="reviewers-title block-right">
316 <div class="pr-details-title">
316 <div class="pr-details-title">
317 ${_('Pull request reviewers')}
317 ${_('Pull request reviewers')}
318 %if c.allowed_to_update:
318 %if c.allowed_to_update:
319 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
319 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
320 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
320 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
321 %endif
321 %endif
322 </div>
322 </div>
323 </div>
323 </div>
324 <div id="reviewers" class="block-right pr-details-content reviewers">
324 <div id="reviewers" class="block-right pr-details-content reviewers">
325 ## members goes here !
325 ## members goes here !
326 <input type="hidden" name="__start__" value="review_members:sequence">
326 <input type="hidden" name="__start__" value="review_members:sequence">
327 <ul id="review_members" class="group_members">
327 <ul id="review_members" class="group_members">
328 %for member,reasons,status in c.pull_request_reviewers:
328 %for member,reasons,status in c.pull_request_reviewers:
329 <li id="reviewer_${member.user_id}">
329 <li id="reviewer_${member.user_id}">
330 <div class="reviewers_member">
330 <div class="reviewers_member">
331 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
331 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
332 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
332 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
333 </div>
333 </div>
334 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
334 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
335 ${self.gravatar_with_user(member.email, 16)}
335 ${self.gravatar_with_user(member.email, 16)}
336 </div>
336 </div>
337 <input type="hidden" name="__start__" value="reviewer:mapping">
337 <input type="hidden" name="__start__" value="reviewer:mapping">
338 <input type="hidden" name="__start__" value="reasons:sequence">
338 <input type="hidden" name="__start__" value="reasons:sequence">
339 %for reason in reasons:
339 %for reason in reasons:
340 <div class="reviewer_reason">- ${reason}</div>
340 <div class="reviewer_reason">- ${reason}</div>
341 <input type="hidden" name="reason" value="${reason}">
341 <input type="hidden" name="reason" value="${reason}">
342
342
343 %endfor
343 %endfor
344 <input type="hidden" name="__end__" value="reasons:sequence">
344 <input type="hidden" name="__end__" value="reasons:sequence">
345 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
345 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
346 <input type="hidden" name="__end__" value="reviewer:mapping">
346 <input type="hidden" name="__end__" value="reviewer:mapping">
347 %if c.allowed_to_update:
347 %if c.allowed_to_update:
348 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
348 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
349 <i class="icon-remove-sign" ></i>
349 <i class="icon-remove-sign" ></i>
350 </div>
350 </div>
351 %endif
351 %endif
352 </div>
352 </div>
353 </li>
353 </li>
354 %endfor
354 %endfor
355 </ul>
355 </ul>
356 <input type="hidden" name="__end__" value="review_members:sequence">
356 <input type="hidden" name="__end__" value="review_members:sequence">
357 %if not c.pull_request.is_closed():
357 %if not c.pull_request.is_closed():
358 <div id="add_reviewer_input" class='ac' style="display: none;">
358 <div id="add_reviewer_input" class='ac' style="display: none;">
359 %if c.allowed_to_update:
359 %if c.allowed_to_update:
360 <div class="reviewer_ac">
360 <div class="reviewer_ac">
361 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
361 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
362 <div id="reviewers_container"></div>
362 <div id="reviewers_container"></div>
363 </div>
363 </div>
364 <div>
364 <div>
365 <span id="update_pull_request" class="btn btn-small">${_('Save Changes')}</span>
365 <button id="update_pull_request" class="btn btn-small">${_('Save Changes')}</button>
366 </div>
366 </div>
367 %endif
367 %endif
368 </div>
368 </div>
369 %endif
369 %endif
370 </div>
370 </div>
371 </div>
371 </div>
372 </div>
372 </div>
373 <div class="box">
373 <div class="box">
374 ##DIFF
374 ##DIFF
375 <div class="table" >
375 <div class="table" >
376 <div id="changeset_compare_view_content">
376 <div id="changeset_compare_view_content">
377 ##CS
377 ##CS
378 % if c.missing_requirements:
378 % if c.missing_requirements:
379 <div class="box">
379 <div class="box">
380 <div class="alert alert-warning">
380 <div class="alert alert-warning">
381 <div>
381 <div>
382 <strong>${_('Missing requirements:')}</strong>
382 <strong>${_('Missing requirements:')}</strong>
383 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
383 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
384 </div>
384 </div>
385 </div>
385 </div>
386 </div>
386 </div>
387 % elif c.missing_commits:
387 % elif c.missing_commits:
388 <div class="box">
388 <div class="box">
389 <div class="alert alert-warning">
389 <div class="alert alert-warning">
390 <div>
390 <div>
391 <strong>${_('Missing commits')}:</strong>
391 <strong>${_('Missing commits')}:</strong>
392 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
392 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
393 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
393 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
394 </div>
394 </div>
395 </div>
395 </div>
396 </div>
396 </div>
397 % endif
397 % endif
398
398
399 <div class="compare_view_commits_title">
399 <div class="compare_view_commits_title">
400 % if not c.compare_mode:
400 % if not c.compare_mode:
401
401
402 % if c.at_version_pos:
402 % if c.at_version_pos:
403 <h4>
403 <h4>
404 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
404 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
405 </h4>
405 </h4>
406 % endif
406 % endif
407
407
408 <div class="pull-left">
408 <div class="pull-left">
409 <div class="btn-group">
409 <div class="btn-group">
410 <a
410 <a
411 class="btn"
411 class="btn"
412 href="#"
412 href="#"
413 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
413 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
414 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
414 ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
415 </a>
415 </a>
416 <a
416 <a
417 class="btn"
417 class="btn"
418 href="#"
418 href="#"
419 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
419 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
420 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
420 ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
421 </a>
421 </a>
422 </div>
422 </div>
423 </div>
423 </div>
424
424
425 <div class="pull-right">
425 <div class="pull-right">
426 % if c.allowed_to_update and not c.pull_request.is_closed():
426 % if c.allowed_to_update and not c.pull_request.is_closed():
427 <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a>
427 <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a>
428 % else:
428 % else:
429 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
429 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
430 % endif
430 % endif
431
431
432 </div>
432 </div>
433 % endif
433 % endif
434 </div>
434 </div>
435
435
436 % if not c.missing_commits:
436 % if not c.missing_commits:
437 % if c.compare_mode:
437 % if c.compare_mode:
438 % if c.at_version:
438 % if c.at_version:
439 <h4>
439 <h4>
440 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
440 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
441 </h4>
441 </h4>
442
442
443 <div class="subtitle-compare">
443 <div class="subtitle-compare">
444 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
444 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
445 </div>
445 </div>
446
446
447 <div class="container">
447 <div class="container">
448 <table class="rctable compare_view_commits">
448 <table class="rctable compare_view_commits">
449 <tr>
449 <tr>
450 <th></th>
450 <th></th>
451 <th>${_('Time')}</th>
451 <th>${_('Time')}</th>
452 <th>${_('Author')}</th>
452 <th>${_('Author')}</th>
453 <th>${_('Commit')}</th>
453 <th>${_('Commit')}</th>
454 <th></th>
454 <th></th>
455 <th>${_('Description')}</th>
455 <th>${_('Description')}</th>
456 </tr>
456 </tr>
457
457
458 % for c_type, commit in c.commit_changes:
458 % for c_type, commit in c.commit_changes:
459 % if c_type in ['a', 'r']:
459 % if c_type in ['a', 'r']:
460 <%
460 <%
461 if c_type == 'a':
461 if c_type == 'a':
462 cc_title = _('Commit added in displayed changes')
462 cc_title = _('Commit added in displayed changes')
463 elif c_type == 'r':
463 elif c_type == 'r':
464 cc_title = _('Commit removed in displayed changes')
464 cc_title = _('Commit removed in displayed changes')
465 else:
465 else:
466 cc_title = ''
466 cc_title = ''
467 %>
467 %>
468 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
468 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
469 <td>
469 <td>
470 <div class="commit-change-indicator color-${c_type}-border">
470 <div class="commit-change-indicator color-${c_type}-border">
471 <div class="commit-change-content color-${c_type} tooltip" title="${cc_title}">
471 <div class="commit-change-content color-${c_type} tooltip" title="${cc_title}">
472 ${c_type.upper()}
472 ${c_type.upper()}
473 </div>
473 </div>
474 </div>
474 </div>
475 </td>
475 </td>
476 <td class="td-time">
476 <td class="td-time">
477 ${h.age_component(commit.date)}
477 ${h.age_component(commit.date)}
478 </td>
478 </td>
479 <td class="td-user">
479 <td class="td-user">
480 ${base.gravatar_with_user(commit.author, 16)}
480 ${base.gravatar_with_user(commit.author, 16)}
481 </td>
481 </td>
482 <td class="td-hash">
482 <td class="td-hash">
483 <code>
483 <code>
484 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
484 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
485 r${commit.revision}:${h.short_id(commit.raw_id)}
485 r${commit.revision}:${h.short_id(commit.raw_id)}
486 </a>
486 </a>
487 ${h.hidden('revisions', commit.raw_id)}
487 ${h.hidden('revisions', commit.raw_id)}
488 </code>
488 </code>
489 </td>
489 </td>
490 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
490 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
491 <div class="show_more_col">
491 <div class="show_more_col">
492 <i class="show_more"></i>
492 <i class="show_more"></i>
493 </div>
493 </div>
494 </td>
494 </td>
495 <td class="mid td-description">
495 <td class="mid td-description">
496 <div class="log-container truncate-wrap">
496 <div class="log-container truncate-wrap">
497 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
497 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
498 ${h.urlify_commit_message(commit.message, c.repo_name)}
498 ${h.urlify_commit_message(commit.message, c.repo_name)}
499 </div>
499 </div>
500 </div>
500 </div>
501 </td>
501 </td>
502 </tr>
502 </tr>
503 % endif
503 % endif
504 % endfor
504 % endfor
505 </table>
505 </table>
506 </div>
506 </div>
507
507
508 <script>
508 <script>
509 $('.expand_commit').on('click',function(e){
509 $('.expand_commit').on('click',function(e){
510 var target_expand = $(this);
510 var target_expand = $(this);
511 var cid = target_expand.data('commitId');
511 var cid = target_expand.data('commitId');
512
512
513 if (target_expand.hasClass('open')){
513 if (target_expand.hasClass('open')){
514 $('#c-'+cid).css({
514 $('#c-'+cid).css({
515 'height': '1.5em',
515 'height': '1.5em',
516 'white-space': 'nowrap',
516 'white-space': 'nowrap',
517 'text-overflow': 'ellipsis',
517 'text-overflow': 'ellipsis',
518 'overflow':'hidden'
518 'overflow':'hidden'
519 });
519 });
520 target_expand.removeClass('open');
520 target_expand.removeClass('open');
521 }
521 }
522 else {
522 else {
523 $('#c-'+cid).css({
523 $('#c-'+cid).css({
524 'height': 'auto',
524 'height': 'auto',
525 'white-space': 'pre-line',
525 'white-space': 'pre-line',
526 'text-overflow': 'initial',
526 'text-overflow': 'initial',
527 'overflow':'visible'
527 'overflow':'visible'
528 });
528 });
529 target_expand.addClass('open');
529 target_expand.addClass('open');
530 }
530 }
531 });
531 });
532 </script>
532 </script>
533
533
534 % endif
534 % endif
535
535
536 % else:
536 % else:
537 <%include file="/compare/compare_commits.mako" />
537 <%include file="/compare/compare_commits.mako" />
538 % endif
538 % endif
539
539
540 <div class="cs_files">
540 <div class="cs_files">
541 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
541 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
542 ${cbdiffs.render_diffset_menu()}
542 ${cbdiffs.render_diffset_menu()}
543 ${cbdiffs.render_diffset(
543 ${cbdiffs.render_diffset(
544 c.diffset, use_comments=True,
544 c.diffset, use_comments=True,
545 collapse_when_files_over=30,
545 collapse_when_files_over=30,
546 disable_new_comments=not c.allowed_to_comment,
546 disable_new_comments=not c.allowed_to_comment,
547 deleted_files_comments=c.deleted_files_comments)}
547 deleted_files_comments=c.deleted_files_comments)}
548 </div>
548 </div>
549 % else:
549 % else:
550 ## skipping commits we need to clear the view for missing commits
550 ## skipping commits we need to clear the view for missing commits
551 <div style="clear:both;"></div>
551 <div style="clear:both;"></div>
552 % endif
552 % endif
553
553
554 </div>
554 </div>
555 </div>
555 </div>
556
556
557 ## template for inline comment form
557 ## template for inline comment form
558 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
558 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
559
559
560 ## render general comments
560 ## render general comments
561
561
562 <div id="comment-tr-show">
562 <div id="comment-tr-show">
563 <div class="comment">
563 <div class="comment">
564 % if general_outdated_comm_count_ver:
564 % if general_outdated_comm_count_ver:
565 <div class="meta">
565 <div class="meta">
566 % if general_outdated_comm_count_ver == 1:
566 % if general_outdated_comm_count_ver == 1:
567 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
567 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
568 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
568 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
569 % else:
569 % else:
570 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
570 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
571 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
571 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
572 % endif
572 % endif
573 </div>
573 </div>
574 % endif
574 % endif
575 </div>
575 </div>
576 </div>
576 </div>
577
577
578 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
578 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
579
579
580 % if not c.pull_request.is_closed():
580 % if not c.pull_request.is_closed():
581 ## merge status, and merge action
581 ## merge status, and merge action
582 <div class="pull-request-merge">
582 <div class="pull-request-merge">
583 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
583 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
584 </div>
584 </div>
585
585
586 ## main comment form and it status
586 ## main comment form and it status
587 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
587 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
588 pull_request_id=c.pull_request.pull_request_id),
588 pull_request_id=c.pull_request.pull_request_id),
589 c.pull_request_review_status,
589 c.pull_request_review_status,
590 is_pull_request=True, change_status=c.allowed_to_change_status)}
590 is_pull_request=True, change_status=c.allowed_to_change_status)}
591 %endif
591 %endif
592
592
593 <script type="text/javascript">
593 <script type="text/javascript">
594 if (location.hash) {
594 if (location.hash) {
595 var result = splitDelimitedHash(location.hash);
595 var result = splitDelimitedHash(location.hash);
596 var line = $('html').find(result.loc);
596 var line = $('html').find(result.loc);
597 // show hidden comments if we use location.hash
597 // show hidden comments if we use location.hash
598 if (line.hasClass('comment-general')) {
598 if (line.hasClass('comment-general')) {
599 $(line).show();
599 $(line).show();
600 } else if (line.hasClass('comment-inline')) {
600 } else if (line.hasClass('comment-inline')) {
601 $(line).show();
601 $(line).show();
602 var $cb = $(line).closest('.cb');
602 var $cb = $(line).closest('.cb');
603 $cb.removeClass('cb-collapsed')
603 $cb.removeClass('cb-collapsed')
604 }
604 }
605 if (line.length > 0){
605 if (line.length > 0){
606 offsetScroll(line, 70);
606 offsetScroll(line, 70);
607 }
607 }
608 }
608 }
609
609
610 versionController = new VersionController();
610 versionController = new VersionController();
611 versionController.init();
611 versionController.init();
612
612
613
613
614 $(function(){
614 $(function(){
615 ReviewerAutoComplete('user');
615 ReviewerAutoComplete('user');
616 // custom code mirror
616 // custom code mirror
617 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
617 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
618
618
619 var PRDetails = {
619 var PRDetails = {
620 editButton: $('#open_edit_pullrequest'),
620 editButton: $('#open_edit_pullrequest'),
621 closeButton: $('#close_edit_pullrequest'),
621 closeButton: $('#close_edit_pullrequest'),
622 deleteButton: $('#delete_pullrequest'),
622 deleteButton: $('#delete_pullrequest'),
623 viewFields: $('#pr-desc, #pr-title'),
623 viewFields: $('#pr-desc, #pr-title'),
624 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
624 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
625
625
626 init: function() {
626 init: function() {
627 var that = this;
627 var that = this;
628 this.editButton.on('click', function(e) { that.edit(); });
628 this.editButton.on('click', function(e) { that.edit(); });
629 this.closeButton.on('click', function(e) { that.view(); });
629 this.closeButton.on('click', function(e) { that.view(); });
630 },
630 },
631
631
632 edit: function(event) {
632 edit: function(event) {
633 this.viewFields.hide();
633 this.viewFields.hide();
634 this.editButton.hide();
634 this.editButton.hide();
635 this.deleteButton.hide();
635 this.deleteButton.hide();
636 this.closeButton.show();
636 this.closeButton.show();
637 this.editFields.show();
637 this.editFields.show();
638 codeMirrorInstance.refresh();
638 codeMirrorInstance.refresh();
639 },
639 },
640
640
641 view: function(event) {
641 view: function(event) {
642 this.editButton.show();
642 this.editButton.show();
643 this.deleteButton.show();
643 this.deleteButton.show();
644 this.editFields.hide();
644 this.editFields.hide();
645 this.closeButton.hide();
645 this.closeButton.hide();
646 this.viewFields.show();
646 this.viewFields.show();
647 }
647 }
648 };
648 };
649
649
650 var ReviewersPanel = {
650 var ReviewersPanel = {
651 editButton: $('#open_edit_reviewers'),
651 editButton: $('#open_edit_reviewers'),
652 closeButton: $('#close_edit_reviewers'),
652 closeButton: $('#close_edit_reviewers'),
653 addButton: $('#add_reviewer_input'),
653 addButton: $('#add_reviewer_input'),
654 removeButtons: $('.reviewer_member_remove'),
654 removeButtons: $('.reviewer_member_remove'),
655
655
656 init: function() {
656 init: function() {
657 var that = this;
657 var that = this;
658 this.editButton.on('click', function(e) { that.edit(); });
658 this.editButton.on('click', function(e) { that.edit(); });
659 this.closeButton.on('click', function(e) { that.close(); });
659 this.closeButton.on('click', function(e) { that.close(); });
660 },
660 },
661
661
662 edit: function(event) {
662 edit: function(event) {
663 this.editButton.hide();
663 this.editButton.hide();
664 this.closeButton.show();
664 this.closeButton.show();
665 this.addButton.show();
665 this.addButton.show();
666 this.removeButtons.css('visibility', 'visible');
666 this.removeButtons.css('visibility', 'visible');
667 },
667 },
668
668
669 close: function(event) {
669 close: function(event) {
670 this.editButton.show();
670 this.editButton.show();
671 this.closeButton.hide();
671 this.closeButton.hide();
672 this.addButton.hide();
672 this.addButton.hide();
673 this.removeButtons.css('visibility', 'hidden');
673 this.removeButtons.css('visibility', 'hidden');
674 }
674 }
675 };
675 };
676
676
677 PRDetails.init();
677 PRDetails.init();
678 ReviewersPanel.init();
678 ReviewersPanel.init();
679
679
680 showOutdated = function(self){
680 showOutdated = function(self){
681 $('.comment-inline.comment-outdated').show();
681 $('.comment-inline.comment-outdated').show();
682 $('.filediff-outdated').show();
682 $('.filediff-outdated').show();
683 $('.showOutdatedComments').hide();
683 $('.showOutdatedComments').hide();
684 $('.hideOutdatedComments').show();
684 $('.hideOutdatedComments').show();
685 };
685 };
686
686
687 hideOutdated = function(self){
687 hideOutdated = function(self){
688 $('.comment-inline.comment-outdated').hide();
688 $('.comment-inline.comment-outdated').hide();
689 $('.filediff-outdated').hide();
689 $('.filediff-outdated').hide();
690 $('.hideOutdatedComments').hide();
690 $('.hideOutdatedComments').hide();
691 $('.showOutdatedComments').show();
691 $('.showOutdatedComments').show();
692 };
692 };
693
693
694 refreshMergeChecks = function(){
694 refreshMergeChecks = function(){
695 var loadUrl = "${h.url.current(merge_checks=1)}";
695 var loadUrl = "${h.url.current(merge_checks=1)}";
696 $('.pull-request-merge').css('opacity', 0.3);
696 $('.pull-request-merge').css('opacity', 0.3);
697 $('.action-buttons-extra').css('opacity', 0.3);
697 $('.action-buttons-extra').css('opacity', 0.3);
698
698
699 $('.pull-request-merge').load(
699 $('.pull-request-merge').load(
700 loadUrl, function() {
700 loadUrl, function() {
701 $('.pull-request-merge').css('opacity', 1);
701 $('.pull-request-merge').css('opacity', 1);
702
702
703 $('.action-buttons-extra').css('opacity', 1);
703 $('.action-buttons-extra').css('opacity', 1);
704 injectCloseAction();
704 injectCloseAction();
705 }
705 }
706 );
706 );
707 };
707 };
708
708
709 injectCloseAction = function() {
709 injectCloseAction = function() {
710 var closeAction = $('#close-pull-request-action').html();
710 var closeAction = $('#close-pull-request-action').html();
711 var $actionButtons = $('.action-buttons-extra');
711 var $actionButtons = $('.action-buttons-extra');
712 // clear the action before
712 // clear the action before
713 $actionButtons.html("");
713 $actionButtons.html("");
714 $actionButtons.html(closeAction);
714 $actionButtons.html(closeAction);
715 };
715 };
716
716
717 closePullRequest = function (status) {
717 closePullRequest = function (status) {
718 // inject closing flag
718 // inject closing flag
719 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
719 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
720 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
720 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
721 $(generalCommentForm.submitForm).submit();
721 $(generalCommentForm.submitForm).submit();
722 };
722 };
723
723
724 $('#show-outdated-comments').on('click', function(e){
724 $('#show-outdated-comments').on('click', function(e){
725 var button = $(this);
725 var button = $(this);
726 var outdated = $('.comment-outdated');
726 var outdated = $('.comment-outdated');
727
727
728 if (button.html() === "(Show)") {
728 if (button.html() === "(Show)") {
729 button.html("(Hide)");
729 button.html("(Hide)");
730 outdated.show();
730 outdated.show();
731 } else {
731 } else {
732 button.html("(Show)");
732 button.html("(Show)");
733 outdated.hide();
733 outdated.hide();
734 }
734 }
735 });
735 });
736
736
737 $('.show-inline-comments').on('change', function(e){
737 $('.show-inline-comments').on('change', function(e){
738 var show = 'none';
738 var show = 'none';
739 var target = e.currentTarget;
739 var target = e.currentTarget;
740 if(target.checked){
740 if(target.checked){
741 show = ''
741 show = ''
742 }
742 }
743 var boxid = $(target).attr('id_for');
743 var boxid = $(target).attr('id_for');
744 var comments = $('#{0} .inline-comments'.format(boxid));
744 var comments = $('#{0} .inline-comments'.format(boxid));
745 var fn_display = function(idx){
745 var fn_display = function(idx){
746 $(this).css('display', show);
746 $(this).css('display', show);
747 };
747 };
748 $(comments).each(fn_display);
748 $(comments).each(fn_display);
749 var btns = $('#{0} .inline-comments-button'.format(boxid));
749 var btns = $('#{0} .inline-comments-button'.format(boxid));
750 $(btns).each(fn_display);
750 $(btns).each(fn_display);
751 });
751 });
752
752
753 $('#merge_pull_request_form').submit(function() {
753 $('#merge_pull_request_form').submit(function() {
754 if (!$('#merge_pull_request').attr('disabled')) {
754 if (!$('#merge_pull_request').attr('disabled')) {
755 $('#merge_pull_request').attr('disabled', 'disabled');
755 $('#merge_pull_request').attr('disabled', 'disabled');
756 }
756 }
757 return true;
757 return true;
758 });
758 });
759
759
760 $('#edit_pull_request').on('click', function(e){
760 $('#edit_pull_request').on('click', function(e){
761 var title = $('#pr-title-input').val();
761 var title = $('#pr-title-input').val();
762 var description = codeMirrorInstance.getValue();
762 var description = codeMirrorInstance.getValue();
763 editPullRequest(
763 editPullRequest(
764 "${c.repo_name}", "${c.pull_request.pull_request_id}",
764 "${c.repo_name}", "${c.pull_request.pull_request_id}",
765 title, description);
765 title, description);
766 });
766 });
767
767
768 $('#update_pull_request').on('click', function(e){
768 $('#update_pull_request').on('click', function(e){
769 $(this).attr('disabled', 'disabled');
770 $(this).addClass('disabled');
771 $(this).html(_gettext('saving...'));
769 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
772 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
770 });
773 });
771
774
772 $('#update_commits').on('click', function(e){
775 $('#update_commits').on('click', function(e){
773 var isDisabled = !$(e.currentTarget).attr('disabled');
776 var isDisabled = !$(e.currentTarget).attr('disabled');
774 $(e.currentTarget).text(_gettext('Updating...'));
777 $(e.currentTarget).text(_gettext('Updating...'));
775 $(e.currentTarget).attr('disabled', 'disabled');
778 $(e.currentTarget).attr('disabled', 'disabled');
776 if(isDisabled){
779 if(isDisabled){
777 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
780 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
778 }
781 }
779
782
780 });
783 });
781 // fixing issue with caches on firefox
784 // fixing issue with caches on firefox
782 $('#update_commits').removeAttr("disabled");
785 $('#update_commits').removeAttr("disabled");
783
786
784 $('#close_pull_request').on('click', function(e){
787 $('#close_pull_request').on('click', function(e){
785 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
788 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
786 });
789 });
787
790
788 $('.show-inline-comments').on('click', function(e){
791 $('.show-inline-comments').on('click', function(e){
789 var boxid = $(this).attr('data-comment-id');
792 var boxid = $(this).attr('data-comment-id');
790 var button = $(this);
793 var button = $(this);
791
794
792 if(button.hasClass("comments-visible")) {
795 if(button.hasClass("comments-visible")) {
793 $('#{0} .inline-comments'.format(boxid)).each(function(index){
796 $('#{0} .inline-comments'.format(boxid)).each(function(index){
794 $(this).hide();
797 $(this).hide();
795 });
798 });
796 button.removeClass("comments-visible");
799 button.removeClass("comments-visible");
797 } else {
800 } else {
798 $('#{0} .inline-comments'.format(boxid)).each(function(index){
801 $('#{0} .inline-comments'.format(boxid)).each(function(index){
799 $(this).show();
802 $(this).show();
800 });
803 });
801 button.addClass("comments-visible");
804 button.addClass("comments-visible");
802 }
805 }
803 });
806 });
804
807
805 // register submit callback on commentForm form to track TODOs
808 // register submit callback on commentForm form to track TODOs
806 window.commentFormGlobalSubmitSuccessCallback = function(){
809 window.commentFormGlobalSubmitSuccessCallback = function(){
807 refreshMergeChecks();
810 refreshMergeChecks();
808 };
811 };
809 // initial injection
812 // initial injection
810 injectCloseAction();
813 injectCloseAction();
811
814
812 })
815 })
813 </script>
816 </script>
814
817
815 </div>
818 </div>
816 </div>
819 </div>
817
820
818 </%def>
821 </%def>
General Comments 0
You need to be logged in to leave comments. Login now