Show More
@@ -47,13 +47,12 b' class TestClosePullRequest(object):' | |||
|
47 | 47 | 'closed': True, |
|
48 | 48 | } |
|
49 | 49 | assert_ok(id_, expected, response.body) |
|
50 | action = 'user_closed_pull_request:%d' % pull_request_id | |
|
51 | 50 | journal = UserLog.query()\ |
|
52 | .filter(UserLog.user_id == author)\ | |
|
51 | .filter(UserLog.user_id == author) \ | |
|
52 | .order_by('user_log_id') \ | |
|
53 | 53 | .filter(UserLog.repository_id == repo)\ |
|
54 | .filter(UserLog.action == action)\ | |
|
55 | 54 | .all() |
|
56 | assert len(journal) == 1 | |
|
55 | assert journal[-1].action == 'repo.pull_request.close' | |
|
57 | 56 | |
|
58 | 57 | @pytest.mark.backends("git", "hg") |
|
59 | 58 | def test_api_close_pull_request_already_closed_error(self, pr_util): |
@@ -62,13 +62,12 b' class TestCommentPullRequest(object):' | |||
|
62 | 62 | } |
|
63 | 63 | assert_ok(id_, expected, response.body) |
|
64 | 64 | |
|
65 | action = 'user_commented_pull_request:%d' % pull_request_id | |
|
66 | 65 | journal = UserLog.query()\ |
|
67 | 66 | .filter(UserLog.user_id == author)\ |
|
68 | .filter(UserLog.repository_id == repo)\ | |
|
69 | .filter(UserLog.action == action)\ | |
|
67 | .filter(UserLog.repository_id == repo) \ | |
|
68 | .order_by('user_log_id') \ | |
|
70 | 69 | .all() |
|
71 | assert len(journal) == 2 | |
|
70 | assert journal[-1].action == 'repo.pull_request.comment.create' | |
|
72 | 71 | |
|
73 | 72 | @pytest.mark.backends("git", "hg") |
|
74 | 73 | def test_api_comment_pull_request_change_status( |
@@ -33,7 +33,7 b' pytestmark = pytest.mark.backends("git",' | |||
|
33 | 33 | @pytest.mark.usefixtures("testuser_api", "app") |
|
34 | 34 | class TestGetPullRequest(object): |
|
35 | 35 | |
|
36 |
def test_api_get_pull_request(self, pr_util, |
|
|
36 | def test_api_get_pull_request(self, pr_util, http_host_only_stub): | |
|
37 | 37 | from rhodecode.model.pull_request import PullRequestModel |
|
38 | 38 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
39 | 39 | id_, params = build_data( |
@@ -52,7 +52,7 b' class TestGetPullRequest(object):' | |||
|
52 | 52 | pull_request_id=pull_request.pull_request_id, qualified=True)) |
|
53 | 53 | |
|
54 | 54 | pr_url = safe_unicode( |
|
55 | url_obj.with_netloc(http_host_stub)) | |
|
55 | url_obj.with_netloc(http_host_only_stub)) | |
|
56 | 56 | source_url = safe_unicode( |
|
57 | 57 | pull_request.source_repo.clone_url().with_netloc(http_host_only_stub)) |
|
58 | 58 | target_url = safe_unicode( |
@@ -95,13 +95,13 b' class TestMergePullRequest(object):' | |||
|
95 | 95 | |
|
96 | 96 | assert_ok(id_, expected, response.body) |
|
97 | 97 | |
|
98 | action = 'user_merged_pull_request:%d' % (pull_request_id, ) | |
|
99 | 98 | journal = UserLog.query()\ |
|
100 | 99 | .filter(UserLog.user_id == author)\ |
|
101 | .filter(UserLog.repository_id == repo)\ | |
|
102 | .filter(UserLog.action == action)\ | |
|
100 | .filter(UserLog.repository_id == repo) \ | |
|
101 | .order_by('user_log_id') \ | |
|
103 | 102 | .all() |
|
104 | assert len(journal) == 1 | |
|
103 | assert journal[-2].action == 'repo.pull_request.merge' | |
|
104 | assert journal[-1].action == 'repo.pull_request.close' | |
|
105 | 105 | |
|
106 | 106 | id_, params = build_data( |
|
107 | 107 | self.apikey, 'merge_pull_request', |
@@ -33,7 +33,7 b' class TestUpdatePullRequest(object):' | |||
|
33 | 33 | |
|
34 | 34 | @pytest.mark.backends("git", "hg") |
|
35 | 35 | def test_api_update_pull_request_title_or_description( |
|
36 |
self, pr_util, |
|
|
36 | self, pr_util, no_notifications): | |
|
37 | 37 | pull_request = pr_util.create_pull_request() |
|
38 | 38 | |
|
39 | 39 | id_, params = build_data( |
@@ -61,7 +61,7 b' class TestUpdatePullRequest(object):' | |||
|
61 | 61 | |
|
62 | 62 | @pytest.mark.backends("git", "hg") |
|
63 | 63 | def test_api_try_update_closed_pull_request( |
|
64 |
self, pr_util, |
|
|
64 | self, pr_util, no_notifications): | |
|
65 | 65 | pull_request = pr_util.create_pull_request() |
|
66 | 66 | PullRequestModel().close_pull_request( |
|
67 | 67 | pull_request, TEST_USER_ADMIN_LOGIN) |
@@ -78,8 +78,7 b' class TestUpdatePullRequest(object):' | |||
|
78 | 78 | assert_error(id_, expected, response.body) |
|
79 | 79 | |
|
80 | 80 | @pytest.mark.backends("git", "hg") |
|
81 | def test_api_update_update_commits( | |
|
82 | self, pr_util, silence_action_logger, no_notifications): | |
|
81 | def test_api_update_update_commits(self, pr_util, no_notifications): | |
|
83 | 82 | commits = [ |
|
84 | 83 | {'message': 'a'}, |
|
85 | 84 | {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]}, |
@@ -119,7 +118,7 b' class TestUpdatePullRequest(object):' | |||
|
119 | 118 | |
|
120 | 119 | @pytest.mark.backends("git", "hg") |
|
121 | 120 | def test_api_update_change_reviewers( |
|
122 |
self, user_util, pr_util, |
|
|
121 | self, user_util, pr_util, no_notifications): | |
|
123 | 122 | a = user_util.create_user() |
|
124 | 123 | b = user_util.create_user() |
|
125 | 124 | c = user_util.create_user() |
@@ -669,7 +669,7 b' def update_pull_request(' | |||
|
669 | 669 | if title or description: |
|
670 | 670 | PullRequestModel().edit( |
|
671 | 671 | pull_request, title or pull_request.title, |
|
672 | description or pull_request.description) | |
|
672 | description or pull_request.description, apiuser) | |
|
673 | 673 | Session().commit() |
|
674 | 674 | |
|
675 | 675 | commit_changes = {"added": [], "common": [], "removed": []} |
@@ -683,7 +683,7 b' def update_pull_request(' | |||
|
683 | 683 | reviewers_changes = {"added": [], "removed": []} |
|
684 | 684 | if reviewers: |
|
685 | 685 | added_reviewers, removed_reviewers = \ |
|
686 | PullRequestModel().update_reviewers(pull_request, reviewers) | |
|
686 | PullRequestModel().update_reviewers(pull_request, reviewers, apiuser) | |
|
687 | 687 | |
|
688 | 688 | reviewers_changes['added'] = sorted( |
|
689 | 689 | [get_user_or_error(n).username for n in added_reviewers]) |
@@ -628,8 +628,8 b' class UsersController(BaseController):' | |||
|
628 | 628 | |
|
629 | 629 | ip_id = request.POST.get('del_ip_id') |
|
630 | 630 | user_model = UserModel() |
|
631 | user_data = c.user.get_api_data() | |
|
631 | 632 | ip = UserIpMap.query().get(ip_id).ip_addr |
|
632 | user_data = c.user.get_api_data() | |
|
633 | 633 | user_model.delete_extra_ip(user_id, ip_id) |
|
634 | 634 | audit_logger.store_web( |
|
635 | 635 | 'user.edit.ip.delete', |
@@ -440,7 +440,7 b' class ChangesetController(BaseRepoContro' | |||
|
440 | 440 | owner = (comment.author.user_id == c.rhodecode_user.user_id) |
|
441 | 441 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
442 | 442 | if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner: |
|
443 | CommentsModel().delete(comment=comment) | |
|
443 | CommentsModel().delete(comment=comment, user=c.rhodecode_user) | |
|
444 | 444 | Session().commit() |
|
445 | 445 | return True |
|
446 | 446 | else: |
@@ -38,7 +38,7 b' from rhodecode.lib import diffs, helpers' | |||
|
38 | 38 | from rhodecode.lib import audit_logger |
|
39 | 39 | from rhodecode.lib.codeblocks import ( |
|
40 | 40 | filenode_as_lines_tokens, filenode_as_annotated_lines_tokens) |
|
41 |
from rhodecode.lib.utils import jsonify |
|
|
41 | from rhodecode.lib.utils import jsonify | |
|
42 | 42 | from rhodecode.lib.utils2 import ( |
|
43 | 43 | convert_line_endings, detect_mode, safe_str, str2bool) |
|
44 | 44 | from rhodecode.lib.auth import ( |
@@ -317,7 +317,7 b' class PullrequestsController(BaseRepoCon' | |||
|
317 | 317 | try: |
|
318 | 318 | PullRequestModel().edit( |
|
319 | 319 | pull_request, request.POST.get('title'), |
|
320 | request.POST.get('description')) | |
|
320 | request.POST.get('description'), c.rhodecode_user) | |
|
321 | 321 | except ValueError: |
|
322 | 322 | msg = _(u'Cannot update closed pull requests.') |
|
323 | 323 | h.flash(msg, category='error') |
@@ -456,7 +456,8 b' class PullrequestsController(BaseRepoCon' | |||
|
456 | 456 | h.flash(e, category='error') |
|
457 | 457 | return |
|
458 | 458 | |
|
459 |
PullRequestModel().update_reviewers( |
|
|
459 | PullRequestModel().update_reviewers( | |
|
460 | pull_request_id, reviewers, c.rhodecode_user) | |
|
460 | 461 | h.flash(_('Pull request reviewers updated.'), category='success') |
|
461 | 462 | Session().commit() |
|
462 | 463 | |
@@ -476,7 +477,7 b' class PullrequestsController(BaseRepoCon' | |||
|
476 | 477 | |
|
477 | 478 | # only owner can delete it ! |
|
478 | 479 | if allowed_to_delete: |
|
479 | PullRequestModel().delete(pull_request) | |
|
480 | PullRequestModel().delete(pull_request, c.rhodecode_user) | |
|
480 | 481 | Session().commit() |
|
481 | 482 | h.flash(_('Successfully deleted pull request'), |
|
482 | 483 | category='success') |
@@ -997,7 +998,7 b' class PullrequestsController(BaseRepoCon' | |||
|
997 | 998 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) |
|
998 | 999 | if h.HasPermissionAny('hg.admin')() or is_repo_admin or is_owner: |
|
999 | 1000 | old_calculated_status = co.pull_request.calculated_review_status() |
|
1000 | CommentsModel().delete(comment=co) | |
|
1001 | CommentsModel().delete(comment=co, user=c.rhodecode_user) | |
|
1001 | 1002 | Session().commit() |
|
1002 | 1003 | calculated_status = co.pull_request.calculated_review_status() |
|
1003 | 1004 | if old_calculated_status != calculated_status: |
@@ -28,7 +28,7 b' from rhodecode.model.db import User, Use' | |||
|
28 | 28 | log = logging.getLogger(__name__) |
|
29 | 29 | |
|
30 | 30 | # action as key, and expected action_data as value |
|
31 | ACTIONS = { | |
|
31 | ACTIONS_V1 = { | |
|
32 | 32 | 'user.login.success': {'user_agent': ''}, |
|
33 | 33 | 'user.login.failure': {'user_agent': ''}, |
|
34 | 34 | 'user.logout': {'user_agent': ''}, |
@@ -64,11 +64,28 b' ACTIONS = {' | |||
|
64 | 64 | 'repo.commit.strip': {}, |
|
65 | 65 | 'repo.archive.download': {}, |
|
66 | 66 | |
|
67 | 'repo.pull_request.create': '', | |
|
68 | 'repo.pull_request.edit': '', | |
|
69 | 'repo.pull_request.delete': '', | |
|
70 | 'repo.pull_request.close': '', | |
|
71 | 'repo.pull_request.merge': '', | |
|
72 | 'repo.pull_request.vote': '', | |
|
73 | 'repo.pull_request.comment.create': '', | |
|
74 | 'repo.pull_request.comment.delete': '', | |
|
75 | ||
|
76 | 'repo.pull_request.reviewer.add': '', | |
|
77 | 'repo.pull_request.reviewer.delete': '', | |
|
78 | ||
|
79 | 'repo.commit.comment.create': '', | |
|
80 | 'repo.commit.comment.delete': '', | |
|
81 | 'repo.commit.vote': '', | |
|
82 | ||
|
67 | 83 | 'repo_group.create': {'data': {}}, |
|
68 | 84 | 'repo_group.edit': {'old_data': {}}, |
|
69 | 85 | 'repo_group.edit.permissions': {}, |
|
70 | 86 | 'repo_group.delete': {'old_data': {}}, |
|
71 | 87 | } |
|
88 | ACTIONS = ACTIONS_V1 | |
|
72 | 89 | |
|
73 | 90 | SOURCE_WEB = 'source_web' |
|
74 | 91 | SOURCE_API = 'source_api' |
@@ -139,68 +139,6 b' def get_user_group_slug(request):' | |||
|
139 | 139 | return _group |
|
140 | 140 | |
|
141 | 141 | |
|
142 | def action_logger(user, action, repo, ipaddr='', sa=None, commit=False): | |
|
143 | """ | |
|
144 | Action logger for various actions made by users | |
|
145 | ||
|
146 | :param user: user that made this action, can be a unique username string or | |
|
147 | object containing user_id attribute | |
|
148 | :param action: action to log, should be on of predefined unique actions for | |
|
149 | easy translations | |
|
150 | :param repo: string name of repository or object containing repo_id, | |
|
151 | that action was made on | |
|
152 | :param ipaddr: optional ip address from what the action was made | |
|
153 | :param sa: optional sqlalchemy session | |
|
154 | ||
|
155 | """ | |
|
156 | ||
|
157 | if not sa: | |
|
158 | sa = meta.Session() | |
|
159 | # if we don't get explicit IP address try to get one from registered user | |
|
160 | # in tmpl context var | |
|
161 | if not ipaddr: | |
|
162 | ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '') | |
|
163 | ||
|
164 | try: | |
|
165 | if getattr(user, 'user_id', None): | |
|
166 | user_obj = User.get(user.user_id) | |
|
167 | elif isinstance(user, basestring): | |
|
168 | user_obj = User.get_by_username(user) | |
|
169 | else: | |
|
170 | raise Exception('You have to provide a user object or a username') | |
|
171 | ||
|
172 | if getattr(repo, 'repo_id', None): | |
|
173 | repo_obj = Repository.get(repo.repo_id) | |
|
174 | repo_name = repo_obj.repo_name | |
|
175 | elif isinstance(repo, basestring): | |
|
176 | repo_name = repo.lstrip('/') | |
|
177 | repo_obj = Repository.get_by_repo_name(repo_name) | |
|
178 | else: | |
|
179 | repo_obj = None | |
|
180 | repo_name = '' | |
|
181 | ||
|
182 | user_log = UserLog() | |
|
183 | user_log.user_id = user_obj.user_id | |
|
184 | user_log.username = user_obj.username | |
|
185 | action = safe_unicode(action) | |
|
186 | user_log.action = action[:1200000] | |
|
187 | ||
|
188 | user_log.repository = repo_obj | |
|
189 | user_log.repository_name = repo_name | |
|
190 | ||
|
191 | user_log.action_date = datetime.datetime.now() | |
|
192 | user_log.user_ip = ipaddr | |
|
193 | sa.add(user_log) | |
|
194 | ||
|
195 | log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s', | |
|
196 | action, safe_unicode(repo), user_obj, ipaddr) | |
|
197 | if commit: | |
|
198 | sa.commit() | |
|
199 | except Exception: | |
|
200 | log.error(traceback.format_exc()) | |
|
201 | raise | |
|
202 | ||
|
203 | ||
|
204 | 142 | def get_filesystem_repos(path, recursive=False, skip_removed_repos=True): |
|
205 | 143 | """ |
|
206 | 144 | Scans given path for repos and return (name,(type,path)) tuple |
@@ -34,8 +34,8 b' from sqlalchemy.sql.expression import nu' | |||
|
34 | 34 | from sqlalchemy.sql.functions import coalesce |
|
35 | 35 | |
|
36 | 36 | from rhodecode.lib import helpers as h, diffs |
|
37 | from rhodecode.lib import audit_logger | |
|
37 | 38 | from rhodecode.lib.channelstream import channelstream_request |
|
38 | from rhodecode.lib.utils import action_logger | |
|
39 | 39 | from rhodecode.lib.utils2 import extract_mentioned_users, safe_str |
|
40 | 40 | from rhodecode.model import BaseModel |
|
41 | 41 | from rhodecode.model.db import ( |
@@ -163,6 +163,13 b' class CommentsModel(BaseModel):' | |||
|
163 | 163 | |
|
164 | 164 | return todos |
|
165 | 165 | |
|
166 | def _log_audit_action(self, action, action_data, user, comment): | |
|
167 | audit_logger.store( | |
|
168 | action=action, | |
|
169 | action_data=action_data, | |
|
170 | user=user, | |
|
171 | repo=comment.repo) | |
|
172 | ||
|
166 | 173 | def create(self, text, repo, user, commit_id=None, pull_request=None, |
|
167 | 174 | f_path=None, line_no=None, status_change=None, |
|
168 | 175 | status_change_type=None, comment_type=None, |
@@ -337,13 +344,15 b' class CommentsModel(BaseModel):' | |||
|
337 | 344 | email_kwargs=kwargs, |
|
338 | 345 | ) |
|
339 | 346 | |
|
340 | action = ( | |
|
341 |
|
|
|
342 | comment.pull_request.pull_request_id) | |
|
343 | if comment.pull_request | |
|
344 | else 'user_commented_revision:{}'.format(comment.revision) | |
|
345 | ) | |
|
346 | action_logger(user, action, comment.repo) | |
|
347 | Session().flush() | |
|
348 | if comment.pull_request: | |
|
349 | action = 'repo.pull_request.comment.create' | |
|
350 | else: | |
|
351 | action = 'repo.commit.comment.create' | |
|
352 | ||
|
353 | comment_data = comment.get_api_data() | |
|
354 | self._log_audit_action( | |
|
355 | action, {'data': comment_data}, user, comment) | |
|
347 | 356 | |
|
348 | 357 | registry = get_current_registry() |
|
349 | 358 | rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {}) |
@@ -385,15 +394,22 b' class CommentsModel(BaseModel):' | |||
|
385 | 394 | |
|
386 | 395 | return comment |
|
387 | 396 | |
|
388 | def delete(self, comment): | |
|
397 | def delete(self, comment, user): | |
|
389 | 398 | """ |
|
390 | 399 | Deletes given comment |
|
391 | ||
|
392 | :param comment_id: | |
|
393 | 400 | """ |
|
394 | 401 | comment = self.__get_commit_comment(comment) |
|
402 | old_data = comment.get_api_data() | |
|
395 | 403 | Session().delete(comment) |
|
396 | 404 | |
|
405 | if comment.pull_request: | |
|
406 | action = 'repo.pull_request.comment.delete' | |
|
407 | else: | |
|
408 | action = 'repo.commit.comment.delete' | |
|
409 | ||
|
410 | self._log_audit_action( | |
|
411 | action, {'old_data': old_data}, user, comment) | |
|
412 | ||
|
397 | 413 | return comment |
|
398 | 414 | |
|
399 | 415 | def get_all_comments(self, repo_id, revision=None, pull_request=None): |
@@ -3122,6 +3122,25 b' class ChangesetComment(Base, BaseModel):' | |||
|
3122 | 3122 | else: |
|
3123 | 3123 | return '<DB:Comment at %#x>' % id(self) |
|
3124 | 3124 | |
|
3125 | def get_api_data(self): | |
|
3126 | comment = self | |
|
3127 | data = { | |
|
3128 | 'comment_id': comment.comment_id, | |
|
3129 | 'comment_type': comment.comment_type, | |
|
3130 | 'comment_text': comment.text, | |
|
3131 | 'comment_status': comment.status_change, | |
|
3132 | 'comment_f_path': comment.f_path, | |
|
3133 | 'comment_lineno': comment.line_no, | |
|
3134 | 'comment_author': comment.author, | |
|
3135 | 'comment_created_on': comment.created_on | |
|
3136 | } | |
|
3137 | return data | |
|
3138 | ||
|
3139 | def __json__(self): | |
|
3140 | data = dict() | |
|
3141 | data.update(self.get_api_data()) | |
|
3142 | return data | |
|
3143 | ||
|
3125 | 3144 | |
|
3126 | 3145 | class ChangesetStatus(Base, BaseModel): |
|
3127 | 3146 | __tablename__ = 'changeset_statuses' |
@@ -3173,6 +3192,19 b' class ChangesetStatus(Base, BaseModel):' | |||
|
3173 | 3192 | def status_lbl(self): |
|
3174 | 3193 | return ChangesetStatus.get_status_lbl(self.status) |
|
3175 | 3194 | |
|
3195 | def get_api_data(self): | |
|
3196 | status = self | |
|
3197 | data = { | |
|
3198 | 'status_id': status.changeset_status_id, | |
|
3199 | 'status': status.status, | |
|
3200 | } | |
|
3201 | return data | |
|
3202 | ||
|
3203 | def __json__(self): | |
|
3204 | data = dict() | |
|
3205 | data.update(self.get_api_data()) | |
|
3206 | return data | |
|
3207 | ||
|
3176 | 3208 | |
|
3177 | 3209 | class _PullRequestBase(BaseModel): |
|
3178 | 3210 | """ |
@@ -3304,15 +3336,19 b' class _PullRequestBase(BaseModel):' | |||
|
3304 | 3336 | else: |
|
3305 | 3337 | return None |
|
3306 | 3338 | |
|
3307 | def get_api_data(self): | |
|
3308 | from pylons import url | |
|
3339 | def get_api_data(self, with_merge_state=True): | |
|
3309 | 3340 | from rhodecode.model.pull_request import PullRequestModel |
|
3341 | ||
|
3310 | 3342 | pull_request = self |
|
3311 | merge_status = PullRequestModel().merge_status(pull_request) | |
|
3312 | ||
|
3313 | pull_request_url = url( | |
|
3314 | 'pullrequest_show', repo_name=self.target_repo.repo_name, | |
|
3315 | pull_request_id=self.pull_request_id, qualified=True) | |
|
3343 | if with_merge_state: | |
|
3344 | merge_status = PullRequestModel().merge_status(pull_request) | |
|
3345 | merge_state = { | |
|
3346 | 'status': merge_status[0], | |
|
3347 | 'message': safe_unicode(merge_status[1]), | |
|
3348 | } | |
|
3349 | else: | |
|
3350 | merge_state = {'status': 'not_available', | |
|
3351 | 'message': 'not_available'} | |
|
3316 | 3352 | |
|
3317 | 3353 | merge_data = { |
|
3318 | 3354 | 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request), |
@@ -3323,7 +3359,7 b' class _PullRequestBase(BaseModel):' | |||
|
3323 | 3359 | |
|
3324 | 3360 | data = { |
|
3325 | 3361 | 'pull_request_id': pull_request.pull_request_id, |
|
3326 |
'url': |
|
|
3362 | 'url': PullRequestModel().get_url(pull_request), | |
|
3327 | 3363 | 'title': pull_request.title, |
|
3328 | 3364 | 'description': pull_request.description, |
|
3329 | 3365 | 'status': pull_request.status, |
@@ -3331,10 +3367,7 b' class _PullRequestBase(BaseModel):' | |||
|
3331 | 3367 | 'updated_on': pull_request.updated_on, |
|
3332 | 3368 | 'commit_ids': pull_request.revisions, |
|
3333 | 3369 | 'review_status': pull_request.calculated_review_status(), |
|
3334 |
'mergeable': |
|
|
3335 | 'status': merge_status[0], | |
|
3336 | 'message': unicode(merge_status[1]), | |
|
3337 | }, | |
|
3370 | 'mergeable': merge_state, | |
|
3338 | 3371 | 'source': { |
|
3339 | 3372 | 'clone_url': pull_request.source_repo.clone_url(), |
|
3340 | 3373 | 'repository': pull_request.source_repo.repo_name, |
@@ -3389,7 +3422,8 b' class PullRequest(Base, _PullRequestBase' | |||
|
3389 | 3422 | |
|
3390 | 3423 | reviewers = relationship('PullRequestReviewers', |
|
3391 | 3424 | cascade="all, delete, delete-orphan") |
|
3392 |
statuses = relationship('ChangesetStatus' |
|
|
3425 | statuses = relationship('ChangesetStatus', | |
|
3426 | cascade="all, delete, delete-orphan") | |
|
3393 | 3427 | comments = relationship('ChangesetComment', |
|
3394 | 3428 | cascade="all, delete, delete-orphan") |
|
3395 | 3429 | versions = relationship('PullRequestVersion', |
@@ -36,11 +36,11 b' from sqlalchemy import or_' | |||
|
36 | 36 | |
|
37 | 37 | from rhodecode import events |
|
38 | 38 | from rhodecode.lib import helpers as h, hooks_utils, diffs |
|
39 | from rhodecode.lib import audit_logger | |
|
39 | 40 | from rhodecode.lib.compat import OrderedDict |
|
40 | 41 | from rhodecode.lib.hooks_daemon import prepare_callback_daemon |
|
41 | 42 | from rhodecode.lib.markup_renderer import ( |
|
42 | 43 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
|
43 | from rhodecode.lib.utils import action_logger | |
|
44 | 44 | from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe |
|
45 | 45 | from rhodecode.lib.vcs.backends.base import ( |
|
46 | 46 | Reference, MergeResponse, MergeFailureReason, UpdateFailureReason) |
@@ -470,6 +470,11 b' class PullRequestModel(BaseModel):' | |||
|
470 | 470 | self._trigger_pull_request_hook( |
|
471 | 471 | pull_request, created_by_user, 'create') |
|
472 | 472 | |
|
473 | creation_data = pull_request.get_api_data(with_merge_state=False) | |
|
474 | self._log_audit_action( | |
|
475 | 'repo.pull_request.create', {'data': creation_data}, | |
|
476 | created_by_user, pull_request) | |
|
477 | ||
|
473 | 478 | return pull_request |
|
474 | 479 | |
|
475 | 480 | def _trigger_pull_request_hook(self, pull_request, user, action): |
@@ -520,7 +525,12 b' class PullRequestModel(BaseModel):' | |||
|
520 | 525 | log.debug( |
|
521 | 526 | "Merge was successful, updating the pull request comments.") |
|
522 | 527 | self._comment_and_close_pr(pull_request, user, merge_state) |
|
523 | self._log_action('user_merged_pull_request', user, pull_request) | |
|
528 | ||
|
529 | self._log_audit_action( | |
|
530 | 'repo.pull_request.merge', | |
|
531 | {'merge_state': merge_state.__dict__}, | |
|
532 | user, pull_request) | |
|
533 | ||
|
524 | 534 | else: |
|
525 | 535 | log.warn("Merge failed, not updating the pull request.") |
|
526 | 536 | return merge_state |
@@ -899,8 +909,9 b' class PullRequestModel(BaseModel):' | |||
|
899 | 909 | renderer = RstTemplateRenderer() |
|
900 | 910 | return renderer.render('pull_request_update.mako', **params) |
|
901 | 911 | |
|
902 | def edit(self, pull_request, title, description): | |
|
912 | def edit(self, pull_request, title, description, user): | |
|
903 | 913 | pull_request = self.__get_pull_request(pull_request) |
|
914 | old_data = pull_request.get_api_data(with_merge_state=False) | |
|
904 | 915 | if pull_request.is_closed(): |
|
905 | 916 | raise ValueError('This pull request is closed') |
|
906 | 917 | if title: |
@@ -908,8 +919,11 b' class PullRequestModel(BaseModel):' | |||
|
908 | 919 | pull_request.description = description |
|
909 | 920 | pull_request.updated_on = datetime.datetime.now() |
|
910 | 921 | Session().add(pull_request) |
|
922 | self._log_audit_action( | |
|
923 | 'repo.pull_request.edit', {'old_data': old_data}, | |
|
924 | user, pull_request) | |
|
911 | 925 | |
|
912 | def update_reviewers(self, pull_request, reviewer_data): | |
|
926 | def update_reviewers(self, pull_request, reviewer_data, user): | |
|
913 | 927 | """ |
|
914 | 928 | Update the reviewers in the pull request |
|
915 | 929 | |
@@ -946,8 +960,11 b' class PullRequestModel(BaseModel):' | |||
|
946 | 960 | reviewer.pull_request = pull_request |
|
947 | 961 | reviewer.reasons = reviewers[uid]['reasons'] |
|
948 | 962 | # NOTE(marcink): mandatory shouldn't be changed now |
|
949 | #reviewer.mandatory = reviewers[uid]['reasons'] | |
|
963 | # reviewer.mandatory = reviewers[uid]['reasons'] | |
|
950 | 964 | Session().add(reviewer) |
|
965 | self._log_audit_action( | |
|
966 | 'repo.pull_request.reviewer.add', {'data': reviewer.get_dict()}, | |
|
967 | user, pull_request) | |
|
951 | 968 | |
|
952 | 969 | for uid in ids_to_remove: |
|
953 | 970 | changed = True |
@@ -958,7 +975,11 b' class PullRequestModel(BaseModel):' | |||
|
958 | 975 | # use .all() in case we accidentally added the same person twice |
|
959 | 976 | # this CAN happen due to the lack of DB checks |
|
960 | 977 | for obj in reviewers: |
|
978 | old_data = obj.get_dict() | |
|
961 | 979 | Session().delete(obj) |
|
980 | self._log_audit_action( | |
|
981 | 'repo.pull_request.reviewer.delete', | |
|
982 | {'old_data': old_data}, user, pull_request) | |
|
962 | 983 | |
|
963 | 984 | if changed: |
|
964 | 985 | pull_request.updated_on = datetime.datetime.now() |
@@ -1054,9 +1075,13 b' class PullRequestModel(BaseModel):' | |||
|
1054 | 1075 | email_kwargs=kwargs, |
|
1055 | 1076 | ) |
|
1056 | 1077 | |
|
1057 | def delete(self, pull_request): | |
|
1078 | def delete(self, pull_request, user): | |
|
1058 | 1079 | pull_request = self.__get_pull_request(pull_request) |
|
1080 | old_data = pull_request.get_api_data(with_merge_state=False) | |
|
1059 | 1081 | self._cleanup_merge_workspace(pull_request) |
|
1082 | self._log_audit_action( | |
|
1083 | 'repo.pull_request.delete', {'old_data': old_data}, | |
|
1084 | user, pull_request) | |
|
1060 | 1085 | Session().delete(pull_request) |
|
1061 | 1086 | |
|
1062 | 1087 | def close_pull_request(self, pull_request, user): |
@@ -1067,7 +1092,8 b' class PullRequestModel(BaseModel):' | |||
|
1067 | 1092 | Session().add(pull_request) |
|
1068 | 1093 | self._trigger_pull_request_hook( |
|
1069 | 1094 | pull_request, pull_request.author, 'close') |
|
1070 | self._log_action('user_closed_pull_request', user, pull_request) | |
|
1095 | self._log_audit_action( | |
|
1096 | 'repo.pull_request.close', {}, user, pull_request) | |
|
1071 | 1097 | |
|
1072 | 1098 | def close_pull_request_with_comment( |
|
1073 | 1099 | self, pull_request, user, repo, message=None): |
@@ -1402,12 +1428,12 b' class PullRequestModel(BaseModel):' | |||
|
1402 | 1428 | settings = settings_model.get_general_settings() |
|
1403 | 1429 | return settings.get('rhodecode_hg_use_rebase_for_merging', False) |
|
1404 | 1430 | |
|
1405 | def _log_action(self, action, user, pull_request): | |
|
1406 |
a |
|
|
1407 |
|
|
|
1408 | '{action}:{pr_id}'.format( | |
|
1409 | action=action, pr_id=pull_request.pull_request_id), | |
|
1410 | pull_request.target_repo) | |
|
1431 | def _log_audit_action(self, action, action_data, user, pull_request): | |
|
1432 | audit_logger.store( | |
|
1433 | action=action, | |
|
1434 | action_data=action_data, | |
|
1435 | user=user, | |
|
1436 | repo=pull_request.target_repo) | |
|
1411 | 1437 | |
|
1412 | 1438 | def get_reviewer_functions(self): |
|
1413 | 1439 | """ |
@@ -199,6 +199,9 b' class TestAdminPermissionsController(Tes' | |||
|
199 | 199 | url('edit_user_ips', user_id=default_user_id), |
|
200 | 200 | params={'_method': 'delete', 'del_ip_id': del_ip_id, |
|
201 | 201 | 'csrf_token': self.csrf_token}) |
|
202 | ||
|
203 | assert_session_flash(response, 'Removed ip address from user whitelist') | |
|
204 | ||
|
202 | 205 | clear_all_caches() |
|
203 | 206 | response = self.app.get(url('admin_permissions_ips')) |
|
204 | 207 | response.mustcontain('All IP addresses are allowed') |
@@ -27,7 +27,7 b' from rhodecode.lib.vcs.nodes import File' | |||
|
27 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
29 | 29 | from rhodecode.model.db import ( |
|
30 | PullRequest, ChangesetStatus, UserLog, Notification) | |
|
30 | PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment) | |
|
31 | 31 | from rhodecode.model.meta import Session |
|
32 | 32 | from rhodecode.model.pull_request import PullRequestModel |
|
33 | 33 | from rhodecode.model.user import UserModel |
@@ -256,29 +256,32 b' class TestPullrequestsController(object)' | |||
|
256 | 256 | 'csrf_token': csrf_token}, |
|
257 | 257 | extra_environ=xhr_header,) |
|
258 | 258 | |
|
259 | action = 'user_closed_pull_request:%d' % pull_request_id | |
|
260 | 259 | journal = UserLog.query()\ |
|
261 | 260 | .filter(UserLog.user_id == author)\ |
|
262 | .filter(UserLog.repository_id == repo)\ | |
|
263 | .filter(UserLog.action == action)\ | |
|
261 | .filter(UserLog.repository_id == repo) \ | |
|
262 | .order_by('user_log_id') \ | |
|
264 | 263 | .all() |
|
265 | assert len(journal) == 1 | |
|
264 | assert journal[-1].action == 'repo.pull_request.close' | |
|
266 | 265 | |
|
267 | 266 | pull_request = PullRequest.get(pull_request_id) |
|
268 | 267 | assert pull_request.is_closed() |
|
269 | 268 | |
|
270 | # check only the latest status, not the review status | |
|
271 | 269 | status = ChangesetStatusModel().get_status( |
|
272 | 270 | pull_request.source_repo, pull_request=pull_request) |
|
273 | 271 | assert status == ChangesetStatus.STATUS_APPROVED |
|
274 | assert pull_request.comments[-1].text == 'Closing a PR' | |
|
272 | comments = ChangesetComment().query() \ | |
|
273 | .filter(ChangesetComment.pull_request == pull_request) \ | |
|
274 | .order_by(ChangesetComment.comment_id.asc())\ | |
|
275 | .all() | |
|
276 | assert comments[-1].text == 'Closing a PR' | |
|
275 | 277 | |
|
276 | 278 | def test_comment_force_close_pull_request_rejected( |
|
277 | 279 | self, pr_util, csrf_token, xhr_header): |
|
278 | 280 | pull_request = pr_util.create_pull_request() |
|
279 | 281 | pull_request_id = pull_request.pull_request_id |
|
280 | 282 | PullRequestModel().update_reviewers( |
|
281 |
pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)] |
|
|
283 | pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)], | |
|
284 | pull_request.author) | |
|
282 | 285 | author = pull_request.user_id |
|
283 | 286 | repo = pull_request.target_repo.repo_id |
|
284 | 287 | |
@@ -294,12 +297,11 b' class TestPullrequestsController(object)' | |||
|
294 | 297 | |
|
295 | 298 | pull_request = PullRequest.get(pull_request_id) |
|
296 | 299 | |
|
297 | action = 'user_closed_pull_request:%d' % pull_request_id | |
|
298 | journal = UserLog.query().filter( | |
|
299 | UserLog.user_id == author, | |
|
300 | UserLog.repository_id == repo, | |
|
301 | UserLog.action == action).all() | |
|
302 | assert len(journal) == 1 | |
|
300 | journal = UserLog.query()\ | |
|
301 | .filter(UserLog.user_id == author, UserLog.repository_id == repo) \ | |
|
302 | .order_by('user_log_id') \ | |
|
303 | .all() | |
|
304 | assert journal[-1].action == 'repo.pull_request.close' | |
|
303 | 305 | |
|
304 | 306 | # check only the latest status, not the review status |
|
305 | 307 | status = ChangesetStatusModel().get_status( |
@@ -449,7 +451,8 b' class TestPullrequestsController(object)' | |||
|
449 | 451 | |
|
450 | 452 | # Change reviewers and check that a notification was made |
|
451 | 453 | PullRequestModel().update_reviewers( |
|
452 |
pull_request.pull_request_id, [(1, [], False)] |
|
|
454 | pull_request.pull_request_id, [(1, [], False)], | |
|
455 | pull_request.author) | |
|
453 | 456 | assert len(notifications.all()) == 2 |
|
454 | 457 | |
|
455 | 458 | def test_create_pull_request_stores_ancestor_commit_id(self, backend, |
@@ -541,25 +544,20 b' class TestPullrequestsController(object)' | |||
|
541 | 544 | pull_request, ChangesetStatus.STATUS_APPROVED) |
|
542 | 545 | |
|
543 | 546 | # Check the relevant log entries were added |
|
544 |
user_logs = UserLog.query() |
|
|
545 | .filter(UserLog.version == UserLog.VERSION_1) \ | |
|
546 | .order_by('-user_log_id').limit(3) | |
|
547 | user_logs = UserLog.query().order_by('-user_log_id').limit(3) | |
|
547 | 548 | actions = [log.action for log in user_logs] |
|
548 | 549 | pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request) |
|
549 | 550 | expected_actions = [ |
|
550 | u'user_closed_pull_request:%d' % pull_request_id, | |
|
551 | u'user_merged_pull_request:%d' % pull_request_id, | |
|
552 | # The action below reflect that the post push actions were executed | |
|
553 | u'user_commented_pull_request:%d' % pull_request_id, | |
|
551 | u'repo.pull_request.close', | |
|
552 | u'repo.pull_request.merge', | |
|
553 | u'repo.pull_request.comment.create' | |
|
554 | 554 | ] |
|
555 | 555 | assert actions == expected_actions |
|
556 | 556 | |
|
557 |
user_logs = UserLog.query() |
|
|
558 | .filter(UserLog.version == UserLog.VERSION_2) \ | |
|
559 | .order_by('-user_log_id').limit(1) | |
|
560 | actions = [log.action for log in user_logs] | |
|
561 | assert actions == ['user.push'] | |
|
562 | assert user_logs[0].action_data['commit_ids'] == pr_commit_ids | |
|
557 | user_logs = UserLog.query().order_by('-user_log_id').limit(4) | |
|
558 | actions = [log for log in user_logs] | |
|
559 | assert actions[-1].action == 'user.push' | |
|
560 | assert actions[-1].action_data['commit_ids'] == pr_commit_ids | |
|
563 | 561 | |
|
564 | 562 | # Check post_push rcextension was really executed |
|
565 | 563 | push_calls = rhodecode.EXTENSIONS.calls['post_push'] |
@@ -50,7 +50,9 b' class TestPullRequestModel(object):' | |||
|
50 | 50 | A pull request combined with multiples patches. |
|
51 | 51 | """ |
|
52 | 52 | BackendClass = get_backend(backend.alias) |
|
53 |
self.merge_patcher = mock.patch.object( |
|
|
53 | self.merge_patcher = mock.patch.object( | |
|
54 | BackendClass, 'merge', return_value=MergeResponse( | |
|
55 | False, False, None, MergeFailureReason.UNKNOWN)) | |
|
54 | 56 | self.workspace_remove_patcher = mock.patch.object( |
|
55 | 57 | BackendClass, 'cleanup_merge_workspace') |
|
56 | 58 | |
@@ -117,7 +119,8 b' class TestPullRequestModel(object):' | |||
|
117 | 119 | |
|
118 | 120 | def test_get_awaiting_my_review(self, pull_request): |
|
119 | 121 | PullRequestModel().update_reviewers( |
|
120 |
pull_request, [(pull_request.author, ['author'], False)] |
|
|
122 | pull_request, [(pull_request.author, ['author'], False)], | |
|
123 | pull_request.author) | |
|
121 | 124 | prs = PullRequestModel().get_awaiting_my_review( |
|
122 | 125 | pull_request.target_repo, user_id=pull_request.author.user_id) |
|
123 | 126 | assert isinstance(prs, list) |
@@ -125,13 +128,14 b' class TestPullRequestModel(object):' | |||
|
125 | 128 | |
|
126 | 129 | def test_count_awaiting_my_review(self, pull_request): |
|
127 | 130 | PullRequestModel().update_reviewers( |
|
128 |
pull_request, [(pull_request.author, ['author'], False)] |
|
|
131 | pull_request, [(pull_request.author, ['author'], False)], | |
|
132 | pull_request.author) | |
|
129 | 133 | pr_count = PullRequestModel().count_awaiting_my_review( |
|
130 | 134 | pull_request.target_repo, user_id=pull_request.author.user_id) |
|
131 | 135 | assert pr_count == 1 |
|
132 | 136 | |
|
133 | 137 | def test_delete_calls_cleanup_merge(self, pull_request): |
|
134 | PullRequestModel().delete(pull_request) | |
|
138 | PullRequestModel().delete(pull_request, pull_request.author) | |
|
135 | 139 | |
|
136 | 140 | self.workspace_remove_mock.assert_called_once_with( |
|
137 | 141 | self.workspace_id) |
@@ -892,7 +892,7 b' class RepoServer(object):' | |||
|
892 | 892 | |
|
893 | 893 | |
|
894 | 894 | @pytest.fixture |
|
895 | def pr_util(backend, request): | |
|
895 | def pr_util(backend, request, config_stub): | |
|
896 | 896 | """ |
|
897 | 897 | Utility for tests of models and for functional tests around pull requests. |
|
898 | 898 | |
@@ -1085,7 +1085,7 b' class PRTestUtility(object):' | |||
|
1085 | 1085 | # request will already be deleted. |
|
1086 | 1086 | pull_request = PullRequest().get(self.pull_request_id) |
|
1087 | 1087 | if pull_request: |
|
1088 | PullRequestModel().delete(pull_request) | |
|
1088 | PullRequestModel().delete(pull_request, pull_request.author) | |
|
1089 | 1089 | Session().commit() |
|
1090 | 1090 | |
|
1091 | 1091 | if self.notification_patcher: |
@@ -1648,14 +1648,6 b' def no_notifications(request):' | |||
|
1648 | 1648 | request.addfinalizer(notification_patcher.stop) |
|
1649 | 1649 | |
|
1650 | 1650 | |
|
1651 | @pytest.fixture | |
|
1652 | def silence_action_logger(request): | |
|
1653 | notification_patcher = mock.patch( | |
|
1654 | 'rhodecode.lib.utils.action_logger') | |
|
1655 | notification_patcher.start() | |
|
1656 | request.addfinalizer(notification_patcher.stop) | |
|
1657 | ||
|
1658 | ||
|
1659 | 1651 | @pytest.fixture(scope='session') |
|
1660 | 1652 | def repeat(request): |
|
1661 | 1653 | """ |
General Comments 0
You need to be logged in to leave comments.
Login now