Show More
@@ -0,0 +1,164 b'' | |||
|
1 | ## -*- coding: utf-8 -*- | |
|
2 | <%inherit file="base.mako"/> | |
|
3 | <%namespace name="base" file="base.mako"/> | |
|
4 | ||
|
5 | ## EMAIL SUBJECT | |
|
6 | <%def name="subject()" filter="n,trim,whitespace_filter"> | |
|
7 | <% | |
|
8 | data = { | |
|
9 | 'updating_user': '@'+h.person(updating_user), | |
|
10 | 'pr_id': pull_request.pull_request_id, | |
|
11 | 'pr_title': pull_request.title, | |
|
12 | } | |
|
13 | %> | |
|
14 | ||
|
15 | ${_('{updating_user} updated pull request. !{pr_id}: "{pr_title}"').format(**data) |n} | |
|
16 | </%def> | |
|
17 | ||
|
18 | ## PLAINTEXT VERSION OF BODY | |
|
19 | <%def name="body_plaintext()" filter="n,trim"> | |
|
20 | <% | |
|
21 | data = { | |
|
22 | 'updating_user': h.person(updating_user), | |
|
23 | 'pr_id': pull_request.pull_request_id, | |
|
24 | 'pr_title': pull_request.title, | |
|
25 | 'source_ref_type': pull_request.source_ref_parts.type, | |
|
26 | 'source_ref_name': pull_request.source_ref_parts.name, | |
|
27 | 'target_ref_type': pull_request.target_ref_parts.type, | |
|
28 | 'target_ref_name': pull_request.target_ref_parts.name, | |
|
29 | 'repo_url': pull_request_source_repo_url, | |
|
30 | 'source_repo': pull_request_source_repo.repo_name, | |
|
31 | 'target_repo': pull_request_target_repo.repo_name, | |
|
32 | 'source_repo_url': pull_request_source_repo_url, | |
|
33 | 'target_repo_url': pull_request_target_repo_url, | |
|
34 | } | |
|
35 | %> | |
|
36 | ||
|
37 | * ${_('Pull Request link')}: ${pull_request_url} | |
|
38 | ||
|
39 | * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))} | |
|
40 | ||
|
41 | * ${_('Title')}: ${pull_request.title} | |
|
42 | ||
|
43 | * ${_('Description')}: | |
|
44 | ||
|
45 | ${pull_request.description | trim} | |
|
46 | ||
|
47 | * Changed commits: | |
|
48 | ||
|
49 | - Added: ${len(added_commits)} | |
|
50 | - Removed: ${len(removed_commits)} | |
|
51 | ||
|
52 | * Changed files: | |
|
53 | ||
|
54 | %if not changed_files: | |
|
55 | No file changes found | |
|
56 | %else: | |
|
57 | %for file_name in added_files: | |
|
58 | - A `${file_name}` | |
|
59 | %endfor | |
|
60 | %for file_name in modified_files: | |
|
61 | - M `${file_name}` | |
|
62 | %endfor | |
|
63 | %for file_name in removed_files: | |
|
64 | - R `${file_name}` | |
|
65 | %endfor | |
|
66 | %endif | |
|
67 | ||
|
68 | --- | |
|
69 | ${self.plaintext_footer()} | |
|
70 | </%def> | |
|
71 | <% | |
|
72 | data = { | |
|
73 | 'updating_user': h.person(updating_user), | |
|
74 | 'pr_id': pull_request.pull_request_id, | |
|
75 | 'pr_title': pull_request.title, | |
|
76 | 'source_ref_type': pull_request.source_ref_parts.type, | |
|
77 | 'source_ref_name': pull_request.source_ref_parts.name, | |
|
78 | 'target_ref_type': pull_request.target_ref_parts.type, | |
|
79 | 'target_ref_name': pull_request.target_ref_parts.name, | |
|
80 | 'repo_url': pull_request_source_repo_url, | |
|
81 | 'source_repo': pull_request_source_repo.repo_name, | |
|
82 | 'target_repo': pull_request_target_repo.repo_name, | |
|
83 | 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url), | |
|
84 | 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url), | |
|
85 | } | |
|
86 | %> | |
|
87 | ||
|
88 | <table style="text-align:left;vertical-align:middle;width: 100%"> | |
|
89 | <tr> | |
|
90 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> | |
|
91 | ||
|
92 | <h4 style="margin: 0"> | |
|
93 | <div style="margin-bottom: 4px"> | |
|
94 | <span style="color:#7E7F7F">@${h.person(updating_user.username)}</span> | |
|
95 | ${_('updated')} | |
|
96 | <a href="${pull_request_url}" style="${base.link_css()}"> | |
|
97 | ${_('pull request.').format(**data) } | |
|
98 | </a> | |
|
99 | </div> | |
|
100 | <div style="margin-top: 10px"></div> | |
|
101 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> | |
|
102 | </h4> | |
|
103 | ||
|
104 | </td> | |
|
105 | </tr> | |
|
106 | ||
|
107 | </table> | |
|
108 | ||
|
109 | <table style="text-align:left;vertical-align:middle;width: 100%"> | |
|
110 | ## spacing def | |
|
111 | <tr> | |
|
112 | <td style="width: 130px"></td> | |
|
113 | <td></td> | |
|
114 | </tr> | |
|
115 | ||
|
116 | <tr> | |
|
117 | <td style="padding-right:20px;">${_('Pull request')}:</td> | |
|
118 | <td> | |
|
119 | <a href="${pull_request_url}" style="${base.link_css()}"> | |
|
120 | !${pull_request.pull_request_id} | |
|
121 | </a> | |
|
122 | </td> | |
|
123 | </tr> | |
|
124 | ||
|
125 | <tr> | |
|
126 | <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td> | |
|
127 | <td style="line-height:20px;"> | |
|
128 | <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']} | |
|
129 | → | |
|
130 | <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']} | |
|
131 | </td> | |
|
132 | </tr> | |
|
133 | ||
|
134 | <tr> | |
|
135 | <td style="padding-right:20px;">${_('Description')}:</td> | |
|
136 | <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td> | |
|
137 | </tr> | |
|
138 | <tr> | |
|
139 | <td style="padding-right:20px;">${_('Changes')}:</td> | |
|
140 | <td style="white-space:pre-line">\ | |
|
141 | <strong>Changed commits:</strong> | |
|
142 | ||
|
143 | - Added: ${len(added_commits)} | |
|
144 | - Removed: ${len(removed_commits)} | |
|
145 | ||
|
146 | <strong>Changed files:</strong> | |
|
147 | ||
|
148 | %if not changed_files: | |
|
149 | No file changes found | |
|
150 | %else: | |
|
151 | %for file_name in added_files: | |
|
152 | - A <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a> | |
|
153 | %endfor | |
|
154 | %for file_name in modified_files: | |
|
155 | - M <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a> | |
|
156 | %endfor | |
|
157 | %for file_name in removed_files: | |
|
158 | - R <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a> | |
|
159 | %endfor | |
|
160 | %endif | |
|
161 | </td> | |
|
162 | </tr> | |
|
163 | ||
|
164 | </table> |
@@ -895,7 +895,9 b' def update_pull_request(' | |||
|
895 | 895 | |
|
896 | 896 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
897 | 897 | if PullRequestModel().has_valid_update_type(pull_request): |
|
898 | update_response = PullRequestModel().update_commits(pull_request) | |
|
898 | db_user = apiuser.get_instance() | |
|
899 | update_response = PullRequestModel().update_commits( | |
|
900 | pull_request, db_user) | |
|
899 | 901 | commit_changes = update_response.changes or commit_changes |
|
900 | 902 | Session().commit() |
|
901 | 903 |
@@ -573,8 +573,7 b' class AdminSettingsView(BaseAppView):' | |||
|
573 | 573 | |
|
574 | 574 | email_kwargs = { |
|
575 | 575 | 'date': datetime.datetime.now(), |
|
576 |
'user': c.rhodecode_user |
|
|
577 | 'rhodecode_version': c.rhodecode_version | |
|
576 | 'user': c.rhodecode_user | |
|
578 | 577 | } |
|
579 | 578 | |
|
580 | 579 | (subject, headers, email_body, |
@@ -78,7 +78,16 b' Check if we should use full-topic or min' | |||
|
78 | 78 | target_repo = AttributeDict(repo_name='repo_group/target_repo') |
|
79 | 79 | source_repo = AttributeDict(repo_name='repo_group/source_repo') |
|
80 | 80 | user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user |
|
81 | ||
|
81 | # file/commit changes for PR update | |
|
82 | commit_changes = AttributeDict({ | |
|
83 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], | |
|
84 | 'removed': ['eeeeeeeeeee'], | |
|
85 | }) | |
|
86 | file_changes = AttributeDict({ | |
|
87 | 'added': ['a/file1.md', 'file2.py'], | |
|
88 | 'modified': ['b/modified_file.rst'], | |
|
89 | 'removed': ['.idea'], | |
|
90 | }) | |
|
82 | 91 | email_kwargs = { |
|
83 | 92 | 'test': {}, |
|
84 | 93 | 'message': { |
@@ -87,7 +96,6 b' Check if we should use full-topic or min' | |||
|
87 | 96 | 'email_test': { |
|
88 | 97 | 'user': user, |
|
89 | 98 | 'date': datetime.datetime.now(), |
|
90 | 'rhodecode_version': c.rhodecode_version | |
|
91 | 99 | }, |
|
92 | 100 | 'password_reset': { |
|
93 | 101 | 'password_reset_url': 'http://example.com/reset-rhodecode-password/token', |
@@ -215,6 +223,35 b' This should work better !' | |||
|
215 | 223 | |
|
216 | 224 | }, |
|
217 | 225 | |
|
226 | 'pull_request_update': { | |
|
227 | 'updating_user': user, | |
|
228 | ||
|
229 | 'status_change': None, | |
|
230 | 'status_change_type': None, | |
|
231 | ||
|
232 | 'pull_request': pr, | |
|
233 | 'pull_request_commits': [], | |
|
234 | ||
|
235 | 'pull_request_target_repo': target_repo, | |
|
236 | 'pull_request_target_repo_url': 'http://target-repo/url', | |
|
237 | ||
|
238 | 'pull_request_source_repo': source_repo, | |
|
239 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
|
240 | ||
|
241 | 'pull_request_url': 'http://localhost/pr1', | |
|
242 | ||
|
243 | # update comment links | |
|
244 | 'pr_comment_url': 'http://comment-url', | |
|
245 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
|
246 | 'ancestor_commit_id': 'f39bd443', | |
|
247 | 'added_commits': commit_changes.added, | |
|
248 | 'removed_commits': commit_changes.removed, | |
|
249 | 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed), | |
|
250 | 'added_files': file_changes.added, | |
|
251 | 'modified_files': file_changes.modified, | |
|
252 | 'removed_files': file_changes.removed, | |
|
253 | }, | |
|
254 | ||
|
218 | 255 | 'cs_comment': { |
|
219 | 256 | 'user': user, |
|
220 | 257 | 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'), |
@@ -338,6 +375,8 b' users: description edit fixes' | |||
|
338 | 375 | |
|
339 | 376 | 'pull_request_comment+file': {}, |
|
340 | 377 | 'pull_request_comment+status': {}, |
|
378 | ||
|
379 | 'pull_request_update': {}, | |
|
341 | 380 | } |
|
342 | 381 | c.email_types.update(EmailNotificationModel.email_types) |
|
343 | 382 |
@@ -1117,7 +1117,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1117 | 1117 | _ = self.request.translate |
|
1118 | 1118 | |
|
1119 | 1119 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
1120 |
resp = PullRequestModel().update_commits( |
|
|
1120 | resp = PullRequestModel().update_commits( | |
|
1121 | pull_request, self._rhodecode_db_user) | |
|
1121 | 1122 | |
|
1122 | 1123 | if resp.executed: |
|
1123 | 1124 |
@@ -821,7 +821,7 b' def is_svn_without_proxy(repository):' | |||
|
821 | 821 | |
|
822 | 822 | def discover_user(author): |
|
823 | 823 | """ |
|
824 | Tries to discover RhodeCode User based on the autho string. Author string | |
|
824 | Tries to discover RhodeCode User based on the author string. Author string | |
|
825 | 825 | is typically `FirstName LastName <email@address.com>` |
|
826 | 826 | """ |
|
827 | 827 | |
@@ -1015,10 +1015,11 b' def bool2icon(value, show_at_false=True)' | |||
|
1015 | 1015 | #============================================================================== |
|
1016 | 1016 | # PERMS |
|
1017 | 1017 | #============================================================================== |
|
1018 |
from rhodecode.lib.auth import |
|
|
1019 |
Has |
|
|
1020 |
HasRepo |
|
|
1021 | csrf_token_key | |
|
1018 | from rhodecode.lib.auth import ( | |
|
1019 | HasPermissionAny, HasPermissionAll, | |
|
1020 | HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, | |
|
1021 | HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, | |
|
1022 | csrf_token_key, AuthUser) | |
|
1022 | 1023 | |
|
1023 | 1024 | |
|
1024 | 1025 | #============================================================================== |
@@ -4401,6 +4401,7 b' class Notification(Base, BaseModel):' | |||
|
4401 | 4401 | TYPE_REGISTRATION = u'registration' |
|
4402 | 4402 | TYPE_PULL_REQUEST = u'pull_request' |
|
4403 | 4403 | TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' |
|
4404 | TYPE_PULL_REQUEST_UPDATE = u'pull_request_update' | |
|
4404 | 4405 | |
|
4405 | 4406 | notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) |
|
4406 | 4407 | subject = Column('subject', Unicode(512), nullable=True) |
@@ -293,6 +293,7 b' class EmailNotificationModel(BaseModel):' | |||
|
293 | 293 | TYPE_REGISTRATION = Notification.TYPE_REGISTRATION |
|
294 | 294 | TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST |
|
295 | 295 | TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT |
|
296 | TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE | |
|
296 | 297 | TYPE_MAIN = Notification.TYPE_MESSAGE |
|
297 | 298 | |
|
298 | 299 | TYPE_PASSWORD_RESET = 'password_reset' |
@@ -319,6 +320,8 b' class EmailNotificationModel(BaseModel):' | |||
|
319 | 320 | 'rhodecode:templates/email_templates/pull_request_review.mako', |
|
320 | 321 | TYPE_PULL_REQUEST_COMMENT: |
|
321 | 322 | 'rhodecode:templates/email_templates/pull_request_comment.mako', |
|
323 | TYPE_PULL_REQUEST_UPDATE: | |
|
324 | 'rhodecode:templates/email_templates/pull_request_update.mako', | |
|
322 | 325 | } |
|
323 | 326 | |
|
324 | 327 | def __init__(self): |
@@ -341,6 +344,7 b' class EmailNotificationModel(BaseModel):' | |||
|
341 | 344 | """ |
|
342 | 345 | |
|
343 | 346 | kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name |
|
347 | kwargs['rhodecode_version'] = rhodecode.__version__ | |
|
344 | 348 | instance_url = h.route_url('home') |
|
345 | 349 | _kwargs = { |
|
346 | 350 | 'instance_url': instance_url, |
@@ -65,9 +65,19 b' log = logging.getLogger(__name__)' | |||
|
65 | 65 | |
|
66 | 66 | # Data structure to hold the response data when updating commits during a pull |
|
67 | 67 | # request update. |
|
68 | UpdateResponse = collections.namedtuple('UpdateResponse', [ | |
|
69 | 'executed', 'reason', 'new', 'old', 'changes', | |
|
70 | 'source_changed', 'target_changed']) | |
|
68 | class UpdateResponse(object): | |
|
69 | ||
|
70 | def __init__(self, executed, reason, new, old, common_ancestor_id, | |
|
71 | commit_changes, source_changed, target_changed): | |
|
72 | ||
|
73 | self.executed = executed | |
|
74 | self.reason = reason | |
|
75 | self.new = new | |
|
76 | self.old = old | |
|
77 | self.common_ancestor_id = common_ancestor_id | |
|
78 | self.changes = commit_changes | |
|
79 | self.source_changed = source_changed | |
|
80 | self.target_changed = target_changed | |
|
71 | 81 | |
|
72 | 82 | |
|
73 | 83 | class PullRequestModel(BaseModel): |
@@ -672,11 +682,13 b' class PullRequestModel(BaseModel):' | |||
|
672 | 682 | source_ref_type = pull_request.source_ref_parts.type |
|
673 | 683 | return source_ref_type in self.REF_TYPES |
|
674 | 684 | |
|
675 | def update_commits(self, pull_request): | |
|
685 | def update_commits(self, pull_request, updating_user): | |
|
676 | 686 | """ |
|
677 | 687 | Get the updated list of commits for the pull request |
|
678 | 688 | and return the new pull request version and the list |
|
679 | 689 | of commits processed by this update action |
|
690 | ||
|
691 | updating_user is the user_object who triggered the update | |
|
680 | 692 | """ |
|
681 | 693 | pull_request = self.__get_pull_request(pull_request) |
|
682 | 694 | source_ref_type = pull_request.source_ref_parts.type |
@@ -693,7 +705,7 b' class PullRequestModel(BaseModel):' | |||
|
693 | 705 | return UpdateResponse( |
|
694 | 706 | executed=False, |
|
695 | 707 | reason=UpdateFailureReason.WRONG_REF_TYPE, |
|
696 | old=pull_request, new=None, changes=None, | |
|
708 | old=pull_request, new=None, common_ancestor_id=None, commit_changes=None, | |
|
697 | 709 | source_changed=False, target_changed=False) |
|
698 | 710 | |
|
699 | 711 | # source repo |
@@ -705,7 +717,7 b' class PullRequestModel(BaseModel):' | |||
|
705 | 717 | return UpdateResponse( |
|
706 | 718 | executed=False, |
|
707 | 719 | reason=UpdateFailureReason.MISSING_SOURCE_REF, |
|
708 | old=pull_request, new=None, changes=None, | |
|
720 | old=pull_request, new=None, common_ancestor_id=None, commit_changes=None, | |
|
709 | 721 | source_changed=False, target_changed=False) |
|
710 | 722 | |
|
711 | 723 | source_changed = source_ref_id != source_commit.raw_id |
@@ -719,7 +731,7 b' class PullRequestModel(BaseModel):' | |||
|
719 | 731 | return UpdateResponse( |
|
720 | 732 | executed=False, |
|
721 | 733 | reason=UpdateFailureReason.MISSING_TARGET_REF, |
|
722 | old=pull_request, new=None, changes=None, | |
|
734 | old=pull_request, new=None, common_ancestor_id=None, commit_changes=None, | |
|
723 | 735 | source_changed=False, target_changed=False) |
|
724 | 736 | target_changed = target_ref_id != target_commit.raw_id |
|
725 | 737 | |
@@ -728,7 +740,7 b' class PullRequestModel(BaseModel):' | |||
|
728 | 740 | return UpdateResponse( |
|
729 | 741 | executed=False, |
|
730 | 742 | reason=UpdateFailureReason.NO_CHANGE, |
|
731 | old=pull_request, new=None, changes=None, | |
|
743 | old=pull_request, new=None, common_ancestor_id=None, commit_changes=None, | |
|
732 | 744 | source_changed=target_changed, target_changed=source_changed) |
|
733 | 745 | |
|
734 | 746 | change_in_found = 'target repo' if target_changed else 'source repo' |
@@ -759,7 +771,7 b' class PullRequestModel(BaseModel):' | |||
|
759 | 771 | return UpdateResponse( |
|
760 | 772 | executed=False, |
|
761 | 773 | reason=UpdateFailureReason.MISSING_TARGET_REF, |
|
762 | old=pull_request, new=None, changes=None, | |
|
774 | old=pull_request, new=None, common_ancestor_id=None, commit_changes=None, | |
|
763 | 775 | source_changed=source_changed, target_changed=target_changed) |
|
764 | 776 | |
|
765 | 777 | # re-compute commit ids |
@@ -769,13 +781,13 b' class PullRequestModel(BaseModel):' | |||
|
769 | 781 | target_commit.raw_id, source_commit.raw_id, source_repo, merge=True, |
|
770 | 782 | pre_load=pre_load) |
|
771 | 783 | |
|
772 | ancestor = source_repo.get_common_ancestor( | |
|
784 | ancestor_commit_id = source_repo.get_common_ancestor( | |
|
773 | 785 | source_commit.raw_id, target_commit.raw_id, target_repo) |
|
774 | 786 | |
|
775 | 787 | pull_request.source_ref = '%s:%s:%s' % ( |
|
776 | 788 | source_ref_type, source_ref_name, source_commit.raw_id) |
|
777 | 789 | pull_request.target_ref = '%s:%s:%s' % ( |
|
778 | target_ref_type, target_ref_name, ancestor) | |
|
790 | target_ref_type, target_ref_name, ancestor_commit_id) | |
|
779 | 791 | |
|
780 | 792 | pull_request.revisions = [ |
|
781 | 793 | commit.raw_id for commit in reversed(commit_ranges)] |
@@ -787,7 +799,7 b' class PullRequestModel(BaseModel):' | |||
|
787 | 799 | pull_request, pull_request_version) |
|
788 | 800 | |
|
789 | 801 | # calculate commit and file changes |
|
790 | changes = self._calculate_commit_id_changes( | |
|
802 | commit_changes = self._calculate_commit_id_changes( | |
|
791 | 803 | old_commit_ids, new_commit_ids) |
|
792 | 804 | file_changes = self._calculate_file_changes( |
|
793 | 805 | old_diff_data, new_diff_data) |
@@ -797,23 +809,23 b' class PullRequestModel(BaseModel):' | |||
|
797 | 809 | pull_request, old_diff_data=old_diff_data, |
|
798 | 810 | new_diff_data=new_diff_data) |
|
799 | 811 | |
|
800 | commit_changes = (changes.added or changes.removed) | |
|
812 | valid_commit_changes = (commit_changes.added or commit_changes.removed) | |
|
801 | 813 | file_node_changes = ( |
|
802 | 814 | file_changes.added or file_changes.modified or file_changes.removed) |
|
803 | pr_has_changes = commit_changes or file_node_changes | |
|
815 | pr_has_changes = valid_commit_changes or file_node_changes | |
|
804 | 816 | |
|
805 | 817 | # Add an automatic comment to the pull request, in case |
|
806 | 818 | # anything has changed |
|
807 | 819 | if pr_has_changes: |
|
808 | 820 | update_comment = CommentsModel().create( |
|
809 | text=self._render_update_message(changes, file_changes), | |
|
821 | text=self._render_update_message(ancestor_commit_id, commit_changes, file_changes), | |
|
810 | 822 | repo=pull_request.target_repo, |
|
811 | 823 | user=pull_request.author, |
|
812 | 824 | pull_request=pull_request, |
|
813 | 825 | send_email=False, renderer=DEFAULT_COMMENTS_RENDERER) |
|
814 | 826 | |
|
815 | 827 | # Update status to "Under Review" for added commits |
|
816 | for commit_id in changes.added: | |
|
828 | for commit_id in commit_changes.added: | |
|
817 | 829 | ChangesetStatusModel().set_status( |
|
818 | 830 | repo=pull_request.source_repo, |
|
819 | 831 | status=ChangesetStatus.STATUS_UNDER_REVIEW, |
@@ -822,10 +834,19 b' class PullRequestModel(BaseModel):' | |||
|
822 | 834 | pull_request=pull_request, |
|
823 | 835 | revision=commit_id) |
|
824 | 836 | |
|
837 | # send update email to users | |
|
838 | try: | |
|
839 | self.notify_users(pull_request=pull_request, updating_user=updating_user, | |
|
840 | ancestor_commit_id=ancestor_commit_id, | |
|
841 | commit_changes=commit_changes, | |
|
842 | file_changes=file_changes) | |
|
843 | except Exception: | |
|
844 | log.exception('Failed to send email notification to users') | |
|
845 | ||
|
825 | 846 | log.debug( |
|
826 | 847 | 'Updated pull request %s, added_ids: %s, common_ids: %s, ' |
|
827 | 848 | 'removed_ids: %s', pull_request.pull_request_id, |
|
828 | changes.added, changes.common, changes.removed) | |
|
849 | commit_changes.added, commit_changes.common, commit_changes.removed) | |
|
829 | 850 | log.debug( |
|
830 | 851 | 'Updated pull request with the following file changes: %s', |
|
831 | 852 | file_changes) |
@@ -841,7 +862,8 b' class PullRequestModel(BaseModel):' | |||
|
841 | 862 | |
|
842 | 863 | return UpdateResponse( |
|
843 | 864 | executed=True, reason=UpdateFailureReason.NONE, |
|
844 |
old=pull_request, new=pull_request_version, |
|
|
865 | old=pull_request, new=pull_request_version, | |
|
866 | common_ancestor_id=ancestor_commit_id, commit_changes=commit_changes, | |
|
845 | 867 | source_changed=source_changed, target_changed=target_changed) |
|
846 | 868 | |
|
847 | 869 | def _create_version_from_snapshot(self, pull_request): |
@@ -963,12 +985,13 b' class PullRequestModel(BaseModel):' | |||
|
963 | 985 | |
|
964 | 986 | return FileChangeTuple(added_files, modified_files, removed_files) |
|
965 | 987 | |
|
966 | def _render_update_message(self, changes, file_changes): | |
|
988 | def _render_update_message(self, ancestor_commit_id, changes, file_changes): | |
|
967 | 989 | """ |
|
968 | 990 | render the message using DEFAULT_COMMENTS_RENDERER (RST renderer), |
|
969 | 991 | so it's always looking the same disregarding on which default |
|
970 | 992 | renderer system is using. |
|
971 | 993 | |
|
994 | :param ancestor_commit_id: ancestor raw_id | |
|
972 | 995 | :param changes: changes named tuple |
|
973 | 996 | :param file_changes: file changes named tuple |
|
974 | 997 | |
@@ -987,6 +1010,7 b' class PullRequestModel(BaseModel):' | |||
|
987 | 1010 | 'added_files': file_changes.added, |
|
988 | 1011 | 'modified_files': file_changes.modified, |
|
989 | 1012 | 'removed_files': file_changes.removed, |
|
1013 | 'ancestor_commit_id': ancestor_commit_id | |
|
990 | 1014 | } |
|
991 | 1015 | renderer = RstTemplateRenderer() |
|
992 | 1016 | return renderer.render('pull_request_update.mako', **params) |
@@ -1170,6 +1194,75 b' class PullRequestModel(BaseModel):' | |||
|
1170 | 1194 | email_kwargs=kwargs, |
|
1171 | 1195 | ) |
|
1172 | 1196 | |
|
1197 | def notify_users(self, pull_request, updating_user, ancestor_commit_id, | |
|
1198 | commit_changes, file_changes): | |
|
1199 | ||
|
1200 | updating_user_id = updating_user.user_id | |
|
1201 | reviewers = set([x.user.user_id for x in pull_request.reviewers]) | |
|
1202 | # NOTE(marcink): send notification to all other users except to | |
|
1203 | # person who updated the PR | |
|
1204 | recipients = reviewers.difference(set([updating_user_id])) | |
|
1205 | ||
|
1206 | log.debug('Notify following recipients about pull-request update %s', recipients) | |
|
1207 | ||
|
1208 | pull_request_obj = pull_request | |
|
1209 | ||
|
1210 | # send email about the update | |
|
1211 | changed_files = ( | |
|
1212 | file_changes.added + file_changes.modified + file_changes.removed) | |
|
1213 | ||
|
1214 | pr_source_repo = pull_request_obj.source_repo | |
|
1215 | pr_target_repo = pull_request_obj.target_repo | |
|
1216 | ||
|
1217 | pr_url = h.route_url('pullrequest_show', | |
|
1218 | repo_name=pr_target_repo.repo_name, | |
|
1219 | pull_request_id=pull_request_obj.pull_request_id,) | |
|
1220 | ||
|
1221 | # set some variables for email notification | |
|
1222 | pr_target_repo_url = h.route_url( | |
|
1223 | 'repo_summary', repo_name=pr_target_repo.repo_name) | |
|
1224 | ||
|
1225 | pr_source_repo_url = h.route_url( | |
|
1226 | 'repo_summary', repo_name=pr_source_repo.repo_name) | |
|
1227 | ||
|
1228 | email_kwargs = { | |
|
1229 | 'date': datetime.datetime.now(), | |
|
1230 | 'updating_user': updating_user, | |
|
1231 | ||
|
1232 | 'pull_request': pull_request_obj, | |
|
1233 | ||
|
1234 | 'pull_request_target_repo': pr_target_repo, | |
|
1235 | 'pull_request_target_repo_url': pr_target_repo_url, | |
|
1236 | ||
|
1237 | 'pull_request_source_repo': pr_source_repo, | |
|
1238 | 'pull_request_source_repo_url': pr_source_repo_url, | |
|
1239 | ||
|
1240 | 'pull_request_url': pr_url, | |
|
1241 | ||
|
1242 | 'ancestor_commit_id': ancestor_commit_id, | |
|
1243 | 'added_commits': commit_changes.added, | |
|
1244 | 'removed_commits': commit_changes.removed, | |
|
1245 | 'changed_files': changed_files, | |
|
1246 | 'added_files': file_changes.added, | |
|
1247 | 'modified_files': file_changes.modified, | |
|
1248 | 'removed_files': file_changes.removed, | |
|
1249 | } | |
|
1250 | ||
|
1251 | (subject, | |
|
1252 | _h, _e, # we don't care about those | |
|
1253 | body_plaintext) = EmailNotificationModel().render_email( | |
|
1254 | EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **email_kwargs) | |
|
1255 | ||
|
1256 | # create notification objects, and emails | |
|
1257 | NotificationModel().create( | |
|
1258 | created_by=updating_user, | |
|
1259 | notification_subject=subject, | |
|
1260 | notification_body=body_plaintext, | |
|
1261 | notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, | |
|
1262 | recipients=recipients, | |
|
1263 | email_kwargs=email_kwargs, | |
|
1264 | ) | |
|
1265 | ||
|
1173 | 1266 | def delete(self, pull_request, user): |
|
1174 | 1267 | pull_request = self.__get_pull_request(pull_request) |
|
1175 | 1268 | old_data = pull_request.get_api_data(with_merge_state=False) |
@@ -115,17 +115,17 b' data = {' | |||
|
115 | 115 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> |
|
116 | 116 | |
|
117 | 117 | <h4 style="margin: 0"> |
|
118 |
<div style="margin-bottom: 4px |
|
|
119 | @${h.person(user.username)} | |
|
118 | <div style="margin-bottom: 4px"> | |
|
119 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> | |
|
120 | ${_('left a')} | |
|
121 | <a href="${pr_comment_url}" style="${base.link_css()}"> | |
|
122 | % if comment_file: | |
|
123 | ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)} | |
|
124 | % else: | |
|
125 | ${_('{comment_type} on pull request.').format(**data) |n} | |
|
126 | % endif | |
|
127 | </a> | |
|
120 | 128 | </div> |
|
121 | ${_('left a')} | |
|
122 | <a href="${pr_comment_url}" style="${base.link_css()}"> | |
|
123 | % if comment_file: | |
|
124 | ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)} | |
|
125 | % else: | |
|
126 | ${_('{comment_type} on pull request.').format(**data) |n} | |
|
127 | % endif | |
|
128 | </a> | |
|
129 | 129 | <div style="margin-top: 10px"></div> |
|
130 | 130 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> |
|
131 | 131 | </h4> |
@@ -78,13 +78,13 b' data = {' | |||
|
78 | 78 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> |
|
79 | 79 | |
|
80 | 80 | <h4 style="margin: 0"> |
|
81 |
<div style="margin-bottom: 4px |
|
|
82 | @${h.person(user.username)} | |
|
81 | <div style="margin-bottom: 4px"> | |
|
82 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> | |
|
83 | ${_('requested a')} | |
|
84 | <a href="${pull_request_url}" style="${base.link_css()}"> | |
|
85 | ${_('pull request review.').format(**data) } | |
|
86 | </a> | |
|
83 | 87 | </div> |
|
84 | ${_('requested a')} | |
|
85 | <a href="${pull_request_url}" style="${base.link_css()}"> | |
|
86 | ${_('pull request review.').format(**data) } | |
|
87 | </a> | |
|
88 | 88 | <div style="margin-top: 10px"></div> |
|
89 | 89 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> |
|
90 | 90 | </h4> |
@@ -14,13 +14,13 b' Pull request updated. Auto status change' | |||
|
14 | 14 | %else: |
|
15 | 15 | Changed files: |
|
16 | 16 | %for file_name in added_files: |
|
17 |
* `A ${file_name} <#${'a_' + h.FID( |
|
|
17 | * `A ${file_name} <#${'a_' + h.FID(ancestor_commit_id, file_name)}>`_ | |
|
18 | 18 | %endfor |
|
19 | 19 | %for file_name in modified_files: |
|
20 |
* `M ${file_name} <#${'a_' + h.FID( |
|
|
20 | * `M ${file_name} <#${'a_' + h.FID(ancestor_commit_id, file_name)}>`_ | |
|
21 | 21 | %endfor |
|
22 | 22 | %for file_name in removed_files: |
|
23 | * R ${file_name} | |
|
23 | * `R ${file_name}` | |
|
24 | 24 | %endfor |
|
25 | 25 | %endif |
|
26 | 26 |
@@ -87,6 +87,59 b' def test_render_pr_email(app, user_admin' | |||
|
87 | 87 | assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"' |
|
88 | 88 | |
|
89 | 89 | |
|
90 | def test_render_pr_update_email(app, user_admin): | |
|
91 | ref = collections.namedtuple( | |
|
92 | 'Ref', 'name, type')('fxies123', 'book') | |
|
93 | ||
|
94 | pr = collections.namedtuple('PullRequest', | |
|
95 | 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')( | |
|
96 | 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch') | |
|
97 | ||
|
98 | source_repo = target_repo = collections.namedtuple( | |
|
99 | 'Repo', 'type, repo_name')('hg', 'pull_request_1') | |
|
100 | ||
|
101 | commit_changes = AttributeDict({ | |
|
102 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], | |
|
103 | 'removed': ['eeeeeeeeeee'], | |
|
104 | }) | |
|
105 | file_changes = AttributeDict({ | |
|
106 | 'added': ['a/file1.md', 'file2.py'], | |
|
107 | 'modified': ['b/modified_file.rst'], | |
|
108 | 'removed': ['.idea'], | |
|
109 | }) | |
|
110 | ||
|
111 | kwargs = { | |
|
112 | 'updating_user': User.get_first_super_admin(), | |
|
113 | ||
|
114 | 'pull_request': pr, | |
|
115 | 'pull_request_commits': [], | |
|
116 | ||
|
117 | 'pull_request_target_repo': target_repo, | |
|
118 | 'pull_request_target_repo_url': 'x', | |
|
119 | ||
|
120 | 'pull_request_source_repo': source_repo, | |
|
121 | 'pull_request_source_repo_url': 'x', | |
|
122 | ||
|
123 | 'pull_request_url': 'http://localhost/pr1', | |
|
124 | ||
|
125 | 'pr_comment_url': 'http://comment-url', | |
|
126 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
|
127 | 'ancestor_commit_id': 'f39bd443', | |
|
128 | 'added_commits': commit_changes.added, | |
|
129 | 'removed_commits': commit_changes.removed, | |
|
130 | 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed), | |
|
131 | 'added_files': file_changes.added, | |
|
132 | 'modified_files': file_changes.modified, | |
|
133 | 'removed_files': file_changes.removed, | |
|
134 | } | |
|
135 | ||
|
136 | subject, headers, body, body_plaintext = EmailNotificationModel().render_email( | |
|
137 | EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **kwargs) | |
|
138 | ||
|
139 | # subject | |
|
140 | assert subject == '@test_admin (RhodeCode Admin) updated pull request. !200: "Example Pull Request"' | |
|
141 | ||
|
142 | ||
|
90 | 143 | @pytest.mark.parametrize('mention', [ |
|
91 | 144 | True, |
|
92 | 145 | False |
@@ -538,6 +538,7 b' Pull request updated. Auto status change' | |||
|
538 | 538 | 'added_files': [], |
|
539 | 539 | 'modified_files': [], |
|
540 | 540 | 'removed_files': [], |
|
541 | 'ancestor_commit_id': 'aaabbbcccdddeee', | |
|
541 | 542 | } |
|
542 | 543 | renderer = RstTemplateRenderer() |
|
543 | 544 | rendered = renderer.render('pull_request_update.mako', **params) |
@@ -557,11 +558,11 b' Pull request updated. Auto status change' | |||
|
557 | 558 | * :removed:`3 removed` |
|
558 | 559 | |
|
559 | 560 | Changed files: |
|
560 | * `A /path/a.py <#a_c--68ed34923b68>`_ | |
|
561 | * `A /path/b.js <#a_c--64f90608b607>`_ | |
|
562 | * `M /path/d.js <#a_c--85842bf30c6e>`_ | |
|
563 | * `M /path/ę.py <#a_c--d713adf009cd>`_ | |
|
564 | * R /path/ź.py | |
|
561 | * `A /path/a.py <#a_c-aaabbbcccddd-68ed34923b68>`_ | |
|
562 | * `A /path/b.js <#a_c-aaabbbcccddd-64f90608b607>`_ | |
|
563 | * `M /path/d.js <#a_c-aaabbbcccddd-85842bf30c6e>`_ | |
|
564 | * `M /path/ę.py <#a_c-aaabbbcccddd-d713adf009cd>`_ | |
|
565 | * `R /path/ź.py` | |
|
565 | 566 | |
|
566 | 567 | .. |under_review| replace:: *"NEW STATUS"*''' |
|
567 | 568 | |
@@ -577,6 +578,7 b' Pull request updated. Auto status change' | |||
|
577 | 578 | 'added_files': added, |
|
578 | 579 | 'modified_files': modified, |
|
579 | 580 | 'removed_files': removed, |
|
581 | 'ancestor_commit_id': 'aaabbbcccdddeee', | |
|
580 | 582 | } |
|
581 | 583 | renderer = RstTemplateRenderer() |
|
582 | 584 | rendered = renderer.render('pull_request_update.mako', **params) |
@@ -81,7 +81,7 b' def test_pull_request_stays_if_update_wi' | |||
|
81 | 81 | voted_status, *pull_request.reviewers) |
|
82 | 82 | |
|
83 | 83 | # Update, without change |
|
84 | PullRequestModel().update_commits(pull_request) | |
|
84 | PullRequestModel().update_commits(pull_request, pull_request.author) | |
|
85 | 85 | |
|
86 | 86 | # Expect that review status is the voted_status |
|
87 | 87 | expected_review_status = voted_status |
@@ -100,7 +100,7 b' def test_pull_request_under_review_if_up' | |||
|
100 | 100 | |
|
101 | 101 | # Update, with change |
|
102 | 102 | pr_util.update_source_repository() |
|
103 | PullRequestModel().update_commits(pull_request) | |
|
103 | PullRequestModel().update_commits(pull_request, pull_request.author) | |
|
104 | 104 | |
|
105 | 105 | # Expect that review status is the voted_status |
|
106 | 106 | expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW |
@@ -171,7 +171,7 b' def test_commit_keeps_status_if_unchange' | |||
|
171 | 171 | commit_id = pull_request.revisions[-1] |
|
172 | 172 | pr_util.create_status_votes(voted_status, pull_request.reviewers[0]) |
|
173 | 173 | pr_util.update_source_repository() |
|
174 | PullRequestModel().update_commits(pull_request) | |
|
174 | PullRequestModel().update_commits(pull_request, pull_request.author) | |
|
175 | 175 | assert pull_request.revisions[-1] == commit_id |
|
176 | 176 | |
|
177 | 177 | status = ChangesetStatusModel().get_status( |
@@ -814,7 +814,7 b' def test_update_writes_snapshot_into_pul' | |||
|
814 | 814 | pull_request = pr_util.create_pull_request() |
|
815 | 815 | pr_util.update_source_repository() |
|
816 | 816 | |
|
817 | model.update_commits(pull_request) | |
|
817 | model.update_commits(pull_request, pull_request.author) | |
|
818 | 818 | |
|
819 | 819 | # Expect that it has a version entry now |
|
820 | 820 | assert len(model.get_versions(pull_request)) == 1 |
@@ -823,7 +823,7 b' def test_update_writes_snapshot_into_pul' | |||
|
823 | 823 | def test_update_skips_new_version_if_unchanged(pr_util, config_stub): |
|
824 | 824 | pull_request = pr_util.create_pull_request() |
|
825 | 825 | model = PullRequestModel() |
|
826 | model.update_commits(pull_request) | |
|
826 | model.update_commits(pull_request, pull_request.author) | |
|
827 | 827 | |
|
828 | 828 | # Expect that it still has no versions |
|
829 | 829 | assert len(model.get_versions(pull_request)) == 0 |
@@ -835,7 +835,7 b' def test_update_assigns_comments_to_the_' | |||
|
835 | 835 | comment = pr_util.create_comment() |
|
836 | 836 | pr_util.update_source_repository() |
|
837 | 837 | |
|
838 | model.update_commits(pull_request) | |
|
838 | model.update_commits(pull_request, pull_request.author) | |
|
839 | 839 | |
|
840 | 840 | # Expect that the comment is linked to the pr version now |
|
841 | 841 | assert comment.pull_request_version == model.get_versions(pull_request)[0] |
@@ -847,8 +847,9 b' def test_update_adds_a_comment_to_the_pu' | |||
|
847 | 847 | pr_util.update_source_repository() |
|
848 | 848 | pr_util.update_source_repository() |
|
849 | 849 | |
|
850 | model.update_commits(pull_request) | |
|
850 | update_response = model.update_commits(pull_request, pull_request.author) | |
|
851 | 851 | |
|
852 | commit_id = update_response.common_ancestor_id | |
|
852 | 853 | # Expect to find a new comment about the change |
|
853 | 854 | expected_message = textwrap.dedent( |
|
854 | 855 | """\ |
@@ -863,10 +864,10 b' def test_update_adds_a_comment_to_the_pu' | |||
|
863 | 864 | * :removed:`0 removed` |
|
864 | 865 | |
|
865 | 866 | Changed files: |
|
866 | * `A file_2 <#a_c--92ed3b5f07b4>`_ | |
|
867 | * `A file_2 <#a_c-{}-92ed3b5f07b4>`_ | |
|
867 | 868 | |
|
868 | 869 | .. |under_review| replace:: *"Under Review"*""" |
|
869 | ) | |
|
870 | ).format(commit_id[:12]) | |
|
870 | 871 | pull_request_comments = sorted( |
|
871 | 872 | pull_request.comments, key=lambda c: c.modified_at) |
|
872 | 873 | update_comment = pull_request_comments[-1] |
@@ -1047,7 +1047,7 b' class PRTestUtility(object):' | |||
|
1047 | 1047 | def add_one_commit(self, head=None): |
|
1048 | 1048 | self.update_source_repository(head=head) |
|
1049 | 1049 | old_commit_ids = set(self.pull_request.revisions) |
|
1050 | PullRequestModel().update_commits(self.pull_request) | |
|
1050 | PullRequestModel().update_commits(self.pull_request, self.pull_request.author) | |
|
1051 | 1051 | commit_ids = set(self.pull_request.revisions) |
|
1052 | 1052 | new_commit_ids = commit_ids - old_commit_ids |
|
1053 | 1053 | assert len(new_commit_ids) == 1 |
@@ -1066,7 +1066,7 b' class PRTestUtility(object):' | |||
|
1066 | 1066 | kwargs = {} |
|
1067 | 1067 | source_vcs.strip(removed_commit_id, **kwargs) |
|
1068 | 1068 | |
|
1069 | PullRequestModel().update_commits(self.pull_request) | |
|
1069 | PullRequestModel().update_commits(self.pull_request, self.pull_request.author) | |
|
1070 | 1070 | assert len(self.pull_request.revisions) == 1 |
|
1071 | 1071 | return removed_commit_id |
|
1072 | 1072 |
General Comments 0
You need to be logged in to leave comments.
Login now