##// END OF EJS Templates
feat(ui): added ability to replace binary file through UI, added related tests. Fixes: RCCE-19
ilin.s -
r5274:6d0b768f default
parent child Browse files
Show More
@@ -1,1225 +1,1235 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 from rhodecode.apps._base import add_route_with_slash
18 from rhodecode.apps._base import add_route_with_slash
19
19
20
20
21 def includeme(config):
21 def includeme(config):
22 from rhodecode.apps.repository.views.repo_artifacts import RepoArtifactsView
22 from rhodecode.apps.repository.views.repo_artifacts import RepoArtifactsView
23 from rhodecode.apps.repository.views.repo_audit_logs import AuditLogsView
23 from rhodecode.apps.repository.views.repo_audit_logs import AuditLogsView
24 from rhodecode.apps.repository.views.repo_automation import RepoAutomationView
24 from rhodecode.apps.repository.views.repo_automation import RepoAutomationView
25 from rhodecode.apps.repository.views.repo_bookmarks import RepoBookmarksView
25 from rhodecode.apps.repository.views.repo_bookmarks import RepoBookmarksView
26 from rhodecode.apps.repository.views.repo_branch_permissions import RepoSettingsBranchPermissionsView
26 from rhodecode.apps.repository.views.repo_branch_permissions import RepoSettingsBranchPermissionsView
27 from rhodecode.apps.repository.views.repo_branches import RepoBranchesView
27 from rhodecode.apps.repository.views.repo_branches import RepoBranchesView
28 from rhodecode.apps.repository.views.repo_caches import RepoCachesView
28 from rhodecode.apps.repository.views.repo_caches import RepoCachesView
29 from rhodecode.apps.repository.views.repo_changelog import RepoChangelogView
29 from rhodecode.apps.repository.views.repo_changelog import RepoChangelogView
30 from rhodecode.apps.repository.views.repo_checks import RepoChecksView
30 from rhodecode.apps.repository.views.repo_checks import RepoChecksView
31 from rhodecode.apps.repository.views.repo_commits import RepoCommitsView
31 from rhodecode.apps.repository.views.repo_commits import RepoCommitsView
32 from rhodecode.apps.repository.views.repo_compare import RepoCompareView
32 from rhodecode.apps.repository.views.repo_compare import RepoCompareView
33 from rhodecode.apps.repository.views.repo_feed import RepoFeedView
33 from rhodecode.apps.repository.views.repo_feed import RepoFeedView
34 from rhodecode.apps.repository.views.repo_files import RepoFilesView
34 from rhodecode.apps.repository.views.repo_files import RepoFilesView
35 from rhodecode.apps.repository.views.repo_forks import RepoForksView
35 from rhodecode.apps.repository.views.repo_forks import RepoForksView
36 from rhodecode.apps.repository.views.repo_maintainance import RepoMaintenanceView
36 from rhodecode.apps.repository.views.repo_maintainance import RepoMaintenanceView
37 from rhodecode.apps.repository.views.repo_permissions import RepoSettingsPermissionsView
37 from rhodecode.apps.repository.views.repo_permissions import RepoSettingsPermissionsView
38 from rhodecode.apps.repository.views.repo_pull_requests import RepoPullRequestsView
38 from rhodecode.apps.repository.views.repo_pull_requests import RepoPullRequestsView
39 from rhodecode.apps.repository.views.repo_review_rules import RepoReviewRulesView
39 from rhodecode.apps.repository.views.repo_review_rules import RepoReviewRulesView
40 from rhodecode.apps.repository.views.repo_settings import RepoSettingsView
40 from rhodecode.apps.repository.views.repo_settings import RepoSettingsView
41 from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
41 from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
42 from rhodecode.apps.repository.views.repo_settings_fields import RepoSettingsFieldsView
42 from rhodecode.apps.repository.views.repo_settings_fields import RepoSettingsFieldsView
43 from rhodecode.apps.repository.views.repo_settings_issue_trackers import RepoSettingsIssueTrackersView
43 from rhodecode.apps.repository.views.repo_settings_issue_trackers import RepoSettingsIssueTrackersView
44 from rhodecode.apps.repository.views.repo_settings_remote import RepoSettingsRemoteView
44 from rhodecode.apps.repository.views.repo_settings_remote import RepoSettingsRemoteView
45 from rhodecode.apps.repository.views.repo_settings_vcs import RepoSettingsVcsView
45 from rhodecode.apps.repository.views.repo_settings_vcs import RepoSettingsVcsView
46 from rhodecode.apps.repository.views.repo_strip import RepoStripView
46 from rhodecode.apps.repository.views.repo_strip import RepoStripView
47 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
47 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
48 from rhodecode.apps.repository.views.repo_tags import RepoTagsView
48 from rhodecode.apps.repository.views.repo_tags import RepoTagsView
49
49
50 # repo creating checks, special cases that aren't repo routes
50 # repo creating checks, special cases that aren't repo routes
51 config.add_route(
51 config.add_route(
52 name='repo_creating',
52 name='repo_creating',
53 pattern='/{repo_name:.*?[^/]}/repo_creating')
53 pattern='/{repo_name:.*?[^/]}/repo_creating')
54 config.add_view(
54 config.add_view(
55 RepoChecksView,
55 RepoChecksView,
56 attr='repo_creating',
56 attr='repo_creating',
57 route_name='repo_creating', request_method='GET',
57 route_name='repo_creating', request_method='GET',
58 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
58 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
59
59
60 config.add_route(
60 config.add_route(
61 name='repo_creating_check',
61 name='repo_creating_check',
62 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
62 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
63 config.add_view(
63 config.add_view(
64 RepoChecksView,
64 RepoChecksView,
65 attr='repo_creating_check',
65 attr='repo_creating_check',
66 route_name='repo_creating_check', request_method='GET',
66 route_name='repo_creating_check', request_method='GET',
67 renderer='json_ext')
67 renderer='json_ext')
68
68
69 # Summary
69 # Summary
70 # NOTE(marcink): one additional route is defined in very bottom, catch
70 # NOTE(marcink): one additional route is defined in very bottom, catch
71 # all pattern
71 # all pattern
72 config.add_route(
72 config.add_route(
73 name='repo_summary_explicit',
73 name='repo_summary_explicit',
74 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
74 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
75 config.add_view(
75 config.add_view(
76 RepoSummaryView,
76 RepoSummaryView,
77 attr='summary',
77 attr='summary',
78 route_name='repo_summary_explicit', request_method='GET',
78 route_name='repo_summary_explicit', request_method='GET',
79 renderer='rhodecode:templates/summary/summary.mako')
79 renderer='rhodecode:templates/summary/summary.mako')
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_summary_commits',
82 name='repo_summary_commits',
83 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
84 config.add_view(
84 config.add_view(
85 RepoSummaryView,
85 RepoSummaryView,
86 attr='summary_commits',
86 attr='summary_commits',
87 route_name='repo_summary_commits', request_method='GET',
87 route_name='repo_summary_commits', request_method='GET',
88 renderer='rhodecode:templates/summary/summary_commits.mako')
88 renderer='rhodecode:templates/summary/summary_commits.mako')
89
89
90 # Commits
90 # Commits
91 config.add_route(
91 config.add_route(
92 name='repo_commit',
92 name='repo_commit',
93 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
94 config.add_view(
94 config.add_view(
95 RepoCommitsView,
95 RepoCommitsView,
96 attr='repo_commit_show',
96 attr='repo_commit_show',
97 route_name='repo_commit', request_method='GET',
97 route_name='repo_commit', request_method='GET',
98 renderer=None)
98 renderer=None)
99
99
100 config.add_route(
100 config.add_route(
101 name='repo_commit_children',
101 name='repo_commit_children',
102 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
102 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
103 config.add_view(
103 config.add_view(
104 RepoCommitsView,
104 RepoCommitsView,
105 attr='repo_commit_children',
105 attr='repo_commit_children',
106 route_name='repo_commit_children', request_method='GET',
106 route_name='repo_commit_children', request_method='GET',
107 renderer='json_ext', xhr=True)
107 renderer='json_ext', xhr=True)
108
108
109 config.add_route(
109 config.add_route(
110 name='repo_commit_parents',
110 name='repo_commit_parents',
111 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
111 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
112 config.add_view(
112 config.add_view(
113 RepoCommitsView,
113 RepoCommitsView,
114 attr='repo_commit_parents',
114 attr='repo_commit_parents',
115 route_name='repo_commit_parents', request_method='GET',
115 route_name='repo_commit_parents', request_method='GET',
116 renderer='json_ext')
116 renderer='json_ext')
117
117
118 config.add_route(
118 config.add_route(
119 name='repo_commit_raw',
119 name='repo_commit_raw',
120 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
120 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
121 config.add_view(
121 config.add_view(
122 RepoCommitsView,
122 RepoCommitsView,
123 attr='repo_commit_raw',
123 attr='repo_commit_raw',
124 route_name='repo_commit_raw', request_method='GET',
124 route_name='repo_commit_raw', request_method='GET',
125 renderer=None)
125 renderer=None)
126
126
127 config.add_route(
127 config.add_route(
128 name='repo_commit_patch',
128 name='repo_commit_patch',
129 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
129 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
130 config.add_view(
130 config.add_view(
131 RepoCommitsView,
131 RepoCommitsView,
132 attr='repo_commit_patch',
132 attr='repo_commit_patch',
133 route_name='repo_commit_patch', request_method='GET',
133 route_name='repo_commit_patch', request_method='GET',
134 renderer=None)
134 renderer=None)
135
135
136 config.add_route(
136 config.add_route(
137 name='repo_commit_download',
137 name='repo_commit_download',
138 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
138 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
139 config.add_view(
139 config.add_view(
140 RepoCommitsView,
140 RepoCommitsView,
141 attr='repo_commit_download',
141 attr='repo_commit_download',
142 route_name='repo_commit_download', request_method='GET',
142 route_name='repo_commit_download', request_method='GET',
143 renderer=None)
143 renderer=None)
144
144
145 config.add_route(
145 config.add_route(
146 name='repo_commit_data',
146 name='repo_commit_data',
147 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
147 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
148 config.add_view(
148 config.add_view(
149 RepoCommitsView,
149 RepoCommitsView,
150 attr='repo_commit_data',
150 attr='repo_commit_data',
151 route_name='repo_commit_data', request_method='GET',
151 route_name='repo_commit_data', request_method='GET',
152 renderer='json_ext', xhr=True)
152 renderer='json_ext', xhr=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_commit_comment_create',
155 name='repo_commit_comment_create',
156 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
156 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
157 config.add_view(
157 config.add_view(
158 RepoCommitsView,
158 RepoCommitsView,
159 attr='repo_commit_comment_create',
159 attr='repo_commit_comment_create',
160 route_name='repo_commit_comment_create', request_method='POST',
160 route_name='repo_commit_comment_create', request_method='POST',
161 renderer='json_ext')
161 renderer='json_ext')
162
162
163 config.add_route(
163 config.add_route(
164 name='repo_commit_comment_preview',
164 name='repo_commit_comment_preview',
165 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
165 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
166 config.add_view(
166 config.add_view(
167 RepoCommitsView,
167 RepoCommitsView,
168 attr='repo_commit_comment_preview',
168 attr='repo_commit_comment_preview',
169 route_name='repo_commit_comment_preview', request_method='POST',
169 route_name='repo_commit_comment_preview', request_method='POST',
170 renderer='string', xhr=True)
170 renderer='string', xhr=True)
171
171
172 config.add_route(
172 config.add_route(
173 name='repo_commit_comment_history_view',
173 name='repo_commit_comment_history_view',
174 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/history_view/{comment_history_id}', repo_route=True)
174 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/history_view/{comment_history_id}', repo_route=True)
175 config.add_view(
175 config.add_view(
176 RepoCommitsView,
176 RepoCommitsView,
177 attr='repo_commit_comment_history_view',
177 attr='repo_commit_comment_history_view',
178 route_name='repo_commit_comment_history_view', request_method='POST',
178 route_name='repo_commit_comment_history_view', request_method='POST',
179 renderer='string', xhr=True)
179 renderer='string', xhr=True)
180
180
181 config.add_route(
181 config.add_route(
182 name='repo_commit_comment_attachment_upload',
182 name='repo_commit_comment_attachment_upload',
183 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
183 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True)
184 config.add_view(
184 config.add_view(
185 RepoCommitsView,
185 RepoCommitsView,
186 attr='repo_commit_comment_attachment_upload',
186 attr='repo_commit_comment_attachment_upload',
187 route_name='repo_commit_comment_attachment_upload', request_method='POST',
187 route_name='repo_commit_comment_attachment_upload', request_method='POST',
188 renderer='json_ext', xhr=True)
188 renderer='json_ext', xhr=True)
189
189
190 config.add_route(
190 config.add_route(
191 name='repo_commit_comment_delete',
191 name='repo_commit_comment_delete',
192 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
192 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
193 config.add_view(
193 config.add_view(
194 RepoCommitsView,
194 RepoCommitsView,
195 attr='repo_commit_comment_delete',
195 attr='repo_commit_comment_delete',
196 route_name='repo_commit_comment_delete', request_method='POST',
196 route_name='repo_commit_comment_delete', request_method='POST',
197 renderer='json_ext')
197 renderer='json_ext')
198
198
199 config.add_route(
199 config.add_route(
200 name='repo_commit_comment_edit',
200 name='repo_commit_comment_edit',
201 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/edit', repo_route=True)
202 config.add_view(
202 config.add_view(
203 RepoCommitsView,
203 RepoCommitsView,
204 attr='repo_commit_comment_edit',
204 attr='repo_commit_comment_edit',
205 route_name='repo_commit_comment_edit', request_method='POST',
205 route_name='repo_commit_comment_edit', request_method='POST',
206 renderer='json_ext')
206 renderer='json_ext')
207
207
208 # still working url for backward compat.
208 # still working url for backward compat.
209 config.add_route(
209 config.add_route(
210 name='repo_commit_raw_deprecated',
210 name='repo_commit_raw_deprecated',
211 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
211 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
212 config.add_view(
212 config.add_view(
213 RepoCommitsView,
213 RepoCommitsView,
214 attr='repo_commit_raw',
214 attr='repo_commit_raw',
215 route_name='repo_commit_raw_deprecated', request_method='GET',
215 route_name='repo_commit_raw_deprecated', request_method='GET',
216 renderer=None)
216 renderer=None)
217
217
218 # Files
218 # Files
219 config.add_route(
219 config.add_route(
220 name='repo_archivefile',
220 name='repo_archivefile',
221 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
221 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
222 config.add_view(
222 config.add_view(
223 RepoFilesView,
223 RepoFilesView,
224 attr='repo_archivefile',
224 attr='repo_archivefile',
225 route_name='repo_archivefile', request_method='GET',
225 route_name='repo_archivefile', request_method='GET',
226 renderer=None)
226 renderer=None)
227
227
228 config.add_route(
228 config.add_route(
229 name='repo_files_diff',
229 name='repo_files_diff',
230 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
230 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
231 config.add_view(
231 config.add_view(
232 RepoFilesView,
232 RepoFilesView,
233 attr='repo_files_diff',
233 attr='repo_files_diff',
234 route_name='repo_files_diff', request_method='GET',
234 route_name='repo_files_diff', request_method='GET',
235 renderer=None)
235 renderer=None)
236
236
237 config.add_route( # legacy route to make old links work
237 config.add_route( # legacy route to make old links work
238 name='repo_files_diff_2way_redirect',
238 name='repo_files_diff_2way_redirect',
239 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
239 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
240 config.add_view(
240 config.add_view(
241 RepoFilesView,
241 RepoFilesView,
242 attr='repo_files_diff_2way_redirect',
242 attr='repo_files_diff_2way_redirect',
243 route_name='repo_files_diff_2way_redirect', request_method='GET',
243 route_name='repo_files_diff_2way_redirect', request_method='GET',
244 renderer=None)
244 renderer=None)
245
245
246 config.add_route(
246 config.add_route(
247 name='repo_files',
247 name='repo_files',
248 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
248 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
249 config.add_view(
249 config.add_view(
250 RepoFilesView,
250 RepoFilesView,
251 attr='repo_files',
251 attr='repo_files',
252 route_name='repo_files', request_method='GET',
252 route_name='repo_files', request_method='GET',
253 renderer=None)
253 renderer=None)
254
254
255 config.add_route(
255 config.add_route(
256 name='repo_files:default_path',
256 name='repo_files:default_path',
257 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
257 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
258 config.add_view(
258 config.add_view(
259 RepoFilesView,
259 RepoFilesView,
260 attr='repo_files',
260 attr='repo_files',
261 route_name='repo_files:default_path', request_method='GET',
261 route_name='repo_files:default_path', request_method='GET',
262 renderer=None)
262 renderer=None)
263
263
264 config.add_route(
264 config.add_route(
265 name='repo_files:default_commit',
265 name='repo_files:default_commit',
266 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
266 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
267 config.add_view(
267 config.add_view(
268 RepoFilesView,
268 RepoFilesView,
269 attr='repo_files',
269 attr='repo_files',
270 route_name='repo_files:default_commit', request_method='GET',
270 route_name='repo_files:default_commit', request_method='GET',
271 renderer=None)
271 renderer=None)
272
272
273 config.add_route(
273 config.add_route(
274 name='repo_files:rendered',
274 name='repo_files:rendered',
275 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
275 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
276 config.add_view(
276 config.add_view(
277 RepoFilesView,
277 RepoFilesView,
278 attr='repo_files',
278 attr='repo_files',
279 route_name='repo_files:rendered', request_method='GET',
279 route_name='repo_files:rendered', request_method='GET',
280 renderer=None)
280 renderer=None)
281
281
282 config.add_route(
282 config.add_route(
283 name='repo_files:annotated',
283 name='repo_files:annotated',
284 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
284 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
285 config.add_view(
285 config.add_view(
286 RepoFilesView,
286 RepoFilesView,
287 attr='repo_files',
287 attr='repo_files',
288 route_name='repo_files:annotated', request_method='GET',
288 route_name='repo_files:annotated', request_method='GET',
289 renderer=None)
289 renderer=None)
290
290
291 config.add_route(
291 config.add_route(
292 name='repo_files:annotated_previous',
292 name='repo_files:annotated_previous',
293 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
293 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
294 config.add_view(
294 config.add_view(
295 RepoFilesView,
295 RepoFilesView,
296 attr='repo_files_annotated_previous',
296 attr='repo_files_annotated_previous',
297 route_name='repo_files:annotated_previous', request_method='GET',
297 route_name='repo_files:annotated_previous', request_method='GET',
298 renderer=None)
298 renderer=None)
299
299
300 config.add_route(
300 config.add_route(
301 name='repo_nodetree_full',
301 name='repo_nodetree_full',
302 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
302 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
303 config.add_view(
303 config.add_view(
304 RepoFilesView,
304 RepoFilesView,
305 attr='repo_nodetree_full',
305 attr='repo_nodetree_full',
306 route_name='repo_nodetree_full', request_method='GET',
306 route_name='repo_nodetree_full', request_method='GET',
307 renderer=None, xhr=True)
307 renderer=None, xhr=True)
308
308
309 config.add_route(
309 config.add_route(
310 name='repo_nodetree_full:default_path',
310 name='repo_nodetree_full:default_path',
311 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
311 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
312 config.add_view(
312 config.add_view(
313 RepoFilesView,
313 RepoFilesView,
314 attr='repo_nodetree_full',
314 attr='repo_nodetree_full',
315 route_name='repo_nodetree_full:default_path', request_method='GET',
315 route_name='repo_nodetree_full:default_path', request_method='GET',
316 renderer=None, xhr=True)
316 renderer=None, xhr=True)
317
317
318 config.add_route(
318 config.add_route(
319 name='repo_files_nodelist',
319 name='repo_files_nodelist',
320 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
320 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
321 config.add_view(
321 config.add_view(
322 RepoFilesView,
322 RepoFilesView,
323 attr='repo_nodelist',
323 attr='repo_nodelist',
324 route_name='repo_files_nodelist', request_method='GET',
324 route_name='repo_files_nodelist', request_method='GET',
325 renderer='json_ext', xhr=True)
325 renderer='json_ext', xhr=True)
326
326
327 config.add_route(
327 config.add_route(
328 name='repo_file_raw',
328 name='repo_file_raw',
329 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
329 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
330 config.add_view(
330 config.add_view(
331 RepoFilesView,
331 RepoFilesView,
332 attr='repo_file_raw',
332 attr='repo_file_raw',
333 route_name='repo_file_raw', request_method='GET',
333 route_name='repo_file_raw', request_method='GET',
334 renderer=None)
334 renderer=None)
335
335
336 config.add_route(
336 config.add_route(
337 name='repo_file_download',
337 name='repo_file_download',
338 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
338 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
339 config.add_view(
339 config.add_view(
340 RepoFilesView,
340 RepoFilesView,
341 attr='repo_file_download',
341 attr='repo_file_download',
342 route_name='repo_file_download', request_method='GET',
342 route_name='repo_file_download', request_method='GET',
343 renderer=None)
343 renderer=None)
344
344
345 config.add_route( # backward compat to keep old links working
345 config.add_route( # backward compat to keep old links working
346 name='repo_file_download:legacy',
346 name='repo_file_download:legacy',
347 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
347 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
348 repo_route=True)
348 repo_route=True)
349 config.add_view(
349 config.add_view(
350 RepoFilesView,
350 RepoFilesView,
351 attr='repo_file_download',
351 attr='repo_file_download',
352 route_name='repo_file_download:legacy', request_method='GET',
352 route_name='repo_file_download:legacy', request_method='GET',
353 renderer=None)
353 renderer=None)
354
354
355 config.add_route(
355 config.add_route(
356 name='repo_file_history',
356 name='repo_file_history',
357 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
357 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
358 config.add_view(
358 config.add_view(
359 RepoFilesView,
359 RepoFilesView,
360 attr='repo_file_history',
360 attr='repo_file_history',
361 route_name='repo_file_history', request_method='GET',
361 route_name='repo_file_history', request_method='GET',
362 renderer='json_ext')
362 renderer='json_ext')
363
363
364 config.add_route(
364 config.add_route(
365 name='repo_file_authors',
365 name='repo_file_authors',
366 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
366 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
367 config.add_view(
367 config.add_view(
368 RepoFilesView,
368 RepoFilesView,
369 attr='repo_file_authors',
369 attr='repo_file_authors',
370 route_name='repo_file_authors', request_method='GET',
370 route_name='repo_file_authors', request_method='GET',
371 renderer='rhodecode:templates/files/file_authors_box.mako')
371 renderer='rhodecode:templates/files/file_authors_box.mako')
372
372
373 config.add_route(
373 config.add_route(
374 name='repo_files_check_head',
374 name='repo_files_check_head',
375 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
375 pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
376 repo_route=True)
376 repo_route=True)
377 config.add_view(
377 config.add_view(
378 RepoFilesView,
378 RepoFilesView,
379 attr='repo_files_check_head',
379 attr='repo_files_check_head',
380 route_name='repo_files_check_head', request_method='POST',
380 route_name='repo_files_check_head', request_method='POST',
381 renderer='json_ext', xhr=True)
381 renderer='json_ext', xhr=True)
382
382
383 config.add_route(
383 config.add_route(
384 name='repo_files_remove_file',
384 name='repo_files_remove_file',
385 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
385 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
386 repo_route=True)
386 repo_route=True)
387 config.add_view(
387 config.add_view(
388 RepoFilesView,
388 RepoFilesView,
389 attr='repo_files_remove_file',
389 attr='repo_files_remove_file',
390 route_name='repo_files_remove_file', request_method='GET',
390 route_name='repo_files_remove_file', request_method='GET',
391 renderer='rhodecode:templates/files/files_delete.mako')
391 renderer='rhodecode:templates/files/files_delete.mako')
392
392
393 config.add_route(
393 config.add_route(
394 name='repo_files_delete_file',
394 name='repo_files_delete_file',
395 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
395 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
396 repo_route=True)
396 repo_route=True)
397 config.add_view(
397 config.add_view(
398 RepoFilesView,
398 RepoFilesView,
399 attr='repo_files_delete_file',
399 attr='repo_files_delete_file',
400 route_name='repo_files_delete_file', request_method='POST',
400 route_name='repo_files_delete_file', request_method='POST',
401 renderer=None)
401 renderer=None)
402
402
403 config.add_route(
403 config.add_route(
404 name='repo_files_edit_file',
404 name='repo_files_edit_file',
405 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
405 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
406 repo_route=True)
406 repo_route=True)
407 config.add_view(
407 config.add_view(
408 RepoFilesView,
408 RepoFilesView,
409 attr='repo_files_edit_file',
409 attr='repo_files_edit_file',
410 route_name='repo_files_edit_file', request_method='GET',
410 route_name='repo_files_edit_file', request_method='GET',
411 renderer='rhodecode:templates/files/files_edit.mako')
411 renderer='rhodecode:templates/files/files_edit.mako')
412
412
413 config.add_route(
413 config.add_route(
414 name='repo_files_update_file',
414 name='repo_files_update_file',
415 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
415 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
416 repo_route=True)
416 repo_route=True)
417 config.add_view(
417 config.add_view(
418 RepoFilesView,
418 RepoFilesView,
419 attr='repo_files_update_file',
419 attr='repo_files_update_file',
420 route_name='repo_files_update_file', request_method='POST',
420 route_name='repo_files_update_file', request_method='POST',
421 renderer=None)
421 renderer=None)
422
422
423 config.add_route(
423 config.add_route(
424 name='repo_files_add_file',
424 name='repo_files_add_file',
425 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
425 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
426 repo_route=True)
426 repo_route=True)
427 config.add_view(
427 config.add_view(
428 RepoFilesView,
428 RepoFilesView,
429 attr='repo_files_add_file',
429 attr='repo_files_add_file',
430 route_name='repo_files_add_file', request_method='GET',
430 route_name='repo_files_add_file', request_method='GET',
431 renderer='rhodecode:templates/files/files_add.mako')
431 renderer='rhodecode:templates/files/files_add.mako')
432
432
433 config.add_route(
433 config.add_route(
434 name='repo_files_upload_file',
434 name='repo_files_upload_file',
435 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
435 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
436 repo_route=True)
436 repo_route=True)
437 config.add_view(
437 config.add_view(
438 RepoFilesView,
438 RepoFilesView,
439 attr='repo_files_add_file',
439 attr='repo_files_add_file',
440 route_name='repo_files_upload_file', request_method='GET',
440 route_name='repo_files_upload_file', request_method='GET',
441 renderer='rhodecode:templates/files/files_upload.mako')
441 renderer='rhodecode:templates/files/files_upload.mako')
442 config.add_view( # POST creates
442 config.add_view( # POST creates
443 RepoFilesView,
443 RepoFilesView,
444 attr='repo_files_upload_file',
444 attr='repo_files_upload_file',
445 route_name='repo_files_upload_file', request_method='POST',
445 route_name='repo_files_upload_file', request_method='POST',
446 renderer='json_ext')
446 renderer='json_ext')
447
447
448 config.add_route(
448 config.add_route(
449 name='repo_files_replace_binary',
450 pattern='/{repo_name:.*?[^/]}/replace_binary/{commit_id}/{f_path:.*}',
451 repo_route=True)
452 config.add_view(
453 RepoFilesView,
454 attr='repo_files_replace_file',
455 route_name='repo_files_replace_binary', request_method='POST',
456 renderer='json_ext')
457
458 config.add_route(
449 name='repo_files_create_file',
459 name='repo_files_create_file',
450 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
460 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
451 repo_route=True)
461 repo_route=True)
452 config.add_view( # POST creates
462 config.add_view( # POST creates
453 RepoFilesView,
463 RepoFilesView,
454 attr='repo_files_create_file',
464 attr='repo_files_create_file',
455 route_name='repo_files_create_file', request_method='POST',
465 route_name='repo_files_create_file', request_method='POST',
456 renderer=None)
466 renderer=None)
457
467
458 # Refs data
468 # Refs data
459 config.add_route(
469 config.add_route(
460 name='repo_refs_data',
470 name='repo_refs_data',
461 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
471 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
462 config.add_view(
472 config.add_view(
463 RepoSummaryView,
473 RepoSummaryView,
464 attr='repo_refs_data',
474 attr='repo_refs_data',
465 route_name='repo_refs_data', request_method='GET',
475 route_name='repo_refs_data', request_method='GET',
466 renderer='json_ext')
476 renderer='json_ext')
467
477
468 config.add_route(
478 config.add_route(
469 name='repo_refs_changelog_data',
479 name='repo_refs_changelog_data',
470 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
480 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
471 config.add_view(
481 config.add_view(
472 RepoSummaryView,
482 RepoSummaryView,
473 attr='repo_refs_changelog_data',
483 attr='repo_refs_changelog_data',
474 route_name='repo_refs_changelog_data', request_method='GET',
484 route_name='repo_refs_changelog_data', request_method='GET',
475 renderer='json_ext')
485 renderer='json_ext')
476
486
477 config.add_route(
487 config.add_route(
478 name='repo_stats',
488 name='repo_stats',
479 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
489 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
480 config.add_view(
490 config.add_view(
481 RepoSummaryView,
491 RepoSummaryView,
482 attr='repo_stats',
492 attr='repo_stats',
483 route_name='repo_stats', request_method='GET',
493 route_name='repo_stats', request_method='GET',
484 renderer='json_ext')
494 renderer='json_ext')
485
495
486 # Commits
496 # Commits
487 config.add_route(
497 config.add_route(
488 name='repo_commits',
498 name='repo_commits',
489 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
499 pattern='/{repo_name:.*?[^/]}/commits', repo_route=True)
490 config.add_view(
500 config.add_view(
491 RepoChangelogView,
501 RepoChangelogView,
492 attr='repo_changelog',
502 attr='repo_changelog',
493 route_name='repo_commits', request_method='GET',
503 route_name='repo_commits', request_method='GET',
494 renderer='rhodecode:templates/commits/changelog.mako')
504 renderer='rhodecode:templates/commits/changelog.mako')
495 # old routes for backward compat
505 # old routes for backward compat
496 config.add_view(
506 config.add_view(
497 RepoChangelogView,
507 RepoChangelogView,
498 attr='repo_changelog',
508 attr='repo_changelog',
499 route_name='repo_changelog', request_method='GET',
509 route_name='repo_changelog', request_method='GET',
500 renderer='rhodecode:templates/commits/changelog.mako')
510 renderer='rhodecode:templates/commits/changelog.mako')
501
511
502 config.add_route(
512 config.add_route(
503 name='repo_commits_elements',
513 name='repo_commits_elements',
504 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
514 pattern='/{repo_name:.*?[^/]}/commits_elements', repo_route=True)
505 config.add_view(
515 config.add_view(
506 RepoChangelogView,
516 RepoChangelogView,
507 attr='repo_commits_elements',
517 attr='repo_commits_elements',
508 route_name='repo_commits_elements', request_method=('GET', 'POST'),
518 route_name='repo_commits_elements', request_method=('GET', 'POST'),
509 renderer='rhodecode:templates/commits/changelog_elements.mako',
519 renderer='rhodecode:templates/commits/changelog_elements.mako',
510 xhr=True)
520 xhr=True)
511
521
512 config.add_route(
522 config.add_route(
513 name='repo_commits_elements_file',
523 name='repo_commits_elements_file',
514 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
524 pattern='/{repo_name:.*?[^/]}/commits_elements/{commit_id}/{f_path:.*}', repo_route=True)
515 config.add_view(
525 config.add_view(
516 RepoChangelogView,
526 RepoChangelogView,
517 attr='repo_commits_elements',
527 attr='repo_commits_elements',
518 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
528 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
519 renderer='rhodecode:templates/commits/changelog_elements.mako',
529 renderer='rhodecode:templates/commits/changelog_elements.mako',
520 xhr=True)
530 xhr=True)
521
531
522 config.add_route(
532 config.add_route(
523 name='repo_commits_file',
533 name='repo_commits_file',
524 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
534 pattern='/{repo_name:.*?[^/]}/commits/{commit_id}/{f_path:.*}', repo_route=True)
525 config.add_view(
535 config.add_view(
526 RepoChangelogView,
536 RepoChangelogView,
527 attr='repo_changelog',
537 attr='repo_changelog',
528 route_name='repo_commits_file', request_method='GET',
538 route_name='repo_commits_file', request_method='GET',
529 renderer='rhodecode:templates/commits/changelog.mako')
539 renderer='rhodecode:templates/commits/changelog.mako')
530 # old routes for backward compat
540 # old routes for backward compat
531 config.add_view(
541 config.add_view(
532 RepoChangelogView,
542 RepoChangelogView,
533 attr='repo_changelog',
543 attr='repo_changelog',
534 route_name='repo_changelog_file', request_method='GET',
544 route_name='repo_changelog_file', request_method='GET',
535 renderer='rhodecode:templates/commits/changelog.mako')
545 renderer='rhodecode:templates/commits/changelog.mako')
536
546
537 # Changelog (old deprecated name for commits page)
547 # Changelog (old deprecated name for commits page)
538 config.add_route(
548 config.add_route(
539 name='repo_changelog',
549 name='repo_changelog',
540 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
550 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
541 config.add_route(
551 config.add_route(
542 name='repo_changelog_file',
552 name='repo_changelog_file',
543 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
553 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
544
554
545 # Compare
555 # Compare
546 config.add_route(
556 config.add_route(
547 name='repo_compare_select',
557 name='repo_compare_select',
548 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
558 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
549 config.add_view(
559 config.add_view(
550 RepoCompareView,
560 RepoCompareView,
551 attr='compare_select',
561 attr='compare_select',
552 route_name='repo_compare_select', request_method='GET',
562 route_name='repo_compare_select', request_method='GET',
553 renderer='rhodecode:templates/compare/compare_diff.mako')
563 renderer='rhodecode:templates/compare/compare_diff.mako')
554
564
555 config.add_route(
565 config.add_route(
556 name='repo_compare',
566 name='repo_compare',
557 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
567 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
558 config.add_view(
568 config.add_view(
559 RepoCompareView,
569 RepoCompareView,
560 attr='compare',
570 attr='compare',
561 route_name='repo_compare', request_method='GET',
571 route_name='repo_compare', request_method='GET',
562 renderer=None)
572 renderer=None)
563
573
564 # Tags
574 # Tags
565 config.add_route(
575 config.add_route(
566 name='tags_home',
576 name='tags_home',
567 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
577 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
568 config.add_view(
578 config.add_view(
569 RepoTagsView,
579 RepoTagsView,
570 attr='tags',
580 attr='tags',
571 route_name='tags_home', request_method='GET',
581 route_name='tags_home', request_method='GET',
572 renderer='rhodecode:templates/tags/tags.mako')
582 renderer='rhodecode:templates/tags/tags.mako')
573
583
574 # Branches
584 # Branches
575 config.add_route(
585 config.add_route(
576 name='branches_home',
586 name='branches_home',
577 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
587 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
578 config.add_view(
588 config.add_view(
579 RepoBranchesView,
589 RepoBranchesView,
580 attr='branches',
590 attr='branches',
581 route_name='branches_home', request_method='GET',
591 route_name='branches_home', request_method='GET',
582 renderer='rhodecode:templates/branches/branches.mako')
592 renderer='rhodecode:templates/branches/branches.mako')
583
593
584 # Bookmarks
594 # Bookmarks
585 config.add_route(
595 config.add_route(
586 name='bookmarks_home',
596 name='bookmarks_home',
587 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
597 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
588 config.add_view(
598 config.add_view(
589 RepoBookmarksView,
599 RepoBookmarksView,
590 attr='bookmarks',
600 attr='bookmarks',
591 route_name='bookmarks_home', request_method='GET',
601 route_name='bookmarks_home', request_method='GET',
592 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
602 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
593
603
594 # Forks
604 # Forks
595 config.add_route(
605 config.add_route(
596 name='repo_fork_new',
606 name='repo_fork_new',
597 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
607 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
598 repo_forbid_when_archived=True,
608 repo_forbid_when_archived=True,
599 repo_accepted_types=['hg', 'git'])
609 repo_accepted_types=['hg', 'git'])
600 config.add_view(
610 config.add_view(
601 RepoForksView,
611 RepoForksView,
602 attr='repo_fork_new',
612 attr='repo_fork_new',
603 route_name='repo_fork_new', request_method='GET',
613 route_name='repo_fork_new', request_method='GET',
604 renderer='rhodecode:templates/forks/forks.mako')
614 renderer='rhodecode:templates/forks/forks.mako')
605
615
606 config.add_route(
616 config.add_route(
607 name='repo_fork_create',
617 name='repo_fork_create',
608 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
618 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
609 repo_forbid_when_archived=True,
619 repo_forbid_when_archived=True,
610 repo_accepted_types=['hg', 'git'])
620 repo_accepted_types=['hg', 'git'])
611 config.add_view(
621 config.add_view(
612 RepoForksView,
622 RepoForksView,
613 attr='repo_fork_create',
623 attr='repo_fork_create',
614 route_name='repo_fork_create', request_method='POST',
624 route_name='repo_fork_create', request_method='POST',
615 renderer='rhodecode:templates/forks/fork.mako')
625 renderer='rhodecode:templates/forks/fork.mako')
616
626
617 config.add_route(
627 config.add_route(
618 name='repo_forks_show_all',
628 name='repo_forks_show_all',
619 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
629 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
620 repo_accepted_types=['hg', 'git'])
630 repo_accepted_types=['hg', 'git'])
621 config.add_view(
631 config.add_view(
622 RepoForksView,
632 RepoForksView,
623 attr='repo_forks_show_all',
633 attr='repo_forks_show_all',
624 route_name='repo_forks_show_all', request_method='GET',
634 route_name='repo_forks_show_all', request_method='GET',
625 renderer='rhodecode:templates/forks/forks.mako')
635 renderer='rhodecode:templates/forks/forks.mako')
626
636
627 config.add_route(
637 config.add_route(
628 name='repo_forks_data',
638 name='repo_forks_data',
629 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
639 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
630 repo_accepted_types=['hg', 'git'])
640 repo_accepted_types=['hg', 'git'])
631 config.add_view(
641 config.add_view(
632 RepoForksView,
642 RepoForksView,
633 attr='repo_forks_data',
643 attr='repo_forks_data',
634 route_name='repo_forks_data', request_method='GET',
644 route_name='repo_forks_data', request_method='GET',
635 renderer='json_ext', xhr=True)
645 renderer='json_ext', xhr=True)
636
646
637 # Pull Requests
647 # Pull Requests
638 config.add_route(
648 config.add_route(
639 name='pullrequest_show',
649 name='pullrequest_show',
640 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
650 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
641 repo_route=True)
651 repo_route=True)
642 config.add_view(
652 config.add_view(
643 RepoPullRequestsView,
653 RepoPullRequestsView,
644 attr='pull_request_show',
654 attr='pull_request_show',
645 route_name='pullrequest_show', request_method='GET',
655 route_name='pullrequest_show', request_method='GET',
646 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
656 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
647
657
648 config.add_route(
658 config.add_route(
649 name='pullrequest_show_all',
659 name='pullrequest_show_all',
650 pattern='/{repo_name:.*?[^/]}/pull-request',
660 pattern='/{repo_name:.*?[^/]}/pull-request',
651 repo_route=True, repo_accepted_types=['hg', 'git'])
661 repo_route=True, repo_accepted_types=['hg', 'git'])
652 config.add_view(
662 config.add_view(
653 RepoPullRequestsView,
663 RepoPullRequestsView,
654 attr='pull_request_list',
664 attr='pull_request_list',
655 route_name='pullrequest_show_all', request_method='GET',
665 route_name='pullrequest_show_all', request_method='GET',
656 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
666 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
657
667
658 config.add_route(
668 config.add_route(
659 name='pullrequest_show_all_data',
669 name='pullrequest_show_all_data',
660 pattern='/{repo_name:.*?[^/]}/pull-request-data',
670 pattern='/{repo_name:.*?[^/]}/pull-request-data',
661 repo_route=True, repo_accepted_types=['hg', 'git'])
671 repo_route=True, repo_accepted_types=['hg', 'git'])
662 config.add_view(
672 config.add_view(
663 RepoPullRequestsView,
673 RepoPullRequestsView,
664 attr='pull_request_list_data',
674 attr='pull_request_list_data',
665 route_name='pullrequest_show_all_data', request_method='GET',
675 route_name='pullrequest_show_all_data', request_method='GET',
666 renderer='json_ext', xhr=True)
676 renderer='json_ext', xhr=True)
667
677
668 config.add_route(
678 config.add_route(
669 name='pullrequest_repo_refs',
679 name='pullrequest_repo_refs',
670 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
680 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
671 repo_route=True)
681 repo_route=True)
672 config.add_view(
682 config.add_view(
673 RepoPullRequestsView,
683 RepoPullRequestsView,
674 attr='pull_request_repo_refs',
684 attr='pull_request_repo_refs',
675 route_name='pullrequest_repo_refs', request_method='GET',
685 route_name='pullrequest_repo_refs', request_method='GET',
676 renderer='json_ext', xhr=True)
686 renderer='json_ext', xhr=True)
677
687
678 config.add_route(
688 config.add_route(
679 name='pullrequest_repo_targets',
689 name='pullrequest_repo_targets',
680 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
690 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
681 repo_route=True)
691 repo_route=True)
682 config.add_view(
692 config.add_view(
683 RepoPullRequestsView,
693 RepoPullRequestsView,
684 attr='pullrequest_repo_targets',
694 attr='pullrequest_repo_targets',
685 route_name='pullrequest_repo_targets', request_method='GET',
695 route_name='pullrequest_repo_targets', request_method='GET',
686 renderer='json_ext', xhr=True)
696 renderer='json_ext', xhr=True)
687
697
688 config.add_route(
698 config.add_route(
689 name='pullrequest_new',
699 name='pullrequest_new',
690 pattern='/{repo_name:.*?[^/]}/pull-request/new',
700 pattern='/{repo_name:.*?[^/]}/pull-request/new',
691 repo_route=True, repo_accepted_types=['hg', 'git'],
701 repo_route=True, repo_accepted_types=['hg', 'git'],
692 repo_forbid_when_archived=True)
702 repo_forbid_when_archived=True)
693 config.add_view(
703 config.add_view(
694 RepoPullRequestsView,
704 RepoPullRequestsView,
695 attr='pull_request_new',
705 attr='pull_request_new',
696 route_name='pullrequest_new', request_method='GET',
706 route_name='pullrequest_new', request_method='GET',
697 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
707 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
698
708
699 config.add_route(
709 config.add_route(
700 name='pullrequest_create',
710 name='pullrequest_create',
701 pattern='/{repo_name:.*?[^/]}/pull-request/create',
711 pattern='/{repo_name:.*?[^/]}/pull-request/create',
702 repo_route=True, repo_accepted_types=['hg', 'git'],
712 repo_route=True, repo_accepted_types=['hg', 'git'],
703 repo_forbid_when_archived=True)
713 repo_forbid_when_archived=True)
704 config.add_view(
714 config.add_view(
705 RepoPullRequestsView,
715 RepoPullRequestsView,
706 attr='pull_request_create',
716 attr='pull_request_create',
707 route_name='pullrequest_create', request_method='POST',
717 route_name='pullrequest_create', request_method='POST',
708 renderer=None)
718 renderer=None)
709
719
710 config.add_route(
720 config.add_route(
711 name='pullrequest_update',
721 name='pullrequest_update',
712 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
722 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
713 repo_route=True, repo_forbid_when_archived=True)
723 repo_route=True, repo_forbid_when_archived=True)
714 config.add_view(
724 config.add_view(
715 RepoPullRequestsView,
725 RepoPullRequestsView,
716 attr='pull_request_update',
726 attr='pull_request_update',
717 route_name='pullrequest_update', request_method='POST',
727 route_name='pullrequest_update', request_method='POST',
718 renderer='json_ext')
728 renderer='json_ext')
719
729
720 config.add_route(
730 config.add_route(
721 name='pullrequest_merge',
731 name='pullrequest_merge',
722 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
732 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
723 repo_route=True, repo_forbid_when_archived=True)
733 repo_route=True, repo_forbid_when_archived=True)
724 config.add_view(
734 config.add_view(
725 RepoPullRequestsView,
735 RepoPullRequestsView,
726 attr='pull_request_merge',
736 attr='pull_request_merge',
727 route_name='pullrequest_merge', request_method='POST',
737 route_name='pullrequest_merge', request_method='POST',
728 renderer='json_ext')
738 renderer='json_ext')
729
739
730 config.add_route(
740 config.add_route(
731 name='pullrequest_delete',
741 name='pullrequest_delete',
732 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
742 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
733 repo_route=True, repo_forbid_when_archived=True)
743 repo_route=True, repo_forbid_when_archived=True)
734 config.add_view(
744 config.add_view(
735 RepoPullRequestsView,
745 RepoPullRequestsView,
736 attr='pull_request_delete',
746 attr='pull_request_delete',
737 route_name='pullrequest_delete', request_method='POST',
747 route_name='pullrequest_delete', request_method='POST',
738 renderer='json_ext')
748 renderer='json_ext')
739
749
740 config.add_route(
750 config.add_route(
741 name='pullrequest_comment_create',
751 name='pullrequest_comment_create',
742 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
752 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
743 repo_route=True)
753 repo_route=True)
744 config.add_view(
754 config.add_view(
745 RepoPullRequestsView,
755 RepoPullRequestsView,
746 attr='pull_request_comment_create',
756 attr='pull_request_comment_create',
747 route_name='pullrequest_comment_create', request_method='POST',
757 route_name='pullrequest_comment_create', request_method='POST',
748 renderer='json_ext')
758 renderer='json_ext')
749
759
750 config.add_route(
760 config.add_route(
751 name='pullrequest_comment_edit',
761 name='pullrequest_comment_edit',
752 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
762 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/edit',
753 repo_route=True, repo_accepted_types=['hg', 'git'])
763 repo_route=True, repo_accepted_types=['hg', 'git'])
754 config.add_view(
764 config.add_view(
755 RepoPullRequestsView,
765 RepoPullRequestsView,
756 attr='pull_request_comment_edit',
766 attr='pull_request_comment_edit',
757 route_name='pullrequest_comment_edit', request_method='POST',
767 route_name='pullrequest_comment_edit', request_method='POST',
758 renderer='json_ext')
768 renderer='json_ext')
759
769
760 config.add_route(
770 config.add_route(
761 name='pullrequest_comment_delete',
771 name='pullrequest_comment_delete',
762 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
772 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
763 repo_route=True, repo_accepted_types=['hg', 'git'])
773 repo_route=True, repo_accepted_types=['hg', 'git'])
764 config.add_view(
774 config.add_view(
765 RepoPullRequestsView,
775 RepoPullRequestsView,
766 attr='pull_request_comment_delete',
776 attr='pull_request_comment_delete',
767 route_name='pullrequest_comment_delete', request_method='POST',
777 route_name='pullrequest_comment_delete', request_method='POST',
768 renderer='json_ext')
778 renderer='json_ext')
769
779
770 config.add_route(
780 config.add_route(
771 name='pullrequest_comments',
781 name='pullrequest_comments',
772 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
782 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
773 repo_route=True)
783 repo_route=True)
774 config.add_view(
784 config.add_view(
775 RepoPullRequestsView,
785 RepoPullRequestsView,
776 attr='pullrequest_comments',
786 attr='pullrequest_comments',
777 route_name='pullrequest_comments', request_method='POST',
787 route_name='pullrequest_comments', request_method='POST',
778 renderer='string_html', xhr=True)
788 renderer='string_html', xhr=True)
779
789
780 config.add_route(
790 config.add_route(
781 name='pullrequest_todos',
791 name='pullrequest_todos',
782 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
792 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
783 repo_route=True)
793 repo_route=True)
784 config.add_view(
794 config.add_view(
785 RepoPullRequestsView,
795 RepoPullRequestsView,
786 attr='pullrequest_todos',
796 attr='pullrequest_todos',
787 route_name='pullrequest_todos', request_method='POST',
797 route_name='pullrequest_todos', request_method='POST',
788 renderer='string_html', xhr=True)
798 renderer='string_html', xhr=True)
789
799
790 config.add_route(
800 config.add_route(
791 name='pullrequest_drafts',
801 name='pullrequest_drafts',
792 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts',
802 pattern=r'/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/drafts',
793 repo_route=True)
803 repo_route=True)
794 config.add_view(
804 config.add_view(
795 RepoPullRequestsView,
805 RepoPullRequestsView,
796 attr='pullrequest_drafts',
806 attr='pullrequest_drafts',
797 route_name='pullrequest_drafts', request_method='POST',
807 route_name='pullrequest_drafts', request_method='POST',
798 renderer='string_html', xhr=True)
808 renderer='string_html', xhr=True)
799
809
800 # Artifacts, (EE feature)
810 # Artifacts, (EE feature)
801 config.add_route(
811 config.add_route(
802 name='repo_artifacts_list',
812 name='repo_artifacts_list',
803 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
813 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
804 config.add_view(
814 config.add_view(
805 RepoArtifactsView,
815 RepoArtifactsView,
806 attr='repo_artifacts',
816 attr='repo_artifacts',
807 route_name='repo_artifacts_list', request_method='GET',
817 route_name='repo_artifacts_list', request_method='GET',
808 renderer='rhodecode:templates/artifacts/artifact_list.mako')
818 renderer='rhodecode:templates/artifacts/artifact_list.mako')
809
819
810 # Settings
820 # Settings
811 config.add_route(
821 config.add_route(
812 name='edit_repo',
822 name='edit_repo',
813 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
823 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
814 config.add_view(
824 config.add_view(
815 RepoSettingsView,
825 RepoSettingsView,
816 attr='edit_settings',
826 attr='edit_settings',
817 route_name='edit_repo', request_method='GET',
827 route_name='edit_repo', request_method='GET',
818 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
828 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
819 # update is POST on edit_repo
829 # update is POST on edit_repo
820 config.add_view(
830 config.add_view(
821 RepoSettingsView,
831 RepoSettingsView,
822 attr='edit_settings_update',
832 attr='edit_settings_update',
823 route_name='edit_repo', request_method='POST',
833 route_name='edit_repo', request_method='POST',
824 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
834 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
825
835
826 # Settings advanced
836 # Settings advanced
827 config.add_route(
837 config.add_route(
828 name='edit_repo_advanced',
838 name='edit_repo_advanced',
829 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
839 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
830 config.add_view(
840 config.add_view(
831 RepoSettingsAdvancedView,
841 RepoSettingsAdvancedView,
832 attr='edit_advanced',
842 attr='edit_advanced',
833 route_name='edit_repo_advanced', request_method='GET',
843 route_name='edit_repo_advanced', request_method='GET',
834 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
844 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
835
845
836 config.add_route(
846 config.add_route(
837 name='edit_repo_advanced_archive',
847 name='edit_repo_advanced_archive',
838 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
848 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
839 config.add_view(
849 config.add_view(
840 RepoSettingsAdvancedView,
850 RepoSettingsAdvancedView,
841 attr='edit_advanced_archive',
851 attr='edit_advanced_archive',
842 route_name='edit_repo_advanced_archive', request_method='POST',
852 route_name='edit_repo_advanced_archive', request_method='POST',
843 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
853 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
844
854
845 config.add_route(
855 config.add_route(
846 name='edit_repo_advanced_delete',
856 name='edit_repo_advanced_delete',
847 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
857 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
848 config.add_view(
858 config.add_view(
849 RepoSettingsAdvancedView,
859 RepoSettingsAdvancedView,
850 attr='edit_advanced_delete',
860 attr='edit_advanced_delete',
851 route_name='edit_repo_advanced_delete', request_method='POST',
861 route_name='edit_repo_advanced_delete', request_method='POST',
852 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
862 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
853
863
854 config.add_route(
864 config.add_route(
855 name='edit_repo_advanced_locking',
865 name='edit_repo_advanced_locking',
856 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
866 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
857 config.add_view(
867 config.add_view(
858 RepoSettingsAdvancedView,
868 RepoSettingsAdvancedView,
859 attr='edit_advanced_toggle_locking',
869 attr='edit_advanced_toggle_locking',
860 route_name='edit_repo_advanced_locking', request_method='POST',
870 route_name='edit_repo_advanced_locking', request_method='POST',
861 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
871 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
862
872
863 config.add_route(
873 config.add_route(
864 name='edit_repo_advanced_journal',
874 name='edit_repo_advanced_journal',
865 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
875 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
866 config.add_view(
876 config.add_view(
867 RepoSettingsAdvancedView,
877 RepoSettingsAdvancedView,
868 attr='edit_advanced_journal',
878 attr='edit_advanced_journal',
869 route_name='edit_repo_advanced_journal', request_method='POST',
879 route_name='edit_repo_advanced_journal', request_method='POST',
870 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
880 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
871
881
872 config.add_route(
882 config.add_route(
873 name='edit_repo_advanced_fork',
883 name='edit_repo_advanced_fork',
874 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
884 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
875 config.add_view(
885 config.add_view(
876 RepoSettingsAdvancedView,
886 RepoSettingsAdvancedView,
877 attr='edit_advanced_fork',
887 attr='edit_advanced_fork',
878 route_name='edit_repo_advanced_fork', request_method='POST',
888 route_name='edit_repo_advanced_fork', request_method='POST',
879 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
889 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
880
890
881 config.add_route(
891 config.add_route(
882 name='edit_repo_advanced_hooks',
892 name='edit_repo_advanced_hooks',
883 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
893 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
884 config.add_view(
894 config.add_view(
885 RepoSettingsAdvancedView,
895 RepoSettingsAdvancedView,
886 attr='edit_advanced_install_hooks',
896 attr='edit_advanced_install_hooks',
887 route_name='edit_repo_advanced_hooks', request_method='GET',
897 route_name='edit_repo_advanced_hooks', request_method='GET',
888 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
898 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
889
899
890 # Caches
900 # Caches
891 config.add_route(
901 config.add_route(
892 name='edit_repo_caches',
902 name='edit_repo_caches',
893 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
903 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
894 config.add_view(
904 config.add_view(
895 RepoCachesView,
905 RepoCachesView,
896 attr='repo_caches',
906 attr='repo_caches',
897 route_name='edit_repo_caches', request_method='GET',
907 route_name='edit_repo_caches', request_method='GET',
898 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
908 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
899 config.add_view(
909 config.add_view(
900 RepoCachesView,
910 RepoCachesView,
901 attr='repo_caches_purge',
911 attr='repo_caches_purge',
902 route_name='edit_repo_caches', request_method='POST')
912 route_name='edit_repo_caches', request_method='POST')
903
913
904 # Permissions
914 # Permissions
905 config.add_route(
915 config.add_route(
906 name='edit_repo_perms',
916 name='edit_repo_perms',
907 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
917 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
908 config.add_view(
918 config.add_view(
909 RepoSettingsPermissionsView,
919 RepoSettingsPermissionsView,
910 attr='edit_permissions',
920 attr='edit_permissions',
911 route_name='edit_repo_perms', request_method='GET',
921 route_name='edit_repo_perms', request_method='GET',
912 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
922 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
913 config.add_view(
923 config.add_view(
914 RepoSettingsPermissionsView,
924 RepoSettingsPermissionsView,
915 attr='edit_permissions_update',
925 attr='edit_permissions_update',
916 route_name='edit_repo_perms', request_method='POST',
926 route_name='edit_repo_perms', request_method='POST',
917 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
927 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
918
928
919 config.add_route(
929 config.add_route(
920 name='edit_repo_perms_set_private',
930 name='edit_repo_perms_set_private',
921 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
931 pattern='/{repo_name:.*?[^/]}/settings/permissions/set_private', repo_route=True)
922 config.add_view(
932 config.add_view(
923 RepoSettingsPermissionsView,
933 RepoSettingsPermissionsView,
924 attr='edit_permissions_set_private_repo',
934 attr='edit_permissions_set_private_repo',
925 route_name='edit_repo_perms_set_private', request_method='POST',
935 route_name='edit_repo_perms_set_private', request_method='POST',
926 renderer='json_ext')
936 renderer='json_ext')
927
937
928 # Permissions Branch (EE feature)
938 # Permissions Branch (EE feature)
929 config.add_route(
939 config.add_route(
930 name='edit_repo_perms_branch',
940 name='edit_repo_perms_branch',
931 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
941 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
932 config.add_view(
942 config.add_view(
933 RepoSettingsBranchPermissionsView,
943 RepoSettingsBranchPermissionsView,
934 attr='branch_permissions',
944 attr='branch_permissions',
935 route_name='edit_repo_perms_branch', request_method='GET',
945 route_name='edit_repo_perms_branch', request_method='GET',
936 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
946 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
937
947
938 config.add_route(
948 config.add_route(
939 name='edit_repo_perms_branch_delete',
949 name='edit_repo_perms_branch_delete',
940 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
950 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
941 repo_route=True)
951 repo_route=True)
942 ## Only implemented in EE
952 ## Only implemented in EE
943
953
944 # Maintenance
954 # Maintenance
945 config.add_route(
955 config.add_route(
946 name='edit_repo_maintenance',
956 name='edit_repo_maintenance',
947 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
957 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
948 config.add_view(
958 config.add_view(
949 RepoMaintenanceView,
959 RepoMaintenanceView,
950 attr='repo_maintenance',
960 attr='repo_maintenance',
951 route_name='edit_repo_maintenance', request_method='GET',
961 route_name='edit_repo_maintenance', request_method='GET',
952 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
962 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
953
963
954 config.add_route(
964 config.add_route(
955 name='edit_repo_maintenance_execute',
965 name='edit_repo_maintenance_execute',
956 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
966 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
957 config.add_view(
967 config.add_view(
958 RepoMaintenanceView,
968 RepoMaintenanceView,
959 attr='repo_maintenance_execute',
969 attr='repo_maintenance_execute',
960 route_name='edit_repo_maintenance_execute', request_method='GET',
970 route_name='edit_repo_maintenance_execute', request_method='GET',
961 renderer='json', xhr=True)
971 renderer='json', xhr=True)
962
972
963 # Fields
973 # Fields
964 config.add_route(
974 config.add_route(
965 name='edit_repo_fields',
975 name='edit_repo_fields',
966 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
976 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
967 config.add_view(
977 config.add_view(
968 RepoSettingsFieldsView,
978 RepoSettingsFieldsView,
969 attr='repo_field_edit',
979 attr='repo_field_edit',
970 route_name='edit_repo_fields', request_method='GET',
980 route_name='edit_repo_fields', request_method='GET',
971 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
981 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
972
982
973 config.add_route(
983 config.add_route(
974 name='edit_repo_fields_create',
984 name='edit_repo_fields_create',
975 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
985 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
976 config.add_view(
986 config.add_view(
977 RepoSettingsFieldsView,
987 RepoSettingsFieldsView,
978 attr='repo_field_create',
988 attr='repo_field_create',
979 route_name='edit_repo_fields_create', request_method='POST',
989 route_name='edit_repo_fields_create', request_method='POST',
980 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
990 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
981
991
982 config.add_route(
992 config.add_route(
983 name='edit_repo_fields_delete',
993 name='edit_repo_fields_delete',
984 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
994 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
985 config.add_view(
995 config.add_view(
986 RepoSettingsFieldsView,
996 RepoSettingsFieldsView,
987 attr='repo_field_delete',
997 attr='repo_field_delete',
988 route_name='edit_repo_fields_delete', request_method='POST',
998 route_name='edit_repo_fields_delete', request_method='POST',
989 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
999 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
990
1000
991 # quick actions: locking
1001 # quick actions: locking
992 config.add_route(
1002 config.add_route(
993 name='repo_settings_quick_actions',
1003 name='repo_settings_quick_actions',
994 pattern='/{repo_name:.*?[^/]}/settings/quick-action', repo_route=True)
1004 pattern='/{repo_name:.*?[^/]}/settings/quick-action', repo_route=True)
995 config.add_view(
1005 config.add_view(
996 RepoSettingsView,
1006 RepoSettingsView,
997 attr='repo_settings_quick_actions',
1007 attr='repo_settings_quick_actions',
998 route_name='repo_settings_quick_actions', request_method='GET',
1008 route_name='repo_settings_quick_actions', request_method='GET',
999 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1009 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1000
1010
1001 # Remote
1011 # Remote
1002 config.add_route(
1012 config.add_route(
1003 name='edit_repo_remote',
1013 name='edit_repo_remote',
1004 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
1014 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
1005 config.add_view(
1015 config.add_view(
1006 RepoSettingsRemoteView,
1016 RepoSettingsRemoteView,
1007 attr='repo_remote_edit_form',
1017 attr='repo_remote_edit_form',
1008 route_name='edit_repo_remote', request_method='GET',
1018 route_name='edit_repo_remote', request_method='GET',
1009 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1019 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1010
1020
1011 config.add_route(
1021 config.add_route(
1012 name='edit_repo_remote_pull',
1022 name='edit_repo_remote_pull',
1013 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
1023 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
1014 config.add_view(
1024 config.add_view(
1015 RepoSettingsRemoteView,
1025 RepoSettingsRemoteView,
1016 attr='repo_remote_pull_changes',
1026 attr='repo_remote_pull_changes',
1017 route_name='edit_repo_remote_pull', request_method='POST',
1027 route_name='edit_repo_remote_pull', request_method='POST',
1018 renderer=None)
1028 renderer=None)
1019
1029
1020 config.add_route(
1030 config.add_route(
1021 name='edit_repo_remote_push',
1031 name='edit_repo_remote_push',
1022 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
1032 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
1023
1033
1024 # Statistics
1034 # Statistics
1025 config.add_route(
1035 config.add_route(
1026 name='edit_repo_statistics',
1036 name='edit_repo_statistics',
1027 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
1037 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
1028 config.add_view(
1038 config.add_view(
1029 RepoSettingsView,
1039 RepoSettingsView,
1030 attr='edit_statistics_form',
1040 attr='edit_statistics_form',
1031 route_name='edit_repo_statistics', request_method='GET',
1041 route_name='edit_repo_statistics', request_method='GET',
1032 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1042 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1033
1043
1034 config.add_route(
1044 config.add_route(
1035 name='edit_repo_statistics_reset',
1045 name='edit_repo_statistics_reset',
1036 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
1046 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
1037 config.add_view(
1047 config.add_view(
1038 RepoSettingsView,
1048 RepoSettingsView,
1039 attr='repo_statistics_reset',
1049 attr='repo_statistics_reset',
1040 route_name='edit_repo_statistics_reset', request_method='POST',
1050 route_name='edit_repo_statistics_reset', request_method='POST',
1041 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1051 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1042
1052
1043 # Issue trackers
1053 # Issue trackers
1044 config.add_route(
1054 config.add_route(
1045 name='edit_repo_issuetracker',
1055 name='edit_repo_issuetracker',
1046 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
1056 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
1047 config.add_view(
1057 config.add_view(
1048 RepoSettingsIssueTrackersView,
1058 RepoSettingsIssueTrackersView,
1049 attr='repo_issuetracker',
1059 attr='repo_issuetracker',
1050 route_name='edit_repo_issuetracker', request_method='GET',
1060 route_name='edit_repo_issuetracker', request_method='GET',
1051 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1061 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1052
1062
1053 config.add_route(
1063 config.add_route(
1054 name='edit_repo_issuetracker_test',
1064 name='edit_repo_issuetracker_test',
1055 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
1065 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
1056 config.add_view(
1066 config.add_view(
1057 RepoSettingsIssueTrackersView,
1067 RepoSettingsIssueTrackersView,
1058 attr='repo_issuetracker_test',
1068 attr='repo_issuetracker_test',
1059 route_name='edit_repo_issuetracker_test', request_method='POST',
1069 route_name='edit_repo_issuetracker_test', request_method='POST',
1060 renderer='string', xhr=True)
1070 renderer='string', xhr=True)
1061
1071
1062 config.add_route(
1072 config.add_route(
1063 name='edit_repo_issuetracker_delete',
1073 name='edit_repo_issuetracker_delete',
1064 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
1074 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
1065 config.add_view(
1075 config.add_view(
1066 RepoSettingsIssueTrackersView,
1076 RepoSettingsIssueTrackersView,
1067 attr='repo_issuetracker_delete',
1077 attr='repo_issuetracker_delete',
1068 route_name='edit_repo_issuetracker_delete', request_method='POST',
1078 route_name='edit_repo_issuetracker_delete', request_method='POST',
1069 renderer='json_ext', xhr=True)
1079 renderer='json_ext', xhr=True)
1070
1080
1071 config.add_route(
1081 config.add_route(
1072 name='edit_repo_issuetracker_update',
1082 name='edit_repo_issuetracker_update',
1073 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
1083 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
1074 config.add_view(
1084 config.add_view(
1075 RepoSettingsIssueTrackersView,
1085 RepoSettingsIssueTrackersView,
1076 attr='repo_issuetracker_update',
1086 attr='repo_issuetracker_update',
1077 route_name='edit_repo_issuetracker_update', request_method='POST',
1087 route_name='edit_repo_issuetracker_update', request_method='POST',
1078 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1088 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1079
1089
1080 # VCS Settings
1090 # VCS Settings
1081 config.add_route(
1091 config.add_route(
1082 name='edit_repo_vcs',
1092 name='edit_repo_vcs',
1083 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
1093 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
1084 config.add_view(
1094 config.add_view(
1085 RepoSettingsVcsView,
1095 RepoSettingsVcsView,
1086 attr='repo_vcs_settings',
1096 attr='repo_vcs_settings',
1087 route_name='edit_repo_vcs', request_method='GET',
1097 route_name='edit_repo_vcs', request_method='GET',
1088 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1098 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1089
1099
1090 config.add_route(
1100 config.add_route(
1091 name='edit_repo_vcs_update',
1101 name='edit_repo_vcs_update',
1092 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
1102 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
1093 config.add_view(
1103 config.add_view(
1094 RepoSettingsVcsView,
1104 RepoSettingsVcsView,
1095 attr='repo_settings_vcs_update',
1105 attr='repo_settings_vcs_update',
1096 route_name='edit_repo_vcs_update', request_method='POST',
1106 route_name='edit_repo_vcs_update', request_method='POST',
1097 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1107 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1098
1108
1099 # svn pattern
1109 # svn pattern
1100 config.add_route(
1110 config.add_route(
1101 name='edit_repo_vcs_svn_pattern_delete',
1111 name='edit_repo_vcs_svn_pattern_delete',
1102 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
1112 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
1103 config.add_view(
1113 config.add_view(
1104 RepoSettingsVcsView,
1114 RepoSettingsVcsView,
1105 attr='repo_settings_delete_svn_pattern',
1115 attr='repo_settings_delete_svn_pattern',
1106 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
1116 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
1107 renderer='json_ext', xhr=True)
1117 renderer='json_ext', xhr=True)
1108
1118
1109 # Repo Review Rules (EE feature)
1119 # Repo Review Rules (EE feature)
1110 config.add_route(
1120 config.add_route(
1111 name='repo_reviewers',
1121 name='repo_reviewers',
1112 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
1122 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
1113 config.add_view(
1123 config.add_view(
1114 RepoReviewRulesView,
1124 RepoReviewRulesView,
1115 attr='repo_review_rules',
1125 attr='repo_review_rules',
1116 route_name='repo_reviewers', request_method='GET',
1126 route_name='repo_reviewers', request_method='GET',
1117 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1127 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1118
1128
1119 config.add_route(
1129 config.add_route(
1120 name='repo_default_reviewers_data',
1130 name='repo_default_reviewers_data',
1121 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
1131 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
1122 config.add_view(
1132 config.add_view(
1123 RepoReviewRulesView,
1133 RepoReviewRulesView,
1124 attr='repo_default_reviewers_data',
1134 attr='repo_default_reviewers_data',
1125 route_name='repo_default_reviewers_data', request_method='GET',
1135 route_name='repo_default_reviewers_data', request_method='GET',
1126 renderer='json_ext')
1136 renderer='json_ext')
1127
1137
1128 # Repo Automation (EE feature)
1138 # Repo Automation (EE feature)
1129 config.add_route(
1139 config.add_route(
1130 name='repo_automation',
1140 name='repo_automation',
1131 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
1141 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
1132 config.add_view(
1142 config.add_view(
1133 RepoAutomationView,
1143 RepoAutomationView,
1134 attr='repo_automation',
1144 attr='repo_automation',
1135 route_name='repo_automation', request_method='GET',
1145 route_name='repo_automation', request_method='GET',
1136 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1146 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1137
1147
1138 # Strip
1148 # Strip
1139 config.add_route(
1149 config.add_route(
1140 name='edit_repo_strip',
1150 name='edit_repo_strip',
1141 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
1151 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
1142 config.add_view(
1152 config.add_view(
1143 RepoStripView,
1153 RepoStripView,
1144 attr='strip',
1154 attr='strip',
1145 route_name='edit_repo_strip', request_method='GET',
1155 route_name='edit_repo_strip', request_method='GET',
1146 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1156 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1147
1157
1148 config.add_route(
1158 config.add_route(
1149 name='strip_check',
1159 name='strip_check',
1150 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
1160 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
1151 config.add_view(
1161 config.add_view(
1152 RepoStripView,
1162 RepoStripView,
1153 attr='strip_check',
1163 attr='strip_check',
1154 route_name='strip_check', request_method='POST',
1164 route_name='strip_check', request_method='POST',
1155 renderer='json', xhr=True)
1165 renderer='json', xhr=True)
1156
1166
1157 config.add_route(
1167 config.add_route(
1158 name='strip_execute',
1168 name='strip_execute',
1159 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
1169 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
1160 config.add_view(
1170 config.add_view(
1161 RepoStripView,
1171 RepoStripView,
1162 attr='strip_execute',
1172 attr='strip_execute',
1163 route_name='strip_execute', request_method='POST',
1173 route_name='strip_execute', request_method='POST',
1164 renderer='json', xhr=True)
1174 renderer='json', xhr=True)
1165
1175
1166 # Audit logs
1176 # Audit logs
1167 config.add_route(
1177 config.add_route(
1168 name='edit_repo_audit_logs',
1178 name='edit_repo_audit_logs',
1169 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
1179 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
1170 config.add_view(
1180 config.add_view(
1171 AuditLogsView,
1181 AuditLogsView,
1172 attr='repo_audit_logs',
1182 attr='repo_audit_logs',
1173 route_name='edit_repo_audit_logs', request_method='GET',
1183 route_name='edit_repo_audit_logs', request_method='GET',
1174 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1184 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
1175
1185
1176 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
1186 # ATOM/RSS Feed, shouldn't contain slashes for outlook compatibility
1177 config.add_route(
1187 config.add_route(
1178 name='rss_feed_home',
1188 name='rss_feed_home',
1179 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
1189 pattern='/{repo_name:.*?[^/]}/feed-rss', repo_route=True)
1180 config.add_view(
1190 config.add_view(
1181 RepoFeedView,
1191 RepoFeedView,
1182 attr='rss',
1192 attr='rss',
1183 route_name='rss_feed_home', request_method='GET', renderer=None)
1193 route_name='rss_feed_home', request_method='GET', renderer=None)
1184
1194
1185 config.add_route(
1195 config.add_route(
1186 name='rss_feed_home_old',
1196 name='rss_feed_home_old',
1187 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
1197 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
1188 config.add_view(
1198 config.add_view(
1189 RepoFeedView,
1199 RepoFeedView,
1190 attr='rss',
1200 attr='rss',
1191 route_name='rss_feed_home_old', request_method='GET', renderer=None)
1201 route_name='rss_feed_home_old', request_method='GET', renderer=None)
1192
1202
1193 config.add_route(
1203 config.add_route(
1194 name='atom_feed_home',
1204 name='atom_feed_home',
1195 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
1205 pattern='/{repo_name:.*?[^/]}/feed-atom', repo_route=True)
1196 config.add_view(
1206 config.add_view(
1197 RepoFeedView,
1207 RepoFeedView,
1198 attr='atom',
1208 attr='atom',
1199 route_name='atom_feed_home', request_method='GET', renderer=None)
1209 route_name='atom_feed_home', request_method='GET', renderer=None)
1200
1210
1201 config.add_route(
1211 config.add_route(
1202 name='atom_feed_home_old',
1212 name='atom_feed_home_old',
1203 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
1213 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
1204 config.add_view(
1214 config.add_view(
1205 RepoFeedView,
1215 RepoFeedView,
1206 attr='atom',
1216 attr='atom',
1207 route_name='atom_feed_home_old', request_method='GET', renderer=None)
1217 route_name='atom_feed_home_old', request_method='GET', renderer=None)
1208
1218
1209 # NOTE(marcink): needs to be at the end for catch-all
1219 # NOTE(marcink): needs to be at the end for catch-all
1210 add_route_with_slash(
1220 add_route_with_slash(
1211 config,
1221 config,
1212 name='repo_summary',
1222 name='repo_summary',
1213 pattern='/{repo_name:.*?[^/]}', repo_route=True)
1223 pattern='/{repo_name:.*?[^/]}', repo_route=True)
1214 config.add_view(
1224 config.add_view(
1215 RepoSummaryView,
1225 RepoSummaryView,
1216 attr='summary',
1226 attr='summary',
1217 route_name='repo_summary', request_method='GET',
1227 route_name='repo_summary', request_method='GET',
1218 renderer='rhodecode:templates/summary/summary.mako')
1228 renderer='rhodecode:templates/summary/summary.mako')
1219
1229
1220 # TODO(marcink): there's no such route??
1230 # TODO(marcink): there's no such route??
1221 config.add_view(
1231 config.add_view(
1222 RepoSummaryView,
1232 RepoSummaryView,
1223 attr='summary',
1233 attr='summary',
1224 route_name='repo_summary_slash', request_method='GET',
1234 route_name='repo_summary_slash', request_method='GET',
1225 renderer='rhodecode:templates/summary/summary.mako') No newline at end of file
1235 renderer='rhodecode:templates/summary/summary.mako')
@@ -1,1090 +1,1152 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import os
20 import os
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24 from collections import OrderedDict
24 from collections import OrderedDict
25
25
26 from rhodecode.apps.repository.tests.test_repo_compare import ComparePage
26 from rhodecode.apps.repository.tests.test_repo_compare import ComparePage
27 from rhodecode.apps.repository.views.repo_files import RepoFilesView, get_archive_name, get_path_sha
27 from rhodecode.apps.repository.views.repo_files import RepoFilesView, get_archive_name, get_path_sha
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
30 from rhodecode.lib.str_utils import safe_str
30 from rhodecode.lib.str_utils import safe_str
31 from rhodecode.lib.vcs import nodes
31 from rhodecode.lib.vcs import nodes
32 from rhodecode.lib.vcs.conf import settings
32 from rhodecode.lib.vcs.conf import settings
33 from rhodecode.model.db import Session, Repository
33 from rhodecode.model.db import Session, Repository
34
34
35 from rhodecode.tests import assert_session_flash
35 from rhodecode.tests import assert_session_flash
36 from rhodecode.tests.fixture import Fixture
36 from rhodecode.tests.fixture import Fixture
37 from rhodecode.tests.routes import route_path
37 from rhodecode.tests.routes import route_path
38
38
39
39
40 fixture = Fixture()
40 fixture = Fixture()
41
41
42
42
43 def get_node_history(backend_type):
43 def get_node_history(backend_type):
44 return {
44 return {
45 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')),
45 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')),
46 'git': json.loads(fixture.load_resource('git_node_history_response.json')),
46 'git': json.loads(fixture.load_resource('git_node_history_response.json')),
47 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')),
47 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')),
48 }[backend_type]
48 }[backend_type]
49
49
50
50
51 def assert_files_in_response(response, files, params):
51 def assert_files_in_response(response, files, params):
52 template = (
52 template = (
53 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
53 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
54 _assert_items_in_response(response, files, template, params)
54 _assert_items_in_response(response, files, template, params)
55
55
56
56
57 def assert_dirs_in_response(response, dirs, params):
57 def assert_dirs_in_response(response, dirs, params):
58 template = (
58 template = (
59 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
59 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
60 _assert_items_in_response(response, dirs, template, params)
60 _assert_items_in_response(response, dirs, template, params)
61
61
62
62
63 def _assert_items_in_response(response, items, template, params):
63 def _assert_items_in_response(response, items, template, params):
64 for item in items:
64 for item in items:
65 item_params = {'name': item}
65 item_params = {'name': item}
66 item_params.update(params)
66 item_params.update(params)
67 response.mustcontain(template % item_params)
67 response.mustcontain(template % item_params)
68
68
69
69
70 def assert_timeago_in_response(response, items, params):
70 def assert_timeago_in_response(response, items, params):
71 for item in items:
71 for item in items:
72 response.mustcontain(h.age_component(params['date']))
72 response.mustcontain(h.age_component(params['date']))
73
73
74
74
75 @pytest.mark.usefixtures("app")
75 @pytest.mark.usefixtures("app")
76 class TestFilesViews(object):
76 class TestFilesViews(object):
77
77
78 def test_show_files(self, backend):
78 def test_show_files(self, backend):
79 response = self.app.get(
79 response = self.app.get(
80 route_path('repo_files',
80 route_path('repo_files',
81 repo_name=backend.repo_name,
81 repo_name=backend.repo_name,
82 commit_id='tip', f_path='/'))
82 commit_id='tip', f_path='/'))
83 commit = backend.repo.get_commit()
83 commit = backend.repo.get_commit()
84
84
85 params = {
85 params = {
86 'repo_name': backend.repo_name,
86 'repo_name': backend.repo_name,
87 'commit_id': commit.raw_id,
87 'commit_id': commit.raw_id,
88 'date': commit.date
88 'date': commit.date
89 }
89 }
90 assert_dirs_in_response(response, ['docs', 'vcs'], params)
90 assert_dirs_in_response(response, ['docs', 'vcs'], params)
91 files = [
91 files = [
92 '.gitignore',
92 '.gitignore',
93 '.hgignore',
93 '.hgignore',
94 '.hgtags',
94 '.hgtags',
95 # TODO: missing in Git
95 # TODO: missing in Git
96 # '.travis.yml',
96 # '.travis.yml',
97 'MANIFEST.in',
97 'MANIFEST.in',
98 'README.rst',
98 'README.rst',
99 # TODO: File is missing in svn repository
99 # TODO: File is missing in svn repository
100 # 'run_test_and_report.sh',
100 # 'run_test_and_report.sh',
101 'setup.cfg',
101 'setup.cfg',
102 'setup.py',
102 'setup.py',
103 'test_and_report.sh',
103 'test_and_report.sh',
104 'tox.ini',
104 'tox.ini',
105 ]
105 ]
106 assert_files_in_response(response, files, params)
106 assert_files_in_response(response, files, params)
107 assert_timeago_in_response(response, files, params)
107 assert_timeago_in_response(response, files, params)
108
108
109 def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
109 def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
110 repo = backend_hg['subrepos']
110 repo = backend_hg['subrepos']
111 response = self.app.get(
111 response = self.app.get(
112 route_path('repo_files',
112 route_path('repo_files',
113 repo_name=repo.repo_name,
113 repo_name=repo.repo_name,
114 commit_id='tip', f_path='/'))
114 commit_id='tip', f_path='/'))
115 assert_response = response.assert_response()
115 assert_response = response.assert_response()
116 assert_response.contains_one_link(
116 assert_response.contains_one_link(
117 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
117 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
118
118
119 def test_show_files_links_submodules_with_absolute_url_subpaths(
119 def test_show_files_links_submodules_with_absolute_url_subpaths(
120 self, backend_hg):
120 self, backend_hg):
121 repo = backend_hg['subrepos']
121 repo = backend_hg['subrepos']
122 response = self.app.get(
122 response = self.app.get(
123 route_path('repo_files',
123 route_path('repo_files',
124 repo_name=repo.repo_name,
124 repo_name=repo.repo_name,
125 commit_id='tip', f_path='/'))
125 commit_id='tip', f_path='/'))
126 assert_response = response.assert_response()
126 assert_response = response.assert_response()
127 assert_response.contains_one_link(
127 assert_response.contains_one_link(
128 'subpaths-path @ 000000000000',
128 'subpaths-path @ 000000000000',
129 'http://sub-base.example.com/subpaths-path')
129 'http://sub-base.example.com/subpaths-path')
130
130
131 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
131 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
132 def test_files_menu(self, backend):
132 def test_files_menu(self, backend):
133 new_branch = "temp_branch_name"
133 new_branch = "temp_branch_name"
134 commits = [
134 commits = [
135 {'message': 'a'},
135 {'message': 'a'},
136 {'message': 'b', 'branch': new_branch}
136 {'message': 'b', 'branch': new_branch}
137 ]
137 ]
138 backend.create_repo(commits)
138 backend.create_repo(commits)
139 backend.repo.landing_rev = f"branch:{new_branch}"
139 backend.repo.landing_rev = f"branch:{new_branch}"
140 Session().commit()
140 Session().commit()
141
141
142 # get response based on tip and not new commit
142 # get response based on tip and not new commit
143 response = self.app.get(
143 response = self.app.get(
144 route_path('repo_files',
144 route_path('repo_files',
145 repo_name=backend.repo_name,
145 repo_name=backend.repo_name,
146 commit_id='tip', f_path='/'))
146 commit_id='tip', f_path='/'))
147
147
148 # make sure Files menu url is not tip but new commit
148 # make sure Files menu url is not tip but new commit
149 landing_rev = backend.repo.landing_ref_name
149 landing_rev = backend.repo.landing_ref_name
150 files_url = route_path('repo_files:default_path',
150 files_url = route_path('repo_files:default_path',
151 repo_name=backend.repo_name,
151 repo_name=backend.repo_name,
152 commit_id=landing_rev, params={'at': landing_rev})
152 commit_id=landing_rev, params={'at': landing_rev})
153
153
154 assert landing_rev != 'tip'
154 assert landing_rev != 'tip'
155 response.mustcontain(f'<li class="active"><a class="menulink" href="{files_url}">')
155 response.mustcontain(f'<li class="active"><a class="menulink" href="{files_url}">')
156
156
157 def test_show_files_commit(self, backend):
157 def test_show_files_commit(self, backend):
158 commit = backend.repo.get_commit(commit_idx=32)
158 commit = backend.repo.get_commit(commit_idx=32)
159
159
160 response = self.app.get(
160 response = self.app.get(
161 route_path('repo_files',
161 route_path('repo_files',
162 repo_name=backend.repo_name,
162 repo_name=backend.repo_name,
163 commit_id=commit.raw_id, f_path='/'))
163 commit_id=commit.raw_id, f_path='/'))
164
164
165 dirs = ['docs', 'tests']
165 dirs = ['docs', 'tests']
166 files = ['README.rst']
166 files = ['README.rst']
167 params = {
167 params = {
168 'repo_name': backend.repo_name,
168 'repo_name': backend.repo_name,
169 'commit_id': commit.raw_id,
169 'commit_id': commit.raw_id,
170 }
170 }
171 assert_dirs_in_response(response, dirs, params)
171 assert_dirs_in_response(response, dirs, params)
172 assert_files_in_response(response, files, params)
172 assert_files_in_response(response, files, params)
173
173
174 def test_show_files_different_branch(self, backend):
174 def test_show_files_different_branch(self, backend):
175 branches = dict(
175 branches = dict(
176 hg=(150, ['git']),
176 hg=(150, ['git']),
177 # TODO: Git test repository does not contain other branches
177 # TODO: Git test repository does not contain other branches
178 git=(633, ['master']),
178 git=(633, ['master']),
179 # TODO: Branch support in Subversion
179 # TODO: Branch support in Subversion
180 svn=(150, [])
180 svn=(150, [])
181 )
181 )
182 idx, branches = branches[backend.alias]
182 idx, branches = branches[backend.alias]
183 commit = backend.repo.get_commit(commit_idx=idx)
183 commit = backend.repo.get_commit(commit_idx=idx)
184 response = self.app.get(
184 response = self.app.get(
185 route_path('repo_files',
185 route_path('repo_files',
186 repo_name=backend.repo_name,
186 repo_name=backend.repo_name,
187 commit_id=commit.raw_id, f_path='/'))
187 commit_id=commit.raw_id, f_path='/'))
188
188
189 assert_response = response.assert_response()
189 assert_response = response.assert_response()
190 for branch in branches:
190 for branch in branches:
191 assert_response.element_contains('.tags .branchtag', branch)
191 assert_response.element_contains('.tags .branchtag', branch)
192
192
193 def test_show_files_paging(self, backend):
193 def test_show_files_paging(self, backend):
194 repo = backend.repo
194 repo = backend.repo
195 indexes = [73, 92, 109, 1, 0]
195 indexes = [73, 92, 109, 1, 0]
196 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
196 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
197 for rev in indexes]
197 for rev in indexes]
198
198
199 for idx in idx_map:
199 for idx in idx_map:
200 response = self.app.get(
200 response = self.app.get(
201 route_path('repo_files',
201 route_path('repo_files',
202 repo_name=backend.repo_name,
202 repo_name=backend.repo_name,
203 commit_id=idx[1], f_path='/'))
203 commit_id=idx[1], f_path='/'))
204
204
205 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
205 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
206
206
207 def test_file_source(self, backend):
207 def test_file_source(self, backend):
208 commit = backend.repo.get_commit(commit_idx=167)
208 commit = backend.repo.get_commit(commit_idx=167)
209 response = self.app.get(
209 response = self.app.get(
210 route_path('repo_files',
210 route_path('repo_files',
211 repo_name=backend.repo_name,
211 repo_name=backend.repo_name,
212 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
212 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
213
213
214 msgbox = """<div class="commit">%s</div>"""
214 msgbox = """<div class="commit">%s</div>"""
215 response.mustcontain(msgbox % (commit.message, ))
215 response.mustcontain(msgbox % (commit.message, ))
216
216
217 assert_response = response.assert_response()
217 assert_response = response.assert_response()
218 if commit.branch:
218 if commit.branch:
219 assert_response.element_contains(
219 assert_response.element_contains(
220 '.tags.tags-main .branchtag', commit.branch)
220 '.tags.tags-main .branchtag', commit.branch)
221 if commit.tags:
221 if commit.tags:
222 for tag in commit.tags:
222 for tag in commit.tags:
223 assert_response.element_contains('.tags.tags-main .tagtag', tag)
223 assert_response.element_contains('.tags.tags-main .tagtag', tag)
224
224
225 def test_file_source_annotated(self, backend):
225 def test_file_source_annotated(self, backend):
226 response = self.app.get(
226 response = self.app.get(
227 route_path('repo_files:annotated',
227 route_path('repo_files:annotated',
228 repo_name=backend.repo_name,
228 repo_name=backend.repo_name,
229 commit_id='tip', f_path='vcs/nodes.py'))
229 commit_id='tip', f_path='vcs/nodes.py'))
230 expected_commits = {
230 expected_commits = {
231 'hg': 'r356',
231 'hg': 'r356',
232 'git': 'r345',
232 'git': 'r345',
233 'svn': 'r208',
233 'svn': 'r208',
234 }
234 }
235 response.mustcontain(expected_commits[backend.alias])
235 response.mustcontain(expected_commits[backend.alias])
236
236
237 def test_file_source_authors(self, backend):
237 def test_file_source_authors(self, backend):
238 response = self.app.get(
238 response = self.app.get(
239 route_path('repo_file_authors',
239 route_path('repo_file_authors',
240 repo_name=backend.repo_name,
240 repo_name=backend.repo_name,
241 commit_id='tip', f_path='vcs/nodes.py'))
241 commit_id='tip', f_path='vcs/nodes.py'))
242 expected_authors = {
242 expected_authors = {
243 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
243 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
244 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
244 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
245 'svn': ('marcin', 'lukasz'),
245 'svn': ('marcin', 'lukasz'),
246 }
246 }
247
247
248 for author in expected_authors[backend.alias]:
248 for author in expected_authors[backend.alias]:
249 response.mustcontain(author)
249 response.mustcontain(author)
250
250
251 def test_file_source_authors_with_annotation(self, backend):
251 def test_file_source_authors_with_annotation(self, backend):
252 response = self.app.get(
252 response = self.app.get(
253 route_path('repo_file_authors',
253 route_path('repo_file_authors',
254 repo_name=backend.repo_name,
254 repo_name=backend.repo_name,
255 commit_id='tip', f_path='vcs/nodes.py',
255 commit_id='tip', f_path='vcs/nodes.py',
256 params=dict(annotate=1)))
256 params=dict(annotate=1)))
257 expected_authors = {
257 expected_authors = {
258 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
258 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
259 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
259 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
260 'svn': ('marcin', 'lukasz'),
260 'svn': ('marcin', 'lukasz'),
261 }
261 }
262
262
263 for author in expected_authors[backend.alias]:
263 for author in expected_authors[backend.alias]:
264 response.mustcontain(author)
264 response.mustcontain(author)
265
265
266 def test_file_source_history(self, backend, xhr_header):
266 def test_file_source_history(self, backend, xhr_header):
267 response = self.app.get(
267 response = self.app.get(
268 route_path('repo_file_history',
268 route_path('repo_file_history',
269 repo_name=backend.repo_name,
269 repo_name=backend.repo_name,
270 commit_id='tip', f_path='vcs/nodes.py'),
270 commit_id='tip', f_path='vcs/nodes.py'),
271 extra_environ=xhr_header)
271 extra_environ=xhr_header)
272 assert get_node_history(backend.alias) == json.loads(response.body)
272 assert get_node_history(backend.alias) == json.loads(response.body)
273
273
274 def test_file_source_history_svn(self, backend_svn, xhr_header):
274 def test_file_source_history_svn(self, backend_svn, xhr_header):
275 simple_repo = backend_svn['svn-simple-layout']
275 simple_repo = backend_svn['svn-simple-layout']
276 response = self.app.get(
276 response = self.app.get(
277 route_path('repo_file_history',
277 route_path('repo_file_history',
278 repo_name=simple_repo.repo_name,
278 repo_name=simple_repo.repo_name,
279 commit_id='tip', f_path='trunk/example.py'),
279 commit_id='tip', f_path='trunk/example.py'),
280 extra_environ=xhr_header)
280 extra_environ=xhr_header)
281
281
282 expected_data = json.loads(
282 expected_data = json.loads(
283 fixture.load_resource('svn_node_history_branches.json'))
283 fixture.load_resource('svn_node_history_branches.json'))
284
284
285 assert expected_data == response.json
285 assert expected_data == response.json
286
286
287 def test_file_source_history_with_annotation(self, backend, xhr_header):
287 def test_file_source_history_with_annotation(self, backend, xhr_header):
288 response = self.app.get(
288 response = self.app.get(
289 route_path('repo_file_history',
289 route_path('repo_file_history',
290 repo_name=backend.repo_name,
290 repo_name=backend.repo_name,
291 commit_id='tip', f_path='vcs/nodes.py',
291 commit_id='tip', f_path='vcs/nodes.py',
292 params=dict(annotate=1)),
292 params=dict(annotate=1)),
293
293
294 extra_environ=xhr_header)
294 extra_environ=xhr_header)
295 assert get_node_history(backend.alias) == json.loads(response.body)
295 assert get_node_history(backend.alias) == json.loads(response.body)
296
296
297 def test_tree_search_top_level(self, backend, xhr_header):
297 def test_tree_search_top_level(self, backend, xhr_header):
298 commit = backend.repo.get_commit(commit_idx=173)
298 commit = backend.repo.get_commit(commit_idx=173)
299 response = self.app.get(
299 response = self.app.get(
300 route_path('repo_files_nodelist',
300 route_path('repo_files_nodelist',
301 repo_name=backend.repo_name,
301 repo_name=backend.repo_name,
302 commit_id=commit.raw_id, f_path='/'),
302 commit_id=commit.raw_id, f_path='/'),
303 extra_environ=xhr_header)
303 extra_environ=xhr_header)
304 assert 'nodes' in response.json
304 assert 'nodes' in response.json
305 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
305 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
306
306
307 def test_tree_search_missing_xhr(self, backend):
307 def test_tree_search_missing_xhr(self, backend):
308 self.app.get(
308 self.app.get(
309 route_path('repo_files_nodelist',
309 route_path('repo_files_nodelist',
310 repo_name=backend.repo_name,
310 repo_name=backend.repo_name,
311 commit_id='tip', f_path='/'),
311 commit_id='tip', f_path='/'),
312 status=404)
312 status=404)
313
313
314 def test_tree_search_at_path(self, backend, xhr_header):
314 def test_tree_search_at_path(self, backend, xhr_header):
315 commit = backend.repo.get_commit(commit_idx=173)
315 commit = backend.repo.get_commit(commit_idx=173)
316 response = self.app.get(
316 response = self.app.get(
317 route_path('repo_files_nodelist',
317 route_path('repo_files_nodelist',
318 repo_name=backend.repo_name,
318 repo_name=backend.repo_name,
319 commit_id=commit.raw_id, f_path='/docs'),
319 commit_id=commit.raw_id, f_path='/docs'),
320 extra_environ=xhr_header)
320 extra_environ=xhr_header)
321 assert 'nodes' in response.json
321 assert 'nodes' in response.json
322 nodes = response.json['nodes']
322 nodes = response.json['nodes']
323 assert {'name': 'docs/api', 'type': 'dir'} in nodes
323 assert {'name': 'docs/api', 'type': 'dir'} in nodes
324 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
324 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
325
325
326 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
326 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
327 commit = backend.repo.get_commit(commit_idx=173)
327 commit = backend.repo.get_commit(commit_idx=173)
328 response = self.app.get(
328 response = self.app.get(
329 route_path('repo_files_nodelist',
329 route_path('repo_files_nodelist',
330 repo_name=backend.repo_name,
330 repo_name=backend.repo_name,
331 commit_id=commit.raw_id, f_path='/docs/api'),
331 commit_id=commit.raw_id, f_path='/docs/api'),
332 extra_environ=xhr_header)
332 extra_environ=xhr_header)
333 assert 'nodes' in response.json
333 assert 'nodes' in response.json
334 nodes = response.json['nodes']
334 nodes = response.json['nodes']
335 assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes
335 assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes
336
336
337 def test_tree_search_at_path_missing_xhr(self, backend):
337 def test_tree_search_at_path_missing_xhr(self, backend):
338 self.app.get(
338 self.app.get(
339 route_path('repo_files_nodelist',
339 route_path('repo_files_nodelist',
340 repo_name=backend.repo_name,
340 repo_name=backend.repo_name,
341 commit_id='tip', f_path='/docs'),
341 commit_id='tip', f_path='/docs'),
342 status=404)
342 status=404)
343
343
344 def test_nodetree(self, backend, xhr_header):
344 def test_nodetree(self, backend, xhr_header):
345 commit = backend.repo.get_commit(commit_idx=173)
345 commit = backend.repo.get_commit(commit_idx=173)
346 response = self.app.get(
346 response = self.app.get(
347 route_path('repo_nodetree_full',
347 route_path('repo_nodetree_full',
348 repo_name=backend.repo_name,
348 repo_name=backend.repo_name,
349 commit_id=commit.raw_id, f_path='/'),
349 commit_id=commit.raw_id, f_path='/'),
350 extra_environ=xhr_header)
350 extra_environ=xhr_header)
351
351
352 assert_response = response.assert_response()
352 assert_response = response.assert_response()
353
353
354 for attr in ['data-commit-id', 'data-date', 'data-author']:
354 for attr in ['data-commit-id', 'data-date', 'data-author']:
355 elements = assert_response.get_elements('[{}]'.format(attr))
355 elements = assert_response.get_elements('[{}]'.format(attr))
356 assert len(elements) > 1
356 assert len(elements) > 1
357
357
358 for element in elements:
358 for element in elements:
359 assert element.get(attr)
359 assert element.get(attr)
360
360
361 def test_nodetree_if_file(self, backend, xhr_header):
361 def test_nodetree_if_file(self, backend, xhr_header):
362 commit = backend.repo.get_commit(commit_idx=173)
362 commit = backend.repo.get_commit(commit_idx=173)
363 response = self.app.get(
363 response = self.app.get(
364 route_path('repo_nodetree_full',
364 route_path('repo_nodetree_full',
365 repo_name=backend.repo_name,
365 repo_name=backend.repo_name,
366 commit_id=commit.raw_id, f_path='README.rst'),
366 commit_id=commit.raw_id, f_path='README.rst'),
367 extra_environ=xhr_header)
367 extra_environ=xhr_header)
368 assert response.text == ''
368 assert response.text == ''
369
369
370 def test_nodetree_wrong_path(self, backend, xhr_header):
370 def test_nodetree_wrong_path(self, backend, xhr_header):
371 commit = backend.repo.get_commit(commit_idx=173)
371 commit = backend.repo.get_commit(commit_idx=173)
372 response = self.app.get(
372 response = self.app.get(
373 route_path('repo_nodetree_full',
373 route_path('repo_nodetree_full',
374 repo_name=backend.repo_name,
374 repo_name=backend.repo_name,
375 commit_id=commit.raw_id, f_path='/dont-exist'),
375 commit_id=commit.raw_id, f_path='/dont-exist'),
376 extra_environ=xhr_header)
376 extra_environ=xhr_header)
377
377
378 err = 'error: There is no file nor ' \
378 err = 'error: There is no file nor ' \
379 'directory at the given path'
379 'directory at the given path'
380 assert err in response.text
380 assert err in response.text
381
381
382 def test_nodetree_missing_xhr(self, backend):
382 def test_nodetree_missing_xhr(self, backend):
383 self.app.get(
383 self.app.get(
384 route_path('repo_nodetree_full',
384 route_path('repo_nodetree_full',
385 repo_name=backend.repo_name,
385 repo_name=backend.repo_name,
386 commit_id='tip', f_path='/'),
386 commit_id='tip', f_path='/'),
387 status=404)
387 status=404)
388
388
389
389
390 @pytest.mark.usefixtures("app", "autologin_user")
390 @pytest.mark.usefixtures("app", "autologin_user")
391 class TestRawFileHandling(object):
391 class TestRawFileHandling(object):
392
392
393 def test_download_file(self, backend):
393 def test_download_file(self, backend):
394 commit = backend.repo.get_commit(commit_idx=173)
394 commit = backend.repo.get_commit(commit_idx=173)
395 response = self.app.get(
395 response = self.app.get(
396 route_path('repo_file_download',
396 route_path('repo_file_download',
397 repo_name=backend.repo_name,
397 repo_name=backend.repo_name,
398 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
398 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
399
399
400 assert response.content_disposition == 'attachment; filename="nodes.py"; filename*=UTF-8\'\'nodes.py'
400 assert response.content_disposition == 'attachment; filename="nodes.py"; filename*=UTF-8\'\'nodes.py'
401 assert response.content_type == "text/x-python"
401 assert response.content_type == "text/x-python"
402
402
403 def test_download_file_wrong_cs(self, backend):
403 def test_download_file_wrong_cs(self, backend):
404 raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
404 raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
405
405
406 response = self.app.get(
406 response = self.app.get(
407 route_path('repo_file_download',
407 route_path('repo_file_download',
408 repo_name=backend.repo_name,
408 repo_name=backend.repo_name,
409 commit_id=raw_id, f_path='vcs/nodes.svg'),
409 commit_id=raw_id, f_path='vcs/nodes.svg'),
410 status=404)
410 status=404)
411
411
412 msg = """No such commit exists for this repository"""
412 msg = """No such commit exists for this repository"""
413 response.mustcontain(msg)
413 response.mustcontain(msg)
414
414
415 def test_download_file_wrong_f_path(self, backend):
415 def test_download_file_wrong_f_path(self, backend):
416 commit = backend.repo.get_commit(commit_idx=173)
416 commit = backend.repo.get_commit(commit_idx=173)
417 f_path = 'vcs/ERRORnodes.py'
417 f_path = 'vcs/ERRORnodes.py'
418
418
419 response = self.app.get(
419 response = self.app.get(
420 route_path('repo_file_download',
420 route_path('repo_file_download',
421 repo_name=backend.repo_name,
421 repo_name=backend.repo_name,
422 commit_id=commit.raw_id, f_path=f_path),
422 commit_id=commit.raw_id, f_path=f_path),
423 status=404)
423 status=404)
424
424
425 msg = (
425 msg = (
426 "There is no file nor directory at the given path: "
426 "There is no file nor directory at the given path: "
427 "`%s` at commit %s" % (f_path, commit.short_id))
427 "`%s` at commit %s" % (f_path, commit.short_id))
428 response.mustcontain(msg)
428 response.mustcontain(msg)
429
429
430 def test_file_raw(self, backend):
430 def test_file_raw(self, backend):
431 commit = backend.repo.get_commit(commit_idx=173)
431 commit = backend.repo.get_commit(commit_idx=173)
432 response = self.app.get(
432 response = self.app.get(
433 route_path('repo_file_raw',
433 route_path('repo_file_raw',
434 repo_name=backend.repo_name,
434 repo_name=backend.repo_name,
435 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
435 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
436
436
437 assert response.content_type == "text/plain"
437 assert response.content_type == "text/plain"
438
438
439 def test_file_raw_binary(self, backend):
439 def test_file_raw_binary(self, backend):
440 commit = backend.repo.get_commit()
440 commit = backend.repo.get_commit()
441 response = self.app.get(
441 response = self.app.get(
442 route_path('repo_file_raw',
442 route_path('repo_file_raw',
443 repo_name=backend.repo_name,
443 repo_name=backend.repo_name,
444 commit_id=commit.raw_id,
444 commit_id=commit.raw_id,
445 f_path='docs/theme/ADC/static/breadcrumb_background.png'),)
445 f_path='docs/theme/ADC/static/breadcrumb_background.png'),)
446
446
447 assert response.content_disposition == 'inline'
447 assert response.content_disposition == 'inline'
448
448
449 def test_raw_file_wrong_cs(self, backend):
449 def test_raw_file_wrong_cs(self, backend):
450 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
450 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
451
451
452 response = self.app.get(
452 response = self.app.get(
453 route_path('repo_file_raw',
453 route_path('repo_file_raw',
454 repo_name=backend.repo_name,
454 repo_name=backend.repo_name,
455 commit_id=raw_id, f_path='vcs/nodes.svg'),
455 commit_id=raw_id, f_path='vcs/nodes.svg'),
456 status=404)
456 status=404)
457
457
458 msg = """No such commit exists for this repository"""
458 msg = """No such commit exists for this repository"""
459 response.mustcontain(msg)
459 response.mustcontain(msg)
460
460
461 def test_raw_wrong_f_path(self, backend):
461 def test_raw_wrong_f_path(self, backend):
462 commit = backend.repo.get_commit(commit_idx=173)
462 commit = backend.repo.get_commit(commit_idx=173)
463 f_path = 'vcs/ERRORnodes.py'
463 f_path = 'vcs/ERRORnodes.py'
464 response = self.app.get(
464 response = self.app.get(
465 route_path('repo_file_raw',
465 route_path('repo_file_raw',
466 repo_name=backend.repo_name,
466 repo_name=backend.repo_name,
467 commit_id=commit.raw_id, f_path=f_path),
467 commit_id=commit.raw_id, f_path=f_path),
468 status=404)
468 status=404)
469
469
470 msg = (
470 msg = (
471 "There is no file nor directory at the given path: "
471 "There is no file nor directory at the given path: "
472 "`%s` at commit %s" % (f_path, commit.short_id))
472 "`%s` at commit %s" % (f_path, commit.short_id))
473 response.mustcontain(msg)
473 response.mustcontain(msg)
474
474
475 def test_raw_svg_should_not_be_rendered(self, backend):
475 def test_raw_svg_should_not_be_rendered(self, backend):
476 backend.create_repo()
476 backend.create_repo()
477 backend.ensure_file(b"xss.svg")
477 backend.ensure_file(b"xss.svg")
478 response = self.app.get(
478 response = self.app.get(
479 route_path('repo_file_raw',
479 route_path('repo_file_raw',
480 repo_name=backend.repo_name,
480 repo_name=backend.repo_name,
481 commit_id='tip', f_path='xss.svg'),)
481 commit_id='tip', f_path='xss.svg'),)
482 # If the content type is image/svg+xml then it allows to render HTML
482 # If the content type is image/svg+xml then it allows to render HTML
483 # and malicious SVG.
483 # and malicious SVG.
484 assert response.content_type == "text/plain"
484 assert response.content_type == "text/plain"
485
485
486
486
487 @pytest.mark.usefixtures("app")
487 @pytest.mark.usefixtures("app")
488 class TestRepositoryArchival(object):
488 class TestRepositoryArchival(object):
489
489
490 def test_archival(self, backend):
490 def test_archival(self, backend):
491 backend.enable_downloads()
491 backend.enable_downloads()
492 commit = backend.repo.get_commit(commit_idx=173)
492 commit = backend.repo.get_commit(commit_idx=173)
493
493
494 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
494 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
495 path_sha = get_path_sha('/')
495 path_sha = get_path_sha('/')
496 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha)
496 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha)
497
497
498 fname = commit.raw_id + extension
498 fname = commit.raw_id + extension
499 response = self.app.get(
499 response = self.app.get(
500 route_path('repo_archivefile',
500 route_path('repo_archivefile',
501 repo_name=backend.repo_name,
501 repo_name=backend.repo_name,
502 fname=fname))
502 fname=fname))
503
503
504 assert response.status == '200 OK'
504 assert response.status == '200 OK'
505 headers = [
505 headers = [
506 ('Content-Disposition', f'attachment; filename={filename}'),
506 ('Content-Disposition', f'attachment; filename={filename}'),
507 ('Content-Type', content_type),
507 ('Content-Type', content_type),
508 ]
508 ]
509
509
510 for header in headers:
510 for header in headers:
511 assert header in list(response.headers.items())
511 assert header in list(response.headers.items())
512
512
513 def test_archival_no_hash(self, backend):
513 def test_archival_no_hash(self, backend):
514 backend.enable_downloads()
514 backend.enable_downloads()
515 commit = backend.repo.get_commit(commit_idx=173)
515 commit = backend.repo.get_commit(commit_idx=173)
516 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
516 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
517 path_sha = get_path_sha('/')
517 path_sha = get_path_sha('/')
518 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha, with_hash=False)
518 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha, with_hash=False)
519
519
520 fname = commit.raw_id + extension
520 fname = commit.raw_id + extension
521 response = self.app.get(
521 response = self.app.get(
522 route_path('repo_archivefile',
522 route_path('repo_archivefile',
523 repo_name=backend.repo_name,
523 repo_name=backend.repo_name,
524 fname=fname, params={'with_hash': 0}))
524 fname=fname, params={'with_hash': 0}))
525
525
526 assert response.status == '200 OK'
526 assert response.status == '200 OK'
527 headers = [
527 headers = [
528 ('Content-Disposition', f'attachment; filename={filename}'),
528 ('Content-Disposition', f'attachment; filename={filename}'),
529 ('Content-Type', content_type),
529 ('Content-Type', content_type),
530 ]
530 ]
531
531
532 for header in headers:
532 for header in headers:
533 assert header in list(response.headers.items())
533 assert header in list(response.headers.items())
534
534
535 def test_archival_at_path(self, backend):
535 def test_archival_at_path(self, backend):
536 backend.enable_downloads()
536 backend.enable_downloads()
537 commit = backend.repo.get_commit(commit_idx=190)
537 commit = backend.repo.get_commit(commit_idx=190)
538 at_path = 'vcs'
538 at_path = 'vcs'
539
539
540 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
540 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
541 path_sha = get_path_sha(at_path)
541 path_sha = get_path_sha(at_path)
542 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha)
542 filename = get_archive_name(backend.repo_id, backend.repo_name, commit_sha=commit.short_id, ext=extension, path_sha=path_sha)
543
543
544 fname = commit.raw_id + extension
544 fname = commit.raw_id + extension
545 response = self.app.get(
545 response = self.app.get(
546 route_path('repo_archivefile',
546 route_path('repo_archivefile',
547 repo_name=backend.repo_name,
547 repo_name=backend.repo_name,
548 fname=fname, params={'at_path': at_path}))
548 fname=fname, params={'at_path': at_path}))
549
549
550 assert response.status == '200 OK'
550 assert response.status == '200 OK'
551 headers = [
551 headers = [
552 ('Content-Disposition', f'attachment; filename={filename}'),
552 ('Content-Disposition', f'attachment; filename={filename}'),
553 ('Content-Type', content_type),
553 ('Content-Type', content_type),
554 ]
554 ]
555
555
556 for header in headers:
556 for header in headers:
557 assert header in list(response.headers.items())
557 assert header in list(response.headers.items())
558
558
559 @pytest.mark.parametrize('arch_ext',[
559 @pytest.mark.parametrize('arch_ext',[
560 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
560 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
561 def test_archival_wrong_ext(self, backend, arch_ext):
561 def test_archival_wrong_ext(self, backend, arch_ext):
562 backend.enable_downloads()
562 backend.enable_downloads()
563 commit = backend.repo.get_commit(commit_idx=173)
563 commit = backend.repo.get_commit(commit_idx=173)
564
564
565 fname = commit.raw_id + '.' + arch_ext
565 fname = commit.raw_id + '.' + arch_ext
566
566
567 response = self.app.get(
567 response = self.app.get(
568 route_path('repo_archivefile',
568 route_path('repo_archivefile',
569 repo_name=backend.repo_name,
569 repo_name=backend.repo_name,
570 fname=fname))
570 fname=fname))
571 response.mustcontain(
571 response.mustcontain(
572 'Unknown archive type for: `{}`'.format(fname))
572 'Unknown archive type for: `{}`'.format(fname))
573
573
574 @pytest.mark.parametrize('commit_id', [
574 @pytest.mark.parametrize('commit_id', [
575 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
575 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
576 def test_archival_wrong_commit_id(self, backend, commit_id):
576 def test_archival_wrong_commit_id(self, backend, commit_id):
577 backend.enable_downloads()
577 backend.enable_downloads()
578 fname = f'{commit_id}.zip'
578 fname = f'{commit_id}.zip'
579
579
580 response = self.app.get(
580 response = self.app.get(
581 route_path('repo_archivefile',
581 route_path('repo_archivefile',
582 repo_name=backend.repo_name,
582 repo_name=backend.repo_name,
583 fname=fname))
583 fname=fname))
584 response.mustcontain('Unknown commit_id')
584 response.mustcontain('Unknown commit_id')
585
585
586
586
587 @pytest.mark.usefixtures("app")
587 @pytest.mark.usefixtures("app")
588 class TestFilesDiff(object):
588 class TestFilesDiff(object):
589
589
590 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
590 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
591 def test_file_full_diff(self, backend, diff):
591 def test_file_full_diff(self, backend, diff):
592 commit1 = backend.repo.get_commit(commit_idx=-1)
592 commit1 = backend.repo.get_commit(commit_idx=-1)
593 commit2 = backend.repo.get_commit(commit_idx=-2)
593 commit2 = backend.repo.get_commit(commit_idx=-2)
594
594
595 response = self.app.get(
595 response = self.app.get(
596 route_path('repo_files_diff',
596 route_path('repo_files_diff',
597 repo_name=backend.repo_name,
597 repo_name=backend.repo_name,
598 f_path='README'),
598 f_path='README'),
599 params={
599 params={
600 'diff1': commit2.raw_id,
600 'diff1': commit2.raw_id,
601 'diff2': commit1.raw_id,
601 'diff2': commit1.raw_id,
602 'fulldiff': '1',
602 'fulldiff': '1',
603 'diff': diff,
603 'diff': diff,
604 })
604 })
605
605
606 if diff == 'diff':
606 if diff == 'diff':
607 # use redirect since this is OLD view redirecting to compare page
607 # use redirect since this is OLD view redirecting to compare page
608 response = response.follow()
608 response = response.follow()
609
609
610 # It's a symlink to README.rst
610 # It's a symlink to README.rst
611 response.mustcontain('README.rst')
611 response.mustcontain('README.rst')
612 response.mustcontain('No newline at end of file')
612 response.mustcontain('No newline at end of file')
613
613
614 def test_file_binary_diff(self, backend):
614 def test_file_binary_diff(self, backend):
615 commits = [
615 commits = [
616 {'message': 'First commit'},
616 {'message': 'First commit'},
617 {'message': 'Commit with binary',
617 {'message': 'Commit with binary',
618 'added': [nodes.FileNode(b'file.bin', content='\0BINARY\0')]},
618 'added': [nodes.FileNode(b'file.bin', content='\0BINARY\0')]},
619 ]
619 ]
620 repo = backend.create_repo(commits=commits)
620 repo = backend.create_repo(commits=commits)
621
621
622 response = self.app.get(
622 response = self.app.get(
623 route_path('repo_files_diff',
623 route_path('repo_files_diff',
624 repo_name=backend.repo_name,
624 repo_name=backend.repo_name,
625 f_path='file.bin'),
625 f_path='file.bin'),
626 params={
626 params={
627 'diff1': repo.get_commit(commit_idx=0).raw_id,
627 'diff1': repo.get_commit(commit_idx=0).raw_id,
628 'diff2': repo.get_commit(commit_idx=1).raw_id,
628 'diff2': repo.get_commit(commit_idx=1).raw_id,
629 'fulldiff': '1',
629 'fulldiff': '1',
630 'diff': 'diff',
630 'diff': 'diff',
631 })
631 })
632 # use redirect since this is OLD view redirecting to compare page
632 # use redirect since this is OLD view redirecting to compare page
633 response = response.follow()
633 response = response.follow()
634 response.mustcontain('Collapse 1 commit')
634 response.mustcontain('Collapse 1 commit')
635 file_changes = (1, 0, 0)
635 file_changes = (1, 0, 0)
636
636
637 compare_page = ComparePage(response)
637 compare_page = ComparePage(response)
638 compare_page.contains_change_summary(*file_changes)
638 compare_page.contains_change_summary(*file_changes)
639
639
640 if backend.alias == 'svn':
640 if backend.alias == 'svn':
641 response.mustcontain('new file 10644')
641 response.mustcontain('new file 10644')
642 # TODO(marcink): SVN doesn't yet detect binary changes
642 # TODO(marcink): SVN doesn't yet detect binary changes
643 else:
643 else:
644 response.mustcontain('new file 100644')
644 response.mustcontain('new file 100644')
645 response.mustcontain('binary diff hidden')
645 response.mustcontain('binary diff hidden')
646
646
647 def test_diff_2way(self, backend):
647 def test_diff_2way(self, backend):
648 commit1 = backend.repo.get_commit(commit_idx=-1)
648 commit1 = backend.repo.get_commit(commit_idx=-1)
649 commit2 = backend.repo.get_commit(commit_idx=-2)
649 commit2 = backend.repo.get_commit(commit_idx=-2)
650 response = self.app.get(
650 response = self.app.get(
651 route_path('repo_files_diff_2way_redirect',
651 route_path('repo_files_diff_2way_redirect',
652 repo_name=backend.repo_name,
652 repo_name=backend.repo_name,
653 f_path='README'),
653 f_path='README'),
654 params={
654 params={
655 'diff1': commit2.raw_id,
655 'diff1': commit2.raw_id,
656 'diff2': commit1.raw_id,
656 'diff2': commit1.raw_id,
657 })
657 })
658 # use redirect since this is OLD view redirecting to compare page
658 # use redirect since this is OLD view redirecting to compare page
659 response = response.follow()
659 response = response.follow()
660
660
661 # It's a symlink to README.rst
661 # It's a symlink to README.rst
662 response.mustcontain('README.rst')
662 response.mustcontain('README.rst')
663 response.mustcontain('No newline at end of file')
663 response.mustcontain('No newline at end of file')
664
664
665 def test_requires_one_commit_id(self, backend, autologin_user):
665 def test_requires_one_commit_id(self, backend, autologin_user):
666 response = self.app.get(
666 response = self.app.get(
667 route_path('repo_files_diff',
667 route_path('repo_files_diff',
668 repo_name=backend.repo_name,
668 repo_name=backend.repo_name,
669 f_path='README.rst'),
669 f_path='README.rst'),
670 status=400)
670 status=400)
671 response.mustcontain(
671 response.mustcontain(
672 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
672 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
673
673
674 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
674 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
675 repo = vcsbackend.repo
675 repo = vcsbackend.repo
676 response = self.app.get(
676 response = self.app.get(
677 route_path('repo_files_diff',
677 route_path('repo_files_diff',
678 repo_name=repo.name,
678 repo_name=repo.name,
679 f_path='does-not-exist-in-any-commit'),
679 f_path='does-not-exist-in-any-commit'),
680 params={
680 params={
681 'diff1': repo[0].raw_id,
681 'diff1': repo[0].raw_id,
682 'diff2': repo[1].raw_id
682 'diff2': repo[1].raw_id
683 })
683 })
684
684
685 response = response.follow()
685 response = response.follow()
686 response.mustcontain('No files')
686 response.mustcontain('No files')
687
687
688 def test_returns_redirect_if_file_not_changed(self, backend):
688 def test_returns_redirect_if_file_not_changed(self, backend):
689 commit = backend.repo.get_commit(commit_idx=-1)
689 commit = backend.repo.get_commit(commit_idx=-1)
690 response = self.app.get(
690 response = self.app.get(
691 route_path('repo_files_diff_2way_redirect',
691 route_path('repo_files_diff_2way_redirect',
692 repo_name=backend.repo_name,
692 repo_name=backend.repo_name,
693 f_path='README'),
693 f_path='README'),
694 params={
694 params={
695 'diff1': commit.raw_id,
695 'diff1': commit.raw_id,
696 'diff2': commit.raw_id,
696 'diff2': commit.raw_id,
697 })
697 })
698
698
699 response = response.follow()
699 response = response.follow()
700 response.mustcontain('No files')
700 response.mustcontain('No files')
701 response.mustcontain('No commits in this compare')
701 response.mustcontain('No commits in this compare')
702
702
703 def test_supports_diff_to_different_path_svn(self, backend_svn):
703 def test_supports_diff_to_different_path_svn(self, backend_svn):
704 #TODO: check this case
704 #TODO: check this case
705 return
705 return
706
706
707 repo = backend_svn['svn-simple-layout'].scm_instance()
707 repo = backend_svn['svn-simple-layout'].scm_instance()
708 commit_id_1 = '24'
708 commit_id_1 = '24'
709 commit_id_2 = '26'
709 commit_id_2 = '26'
710
710
711 response = self.app.get(
711 response = self.app.get(
712 route_path('repo_files_diff',
712 route_path('repo_files_diff',
713 repo_name=backend_svn.repo_name,
713 repo_name=backend_svn.repo_name,
714 f_path='trunk/example.py'),
714 f_path='trunk/example.py'),
715 params={
715 params={
716 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
716 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
717 'diff2': commit_id_2,
717 'diff2': commit_id_2,
718 })
718 })
719
719
720 response = response.follow()
720 response = response.follow()
721 response.mustcontain(
721 response.mustcontain(
722 # diff contains this
722 # diff contains this
723 "Will print out a useful message on invocation.")
723 "Will print out a useful message on invocation.")
724
724
725 # Note: Expecting that we indicate the user what's being compared
725 # Note: Expecting that we indicate the user what's being compared
726 response.mustcontain("trunk/example.py")
726 response.mustcontain("trunk/example.py")
727 response.mustcontain("tags/v0.2/example.py")
727 response.mustcontain("tags/v0.2/example.py")
728
728
729 def test_show_rev_redirects_to_svn_path(self, backend_svn):
729 def test_show_rev_redirects_to_svn_path(self, backend_svn):
730 #TODO: check this case
730 #TODO: check this case
731 return
731 return
732
732
733 repo = backend_svn['svn-simple-layout'].scm_instance()
733 repo = backend_svn['svn-simple-layout'].scm_instance()
734 commit_id = repo[-1].raw_id
734 commit_id = repo[-1].raw_id
735
735
736 response = self.app.get(
736 response = self.app.get(
737 route_path('repo_files_diff',
737 route_path('repo_files_diff',
738 repo_name=backend_svn.repo_name,
738 repo_name=backend_svn.repo_name,
739 f_path='trunk/example.py'),
739 f_path='trunk/example.py'),
740 params={
740 params={
741 'diff1': 'branches/argparse/example.py@' + commit_id,
741 'diff1': 'branches/argparse/example.py@' + commit_id,
742 'diff2': commit_id,
742 'diff2': commit_id,
743 },
743 },
744 status=302)
744 status=302)
745 response = response.follow()
745 response = response.follow()
746 assert response.headers['Location'].endswith(
746 assert response.headers['Location'].endswith(
747 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
747 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
748
748
749 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
749 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
750 #TODO: check this case
750 #TODO: check this case
751 return
751 return
752
752
753 repo = backend_svn['svn-simple-layout'].scm_instance()
753 repo = backend_svn['svn-simple-layout'].scm_instance()
754 commit_id = repo[-1].raw_id
754 commit_id = repo[-1].raw_id
755 response = self.app.get(
755 response = self.app.get(
756 route_path('repo_files_diff',
756 route_path('repo_files_diff',
757 repo_name=backend_svn.repo_name,
757 repo_name=backend_svn.repo_name,
758 f_path='trunk/example.py'),
758 f_path='trunk/example.py'),
759 params={
759 params={
760 'diff1': 'branches/argparse/example.py@' + commit_id,
760 'diff1': 'branches/argparse/example.py@' + commit_id,
761 'diff2': commit_id,
761 'diff2': commit_id,
762 'show_rev': 'Show at Revision',
762 'show_rev': 'Show at Revision',
763 'annotate': 'true',
763 'annotate': 'true',
764 },
764 },
765 status=302)
765 status=302)
766 response = response.follow()
766 response = response.follow()
767 assert response.headers['Location'].endswith(
767 assert response.headers['Location'].endswith(
768 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
768 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
769
769
770
770
771 @pytest.mark.usefixtures("app", "autologin_user")
771 @pytest.mark.usefixtures("app", "autologin_user")
772 class TestModifyFilesWithWebInterface(object):
772 class TestModifyFilesWithWebInterface(object):
773
773
774 def test_add_file_view(self, backend):
774 def test_add_file_view(self, backend):
775 self.app.get(
775 self.app.get(
776 route_path('repo_files_add_file',
776 route_path('repo_files_add_file',
777 repo_name=backend.repo_name,
777 repo_name=backend.repo_name,
778 commit_id='tip', f_path='/')
778 commit_id='tip', f_path='/')
779 )
779 )
780
780
781 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
781 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
782 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
782 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
783 backend.create_repo()
783 backend.create_repo()
784 filename = 'init.py'
784 filename = 'init.py'
785 response = self.app.post(
785 response = self.app.post(
786 route_path('repo_files_create_file',
786 route_path('repo_files_create_file',
787 repo_name=backend.repo_name,
787 repo_name=backend.repo_name,
788 commit_id='tip', f_path='/'),
788 commit_id='tip', f_path='/'),
789 params={
789 params={
790 'content': "",
790 'content': "",
791 'filename': filename,
791 'filename': filename,
792 'csrf_token': csrf_token,
792 'csrf_token': csrf_token,
793 },
793 },
794 status=302)
794 status=302)
795 expected_msg = 'Successfully committed new file `{}`'.format(os.path.join(filename))
795 expected_msg = 'Successfully committed new file `{}`'.format(os.path.join(filename))
796 assert_session_flash(response, expected_msg)
796 assert_session_flash(response, expected_msg)
797
797
798 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
798 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
799 commit_id = backend.repo.get_commit().raw_id
799 commit_id = backend.repo.get_commit().raw_id
800 response = self.app.post(
800 response = self.app.post(
801 route_path('repo_files_create_file',
801 route_path('repo_files_create_file',
802 repo_name=backend.repo_name,
802 repo_name=backend.repo_name,
803 commit_id=commit_id, f_path='/'),
803 commit_id=commit_id, f_path='/'),
804 params={
804 params={
805 'content': "foo",
805 'content': "foo",
806 'csrf_token': csrf_token,
806 'csrf_token': csrf_token,
807 },
807 },
808 status=302)
808 status=302)
809
809
810 assert_session_flash(response, 'No filename specified')
810 assert_session_flash(response, 'No filename specified')
811
811
812 def test_add_file_into_repo_errors_and_no_commits(
812 def test_add_file_into_repo_errors_and_no_commits(
813 self, backend, csrf_token):
813 self, backend, csrf_token):
814 repo = backend.create_repo()
814 repo = backend.create_repo()
815 # Create a file with no filename, it will display an error but
815 # Create a file with no filename, it will display an error but
816 # the repo has no commits yet
816 # the repo has no commits yet
817 response = self.app.post(
817 response = self.app.post(
818 route_path('repo_files_create_file',
818 route_path('repo_files_create_file',
819 repo_name=repo.repo_name,
819 repo_name=repo.repo_name,
820 commit_id='tip', f_path='/'),
820 commit_id='tip', f_path='/'),
821 params={
821 params={
822 'content': "foo",
822 'content': "foo",
823 'csrf_token': csrf_token,
823 'csrf_token': csrf_token,
824 },
824 },
825 status=302)
825 status=302)
826
826
827 assert_session_flash(response, 'No filename specified')
827 assert_session_flash(response, 'No filename specified')
828
828
829 # Not allowed, redirect to the summary
829 # Not allowed, redirect to the summary
830 redirected = response.follow()
830 redirected = response.follow()
831 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
831 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
832
832
833 # As there are no commits, displays the summary page with the error of
833 # As there are no commits, displays the summary page with the error of
834 # creating a file with no filename
834 # creating a file with no filename
835
835
836 assert redirected.request.path == summary_url
836 assert redirected.request.path == summary_url
837
837
838 @pytest.mark.parametrize("filename, clean_filename", [
838 @pytest.mark.parametrize("filename, clean_filename", [
839 ('/abs/foo', 'abs/foo'),
839 ('/abs/foo', 'abs/foo'),
840 ('../rel/foo', 'rel/foo'),
840 ('../rel/foo', 'rel/foo'),
841 ('file/../foo/foo', 'file/foo/foo'),
841 ('file/../foo/foo', 'file/foo/foo'),
842 ])
842 ])
843 def test_add_file_into_repo_bad_filenames(self, filename, clean_filename, backend, csrf_token):
843 def test_add_file_into_repo_bad_filenames(self, filename, clean_filename, backend, csrf_token):
844 repo = backend.create_repo()
844 repo = backend.create_repo()
845 commit_id = repo.get_commit().raw_id
845 commit_id = repo.get_commit().raw_id
846
846
847 response = self.app.post(
847 response = self.app.post(
848 route_path('repo_files_create_file',
848 route_path('repo_files_create_file',
849 repo_name=repo.repo_name,
849 repo_name=repo.repo_name,
850 commit_id=commit_id, f_path='/'),
850 commit_id=commit_id, f_path='/'),
851 params={
851 params={
852 'content': "foo",
852 'content': "foo",
853 'filename': filename,
853 'filename': filename,
854 'csrf_token': csrf_token,
854 'csrf_token': csrf_token,
855 },
855 },
856 status=302)
856 status=302)
857
857
858 expected_msg = 'Successfully committed new file `{}`'.format(clean_filename)
858 expected_msg = 'Successfully committed new file `{}`'.format(clean_filename)
859 assert_session_flash(response, expected_msg)
859 assert_session_flash(response, expected_msg)
860
860
861 @pytest.mark.parametrize("cnt, filename, content", [
861 @pytest.mark.parametrize("cnt, filename, content", [
862 (1, 'foo.txt', "Content"),
862 (1, 'foo.txt', "Content"),
863 (2, 'dir/foo.rst', "Content"),
863 (2, 'dir/foo.rst', "Content"),
864 (3, 'dir/foo-second.rst', "Content"),
864 (3, 'dir/foo-second.rst', "Content"),
865 (4, 'rel/dir/foo.bar', "Content"),
865 (4, 'rel/dir/foo.bar', "Content"),
866 ])
866 ])
867 def test_add_file_into_empty_repo(self, cnt, filename, content, backend, csrf_token):
867 def test_add_file_into_empty_repo(self, cnt, filename, content, backend, csrf_token):
868 repo = backend.create_repo()
868 repo = backend.create_repo()
869 commit_id = repo.get_commit().raw_id
869 commit_id = repo.get_commit().raw_id
870 response = self.app.post(
870 response = self.app.post(
871 route_path('repo_files_create_file',
871 route_path('repo_files_create_file',
872 repo_name=repo.repo_name,
872 repo_name=repo.repo_name,
873 commit_id=commit_id, f_path='/'),
873 commit_id=commit_id, f_path='/'),
874 params={
874 params={
875 'content': content,
875 'content': content,
876 'filename': filename,
876 'filename': filename,
877 'csrf_token': csrf_token,
877 'csrf_token': csrf_token,
878 },
878 },
879 status=302)
879 status=302)
880
880
881 expected_msg = 'Successfully committed new file `{}`'.format(filename)
881 expected_msg = 'Successfully committed new file `{}`'.format(filename)
882 assert_session_flash(response, expected_msg)
882 assert_session_flash(response, expected_msg)
883
883
884 def test_edit_file_view(self, backend):
884 def test_edit_file_view(self, backend):
885 response = self.app.get(
885 response = self.app.get(
886 route_path('repo_files_edit_file',
886 route_path('repo_files_edit_file',
887 repo_name=backend.repo_name,
887 repo_name=backend.repo_name,
888 commit_id=backend.default_head_id,
888 commit_id=backend.default_head_id,
889 f_path='vcs/nodes.py'),
889 f_path='vcs/nodes.py'),
890 status=200)
890 status=200)
891 response.mustcontain("Module holding everything related to vcs nodes.")
891 response.mustcontain("Module holding everything related to vcs nodes.")
892
892
893 def test_edit_file_view_not_on_branch(self, backend):
893 def test_edit_file_view_not_on_branch(self, backend):
894 repo = backend.create_repo()
894 repo = backend.create_repo()
895 backend.ensure_file(b"vcs/nodes.py")
895 backend.ensure_file(b"vcs/nodes.py")
896
896
897 response = self.app.get(
897 response = self.app.get(
898 route_path('repo_files_edit_file',
898 route_path('repo_files_edit_file',
899 repo_name=repo.repo_name,
899 repo_name=repo.repo_name,
900 commit_id='tip',
900 commit_id='tip',
901 f_path='vcs/nodes.py'),
901 f_path='vcs/nodes.py'),
902 status=302)
902 status=302)
903 assert_session_flash(
903 assert_session_flash(
904 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
904 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
905
905
906 def test_edit_file_view_commit_changes(self, backend, csrf_token):
906 def test_edit_file_view_commit_changes(self, backend, csrf_token):
907 repo = backend.create_repo()
907 repo = backend.create_repo()
908 backend.ensure_file(b"vcs/nodes.py", content=b"print 'hello'")
908 backend.ensure_file(b"vcs/nodes.py", content=b"print 'hello'")
909
909
910 response = self.app.post(
910 response = self.app.post(
911 route_path('repo_files_update_file',
911 route_path('repo_files_update_file',
912 repo_name=repo.repo_name,
912 repo_name=repo.repo_name,
913 commit_id=backend.default_head_id,
913 commit_id=backend.default_head_id,
914 f_path='vcs/nodes.py'),
914 f_path='vcs/nodes.py'),
915 params={
915 params={
916 'content': "print 'hello world'",
916 'content': "print 'hello world'",
917 'message': 'I committed',
917 'message': 'I committed',
918 'filename': "vcs/nodes.py",
918 'filename': "vcs/nodes.py",
919 'csrf_token': csrf_token,
919 'csrf_token': csrf_token,
920 },
920 },
921 status=302)
921 status=302)
922 assert_session_flash(
922 assert_session_flash(
923 response, 'Successfully committed changes to file `vcs/nodes.py`')
923 response, 'Successfully committed changes to file `vcs/nodes.py`')
924 tip = repo.get_commit(commit_idx=-1)
924 tip = repo.get_commit(commit_idx=-1)
925 assert tip.message == 'I committed'
925 assert tip.message == 'I committed'
926
926
927 def test_replace_binary_file_view_commit_changes(self, backend, csrf_token):
928 repo = backend.create_repo()
929 backend.ensure_file(b"vcs/nodes.docx", content=b"PREVIOUS CONTENT'")
930
931 response = self.app.post(
932 route_path('repo_files_replace_binary',
933 repo_name=repo.repo_name,
934 commit_id=backend.default_head_id,
935 f_path='vcs/nodes.docx'),
936 params={
937 'message': 'I committed',
938 'csrf_token': csrf_token,
939 },
940 upload_files=[('files_upload', 'vcs/nodes.docx', b'SOME CONTENT')],
941 status=200)
942 assert_session_flash(
943 response, 'Successfully committed 1 new file')
944 tip = repo.get_commit(commit_idx=-1)
945 assert tip.message == 'I committed'
946
927 def test_edit_file_view_commit_changes_default_message(self, backend,
947 def test_edit_file_view_commit_changes_default_message(self, backend,
928 csrf_token):
948 csrf_token):
929 repo = backend.create_repo()
949 repo = backend.create_repo()
930 backend.ensure_file(b"vcs/nodes.py", content=b"print 'hello'")
950 backend.ensure_file(b"vcs/nodes.py", content=b"print 'hello'")
931
951
932 commit_id = (
952 commit_id = (
933 backend.default_branch_name or
953 backend.default_branch_name or
934 backend.repo.scm_instance().commit_ids[-1])
954 backend.repo.scm_instance().commit_ids[-1])
935
955
936 response = self.app.post(
956 response = self.app.post(
937 route_path('repo_files_update_file',
957 route_path('repo_files_update_file',
938 repo_name=repo.repo_name,
958 repo_name=repo.repo_name,
939 commit_id=commit_id,
959 commit_id=commit_id,
940 f_path='vcs/nodes.py'),
960 f_path='vcs/nodes.py'),
941 params={
961 params={
942 'content': "print 'hello world'",
962 'content': "print 'hello world'",
943 'message': '',
963 'message': '',
944 'filename': "vcs/nodes.py",
964 'filename': "vcs/nodes.py",
945 'csrf_token': csrf_token,
965 'csrf_token': csrf_token,
946 },
966 },
947 status=302)
967 status=302)
948 assert_session_flash(
968 assert_session_flash(
949 response, 'Successfully committed changes to file `vcs/nodes.py`')
969 response, 'Successfully committed changes to file `vcs/nodes.py`')
950 tip = repo.get_commit(commit_idx=-1)
970 tip = repo.get_commit(commit_idx=-1)
951 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
971 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
952
972
973 def test_replace_binary_file_content_with_content_that_not_belong_to_original_type(self, backend, csrf_token):
974 repo = backend.create_repo()
975 backend.ensure_file(b"vcs/sheet.xlsx", content=b"PREVIOUS CONTENT'")
976
977 response = self.app.post(
978 route_path('repo_files_replace_binary',
979 repo_name=repo.repo_name,
980 commit_id=backend.default_head_id,
981 f_path='vcs/sheet.xlsx'),
982 params={
983 'message': 'I committed',
984 'csrf_token': csrf_token,
985 },
986 upload_files=[('files_upload', 'vcs/sheet.docx', b'SOME CONTENT')],
987 status=200)
988 assert response.json['error'] == "file extension of uploaded file doesn't match an original file's extension"
989
990 @pytest.mark.parametrize("replacement_files, expected_error", [
991 ([], 'missing files'),
992 (
993 [('files_upload', 'vcs/node1.docx', b'SOME CONTENT'),
994 ('files_upload', 'vcs/node2.docx', b'SOME CONTENT')],
995 'too many files for replacement'),
996 ])
997 def test_replace_binary_with_wrong_amount_of_content_sources(self, replacement_files, expected_error, backend,
998 csrf_token):
999 repo = backend.create_repo()
1000 backend.ensure_file(b"vcs/node.docx", content=b"PREVIOUS CONTENT'")
1001
1002 response = self.app.post(
1003 route_path('repo_files_replace_binary',
1004 repo_name=repo.repo_name,
1005 commit_id=backend.default_head_id,
1006 f_path='vcs/node.docx'),
1007 params={
1008 'message': 'I committed',
1009 'csrf_token': csrf_token,
1010 },
1011 upload_files=replacement_files,
1012 status=200)
1013 assert response.json['error'] == expected_error
1014
953 def test_delete_file_view(self, backend):
1015 def test_delete_file_view(self, backend):
954 self.app.get(
1016 self.app.get(
955 route_path('repo_files_remove_file',
1017 route_path('repo_files_remove_file',
956 repo_name=backend.repo_name,
1018 repo_name=backend.repo_name,
957 commit_id=backend.default_head_id,
1019 commit_id=backend.default_head_id,
958 f_path='vcs/nodes.py'),
1020 f_path='vcs/nodes.py'),
959 status=200)
1021 status=200)
960
1022
961 def test_delete_file_view_not_on_branch(self, backend):
1023 def test_delete_file_view_not_on_branch(self, backend):
962 repo = backend.create_repo()
1024 repo = backend.create_repo()
963 backend.ensure_file(b'vcs/nodes.py')
1025 backend.ensure_file(b'vcs/nodes.py')
964
1026
965 response = self.app.get(
1027 response = self.app.get(
966 route_path('repo_files_remove_file',
1028 route_path('repo_files_remove_file',
967 repo_name=repo.repo_name,
1029 repo_name=repo.repo_name,
968 commit_id='tip',
1030 commit_id='tip',
969 f_path='vcs/nodes.py'),
1031 f_path='vcs/nodes.py'),
970 status=302)
1032 status=302)
971 assert_session_flash(
1033 assert_session_flash(
972 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
1034 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
973
1035
974 def test_delete_file_view_commit_changes(self, backend, csrf_token):
1036 def test_delete_file_view_commit_changes(self, backend, csrf_token):
975 repo = backend.create_repo()
1037 repo = backend.create_repo()
976 backend.ensure_file(b"vcs/nodes.py")
1038 backend.ensure_file(b"vcs/nodes.py")
977
1039
978 response = self.app.post(
1040 response = self.app.post(
979 route_path('repo_files_delete_file',
1041 route_path('repo_files_delete_file',
980 repo_name=repo.repo_name,
1042 repo_name=repo.repo_name,
981 commit_id=backend.default_head_id,
1043 commit_id=backend.default_head_id,
982 f_path='vcs/nodes.py'),
1044 f_path='vcs/nodes.py'),
983 params={
1045 params={
984 'message': 'i committed',
1046 'message': 'i committed',
985 'csrf_token': csrf_token,
1047 'csrf_token': csrf_token,
986 },
1048 },
987 status=302)
1049 status=302)
988 assert_session_flash(
1050 assert_session_flash(
989 response, 'Successfully deleted file `vcs/nodes.py`')
1051 response, 'Successfully deleted file `vcs/nodes.py`')
990
1052
991
1053
992 @pytest.mark.usefixtures("app")
1054 @pytest.mark.usefixtures("app")
993 class TestFilesViewOtherCases(object):
1055 class TestFilesViewOtherCases(object):
994
1056
995 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
1057 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
996 self, backend_stub, autologin_regular_user, user_regular,
1058 self, backend_stub, autologin_regular_user, user_regular,
997 user_util):
1059 user_util):
998
1060
999 repo = backend_stub.create_repo()
1061 repo = backend_stub.create_repo()
1000 user_util.grant_user_permission_to_repo(
1062 user_util.grant_user_permission_to_repo(
1001 repo, user_regular, 'repository.write')
1063 repo, user_regular, 'repository.write')
1002 response = self.app.get(
1064 response = self.app.get(
1003 route_path('repo_files',
1065 route_path('repo_files',
1004 repo_name=repo.repo_name,
1066 repo_name=repo.repo_name,
1005 commit_id='tip', f_path='/'))
1067 commit_id='tip', f_path='/'))
1006
1068
1007 repo_file_add_url = route_path(
1069 repo_file_add_url = route_path(
1008 'repo_files_add_file',
1070 'repo_files_add_file',
1009 repo_name=repo.repo_name,
1071 repo_name=repo.repo_name,
1010 commit_id=0, f_path='')
1072 commit_id=0, f_path='')
1011 add_new = f'<a class="alert-link" href="{repo_file_add_url}">add a new file</a>'
1073 add_new = f'<a class="alert-link" href="{repo_file_add_url}">add a new file</a>'
1012
1074
1013 repo_file_upload_url = route_path(
1075 repo_file_upload_url = route_path(
1014 'repo_files_upload_file',
1076 'repo_files_upload_file',
1015 repo_name=repo.repo_name,
1077 repo_name=repo.repo_name,
1016 commit_id=0, f_path='')
1078 commit_id=0, f_path='')
1017 upload_new = f'<a class="alert-link" href="{repo_file_upload_url}">upload a new file</a>'
1079 upload_new = f'<a class="alert-link" href="{repo_file_upload_url}">upload a new file</a>'
1018
1080
1019 assert_session_flash(
1081 assert_session_flash(
1020 response,
1082 response,
1021 'There are no files yet. Click here to %s or %s.' % (add_new, upload_new)
1083 'There are no files yet. Click here to %s or %s.' % (add_new, upload_new)
1022 )
1084 )
1023
1085
1024 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1086 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1025 self, backend_stub, autologin_regular_user):
1087 self, backend_stub, autologin_regular_user):
1026 repo = backend_stub.create_repo()
1088 repo = backend_stub.create_repo()
1027 # init session for anon user
1089 # init session for anon user
1028 route_path('repo_summary', repo_name=repo.repo_name)
1090 route_path('repo_summary', repo_name=repo.repo_name)
1029
1091
1030 repo_file_add_url = route_path(
1092 repo_file_add_url = route_path(
1031 'repo_files_add_file',
1093 'repo_files_add_file',
1032 repo_name=repo.repo_name,
1094 repo_name=repo.repo_name,
1033 commit_id=0, f_path='')
1095 commit_id=0, f_path='')
1034
1096
1035 response = self.app.get(
1097 response = self.app.get(
1036 route_path('repo_files',
1098 route_path('repo_files',
1037 repo_name=repo.repo_name,
1099 repo_name=repo.repo_name,
1038 commit_id='tip', f_path='/'))
1100 commit_id='tip', f_path='/'))
1039
1101
1040 assert_session_flash(response, no_=repo_file_add_url)
1102 assert_session_flash(response, no_=repo_file_add_url)
1041
1103
1042 @pytest.mark.parametrize('file_node', [
1104 @pytest.mark.parametrize('file_node', [
1043 b'archive/file.zip',
1105 b'archive/file.zip',
1044 b'diff/my-file.txt',
1106 b'diff/my-file.txt',
1045 b'render.py',
1107 b'render.py',
1046 b'render',
1108 b'render',
1047 b'remove_file',
1109 b'remove_file',
1048 b'remove_file/to-delete.txt',
1110 b'remove_file/to-delete.txt',
1049 ])
1111 ])
1050 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1112 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1051 backend.create_repo()
1113 backend.create_repo()
1052 backend.ensure_file(file_node)
1114 backend.ensure_file(file_node)
1053
1115
1054 self.app.get(
1116 self.app.get(
1055 route_path('repo_files',
1117 route_path('repo_files',
1056 repo_name=backend.repo_name,
1118 repo_name=backend.repo_name,
1057 commit_id='tip', f_path=safe_str(file_node)),
1119 commit_id='tip', f_path=safe_str(file_node)),
1058 status=200)
1120 status=200)
1059
1121
1060
1122
1061 class TestAdjustFilePathForSvn(object):
1123 class TestAdjustFilePathForSvn(object):
1062 """
1124 """
1063 SVN specific adjustments of node history in RepoFilesView.
1125 SVN specific adjustments of node history in RepoFilesView.
1064 """
1126 """
1065
1127
1066 def test_returns_path_relative_to_matched_reference(self):
1128 def test_returns_path_relative_to_matched_reference(self):
1067 repo = self._repo(branches=['trunk'])
1129 repo = self._repo(branches=['trunk'])
1068 self.assert_file_adjustment('trunk/file', 'file', repo)
1130 self.assert_file_adjustment('trunk/file', 'file', repo)
1069
1131
1070 def test_does_not_modify_file_if_no_reference_matches(self):
1132 def test_does_not_modify_file_if_no_reference_matches(self):
1071 repo = self._repo(branches=['trunk'])
1133 repo = self._repo(branches=['trunk'])
1072 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1134 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1073
1135
1074 def test_does_not_adjust_partial_directory_names(self):
1136 def test_does_not_adjust_partial_directory_names(self):
1075 repo = self._repo(branches=['trun'])
1137 repo = self._repo(branches=['trun'])
1076 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1138 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1077
1139
1078 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1140 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1079 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1141 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1080 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1142 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1081
1143
1082 def assert_file_adjustment(self, f_path, expected, repo):
1144 def assert_file_adjustment(self, f_path, expected, repo):
1083 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1145 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1084 assert result == expected
1146 assert result == expected
1085
1147
1086 def _repo(self, branches=None):
1148 def _repo(self, branches=None):
1087 repo = mock.Mock()
1149 repo = mock.Mock()
1088 repo.branches = OrderedDict((name, '0') for name in branches or [])
1150 repo.branches = OrderedDict((name, '0') for name in branches or [])
1089 repo.tags = {}
1151 repo.tags = {}
1090 return repo
1152 return repo
@@ -1,1586 +1,1708 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import itertools
19 import itertools
20 import logging
20 import logging
21 import os
21 import os
22 import collections
22 import collections
23 import urllib.request
23 import urllib.request
24 import urllib.parse
24 import urllib.parse
25 import urllib.error
25 import urllib.error
26 import pathlib
26 import pathlib
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29
29
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
35
35
36
36
37 from rhodecode.lib import diffs, helpers as h, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.hash_utils import sha1_safe
39 from rhodecode.lib.hash_utils import sha1_safe
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
40 from rhodecode.lib.rc_cache.archive_cache import get_archival_cache_store, get_archival_config, ReentrantLock
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
41 from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
42 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.view_utils import parse_path_ref
43 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.exceptions import NonRelativePathError
44 from rhodecode.lib.codeblocks import (
44 from rhodecode.lib.codeblocks import (
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
46 from rhodecode.lib.utils2 import convert_line_endings, detect_mode
47 from rhodecode.lib.type_utils import str2bool
47 from rhodecode.lib.type_utils import str2bool
48 from rhodecode.lib.str_utils import safe_str, safe_int
48 from rhodecode.lib.str_utils import safe_str, safe_int
49 from rhodecode.lib.auth import (
49 from rhodecode.lib.auth import (
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
50 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
51 from rhodecode.lib.vcs import path as vcspath
51 from rhodecode.lib.vcs import path as vcspath
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
52 from rhodecode.lib.vcs.backends.base import EmptyCommit
53 from rhodecode.lib.vcs.conf import settings
53 from rhodecode.lib.vcs.conf import settings
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.exceptions import (
55 from rhodecode.lib.vcs.exceptions import (
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
56 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
57 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
58 NodeDoesNotExistError, CommitError, NodeError)
58 NodeDoesNotExistError, CommitError, NodeError)
59
59
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.db import Repository
61 from rhodecode.model.db import Repository
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
66 def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
67 # original backward compat name of archive
67 # original backward compat name of archive
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
68 clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
69
69
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
70 # e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
71 # vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
72 id_sha = sha1_safe(str(db_repo_id))[:4]
72 id_sha = sha1_safe(str(db_repo_id))[:4]
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
73 sub_repo = 'sub-1' if subrepos else 'sub-0'
74 commit = commit_sha if with_hash else 'archive'
74 commit = commit_sha if with_hash else 'archive'
75 path_marker = (path_sha if with_hash else '') or 'all'
75 path_marker = (path_sha if with_hash else '') or 'all'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
76 archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
77
77
78 return archive_name
78 return archive_name
79
79
80
80
81 def get_path_sha(at_path):
81 def get_path_sha(at_path):
82 return safe_str(sha1_safe(at_path)[:8])
82 return safe_str(sha1_safe(at_path)[:8])
83
83
84
84
85 def _get_archive_spec(fname):
85 def _get_archive_spec(fname):
86 log.debug('Detecting archive spec for: `%s`', fname)
86 log.debug('Detecting archive spec for: `%s`', fname)
87
87
88 fileformat = None
88 fileformat = None
89 ext = None
89 ext = None
90 content_type = None
90 content_type = None
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
91 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
92
92
93 if fname.endswith(extension):
93 if fname.endswith(extension):
94 fileformat = a_type
94 fileformat = a_type
95 log.debug('archive is of type: %s', fileformat)
95 log.debug('archive is of type: %s', fileformat)
96 ext = extension
96 ext = extension
97 break
97 break
98
98
99 if not fileformat:
99 if not fileformat:
100 raise ValueError()
100 raise ValueError()
101
101
102 # left over part of whole fname is the commit
102 # left over part of whole fname is the commit
103 commit_id = fname[:-len(ext)]
103 commit_id = fname[:-len(ext)]
104
104
105 return commit_id, ext, fileformat, content_type
105 return commit_id, ext, fileformat, content_type
106
106
107
107
108 class RepoFilesView(RepoAppView):
108 class RepoFilesView(RepoAppView):
109
109
110 @staticmethod
110 @staticmethod
111 def adjust_file_path_for_svn(f_path, repo):
111 def adjust_file_path_for_svn(f_path, repo):
112 """
112 """
113 Computes the relative path of `f_path`.
113 Computes the relative path of `f_path`.
114
114
115 This is mainly based on prefix matching of the recognized tags and
115 This is mainly based on prefix matching of the recognized tags and
116 branches in the underlying repository.
116 branches in the underlying repository.
117 """
117 """
118 tags_and_branches = itertools.chain(
118 tags_and_branches = itertools.chain(
119 repo.branches.keys(),
119 repo.branches.keys(),
120 repo.tags.keys())
120 repo.tags.keys())
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
121 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
122
122
123 for name in tags_and_branches:
123 for name in tags_and_branches:
124 if f_path.startswith(f'{name}/'):
124 if f_path.startswith(f'{name}/'):
125 f_path = vcspath.relpath(f_path, name)
125 f_path = vcspath.relpath(f_path, name)
126 break
126 break
127 return f_path
127 return f_path
128
128
129 def load_default_context(self):
129 def load_default_context(self):
130 c = self._get_local_tmpl_context(include_app_defaults=True)
130 c = self._get_local_tmpl_context(include_app_defaults=True)
131 c.rhodecode_repo = self.rhodecode_vcs_repo
131 c.rhodecode_repo = self.rhodecode_vcs_repo
132 c.enable_downloads = self.db_repo.enable_downloads
132 c.enable_downloads = self.db_repo.enable_downloads
133 return c
133 return c
134
134
135 def _ensure_not_locked(self, commit_id='tip'):
135 def _ensure_not_locked(self, commit_id='tip'):
136 _ = self.request.translate
136 _ = self.request.translate
137
137
138 repo = self.db_repo
138 repo = self.db_repo
139 if repo.enable_locking and repo.locked[0]:
139 if repo.enable_locking and repo.locked[0]:
140 h.flash(_('This repository has been locked by %s on %s')
140 h.flash(_('This repository has been locked by %s on %s')
141 % (h.person_by_id(repo.locked[0]),
141 % (h.person_by_id(repo.locked[0]),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
142 h.format_date(h.time_to_datetime(repo.locked[1]))),
143 'warning')
143 'warning')
144 files_url = h.route_path(
144 files_url = h.route_path(
145 'repo_files:default_path',
145 'repo_files:default_path',
146 repo_name=self.db_repo_name, commit_id=commit_id)
146 repo_name=self.db_repo_name, commit_id=commit_id)
147 raise HTTPFound(files_url)
147 raise HTTPFound(files_url)
148
148
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
149 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
150 _ = self.request.translate
150 _ = self.request.translate
151
151
152 if not is_head:
152 if not is_head:
153 message = _('Cannot modify file. '
153 message = _('Cannot modify file. '
154 'Given commit `{}` is not head of a branch.').format(commit_id)
154 'Given commit `{}` is not head of a branch.').format(commit_id)
155 h.flash(message, category='warning')
155 h.flash(message, category='warning')
156
156
157 if json_mode:
157 if json_mode:
158 return message
158 return message
159
159
160 files_url = h.route_path(
160 files_url = h.route_path(
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
161 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
162 f_path=f_path)
162 f_path=f_path)
163 raise HTTPFound(files_url)
163 raise HTTPFound(files_url)
164
164
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
165 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
166 _ = self.request.translate
166 _ = self.request.translate
167
167
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
168 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
169 self.db_repo_name, branch_name)
169 self.db_repo_name, branch_name)
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
170 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
171 message = _('Branch `{}` changes forbidden by rule {}.').format(
172 h.escape(branch_name), h.escape(rule))
172 h.escape(branch_name), h.escape(rule))
173 h.flash(message, 'warning')
173 h.flash(message, 'warning')
174
174
175 if json_mode:
175 if json_mode:
176 return message
176 return message
177
177
178 files_url = h.route_path(
178 files_url = h.route_path(
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
179 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
180
180
181 raise HTTPFound(files_url)
181 raise HTTPFound(files_url)
182
182
183 def _get_commit_and_path(self):
183 def _get_commit_and_path(self):
184 default_commit_id = self.db_repo.landing_ref_name
184 default_commit_id = self.db_repo.landing_ref_name
185 default_f_path = '/'
185 default_f_path = '/'
186
186
187 commit_id = self.request.matchdict.get(
187 commit_id = self.request.matchdict.get(
188 'commit_id', default_commit_id)
188 'commit_id', default_commit_id)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
189 f_path = self._get_f_path(self.request.matchdict, default_f_path)
190 return commit_id, f_path
190 return commit_id, f_path
191
191
192 def _get_default_encoding(self, c):
192 def _get_default_encoding(self, c):
193 enc_list = getattr(c, 'default_encodings', [])
193 enc_list = getattr(c, 'default_encodings', [])
194 return enc_list[0] if enc_list else 'UTF-8'
194 return enc_list[0] if enc_list else 'UTF-8'
195
195
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
196 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
197 """
197 """
198 This is a safe way to get commit. If an error occurs it redirects to
198 This is a safe way to get commit. If an error occurs it redirects to
199 tip with proper message
199 tip with proper message
200
200
201 :param commit_id: id of commit to fetch
201 :param commit_id: id of commit to fetch
202 :param redirect_after: toggle redirection
202 :param redirect_after: toggle redirection
203 """
203 """
204 _ = self.request.translate
204 _ = self.request.translate
205
205
206 try:
206 try:
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
207 return self.rhodecode_vcs_repo.get_commit(commit_id)
208 except EmptyRepositoryError:
208 except EmptyRepositoryError:
209 if not redirect_after:
209 if not redirect_after:
210 return None
210 return None
211
211
212 add_new = upload_new = ""
212 add_new = upload_new = ""
213 if h.HasRepoPermissionAny(
213 if h.HasRepoPermissionAny(
214 'repository.write', 'repository.admin')(self.db_repo_name):
214 'repository.write', 'repository.admin')(self.db_repo_name):
215 _url = h.route_path(
215 _url = h.route_path(
216 'repo_files_add_file',
216 'repo_files_add_file',
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
217 repo_name=self.db_repo_name, commit_id=0, f_path='')
218 add_new = h.link_to(
218 add_new = h.link_to(
219 _('add a new file'), _url, class_="alert-link")
219 _('add a new file'), _url, class_="alert-link")
220
220
221 _url_upld = h.route_path(
221 _url_upld = h.route_path(
222 'repo_files_upload_file',
222 'repo_files_upload_file',
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
223 repo_name=self.db_repo_name, commit_id=0, f_path='')
224 upload_new = h.link_to(
224 upload_new = h.link_to(
225 _('upload a new file'), _url_upld, class_="alert-link")
225 _('upload a new file'), _url_upld, class_="alert-link")
226
226
227 h.flash(h.literal(
227 h.flash(h.literal(
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
228 _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
229 raise HTTPFound(
229 raise HTTPFound(
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
230 h.route_path('repo_summary', repo_name=self.db_repo_name))
231
231
232 except (CommitDoesNotExistError, LookupError) as e:
232 except (CommitDoesNotExistError, LookupError) as e:
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
233 msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
234 h.flash(msg, category='error')
234 h.flash(msg, category='error')
235 raise HTTPNotFound()
235 raise HTTPNotFound()
236 except RepositoryError as e:
236 except RepositoryError as e:
237 h.flash(h.escape(safe_str(e)), category='error')
237 h.flash(h.escape(safe_str(e)), category='error')
238 raise HTTPNotFound()
238 raise HTTPNotFound()
239
239
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
240 def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
241 """
241 """
242 Returns file_node, if error occurs or given path is directory,
242 Returns file_node, if error occurs or given path is directory,
243 it'll redirect to top level path
243 it'll redirect to top level path
244 """
244 """
245 _ = self.request.translate
245 _ = self.request.translate
246
246
247 try:
247 try:
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
248 file_node = commit_obj.get_node(path, pre_load=pre_load)
249 if file_node.is_dir():
249 if file_node.is_dir():
250 raise RepositoryError('The given path is a directory')
250 raise RepositoryError('The given path is a directory')
251 except CommitDoesNotExistError:
251 except CommitDoesNotExistError:
252 log.exception('No such commit exists for this repository')
252 log.exception('No such commit exists for this repository')
253 h.flash(_('No such commit exists for this repository'), category='error')
253 h.flash(_('No such commit exists for this repository'), category='error')
254 raise HTTPNotFound()
254 raise HTTPNotFound()
255 except RepositoryError as e:
255 except RepositoryError as e:
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
256 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
257 h.flash(h.escape(safe_str(e)), category='error')
257 h.flash(h.escape(safe_str(e)), category='error')
258 raise HTTPNotFound()
258 raise HTTPNotFound()
259
259
260 return file_node
260 return file_node
261
261
262 def _is_valid_head(self, commit_id, repo, landing_ref):
262 def _is_valid_head(self, commit_id, repo, landing_ref):
263 branch_name = sha_commit_id = ''
263 branch_name = sha_commit_id = ''
264 is_head = False
264 is_head = False
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
265 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
266
266
267 for _branch_name, branch_commit_id in repo.branches.items():
267 for _branch_name, branch_commit_id in repo.branches.items():
268 # simple case we pass in branch name, it's a HEAD
268 # simple case we pass in branch name, it's a HEAD
269 if commit_id == _branch_name:
269 if commit_id == _branch_name:
270 is_head = True
270 is_head = True
271 branch_name = _branch_name
271 branch_name = _branch_name
272 sha_commit_id = branch_commit_id
272 sha_commit_id = branch_commit_id
273 break
273 break
274 # case when we pass in full sha commit_id, which is a head
274 # case when we pass in full sha commit_id, which is a head
275 elif commit_id == branch_commit_id:
275 elif commit_id == branch_commit_id:
276 is_head = True
276 is_head = True
277 branch_name = _branch_name
277 branch_name = _branch_name
278 sha_commit_id = branch_commit_id
278 sha_commit_id = branch_commit_id
279 break
279 break
280
280
281 if h.is_svn(repo) and not repo.is_empty():
281 if h.is_svn(repo) and not repo.is_empty():
282 # Note: Subversion only has one head.
282 # Note: Subversion only has one head.
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
283 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
284 is_head = True
284 is_head = True
285 return branch_name, sha_commit_id, is_head
285 return branch_name, sha_commit_id, is_head
286
286
287 # checked branches, means we only need to try to get the branch/commit_sha
287 # checked branches, means we only need to try to get the branch/commit_sha
288 if repo.is_empty():
288 if repo.is_empty():
289 is_head = True
289 is_head = True
290 branch_name = landing_ref
290 branch_name = landing_ref
291 sha_commit_id = EmptyCommit().raw_id
291 sha_commit_id = EmptyCommit().raw_id
292 else:
292 else:
293 commit = repo.get_commit(commit_id=commit_id)
293 commit = repo.get_commit(commit_id=commit_id)
294 if commit:
294 if commit:
295 branch_name = commit.branch
295 branch_name = commit.branch
296 sha_commit_id = commit.raw_id
296 sha_commit_id = commit.raw_id
297
297
298 return branch_name, sha_commit_id, is_head
298 return branch_name, sha_commit_id, is_head
299
299
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
300 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
301
301
302 repo_id = self.db_repo.repo_id
302 repo_id = self.db_repo.repo_id
303 force_recache = self.get_recache_flag()
303 force_recache = self.get_recache_flag()
304
304
305 cache_seconds = safe_int(
305 cache_seconds = safe_int(
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
306 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
307 cache_on = not force_recache and cache_seconds > 0
307 cache_on = not force_recache and cache_seconds > 0
308 log.debug(
308 log.debug(
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
309 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
310 'with caching: %s[TTL: %ss]' % (
310 'with caching: %s[TTL: %ss]' % (
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
311 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
312
312
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
313 cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
314 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
315
315
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
316 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
317 def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
318 log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
319 _repo_id, _commit_id, _f_path)
319 _repo_id, _commit_id, _f_path)
320
320
321 c.full_load = _full_load
321 c.full_load = _full_load
322 return render(
322 return render(
323 'rhodecode:templates/files/files_browser_tree.mako',
323 'rhodecode:templates/files/files_browser_tree.mako',
324 self._get_template_context(c), self.request, _at_rev)
324 self._get_template_context(c), self.request, _at_rev)
325
325
326 return compute_file_tree(
326 return compute_file_tree(
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
327 self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
328
328
329 def create_pure_path(self, *parts):
329 def create_pure_path(self, *parts):
330 # Split paths and sanitize them, removing any ../ etc
330 # Split paths and sanitize them, removing any ../ etc
331 sanitized_path = [
331 sanitized_path = [
332 x for x in pathlib.PurePath(*parts).parts
332 x for x in pathlib.PurePath(*parts).parts
333 if x not in ['.', '..']]
333 if x not in ['.', '..']]
334
334
335 pure_path = pathlib.PurePath(*sanitized_path)
335 pure_path = pathlib.PurePath(*sanitized_path)
336 return pure_path
336 return pure_path
337
337
338 def _is_lf_enabled(self, target_repo):
338 def _is_lf_enabled(self, target_repo):
339 lf_enabled = False
339 lf_enabled = False
340
340
341 lf_key_for_vcs_map = {
341 lf_key_for_vcs_map = {
342 'hg': 'extensions_largefiles',
342 'hg': 'extensions_largefiles',
343 'git': 'vcs_git_lfs_enabled'
343 'git': 'vcs_git_lfs_enabled'
344 }
344 }
345
345
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
346 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
347
347
348 if lf_key_for_vcs:
348 if lf_key_for_vcs:
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
349 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
350
350
351 return lf_enabled
351 return lf_enabled
352
352
353 @LoginRequired()
353 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
356 def repo_archivefile(self):
356 def repo_archivefile(self):
357 # archive cache config
357 # archive cache config
358 from rhodecode import CONFIG
358 from rhodecode import CONFIG
359 _ = self.request.translate
359 _ = self.request.translate
360 self.load_default_context()
360 self.load_default_context()
361 default_at_path = '/'
361 default_at_path = '/'
362 fname = self.request.matchdict['fname']
362 fname = self.request.matchdict['fname']
363 subrepos = self.request.GET.get('subrepos') == 'true'
363 subrepos = self.request.GET.get('subrepos') == 'true'
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
364 with_hash = str2bool(self.request.GET.get('with_hash', '1'))
365 at_path = self.request.GET.get('at_path') or default_at_path
365 at_path = self.request.GET.get('at_path') or default_at_path
366
366
367 if not self.db_repo.enable_downloads:
367 if not self.db_repo.enable_downloads:
368 return Response(_('Downloads disabled'))
368 return Response(_('Downloads disabled'))
369
369
370 try:
370 try:
371 commit_id, ext, fileformat, content_type = \
371 commit_id, ext, fileformat, content_type = \
372 _get_archive_spec(fname)
372 _get_archive_spec(fname)
373 except ValueError:
373 except ValueError:
374 return Response(_('Unknown archive type for: `{}`').format(
374 return Response(_('Unknown archive type for: `{}`').format(
375 h.escape(fname)))
375 h.escape(fname)))
376
376
377 try:
377 try:
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
378 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
379 except CommitDoesNotExistError:
379 except CommitDoesNotExistError:
380 return Response(_('Unknown commit_id {}').format(
380 return Response(_('Unknown commit_id {}').format(
381 h.escape(commit_id)))
381 h.escape(commit_id)))
382 except EmptyRepositoryError:
382 except EmptyRepositoryError:
383 return Response(_('Empty repository'))
383 return Response(_('Empty repository'))
384
384
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
385 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
386 if commit_id != commit.raw_id:
386 if commit_id != commit.raw_id:
387 fname=f'{commit.raw_id}{ext}'
387 fname=f'{commit.raw_id}{ext}'
388 raise HTTPFound(self.request.current_route_path(fname=fname))
388 raise HTTPFound(self.request.current_route_path(fname=fname))
389
389
390 try:
390 try:
391 at_path = commit.get_node(at_path).path or default_at_path
391 at_path = commit.get_node(at_path).path or default_at_path
392 except Exception:
392 except Exception:
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
393 return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
394
394
395 path_sha = get_path_sha(at_path)
395 path_sha = get_path_sha(at_path)
396
396
397 # used for cache etc, consistent unique archive name
397 # used for cache etc, consistent unique archive name
398 archive_name_key = get_archive_name(
398 archive_name_key = get_archive_name(
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
399 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
400 path_sha=path_sha, with_hash=True)
400 path_sha=path_sha, with_hash=True)
401
401
402 if not with_hash:
402 if not with_hash:
403 path_sha = ''
403 path_sha = ''
404
404
405 # what end client gets served
405 # what end client gets served
406 response_archive_name = get_archive_name(
406 response_archive_name = get_archive_name(
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
407 self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
408 path_sha=path_sha, with_hash=with_hash)
408 path_sha=path_sha, with_hash=with_hash)
409
409
410 # remove extension from our archive directory name
410 # remove extension from our archive directory name
411 archive_dir_name = response_archive_name[:-len(ext)]
411 archive_dir_name = response_archive_name[:-len(ext)]
412
412
413 archive_cache_disable = self.request.GET.get('no_cache')
413 archive_cache_disable = self.request.GET.get('no_cache')
414
414
415 d_cache = get_archival_cache_store(config=CONFIG)
415 d_cache = get_archival_cache_store(config=CONFIG)
416
416
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
417 # NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
418 d_cache_conf = get_archival_config(config=CONFIG)
418 d_cache_conf = get_archival_config(config=CONFIG)
419
419
420 reentrant_lock_key = archive_name_key + '.lock'
420 reentrant_lock_key = archive_name_key + '.lock'
421 with ReentrantLock(d_cache, reentrant_lock_key):
421 with ReentrantLock(d_cache, reentrant_lock_key):
422 # This is also a cache key
422 # This is also a cache key
423 use_cached_archive = False
423 use_cached_archive = False
424 if archive_name_key in d_cache and not archive_cache_disable:
424 if archive_name_key in d_cache and not archive_cache_disable:
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
425 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
426 use_cached_archive = True
426 use_cached_archive = True
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
427 log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
428 archive_name_key, tag, reader.name)
428 archive_name_key, tag, reader.name)
429 else:
429 else:
430 reader = None
430 reader = None
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
431 log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
432
432
433 # generate new archive, as previous was not found in the cache
433 # generate new archive, as previous was not found in the cache
434 if not reader:
434 if not reader:
435
435
436 try:
436 try:
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
437 commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
438 kind=fileformat, subrepos=subrepos,
438 kind=fileformat, subrepos=subrepos,
439 archive_at_path=at_path, cache_config=d_cache_conf)
439 archive_at_path=at_path, cache_config=d_cache_conf)
440 except ImproperArchiveTypeError:
440 except ImproperArchiveTypeError:
441 return _('Unknown archive type')
441 return _('Unknown archive type')
442
442
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
443 reader, tag = d_cache.get(archive_name_key, read=True, tag=True, retry=True)
444
444
445 if not reader:
445 if not reader:
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
446 raise ValueError('archive cache reader is empty, failed to fetch file from distributed archive cache')
447
447
448 def archive_iterator(_reader, block_size: int = 4096*512):
448 def archive_iterator(_reader, block_size: int = 4096*512):
449 # 4096 * 64 = 64KB
449 # 4096 * 64 = 64KB
450 while 1:
450 while 1:
451 data = _reader.read(block_size)
451 data = _reader.read(block_size)
452 if not data:
452 if not data:
453 break
453 break
454 yield data
454 yield data
455
455
456 response = Response(app_iter=archive_iterator(reader))
456 response = Response(app_iter=archive_iterator(reader))
457 response.content_disposition = f'attachment; filename={response_archive_name}'
457 response.content_disposition = f'attachment; filename={response_archive_name}'
458 response.content_type = str(content_type)
458 response.content_type = str(content_type)
459
459
460 try:
460 try:
461 return response
461 return response
462 finally:
462 finally:
463 # store download action
463 # store download action
464 audit_logger.store_web(
464 audit_logger.store_web(
465 'repo.archive.download', action_data={
465 'repo.archive.download', action_data={
466 'user_agent': self.request.user_agent,
466 'user_agent': self.request.user_agent,
467 'archive_name': archive_name_key,
467 'archive_name': archive_name_key,
468 'archive_spec': fname,
468 'archive_spec': fname,
469 'archive_cached': use_cached_archive},
469 'archive_cached': use_cached_archive},
470 user=self._rhodecode_user,
470 user=self._rhodecode_user,
471 repo=self.db_repo,
471 repo=self.db_repo,
472 commit=True
472 commit=True
473 )
473 )
474
474
475 def _get_file_node(self, commit_id, f_path):
475 def _get_file_node(self, commit_id, f_path):
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
476 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
477 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
478 try:
478 try:
479 node = commit.get_node(f_path)
479 node = commit.get_node(f_path)
480 if node.is_dir():
480 if node.is_dir():
481 raise NodeError(f'{node} path is a {type(node)} not a file')
481 raise NodeError(f'{node} path is a {type(node)} not a file')
482 except NodeDoesNotExistError:
482 except NodeDoesNotExistError:
483 commit = EmptyCommit(
483 commit = EmptyCommit(
484 commit_id=commit_id,
484 commit_id=commit_id,
485 idx=commit.idx,
485 idx=commit.idx,
486 repo=commit.repository,
486 repo=commit.repository,
487 alias=commit.repository.alias,
487 alias=commit.repository.alias,
488 message=commit.message,
488 message=commit.message,
489 author=commit.author,
489 author=commit.author,
490 date=commit.date)
490 date=commit.date)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
491 node = FileNode(safe_bytes(f_path), b'', commit=commit)
492 else:
492 else:
493 commit = EmptyCommit(
493 commit = EmptyCommit(
494 repo=self.rhodecode_vcs_repo,
494 repo=self.rhodecode_vcs_repo,
495 alias=self.rhodecode_vcs_repo.alias)
495 alias=self.rhodecode_vcs_repo.alias)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
496 node = FileNode(safe_bytes(f_path), b'', commit=commit)
497 return node
497 return node
498
498
499 @LoginRequired()
499 @LoginRequired()
500 @HasRepoPermissionAnyDecorator(
500 @HasRepoPermissionAnyDecorator(
501 'repository.read', 'repository.write', 'repository.admin')
501 'repository.read', 'repository.write', 'repository.admin')
502 def repo_files_diff(self):
502 def repo_files_diff(self):
503 c = self.load_default_context()
503 c = self.load_default_context()
504 f_path = self._get_f_path(self.request.matchdict)
504 f_path = self._get_f_path(self.request.matchdict)
505 diff1 = self.request.GET.get('diff1', '')
505 diff1 = self.request.GET.get('diff1', '')
506 diff2 = self.request.GET.get('diff2', '')
506 diff2 = self.request.GET.get('diff2', '')
507
507
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
508 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
509
509
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
510 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
511 line_context = self.request.GET.get('context', 3)
511 line_context = self.request.GET.get('context', 3)
512
512
513 if not any((diff1, diff2)):
513 if not any((diff1, diff2)):
514 h.flash(
514 h.flash(
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
515 'Need query parameter "diff1" or "diff2" to generate a diff.',
516 category='error')
516 category='error')
517 raise HTTPBadRequest()
517 raise HTTPBadRequest()
518
518
519 c.action = self.request.GET.get('diff')
519 c.action = self.request.GET.get('diff')
520 if c.action not in ['download', 'raw']:
520 if c.action not in ['download', 'raw']:
521 compare_url = h.route_path(
521 compare_url = h.route_path(
522 'repo_compare',
522 'repo_compare',
523 repo_name=self.db_repo_name,
523 repo_name=self.db_repo_name,
524 source_ref_type='rev',
524 source_ref_type='rev',
525 source_ref=diff1,
525 source_ref=diff1,
526 target_repo=self.db_repo_name,
526 target_repo=self.db_repo_name,
527 target_ref_type='rev',
527 target_ref_type='rev',
528 target_ref=diff2,
528 target_ref=diff2,
529 _query=dict(f_path=f_path))
529 _query=dict(f_path=f_path))
530 # redirect to new view if we render diff
530 # redirect to new view if we render diff
531 raise HTTPFound(compare_url)
531 raise HTTPFound(compare_url)
532
532
533 try:
533 try:
534 node1 = self._get_file_node(diff1, path1)
534 node1 = self._get_file_node(diff1, path1)
535 node2 = self._get_file_node(diff2, f_path)
535 node2 = self._get_file_node(diff2, f_path)
536 except (RepositoryError, NodeError):
536 except (RepositoryError, NodeError):
537 log.exception("Exception while trying to get node from repository")
537 log.exception("Exception while trying to get node from repository")
538 raise HTTPFound(
538 raise HTTPFound(
539 h.route_path('repo_files', repo_name=self.db_repo_name,
539 h.route_path('repo_files', repo_name=self.db_repo_name,
540 commit_id='tip', f_path=f_path))
540 commit_id='tip', f_path=f_path))
541
541
542 if all(isinstance(node.commit, EmptyCommit)
542 if all(isinstance(node.commit, EmptyCommit)
543 for node in (node1, node2)):
543 for node in (node1, node2)):
544 raise HTTPNotFound()
544 raise HTTPNotFound()
545
545
546 c.commit_1 = node1.commit
546 c.commit_1 = node1.commit
547 c.commit_2 = node2.commit
547 c.commit_2 = node2.commit
548
548
549 if c.action == 'download':
549 if c.action == 'download':
550 _diff = diffs.get_gitdiff(node1, node2,
550 _diff = diffs.get_gitdiff(node1, node2,
551 ignore_whitespace=ignore_whitespace,
551 ignore_whitespace=ignore_whitespace,
552 context=line_context)
552 context=line_context)
553 # NOTE: this was using diff_format='gitdiff'
553 # NOTE: this was using diff_format='gitdiff'
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
554 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
555
555
556 response = Response(self.path_filter.get_raw_patch(diff))
556 response = Response(self.path_filter.get_raw_patch(diff))
557 response.content_type = 'text/plain'
557 response.content_type = 'text/plain'
558 response.content_disposition = (
558 response.content_disposition = (
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
559 f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
560 )
560 )
561 charset = self._get_default_encoding(c)
561 charset = self._get_default_encoding(c)
562 if charset:
562 if charset:
563 response.charset = charset
563 response.charset = charset
564 return response
564 return response
565
565
566 elif c.action == 'raw':
566 elif c.action == 'raw':
567 _diff = diffs.get_gitdiff(node1, node2,
567 _diff = diffs.get_gitdiff(node1, node2,
568 ignore_whitespace=ignore_whitespace,
568 ignore_whitespace=ignore_whitespace,
569 context=line_context)
569 context=line_context)
570 # NOTE: this was using diff_format='gitdiff'
570 # NOTE: this was using diff_format='gitdiff'
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
571 diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
572
572
573 response = Response(self.path_filter.get_raw_patch(diff))
573 response = Response(self.path_filter.get_raw_patch(diff))
574 response.content_type = 'text/plain'
574 response.content_type = 'text/plain'
575 charset = self._get_default_encoding(c)
575 charset = self._get_default_encoding(c)
576 if charset:
576 if charset:
577 response.charset = charset
577 response.charset = charset
578 return response
578 return response
579
579
580 # in case we ever end up here
580 # in case we ever end up here
581 raise HTTPNotFound()
581 raise HTTPNotFound()
582
582
583 @LoginRequired()
583 @LoginRequired()
584 @HasRepoPermissionAnyDecorator(
584 @HasRepoPermissionAnyDecorator(
585 'repository.read', 'repository.write', 'repository.admin')
585 'repository.read', 'repository.write', 'repository.admin')
586 def repo_files_diff_2way_redirect(self):
586 def repo_files_diff_2way_redirect(self):
587 """
587 """
588 Kept only to make OLD links work
588 Kept only to make OLD links work
589 """
589 """
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
590 f_path = self._get_f_path_unchecked(self.request.matchdict)
591 diff1 = self.request.GET.get('diff1', '')
591 diff1 = self.request.GET.get('diff1', '')
592 diff2 = self.request.GET.get('diff2', '')
592 diff2 = self.request.GET.get('diff2', '')
593
593
594 if not any((diff1, diff2)):
594 if not any((diff1, diff2)):
595 h.flash(
595 h.flash(
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
596 'Need query parameter "diff1" or "diff2" to generate a diff.',
597 category='error')
597 category='error')
598 raise HTTPBadRequest()
598 raise HTTPBadRequest()
599
599
600 compare_url = h.route_path(
600 compare_url = h.route_path(
601 'repo_compare',
601 'repo_compare',
602 repo_name=self.db_repo_name,
602 repo_name=self.db_repo_name,
603 source_ref_type='rev',
603 source_ref_type='rev',
604 source_ref=diff1,
604 source_ref=diff1,
605 target_ref_type='rev',
605 target_ref_type='rev',
606 target_ref=diff2,
606 target_ref=diff2,
607 _query=dict(f_path=f_path, diffmode='sideside',
607 _query=dict(f_path=f_path, diffmode='sideside',
608 target_repo=self.db_repo_name,))
608 target_repo=self.db_repo_name,))
609 raise HTTPFound(compare_url)
609 raise HTTPFound(compare_url)
610
610
611 @LoginRequired()
611 @LoginRequired()
612 def repo_files_default_commit_redirect(self):
612 def repo_files_default_commit_redirect(self):
613 """
613 """
614 Special page that redirects to the landing page of files based on the default
614 Special page that redirects to the landing page of files based on the default
615 commit for repository
615 commit for repository
616 """
616 """
617 c = self.load_default_context()
617 c = self.load_default_context()
618 ref_name = c.rhodecode_db_repo.landing_ref_name
618 ref_name = c.rhodecode_db_repo.landing_ref_name
619 landing_url = h.repo_files_by_ref_url(
619 landing_url = h.repo_files_by_ref_url(
620 c.rhodecode_db_repo.repo_name,
620 c.rhodecode_db_repo.repo_name,
621 c.rhodecode_db_repo.repo_type,
621 c.rhodecode_db_repo.repo_type,
622 f_path='',
622 f_path='',
623 ref_name=ref_name,
623 ref_name=ref_name,
624 commit_id='tip',
624 commit_id='tip',
625 query=dict(at=ref_name)
625 query=dict(at=ref_name)
626 )
626 )
627
627
628 raise HTTPFound(landing_url)
628 raise HTTPFound(landing_url)
629
629
630 @LoginRequired()
630 @LoginRequired()
631 @HasRepoPermissionAnyDecorator(
631 @HasRepoPermissionAnyDecorator(
632 'repository.read', 'repository.write', 'repository.admin')
632 'repository.read', 'repository.write', 'repository.admin')
633 def repo_files(self):
633 def repo_files(self):
634 c = self.load_default_context()
634 c = self.load_default_context()
635
635
636 view_name = getattr(self.request.matched_route, 'name', None)
636 view_name = getattr(self.request.matched_route, 'name', None)
637
637
638 c.annotate = view_name == 'repo_files:annotated'
638 c.annotate = view_name == 'repo_files:annotated'
639 # default is false, but .rst/.md files later are auto rendered, we can
639 # default is false, but .rst/.md files later are auto rendered, we can
640 # overwrite auto rendering by setting this GET flag
640 # overwrite auto rendering by setting this GET flag
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
641 c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
642
642
643 commit_id, f_path = self._get_commit_and_path()
643 commit_id, f_path = self._get_commit_and_path()
644
644
645 c.commit = self._get_commit_or_redirect(commit_id)
645 c.commit = self._get_commit_or_redirect(commit_id)
646 c.branch = self.request.GET.get('branch', None)
646 c.branch = self.request.GET.get('branch', None)
647 c.f_path = f_path
647 c.f_path = f_path
648 at_rev = self.request.GET.get('at')
648 at_rev = self.request.GET.get('at')
649
649
650 # files or dirs
650 # files or dirs
651 try:
651 try:
652 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
652 c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
653
653
654 c.file_author = True
654 c.file_author = True
655 c.file_tree = ''
655 c.file_tree = ''
656
656
657 # prev link
657 # prev link
658 try:
658 try:
659 prev_commit = c.commit.prev(c.branch)
659 prev_commit = c.commit.prev(c.branch)
660 c.prev_commit = prev_commit
660 c.prev_commit = prev_commit
661 c.url_prev = h.route_path(
661 c.url_prev = h.route_path(
662 'repo_files', repo_name=self.db_repo_name,
662 'repo_files', repo_name=self.db_repo_name,
663 commit_id=prev_commit.raw_id, f_path=f_path)
663 commit_id=prev_commit.raw_id, f_path=f_path)
664 if c.branch:
664 if c.branch:
665 c.url_prev += '?branch=%s' % c.branch
665 c.url_prev += '?branch=%s' % c.branch
666 except (CommitDoesNotExistError, VCSError):
666 except (CommitDoesNotExistError, VCSError):
667 c.url_prev = '#'
667 c.url_prev = '#'
668 c.prev_commit = EmptyCommit()
668 c.prev_commit = EmptyCommit()
669
669
670 # next link
670 # next link
671 try:
671 try:
672 next_commit = c.commit.next(c.branch)
672 next_commit = c.commit.next(c.branch)
673 c.next_commit = next_commit
673 c.next_commit = next_commit
674 c.url_next = h.route_path(
674 c.url_next = h.route_path(
675 'repo_files', repo_name=self.db_repo_name,
675 'repo_files', repo_name=self.db_repo_name,
676 commit_id=next_commit.raw_id, f_path=f_path)
676 commit_id=next_commit.raw_id, f_path=f_path)
677 if c.branch:
677 if c.branch:
678 c.url_next += '?branch=%s' % c.branch
678 c.url_next += '?branch=%s' % c.branch
679 except (CommitDoesNotExistError, VCSError):
679 except (CommitDoesNotExistError, VCSError):
680 c.url_next = '#'
680 c.url_next = '#'
681 c.next_commit = EmptyCommit()
681 c.next_commit = EmptyCommit()
682
682
683 # load file content
683 # load file content
684 if c.file.is_file():
684 if c.file.is_file():
685
685
686 c.lf_node = {}
686 c.lf_node = {}
687
687
688 has_lf_enabled = self._is_lf_enabled(self.db_repo)
688 has_lf_enabled = self._is_lf_enabled(self.db_repo)
689 if has_lf_enabled:
689 if has_lf_enabled:
690 c.lf_node = c.file.get_largefile_node()
690 c.lf_node = c.file.get_largefile_node()
691
691
692 c.file_source_page = 'true'
692 c.file_source_page = 'true'
693 c.file_last_commit = c.file.last_commit
693 c.file_last_commit = c.file.last_commit
694
694
695 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
695 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
696
696
697 if not (c.file_size_too_big or c.file.is_binary):
697 if not (c.file_size_too_big or c.file.is_binary):
698 if c.annotate: # annotation has precedence over renderer
698 if c.annotate: # annotation has precedence over renderer
699 c.annotated_lines = filenode_as_annotated_lines_tokens(
699 c.annotated_lines = filenode_as_annotated_lines_tokens(
700 c.file
700 c.file
701 )
701 )
702 else:
702 else:
703 c.renderer = (
703 c.renderer = (
704 c.renderer and h.renderer_from_filename(c.file.path)
704 c.renderer and h.renderer_from_filename(c.file.path)
705 )
705 )
706 if not c.renderer:
706 if not c.renderer:
707 c.lines = filenode_as_lines_tokens(c.file)
707 c.lines = filenode_as_lines_tokens(c.file)
708
708
709 _branch_name, _sha_commit_id, is_head = \
709 _branch_name, _sha_commit_id, is_head = \
710 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
710 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
711 landing_ref=self.db_repo.landing_ref_name)
711 landing_ref=self.db_repo.landing_ref_name)
712 c.on_branch_head = is_head
712 c.on_branch_head = is_head
713
713
714 branch = c.commit.branch if (
714 branch = c.commit.branch if (
715 c.commit.branch and '/' not in c.commit.branch) else None
715 c.commit.branch and '/' not in c.commit.branch) else None
716 c.branch_or_raw_id = branch or c.commit.raw_id
716 c.branch_or_raw_id = branch or c.commit.raw_id
717 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
717 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
718
718
719 author = c.file_last_commit.author
719 author = c.file_last_commit.author
720 c.authors = [[
720 c.authors = [[
721 h.email(author),
721 h.email(author),
722 h.person(author, 'username_or_name_or_email'),
722 h.person(author, 'username_or_name_or_email'),
723 1
723 1
724 ]]
724 ]]
725
725
726 else: # load tree content at path
726 else: # load tree content at path
727 c.file_source_page = 'false'
727 c.file_source_page = 'false'
728 c.authors = []
728 c.authors = []
729 # this loads a simple tree without metadata to speed things up
729 # this loads a simple tree without metadata to speed things up
730 # later via ajax we call repo_nodetree_full and fetch whole
730 # later via ajax we call repo_nodetree_full and fetch whole
731 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
731 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
732
732
733 c.readme_data, c.readme_file = \
733 c.readme_data, c.readme_file = \
734 self._get_readme_data(self.db_repo, c.visual.default_renderer,
734 self._get_readme_data(self.db_repo, c.visual.default_renderer,
735 c.commit.raw_id, f_path)
735 c.commit.raw_id, f_path)
736
736
737 except RepositoryError as e:
737 except RepositoryError as e:
738 h.flash(h.escape(safe_str(e)), category='error')
738 h.flash(h.escape(safe_str(e)), category='error')
739 raise HTTPNotFound()
739 raise HTTPNotFound()
740
740
741 if self.request.environ.get('HTTP_X_PJAX'):
741 if self.request.environ.get('HTTP_X_PJAX'):
742 html = render('rhodecode:templates/files/files_pjax.mako',
742 html = render('rhodecode:templates/files/files_pjax.mako',
743 self._get_template_context(c), self.request)
743 self._get_template_context(c), self.request)
744 else:
744 else:
745 html = render('rhodecode:templates/files/files.mako',
745 html = render('rhodecode:templates/files/files.mako',
746 self._get_template_context(c), self.request)
746 self._get_template_context(c), self.request)
747 return Response(html)
747 return Response(html)
748
748
749 @HasRepoPermissionAnyDecorator(
749 @HasRepoPermissionAnyDecorator(
750 'repository.read', 'repository.write', 'repository.admin')
750 'repository.read', 'repository.write', 'repository.admin')
751 def repo_files_annotated_previous(self):
751 def repo_files_annotated_previous(self):
752 self.load_default_context()
752 self.load_default_context()
753
753
754 commit_id, f_path = self._get_commit_and_path()
754 commit_id, f_path = self._get_commit_and_path()
755 commit = self._get_commit_or_redirect(commit_id)
755 commit = self._get_commit_or_redirect(commit_id)
756 prev_commit_id = commit.raw_id
756 prev_commit_id = commit.raw_id
757 line_anchor = self.request.GET.get('line_anchor')
757 line_anchor = self.request.GET.get('line_anchor')
758 is_file = False
758 is_file = False
759 try:
759 try:
760 _file = commit.get_node(f_path)
760 _file = commit.get_node(f_path)
761 is_file = _file.is_file()
761 is_file = _file.is_file()
762 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
762 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
763 pass
763 pass
764
764
765 if is_file:
765 if is_file:
766 history = commit.get_path_history(f_path)
766 history = commit.get_path_history(f_path)
767 prev_commit_id = history[1].raw_id \
767 prev_commit_id = history[1].raw_id \
768 if len(history) > 1 else prev_commit_id
768 if len(history) > 1 else prev_commit_id
769 prev_url = h.route_path(
769 prev_url = h.route_path(
770 'repo_files:annotated', repo_name=self.db_repo_name,
770 'repo_files:annotated', repo_name=self.db_repo_name,
771 commit_id=prev_commit_id, f_path=f_path,
771 commit_id=prev_commit_id, f_path=f_path,
772 _anchor=f'L{line_anchor}')
772 _anchor=f'L{line_anchor}')
773
773
774 raise HTTPFound(prev_url)
774 raise HTTPFound(prev_url)
775
775
776 @LoginRequired()
776 @LoginRequired()
777 @HasRepoPermissionAnyDecorator(
777 @HasRepoPermissionAnyDecorator(
778 'repository.read', 'repository.write', 'repository.admin')
778 'repository.read', 'repository.write', 'repository.admin')
779 def repo_nodetree_full(self):
779 def repo_nodetree_full(self):
780 """
780 """
781 Returns rendered html of file tree that contains commit date,
781 Returns rendered html of file tree that contains commit date,
782 author, commit_id for the specified combination of
782 author, commit_id for the specified combination of
783 repo, commit_id and file path
783 repo, commit_id and file path
784 """
784 """
785 c = self.load_default_context()
785 c = self.load_default_context()
786
786
787 commit_id, f_path = self._get_commit_and_path()
787 commit_id, f_path = self._get_commit_and_path()
788 commit = self._get_commit_or_redirect(commit_id)
788 commit = self._get_commit_or_redirect(commit_id)
789 try:
789 try:
790 dir_node = commit.get_node(f_path)
790 dir_node = commit.get_node(f_path)
791 except RepositoryError as e:
791 except RepositoryError as e:
792 return Response(f'error: {h.escape(safe_str(e))}')
792 return Response(f'error: {h.escape(safe_str(e))}')
793
793
794 if dir_node.is_file():
794 if dir_node.is_file():
795 return Response('')
795 return Response('')
796
796
797 c.file = dir_node
797 c.file = dir_node
798 c.commit = commit
798 c.commit = commit
799 at_rev = self.request.GET.get('at')
799 at_rev = self.request.GET.get('at')
800
800
801 html = self._get_tree_at_commit(
801 html = self._get_tree_at_commit(
802 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
802 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
803
803
804 return Response(html)
804 return Response(html)
805
805
806 def _get_attachement_headers(self, f_path):
806 def _get_attachement_headers(self, f_path):
807 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
807 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
808 safe_path = f_name.replace('"', '\\"')
808 safe_path = f_name.replace('"', '\\"')
809 encoded_path = urllib.parse.quote(f_name)
809 encoded_path = urllib.parse.quote(f_name)
810
810
811 headers = "attachment; " \
811 headers = "attachment; " \
812 "filename=\"{}\"; " \
812 "filename=\"{}\"; " \
813 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
813 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
814
814
815 return safe_bytes(headers).decode('latin-1', errors='replace')
815 return safe_bytes(headers).decode('latin-1', errors='replace')
816
816
817 @LoginRequired()
817 @LoginRequired()
818 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
819 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
820 def repo_file_raw(self):
820 def repo_file_raw(self):
821 """
821 """
822 Action for show as raw, some mimetypes are "rendered",
822 Action for show as raw, some mimetypes are "rendered",
823 those include images, icons.
823 those include images, icons.
824 """
824 """
825 c = self.load_default_context()
825 c = self.load_default_context()
826
826
827 commit_id, f_path = self._get_commit_and_path()
827 commit_id, f_path = self._get_commit_and_path()
828 commit = self._get_commit_or_redirect(commit_id)
828 commit = self._get_commit_or_redirect(commit_id)
829 file_node = self._get_filenode_or_redirect(commit, f_path)
829 file_node = self._get_filenode_or_redirect(commit, f_path)
830
830
831 raw_mimetype_mapping = {
831 raw_mimetype_mapping = {
832 # map original mimetype to a mimetype used for "show as raw"
832 # map original mimetype to a mimetype used for "show as raw"
833 # you can also provide a content-disposition to override the
833 # you can also provide a content-disposition to override the
834 # default "attachment" disposition.
834 # default "attachment" disposition.
835 # orig_type: (new_type, new_dispo)
835 # orig_type: (new_type, new_dispo)
836
836
837 # show images inline:
837 # show images inline:
838 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
838 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
839 # for example render an SVG with javascript inside or even render
839 # for example render an SVG with javascript inside or even render
840 # HTML.
840 # HTML.
841 'image/x-icon': ('image/x-icon', 'inline'),
841 'image/x-icon': ('image/x-icon', 'inline'),
842 'image/png': ('image/png', 'inline'),
842 'image/png': ('image/png', 'inline'),
843 'image/gif': ('image/gif', 'inline'),
843 'image/gif': ('image/gif', 'inline'),
844 'image/jpeg': ('image/jpeg', 'inline'),
844 'image/jpeg': ('image/jpeg', 'inline'),
845 'application/pdf': ('application/pdf', 'inline'),
845 'application/pdf': ('application/pdf', 'inline'),
846 }
846 }
847
847
848 mimetype = file_node.mimetype
848 mimetype = file_node.mimetype
849 try:
849 try:
850 mimetype, disposition = raw_mimetype_mapping[mimetype]
850 mimetype, disposition = raw_mimetype_mapping[mimetype]
851 except KeyError:
851 except KeyError:
852 # we don't know anything special about this, handle it safely
852 # we don't know anything special about this, handle it safely
853 if file_node.is_binary:
853 if file_node.is_binary:
854 # do same as download raw for binary files
854 # do same as download raw for binary files
855 mimetype, disposition = 'application/octet-stream', 'attachment'
855 mimetype, disposition = 'application/octet-stream', 'attachment'
856 else:
856 else:
857 # do not just use the original mimetype, but force text/plain,
857 # do not just use the original mimetype, but force text/plain,
858 # otherwise it would serve text/html and that might be unsafe.
858 # otherwise it would serve text/html and that might be unsafe.
859 # Note: underlying vcs library fakes text/plain mimetype if the
859 # Note: underlying vcs library fakes text/plain mimetype if the
860 # mimetype can not be determined and it thinks it is not
860 # mimetype can not be determined and it thinks it is not
861 # binary.This might lead to erroneous text display in some
861 # binary.This might lead to erroneous text display in some
862 # cases, but helps in other cases, like with text files
862 # cases, but helps in other cases, like with text files
863 # without extension.
863 # without extension.
864 mimetype, disposition = 'text/plain', 'inline'
864 mimetype, disposition = 'text/plain', 'inline'
865
865
866 if disposition == 'attachment':
866 if disposition == 'attachment':
867 disposition = self._get_attachement_headers(f_path)
867 disposition = self._get_attachement_headers(f_path)
868
868
869 stream_content = file_node.stream_bytes()
869 stream_content = file_node.stream_bytes()
870
870
871 response = Response(app_iter=stream_content)
871 response = Response(app_iter=stream_content)
872 response.content_disposition = disposition
872 response.content_disposition = disposition
873 response.content_type = mimetype
873 response.content_type = mimetype
874
874
875 charset = self._get_default_encoding(c)
875 charset = self._get_default_encoding(c)
876 if charset:
876 if charset:
877 response.charset = charset
877 response.charset = charset
878
878
879 return response
879 return response
880
880
881 @LoginRequired()
881 @LoginRequired()
882 @HasRepoPermissionAnyDecorator(
882 @HasRepoPermissionAnyDecorator(
883 'repository.read', 'repository.write', 'repository.admin')
883 'repository.read', 'repository.write', 'repository.admin')
884 def repo_file_download(self):
884 def repo_file_download(self):
885 c = self.load_default_context()
885 c = self.load_default_context()
886
886
887 commit_id, f_path = self._get_commit_and_path()
887 commit_id, f_path = self._get_commit_and_path()
888 commit = self._get_commit_or_redirect(commit_id)
888 commit = self._get_commit_or_redirect(commit_id)
889 file_node = self._get_filenode_or_redirect(commit, f_path)
889 file_node = self._get_filenode_or_redirect(commit, f_path)
890
890
891 if self.request.GET.get('lf'):
891 if self.request.GET.get('lf'):
892 # only if lf get flag is passed, we download this file
892 # only if lf get flag is passed, we download this file
893 # as LFS/Largefile
893 # as LFS/Largefile
894 lf_node = file_node.get_largefile_node()
894 lf_node = file_node.get_largefile_node()
895 if lf_node:
895 if lf_node:
896 # overwrite our pointer with the REAL large-file
896 # overwrite our pointer with the REAL large-file
897 file_node = lf_node
897 file_node = lf_node
898
898
899 disposition = self._get_attachement_headers(f_path)
899 disposition = self._get_attachement_headers(f_path)
900
900
901 stream_content = file_node.stream_bytes()
901 stream_content = file_node.stream_bytes()
902
902
903 response = Response(app_iter=stream_content)
903 response = Response(app_iter=stream_content)
904 response.content_disposition = disposition
904 response.content_disposition = disposition
905 response.content_type = file_node.mimetype
905 response.content_type = file_node.mimetype
906
906
907 charset = self._get_default_encoding(c)
907 charset = self._get_default_encoding(c)
908 if charset:
908 if charset:
909 response.charset = charset
909 response.charset = charset
910
910
911 return response
911 return response
912
912
913 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
913 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
914
914
915 cache_seconds = safe_int(
915 cache_seconds = safe_int(
916 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
916 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
917 cache_on = cache_seconds > 0
917 cache_on = cache_seconds > 0
918 log.debug(
918 log.debug(
919 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
919 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
920 'with caching: %s[TTL: %ss]' % (
920 'with caching: %s[TTL: %ss]' % (
921 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
921 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
922
922
923 cache_namespace_uid = f'repo.{repo_id}'
923 cache_namespace_uid = f'repo.{repo_id}'
924 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
924 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
925
925
926 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
926 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
927 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
927 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
928 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
928 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
929 _repo_id, commit_id, f_path)
929 _repo_id, commit_id, f_path)
930 try:
930 try:
931 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
931 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
932 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
932 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
933 log.exception(safe_str(e))
933 log.exception(safe_str(e))
934 h.flash(h.escape(safe_str(e)), category='error')
934 h.flash(h.escape(safe_str(e)), category='error')
935 raise HTTPFound(h.route_path(
935 raise HTTPFound(h.route_path(
936 'repo_files', repo_name=self.db_repo_name,
936 'repo_files', repo_name=self.db_repo_name,
937 commit_id='tip', f_path='/'))
937 commit_id='tip', f_path='/'))
938
938
939 return _d + _f
939 return _d + _f
940
940
941 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
941 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
942 commit_id, f_path)
942 commit_id, f_path)
943 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
943 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
944
944
945 @LoginRequired()
945 @LoginRequired()
946 @HasRepoPermissionAnyDecorator(
946 @HasRepoPermissionAnyDecorator(
947 'repository.read', 'repository.write', 'repository.admin')
947 'repository.read', 'repository.write', 'repository.admin')
948 def repo_nodelist(self):
948 def repo_nodelist(self):
949 self.load_default_context()
949 self.load_default_context()
950
950
951 commit_id, f_path = self._get_commit_and_path()
951 commit_id, f_path = self._get_commit_and_path()
952 commit = self._get_commit_or_redirect(commit_id)
952 commit = self._get_commit_or_redirect(commit_id)
953
953
954 metadata = self._get_nodelist_at_commit(
954 metadata = self._get_nodelist_at_commit(
955 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
955 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
956 return {'nodes': [x for x in metadata]}
956 return {'nodes': [x for x in metadata]}
957
957
958 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
958 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
959 items = []
959 items = []
960 for name, commit_id in branches_or_tags.items():
960 for name, commit_id in branches_or_tags.items():
961 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
961 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
962 items.append((sym_ref, name, ref_type))
962 items.append((sym_ref, name, ref_type))
963 return items
963 return items
964
964
965 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
965 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
966 return commit_id
966 return commit_id
967
967
968 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
968 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
969 return commit_id
969 return commit_id
970
970
971 # NOTE(dan): old code we used in "diff" mode compare
971 # NOTE(dan): old code we used in "diff" mode compare
972 new_f_path = vcspath.join(name, f_path)
972 new_f_path = vcspath.join(name, f_path)
973 return f'{new_f_path}@{commit_id}'
973 return f'{new_f_path}@{commit_id}'
974
974
975 def _get_node_history(self, commit_obj, f_path, commits=None):
975 def _get_node_history(self, commit_obj, f_path, commits=None):
976 """
976 """
977 get commit history for given node
977 get commit history for given node
978
978
979 :param commit_obj: commit to calculate history
979 :param commit_obj: commit to calculate history
980 :param f_path: path for node to calculate history for
980 :param f_path: path for node to calculate history for
981 :param commits: if passed don't calculate history and take
981 :param commits: if passed don't calculate history and take
982 commits defined in this list
982 commits defined in this list
983 """
983 """
984 _ = self.request.translate
984 _ = self.request.translate
985
985
986 # calculate history based on tip
986 # calculate history based on tip
987 tip = self.rhodecode_vcs_repo.get_commit()
987 tip = self.rhodecode_vcs_repo.get_commit()
988 if commits is None:
988 if commits is None:
989 pre_load = ["author", "branch"]
989 pre_load = ["author", "branch"]
990 try:
990 try:
991 commits = tip.get_path_history(f_path, pre_load=pre_load)
991 commits = tip.get_path_history(f_path, pre_load=pre_load)
992 except (NodeDoesNotExistError, CommitError):
992 except (NodeDoesNotExistError, CommitError):
993 # this node is not present at tip!
993 # this node is not present at tip!
994 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
994 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
995
995
996 history = []
996 history = []
997 commits_group = ([], _("Changesets"))
997 commits_group = ([], _("Changesets"))
998 for commit in commits:
998 for commit in commits:
999 branch = ' (%s)' % commit.branch if commit.branch else ''
999 branch = ' (%s)' % commit.branch if commit.branch else ''
1000 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
1000 n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
1001 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1001 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
1002 history.append(commits_group)
1002 history.append(commits_group)
1003
1003
1004 symbolic_reference = self._symbolic_reference
1004 symbolic_reference = self._symbolic_reference
1005
1005
1006 if self.rhodecode_vcs_repo.alias == 'svn':
1006 if self.rhodecode_vcs_repo.alias == 'svn':
1007 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1007 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1008 f_path, self.rhodecode_vcs_repo)
1008 f_path, self.rhodecode_vcs_repo)
1009 if adjusted_f_path != f_path:
1009 if adjusted_f_path != f_path:
1010 log.debug(
1010 log.debug(
1011 'Recognized svn tag or branch in file "%s", using svn '
1011 'Recognized svn tag or branch in file "%s", using svn '
1012 'specific symbolic references', f_path)
1012 'specific symbolic references', f_path)
1013 f_path = adjusted_f_path
1013 f_path = adjusted_f_path
1014 symbolic_reference = self._symbolic_reference_svn
1014 symbolic_reference = self._symbolic_reference_svn
1015
1015
1016 branches = self._create_references(
1016 branches = self._create_references(
1017 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1017 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1018 branches_group = (branches, _("Branches"))
1018 branches_group = (branches, _("Branches"))
1019
1019
1020 tags = self._create_references(
1020 tags = self._create_references(
1021 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1021 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1022 tags_group = (tags, _("Tags"))
1022 tags_group = (tags, _("Tags"))
1023
1023
1024 history.append(branches_group)
1024 history.append(branches_group)
1025 history.append(tags_group)
1025 history.append(tags_group)
1026
1026
1027 return history, commits
1027 return history, commits
1028
1028
1029 @LoginRequired()
1029 @LoginRequired()
1030 @HasRepoPermissionAnyDecorator(
1030 @HasRepoPermissionAnyDecorator(
1031 'repository.read', 'repository.write', 'repository.admin')
1031 'repository.read', 'repository.write', 'repository.admin')
1032 def repo_file_history(self):
1032 def repo_file_history(self):
1033 self.load_default_context()
1033 self.load_default_context()
1034
1034
1035 commit_id, f_path = self._get_commit_and_path()
1035 commit_id, f_path = self._get_commit_and_path()
1036 commit = self._get_commit_or_redirect(commit_id)
1036 commit = self._get_commit_or_redirect(commit_id)
1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1037 file_node = self._get_filenode_or_redirect(commit, f_path)
1038
1038
1039 if file_node.is_file():
1039 if file_node.is_file():
1040 file_history, _hist = self._get_node_history(commit, f_path)
1040 file_history, _hist = self._get_node_history(commit, f_path)
1041
1041
1042 res = []
1042 res = []
1043 for section_items, section in file_history:
1043 for section_items, section in file_history:
1044 items = []
1044 items = []
1045 for obj_id, obj_text, obj_type in section_items:
1045 for obj_id, obj_text, obj_type in section_items:
1046 at_rev = ''
1046 at_rev = ''
1047 if obj_type in ['branch', 'bookmark', 'tag']:
1047 if obj_type in ['branch', 'bookmark', 'tag']:
1048 at_rev = obj_text
1048 at_rev = obj_text
1049 entry = {
1049 entry = {
1050 'id': obj_id,
1050 'id': obj_id,
1051 'text': obj_text,
1051 'text': obj_text,
1052 'type': obj_type,
1052 'type': obj_type,
1053 'at_rev': at_rev
1053 'at_rev': at_rev
1054 }
1054 }
1055
1055
1056 items.append(entry)
1056 items.append(entry)
1057
1057
1058 res.append({
1058 res.append({
1059 'text': section,
1059 'text': section,
1060 'children': items
1060 'children': items
1061 })
1061 })
1062
1062
1063 data = {
1063 data = {
1064 'more': False,
1064 'more': False,
1065 'results': res
1065 'results': res
1066 }
1066 }
1067 return data
1067 return data
1068
1068
1069 log.warning('Cannot fetch history for directory')
1069 log.warning('Cannot fetch history for directory')
1070 raise HTTPBadRequest()
1070 raise HTTPBadRequest()
1071
1071
1072 @LoginRequired()
1072 @LoginRequired()
1073 @HasRepoPermissionAnyDecorator(
1073 @HasRepoPermissionAnyDecorator(
1074 'repository.read', 'repository.write', 'repository.admin')
1074 'repository.read', 'repository.write', 'repository.admin')
1075 def repo_file_authors(self):
1075 def repo_file_authors(self):
1076 c = self.load_default_context()
1076 c = self.load_default_context()
1077
1077
1078 commit_id, f_path = self._get_commit_and_path()
1078 commit_id, f_path = self._get_commit_and_path()
1079 commit = self._get_commit_or_redirect(commit_id)
1079 commit = self._get_commit_or_redirect(commit_id)
1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1080 file_node = self._get_filenode_or_redirect(commit, f_path)
1081
1081
1082 if not file_node.is_file():
1082 if not file_node.is_file():
1083 raise HTTPBadRequest()
1083 raise HTTPBadRequest()
1084
1084
1085 c.file_last_commit = file_node.last_commit
1085 c.file_last_commit = file_node.last_commit
1086 if self.request.GET.get('annotate') == '1':
1086 if self.request.GET.get('annotate') == '1':
1087 # use _hist from annotation if annotation mode is on
1087 # use _hist from annotation if annotation mode is on
1088 commit_ids = {x[1] for x in file_node.annotate}
1088 commit_ids = {x[1] for x in file_node.annotate}
1089 _hist = (
1089 _hist = (
1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1090 self.rhodecode_vcs_repo.get_commit(commit_id)
1091 for commit_id in commit_ids)
1091 for commit_id in commit_ids)
1092 else:
1092 else:
1093 _f_history, _hist = self._get_node_history(commit, f_path)
1093 _f_history, _hist = self._get_node_history(commit, f_path)
1094 c.file_author = False
1094 c.file_author = False
1095
1095
1096 unique = collections.OrderedDict()
1096 unique = collections.OrderedDict()
1097 for commit in _hist:
1097 for commit in _hist:
1098 author = commit.author
1098 author = commit.author
1099 if author not in unique:
1099 if author not in unique:
1100 unique[commit.author] = [
1100 unique[commit.author] = [
1101 h.email(author),
1101 h.email(author),
1102 h.person(author, 'username_or_name_or_email'),
1102 h.person(author, 'username_or_name_or_email'),
1103 1 # counter
1103 1 # counter
1104 ]
1104 ]
1105
1105
1106 else:
1106 else:
1107 # increase counter
1107 # increase counter
1108 unique[commit.author][2] += 1
1108 unique[commit.author][2] += 1
1109
1109
1110 c.authors = [val for val in unique.values()]
1110 c.authors = [val for val in unique.values()]
1111
1111
1112 return self._get_template_context(c)
1112 return self._get_template_context(c)
1113
1113
1114 @LoginRequired()
1114 @LoginRequired()
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1116 def repo_files_check_head(self):
1116 def repo_files_check_head(self):
1117 self.load_default_context()
1117 self.load_default_context()
1118
1118
1119 commit_id, f_path = self._get_commit_and_path()
1119 commit_id, f_path = self._get_commit_and_path()
1120 _branch_name, _sha_commit_id, is_head = \
1120 _branch_name, _sha_commit_id, is_head = \
1121 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1121 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1122 landing_ref=self.db_repo.landing_ref_name)
1122 landing_ref=self.db_repo.landing_ref_name)
1123
1123
1124 new_path = self.request.POST.get('path')
1124 new_path = self.request.POST.get('path')
1125 operation = self.request.POST.get('operation')
1125 operation = self.request.POST.get('operation')
1126 path_exist = ''
1126 path_exist = ''
1127
1127
1128 if new_path and operation in ['create', 'upload']:
1128 if new_path and operation in ['create', 'upload']:
1129 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1129 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1130 try:
1130 try:
1131 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1131 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1132 # NOTE(dan): construct whole path without leading /
1132 # NOTE(dan): construct whole path without leading /
1133 file_node = commit_obj.get_node(new_f_path)
1133 file_node = commit_obj.get_node(new_f_path)
1134 if file_node is not None:
1134 if file_node is not None:
1135 path_exist = new_f_path
1135 path_exist = new_f_path
1136 except EmptyRepositoryError:
1136 except EmptyRepositoryError:
1137 pass
1137 pass
1138 except Exception:
1138 except Exception:
1139 pass
1139 pass
1140
1140
1141 return {
1141 return {
1142 'branch': _branch_name,
1142 'branch': _branch_name,
1143 'sha': _sha_commit_id,
1143 'sha': _sha_commit_id,
1144 'is_head': is_head,
1144 'is_head': is_head,
1145 'path_exists': path_exist
1145 'path_exists': path_exist
1146 }
1146 }
1147
1147
1148 @LoginRequired()
1148 @LoginRequired()
1149 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1149 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1150 def repo_files_remove_file(self):
1150 def repo_files_remove_file(self):
1151 _ = self.request.translate
1151 _ = self.request.translate
1152 c = self.load_default_context()
1152 c = self.load_default_context()
1153 commit_id, f_path = self._get_commit_and_path()
1153 commit_id, f_path = self._get_commit_and_path()
1154
1154
1155 self._ensure_not_locked()
1155 self._ensure_not_locked()
1156 _branch_name, _sha_commit_id, is_head = \
1156 _branch_name, _sha_commit_id, is_head = \
1157 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1157 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1158 landing_ref=self.db_repo.landing_ref_name)
1158 landing_ref=self.db_repo.landing_ref_name)
1159
1159
1160 self.forbid_non_head(is_head, f_path)
1160 self.forbid_non_head(is_head, f_path)
1161 self.check_branch_permission(_branch_name)
1161 self.check_branch_permission(_branch_name)
1162
1162
1163 c.commit = self._get_commit_or_redirect(commit_id)
1163 c.commit = self._get_commit_or_redirect(commit_id)
1164 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1164 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1165
1165
1166 c.default_message = _(
1166 c.default_message = _(
1167 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1167 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1168 c.f_path = f_path
1168 c.f_path = f_path
1169
1169
1170 return self._get_template_context(c)
1170 return self._get_template_context(c)
1171
1171
1172 @LoginRequired()
1172 @LoginRequired()
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1173 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1174 @CSRFRequired()
1174 @CSRFRequired()
1175 def repo_files_delete_file(self):
1175 def repo_files_delete_file(self):
1176 _ = self.request.translate
1176 _ = self.request.translate
1177
1177
1178 c = self.load_default_context()
1178 c = self.load_default_context()
1179 commit_id, f_path = self._get_commit_and_path()
1179 commit_id, f_path = self._get_commit_and_path()
1180
1180
1181 self._ensure_not_locked()
1181 self._ensure_not_locked()
1182 _branch_name, _sha_commit_id, is_head = \
1182 _branch_name, _sha_commit_id, is_head = \
1183 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1183 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1184 landing_ref=self.db_repo.landing_ref_name)
1184 landing_ref=self.db_repo.landing_ref_name)
1185
1185
1186 self.forbid_non_head(is_head, f_path)
1186 self.forbid_non_head(is_head, f_path)
1187 self.check_branch_permission(_branch_name)
1187 self.check_branch_permission(_branch_name)
1188
1188
1189 c.commit = self._get_commit_or_redirect(commit_id)
1189 c.commit = self._get_commit_or_redirect(commit_id)
1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1190 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1191
1191
1192 c.default_message = _(
1192 c.default_message = _(
1193 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1193 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1194 c.f_path = f_path
1194 c.f_path = f_path
1195 node_path = f_path
1195 node_path = f_path
1196 author = self._rhodecode_db_user.full_contact
1196 author = self._rhodecode_db_user.full_contact
1197 message = self.request.POST.get('message') or c.default_message
1197 message = self.request.POST.get('message') or c.default_message
1198 try:
1198 try:
1199 nodes = {
1199 nodes = {
1200 safe_bytes(node_path): {
1200 safe_bytes(node_path): {
1201 'content': b''
1201 'content': b''
1202 }
1202 }
1203 }
1203 }
1204 ScmModel().delete_nodes(
1204 ScmModel().delete_nodes(
1205 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1205 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1206 message=message,
1206 message=message,
1207 nodes=nodes,
1207 nodes=nodes,
1208 parent_commit=c.commit,
1208 parent_commit=c.commit,
1209 author=author,
1209 author=author,
1210 )
1210 )
1211
1211
1212 h.flash(
1212 h.flash(
1213 _('Successfully deleted file `{}`').format(
1213 _('Successfully deleted file `{}`').format(
1214 h.escape(f_path)), category='success')
1214 h.escape(f_path)), category='success')
1215 except Exception:
1215 except Exception:
1216 log.exception('Error during commit operation')
1216 log.exception('Error during commit operation')
1217 h.flash(_('Error occurred during commit'), category='error')
1217 h.flash(_('Error occurred during commit'), category='error')
1218 raise HTTPFound(
1218 raise HTTPFound(
1219 h.route_path('repo_commit', repo_name=self.db_repo_name,
1219 h.route_path('repo_commit', repo_name=self.db_repo_name,
1220 commit_id='tip'))
1220 commit_id='tip'))
1221
1221
1222 @LoginRequired()
1222 @LoginRequired()
1223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1223 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1224 def repo_files_edit_file(self):
1224 def repo_files_edit_file(self):
1225 _ = self.request.translate
1225 _ = self.request.translate
1226 c = self.load_default_context()
1226 c = self.load_default_context()
1227 commit_id, f_path = self._get_commit_and_path()
1227 commit_id, f_path = self._get_commit_and_path()
1228
1228
1229 self._ensure_not_locked()
1229 self._ensure_not_locked()
1230 _branch_name, _sha_commit_id, is_head = \
1230 _branch_name, _sha_commit_id, is_head = \
1231 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1231 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1232 landing_ref=self.db_repo.landing_ref_name)
1232 landing_ref=self.db_repo.landing_ref_name)
1233
1233
1234 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1234 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1236
1236
1237 c.commit = self._get_commit_or_redirect(commit_id)
1237 c.commit = self._get_commit_or_redirect(commit_id)
1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1238 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1239
1239
1240 if c.file.is_binary:
1240 if c.file.is_binary:
1241 files_url = h.route_path(
1241 files_url = h.route_path(
1242 'repo_files',
1242 'repo_files',
1243 repo_name=self.db_repo_name,
1243 repo_name=self.db_repo_name,
1244 commit_id=c.commit.raw_id, f_path=f_path)
1244 commit_id=c.commit.raw_id, f_path=f_path)
1245 raise HTTPFound(files_url)
1245 raise HTTPFound(files_url)
1246
1246
1247 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1247 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1248 c.f_path = f_path
1248 c.f_path = f_path
1249
1249
1250 return self._get_template_context(c)
1250 return self._get_template_context(c)
1251
1251
1252 @LoginRequired()
1252 @LoginRequired()
1253 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1253 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1254 @CSRFRequired()
1254 @CSRFRequired()
1255 def repo_files_update_file(self):
1255 def repo_files_update_file(self):
1256 _ = self.request.translate
1256 _ = self.request.translate
1257 c = self.load_default_context()
1257 c = self.load_default_context()
1258 commit_id, f_path = self._get_commit_and_path()
1258 commit_id, f_path = self._get_commit_and_path()
1259
1259
1260 self._ensure_not_locked()
1260 self._ensure_not_locked()
1261
1261
1262 c.commit = self._get_commit_or_redirect(commit_id)
1262 c.commit = self._get_commit_or_redirect(commit_id)
1263 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1263 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1264
1264
1265 if c.file.is_binary:
1265 if c.file.is_binary:
1266 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1266 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1267 commit_id=c.commit.raw_id, f_path=f_path))
1267 commit_id=c.commit.raw_id, f_path=f_path))
1268
1268
1269 _branch_name, _sha_commit_id, is_head = \
1269 _branch_name, _sha_commit_id, is_head = \
1270 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1270 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1271 landing_ref=self.db_repo.landing_ref_name)
1271 landing_ref=self.db_repo.landing_ref_name)
1272
1272
1273 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1273 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1274 self.check_branch_permission(_branch_name, commit_id=commit_id)
1274 self.check_branch_permission(_branch_name, commit_id=commit_id)
1275
1275
1276 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1276 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1277 c.f_path = f_path
1277 c.f_path = f_path
1278
1278
1279 old_content = c.file.str_content
1279 old_content = c.file.str_content
1280 sl = old_content.splitlines(1)
1280 sl = old_content.splitlines(1)
1281 first_line = sl[0] if sl else ''
1281 first_line = sl[0] if sl else ''
1282
1282
1283 r_post = self.request.POST
1283 r_post = self.request.POST
1284 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1284 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1285 line_ending_mode = detect_mode(first_line, 0)
1285 line_ending_mode = detect_mode(first_line, 0)
1286 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1286 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1287
1287
1288 message = r_post.get('message') or c.default_message
1288 message = r_post.get('message') or c.default_message
1289
1289
1290 org_node_path = c.file.str_path
1290 org_node_path = c.file.str_path
1291 filename = r_post['filename']
1291 filename = r_post['filename']
1292
1292
1293 root_path = c.file.dir_path
1293 root_path = c.file.dir_path
1294 pure_path = self.create_pure_path(root_path, filename)
1294 pure_path = self.create_pure_path(root_path, filename)
1295 node_path = pure_path.as_posix()
1295 node_path = pure_path.as_posix()
1296
1296
1297 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1297 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1298 commit_id=commit_id)
1298 commit_id=commit_id)
1299 if content == old_content and node_path == org_node_path:
1299 if content == old_content and node_path == org_node_path:
1300 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1300 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1301 category='warning')
1301 category='warning')
1302 raise HTTPFound(default_redirect_url)
1302 raise HTTPFound(default_redirect_url)
1303
1303
1304 try:
1304 try:
1305 mapping = {
1305 mapping = {
1306 c.file.bytes_path: {
1306 c.file.bytes_path: {
1307 'org_filename': org_node_path,
1307 'org_filename': org_node_path,
1308 'filename': safe_bytes(node_path),
1308 'filename': safe_bytes(node_path),
1309 'content': safe_bytes(content),
1309 'content': safe_bytes(content),
1310 'lexer': '',
1310 'lexer': '',
1311 'op': 'mod',
1311 'op': 'mod',
1312 'mode': c.file.mode
1312 'mode': c.file.mode
1313 }
1313 }
1314 }
1314 }
1315
1315
1316 commit = ScmModel().update_nodes(
1316 commit = ScmModel().update_nodes(
1317 user=self._rhodecode_db_user.user_id,
1317 user=self._rhodecode_db_user.user_id,
1318 repo=self.db_repo,
1318 repo=self.db_repo,
1319 message=message,
1319 message=message,
1320 nodes=mapping,
1320 nodes=mapping,
1321 parent_commit=c.commit,
1321 parent_commit=c.commit,
1322 )
1322 )
1323
1323
1324 h.flash(_('Successfully committed changes to file `{}`').format(
1324 h.flash(_('Successfully committed changes to file `{}`').format(
1325 h.escape(f_path)), category='success')
1325 h.escape(f_path)), category='success')
1326 default_redirect_url = h.route_path(
1326 default_redirect_url = h.route_path(
1327 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1327 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1328
1328
1329 except Exception:
1329 except Exception:
1330 log.exception('Error occurred during commit')
1330 log.exception('Error occurred during commit')
1331 h.flash(_('Error occurred during commit'), category='error')
1331 h.flash(_('Error occurred during commit'), category='error')
1332
1332
1333 raise HTTPFound(default_redirect_url)
1333 raise HTTPFound(default_redirect_url)
1334
1334
1335 @LoginRequired()
1335 @LoginRequired()
1336 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1336 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1337 def repo_files_add_file(self):
1337 def repo_files_add_file(self):
1338 _ = self.request.translate
1338 _ = self.request.translate
1339 c = self.load_default_context()
1339 c = self.load_default_context()
1340 commit_id, f_path = self._get_commit_and_path()
1340 commit_id, f_path = self._get_commit_and_path()
1341
1341
1342 self._ensure_not_locked()
1342 self._ensure_not_locked()
1343
1343
1344 # Check if we need to use this page to upload binary
1345 upload_binary = str2bool(self.request.params.get('upload_binary', False))
1346
1344 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1347 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1345 if c.commit is None:
1348 if c.commit is None:
1346 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1349 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1347
1350
1348 if self.rhodecode_vcs_repo.is_empty():
1351 if self.rhodecode_vcs_repo.is_empty():
1349 # for empty repository we cannot check for current branch, we rely on
1352 # for empty repository we cannot check for current branch, we rely on
1350 # c.commit.branch instead
1353 # c.commit.branch instead
1351 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1354 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1352 else:
1355 else:
1353 _branch_name, _sha_commit_id, is_head = \
1356 _branch_name, _sha_commit_id, is_head = \
1354 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1357 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1355 landing_ref=self.db_repo.landing_ref_name)
1358 landing_ref=self.db_repo.landing_ref_name)
1356
1359
1357 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1360 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1358 self.check_branch_permission(_branch_name, commit_id=commit_id)
1361 self.check_branch_permission(_branch_name, commit_id=commit_id)
1359
1362
1360 c.default_message = (_('Added file via RhodeCode Enterprise'))
1363 c.default_message = (_('Added file via RhodeCode Enterprise')) \
1364 if not upload_binary else (_('Edited file {} via RhodeCode Enterprise').format(f_path))
1361 c.f_path = f_path.lstrip('/') # ensure not relative path
1365 c.f_path = f_path.lstrip('/') # ensure not relative path
1366 c.replace_binary = upload_binary
1362
1367
1363 return self._get_template_context(c)
1368 return self._get_template_context(c)
1364
1369
1365 @LoginRequired()
1370 @LoginRequired()
1366 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1371 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1367 @CSRFRequired()
1372 @CSRFRequired()
1368 def repo_files_create_file(self):
1373 def repo_files_create_file(self):
1369 _ = self.request.translate
1374 _ = self.request.translate
1370 c = self.load_default_context()
1375 c = self.load_default_context()
1371 commit_id, f_path = self._get_commit_and_path()
1376 commit_id, f_path = self._get_commit_and_path()
1372
1377
1373 self._ensure_not_locked()
1378 self._ensure_not_locked()
1374
1379
1375 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1380 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1376 if c.commit is None:
1381 if c.commit is None:
1377 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1382 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1378
1383
1379 # calculate redirect URL
1384 # calculate redirect URL
1380 if self.rhodecode_vcs_repo.is_empty():
1385 if self.rhodecode_vcs_repo.is_empty():
1381 default_redirect_url = h.route_path(
1386 default_redirect_url = h.route_path(
1382 'repo_summary', repo_name=self.db_repo_name)
1387 'repo_summary', repo_name=self.db_repo_name)
1383 else:
1388 else:
1384 default_redirect_url = h.route_path(
1389 default_redirect_url = h.route_path(
1385 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1390 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1386
1391
1387 if self.rhodecode_vcs_repo.is_empty():
1392 if self.rhodecode_vcs_repo.is_empty():
1388 # for empty repository we cannot check for current branch, we rely on
1393 # for empty repository we cannot check for current branch, we rely on
1389 # c.commit.branch instead
1394 # c.commit.branch instead
1390 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1395 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1391 else:
1396 else:
1392 _branch_name, _sha_commit_id, is_head = \
1397 _branch_name, _sha_commit_id, is_head = \
1393 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1398 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1394 landing_ref=self.db_repo.landing_ref_name)
1399 landing_ref=self.db_repo.landing_ref_name)
1395
1400
1396 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1401 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1397 self.check_branch_permission(_branch_name, commit_id=commit_id)
1402 self.check_branch_permission(_branch_name, commit_id=commit_id)
1398
1403
1399 c.default_message = (_('Added file via RhodeCode Enterprise'))
1404 c.default_message = (_('Added file via RhodeCode Enterprise'))
1400 c.f_path = f_path
1405 c.f_path = f_path
1401
1406
1402 r_post = self.request.POST
1407 r_post = self.request.POST
1403 message = r_post.get('message') or c.default_message
1408 message = r_post.get('message') or c.default_message
1404 filename = r_post.get('filename')
1409 filename = r_post.get('filename')
1405 unix_mode = 0
1410 unix_mode = 0
1406
1411
1407 if not filename:
1412 if not filename:
1408 # If there's no commit, redirect to repo summary
1413 # If there's no commit, redirect to repo summary
1409 if type(c.commit) is EmptyCommit:
1414 if type(c.commit) is EmptyCommit:
1410 redirect_url = h.route_path(
1415 redirect_url = h.route_path(
1411 'repo_summary', repo_name=self.db_repo_name)
1416 'repo_summary', repo_name=self.db_repo_name)
1412 else:
1417 else:
1413 redirect_url = default_redirect_url
1418 redirect_url = default_redirect_url
1414 h.flash(_('No filename specified'), category='warning')
1419 h.flash(_('No filename specified'), category='warning')
1415 raise HTTPFound(redirect_url)
1420 raise HTTPFound(redirect_url)
1416
1421
1417 root_path = f_path
1422 root_path = f_path
1418 pure_path = self.create_pure_path(root_path, filename)
1423 pure_path = self.create_pure_path(root_path, filename)
1419 node_path = pure_path.as_posix().lstrip('/')
1424 node_path = pure_path.as_posix().lstrip('/')
1420
1425
1421 author = self._rhodecode_db_user.full_contact
1426 author = self._rhodecode_db_user.full_contact
1422 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1427 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1423 nodes = {
1428 nodes = {
1424 safe_bytes(node_path): {
1429 safe_bytes(node_path): {
1425 'content': safe_bytes(content)
1430 'content': safe_bytes(content)
1426 }
1431 }
1427 }
1432 }
1428
1433
1429 try:
1434 try:
1430
1435
1431 commit = ScmModel().create_nodes(
1436 commit = ScmModel().create_nodes(
1432 user=self._rhodecode_db_user.user_id,
1437 user=self._rhodecode_db_user.user_id,
1433 repo=self.db_repo,
1438 repo=self.db_repo,
1434 message=message,
1439 message=message,
1435 nodes=nodes,
1440 nodes=nodes,
1436 parent_commit=c.commit,
1441 parent_commit=c.commit,
1437 author=author,
1442 author=author,
1438 )
1443 )
1439
1444
1440 h.flash(_('Successfully committed new file `{}`').format(
1445 h.flash(_('Successfully committed new file `{}`').format(
1441 h.escape(node_path)), category='success')
1446 h.escape(node_path)), category='success')
1442
1447
1443 default_redirect_url = h.route_path(
1448 default_redirect_url = h.route_path(
1444 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1449 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1445
1450
1446 except NonRelativePathError:
1451 except NonRelativePathError:
1447 log.exception('Non Relative path found')
1452 log.exception('Non Relative path found')
1448 h.flash(_('The location specified must be a relative path and must not '
1453 h.flash(_('The location specified must be a relative path and must not '
1449 'contain .. in the path'), category='warning')
1454 'contain .. in the path'), category='warning')
1450 raise HTTPFound(default_redirect_url)
1455 raise HTTPFound(default_redirect_url)
1451 except (NodeError, NodeAlreadyExistsError) as e:
1456 except (NodeError, NodeAlreadyExistsError) as e:
1452 h.flash(h.escape(safe_str(e)), category='error')
1457 h.flash(h.escape(safe_str(e)), category='error')
1453 except Exception:
1458 except Exception:
1454 log.exception('Error occurred during commit')
1459 log.exception('Error occurred during commit')
1455 h.flash(_('Error occurred during commit'), category='error')
1460 h.flash(_('Error occurred during commit'), category='error')
1456
1461
1457 raise HTTPFound(default_redirect_url)
1462 raise HTTPFound(default_redirect_url)
1458
1463
1459 @LoginRequired()
1464 @LoginRequired()
1460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1465 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1461 @CSRFRequired()
1466 @CSRFRequired()
1462 def repo_files_upload_file(self):
1467 def repo_files_upload_file(self):
1463 _ = self.request.translate
1468 _ = self.request.translate
1464 c = self.load_default_context()
1469 c = self.load_default_context()
1465 commit_id, f_path = self._get_commit_and_path()
1470 commit_id, f_path = self._get_commit_and_path()
1466
1471
1467 self._ensure_not_locked()
1472 self._ensure_not_locked()
1468
1473
1469 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1474 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1470 if c.commit is None:
1475 if c.commit is None:
1471 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1476 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1472
1477
1473 # calculate redirect URL
1478 # calculate redirect URL
1474 if self.rhodecode_vcs_repo.is_empty():
1479 if self.rhodecode_vcs_repo.is_empty():
1475 default_redirect_url = h.route_path(
1480 default_redirect_url = h.route_path(
1476 'repo_summary', repo_name=self.db_repo_name)
1481 'repo_summary', repo_name=self.db_repo_name)
1477 else:
1482 else:
1478 default_redirect_url = h.route_path(
1483 default_redirect_url = h.route_path(
1479 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1484 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1480
1485
1481 if self.rhodecode_vcs_repo.is_empty():
1486 if self.rhodecode_vcs_repo.is_empty():
1482 # for empty repository we cannot check for current branch, we rely on
1487 # for empty repository we cannot check for current branch, we rely on
1483 # c.commit.branch instead
1488 # c.commit.branch instead
1484 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1489 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1485 else:
1490 else:
1486 _branch_name, _sha_commit_id, is_head = \
1491 _branch_name, _sha_commit_id, is_head = \
1487 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1492 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1488 landing_ref=self.db_repo.landing_ref_name)
1493 landing_ref=self.db_repo.landing_ref_name)
1489
1494
1490 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1495 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1491 if error:
1496 if error:
1492 return {
1497 return {
1493 'error': error,
1498 'error': error,
1494 'redirect_url': default_redirect_url
1499 'redirect_url': default_redirect_url
1495 }
1500 }
1496 error = self.check_branch_permission(_branch_name, json_mode=True)
1501 error = self.check_branch_permission(_branch_name, json_mode=True)
1497 if error:
1502 if error:
1498 return {
1503 return {
1499 'error': error,
1504 'error': error,
1500 'redirect_url': default_redirect_url
1505 'redirect_url': default_redirect_url
1501 }
1506 }
1502
1507
1503 c.default_message = (_('Added file via RhodeCode Enterprise'))
1508 c.default_message = (_('Added file via RhodeCode Enterprise'))
1504 c.f_path = f_path
1509 c.f_path = f_path
1505
1510
1506 r_post = self.request.POST
1511 r_post = self.request.POST
1507
1512
1508 message = c.default_message
1513 message = c.default_message
1509 user_message = r_post.getall('message')
1514 user_message = r_post.getall('message')
1510 if isinstance(user_message, list) and user_message:
1515 if isinstance(user_message, list) and user_message:
1511 # we take the first from duplicated results if it's not empty
1516 # we take the first from duplicated results if it's not empty
1512 message = user_message[0] if user_message[0] else message
1517 message = user_message[0] if user_message[0] else message
1513
1518
1514 nodes = {}
1519 nodes = {}
1515
1520
1516 for file_obj in r_post.getall('files_upload') or []:
1521 for file_obj in r_post.getall('files_upload') or []:
1517 content = file_obj.file
1522 content = file_obj.file
1518 filename = file_obj.filename
1523 filename = file_obj.filename
1519
1524
1520 root_path = f_path
1525 root_path = f_path
1521 pure_path = self.create_pure_path(root_path, filename)
1526 pure_path = self.create_pure_path(root_path, filename)
1522 node_path = pure_path.as_posix().lstrip('/')
1527 node_path = pure_path.as_posix().lstrip('/')
1523
1528
1524 nodes[safe_bytes(node_path)] = {
1529 nodes[safe_bytes(node_path)] = {
1525 'content': content
1530 'content': content
1526 }
1531 }
1527
1532
1528 if not nodes:
1533 if not nodes:
1529 error = 'missing files'
1534 error = 'missing files'
1530 return {
1535 return {
1531 'error': error,
1536 'error': error,
1532 'redirect_url': default_redirect_url
1537 'redirect_url': default_redirect_url
1533 }
1538 }
1534
1539
1535 author = self._rhodecode_db_user.full_contact
1540 author = self._rhodecode_db_user.full_contact
1536
1541
1537 try:
1542 try:
1538 commit = ScmModel().create_nodes(
1543 commit = ScmModel().create_nodes(
1539 user=self._rhodecode_db_user.user_id,
1544 user=self._rhodecode_db_user.user_id,
1540 repo=self.db_repo,
1545 repo=self.db_repo,
1541 message=message,
1546 message=message,
1542 nodes=nodes,
1547 nodes=nodes,
1543 parent_commit=c.commit,
1548 parent_commit=c.commit,
1544 author=author,
1549 author=author,
1545 )
1550 )
1546 if len(nodes) == 1:
1551 if len(nodes) == 1:
1547 flash_message = _('Successfully committed {} new files').format(len(nodes))
1552 flash_message = _('Successfully committed {} new files').format(len(nodes))
1548 else:
1553 else:
1549 flash_message = _('Successfully committed 1 new file')
1554 flash_message = _('Successfully committed 1 new file')
1550
1555
1551 h.flash(flash_message, category='success')
1556 h.flash(flash_message, category='success')
1552
1557
1553 default_redirect_url = h.route_path(
1558 default_redirect_url = h.route_path(
1554 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1559 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1555
1560
1556 except NonRelativePathError:
1561 except NonRelativePathError:
1557 log.exception('Non Relative path found')
1562 log.exception('Non Relative path found')
1558 error = _('The location specified must be a relative path and must not '
1563 error = _('The location specified must be a relative path and must not '
1559 'contain .. in the path')
1564 'contain .. in the path')
1560 h.flash(error, category='warning')
1565 h.flash(error, category='warning')
1561
1566
1562 return {
1567 return {
1563 'error': error,
1568 'error': error,
1564 'redirect_url': default_redirect_url
1569 'redirect_url': default_redirect_url
1565 }
1570 }
1566 except (NodeError, NodeAlreadyExistsError) as e:
1571 except (NodeError, NodeAlreadyExistsError) as e:
1567 error = h.escape(e)
1572 error = h.escape(e)
1568 h.flash(error, category='error')
1573 h.flash(error, category='error')
1569
1574
1570 return {
1575 return {
1571 'error': error,
1576 'error': error,
1572 'redirect_url': default_redirect_url
1577 'redirect_url': default_redirect_url
1573 }
1578 }
1574 except Exception:
1579 except Exception:
1575 log.exception('Error occurred during commit')
1580 log.exception('Error occurred during commit')
1576 error = _('Error occurred during commit')
1581 error = _('Error occurred during commit')
1577 h.flash(error, category='error')
1582 h.flash(error, category='error')
1578 return {
1583 return {
1579 'error': error,
1584 'error': error,
1580 'redirect_url': default_redirect_url
1585 'redirect_url': default_redirect_url
1581 }
1586 }
1582
1587
1583 return {
1588 return {
1584 'error': None,
1589 'error': None,
1585 'redirect_url': default_redirect_url
1590 'redirect_url': default_redirect_url
1586 }
1591 }
1592
1593 @LoginRequired()
1594 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1595 @CSRFRequired()
1596 def repo_files_replace_file(self):
1597 _ = self.request.translate
1598 c = self.load_default_context()
1599 commit_id, f_path = self._get_commit_and_path()
1600
1601 self._ensure_not_locked()
1602
1603 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1604 if c.commit is None:
1605 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1606
1607 if self.rhodecode_vcs_repo.is_empty():
1608 default_redirect_url = h.route_path(
1609 'repo_summary', repo_name=self.db_repo_name)
1610 else:
1611 default_redirect_url = h.route_path(
1612 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1613
1614 if self.rhodecode_vcs_repo.is_empty():
1615 # for empty repository we cannot check for current branch, we rely on
1616 # c.commit.branch instead
1617 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1618 else:
1619 _branch_name, _sha_commit_id, is_head = \
1620 self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
1621 landing_ref=self.db_repo.landing_ref_name)
1622
1623 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1624 if error:
1625 return {
1626 'error': error,
1627 'redirect_url': default_redirect_url
1628 }
1629 error = self.check_branch_permission(_branch_name, json_mode=True)
1630 if error:
1631 return {
1632 'error': error,
1633 'redirect_url': default_redirect_url
1634 }
1635
1636 c.default_message = (_('Edited file {} via RhodeCode Enterprise').format(f_path))
1637 c.f_path = f_path
1638
1639 r_post = self.request.POST
1640
1641 message = c.default_message
1642 user_message = r_post.getall('message')
1643 if isinstance(user_message, list) and user_message:
1644 # we take the first from duplicated results if it's not empty
1645 message = user_message[0] if user_message[0] else message
1646
1647 data_for_replacement = r_post.getall('files_upload') or []
1648 if (objects_count := len(data_for_replacement)) > 1:
1649 return {
1650 'error': 'too many files for replacement',
1651 'redirect_url': default_redirect_url
1652 }
1653 elif not objects_count:
1654 return {
1655 'error': 'missing files',
1656 'redirect_url': default_redirect_url
1657 }
1658
1659 content = data_for_replacement[0].file
1660 retrieved_filename = data_for_replacement[0].filename
1661
1662 if retrieved_filename.split('.')[-1] != f_path.split('.')[-1]:
1663 return {
1664 'error': 'file extension of uploaded file doesn\'t match an original file\'s extension',
1665 'redirect_url': default_redirect_url
1666 }
1667
1668 author = self._rhodecode_db_user.full_contact
1669
1670 try:
1671 commit = ScmModel().update_binary_node(
1672 user=self._rhodecode_db_user.user_id,
1673 repo=self.db_repo,
1674 message=message,
1675 node={
1676 'content': content,
1677 'file_path': f_path.encode(),
1678 },
1679 parent_commit=c.commit,
1680 author=author,
1681 )
1682
1683 h.flash(_('Successfully committed 1 new file'), category='success')
1684
1685 default_redirect_url = h.route_path(
1686 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1687
1688 except (NodeError, NodeAlreadyExistsError) as e:
1689 error = h.escape(e)
1690 h.flash(error, category='error')
1691
1692 return {
1693 'error': error,
1694 'redirect_url': default_redirect_url
1695 }
1696 except Exception:
1697 log.exception('Error occurred during commit')
1698 error = _('Error occurred during commit')
1699 h.flash(error, category='error')
1700 return {
1701 'error': error,
1702 'redirect_url': default_redirect_url
1703 }
1704
1705 return {
1706 'error': None,
1707 'redirect_url': default_redirect_url
1708 }
@@ -1,1044 +1,1063 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Scm model for RhodeCode
20 Scm model for RhodeCode
21 """
21 """
22
22
23 import os.path
23 import os.path
24 import traceback
24 import traceback
25 import logging
25 import logging
26 import io
26 import io
27
27
28 from sqlalchemy import func
28 from sqlalchemy import func
29 from zope.cachedescriptors.property import Lazy as LazyProperty
29 from zope.cachedescriptors.property import Lazy as LazyProperty
30
30
31 import rhodecode
31 import rhodecode
32 from rhodecode.lib.str_utils import safe_bytes
32 from rhodecode.lib.str_utils import safe_bytes
33 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
34 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
35 from rhodecode.lib.vcs.nodes import FileNode
35 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib import helpers as h, rc_cache
37 from rhodecode.lib import helpers as h, rc_cache
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 HasRepoPermissionAny, HasRepoGroupPermissionAny,
39 HasRepoPermissionAny, HasRepoGroupPermissionAny,
40 HasUserGroupPermissionAny)
40 HasUserGroupPermissionAny)
41 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
41 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
42 from rhodecode.lib import hooks_utils
42 from rhodecode.lib import hooks_utils
43 from rhodecode.lib.utils import (
43 from rhodecode.lib.utils import (
44 get_filesystem_repos, make_db_config)
44 get_filesystem_repos, make_db_config)
45 from rhodecode.lib.str_utils import safe_str
45 from rhodecode.lib.str_utils import safe_str
46 from rhodecode.lib.system_info import get_system_info
46 from rhodecode.lib.system_info import get_system_info
47 from rhodecode.model import BaseModel
47 from rhodecode.model import BaseModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 or_, false, null,
49 or_, false, null,
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 PullRequest, FileStore)
51 PullRequest, FileStore)
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<{}('id:{}')>".format(self.__class__.__name__, self.user_id)
63 return "<{}('id:{}')>".format(self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<{}('id:{}')>".format(self.__class__.__name__, self.repo_id)
71 return "<{}('id:{}')>".format(self.__class__.__name__, self.repo_id)
72
72
73
73
74 class SimpleCachedRepoList(object):
74 class SimpleCachedRepoList(object):
75 """
75 """
76 Lighter version of of iteration of repos without the scm initialisation,
76 Lighter version of of iteration of repos without the scm initialisation,
77 and with cache usage
77 and with cache usage
78 """
78 """
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 self.db_repo_list = db_repo_list
80 self.db_repo_list = db_repo_list
81 self.repos_path = repos_path
81 self.repos_path = repos_path
82 self.order_by = order_by
82 self.order_by = order_by
83 self.reversed = (order_by or '').startswith('-')
83 self.reversed = (order_by or '').startswith('-')
84 if not perm_set:
84 if not perm_set:
85 perm_set = ['repository.read', 'repository.write',
85 perm_set = ['repository.read', 'repository.write',
86 'repository.admin']
86 'repository.admin']
87 self.perm_set = perm_set
87 self.perm_set = perm_set
88
88
89 def __len__(self):
89 def __len__(self):
90 return len(self.db_repo_list)
90 return len(self.db_repo_list)
91
91
92 def __repr__(self):
92 def __repr__(self):
93 return '<{} ({})>'.format(self.__class__.__name__, self.__len__())
93 return '<{} ({})>'.format(self.__class__.__name__, self.__len__())
94
94
95 def __iter__(self):
95 def __iter__(self):
96 for dbr in self.db_repo_list:
96 for dbr in self.db_repo_list:
97 # check permission at this level
97 # check permission at this level
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 dbr.repo_name, 'SimpleCachedRepoList check')
99 dbr.repo_name, 'SimpleCachedRepoList check')
100 if not has_perm:
100 if not has_perm:
101 continue
101 continue
102
102
103 tmp_d = {
103 tmp_d = {
104 'name': dbr.repo_name,
104 'name': dbr.repo_name,
105 'dbrepo': dbr.get_dict(),
105 'dbrepo': dbr.get_dict(),
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 }
107 }
108 yield tmp_d
108 yield tmp_d
109
109
110
110
111 class _PermCheckIterator(object):
111 class _PermCheckIterator(object):
112
112
113 def __init__(
113 def __init__(
114 self, obj_list, obj_attr, perm_set, perm_checker,
114 self, obj_list, obj_attr, perm_set, perm_checker,
115 extra_kwargs=None):
115 extra_kwargs=None):
116 """
116 """
117 Creates iterator from given list of objects, additionally
117 Creates iterator from given list of objects, additionally
118 checking permission for them from perm_set var
118 checking permission for them from perm_set var
119
119
120 :param obj_list: list of db objects
120 :param obj_list: list of db objects
121 :param obj_attr: attribute of object to pass into perm_checker
121 :param obj_attr: attribute of object to pass into perm_checker
122 :param perm_set: list of permissions to check
122 :param perm_set: list of permissions to check
123 :param perm_checker: callable to check permissions against
123 :param perm_checker: callable to check permissions against
124 """
124 """
125 self.obj_list = obj_list
125 self.obj_list = obj_list
126 self.obj_attr = obj_attr
126 self.obj_attr = obj_attr
127 self.perm_set = perm_set
127 self.perm_set = perm_set
128 self.perm_checker = perm_checker(*self.perm_set)
128 self.perm_checker = perm_checker(*self.perm_set)
129 self.extra_kwargs = extra_kwargs or {}
129 self.extra_kwargs = extra_kwargs or {}
130
130
131 def __len__(self):
131 def __len__(self):
132 return len(self.obj_list)
132 return len(self.obj_list)
133
133
134 def __repr__(self):
134 def __repr__(self):
135 return '<{} ({})>'.format(self.__class__.__name__, self.__len__())
135 return '<{} ({})>'.format(self.__class__.__name__, self.__len__())
136
136
137 def __iter__(self):
137 def __iter__(self):
138 for db_obj in self.obj_list:
138 for db_obj in self.obj_list:
139 # check permission at this level
139 # check permission at this level
140 # NOTE(marcink): the __dict__.get() is ~4x faster then getattr()
140 # NOTE(marcink): the __dict__.get() is ~4x faster then getattr()
141 name = db_obj.__dict__.get(self.obj_attr, None)
141 name = db_obj.__dict__.get(self.obj_attr, None)
142 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
142 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
143 continue
143 continue
144
144
145 yield db_obj
145 yield db_obj
146
146
147
147
148 class RepoList(_PermCheckIterator):
148 class RepoList(_PermCheckIterator):
149
149
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 if not perm_set:
151 if not perm_set:
152 perm_set = ['repository.read', 'repository.write', 'repository.admin']
152 perm_set = ['repository.read', 'repository.write', 'repository.admin']
153
153
154 super().__init__(
154 super().__init__(
155 obj_list=db_repo_list,
155 obj_list=db_repo_list,
156 obj_attr='_repo_name', perm_set=perm_set,
156 obj_attr='_repo_name', perm_set=perm_set,
157 perm_checker=HasRepoPermissionAny,
157 perm_checker=HasRepoPermissionAny,
158 extra_kwargs=extra_kwargs)
158 extra_kwargs=extra_kwargs)
159
159
160
160
161 class RepoGroupList(_PermCheckIterator):
161 class RepoGroupList(_PermCheckIterator):
162
162
163 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
163 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
164 if not perm_set:
164 if not perm_set:
165 perm_set = ['group.read', 'group.write', 'group.admin']
165 perm_set = ['group.read', 'group.write', 'group.admin']
166
166
167 super().__init__(
167 super().__init__(
168 obj_list=db_repo_group_list,
168 obj_list=db_repo_group_list,
169 obj_attr='_group_name', perm_set=perm_set,
169 obj_attr='_group_name', perm_set=perm_set,
170 perm_checker=HasRepoGroupPermissionAny,
170 perm_checker=HasRepoGroupPermissionAny,
171 extra_kwargs=extra_kwargs)
171 extra_kwargs=extra_kwargs)
172
172
173
173
174 class UserGroupList(_PermCheckIterator):
174 class UserGroupList(_PermCheckIterator):
175
175
176 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
176 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
177 if not perm_set:
177 if not perm_set:
178 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
178 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
179
179
180 super().__init__(
180 super().__init__(
181 obj_list=db_user_group_list,
181 obj_list=db_user_group_list,
182 obj_attr='users_group_name', perm_set=perm_set,
182 obj_attr='users_group_name', perm_set=perm_set,
183 perm_checker=HasUserGroupPermissionAny,
183 perm_checker=HasUserGroupPermissionAny,
184 extra_kwargs=extra_kwargs)
184 extra_kwargs=extra_kwargs)
185
185
186
186
187 class ScmModel(BaseModel):
187 class ScmModel(BaseModel):
188 """
188 """
189 Generic Scm Model
189 Generic Scm Model
190 """
190 """
191
191
192 @LazyProperty
192 @LazyProperty
193 def repos_path(self):
193 def repos_path(self):
194 """
194 """
195 Gets the repositories root path from database
195 Gets the repositories root path from database
196 """
196 """
197
197
198 settings_model = VcsSettingsModel(sa=self.sa)
198 settings_model = VcsSettingsModel(sa=self.sa)
199 return settings_model.get_repos_location()
199 return settings_model.get_repos_location()
200
200
201 def repo_scan(self, repos_path=None):
201 def repo_scan(self, repos_path=None):
202 """
202 """
203 Listing of repositories in given path. This path should not be a
203 Listing of repositories in given path. This path should not be a
204 repository itself. Return a dictionary of repository objects
204 repository itself. Return a dictionary of repository objects
205
205
206 :param repos_path: path to directory containing repositories
206 :param repos_path: path to directory containing repositories
207 """
207 """
208
208
209 if repos_path is None:
209 if repos_path is None:
210 repos_path = self.repos_path
210 repos_path = self.repos_path
211
211
212 log.info('scanning for repositories in %s', repos_path)
212 log.info('scanning for repositories in %s', repos_path)
213
213
214 config = make_db_config()
214 config = make_db_config()
215 config.set('extensions', 'largefiles', '')
215 config.set('extensions', 'largefiles', '')
216 repos = {}
216 repos = {}
217
217
218 for name, path in get_filesystem_repos(repos_path, recursive=True):
218 for name, path in get_filesystem_repos(repos_path, recursive=True):
219 # name need to be decomposed and put back together using the /
219 # name need to be decomposed and put back together using the /
220 # since this is internal storage separator for rhodecode
220 # since this is internal storage separator for rhodecode
221 name = Repository.normalize_repo_name(name)
221 name = Repository.normalize_repo_name(name)
222
222
223 try:
223 try:
224 if name in repos:
224 if name in repos:
225 raise RepositoryError('Duplicate repository name %s '
225 raise RepositoryError('Duplicate repository name %s '
226 'found in %s' % (name, path))
226 'found in %s' % (name, path))
227 elif path[0] in rhodecode.BACKENDS:
227 elif path[0] in rhodecode.BACKENDS:
228 backend = get_backend(path[0])
228 backend = get_backend(path[0])
229 repos[name] = backend(path[1], config=config,
229 repos[name] = backend(path[1], config=config,
230 with_wire={"cache": False})
230 with_wire={"cache": False})
231 except OSError:
231 except OSError:
232 continue
232 continue
233 except RepositoryError:
233 except RepositoryError:
234 log.exception('Failed to create a repo')
234 log.exception('Failed to create a repo')
235 continue
235 continue
236
236
237 log.debug('found %s paths with repositories', len(repos))
237 log.debug('found %s paths with repositories', len(repos))
238 return repos
238 return repos
239
239
240 def get_repos(self, all_repos=None, sort_key=None):
240 def get_repos(self, all_repos=None, sort_key=None):
241 """
241 """
242 Get all repositories from db and for each repo create it's
242 Get all repositories from db and for each repo create it's
243 backend instance and fill that backed with information from database
243 backend instance and fill that backed with information from database
244
244
245 :param all_repos: list of repository names as strings
245 :param all_repos: list of repository names as strings
246 give specific repositories list, good for filtering
246 give specific repositories list, good for filtering
247
247
248 :param sort_key: initial sorting of repositories
248 :param sort_key: initial sorting of repositories
249 """
249 """
250 if all_repos is None:
250 if all_repos is None:
251 all_repos = self.sa.query(Repository)\
251 all_repos = self.sa.query(Repository)\
252 .filter(Repository.group_id == null())\
252 .filter(Repository.group_id == null())\
253 .order_by(func.lower(Repository.repo_name)).all()
253 .order_by(func.lower(Repository.repo_name)).all()
254 repo_iter = SimpleCachedRepoList(
254 repo_iter = SimpleCachedRepoList(
255 all_repos, repos_path=self.repos_path, order_by=sort_key)
255 all_repos, repos_path=self.repos_path, order_by=sort_key)
256 return repo_iter
256 return repo_iter
257
257
258 @staticmethod
259 def get_parent_commits(parent_commit, scm_instance):
260 if not parent_commit:
261 parent_commit = EmptyCommit(alias=scm_instance.alias)
262
263 if isinstance(parent_commit, EmptyCommit):
264 # EmptyCommit means we're editing empty repository
265 parents = None
266 else:
267 parents = [parent_commit]
268 return parent_commit, parents
269
270 def initialize_inmemory_vars(self, user, repo, message, author):
271 """
272 Initialize node specific objects for further usage
273 """
274 user = self._get_user(user)
275 scm_instance = repo.scm_instance(cache=False)
276 message = safe_str(message)
277 commiter = user.full_contact
278 author = safe_str(author) if author else commiter
279 imc = scm_instance.in_memory_commit
280
281 return user, scm_instance, message, commiter, author, imc
282
258 def get_repo_groups(self, all_groups=None):
283 def get_repo_groups(self, all_groups=None):
259 if all_groups is None:
284 if all_groups is None:
260 all_groups = RepoGroup.query()\
285 all_groups = RepoGroup.query()\
261 .filter(RepoGroup.group_parent_id == null()).all()
286 .filter(RepoGroup.group_parent_id == null()).all()
262 return [x for x in RepoGroupList(all_groups)]
287 return [x for x in RepoGroupList(all_groups)]
263
288
264 def mark_for_invalidation(self, repo_name, delete=False):
289 def mark_for_invalidation(self, repo_name, delete=False):
265 """
290 """
266 Mark caches of this repo invalid in the database. `delete` flag
291 Mark caches of this repo invalid in the database. `delete` flag
267 removes the cache entries
292 removes the cache entries
268
293
269 :param repo_name: the repo_name for which caches should be marked
294 :param repo_name: the repo_name for which caches should be marked
270 invalid, or deleted
295 invalid, or deleted
271 :param delete: delete the entry keys instead of setting bool
296 :param delete: delete the entry keys instead of setting bool
272 flag on them, and also purge caches used by the dogpile
297 flag on them, and also purge caches used by the dogpile
273 """
298 """
274 repo = Repository.get_by_repo_name(repo_name)
299 repo = Repository.get_by_repo_name(repo_name)
275
300
276 if repo:
301 if repo:
277 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
302 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
278 repo_id=repo.repo_id)
303 repo_id=repo.repo_id)
279 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
304 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
280
305
281 repo_id = repo.repo_id
306 repo_id = repo.repo_id
282 config = repo._config
307 config = repo._config
283 config.set('extensions', 'largefiles', '')
308 config.set('extensions', 'largefiles', '')
284 repo.update_commit_cache(config=config, cs_cache=None)
309 repo.update_commit_cache(config=config, cs_cache=None)
285 if delete:
310 if delete:
286 cache_namespace_uid = f'cache_repo.{repo_id}'
311 cache_namespace_uid = f'cache_repo.{repo_id}'
287 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid, method=rc_cache.CLEAR_INVALIDATE)
312 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid, method=rc_cache.CLEAR_INVALIDATE)
288
313
289 def toggle_following_repo(self, follow_repo_id, user_id):
314 def toggle_following_repo(self, follow_repo_id, user_id):
290
315
291 f = self.sa.query(UserFollowing)\
316 f = self.sa.query(UserFollowing)\
292 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
317 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
293 .filter(UserFollowing.user_id == user_id).scalar()
318 .filter(UserFollowing.user_id == user_id).scalar()
294
319
295 if f is not None:
320 if f is not None:
296 try:
321 try:
297 self.sa.delete(f)
322 self.sa.delete(f)
298 return
323 return
299 except Exception:
324 except Exception:
300 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
301 raise
326 raise
302
327
303 try:
328 try:
304 f = UserFollowing()
329 f = UserFollowing()
305 f.user_id = user_id
330 f.user_id = user_id
306 f.follows_repo_id = follow_repo_id
331 f.follows_repo_id = follow_repo_id
307 self.sa.add(f)
332 self.sa.add(f)
308 except Exception:
333 except Exception:
309 log.error(traceback.format_exc())
334 log.error(traceback.format_exc())
310 raise
335 raise
311
336
312 def toggle_following_user(self, follow_user_id, user_id):
337 def toggle_following_user(self, follow_user_id, user_id):
313 f = self.sa.query(UserFollowing)\
338 f = self.sa.query(UserFollowing)\
314 .filter(UserFollowing.follows_user_id == follow_user_id)\
339 .filter(UserFollowing.follows_user_id == follow_user_id)\
315 .filter(UserFollowing.user_id == user_id).scalar()
340 .filter(UserFollowing.user_id == user_id).scalar()
316
341
317 if f is not None:
342 if f is not None:
318 try:
343 try:
319 self.sa.delete(f)
344 self.sa.delete(f)
320 return
345 return
321 except Exception:
346 except Exception:
322 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
323 raise
348 raise
324
349
325 try:
350 try:
326 f = UserFollowing()
351 f = UserFollowing()
327 f.user_id = user_id
352 f.user_id = user_id
328 f.follows_user_id = follow_user_id
353 f.follows_user_id = follow_user_id
329 self.sa.add(f)
354 self.sa.add(f)
330 except Exception:
355 except Exception:
331 log.error(traceback.format_exc())
356 log.error(traceback.format_exc())
332 raise
357 raise
333
358
334 def is_following_repo(self, repo_name, user_id, cache=False):
359 def is_following_repo(self, repo_name, user_id, cache=False):
335 r = self.sa.query(Repository)\
360 r = self.sa.query(Repository)\
336 .filter(Repository.repo_name == repo_name).scalar()
361 .filter(Repository.repo_name == repo_name).scalar()
337
362
338 f = self.sa.query(UserFollowing)\
363 f = self.sa.query(UserFollowing)\
339 .filter(UserFollowing.follows_repository == r)\
364 .filter(UserFollowing.follows_repository == r)\
340 .filter(UserFollowing.user_id == user_id).scalar()
365 .filter(UserFollowing.user_id == user_id).scalar()
341
366
342 return f is not None
367 return f is not None
343
368
344 def is_following_user(self, username, user_id, cache=False):
369 def is_following_user(self, username, user_id, cache=False):
345 u = User.get_by_username(username)
370 u = User.get_by_username(username)
346
371
347 f = self.sa.query(UserFollowing)\
372 f = self.sa.query(UserFollowing)\
348 .filter(UserFollowing.follows_user == u)\
373 .filter(UserFollowing.follows_user == u)\
349 .filter(UserFollowing.user_id == user_id).scalar()
374 .filter(UserFollowing.user_id == user_id).scalar()
350
375
351 return f is not None
376 return f is not None
352
377
353 def get_followers(self, repo):
378 def get_followers(self, repo):
354 repo = self._get_repo(repo)
379 repo = self._get_repo(repo)
355
380
356 return self.sa.query(UserFollowing)\
381 return self.sa.query(UserFollowing)\
357 .filter(UserFollowing.follows_repository == repo).count()
382 .filter(UserFollowing.follows_repository == repo).count()
358
383
359 def get_forks(self, repo):
384 def get_forks(self, repo):
360 repo = self._get_repo(repo)
385 repo = self._get_repo(repo)
361 return self.sa.query(Repository)\
386 return self.sa.query(Repository)\
362 .filter(Repository.fork == repo).count()
387 .filter(Repository.fork == repo).count()
363
388
364 def get_pull_requests(self, repo):
389 def get_pull_requests(self, repo):
365 repo = self._get_repo(repo)
390 repo = self._get_repo(repo)
366 return self.sa.query(PullRequest)\
391 return self.sa.query(PullRequest)\
367 .filter(PullRequest.target_repo == repo)\
392 .filter(PullRequest.target_repo == repo)\
368 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
393 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
369
394
370 def get_artifacts(self, repo):
395 def get_artifacts(self, repo):
371 repo = self._get_repo(repo)
396 repo = self._get_repo(repo)
372 return self.sa.query(FileStore)\
397 return self.sa.query(FileStore)\
373 .filter(FileStore.repo == repo)\
398 .filter(FileStore.repo == repo)\
374 .filter(or_(FileStore.hidden == null(), FileStore.hidden == false())).count()
399 .filter(or_(FileStore.hidden == null(), FileStore.hidden == false())).count()
375
400
376 def mark_as_fork(self, repo, fork, user):
401 def mark_as_fork(self, repo, fork, user):
377 repo = self._get_repo(repo)
402 repo = self._get_repo(repo)
378 fork = self._get_repo(fork)
403 fork = self._get_repo(fork)
379 if fork and repo.repo_id == fork.repo_id:
404 if fork and repo.repo_id == fork.repo_id:
380 raise Exception("Cannot set repository as fork of itself")
405 raise Exception("Cannot set repository as fork of itself")
381
406
382 if fork and repo.repo_type != fork.repo_type:
407 if fork and repo.repo_type != fork.repo_type:
383 raise RepositoryError(
408 raise RepositoryError(
384 "Cannot set repository as fork of repository with other type")
409 "Cannot set repository as fork of repository with other type")
385
410
386 repo.fork = fork
411 repo.fork = fork
387 self.sa.add(repo)
412 self.sa.add(repo)
388 return repo
413 return repo
389
414
390 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True, **kwargs):
415 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True, **kwargs):
391 dbrepo = self._get_repo(repo)
416 dbrepo = self._get_repo(repo)
392 remote_uri = remote_uri or dbrepo.clone_uri
417 remote_uri = remote_uri or dbrepo.clone_uri
393 if not remote_uri:
418 if not remote_uri:
394 raise Exception("This repository doesn't have a clone uri")
419 raise Exception("This repository doesn't have a clone uri")
395
420
396 repo = dbrepo.scm_instance(cache=False)
421 repo = dbrepo.scm_instance(cache=False)
397 repo.config.clear_section('hooks')
422 repo.config.clear_section('hooks')
398
423
399 try:
424 try:
400 # NOTE(marcink): add extra validation so we skip invalid urls
425 # NOTE(marcink): add extra validation so we skip invalid urls
401 # this is due this tasks can be executed via scheduler without
426 # this is due this tasks can be executed via scheduler without
402 # proper validation of remote_uri
427 # proper validation of remote_uri
403 if validate_uri:
428 if validate_uri:
404 config = make_db_config(clear_session=False)
429 config = make_db_config(clear_session=False)
405 url_validator(remote_uri, dbrepo.repo_type, config)
430 url_validator(remote_uri, dbrepo.repo_type, config)
406 except InvalidCloneUrl:
431 except InvalidCloneUrl:
407 raise
432 raise
408
433
409 repo_name = dbrepo.repo_name
434 repo_name = dbrepo.repo_name
410 try:
435 try:
411 # TODO: we need to make sure those operations call proper hooks !
436 # TODO: we need to make sure those operations call proper hooks !
412 repo.fetch(remote_uri, **kwargs)
437 repo.fetch(remote_uri, **kwargs)
413
438
414 self.mark_for_invalidation(repo_name)
439 self.mark_for_invalidation(repo_name)
415 except Exception:
440 except Exception:
416 log.error(traceback.format_exc())
441 log.error(traceback.format_exc())
417 raise
442 raise
418
443
419 def push_changes(self, repo, username, remote_uri=None, validate_uri=True, **kwargs):
444 def push_changes(self, repo, username, remote_uri=None, validate_uri=True, **kwargs):
420 dbrepo = self._get_repo(repo)
445 dbrepo = self._get_repo(repo)
421 remote_uri = remote_uri or dbrepo.push_uri
446 remote_uri = remote_uri or dbrepo.push_uri
422 if not remote_uri:
447 if not remote_uri:
423 raise Exception("This repository doesn't have a clone uri")
448 raise Exception("This repository doesn't have a clone uri")
424
449
425 repo = dbrepo.scm_instance(cache=False)
450 repo = dbrepo.scm_instance(cache=False)
426 repo.config.clear_section('hooks')
451 repo.config.clear_section('hooks')
427
452
428 try:
453 try:
429 # NOTE(marcink): add extra validation so we skip invalid urls
454 # NOTE(marcink): add extra validation so we skip invalid urls
430 # this is due this tasks can be executed via scheduler without
455 # this is due this tasks can be executed via scheduler without
431 # proper validation of remote_uri
456 # proper validation of remote_uri
432 if validate_uri:
457 if validate_uri:
433 config = make_db_config(clear_session=False)
458 config = make_db_config(clear_session=False)
434 url_validator(remote_uri, dbrepo.repo_type, config)
459 url_validator(remote_uri, dbrepo.repo_type, config)
435 except InvalidCloneUrl:
460 except InvalidCloneUrl:
436 raise
461 raise
437
462
438 try:
463 try:
439 repo.push(remote_uri, **kwargs)
464 repo.push(remote_uri, **kwargs)
440 except Exception:
465 except Exception:
441 log.error(traceback.format_exc())
466 log.error(traceback.format_exc())
442 raise
467 raise
443
468
444 def commit_change(self, repo, repo_name, commit, user, author, message,
469 def commit_change(self, repo, repo_name, commit, user, author, message,
445 content: bytes, f_path: bytes, branch: str = None):
470 content: bytes, f_path: bytes, branch: str = None):
446 """
471 """
447 Commits changes
472 Commits changes
448 """
473 """
449 user = self._get_user(user)
474 user = self._get_user(user)
450
475
451 # message and author needs to be unicode
476 # message and author needs to be unicode
452 # proper backend should then translate that into required type
477 # proper backend should then translate that into required type
453 message = safe_str(message)
478 message = safe_str(message)
454 author = safe_str(author)
479 author = safe_str(author)
455 imc = repo.in_memory_commit
480 imc = repo.in_memory_commit
456 imc.change(FileNode(f_path, content, mode=commit.get_file_mode(f_path)))
481 imc.change(FileNode(f_path, content, mode=commit.get_file_mode(f_path)))
457 try:
482 try:
458 # TODO: handle pre-push action !
483 # TODO: handle pre-push action !
459 tip = imc.commit(
484 tip = imc.commit(
460 message=message, author=author, parents=[commit],
485 message=message, author=author, parents=[commit],
461 branch=branch or commit.branch)
486 branch=branch or commit.branch)
462 except Exception as e:
487 except Exception as e:
463 log.error(traceback.format_exc())
488 log.error(traceback.format_exc())
464 raise IMCCommitError(str(e))
489 raise IMCCommitError(str(e))
465 finally:
490 finally:
466 # always clear caches, if commit fails we want fresh object also
491 # always clear caches, if commit fails we want fresh object also
467 self.mark_for_invalidation(repo_name)
492 self.mark_for_invalidation(repo_name)
468
493
469 # We trigger the post-push action
494 # We trigger the post-push action
470 hooks_utils.trigger_post_push_hook(
495 hooks_utils.trigger_post_push_hook(
471 username=user.username, action='push_local', hook_type='post_push',
496 username=user.username, action='push_local', hook_type='post_push',
472 repo_name=repo_name, repo_type=repo.alias, commit_ids=[tip.raw_id])
497 repo_name=repo_name, repo_type=repo.alias, commit_ids=[tip.raw_id])
473 return tip
498 return tip
474
499
475 def _sanitize_path(self, f_path: bytes):
500 def _sanitize_path(self, f_path: bytes):
476 if f_path.startswith(b'/') or f_path.startswith(b'./') or b'../' in f_path:
501 if f_path.startswith(b'/') or f_path.startswith(b'./') or b'../' in f_path:
477 raise NonRelativePathError(b'%b is not an relative path' % f_path)
502 raise NonRelativePathError(b'%b is not an relative path' % f_path)
478 if f_path:
503 if f_path:
479 f_path = os.path.normpath(f_path)
504 f_path = os.path.normpath(f_path)
480 return f_path
505 return f_path
481
506
482 def get_dirnode_metadata(self, request, commit, dir_node):
507 def get_dirnode_metadata(self, request, commit, dir_node):
483 if not dir_node.is_dir():
508 if not dir_node.is_dir():
484 return []
509 return []
485
510
486 data = []
511 data = []
487 for node in dir_node:
512 for node in dir_node:
488 if not node.is_file():
513 if not node.is_file():
489 # we skip file-nodes
514 # we skip file-nodes
490 continue
515 continue
491
516
492 last_commit = node.last_commit
517 last_commit = node.last_commit
493 last_commit_date = last_commit.date
518 last_commit_date = last_commit.date
494 data.append({
519 data.append({
495 'name': node.name,
520 'name': node.name,
496 'size': h.format_byte_size_binary(node.size),
521 'size': h.format_byte_size_binary(node.size),
497 'modified_at': h.format_date(last_commit_date),
522 'modified_at': h.format_date(last_commit_date),
498 'modified_ts': last_commit_date.isoformat(),
523 'modified_ts': last_commit_date.isoformat(),
499 'revision': last_commit.revision,
524 'revision': last_commit.revision,
500 'short_id': last_commit.short_id,
525 'short_id': last_commit.short_id,
501 'message': h.escape(last_commit.message),
526 'message': h.escape(last_commit.message),
502 'author': h.escape(last_commit.author),
527 'author': h.escape(last_commit.author),
503 'user_profile': h.gravatar_with_user(
528 'user_profile': h.gravatar_with_user(
504 request, last_commit.author),
529 request, last_commit.author),
505 })
530 })
506
531
507 return data
532 return data
508
533
509 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
534 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
510 extended_info=False, content=False, max_file_bytes=None):
535 extended_info=False, content=False, max_file_bytes=None):
511 """
536 """
512 recursive walk in root dir and return a set of all path in that dir
537 recursive walk in root dir and return a set of all path in that dir
513 based on repository walk function
538 based on repository walk function
514
539
515 :param repo_name: name of repository
540 :param repo_name: name of repository
516 :param commit_id: commit id for which to list nodes
541 :param commit_id: commit id for which to list nodes
517 :param root_path: root path to list
542 :param root_path: root path to list
518 :param flat: return as a list, if False returns a dict with description
543 :param flat: return as a list, if False returns a dict with description
519 :param extended_info: show additional info such as md5, binary, size etc
544 :param extended_info: show additional info such as md5, binary, size etc
520 :param content: add nodes content to the return data
545 :param content: add nodes content to the return data
521 :param max_file_bytes: will not return file contents over this limit
546 :param max_file_bytes: will not return file contents over this limit
522
547
523 """
548 """
524 _files = list()
549 _files = list()
525 _dirs = list()
550 _dirs = list()
526
551
527 try:
552 try:
528 _repo = self._get_repo(repo_name)
553 _repo = self._get_repo(repo_name)
529 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
554 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
530 root_path = root_path.lstrip('/')
555 root_path = root_path.lstrip('/')
531
556
532 # get RootNode, inject pre-load options before walking
557 # get RootNode, inject pre-load options before walking
533 top_node = commit.get_node(root_path)
558 top_node = commit.get_node(root_path)
534 extended_info_pre_load = []
559 extended_info_pre_load = []
535 if extended_info:
560 if extended_info:
536 extended_info_pre_load += ['md5']
561 extended_info_pre_load += ['md5']
537 top_node.default_pre_load = ['is_binary', 'size'] + extended_info_pre_load
562 top_node.default_pre_load = ['is_binary', 'size'] + extended_info_pre_load
538
563
539 for __, dirs, files in commit.walk(top_node):
564 for __, dirs, files in commit.walk(top_node):
540
565
541 for f in files:
566 for f in files:
542 _content = None
567 _content = None
543 _data = f_name = f.str_path
568 _data = f_name = f.str_path
544
569
545 if not flat:
570 if not flat:
546 _data = {
571 _data = {
547 "name": h.escape(f_name),
572 "name": h.escape(f_name),
548 "type": "file",
573 "type": "file",
549 }
574 }
550 if extended_info:
575 if extended_info:
551 _data.update({
576 _data.update({
552 "md5": f.md5,
577 "md5": f.md5,
553 "binary": f.is_binary,
578 "binary": f.is_binary,
554 "size": f.size,
579 "size": f.size,
555 "extension": f.extension,
580 "extension": f.extension,
556 "mimetype": f.mimetype,
581 "mimetype": f.mimetype,
557 "lines": f.lines()[0]
582 "lines": f.lines()[0]
558 })
583 })
559
584
560 if content:
585 if content:
561 over_size_limit = (max_file_bytes is not None
586 over_size_limit = (max_file_bytes is not None
562 and f.size > max_file_bytes)
587 and f.size > max_file_bytes)
563 full_content = None
588 full_content = None
564 if not f.is_binary and not over_size_limit:
589 if not f.is_binary and not over_size_limit:
565 full_content = f.str_content
590 full_content = f.str_content
566
591
567 _data.update({
592 _data.update({
568 "content": full_content,
593 "content": full_content,
569 })
594 })
570 _files.append(_data)
595 _files.append(_data)
571
596
572 for d in dirs:
597 for d in dirs:
573 _data = d_name = d.str_path
598 _data = d_name = d.str_path
574 if not flat:
599 if not flat:
575 _data = {
600 _data = {
576 "name": h.escape(d_name),
601 "name": h.escape(d_name),
577 "type": "dir",
602 "type": "dir",
578 }
603 }
579 if extended_info:
604 if extended_info:
580 _data.update({
605 _data.update({
581 "md5": "",
606 "md5": "",
582 "binary": False,
607 "binary": False,
583 "size": 0,
608 "size": 0,
584 "extension": "",
609 "extension": "",
585 })
610 })
586 if content:
611 if content:
587 _data.update({
612 _data.update({
588 "content": None
613 "content": None
589 })
614 })
590 _dirs.append(_data)
615 _dirs.append(_data)
591 except RepositoryError:
616 except RepositoryError:
592 log.exception("Exception in get_nodes")
617 log.exception("Exception in get_nodes")
593 raise
618 raise
594
619
595 return _dirs, _files
620 return _dirs, _files
596
621
597 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
622 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
598 """
623 """
599 Generate files for quick filter in files view
624 Generate files for quick filter in files view
600 """
625 """
601
626
602 _files = list()
627 _files = list()
603 _dirs = list()
628 _dirs = list()
604 try:
629 try:
605 _repo = self._get_repo(repo_name)
630 _repo = self._get_repo(repo_name)
606 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
631 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
607 root_path = root_path.lstrip('/')
632 root_path = root_path.lstrip('/')
608
633
609 top_node = commit.get_node(root_path)
634 top_node = commit.get_node(root_path)
610 top_node.default_pre_load = []
635 top_node.default_pre_load = []
611
636
612 for __, dirs, files in commit.walk(top_node):
637 for __, dirs, files in commit.walk(top_node):
613 for f in files:
638 for f in files:
614
639
615 _data = {
640 _data = {
616 "name": h.escape(f.str_path),
641 "name": h.escape(f.str_path),
617 "type": "file",
642 "type": "file",
618 }
643 }
619
644
620 _files.append(_data)
645 _files.append(_data)
621
646
622 for d in dirs:
647 for d in dirs:
623
648
624 _data = {
649 _data = {
625 "name": h.escape(d.str_path),
650 "name": h.escape(d.str_path),
626 "type": "dir",
651 "type": "dir",
627 }
652 }
628
653
629 _dirs.append(_data)
654 _dirs.append(_data)
630 except RepositoryError:
655 except RepositoryError:
631 log.exception("Exception in get_quick_filter_nodes")
656 log.exception("Exception in get_quick_filter_nodes")
632 raise
657 raise
633
658
634 return _dirs, _files
659 return _dirs, _files
635
660
636 def get_node(self, repo_name, commit_id, file_path,
661 def get_node(self, repo_name, commit_id, file_path,
637 extended_info=False, content=False, max_file_bytes=None, cache=True):
662 extended_info=False, content=False, max_file_bytes=None, cache=True):
638 """
663 """
639 retrieve single node from commit
664 retrieve single node from commit
640 """
665 """
641
666
642 try:
667 try:
643
668
644 _repo = self._get_repo(repo_name)
669 _repo = self._get_repo(repo_name)
645 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
670 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
646
671
647 file_node = commit.get_node(file_path)
672 file_node = commit.get_node(file_path)
648 if file_node.is_dir():
673 if file_node.is_dir():
649 raise RepositoryError('The given path is a directory')
674 raise RepositoryError('The given path is a directory')
650
675
651 _content = None
676 _content = None
652 f_name = file_node.str_path
677 f_name = file_node.str_path
653
678
654 file_data = {
679 file_data = {
655 "name": h.escape(f_name),
680 "name": h.escape(f_name),
656 "type": "file",
681 "type": "file",
657 }
682 }
658
683
659 if extended_info:
684 if extended_info:
660 file_data.update({
685 file_data.update({
661 "extension": file_node.extension,
686 "extension": file_node.extension,
662 "mimetype": file_node.mimetype,
687 "mimetype": file_node.mimetype,
663 })
688 })
664
689
665 if cache:
690 if cache:
666 md5 = file_node.md5
691 md5 = file_node.md5
667 is_binary = file_node.is_binary
692 is_binary = file_node.is_binary
668 size = file_node.size
693 size = file_node.size
669 else:
694 else:
670 is_binary, md5, size, _content = file_node.metadata_uncached()
695 is_binary, md5, size, _content = file_node.metadata_uncached()
671
696
672 file_data.update({
697 file_data.update({
673 "md5": md5,
698 "md5": md5,
674 "binary": is_binary,
699 "binary": is_binary,
675 "size": size,
700 "size": size,
676 })
701 })
677
702
678 if content and cache:
703 if content and cache:
679 # get content + cache
704 # get content + cache
680 size = file_node.size
705 size = file_node.size
681 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
706 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
682 full_content = None
707 full_content = None
683 all_lines = 0
708 all_lines = 0
684 if not file_node.is_binary and not over_size_limit:
709 if not file_node.is_binary and not over_size_limit:
685 full_content = safe_str(file_node.content)
710 full_content = safe_str(file_node.content)
686 all_lines, empty_lines = file_node.count_lines(full_content)
711 all_lines, empty_lines = file_node.count_lines(full_content)
687
712
688 file_data.update({
713 file_data.update({
689 "content": full_content,
714 "content": full_content,
690 "lines": all_lines
715 "lines": all_lines
691 })
716 })
692 elif content:
717 elif content:
693 # get content *without* cache
718 # get content *without* cache
694 if _content is None:
719 if _content is None:
695 is_binary, md5, size, _content = file_node.metadata_uncached()
720 is_binary, md5, size, _content = file_node.metadata_uncached()
696
721
697 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
722 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
698 full_content = None
723 full_content = None
699 all_lines = 0
724 all_lines = 0
700 if not is_binary and not over_size_limit:
725 if not is_binary and not over_size_limit:
701 full_content = safe_str(_content)
726 full_content = safe_str(_content)
702 all_lines, empty_lines = file_node.count_lines(full_content)
727 all_lines, empty_lines = file_node.count_lines(full_content)
703
728
704 file_data.update({
729 file_data.update({
705 "content": full_content,
730 "content": full_content,
706 "lines": all_lines
731 "lines": all_lines
707 })
732 })
708
733
709 except RepositoryError:
734 except RepositoryError:
710 log.exception("Exception in get_node")
735 log.exception("Exception in get_node")
711 raise
736 raise
712
737
713 return file_data
738 return file_data
714
739
715 def get_fts_data(self, repo_name, commit_id, root_path='/'):
740 def get_fts_data(self, repo_name, commit_id, root_path='/'):
716 """
741 """
717 Fetch node tree for usage in full text search
742 Fetch node tree for usage in full text search
718 """
743 """
719
744
720 tree_info = list()
745 tree_info = list()
721
746
722 try:
747 try:
723 _repo = self._get_repo(repo_name)
748 _repo = self._get_repo(repo_name)
724 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
749 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
725 root_path = root_path.lstrip('/')
750 root_path = root_path.lstrip('/')
726 top_node = commit.get_node(root_path)
751 top_node = commit.get_node(root_path)
727 top_node.default_pre_load = []
752 top_node.default_pre_load = []
728
753
729 for __, dirs, files in commit.walk(top_node):
754 for __, dirs, files in commit.walk(top_node):
730
755
731 for f in files:
756 for f in files:
732 is_binary, md5, size, _content = f.metadata_uncached()
757 is_binary, md5, size, _content = f.metadata_uncached()
733 _data = {
758 _data = {
734 "name": f.str_path,
759 "name": f.str_path,
735 "md5": md5,
760 "md5": md5,
736 "extension": f.extension,
761 "extension": f.extension,
737 "binary": is_binary,
762 "binary": is_binary,
738 "size": size
763 "size": size
739 }
764 }
740
765
741 tree_info.append(_data)
766 tree_info.append(_data)
742
767
743 except RepositoryError:
768 except RepositoryError:
744 log.exception("Exception in get_nodes")
769 log.exception("Exception in get_nodes")
745 raise
770 raise
746
771
747 return tree_info
772 return tree_info
748
773
749 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
774 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
750 author=None, trigger_push_hook=True):
775 author=None, trigger_push_hook=True):
751 """
776 """
752 Commits given multiple nodes into repo
777 Commits given multiple nodes into repo
753
778
754 :param user: RhodeCode User object or user_id, the commiter
779 :param user: RhodeCode User object or user_id, the commiter
755 :param repo: RhodeCode Repository object
780 :param repo: RhodeCode Repository object
756 :param message: commit message
781 :param message: commit message
757 :param nodes: mapping {filename:{'content':content},...}
782 :param nodes: mapping {filename:{'content':content},...}
758 :param parent_commit: parent commit, can be empty than it's
783 :param parent_commit: parent commit, can be empty than it's
759 initial commit
784 initial commit
760 :param author: author of commit, cna be different that commiter
785 :param author: author of commit, cna be different that commiter
761 only for git
786 only for git
762 :param trigger_push_hook: trigger push hooks
787 :param trigger_push_hook: trigger push hooks
763
788
764 :returns: new committed commit
789 :returns: new committed commit
765 """
790 """
766
791 user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars(
767 user = self._get_user(user)
792 user, repo, message, author)
768 scm_instance = repo.scm_instance(cache=False)
769
770 message = safe_str(message)
771 commiter = user.full_contact
772 author = safe_str(author) if author else commiter
773
793
774 imc = scm_instance.in_memory_commit
794 parent_commit, parents = self.get_parent_commits(parent_commit, scm_instance)
775
776 if not parent_commit:
777 parent_commit = EmptyCommit(alias=scm_instance.alias)
778
779 if isinstance(parent_commit, EmptyCommit):
780 # EmptyCommit means we're editing empty repository
781 parents = None
782 else:
783 parents = [parent_commit]
784
795
785 upload_file_types = (io.BytesIO, io.BufferedRandom)
796 upload_file_types = (io.BytesIO, io.BufferedRandom)
786 processed_nodes = []
797 processed_nodes = []
787 for filename, content_dict in nodes.items():
798 for filename, content_dict in nodes.items():
788 if not isinstance(filename, bytes):
799 if not isinstance(filename, bytes):
789 raise ValueError(f'filename key in nodes needs to be bytes , or {upload_file_types}')
800 raise ValueError(f'filename key in nodes needs to be bytes , or {upload_file_types}')
790 content = content_dict['content']
801 content = content_dict['content']
791 if not isinstance(content, upload_file_types + (bytes,)):
802 if not isinstance(content, upload_file_types + (bytes,)):
792 raise ValueError('content key value in nodes needs to be bytes')
803 raise ValueError('content key value in nodes needs to be bytes')
793
804
794 for f_path in nodes:
805 for f_path in nodes:
795 f_path = self._sanitize_path(f_path)
806 f_path = self._sanitize_path(f_path)
796 content = nodes[f_path]['content']
807 content = nodes[f_path]['content']
797
808
798 # decoding here will force that we have proper encoded values
809 # decoding here will force that we have proper encoded values
799 # in any other case this will throw exceptions and deny commit
810 # in any other case this will throw exceptions and deny commit
800
811
801 if isinstance(content, bytes):
812 if isinstance(content, bytes):
802 pass
813 pass
803 elif isinstance(content, upload_file_types):
814 elif isinstance(content, upload_file_types):
804 content = content.read()
815 content = content.read()
805 else:
816 else:
806 raise Exception(f'Content is of unrecognized type {type(content)}, expected {upload_file_types}')
817 raise Exception(f'Content is of unrecognized type {type(content)}, expected {upload_file_types}')
807 processed_nodes.append((f_path, content))
818 processed_nodes.append((f_path, content))
808
819
809 # add multiple nodes
820 # add multiple nodes
810 for path, content in processed_nodes:
821 for path, content in processed_nodes:
811 imc.add(FileNode(path, content=content))
822 imc.add(FileNode(path, content=content))
812
823
813 # TODO: handle pre push scenario
824 # TODO: handle pre push scenario
814 tip = imc.commit(message=message,
825 tip = imc.commit(message=message,
815 author=author,
826 author=author,
816 parents=parents,
827 parents=parents,
817 branch=parent_commit.branch)
828 branch=parent_commit.branch)
818
829
819 self.mark_for_invalidation(repo.repo_name)
830 self.mark_for_invalidation(repo.repo_name)
820 if trigger_push_hook:
831 if trigger_push_hook:
821 hooks_utils.trigger_post_push_hook(
832 hooks_utils.trigger_post_push_hook(
822 username=user.username, action='push_local',
833 username=user.username, action='push_local',
823 repo_name=repo.repo_name, repo_type=scm_instance.alias,
834 repo_name=repo.repo_name, repo_type=scm_instance.alias,
824 hook_type='post_push',
835 hook_type='post_push',
825 commit_ids=[tip.raw_id])
836 commit_ids=[tip.raw_id])
826 return tip
837 return tip
827
838
828 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
839 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
829 author=None, trigger_push_hook=True):
840 author=None, trigger_push_hook=True):
830 user = self._get_user(user)
841 user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars(
831 scm_instance = repo.scm_instance(cache=False)
842 user, repo, message, author)
832
833 message = safe_str(message)
834 commiter = user.full_contact
835 author = safe_str(author) if author else commiter
836
837 imc = scm_instance.in_memory_commit
838
843
839 if not parent_commit:
844 parent_commit, parents = self.get_parent_commits(parent_commit, scm_instance)
840 parent_commit = EmptyCommit(alias=scm_instance.alias)
841
842 if isinstance(parent_commit, EmptyCommit):
843 # EmptyCommit means we we're editing empty repository
844 parents = None
845 else:
846 parents = [parent_commit]
847
845
848 # add multiple nodes
846 # add multiple nodes
849 for _filename, data in nodes.items():
847 for _filename, data in nodes.items():
850 # new filename, can be renamed from the old one, also sanitaze
848 # new filename, can be renamed from the old one, also sanitaze
851 # the path for any hack around relative paths like ../../ etc.
849 # the path for any hack around relative paths like ../../ etc.
852 filename = self._sanitize_path(data['filename'])
850 filename = self._sanitize_path(data['filename'])
853 old_filename = self._sanitize_path(_filename)
851 old_filename = self._sanitize_path(_filename)
854 content = data['content']
852 content = data['content']
855 file_mode = data.get('mode')
853 file_mode = data.get('mode')
856 filenode = FileNode(old_filename, content=content, mode=file_mode)
854 filenode = FileNode(old_filename, content=content, mode=file_mode)
857 op = data['op']
855 op = data['op']
858 if op == 'add':
856 if op == 'add':
859 imc.add(filenode)
857 imc.add(filenode)
860 elif op == 'del':
858 elif op == 'del':
861 imc.remove(filenode)
859 imc.remove(filenode)
862 elif op == 'mod':
860 elif op == 'mod':
863 if filename != old_filename:
861 if filename != old_filename:
864 # TODO: handle renames more efficient, needs vcs lib changes
862 # TODO: handle renames more efficient, needs vcs lib changes
865 imc.remove(filenode)
863 imc.remove(filenode)
866 imc.add(FileNode(filename, content=content, mode=file_mode))
864 imc.add(FileNode(filename, content=content, mode=file_mode))
867 else:
865 else:
868 imc.change(filenode)
866 imc.change(filenode)
869
867
870 try:
868 try:
871 # TODO: handle pre push scenario commit changes
869 # TODO: handle pre push scenario commit changes
872 tip = imc.commit(message=message,
870 tip = imc.commit(message=message,
873 author=author,
871 author=author,
874 parents=parents,
872 parents=parents,
875 branch=parent_commit.branch)
873 branch=parent_commit.branch)
876 except NodeNotChangedError:
874 except NodeNotChangedError:
877 raise
875 raise
878 except Exception as e:
876 except Exception as e:
879 log.exception("Unexpected exception during call to imc.commit")
877 log.exception("Unexpected exception during call to imc.commit")
880 raise IMCCommitError(str(e))
878 raise IMCCommitError(str(e))
881 finally:
879 finally:
882 # always clear caches, if commit fails we want fresh object also
880 # always clear caches, if commit fails we want fresh object also
883 self.mark_for_invalidation(repo.repo_name)
881 self.mark_for_invalidation(repo.repo_name)
884
882
885 if trigger_push_hook:
883 if trigger_push_hook:
886 hooks_utils.trigger_post_push_hook(
884 hooks_utils.trigger_post_push_hook(
887 username=user.username, action='push_local', hook_type='post_push',
885 username=user.username, action='push_local', hook_type='post_push',
888 repo_name=repo.repo_name, repo_type=scm_instance.alias,
886 repo_name=repo.repo_name, repo_type=scm_instance.alias,
889 commit_ids=[tip.raw_id])
887 commit_ids=[tip.raw_id])
890
888
891 return tip
889 return tip
892
890
891 def update_binary_node(self, user, repo, message, node, parent_commit=None, author=None):
892 user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars(
893 user, repo, message, author)
894
895 parent_commit, parents = self.get_parent_commits(parent_commit, scm_instance)
896
897 file_path = node.get('file_path')
898 if isinstance(raw_content := node.get('content'), (io.BytesIO, io.BufferedRandom)):
899 content = raw_content.read()
900 else:
901 raise Exception("Wrong content was provided")
902 file_node = FileNode(file_path, content=content)
903 imc.change(file_node)
904
905 try:
906 tip = imc.commit(message=message,
907 author=author,
908 parents=parents,
909 branch=parent_commit.branch)
910 except NodeNotChangedError:
911 raise
912 except Exception as e:
913 log.exception("Unexpected exception during call to imc.commit")
914 raise IMCCommitError(str(e))
915 finally:
916 self.mark_for_invalidation(repo.repo_name)
917
918 hooks_utils.trigger_post_push_hook(
919 username=user.username, action='push_local', hook_type='post_push',
920 repo_name=repo.repo_name, repo_type=scm_instance.alias,
921 commit_ids=[tip.raw_id])
922 return tip
923
893 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
924 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
894 author=None, trigger_push_hook=True):
925 author=None, trigger_push_hook=True):
895 """
926 """
896 Deletes given multiple nodes into `repo`
927 Deletes given multiple nodes into `repo`
897
928
898 :param user: RhodeCode User object or user_id, the committer
929 :param user: RhodeCode User object or user_id, the committer
899 :param repo: RhodeCode Repository object
930 :param repo: RhodeCode Repository object
900 :param message: commit message
931 :param message: commit message
901 :param nodes: mapping {filename:{'content':content},...}
932 :param nodes: mapping {filename:{'content':content},...}
902 :param parent_commit: parent commit, can be empty than it's initial
933 :param parent_commit: parent commit, can be empty than it's initial
903 commit
934 commit
904 :param author: author of commit, cna be different that commiter only
935 :param author: author of commit, cna be different that commiter only
905 for git
936 for git
906 :param trigger_push_hook: trigger push hooks
937 :param trigger_push_hook: trigger push hooks
907
938
908 :returns: new commit after deletion
939 :returns: new commit after deletion
909 """
940 """
910
941
911 user = self._get_user(user)
942 user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars(
912 scm_instance = repo.scm_instance(cache=False)
943 user, repo, message, author)
913
944
914 processed_nodes = []
945 processed_nodes = []
915 for f_path in nodes:
946 for f_path in nodes:
916 f_path = self._sanitize_path(f_path)
947 f_path = self._sanitize_path(f_path)
917 # content can be empty but for compatibility it allows same dicts
948 # content can be empty but for compatibility it allows same dicts
918 # structure as add_nodes
949 # structure as add_nodes
919 content = nodes[f_path].get('content')
950 content = nodes[f_path].get('content')
920 processed_nodes.append((safe_bytes(f_path), content))
951 processed_nodes.append((safe_bytes(f_path), content))
921
952
922 message = safe_str(message)
953 parent_commit, parents = self.get_parent_commits(parent_commit, scm_instance)
923 commiter = user.full_contact
924 author = safe_str(author) if author else commiter
925
926 imc = scm_instance.in_memory_commit
927
954
928 if not parent_commit:
929 parent_commit = EmptyCommit(alias=scm_instance.alias)
930
931 if isinstance(parent_commit, EmptyCommit):
932 # EmptyCommit means we we're editing empty repository
933 parents = None
934 else:
935 parents = [parent_commit]
936 # add multiple nodes
955 # add multiple nodes
937 for path, content in processed_nodes:
956 for path, content in processed_nodes:
938 imc.remove(FileNode(path, content=content))
957 imc.remove(FileNode(path, content=content))
939
958
940 # TODO: handle pre push scenario
959 # TODO: handle pre push scenario
941 tip = imc.commit(message=message,
960 tip = imc.commit(message=message,
942 author=author,
961 author=author,
943 parents=parents,
962 parents=parents,
944 branch=parent_commit.branch)
963 branch=parent_commit.branch)
945
964
946 self.mark_for_invalidation(repo.repo_name)
965 self.mark_for_invalidation(repo.repo_name)
947 if trigger_push_hook:
966 if trigger_push_hook:
948 hooks_utils.trigger_post_push_hook(
967 hooks_utils.trigger_post_push_hook(
949 username=user.username, action='push_local', hook_type='post_push',
968 username=user.username, action='push_local', hook_type='post_push',
950 repo_name=repo.repo_name, repo_type=scm_instance.alias,
969 repo_name=repo.repo_name, repo_type=scm_instance.alias,
951 commit_ids=[tip.raw_id])
970 commit_ids=[tip.raw_id])
952 return tip
971 return tip
953
972
954 def strip(self, repo, commit_id, branch):
973 def strip(self, repo, commit_id, branch):
955 scm_instance = repo.scm_instance(cache=False)
974 scm_instance = repo.scm_instance(cache=False)
956 scm_instance.config.clear_section('hooks')
975 scm_instance.config.clear_section('hooks')
957 scm_instance.strip(commit_id, branch)
976 scm_instance.strip(commit_id, branch)
958 self.mark_for_invalidation(repo.repo_name)
977 self.mark_for_invalidation(repo.repo_name)
959
978
960 def get_unread_journal(self):
979 def get_unread_journal(self):
961 return self.sa.query(UserLog).count()
980 return self.sa.query(UserLog).count()
962
981
963 @classmethod
982 @classmethod
964 def backend_landing_ref(cls, repo_type):
983 def backend_landing_ref(cls, repo_type):
965 """
984 """
966 Return a default landing ref based on a repository type.
985 Return a default landing ref based on a repository type.
967 """
986 """
968
987
969 landing_ref = {
988 landing_ref = {
970 'hg': ('branch:default', 'default'),
989 'hg': ('branch:default', 'default'),
971 'git': ('branch:master', 'master'),
990 'git': ('branch:master', 'master'),
972 'svn': ('rev:tip', 'latest tip'),
991 'svn': ('rev:tip', 'latest tip'),
973 'default': ('rev:tip', 'latest tip'),
992 'default': ('rev:tip', 'latest tip'),
974 }
993 }
975
994
976 return landing_ref.get(repo_type) or landing_ref['default']
995 return landing_ref.get(repo_type) or landing_ref['default']
977
996
978 def get_repo_landing_revs(self, translator, repo=None):
997 def get_repo_landing_revs(self, translator, repo=None):
979 """
998 """
980 Generates select option with tags branches and bookmarks (for hg only)
999 Generates select option with tags branches and bookmarks (for hg only)
981 grouped by type
1000 grouped by type
982
1001
983 :param repo:
1002 :param repo:
984 """
1003 """
985 from rhodecode.lib.vcs.backends.git import GitRepository
1004 from rhodecode.lib.vcs.backends.git import GitRepository
986
1005
987 _ = translator
1006 _ = translator
988 repo = self._get_repo(repo)
1007 repo = self._get_repo(repo)
989
1008
990 if repo:
1009 if repo:
991 repo_type = repo.repo_type
1010 repo_type = repo.repo_type
992 else:
1011 else:
993 repo_type = 'default'
1012 repo_type = 'default'
994
1013
995 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
1014 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
996
1015
997 default_ref_options = [
1016 default_ref_options = [
998 [default_landing_ref, landing_ref_lbl]
1017 [default_landing_ref, landing_ref_lbl]
999 ]
1018 ]
1000 default_choices = [
1019 default_choices = [
1001 default_landing_ref
1020 default_landing_ref
1002 ]
1021 ]
1003
1022
1004 if not repo:
1023 if not repo:
1005 # presented at NEW repo creation
1024 # presented at NEW repo creation
1006 return default_choices, default_ref_options
1025 return default_choices, default_ref_options
1007
1026
1008 repo = repo.scm_instance()
1027 repo = repo.scm_instance()
1009
1028
1010 ref_options = [(default_landing_ref, landing_ref_lbl)]
1029 ref_options = [(default_landing_ref, landing_ref_lbl)]
1011 choices = [default_landing_ref]
1030 choices = [default_landing_ref]
1012
1031
1013 # branches
1032 # branches
1014 branch_group = [(f'branch:{safe_str(b)}', safe_str(b)) for b in repo.branches]
1033 branch_group = [(f'branch:{safe_str(b)}', safe_str(b)) for b in repo.branches]
1015 if not branch_group:
1034 if not branch_group:
1016 # new repo, or without maybe a branch?
1035 # new repo, or without maybe a branch?
1017 branch_group = default_ref_options
1036 branch_group = default_ref_options
1018
1037
1019 branches_group = (branch_group, _("Branches"))
1038 branches_group = (branch_group, _("Branches"))
1020 ref_options.append(branches_group)
1039 ref_options.append(branches_group)
1021 choices.extend([x[0] for x in branches_group[0]])
1040 choices.extend([x[0] for x in branches_group[0]])
1022
1041
1023 # bookmarks for HG
1042 # bookmarks for HG
1024 if repo.alias == 'hg':
1043 if repo.alias == 'hg':
1025 bookmarks_group = (
1044 bookmarks_group = (
1026 [(f'book:{safe_str(b)}', safe_str(b))
1045 [(f'book:{safe_str(b)}', safe_str(b))
1027 for b in repo.bookmarks],
1046 for b in repo.bookmarks],
1028 _("Bookmarks"))
1047 _("Bookmarks"))
1029 ref_options.append(bookmarks_group)
1048 ref_options.append(bookmarks_group)
1030 choices.extend([x[0] for x in bookmarks_group[0]])
1049 choices.extend([x[0] for x in bookmarks_group[0]])
1031
1050
1032 # tags
1051 # tags
1033 tags_group = (
1052 tags_group = (
1034 [(f'tag:{safe_str(t)}', safe_str(t))
1053 [(f'tag:{safe_str(t)}', safe_str(t))
1035 for t in repo.tags],
1054 for t in repo.tags],
1036 _("Tags"))
1055 _("Tags"))
1037 ref_options.append(tags_group)
1056 ref_options.append(tags_group)
1038 choices.extend([x[0] for x in tags_group[0]])
1057 choices.extend([x[0] for x in tags_group[0]])
1039
1058
1040 return choices, ref_options
1059 return choices, ref_options
1041
1060
1042 def get_server_info(self, environ=None):
1061 def get_server_info(self, environ=None):
1043 server_info = get_system_info(environ)
1062 server_info = get_system_info(environ)
1044 return server_info
1063 return server_info
@@ -1,189 +1,189 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
2
2
3 <%
3 <%
4 at_ref = request.GET.get('at')
4 at_ref = request.GET.get('at')
5 if at_ref:
5 if at_ref:
6 query={'at': at_ref}
6 query={'at': at_ref}
7 default_commit_id = at_ref or c.rhodecode_db_repo.landing_ref_name
7 default_commit_id = at_ref or c.rhodecode_db_repo.landing_ref_name
8 else:
8 else:
9 query=None
9 query=None
10 default_commit_id = c.commit.raw_id
10 default_commit_id = c.commit.raw_id
11 %>
11 %>
12
12
13 <div id="codeblock" class="browserblock">
13 <div id="codeblock" class="browserblock">
14 <div class="browser-header">
14 <div class="browser-header">
15 <div class="browser-nav">
15 <div class="browser-nav">
16 <div class="pull-left">
16 <div class="pull-left">
17 ## loads the history for a file
17 ## loads the history for a file
18 ${h.hidden('file_refs_filter')}
18 ${h.hidden('file_refs_filter')}
19 </div>
19 </div>
20
20
21 <div class="pull-right">
21 <div class="pull-right">
22
22
23 ## Download
23 ## Download
24 % if c.lf_node:
24 % if c.lf_node:
25 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
25 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
26 ${_('Download largefile')}
26 ${_('Download largefile')}
27 </a>
27 </a>
28 % else:
28 % else:
29 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
29 <a class="btn btn-default" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
30 ${_('Download file')}
30 ${_('Download file')}
31 </a>
31 </a>
32 % endif
32 % endif
33
33
34 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
34 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
35 ## on branch head, can edit files
35 ## on branch head, can edit files
36 %if c.on_branch_head and c.branch_or_raw_id:
36 %if c.on_branch_head and c.branch_or_raw_id:
37 ## binary files are delete only
37 ## binary files are delete only
38 % if c.file.is_binary:
38 % if c.file.is_binary:
39 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing binary files not allowed'))}
39 ${h.link_to(_('Replace'), h.route_path('repo_files_upload_file', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query={'upload_binary': 'true'}), class_="btn btn-default active tooltip", title=_('You can replace content of your binary file'))}
40 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query),class_="btn btn-danger")}
40 ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query),class_="btn btn-danger")}
41 % else:
41 % else:
42 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
42 <a class="btn btn-default" href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
43 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
43 ${_('Edit on branch: ')}<code>${c.branch_name}</code>
44 </a>
44 </a>
45
45
46 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
46 <a class="btn btn-danger" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _query=query)}">
47 ${_('Delete')}
47 ${_('Delete')}
48 </a>
48 </a>
49 % endif
49 % endif
50 ## not on head, forbid all
50 ## not on head, forbid all
51 % else:
51 % else:
52 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
52 ${h.link_to(_('Edit'), '#Edit', class_="btn btn-default disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
53 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
53 ${h.link_to(_('Delete'), '#Delete', class_="btn btn-default btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
54 % endif
54 % endif
55 %endif
55 %endif
56
56
57 </div>
57 </div>
58 </div>
58 </div>
59 <div id="file_history_container"></div>
59 <div id="file_history_container"></div>
60
60
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="codeblock">
64 <div class="codeblock">
65 <div class=" codeblock-header">
65 <div class=" codeblock-header">
66 <div class="file-filename">
66 <div class="file-filename">
67 <i class="icon-file"></i> ${c.file.name}
67 <i class="icon-file"></i> ${c.file.name}
68 </div>
68 </div>
69
69
70 <div class="file-stats">
70 <div class="file-stats">
71
71
72 <div class="stats-info">
72 <div class="stats-info">
73 <span class="stats-first-item">
73 <span class="stats-first-item">
74 % if c.file_size_too_big:
74 % if c.file_size_too_big:
75 0 ${(_('lines'))}
75 0 ${(_('lines'))}
76 % else:
76 % else:
77 ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}
77 ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}
78 % endif
78 % endif
79 </span>
79 </span>
80
80
81 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
81 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
82 % if c.lf_node:
82 % if c.lf_node:
83 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
83 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
84 % endif
84 % endif
85 <span>
85 <span>
86 | ${c.file.mimetype}
86 | ${c.file.mimetype}
87 </span>
87 </span>
88
88
89 % if not c.file_size_too_big:
89 % if not c.file_size_too_big:
90 <span> |
90 <span> |
91 ${h.get_lexer_for_filenode(c.file).__class__.__name__}
91 ${h.get_lexer_for_filenode(c.file).__class__.__name__}
92 </span>
92 </span>
93 % endif
93 % endif
94
94
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <div class="path clear-fix">
99 <div class="path clear-fix">
100 <div class="pull-left">
100 <div class="pull-left">
101 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'))}
101 ${h.files_breadcrumbs(c.repo_name, c.rhodecode_db_repo.repo_type, c.commit.raw_id, c.file.path, c.rhodecode_db_repo.landing_ref_name, request.GET.get('at'))}
102 </div>
102 </div>
103
103
104 <div class="pull-right stats">
104 <div class="pull-right stats">
105 <a id="file_history_overview" href="#loadHistory">
105 <a id="file_history_overview" href="#loadHistory">
106 ${_('History')}
106 ${_('History')}
107 </a>
107 </a>
108 |
108 |
109 %if c.annotate:
109 %if c.annotate:
110 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
110 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
111 %else:
111 %else:
112 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
112 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
113 %endif
113 %endif
114 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
114 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
115 % if not c.file.is_binary:
115 % if not c.file.is_binary:
116 |<a href="#copySource" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${c.file.content}">${_('Copy content')}</a>
116 |<a href="#copySource" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${c.file.content}">${_('Copy content')}</a>
117 |<a href="#copyPermaLink" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${h.route_url('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">${_('Copy permalink')}</a>
117 |<a href="#copyPermaLink" onclick="return false;" class="no-grey clipboard-action" data-clipboard-text="${h.route_url('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">${_('Copy permalink')}</a>
118 % endif
118 % endif
119
119
120 </div>
120 </div>
121 <div class="clear-fix"></div>
121 <div class="clear-fix"></div>
122 </div>
122 </div>
123
123
124 <div class="code-body clear-fix ">
124 <div class="code-body clear-fix ">
125 %if c.file.is_binary:
125 %if c.file.is_binary:
126 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
126 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
127 % if rendered_binary:
127 % if rendered_binary:
128 <div class="text-center">
128 <div class="text-center">
129 ${rendered_binary}
129 ${rendered_binary}
130 </div>
130 </div>
131 % else:
131 % else:
132 <div>
132 <div>
133 ${_('Binary file ({})').format(c.file.mimetype)}
133 ${_('Binary file ({})').format(c.file.mimetype)}
134 </div>
134 </div>
135 % endif
135 % endif
136 %else:
136 %else:
137 % if c.file_size_too_big:
137 % if c.file_size_too_big:
138 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
138 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
139 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
139 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
140 % else:
140 % else:
141 %if c.renderer and not c.annotate:
141 %if c.renderer and not c.annotate:
142 ## pick relative url based on renderer
142 ## pick relative url based on renderer
143 <%
143 <%
144 relative_urls = {
144 relative_urls = {
145 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
145 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
146 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
146 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
147 }
147 }
148 %>
148 %>
149 ${h.render(c.file.str_content, renderer=c.renderer, relative_urls=relative_urls)}
149 ${h.render(c.file.str_content, renderer=c.renderer, relative_urls=relative_urls)}
150 %else:
150 %else:
151 <table class="cb codehilite">
151 <table class="cb codehilite">
152 %if c.annotate:
152 %if c.annotate:
153 <% color_hasher = h.color_hasher() %>
153 <% color_hasher = h.color_hasher() %>
154 %for annotation, lines in c.annotated_lines:
154 %for annotation, lines in c.annotated_lines:
155 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
155 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
156 %endfor
156 %endfor
157 %else:
157 %else:
158 %for line_num, tokens in enumerate(c.lines, 1):
158 %for line_num, tokens in enumerate(c.lines, 1):
159 ${sourceblock.render_line(line_num, tokens)}
159 ${sourceblock.render_line(line_num, tokens)}
160 %endfor
160 %endfor
161 %endif
161 %endif
162 </table>
162 </table>
163 %endif
163 %endif
164 % endif
164 % endif
165 %endif
165 %endif
166 </div>
166 </div>
167
167
168 </div>
168 </div>
169
169
170 <script type="text/javascript">
170 <script type="text/javascript">
171 % if request.GET.get('mark'):
171 % if request.GET.get('mark'):
172
172
173 $(function(){
173 $(function(){
174 $(".codehilite").mark(
174 $(".codehilite").mark(
175 "${request.GET.get('mark')}",
175 "${request.GET.get('mark')}",
176 {
176 {
177 "className": 'match',
177 "className": 'match',
178 "accuracy": "complementary",
178 "accuracy": "complementary",
179 "ignorePunctuation": ":._(){}[]!'+=".split(""),
179 "ignorePunctuation": ":._(){}[]!'+=".split(""),
180 "each": function(el) {
180 "each": function(el) {
181 // and also highlight lines !
181 // and also highlight lines !
182 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
182 $($(el).closest('tr')).find('td.cb-lineno').addClass('cb-line-selected');
183 }
183 }
184 }
184 }
185 );
185 );
186
186
187 });
187 });
188 % endif
188 % endif
189 </script>
189 </script>
@@ -1,211 +1,222 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('{} Files Upload').format(c.repo_name)}
4 ${_('{} Files Upload').format(c.repo_name)}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()"></%def>
14 <%def name="breadcrumbs_links()"></%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='files')}
17 ${self.repo_menu(active='files')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21
21
22 <div class="box">
22 <div class="box">
23 ## Template for uploads
23 ## Template for uploads
24 <div style="display: none" id="tpl-dropzone">
24 <div style="display: none" id="tpl-dropzone">
25 <div class="dz-preview dz-file-preview">
25 <div class="dz-preview dz-file-preview">
26 <div class="dz-details">
26 <div class="dz-details">
27
27
28 <div class="dz-filename">
28 <div class="dz-filename">
29 <span data-dz-name></span>
29 <span data-dz-name></span>
30 </div>
30 </div>
31 <div class="dz-filename-size">
31 <div class="dz-filename-size">
32 <span class="dz-size" data-dz-size></span>
32 <span class="dz-size" data-dz-size></span>
33
33
34 </div>
34 </div>
35
35
36 <div class="dz-sending" style="display: none">${_('Uploading...')}</div>
36 <div class="dz-sending" style="display: none">${_('Uploading...')}</div>
37 <div class="dz-response" style="display: none">
37 <div class="dz-response" style="display: none">
38 ${_('Uploaded')} 100%
38 ${_('Uploaded')} 100%
39 </div>
39 </div>
40
40
41 </div>
41 </div>
42 <div class="dz-progress">
42 <div class="dz-progress">
43 <span class="dz-upload" data-dz-uploadprogress></span>
43 <span class="dz-upload" data-dz-uploadprogress></span>
44 </div>
44 </div>
45
45
46 <div class="dz-error-message">
46 <div class="dz-error-message">
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class="edit-file-title">
51 <div class="edit-file-title">
52 <span class="title-heading">${_('Upload new file')} @ <code>${h.show_id(c.commit)}</code></span>
52 % if c.replace_binary:
53 <span class="title-heading">${_('Replace content of')} <b>${c.f_path}</b> @ <code>${h.show_id(c.commit)}</code></span>
54 % else:
55 <span class="title-heading">${_('Upload new file')} @ <code>${h.show_id(c.commit)}</code></span>
56 % endif
53 % if c.commit.branch:
57 % if c.commit.branch:
54 <span class="tag branchtag">
58 <span class="tag branchtag">
55 <i class="icon-branch"></i> ${c.commit.branch}
59 <i class="icon-branch"></i> ${c.commit.branch}
56 </span>
60 </span>
57 % endif
61 % endif
58 </div>
62 </div>
59
63
60 <% form_url = h.route_path('repo_files_upload_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path) %>
64 % if not c.replace_binary:
61 ##${h.secure_form(form_url, id='eform', enctype="multipart/form-data", request=request)}
65 <% form_url = h.route_path('repo_files_upload_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path) %>
62 <div class="edit-file-fieldset">
66 <div class="edit-file-fieldset">
63 <div class="path-items">
67 <div class="path-items">
64 <ul class="tooltip" title="Repository path to store uploaded files. To change it, navigate to different path and click upload from there.">
68 <ul class="tooltip" title="Repository path to store uploaded files. To change it, navigate to different path and click upload from there.">
65 <li class="breadcrumb-path">
69 <li class="breadcrumb-path">
66 <div>
70 <div>
67 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
71 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path='')}"><i class="icon-home"></i></a> /
68 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">${c.f_path}</a>${('/' if c.f_path else '')}
72 <a href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">${c.f_path}</a>${('/' if c.f_path else '')}
69 </div>
73 </div>
70 </li>
74 </li>
71 <li class="location-path">
75 <li class="location-path">
72
76
73 </li>
77 </li>
74 </ul>
78 </ul>
79 </div>
80
75 </div>
81 </div>
76
82 % else:
77 </div>
83 <% form_url = h.route_path('repo_files_replace_binary', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path) %>
84 % endif
78
85
79 <div class="upload-form table">
86 <div class="upload-form table">
80 <div>
87 <div>
81
88
82 <div class="dropzone-wrapper" id="file-uploader" style="border: none; padding: 40px 0">
89 <div class="dropzone-wrapper" id="file-uploader" style="border: none; padding: 40px 0">
83 <div class="dropzone-pure">
90 <div class="dropzone-pure">
84 <div class="dz-message">
91 <div class="dz-message">
85 <i class="icon-upload" style="font-size:36px"></i></br>
92 <i class="icon-upload" style="font-size:36px"></i></br>
86 ${_("Drag'n Drop files here or")} <span class="link">${_('Choose your files')}</span>.<br>
93 % if not c.replace_binary:
94 ${_("Drag'n Drop files here or")} <span class="link">${_('Choose your files')}</span>.<br>
95 % else:
96 ${_("Drag'n Drop file here or")} <span class="link">${_('Choose your file')}</span>.<br>
97 % endif
87 </div>
98 </div>
88 </div>
99 </div>
89
100
90 </div>
101 </div>
91 </div>
102 </div>
92
103
93 </div>
104 </div>
94
105
95 <div class="upload-form edit-file-fieldset">
106 <div class="upload-form edit-file-fieldset">
96 <div class="fieldset">
107 <div class="fieldset">
97 <div class="message">
108 <div class="message">
98 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
109 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
99 </div>
110 </div>
100 </div>
111 </div>
101 <div class="pull-left">
112 <div class="pull-left">
102 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
113 ${h.submit('commit_btn',_('Commit changes'), class_="btn btn-small btn-success")}
103 </div>
114 </div>
104 </div>
115 </div>
105 ##${h.end_form()}
116 ##${h.end_form()}
106
117
107 <div class="file-upload-transaction-wrapper" style="display: none">
118 <div class="file-upload-transaction-wrapper" style="display: none">
108 <div class="file-upload-transaction">
119 <div class="file-upload-transaction">
109 <h3>${_('Committing...')}</h3>
120 <h3>${_('Committing...')}</h3>
110 <p>${_('Please wait while the files are being uploaded')}</p>
121 <p>${_('Please wait while the files are being uploaded')}</p>
111 <p class="error" style="display: none">
122 <p class="error" style="display: none">
112
123
113 </p>
124 </p>
114 <i class="icon-spin animate-spin"></i>
125 <i class="icon-spin animate-spin"></i>
115 <p></p>
126 <p></p>
116 </div>
127 </div>
117 </div>
128 </div>
118
129
119 </div>
130 </div>
120
131
121 <script type="text/javascript">
132 <script type="text/javascript">
122
133
123 $(document).ready(function () {
134 $(document).ready(function () {
124
135
125 //see: https://www.dropzonejs.com/#configuration
136 //see: https://www.dropzonejs.com/#configuration
126 myDropzone = new Dropzone("div#file-uploader", {
137 myDropzone = new Dropzone("div#file-uploader", {
127 url: "${form_url}",
138 url: "${form_url}",
128 headers: {"X-CSRF-Token": CSRF_TOKEN},
139 headers: {"X-CSRF-Token": CSRF_TOKEN},
129 paramName: function () {
140 paramName: function () {
130 return "files_upload"
141 return "files_upload"
131 }, // The name that will be used to transfer the file
142 }, // The name that will be used to transfer the file
132 parallelUploads: 20,
143 parallelUploads: 20,
133 maxFiles: 20,
144 maxFiles: 20,
134 uploadMultiple: true,
145 uploadMultiple: true,
135 //chunking: true, // use chunking transfer, not supported at the moment
146 //chunking: true, // use chunking transfer, not supported at the moment
136 //maxFilesize: 2, // in MBs
147 //maxFilesize: 2, // in MBs
137 autoProcessQueue: false, // if false queue will not be processed automatically.
148 autoProcessQueue: false, // if false queue will not be processed automatically.
138 createImageThumbnails: false,
149 createImageThumbnails: false,
139 previewTemplate: document.querySelector('#tpl-dropzone').innerHTML,
150 previewTemplate: document.querySelector('#tpl-dropzone').innerHTML,
140 accept: function (file, done) {
151 accept: function (file, done) {
141 done();
152 done();
142 },
153 },
143 init: function () {
154 init: function () {
144 this.on("addedfile", function (file) {
155 this.on("addedfile", function (file) {
145
156
146 });
157 });
147
158
148 this.on("sending", function (file, xhr, formData) {
159 this.on("sending", function (file, xhr, formData) {
149 formData.append("message", $('#commit').val());
160 formData.append("message", $('#commit').val());
150 $(file.previewElement).find('.dz-sending').show();
161 $(file.previewElement).find('.dz-sending').show();
151 });
162 });
152
163
153 this.on("success", function (file, response) {
164 this.on("success", function (file, response) {
154 $(file.previewElement).find('.dz-sending').hide();
165 $(file.previewElement).find('.dz-sending').hide();
155 $(file.previewElement).find('.dz-response').show();
166 $(file.previewElement).find('.dz-response').show();
156
167
157 if (response.error !== null) {
168 if (response.error !== null) {
158 $('.file-upload-transaction-wrapper .error').html('ERROR: {0}'.format(response.error));
169 $('.file-upload-transaction-wrapper .error').html('ERROR: {0}'.format(response.error));
159 $('.file-upload-transaction-wrapper .error').show();
170 $('.file-upload-transaction-wrapper .error').show();
160 $('.file-upload-transaction-wrapper i').hide()
171 $('.file-upload-transaction-wrapper i').hide()
161 }
172 }
162
173
163 var redirect_url = response.redirect_url || '/';
174 var redirect_url = response.redirect_url || '/';
164 window.location = redirect_url
175 window.location = redirect_url
165
176
166 });
177 });
167
178
168 this.on("error", function (file, errorMessage, xhr) {
179 this.on("error", function (file, errorMessage, xhr) {
169 var error = null;
180 var error = null;
170
181
171 if (xhr !== undefined){
182 if (xhr !== undefined){
172 var httpStatus = xhr.status + " " + xhr.statusText;
183 var httpStatus = xhr.status + " " + xhr.statusText;
173 if (xhr !== undefined && xhr.status >= 500) {
184 if (xhr !== undefined && xhr.status >= 500) {
174 error = httpStatus;
185 error = httpStatus;
175 }
186 }
176 }
187 }
177
188
178 if (error === null) {
189 if (error === null) {
179 error = errorMessage.error || errorMessage || httpStatus;
190 error = errorMessage.error || errorMessage || httpStatus;
180 }
191 }
181
192
182 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
193 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
183 });
194 });
184 }
195 }
185 });
196 });
186
197
187 $('#commit_btn').on('click', function(e) {
198 $('#commit_btn').on('click', function(e) {
188 e.preventDefault();
199 e.preventDefault();
189 var button = $(this);
200 var button = $(this);
190 if (button.hasClass('clicked')) {
201 if (button.hasClass('clicked')) {
191 button.attr('disabled', true);
202 button.attr('disabled', true);
192 } else {
203 } else {
193 button.addClass('clicked');
204 button.addClass('clicked');
194 }
205 }
195
206
196 var files = myDropzone.getQueuedFiles();
207 var files = myDropzone.getQueuedFiles();
197 if (files.length === 0) {
208 if (files.length === 0) {
198 alert("Missing files");
209 alert("Missing files");
199 e.preventDefault();
210 e.preventDefault();
200 }
211 }
201
212
202 $('.upload-form').hide();
213 $('.upload-form').hide();
203 $('.file-upload-transaction-wrapper').show();
214 $('.file-upload-transaction-wrapper').show();
204 myDropzone.processQueue();
215 myDropzone.processQueue();
205
216
206 });
217 });
207
218
208 });
219 });
209
220
210 </script>
221 </script>
211 </%def>
222 </%def>
@@ -1,319 +1,320 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 def get_url_defs():
20 def get_url_defs():
21 from rhodecode.apps._base import ADMIN_PREFIX
21 from rhodecode.apps._base import ADMIN_PREFIX
22
22
23 return {
23 return {
24 "home": "/",
24 "home": "/",
25 "main_page_repos_data": "/_home_repos",
25 "main_page_repos_data": "/_home_repos",
26 "main_page_repo_groups_data": "/_home_repo_groups",
26 "main_page_repo_groups_data": "/_home_repo_groups",
27 "repo_group_home": "/{repo_group_name}",
27 "repo_group_home": "/{repo_group_name}",
28 "user_autocomplete_data": "/_users",
28 "user_autocomplete_data": "/_users",
29 "user_group_autocomplete_data": "/_user_groups",
29 "user_group_autocomplete_data": "/_user_groups",
30 "repo_list_data": "/_repos",
30 "repo_list_data": "/_repos",
31 "goto_switcher_data": "/_goto_data",
31 "goto_switcher_data": "/_goto_data",
32 "admin_home": ADMIN_PREFIX + "",
32 "admin_home": ADMIN_PREFIX + "",
33 "admin_audit_logs": ADMIN_PREFIX + "/audit_logs",
33 "admin_audit_logs": ADMIN_PREFIX + "/audit_logs",
34 "admin_defaults_repositories": ADMIN_PREFIX + "/defaults/repositories",
34 "admin_defaults_repositories": ADMIN_PREFIX + "/defaults/repositories",
35 "admin_defaults_repositories_update": ADMIN_PREFIX
35 "admin_defaults_repositories_update": ADMIN_PREFIX
36 + "/defaults/repositories/update",
36 + "/defaults/repositories/update",
37 "search": ADMIN_PREFIX + "/search",
37 "search": ADMIN_PREFIX + "/search",
38 "search_repo": "/{repo_name}/search",
38 "search_repo": "/{repo_name}/search",
39 "my_account_auth_tokens": ADMIN_PREFIX + "/my_account/auth_tokens",
39 "my_account_auth_tokens": ADMIN_PREFIX + "/my_account/auth_tokens",
40 "my_account_auth_tokens_add": ADMIN_PREFIX + "/my_account/auth_tokens/new",
40 "my_account_auth_tokens_add": ADMIN_PREFIX + "/my_account/auth_tokens/new",
41 "my_account_auth_tokens_delete": ADMIN_PREFIX
41 "my_account_auth_tokens_delete": ADMIN_PREFIX
42 + "/my_account/auth_tokens/delete",
42 + "/my_account/auth_tokens/delete",
43 "repos": ADMIN_PREFIX + "/repos",
43 "repos": ADMIN_PREFIX + "/repos",
44 "repos_data": ADMIN_PREFIX + "/repos_data",
44 "repos_data": ADMIN_PREFIX + "/repos_data",
45 "repo_groups": ADMIN_PREFIX + "/repo_groups",
45 "repo_groups": ADMIN_PREFIX + "/repo_groups",
46 "repo_groups_data": ADMIN_PREFIX + "/repo_groups_data",
46 "repo_groups_data": ADMIN_PREFIX + "/repo_groups_data",
47 "user_groups": ADMIN_PREFIX + "/user_groups",
47 "user_groups": ADMIN_PREFIX + "/user_groups",
48 "user_groups_data": ADMIN_PREFIX + "/user_groups_data",
48 "user_groups_data": ADMIN_PREFIX + "/user_groups_data",
49 "user_profile": "/_profiles/{username}",
49 "user_profile": "/_profiles/{username}",
50 "profile_user_group": "/_profile_user_group/{user_group_name}",
50 "profile_user_group": "/_profile_user_group/{user_group_name}",
51 "repo_summary": "/{repo_name}",
51 "repo_summary": "/{repo_name}",
52 "repo_creating_check": "/{repo_name}/repo_creating_check",
52 "repo_creating_check": "/{repo_name}/repo_creating_check",
53 "edit_repo": "/{repo_name}/settings",
53 "edit_repo": "/{repo_name}/settings",
54 "edit_repo_vcs": "/{repo_name}/settings/vcs",
54 "edit_repo_vcs": "/{repo_name}/settings/vcs",
55 "edit_repo_vcs_update": "/{repo_name}/settings/vcs/update",
55 "edit_repo_vcs_update": "/{repo_name}/settings/vcs/update",
56 "edit_repo_vcs_svn_pattern_delete": "/{repo_name}/settings/vcs/svn_pattern/delete",
56 "edit_repo_vcs_svn_pattern_delete": "/{repo_name}/settings/vcs/svn_pattern/delete",
57 "repo_archivefile": "/{repo_name}/archive/{fname}",
57 "repo_archivefile": "/{repo_name}/archive/{fname}",
58 "repo_files_diff": "/{repo_name}/diff/{f_path}",
58 "repo_files_diff": "/{repo_name}/diff/{f_path}",
59 "repo_files_diff_2way_redirect": "/{repo_name}/diff-2way/{f_path}",
59 "repo_files_diff_2way_redirect": "/{repo_name}/diff-2way/{f_path}",
60 "repo_files": "/{repo_name}/files/{commit_id}/{f_path}",
60 "repo_files": "/{repo_name}/files/{commit_id}/{f_path}",
61 "repo_files:default_path": "/{repo_name}/files/{commit_id}/",
61 "repo_files:default_path": "/{repo_name}/files/{commit_id}/",
62 "repo_files:default_commit": "/{repo_name}/files",
62 "repo_files:default_commit": "/{repo_name}/files",
63 "repo_files:rendered": "/{repo_name}/render/{commit_id}/{f_path}",
63 "repo_files:rendered": "/{repo_name}/render/{commit_id}/{f_path}",
64 "repo_files:annotated": "/{repo_name}/annotate/{commit_id}/{f_path}",
64 "repo_files:annotated": "/{repo_name}/annotate/{commit_id}/{f_path}",
65 "repo_files:annotated_previous": "/{repo_name}/annotate-previous/{commit_id}/{f_path}",
65 "repo_files:annotated_previous": "/{repo_name}/annotate-previous/{commit_id}/{f_path}",
66 "repo_files_nodelist": "/{repo_name}/nodelist/{commit_id}/{f_path}",
66 "repo_files_nodelist": "/{repo_name}/nodelist/{commit_id}/{f_path}",
67 "repo_file_raw": "/{repo_name}/raw/{commit_id}/{f_path}",
67 "repo_file_raw": "/{repo_name}/raw/{commit_id}/{f_path}",
68 "repo_file_download": "/{repo_name}/download/{commit_id}/{f_path}",
68 "repo_file_download": "/{repo_name}/download/{commit_id}/{f_path}",
69 "repo_file_history": "/{repo_name}/history/{commit_id}/{f_path}",
69 "repo_file_history": "/{repo_name}/history/{commit_id}/{f_path}",
70 "repo_file_authors": "/{repo_name}/authors/{commit_id}/{f_path}",
70 "repo_file_authors": "/{repo_name}/authors/{commit_id}/{f_path}",
71 "repo_files_remove_file": "/{repo_name}/remove_file/{commit_id}/{f_path}",
71 "repo_files_remove_file": "/{repo_name}/remove_file/{commit_id}/{f_path}",
72 "repo_files_delete_file": "/{repo_name}/delete_file/{commit_id}/{f_path}",
72 "repo_files_delete_file": "/{repo_name}/delete_file/{commit_id}/{f_path}",
73 "repo_files_edit_file": "/{repo_name}/edit_file/{commit_id}/{f_path}",
73 "repo_files_edit_file": "/{repo_name}/edit_file/{commit_id}/{f_path}",
74 "repo_files_update_file": "/{repo_name}/update_file/{commit_id}/{f_path}",
74 "repo_files_update_file": "/{repo_name}/update_file/{commit_id}/{f_path}",
75 "repo_files_add_file": "/{repo_name}/add_file/{commit_id}/{f_path}",
75 "repo_files_add_file": "/{repo_name}/add_file/{commit_id}/{f_path}",
76 "repo_files_upload_file": "/{repo_name}/upload_file/{commit_id}/{f_path}",
76 "repo_files_upload_file": "/{repo_name}/upload_file/{commit_id}/{f_path}",
77 "repo_files_create_file": "/{repo_name}/create_file/{commit_id}/{f_path}",
77 "repo_files_create_file": "/{repo_name}/create_file/{commit_id}/{f_path}",
78 "repo_files_replace_binary": "/{repo_name}/replace_binary/{commit_id}/{f_path}",
78 "repo_nodetree_full": "/{repo_name}/nodetree_full/{commit_id}/{f_path}",
79 "repo_nodetree_full": "/{repo_name}/nodetree_full/{commit_id}/{f_path}",
79 "repo_nodetree_full:default_path": "/{repo_name}/nodetree_full/{commit_id}/",
80 "repo_nodetree_full:default_path": "/{repo_name}/nodetree_full/{commit_id}/",
80 "journal": ADMIN_PREFIX + "/journal",
81 "journal": ADMIN_PREFIX + "/journal",
81 "journal_rss": ADMIN_PREFIX + "/journal/rss",
82 "journal_rss": ADMIN_PREFIX + "/journal/rss",
82 "journal_atom": ADMIN_PREFIX + "/journal/atom",
83 "journal_atom": ADMIN_PREFIX + "/journal/atom",
83 "journal_public": ADMIN_PREFIX + "/public_journal",
84 "journal_public": ADMIN_PREFIX + "/public_journal",
84 "journal_public_atom": ADMIN_PREFIX + "/public_journal/atom",
85 "journal_public_atom": ADMIN_PREFIX + "/public_journal/atom",
85 "journal_public_atom_old": ADMIN_PREFIX + "/public_journal_atom",
86 "journal_public_atom_old": ADMIN_PREFIX + "/public_journal_atom",
86 "journal_public_rss": ADMIN_PREFIX + "/public_journal/rss",
87 "journal_public_rss": ADMIN_PREFIX + "/public_journal/rss",
87 "journal_public_rss_old": ADMIN_PREFIX + "/public_journal_rss",
88 "journal_public_rss_old": ADMIN_PREFIX + "/public_journal_rss",
88 "toggle_following": ADMIN_PREFIX + "/toggle_following",
89 "toggle_following": ADMIN_PREFIX + "/toggle_following",
89 "upload_file": "/_file_store/upload",
90 "upload_file": "/_file_store/upload",
90 "download_file": "/_file_store/download/{fid}",
91 "download_file": "/_file_store/download/{fid}",
91 "download_file_by_token": "/_file_store/token-download/{_auth_token}/{fid}",
92 "download_file_by_token": "/_file_store/token-download/{_auth_token}/{fid}",
92 "gists_show": ADMIN_PREFIX + "/gists",
93 "gists_show": ADMIN_PREFIX + "/gists",
93 "gists_new": ADMIN_PREFIX + "/gists/new",
94 "gists_new": ADMIN_PREFIX + "/gists/new",
94 "gists_create": ADMIN_PREFIX + "/gists/create",
95 "gists_create": ADMIN_PREFIX + "/gists/create",
95 "gist_show": ADMIN_PREFIX + "/gists/{gist_id}",
96 "gist_show": ADMIN_PREFIX + "/gists/{gist_id}",
96 "gist_delete": ADMIN_PREFIX + "/gists/{gist_id}/delete",
97 "gist_delete": ADMIN_PREFIX + "/gists/{gist_id}/delete",
97 "gist_edit": ADMIN_PREFIX + "/gists/{gist_id}/edit",
98 "gist_edit": ADMIN_PREFIX + "/gists/{gist_id}/edit",
98 "gist_edit_check_revision": ADMIN_PREFIX
99 "gist_edit_check_revision": ADMIN_PREFIX
99 + "/gists/{gist_id}/edit/check_revision",
100 + "/gists/{gist_id}/edit/check_revision",
100 "gist_update": ADMIN_PREFIX + "/gists/{gist_id}/update",
101 "gist_update": ADMIN_PREFIX + "/gists/{gist_id}/update",
101 "gist_show_rev": ADMIN_PREFIX + "/gists/{gist_id}/rev/{revision}",
102 "gist_show_rev": ADMIN_PREFIX + "/gists/{gist_id}/rev/{revision}",
102 "gist_show_formatted": ADMIN_PREFIX
103 "gist_show_formatted": ADMIN_PREFIX
103 + "/gists/{gist_id}/rev/{revision}/{format}",
104 + "/gists/{gist_id}/rev/{revision}/{format}",
104 "gist_show_formatted_path": ADMIN_PREFIX
105 "gist_show_formatted_path": ADMIN_PREFIX
105 + "/gists/{gist_id}/rev/{revision}/{format}/{f_path}",
106 + "/gists/{gist_id}/rev/{revision}/{format}/{f_path}",
106 "login": ADMIN_PREFIX + "/login",
107 "login": ADMIN_PREFIX + "/login",
107 "logout": ADMIN_PREFIX + "/logout",
108 "logout": ADMIN_PREFIX + "/logout",
108 "register": ADMIN_PREFIX + "/register",
109 "register": ADMIN_PREFIX + "/register",
109 "reset_password": ADMIN_PREFIX + "/password_reset",
110 "reset_password": ADMIN_PREFIX + "/password_reset",
110 "reset_password_confirmation": ADMIN_PREFIX + "/password_reset_confirmation",
111 "reset_password_confirmation": ADMIN_PREFIX + "/password_reset_confirmation",
111 "admin_permissions_application": ADMIN_PREFIX + "/permissions/application",
112 "admin_permissions_application": ADMIN_PREFIX + "/permissions/application",
112 "admin_permissions_application_update": ADMIN_PREFIX
113 "admin_permissions_application_update": ADMIN_PREFIX
113 + "/permissions/application/update",
114 + "/permissions/application/update",
114 "repo_commit_raw": "/{repo_name}/changeset-diff/{commit_id}",
115 "repo_commit_raw": "/{repo_name}/changeset-diff/{commit_id}",
115 "user_group_members_data": ADMIN_PREFIX
116 "user_group_members_data": ADMIN_PREFIX
116 + "/user_groups/{user_group_id}/members",
117 + "/user_groups/{user_group_id}/members",
117 "user_groups_new": ADMIN_PREFIX + "/user_groups/new",
118 "user_groups_new": ADMIN_PREFIX + "/user_groups/new",
118 "user_groups_create": ADMIN_PREFIX + "/user_groups/create",
119 "user_groups_create": ADMIN_PREFIX + "/user_groups/create",
119 "edit_user_group": ADMIN_PREFIX + "/user_groups/{user_group_id}/edit",
120 "edit_user_group": ADMIN_PREFIX + "/user_groups/{user_group_id}/edit",
120 "edit_user_group_advanced_sync": ADMIN_PREFIX
121 "edit_user_group_advanced_sync": ADMIN_PREFIX
121 + "/user_groups/{user_group_id}/edit/advanced/sync",
122 + "/user_groups/{user_group_id}/edit/advanced/sync",
122 "edit_user_group_global_perms_update": ADMIN_PREFIX
123 "edit_user_group_global_perms_update": ADMIN_PREFIX
123 + "/user_groups/{user_group_id}/edit/global_permissions/update",
124 + "/user_groups/{user_group_id}/edit/global_permissions/update",
124 "user_groups_update": ADMIN_PREFIX + "/user_groups/{user_group_id}/update",
125 "user_groups_update": ADMIN_PREFIX + "/user_groups/{user_group_id}/update",
125 "user_groups_delete": ADMIN_PREFIX + "/user_groups/{user_group_id}/delete",
126 "user_groups_delete": ADMIN_PREFIX + "/user_groups/{user_group_id}/delete",
126 "edit_user_group_perms": ADMIN_PREFIX
127 "edit_user_group_perms": ADMIN_PREFIX
127 + "/user_groups/{user_group_id}/edit/permissions",
128 + "/user_groups/{user_group_id}/edit/permissions",
128 "edit_user_group_perms_update": ADMIN_PREFIX
129 "edit_user_group_perms_update": ADMIN_PREFIX
129 + "/user_groups/{user_group_id}/edit/permissions/update",
130 + "/user_groups/{user_group_id}/edit/permissions/update",
130 "edit_repo_group": "/{repo_group_name}/_edit",
131 "edit_repo_group": "/{repo_group_name}/_edit",
131 "edit_repo_group_perms": "/{repo_group_name:}/_settings/permissions",
132 "edit_repo_group_perms": "/{repo_group_name:}/_settings/permissions",
132 "edit_repo_group_perms_update": "/{repo_group_name}/_settings/permissions/update",
133 "edit_repo_group_perms_update": "/{repo_group_name}/_settings/permissions/update",
133 "edit_repo_group_advanced": "/{repo_group_name}/_settings/advanced",
134 "edit_repo_group_advanced": "/{repo_group_name}/_settings/advanced",
134 "edit_repo_group_advanced_delete": "/{repo_group_name}/_settings/advanced/delete",
135 "edit_repo_group_advanced_delete": "/{repo_group_name}/_settings/advanced/delete",
135 "edit_user_ssh_keys": ADMIN_PREFIX + "/users/{user_id}/edit/ssh_keys",
136 "edit_user_ssh_keys": ADMIN_PREFIX + "/users/{user_id}/edit/ssh_keys",
136 "edit_user_ssh_keys_generate_keypair": ADMIN_PREFIX
137 "edit_user_ssh_keys_generate_keypair": ADMIN_PREFIX
137 + "/users/{user_id}/edit/ssh_keys/generate",
138 + "/users/{user_id}/edit/ssh_keys/generate",
138 "edit_user_ssh_keys_add": ADMIN_PREFIX + "/users/{user_id}/edit/ssh_keys/new",
139 "edit_user_ssh_keys_add": ADMIN_PREFIX + "/users/{user_id}/edit/ssh_keys/new",
139 "edit_user_ssh_keys_delete": ADMIN_PREFIX
140 "edit_user_ssh_keys_delete": ADMIN_PREFIX
140 + "/users/{user_id}/edit/ssh_keys/delete",
141 + "/users/{user_id}/edit/ssh_keys/delete",
141 "users": ADMIN_PREFIX + "/users",
142 "users": ADMIN_PREFIX + "/users",
142 "users_data": ADMIN_PREFIX + "/users_data",
143 "users_data": ADMIN_PREFIX + "/users_data",
143 "users_create": ADMIN_PREFIX + "/users/create",
144 "users_create": ADMIN_PREFIX + "/users/create",
144 "users_new": ADMIN_PREFIX + "/users/new",
145 "users_new": ADMIN_PREFIX + "/users/new",
145 "user_edit": ADMIN_PREFIX + "/users/{user_id}/edit",
146 "user_edit": ADMIN_PREFIX + "/users/{user_id}/edit",
146 "user_edit_advanced": ADMIN_PREFIX + "/users/{user_id}/edit/advanced",
147 "user_edit_advanced": ADMIN_PREFIX + "/users/{user_id}/edit/advanced",
147 "user_edit_global_perms": ADMIN_PREFIX
148 "user_edit_global_perms": ADMIN_PREFIX
148 + "/users/{user_id}/edit/global_permissions",
149 + "/users/{user_id}/edit/global_permissions",
149 "user_edit_global_perms_update": ADMIN_PREFIX
150 "user_edit_global_perms_update": ADMIN_PREFIX
150 + "/users/{user_id}/edit/global_permissions/update",
151 + "/users/{user_id}/edit/global_permissions/update",
151 "user_update": ADMIN_PREFIX + "/users/{user_id}/update",
152 "user_update": ADMIN_PREFIX + "/users/{user_id}/update",
152 "user_delete": ADMIN_PREFIX + "/users/{user_id}/delete",
153 "user_delete": ADMIN_PREFIX + "/users/{user_id}/delete",
153 "user_create_personal_repo_group": ADMIN_PREFIX
154 "user_create_personal_repo_group": ADMIN_PREFIX
154 + "/users/{user_id}/create_repo_group",
155 + "/users/{user_id}/create_repo_group",
155 "edit_user_auth_tokens": ADMIN_PREFIX + "/users/{user_id}/edit/auth_tokens",
156 "edit_user_auth_tokens": ADMIN_PREFIX + "/users/{user_id}/edit/auth_tokens",
156 "edit_user_auth_tokens_add": ADMIN_PREFIX
157 "edit_user_auth_tokens_add": ADMIN_PREFIX
157 + "/users/{user_id}/edit/auth_tokens/new",
158 + "/users/{user_id}/edit/auth_tokens/new",
158 "edit_user_auth_tokens_delete": ADMIN_PREFIX
159 "edit_user_auth_tokens_delete": ADMIN_PREFIX
159 + "/users/{user_id}/edit/auth_tokens/delete",
160 + "/users/{user_id}/edit/auth_tokens/delete",
160 "edit_user_emails": ADMIN_PREFIX + "/users/{user_id}/edit/emails",
161 "edit_user_emails": ADMIN_PREFIX + "/users/{user_id}/edit/emails",
161 "edit_user_emails_add": ADMIN_PREFIX + "/users/{user_id}/edit/emails/new",
162 "edit_user_emails_add": ADMIN_PREFIX + "/users/{user_id}/edit/emails/new",
162 "edit_user_emails_delete": ADMIN_PREFIX + "/users/{user_id}/edit/emails/delete",
163 "edit_user_emails_delete": ADMIN_PREFIX + "/users/{user_id}/edit/emails/delete",
163 "edit_user_ips": ADMIN_PREFIX + "/users/{user_id}/edit/ips",
164 "edit_user_ips": ADMIN_PREFIX + "/users/{user_id}/edit/ips",
164 "edit_user_ips_add": ADMIN_PREFIX + "/users/{user_id}/edit/ips/new",
165 "edit_user_ips_add": ADMIN_PREFIX + "/users/{user_id}/edit/ips/new",
165 "edit_user_ips_delete": ADMIN_PREFIX + "/users/{user_id}/edit/ips/delete",
166 "edit_user_ips_delete": ADMIN_PREFIX + "/users/{user_id}/edit/ips/delete",
166 "edit_user_perms_summary": ADMIN_PREFIX
167 "edit_user_perms_summary": ADMIN_PREFIX
167 + "/users/{user_id}/edit/permissions_summary",
168 + "/users/{user_id}/edit/permissions_summary",
168 "edit_user_perms_summary_json": ADMIN_PREFIX
169 "edit_user_perms_summary_json": ADMIN_PREFIX
169 + "/users/{user_id}/edit/permissions_summary/json",
170 + "/users/{user_id}/edit/permissions_summary/json",
170 "edit_user_audit_logs": ADMIN_PREFIX + "/users/{user_id}/edit/audit",
171 "edit_user_audit_logs": ADMIN_PREFIX + "/users/{user_id}/edit/audit",
171 "edit_user_audit_logs_download": ADMIN_PREFIX
172 "edit_user_audit_logs_download": ADMIN_PREFIX
172 + "/users/{user_id}/edit/audit/download",
173 + "/users/{user_id}/edit/audit/download",
173 "admin_settings": ADMIN_PREFIX + "/settings",
174 "admin_settings": ADMIN_PREFIX + "/settings",
174 "admin_settings_update": ADMIN_PREFIX + "/settings/update",
175 "admin_settings_update": ADMIN_PREFIX + "/settings/update",
175 "admin_settings_global": ADMIN_PREFIX + "/settings/global",
176 "admin_settings_global": ADMIN_PREFIX + "/settings/global",
176 "admin_settings_global_update": ADMIN_PREFIX + "/settings/global/update",
177 "admin_settings_global_update": ADMIN_PREFIX + "/settings/global/update",
177 "admin_settings_vcs": ADMIN_PREFIX + "/settings/vcs",
178 "admin_settings_vcs": ADMIN_PREFIX + "/settings/vcs",
178 "admin_settings_vcs_update": ADMIN_PREFIX + "/settings/vcs/update",
179 "admin_settings_vcs_update": ADMIN_PREFIX + "/settings/vcs/update",
179 "admin_settings_vcs_svn_pattern_delete": ADMIN_PREFIX
180 "admin_settings_vcs_svn_pattern_delete": ADMIN_PREFIX
180 + "/settings/vcs/svn_pattern_delete",
181 + "/settings/vcs/svn_pattern_delete",
181 "admin_settings_mapping": ADMIN_PREFIX + "/settings/mapping",
182 "admin_settings_mapping": ADMIN_PREFIX + "/settings/mapping",
182 "admin_settings_mapping_update": ADMIN_PREFIX + "/settings/mapping/update",
183 "admin_settings_mapping_update": ADMIN_PREFIX + "/settings/mapping/update",
183 "admin_settings_visual": ADMIN_PREFIX + "/settings/visual",
184 "admin_settings_visual": ADMIN_PREFIX + "/settings/visual",
184 "admin_settings_visual_update": ADMIN_PREFIX + "/settings/visual/update",
185 "admin_settings_visual_update": ADMIN_PREFIX + "/settings/visual/update",
185 "admin_settings_issuetracker": ADMIN_PREFIX + "/settings/issue-tracker",
186 "admin_settings_issuetracker": ADMIN_PREFIX + "/settings/issue-tracker",
186 "admin_settings_issuetracker_update": ADMIN_PREFIX
187 "admin_settings_issuetracker_update": ADMIN_PREFIX
187 + "/settings/issue-tracker/update",
188 + "/settings/issue-tracker/update",
188 "admin_settings_issuetracker_test": ADMIN_PREFIX
189 "admin_settings_issuetracker_test": ADMIN_PREFIX
189 + "/settings/issue-tracker/test",
190 + "/settings/issue-tracker/test",
190 "admin_settings_issuetracker_delete": ADMIN_PREFIX
191 "admin_settings_issuetracker_delete": ADMIN_PREFIX
191 + "/settings/issue-tracker/delete",
192 + "/settings/issue-tracker/delete",
192 "admin_settings_email": ADMIN_PREFIX + "/settings/email",
193 "admin_settings_email": ADMIN_PREFIX + "/settings/email",
193 "admin_settings_email_update": ADMIN_PREFIX + "/settings/email/update",
194 "admin_settings_email_update": ADMIN_PREFIX + "/settings/email/update",
194 "admin_settings_hooks": ADMIN_PREFIX + "/settings/hooks",
195 "admin_settings_hooks": ADMIN_PREFIX + "/settings/hooks",
195 "admin_settings_hooks_update": ADMIN_PREFIX + "/settings/hooks/update",
196 "admin_settings_hooks_update": ADMIN_PREFIX + "/settings/hooks/update",
196 "admin_settings_hooks_delete": ADMIN_PREFIX + "/settings/hooks/delete",
197 "admin_settings_hooks_delete": ADMIN_PREFIX + "/settings/hooks/delete",
197 "admin_settings_search": ADMIN_PREFIX + "/settings/search",
198 "admin_settings_search": ADMIN_PREFIX + "/settings/search",
198 "admin_settings_labs": ADMIN_PREFIX + "/settings/labs",
199 "admin_settings_labs": ADMIN_PREFIX + "/settings/labs",
199 "admin_settings_labs_update": ADMIN_PREFIX + "/settings/labs/update",
200 "admin_settings_labs_update": ADMIN_PREFIX + "/settings/labs/update",
200 "admin_settings_sessions": ADMIN_PREFIX + "/settings/sessions",
201 "admin_settings_sessions": ADMIN_PREFIX + "/settings/sessions",
201 "admin_settings_sessions_cleanup": ADMIN_PREFIX + "/settings/sessions/cleanup",
202 "admin_settings_sessions_cleanup": ADMIN_PREFIX + "/settings/sessions/cleanup",
202 "admin_settings_system": ADMIN_PREFIX + "/settings/system",
203 "admin_settings_system": ADMIN_PREFIX + "/settings/system",
203 "admin_settings_system_update": ADMIN_PREFIX + "/settings/system/updates",
204 "admin_settings_system_update": ADMIN_PREFIX + "/settings/system/updates",
204 "admin_settings_open_source": ADMIN_PREFIX + "/settings/open_source",
205 "admin_settings_open_source": ADMIN_PREFIX + "/settings/open_source",
205 "repo_group_new": ADMIN_PREFIX + "/repo_group/new",
206 "repo_group_new": ADMIN_PREFIX + "/repo_group/new",
206 "repo_group_create": ADMIN_PREFIX + "/repo_group/create",
207 "repo_group_create": ADMIN_PREFIX + "/repo_group/create",
207 "repo_new": ADMIN_PREFIX + "/repos/new",
208 "repo_new": ADMIN_PREFIX + "/repos/new",
208 "repo_create": ADMIN_PREFIX + "/repos/create",
209 "repo_create": ADMIN_PREFIX + "/repos/create",
209 "admin_permissions_global": ADMIN_PREFIX + "/permissions/global",
210 "admin_permissions_global": ADMIN_PREFIX + "/permissions/global",
210 "admin_permissions_global_update": ADMIN_PREFIX + "/permissions/global/update",
211 "admin_permissions_global_update": ADMIN_PREFIX + "/permissions/global/update",
211 "admin_permissions_object": ADMIN_PREFIX + "/permissions/object",
212 "admin_permissions_object": ADMIN_PREFIX + "/permissions/object",
212 "admin_permissions_object_update": ADMIN_PREFIX + "/permissions/object/update",
213 "admin_permissions_object_update": ADMIN_PREFIX + "/permissions/object/update",
213 "admin_permissions_ips": ADMIN_PREFIX + "/permissions/ips",
214 "admin_permissions_ips": ADMIN_PREFIX + "/permissions/ips",
214 "admin_permissions_overview": ADMIN_PREFIX + "/permissions/overview",
215 "admin_permissions_overview": ADMIN_PREFIX + "/permissions/overview",
215 "admin_permissions_ssh_keys": ADMIN_PREFIX + "/permissions/ssh_keys",
216 "admin_permissions_ssh_keys": ADMIN_PREFIX + "/permissions/ssh_keys",
216 "admin_permissions_ssh_keys_data": ADMIN_PREFIX + "/permissions/ssh_keys/data",
217 "admin_permissions_ssh_keys_data": ADMIN_PREFIX + "/permissions/ssh_keys/data",
217 "admin_permissions_ssh_keys_update": ADMIN_PREFIX
218 "admin_permissions_ssh_keys_update": ADMIN_PREFIX
218 + "/permissions/ssh_keys/update",
219 + "/permissions/ssh_keys/update",
219 "pullrequest_show": "/{repo_name}/pull-request/{pull_request_id}",
220 "pullrequest_show": "/{repo_name}/pull-request/{pull_request_id}",
220 "pull_requests_global": ADMIN_PREFIX + "/pull-request/{pull_request_id}",
221 "pull_requests_global": ADMIN_PREFIX + "/pull-request/{pull_request_id}",
221 "pull_requests_global_0": ADMIN_PREFIX + "/pull_requests/{pull_request_id}",
222 "pull_requests_global_0": ADMIN_PREFIX + "/pull_requests/{pull_request_id}",
222 "pull_requests_global_1": ADMIN_PREFIX + "/pull-requests/{pull_request_id}",
223 "pull_requests_global_1": ADMIN_PREFIX + "/pull-requests/{pull_request_id}",
223 "notifications_show_all": ADMIN_PREFIX + "/notifications",
224 "notifications_show_all": ADMIN_PREFIX + "/notifications",
224 "notifications_mark_all_read": ADMIN_PREFIX + "/notifications_mark_all_read",
225 "notifications_mark_all_read": ADMIN_PREFIX + "/notifications_mark_all_read",
225 "notifications_show": ADMIN_PREFIX + "/notifications/{notification_id}",
226 "notifications_show": ADMIN_PREFIX + "/notifications/{notification_id}",
226 "notifications_update": ADMIN_PREFIX
227 "notifications_update": ADMIN_PREFIX
227 + "/notifications/{notification_id}/update",
228 + "/notifications/{notification_id}/update",
228 "notifications_delete": ADMIN_PREFIX
229 "notifications_delete": ADMIN_PREFIX
229 + "/notifications/{notification_id}/delete",
230 + "/notifications/{notification_id}/delete",
230 "my_account": ADMIN_PREFIX + "/my_account/profile",
231 "my_account": ADMIN_PREFIX + "/my_account/profile",
231 "my_account_edit": ADMIN_PREFIX + "/my_account/edit",
232 "my_account_edit": ADMIN_PREFIX + "/my_account/edit",
232 "my_account_update": ADMIN_PREFIX + "/my_account/update",
233 "my_account_update": ADMIN_PREFIX + "/my_account/update",
233 "my_account_pullrequests": ADMIN_PREFIX + "/my_account/pull_requests",
234 "my_account_pullrequests": ADMIN_PREFIX + "/my_account/pull_requests",
234 "my_account_pullrequests_data": ADMIN_PREFIX + "/my_account/pull_requests/data",
235 "my_account_pullrequests_data": ADMIN_PREFIX + "/my_account/pull_requests/data",
235 "my_account_emails": ADMIN_PREFIX + "/my_account/emails",
236 "my_account_emails": ADMIN_PREFIX + "/my_account/emails",
236 "my_account_emails_add": ADMIN_PREFIX + "/my_account/emails/new",
237 "my_account_emails_add": ADMIN_PREFIX + "/my_account/emails/new",
237 "my_account_emails_delete": ADMIN_PREFIX + "/my_account/emails/delete",
238 "my_account_emails_delete": ADMIN_PREFIX + "/my_account/emails/delete",
238 "my_account_password": ADMIN_PREFIX + "/my_account/password",
239 "my_account_password": ADMIN_PREFIX + "/my_account/password",
239 "my_account_password_update": ADMIN_PREFIX + "/my_account/password/update",
240 "my_account_password_update": ADMIN_PREFIX + "/my_account/password/update",
240 "my_account_repos": ADMIN_PREFIX + "/my_account/repos",
241 "my_account_repos": ADMIN_PREFIX + "/my_account/repos",
241 "my_account_watched": ADMIN_PREFIX + "/my_account/watched",
242 "my_account_watched": ADMIN_PREFIX + "/my_account/watched",
242 "my_account_perms": ADMIN_PREFIX + "/my_account/perms",
243 "my_account_perms": ADMIN_PREFIX + "/my_account/perms",
243 "my_account_notifications": ADMIN_PREFIX + "/my_account/notifications",
244 "my_account_notifications": ADMIN_PREFIX + "/my_account/notifications",
244 "my_account_ssh_keys": ADMIN_PREFIX + "/my_account/ssh_keys",
245 "my_account_ssh_keys": ADMIN_PREFIX + "/my_account/ssh_keys",
245 "my_account_ssh_keys_generate": ADMIN_PREFIX + "/my_account/ssh_keys/generate",
246 "my_account_ssh_keys_generate": ADMIN_PREFIX + "/my_account/ssh_keys/generate",
246 "my_account_ssh_keys_add": ADMIN_PREFIX + "/my_account/ssh_keys/new",
247 "my_account_ssh_keys_add": ADMIN_PREFIX + "/my_account/ssh_keys/new",
247 "my_account_ssh_keys_delete": ADMIN_PREFIX + "/my_account/ssh_keys/delete",
248 "my_account_ssh_keys_delete": ADMIN_PREFIX + "/my_account/ssh_keys/delete",
248 "pullrequest_show_all": "/{repo_name}/pull-request",
249 "pullrequest_show_all": "/{repo_name}/pull-request",
249 "pullrequest_show_all_data": "/{repo_name}/pull-request-data",
250 "pullrequest_show_all_data": "/{repo_name}/pull-request-data",
250 "bookmarks_home": "/{repo_name}/bookmarks",
251 "bookmarks_home": "/{repo_name}/bookmarks",
251 "branches_home": "/{repo_name}/branches",
252 "branches_home": "/{repo_name}/branches",
252 "tags_home": "/{repo_name}/tags",
253 "tags_home": "/{repo_name}/tags",
253 "repo_changelog": "/{repo_name}/changelog",
254 "repo_changelog": "/{repo_name}/changelog",
254 "repo_commits": "/{repo_name}/commits",
255 "repo_commits": "/{repo_name}/commits",
255 "repo_commits_file": "/{repo_name}/commits/{commit_id}/{f_path}",
256 "repo_commits_file": "/{repo_name}/commits/{commit_id}/{f_path}",
256 "repo_commits_elements": "/{repo_name}/commits_elements",
257 "repo_commits_elements": "/{repo_name}/commits_elements",
257 "repo_commit": "/{repo_name}/changeset/{commit_id}",
258 "repo_commit": "/{repo_name}/changeset/{commit_id}",
258 "repo_commit_comment_create": "/{repo_name}/changeset/{commit_id}/comment/create",
259 "repo_commit_comment_create": "/{repo_name}/changeset/{commit_id}/comment/create",
259 "repo_commit_comment_preview": "/{repo_name}/changeset/{commit_id}/comment/preview",
260 "repo_commit_comment_preview": "/{repo_name}/changeset/{commit_id}/comment/preview",
260 "repo_commit_comment_delete": "/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete",
261 "repo_commit_comment_delete": "/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete",
261 "repo_commit_comment_edit": "/{repo_name}/changeset/{commit_id}/comment/{comment_id}/edit",
262 "repo_commit_comment_edit": "/{repo_name}/changeset/{commit_id}/comment/{comment_id}/edit",
262 "repo_commit_children": "/{repo_name}/changeset_children/{commit_id}",
263 "repo_commit_children": "/{repo_name}/changeset_children/{commit_id}",
263 "repo_commit_parents": "/{repo_name}/changeset_parents/{commit_id}",
264 "repo_commit_parents": "/{repo_name}/changeset_parents/{commit_id}",
264 "repo_commit_patch": "/{repo_name}/changeset-patch/{commit_id}",
265 "repo_commit_patch": "/{repo_name}/changeset-patch/{commit_id}",
265 "repo_commit_download": "/{repo_name}/changeset-download/{commit_id}",
266 "repo_commit_download": "/{repo_name}/changeset-download/{commit_id}",
266 "repo_commit_data": "/{repo_name}/changeset-data/{commit_id}",
267 "repo_commit_data": "/{repo_name}/changeset-data/{commit_id}",
267 "repo_compare": "/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}",
268 "repo_compare": "/{repo_name}/compare/{source_ref_type}@{source_ref}...{target_ref_type}@{target_ref}",
268 "repo_compare_select": "/{repo_name}/compare",
269 "repo_compare_select": "/{repo_name}/compare",
269 "rss_feed_home": "/{repo_name}/feed-rss",
270 "rss_feed_home": "/{repo_name}/feed-rss",
270 "atom_feed_home": "/{repo_name}/feed-atom",
271 "atom_feed_home": "/{repo_name}/feed-atom",
271 "rss_feed_home_old": "/{repo_name}/feed/rss",
272 "rss_feed_home_old": "/{repo_name}/feed/rss",
272 "atom_feed_home_old": "/{repo_name}/feed/atom",
273 "atom_feed_home_old": "/{repo_name}/feed/atom",
273 "repo_fork_new": "/{repo_name}/fork",
274 "repo_fork_new": "/{repo_name}/fork",
274 "repo_fork_create": "/{repo_name}/fork/create",
275 "repo_fork_create": "/{repo_name}/fork/create",
275 "repo_forks_show_all": "/{repo_name}/forks",
276 "repo_forks_show_all": "/{repo_name}/forks",
276 "repo_forks_data": "/{repo_name}/forks/data",
277 "repo_forks_data": "/{repo_name}/forks/data",
277 "edit_repo_issuetracker": "/{repo_name}/settings/issue_trackers",
278 "edit_repo_issuetracker": "/{repo_name}/settings/issue_trackers",
278 "edit_repo_issuetracker_test": "/{repo_name}/settings/issue_trackers/test",
279 "edit_repo_issuetracker_test": "/{repo_name}/settings/issue_trackers/test",
279 "edit_repo_issuetracker_delete": "/{repo_name}/settings/issue_trackers/delete",
280 "edit_repo_issuetracker_delete": "/{repo_name}/settings/issue_trackers/delete",
280 "edit_repo_issuetracker_update": "/{repo_name}/settings/issue_trackers/update",
281 "edit_repo_issuetracker_update": "/{repo_name}/settings/issue_trackers/update",
281 "edit_repo_maintenance": "/{repo_name}/settings/maintenance",
282 "edit_repo_maintenance": "/{repo_name}/settings/maintenance",
282 "edit_repo_maintenance_execute": "/{repo_name}/settings/maintenance/execute",
283 "edit_repo_maintenance_execute": "/{repo_name}/settings/maintenance/execute",
283 "repo_changelog_file": "/{repo_name}/changelog/{commit_id}/{f_path}",
284 "repo_changelog_file": "/{repo_name}/changelog/{commit_id}/{f_path}",
284 "pullrequest_repo_refs": "/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}",
285 "pullrequest_repo_refs": "/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}",
285 "pullrequest_repo_targets": "/{repo_name}/pull-request/repo-destinations",
286 "pullrequest_repo_targets": "/{repo_name}/pull-request/repo-destinations",
286 "pullrequest_new": "/{repo_name}/pull-request/new",
287 "pullrequest_new": "/{repo_name}/pull-request/new",
287 "pullrequest_create": "/{repo_name}/pull-request/create",
288 "pullrequest_create": "/{repo_name}/pull-request/create",
288 "pullrequest_update": "/{repo_name}/pull-request/{pull_request_id}/update",
289 "pullrequest_update": "/{repo_name}/pull-request/{pull_request_id}/update",
289 "pullrequest_merge": "/{repo_name}/pull-request/{pull_request_id}/merge",
290 "pullrequest_merge": "/{repo_name}/pull-request/{pull_request_id}/merge",
290 "pullrequest_delete": "/{repo_name}/pull-request/{pull_request_id}/delete",
291 "pullrequest_delete": "/{repo_name}/pull-request/{pull_request_id}/delete",
291 "pullrequest_comment_create": "/{repo_name}/pull-request/{pull_request_id}/comment",
292 "pullrequest_comment_create": "/{repo_name}/pull-request/{pull_request_id}/comment",
292 "pullrequest_comment_delete": "/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete",
293 "pullrequest_comment_delete": "/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete",
293 "pullrequest_comment_edit": "/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit",
294 "pullrequest_comment_edit": "/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit",
294 "edit_repo_caches": "/{repo_name}/settings/caches",
295 "edit_repo_caches": "/{repo_name}/settings/caches",
295 "edit_repo_perms": "/{repo_name}/settings/permissions",
296 "edit_repo_perms": "/{repo_name}/settings/permissions",
296 "edit_repo_fields": "/{repo_name}/settings/fields",
297 "edit_repo_fields": "/{repo_name}/settings/fields",
297 "edit_repo_remote": "/{repo_name}/settings/remote",
298 "edit_repo_remote": "/{repo_name}/settings/remote",
298 "edit_repo_statistics": "/{repo_name}/settings/statistics",
299 "edit_repo_statistics": "/{repo_name}/settings/statistics",
299 "edit_repo_advanced": "/{repo_name}/settings/advanced",
300 "edit_repo_advanced": "/{repo_name}/settings/advanced",
300 "edit_repo_advanced_delete": "/{repo_name}/settings/advanced/delete",
301 "edit_repo_advanced_delete": "/{repo_name}/settings/advanced/delete",
301 "edit_repo_advanced_archive": "/{repo_name}/settings/advanced/archive",
302 "edit_repo_advanced_archive": "/{repo_name}/settings/advanced/archive",
302 "edit_repo_advanced_fork": "/{repo_name}/settings/advanced/fork",
303 "edit_repo_advanced_fork": "/{repo_name}/settings/advanced/fork",
303 "edit_repo_advanced_locking": "/{repo_name}/settings/advanced/locking",
304 "edit_repo_advanced_locking": "/{repo_name}/settings/advanced/locking",
304 "edit_repo_advanced_journal": "/{repo_name}/settings/advanced/journal",
305 "edit_repo_advanced_journal": "/{repo_name}/settings/advanced/journal",
305 "repo_stats": "/{repo_name}/repo_stats/{commit_id}",
306 "repo_stats": "/{repo_name}/repo_stats/{commit_id}",
306 "repo_refs_data": "/{repo_name}/refs-data",
307 "repo_refs_data": "/{repo_name}/refs-data",
307 "repo_refs_changelog_data": "/{repo_name}/refs-data-changelog",
308 "repo_refs_changelog_data": "/{repo_name}/refs-data-changelog",
308 "repo_artifacts_stream_store": "/_file_store/stream-upload",
309 "repo_artifacts_stream_store": "/_file_store/stream-upload",
309 }
310 }
310
311
311
312
312 def route_path(name, params=None, **kwargs):
313 def route_path(name, params=None, **kwargs):
313 import urllib.parse
314 import urllib.parse
314
315
315 base_url = get_url_defs()[name].format(**kwargs)
316 base_url = get_url_defs()[name].format(**kwargs)
316
317
317 if params:
318 if params:
318 base_url = f"{base_url}?{urllib.parse.urlencode(params)}"
319 base_url = f"{base_url}?{urllib.parse.urlencode(params)}"
319 return base_url
320 return base_url
General Comments 0
You need to be logged in to leave comments. Login now