##// END OF EJS Templates
compare/pullrequest: introduce merge parameter...
Mads Kiilerich -
r3486:2053053e beta
parent child Browse files
Show More
@@ -1,177 +1,188 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28
29 29 from webob.exc import HTTPNotFound
30 30 from pylons import request, response, session, tmpl_context as c, url
31 31 from pylons.controllers.util import abort, redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib import diffs
39 39
40 40 from rhodecode.model.db import Repository
41 41 from rhodecode.model.pull_request import PullRequestModel
42 42 from webob.exc import HTTPBadRequest
43 43 from rhodecode.lib.diffs import LimitedDiffContainer
44 44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class CompareController(BaseRepoController):
50 50
51 51 @LoginRequired()
52 52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 53 'repository.admin')
54 54 def __before__(self):
55 55 super(CompareController, self).__before__()
56 56
57 57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 58 partial=False):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to changeset with
61 61 proper message. If partial is set then don't do redirect raise Exception
62 62 instead
63 63
64 64 :param rev: revision to fetch
65 65 :param repo: repo instance
66 66 """
67 67
68 68 try:
69 69 type_, rev = rev
70 70 return repo.scm_instance.get_changeset(rev)
71 71 except EmptyRepositoryError, e:
72 72 if not redirect_after:
73 73 return None
74 74 h.flash(h.literal(_('There are no changesets yet')),
75 75 category='warning')
76 76 redirect(url('summary_home', repo_name=repo.repo_name))
77 77
78 78 except RepositoryError, e:
79 79 log.error(traceback.format_exc())
80 80 h.flash(str(e), category='warning')
81 81 if not partial:
82 82 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 83 raise HTTPBadRequest()
84 84
85 85 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
86 86 # org_ref will be evaluated in org_repo
87 87 org_repo = c.rhodecode_db_repo.repo_name
88 88 org_ref = (org_ref_type, org_ref)
89 89 # other_ref will be evaluated in other_repo
90 90 other_ref = (other_ref_type, other_ref)
91 91 other_repo = request.GET.get('other_repo', org_repo)
92 # If merge is True:
93 # Show what org would get if merged with other:
94 # List changesets that are ancestors of other but not of org.
95 # New changesets in org is thus ignored.
96 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
97 # If merge is False:
98 # Make a raw diff from org to other, no matter if related or not.
99 # Changesets in one and not in the other will be ignored
100 merge = bool(request.GET.get('merge'))
92 101 # fulldiff disables cut_off_limit
93 102 c.fulldiff = request.GET.get('fulldiff')
94 103 # partial uses compare_cs.html template directly
95 104 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
96 105 # as_form puts hidden input field with changeset revisions
97 106 c.as_form = partial and request.GET.get('as_form')
98 107 # swap url for compare_diff page - never partial and never as_form
99 108 c.swap_url = h.url('compare_url',
100 109 repo_name=other_repo,
101 110 org_ref_type=other_ref[0], org_ref=other_ref[1],
102 111 other_repo=org_repo,
103 other_ref_type=org_ref[0], other_ref=org_ref[1])
112 other_ref_type=org_ref[0], other_ref=org_ref[1],
113 merge=merge or '')
104 114
105 115 org_repo = Repository.get_by_repo_name(org_repo)
106 116 other_repo = Repository.get_by_repo_name(other_repo)
107 117
108 118 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
109 119 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
110 120
111 121 if org_repo is None:
112 122 log.error('Could not find org repo %s' % org_repo)
113 123 raise HTTPNotFound
114 124 if other_repo is None:
115 125 log.error('Could not find other repo %s' % other_repo)
116 126 raise HTTPNotFound
117 127
118 128 if org_repo != other_repo and h.is_git(org_repo):
119 129 log.error('compare of two remote repos not available for GIT REPOS')
120 130 raise HTTPNotFound
121 131
122 132 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
123 133 log.error('compare of two different kind of remote repos not available')
124 134 raise HTTPNotFound
125 135
126 136 c.org_repo = org_repo
127 137 c.other_repo = other_repo
128 138 c.org_ref = org_ref[1]
129 139 c.other_ref = other_ref[1]
130 140 c.org_ref_type = org_ref[0]
131 141 c.other_ref_type = other_ref[0]
132 142
133 c.cs_ranges, ancestor = PullRequestModel().get_compare_data(
134 org_repo, org_ref, other_repo, other_ref)
143 c.cs_ranges, c.ancestor = PullRequestModel().get_compare_data(
144 org_repo, org_ref, other_repo, other_ref, merge)
135 145
136 146 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
137 147 c.cs_ranges])
138 148 if partial:
149 assert c.ancestor
139 150 return render('compare/compare_cs.html')
140 151
141 if ancestor and org_repo != other_repo:
152 if c.ancestor:
153 assert merge
142 154 # case we want a simple diff without incoming changesets,
143 155 # previewing what will be merged.
144 # Make the diff on the forked repo, with
145 # revision that is common ancestor
156 # Make the diff on the other repo (which is known to have other_ref)
146 157 log.debug('Using ancestor %s as org_ref instead of %s'
147 % (ancestor, org_ref))
148 org_ref = ('rev', ancestor)
158 % (c.ancestor, org_ref))
159 org_ref = ('rev', c.ancestor)
149 160 org_repo = other_repo
150 161
151 162 diff_limit = self.cut_off_limit if not c.fulldiff else None
152 163
153 164 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
154 165
155 166 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
156 167 diff_limit=diff_limit)
157 168 _parsed = diff_processor.prepare()
158 169
159 170 c.limited_diff = False
160 171 if isinstance(_parsed, LimitedDiffContainer):
161 172 c.limited_diff = True
162 173
163 174 c.files = []
164 175 c.changes = {}
165 176 c.lines_added = 0
166 177 c.lines_deleted = 0
167 178 for f in _parsed:
168 179 st = f['stats']
169 180 if st[0] != 'b':
170 181 c.lines_added += st[0]
171 182 c.lines_deleted += st[1]
172 183 fid = h.FID('', f['filename'])
173 184 c.files.append([fid, f['operation'], f['filename'], f['stats']])
174 185 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
175 186 c.changes[fid] = [f['operation'], f['filename'], diff]
176 187
177 188 return render('compare/compare_diff.html')
@@ -1,479 +1,476 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27 import formencode
28 28
29 29 from webob.exc import HTTPNotFound, HTTPForbidden
30 30 from collections import defaultdict
31 31 from itertools import groupby
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.base import BaseRepoController, render
39 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 40 NotAnonymous
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib import diffs
43 43 from rhodecode.lib.utils import action_logger, jsonify
44 44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
45 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 46 from rhodecode.lib.diffs import LimitedDiffContainer
47 47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
48 48 ChangesetComment
49 49 from rhodecode.model.pull_request import PullRequestModel
50 50 from rhodecode.model.meta import Session
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.comment import ChangesetCommentsModel
53 53 from rhodecode.model.changeset_status import ChangesetStatusModel
54 54 from rhodecode.model.forms import PullRequestForm
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class PullrequestsController(BaseRepoController):
60 60
61 61 @LoginRequired()
62 62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
63 63 'repository.admin')
64 64 def __before__(self):
65 65 super(PullrequestsController, self).__before__()
66 66 repo_model = RepoModel()
67 67 c.users_array = repo_model.get_users_js()
68 68 c.users_groups_array = repo_model.get_users_groups_js()
69 69
70 70 def _get_repo_refs(self, repo, rev=None):
71 71 """return a structure with repo's interesting changesets, suitable for
72 72 the selectors in pullrequest.html"""
73 73 branches = [('branch:%s:%s' % (k, v), k)
74 74 for k, v in repo.branches.iteritems()]
75 75 bookmarks = [('book:%s:%s' % (k, v), k)
76 76 for k, v in repo.bookmarks.iteritems()]
77 77 tags = [('tag:%s:%s' % (k, v), k)
78 78 for k, v in repo.tags.iteritems()
79 79 if k != 'tip']
80 80
81 81 tip = repo.tags['tip']
82 82 colontip = ':' + tip
83 83 tips = [x[1] for x in branches + bookmarks + tags
84 84 if x[0].endswith(colontip)]
85 85 selected = 'tag:tip:%s' % tip
86 86 special = [(selected, 'tip (%s)' % ', '.join(tips))]
87 87
88 88 if rev:
89 89 selected = 'rev:%s:%s' % (rev, rev)
90 90 special.append((selected, rev))
91 91
92 92 return [(special, _("Special")),
93 93 (bookmarks, _("Bookmarks")),
94 94 (branches, _("Branches")),
95 95 (tags, _("Tags")),
96 96 ], selected
97 97
98 98 def _get_is_allowed_change_status(self, pull_request):
99 99 owner = self.rhodecode_user.user_id == pull_request.user_id
100 100 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
101 101 pull_request.reviewers]
102 102 return (self.rhodecode_user.admin or owner or reviewer)
103 103
104 104 def show_all(self, repo_name):
105 105 c.pull_requests = PullRequestModel().get_all(repo_name)
106 106 c.repo_name = repo_name
107 107 return render('/pullrequests/pullrequest_show_all.html')
108 108
109 109 @NotAnonymous()
110 110 def index(self):
111 111 org_repo = c.rhodecode_db_repo
112 112
113 113 if org_repo.scm_instance.alias != 'hg':
114 114 log.error('Review not available for GIT REPOS')
115 115 raise HTTPNotFound
116 116
117 117 try:
118 118 org_repo.scm_instance.get_changeset()
119 119 except EmptyRepositoryError, e:
120 120 h.flash(h.literal(_('There are no changesets yet')),
121 121 category='warning')
122 122 redirect(url('summary_home', repo_name=org_repo.repo_name))
123 123
124 124 org_rev = request.GET.get('rev_end')
125 125 # rev_start is not directly useful - its parent could however be used
126 126 # as default for other and thus give a simple compare view
127 127 #other_rev = request.POST.get('rev_start')
128 128
129 129 other_repos_info = {}
130 130
131 131 c.org_repos = []
132 132 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
133 133 c.default_org_repo = org_repo.repo_name
134 134 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
135 135
136 136 c.other_repos = []
137 137 # add org repo to other so we can open pull request against itself
138 138 c.other_repos.extend(c.org_repos)
139 139 c.default_other_repo = org_repo.repo_name
140 140 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
141 141 usr_data = lambda usr: dict(user_id=usr.user_id,
142 142 username=usr.username,
143 143 firstname=usr.firstname,
144 144 lastname=usr.lastname,
145 145 gravatar_link=h.gravatar_url(usr.email, 14))
146 146 other_repos_info[org_repo.repo_name] = {
147 147 'user': usr_data(org_repo.user),
148 148 'description': org_repo.description,
149 149 'revs': h.select('other_ref', c.default_other_ref,
150 150 c.default_other_refs, class_='refs')
151 151 }
152 152
153 153 # gather forks and add to this list ... even though it is rare to
154 154 # request forks to pull their parent
155 155 for fork in org_repo.forks:
156 156 c.other_repos.append((fork.repo_name, fork.repo_name))
157 157 refs, default_ref = self._get_repo_refs(fork.scm_instance)
158 158 other_repos_info[fork.repo_name] = {
159 159 'user': usr_data(fork.user),
160 160 'description': fork.description,
161 161 'revs': h.select('other_ref', default_ref, refs, class_='refs')
162 162 }
163 163
164 164 # add parents of this fork also, but only if it's not empty
165 165 if org_repo.parent and org_repo.parent.scm_instance.revisions:
166 166 c.default_other_repo = org_repo.parent.repo_name
167 167 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance)
168 168 c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name))
169 169 other_repos_info[org_repo.parent.repo_name] = {
170 170 'user': usr_data(org_repo.parent.user),
171 171 'description': org_repo.parent.description,
172 172 'revs': h.select('other_ref', c.default_other_ref,
173 173 c.default_other_refs, class_='refs')
174 174 }
175 175
176 176 c.other_repos_info = json.dumps(other_repos_info)
177 177 # other repo owner
178 178 c.review_members = []
179 179 return render('/pullrequests/pullrequest.html')
180 180
181 181 @NotAnonymous()
182 182 def create(self, repo_name):
183 183 repo = RepoModel()._get_repo(repo_name)
184 184 try:
185 185 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
186 186 except formencode.Invalid, errors:
187 187 log.error(traceback.format_exc())
188 188 if errors.error_dict.get('revisions'):
189 189 msg = 'Revisions: %s' % errors.error_dict['revisions']
190 190 elif errors.error_dict.get('pullrequest_title'):
191 191 msg = _('Pull request requires a title with min. 3 chars')
192 192 else:
193 193 msg = _('error during creation of pull request')
194 194
195 195 h.flash(msg, 'error')
196 196 return redirect(url('pullrequest_home', repo_name=repo_name))
197 197
198 198 org_repo = _form['org_repo']
199 org_ref = _form['org_ref']
199 org_ref = 'rev:merge:%s' % _form['merge_rev']
200 200 other_repo = _form['other_repo']
201 other_ref = _form['other_ref']
201 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
202 202 revisions = _form['revisions']
203 203 reviewers = _form['review_members']
204 204
205 205 title = _form['pullrequest_title']
206 206 description = _form['pullrequest_desc']
207 207
208 208 try:
209 209 pull_request = PullRequestModel().create(
210 210 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
211 211 other_ref, revisions, reviewers, title, description
212 212 )
213 213 Session().commit()
214 214 h.flash(_('Successfully opened new pull request'),
215 215 category='success')
216 216 except Exception:
217 217 h.flash(_('Error occurred during sending pull request'),
218 218 category='error')
219 219 log.error(traceback.format_exc())
220 220 return redirect(url('pullrequest_home', repo_name=repo_name))
221 221
222 222 return redirect(url('pullrequest_show', repo_name=other_repo,
223 223 pull_request_id=pull_request.pull_request_id))
224 224
225 225 @NotAnonymous()
226 226 @jsonify
227 227 def update(self, repo_name, pull_request_id):
228 228 pull_request = PullRequest.get_or_404(pull_request_id)
229 229 if pull_request.is_closed():
230 230 raise HTTPForbidden()
231 231 #only owner or admin can update it
232 232 owner = pull_request.author.user_id == c.rhodecode_user.user_id
233 233 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
234 234 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
235 235 request.POST.get('reviewers_ids', '').split(',')))
236 236
237 237 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
238 238 Session().commit()
239 239 return True
240 240 raise HTTPForbidden()
241 241
242 242 @NotAnonymous()
243 243 @jsonify
244 244 def delete(self, repo_name, pull_request_id):
245 245 pull_request = PullRequest.get_or_404(pull_request_id)
246 246 #only owner can delete it !
247 247 if pull_request.author.user_id == c.rhodecode_user.user_id:
248 248 PullRequestModel().delete(pull_request)
249 249 Session().commit()
250 250 h.flash(_('Successfully deleted pull request'),
251 251 category='success')
252 252 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
253 253 raise HTTPForbidden()
254 254
255 255 def _load_compare_data(self, pull_request, enable_comments=True):
256 256 """
257 257 Load context data needed for generating compare diff
258 258
259 259 :param pull_request:
260 260 :type pull_request:
261 261 """
262 262 org_repo = pull_request.org_repo
263 263 (org_ref_type,
264 264 org_ref_name,
265 265 org_ref_rev) = pull_request.org_ref.split(':')
266 266
267 267 other_repo = org_repo
268 268 (other_ref_type,
269 269 other_ref_name,
270 270 other_ref_rev) = pull_request.other_ref.split(':')
271 271
272 272 # despite opening revisions for bookmarks/branches/tags, we always
273 273 # convert this to rev to prevent changes after bookmark or branch change
274 274 org_ref = ('rev', org_ref_rev)
275 275 other_ref = ('rev', other_ref_rev)
276 276
277 277 c.org_repo = org_repo
278 278 c.other_repo = other_repo
279 279
280 280 c.fulldiff = fulldiff = request.GET.get('fulldiff')
281 281
282 282 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
283 283
284 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
285 if c.cs_ranges[0].parents
286 else EmptyChangeset(), 'raw_id'))
287
288 284 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
289 285
290 286 c.org_ref = org_ref[1]
291 287 c.org_ref_type = org_ref[0]
292 288 c.other_ref = other_ref[1]
293 289 c.other_ref_type = other_ref[0]
294 290
295 291 diff_limit = self.cut_off_limit if not fulldiff else None
296 292
297 293 #we swap org/other ref since we run a simple diff on one repo
298 294 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
299 295
300 296 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
301 297 diff_limit=diff_limit)
302 298 _parsed = diff_processor.prepare()
303 299
304 300 c.limited_diff = False
305 301 if isinstance(_parsed, LimitedDiffContainer):
306 302 c.limited_diff = True
307 303
308 304 c.files = []
309 305 c.changes = {}
310 306 c.lines_added = 0
311 307 c.lines_deleted = 0
312 308 for f in _parsed:
313 309 st = f['stats']
314 310 if st[0] != 'b':
315 311 c.lines_added += st[0]
316 312 c.lines_deleted += st[1]
317 313 fid = h.FID('', f['filename'])
318 314 c.files.append([fid, f['operation'], f['filename'], f['stats']])
319 315 diff = diff_processor.as_html(enable_comments=enable_comments,
320 316 parsed_lines=[f])
321 317 c.changes[fid] = [f['operation'], f['filename'], diff]
322 318
323 319 def show(self, repo_name, pull_request_id):
324 320 repo_model = RepoModel()
325 321 c.users_array = repo_model.get_users_js()
326 322 c.users_groups_array = repo_model.get_users_groups_js()
327 323 c.pull_request = PullRequest.get_or_404(pull_request_id)
328 324 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
329 325 cc_model = ChangesetCommentsModel()
330 326 cs_model = ChangesetStatusModel()
331 327 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
332 328 pull_request=c.pull_request,
333 329 with_revisions=True)
334 330
335 331 cs_statuses = defaultdict(list)
336 332 for st in _cs_statuses:
337 333 cs_statuses[st.author.username] += [st]
338 334
339 335 c.pull_request_reviewers = []
340 336 c.pull_request_pending_reviewers = []
341 337 for o in c.pull_request.reviewers:
342 338 st = cs_statuses.get(o.user.username, None)
343 339 if st:
344 340 sorter = lambda k: k.version
345 341 st = [(x, list(y)[0])
346 342 for x, y in (groupby(sorted(st, key=sorter), sorter))]
347 343 else:
348 344 c.pull_request_pending_reviewers.append(o.user)
349 345 c.pull_request_reviewers.append([o.user, st])
350 346
351 347 # pull_requests repo_name we opened it against
352 348 # ie. other_repo must match
353 349 if repo_name != c.pull_request.other_repo.repo_name:
354 350 raise HTTPNotFound
355 351
356 352 # load compare data into template context
357 353 enable_comments = not c.pull_request.is_closed()
358 354 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
359 355
360 356 # inline comments
361 357 c.inline_cnt = 0
362 358 c.inline_comments = cc_model.get_inline_comments(
363 359 c.rhodecode_db_repo.repo_id,
364 360 pull_request=pull_request_id)
365 361 # count inline comments
366 362 for __, lines in c.inline_comments:
367 363 for comments in lines.values():
368 364 c.inline_cnt += len(comments)
369 365 # comments
370 366 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
371 367 pull_request=pull_request_id)
372 368
373 369 try:
374 370 cur_status = c.statuses[c.pull_request.revisions[0]][0]
375 371 except:
376 372 log.error(traceback.format_exc())
377 373 cur_status = 'undefined'
378 374 if c.pull_request.is_closed() and 0:
379 375 c.current_changeset_status = cur_status
380 376 else:
381 377 # changeset(pull-request) status calulation based on reviewers
382 378 c.current_changeset_status = cs_model.calculate_status(
383 379 c.pull_request_reviewers,
384 380 )
385 381 c.changeset_statuses = ChangesetStatus.STATUSES
386 382
387 383 c.as_form = False
384 c.ancestor = None # there is one - but right here we don't know which
388 385 return render('/pullrequests/pullrequest_show.html')
389 386
390 387 @NotAnonymous()
391 388 @jsonify
392 389 def comment(self, repo_name, pull_request_id):
393 390 pull_request = PullRequest.get_or_404(pull_request_id)
394 391 if pull_request.is_closed():
395 392 raise HTTPForbidden()
396 393
397 394 status = request.POST.get('changeset_status')
398 395 change_status = request.POST.get('change_changeset_status')
399 396 text = request.POST.get('text')
400 397 close_pr = request.POST.get('save_close')
401 398
402 399 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
403 400 if status and change_status and allowed_to_change_status:
404 401 _def = (_('status change -> %s')
405 402 % ChangesetStatus.get_status_lbl(status))
406 403 if close_pr:
407 404 _def = _('Closing with') + ' ' + _def
408 405 text = text or _def
409 406 comm = ChangesetCommentsModel().create(
410 407 text=text,
411 408 repo=c.rhodecode_db_repo.repo_id,
412 409 user=c.rhodecode_user.user_id,
413 410 pull_request=pull_request_id,
414 411 f_path=request.POST.get('f_path'),
415 412 line_no=request.POST.get('line'),
416 413 status_change=(ChangesetStatus.get_status_lbl(status)
417 414 if status and change_status
418 415 and allowed_to_change_status else None),
419 416 closing_pr=close_pr
420 417 )
421 418
422 419 action_logger(self.rhodecode_user,
423 420 'user_commented_pull_request:%s' % pull_request_id,
424 421 c.rhodecode_db_repo, self.ip_addr, self.sa)
425 422
426 423 if allowed_to_change_status:
427 424 # get status if set !
428 425 if status and change_status:
429 426 ChangesetStatusModel().set_status(
430 427 c.rhodecode_db_repo.repo_id,
431 428 status,
432 429 c.rhodecode_user.user_id,
433 430 comm,
434 431 pull_request=pull_request_id
435 432 )
436 433
437 434 if close_pr:
438 435 if status in ['rejected', 'approved']:
439 436 PullRequestModel().close_pull_request(pull_request_id)
440 437 action_logger(self.rhodecode_user,
441 438 'user_closed_pull_request:%s' % pull_request_id,
442 439 c.rhodecode_db_repo, self.ip_addr, self.sa)
443 440 else:
444 441 h.flash(_('Closing pull request on other statuses than '
445 442 'rejected or approved forbidden'),
446 443 category='warning')
447 444
448 445 Session().commit()
449 446
450 447 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
451 448 return redirect(h.url('pullrequest_show', repo_name=repo_name,
452 449 pull_request_id=pull_request_id))
453 450
454 451 data = {
455 452 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
456 453 }
457 454 if comm:
458 455 c.co = comm
459 456 data.update(comm.get_dict())
460 457 data.update({'rendered_text':
461 458 render('changeset/changeset_comment_block.html')})
462 459
463 460 return data
464 461
465 462 @NotAnonymous()
466 463 @jsonify
467 464 def delete_comment(self, repo_name, comment_id):
468 465 co = ChangesetComment.get(comment_id)
469 466 if co.pull_request.is_closed():
470 467 #don't allow deleting comments on closed pull request
471 468 raise HTTPForbidden()
472 469
473 470 owner = co.author.user_id == c.rhodecode_user.user_id
474 471 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
475 472 ChangesetCommentsModel().delete(comment=co)
476 473 Session().commit()
477 474 return True
478 475 else:
479 476 raise HTTPForbidden()
@@ -1,398 +1,401 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.ValidPassword(),
70 70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 71 )
72 72 password_confirmation = All(
73 73 v.ValidPassword(),
74 74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 75 )
76 76 admin = v.StringBoolean(if_missing=False)
77 77 else:
78 78 password = All(
79 79 v.ValidPassword(),
80 80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 81 )
82 82 password_confirmation = All(
83 83 v.ValidPassword(),
84 84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 85 )
86 86
87 87 active = v.StringBoolean(if_missing=False)
88 88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91 91
92 92 chained_validators = [v.ValidPasswordsMatch()]
93 93
94 94 return _UserForm
95 95
96 96
97 97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
98 98 class _UserGroupForm(formencode.Schema):
99 99 allow_extra_fields = True
100 100 filter_extra_fields = True
101 101
102 102 users_group_name = All(
103 103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 104 v.ValidUserGroup(edit, old_data)
105 105 )
106 106
107 107 users_group_active = v.StringBoolean(if_missing=False)
108 108
109 109 if edit:
110 110 users_group_members = v.OneOf(
111 111 available_members, hideList=False, testValueList=True,
112 112 if_missing=None, not_empty=False
113 113 )
114 114
115 115 return _UserGroupForm
116 116
117 117
118 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
119 119 can_create_in_root=False):
120 120 class _ReposGroupForm(formencode.Schema):
121 121 allow_extra_fields = True
122 122 filter_extra_fields = False
123 123
124 124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
125 125 v.SlugifyName())
126 126 group_description = v.UnicodeString(strip=True, min=1,
127 127 not_empty=False)
128 128 if edit:
129 129 #FIXME: do a special check that we cannot move a group to one of
130 130 #it's children
131 131 pass
132 132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
133 133 v.OneOf(available_groups, hideList=False,
134 134 testValueList=True,
135 135 if_missing=None, not_empty=True))
136 136 enable_locking = v.StringBoolean(if_missing=False)
137 137 recursive = v.StringBoolean(if_missing=False)
138 138 chained_validators = [v.ValidReposGroup(edit, old_data),
139 139 v.ValidPerms('group')]
140 140
141 141 return _ReposGroupForm
142 142
143 143
144 144 def RegisterForm(edit=False, old_data={}):
145 145 class _RegisterForm(formencode.Schema):
146 146 allow_extra_fields = True
147 147 filter_extra_fields = True
148 148 username = All(
149 149 v.ValidUsername(edit, old_data),
150 150 v.UnicodeString(strip=True, min=1, not_empty=True)
151 151 )
152 152 password = All(
153 153 v.ValidPassword(),
154 154 v.UnicodeString(strip=False, min=6, not_empty=True)
155 155 )
156 156 password_confirmation = All(
157 157 v.ValidPassword(),
158 158 v.UnicodeString(strip=False, min=6, not_empty=True)
159 159 )
160 160 active = v.StringBoolean(if_missing=False)
161 161 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
162 162 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
163 163 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
164 164
165 165 chained_validators = [v.ValidPasswordsMatch()]
166 166
167 167 return _RegisterForm
168 168
169 169
170 170 def PasswordResetForm():
171 171 class _PasswordResetForm(formencode.Schema):
172 172 allow_extra_fields = True
173 173 filter_extra_fields = True
174 174 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
175 175 return _PasswordResetForm
176 176
177 177
178 178 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
179 179 repo_groups=[], landing_revs=[]):
180 180 class _RepoForm(formencode.Schema):
181 181 allow_extra_fields = True
182 182 filter_extra_fields = False
183 183 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
184 184 v.SlugifyName())
185 185 repo_group = All(v.CanWriteGroup(),
186 186 v.OneOf(repo_groups, hideList=True))
187 187 repo_type = v.OneOf(supported_backends)
188 188 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
189 189 repo_private = v.StringBoolean(if_missing=False)
190 190 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
191 191 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
192 192
193 193 repo_enable_statistics = v.StringBoolean(if_missing=False)
194 194 repo_enable_downloads = v.StringBoolean(if_missing=False)
195 195 repo_enable_locking = v.StringBoolean(if_missing=False)
196 196
197 197 if edit:
198 198 #this is repo owner
199 199 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
200 200
201 201 chained_validators = [v.ValidCloneUri(),
202 202 v.ValidRepoName(edit, old_data),
203 203 v.ValidPerms()]
204 204 return _RepoForm
205 205
206 206
207 207 def RepoFieldForm():
208 208 class _RepoFieldForm(formencode.Schema):
209 209 filter_extra_fields = True
210 210 allow_extra_fields = True
211 211
212 212 new_field_key = All(v.FieldKey(),
213 213 v.UnicodeString(strip=True, min=3, not_empty=True))
214 214 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
215 215 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
216 216 if_missing='str')
217 217 new_field_label = v.UnicodeString(not_empty=False)
218 218 new_field_desc = v.UnicodeString(not_empty=False)
219 219
220 220 return _RepoFieldForm
221 221
222 222
223 223 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
224 224 repo_groups=[], landing_revs=[]):
225 225 class _RepoForm(formencode.Schema):
226 226 allow_extra_fields = True
227 227 filter_extra_fields = False
228 228 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 229 v.SlugifyName())
230 230 repo_group = All(v.CanWriteGroup(),
231 231 v.OneOf(repo_groups, hideList=True))
232 232 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
233 233 repo_private = v.StringBoolean(if_missing=False)
234 234 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
235 235 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
236 236
237 237 chained_validators = [v.ValidCloneUri(),
238 238 v.ValidRepoName(edit, old_data),
239 239 v.ValidPerms(),
240 240 v.ValidSettings()]
241 241 return _RepoForm
242 242
243 243
244 244 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
245 245 repo_groups=[], landing_revs=[]):
246 246 class _RepoForkForm(formencode.Schema):
247 247 allow_extra_fields = True
248 248 filter_extra_fields = False
249 249 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 250 v.SlugifyName())
251 251 repo_group = All(v.CanWriteGroup(),
252 252 v.OneOf(repo_groups, hideList=True))
253 253 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
254 254 description = v.UnicodeString(strip=True, min=1, not_empty=True)
255 255 private = v.StringBoolean(if_missing=False)
256 256 copy_permissions = v.StringBoolean(if_missing=False)
257 257 update_after_clone = v.StringBoolean(if_missing=False)
258 258 fork_parent_id = v.UnicodeString()
259 259 chained_validators = [v.ValidForkName(edit, old_data)]
260 260 landing_rev = v.OneOf(landing_revs, hideList=True)
261 261
262 262 return _RepoForkForm
263 263
264 264
265 265 def ApplicationSettingsForm():
266 266 class _ApplicationSettingsForm(formencode.Schema):
267 267 allow_extra_fields = True
268 268 filter_extra_fields = False
269 269 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
270 270 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
271 271 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
272 272
273 273 return _ApplicationSettingsForm
274 274
275 275
276 276 def ApplicationVisualisationForm():
277 277 class _ApplicationVisualisationForm(formencode.Schema):
278 278 allow_extra_fields = True
279 279 filter_extra_fields = False
280 280 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
281 281 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
282 282 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
283 283
284 284 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
285 285 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
286 286 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
287 287
288 288 return _ApplicationVisualisationForm
289 289
290 290
291 291 def ApplicationUiSettingsForm():
292 292 class _ApplicationUiSettingsForm(formencode.Schema):
293 293 allow_extra_fields = True
294 294 filter_extra_fields = False
295 295 web_push_ssl = v.StringBoolean(if_missing=False)
296 296 paths_root_path = All(
297 297 v.ValidPath(),
298 298 v.UnicodeString(strip=True, min=1, not_empty=True)
299 299 )
300 300 hooks_changegroup_update = v.StringBoolean(if_missing=False)
301 301 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
302 302 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
303 303 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
304 304
305 305 extensions_largefiles = v.StringBoolean(if_missing=False)
306 306 extensions_hgsubversion = v.StringBoolean(if_missing=False)
307 307 extensions_hggit = v.StringBoolean(if_missing=False)
308 308
309 309 return _ApplicationUiSettingsForm
310 310
311 311
312 312 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
313 313 register_choices, create_choices, fork_choices):
314 314 class _DefaultPermissionsForm(formencode.Schema):
315 315 allow_extra_fields = True
316 316 filter_extra_fields = True
317 317 overwrite_default_repo = v.StringBoolean(if_missing=False)
318 318 overwrite_default_group = v.StringBoolean(if_missing=False)
319 319 anonymous = v.StringBoolean(if_missing=False)
320 320 default_repo_perm = v.OneOf(repo_perms_choices)
321 321 default_group_perm = v.OneOf(group_perms_choices)
322 322 default_register = v.OneOf(register_choices)
323 323 default_create = v.OneOf(create_choices)
324 324 default_fork = v.OneOf(fork_choices)
325 325
326 326 return _DefaultPermissionsForm
327 327
328 328
329 329 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
330 330 class _DefaultsForm(formencode.Schema):
331 331 allow_extra_fields = True
332 332 filter_extra_fields = True
333 333 default_repo_type = v.OneOf(supported_backends)
334 334 default_repo_private = v.StringBoolean(if_missing=False)
335 335 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
336 336 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
337 337 default_repo_enable_locking = v.StringBoolean(if_missing=False)
338 338
339 339 return _DefaultsForm
340 340
341 341
342 342 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
343 343 tls_kind_choices):
344 344 class _LdapSettingsForm(formencode.Schema):
345 345 allow_extra_fields = True
346 346 filter_extra_fields = True
347 347 #pre_validators = [LdapLibValidator]
348 348 ldap_active = v.StringBoolean(if_missing=False)
349 349 ldap_host = v.UnicodeString(strip=True,)
350 350 ldap_port = v.Number(strip=True,)
351 351 ldap_tls_kind = v.OneOf(tls_kind_choices)
352 352 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
353 353 ldap_dn_user = v.UnicodeString(strip=True,)
354 354 ldap_dn_pass = v.UnicodeString(strip=True,)
355 355 ldap_base_dn = v.UnicodeString(strip=True,)
356 356 ldap_filter = v.UnicodeString(strip=True,)
357 357 ldap_search_scope = v.OneOf(search_scope_choices)
358 358 ldap_attr_login = All(
359 359 v.AttrLoginValidator(),
360 360 v.UnicodeString(strip=True,)
361 361 )
362 362 ldap_attr_firstname = v.UnicodeString(strip=True,)
363 363 ldap_attr_lastname = v.UnicodeString(strip=True,)
364 364 ldap_attr_email = v.UnicodeString(strip=True,)
365 365
366 366 return _LdapSettingsForm
367 367
368 368
369 369 def UserExtraEmailForm():
370 370 class _UserExtraEmailForm(formencode.Schema):
371 371 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
372 372 return _UserExtraEmailForm
373 373
374 374
375 375 def UserExtraIpForm():
376 376 class _UserExtraIpForm(formencode.Schema):
377 377 ip = v.ValidIp()(not_empty=True)
378 378 return _UserExtraIpForm
379 379
380 380
381 381 def PullRequestForm(repo_id):
382 382 class _PullRequestForm(formencode.Schema):
383 383 allow_extra_fields = True
384 384 filter_extra_fields = True
385 385
386 386 user = v.UnicodeString(strip=True, required=True)
387 387 org_repo = v.UnicodeString(strip=True, required=True)
388 388 org_ref = v.UnicodeString(strip=True, required=True)
389 389 other_repo = v.UnicodeString(strip=True, required=True)
390 390 other_ref = v.UnicodeString(strip=True, required=True)
391 391 revisions = All(#v.NotReviewedRevisions(repo_id)(),
392 392 v.UniqueList(not_empty=True))
393 393 review_members = v.UniqueList(not_empty=True)
394 394
395 395 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
396 396 pullrequest_desc = v.UnicodeString(strip=True, required=False)
397 397
398 ancestor_rev = v.UnicodeString(strip=True, required=True)
399 merge_rev = v.UnicodeString(strip=True, required=True)
400
398 401 return _PullRequestForm
@@ -1,255 +1,261 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.pull_request
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull request model for RhodeCode
7 7
8 8 :created_on: Jun 6, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import datetime
28 28 import re
29 29
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.lib import helpers as h, unionrepo
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
36 36 ChangesetStatus
37 37 from rhodecode.model.notification import NotificationModel
38 38 from rhodecode.lib.utils2 import safe_unicode
39 39
40 40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
41 41 from rhodecode.lib.vcs.utils import safe_str
42 42 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class PullRequestModel(BaseModel):
48 48
49 49 cls = PullRequest
50 50
51 51 def __get_pull_request(self, pull_request):
52 52 return self._get_instance(PullRequest, pull_request)
53 53
54 54 def get_all(self, repo):
55 55 repo = self._get_repo(repo)
56 56 return PullRequest.query()\
57 57 .filter(PullRequest.other_repo == repo)\
58 58 .order_by(PullRequest.created_on.desc())\
59 59 .all()
60 60
61 61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
62 62 revisions, reviewers, title, description=None):
63 63 from rhodecode.model.changeset_status import ChangesetStatusModel
64 64
65 65 created_by_user = self._get_user(created_by)
66 66 org_repo = self._get_repo(org_repo)
67 67 other_repo = self._get_repo(other_repo)
68 68
69 69 new = PullRequest()
70 70 new.org_repo = org_repo
71 71 new.org_ref = org_ref
72 72 new.other_repo = other_repo
73 73 new.other_ref = other_ref
74 74 new.revisions = revisions
75 75 new.title = title
76 76 new.description = description
77 77 new.author = created_by_user
78 78 Session().add(new)
79 79 Session().flush()
80 80 #members
81 81 for member in set(reviewers):
82 82 _usr = self._get_user(member)
83 83 reviewer = PullRequestReviewers(_usr, new)
84 84 Session().add(reviewer)
85 85
86 86 #reset state to under-review
87 87 ChangesetStatusModel().set_status(
88 88 repo=org_repo,
89 89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
90 90 user=created_by_user,
91 91 pull_request=new
92 92 )
93 93 revision_data = [(x.raw_id, x.message)
94 94 for x in map(org_repo.get_changeset, revisions)]
95 95 #notification to reviewers
96 96 notif = NotificationModel()
97 97
98 98 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
99 99 pull_request_id=new.pull_request_id,
100 100 qualified=True,
101 101 )
102 102 subject = safe_unicode(
103 103 h.link_to(
104 104 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
105 105 {'user': created_by_user.username,
106 106 'pr_title': new.title,
107 107 'pr_id': new.pull_request_id},
108 108 pr_url
109 109 )
110 110 )
111 111 body = description
112 112 kwargs = {
113 113 'pr_title': title,
114 114 'pr_user_created': h.person(created_by_user.email),
115 115 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
116 116 qualified=True,),
117 117 'pr_url': pr_url,
118 118 'pr_revisions': revision_data
119 119 }
120 120
121 121 notif.create(created_by=created_by_user, subject=subject, body=body,
122 122 recipients=reviewers,
123 123 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
124 124 return new
125 125
126 126 def update_reviewers(self, pull_request, reviewers_ids):
127 127 reviewers_ids = set(reviewers_ids)
128 128 pull_request = self.__get_pull_request(pull_request)
129 129 current_reviewers = PullRequestReviewers.query()\
130 130 .filter(PullRequestReviewers.pull_request==
131 131 pull_request)\
132 132 .all()
133 133 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
134 134
135 135 to_add = reviewers_ids.difference(current_reviewers_ids)
136 136 to_remove = current_reviewers_ids.difference(reviewers_ids)
137 137
138 138 log.debug("Adding %s reviewers" % to_add)
139 139 log.debug("Removing %s reviewers" % to_remove)
140 140
141 141 for uid in to_add:
142 142 _usr = self._get_user(uid)
143 143 reviewer = PullRequestReviewers(_usr, pull_request)
144 144 Session().add(reviewer)
145 145
146 146 for uid in to_remove:
147 147 reviewer = PullRequestReviewers.query()\
148 148 .filter(PullRequestReviewers.user_id==uid,
149 149 PullRequestReviewers.pull_request==pull_request)\
150 150 .scalar()
151 151 if reviewer:
152 152 Session().delete(reviewer)
153 153
154 154 def delete(self, pull_request):
155 155 pull_request = self.__get_pull_request(pull_request)
156 156 Session().delete(pull_request)
157 157
158 158 def close_pull_request(self, pull_request):
159 159 pull_request = self.__get_pull_request(pull_request)
160 160 pull_request.status = PullRequest.STATUS_CLOSED
161 161 pull_request.updated_on = datetime.datetime.now()
162 162 Session().add(pull_request)
163 163
164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
165 165 """
166 166 Returns a list of changesets that can be merged from org_repo@org_ref
167 167 to other_repo@other_ref ... and the ancestor that would be used for merge
168 168
169 169 :param org_repo:
170 170 :param org_ref:
171 171 :param other_repo:
172 172 :param other_ref:
173 173 :param tmp:
174 174 """
175 175
176 176 ancestor = None
177 177
178 178 if alias == 'hg':
179 179 # lookup up the exact node id
180 180 _revset_predicates = {
181 181 'branch': 'branch',
182 182 'book': 'bookmark',
183 183 'tag': 'tag',
184 184 'rev': 'id',
185 185 }
186 186
187 187 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
188 188 safe_str(org_ref[1]))
189 189 if org_ref[1] == EmptyChangeset().raw_id:
190 190 org_rev = org_ref[1]
191 191 else:
192 192 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
193 193 [org_rev_spec])[-1]]
194 194 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
195 195 safe_str(other_ref[1]))
196 196 if other_ref[1] == EmptyChangeset().raw_id:
197 197 other_rev = other_ref[1]
198 198 else:
199 199 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
200 200 [other_rev_spec])[-1]]
201 201
202 202 #case two independent repos
203 203 if org_repo != other_repo:
204 204 hgrepo = unionrepo.unionrepository(other_repo.baseui,
205 205 other_repo.path,
206 206 org_repo.path)
207 207 # all the changesets we are looking for will be in other_repo,
208 208 # so rev numbers from hgrepo can be used in other_repo
209 209
210 210 #no remote compare do it on the same repository
211 211 else:
212 212 hgrepo = other_repo._repo
213 213
214 revs = ["ancestors(id('%s')) and not ancestors(id('%s'))" %
215 (other_rev, org_rev)]
216 changesets = [other_repo.get_changeset(cs)
217 for cs in scmutil.revrange(hgrepo, revs)]
214 if merge:
215 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
216 (other_rev, org_rev, org_rev)]
218 217
219 if org_repo != other_repo:
220 218 ancestors = scmutil.revrange(hgrepo,
221 219 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
222 220 if len(ancestors) == 1:
223 221 ancestor = hgrepo[ancestors[0]].hex()
222 else:
223 # TODO: have both + and - changesets
224 revs = ["id('%s') :: id('%s') - id('%s')" %
225 (org_rev, other_rev, org_rev)]
226
227 changesets = [other_repo.get_changeset(cs)
228 for cs in scmutil.revrange(hgrepo, revs)]
224 229
225 230 elif alias == 'git':
226 231 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
227 232 so, se = org_repo.run_git_command(
228 233 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
229 234 other_ref[1])
230 235 )
231 236 changesets = [org_repo.get_changeset(cs)
232 237 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
233 238
234 239 return changesets, ancestor
235 240
236 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
241 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
237 242 """
238 243 Returns incoming changesets for mercurial repositories
239 244
240 245 :param org_repo:
241 246 :param org_ref:
242 247 :param other_repo:
243 248 :param other_ref:
244 249 """
245 250
246 251 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
247 252 raise Exception('org_ref must be a two element list/tuple')
248 253
249 254 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
250 255 raise Exception('other_ref must be a two element list/tuple')
251 256
252 257 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
253 258 org_repo.scm_instance, org_ref,
254 other_repo.scm_instance, other_ref)
259 other_repo.scm_instance, other_ref,
260 merge)
255 261 return cs_ranges, ancestor
@@ -1,296 +1,296 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_(u'Home'),h.url('/'))}
11 11 &raquo;
12 12 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
13 13 &raquo;
14 14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 15 ${_('changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 16 </%def>
17 17
18 18 <%def name="page_nav()">
19 19 ${self.menu('changelog')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <div class="table">
29 29 % if c.pagination:
30 30 <div id="graph">
31 31 <div id="graph_nodes">
32 32 <canvas id="graph_canvas"></canvas>
33 33 </div>
34 34 <div id="graph_content">
35 35 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
36 36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
37 37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
38 38
39 39 %if c.rhodecode_db_repo.fork:
40 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default')}" class="ui-btn small">${_('Compare fork with parent')}</a>
40 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default',merge=1)}" class="ui-btn small">${_('Compare fork with parent')}</a>
41 41 %endif
42 42 %if h.is_hg(c.rhodecode_repo):
43 43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
44 44 %endif
45 45 </div>
46 46 <div class="container_header">
47 47 ${h.form(h.url.current(),method='get')}
48 48 <div class="info_box" style="float:left">
49 49 ${h.submit('set',_('Show'),class_="ui-btn")}
50 50 ${h.text('size',size=1,value=c.size)}
51 51 ${_('revisions')}
52 52 </div>
53 53 ${h.end_form()}
54 54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
55 55 </div>
56 56
57 57 %for cnt,cs in enumerate(c.pagination):
58 58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
59 59 <div class="left">
60 60 <div>
61 61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
62 62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
63 63 </div>
64 64 <div class="author">
65 65 <div class="gravatar">
66 66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
67 67 </div>
68 68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
69 69 </div>
70 70 <div class="date">${h.fmt_date(cs.date)}</div>
71 71 </div>
72 72 <div class="mid">
73 73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
74 74 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
75 75 </div>
76 76 <div class="right">
77 77 <div class="changes">
78 78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
79 79 <div class="comments-container">
80 80 %if len(c.comments.get(cs.raw_id,[])) > 0:
81 81 <div class="comments-cnt" title="${('comments')}">
82 82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
83 83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
84 84 <img src="${h.url('/images/icons/comments.png')}">
85 85 </a>
86 86 </div>
87 87 %endif
88 88 </div>
89 89 <div class="changeset-status-container">
90 90 %if c.statuses.get(cs.raw_id):
91 91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
92 92 <div class="changeset-status-ico">
93 93 %if c.statuses.get(cs.raw_id)[2]:
94 94 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
95 95 %else:
96 96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
97 97 %endif
98 98 </div>
99 99 %endif
100 100 </div>
101 101 </div>
102 102 %if cs.parents:
103 103 %for p_cs in reversed(cs.parents):
104 104 <div class="parent">${_('Parent')}
105 105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
106 106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
107 107 </div>
108 108 %endfor
109 109 %else:
110 110 <div class="parent">${_('No parents')}</div>
111 111 %endif
112 112
113 113 <span class="logtags">
114 114 %if len(cs.parents)>1:
115 115 <span class="merge">${_('merge')}</span>
116 116 %endif
117 117 %if cs.branch:
118 118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
119 119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
120 120 </span>
121 121 %endif
122 122 %if h.is_hg(c.rhodecode_repo):
123 123 %for book in cs.bookmarks:
124 124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
125 125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
126 126 </span>
127 127 %endfor
128 128 %endif
129 129 %for tag in cs.tags:
130 130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
131 131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
132 132 %endfor
133 133 </span>
134 134 </div>
135 135 </div>
136 136
137 137 %endfor
138 138 <div class="pagination-wh pagination-left">
139 139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 140 </div>
141 141 </div>
142 142 </div>
143 143
144 144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 145 <script type="text/javascript">
146 146 YAHOO.util.Event.onDOMReady(function(){
147 147
148 148 //Monitor range checkboxes and build a link to changesets
149 149 //ranges
150 150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
153 153
154 154 var checkbox_checker = function(e){
155 155 var clicked_cb = e.currentTarget;
156 156 var checked_checkboxes = [];
157 157 for (pos in checkboxes){
158 158 if(checkboxes[pos].checked){
159 159 checked_checkboxes.push(checkboxes[pos]);
160 160 }
161 161 }
162 162 if(YUD.get('open_new_pr')){
163 163 if(checked_checkboxes.length>1){
164 164 YUD.setStyle('open_new_pr','display','none');
165 165 } else {
166 166 YUD.setStyle('open_new_pr','display','');
167 167 if(checked_checkboxes.length>0){
168 168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
169 169 }else{
170 170 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
171 171 }
172 172 }
173 173 }
174 174
175 175 if(checked_checkboxes.length>0){
176 176 var rev_end = checked_checkboxes[0].name;
177 177 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
178 178 var url = url_tmpl.replace('__REVRANGE__',
179 179 rev_start+'...'+rev_end);
180 180
181 181 var link = (rev_start == rev_end)
182 182 ? _TM['Show selected change __S']
183 183 : _TM['Show selected changes __S -> __E'];
184 184
185 185 link = link.replace('__S',rev_start.substr(0,6));
186 186 link = link.replace('__E',rev_end.substr(0,6));
187 187 YUD.get('rev_range_container').href = url;
188 188 YUD.get('rev_range_container').innerHTML = link;
189 189 YUD.setStyle('rev_range_container','display','');
190 190 YUD.setStyle('rev_range_clear','display','');
191 191
192 192 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
193 193 YUD.setStyle('compare_fork','display','none');
194 194 }
195 195 else{
196 196 YUD.setStyle('rev_range_container','display','none');
197 197 YUD.setStyle('rev_range_clear','display','none');
198 198 YUD.get('open_new_pr').href = pr_tmpl
199 199 YUD.setStyle('compare_fork','display','');
200 200 }
201 201 };
202 202 YUE.onDOMReady(checkbox_checker);
203 203 YUE.on(checkboxes,'click', checkbox_checker);
204 204
205 205 YUE.on('rev_range_clear','click',function(e){
206 206 for (var i=0; i<checkboxes.length; i++){
207 207 var cb = checkboxes[i];
208 208 cb.checked = false;
209 209 }
210 210 YUE.preventDefault(e);
211 211 })
212 212 var msgs = YUQ('.message');
213 213 // get first element height
214 214 var el = YUQ('#graph_content .container')[0];
215 215 var row_h = el.clientHeight;
216 216 for(var i=0;i<msgs.length;i++){
217 217 var m = msgs[i];
218 218
219 219 var h = m.clientHeight;
220 220 var pad = YUD.getStyle(m,'padding');
221 221 if(h > row_h){
222 222 var offset = row_h - (h+12);
223 223 YUD.setStyle(m.nextElementSibling,'display','block');
224 224 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
225 225 };
226 226 }
227 227 YUE.on(YUQ('.expand'),'click',function(e){
228 228 var elem = e.currentTarget.parentNode.parentNode;
229 229 YUD.setStyle(e.currentTarget,'display','none');
230 230 YUD.setStyle(elem,'height','auto');
231 231
232 232 //redraw the graph, line_count and jsdata are global vars
233 233 set_canvas(100);
234 234
235 235 var r = new BranchRenderer();
236 236 r.render(jsdata,100,line_count);
237 237
238 238 })
239 239
240 240 // Fetch changeset details
241 241 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
242 242 var id = e.currentTarget.id;
243 243 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
244 244 var url = url.replace('__CS__',id.replace('changed_total_',''));
245 245 ypjax(url,id,function(){tooltip_activate()});
246 246 });
247 247
248 248 // change branch filter
249 249 YUE.on(YUD.get('branch_filter'),'change',function(e){
250 250 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
251 251 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
252 252 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
253 253 var url = url.replace('__BRANCH__',selected_branch);
254 254 if(selected_branch != ''){
255 255 window.location = url;
256 256 }else{
257 257 window.location = url_main;
258 258 }
259 259
260 260 });
261 261
262 262 function set_canvas(width) {
263 263 var c = document.getElementById('graph_nodes');
264 264 var t = document.getElementById('graph_content');
265 265 canvas = document.getElementById('graph_canvas');
266 266 var div_h = t.clientHeight;
267 267 c.style.height=div_h+'px';
268 268 canvas.setAttribute('height',div_h);
269 269 c.style.height=width+'px';
270 270 canvas.setAttribute('width',width);
271 271 };
272 272 var heads = 1;
273 273 var line_count = 0;
274 274 var jsdata = ${c.jsdata|n};
275 275
276 276 for (var i=0;i<jsdata.length;i++) {
277 277 var in_l = jsdata[i][2];
278 278 for (var j in in_l) {
279 279 var m = in_l[j][1];
280 280 if (m > line_count)
281 281 line_count = m;
282 282 }
283 283 }
284 284 set_canvas(100);
285 285
286 286 var r = new BranchRenderer();
287 287 r.render(jsdata,100,line_count);
288 288
289 289 });
290 290 </script>
291 291 %else:
292 292 ${_('There are no changes yet')}
293 293 %endif
294 294 </div>
295 295 </div>
296 296 </%def>
@@ -1,28 +1,36 b''
1 1 ## Changesets table !
2 2 <div class="container">
3 <table class="compare_view_commits noborder">
4 3 %if not c.cs_ranges:
5 4 <span class="empty_data">${_('No changesets')}</span>
6 5 %else:
6 <table class="compare_view_commits noborder">
7 7 %for cnt, cs in enumerate(c.cs_ranges):
8 8 <tr>
9 9 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
10 10 <td>
11 11 %if cs.raw_id in c.statuses:
12 12 <div title="${c.statuses[cs.raw_id][1]}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cs.raw_id][0])}" /></div>
13 13 %endif
14 14 </td>
15 15 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.other_repo.repo_name,revision=cs.raw_id))}
16 16 %if c.as_form:
17 17 ${h.hidden('revisions',cs.raw_id)}
18 18 %endif
19 19 </td>
20 20 <td><div class="author">${h.person(cs.author)}</div></td>
21 21 <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${cs.date}</span></td>
22 22 <td><div class="message tooltip" title="${h.tooltip(cs.message)}" style="white-space:normal">${h.urlify_commit(h.shorter(cs.message, 60),c.repo_name)}</div></td>
23 23 </tr>
24 24 %endfor
25
25 </table>
26 %if c.ancestor:
27 <span class="ancestor">${_('Ancestor')}:
28 ${h.link_to(c.ancestor,h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
29 </span>
26 30 %endif
27 </table>
31 %if c.as_form:
32 ${h.hidden('ancestor_rev',c.ancestor)}
33 ${h.hidden('merge_rev',c.cs_ranges[-1].raw_id)}
34 %endif
35 %endif
28 36 </div>
@@ -1,198 +1,199 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('New pull request')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
11 11 &raquo;
12 12 ${_('new pull request')}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 23 <div style="float:left;padding:0px 30px 30px 30px">
24 24 ##ORG
25 25 <div style="float:left">
26 26 <div>
27 27 <span style="font-size: 20px">
28 28 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref',c.default_org_ref,c.org_refs,class_='refs')}
29 29 </span>
30 30 <div style="padding:5px 3px 3px 20px;">${c.rhodecode_db_repo.description}</div>
31 31 </div>
32 32 <div style="clear:both;padding-top: 10px"></div>
33 33 </div>
34 34 <div style="float:left;font-size:24px;padding:0px 20px">
35 35 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
36 36 </div>
37 37
38 38 ##OTHER, most Probably the PARENT OF THIS FORK
39 39 <div style="float:left">
40 40 <div>
41 41 <span style="font-size: 20px">
42 42 ${h.select('other_repo',c.default_other_repo,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_other_ref,c.default_other_refs,class_='refs')}
43 43 </span>
44 44 <div id="other_repo_desc" style="padding:5px 3px 3px 20px;"></div>
45 45 </div>
46 46 <div style="clear:both;padding-top: 10px"></div>
47 47 </div>
48 48 <div style="clear:both;padding-top: 10px"></div>
49 49 ## overview pulled by ajax
50 50 <div style="float:left" id="pull_request_overview"></div>
51 51 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
52 52 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
53 53 </div>
54 54 </div>
55 55 <div style="float:left; border-left:1px dashed #eee">
56 56 <h4>${_('Pull request reviewers')}</h4>
57 57 <div id="reviewers" style="padding:0px 0px 0px 15px">
58 58 ## members goes here !
59 59 <div class="group_members_wrap">
60 60 <ul id="review_members" class="group_members">
61 61 %for member in c.review_members:
62 62 <li id="reviewer_${member.user_id}">
63 63 <div class="reviewers_member">
64 64 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
65 65 <div style="float:left">${member.full_name} (${_('owner')})</div>
66 66 <input type="hidden" value="${member.user_id}" name="review_members" />
67 67 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
68 68 </div>
69 69 </li>
70 70 %endfor
71 71 </ul>
72 72 </div>
73 73
74 74 <div class='ac'>
75 75 <div class="reviewer_ac">
76 76 ${h.text('user', class_='yui-ac-input')}
77 77 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
78 78 <div id="reviewers_container"></div>
79 79 </div>
80 80 </div>
81 81 </div>
82 82 </div>
83 83 <h3>${_('Create new pull request')}</h3>
84 84
85 85 <div class="form">
86 86 <!-- fields -->
87 87
88 88 <div class="fields">
89 89
90 90 <div class="field">
91 91 <div class="label">
92 92 <label for="pullrequest_title">${_('Title')}:</label>
93 93 </div>
94 94 <div class="input">
95 95 ${h.text('pullrequest_title',size=30)}
96 96 </div>
97 97 </div>
98 98
99 99 <div class="field">
100 100 <div class="label label-textarea">
101 101 <label for="pullrequest_desc">${_('description')}:</label>
102 102 </div>
103 103 <div class="textarea text-area editor">
104 104 ${h.textarea('pullrequest_desc',size=30)}
105 105 </div>
106 106 </div>
107 107
108 108 <div class="buttons">
109 109 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
110 110 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
111 111 </div>
112 112 </div>
113 113 </div>
114 114 ${h.end_form()}
115 115
116 116 </div>
117 117
118 118 <script type="text/javascript">
119 119 var _USERS_AC_DATA = ${c.users_array|n};
120 120 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
121 121 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
122 122
123 123 var other_repos_info = ${c.other_repos_info|n};
124 124
125 125 var loadPreview = function(){
126 126 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
127 127 //url template
128 128 var url = "${h.url('compare_url',
129 129 repo_name='__other_repo__',
130 130 org_ref_type='__other_ref_type__',
131 131 org_ref='__other_ref__',
132 132 other_repo='__org_repo__',
133 133 other_ref_type='__org_ref_type__',
134 134 other_ref='__org_ref__',
135 135 as_form=True,
136 merge=True,
136 137 )}";
137 138 var org_repo = YUQ('#pull_request_form #org_repo')[0].value;
138 139 var org_ref = YUQ('#pull_request_form #org_ref')[0].value.split(':');
139 140
140 141 var other_repo = YUQ('#pull_request_form #other_repo')[0].value;
141 142 var other_ref = YUQ('#pull_request_form #other_ref')[0].value.split(':');
142 143
143 144 var select_refs = YUQ('#pull_request_form select.refs')
144 145 var rev_data = {
145 146 'org_repo': org_repo,
146 147 'org_ref': org_ref[2],
147 148 'org_ref_type': 'rev',
148 149 'other_repo': other_repo,
149 150 'other_ref': other_ref[2],
150 151 'other_ref_type': 'rev',
151 152 }; // gather the org/other ref and repo here
152 153
153 154 for (k in rev_data){
154 155 url = url.replace('__'+k+'__',rev_data[k]);
155 156 }
156 157
157 158 YUD.get('pull_request_overview').innerHTML = "${_('Loading ...')}";
158 159 YUD.get('pull_request_overview_url').href = url; // shouldn't have as_form ... but ...
159 160 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
160 161 ypjax(url,'pull_request_overview', function(data){
161 162 var sel_box = YUQ('#pull_request_form #other_repo')[0];
162 163 var repo_name = sel_box.options[sel_box.selectedIndex].value;
163 164 var _data = other_repos_info[repo_name];
164 165 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
165 166 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
166 167 // select back the revision that was just compared
167 168 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
168 169 // reset && add the reviewer based on selected repo
169 170 YUD.get('review_members').innerHTML = '';
170 171 addReviewMember(_data.user.user_id, _data.user.firstname,
171 172 _data.user.lastname, _data.user.username,
172 173 _data.user.gravatar_link);
173 174 })
174 175 }
175 176
176 177 ## refresh automatically when something changes (org_repo can't change)
177 178
178 179 YUE.on('org_ref', 'change', function(e){
179 180 loadPreview();
180 181 });
181 182
182 183 YUE.on('other_repo', 'change', function(e){
183 184 var repo_name = e.currentTarget.value;
184 185 // replace the <select> of changed repo
185 186 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
186 187 loadPreview();
187 188 });
188 189
189 190 YUE.on('other_ref', 'change', function(e){
190 191 loadPreview();
191 192 });
192 193
193 194 //lazy load overview after 0.5s
194 195 setTimeout(loadPreview, 500)
195 196
196 197 </script>
197 198
198 199 </%def>
@@ -1,434 +1,441 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.repo import RepoModel
3 3 from rhodecode.model.meta import Session
4 4 from rhodecode.model.db import Repository
5 5 from rhodecode.model.scm import ScmModel
6 6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7 7
8 8
9 9 def _fork_repo(fork_name, vcs_type, parent=None):
10 10 if vcs_type =='hg':
11 11 _REPO = HG_REPO
12 12 elif vcs_type == 'git':
13 13 _REPO = GIT_REPO
14 14
15 15 if parent:
16 16 _REPO = parent
17 17
18 18 form_data = dict(
19 19 repo_name=fork_name,
20 20 repo_name_full=fork_name,
21 21 repo_group=None,
22 22 repo_type=vcs_type,
23 23 description='',
24 24 private=False,
25 25 copy_permissions=False,
26 26 landing_rev='tip',
27 27 update_after_clone=False,
28 28 fork_parent_id=Repository.get_by_repo_name(_REPO),
29 29 )
30 30 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
31 31
32 32 Session().commit()
33 33 return Repository.get_by_repo_name(fork_name)
34 34
35 35
36 36 def _commit_change(repo, filename, content, message, vcs_type, parent=None, newfile=False):
37 37 repo = Repository.get_by_repo_name(repo)
38 38 _cs = parent
39 39 if not parent:
40 40 _cs = EmptyChangeset(alias=vcs_type)
41 41
42 42 if newfile:
43 43 cs = ScmModel().create_node(
44 44 repo=repo.scm_instance, repo_name=repo.repo_name,
45 45 cs=_cs, user=TEST_USER_ADMIN_LOGIN,
46 46 author=TEST_USER_ADMIN_LOGIN,
47 47 message=message,
48 48 content=content,
49 49 f_path=filename
50 50 )
51 51 else:
52 52 cs = ScmModel().commit_change(
53 53 repo=repo.scm_instance, repo_name=repo.repo_name,
54 54 cs=parent, user=TEST_USER_ADMIN_LOGIN,
55 55 author=TEST_USER_ADMIN_LOGIN,
56 56 message=message,
57 57 content=content,
58 58 f_path=filename
59 59 )
60 60 return cs
61 61
62 62
63 63 class TestCompareController(TestController):
64 64
65 65 def setUp(self):
66 66 self.r1_id = None
67 67 self.r2_id = None
68 68
69 69 def tearDown(self):
70 70 if self.r2_id:
71 71 RepoModel().delete(self.r2_id)
72 72 if self.r1_id:
73 73 RepoModel().delete(self.r1_id)
74 74 Session().commit()
75 75 Session.remove()
76 76
77 77 def test_compare_forks_on_branch_extra_commits_hg(self):
78 78 self.log_user()
79 79 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
80 80 description='diff-test',
81 81 owner=TEST_USER_ADMIN_LOGIN)
82 82 Session().commit()
83 83 self.r1_id = repo1.repo_id
84 84 #commit something !
85 85 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
86 86 message='commit1', vcs_type='hg', parent=None, newfile=True)
87 87
88 88 #fork this repo
89 89 repo2 = _fork_repo('one-fork', 'hg', parent='one')
90 90 self.r2_id = repo2.repo_id
91 91
92 92 #add two extra commit into fork
93 93 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
94 94 message='commit2', vcs_type='hg', parent=cs0)
95 95
96 96 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
97 97 message='commit3', vcs_type='hg', parent=cs1)
98 98
99 99 rev1 = 'default'
100 100 rev2 = 'default'
101 101
102 102 response = self.app.get(url(controller='compare', action='index',
103 103 repo_name=repo1.repo_name,
104 104 org_ref_type="branch",
105 105 org_ref=rev2,
106 106 other_repo=repo2.repo_name,
107 107 other_ref_type="branch",
108 108 other_ref=rev1,
109 merge='1',
109 110 ))
110 111
111 112 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
112 113 response.mustcontain("""Showing 2 commits""")
113 114 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
114 115
115 116 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
116 117 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
117 118
118 119 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
119 120 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
120 121 ## files
121 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
122 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=1#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
122 123 #swap
123 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
124 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
124 125
125 126 def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
126 127 self.log_user()
127 128
128 129 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
129 130 description='diff-test',
130 131 owner=TEST_USER_ADMIN_LOGIN)
131 132 Session().commit()
132 133 self.r1_id = repo1.repo_id
133 134
134 135 #commit something !
135 136 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
136 137 message='commit1', vcs_type='hg', parent=None, newfile=True)
137 138
138 139 #fork this repo
139 140 repo2 = _fork_repo('one-fork', 'hg', parent='one')
140 141 self.r2_id = repo2.repo_id
141 142
142 143 #now commit something to origin repo
143 144 cs1_prim = _commit_change(repo1.repo_name, filename='file2', content='line1file2\n',
144 145 message='commit2', vcs_type='hg', parent=cs0, newfile=True)
145 146
146 147 #add two extra commit into fork
147 148 cs1 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\n',
148 149 message='commit2', vcs_type='hg', parent=cs0)
149 150
150 151 cs2 = _commit_change(repo2.repo_name, filename='file1', content='line1\nline2\nline3\n',
151 152 message='commit3', vcs_type='hg', parent=cs1)
152 153
153 154 rev1 = 'default'
154 155 rev2 = 'default'
155 156
156 157 response = self.app.get(url(controller='compare', action='index',
157 158 repo_name=repo1.repo_name,
158 159 org_ref_type="branch",
159 160 org_ref=rev2,
160 161 other_repo=repo2.repo_name,
161 162 other_ref_type="branch",
162 163 other_ref=rev1,
164 merge='x',
163 165 ))
164 166 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, rev2, repo2.repo_name, rev1))
165 167 response.mustcontain("""Showing 2 commits""")
166 168 response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
167 169
168 170 response.mustcontain("""<div class="message tooltip" title="commit2" style="white-space:normal">commit2</div>""")
169 171 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
170 172
171 173 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
172 174 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
173 175 ## files
174 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
176 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=x#C--826e8142e6ba">file1</a>""" % (repo1.repo_name, rev2, rev1, repo2.repo_name))
175 177 #swap
176 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
178 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True">[swap]</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
177 179
178 180 def test_compare_cherry_pick_changesets_from_bottom(self):
179 181
180 182 # repo1:
181 183 # cs0:
182 184 # cs1:
183 185 # repo1-fork- in which we will cherry pick bottom changesets
184 186 # cs0:
185 187 # cs1:
186 188 # cs2: x
187 189 # cs3: x
188 190 # cs4: x
189 191 # cs5:
190 192 #make repo1, and cs1+cs2
191 193 self.log_user()
192 194
193 195 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
194 196 description='diff-test',
195 197 owner=TEST_USER_ADMIN_LOGIN)
196 198 Session().commit()
197 199 self.r1_id = repo1.repo_id
198 200
199 201 #commit something !
200 202 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
201 203 message='commit1', vcs_type='hg', parent=None,
202 204 newfile=True)
203 205 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
204 206 message='commit2', vcs_type='hg', parent=cs0)
205 207 #fork this repo
206 208 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
207 209 self.r2_id = repo2.repo_id
208 210 #now make cs3-6
209 211 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
210 212 message='commit3', vcs_type='hg', parent=cs1)
211 213 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
212 214 message='commit4', vcs_type='hg', parent=cs2)
213 215 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
214 216 message='commit5', vcs_type='hg', parent=cs3)
215 217 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
216 218 message='commit6', vcs_type='hg', parent=cs4)
217 219
218 220 response = self.app.get(url(controller='compare', action='index',
219 221 repo_name=repo2.repo_name,
220 222 org_ref_type="rev",
221 223 org_ref=cs1.short_id, # parent of cs2, in repo2
222 224 other_repo=repo1.repo_name,
223 225 other_ref_type="rev",
224 226 other_ref=cs4.short_id,
227 merge='True',
225 228 ))
226 229 response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, cs1.short_id, repo1.repo_name, cs4.short_id))
227 230 response.mustcontain("""Showing 3 commits""")
228 231 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
229 232
230 233 response.mustcontain("""<div class="message tooltip" title="commit3" style="white-space:normal">commit3</div>""")
231 234 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
232 235 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
233 236
234 237 response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
235 238 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
236 239 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
237 240 ## files
238 241 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
239 242
240 243 def test_compare_cherry_pick_changesets_from_top(self):
241 244 # repo1:
242 245 # cs0:
243 246 # cs1:
244 247 # repo1-fork- in which we will cherry pick bottom changesets
245 248 # cs0:
246 249 # cs1:
247 250 # cs2:
248 251 # cs3: x
249 252 # cs4: x
250 253 # cs5: x
251 254 #
252 255 #make repo1, and cs1+cs2
253 256 self.log_user()
254 257 repo1 = RepoModel().create_repo(repo_name='repo1', repo_type='hg',
255 258 description='diff-test',
256 259 owner=TEST_USER_ADMIN_LOGIN)
257 260 Session().commit()
258 261 self.r1_id = repo1.repo_id
259 262
260 263 #commit something !
261 264 cs0 = _commit_change(repo1.repo_name, filename='file1', content='line1\n',
262 265 message='commit1', vcs_type='hg', parent=None,
263 266 newfile=True)
264 267 cs1 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\n',
265 268 message='commit2', vcs_type='hg', parent=cs0)
266 269 #fork this repo
267 270 repo2 = _fork_repo('repo1-fork', 'hg', parent='repo1')
268 271 self.r2_id = repo2.repo_id
269 272 #now make cs3-6
270 273 cs2 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\n',
271 274 message='commit3', vcs_type='hg', parent=cs1)
272 275 cs3 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\n',
273 276 message='commit4', vcs_type='hg', parent=cs2)
274 277 cs4 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\n',
275 278 message='commit5', vcs_type='hg', parent=cs3)
276 279 cs5 = _commit_change(repo1.repo_name, filename='file1', content='line1\nline2\nline3\nline4\nline5\nline6\n',
277 280 message='commit6', vcs_type='hg', parent=cs4)
278 281 response = self.app.get(url(controller='compare', action='index',
279 282 repo_name=repo1.repo_name,
280 283 org_ref_type="rev",
281 284 org_ref=cs2.short_id, # parent of cs3, not in repo2
282 285 other_ref_type="rev",
283 286 other_ref=cs5.short_id,
287 merge='1',
284 288 ))
285 289
286 290 response.mustcontain('%s@%s -&gt; %s@%s' % (repo1.repo_name, cs2.short_id, repo1.repo_name, cs5.short_id))
287 291 response.mustcontain("""Showing 3 commits""")
288 292 response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
289 293
290 294 response.mustcontain("""<div class="message tooltip" title="commit4" style="white-space:normal">commit4</div>""")
291 295 response.mustcontain("""<div class="message tooltip" title="commit5" style="white-space:normal">commit5</div>""")
292 296 response.mustcontain("""<div class="message tooltip" title="commit6" style="white-space:normal">commit6</div>""")
293 297
294 298 response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
295 299 response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
296 300 response.mustcontain("""<a href="/%s/changeset/%s">r5:%s</a>""" % (repo1.repo_name, cs5.raw_id, cs5.short_id))
297 301 ## files
298 302 response.mustcontain("""#C--826e8142e6ba">file1</a>""")
299 303
300 304 def test_compare_cherry_pick_changeset_mixed_branches(self):
301 305 """
302 306
303 307 """
304 308 pass
305 309 #TODO write this tastecase
306 310
307 311 def test_compare_remote_branches_hg(self):
308 312 self.log_user()
309 313
310 314 repo2 = _fork_repo(HG_FORK, 'hg')
311 315 self.r2_id = repo2.repo_id
312 316 rev1 = '56349e29c2af'
313 317 rev2 = '7d4bc8ec6be5'
314 318
315 319 response = self.app.get(url(controller='compare', action='index',
316 320 repo_name=HG_REPO,
317 321 org_ref_type="rev",
318 322 org_ref=rev1,
319 323 other_ref_type="rev",
320 324 other_ref=rev2,
321 325 other_repo=HG_FORK,
326 merge='1',
322 327 ))
323 328 response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
324 329 ## outgoing changesets between those revisions
325 330
326 331 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
327 332 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
328 333 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
329 334
330 335 ## files
331 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
332 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
333 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
336 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
337 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
338 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s?other_repo=%s&amp;merge=1#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2, HG_FORK))
334 339
335 340 def test_org_repo_new_commits_after_forking_simple_diff(self):
336 341 self.log_user()
337 342
338 343 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
339 344 description='diff-test',
340 345 owner=TEST_USER_ADMIN_LOGIN)
341 346
342 347 Session().commit()
343 348 self.r1_id = repo1.repo_id
344 349 r1_name = repo1.repo_name
345 350
346 351 #commit something initially !
347 352 cs0 = ScmModel().create_node(
348 353 repo=repo1.scm_instance, repo_name=r1_name,
349 354 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
350 355 author=TEST_USER_ADMIN_LOGIN,
351 356 message='commit1',
352 357 content='line1',
353 358 f_path='file1'
354 359 )
355 360 Session().commit()
356 361 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
357 362 #fork the repo1
358 363 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
359 364 description='compare-test',
360 365 clone_uri=repo1.repo_full_path,
361 366 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
362 367 Session().commit()
363 368 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
364 369 self.r2_id = repo2.repo_id
365 370 r2_name = repo2.repo_name
366 371
367 372 #make 3 new commits in fork
368 373 cs1 = ScmModel().create_node(
369 374 repo=repo2.scm_instance, repo_name=r2_name,
370 375 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
371 376 author=TEST_USER_ADMIN_LOGIN,
372 377 message='commit1-fork',
373 378 content='file1-line1-from-fork',
374 379 f_path='file1-fork'
375 380 )
376 381 cs2 = ScmModel().create_node(
377 382 repo=repo2.scm_instance, repo_name=r2_name,
378 383 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
379 384 author=TEST_USER_ADMIN_LOGIN,
380 385 message='commit2-fork',
381 386 content='file2-line1-from-fork',
382 387 f_path='file2-fork'
383 388 )
384 389 cs3 = ScmModel().create_node(
385 390 repo=repo2.scm_instance, repo_name=r2_name,
386 391 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
387 392 author=TEST_USER_ADMIN_LOGIN,
388 393 message='commit3-fork',
389 394 content='file3-line1-from-fork',
390 395 f_path='file3-fork'
391 396 )
392 397
393 398 #compare !
394 399 rev1 = 'default'
395 400 rev2 = 'default'
396 401
397 402 response = self.app.get(url(controller='compare', action='index',
398 403 repo_name=r2_name,
399 404 org_ref_type="branch",
400 405 org_ref=rev1,
401 406 other_ref_type="branch",
402 407 other_ref=rev2,
403 408 other_repo=r1_name,
409 merge='1',
404 410 ))
405 411 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
406 412 response.mustcontain('No files')
407 413 response.mustcontain('No changesets')
408 414
409 415 #add new commit into parent !
410 416 cs0 = ScmModel().create_node(
411 417 repo=repo1.scm_instance, repo_name=r1_name,
412 418 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
413 419 author=TEST_USER_ADMIN_LOGIN,
414 420 message='commit2-parent',
415 421 content='line1-added-after-fork',
416 422 f_path='file2'
417 423 )
418 424 #compare !
419 425 rev1 = 'default'
420 426 rev2 = 'default'
421 427 response = self.app.get(url(controller='compare', action='index',
422 428 repo_name=r2_name,
423 429 org_ref_type="branch",
424 430 org_ref=rev1,
425 431 other_ref_type="branch",
426 432 other_ref=rev2,
427 433 other_repo=r1_name,
434 merge='1',
428 435 ))
429 436
430 437 response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
431 438
432 439 response.mustcontain("""commit2-parent""")
433 440 response.mustcontain("""1 file changed with 1 insertions and 0 deletions""")
434 441 response.mustcontain("""line1-added-after-fork""")
General Comments 0
You need to be logged in to leave comments. Login now