##// END OF EJS Templates
pull-requests: allow having repo targets all forks and parent forks of target....
marcink -
r3330:7e8ec062 default
parent child Browse files
Show More
@@ -1,483 +1,483 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84
84
85 # still working url for backward compat.
85 # still working url for backward compat.
86 config.add_route(
86 config.add_route(
87 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89
89
90 # Files
90 # Files
91 config.add_route(
91 config.add_route(
92 name='repo_archivefile',
92 name='repo_archivefile',
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94
94
95 config.add_route(
95 config.add_route(
96 name='repo_files_diff',
96 name='repo_files_diff',
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
99 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101
101
102 config.add_route(
102 config.add_route(
103 name='repo_files',
103 name='repo_files',
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 config.add_route(
105 config.add_route(
106 name='repo_files:default_path',
106 name='repo_files:default_path',
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files:default_commit',
109 name='repo_files:default_commit',
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111
111
112 config.add_route(
112 config.add_route(
113 name='repo_files:rendered',
113 name='repo_files:rendered',
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115
115
116 config.add_route(
116 config.add_route(
117 name='repo_files:annotated',
117 name='repo_files:annotated',
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 config.add_route(
119 config.add_route(
120 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122
122
123 config.add_route(
123 config.add_route(
124 name='repo_nodetree_full',
124 name='repo_nodetree_full',
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 config.add_route(
126 config.add_route(
127 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129
129
130 config.add_route(
130 config.add_route(
131 name='repo_files_nodelist',
131 name='repo_files_nodelist',
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_file_raw',
135 name='repo_file_raw',
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_file_download',
139 name='repo_file_download',
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
142 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 repo_route=True)
144 repo_route=True)
145
145
146 config.add_route(
146 config.add_route(
147 name='repo_file_history',
147 name='repo_file_history',
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149
149
150 config.add_route(
150 config.add_route(
151 name='repo_file_authors',
151 name='repo_file_authors',
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_files_remove_file',
155 name='repo_files_remove_file',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
157 repo_route=True)
158 config.add_route(
158 config.add_route(
159 name='repo_files_delete_file',
159 name='repo_files_delete_file',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
161 repo_route=True)
162 config.add_route(
162 config.add_route(
163 name='repo_files_edit_file',
163 name='repo_files_edit_file',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
165 repo_route=True)
166 config.add_route(
166 config.add_route(
167 name='repo_files_update_file',
167 name='repo_files_update_file',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
169 repo_route=True)
170 config.add_route(
170 config.add_route(
171 name='repo_files_add_file',
171 name='repo_files_add_file',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
173 repo_route=True)
174 config.add_route(
174 config.add_route(
175 name='repo_files_create_file',
175 name='repo_files_create_file',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
177 repo_route=True)
178
178
179 # Refs data
179 # Refs data
180 config.add_route(
180 config.add_route(
181 name='repo_refs_data',
181 name='repo_refs_data',
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183
183
184 config.add_route(
184 config.add_route(
185 name='repo_refs_changelog_data',
185 name='repo_refs_changelog_data',
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187
187
188 config.add_route(
188 config.add_route(
189 name='repo_stats',
189 name='repo_stats',
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191
191
192 # Changelog
192 # Changelog
193 config.add_route(
193 config.add_route(
194 name='repo_changelog',
194 name='repo_changelog',
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 config.add_route(
196 config.add_route(
197 name='repo_changelog_file',
197 name='repo_changelog_file',
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 config.add_route(
199 config.add_route(
200 name='repo_changelog_elements',
200 name='repo_changelog_elements',
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 config.add_route(
202 config.add_route(
203 name='repo_changelog_elements_file',
203 name='repo_changelog_elements_file',
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
204 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205
205
206 # Compare
206 # Compare
207 config.add_route(
207 config.add_route(
208 name='repo_compare_select',
208 name='repo_compare_select',
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
209 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210
210
211 config.add_route(
211 config.add_route(
212 name='repo_compare',
212 name='repo_compare',
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
213 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214
214
215 # Tags
215 # Tags
216 config.add_route(
216 config.add_route(
217 name='tags_home',
217 name='tags_home',
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
218 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219
219
220 # Branches
220 # Branches
221 config.add_route(
221 config.add_route(
222 name='branches_home',
222 name='branches_home',
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
223 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224
224
225 # Bookmarks
225 # Bookmarks
226 config.add_route(
226 config.add_route(
227 name='bookmarks_home',
227 name='bookmarks_home',
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
228 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229
229
230 # Forks
230 # Forks
231 config.add_route(
231 config.add_route(
232 name='repo_fork_new',
232 name='repo_fork_new',
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 repo_forbid_when_archived=True,
234 repo_forbid_when_archived=True,
235 repo_accepted_types=['hg', 'git'])
235 repo_accepted_types=['hg', 'git'])
236
236
237 config.add_route(
237 config.add_route(
238 name='repo_fork_create',
238 name='repo_fork_create',
239 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
240 repo_forbid_when_archived=True,
240 repo_forbid_when_archived=True,
241 repo_accepted_types=['hg', 'git'])
241 repo_accepted_types=['hg', 'git'])
242
242
243 config.add_route(
243 config.add_route(
244 name='repo_forks_show_all',
244 name='repo_forks_show_all',
245 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
245 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
246 repo_accepted_types=['hg', 'git'])
246 repo_accepted_types=['hg', 'git'])
247 config.add_route(
247 config.add_route(
248 name='repo_forks_data',
248 name='repo_forks_data',
249 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
249 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
250 repo_accepted_types=['hg', 'git'])
250 repo_accepted_types=['hg', 'git'])
251
251
252 # Pull Requests
252 # Pull Requests
253 config.add_route(
253 config.add_route(
254 name='pullrequest_show',
254 name='pullrequest_show',
255 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
255 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
256 repo_route=True)
256 repo_route=True)
257
257
258 config.add_route(
258 config.add_route(
259 name='pullrequest_show_all',
259 name='pullrequest_show_all',
260 pattern='/{repo_name:.*?[^/]}/pull-request',
260 pattern='/{repo_name:.*?[^/]}/pull-request',
261 repo_route=True, repo_accepted_types=['hg', 'git'])
261 repo_route=True, repo_accepted_types=['hg', 'git'])
262
262
263 config.add_route(
263 config.add_route(
264 name='pullrequest_show_all_data',
264 name='pullrequest_show_all_data',
265 pattern='/{repo_name:.*?[^/]}/pull-request-data',
265 pattern='/{repo_name:.*?[^/]}/pull-request-data',
266 repo_route=True, repo_accepted_types=['hg', 'git'])
266 repo_route=True, repo_accepted_types=['hg', 'git'])
267
267
268 config.add_route(
268 config.add_route(
269 name='pullrequest_repo_refs',
269 name='pullrequest_repo_refs',
270 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
270 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
271 repo_route=True)
271 repo_route=True)
272
272
273 config.add_route(
273 config.add_route(
274 name='pullrequest_repo_destinations',
274 name='pullrequest_repo_targets',
275 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
275 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
276 repo_route=True)
276 repo_route=True)
277
277
278 config.add_route(
278 config.add_route(
279 name='pullrequest_new',
279 name='pullrequest_new',
280 pattern='/{repo_name:.*?[^/]}/pull-request/new',
280 pattern='/{repo_name:.*?[^/]}/pull-request/new',
281 repo_route=True, repo_accepted_types=['hg', 'git'],
281 repo_route=True, repo_accepted_types=['hg', 'git'],
282 repo_forbid_when_archived=True)
282 repo_forbid_when_archived=True)
283
283
284 config.add_route(
284 config.add_route(
285 name='pullrequest_create',
285 name='pullrequest_create',
286 pattern='/{repo_name:.*?[^/]}/pull-request/create',
286 pattern='/{repo_name:.*?[^/]}/pull-request/create',
287 repo_route=True, repo_accepted_types=['hg', 'git'],
287 repo_route=True, repo_accepted_types=['hg', 'git'],
288 repo_forbid_when_archived=True)
288 repo_forbid_when_archived=True)
289
289
290 config.add_route(
290 config.add_route(
291 name='pullrequest_update',
291 name='pullrequest_update',
292 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
292 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
293 repo_route=True, repo_forbid_when_archived=True)
293 repo_route=True, repo_forbid_when_archived=True)
294
294
295 config.add_route(
295 config.add_route(
296 name='pullrequest_merge',
296 name='pullrequest_merge',
297 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
297 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
298 repo_route=True, repo_forbid_when_archived=True)
298 repo_route=True, repo_forbid_when_archived=True)
299
299
300 config.add_route(
300 config.add_route(
301 name='pullrequest_delete',
301 name='pullrequest_delete',
302 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
302 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
303 repo_route=True, repo_forbid_when_archived=True)
303 repo_route=True, repo_forbid_when_archived=True)
304
304
305 config.add_route(
305 config.add_route(
306 name='pullrequest_comment_create',
306 name='pullrequest_comment_create',
307 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
307 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
308 repo_route=True)
308 repo_route=True)
309
309
310 config.add_route(
310 config.add_route(
311 name='pullrequest_comment_delete',
311 name='pullrequest_comment_delete',
312 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
312 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
313 repo_route=True, repo_accepted_types=['hg', 'git'])
313 repo_route=True, repo_accepted_types=['hg', 'git'])
314
314
315 # Settings
315 # Settings
316 config.add_route(
316 config.add_route(
317 name='edit_repo',
317 name='edit_repo',
318 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
318 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
319 # update is POST on edit_repo
319 # update is POST on edit_repo
320
320
321 # Settings advanced
321 # Settings advanced
322 config.add_route(
322 config.add_route(
323 name='edit_repo_advanced',
323 name='edit_repo_advanced',
324 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
324 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
325 config.add_route(
325 config.add_route(
326 name='edit_repo_advanced_archive',
326 name='edit_repo_advanced_archive',
327 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
327 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
328 config.add_route(
328 config.add_route(
329 name='edit_repo_advanced_delete',
329 name='edit_repo_advanced_delete',
330 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
330 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
331 config.add_route(
331 config.add_route(
332 name='edit_repo_advanced_locking',
332 name='edit_repo_advanced_locking',
333 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
333 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
334 config.add_route(
334 config.add_route(
335 name='edit_repo_advanced_journal',
335 name='edit_repo_advanced_journal',
336 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
336 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
337 config.add_route(
337 config.add_route(
338 name='edit_repo_advanced_fork',
338 name='edit_repo_advanced_fork',
339 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
339 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
340
340
341 config.add_route(
341 config.add_route(
342 name='edit_repo_advanced_hooks',
342 name='edit_repo_advanced_hooks',
343 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
343 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
344
344
345 # Caches
345 # Caches
346 config.add_route(
346 config.add_route(
347 name='edit_repo_caches',
347 name='edit_repo_caches',
348 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
348 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
349
349
350 # Permissions
350 # Permissions
351 config.add_route(
351 config.add_route(
352 name='edit_repo_perms',
352 name='edit_repo_perms',
353 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
353 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
354
354
355 # Permissions Branch (EE feature)
355 # Permissions Branch (EE feature)
356 config.add_route(
356 config.add_route(
357 name='edit_repo_perms_branch',
357 name='edit_repo_perms_branch',
358 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
358 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
359 config.add_route(
359 config.add_route(
360 name='edit_repo_perms_branch_delete',
360 name='edit_repo_perms_branch_delete',
361 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
361 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
362 repo_route=True)
362 repo_route=True)
363
363
364 # Maintenance
364 # Maintenance
365 config.add_route(
365 config.add_route(
366 name='edit_repo_maintenance',
366 name='edit_repo_maintenance',
367 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
367 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
368
368
369 config.add_route(
369 config.add_route(
370 name='edit_repo_maintenance_execute',
370 name='edit_repo_maintenance_execute',
371 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
371 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
372
372
373 # Fields
373 # Fields
374 config.add_route(
374 config.add_route(
375 name='edit_repo_fields',
375 name='edit_repo_fields',
376 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
376 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
377 config.add_route(
377 config.add_route(
378 name='edit_repo_fields_create',
378 name='edit_repo_fields_create',
379 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
379 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
380 config.add_route(
380 config.add_route(
381 name='edit_repo_fields_delete',
381 name='edit_repo_fields_delete',
382 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
382 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
383
383
384 # Locking
384 # Locking
385 config.add_route(
385 config.add_route(
386 name='repo_edit_toggle_locking',
386 name='repo_edit_toggle_locking',
387 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
387 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
388
388
389 # Remote
389 # Remote
390 config.add_route(
390 config.add_route(
391 name='edit_repo_remote',
391 name='edit_repo_remote',
392 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
392 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
393 config.add_route(
393 config.add_route(
394 name='edit_repo_remote_pull',
394 name='edit_repo_remote_pull',
395 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
395 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
396 config.add_route(
396 config.add_route(
397 name='edit_repo_remote_push',
397 name='edit_repo_remote_push',
398 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
398 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
399
399
400 # Statistics
400 # Statistics
401 config.add_route(
401 config.add_route(
402 name='edit_repo_statistics',
402 name='edit_repo_statistics',
403 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
403 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
404 config.add_route(
404 config.add_route(
405 name='edit_repo_statistics_reset',
405 name='edit_repo_statistics_reset',
406 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
406 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
407
407
408 # Issue trackers
408 # Issue trackers
409 config.add_route(
409 config.add_route(
410 name='edit_repo_issuetracker',
410 name='edit_repo_issuetracker',
411 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
411 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
412 config.add_route(
412 config.add_route(
413 name='edit_repo_issuetracker_test',
413 name='edit_repo_issuetracker_test',
414 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
414 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
415 config.add_route(
415 config.add_route(
416 name='edit_repo_issuetracker_delete',
416 name='edit_repo_issuetracker_delete',
417 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
417 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
418 config.add_route(
418 config.add_route(
419 name='edit_repo_issuetracker_update',
419 name='edit_repo_issuetracker_update',
420 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
420 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
421
421
422 # VCS Settings
422 # VCS Settings
423 config.add_route(
423 config.add_route(
424 name='edit_repo_vcs',
424 name='edit_repo_vcs',
425 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
425 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
426 config.add_route(
426 config.add_route(
427 name='edit_repo_vcs_update',
427 name='edit_repo_vcs_update',
428 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
428 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
429
429
430 # svn pattern
430 # svn pattern
431 config.add_route(
431 config.add_route(
432 name='edit_repo_vcs_svn_pattern_delete',
432 name='edit_repo_vcs_svn_pattern_delete',
433 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
433 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
434
434
435 # Repo Review Rules (EE feature)
435 # Repo Review Rules (EE feature)
436 config.add_route(
436 config.add_route(
437 name='repo_reviewers',
437 name='repo_reviewers',
438 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
438 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
439
439
440 config.add_route(
440 config.add_route(
441 name='repo_default_reviewers_data',
441 name='repo_default_reviewers_data',
442 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
442 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
443
443
444 # Repo Automation (EE feature)
444 # Repo Automation (EE feature)
445 config.add_route(
445 config.add_route(
446 name='repo_automation',
446 name='repo_automation',
447 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
447 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
448
448
449 # Strip
449 # Strip
450 config.add_route(
450 config.add_route(
451 name='edit_repo_strip',
451 name='edit_repo_strip',
452 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
452 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
453
453
454 config.add_route(
454 config.add_route(
455 name='strip_check',
455 name='strip_check',
456 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
456 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
457
457
458 config.add_route(
458 config.add_route(
459 name='strip_execute',
459 name='strip_execute',
460 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
460 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
461
461
462 # Audit logs
462 # Audit logs
463 config.add_route(
463 config.add_route(
464 name='edit_repo_audit_logs',
464 name='edit_repo_audit_logs',
465 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
465 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
466
466
467 # ATOM/RSS Feed
467 # ATOM/RSS Feed
468 config.add_route(
468 config.add_route(
469 name='rss_feed_home',
469 name='rss_feed_home',
470 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
470 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
471
471
472 config.add_route(
472 config.add_route(
473 name='atom_feed_home',
473 name='atom_feed_home',
474 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
474 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
475
475
476 # NOTE(marcink): needs to be at the end for catch-all
476 # NOTE(marcink): needs to be at the end for catch-all
477 add_route_with_slash(
477 add_route_with_slash(
478 config,
478 config,
479 name='repo_summary',
479 name='repo_summary',
480 pattern='/{repo_name:.*?[^/]}', repo_route=True)
480 pattern='/{repo_name:.*?[^/]}', repo_route=True)
481
481
482 # Scan module for configuration decorators.
482 # Scan module for configuration decorators.
483 config.scan('.views', ignore='.tests')
483 config.scan('.views', ignore='.tests')
@@ -1,1233 +1,1233 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 from rhodecode.tests.utils import AssertResponse
35 from rhodecode.tests.utils import AssertResponse
36
36
37
37
38 def route_path(name, params=None, **kwargs):
38 def route_path(name, params=None, **kwargs):
39 import urllib
39 import urllib
40
40
41 base_url = {
41 base_url = {
42 'repo_changelog': '/{repo_name}/changelog',
42 'repo_changelog': '/{repo_name}/changelog',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
48 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
56 }[name].format(**kwargs)
56 }[name].format(**kwargs)
57
57
58 if params:
58 if params:
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
59 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
60 return base_url
60 return base_url
61
61
62
62
63 @pytest.mark.usefixtures('app', 'autologin_user')
63 @pytest.mark.usefixtures('app', 'autologin_user')
64 @pytest.mark.backends("git", "hg")
64 @pytest.mark.backends("git", "hg")
65 class TestPullrequestsView(object):
65 class TestPullrequestsView(object):
66
66
67 def test_index(self, backend):
67 def test_index(self, backend):
68 self.app.get(route_path(
68 self.app.get(route_path(
69 'pullrequest_new',
69 'pullrequest_new',
70 repo_name=backend.repo_name))
70 repo_name=backend.repo_name))
71
71
72 def test_option_menu_create_pull_request_exists(self, backend):
72 def test_option_menu_create_pull_request_exists(self, backend):
73 repo_name = backend.repo_name
73 repo_name = backend.repo_name
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
75
75
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 'pullrequest_new', repo_name=repo_name)
77 'pullrequest_new', repo_name=repo_name)
78 response.mustcontain(create_pr_link)
78 response.mustcontain(create_pr_link)
79
79
80 def test_create_pr_form_with_raw_commit_id(self, backend):
80 def test_create_pr_form_with_raw_commit_id(self, backend):
81 repo = backend.repo
81 repo = backend.repo
82
82
83 self.app.get(
83 self.app.get(
84 route_path('pullrequest_new', repo_name=repo.repo_name,
84 route_path('pullrequest_new', repo_name=repo.repo_name,
85 commit=repo.get_commit().raw_id),
85 commit=repo.get_commit().raw_id),
86 status=200)
86 status=200)
87
87
88 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
88 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
89 @pytest.mark.parametrize('range_diff', ["0", "1"])
89 @pytest.mark.parametrize('range_diff', ["0", "1"])
90 def test_show(self, pr_util, pr_merge_enabled, range_diff):
90 def test_show(self, pr_util, pr_merge_enabled, range_diff):
91 pull_request = pr_util.create_pull_request(
91 pull_request = pr_util.create_pull_request(
92 mergeable=pr_merge_enabled, enable_notifications=False)
92 mergeable=pr_merge_enabled, enable_notifications=False)
93
93
94 response = self.app.get(route_path(
94 response = self.app.get(route_path(
95 'pullrequest_show',
95 'pullrequest_show',
96 repo_name=pull_request.target_repo.scm_instance().name,
96 repo_name=pull_request.target_repo.scm_instance().name,
97 pull_request_id=pull_request.pull_request_id,
97 pull_request_id=pull_request.pull_request_id,
98 params={'range-diff': range_diff}))
98 params={'range-diff': range_diff}))
99
99
100 for commit_id in pull_request.revisions:
100 for commit_id in pull_request.revisions:
101 response.mustcontain(commit_id)
101 response.mustcontain(commit_id)
102
102
103 assert pull_request.target_ref_parts.type in response
103 assert pull_request.target_ref_parts.type in response
104 assert pull_request.target_ref_parts.name in response
104 assert pull_request.target_ref_parts.name in response
105 target_clone_url = pull_request.target_repo.clone_url()
105 target_clone_url = pull_request.target_repo.clone_url()
106 assert target_clone_url in response
106 assert target_clone_url in response
107
107
108 assert 'class="pull-request-merge"' in response
108 assert 'class="pull-request-merge"' in response
109 if pr_merge_enabled:
109 if pr_merge_enabled:
110 response.mustcontain('Pull request reviewer approval is pending')
110 response.mustcontain('Pull request reviewer approval is pending')
111 else:
111 else:
112 response.mustcontain('Server-side pull request merging is disabled.')
112 response.mustcontain('Server-side pull request merging is disabled.')
113
113
114 if range_diff == "1":
114 if range_diff == "1":
115 response.mustcontain('Turn off: Show the diff as commit range')
115 response.mustcontain('Turn off: Show the diff as commit range')
116
116
117 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
117 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
118 # Logout
118 # Logout
119 response = self.app.post(
119 response = self.app.post(
120 h.route_path('logout'),
120 h.route_path('logout'),
121 params={'csrf_token': csrf_token})
121 params={'csrf_token': csrf_token})
122 # Login as regular user
122 # Login as regular user
123 response = self.app.post(h.route_path('login'),
123 response = self.app.post(h.route_path('login'),
124 {'username': TEST_USER_REGULAR_LOGIN,
124 {'username': TEST_USER_REGULAR_LOGIN,
125 'password': 'test12'})
125 'password': 'test12'})
126
126
127 pull_request = pr_util.create_pull_request(
127 pull_request = pr_util.create_pull_request(
128 author=TEST_USER_REGULAR_LOGIN)
128 author=TEST_USER_REGULAR_LOGIN)
129
129
130 response = self.app.get(route_path(
130 response = self.app.get(route_path(
131 'pullrequest_show',
131 'pullrequest_show',
132 repo_name=pull_request.target_repo.scm_instance().name,
132 repo_name=pull_request.target_repo.scm_instance().name,
133 pull_request_id=pull_request.pull_request_id))
133 pull_request_id=pull_request.pull_request_id))
134
134
135 response.mustcontain('Server-side pull request merging is disabled.')
135 response.mustcontain('Server-side pull request merging is disabled.')
136
136
137 assert_response = response.assert_response()
137 assert_response = response.assert_response()
138 # for regular user without a merge permissions, we don't see it
138 # for regular user without a merge permissions, we don't see it
139 assert_response.no_element_exists('#close-pull-request-action')
139 assert_response.no_element_exists('#close-pull-request-action')
140
140
141 user_util.grant_user_permission_to_repo(
141 user_util.grant_user_permission_to_repo(
142 pull_request.target_repo,
142 pull_request.target_repo,
143 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
143 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
144 'repository.write')
144 'repository.write')
145 response = self.app.get(route_path(
145 response = self.app.get(route_path(
146 'pullrequest_show',
146 'pullrequest_show',
147 repo_name=pull_request.target_repo.scm_instance().name,
147 repo_name=pull_request.target_repo.scm_instance().name,
148 pull_request_id=pull_request.pull_request_id))
148 pull_request_id=pull_request.pull_request_id))
149
149
150 response.mustcontain('Server-side pull request merging is disabled.')
150 response.mustcontain('Server-side pull request merging is disabled.')
151
151
152 assert_response = response.assert_response()
152 assert_response = response.assert_response()
153 # now regular user has a merge permissions, we have CLOSE button
153 # now regular user has a merge permissions, we have CLOSE button
154 assert_response.one_element_exists('#close-pull-request-action')
154 assert_response.one_element_exists('#close-pull-request-action')
155
155
156 def test_show_invalid_commit_id(self, pr_util):
156 def test_show_invalid_commit_id(self, pr_util):
157 # Simulating invalid revisions which will cause a lookup error
157 # Simulating invalid revisions which will cause a lookup error
158 pull_request = pr_util.create_pull_request()
158 pull_request = pr_util.create_pull_request()
159 pull_request.revisions = ['invalid']
159 pull_request.revisions = ['invalid']
160 Session().add(pull_request)
160 Session().add(pull_request)
161 Session().commit()
161 Session().commit()
162
162
163 response = self.app.get(route_path(
163 response = self.app.get(route_path(
164 'pullrequest_show',
164 'pullrequest_show',
165 repo_name=pull_request.target_repo.scm_instance().name,
165 repo_name=pull_request.target_repo.scm_instance().name,
166 pull_request_id=pull_request.pull_request_id))
166 pull_request_id=pull_request.pull_request_id))
167
167
168 for commit_id in pull_request.revisions:
168 for commit_id in pull_request.revisions:
169 response.mustcontain(commit_id)
169 response.mustcontain(commit_id)
170
170
171 def test_show_invalid_source_reference(self, pr_util):
171 def test_show_invalid_source_reference(self, pr_util):
172 pull_request = pr_util.create_pull_request()
172 pull_request = pr_util.create_pull_request()
173 pull_request.source_ref = 'branch:b:invalid'
173 pull_request.source_ref = 'branch:b:invalid'
174 Session().add(pull_request)
174 Session().add(pull_request)
175 Session().commit()
175 Session().commit()
176
176
177 self.app.get(route_path(
177 self.app.get(route_path(
178 'pullrequest_show',
178 'pullrequest_show',
179 repo_name=pull_request.target_repo.scm_instance().name,
179 repo_name=pull_request.target_repo.scm_instance().name,
180 pull_request_id=pull_request.pull_request_id))
180 pull_request_id=pull_request.pull_request_id))
181
181
182 def test_edit_title_description(self, pr_util, csrf_token):
182 def test_edit_title_description(self, pr_util, csrf_token):
183 pull_request = pr_util.create_pull_request()
183 pull_request = pr_util.create_pull_request()
184 pull_request_id = pull_request.pull_request_id
184 pull_request_id = pull_request.pull_request_id
185
185
186 response = self.app.post(
186 response = self.app.post(
187 route_path('pullrequest_update',
187 route_path('pullrequest_update',
188 repo_name=pull_request.target_repo.repo_name,
188 repo_name=pull_request.target_repo.repo_name,
189 pull_request_id=pull_request_id),
189 pull_request_id=pull_request_id),
190 params={
190 params={
191 'edit_pull_request': 'true',
191 'edit_pull_request': 'true',
192 'title': 'New title',
192 'title': 'New title',
193 'description': 'New description',
193 'description': 'New description',
194 'csrf_token': csrf_token})
194 'csrf_token': csrf_token})
195
195
196 assert_session_flash(
196 assert_session_flash(
197 response, u'Pull request title & description updated.',
197 response, u'Pull request title & description updated.',
198 category='success')
198 category='success')
199
199
200 pull_request = PullRequest.get(pull_request_id)
200 pull_request = PullRequest.get(pull_request_id)
201 assert pull_request.title == 'New title'
201 assert pull_request.title == 'New title'
202 assert pull_request.description == 'New description'
202 assert pull_request.description == 'New description'
203
203
204 def test_edit_title_description_closed(self, pr_util, csrf_token):
204 def test_edit_title_description_closed(self, pr_util, csrf_token):
205 pull_request = pr_util.create_pull_request()
205 pull_request = pr_util.create_pull_request()
206 pull_request_id = pull_request.pull_request_id
206 pull_request_id = pull_request.pull_request_id
207 repo_name = pull_request.target_repo.repo_name
207 repo_name = pull_request.target_repo.repo_name
208 pr_util.close()
208 pr_util.close()
209
209
210 response = self.app.post(
210 response = self.app.post(
211 route_path('pullrequest_update',
211 route_path('pullrequest_update',
212 repo_name=repo_name, pull_request_id=pull_request_id),
212 repo_name=repo_name, pull_request_id=pull_request_id),
213 params={
213 params={
214 'edit_pull_request': 'true',
214 'edit_pull_request': 'true',
215 'title': 'New title',
215 'title': 'New title',
216 'description': 'New description',
216 'description': 'New description',
217 'csrf_token': csrf_token}, status=200)
217 'csrf_token': csrf_token}, status=200)
218 assert_session_flash(
218 assert_session_flash(
219 response, u'Cannot update closed pull requests.',
219 response, u'Cannot update closed pull requests.',
220 category='error')
220 category='error')
221
221
222 def test_update_invalid_source_reference(self, pr_util, csrf_token):
222 def test_update_invalid_source_reference(self, pr_util, csrf_token):
223 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
223 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
224
224
225 pull_request = pr_util.create_pull_request()
225 pull_request = pr_util.create_pull_request()
226 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
226 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
227 Session().add(pull_request)
227 Session().add(pull_request)
228 Session().commit()
228 Session().commit()
229
229
230 pull_request_id = pull_request.pull_request_id
230 pull_request_id = pull_request.pull_request_id
231
231
232 response = self.app.post(
232 response = self.app.post(
233 route_path('pullrequest_update',
233 route_path('pullrequest_update',
234 repo_name=pull_request.target_repo.repo_name,
234 repo_name=pull_request.target_repo.repo_name,
235 pull_request_id=pull_request_id),
235 pull_request_id=pull_request_id),
236 params={'update_commits': 'true',
236 params={'update_commits': 'true',
237 'csrf_token': csrf_token})
237 'csrf_token': csrf_token})
238
238
239 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
239 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
240 UpdateFailureReason.MISSING_SOURCE_REF])
240 UpdateFailureReason.MISSING_SOURCE_REF])
241 assert_session_flash(response, expected_msg, category='error')
241 assert_session_flash(response, expected_msg, category='error')
242
242
243 def test_missing_target_reference(self, pr_util, csrf_token):
243 def test_missing_target_reference(self, pr_util, csrf_token):
244 from rhodecode.lib.vcs.backends.base import MergeFailureReason
244 from rhodecode.lib.vcs.backends.base import MergeFailureReason
245 pull_request = pr_util.create_pull_request(
245 pull_request = pr_util.create_pull_request(
246 approved=True, mergeable=True)
246 approved=True, mergeable=True)
247 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
247 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
248 Session().add(pull_request)
248 Session().add(pull_request)
249 Session().commit()
249 Session().commit()
250
250
251 pull_request_id = pull_request.pull_request_id
251 pull_request_id = pull_request.pull_request_id
252 pull_request_url = route_path(
252 pull_request_url = route_path(
253 'pullrequest_show',
253 'pullrequest_show',
254 repo_name=pull_request.target_repo.repo_name,
254 repo_name=pull_request.target_repo.repo_name,
255 pull_request_id=pull_request_id)
255 pull_request_id=pull_request_id)
256
256
257 response = self.app.get(pull_request_url)
257 response = self.app.get(pull_request_url)
258
258
259 assertr = AssertResponse(response)
259 assertr = AssertResponse(response)
260 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
260 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
261 MergeFailureReason.MISSING_TARGET_REF]
261 MergeFailureReason.MISSING_TARGET_REF]
262 assertr.element_contains(
262 assertr.element_contains(
263 'span[data-role="merge-message"]', str(expected_msg))
263 'span[data-role="merge-message"]', str(expected_msg))
264
264
265 def test_comment_and_close_pull_request_custom_message_approved(
265 def test_comment_and_close_pull_request_custom_message_approved(
266 self, pr_util, csrf_token, xhr_header):
266 self, pr_util, csrf_token, xhr_header):
267
267
268 pull_request = pr_util.create_pull_request(approved=True)
268 pull_request = pr_util.create_pull_request(approved=True)
269 pull_request_id = pull_request.pull_request_id
269 pull_request_id = pull_request.pull_request_id
270 author = pull_request.user_id
270 author = pull_request.user_id
271 repo = pull_request.target_repo.repo_id
271 repo = pull_request.target_repo.repo_id
272
272
273 self.app.post(
273 self.app.post(
274 route_path('pullrequest_comment_create',
274 route_path('pullrequest_comment_create',
275 repo_name=pull_request.target_repo.scm_instance().name,
275 repo_name=pull_request.target_repo.scm_instance().name,
276 pull_request_id=pull_request_id),
276 pull_request_id=pull_request_id),
277 params={
277 params={
278 'close_pull_request': '1',
278 'close_pull_request': '1',
279 'text': 'Closing a PR',
279 'text': 'Closing a PR',
280 'csrf_token': csrf_token},
280 'csrf_token': csrf_token},
281 extra_environ=xhr_header,)
281 extra_environ=xhr_header,)
282
282
283 journal = UserLog.query()\
283 journal = UserLog.query()\
284 .filter(UserLog.user_id == author)\
284 .filter(UserLog.user_id == author)\
285 .filter(UserLog.repository_id == repo) \
285 .filter(UserLog.repository_id == repo) \
286 .order_by('user_log_id') \
286 .order_by('user_log_id') \
287 .all()
287 .all()
288 assert journal[-1].action == 'repo.pull_request.close'
288 assert journal[-1].action == 'repo.pull_request.close'
289
289
290 pull_request = PullRequest.get(pull_request_id)
290 pull_request = PullRequest.get(pull_request_id)
291 assert pull_request.is_closed()
291 assert pull_request.is_closed()
292
292
293 status = ChangesetStatusModel().get_status(
293 status = ChangesetStatusModel().get_status(
294 pull_request.source_repo, pull_request=pull_request)
294 pull_request.source_repo, pull_request=pull_request)
295 assert status == ChangesetStatus.STATUS_APPROVED
295 assert status == ChangesetStatus.STATUS_APPROVED
296 comments = ChangesetComment().query() \
296 comments = ChangesetComment().query() \
297 .filter(ChangesetComment.pull_request == pull_request) \
297 .filter(ChangesetComment.pull_request == pull_request) \
298 .order_by(ChangesetComment.comment_id.asc())\
298 .order_by(ChangesetComment.comment_id.asc())\
299 .all()
299 .all()
300 assert comments[-1].text == 'Closing a PR'
300 assert comments[-1].text == 'Closing a PR'
301
301
302 def test_comment_force_close_pull_request_rejected(
302 def test_comment_force_close_pull_request_rejected(
303 self, pr_util, csrf_token, xhr_header):
303 self, pr_util, csrf_token, xhr_header):
304 pull_request = pr_util.create_pull_request()
304 pull_request = pr_util.create_pull_request()
305 pull_request_id = pull_request.pull_request_id
305 pull_request_id = pull_request.pull_request_id
306 PullRequestModel().update_reviewers(
306 PullRequestModel().update_reviewers(
307 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
307 pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
308 pull_request.author)
308 pull_request.author)
309 author = pull_request.user_id
309 author = pull_request.user_id
310 repo = pull_request.target_repo.repo_id
310 repo = pull_request.target_repo.repo_id
311
311
312 self.app.post(
312 self.app.post(
313 route_path('pullrequest_comment_create',
313 route_path('pullrequest_comment_create',
314 repo_name=pull_request.target_repo.scm_instance().name,
314 repo_name=pull_request.target_repo.scm_instance().name,
315 pull_request_id=pull_request_id),
315 pull_request_id=pull_request_id),
316 params={
316 params={
317 'close_pull_request': '1',
317 'close_pull_request': '1',
318 'csrf_token': csrf_token},
318 'csrf_token': csrf_token},
319 extra_environ=xhr_header)
319 extra_environ=xhr_header)
320
320
321 pull_request = PullRequest.get(pull_request_id)
321 pull_request = PullRequest.get(pull_request_id)
322
322
323 journal = UserLog.query()\
323 journal = UserLog.query()\
324 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
324 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
325 .order_by('user_log_id') \
325 .order_by('user_log_id') \
326 .all()
326 .all()
327 assert journal[-1].action == 'repo.pull_request.close'
327 assert journal[-1].action == 'repo.pull_request.close'
328
328
329 # check only the latest status, not the review status
329 # check only the latest status, not the review status
330 status = ChangesetStatusModel().get_status(
330 status = ChangesetStatusModel().get_status(
331 pull_request.source_repo, pull_request=pull_request)
331 pull_request.source_repo, pull_request=pull_request)
332 assert status == ChangesetStatus.STATUS_REJECTED
332 assert status == ChangesetStatus.STATUS_REJECTED
333
333
334 def test_comment_and_close_pull_request(
334 def test_comment_and_close_pull_request(
335 self, pr_util, csrf_token, xhr_header):
335 self, pr_util, csrf_token, xhr_header):
336 pull_request = pr_util.create_pull_request()
336 pull_request = pr_util.create_pull_request()
337 pull_request_id = pull_request.pull_request_id
337 pull_request_id = pull_request.pull_request_id
338
338
339 response = self.app.post(
339 response = self.app.post(
340 route_path('pullrequest_comment_create',
340 route_path('pullrequest_comment_create',
341 repo_name=pull_request.target_repo.scm_instance().name,
341 repo_name=pull_request.target_repo.scm_instance().name,
342 pull_request_id=pull_request.pull_request_id),
342 pull_request_id=pull_request.pull_request_id),
343 params={
343 params={
344 'close_pull_request': 'true',
344 'close_pull_request': 'true',
345 'csrf_token': csrf_token},
345 'csrf_token': csrf_token},
346 extra_environ=xhr_header)
346 extra_environ=xhr_header)
347
347
348 assert response.json
348 assert response.json
349
349
350 pull_request = PullRequest.get(pull_request_id)
350 pull_request = PullRequest.get(pull_request_id)
351 assert pull_request.is_closed()
351 assert pull_request.is_closed()
352
352
353 # check only the latest status, not the review status
353 # check only the latest status, not the review status
354 status = ChangesetStatusModel().get_status(
354 status = ChangesetStatusModel().get_status(
355 pull_request.source_repo, pull_request=pull_request)
355 pull_request.source_repo, pull_request=pull_request)
356 assert status == ChangesetStatus.STATUS_REJECTED
356 assert status == ChangesetStatus.STATUS_REJECTED
357
357
358 def test_create_pull_request(self, backend, csrf_token):
358 def test_create_pull_request(self, backend, csrf_token):
359 commits = [
359 commits = [
360 {'message': 'ancestor'},
360 {'message': 'ancestor'},
361 {'message': 'change'},
361 {'message': 'change'},
362 {'message': 'change2'},
362 {'message': 'change2'},
363 ]
363 ]
364 commit_ids = backend.create_master_repo(commits)
364 commit_ids = backend.create_master_repo(commits)
365 target = backend.create_repo(heads=['ancestor'])
365 target = backend.create_repo(heads=['ancestor'])
366 source = backend.create_repo(heads=['change2'])
366 source = backend.create_repo(heads=['change2'])
367
367
368 response = self.app.post(
368 response = self.app.post(
369 route_path('pullrequest_create', repo_name=source.repo_name),
369 route_path('pullrequest_create', repo_name=source.repo_name),
370 [
370 [
371 ('source_repo', source.repo_name),
371 ('source_repo', source.repo_name),
372 ('source_ref', 'branch:default:' + commit_ids['change2']),
372 ('source_ref', 'branch:default:' + commit_ids['change2']),
373 ('target_repo', target.repo_name),
373 ('target_repo', target.repo_name),
374 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
374 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
375 ('common_ancestor', commit_ids['ancestor']),
375 ('common_ancestor', commit_ids['ancestor']),
376 ('pullrequest_title', 'Title'),
376 ('pullrequest_title', 'Title'),
377 ('pullrequest_desc', 'Description'),
377 ('pullrequest_desc', 'Description'),
378 ('description_renderer', 'markdown'),
378 ('description_renderer', 'markdown'),
379 ('__start__', 'review_members:sequence'),
379 ('__start__', 'review_members:sequence'),
380 ('__start__', 'reviewer:mapping'),
380 ('__start__', 'reviewer:mapping'),
381 ('user_id', '1'),
381 ('user_id', '1'),
382 ('__start__', 'reasons:sequence'),
382 ('__start__', 'reasons:sequence'),
383 ('reason', 'Some reason'),
383 ('reason', 'Some reason'),
384 ('__end__', 'reasons:sequence'),
384 ('__end__', 'reasons:sequence'),
385 ('__start__', 'rules:sequence'),
385 ('__start__', 'rules:sequence'),
386 ('__end__', 'rules:sequence'),
386 ('__end__', 'rules:sequence'),
387 ('mandatory', 'False'),
387 ('mandatory', 'False'),
388 ('__end__', 'reviewer:mapping'),
388 ('__end__', 'reviewer:mapping'),
389 ('__end__', 'review_members:sequence'),
389 ('__end__', 'review_members:sequence'),
390 ('__start__', 'revisions:sequence'),
390 ('__start__', 'revisions:sequence'),
391 ('revisions', commit_ids['change']),
391 ('revisions', commit_ids['change']),
392 ('revisions', commit_ids['change2']),
392 ('revisions', commit_ids['change2']),
393 ('__end__', 'revisions:sequence'),
393 ('__end__', 'revisions:sequence'),
394 ('user', ''),
394 ('user', ''),
395 ('csrf_token', csrf_token),
395 ('csrf_token', csrf_token),
396 ],
396 ],
397 status=302)
397 status=302)
398
398
399 location = response.headers['Location']
399 location = response.headers['Location']
400 pull_request_id = location.rsplit('/', 1)[1]
400 pull_request_id = location.rsplit('/', 1)[1]
401 assert pull_request_id != 'new'
401 assert pull_request_id != 'new'
402 pull_request = PullRequest.get(int(pull_request_id))
402 pull_request = PullRequest.get(int(pull_request_id))
403
403
404 # check that we have now both revisions
404 # check that we have now both revisions
405 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
405 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
406 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
406 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
407 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
407 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
408 assert pull_request.target_ref == expected_target_ref
408 assert pull_request.target_ref == expected_target_ref
409
409
410 def test_reviewer_notifications(self, backend, csrf_token):
410 def test_reviewer_notifications(self, backend, csrf_token):
411 # We have to use the app.post for this test so it will create the
411 # We have to use the app.post for this test so it will create the
412 # notifications properly with the new PR
412 # notifications properly with the new PR
413 commits = [
413 commits = [
414 {'message': 'ancestor',
414 {'message': 'ancestor',
415 'added': [FileNode('file_A', content='content_of_ancestor')]},
415 'added': [FileNode('file_A', content='content_of_ancestor')]},
416 {'message': 'change',
416 {'message': 'change',
417 'added': [FileNode('file_a', content='content_of_change')]},
417 'added': [FileNode('file_a', content='content_of_change')]},
418 {'message': 'change-child'},
418 {'message': 'change-child'},
419 {'message': 'ancestor-child', 'parents': ['ancestor'],
419 {'message': 'ancestor-child', 'parents': ['ancestor'],
420 'added': [
420 'added': [
421 FileNode('file_B', content='content_of_ancestor_child')]},
421 FileNode('file_B', content='content_of_ancestor_child')]},
422 {'message': 'ancestor-child-2'},
422 {'message': 'ancestor-child-2'},
423 ]
423 ]
424 commit_ids = backend.create_master_repo(commits)
424 commit_ids = backend.create_master_repo(commits)
425 target = backend.create_repo(heads=['ancestor-child'])
425 target = backend.create_repo(heads=['ancestor-child'])
426 source = backend.create_repo(heads=['change'])
426 source = backend.create_repo(heads=['change'])
427
427
428 response = self.app.post(
428 response = self.app.post(
429 route_path('pullrequest_create', repo_name=source.repo_name),
429 route_path('pullrequest_create', repo_name=source.repo_name),
430 [
430 [
431 ('source_repo', source.repo_name),
431 ('source_repo', source.repo_name),
432 ('source_ref', 'branch:default:' + commit_ids['change']),
432 ('source_ref', 'branch:default:' + commit_ids['change']),
433 ('target_repo', target.repo_name),
433 ('target_repo', target.repo_name),
434 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
434 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
435 ('common_ancestor', commit_ids['ancestor']),
435 ('common_ancestor', commit_ids['ancestor']),
436 ('pullrequest_title', 'Title'),
436 ('pullrequest_title', 'Title'),
437 ('pullrequest_desc', 'Description'),
437 ('pullrequest_desc', 'Description'),
438 ('description_renderer', 'markdown'),
438 ('description_renderer', 'markdown'),
439 ('__start__', 'review_members:sequence'),
439 ('__start__', 'review_members:sequence'),
440 ('__start__', 'reviewer:mapping'),
440 ('__start__', 'reviewer:mapping'),
441 ('user_id', '2'),
441 ('user_id', '2'),
442 ('__start__', 'reasons:sequence'),
442 ('__start__', 'reasons:sequence'),
443 ('reason', 'Some reason'),
443 ('reason', 'Some reason'),
444 ('__end__', 'reasons:sequence'),
444 ('__end__', 'reasons:sequence'),
445 ('__start__', 'rules:sequence'),
445 ('__start__', 'rules:sequence'),
446 ('__end__', 'rules:sequence'),
446 ('__end__', 'rules:sequence'),
447 ('mandatory', 'False'),
447 ('mandatory', 'False'),
448 ('__end__', 'reviewer:mapping'),
448 ('__end__', 'reviewer:mapping'),
449 ('__end__', 'review_members:sequence'),
449 ('__end__', 'review_members:sequence'),
450 ('__start__', 'revisions:sequence'),
450 ('__start__', 'revisions:sequence'),
451 ('revisions', commit_ids['change']),
451 ('revisions', commit_ids['change']),
452 ('__end__', 'revisions:sequence'),
452 ('__end__', 'revisions:sequence'),
453 ('user', ''),
453 ('user', ''),
454 ('csrf_token', csrf_token),
454 ('csrf_token', csrf_token),
455 ],
455 ],
456 status=302)
456 status=302)
457
457
458 location = response.headers['Location']
458 location = response.headers['Location']
459
459
460 pull_request_id = location.rsplit('/', 1)[1]
460 pull_request_id = location.rsplit('/', 1)[1]
461 assert pull_request_id != 'new'
461 assert pull_request_id != 'new'
462 pull_request = PullRequest.get(int(pull_request_id))
462 pull_request = PullRequest.get(int(pull_request_id))
463
463
464 # Check that a notification was made
464 # Check that a notification was made
465 notifications = Notification.query()\
465 notifications = Notification.query()\
466 .filter(Notification.created_by == pull_request.author.user_id,
466 .filter(Notification.created_by == pull_request.author.user_id,
467 Notification.type_ == Notification.TYPE_PULL_REQUEST,
467 Notification.type_ == Notification.TYPE_PULL_REQUEST,
468 Notification.subject.contains(
468 Notification.subject.contains(
469 "wants you to review pull request #%s" % pull_request_id))
469 "wants you to review pull request #%s" % pull_request_id))
470 assert len(notifications.all()) == 1
470 assert len(notifications.all()) == 1
471
471
472 # Change reviewers and check that a notification was made
472 # Change reviewers and check that a notification was made
473 PullRequestModel().update_reviewers(
473 PullRequestModel().update_reviewers(
474 pull_request.pull_request_id, [(1, [], False, [])],
474 pull_request.pull_request_id, [(1, [], False, [])],
475 pull_request.author)
475 pull_request.author)
476 assert len(notifications.all()) == 2
476 assert len(notifications.all()) == 2
477
477
478 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
478 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
479 csrf_token):
479 csrf_token):
480 commits = [
480 commits = [
481 {'message': 'ancestor',
481 {'message': 'ancestor',
482 'added': [FileNode('file_A', content='content_of_ancestor')]},
482 'added': [FileNode('file_A', content='content_of_ancestor')]},
483 {'message': 'change',
483 {'message': 'change',
484 'added': [FileNode('file_a', content='content_of_change')]},
484 'added': [FileNode('file_a', content='content_of_change')]},
485 {'message': 'change-child'},
485 {'message': 'change-child'},
486 {'message': 'ancestor-child', 'parents': ['ancestor'],
486 {'message': 'ancestor-child', 'parents': ['ancestor'],
487 'added': [
487 'added': [
488 FileNode('file_B', content='content_of_ancestor_child')]},
488 FileNode('file_B', content='content_of_ancestor_child')]},
489 {'message': 'ancestor-child-2'},
489 {'message': 'ancestor-child-2'},
490 ]
490 ]
491 commit_ids = backend.create_master_repo(commits)
491 commit_ids = backend.create_master_repo(commits)
492 target = backend.create_repo(heads=['ancestor-child'])
492 target = backend.create_repo(heads=['ancestor-child'])
493 source = backend.create_repo(heads=['change'])
493 source = backend.create_repo(heads=['change'])
494
494
495 response = self.app.post(
495 response = self.app.post(
496 route_path('pullrequest_create', repo_name=source.repo_name),
496 route_path('pullrequest_create', repo_name=source.repo_name),
497 [
497 [
498 ('source_repo', source.repo_name),
498 ('source_repo', source.repo_name),
499 ('source_ref', 'branch:default:' + commit_ids['change']),
499 ('source_ref', 'branch:default:' + commit_ids['change']),
500 ('target_repo', target.repo_name),
500 ('target_repo', target.repo_name),
501 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
501 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
502 ('common_ancestor', commit_ids['ancestor']),
502 ('common_ancestor', commit_ids['ancestor']),
503 ('pullrequest_title', 'Title'),
503 ('pullrequest_title', 'Title'),
504 ('pullrequest_desc', 'Description'),
504 ('pullrequest_desc', 'Description'),
505 ('description_renderer', 'markdown'),
505 ('description_renderer', 'markdown'),
506 ('__start__', 'review_members:sequence'),
506 ('__start__', 'review_members:sequence'),
507 ('__start__', 'reviewer:mapping'),
507 ('__start__', 'reviewer:mapping'),
508 ('user_id', '1'),
508 ('user_id', '1'),
509 ('__start__', 'reasons:sequence'),
509 ('__start__', 'reasons:sequence'),
510 ('reason', 'Some reason'),
510 ('reason', 'Some reason'),
511 ('__end__', 'reasons:sequence'),
511 ('__end__', 'reasons:sequence'),
512 ('__start__', 'rules:sequence'),
512 ('__start__', 'rules:sequence'),
513 ('__end__', 'rules:sequence'),
513 ('__end__', 'rules:sequence'),
514 ('mandatory', 'False'),
514 ('mandatory', 'False'),
515 ('__end__', 'reviewer:mapping'),
515 ('__end__', 'reviewer:mapping'),
516 ('__end__', 'review_members:sequence'),
516 ('__end__', 'review_members:sequence'),
517 ('__start__', 'revisions:sequence'),
517 ('__start__', 'revisions:sequence'),
518 ('revisions', commit_ids['change']),
518 ('revisions', commit_ids['change']),
519 ('__end__', 'revisions:sequence'),
519 ('__end__', 'revisions:sequence'),
520 ('user', ''),
520 ('user', ''),
521 ('csrf_token', csrf_token),
521 ('csrf_token', csrf_token),
522 ],
522 ],
523 status=302)
523 status=302)
524
524
525 location = response.headers['Location']
525 location = response.headers['Location']
526
526
527 pull_request_id = location.rsplit('/', 1)[1]
527 pull_request_id = location.rsplit('/', 1)[1]
528 assert pull_request_id != 'new'
528 assert pull_request_id != 'new'
529 pull_request = PullRequest.get(int(pull_request_id))
529 pull_request = PullRequest.get(int(pull_request_id))
530
530
531 # target_ref has to point to the ancestor's commit_id in order to
531 # target_ref has to point to the ancestor's commit_id in order to
532 # show the correct diff
532 # show the correct diff
533 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
533 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
534 assert pull_request.target_ref == expected_target_ref
534 assert pull_request.target_ref == expected_target_ref
535
535
536 # Check generated diff contents
536 # Check generated diff contents
537 response = response.follow()
537 response = response.follow()
538 assert 'content_of_ancestor' not in response.body
538 assert 'content_of_ancestor' not in response.body
539 assert 'content_of_ancestor-child' not in response.body
539 assert 'content_of_ancestor-child' not in response.body
540 assert 'content_of_change' in response.body
540 assert 'content_of_change' in response.body
541
541
542 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
542 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
543 # Clear any previous calls to rcextensions
543 # Clear any previous calls to rcextensions
544 rhodecode.EXTENSIONS.calls.clear()
544 rhodecode.EXTENSIONS.calls.clear()
545
545
546 pull_request = pr_util.create_pull_request(
546 pull_request = pr_util.create_pull_request(
547 approved=True, mergeable=True)
547 approved=True, mergeable=True)
548 pull_request_id = pull_request.pull_request_id
548 pull_request_id = pull_request.pull_request_id
549 repo_name = pull_request.target_repo.scm_instance().name,
549 repo_name = pull_request.target_repo.scm_instance().name,
550
550
551 response = self.app.post(
551 response = self.app.post(
552 route_path('pullrequest_merge',
552 route_path('pullrequest_merge',
553 repo_name=str(repo_name[0]),
553 repo_name=str(repo_name[0]),
554 pull_request_id=pull_request_id),
554 pull_request_id=pull_request_id),
555 params={'csrf_token': csrf_token}).follow()
555 params={'csrf_token': csrf_token}).follow()
556
556
557 pull_request = PullRequest.get(pull_request_id)
557 pull_request = PullRequest.get(pull_request_id)
558
558
559 assert response.status_int == 200
559 assert response.status_int == 200
560 assert pull_request.is_closed()
560 assert pull_request.is_closed()
561 assert_pull_request_status(
561 assert_pull_request_status(
562 pull_request, ChangesetStatus.STATUS_APPROVED)
562 pull_request, ChangesetStatus.STATUS_APPROVED)
563
563
564 # Check the relevant log entries were added
564 # Check the relevant log entries were added
565 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
565 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
566 actions = [log.action for log in user_logs]
566 actions = [log.action for log in user_logs]
567 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
567 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
568 expected_actions = [
568 expected_actions = [
569 u'repo.pull_request.close',
569 u'repo.pull_request.close',
570 u'repo.pull_request.merge',
570 u'repo.pull_request.merge',
571 u'repo.pull_request.comment.create'
571 u'repo.pull_request.comment.create'
572 ]
572 ]
573 assert actions == expected_actions
573 assert actions == expected_actions
574
574
575 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
575 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
576 actions = [log for log in user_logs]
576 actions = [log for log in user_logs]
577 assert actions[-1].action == 'user.push'
577 assert actions[-1].action == 'user.push'
578 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
578 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
579
579
580 # Check post_push rcextension was really executed
580 # Check post_push rcextension was really executed
581 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
581 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
582 assert len(push_calls) == 1
582 assert len(push_calls) == 1
583 unused_last_call_args, last_call_kwargs = push_calls[0]
583 unused_last_call_args, last_call_kwargs = push_calls[0]
584 assert last_call_kwargs['action'] == 'push'
584 assert last_call_kwargs['action'] == 'push'
585 assert last_call_kwargs['commit_ids'] == pr_commit_ids
585 assert last_call_kwargs['commit_ids'] == pr_commit_ids
586
586
587 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
587 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
588 pull_request = pr_util.create_pull_request(mergeable=False)
588 pull_request = pr_util.create_pull_request(mergeable=False)
589 pull_request_id = pull_request.pull_request_id
589 pull_request_id = pull_request.pull_request_id
590 pull_request = PullRequest.get(pull_request_id)
590 pull_request = PullRequest.get(pull_request_id)
591
591
592 response = self.app.post(
592 response = self.app.post(
593 route_path('pullrequest_merge',
593 route_path('pullrequest_merge',
594 repo_name=pull_request.target_repo.scm_instance().name,
594 repo_name=pull_request.target_repo.scm_instance().name,
595 pull_request_id=pull_request.pull_request_id),
595 pull_request_id=pull_request.pull_request_id),
596 params={'csrf_token': csrf_token}).follow()
596 params={'csrf_token': csrf_token}).follow()
597
597
598 assert response.status_int == 200
598 assert response.status_int == 200
599 response.mustcontain(
599 response.mustcontain(
600 'Merge is not currently possible because of below failed checks.')
600 'Merge is not currently possible because of below failed checks.')
601 response.mustcontain('Server-side pull request merging is disabled.')
601 response.mustcontain('Server-side pull request merging is disabled.')
602
602
603 @pytest.mark.skip_backends('svn')
603 @pytest.mark.skip_backends('svn')
604 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
604 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
605 pull_request = pr_util.create_pull_request(mergeable=True)
605 pull_request = pr_util.create_pull_request(mergeable=True)
606 pull_request_id = pull_request.pull_request_id
606 pull_request_id = pull_request.pull_request_id
607 repo_name = pull_request.target_repo.scm_instance().name
607 repo_name = pull_request.target_repo.scm_instance().name
608
608
609 response = self.app.post(
609 response = self.app.post(
610 route_path('pullrequest_merge',
610 route_path('pullrequest_merge',
611 repo_name=repo_name,
611 repo_name=repo_name,
612 pull_request_id=pull_request_id),
612 pull_request_id=pull_request_id),
613 params={'csrf_token': csrf_token}).follow()
613 params={'csrf_token': csrf_token}).follow()
614
614
615 assert response.status_int == 200
615 assert response.status_int == 200
616
616
617 response.mustcontain(
617 response.mustcontain(
618 'Merge is not currently possible because of below failed checks.')
618 'Merge is not currently possible because of below failed checks.')
619 response.mustcontain('Pull request reviewer approval is pending.')
619 response.mustcontain('Pull request reviewer approval is pending.')
620
620
621 def test_merge_pull_request_renders_failure_reason(
621 def test_merge_pull_request_renders_failure_reason(
622 self, user_regular, csrf_token, pr_util):
622 self, user_regular, csrf_token, pr_util):
623 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
623 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
624 pull_request_id = pull_request.pull_request_id
624 pull_request_id = pull_request.pull_request_id
625 repo_name = pull_request.target_repo.scm_instance().name
625 repo_name = pull_request.target_repo.scm_instance().name
626
626
627 model_patcher = mock.patch.multiple(
627 model_patcher = mock.patch.multiple(
628 PullRequestModel,
628 PullRequestModel,
629 merge_repo=mock.Mock(return_value=MergeResponse(
629 merge_repo=mock.Mock(return_value=MergeResponse(
630 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
630 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
631 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
631 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
632
632
633 with model_patcher:
633 with model_patcher:
634 response = self.app.post(
634 response = self.app.post(
635 route_path('pullrequest_merge',
635 route_path('pullrequest_merge',
636 repo_name=repo_name,
636 repo_name=repo_name,
637 pull_request_id=pull_request_id),
637 pull_request_id=pull_request_id),
638 params={'csrf_token': csrf_token}, status=302)
638 params={'csrf_token': csrf_token}, status=302)
639
639
640 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
640 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
641 MergeFailureReason.PUSH_FAILED])
641 MergeFailureReason.PUSH_FAILED])
642
642
643 def test_update_source_revision(self, backend, csrf_token):
643 def test_update_source_revision(self, backend, csrf_token):
644 commits = [
644 commits = [
645 {'message': 'ancestor'},
645 {'message': 'ancestor'},
646 {'message': 'change'},
646 {'message': 'change'},
647 {'message': 'change-2'},
647 {'message': 'change-2'},
648 ]
648 ]
649 commit_ids = backend.create_master_repo(commits)
649 commit_ids = backend.create_master_repo(commits)
650 target = backend.create_repo(heads=['ancestor'])
650 target = backend.create_repo(heads=['ancestor'])
651 source = backend.create_repo(heads=['change'])
651 source = backend.create_repo(heads=['change'])
652
652
653 # create pr from a in source to A in target
653 # create pr from a in source to A in target
654 pull_request = PullRequest()
654 pull_request = PullRequest()
655 pull_request.source_repo = source
655 pull_request.source_repo = source
656 # TODO: johbo: Make sure that we write the source ref this way!
656 # TODO: johbo: Make sure that we write the source ref this way!
657 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
657 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
658 branch=backend.default_branch_name, commit_id=commit_ids['change'])
658 branch=backend.default_branch_name, commit_id=commit_ids['change'])
659 pull_request.target_repo = target
659 pull_request.target_repo = target
660
660
661 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
661 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
662 branch=backend.default_branch_name,
662 branch=backend.default_branch_name,
663 commit_id=commit_ids['ancestor'])
663 commit_id=commit_ids['ancestor'])
664 pull_request.revisions = [commit_ids['change']]
664 pull_request.revisions = [commit_ids['change']]
665 pull_request.title = u"Test"
665 pull_request.title = u"Test"
666 pull_request.description = u"Description"
666 pull_request.description = u"Description"
667 pull_request.author = UserModel().get_by_username(
667 pull_request.author = UserModel().get_by_username(
668 TEST_USER_ADMIN_LOGIN)
668 TEST_USER_ADMIN_LOGIN)
669 Session().add(pull_request)
669 Session().add(pull_request)
670 Session().commit()
670 Session().commit()
671 pull_request_id = pull_request.pull_request_id
671 pull_request_id = pull_request.pull_request_id
672
672
673 # source has ancestor - change - change-2
673 # source has ancestor - change - change-2
674 backend.pull_heads(source, heads=['change-2'])
674 backend.pull_heads(source, heads=['change-2'])
675
675
676 # update PR
676 # update PR
677 self.app.post(
677 self.app.post(
678 route_path('pullrequest_update',
678 route_path('pullrequest_update',
679 repo_name=target.repo_name,
679 repo_name=target.repo_name,
680 pull_request_id=pull_request_id),
680 pull_request_id=pull_request_id),
681 params={'update_commits': 'true',
681 params={'update_commits': 'true',
682 'csrf_token': csrf_token})
682 'csrf_token': csrf_token})
683
683
684 # check that we have now both revisions
684 # check that we have now both revisions
685 pull_request = PullRequest.get(pull_request_id)
685 pull_request = PullRequest.get(pull_request_id)
686 assert pull_request.revisions == [
686 assert pull_request.revisions == [
687 commit_ids['change-2'], commit_ids['change']]
687 commit_ids['change-2'], commit_ids['change']]
688
688
689 # TODO: johbo: this should be a test on its own
689 # TODO: johbo: this should be a test on its own
690 response = self.app.get(route_path(
690 response = self.app.get(route_path(
691 'pullrequest_new',
691 'pullrequest_new',
692 repo_name=target.repo_name))
692 repo_name=target.repo_name))
693 assert response.status_int == 200
693 assert response.status_int == 200
694 assert 'Pull request updated to' in response.body
694 assert 'Pull request updated to' in response.body
695 assert 'with 1 added, 0 removed commits.' in response.body
695 assert 'with 1 added, 0 removed commits.' in response.body
696
696
697 def test_update_target_revision(self, backend, csrf_token):
697 def test_update_target_revision(self, backend, csrf_token):
698 commits = [
698 commits = [
699 {'message': 'ancestor'},
699 {'message': 'ancestor'},
700 {'message': 'change'},
700 {'message': 'change'},
701 {'message': 'ancestor-new', 'parents': ['ancestor']},
701 {'message': 'ancestor-new', 'parents': ['ancestor']},
702 {'message': 'change-rebased'},
702 {'message': 'change-rebased'},
703 ]
703 ]
704 commit_ids = backend.create_master_repo(commits)
704 commit_ids = backend.create_master_repo(commits)
705 target = backend.create_repo(heads=['ancestor'])
705 target = backend.create_repo(heads=['ancestor'])
706 source = backend.create_repo(heads=['change'])
706 source = backend.create_repo(heads=['change'])
707
707
708 # create pr from a in source to A in target
708 # create pr from a in source to A in target
709 pull_request = PullRequest()
709 pull_request = PullRequest()
710 pull_request.source_repo = source
710 pull_request.source_repo = source
711 # TODO: johbo: Make sure that we write the source ref this way!
711 # TODO: johbo: Make sure that we write the source ref this way!
712 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
712 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
713 branch=backend.default_branch_name, commit_id=commit_ids['change'])
713 branch=backend.default_branch_name, commit_id=commit_ids['change'])
714 pull_request.target_repo = target
714 pull_request.target_repo = target
715 # TODO: johbo: Target ref should be branch based, since tip can jump
715 # TODO: johbo: Target ref should be branch based, since tip can jump
716 # from branch to branch
716 # from branch to branch
717 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
717 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
718 branch=backend.default_branch_name,
718 branch=backend.default_branch_name,
719 commit_id=commit_ids['ancestor'])
719 commit_id=commit_ids['ancestor'])
720 pull_request.revisions = [commit_ids['change']]
720 pull_request.revisions = [commit_ids['change']]
721 pull_request.title = u"Test"
721 pull_request.title = u"Test"
722 pull_request.description = u"Description"
722 pull_request.description = u"Description"
723 pull_request.author = UserModel().get_by_username(
723 pull_request.author = UserModel().get_by_username(
724 TEST_USER_ADMIN_LOGIN)
724 TEST_USER_ADMIN_LOGIN)
725 Session().add(pull_request)
725 Session().add(pull_request)
726 Session().commit()
726 Session().commit()
727 pull_request_id = pull_request.pull_request_id
727 pull_request_id = pull_request.pull_request_id
728
728
729 # target has ancestor - ancestor-new
729 # target has ancestor - ancestor-new
730 # source has ancestor - ancestor-new - change-rebased
730 # source has ancestor - ancestor-new - change-rebased
731 backend.pull_heads(target, heads=['ancestor-new'])
731 backend.pull_heads(target, heads=['ancestor-new'])
732 backend.pull_heads(source, heads=['change-rebased'])
732 backend.pull_heads(source, heads=['change-rebased'])
733
733
734 # update PR
734 # update PR
735 self.app.post(
735 self.app.post(
736 route_path('pullrequest_update',
736 route_path('pullrequest_update',
737 repo_name=target.repo_name,
737 repo_name=target.repo_name,
738 pull_request_id=pull_request_id),
738 pull_request_id=pull_request_id),
739 params={'update_commits': 'true',
739 params={'update_commits': 'true',
740 'csrf_token': csrf_token},
740 'csrf_token': csrf_token},
741 status=200)
741 status=200)
742
742
743 # check that we have now both revisions
743 # check that we have now both revisions
744 pull_request = PullRequest.get(pull_request_id)
744 pull_request = PullRequest.get(pull_request_id)
745 assert pull_request.revisions == [commit_ids['change-rebased']]
745 assert pull_request.revisions == [commit_ids['change-rebased']]
746 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
746 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
747 branch=backend.default_branch_name,
747 branch=backend.default_branch_name,
748 commit_id=commit_ids['ancestor-new'])
748 commit_id=commit_ids['ancestor-new'])
749
749
750 # TODO: johbo: This should be a test on its own
750 # TODO: johbo: This should be a test on its own
751 response = self.app.get(route_path(
751 response = self.app.get(route_path(
752 'pullrequest_new',
752 'pullrequest_new',
753 repo_name=target.repo_name))
753 repo_name=target.repo_name))
754 assert response.status_int == 200
754 assert response.status_int == 200
755 assert 'Pull request updated to' in response.body
755 assert 'Pull request updated to' in response.body
756 assert 'with 1 added, 1 removed commits.' in response.body
756 assert 'with 1 added, 1 removed commits.' in response.body
757
757
758 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
758 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
759 backend = backend_git
759 backend = backend_git
760 commits = [
760 commits = [
761 {'message': 'master-commit-1'},
761 {'message': 'master-commit-1'},
762 {'message': 'master-commit-2-change-1'},
762 {'message': 'master-commit-2-change-1'},
763 {'message': 'master-commit-3-change-2'},
763 {'message': 'master-commit-3-change-2'},
764
764
765 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
765 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
766 {'message': 'feat-commit-2'},
766 {'message': 'feat-commit-2'},
767 ]
767 ]
768 commit_ids = backend.create_master_repo(commits)
768 commit_ids = backend.create_master_repo(commits)
769 target = backend.create_repo(heads=['master-commit-3-change-2'])
769 target = backend.create_repo(heads=['master-commit-3-change-2'])
770 source = backend.create_repo(heads=['feat-commit-2'])
770 source = backend.create_repo(heads=['feat-commit-2'])
771
771
772 # create pr from a in source to A in target
772 # create pr from a in source to A in target
773 pull_request = PullRequest()
773 pull_request = PullRequest()
774 pull_request.source_repo = source
774 pull_request.source_repo = source
775 # TODO: johbo: Make sure that we write the source ref this way!
775 # TODO: johbo: Make sure that we write the source ref this way!
776 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
776 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
777 branch=backend.default_branch_name,
777 branch=backend.default_branch_name,
778 commit_id=commit_ids['master-commit-3-change-2'])
778 commit_id=commit_ids['master-commit-3-change-2'])
779
779
780 pull_request.target_repo = target
780 pull_request.target_repo = target
781 # TODO: johbo: Target ref should be branch based, since tip can jump
781 # TODO: johbo: Target ref should be branch based, since tip can jump
782 # from branch to branch
782 # from branch to branch
783 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
783 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
784 branch=backend.default_branch_name,
784 branch=backend.default_branch_name,
785 commit_id=commit_ids['feat-commit-2'])
785 commit_id=commit_ids['feat-commit-2'])
786
786
787 pull_request.revisions = [
787 pull_request.revisions = [
788 commit_ids['feat-commit-1'],
788 commit_ids['feat-commit-1'],
789 commit_ids['feat-commit-2']
789 commit_ids['feat-commit-2']
790 ]
790 ]
791 pull_request.title = u"Test"
791 pull_request.title = u"Test"
792 pull_request.description = u"Description"
792 pull_request.description = u"Description"
793 pull_request.author = UserModel().get_by_username(
793 pull_request.author = UserModel().get_by_username(
794 TEST_USER_ADMIN_LOGIN)
794 TEST_USER_ADMIN_LOGIN)
795 Session().add(pull_request)
795 Session().add(pull_request)
796 Session().commit()
796 Session().commit()
797 pull_request_id = pull_request.pull_request_id
797 pull_request_id = pull_request.pull_request_id
798
798
799 # PR is created, now we simulate a force-push into target,
799 # PR is created, now we simulate a force-push into target,
800 # that drops a 2 last commits
800 # that drops a 2 last commits
801 vcsrepo = target.scm_instance()
801 vcsrepo = target.scm_instance()
802 vcsrepo.config.clear_section('hooks')
802 vcsrepo.config.clear_section('hooks')
803 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
803 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
804
804
805 # update PR
805 # update PR
806 self.app.post(
806 self.app.post(
807 route_path('pullrequest_update',
807 route_path('pullrequest_update',
808 repo_name=target.repo_name,
808 repo_name=target.repo_name,
809 pull_request_id=pull_request_id),
809 pull_request_id=pull_request_id),
810 params={'update_commits': 'true',
810 params={'update_commits': 'true',
811 'csrf_token': csrf_token},
811 'csrf_token': csrf_token},
812 status=200)
812 status=200)
813
813
814 response = self.app.get(route_path(
814 response = self.app.get(route_path(
815 'pullrequest_new',
815 'pullrequest_new',
816 repo_name=target.repo_name))
816 repo_name=target.repo_name))
817 assert response.status_int == 200
817 assert response.status_int == 200
818 response.mustcontain('Pull request updated to')
818 response.mustcontain('Pull request updated to')
819 response.mustcontain('with 0 added, 0 removed commits.')
819 response.mustcontain('with 0 added, 0 removed commits.')
820
820
821 def test_update_of_ancestor_reference(self, backend, csrf_token):
821 def test_update_of_ancestor_reference(self, backend, csrf_token):
822 commits = [
822 commits = [
823 {'message': 'ancestor'},
823 {'message': 'ancestor'},
824 {'message': 'change'},
824 {'message': 'change'},
825 {'message': 'change-2'},
825 {'message': 'change-2'},
826 {'message': 'ancestor-new', 'parents': ['ancestor']},
826 {'message': 'ancestor-new', 'parents': ['ancestor']},
827 {'message': 'change-rebased'},
827 {'message': 'change-rebased'},
828 ]
828 ]
829 commit_ids = backend.create_master_repo(commits)
829 commit_ids = backend.create_master_repo(commits)
830 target = backend.create_repo(heads=['ancestor'])
830 target = backend.create_repo(heads=['ancestor'])
831 source = backend.create_repo(heads=['change'])
831 source = backend.create_repo(heads=['change'])
832
832
833 # create pr from a in source to A in target
833 # create pr from a in source to A in target
834 pull_request = PullRequest()
834 pull_request = PullRequest()
835 pull_request.source_repo = source
835 pull_request.source_repo = source
836 # TODO: johbo: Make sure that we write the source ref this way!
836 # TODO: johbo: Make sure that we write the source ref this way!
837 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
837 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
838 branch=backend.default_branch_name,
838 branch=backend.default_branch_name,
839 commit_id=commit_ids['change'])
839 commit_id=commit_ids['change'])
840 pull_request.target_repo = target
840 pull_request.target_repo = target
841 # TODO: johbo: Target ref should be branch based, since tip can jump
841 # TODO: johbo: Target ref should be branch based, since tip can jump
842 # from branch to branch
842 # from branch to branch
843 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
843 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
844 branch=backend.default_branch_name,
844 branch=backend.default_branch_name,
845 commit_id=commit_ids['ancestor'])
845 commit_id=commit_ids['ancestor'])
846 pull_request.revisions = [commit_ids['change']]
846 pull_request.revisions = [commit_ids['change']]
847 pull_request.title = u"Test"
847 pull_request.title = u"Test"
848 pull_request.description = u"Description"
848 pull_request.description = u"Description"
849 pull_request.author = UserModel().get_by_username(
849 pull_request.author = UserModel().get_by_username(
850 TEST_USER_ADMIN_LOGIN)
850 TEST_USER_ADMIN_LOGIN)
851 Session().add(pull_request)
851 Session().add(pull_request)
852 Session().commit()
852 Session().commit()
853 pull_request_id = pull_request.pull_request_id
853 pull_request_id = pull_request.pull_request_id
854
854
855 # target has ancestor - ancestor-new
855 # target has ancestor - ancestor-new
856 # source has ancestor - ancestor-new - change-rebased
856 # source has ancestor - ancestor-new - change-rebased
857 backend.pull_heads(target, heads=['ancestor-new'])
857 backend.pull_heads(target, heads=['ancestor-new'])
858 backend.pull_heads(source, heads=['change-rebased'])
858 backend.pull_heads(source, heads=['change-rebased'])
859
859
860 # update PR
860 # update PR
861 self.app.post(
861 self.app.post(
862 route_path('pullrequest_update',
862 route_path('pullrequest_update',
863 repo_name=target.repo_name,
863 repo_name=target.repo_name,
864 pull_request_id=pull_request_id),
864 pull_request_id=pull_request_id),
865 params={'update_commits': 'true',
865 params={'update_commits': 'true',
866 'csrf_token': csrf_token},
866 'csrf_token': csrf_token},
867 status=200)
867 status=200)
868
868
869 # Expect the target reference to be updated correctly
869 # Expect the target reference to be updated correctly
870 pull_request = PullRequest.get(pull_request_id)
870 pull_request = PullRequest.get(pull_request_id)
871 assert pull_request.revisions == [commit_ids['change-rebased']]
871 assert pull_request.revisions == [commit_ids['change-rebased']]
872 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
872 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
873 branch=backend.default_branch_name,
873 branch=backend.default_branch_name,
874 commit_id=commit_ids['ancestor-new'])
874 commit_id=commit_ids['ancestor-new'])
875 assert pull_request.target_ref == expected_target_ref
875 assert pull_request.target_ref == expected_target_ref
876
876
877 def test_remove_pull_request_branch(self, backend_git, csrf_token):
877 def test_remove_pull_request_branch(self, backend_git, csrf_token):
878 branch_name = 'development'
878 branch_name = 'development'
879 commits = [
879 commits = [
880 {'message': 'initial-commit'},
880 {'message': 'initial-commit'},
881 {'message': 'old-feature'},
881 {'message': 'old-feature'},
882 {'message': 'new-feature', 'branch': branch_name},
882 {'message': 'new-feature', 'branch': branch_name},
883 ]
883 ]
884 repo = backend_git.create_repo(commits)
884 repo = backend_git.create_repo(commits)
885 commit_ids = backend_git.commit_ids
885 commit_ids = backend_git.commit_ids
886
886
887 pull_request = PullRequest()
887 pull_request = PullRequest()
888 pull_request.source_repo = repo
888 pull_request.source_repo = repo
889 pull_request.target_repo = repo
889 pull_request.target_repo = repo
890 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
890 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
891 branch=branch_name, commit_id=commit_ids['new-feature'])
891 branch=branch_name, commit_id=commit_ids['new-feature'])
892 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
892 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
893 branch=backend_git.default_branch_name,
893 branch=backend_git.default_branch_name,
894 commit_id=commit_ids['old-feature'])
894 commit_id=commit_ids['old-feature'])
895 pull_request.revisions = [commit_ids['new-feature']]
895 pull_request.revisions = [commit_ids['new-feature']]
896 pull_request.title = u"Test"
896 pull_request.title = u"Test"
897 pull_request.description = u"Description"
897 pull_request.description = u"Description"
898 pull_request.author = UserModel().get_by_username(
898 pull_request.author = UserModel().get_by_username(
899 TEST_USER_ADMIN_LOGIN)
899 TEST_USER_ADMIN_LOGIN)
900 Session().add(pull_request)
900 Session().add(pull_request)
901 Session().commit()
901 Session().commit()
902
902
903 vcs = repo.scm_instance()
903 vcs = repo.scm_instance()
904 vcs.remove_ref('refs/heads/{}'.format(branch_name))
904 vcs.remove_ref('refs/heads/{}'.format(branch_name))
905
905
906 response = self.app.get(route_path(
906 response = self.app.get(route_path(
907 'pullrequest_show',
907 'pullrequest_show',
908 repo_name=repo.repo_name,
908 repo_name=repo.repo_name,
909 pull_request_id=pull_request.pull_request_id))
909 pull_request_id=pull_request.pull_request_id))
910
910
911 assert response.status_int == 200
911 assert response.status_int == 200
912 assert_response = AssertResponse(response)
912 assert_response = AssertResponse(response)
913 assert_response.element_contains(
913 assert_response.element_contains(
914 '#changeset_compare_view_content .alert strong',
914 '#changeset_compare_view_content .alert strong',
915 'Missing commits')
915 'Missing commits')
916 assert_response.element_contains(
916 assert_response.element_contains(
917 '#changeset_compare_view_content .alert',
917 '#changeset_compare_view_content .alert',
918 'This pull request cannot be displayed, because one or more'
918 'This pull request cannot be displayed, because one or more'
919 ' commits no longer exist in the source repository.')
919 ' commits no longer exist in the source repository.')
920
920
921 def test_strip_commits_from_pull_request(
921 def test_strip_commits_from_pull_request(
922 self, backend, pr_util, csrf_token):
922 self, backend, pr_util, csrf_token):
923 commits = [
923 commits = [
924 {'message': 'initial-commit'},
924 {'message': 'initial-commit'},
925 {'message': 'old-feature'},
925 {'message': 'old-feature'},
926 {'message': 'new-feature', 'parents': ['initial-commit']},
926 {'message': 'new-feature', 'parents': ['initial-commit']},
927 ]
927 ]
928 pull_request = pr_util.create_pull_request(
928 pull_request = pr_util.create_pull_request(
929 commits, target_head='initial-commit', source_head='new-feature',
929 commits, target_head='initial-commit', source_head='new-feature',
930 revisions=['new-feature'])
930 revisions=['new-feature'])
931
931
932 vcs = pr_util.source_repository.scm_instance()
932 vcs = pr_util.source_repository.scm_instance()
933 if backend.alias == 'git':
933 if backend.alias == 'git':
934 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
934 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
935 else:
935 else:
936 vcs.strip(pr_util.commit_ids['new-feature'])
936 vcs.strip(pr_util.commit_ids['new-feature'])
937
937
938 response = self.app.get(route_path(
938 response = self.app.get(route_path(
939 'pullrequest_show',
939 'pullrequest_show',
940 repo_name=pr_util.target_repository.repo_name,
940 repo_name=pr_util.target_repository.repo_name,
941 pull_request_id=pull_request.pull_request_id))
941 pull_request_id=pull_request.pull_request_id))
942
942
943 assert response.status_int == 200
943 assert response.status_int == 200
944 assert_response = AssertResponse(response)
944 assert_response = AssertResponse(response)
945 assert_response.element_contains(
945 assert_response.element_contains(
946 '#changeset_compare_view_content .alert strong',
946 '#changeset_compare_view_content .alert strong',
947 'Missing commits')
947 'Missing commits')
948 assert_response.element_contains(
948 assert_response.element_contains(
949 '#changeset_compare_view_content .alert',
949 '#changeset_compare_view_content .alert',
950 'This pull request cannot be displayed, because one or more'
950 'This pull request cannot be displayed, because one or more'
951 ' commits no longer exist in the source repository.')
951 ' commits no longer exist in the source repository.')
952 assert_response.element_contains(
952 assert_response.element_contains(
953 '#update_commits',
953 '#update_commits',
954 'Update commits')
954 'Update commits')
955
955
956 def test_strip_commits_and_update(
956 def test_strip_commits_and_update(
957 self, backend, pr_util, csrf_token):
957 self, backend, pr_util, csrf_token):
958 commits = [
958 commits = [
959 {'message': 'initial-commit'},
959 {'message': 'initial-commit'},
960 {'message': 'old-feature'},
960 {'message': 'old-feature'},
961 {'message': 'new-feature', 'parents': ['old-feature']},
961 {'message': 'new-feature', 'parents': ['old-feature']},
962 ]
962 ]
963 pull_request = pr_util.create_pull_request(
963 pull_request = pr_util.create_pull_request(
964 commits, target_head='old-feature', source_head='new-feature',
964 commits, target_head='old-feature', source_head='new-feature',
965 revisions=['new-feature'], mergeable=True)
965 revisions=['new-feature'], mergeable=True)
966
966
967 vcs = pr_util.source_repository.scm_instance()
967 vcs = pr_util.source_repository.scm_instance()
968 if backend.alias == 'git':
968 if backend.alias == 'git':
969 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
969 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
970 else:
970 else:
971 vcs.strip(pr_util.commit_ids['new-feature'])
971 vcs.strip(pr_util.commit_ids['new-feature'])
972
972
973 response = self.app.post(
973 response = self.app.post(
974 route_path('pullrequest_update',
974 route_path('pullrequest_update',
975 repo_name=pull_request.target_repo.repo_name,
975 repo_name=pull_request.target_repo.repo_name,
976 pull_request_id=pull_request.pull_request_id),
976 pull_request_id=pull_request.pull_request_id),
977 params={'update_commits': 'true',
977 params={'update_commits': 'true',
978 'csrf_token': csrf_token})
978 'csrf_token': csrf_token})
979
979
980 assert response.status_int == 200
980 assert response.status_int == 200
981 assert response.body == 'true'
981 assert response.body == 'true'
982
982
983 # Make sure that after update, it won't raise 500 errors
983 # Make sure that after update, it won't raise 500 errors
984 response = self.app.get(route_path(
984 response = self.app.get(route_path(
985 'pullrequest_show',
985 'pullrequest_show',
986 repo_name=pr_util.target_repository.repo_name,
986 repo_name=pr_util.target_repository.repo_name,
987 pull_request_id=pull_request.pull_request_id))
987 pull_request_id=pull_request.pull_request_id))
988
988
989 assert response.status_int == 200
989 assert response.status_int == 200
990 assert_response = AssertResponse(response)
990 assert_response = AssertResponse(response)
991 assert_response.element_contains(
991 assert_response.element_contains(
992 '#changeset_compare_view_content .alert strong',
992 '#changeset_compare_view_content .alert strong',
993 'Missing commits')
993 'Missing commits')
994
994
995 def test_branch_is_a_link(self, pr_util):
995 def test_branch_is_a_link(self, pr_util):
996 pull_request = pr_util.create_pull_request()
996 pull_request = pr_util.create_pull_request()
997 pull_request.source_ref = 'branch:origin:1234567890abcdef'
997 pull_request.source_ref = 'branch:origin:1234567890abcdef'
998 pull_request.target_ref = 'branch:target:abcdef1234567890'
998 pull_request.target_ref = 'branch:target:abcdef1234567890'
999 Session().add(pull_request)
999 Session().add(pull_request)
1000 Session().commit()
1000 Session().commit()
1001
1001
1002 response = self.app.get(route_path(
1002 response = self.app.get(route_path(
1003 'pullrequest_show',
1003 'pullrequest_show',
1004 repo_name=pull_request.target_repo.scm_instance().name,
1004 repo_name=pull_request.target_repo.scm_instance().name,
1005 pull_request_id=pull_request.pull_request_id))
1005 pull_request_id=pull_request.pull_request_id))
1006 assert response.status_int == 200
1006 assert response.status_int == 200
1007 assert_response = AssertResponse(response)
1007 assert_response = AssertResponse(response)
1008
1008
1009 origin = assert_response.get_element('.pr-origininfo .tag')
1009 origin = assert_response.get_element('.pr-origininfo .tag')
1010 origin_children = origin.getchildren()
1010 origin_children = origin.getchildren()
1011 assert len(origin_children) == 1
1011 assert len(origin_children) == 1
1012 target = assert_response.get_element('.pr-targetinfo .tag')
1012 target = assert_response.get_element('.pr-targetinfo .tag')
1013 target_children = target.getchildren()
1013 target_children = target.getchildren()
1014 assert len(target_children) == 1
1014 assert len(target_children) == 1
1015
1015
1016 expected_origin_link = route_path(
1016 expected_origin_link = route_path(
1017 'repo_changelog',
1017 'repo_changelog',
1018 repo_name=pull_request.source_repo.scm_instance().name,
1018 repo_name=pull_request.source_repo.scm_instance().name,
1019 params=dict(branch='origin'))
1019 params=dict(branch='origin'))
1020 expected_target_link = route_path(
1020 expected_target_link = route_path(
1021 'repo_changelog',
1021 'repo_changelog',
1022 repo_name=pull_request.target_repo.scm_instance().name,
1022 repo_name=pull_request.target_repo.scm_instance().name,
1023 params=dict(branch='target'))
1023 params=dict(branch='target'))
1024 assert origin_children[0].attrib['href'] == expected_origin_link
1024 assert origin_children[0].attrib['href'] == expected_origin_link
1025 assert origin_children[0].text == 'branch: origin'
1025 assert origin_children[0].text == 'branch: origin'
1026 assert target_children[0].attrib['href'] == expected_target_link
1026 assert target_children[0].attrib['href'] == expected_target_link
1027 assert target_children[0].text == 'branch: target'
1027 assert target_children[0].text == 'branch: target'
1028
1028
1029 def test_bookmark_is_not_a_link(self, pr_util):
1029 def test_bookmark_is_not_a_link(self, pr_util):
1030 pull_request = pr_util.create_pull_request()
1030 pull_request = pr_util.create_pull_request()
1031 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1031 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1032 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1032 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1033 Session().add(pull_request)
1033 Session().add(pull_request)
1034 Session().commit()
1034 Session().commit()
1035
1035
1036 response = self.app.get(route_path(
1036 response = self.app.get(route_path(
1037 'pullrequest_show',
1037 'pullrequest_show',
1038 repo_name=pull_request.target_repo.scm_instance().name,
1038 repo_name=pull_request.target_repo.scm_instance().name,
1039 pull_request_id=pull_request.pull_request_id))
1039 pull_request_id=pull_request.pull_request_id))
1040 assert response.status_int == 200
1040 assert response.status_int == 200
1041 assert_response = AssertResponse(response)
1041 assert_response = AssertResponse(response)
1042
1042
1043 origin = assert_response.get_element('.pr-origininfo .tag')
1043 origin = assert_response.get_element('.pr-origininfo .tag')
1044 assert origin.text.strip() == 'bookmark: origin'
1044 assert origin.text.strip() == 'bookmark: origin'
1045 assert origin.getchildren() == []
1045 assert origin.getchildren() == []
1046
1046
1047 target = assert_response.get_element('.pr-targetinfo .tag')
1047 target = assert_response.get_element('.pr-targetinfo .tag')
1048 assert target.text.strip() == 'bookmark: target'
1048 assert target.text.strip() == 'bookmark: target'
1049 assert target.getchildren() == []
1049 assert target.getchildren() == []
1050
1050
1051 def test_tag_is_not_a_link(self, pr_util):
1051 def test_tag_is_not_a_link(self, pr_util):
1052 pull_request = pr_util.create_pull_request()
1052 pull_request = pr_util.create_pull_request()
1053 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1053 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1054 pull_request.target_ref = 'tag:target:abcdef1234567890'
1054 pull_request.target_ref = 'tag:target:abcdef1234567890'
1055 Session().add(pull_request)
1055 Session().add(pull_request)
1056 Session().commit()
1056 Session().commit()
1057
1057
1058 response = self.app.get(route_path(
1058 response = self.app.get(route_path(
1059 'pullrequest_show',
1059 'pullrequest_show',
1060 repo_name=pull_request.target_repo.scm_instance().name,
1060 repo_name=pull_request.target_repo.scm_instance().name,
1061 pull_request_id=pull_request.pull_request_id))
1061 pull_request_id=pull_request.pull_request_id))
1062 assert response.status_int == 200
1062 assert response.status_int == 200
1063 assert_response = AssertResponse(response)
1063 assert_response = AssertResponse(response)
1064
1064
1065 origin = assert_response.get_element('.pr-origininfo .tag')
1065 origin = assert_response.get_element('.pr-origininfo .tag')
1066 assert origin.text.strip() == 'tag: origin'
1066 assert origin.text.strip() == 'tag: origin'
1067 assert origin.getchildren() == []
1067 assert origin.getchildren() == []
1068
1068
1069 target = assert_response.get_element('.pr-targetinfo .tag')
1069 target = assert_response.get_element('.pr-targetinfo .tag')
1070 assert target.text.strip() == 'tag: target'
1070 assert target.text.strip() == 'tag: target'
1071 assert target.getchildren() == []
1071 assert target.getchildren() == []
1072
1072
1073 @pytest.mark.parametrize('mergeable', [True, False])
1073 @pytest.mark.parametrize('mergeable', [True, False])
1074 def test_shadow_repository_link(
1074 def test_shadow_repository_link(
1075 self, mergeable, pr_util, http_host_only_stub):
1075 self, mergeable, pr_util, http_host_only_stub):
1076 """
1076 """
1077 Check that the pull request summary page displays a link to the shadow
1077 Check that the pull request summary page displays a link to the shadow
1078 repository if the pull request is mergeable. If it is not mergeable
1078 repository if the pull request is mergeable. If it is not mergeable
1079 the link should not be displayed.
1079 the link should not be displayed.
1080 """
1080 """
1081 pull_request = pr_util.create_pull_request(
1081 pull_request = pr_util.create_pull_request(
1082 mergeable=mergeable, enable_notifications=False)
1082 mergeable=mergeable, enable_notifications=False)
1083 target_repo = pull_request.target_repo.scm_instance()
1083 target_repo = pull_request.target_repo.scm_instance()
1084 pr_id = pull_request.pull_request_id
1084 pr_id = pull_request.pull_request_id
1085 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1085 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1086 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1086 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1087
1087
1088 response = self.app.get(route_path(
1088 response = self.app.get(route_path(
1089 'pullrequest_show',
1089 'pullrequest_show',
1090 repo_name=target_repo.name,
1090 repo_name=target_repo.name,
1091 pull_request_id=pr_id))
1091 pull_request_id=pr_id))
1092
1092
1093 assertr = AssertResponse(response)
1093 assertr = AssertResponse(response)
1094 if mergeable:
1094 if mergeable:
1095 assertr.element_value_contains('input.pr-mergeinfo', shadow_url)
1095 assertr.element_value_contains('input.pr-mergeinfo', shadow_url)
1096 assertr.element_value_contains('input.pr-mergeinfo ', 'pr-merge')
1096 assertr.element_value_contains('input.pr-mergeinfo ', 'pr-merge')
1097 else:
1097 else:
1098 assertr.no_element_exists('.pr-mergeinfo')
1098 assertr.no_element_exists('.pr-mergeinfo')
1099
1099
1100
1100
1101 @pytest.mark.usefixtures('app')
1101 @pytest.mark.usefixtures('app')
1102 @pytest.mark.backends("git", "hg")
1102 @pytest.mark.backends("git", "hg")
1103 class TestPullrequestsControllerDelete(object):
1103 class TestPullrequestsControllerDelete(object):
1104 def test_pull_request_delete_button_permissions_admin(
1104 def test_pull_request_delete_button_permissions_admin(
1105 self, autologin_user, user_admin, pr_util):
1105 self, autologin_user, user_admin, pr_util):
1106 pull_request = pr_util.create_pull_request(
1106 pull_request = pr_util.create_pull_request(
1107 author=user_admin.username, enable_notifications=False)
1107 author=user_admin.username, enable_notifications=False)
1108
1108
1109 response = self.app.get(route_path(
1109 response = self.app.get(route_path(
1110 'pullrequest_show',
1110 'pullrequest_show',
1111 repo_name=pull_request.target_repo.scm_instance().name,
1111 repo_name=pull_request.target_repo.scm_instance().name,
1112 pull_request_id=pull_request.pull_request_id))
1112 pull_request_id=pull_request.pull_request_id))
1113
1113
1114 response.mustcontain('id="delete_pullrequest"')
1114 response.mustcontain('id="delete_pullrequest"')
1115 response.mustcontain('Confirm to delete this pull request')
1115 response.mustcontain('Confirm to delete this pull request')
1116
1116
1117 def test_pull_request_delete_button_permissions_owner(
1117 def test_pull_request_delete_button_permissions_owner(
1118 self, autologin_regular_user, user_regular, pr_util):
1118 self, autologin_regular_user, user_regular, pr_util):
1119 pull_request = pr_util.create_pull_request(
1119 pull_request = pr_util.create_pull_request(
1120 author=user_regular.username, enable_notifications=False)
1120 author=user_regular.username, enable_notifications=False)
1121
1121
1122 response = self.app.get(route_path(
1122 response = self.app.get(route_path(
1123 'pullrequest_show',
1123 'pullrequest_show',
1124 repo_name=pull_request.target_repo.scm_instance().name,
1124 repo_name=pull_request.target_repo.scm_instance().name,
1125 pull_request_id=pull_request.pull_request_id))
1125 pull_request_id=pull_request.pull_request_id))
1126
1126
1127 response.mustcontain('id="delete_pullrequest"')
1127 response.mustcontain('id="delete_pullrequest"')
1128 response.mustcontain('Confirm to delete this pull request')
1128 response.mustcontain('Confirm to delete this pull request')
1129
1129
1130 def test_pull_request_delete_button_permissions_forbidden(
1130 def test_pull_request_delete_button_permissions_forbidden(
1131 self, autologin_regular_user, user_regular, user_admin, pr_util):
1131 self, autologin_regular_user, user_regular, user_admin, pr_util):
1132 pull_request = pr_util.create_pull_request(
1132 pull_request = pr_util.create_pull_request(
1133 author=user_admin.username, enable_notifications=False)
1133 author=user_admin.username, enable_notifications=False)
1134
1134
1135 response = self.app.get(route_path(
1135 response = self.app.get(route_path(
1136 'pullrequest_show',
1136 'pullrequest_show',
1137 repo_name=pull_request.target_repo.scm_instance().name,
1137 repo_name=pull_request.target_repo.scm_instance().name,
1138 pull_request_id=pull_request.pull_request_id))
1138 pull_request_id=pull_request.pull_request_id))
1139 response.mustcontain(no=['id="delete_pullrequest"'])
1139 response.mustcontain(no=['id="delete_pullrequest"'])
1140 response.mustcontain(no=['Confirm to delete this pull request'])
1140 response.mustcontain(no=['Confirm to delete this pull request'])
1141
1141
1142 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1142 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1143 self, autologin_regular_user, user_regular, user_admin, pr_util,
1143 self, autologin_regular_user, user_regular, user_admin, pr_util,
1144 user_util):
1144 user_util):
1145
1145
1146 pull_request = pr_util.create_pull_request(
1146 pull_request = pr_util.create_pull_request(
1147 author=user_admin.username, enable_notifications=False)
1147 author=user_admin.username, enable_notifications=False)
1148
1148
1149 user_util.grant_user_permission_to_repo(
1149 user_util.grant_user_permission_to_repo(
1150 pull_request.target_repo, user_regular,
1150 pull_request.target_repo, user_regular,
1151 'repository.write')
1151 'repository.write')
1152
1152
1153 response = self.app.get(route_path(
1153 response = self.app.get(route_path(
1154 'pullrequest_show',
1154 'pullrequest_show',
1155 repo_name=pull_request.target_repo.scm_instance().name,
1155 repo_name=pull_request.target_repo.scm_instance().name,
1156 pull_request_id=pull_request.pull_request_id))
1156 pull_request_id=pull_request.pull_request_id))
1157
1157
1158 response.mustcontain('id="open_edit_pullrequest"')
1158 response.mustcontain('id="open_edit_pullrequest"')
1159 response.mustcontain('id="delete_pullrequest"')
1159 response.mustcontain('id="delete_pullrequest"')
1160 response.mustcontain(no=['Confirm to delete this pull request'])
1160 response.mustcontain(no=['Confirm to delete this pull request'])
1161
1161
1162 def test_delete_comment_returns_404_if_comment_does_not_exist(
1162 def test_delete_comment_returns_404_if_comment_does_not_exist(
1163 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1163 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1164
1164
1165 pull_request = pr_util.create_pull_request(
1165 pull_request = pr_util.create_pull_request(
1166 author=user_admin.username, enable_notifications=False)
1166 author=user_admin.username, enable_notifications=False)
1167
1167
1168 self.app.post(
1168 self.app.post(
1169 route_path(
1169 route_path(
1170 'pullrequest_comment_delete',
1170 'pullrequest_comment_delete',
1171 repo_name=pull_request.target_repo.scm_instance().name,
1171 repo_name=pull_request.target_repo.scm_instance().name,
1172 pull_request_id=pull_request.pull_request_id,
1172 pull_request_id=pull_request.pull_request_id,
1173 comment_id=1024404),
1173 comment_id=1024404),
1174 extra_environ=xhr_header,
1174 extra_environ=xhr_header,
1175 params={'csrf_token': csrf_token},
1175 params={'csrf_token': csrf_token},
1176 status=404
1176 status=404
1177 )
1177 )
1178
1178
1179 def test_delete_comment(
1179 def test_delete_comment(
1180 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1180 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1181
1181
1182 pull_request = pr_util.create_pull_request(
1182 pull_request = pr_util.create_pull_request(
1183 author=user_admin.username, enable_notifications=False)
1183 author=user_admin.username, enable_notifications=False)
1184 comment = pr_util.create_comment()
1184 comment = pr_util.create_comment()
1185 comment_id = comment.comment_id
1185 comment_id = comment.comment_id
1186
1186
1187 response = self.app.post(
1187 response = self.app.post(
1188 route_path(
1188 route_path(
1189 'pullrequest_comment_delete',
1189 'pullrequest_comment_delete',
1190 repo_name=pull_request.target_repo.scm_instance().name,
1190 repo_name=pull_request.target_repo.scm_instance().name,
1191 pull_request_id=pull_request.pull_request_id,
1191 pull_request_id=pull_request.pull_request_id,
1192 comment_id=comment_id),
1192 comment_id=comment_id),
1193 extra_environ=xhr_header,
1193 extra_environ=xhr_header,
1194 params={'csrf_token': csrf_token},
1194 params={'csrf_token': csrf_token},
1195 status=200
1195 status=200
1196 )
1196 )
1197 assert response.body == 'true'
1197 assert response.body == 'true'
1198
1198
1199 @pytest.mark.parametrize('url_type', [
1199 @pytest.mark.parametrize('url_type', [
1200 'pullrequest_new',
1200 'pullrequest_new',
1201 'pullrequest_create',
1201 'pullrequest_create',
1202 'pullrequest_update',
1202 'pullrequest_update',
1203 'pullrequest_merge',
1203 'pullrequest_merge',
1204 ])
1204 ])
1205 def test_pull_request_is_forbidden_on_archived_repo(
1205 def test_pull_request_is_forbidden_on_archived_repo(
1206 self, autologin_user, backend, xhr_header, user_util, url_type):
1206 self, autologin_user, backend, xhr_header, user_util, url_type):
1207
1207
1208 # create a temporary repo
1208 # create a temporary repo
1209 source = user_util.create_repo(repo_type=backend.alias)
1209 source = user_util.create_repo(repo_type=backend.alias)
1210 repo_name = source.repo_name
1210 repo_name = source.repo_name
1211 repo = Repository.get_by_repo_name(repo_name)
1211 repo = Repository.get_by_repo_name(repo_name)
1212 repo.archived = True
1212 repo.archived = True
1213 Session().commit()
1213 Session().commit()
1214
1214
1215 response = self.app.get(
1215 response = self.app.get(
1216 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1216 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1217
1217
1218 msg = 'Action not supported for archived repository.'
1218 msg = 'Action not supported for archived repository.'
1219 assert_session_flash(response, msg)
1219 assert_session_flash(response, msg)
1220
1220
1221
1221
1222 def assert_pull_request_status(pull_request, expected_status):
1222 def assert_pull_request_status(pull_request, expected_status):
1223 status = ChangesetStatusModel().calculated_review_status(
1223 status = ChangesetStatusModel().calculated_review_status(
1224 pull_request=pull_request)
1224 pull_request=pull_request)
1225 assert status == expected_status
1225 assert status == expected_status
1226
1226
1227
1227
1228 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1228 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1229 @pytest.mark.usefixtures("autologin_user")
1229 @pytest.mark.usefixtures("autologin_user")
1230 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1230 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1231 response = app.get(
1231 response = app.get(
1232 route_path(route, repo_name=backend_svn.repo_name), status=404)
1232 route_path(route, repo_name=backend_svn.repo_name), status=404)
1233
1233
@@ -1,1401 +1,1414 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
45 RepositoryRequirementError, EmptyRepositoryError)
45 RepositoryRequirementError, EmptyRepositoryError)
46 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
49 ChangesetComment, ChangesetStatus, Repository)
49 ChangesetComment, ChangesetStatus, Repository)
50 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.forms import PullRequestForm
51 from rhodecode.model.meta import Session
51 from rhodecode.model.meta import Session
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
59
59
60 def load_default_context(self):
60 def load_default_context(self):
61 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 # backward compat., we use for OLD PRs a plain renderer
64 # backward compat., we use for OLD PRs a plain renderer
65 c.renderer = 'plain'
65 c.renderer = 'plain'
66 return c
66 return c
67
67
68 def _get_pull_requests_list(
68 def _get_pull_requests_list(
69 self, repo_name, source, filter_type, opened_by, statuses):
69 self, repo_name, source, filter_type, opened_by, statuses):
70
70
71 draw, start, limit = self._extract_chunk(self.request)
71 draw, start, limit = self._extract_chunk(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
73 _render = self.request.get_partial_renderer(
73 _render = self.request.get_partial_renderer(
74 'rhodecode:templates/data_table/_dt_elements.mako')
74 'rhodecode:templates/data_table/_dt_elements.mako')
75
75
76 # pagination
76 # pagination
77
77
78 if filter_type == 'awaiting_review':
78 if filter_type == 'awaiting_review':
79 pull_requests = PullRequestModel().get_awaiting_review(
79 pull_requests = PullRequestModel().get_awaiting_review(
80 repo_name, source=source, opened_by=opened_by,
80 repo_name, source=source, opened_by=opened_by,
81 statuses=statuses, offset=start, length=limit,
81 statuses=statuses, offset=start, length=limit,
82 order_by=order_by, order_dir=order_dir)
82 order_by=order_by, order_dir=order_dir)
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
84 repo_name, source=source, statuses=statuses,
84 repo_name, source=source, statuses=statuses,
85 opened_by=opened_by)
85 opened_by=opened_by)
86 elif filter_type == 'awaiting_my_review':
86 elif filter_type == 'awaiting_my_review':
87 pull_requests = PullRequestModel().get_awaiting_my_review(
87 pull_requests = PullRequestModel().get_awaiting_my_review(
88 repo_name, source=source, opened_by=opened_by,
88 repo_name, source=source, opened_by=opened_by,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
90 offset=start, length=limit, order_by=order_by,
90 offset=start, length=limit, order_by=order_by,
91 order_dir=order_dir)
91 order_dir=order_dir)
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
93 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 repo_name, source=source, user_id=self._rhodecode_user.user_id,
94 statuses=statuses, opened_by=opened_by)
94 statuses=statuses, opened_by=opened_by)
95 else:
95 else:
96 pull_requests = PullRequestModel().get_all(
96 pull_requests = PullRequestModel().get_all(
97 repo_name, source=source, opened_by=opened_by,
97 repo_name, source=source, opened_by=opened_by,
98 statuses=statuses, offset=start, length=limit,
98 statuses=statuses, offset=start, length=limit,
99 order_by=order_by, order_dir=order_dir)
99 order_by=order_by, order_dir=order_dir)
100 pull_requests_total_count = PullRequestModel().count_all(
100 pull_requests_total_count = PullRequestModel().count_all(
101 repo_name, source=source, statuses=statuses,
101 repo_name, source=source, statuses=statuses,
102 opened_by=opened_by)
102 opened_by=opened_by)
103
103
104 data = []
104 data = []
105 comments_model = CommentsModel()
105 comments_model = CommentsModel()
106 for pr in pull_requests:
106 for pr in pull_requests:
107 comments = comments_model.get_all_comments(
107 comments = comments_model.get_all_comments(
108 self.db_repo.repo_id, pull_request=pr)
108 self.db_repo.repo_id, pull_request=pr)
109
109
110 data.append({
110 data.append({
111 'name': _render('pullrequest_name',
111 'name': _render('pullrequest_name',
112 pr.pull_request_id, pr.target_repo.repo_name),
112 pr.pull_request_id, pr.target_repo.repo_name),
113 'name_raw': pr.pull_request_id,
113 'name_raw': pr.pull_request_id,
114 'status': _render('pullrequest_status',
114 'status': _render('pullrequest_status',
115 pr.calculated_review_status()),
115 pr.calculated_review_status()),
116 'title': _render(
116 'title': _render(
117 'pullrequest_title', pr.title, pr.description),
117 'pullrequest_title', pr.title, pr.description),
118 'description': h.escape(pr.description),
118 'description': h.escape(pr.description),
119 'updated_on': _render('pullrequest_updated_on',
119 'updated_on': _render('pullrequest_updated_on',
120 h.datetime_to_time(pr.updated_on)),
120 h.datetime_to_time(pr.updated_on)),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
122 'created_on': _render('pullrequest_updated_on',
122 'created_on': _render('pullrequest_updated_on',
123 h.datetime_to_time(pr.created_on)),
123 h.datetime_to_time(pr.created_on)),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
125 'author': _render('pullrequest_author',
125 'author': _render('pullrequest_author',
126 pr.author.full_contact, ),
126 pr.author.full_contact, ),
127 'author_raw': pr.author.full_name,
127 'author_raw': pr.author.full_name,
128 'comments': _render('pullrequest_comments', len(comments)),
128 'comments': _render('pullrequest_comments', len(comments)),
129 'comments_raw': len(comments),
129 'comments_raw': len(comments),
130 'closed': pr.is_closed(),
130 'closed': pr.is_closed(),
131 })
131 })
132
132
133 data = ({
133 data = ({
134 'draw': draw,
134 'draw': draw,
135 'data': data,
135 'data': data,
136 'recordsTotal': pull_requests_total_count,
136 'recordsTotal': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
138 })
138 })
139 return data
139 return data
140
140
141 def get_recache_flag(self):
141 def get_recache_flag(self):
142 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
142 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
143 flag_val = self.request.GET.get(flag_name)
143 flag_val = self.request.GET.get(flag_name)
144 if str2bool(flag_val):
144 if str2bool(flag_val):
145 return True
145 return True
146 return False
146 return False
147
147
148 @LoginRequired()
148 @LoginRequired()
149 @HasRepoPermissionAnyDecorator(
149 @HasRepoPermissionAnyDecorator(
150 'repository.read', 'repository.write', 'repository.admin')
150 'repository.read', 'repository.write', 'repository.admin')
151 @view_config(
151 @view_config(
152 route_name='pullrequest_show_all', request_method='GET',
152 route_name='pullrequest_show_all', request_method='GET',
153 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
153 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
154 def pull_request_list(self):
154 def pull_request_list(self):
155 c = self.load_default_context()
155 c = self.load_default_context()
156
156
157 req_get = self.request.GET
157 req_get = self.request.GET
158 c.source = str2bool(req_get.get('source'))
158 c.source = str2bool(req_get.get('source'))
159 c.closed = str2bool(req_get.get('closed'))
159 c.closed = str2bool(req_get.get('closed'))
160 c.my = str2bool(req_get.get('my'))
160 c.my = str2bool(req_get.get('my'))
161 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
161 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
162 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
162 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
163
163
164 c.active = 'open'
164 c.active = 'open'
165 if c.my:
165 if c.my:
166 c.active = 'my'
166 c.active = 'my'
167 if c.closed:
167 if c.closed:
168 c.active = 'closed'
168 c.active = 'closed'
169 if c.awaiting_review and not c.source:
169 if c.awaiting_review and not c.source:
170 c.active = 'awaiting'
170 c.active = 'awaiting'
171 if c.source and not c.awaiting_review:
171 if c.source and not c.awaiting_review:
172 c.active = 'source'
172 c.active = 'source'
173 if c.awaiting_my_review:
173 if c.awaiting_my_review:
174 c.active = 'awaiting_my'
174 c.active = 'awaiting_my'
175
175
176 return self._get_template_context(c)
176 return self._get_template_context(c)
177
177
178 @LoginRequired()
178 @LoginRequired()
179 @HasRepoPermissionAnyDecorator(
179 @HasRepoPermissionAnyDecorator(
180 'repository.read', 'repository.write', 'repository.admin')
180 'repository.read', 'repository.write', 'repository.admin')
181 @view_config(
181 @view_config(
182 route_name='pullrequest_show_all_data', request_method='GET',
182 route_name='pullrequest_show_all_data', request_method='GET',
183 renderer='json_ext', xhr=True)
183 renderer='json_ext', xhr=True)
184 def pull_request_list_data(self):
184 def pull_request_list_data(self):
185 self.load_default_context()
185 self.load_default_context()
186
186
187 # additional filters
187 # additional filters
188 req_get = self.request.GET
188 req_get = self.request.GET
189 source = str2bool(req_get.get('source'))
189 source = str2bool(req_get.get('source'))
190 closed = str2bool(req_get.get('closed'))
190 closed = str2bool(req_get.get('closed'))
191 my = str2bool(req_get.get('my'))
191 my = str2bool(req_get.get('my'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
192 awaiting_review = str2bool(req_get.get('awaiting_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
193 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
194
194
195 filter_type = 'awaiting_review' if awaiting_review \
195 filter_type = 'awaiting_review' if awaiting_review \
196 else 'awaiting_my_review' if awaiting_my_review \
196 else 'awaiting_my_review' if awaiting_my_review \
197 else None
197 else None
198
198
199 opened_by = None
199 opened_by = None
200 if my:
200 if my:
201 opened_by = [self._rhodecode_user.user_id]
201 opened_by = [self._rhodecode_user.user_id]
202
202
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
203 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
204 if closed:
204 if closed:
205 statuses = [PullRequest.STATUS_CLOSED]
205 statuses = [PullRequest.STATUS_CLOSED]
206
206
207 data = self._get_pull_requests_list(
207 data = self._get_pull_requests_list(
208 repo_name=self.db_repo_name, source=source,
208 repo_name=self.db_repo_name, source=source,
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
209 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
210
210
211 return data
211 return data
212
212
213 def _is_diff_cache_enabled(self, target_repo):
213 def _is_diff_cache_enabled(self, target_repo):
214 caching_enabled = self._get_general_setting(
214 caching_enabled = self._get_general_setting(
215 target_repo, 'rhodecode_diff_cache')
215 target_repo, 'rhodecode_diff_cache')
216 log.debug('Diff caching enabled: %s', caching_enabled)
216 log.debug('Diff caching enabled: %s', caching_enabled)
217 return caching_enabled
217 return caching_enabled
218
218
219 def _get_diffset(self, source_repo_name, source_repo,
219 def _get_diffset(self, source_repo_name, source_repo,
220 source_ref_id, target_ref_id,
220 source_ref_id, target_ref_id,
221 target_commit, source_commit, diff_limit, file_limit,
221 target_commit, source_commit, diff_limit, file_limit,
222 fulldiff, hide_whitespace_changes, diff_context):
222 fulldiff, hide_whitespace_changes, diff_context):
223
223
224 vcs_diff = PullRequestModel().get_diff(
224 vcs_diff = PullRequestModel().get_diff(
225 source_repo, source_ref_id, target_ref_id,
225 source_repo, source_ref_id, target_ref_id,
226 hide_whitespace_changes, diff_context)
226 hide_whitespace_changes, diff_context)
227
227
228 diff_processor = diffs.DiffProcessor(
228 diff_processor = diffs.DiffProcessor(
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
230 file_limit=file_limit, show_full_diff=fulldiff)
230 file_limit=file_limit, show_full_diff=fulldiff)
231
231
232 _parsed = diff_processor.prepare()
232 _parsed = diff_processor.prepare()
233
233
234 diffset = codeblocks.DiffSet(
234 diffset = codeblocks.DiffSet(
235 repo_name=self.db_repo_name,
235 repo_name=self.db_repo_name,
236 source_repo_name=source_repo_name,
236 source_repo_name=source_repo_name,
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
239 )
239 )
240 diffset = self.path_filter.render_patchset_filtered(
240 diffset = self.path_filter.render_patchset_filtered(
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
242
242
243 return diffset
243 return diffset
244
244
245 def _get_range_diffset(self, source_scm, source_repo,
245 def _get_range_diffset(self, source_scm, source_repo,
246 commit1, commit2, diff_limit, file_limit,
246 commit1, commit2, diff_limit, file_limit,
247 fulldiff, hide_whitespace_changes, diff_context):
247 fulldiff, hide_whitespace_changes, diff_context):
248 vcs_diff = source_scm.get_diff(
248 vcs_diff = source_scm.get_diff(
249 commit1, commit2,
249 commit1, commit2,
250 ignore_whitespace=hide_whitespace_changes,
250 ignore_whitespace=hide_whitespace_changes,
251 context=diff_context)
251 context=diff_context)
252
252
253 diff_processor = diffs.DiffProcessor(
253 diff_processor = diffs.DiffProcessor(
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
255 file_limit=file_limit, show_full_diff=fulldiff)
255 file_limit=file_limit, show_full_diff=fulldiff)
256
256
257 _parsed = diff_processor.prepare()
257 _parsed = diff_processor.prepare()
258
258
259 diffset = codeblocks.DiffSet(
259 diffset = codeblocks.DiffSet(
260 repo_name=source_repo.repo_name,
260 repo_name=source_repo.repo_name,
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
263
263
264 diffset = self.path_filter.render_patchset_filtered(
264 diffset = self.path_filter.render_patchset_filtered(
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
266
266
267 return diffset
267 return diffset
268
268
269 @LoginRequired()
269 @LoginRequired()
270 @HasRepoPermissionAnyDecorator(
270 @HasRepoPermissionAnyDecorator(
271 'repository.read', 'repository.write', 'repository.admin')
271 'repository.read', 'repository.write', 'repository.admin')
272 @view_config(
272 @view_config(
273 route_name='pullrequest_show', request_method='GET',
273 route_name='pullrequest_show', request_method='GET',
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
275 def pull_request_show(self):
275 def pull_request_show(self):
276 pull_request_id = self.request.matchdict['pull_request_id']
276 pull_request_id = self.request.matchdict['pull_request_id']
277
277
278 c = self.load_default_context()
278 c = self.load_default_context()
279
279
280 version = self.request.GET.get('version')
280 version = self.request.GET.get('version')
281 from_version = self.request.GET.get('from_version') or version
281 from_version = self.request.GET.get('from_version') or version
282 merge_checks = self.request.GET.get('merge_checks')
282 merge_checks = self.request.GET.get('merge_checks')
283 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
283 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
284
284
285 # fetch global flags of ignore ws or context lines
285 # fetch global flags of ignore ws or context lines
286 diff_context = diffs.get_diff_context(self.request)
286 diff_context = diffs.get_diff_context(self.request)
287 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
287 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
288
288
289 force_refresh = str2bool(self.request.GET.get('force_refresh'))
289 force_refresh = str2bool(self.request.GET.get('force_refresh'))
290
290
291 (pull_request_latest,
291 (pull_request_latest,
292 pull_request_at_ver,
292 pull_request_at_ver,
293 pull_request_display_obj,
293 pull_request_display_obj,
294 at_version) = PullRequestModel().get_pr_version(
294 at_version) = PullRequestModel().get_pr_version(
295 pull_request_id, version=version)
295 pull_request_id, version=version)
296 pr_closed = pull_request_latest.is_closed()
296 pr_closed = pull_request_latest.is_closed()
297
297
298 if pr_closed and (version or from_version):
298 if pr_closed and (version or from_version):
299 # not allow to browse versions
299 # not allow to browse versions
300 raise HTTPFound(h.route_path(
300 raise HTTPFound(h.route_path(
301 'pullrequest_show', repo_name=self.db_repo_name,
301 'pullrequest_show', repo_name=self.db_repo_name,
302 pull_request_id=pull_request_id))
302 pull_request_id=pull_request_id))
303
303
304 versions = pull_request_display_obj.versions()
304 versions = pull_request_display_obj.versions()
305 # used to store per-commit range diffs
305 # used to store per-commit range diffs
306 c.changes = collections.OrderedDict()
306 c.changes = collections.OrderedDict()
307 c.range_diff_on = self.request.GET.get('range-diff') == "1"
307 c.range_diff_on = self.request.GET.get('range-diff') == "1"
308
308
309 c.at_version = at_version
309 c.at_version = at_version
310 c.at_version_num = (at_version
310 c.at_version_num = (at_version
311 if at_version and at_version != 'latest'
311 if at_version and at_version != 'latest'
312 else None)
312 else None)
313 c.at_version_pos = ChangesetComment.get_index_from_version(
313 c.at_version_pos = ChangesetComment.get_index_from_version(
314 c.at_version_num, versions)
314 c.at_version_num, versions)
315
315
316 (prev_pull_request_latest,
316 (prev_pull_request_latest,
317 prev_pull_request_at_ver,
317 prev_pull_request_at_ver,
318 prev_pull_request_display_obj,
318 prev_pull_request_display_obj,
319 prev_at_version) = PullRequestModel().get_pr_version(
319 prev_at_version) = PullRequestModel().get_pr_version(
320 pull_request_id, version=from_version)
320 pull_request_id, version=from_version)
321
321
322 c.from_version = prev_at_version
322 c.from_version = prev_at_version
323 c.from_version_num = (prev_at_version
323 c.from_version_num = (prev_at_version
324 if prev_at_version and prev_at_version != 'latest'
324 if prev_at_version and prev_at_version != 'latest'
325 else None)
325 else None)
326 c.from_version_pos = ChangesetComment.get_index_from_version(
326 c.from_version_pos = ChangesetComment.get_index_from_version(
327 c.from_version_num, versions)
327 c.from_version_num, versions)
328
328
329 # define if we're in COMPARE mode or VIEW at version mode
329 # define if we're in COMPARE mode or VIEW at version mode
330 compare = at_version != prev_at_version
330 compare = at_version != prev_at_version
331
331
332 # pull_requests repo_name we opened it against
332 # pull_requests repo_name we opened it against
333 # ie. target_repo must match
333 # ie. target_repo must match
334 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
334 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
335 raise HTTPNotFound()
335 raise HTTPNotFound()
336
336
337 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
337 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
338 pull_request_at_ver)
338 pull_request_at_ver)
339
339
340 c.pull_request = pull_request_display_obj
340 c.pull_request = pull_request_display_obj
341 c.renderer = pull_request_at_ver.description_renderer or c.renderer
341 c.renderer = pull_request_at_ver.description_renderer or c.renderer
342 c.pull_request_latest = pull_request_latest
342 c.pull_request_latest = pull_request_latest
343
343
344 if compare or (at_version and not at_version == 'latest'):
344 if compare or (at_version and not at_version == 'latest'):
345 c.allowed_to_change_status = False
345 c.allowed_to_change_status = False
346 c.allowed_to_update = False
346 c.allowed_to_update = False
347 c.allowed_to_merge = False
347 c.allowed_to_merge = False
348 c.allowed_to_delete = False
348 c.allowed_to_delete = False
349 c.allowed_to_comment = False
349 c.allowed_to_comment = False
350 c.allowed_to_close = False
350 c.allowed_to_close = False
351 else:
351 else:
352 can_change_status = PullRequestModel().check_user_change_status(
352 can_change_status = PullRequestModel().check_user_change_status(
353 pull_request_at_ver, self._rhodecode_user)
353 pull_request_at_ver, self._rhodecode_user)
354 c.allowed_to_change_status = can_change_status and not pr_closed
354 c.allowed_to_change_status = can_change_status and not pr_closed
355
355
356 c.allowed_to_update = PullRequestModel().check_user_update(
356 c.allowed_to_update = PullRequestModel().check_user_update(
357 pull_request_latest, self._rhodecode_user) and not pr_closed
357 pull_request_latest, self._rhodecode_user) and not pr_closed
358 c.allowed_to_merge = PullRequestModel().check_user_merge(
358 c.allowed_to_merge = PullRequestModel().check_user_merge(
359 pull_request_latest, self._rhodecode_user) and not pr_closed
359 pull_request_latest, self._rhodecode_user) and not pr_closed
360 c.allowed_to_delete = PullRequestModel().check_user_delete(
360 c.allowed_to_delete = PullRequestModel().check_user_delete(
361 pull_request_latest, self._rhodecode_user) and not pr_closed
361 pull_request_latest, self._rhodecode_user) and not pr_closed
362 c.allowed_to_comment = not pr_closed
362 c.allowed_to_comment = not pr_closed
363 c.allowed_to_close = c.allowed_to_merge and not pr_closed
363 c.allowed_to_close = c.allowed_to_merge and not pr_closed
364
364
365 c.forbid_adding_reviewers = False
365 c.forbid_adding_reviewers = False
366 c.forbid_author_to_review = False
366 c.forbid_author_to_review = False
367 c.forbid_commit_author_to_review = False
367 c.forbid_commit_author_to_review = False
368
368
369 if pull_request_latest.reviewer_data and \
369 if pull_request_latest.reviewer_data and \
370 'rules' in pull_request_latest.reviewer_data:
370 'rules' in pull_request_latest.reviewer_data:
371 rules = pull_request_latest.reviewer_data['rules'] or {}
371 rules = pull_request_latest.reviewer_data['rules'] or {}
372 try:
372 try:
373 c.forbid_adding_reviewers = rules.get(
373 c.forbid_adding_reviewers = rules.get(
374 'forbid_adding_reviewers')
374 'forbid_adding_reviewers')
375 c.forbid_author_to_review = rules.get(
375 c.forbid_author_to_review = rules.get(
376 'forbid_author_to_review')
376 'forbid_author_to_review')
377 c.forbid_commit_author_to_review = rules.get(
377 c.forbid_commit_author_to_review = rules.get(
378 'forbid_commit_author_to_review')
378 'forbid_commit_author_to_review')
379 except Exception:
379 except Exception:
380 pass
380 pass
381
381
382 # check merge capabilities
382 # check merge capabilities
383 _merge_check = MergeCheck.validate(
383 _merge_check = MergeCheck.validate(
384 pull_request_latest, auth_user=self._rhodecode_user,
384 pull_request_latest, auth_user=self._rhodecode_user,
385 translator=self.request.translate,
385 translator=self.request.translate,
386 force_shadow_repo_refresh=force_refresh)
386 force_shadow_repo_refresh=force_refresh)
387 c.pr_merge_errors = _merge_check.error_details
387 c.pr_merge_errors = _merge_check.error_details
388 c.pr_merge_possible = not _merge_check.failed
388 c.pr_merge_possible = not _merge_check.failed
389 c.pr_merge_message = _merge_check.merge_msg
389 c.pr_merge_message = _merge_check.merge_msg
390
390
391 c.pr_merge_info = MergeCheck.get_merge_conditions(
391 c.pr_merge_info = MergeCheck.get_merge_conditions(
392 pull_request_latest, translator=self.request.translate)
392 pull_request_latest, translator=self.request.translate)
393
393
394 c.pull_request_review_status = _merge_check.review_status
394 c.pull_request_review_status = _merge_check.review_status
395 if merge_checks:
395 if merge_checks:
396 self.request.override_renderer = \
396 self.request.override_renderer = \
397 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
397 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
398 return self._get_template_context(c)
398 return self._get_template_context(c)
399
399
400 comments_model = CommentsModel()
400 comments_model = CommentsModel()
401
401
402 # reviewers and statuses
402 # reviewers and statuses
403 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
403 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
404 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
404 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
405
405
406 # GENERAL COMMENTS with versions #
406 # GENERAL COMMENTS with versions #
407 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
407 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
408 q = q.order_by(ChangesetComment.comment_id.asc())
408 q = q.order_by(ChangesetComment.comment_id.asc())
409 general_comments = q
409 general_comments = q
410
410
411 # pick comments we want to render at current version
411 # pick comments we want to render at current version
412 c.comment_versions = comments_model.aggregate_comments(
412 c.comment_versions = comments_model.aggregate_comments(
413 general_comments, versions, c.at_version_num)
413 general_comments, versions, c.at_version_num)
414 c.comments = c.comment_versions[c.at_version_num]['until']
414 c.comments = c.comment_versions[c.at_version_num]['until']
415
415
416 # INLINE COMMENTS with versions #
416 # INLINE COMMENTS with versions #
417 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
417 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
418 q = q.order_by(ChangesetComment.comment_id.asc())
418 q = q.order_by(ChangesetComment.comment_id.asc())
419 inline_comments = q
419 inline_comments = q
420
420
421 c.inline_versions = comments_model.aggregate_comments(
421 c.inline_versions = comments_model.aggregate_comments(
422 inline_comments, versions, c.at_version_num, inline=True)
422 inline_comments, versions, c.at_version_num, inline=True)
423
423
424 # inject latest version
424 # inject latest version
425 latest_ver = PullRequest.get_pr_display_object(
425 latest_ver = PullRequest.get_pr_display_object(
426 pull_request_latest, pull_request_latest)
426 pull_request_latest, pull_request_latest)
427
427
428 c.versions = versions + [latest_ver]
428 c.versions = versions + [latest_ver]
429
429
430 # if we use version, then do not show later comments
430 # if we use version, then do not show later comments
431 # than current version
431 # than current version
432 display_inline_comments = collections.defaultdict(
432 display_inline_comments = collections.defaultdict(
433 lambda: collections.defaultdict(list))
433 lambda: collections.defaultdict(list))
434 for co in inline_comments:
434 for co in inline_comments:
435 if c.at_version_num:
435 if c.at_version_num:
436 # pick comments that are at least UPTO given version, so we
436 # pick comments that are at least UPTO given version, so we
437 # don't render comments for higher version
437 # don't render comments for higher version
438 should_render = co.pull_request_version_id and \
438 should_render = co.pull_request_version_id and \
439 co.pull_request_version_id <= c.at_version_num
439 co.pull_request_version_id <= c.at_version_num
440 else:
440 else:
441 # showing all, for 'latest'
441 # showing all, for 'latest'
442 should_render = True
442 should_render = True
443
443
444 if should_render:
444 if should_render:
445 display_inline_comments[co.f_path][co.line_no].append(co)
445 display_inline_comments[co.f_path][co.line_no].append(co)
446
446
447 # load diff data into template context, if we use compare mode then
447 # load diff data into template context, if we use compare mode then
448 # diff is calculated based on changes between versions of PR
448 # diff is calculated based on changes between versions of PR
449
449
450 source_repo = pull_request_at_ver.source_repo
450 source_repo = pull_request_at_ver.source_repo
451 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
451 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
452
452
453 target_repo = pull_request_at_ver.target_repo
453 target_repo = pull_request_at_ver.target_repo
454 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
454 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
455
455
456 if compare:
456 if compare:
457 # in compare switch the diff base to latest commit from prev version
457 # in compare switch the diff base to latest commit from prev version
458 target_ref_id = prev_pull_request_display_obj.revisions[0]
458 target_ref_id = prev_pull_request_display_obj.revisions[0]
459
459
460 # despite opening commits for bookmarks/branches/tags, we always
460 # despite opening commits for bookmarks/branches/tags, we always
461 # convert this to rev to prevent changes after bookmark or branch change
461 # convert this to rev to prevent changes after bookmark or branch change
462 c.source_ref_type = 'rev'
462 c.source_ref_type = 'rev'
463 c.source_ref = source_ref_id
463 c.source_ref = source_ref_id
464
464
465 c.target_ref_type = 'rev'
465 c.target_ref_type = 'rev'
466 c.target_ref = target_ref_id
466 c.target_ref = target_ref_id
467
467
468 c.source_repo = source_repo
468 c.source_repo = source_repo
469 c.target_repo = target_repo
469 c.target_repo = target_repo
470
470
471 c.commit_ranges = []
471 c.commit_ranges = []
472 source_commit = EmptyCommit()
472 source_commit = EmptyCommit()
473 target_commit = EmptyCommit()
473 target_commit = EmptyCommit()
474 c.missing_requirements = False
474 c.missing_requirements = False
475
475
476 source_scm = source_repo.scm_instance()
476 source_scm = source_repo.scm_instance()
477 target_scm = target_repo.scm_instance()
477 target_scm = target_repo.scm_instance()
478
478
479 shadow_scm = None
479 shadow_scm = None
480 try:
480 try:
481 shadow_scm = pull_request_latest.get_shadow_repo()
481 shadow_scm = pull_request_latest.get_shadow_repo()
482 except Exception:
482 except Exception:
483 log.debug('Failed to get shadow repo', exc_info=True)
483 log.debug('Failed to get shadow repo', exc_info=True)
484 # try first the existing source_repo, and then shadow
484 # try first the existing source_repo, and then shadow
485 # repo if we can obtain one
485 # repo if we can obtain one
486 commits_source_repo = source_scm or shadow_scm
486 commits_source_repo = source_scm or shadow_scm
487
487
488 c.commits_source_repo = commits_source_repo
488 c.commits_source_repo = commits_source_repo
489 c.ancestor = None # set it to None, to hide it from PR view
489 c.ancestor = None # set it to None, to hide it from PR view
490
490
491 # empty version means latest, so we keep this to prevent
491 # empty version means latest, so we keep this to prevent
492 # double caching
492 # double caching
493 version_normalized = version or 'latest'
493 version_normalized = version or 'latest'
494 from_version_normalized = from_version or 'latest'
494 from_version_normalized = from_version or 'latest'
495
495
496 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
496 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
497 cache_file_path = diff_cache_exist(
497 cache_file_path = diff_cache_exist(
498 cache_path, 'pull_request', pull_request_id, version_normalized,
498 cache_path, 'pull_request', pull_request_id, version_normalized,
499 from_version_normalized, source_ref_id, target_ref_id,
499 from_version_normalized, source_ref_id, target_ref_id,
500 hide_whitespace_changes, diff_context, c.fulldiff)
500 hide_whitespace_changes, diff_context, c.fulldiff)
501
501
502 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
502 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
503 force_recache = self.get_recache_flag()
503 force_recache = self.get_recache_flag()
504
504
505 cached_diff = None
505 cached_diff = None
506 if caching_enabled:
506 if caching_enabled:
507 cached_diff = load_cached_diff(cache_file_path)
507 cached_diff = load_cached_diff(cache_file_path)
508
508
509 has_proper_commit_cache = (
509 has_proper_commit_cache = (
510 cached_diff and cached_diff.get('commits')
510 cached_diff and cached_diff.get('commits')
511 and len(cached_diff.get('commits', [])) == 5
511 and len(cached_diff.get('commits', [])) == 5
512 and cached_diff.get('commits')[0]
512 and cached_diff.get('commits')[0]
513 and cached_diff.get('commits')[3])
513 and cached_diff.get('commits')[3])
514
514
515 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
515 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
516 diff_commit_cache = \
516 diff_commit_cache = \
517 (ancestor_commit, commit_cache, missing_requirements,
517 (ancestor_commit, commit_cache, missing_requirements,
518 source_commit, target_commit) = cached_diff['commits']
518 source_commit, target_commit) = cached_diff['commits']
519 else:
519 else:
520 diff_commit_cache = \
520 diff_commit_cache = \
521 (ancestor_commit, commit_cache, missing_requirements,
521 (ancestor_commit, commit_cache, missing_requirements,
522 source_commit, target_commit) = self.get_commits(
522 source_commit, target_commit) = self.get_commits(
523 commits_source_repo,
523 commits_source_repo,
524 pull_request_at_ver,
524 pull_request_at_ver,
525 source_commit,
525 source_commit,
526 source_ref_id,
526 source_ref_id,
527 source_scm,
527 source_scm,
528 target_commit,
528 target_commit,
529 target_ref_id,
529 target_ref_id,
530 target_scm)
530 target_scm)
531
531
532 # register our commit range
532 # register our commit range
533 for comm in commit_cache.values():
533 for comm in commit_cache.values():
534 c.commit_ranges.append(comm)
534 c.commit_ranges.append(comm)
535
535
536 c.missing_requirements = missing_requirements
536 c.missing_requirements = missing_requirements
537 c.ancestor_commit = ancestor_commit
537 c.ancestor_commit = ancestor_commit
538 c.statuses = source_repo.statuses(
538 c.statuses = source_repo.statuses(
539 [x.raw_id for x in c.commit_ranges])
539 [x.raw_id for x in c.commit_ranges])
540
540
541 # auto collapse if we have more than limit
541 # auto collapse if we have more than limit
542 collapse_limit = diffs.DiffProcessor._collapse_commits_over
542 collapse_limit = diffs.DiffProcessor._collapse_commits_over
543 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
543 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
544 c.compare_mode = compare
544 c.compare_mode = compare
545
545
546 # diff_limit is the old behavior, will cut off the whole diff
546 # diff_limit is the old behavior, will cut off the whole diff
547 # if the limit is applied otherwise will just hide the
547 # if the limit is applied otherwise will just hide the
548 # big files from the front-end
548 # big files from the front-end
549 diff_limit = c.visual.cut_off_limit_diff
549 diff_limit = c.visual.cut_off_limit_diff
550 file_limit = c.visual.cut_off_limit_file
550 file_limit = c.visual.cut_off_limit_file
551
551
552 c.missing_commits = False
552 c.missing_commits = False
553 if (c.missing_requirements
553 if (c.missing_requirements
554 or isinstance(source_commit, EmptyCommit)
554 or isinstance(source_commit, EmptyCommit)
555 or source_commit == target_commit):
555 or source_commit == target_commit):
556
556
557 c.missing_commits = True
557 c.missing_commits = True
558 else:
558 else:
559 c.inline_comments = display_inline_comments
559 c.inline_comments = display_inline_comments
560
560
561 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
561 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
562 if not force_recache and has_proper_diff_cache:
562 if not force_recache and has_proper_diff_cache:
563 c.diffset = cached_diff['diff']
563 c.diffset = cached_diff['diff']
564 (ancestor_commit, commit_cache, missing_requirements,
564 (ancestor_commit, commit_cache, missing_requirements,
565 source_commit, target_commit) = cached_diff['commits']
565 source_commit, target_commit) = cached_diff['commits']
566 else:
566 else:
567 c.diffset = self._get_diffset(
567 c.diffset = self._get_diffset(
568 c.source_repo.repo_name, commits_source_repo,
568 c.source_repo.repo_name, commits_source_repo,
569 source_ref_id, target_ref_id,
569 source_ref_id, target_ref_id,
570 target_commit, source_commit,
570 target_commit, source_commit,
571 diff_limit, file_limit, c.fulldiff,
571 diff_limit, file_limit, c.fulldiff,
572 hide_whitespace_changes, diff_context)
572 hide_whitespace_changes, diff_context)
573
573
574 # save cached diff
574 # save cached diff
575 if caching_enabled:
575 if caching_enabled:
576 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
576 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
577
577
578 c.limited_diff = c.diffset.limited_diff
578 c.limited_diff = c.diffset.limited_diff
579
579
580 # calculate removed files that are bound to comments
580 # calculate removed files that are bound to comments
581 comment_deleted_files = [
581 comment_deleted_files = [
582 fname for fname in display_inline_comments
582 fname for fname in display_inline_comments
583 if fname not in c.diffset.file_stats]
583 if fname not in c.diffset.file_stats]
584
584
585 c.deleted_files_comments = collections.defaultdict(dict)
585 c.deleted_files_comments = collections.defaultdict(dict)
586 for fname, per_line_comments in display_inline_comments.items():
586 for fname, per_line_comments in display_inline_comments.items():
587 if fname in comment_deleted_files:
587 if fname in comment_deleted_files:
588 c.deleted_files_comments[fname]['stats'] = 0
588 c.deleted_files_comments[fname]['stats'] = 0
589 c.deleted_files_comments[fname]['comments'] = list()
589 c.deleted_files_comments[fname]['comments'] = list()
590 for lno, comments in per_line_comments.items():
590 for lno, comments in per_line_comments.items():
591 c.deleted_files_comments[fname]['comments'].extend(comments)
591 c.deleted_files_comments[fname]['comments'].extend(comments)
592
592
593 # maybe calculate the range diff
593 # maybe calculate the range diff
594 if c.range_diff_on:
594 if c.range_diff_on:
595 # TODO(marcink): set whitespace/context
595 # TODO(marcink): set whitespace/context
596 context_lcl = 3
596 context_lcl = 3
597 ign_whitespace_lcl = False
597 ign_whitespace_lcl = False
598
598
599 for commit in c.commit_ranges:
599 for commit in c.commit_ranges:
600 commit2 = commit
600 commit2 = commit
601 commit1 = commit.first_parent
601 commit1 = commit.first_parent
602
602
603 range_diff_cache_file_path = diff_cache_exist(
603 range_diff_cache_file_path = diff_cache_exist(
604 cache_path, 'diff', commit.raw_id,
604 cache_path, 'diff', commit.raw_id,
605 ign_whitespace_lcl, context_lcl, c.fulldiff)
605 ign_whitespace_lcl, context_lcl, c.fulldiff)
606
606
607 cached_diff = None
607 cached_diff = None
608 if caching_enabled:
608 if caching_enabled:
609 cached_diff = load_cached_diff(range_diff_cache_file_path)
609 cached_diff = load_cached_diff(range_diff_cache_file_path)
610
610
611 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
611 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
612 if not force_recache and has_proper_diff_cache:
612 if not force_recache and has_proper_diff_cache:
613 diffset = cached_diff['diff']
613 diffset = cached_diff['diff']
614 else:
614 else:
615 diffset = self._get_range_diffset(
615 diffset = self._get_range_diffset(
616 source_scm, source_repo,
616 source_scm, source_repo,
617 commit1, commit2, diff_limit, file_limit,
617 commit1, commit2, diff_limit, file_limit,
618 c.fulldiff, ign_whitespace_lcl, context_lcl
618 c.fulldiff, ign_whitespace_lcl, context_lcl
619 )
619 )
620
620
621 # save cached diff
621 # save cached diff
622 if caching_enabled:
622 if caching_enabled:
623 cache_diff(range_diff_cache_file_path, diffset, None)
623 cache_diff(range_diff_cache_file_path, diffset, None)
624
624
625 c.changes[commit.raw_id] = diffset
625 c.changes[commit.raw_id] = diffset
626
626
627 # this is a hack to properly display links, when creating PR, the
627 # this is a hack to properly display links, when creating PR, the
628 # compare view and others uses different notation, and
628 # compare view and others uses different notation, and
629 # compare_commits.mako renders links based on the target_repo.
629 # compare_commits.mako renders links based on the target_repo.
630 # We need to swap that here to generate it properly on the html side
630 # We need to swap that here to generate it properly on the html side
631 c.target_repo = c.source_repo
631 c.target_repo = c.source_repo
632
632
633 c.commit_statuses = ChangesetStatus.STATUSES
633 c.commit_statuses = ChangesetStatus.STATUSES
634
634
635 c.show_version_changes = not pr_closed
635 c.show_version_changes = not pr_closed
636 if c.show_version_changes:
636 if c.show_version_changes:
637 cur_obj = pull_request_at_ver
637 cur_obj = pull_request_at_ver
638 prev_obj = prev_pull_request_at_ver
638 prev_obj = prev_pull_request_at_ver
639
639
640 old_commit_ids = prev_obj.revisions
640 old_commit_ids = prev_obj.revisions
641 new_commit_ids = cur_obj.revisions
641 new_commit_ids = cur_obj.revisions
642 commit_changes = PullRequestModel()._calculate_commit_id_changes(
642 commit_changes = PullRequestModel()._calculate_commit_id_changes(
643 old_commit_ids, new_commit_ids)
643 old_commit_ids, new_commit_ids)
644 c.commit_changes_summary = commit_changes
644 c.commit_changes_summary = commit_changes
645
645
646 # calculate the diff for commits between versions
646 # calculate the diff for commits between versions
647 c.commit_changes = []
647 c.commit_changes = []
648 mark = lambda cs, fw: list(
648 mark = lambda cs, fw: list(
649 h.itertools.izip_longest([], cs, fillvalue=fw))
649 h.itertools.izip_longest([], cs, fillvalue=fw))
650 for c_type, raw_id in mark(commit_changes.added, 'a') \
650 for c_type, raw_id in mark(commit_changes.added, 'a') \
651 + mark(commit_changes.removed, 'r') \
651 + mark(commit_changes.removed, 'r') \
652 + mark(commit_changes.common, 'c'):
652 + mark(commit_changes.common, 'c'):
653
653
654 if raw_id in commit_cache:
654 if raw_id in commit_cache:
655 commit = commit_cache[raw_id]
655 commit = commit_cache[raw_id]
656 else:
656 else:
657 try:
657 try:
658 commit = commits_source_repo.get_commit(raw_id)
658 commit = commits_source_repo.get_commit(raw_id)
659 except CommitDoesNotExistError:
659 except CommitDoesNotExistError:
660 # in case we fail extracting still use "dummy" commit
660 # in case we fail extracting still use "dummy" commit
661 # for display in commit diff
661 # for display in commit diff
662 commit = h.AttributeDict(
662 commit = h.AttributeDict(
663 {'raw_id': raw_id,
663 {'raw_id': raw_id,
664 'message': 'EMPTY or MISSING COMMIT'})
664 'message': 'EMPTY or MISSING COMMIT'})
665 c.commit_changes.append([c_type, commit])
665 c.commit_changes.append([c_type, commit])
666
666
667 # current user review statuses for each version
667 # current user review statuses for each version
668 c.review_versions = {}
668 c.review_versions = {}
669 if self._rhodecode_user.user_id in allowed_reviewers:
669 if self._rhodecode_user.user_id in allowed_reviewers:
670 for co in general_comments:
670 for co in general_comments:
671 if co.author.user_id == self._rhodecode_user.user_id:
671 if co.author.user_id == self._rhodecode_user.user_id:
672 status = co.status_change
672 status = co.status_change
673 if status:
673 if status:
674 _ver_pr = status[0].comment.pull_request_version_id
674 _ver_pr = status[0].comment.pull_request_version_id
675 c.review_versions[_ver_pr] = status[0]
675 c.review_versions[_ver_pr] = status[0]
676
676
677 return self._get_template_context(c)
677 return self._get_template_context(c)
678
678
679 def get_commits(
679 def get_commits(
680 self, commits_source_repo, pull_request_at_ver, source_commit,
680 self, commits_source_repo, pull_request_at_ver, source_commit,
681 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
681 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
682 commit_cache = collections.OrderedDict()
682 commit_cache = collections.OrderedDict()
683 missing_requirements = False
683 missing_requirements = False
684 try:
684 try:
685 pre_load = ["author", "branch", "date", "message", "parents"]
685 pre_load = ["author", "branch", "date", "message", "parents"]
686 show_revs = pull_request_at_ver.revisions
686 show_revs = pull_request_at_ver.revisions
687 for rev in show_revs:
687 for rev in show_revs:
688 comm = commits_source_repo.get_commit(
688 comm = commits_source_repo.get_commit(
689 commit_id=rev, pre_load=pre_load)
689 commit_id=rev, pre_load=pre_load)
690 commit_cache[comm.raw_id] = comm
690 commit_cache[comm.raw_id] = comm
691
691
692 # Order here matters, we first need to get target, and then
692 # Order here matters, we first need to get target, and then
693 # the source
693 # the source
694 target_commit = commits_source_repo.get_commit(
694 target_commit = commits_source_repo.get_commit(
695 commit_id=safe_str(target_ref_id))
695 commit_id=safe_str(target_ref_id))
696
696
697 source_commit = commits_source_repo.get_commit(
697 source_commit = commits_source_repo.get_commit(
698 commit_id=safe_str(source_ref_id))
698 commit_id=safe_str(source_ref_id))
699 except CommitDoesNotExistError:
699 except CommitDoesNotExistError:
700 log.warning(
700 log.warning(
701 'Failed to get commit from `{}` repo'.format(
701 'Failed to get commit from `{}` repo'.format(
702 commits_source_repo), exc_info=True)
702 commits_source_repo), exc_info=True)
703 except RepositoryRequirementError:
703 except RepositoryRequirementError:
704 log.warning(
704 log.warning(
705 'Failed to get all required data from repo', exc_info=True)
705 'Failed to get all required data from repo', exc_info=True)
706 missing_requirements = True
706 missing_requirements = True
707 ancestor_commit = None
707 ancestor_commit = None
708 try:
708 try:
709 ancestor_id = source_scm.get_common_ancestor(
709 ancestor_id = source_scm.get_common_ancestor(
710 source_commit.raw_id, target_commit.raw_id, target_scm)
710 source_commit.raw_id, target_commit.raw_id, target_scm)
711 ancestor_commit = source_scm.get_commit(ancestor_id)
711 ancestor_commit = source_scm.get_commit(ancestor_id)
712 except Exception:
712 except Exception:
713 ancestor_commit = None
713 ancestor_commit = None
714 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
714 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
715
715
716 def assure_not_empty_repo(self):
716 def assure_not_empty_repo(self):
717 _ = self.request.translate
717 _ = self.request.translate
718
718
719 try:
719 try:
720 self.db_repo.scm_instance().get_commit()
720 self.db_repo.scm_instance().get_commit()
721 except EmptyRepositoryError:
721 except EmptyRepositoryError:
722 h.flash(h.literal(_('There are no commits yet')),
722 h.flash(h.literal(_('There are no commits yet')),
723 category='warning')
723 category='warning')
724 raise HTTPFound(
724 raise HTTPFound(
725 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
725 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
726
726
727 @LoginRequired()
727 @LoginRequired()
728 @NotAnonymous()
728 @NotAnonymous()
729 @HasRepoPermissionAnyDecorator(
729 @HasRepoPermissionAnyDecorator(
730 'repository.read', 'repository.write', 'repository.admin')
730 'repository.read', 'repository.write', 'repository.admin')
731 @view_config(
731 @view_config(
732 route_name='pullrequest_new', request_method='GET',
732 route_name='pullrequest_new', request_method='GET',
733 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
733 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
734 def pull_request_new(self):
734 def pull_request_new(self):
735 _ = self.request.translate
735 _ = self.request.translate
736 c = self.load_default_context()
736 c = self.load_default_context()
737
737
738 self.assure_not_empty_repo()
738 self.assure_not_empty_repo()
739 source_repo = self.db_repo
739 source_repo = self.db_repo
740
740
741 commit_id = self.request.GET.get('commit')
741 commit_id = self.request.GET.get('commit')
742 branch_ref = self.request.GET.get('branch')
742 branch_ref = self.request.GET.get('branch')
743 bookmark_ref = self.request.GET.get('bookmark')
743 bookmark_ref = self.request.GET.get('bookmark')
744
744
745 try:
745 try:
746 source_repo_data = PullRequestModel().generate_repo_data(
746 source_repo_data = PullRequestModel().generate_repo_data(
747 source_repo, commit_id=commit_id,
747 source_repo, commit_id=commit_id,
748 branch=branch_ref, bookmark=bookmark_ref,
748 branch=branch_ref, bookmark=bookmark_ref,
749 translator=self.request.translate)
749 translator=self.request.translate)
750 except CommitDoesNotExistError as e:
750 except CommitDoesNotExistError as e:
751 log.exception(e)
751 log.exception(e)
752 h.flash(_('Commit does not exist'), 'error')
752 h.flash(_('Commit does not exist'), 'error')
753 raise HTTPFound(
753 raise HTTPFound(
754 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
754 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
755
755
756 default_target_repo = source_repo
756 default_target_repo = source_repo
757
757
758 if source_repo.parent:
758 if source_repo.parent:
759 parent_vcs_obj = source_repo.parent.scm_instance()
759 parent_vcs_obj = source_repo.parent.scm_instance()
760 if parent_vcs_obj and not parent_vcs_obj.is_empty():
760 if parent_vcs_obj and not parent_vcs_obj.is_empty():
761 # change default if we have a parent repo
761 # change default if we have a parent repo
762 default_target_repo = source_repo.parent
762 default_target_repo = source_repo.parent
763
763
764 target_repo_data = PullRequestModel().generate_repo_data(
764 target_repo_data = PullRequestModel().generate_repo_data(
765 default_target_repo, translator=self.request.translate)
765 default_target_repo, translator=self.request.translate)
766
766
767 selected_source_ref = source_repo_data['refs']['selected_ref']
767 selected_source_ref = source_repo_data['refs']['selected_ref']
768 title_source_ref = ''
768 title_source_ref = ''
769 if selected_source_ref:
769 if selected_source_ref:
770 title_source_ref = selected_source_ref.split(':', 2)[1]
770 title_source_ref = selected_source_ref.split(':', 2)[1]
771 c.default_title = PullRequestModel().generate_pullrequest_title(
771 c.default_title = PullRequestModel().generate_pullrequest_title(
772 source=source_repo.repo_name,
772 source=source_repo.repo_name,
773 source_ref=title_source_ref,
773 source_ref=title_source_ref,
774 target=default_target_repo.repo_name
774 target=default_target_repo.repo_name
775 )
775 )
776
776
777 c.default_repo_data = {
777 c.default_repo_data = {
778 'source_repo_name': source_repo.repo_name,
778 'source_repo_name': source_repo.repo_name,
779 'source_refs_json': json.dumps(source_repo_data),
779 'source_refs_json': json.dumps(source_repo_data),
780 'target_repo_name': default_target_repo.repo_name,
780 'target_repo_name': default_target_repo.repo_name,
781 'target_refs_json': json.dumps(target_repo_data),
781 'target_refs_json': json.dumps(target_repo_data),
782 }
782 }
783 c.default_source_ref = selected_source_ref
783 c.default_source_ref = selected_source_ref
784
784
785 return self._get_template_context(c)
785 return self._get_template_context(c)
786
786
787 @LoginRequired()
787 @LoginRequired()
788 @NotAnonymous()
788 @NotAnonymous()
789 @HasRepoPermissionAnyDecorator(
789 @HasRepoPermissionAnyDecorator(
790 'repository.read', 'repository.write', 'repository.admin')
790 'repository.read', 'repository.write', 'repository.admin')
791 @view_config(
791 @view_config(
792 route_name='pullrequest_repo_refs', request_method='GET',
792 route_name='pullrequest_repo_refs', request_method='GET',
793 renderer='json_ext', xhr=True)
793 renderer='json_ext', xhr=True)
794 def pull_request_repo_refs(self):
794 def pull_request_repo_refs(self):
795 self.load_default_context()
795 self.load_default_context()
796 target_repo_name = self.request.matchdict['target_repo_name']
796 target_repo_name = self.request.matchdict['target_repo_name']
797 repo = Repository.get_by_repo_name(target_repo_name)
797 repo = Repository.get_by_repo_name(target_repo_name)
798 if not repo:
798 if not repo:
799 raise HTTPNotFound()
799 raise HTTPNotFound()
800
800
801 target_perm = HasRepoPermissionAny(
801 target_perm = HasRepoPermissionAny(
802 'repository.read', 'repository.write', 'repository.admin')(
802 'repository.read', 'repository.write', 'repository.admin')(
803 target_repo_name)
803 target_repo_name)
804 if not target_perm:
804 if not target_perm:
805 raise HTTPNotFound()
805 raise HTTPNotFound()
806
806
807 return PullRequestModel().generate_repo_data(
807 return PullRequestModel().generate_repo_data(
808 repo, translator=self.request.translate)
808 repo, translator=self.request.translate)
809
809
810 @LoginRequired()
810 @LoginRequired()
811 @NotAnonymous()
811 @NotAnonymous()
812 @HasRepoPermissionAnyDecorator(
812 @HasRepoPermissionAnyDecorator(
813 'repository.read', 'repository.write', 'repository.admin')
813 'repository.read', 'repository.write', 'repository.admin')
814 @view_config(
814 @view_config(
815 route_name='pullrequest_repo_destinations', request_method='GET',
815 route_name='pullrequest_repo_targets', request_method='GET',
816 renderer='json_ext', xhr=True)
816 renderer='json_ext', xhr=True)
817 def pull_request_repo_destinations(self):
817 def pullrequest_repo_targets(self):
818 _ = self.request.translate
818 _ = self.request.translate
819 filter_query = self.request.GET.get('query')
819 filter_query = self.request.GET.get('query')
820
820
821 # get the parents
822 parent_target_repos = []
823 if self.db_repo.parent:
824 parents_query = Repository.query() \
825 .order_by(func.length(Repository.repo_name)) \
826 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
827
828 if filter_query:
829 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
830 parents_query = parents_query.filter(
831 Repository.repo_name.ilike(ilike_expression))
832 parents = parents_query.limit(20).all()
833
834 for parent in parents:
835 parent_vcs_obj = parent.scm_instance()
836 if parent_vcs_obj and not parent_vcs_obj.is_empty():
837 parent_target_repos.append(parent)
838
839 # get other forks, and repo itself
821 query = Repository.query() \
840 query = Repository.query() \
822 .order_by(func.length(Repository.repo_name)) \
841 .order_by(func.length(Repository.repo_name)) \
823 .filter(
842 .filter(
824 or_(Repository.repo_name == self.db_repo.repo_name,
843 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
825 Repository.fork_id == self.db_repo.repo_id))
844 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
845 ) \
846 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
826
847
827 if filter_query:
848 if filter_query:
828 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
849 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
829 query = query.filter(
850 query = query.filter(Repository.repo_name.ilike(ilike_expression))
830 Repository.repo_name.ilike(ilike_expression))
831
851
832 add_parent = False
852 limit = max(20 - len(parent_target_repos), 5) # not less then 5
833 if self.db_repo.parent:
853 target_repos = query.limit(limit).all()
834 if filter_query in self.db_repo.parent.repo_name:
835 parent_vcs_obj = self.db_repo.parent.scm_instance()
836 if parent_vcs_obj and not parent_vcs_obj.is_empty():
837 add_parent = True
838
854
839 limit = 20 - 1 if add_parent else 20
855 all_target_repos = target_repos + parent_target_repos
840 all_repos = query.limit(limit).all()
841 if add_parent:
842 all_repos += [self.db_repo.parent]
843
856
844 repos = []
857 repos = []
845 for obj in ScmModel().get_repos(all_repos):
858 for obj in ScmModel().get_repos(all_target_repos):
846 repos.append({
859 repos.append({
847 'id': obj['name'],
860 'id': obj['name'],
848 'text': obj['name'],
861 'text': obj['name'],
849 'type': 'repo',
862 'type': 'repo',
850 'repo_id': obj['dbrepo']['repo_id'],
863 'repo_id': obj['dbrepo']['repo_id'],
851 'repo_type': obj['dbrepo']['repo_type'],
864 'repo_type': obj['dbrepo']['repo_type'],
852 'private': obj['dbrepo']['private'],
865 'private': obj['dbrepo']['private'],
853
866
854 })
867 })
855
868
856 data = {
869 data = {
857 'more': False,
870 'more': False,
858 'results': [{
871 'results': [{
859 'text': _('Repositories'),
872 'text': _('Repositories'),
860 'children': repos
873 'children': repos
861 }] if repos else []
874 }] if repos else []
862 }
875 }
863 return data
876 return data
864
877
865 @LoginRequired()
878 @LoginRequired()
866 @NotAnonymous()
879 @NotAnonymous()
867 @HasRepoPermissionAnyDecorator(
880 @HasRepoPermissionAnyDecorator(
868 'repository.read', 'repository.write', 'repository.admin')
881 'repository.read', 'repository.write', 'repository.admin')
869 @CSRFRequired()
882 @CSRFRequired()
870 @view_config(
883 @view_config(
871 route_name='pullrequest_create', request_method='POST',
884 route_name='pullrequest_create', request_method='POST',
872 renderer=None)
885 renderer=None)
873 def pull_request_create(self):
886 def pull_request_create(self):
874 _ = self.request.translate
887 _ = self.request.translate
875 self.assure_not_empty_repo()
888 self.assure_not_empty_repo()
876 self.load_default_context()
889 self.load_default_context()
877
890
878 controls = peppercorn.parse(self.request.POST.items())
891 controls = peppercorn.parse(self.request.POST.items())
879
892
880 try:
893 try:
881 form = PullRequestForm(
894 form = PullRequestForm(
882 self.request.translate, self.db_repo.repo_id)()
895 self.request.translate, self.db_repo.repo_id)()
883 _form = form.to_python(controls)
896 _form = form.to_python(controls)
884 except formencode.Invalid as errors:
897 except formencode.Invalid as errors:
885 if errors.error_dict.get('revisions'):
898 if errors.error_dict.get('revisions'):
886 msg = 'Revisions: %s' % errors.error_dict['revisions']
899 msg = 'Revisions: %s' % errors.error_dict['revisions']
887 elif errors.error_dict.get('pullrequest_title'):
900 elif errors.error_dict.get('pullrequest_title'):
888 msg = errors.error_dict.get('pullrequest_title')
901 msg = errors.error_dict.get('pullrequest_title')
889 else:
902 else:
890 msg = _('Error creating pull request: {}').format(errors)
903 msg = _('Error creating pull request: {}').format(errors)
891 log.exception(msg)
904 log.exception(msg)
892 h.flash(msg, 'error')
905 h.flash(msg, 'error')
893
906
894 # would rather just go back to form ...
907 # would rather just go back to form ...
895 raise HTTPFound(
908 raise HTTPFound(
896 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
909 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
897
910
898 source_repo = _form['source_repo']
911 source_repo = _form['source_repo']
899 source_ref = _form['source_ref']
912 source_ref = _form['source_ref']
900 target_repo = _form['target_repo']
913 target_repo = _form['target_repo']
901 target_ref = _form['target_ref']
914 target_ref = _form['target_ref']
902 commit_ids = _form['revisions'][::-1]
915 commit_ids = _form['revisions'][::-1]
903
916
904 # find the ancestor for this pr
917 # find the ancestor for this pr
905 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
918 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
906 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
919 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
907
920
908 # re-check permissions again here
921 # re-check permissions again here
909 # source_repo we must have read permissions
922 # source_repo we must have read permissions
910
923
911 source_perm = HasRepoPermissionAny(
924 source_perm = HasRepoPermissionAny(
912 'repository.read',
925 'repository.read',
913 'repository.write', 'repository.admin')(source_db_repo.repo_name)
926 'repository.write', 'repository.admin')(source_db_repo.repo_name)
914 if not source_perm:
927 if not source_perm:
915 msg = _('Not Enough permissions to source repo `{}`.'.format(
928 msg = _('Not Enough permissions to source repo `{}`.'.format(
916 source_db_repo.repo_name))
929 source_db_repo.repo_name))
917 h.flash(msg, category='error')
930 h.flash(msg, category='error')
918 # copy the args back to redirect
931 # copy the args back to redirect
919 org_query = self.request.GET.mixed()
932 org_query = self.request.GET.mixed()
920 raise HTTPFound(
933 raise HTTPFound(
921 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
934 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
922 _query=org_query))
935 _query=org_query))
923
936
924 # target repo we must have read permissions, and also later on
937 # target repo we must have read permissions, and also later on
925 # we want to check branch permissions here
938 # we want to check branch permissions here
926 target_perm = HasRepoPermissionAny(
939 target_perm = HasRepoPermissionAny(
927 'repository.read',
940 'repository.read',
928 'repository.write', 'repository.admin')(target_db_repo.repo_name)
941 'repository.write', 'repository.admin')(target_db_repo.repo_name)
929 if not target_perm:
942 if not target_perm:
930 msg = _('Not Enough permissions to target repo `{}`.'.format(
943 msg = _('Not Enough permissions to target repo `{}`.'.format(
931 target_db_repo.repo_name))
944 target_db_repo.repo_name))
932 h.flash(msg, category='error')
945 h.flash(msg, category='error')
933 # copy the args back to redirect
946 # copy the args back to redirect
934 org_query = self.request.GET.mixed()
947 org_query = self.request.GET.mixed()
935 raise HTTPFound(
948 raise HTTPFound(
936 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
949 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
937 _query=org_query))
950 _query=org_query))
938
951
939 source_scm = source_db_repo.scm_instance()
952 source_scm = source_db_repo.scm_instance()
940 target_scm = target_db_repo.scm_instance()
953 target_scm = target_db_repo.scm_instance()
941
954
942 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
955 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
943 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
956 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
944
957
945 ancestor = source_scm.get_common_ancestor(
958 ancestor = source_scm.get_common_ancestor(
946 source_commit.raw_id, target_commit.raw_id, target_scm)
959 source_commit.raw_id, target_commit.raw_id, target_scm)
947
960
948 # recalculate target ref based on ancestor
961 # recalculate target ref based on ancestor
949 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
962 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
950 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
963 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
951
964
952 get_default_reviewers_data, validate_default_reviewers = \
965 get_default_reviewers_data, validate_default_reviewers = \
953 PullRequestModel().get_reviewer_functions()
966 PullRequestModel().get_reviewer_functions()
954
967
955 # recalculate reviewers logic, to make sure we can validate this
968 # recalculate reviewers logic, to make sure we can validate this
956 reviewer_rules = get_default_reviewers_data(
969 reviewer_rules = get_default_reviewers_data(
957 self._rhodecode_db_user, source_db_repo,
970 self._rhodecode_db_user, source_db_repo,
958 source_commit, target_db_repo, target_commit)
971 source_commit, target_db_repo, target_commit)
959
972
960 given_reviewers = _form['review_members']
973 given_reviewers = _form['review_members']
961 reviewers = validate_default_reviewers(
974 reviewers = validate_default_reviewers(
962 given_reviewers, reviewer_rules)
975 given_reviewers, reviewer_rules)
963
976
964 pullrequest_title = _form['pullrequest_title']
977 pullrequest_title = _form['pullrequest_title']
965 title_source_ref = source_ref.split(':', 2)[1]
978 title_source_ref = source_ref.split(':', 2)[1]
966 if not pullrequest_title:
979 if not pullrequest_title:
967 pullrequest_title = PullRequestModel().generate_pullrequest_title(
980 pullrequest_title = PullRequestModel().generate_pullrequest_title(
968 source=source_repo,
981 source=source_repo,
969 source_ref=title_source_ref,
982 source_ref=title_source_ref,
970 target=target_repo
983 target=target_repo
971 )
984 )
972
985
973 description = _form['pullrequest_desc']
986 description = _form['pullrequest_desc']
974 description_renderer = _form['description_renderer']
987 description_renderer = _form['description_renderer']
975
988
976 try:
989 try:
977 pull_request = PullRequestModel().create(
990 pull_request = PullRequestModel().create(
978 created_by=self._rhodecode_user.user_id,
991 created_by=self._rhodecode_user.user_id,
979 source_repo=source_repo,
992 source_repo=source_repo,
980 source_ref=source_ref,
993 source_ref=source_ref,
981 target_repo=target_repo,
994 target_repo=target_repo,
982 target_ref=target_ref,
995 target_ref=target_ref,
983 revisions=commit_ids,
996 revisions=commit_ids,
984 reviewers=reviewers,
997 reviewers=reviewers,
985 title=pullrequest_title,
998 title=pullrequest_title,
986 description=description,
999 description=description,
987 description_renderer=description_renderer,
1000 description_renderer=description_renderer,
988 reviewer_data=reviewer_rules,
1001 reviewer_data=reviewer_rules,
989 auth_user=self._rhodecode_user
1002 auth_user=self._rhodecode_user
990 )
1003 )
991 Session().commit()
1004 Session().commit()
992
1005
993 h.flash(_('Successfully opened new pull request'),
1006 h.flash(_('Successfully opened new pull request'),
994 category='success')
1007 category='success')
995 except Exception:
1008 except Exception:
996 msg = _('Error occurred during creation of this pull request.')
1009 msg = _('Error occurred during creation of this pull request.')
997 log.exception(msg)
1010 log.exception(msg)
998 h.flash(msg, category='error')
1011 h.flash(msg, category='error')
999
1012
1000 # copy the args back to redirect
1013 # copy the args back to redirect
1001 org_query = self.request.GET.mixed()
1014 org_query = self.request.GET.mixed()
1002 raise HTTPFound(
1015 raise HTTPFound(
1003 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1016 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1004 _query=org_query))
1017 _query=org_query))
1005
1018
1006 raise HTTPFound(
1019 raise HTTPFound(
1007 h.route_path('pullrequest_show', repo_name=target_repo,
1020 h.route_path('pullrequest_show', repo_name=target_repo,
1008 pull_request_id=pull_request.pull_request_id))
1021 pull_request_id=pull_request.pull_request_id))
1009
1022
1010 @LoginRequired()
1023 @LoginRequired()
1011 @NotAnonymous()
1024 @NotAnonymous()
1012 @HasRepoPermissionAnyDecorator(
1025 @HasRepoPermissionAnyDecorator(
1013 'repository.read', 'repository.write', 'repository.admin')
1026 'repository.read', 'repository.write', 'repository.admin')
1014 @CSRFRequired()
1027 @CSRFRequired()
1015 @view_config(
1028 @view_config(
1016 route_name='pullrequest_update', request_method='POST',
1029 route_name='pullrequest_update', request_method='POST',
1017 renderer='json_ext')
1030 renderer='json_ext')
1018 def pull_request_update(self):
1031 def pull_request_update(self):
1019 pull_request = PullRequest.get_or_404(
1032 pull_request = PullRequest.get_or_404(
1020 self.request.matchdict['pull_request_id'])
1033 self.request.matchdict['pull_request_id'])
1021 _ = self.request.translate
1034 _ = self.request.translate
1022
1035
1023 self.load_default_context()
1036 self.load_default_context()
1024
1037
1025 if pull_request.is_closed():
1038 if pull_request.is_closed():
1026 log.debug('update: forbidden because pull request is closed')
1039 log.debug('update: forbidden because pull request is closed')
1027 msg = _(u'Cannot update closed pull requests.')
1040 msg = _(u'Cannot update closed pull requests.')
1028 h.flash(msg, category='error')
1041 h.flash(msg, category='error')
1029 return True
1042 return True
1030
1043
1031 # only owner or admin can update it
1044 # only owner or admin can update it
1032 allowed_to_update = PullRequestModel().check_user_update(
1045 allowed_to_update = PullRequestModel().check_user_update(
1033 pull_request, self._rhodecode_user)
1046 pull_request, self._rhodecode_user)
1034 if allowed_to_update:
1047 if allowed_to_update:
1035 controls = peppercorn.parse(self.request.POST.items())
1048 controls = peppercorn.parse(self.request.POST.items())
1036
1049
1037 if 'review_members' in controls:
1050 if 'review_members' in controls:
1038 self._update_reviewers(
1051 self._update_reviewers(
1039 pull_request, controls['review_members'],
1052 pull_request, controls['review_members'],
1040 pull_request.reviewer_data)
1053 pull_request.reviewer_data)
1041 elif str2bool(self.request.POST.get('update_commits', 'false')):
1054 elif str2bool(self.request.POST.get('update_commits', 'false')):
1042 self._update_commits(pull_request)
1055 self._update_commits(pull_request)
1043 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1056 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1044 self._edit_pull_request(pull_request)
1057 self._edit_pull_request(pull_request)
1045 else:
1058 else:
1046 raise HTTPBadRequest()
1059 raise HTTPBadRequest()
1047 return True
1060 return True
1048 raise HTTPForbidden()
1061 raise HTTPForbidden()
1049
1062
1050 def _edit_pull_request(self, pull_request):
1063 def _edit_pull_request(self, pull_request):
1051 _ = self.request.translate
1064 _ = self.request.translate
1052
1065
1053 try:
1066 try:
1054 PullRequestModel().edit(
1067 PullRequestModel().edit(
1055 pull_request,
1068 pull_request,
1056 self.request.POST.get('title'),
1069 self.request.POST.get('title'),
1057 self.request.POST.get('description'),
1070 self.request.POST.get('description'),
1058 self.request.POST.get('description_renderer'),
1071 self.request.POST.get('description_renderer'),
1059 self._rhodecode_user)
1072 self._rhodecode_user)
1060 except ValueError:
1073 except ValueError:
1061 msg = _(u'Cannot update closed pull requests.')
1074 msg = _(u'Cannot update closed pull requests.')
1062 h.flash(msg, category='error')
1075 h.flash(msg, category='error')
1063 return
1076 return
1064 else:
1077 else:
1065 Session().commit()
1078 Session().commit()
1066
1079
1067 msg = _(u'Pull request title & description updated.')
1080 msg = _(u'Pull request title & description updated.')
1068 h.flash(msg, category='success')
1081 h.flash(msg, category='success')
1069 return
1082 return
1070
1083
1071 def _update_commits(self, pull_request):
1084 def _update_commits(self, pull_request):
1072 _ = self.request.translate
1085 _ = self.request.translate
1073 resp = PullRequestModel().update_commits(pull_request)
1086 resp = PullRequestModel().update_commits(pull_request)
1074
1087
1075 if resp.executed:
1088 if resp.executed:
1076
1089
1077 if resp.target_changed and resp.source_changed:
1090 if resp.target_changed and resp.source_changed:
1078 changed = 'target and source repositories'
1091 changed = 'target and source repositories'
1079 elif resp.target_changed and not resp.source_changed:
1092 elif resp.target_changed and not resp.source_changed:
1080 changed = 'target repository'
1093 changed = 'target repository'
1081 elif not resp.target_changed and resp.source_changed:
1094 elif not resp.target_changed and resp.source_changed:
1082 changed = 'source repository'
1095 changed = 'source repository'
1083 else:
1096 else:
1084 changed = 'nothing'
1097 changed = 'nothing'
1085
1098
1086 msg = _(
1099 msg = _(
1087 u'Pull request updated to "{source_commit_id}" with '
1100 u'Pull request updated to "{source_commit_id}" with '
1088 u'{count_added} added, {count_removed} removed commits. '
1101 u'{count_added} added, {count_removed} removed commits. '
1089 u'Source of changes: {change_source}')
1102 u'Source of changes: {change_source}')
1090 msg = msg.format(
1103 msg = msg.format(
1091 source_commit_id=pull_request.source_ref_parts.commit_id,
1104 source_commit_id=pull_request.source_ref_parts.commit_id,
1092 count_added=len(resp.changes.added),
1105 count_added=len(resp.changes.added),
1093 count_removed=len(resp.changes.removed),
1106 count_removed=len(resp.changes.removed),
1094 change_source=changed)
1107 change_source=changed)
1095 h.flash(msg, category='success')
1108 h.flash(msg, category='success')
1096
1109
1097 channel = '/repo${}$/pr/{}'.format(
1110 channel = '/repo${}$/pr/{}'.format(
1098 pull_request.target_repo.repo_name,
1111 pull_request.target_repo.repo_name,
1099 pull_request.pull_request_id)
1112 pull_request.pull_request_id)
1100 message = msg + (
1113 message = msg + (
1101 ' - <a onclick="window.location.reload()">'
1114 ' - <a onclick="window.location.reload()">'
1102 '<strong>{}</strong></a>'.format(_('Reload page')))
1115 '<strong>{}</strong></a>'.format(_('Reload page')))
1103 channelstream.post_message(
1116 channelstream.post_message(
1104 channel, message, self._rhodecode_user.username,
1117 channel, message, self._rhodecode_user.username,
1105 registry=self.request.registry)
1118 registry=self.request.registry)
1106 else:
1119 else:
1107 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1120 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1108 warning_reasons = [
1121 warning_reasons = [
1109 UpdateFailureReason.NO_CHANGE,
1122 UpdateFailureReason.NO_CHANGE,
1110 UpdateFailureReason.WRONG_REF_TYPE,
1123 UpdateFailureReason.WRONG_REF_TYPE,
1111 ]
1124 ]
1112 category = 'warning' if resp.reason in warning_reasons else 'error'
1125 category = 'warning' if resp.reason in warning_reasons else 'error'
1113 h.flash(msg, category=category)
1126 h.flash(msg, category=category)
1114
1127
1115 @LoginRequired()
1128 @LoginRequired()
1116 @NotAnonymous()
1129 @NotAnonymous()
1117 @HasRepoPermissionAnyDecorator(
1130 @HasRepoPermissionAnyDecorator(
1118 'repository.read', 'repository.write', 'repository.admin')
1131 'repository.read', 'repository.write', 'repository.admin')
1119 @CSRFRequired()
1132 @CSRFRequired()
1120 @view_config(
1133 @view_config(
1121 route_name='pullrequest_merge', request_method='POST',
1134 route_name='pullrequest_merge', request_method='POST',
1122 renderer='json_ext')
1135 renderer='json_ext')
1123 def pull_request_merge(self):
1136 def pull_request_merge(self):
1124 """
1137 """
1125 Merge will perform a server-side merge of the specified
1138 Merge will perform a server-side merge of the specified
1126 pull request, if the pull request is approved and mergeable.
1139 pull request, if the pull request is approved and mergeable.
1127 After successful merging, the pull request is automatically
1140 After successful merging, the pull request is automatically
1128 closed, with a relevant comment.
1141 closed, with a relevant comment.
1129 """
1142 """
1130 pull_request = PullRequest.get_or_404(
1143 pull_request = PullRequest.get_or_404(
1131 self.request.matchdict['pull_request_id'])
1144 self.request.matchdict['pull_request_id'])
1132
1145
1133 self.load_default_context()
1146 self.load_default_context()
1134 check = MergeCheck.validate(
1147 check = MergeCheck.validate(
1135 pull_request, auth_user=self._rhodecode_user,
1148 pull_request, auth_user=self._rhodecode_user,
1136 translator=self.request.translate)
1149 translator=self.request.translate)
1137 merge_possible = not check.failed
1150 merge_possible = not check.failed
1138
1151
1139 for err_type, error_msg in check.errors:
1152 for err_type, error_msg in check.errors:
1140 h.flash(error_msg, category=err_type)
1153 h.flash(error_msg, category=err_type)
1141
1154
1142 if merge_possible:
1155 if merge_possible:
1143 log.debug("Pre-conditions checked, trying to merge.")
1156 log.debug("Pre-conditions checked, trying to merge.")
1144 extras = vcs_operation_context(
1157 extras = vcs_operation_context(
1145 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1158 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1146 username=self._rhodecode_db_user.username, action='push',
1159 username=self._rhodecode_db_user.username, action='push',
1147 scm=pull_request.target_repo.repo_type)
1160 scm=pull_request.target_repo.repo_type)
1148 self._merge_pull_request(
1161 self._merge_pull_request(
1149 pull_request, self._rhodecode_db_user, extras)
1162 pull_request, self._rhodecode_db_user, extras)
1150 else:
1163 else:
1151 log.debug("Pre-conditions failed, NOT merging.")
1164 log.debug("Pre-conditions failed, NOT merging.")
1152
1165
1153 raise HTTPFound(
1166 raise HTTPFound(
1154 h.route_path('pullrequest_show',
1167 h.route_path('pullrequest_show',
1155 repo_name=pull_request.target_repo.repo_name,
1168 repo_name=pull_request.target_repo.repo_name,
1156 pull_request_id=pull_request.pull_request_id))
1169 pull_request_id=pull_request.pull_request_id))
1157
1170
1158 def _merge_pull_request(self, pull_request, user, extras):
1171 def _merge_pull_request(self, pull_request, user, extras):
1159 _ = self.request.translate
1172 _ = self.request.translate
1160 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1173 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1161
1174
1162 if merge_resp.executed:
1175 if merge_resp.executed:
1163 log.debug("The merge was successful, closing the pull request.")
1176 log.debug("The merge was successful, closing the pull request.")
1164 PullRequestModel().close_pull_request(
1177 PullRequestModel().close_pull_request(
1165 pull_request.pull_request_id, user)
1178 pull_request.pull_request_id, user)
1166 Session().commit()
1179 Session().commit()
1167 msg = _('Pull request was successfully merged and closed.')
1180 msg = _('Pull request was successfully merged and closed.')
1168 h.flash(msg, category='success')
1181 h.flash(msg, category='success')
1169 else:
1182 else:
1170 log.debug(
1183 log.debug(
1171 "The merge was not successful. Merge response: %s",
1184 "The merge was not successful. Merge response: %s",
1172 merge_resp)
1185 merge_resp)
1173 msg = PullRequestModel().merge_status_message(
1186 msg = PullRequestModel().merge_status_message(
1174 merge_resp.failure_reason)
1187 merge_resp.failure_reason)
1175 h.flash(msg, category='error')
1188 h.flash(msg, category='error')
1176
1189
1177 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1190 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1178 _ = self.request.translate
1191 _ = self.request.translate
1179 get_default_reviewers_data, validate_default_reviewers = \
1192 get_default_reviewers_data, validate_default_reviewers = \
1180 PullRequestModel().get_reviewer_functions()
1193 PullRequestModel().get_reviewer_functions()
1181
1194
1182 try:
1195 try:
1183 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1196 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1184 except ValueError as e:
1197 except ValueError as e:
1185 log.error('Reviewers Validation: {}'.format(e))
1198 log.error('Reviewers Validation: {}'.format(e))
1186 h.flash(e, category='error')
1199 h.flash(e, category='error')
1187 return
1200 return
1188
1201
1189 PullRequestModel().update_reviewers(
1202 PullRequestModel().update_reviewers(
1190 pull_request, reviewers, self._rhodecode_user)
1203 pull_request, reviewers, self._rhodecode_user)
1191 h.flash(_('Pull request reviewers updated.'), category='success')
1204 h.flash(_('Pull request reviewers updated.'), category='success')
1192 Session().commit()
1205 Session().commit()
1193
1206
1194 @LoginRequired()
1207 @LoginRequired()
1195 @NotAnonymous()
1208 @NotAnonymous()
1196 @HasRepoPermissionAnyDecorator(
1209 @HasRepoPermissionAnyDecorator(
1197 'repository.read', 'repository.write', 'repository.admin')
1210 'repository.read', 'repository.write', 'repository.admin')
1198 @CSRFRequired()
1211 @CSRFRequired()
1199 @view_config(
1212 @view_config(
1200 route_name='pullrequest_delete', request_method='POST',
1213 route_name='pullrequest_delete', request_method='POST',
1201 renderer='json_ext')
1214 renderer='json_ext')
1202 def pull_request_delete(self):
1215 def pull_request_delete(self):
1203 _ = self.request.translate
1216 _ = self.request.translate
1204
1217
1205 pull_request = PullRequest.get_or_404(
1218 pull_request = PullRequest.get_or_404(
1206 self.request.matchdict['pull_request_id'])
1219 self.request.matchdict['pull_request_id'])
1207 self.load_default_context()
1220 self.load_default_context()
1208
1221
1209 pr_closed = pull_request.is_closed()
1222 pr_closed = pull_request.is_closed()
1210 allowed_to_delete = PullRequestModel().check_user_delete(
1223 allowed_to_delete = PullRequestModel().check_user_delete(
1211 pull_request, self._rhodecode_user) and not pr_closed
1224 pull_request, self._rhodecode_user) and not pr_closed
1212
1225
1213 # only owner can delete it !
1226 # only owner can delete it !
1214 if allowed_to_delete:
1227 if allowed_to_delete:
1215 PullRequestModel().delete(pull_request, self._rhodecode_user)
1228 PullRequestModel().delete(pull_request, self._rhodecode_user)
1216 Session().commit()
1229 Session().commit()
1217 h.flash(_('Successfully deleted pull request'),
1230 h.flash(_('Successfully deleted pull request'),
1218 category='success')
1231 category='success')
1219 raise HTTPFound(h.route_path('pullrequest_show_all',
1232 raise HTTPFound(h.route_path('pullrequest_show_all',
1220 repo_name=self.db_repo_name))
1233 repo_name=self.db_repo_name))
1221
1234
1222 log.warning('user %s tried to delete pull request without access',
1235 log.warning('user %s tried to delete pull request without access',
1223 self._rhodecode_user)
1236 self._rhodecode_user)
1224 raise HTTPNotFound()
1237 raise HTTPNotFound()
1225
1238
1226 @LoginRequired()
1239 @LoginRequired()
1227 @NotAnonymous()
1240 @NotAnonymous()
1228 @HasRepoPermissionAnyDecorator(
1241 @HasRepoPermissionAnyDecorator(
1229 'repository.read', 'repository.write', 'repository.admin')
1242 'repository.read', 'repository.write', 'repository.admin')
1230 @CSRFRequired()
1243 @CSRFRequired()
1231 @view_config(
1244 @view_config(
1232 route_name='pullrequest_comment_create', request_method='POST',
1245 route_name='pullrequest_comment_create', request_method='POST',
1233 renderer='json_ext')
1246 renderer='json_ext')
1234 def pull_request_comment_create(self):
1247 def pull_request_comment_create(self):
1235 _ = self.request.translate
1248 _ = self.request.translate
1236
1249
1237 pull_request = PullRequest.get_or_404(
1250 pull_request = PullRequest.get_or_404(
1238 self.request.matchdict['pull_request_id'])
1251 self.request.matchdict['pull_request_id'])
1239 pull_request_id = pull_request.pull_request_id
1252 pull_request_id = pull_request.pull_request_id
1240
1253
1241 if pull_request.is_closed():
1254 if pull_request.is_closed():
1242 log.debug('comment: forbidden because pull request is closed')
1255 log.debug('comment: forbidden because pull request is closed')
1243 raise HTTPForbidden()
1256 raise HTTPForbidden()
1244
1257
1245 allowed_to_comment = PullRequestModel().check_user_comment(
1258 allowed_to_comment = PullRequestModel().check_user_comment(
1246 pull_request, self._rhodecode_user)
1259 pull_request, self._rhodecode_user)
1247 if not allowed_to_comment:
1260 if not allowed_to_comment:
1248 log.debug(
1261 log.debug(
1249 'comment: forbidden because pull request is from forbidden repo')
1262 'comment: forbidden because pull request is from forbidden repo')
1250 raise HTTPForbidden()
1263 raise HTTPForbidden()
1251
1264
1252 c = self.load_default_context()
1265 c = self.load_default_context()
1253
1266
1254 status = self.request.POST.get('changeset_status', None)
1267 status = self.request.POST.get('changeset_status', None)
1255 text = self.request.POST.get('text')
1268 text = self.request.POST.get('text')
1256 comment_type = self.request.POST.get('comment_type')
1269 comment_type = self.request.POST.get('comment_type')
1257 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1270 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1258 close_pull_request = self.request.POST.get('close_pull_request')
1271 close_pull_request = self.request.POST.get('close_pull_request')
1259
1272
1260 # the logic here should work like following, if we submit close
1273 # the logic here should work like following, if we submit close
1261 # pr comment, use `close_pull_request_with_comment` function
1274 # pr comment, use `close_pull_request_with_comment` function
1262 # else handle regular comment logic
1275 # else handle regular comment logic
1263
1276
1264 if close_pull_request:
1277 if close_pull_request:
1265 # only owner or admin or person with write permissions
1278 # only owner or admin or person with write permissions
1266 allowed_to_close = PullRequestModel().check_user_update(
1279 allowed_to_close = PullRequestModel().check_user_update(
1267 pull_request, self._rhodecode_user)
1280 pull_request, self._rhodecode_user)
1268 if not allowed_to_close:
1281 if not allowed_to_close:
1269 log.debug('comment: forbidden because not allowed to close '
1282 log.debug('comment: forbidden because not allowed to close '
1270 'pull request %s', pull_request_id)
1283 'pull request %s', pull_request_id)
1271 raise HTTPForbidden()
1284 raise HTTPForbidden()
1272 comment, status = PullRequestModel().close_pull_request_with_comment(
1285 comment, status = PullRequestModel().close_pull_request_with_comment(
1273 pull_request, self._rhodecode_user, self.db_repo, message=text,
1286 pull_request, self._rhodecode_user, self.db_repo, message=text,
1274 auth_user=self._rhodecode_user)
1287 auth_user=self._rhodecode_user)
1275 Session().flush()
1288 Session().flush()
1276 events.trigger(
1289 events.trigger(
1277 events.PullRequestCommentEvent(pull_request, comment))
1290 events.PullRequestCommentEvent(pull_request, comment))
1278
1291
1279 else:
1292 else:
1280 # regular comment case, could be inline, or one with status.
1293 # regular comment case, could be inline, or one with status.
1281 # for that one we check also permissions
1294 # for that one we check also permissions
1282
1295
1283 allowed_to_change_status = PullRequestModel().check_user_change_status(
1296 allowed_to_change_status = PullRequestModel().check_user_change_status(
1284 pull_request, self._rhodecode_user)
1297 pull_request, self._rhodecode_user)
1285
1298
1286 if status and allowed_to_change_status:
1299 if status and allowed_to_change_status:
1287 message = (_('Status change %(transition_icon)s %(status)s')
1300 message = (_('Status change %(transition_icon)s %(status)s')
1288 % {'transition_icon': '>',
1301 % {'transition_icon': '>',
1289 'status': ChangesetStatus.get_status_lbl(status)})
1302 'status': ChangesetStatus.get_status_lbl(status)})
1290 text = text or message
1303 text = text or message
1291
1304
1292 comment = CommentsModel().create(
1305 comment = CommentsModel().create(
1293 text=text,
1306 text=text,
1294 repo=self.db_repo.repo_id,
1307 repo=self.db_repo.repo_id,
1295 user=self._rhodecode_user.user_id,
1308 user=self._rhodecode_user.user_id,
1296 pull_request=pull_request,
1309 pull_request=pull_request,
1297 f_path=self.request.POST.get('f_path'),
1310 f_path=self.request.POST.get('f_path'),
1298 line_no=self.request.POST.get('line'),
1311 line_no=self.request.POST.get('line'),
1299 status_change=(ChangesetStatus.get_status_lbl(status)
1312 status_change=(ChangesetStatus.get_status_lbl(status)
1300 if status and allowed_to_change_status else None),
1313 if status and allowed_to_change_status else None),
1301 status_change_type=(status
1314 status_change_type=(status
1302 if status and allowed_to_change_status else None),
1315 if status and allowed_to_change_status else None),
1303 comment_type=comment_type,
1316 comment_type=comment_type,
1304 resolves_comment_id=resolves_comment_id,
1317 resolves_comment_id=resolves_comment_id,
1305 auth_user=self._rhodecode_user
1318 auth_user=self._rhodecode_user
1306 )
1319 )
1307
1320
1308 if allowed_to_change_status:
1321 if allowed_to_change_status:
1309 # calculate old status before we change it
1322 # calculate old status before we change it
1310 old_calculated_status = pull_request.calculated_review_status()
1323 old_calculated_status = pull_request.calculated_review_status()
1311
1324
1312 # get status if set !
1325 # get status if set !
1313 if status:
1326 if status:
1314 ChangesetStatusModel().set_status(
1327 ChangesetStatusModel().set_status(
1315 self.db_repo.repo_id,
1328 self.db_repo.repo_id,
1316 status,
1329 status,
1317 self._rhodecode_user.user_id,
1330 self._rhodecode_user.user_id,
1318 comment,
1331 comment,
1319 pull_request=pull_request
1332 pull_request=pull_request
1320 )
1333 )
1321
1334
1322 Session().flush()
1335 Session().flush()
1323 # this is somehow required to get access to some relationship
1336 # this is somehow required to get access to some relationship
1324 # loaded on comment
1337 # loaded on comment
1325 Session().refresh(comment)
1338 Session().refresh(comment)
1326
1339
1327 events.trigger(
1340 events.trigger(
1328 events.PullRequestCommentEvent(pull_request, comment))
1341 events.PullRequestCommentEvent(pull_request, comment))
1329
1342
1330 # we now calculate the status of pull request, and based on that
1343 # we now calculate the status of pull request, and based on that
1331 # calculation we set the commits status
1344 # calculation we set the commits status
1332 calculated_status = pull_request.calculated_review_status()
1345 calculated_status = pull_request.calculated_review_status()
1333 if old_calculated_status != calculated_status:
1346 if old_calculated_status != calculated_status:
1334 PullRequestModel()._trigger_pull_request_hook(
1347 PullRequestModel()._trigger_pull_request_hook(
1335 pull_request, self._rhodecode_user, 'review_status_change')
1348 pull_request, self._rhodecode_user, 'review_status_change')
1336
1349
1337 Session().commit()
1350 Session().commit()
1338
1351
1339 data = {
1352 data = {
1340 'target_id': h.safeid(h.safe_unicode(
1353 'target_id': h.safeid(h.safe_unicode(
1341 self.request.POST.get('f_path'))),
1354 self.request.POST.get('f_path'))),
1342 }
1355 }
1343 if comment:
1356 if comment:
1344 c.co = comment
1357 c.co = comment
1345 rendered_comment = render(
1358 rendered_comment = render(
1346 'rhodecode:templates/changeset/changeset_comment_block.mako',
1359 'rhodecode:templates/changeset/changeset_comment_block.mako',
1347 self._get_template_context(c), self.request)
1360 self._get_template_context(c), self.request)
1348
1361
1349 data.update(comment.get_dict())
1362 data.update(comment.get_dict())
1350 data.update({'rendered_text': rendered_comment})
1363 data.update({'rendered_text': rendered_comment})
1351
1364
1352 return data
1365 return data
1353
1366
1354 @LoginRequired()
1367 @LoginRequired()
1355 @NotAnonymous()
1368 @NotAnonymous()
1356 @HasRepoPermissionAnyDecorator(
1369 @HasRepoPermissionAnyDecorator(
1357 'repository.read', 'repository.write', 'repository.admin')
1370 'repository.read', 'repository.write', 'repository.admin')
1358 @CSRFRequired()
1371 @CSRFRequired()
1359 @view_config(
1372 @view_config(
1360 route_name='pullrequest_comment_delete', request_method='POST',
1373 route_name='pullrequest_comment_delete', request_method='POST',
1361 renderer='json_ext')
1374 renderer='json_ext')
1362 def pull_request_comment_delete(self):
1375 def pull_request_comment_delete(self):
1363 pull_request = PullRequest.get_or_404(
1376 pull_request = PullRequest.get_or_404(
1364 self.request.matchdict['pull_request_id'])
1377 self.request.matchdict['pull_request_id'])
1365
1378
1366 comment = ChangesetComment.get_or_404(
1379 comment = ChangesetComment.get_or_404(
1367 self.request.matchdict['comment_id'])
1380 self.request.matchdict['comment_id'])
1368 comment_id = comment.comment_id
1381 comment_id = comment.comment_id
1369
1382
1370 if pull_request.is_closed():
1383 if pull_request.is_closed():
1371 log.debug('comment: forbidden because pull request is closed')
1384 log.debug('comment: forbidden because pull request is closed')
1372 raise HTTPForbidden()
1385 raise HTTPForbidden()
1373
1386
1374 if not comment:
1387 if not comment:
1375 log.debug('Comment with id:%s not found, skipping', comment_id)
1388 log.debug('Comment with id:%s not found, skipping', comment_id)
1376 # comment already deleted in another call probably
1389 # comment already deleted in another call probably
1377 return True
1390 return True
1378
1391
1379 if comment.pull_request.is_closed():
1392 if comment.pull_request.is_closed():
1380 # don't allow deleting comments on closed pull request
1393 # don't allow deleting comments on closed pull request
1381 raise HTTPForbidden()
1394 raise HTTPForbidden()
1382
1395
1383 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1396 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1384 super_admin = h.HasPermissionAny('hg.admin')()
1397 super_admin = h.HasPermissionAny('hg.admin')()
1385 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1398 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1386 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1399 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1387 comment_repo_admin = is_repo_admin and is_repo_comment
1400 comment_repo_admin = is_repo_admin and is_repo_comment
1388
1401
1389 if super_admin or comment_owner or comment_repo_admin:
1402 if super_admin or comment_owner or comment_repo_admin:
1390 old_calculated_status = comment.pull_request.calculated_review_status()
1403 old_calculated_status = comment.pull_request.calculated_review_status()
1391 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1404 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1392 Session().commit()
1405 Session().commit()
1393 calculated_status = comment.pull_request.calculated_review_status()
1406 calculated_status = comment.pull_request.calculated_review_status()
1394 if old_calculated_status != calculated_status:
1407 if old_calculated_status != calculated_status:
1395 PullRequestModel()._trigger_pull_request_hook(
1408 PullRequestModel()._trigger_pull_request_hook(
1396 comment.pull_request, self._rhodecode_user, 'review_status_change')
1409 comment.pull_request, self._rhodecode_user, 'review_status_change')
1397 return True
1410 return True
1398 else:
1411 else:
1399 log.warning('No permissions for user %s to delete comment_id: %s',
1412 log.warning('No permissions for user %s to delete comment_id: %s',
1400 self._rhodecode_db_user, comment_id)
1413 self._rhodecode_db_user, comment_id)
1401 raise HTTPNotFound()
1414 raise HTTPNotFound()
@@ -1,336 +1,336 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
83 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
84 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
85 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
86 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
87 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
88 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
89 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
90 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
90 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
91 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
91 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
92 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
92 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
93 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
93 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
94 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
94 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
95 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
95 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
96 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
96 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
97 pyroutes.register('users', '/_admin/users', []);
97 pyroutes.register('users', '/_admin/users', []);
98 pyroutes.register('users_data', '/_admin/users_data', []);
98 pyroutes.register('users_data', '/_admin/users_data', []);
99 pyroutes.register('users_create', '/_admin/users/create', []);
99 pyroutes.register('users_create', '/_admin/users/create', []);
100 pyroutes.register('users_new', '/_admin/users/new', []);
100 pyroutes.register('users_new', '/_admin/users/new', []);
101 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
101 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
102 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
102 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
103 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
103 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
104 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
104 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
105 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
105 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
106 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
106 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
107 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
107 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
108 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
109 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
109 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
110 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
110 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
111 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
116 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
117 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
118 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
119 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
120 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
121 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
122 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
123 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
124 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
125 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
126 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
127 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
128 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
128 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
129 pyroutes.register('user_groups', '/_admin/user_groups', []);
129 pyroutes.register('user_groups', '/_admin/user_groups', []);
130 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
130 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
131 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
131 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
132 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
132 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
133 pyroutes.register('repos', '/_admin/repos', []);
133 pyroutes.register('repos', '/_admin/repos', []);
134 pyroutes.register('repo_new', '/_admin/repos/new', []);
134 pyroutes.register('repo_new', '/_admin/repos/new', []);
135 pyroutes.register('repo_create', '/_admin/repos/create', []);
135 pyroutes.register('repo_create', '/_admin/repos/create', []);
136 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
136 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
137 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
137 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
138 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
138 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
139 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
139 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
140 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
140 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
141 pyroutes.register('channelstream_proxy', '/_channelstream', []);
141 pyroutes.register('channelstream_proxy', '/_channelstream', []);
142 pyroutes.register('login', '/_admin/login', []);
142 pyroutes.register('login', '/_admin/login', []);
143 pyroutes.register('logout', '/_admin/logout', []);
143 pyroutes.register('logout', '/_admin/logout', []);
144 pyroutes.register('register', '/_admin/register', []);
144 pyroutes.register('register', '/_admin/register', []);
145 pyroutes.register('reset_password', '/_admin/password_reset', []);
145 pyroutes.register('reset_password', '/_admin/password_reset', []);
146 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
146 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
147 pyroutes.register('home', '/', []);
147 pyroutes.register('home', '/', []);
148 pyroutes.register('user_autocomplete_data', '/_users', []);
148 pyroutes.register('user_autocomplete_data', '/_users', []);
149 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
149 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
150 pyroutes.register('repo_list_data', '/_repos', []);
150 pyroutes.register('repo_list_data', '/_repos', []);
151 pyroutes.register('goto_switcher_data', '/_goto_data', []);
151 pyroutes.register('goto_switcher_data', '/_goto_data', []);
152 pyroutes.register('markup_preview', '/_markup_preview', []);
152 pyroutes.register('markup_preview', '/_markup_preview', []);
153 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
153 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
154 pyroutes.register('journal', '/_admin/journal', []);
154 pyroutes.register('journal', '/_admin/journal', []);
155 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
155 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
156 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
156 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
157 pyroutes.register('journal_public', '/_admin/public_journal', []);
157 pyroutes.register('journal_public', '/_admin/public_journal', []);
158 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
158 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
159 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
159 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
160 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
160 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
161 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
161 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
162 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
162 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
163 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
163 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
164 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
164 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
165 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
165 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
166 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
166 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
167 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
176 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
177 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
178 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
179 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
179 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
180 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
181 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
183 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
184 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
201 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
202 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
203 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
203 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
204 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
204 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
205 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
206 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
207 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
208 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
209 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
209 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
210 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
210 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
211 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
211 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
212 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
212 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
213 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
213 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
214 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
214 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
215 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
215 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
216 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
216 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
217 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
217 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
218 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
218 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
219 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
219 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
220 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
220 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
221 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
221 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
222 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
222 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
223 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
223 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
224 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
224 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
226 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
227 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
228 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
229 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
229 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
230 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
230 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
235 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
236 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
237 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
237 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
238 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
238 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
239 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
239 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
240 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
240 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
241 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
241 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
242 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
243 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
243 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
244 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
244 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
245 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
245 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
246 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
246 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
247 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
247 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
248 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
248 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
249 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
249 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
250 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
250 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
251 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
251 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
252 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
253 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
254 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
255 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
255 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
256 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
256 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
257 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
258 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
258 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
259 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
259 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
260 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
260 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
261 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
261 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
262 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
262 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
263 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
263 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
264 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
264 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
265 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
265 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
266 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
266 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
267 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
267 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
268 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
268 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
269 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
269 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
270 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
270 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
271 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
272 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
273 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
273 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
274 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
274 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
275 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
275 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
276 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
276 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
277 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
277 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
279 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
279 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
280 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
280 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
281 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
281 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
282 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
282 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
283 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
283 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
284 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
284 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
285 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
285 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
286 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
286 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
287 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
287 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
288 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
288 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
289 pyroutes.register('search', '/_admin/search', []);
289 pyroutes.register('search', '/_admin/search', []);
290 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
290 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
291 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
291 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
292 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
292 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
293 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
293 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
294 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
294 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
295 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
295 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
296 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
296 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
297 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
297 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
298 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
298 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
299 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
299 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
300 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
300 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
301 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
301 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
302 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
302 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
303 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
303 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
304 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
304 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
305 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
305 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
306 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
306 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
307 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
307 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
308 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
308 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
309 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
309 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
310 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
310 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
311 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
311 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
312 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
312 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
313 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
313 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
314 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
314 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
315 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
315 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
316 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
316 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
317 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
317 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
318 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
318 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
319 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
319 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
320 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
320 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
321 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
321 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
322 pyroutes.register('gists_show', '/_admin/gists', []);
322 pyroutes.register('gists_show', '/_admin/gists', []);
323 pyroutes.register('gists_new', '/_admin/gists/new', []);
323 pyroutes.register('gists_new', '/_admin/gists/new', []);
324 pyroutes.register('gists_create', '/_admin/gists/create', []);
324 pyroutes.register('gists_create', '/_admin/gists/create', []);
325 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
325 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
326 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
326 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
327 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
327 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
328 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
328 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
329 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
329 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
330 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
330 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
331 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
331 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
332 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
332 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
333 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
333 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
334 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
334 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
335 pyroutes.register('apiv2', '/_admin/api', []);
335 pyroutes.register('apiv2', '/_admin/api', []);
336 }
336 }
@@ -1,548 +1,548 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('New pull request')}
5 ${c.repo_name} ${_('New pull request')}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('New pull request')}
9 ${_('New pull request')}
10 </%def>
10 </%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='repositories')}
13 ${self.menu_items(active='repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='showpullrequest')}
17 ${self.repo_menu(active='showpullrequest')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.repo_page_title(c.rhodecode_db_repo)}
24 </div>
24 </div>
25
25
26 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
26 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
27
27
28 ${self.breadcrumbs()}
28 ${self.breadcrumbs()}
29
29
30 <div class="box pr-summary">
30 <div class="box pr-summary">
31
31
32 <div class="summary-details block-left">
32 <div class="summary-details block-left">
33
33
34
34
35 <div class="pr-details-title">
35 <div class="pr-details-title">
36 ${_('Pull request summary')}
36 ${_('Pull request summary')}
37 </div>
37 </div>
38
38
39 <div class="form" style="padding-top: 10px">
39 <div class="form" style="padding-top: 10px">
40 <!-- fields -->
40 <!-- fields -->
41
41
42 <div class="fields" >
42 <div class="fields" >
43
43
44 <div class="field">
44 <div class="field">
45 <div class="label">
45 <div class="label">
46 <label for="pullrequest_title">${_('Title')}:</label>
46 <label for="pullrequest_title">${_('Title')}:</label>
47 </div>
47 </div>
48 <div class="input">
48 <div class="input">
49 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div class="field">
53 <div class="field">
54 <div class="label label-textarea">
54 <div class="label label-textarea">
55 <label for="pullrequest_desc">${_('Description')}:</label>
55 <label for="pullrequest_desc">${_('Description')}:</label>
56 </div>
56 </div>
57 <div class="textarea text-area editor">
57 <div class="textarea text-area editor">
58 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
58 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
59 ${dt.markup_form('pullrequest_desc')}
59 ${dt.markup_form('pullrequest_desc')}
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <div class="field">
63 <div class="field">
64 <div class="label label-textarea">
64 <div class="label label-textarea">
65 <label for="commit_flow">${_('Commit flow')}:</label>
65 <label for="commit_flow">${_('Commit flow')}:</label>
66 </div>
66 </div>
67
67
68 ## TODO: johbo: Abusing the "content" class here to get the
68 ## TODO: johbo: Abusing the "content" class here to get the
69 ## desired effect. Should be replaced by a proper solution.
69 ## desired effect. Should be replaced by a proper solution.
70
70
71 ##ORG
71 ##ORG
72 <div class="content">
72 <div class="content">
73 <strong>${_('Source repository')}:</strong>
73 <strong>${_('Source repository')}:</strong>
74 ${c.rhodecode_db_repo.description}
74 ${c.rhodecode_db_repo.description}
75 </div>
75 </div>
76 <div class="content">
76 <div class="content">
77 ${h.hidden('source_repo')}
77 ${h.hidden('source_repo')}
78 ${h.hidden('source_ref')}
78 ${h.hidden('source_ref')}
79 </div>
79 </div>
80
80
81 ##OTHER, most Probably the PARENT OF THIS FORK
81 ##OTHER, most Probably the PARENT OF THIS FORK
82 <div class="content">
82 <div class="content">
83 ## filled with JS
83 ## filled with JS
84 <div id="target_repo_desc"></div>
84 <div id="target_repo_desc"></div>
85 </div>
85 </div>
86
86
87 <div class="content">
87 <div class="content">
88 ${h.hidden('target_repo')}
88 ${h.hidden('target_repo')}
89 ${h.hidden('target_ref')}
89 ${h.hidden('target_ref')}
90 <span id="target_ref_loading" style="display: none">
90 <span id="target_ref_loading" style="display: none">
91 ${_('Loading refs...')}
91 ${_('Loading refs...')}
92 </span>
92 </span>
93 </div>
93 </div>
94 </div>
94 </div>
95
95
96 <div class="field">
96 <div class="field">
97 <div class="label label-textarea">
97 <div class="label label-textarea">
98 <label for="pullrequest_submit"></label>
98 <label for="pullrequest_submit"></label>
99 </div>
99 </div>
100 <div class="input">
100 <div class="input">
101 <div class="pr-submit-button">
101 <div class="pr-submit-button">
102 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
102 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
103 </div>
103 </div>
104 <div id="pr_open_message"></div>
104 <div id="pr_open_message"></div>
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="pr-spacing-container"></div>
108 <div class="pr-spacing-container"></div>
109 </div>
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 <div>
112 <div>
113 ## AUTHOR
113 ## AUTHOR
114 <div class="reviewers-title block-right">
114 <div class="reviewers-title block-right">
115 <div class="pr-details-title">
115 <div class="pr-details-title">
116 ${_('Author of this pull request')}
116 ${_('Author of this pull request')}
117 </div>
117 </div>
118 </div>
118 </div>
119 <div class="block-right pr-details-content reviewers">
119 <div class="block-right pr-details-content reviewers">
120 <ul class="group_members">
120 <ul class="group_members">
121 <li>
121 <li>
122 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
123 </li>
123 </li>
124 </ul>
124 </ul>
125 </div>
125 </div>
126
126
127 ## REVIEW RULES
127 ## REVIEW RULES
128 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div id="review_rules" style="display: none" class="reviewers-title block-right">
129 <div class="pr-details-title">
129 <div class="pr-details-title">
130 ${_('Reviewer rules')}
130 ${_('Reviewer rules')}
131 </div>
131 </div>
132 <div class="pr-reviewer-rules">
132 <div class="pr-reviewer-rules">
133 ## review rules will be appended here, by default reviewers logic
133 ## review rules will be appended here, by default reviewers logic
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 ## REVIEWERS
137 ## REVIEWERS
138 <div class="reviewers-title block-right">
138 <div class="reviewers-title block-right">
139 <div class="pr-details-title">
139 <div class="pr-details-title">
140 ${_('Pull request reviewers')}
140 ${_('Pull request reviewers')}
141 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 <span class="calculate-reviewers"> - ${_('loading...')}</span>
142 </div>
142 </div>
143 </div>
143 </div>
144 <div id="reviewers" class="block-right pr-details-content reviewers">
144 <div id="reviewers" class="block-right pr-details-content reviewers">
145 ## members goes here, filled via JS based on initial selection !
145 ## members goes here, filled via JS based on initial selection !
146 <input type="hidden" name="__start__" value="review_members:sequence">
146 <input type="hidden" name="__start__" value="review_members:sequence">
147 <ul id="review_members" class="group_members"></ul>
147 <ul id="review_members" class="group_members"></ul>
148 <input type="hidden" name="__end__" value="review_members:sequence">
148 <input type="hidden" name="__end__" value="review_members:sequence">
149 <div id="add_reviewer_input" class='ac'>
149 <div id="add_reviewer_input" class='ac'>
150 <div class="reviewer_ac">
150 <div class="reviewer_ac">
151 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
152 <div id="reviewers_container"></div>
152 <div id="reviewers_container"></div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 </div>
157 </div>
158 <div class="box">
158 <div class="box">
159 <div>
159 <div>
160 ## overview pulled by ajax
160 ## overview pulled by ajax
161 <div id="pull_request_overview"></div>
161 <div id="pull_request_overview"></div>
162 </div>
162 </div>
163 </div>
163 </div>
164 ${h.end_form()}
164 ${h.end_form()}
165 </div>
165 </div>
166
166
167 <script type="text/javascript">
167 <script type="text/javascript">
168 $(function(){
168 $(function(){
169 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
170 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
171 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
172 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
173
173
174 var $pullRequestForm = $('#pull_request_form');
174 var $pullRequestForm = $('#pull_request_form');
175 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
175 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
176 var $sourceRepo = $('#source_repo', $pullRequestForm);
176 var $sourceRepo = $('#source_repo', $pullRequestForm);
177 var $targetRepo = $('#target_repo', $pullRequestForm);
177 var $targetRepo = $('#target_repo', $pullRequestForm);
178 var $sourceRef = $('#source_ref', $pullRequestForm);
178 var $sourceRef = $('#source_ref', $pullRequestForm);
179 var $targetRef = $('#target_ref', $pullRequestForm);
179 var $targetRef = $('#target_ref', $pullRequestForm);
180
180
181 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
181 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
182 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
182 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
183
183
184 var targetRepo = function() { return $targetRepo.eq(0).val() };
184 var targetRepo = function() { return $targetRepo.eq(0).val() };
185 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
185 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
186
186
187 var calculateContainerWidth = function() {
187 var calculateContainerWidth = function() {
188 var maxWidth = 0;
188 var maxWidth = 0;
189 var repoSelect2Containers = ['#source_repo', '#target_repo'];
189 var repoSelect2Containers = ['#source_repo', '#target_repo'];
190 $.each(repoSelect2Containers, function(idx, value) {
190 $.each(repoSelect2Containers, function(idx, value) {
191 $(value).select2('container').width('auto');
191 $(value).select2('container').width('auto');
192 var curWidth = $(value).select2('container').width();
192 var curWidth = $(value).select2('container').width();
193 if (maxWidth <= curWidth) {
193 if (maxWidth <= curWidth) {
194 maxWidth = curWidth;
194 maxWidth = curWidth;
195 }
195 }
196 $.each(repoSelect2Containers, function(idx, value) {
196 $.each(repoSelect2Containers, function(idx, value) {
197 $(value).select2('container').width(maxWidth + 10);
197 $(value).select2('container').width(maxWidth + 10);
198 });
198 });
199 });
199 });
200 };
200 };
201
201
202 var initRefSelection = function(selectedRef) {
202 var initRefSelection = function(selectedRef) {
203 return function(element, callback) {
203 return function(element, callback) {
204 // translate our select2 id into a text, it's a mapping to show
204 // translate our select2 id into a text, it's a mapping to show
205 // simple label when selecting by internal ID.
205 // simple label when selecting by internal ID.
206 var id, refData;
206 var id, refData;
207 if (selectedRef === undefined || selectedRef === null) {
207 if (selectedRef === undefined || selectedRef === null) {
208 id = element.val();
208 id = element.val();
209 refData = element.val().split(':');
209 refData = element.val().split(':');
210
210
211 if (refData.length !== 3){
211 if (refData.length !== 3){
212 refData = ["", "", ""]
212 refData = ["", "", ""]
213 }
213 }
214 } else {
214 } else {
215 id = selectedRef;
215 id = selectedRef;
216 refData = selectedRef.split(':');
216 refData = selectedRef.split(':');
217 }
217 }
218
218
219 var text = refData[1];
219 var text = refData[1];
220 if (refData[0] === 'rev') {
220 if (refData[0] === 'rev') {
221 text = text.substring(0, 12);
221 text = text.substring(0, 12);
222 }
222 }
223
223
224 var data = {id: id, text: text};
224 var data = {id: id, text: text};
225 callback(data);
225 callback(data);
226 };
226 };
227 };
227 };
228
228
229 var formatRefSelection = function(data, container, escapeMarkup) {
229 var formatRefSelection = function(data, container, escapeMarkup) {
230 var prefix = '';
230 var prefix = '';
231 var refData = data.id.split(':');
231 var refData = data.id.split(':');
232 if (refData[0] === 'branch') {
232 if (refData[0] === 'branch') {
233 prefix = '<i class="icon-branch"></i>';
233 prefix = '<i class="icon-branch"></i>';
234 }
234 }
235 else if (refData[0] === 'book') {
235 else if (refData[0] === 'book') {
236 prefix = '<i class="icon-bookmark"></i>';
236 prefix = '<i class="icon-bookmark"></i>';
237 }
237 }
238 else if (refData[0] === 'tag') {
238 else if (refData[0] === 'tag') {
239 prefix = '<i class="icon-tag"></i>';
239 prefix = '<i class="icon-tag"></i>';
240 }
240 }
241
241
242 var originalOption = data.element;
242 var originalOption = data.element;
243 return prefix + escapeMarkup(data.text);
243 return prefix + escapeMarkup(data.text);
244 };formatSelection:
244 };formatSelection:
245
245
246 // custom code mirror
246 // custom code mirror
247 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
247 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
248
248
249 reviewersController = new ReviewersController();
249 reviewersController = new ReviewersController();
250
250
251 var queryTargetRepo = function(self, query) {
251 var queryTargetRepo = function(self, query) {
252 // cache ALL results if query is empty
252 // cache ALL results if query is empty
253 var cacheKey = query.term || '__';
253 var cacheKey = query.term || '__';
254 var cachedData = self.cachedDataSource[cacheKey];
254 var cachedData = self.cachedDataSource[cacheKey];
255
255
256 if (cachedData) {
256 if (cachedData) {
257 query.callback({results: cachedData.results});
257 query.callback({results: cachedData.results});
258 } else {
258 } else {
259 $.ajax({
259 $.ajax({
260 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
260 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
261 data: {query: query.term},
261 data: {query: query.term},
262 dataType: 'json',
262 dataType: 'json',
263 type: 'GET',
263 type: 'GET',
264 success: function(data) {
264 success: function(data) {
265 self.cachedDataSource[cacheKey] = data;
265 self.cachedDataSource[cacheKey] = data;
266 query.callback({results: data.results});
266 query.callback({results: data.results});
267 },
267 },
268 error: function(data, textStatus, errorThrown) {
268 error: function(data, textStatus, errorThrown) {
269 alert(
269 alert(
270 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
270 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
271 }
271 }
272 });
272 });
273 }
273 }
274 };
274 };
275
275
276 var queryTargetRefs = function(initialData, query) {
276 var queryTargetRefs = function(initialData, query) {
277 var data = {results: []};
277 var data = {results: []};
278 // filter initialData
278 // filter initialData
279 $.each(initialData, function() {
279 $.each(initialData, function() {
280 var section = this.text;
280 var section = this.text;
281 var children = [];
281 var children = [];
282 $.each(this.children, function() {
282 $.each(this.children, function() {
283 if (query.term.length === 0 ||
283 if (query.term.length === 0 ||
284 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
284 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
285 children.push({'id': this.id, 'text': this.text})
285 children.push({'id': this.id, 'text': this.text})
286 }
286 }
287 });
287 });
288 data.results.push({'text': section, 'children': children})
288 data.results.push({'text': section, 'children': children})
289 });
289 });
290 query.callback({results: data.results});
290 query.callback({results: data.results});
291 };
291 };
292
292
293 var loadRepoRefDiffPreview = function() {
293 var loadRepoRefDiffPreview = function() {
294
294
295 var url_data = {
295 var url_data = {
296 'repo_name': targetRepo(),
296 'repo_name': targetRepo(),
297 'target_repo': sourceRepo(),
297 'target_repo': sourceRepo(),
298 'source_ref': targetRef()[2],
298 'source_ref': targetRef()[2],
299 'source_ref_type': 'rev',
299 'source_ref_type': 'rev',
300 'target_ref': sourceRef()[2],
300 'target_ref': sourceRef()[2],
301 'target_ref_type': 'rev',
301 'target_ref_type': 'rev',
302 'merge': true,
302 'merge': true,
303 '_': Date.now() // bypass browser caching
303 '_': Date.now() // bypass browser caching
304 }; // gather the source/target ref and repo here
304 }; // gather the source/target ref and repo here
305
305
306 if (sourceRef().length !== 3 || targetRef().length !== 3) {
306 if (sourceRef().length !== 3 || targetRef().length !== 3) {
307 prButtonLock(true, "${_('Please select source and target')}");
307 prButtonLock(true, "${_('Please select source and target')}");
308 return;
308 return;
309 }
309 }
310 var url = pyroutes.url('repo_compare', url_data);
310 var url = pyroutes.url('repo_compare', url_data);
311
311
312 // lock PR button, so we cannot send PR before it's calculated
312 // lock PR button, so we cannot send PR before it's calculated
313 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
313 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
314
314
315 if (loadRepoRefDiffPreview._currentRequest) {
315 if (loadRepoRefDiffPreview._currentRequest) {
316 loadRepoRefDiffPreview._currentRequest.abort();
316 loadRepoRefDiffPreview._currentRequest.abort();
317 }
317 }
318
318
319 loadRepoRefDiffPreview._currentRequest = $.get(url)
319 loadRepoRefDiffPreview._currentRequest = $.get(url)
320 .error(function(data, textStatus, errorThrown) {
320 .error(function(data, textStatus, errorThrown) {
321 if (textStatus !== 'abort') {
321 if (textStatus !== 'abort') {
322 alert(
322 alert(
323 "Error while processing request.\nError code {0} ({1}).".format(
323 "Error while processing request.\nError code {0} ({1}).".format(
324 data.status, data.statusText));
324 data.status, data.statusText));
325 }
325 }
326
326
327 })
327 })
328 .done(function(data) {
328 .done(function(data) {
329 loadRepoRefDiffPreview._currentRequest = null;
329 loadRepoRefDiffPreview._currentRequest = null;
330 $('#pull_request_overview').html(data);
330 $('#pull_request_overview').html(data);
331
331
332 var commitElements = $(data).find('tr[commit_id]');
332 var commitElements = $(data).find('tr[commit_id]');
333
333
334 var prTitleAndDesc = getTitleAndDescription(
334 var prTitleAndDesc = getTitleAndDescription(
335 sourceRef()[1], commitElements, 5);
335 sourceRef()[1], commitElements, 5);
336
336
337 var title = prTitleAndDesc[0];
337 var title = prTitleAndDesc[0];
338 var proposedDescription = prTitleAndDesc[1];
338 var proposedDescription = prTitleAndDesc[1];
339
339
340 var useGeneratedTitle = (
340 var useGeneratedTitle = (
341 $('#pullrequest_title').hasClass('autogenerated-title') ||
341 $('#pullrequest_title').hasClass('autogenerated-title') ||
342 $('#pullrequest_title').val() === "");
342 $('#pullrequest_title').val() === "");
343
343
344 if (title && useGeneratedTitle) {
344 if (title && useGeneratedTitle) {
345 // use generated title if we haven't specified our own
345 // use generated title if we haven't specified our own
346 $('#pullrequest_title').val(title);
346 $('#pullrequest_title').val(title);
347 $('#pullrequest_title').addClass('autogenerated-title');
347 $('#pullrequest_title').addClass('autogenerated-title');
348
348
349 }
349 }
350
350
351 var useGeneratedDescription = (
351 var useGeneratedDescription = (
352 !codeMirrorInstance._userDefinedValue ||
352 !codeMirrorInstance._userDefinedValue ||
353 codeMirrorInstance.getValue() === "");
353 codeMirrorInstance.getValue() === "");
354
354
355 if (proposedDescription && useGeneratedDescription) {
355 if (proposedDescription && useGeneratedDescription) {
356 // set proposed content, if we haven't defined our own,
356 // set proposed content, if we haven't defined our own,
357 // or we don't have description written
357 // or we don't have description written
358 codeMirrorInstance._userDefinedValue = false; // reset state
358 codeMirrorInstance._userDefinedValue = false; // reset state
359 codeMirrorInstance.setValue(proposedDescription);
359 codeMirrorInstance.setValue(proposedDescription);
360 }
360 }
361
361
362 // refresh our codeMirror so events kicks in and it's change aware
362 // refresh our codeMirror so events kicks in and it's change aware
363 codeMirrorInstance.refresh();
363 codeMirrorInstance.refresh();
364
364
365 var msg = '';
365 var msg = '';
366 if (commitElements.length === 1) {
366 if (commitElements.length === 1) {
367 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
367 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
368 } else {
368 } else {
369 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
369 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
370 }
370 }
371
371
372 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
372 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
373
373
374 if (commitElements.length) {
374 if (commitElements.length) {
375 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
375 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
376 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
376 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
377 }
377 }
378 else {
378 else {
379 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
379 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
380 }
380 }
381
381
382
382
383 });
383 });
384 };
384 };
385
385
386 var Select2Box = function(element, overrides) {
386 var Select2Box = function(element, overrides) {
387 var globalDefaults = {
387 var globalDefaults = {
388 dropdownAutoWidth: true,
388 dropdownAutoWidth: true,
389 containerCssClass: "drop-menu",
389 containerCssClass: "drop-menu",
390 dropdownCssClass: "drop-menu-dropdown"
390 dropdownCssClass: "drop-menu-dropdown"
391 };
391 };
392
392
393 var initSelect2 = function(defaultOptions) {
393 var initSelect2 = function(defaultOptions) {
394 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
394 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
395 element.select2(options);
395 element.select2(options);
396 };
396 };
397
397
398 return {
398 return {
399 initRef: function() {
399 initRef: function() {
400 var defaultOptions = {
400 var defaultOptions = {
401 minimumResultsForSearch: 5,
401 minimumResultsForSearch: 5,
402 formatSelection: formatRefSelection
402 formatSelection: formatRefSelection
403 };
403 };
404
404
405 initSelect2(defaultOptions);
405 initSelect2(defaultOptions);
406 },
406 },
407
407
408 initRepo: function(defaultValue, readOnly) {
408 initRepo: function(defaultValue, readOnly) {
409 var defaultOptions = {
409 var defaultOptions = {
410 initSelection : function (element, callback) {
410 initSelection : function (element, callback) {
411 var data = {id: defaultValue, text: defaultValue};
411 var data = {id: defaultValue, text: defaultValue};
412 callback(data);
412 callback(data);
413 }
413 }
414 };
414 };
415
415
416 initSelect2(defaultOptions);
416 initSelect2(defaultOptions);
417
417
418 element.select2('val', defaultSourceRepo);
418 element.select2('val', defaultSourceRepo);
419 if (readOnly === true) {
419 if (readOnly === true) {
420 element.select2('readonly', true);
420 element.select2('readonly', true);
421 }
421 }
422 }
422 }
423 };
423 };
424 };
424 };
425
425
426 var initTargetRefs = function(refsData, selectedRef) {
426 var initTargetRefs = function(refsData, selectedRef) {
427
427
428 Select2Box($targetRef, {
428 Select2Box($targetRef, {
429 placeholder: "${_('Select commit reference')}",
429 placeholder: "${_('Select commit reference')}",
430 query: function(query) {
430 query: function(query) {
431 queryTargetRefs(refsData, query);
431 queryTargetRefs(refsData, query);
432 },
432 },
433 initSelection : initRefSelection(selectedRef)
433 initSelection : initRefSelection(selectedRef)
434 }).initRef();
434 }).initRef();
435
435
436 if (!(selectedRef === undefined)) {
436 if (!(selectedRef === undefined)) {
437 $targetRef.select2('val', selectedRef);
437 $targetRef.select2('val', selectedRef);
438 }
438 }
439 };
439 };
440
440
441 var targetRepoChanged = function(repoData) {
441 var targetRepoChanged = function(repoData) {
442 // generate new DESC of target repo displayed next to select
442 // generate new DESC of target repo displayed next to select
443 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
443 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
444 $('#target_repo_desc').html(
444 $('#target_repo_desc').html(
445 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
445 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
446 );
446 );
447
447
448 // generate dynamic select2 for refs.
448 // generate dynamic select2 for refs.
449 initTargetRefs(repoData['refs']['select2_refs'],
449 initTargetRefs(repoData['refs']['select2_refs'],
450 repoData['refs']['selected_ref']);
450 repoData['refs']['selected_ref']);
451
451
452 };
452 };
453
453
454 var sourceRefSelect2 = Select2Box($sourceRef, {
454 var sourceRefSelect2 = Select2Box($sourceRef, {
455 placeholder: "${_('Select commit reference')}",
455 placeholder: "${_('Select commit reference')}",
456 query: function(query) {
456 query: function(query) {
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
458 queryTargetRefs(initialData, query)
458 queryTargetRefs(initialData, query)
459 },
459 },
460 initSelection: initRefSelection()
460 initSelection: initRefSelection()
461 }
461 }
462 );
462 );
463
463
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
465 query: function(query) {}
465 query: function(query) {}
466 });
466 });
467
467
468 var targetRepoSelect2 = Select2Box($targetRepo, {
468 var targetRepoSelect2 = Select2Box($targetRepo, {
469 cachedDataSource: {},
469 cachedDataSource: {},
470 query: $.debounce(250, function(query) {
470 query: $.debounce(250, function(query) {
471 queryTargetRepo(this, query);
471 queryTargetRepo(this, query);
472 }),
472 }),
473 formatResult: formatRepoResult
473 formatResult: formatRepoResult
474 });
474 });
475
475
476 sourceRefSelect2.initRef();
476 sourceRefSelect2.initRef();
477
477
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
479
479
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
481
481
482 $sourceRef.on('change', function(e){
482 $sourceRef.on('change', function(e){
483 loadRepoRefDiffPreview();
483 loadRepoRefDiffPreview();
484 reviewersController.loadDefaultReviewers(
484 reviewersController.loadDefaultReviewers(
485 sourceRepo(), sourceRef(), targetRepo(), targetRef());
485 sourceRepo(), sourceRef(), targetRepo(), targetRef());
486 });
486 });
487
487
488 $targetRef.on('change', function(e){
488 $targetRef.on('change', function(e){
489 loadRepoRefDiffPreview();
489 loadRepoRefDiffPreview();
490 reviewersController.loadDefaultReviewers(
490 reviewersController.loadDefaultReviewers(
491 sourceRepo(), sourceRef(), targetRepo(), targetRef());
491 sourceRepo(), sourceRef(), targetRepo(), targetRef());
492 });
492 });
493
493
494 $targetRepo.on('change', function(e){
494 $targetRepo.on('change', function(e){
495 var repoName = $(this).val();
495 var repoName = $(this).val();
496 calculateContainerWidth();
496 calculateContainerWidth();
497 $targetRef.select2('destroy');
497 $targetRef.select2('destroy');
498 $('#target_ref_loading').show();
498 $('#target_ref_loading').show();
499
499
500 $.ajax({
500 $.ajax({
501 url: pyroutes.url('pullrequest_repo_refs',
501 url: pyroutes.url('pullrequest_repo_refs',
502 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
502 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
503 data: {},
503 data: {},
504 dataType: 'json',
504 dataType: 'json',
505 type: 'GET',
505 type: 'GET',
506 success: function(data) {
506 success: function(data) {
507 $('#target_ref_loading').hide();
507 $('#target_ref_loading').hide();
508 targetRepoChanged(data);
508 targetRepoChanged(data);
509 loadRepoRefDiffPreview();
509 loadRepoRefDiffPreview();
510 },
510 },
511 error: function(data, textStatus, errorThrown) {
511 error: function(data, textStatus, errorThrown) {
512 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
513 }
513 }
514 })
514 })
515
515
516 });
516 });
517
517
518 $pullRequestForm.on('submit', function(e){
518 $pullRequestForm.on('submit', function(e){
519 // Flush changes into textarea
519 // Flush changes into textarea
520 codeMirrorInstance.save();
520 codeMirrorInstance.save();
521 prButtonLock(true, null, 'all');
521 prButtonLock(true, null, 'all');
522 });
522 });
523
523
524 prButtonLock(true, "${_('Please select source and target')}", 'all');
524 prButtonLock(true, "${_('Please select source and target')}", 'all');
525
525
526 // auto-load on init, the target refs select2
526 // auto-load on init, the target refs select2
527 calculateContainerWidth();
527 calculateContainerWidth();
528 targetRepoChanged(defaultTargetRepoData);
528 targetRepoChanged(defaultTargetRepoData);
529
529
530 $('#pullrequest_title').on('keyup', function(e){
530 $('#pullrequest_title').on('keyup', function(e){
531 $(this).removeClass('autogenerated-title');
531 $(this).removeClass('autogenerated-title');
532 });
532 });
533
533
534 % if c.default_source_ref:
534 % if c.default_source_ref:
535 // in case we have a pre-selected value, use it now
535 // in case we have a pre-selected value, use it now
536 $sourceRef.select2('val', '${c.default_source_ref}');
536 $sourceRef.select2('val', '${c.default_source_ref}');
537 // diff preview load
537 // diff preview load
538 loadRepoRefDiffPreview();
538 loadRepoRefDiffPreview();
539 // default reviewers
539 // default reviewers
540 reviewersController.loadDefaultReviewers(
540 reviewersController.loadDefaultReviewers(
541 sourceRepo(), sourceRef(), targetRepo(), targetRef());
541 sourceRepo(), sourceRef(), targetRepo(), targetRef());
542 % endif
542 % endif
543
543
544 ReviewerAutoComplete('#user');
544 ReviewerAutoComplete('#user');
545 });
545 });
546 </script>
546 </script>
547
547
548 </%def>
548 </%def>
General Comments 0
You need to be logged in to leave comments. Login now