##// END OF EJS Templates
events: improve the error message on missing event commits
marcink -
r2643:c2891b47 default
parent child Browse files
Show More
@@ -1,355 +1,356 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import collections
19 import collections
20 import logging
20 import logging
21 import datetime
21 import datetime
22
22
23 from rhodecode.translation import lazy_ugettext
23 from rhodecode.translation import lazy_ugettext
24 from rhodecode.model.db import User, Repository, Session
24 from rhodecode.model.db import User, Repository, Session
25 from rhodecode.events.base import RhodecodeEvent
25 from rhodecode.events.base import RhodecodeEvent
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def _commits_as_dict(event, commit_ids, repos):
31 def _commits_as_dict(event, commit_ids, repos):
32 """
32 """
33 Helper function to serialize commit_ids
33 Helper function to serialize commit_ids
34
34
35 :param event: class calling this method
35 :param event: class calling this method
36 :param commit_ids: commits to get
36 :param commit_ids: commits to get
37 :param repos: list of repos to check
37 :param repos: list of repos to check
38 """
38 """
39 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.lib.helpers import (
40 from rhodecode.lib.helpers import (
41 urlify_commit_message, process_patterns, chop_at_smart)
41 urlify_commit_message, process_patterns, chop_at_smart)
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43
43
44 if not repos:
44 if not repos:
45 raise Exception('no repo defined')
45 raise Exception('no repo defined')
46
46
47 if not isinstance(repos, (tuple, list)):
47 if not isinstance(repos, (tuple, list)):
48 repos = [repos]
48 repos = [repos]
49
49
50 if not commit_ids:
50 if not commit_ids:
51 return []
51 return []
52
52
53 needed_commits = list(commit_ids)
53 needed_commits = list(commit_ids)
54
54
55 commits = []
55 commits = []
56 reviewers = []
56 reviewers = []
57 for repo in repos:
57 for repo in repos:
58 if not needed_commits:
58 if not needed_commits:
59 return commits # return early if we have the commits we need
59 return commits # return early if we have the commits we need
60
60
61 vcs_repo = repo.scm_instance(cache=False)
61 vcs_repo = repo.scm_instance(cache=False)
62
62
63 try:
63 try:
64 # use copy of needed_commits since we modify it while iterating
64 # use copy of needed_commits since we modify it while iterating
65 for commit_id in list(needed_commits):
65 for commit_id in list(needed_commits):
66 if commit_id.startswith('tag=>'):
66 if commit_id.startswith('tag=>'):
67 raw_id = commit_id[5:]
67 raw_id = commit_id[5:]
68 cs_data = {
68 cs_data = {
69 'raw_id': commit_id, 'short_id': commit_id,
69 'raw_id': commit_id, 'short_id': commit_id,
70 'branch': None,
70 'branch': None,
71 'git_ref_change': 'tag_add',
71 'git_ref_change': 'tag_add',
72 'message': 'Added new tag {}'.format(raw_id),
72 'message': 'Added new tag {}'.format(raw_id),
73 'author': event.actor.full_contact,
73 'author': event.actor.full_contact,
74 'date': datetime.datetime.now(),
74 'date': datetime.datetime.now(),
75 'refs': {
75 'refs': {
76 'branches': [],
76 'branches': [],
77 'bookmarks': [],
77 'bookmarks': [],
78 'tags': []
78 'tags': []
79 }
79 }
80 }
80 }
81 commits.append(cs_data)
81 commits.append(cs_data)
82
82
83 elif commit_id.startswith('delete_branch=>'):
83 elif commit_id.startswith('delete_branch=>'):
84 raw_id = commit_id[15:]
84 raw_id = commit_id[15:]
85 cs_data = {
85 cs_data = {
86 'raw_id': commit_id, 'short_id': commit_id,
86 'raw_id': commit_id, 'short_id': commit_id,
87 'branch': None,
87 'branch': None,
88 'git_ref_change': 'branch_delete',
88 'git_ref_change': 'branch_delete',
89 'message': 'Deleted branch {}'.format(raw_id),
89 'message': 'Deleted branch {}'.format(raw_id),
90 'author': event.actor.full_contact,
90 'author': event.actor.full_contact,
91 'date': datetime.datetime.now(),
91 'date': datetime.datetime.now(),
92 'refs': {
92 'refs': {
93 'branches': [],
93 'branches': [],
94 'bookmarks': [],
94 'bookmarks': [],
95 'tags': []
95 'tags': []
96 }
96 }
97 }
97 }
98 commits.append(cs_data)
98 commits.append(cs_data)
99
99
100 else:
100 else:
101 try:
101 try:
102 cs = vcs_repo.get_changeset(commit_id)
102 cs = vcs_repo.get_changeset(commit_id)
103 except CommitDoesNotExistError:
103 except CommitDoesNotExistError:
104 continue # maybe its in next repo
104 continue # maybe its in next repo
105
105
106 cs_data = cs.__json__()
106 cs_data = cs.__json__()
107 cs_data['refs'] = cs._get_refs()
107 cs_data['refs'] = cs._get_refs()
108
108
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
110 cs_data['reviewers'] = reviewers
110 cs_data['reviewers'] = reviewers
111 cs_data['url'] = RepoModel().get_commit_url(
111 cs_data['url'] = RepoModel().get_commit_url(
112 repo, cs_data['raw_id'], request=event.request)
112 repo, cs_data['raw_id'], request=event.request)
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
113 cs_data['permalink_url'] = RepoModel().get_commit_url(
114 repo, cs_data['raw_id'], request=event.request,
114 repo, cs_data['raw_id'], request=event.request,
115 permalink=True)
115 permalink=True)
116 urlified_message, issues_data = process_patterns(
116 urlified_message, issues_data = process_patterns(
117 cs_data['message'], repo.repo_name)
117 cs_data['message'], repo.repo_name)
118 cs_data['issues'] = issues_data
118 cs_data['issues'] = issues_data
119 cs_data['message_html'] = urlify_commit_message(
119 cs_data['message_html'] = urlify_commit_message(
120 cs_data['message'], repo.repo_name)
120 cs_data['message'], repo.repo_name)
121 cs_data['message_html_title'] = chop_at_smart(
121 cs_data['message_html_title'] = chop_at_smart(
122 cs_data['message'], '\n', suffix_if_chopped='...')
122 cs_data['message'], '\n', suffix_if_chopped='...')
123 commits.append(cs_data)
123 commits.append(cs_data)
124
124
125 needed_commits.remove(commit_id)
125 needed_commits.remove(commit_id)
126
126
127 except Exception:
127 except Exception:
128 log.exception('Failed to extract commits data')
128 log.exception('Failed to extract commits data')
129 # we don't send any commits when crash happens, only full list
129 # we don't send any commits when crash happens, only full list
130 # matters we short circuit then.
130 # matters we short circuit then.
131 return []
131 return []
132
132
133 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
133 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
134 if missing_commits:
134 if missing_commits:
135 log.error('missing commits: %s' % ', '.join(missing_commits))
135 log.error('Inconsistent repository state. '
136 'Missing commits: %s' % ', '.join(missing_commits))
136
137
137 return commits
138 return commits
138
139
139
140
140 def _issues_as_dict(commits):
141 def _issues_as_dict(commits):
141 """ Helper function to serialize issues from commits """
142 """ Helper function to serialize issues from commits """
142 issues = {}
143 issues = {}
143 for commit in commits:
144 for commit in commits:
144 for issue in commit['issues']:
145 for issue in commit['issues']:
145 issues[issue['id']] = issue
146 issues[issue['id']] = issue
146 return issues
147 return issues
147
148
148
149
149 class RepoEvent(RhodecodeEvent):
150 class RepoEvent(RhodecodeEvent):
150 """
151 """
151 Base class for events acting on a repository.
152 Base class for events acting on a repository.
152
153
153 :param repo: a :class:`Repository` instance
154 :param repo: a :class:`Repository` instance
154 """
155 """
155
156
156 def __init__(self, repo):
157 def __init__(self, repo):
157 super(RepoEvent, self).__init__()
158 super(RepoEvent, self).__init__()
158 self.repo = repo
159 self.repo = repo
159
160
160 def as_dict(self):
161 def as_dict(self):
161 from rhodecode.model.repo import RepoModel
162 from rhodecode.model.repo import RepoModel
162 data = super(RepoEvent, self).as_dict()
163 data = super(RepoEvent, self).as_dict()
163
164
164 extra_fields = collections.OrderedDict()
165 extra_fields = collections.OrderedDict()
165 for field in self.repo.extra_fields:
166 for field in self.repo.extra_fields:
166 extra_fields[field.field_key] = field.field_value
167 extra_fields[field.field_key] = field.field_value
167
168
168 data.update({
169 data.update({
169 'repo': {
170 'repo': {
170 'repo_id': self.repo.repo_id,
171 'repo_id': self.repo.repo_id,
171 'repo_name': self.repo.repo_name,
172 'repo_name': self.repo.repo_name,
172 'repo_type': self.repo.repo_type,
173 'repo_type': self.repo.repo_type,
173 'url': RepoModel().get_url(
174 'url': RepoModel().get_url(
174 self.repo, request=self.request),
175 self.repo, request=self.request),
175 'permalink_url': RepoModel().get_url(
176 'permalink_url': RepoModel().get_url(
176 self.repo, request=self.request, permalink=True),
177 self.repo, request=self.request, permalink=True),
177 'extra_fields': extra_fields
178 'extra_fields': extra_fields
178 }
179 }
179 })
180 })
180 return data
181 return data
181
182
182
183
183 class RepoPreCreateEvent(RepoEvent):
184 class RepoPreCreateEvent(RepoEvent):
184 """
185 """
185 An instance of this class is emitted as an :term:`event` before a repo is
186 An instance of this class is emitted as an :term:`event` before a repo is
186 created.
187 created.
187 """
188 """
188 name = 'repo-pre-create'
189 name = 'repo-pre-create'
189 display_name = lazy_ugettext('repository pre create')
190 display_name = lazy_ugettext('repository pre create')
190
191
191
192
192 class RepoCreateEvent(RepoEvent):
193 class RepoCreateEvent(RepoEvent):
193 """
194 """
194 An instance of this class is emitted as an :term:`event` whenever a repo is
195 An instance of this class is emitted as an :term:`event` whenever a repo is
195 created.
196 created.
196 """
197 """
197 name = 'repo-create'
198 name = 'repo-create'
198 display_name = lazy_ugettext('repository created')
199 display_name = lazy_ugettext('repository created')
199
200
200
201
201 class RepoPreDeleteEvent(RepoEvent):
202 class RepoPreDeleteEvent(RepoEvent):
202 """
203 """
203 An instance of this class is emitted as an :term:`event` whenever a repo is
204 An instance of this class is emitted as an :term:`event` whenever a repo is
204 created.
205 created.
205 """
206 """
206 name = 'repo-pre-delete'
207 name = 'repo-pre-delete'
207 display_name = lazy_ugettext('repository pre delete')
208 display_name = lazy_ugettext('repository pre delete')
208
209
209
210
210 class RepoDeleteEvent(RepoEvent):
211 class RepoDeleteEvent(RepoEvent):
211 """
212 """
212 An instance of this class is emitted as an :term:`event` whenever a repo is
213 An instance of this class is emitted as an :term:`event` whenever a repo is
213 created.
214 created.
214 """
215 """
215 name = 'repo-delete'
216 name = 'repo-delete'
216 display_name = lazy_ugettext('repository deleted')
217 display_name = lazy_ugettext('repository deleted')
217
218
218
219
219 class RepoVCSEvent(RepoEvent):
220 class RepoVCSEvent(RepoEvent):
220 """
221 """
221 Base class for events triggered by the VCS
222 Base class for events triggered by the VCS
222 """
223 """
223 def __init__(self, repo_name, extras):
224 def __init__(self, repo_name, extras):
224 self.repo = Repository.get_by_repo_name(repo_name)
225 self.repo = Repository.get_by_repo_name(repo_name)
225 if not self.repo:
226 if not self.repo:
226 raise Exception('repo by this name %s does not exist' % repo_name)
227 raise Exception('repo by this name %s does not exist' % repo_name)
227 self.extras = extras
228 self.extras = extras
228 super(RepoVCSEvent, self).__init__(self.repo)
229 super(RepoVCSEvent, self).__init__(self.repo)
229
230
230 @property
231 @property
231 def actor(self):
232 def actor(self):
232 if self.extras.get('username'):
233 if self.extras.get('username'):
233 return User.get_by_username(self.extras['username'])
234 return User.get_by_username(self.extras['username'])
234
235
235 @property
236 @property
236 def actor_ip(self):
237 def actor_ip(self):
237 if self.extras.get('ip'):
238 if self.extras.get('ip'):
238 return self.extras['ip']
239 return self.extras['ip']
239
240
240 @property
241 @property
241 def server_url(self):
242 def server_url(self):
242 if self.extras.get('server_url'):
243 if self.extras.get('server_url'):
243 return self.extras['server_url']
244 return self.extras['server_url']
244
245
245 @property
246 @property
246 def request(self):
247 def request(self):
247 return self.extras.get('request') or self.get_request()
248 return self.extras.get('request') or self.get_request()
248
249
249
250
250 class RepoPrePullEvent(RepoVCSEvent):
251 class RepoPrePullEvent(RepoVCSEvent):
251 """
252 """
252 An instance of this class is emitted as an :term:`event` before commits
253 An instance of this class is emitted as an :term:`event` before commits
253 are pulled from a repo.
254 are pulled from a repo.
254 """
255 """
255 name = 'repo-pre-pull'
256 name = 'repo-pre-pull'
256 display_name = lazy_ugettext('repository pre pull')
257 display_name = lazy_ugettext('repository pre pull')
257
258
258
259
259 class RepoPullEvent(RepoVCSEvent):
260 class RepoPullEvent(RepoVCSEvent):
260 """
261 """
261 An instance of this class is emitted as an :term:`event` after commits
262 An instance of this class is emitted as an :term:`event` after commits
262 are pulled from a repo.
263 are pulled from a repo.
263 """
264 """
264 name = 'repo-pull'
265 name = 'repo-pull'
265 display_name = lazy_ugettext('repository pull')
266 display_name = lazy_ugettext('repository pull')
266
267
267
268
268 class RepoPrePushEvent(RepoVCSEvent):
269 class RepoPrePushEvent(RepoVCSEvent):
269 """
270 """
270 An instance of this class is emitted as an :term:`event` before commits
271 An instance of this class is emitted as an :term:`event` before commits
271 are pushed to a repo.
272 are pushed to a repo.
272 """
273 """
273 name = 'repo-pre-push'
274 name = 'repo-pre-push'
274 display_name = lazy_ugettext('repository pre push')
275 display_name = lazy_ugettext('repository pre push')
275
276
276
277
277 class RepoPushEvent(RepoVCSEvent):
278 class RepoPushEvent(RepoVCSEvent):
278 """
279 """
279 An instance of this class is emitted as an :term:`event` after commits
280 An instance of this class is emitted as an :term:`event` after commits
280 are pushed to a repo.
281 are pushed to a repo.
281
282
282 :param extras: (optional) dict of data from proxied VCS actions
283 :param extras: (optional) dict of data from proxied VCS actions
283 """
284 """
284 name = 'repo-push'
285 name = 'repo-push'
285 display_name = lazy_ugettext('repository push')
286 display_name = lazy_ugettext('repository push')
286
287
287 def __init__(self, repo_name, pushed_commit_ids, extras):
288 def __init__(self, repo_name, pushed_commit_ids, extras):
288 super(RepoPushEvent, self).__init__(repo_name, extras)
289 super(RepoPushEvent, self).__init__(repo_name, extras)
289 self.pushed_commit_ids = pushed_commit_ids
290 self.pushed_commit_ids = pushed_commit_ids
290 self.new_refs = extras.new_refs
291 self.new_refs = extras.new_refs
291
292
292 def as_dict(self):
293 def as_dict(self):
293 data = super(RepoPushEvent, self).as_dict()
294 data = super(RepoPushEvent, self).as_dict()
294
295
295 def branch_url(branch_name):
296 def branch_url(branch_name):
296 return '{}/changelog?branch={}'.format(
297 return '{}/changelog?branch={}'.format(
297 data['repo']['url'], branch_name)
298 data['repo']['url'], branch_name)
298
299
299 def tag_url(tag_name):
300 def tag_url(tag_name):
300 return '{}/files/{}/'.format(
301 return '{}/files/{}/'.format(
301 data['repo']['url'], tag_name)
302 data['repo']['url'], tag_name)
302
303
303 commits = _commits_as_dict(
304 commits = _commits_as_dict(
304 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
305 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
305
306
306 last_branch = None
307 last_branch = None
307 for commit in reversed(commits):
308 for commit in reversed(commits):
308 commit['branch'] = commit['branch'] or last_branch
309 commit['branch'] = commit['branch'] or last_branch
309 last_branch = commit['branch']
310 last_branch = commit['branch']
310 issues = _issues_as_dict(commits)
311 issues = _issues_as_dict(commits)
311
312
312 branches = set()
313 branches = set()
313 tags = set()
314 tags = set()
314 for commit in commits:
315 for commit in commits:
315 if commit['refs']['tags']:
316 if commit['refs']['tags']:
316 for tag in commit['refs']['tags']:
317 for tag in commit['refs']['tags']:
317 tags.add(tag)
318 tags.add(tag)
318 if commit['branch']:
319 if commit['branch']:
319 branches.add(commit['branch'])
320 branches.add(commit['branch'])
320
321
321 # maybe we have branches in new_refs ?
322 # maybe we have branches in new_refs ?
322 try:
323 try:
323 branches = branches.union(set(self.new_refs['branches']))
324 branches = branches.union(set(self.new_refs['branches']))
324 except Exception:
325 except Exception:
325 pass
326 pass
326
327
327 branches = [
328 branches = [
328 {
329 {
329 'name': branch,
330 'name': branch,
330 'url': branch_url(branch)
331 'url': branch_url(branch)
331 }
332 }
332 for branch in branches
333 for branch in branches
333 ]
334 ]
334
335
335 # maybe we have branches in new_refs ?
336 # maybe we have branches in new_refs ?
336 try:
337 try:
337 tags = tags.union(set(self.new_refs['tags']))
338 tags = tags.union(set(self.new_refs['tags']))
338 except Exception:
339 except Exception:
339 pass
340 pass
340
341
341 tags = [
342 tags = [
342 {
343 {
343 'name': tag,
344 'name': tag,
344 'url': tag_url(tag)
345 'url': tag_url(tag)
345 }
346 }
346 for tag in tags
347 for tag in tags
347 ]
348 ]
348
349
349 data['push'] = {
350 data['push'] = {
350 'commits': commits,
351 'commits': commits,
351 'issues': issues,
352 'issues': issues,
352 'branches': branches,
353 'branches': branches,
353 'tags': tags,
354 'tags': tags,
354 }
355 }
355 return data
356 return data
General Comments 0
You need to be logged in to leave comments. Login now