##// END OF EJS Templates
better handling of EmptyChangeset case in cherry pick pull request
marcink -
r3387:bd5420ea beta
parent child Browse files
Show More
@@ -1,192 +1,192 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
87 87 org_repo = c.rhodecode_db_repo.repo_name
88 88 org_ref = (org_ref_type, org_ref)
89 89 other_ref = (other_ref_type, other_ref)
90 90 other_repo = request.GET.get('other_repo', org_repo)
91 91 c.fulldiff = fulldiff = request.GET.get('fulldiff')
92 92 rev_start = request.GET.get('rev_start')
93 93 rev_end = request.GET.get('rev_end')
94 94
95 95 c.swap_url = h.url('compare_url', as_form=request.GET.get('as_form'),
96 96 repo_name=other_repo,
97 97 org_ref_type=other_ref[0], org_ref=other_ref[1],
98 98 other_repo=org_repo,
99 99 other_ref_type=org_ref[0], other_ref=org_ref[1])
100 100
101 101 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
102 102
103 103 org_repo = Repository.get_by_repo_name(org_repo)
104 104 other_repo = Repository.get_by_repo_name(other_repo)
105 105
106 106 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
107 107 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
108 108
109 109 if org_repo is None:
110 110 log.error('Could not find org repo %s' % org_repo)
111 111 raise HTTPNotFound
112 112 if other_repo is None:
113 113 log.error('Could not find other repo %s' % other_repo)
114 114 raise HTTPNotFound
115 115
116 116 if org_repo != other_repo and h.is_git(org_repo):
117 117 log.error('compare of two remote repos not available for GIT REPOS')
118 118 raise HTTPNotFound
119 119
120 120 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
121 121 log.error('compare of two different kind of remote repos not available')
122 122 raise HTTPNotFound
123 123
124 124 c.org_repo = org_repo
125 125 c.other_repo = other_repo
126 126 c.org_ref = org_ref[1]
127 127 c.other_ref = other_ref[1]
128 128 c.org_ref_type = org_ref[0]
129 129 c.other_ref_type = other_ref[0]
130 130
131 131 if rev_start and rev_end:
132 132 # swap revs with cherry picked ones, save them for display
133 133 #org_ref = ('rev', rev_start)
134 134 #other_ref = ('rev', rev_end)
135 135 c.org_ref = rev_start[:12]
136 136 c.other_ref = rev_end[:12]
137 137 # get parent of
138 138 # rev start to include it in the diff
139 139 _cs = other_repo.scm_instance.get_changeset(rev_start)
140 rev_start = _cs.parents[0].raw_id if _cs.parents else EmptyChangeset()
140 rev_start = _cs.parents[0].raw_id if _cs.parents else EmptyChangeset().raw_id
141 141 org_ref = ('rev', rev_start)
142 142 other_ref = ('rev', rev_end)
143 143 #if we cherry pick it's not remote, make the other_repo org_repo
144 144 org_repo = other_repo
145 145
146 146 c.cs_ranges, ancestor = PullRequestModel().get_compare_data(
147 147 org_repo, org_ref, other_repo, other_ref)
148 148
149 149 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
150 150 c.cs_ranges])
151 151 # defines that we need hidden inputs with changesets
152 152 c.as_form = request.GET.get('as_form', False)
153 153 if partial:
154 154 return render('compare/compare_cs.html')
155 155
156 156 if ancestor and org_repo != other_repo:
157 157 # case we want a simple diff without incoming changesets,
158 158 # previewing what will be merged.
159 159 # Make the diff on the forked repo, with
160 160 # revision that is common ancestor
161 161 log.debug('Using ancestor %s as org_ref instead of %s'
162 162 % (ancestor, org_ref))
163 163 org_ref = ('rev', ancestor)
164 164 org_repo = other_repo
165 165
166 166 diff_limit = self.cut_off_limit if not fulldiff else None
167 167
168 168 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
169 169
170 170 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
171 171 diff_limit=diff_limit)
172 172 _parsed = diff_processor.prepare()
173 173
174 174 c.limited_diff = False
175 175 if isinstance(_parsed, LimitedDiffContainer):
176 176 c.limited_diff = True
177 177
178 178 c.files = []
179 179 c.changes = {}
180 180 c.lines_added = 0
181 181 c.lines_deleted = 0
182 182 for f in _parsed:
183 183 st = f['stats']
184 184 if st[0] != 'b':
185 185 c.lines_added += st[0]
186 186 c.lines_deleted += st[1]
187 187 fid = h.FID('', f['filename'])
188 188 c.files.append([fid, f['operation'], f['filename'], f['stats']])
189 189 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
190 190 c.changes[fid] = [f['operation'], f['filename'], diff]
191 191
192 192 return render('compare/compare_diff.html')
@@ -1,248 +1,257 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 from rhodecode.lib.vcs.backends.base import EmptyChangeset
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46
46 47 class PullRequestModel(BaseModel):
47 48
48 49 cls = PullRequest
49 50
50 51 def __get_pull_request(self, pull_request):
51 52 return self._get_instance(PullRequest, pull_request)
52 53
53 54 def get_all(self, repo):
54 55 repo = self._get_repo(repo)
55 56 return PullRequest.query()\
56 57 .filter(PullRequest.other_repo == repo)\
57 58 .order_by(PullRequest.created_on)\
58 59 .all()
59 60
60 61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
61 62 revisions, reviewers, title, description=None):
62 63 from rhodecode.model.changeset_status import ChangesetStatusModel
63 64
64 65 created_by_user = self._get_user(created_by)
65 66 org_repo = self._get_repo(org_repo)
66 67 other_repo = self._get_repo(other_repo)
67 68
68 69 new = PullRequest()
69 70 new.org_repo = org_repo
70 71 new.org_ref = org_ref
71 72 new.other_repo = other_repo
72 73 new.other_ref = other_ref
73 74 new.revisions = revisions
74 75 new.title = title
75 76 new.description = description
76 77 new.author = created_by_user
77 78 self.sa.add(new)
78 79 Session().flush()
79 80 #members
80 81 for member in reviewers:
81 82 _usr = self._get_user(member)
82 83 reviewer = PullRequestReviewers(_usr, new)
83 84 self.sa.add(reviewer)
84 85
85 86 #reset state to under-review
86 87 ChangesetStatusModel().set_status(
87 88 repo=org_repo,
88 89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
89 90 user=created_by_user,
90 91 pull_request=new
91 92 )
92 93
93 94 #notification to reviewers
94 95 notif = NotificationModel()
95 96
96 97 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
97 98 pull_request_id=new.pull_request_id,
98 99 qualified=True,
99 100 )
100 101 subject = safe_unicode(
101 102 h.link_to(
102 103 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
103 104 {'user': created_by_user.username,
104 105 'pr_title': new.title,
105 106 'pr_id': new.pull_request_id},
106 107 pr_url
107 108 )
108 109 )
109 110 body = description
110 111 kwargs = {
111 112 'pr_title': title,
112 113 'pr_user_created': h.person(created_by_user.email),
113 114 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
114 115 qualified=True,),
115 116 'pr_url': pr_url,
116 117 'pr_revisions': revisions
117 118 }
118 119 notif.create(created_by=created_by_user, subject=subject, body=body,
119 120 recipients=reviewers,
120 121 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
121 122 return new
122 123
123 124 def update_reviewers(self, pull_request, reviewers_ids):
124 125 reviewers_ids = set(reviewers_ids)
125 126 pull_request = self.__get_pull_request(pull_request)
126 127 current_reviewers = PullRequestReviewers.query()\
127 128 .filter(PullRequestReviewers.pull_request==
128 129 pull_request)\
129 130 .all()
130 131 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
131 132
132 133 to_add = reviewers_ids.difference(current_reviewers_ids)
133 134 to_remove = current_reviewers_ids.difference(reviewers_ids)
134 135
135 136 log.debug("Adding %s reviewers" % to_add)
136 137 log.debug("Removing %s reviewers" % to_remove)
137 138
138 139 for uid in to_add:
139 140 _usr = self._get_user(uid)
140 141 reviewer = PullRequestReviewers(_usr, pull_request)
141 142 self.sa.add(reviewer)
142 143
143 144 for uid in to_remove:
144 145 reviewer = PullRequestReviewers.query()\
145 146 .filter(PullRequestReviewers.user_id==uid,
146 147 PullRequestReviewers.pull_request==pull_request)\
147 148 .scalar()
148 149 if reviewer:
149 150 self.sa.delete(reviewer)
150 151
151 152 def delete(self, pull_request):
152 153 pull_request = self.__get_pull_request(pull_request)
153 154 Session().delete(pull_request)
154 155
155 156 def close_pull_request(self, pull_request):
156 157 pull_request = self.__get_pull_request(pull_request)
157 158 pull_request.status = PullRequest.STATUS_CLOSED
158 159 pull_request.updated_on = datetime.datetime.now()
159 160 self.sa.add(pull_request)
160 161
161 162 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
162 163 """
163 164 Returns a list of changesets that can be merged from org_repo@org_ref
164 165 to other_repo@other_ref ... and the ancestor that would be used for merge
165 166
166 167 :param org_repo:
167 168 :param org_ref:
168 169 :param other_repo:
169 170 :param other_ref:
170 171 :param tmp:
171 172 """
172 173
173 174 ancestor = None
174 175
175 176 if alias == 'hg':
176 177 # lookup up the exact node id
177 178 _revset_predicates = {
178 179 'branch': 'branch',
179 180 'book': 'bookmark',
180 181 'tag': 'tag',
181 182 'rev': 'id',
182 183 }
184
183 185 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
184 186 safe_str(org_ref[1]))
185 org_rev = scmutil.revsingle(org_repo._repo,
186 org_rev_spec)
187 if org_ref[1] == EmptyChangeset().raw_id:
188 org_rev = org_ref[1]
189 else:
190 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
191 [org_rev_spec])[-1]]
187 192 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
188 193 safe_str(other_ref[1]))
189 other_rev = scmutil.revsingle(other_repo._repo, other_rev_spec)
194 if other_ref[1] == EmptyChangeset().raw_id:
195 other_rev = other_ref[1]
196 else:
197 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
198 [other_rev_spec])[-1]]
190 199
191 200 #case two independent repos
192 201 if org_repo != other_repo:
193 202 hgrepo = unionrepo.unionrepository(other_repo.baseui,
194 203 other_repo.path,
195 204 org_repo.path)
196 205 # all the changesets we are looking for will be in other_repo,
197 206 # so rev numbers from hgrepo can be used in other_repo
198 207
199 208 #no remote compare do it on the same repository
200 209 else:
201 210 hgrepo = other_repo._repo
202 211
203 212 revs = ["ancestors(id('%s')) and not ancestors(id('%s'))" %
204 213 (other_rev, org_rev)]
205 214 changesets = [other_repo.get_changeset(cs)
206 215 for cs in scmutil.revrange(hgrepo, revs)]
207 216
208 217 if org_repo != other_repo:
209 218 ancestors = scmutil.revrange(hgrepo,
210 219 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
211 220 if len(ancestors) == 1:
212 221 ancestor = hgrepo[ancestors[0]].hex()
213 222
214 223 elif alias == 'git':
215 224 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
216 225 so, se = org_repo.run_git_command(
217 226 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
218 227 other_ref[1])
219 228 )
220 229 changesets = [org_repo.get_changeset(cs)
221 230 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
222 231
223 232 return changesets, ancestor
224 233
225 234 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
226 235 """
227 236 Returns incoming changesets for mercurial repositories
228 237
229 238 :param org_repo:
230 239 :param org_ref:
231 240 :param other_repo:
232 241 :param other_ref:
233 242 """
234 243
235 244 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
236 245 raise Exception('org_ref must be a two element list/tuple')
237 246
238 247 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
239 248 raise Exception('other_ref must be a two element list/tuple')
240 249
241 250 org_repo_scm = org_repo.scm_instance
242 251 other_repo_scm = other_repo.scm_instance
243 252
244 253 alias = org_repo.scm_instance.alias
245 254 cs_ranges, ancestor = self._get_changesets(alias,
246 255 org_repo_scm, org_ref,
247 256 other_repo_scm, other_ref)
248 257 return cs_ranges, ancestor
General Comments 0
You need to be logged in to leave comments. Login now