##// END OF EJS Templates
files: split add/upload templaates
marcink -
r3710:76146f20 new-ui
parent child Browse files
Show More
@@ -0,0 +1,190 b''
1 <%inherit file="/base/base.mako"/>
2
3 <%def name="title()">
4 ${_('%s Files Add') % c.repo_name}
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
8 </%def>
9
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
12 </%def>
13
14 <%def name="breadcrumbs_links()">
15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
16 </%def>
17
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
20 </%def>
21
22 <%def name="main()">
23 <div class="box">
24
25 <div class="edit-file-title">
26 ${self.breadcrumbs()}
27 </div>
28
29 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
30 <div class="edit-file-fieldset">
31 <div class="fieldset">
32 <div id="destination-label" class="left-label">
33 ${_('Path')}:
34 </div>
35 <div class="right-content">
36 <div>
37 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))} /
38 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
39 </div>
40 </div>
41 </div>
42
43 <div id="upload_file_container" class="fieldset">
44 <div class="filename-label left-label">
45 ${_('Filename')}:
46 </div>
47 <div class="right-content">
48 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
49 </div>
50 <div class="filename-label left-label file-upload-label">
51 ${_('Upload file')}:
52 </div>
53 <div class="right-content file-upload-input">
54 <label for="upload_file" class="btn btn-default">Browse</label>
55
56 <input type="file" name="upload_file" id="upload_file">
57 </div>
58 </div>
59
60 </div>
61
62 <div class="table">
63 <div id="files_data">
64 <div id="codeblock" class="codeblock">
65 <div class="code-header form" id="set_mode_header">
66 <div class="fields">
67 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
68 <label for="line_wrap">${_('line wraps')}</label>
69 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
70
71 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
72 </div>
73 </div>
74 <div id="editor_container">
75 <pre id="editor_pre"></pre>
76 <textarea id="editor" name="content" ></textarea>
77 <div id="editor_preview"></div>
78 </div>
79 </div>
80 </div>
81 </div>
82
83 <div class="edit-file-fieldset">
84 <div class="fieldset">
85 <div id="commit-message-label" class="commit-message-label left-label">
86 ${_('Commit Message')}:
87 </div>
88 <div class="right-content">
89 <div class="message">
90 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
91 </div>
92 </div>
93 </div>
94 <div class="pull-right">
95 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
96 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
97 </div>
98 </div>
99 ${h.end_form()}
100 </div>
101 <script type="text/javascript">
102
103 $('#commit_btn').on('click', function() {
104 var button = $(this);
105 if (button.hasClass('clicked')) {
106 button.attr('disabled', true);
107 } else {
108 button.addClass('clicked');
109 }
110 });
111
112 var hide_upload = function(){
113 $('#files_data').show();
114 $('#upload_file_container').hide();
115 $('#filename_container').show();
116 };
117
118 $('#file_enable').on('click', function(e){
119 e.preventDefault();
120 hide_upload();
121 });
122
123 var renderer = "";
124 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
125 var myCodeMirror = initCodeMirror('editor', reset_url, false);
126
127 var modes_select = $('#set_mode');
128 fillCodeMirrorOptions(modes_select);
129
130 var filename_selector = '#filename';
131 var callback = function(filename, mimetype, mode){
132 CodeMirrorPreviewEnable(mode);
133 };
134 // on change of select field set mode
135 setCodeMirrorModeFromSelect(
136 modes_select, filename_selector, myCodeMirror, callback);
137
138 // on entering the new filename set mode, from given extension
139 setCodeMirrorModeFromInput(
140 modes_select, filename_selector, myCodeMirror, callback);
141
142 // if the file is renderable set line wraps automatically
143 if (renderer !== ""){
144 var line_wrap = 'on';
145 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
146 setCodeMirrorLineWrap(myCodeMirror, true);
147 }
148
149 // on select line wraps change the editor
150 $('#line_wrap').on('change', function(e){
151 var selected = e.currentTarget;
152 var line_wraps = {'on': true, 'off': false}[selected.value];
153 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
154 });
155
156 // render preview/edit button
157 $('#render_preview').on('click', function(e){
158 if($(this).hasClass('preview')){
159 $(this).removeClass('preview');
160 $(this).html("${_('Edit')}");
161 $('#editor_preview').show();
162 $(myCodeMirror.getWrapperElement()).hide();
163
164 var possible_renderer = {
165 'rst':'rst',
166 'markdown':'markdown',
167 'gfm': 'markdown'}[myCodeMirror.getMode().name];
168 var _text = myCodeMirror.getValue();
169 var _renderer = possible_renderer || DEFAULT_RENDERER;
170 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
171 $('#editor_preview').html(_gettext('Loading ...'));
172 var url = pyroutes.url('repo_commit_comment_preview',
173 {'repo_name': '${c.repo_name}',
174 'commit_id': '${c.commit.raw_id}'});
175
176 ajaxPOST(url, post_data, function(o){
177 $('#editor_preview').html(o);
178 })
179 }
180 else{
181 $(this).addClass('preview');
182 $(this).html("${_('Preview')}");
183 $('#editor_preview').hide();
184 $(myCodeMirror.getWrapperElement()).show();
185 }
186 });
187 $('#filename').focus();
188
189 </script>
190 </%def>
@@ -1,488 +1,492 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.apps._base import add_route_with_slash
21 21
22 22
23 23 def includeme(config):
24 24
25 25 # repo creating checks, special cases that aren't repo routes
26 26 config.add_route(
27 27 name='repo_creating',
28 28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29 29
30 30 config.add_route(
31 31 name='repo_creating_check',
32 32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33 33
34 34 # Summary
35 35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 36 # all pattern
37 37 config.add_route(
38 38 name='repo_summary_explicit',
39 39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 40 config.add_route(
41 41 name='repo_summary_commits',
42 42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43 43
44 44 # Commits
45 45 config.add_route(
46 46 name='repo_commit',
47 47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48 48
49 49 config.add_route(
50 50 name='repo_commit_children',
51 51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52 52
53 53 config.add_route(
54 54 name='repo_commit_parents',
55 55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56 56
57 57 config.add_route(
58 58 name='repo_commit_raw',
59 59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60 60
61 61 config.add_route(
62 62 name='repo_commit_patch',
63 63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64 64
65 65 config.add_route(
66 66 name='repo_commit_download',
67 67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68 68
69 69 config.add_route(
70 70 name='repo_commit_data',
71 71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72 72
73 73 config.add_route(
74 74 name='repo_commit_comment_create',
75 75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76 76
77 77 config.add_route(
78 78 name='repo_commit_comment_preview',
79 79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80 80
81 81 config.add_route(
82 82 name='repo_commit_comment_delete',
83 83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84 84
85 85 # still working url for backward compat.
86 86 config.add_route(
87 87 name='repo_commit_raw_deprecated',
88 88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89 89
90 90 # Files
91 91 config.add_route(
92 92 name='repo_archivefile',
93 93 pattern='/{repo_name:.*?[^/]}/archive/{fname:.*}', repo_route=True)
94 94
95 95 config.add_route(
96 96 name='repo_files_diff',
97 97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 98 config.add_route( # legacy route to make old links work
99 99 name='repo_files_diff_2way_redirect',
100 100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101 101
102 102 config.add_route(
103 103 name='repo_files',
104 104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 105 config.add_route(
106 106 name='repo_files:default_path',
107 107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 108 config.add_route(
109 109 name='repo_files:default_commit',
110 110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111 111
112 112 config.add_route(
113 113 name='repo_files:rendered',
114 114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115 115
116 116 config.add_route(
117 117 name='repo_files:annotated',
118 118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 119 config.add_route(
120 120 name='repo_files:annotated_previous',
121 121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122 122
123 123 config.add_route(
124 124 name='repo_nodetree_full',
125 125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 126 config.add_route(
127 127 name='repo_nodetree_full:default_path',
128 128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129 129
130 130 config.add_route(
131 131 name='repo_files_nodelist',
132 132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133 133
134 134 config.add_route(
135 135 name='repo_file_raw',
136 136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137 137
138 138 config.add_route(
139 139 name='repo_file_download',
140 140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 141 config.add_route( # backward compat to keep old links working
142 142 name='repo_file_download:legacy',
143 143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 144 repo_route=True)
145 145
146 146 config.add_route(
147 147 name='repo_file_history',
148 148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149 149
150 150 config.add_route(
151 151 name='repo_file_authors',
152 152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153 153
154 154 config.add_route(
155 155 name='repo_files_remove_file',
156 156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 157 repo_route=True)
158 158 config.add_route(
159 159 name='repo_files_delete_file',
160 160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 161 repo_route=True)
162 162 config.add_route(
163 163 name='repo_files_edit_file',
164 164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 165 repo_route=True)
166 166 config.add_route(
167 167 name='repo_files_update_file',
168 168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 169 repo_route=True)
170 170 config.add_route(
171 171 name='repo_files_add_file',
172 172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 173 repo_route=True)
174 174 config.add_route(
175 name='repo_files_upload_file',
176 pattern='/{repo_name:.*?[^/]}/upload_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
178 config.add_route(
175 179 name='repo_files_create_file',
176 180 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 181 repo_route=True)
178 182
179 183 # Refs data
180 184 config.add_route(
181 185 name='repo_refs_data',
182 186 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183 187
184 188 config.add_route(
185 189 name='repo_refs_changelog_data',
186 190 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187 191
188 192 config.add_route(
189 193 name='repo_stats',
190 194 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191 195
192 196 # Changelog
193 197 config.add_route(
194 198 name='repo_changelog',
195 199 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 200 config.add_route(
197 201 name='repo_changelog_file',
198 202 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 203 config.add_route(
200 204 name='repo_changelog_elements',
201 205 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202 206 config.add_route(
203 207 name='repo_changelog_elements_file',
204 208 pattern='/{repo_name:.*?[^/]}/changelog_elements/{commit_id}/{f_path:.*}', repo_route=True)
205 209
206 210 # Compare
207 211 config.add_route(
208 212 name='repo_compare_select',
209 213 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
210 214
211 215 config.add_route(
212 216 name='repo_compare',
213 217 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
214 218
215 219 # Tags
216 220 config.add_route(
217 221 name='tags_home',
218 222 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
219 223
220 224 # Branches
221 225 config.add_route(
222 226 name='branches_home',
223 227 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
224 228
225 229 # Bookmarks
226 230 config.add_route(
227 231 name='bookmarks_home',
228 232 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
229 233
230 234 # Forks
231 235 config.add_route(
232 236 name='repo_fork_new',
233 237 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 238 repo_forbid_when_archived=True,
235 239 repo_accepted_types=['hg', 'git'])
236 240
237 241 config.add_route(
238 242 name='repo_fork_create',
239 243 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
240 244 repo_forbid_when_archived=True,
241 245 repo_accepted_types=['hg', 'git'])
242 246
243 247 config.add_route(
244 248 name='repo_forks_show_all',
245 249 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
246 250 repo_accepted_types=['hg', 'git'])
247 251 config.add_route(
248 252 name='repo_forks_data',
249 253 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
250 254 repo_accepted_types=['hg', 'git'])
251 255
252 256 # Pull Requests
253 257 config.add_route(
254 258 name='pullrequest_show',
255 259 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
256 260 repo_route=True)
257 261
258 262 config.add_route(
259 263 name='pullrequest_show_all',
260 264 pattern='/{repo_name:.*?[^/]}/pull-request',
261 265 repo_route=True, repo_accepted_types=['hg', 'git'])
262 266
263 267 config.add_route(
264 268 name='pullrequest_show_all_data',
265 269 pattern='/{repo_name:.*?[^/]}/pull-request-data',
266 270 repo_route=True, repo_accepted_types=['hg', 'git'])
267 271
268 272 config.add_route(
269 273 name='pullrequest_repo_refs',
270 274 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
271 275 repo_route=True)
272 276
273 277 config.add_route(
274 278 name='pullrequest_repo_targets',
275 279 pattern='/{repo_name:.*?[^/]}/pull-request/repo-targets',
276 280 repo_route=True)
277 281
278 282 config.add_route(
279 283 name='pullrequest_new',
280 284 pattern='/{repo_name:.*?[^/]}/pull-request/new',
281 285 repo_route=True, repo_accepted_types=['hg', 'git'],
282 286 repo_forbid_when_archived=True)
283 287
284 288 config.add_route(
285 289 name='pullrequest_create',
286 290 pattern='/{repo_name:.*?[^/]}/pull-request/create',
287 291 repo_route=True, repo_accepted_types=['hg', 'git'],
288 292 repo_forbid_when_archived=True)
289 293
290 294 config.add_route(
291 295 name='pullrequest_update',
292 296 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
293 297 repo_route=True, repo_forbid_when_archived=True)
294 298
295 299 config.add_route(
296 300 name='pullrequest_merge',
297 301 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
298 302 repo_route=True, repo_forbid_when_archived=True)
299 303
300 304 config.add_route(
301 305 name='pullrequest_delete',
302 306 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
303 307 repo_route=True, repo_forbid_when_archived=True)
304 308
305 309 config.add_route(
306 310 name='pullrequest_comment_create',
307 311 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
308 312 repo_route=True)
309 313
310 314 config.add_route(
311 315 name='pullrequest_comment_delete',
312 316 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
313 317 repo_route=True, repo_accepted_types=['hg', 'git'])
314 318
315 319 # Artifacts, (EE feature)
316 320 config.add_route(
317 321 name='repo_artifacts_list',
318 322 pattern='/{repo_name:.*?[^/]}/artifacts', repo_route=True)
319 323
320 324 # Settings
321 325 config.add_route(
322 326 name='edit_repo',
323 327 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
324 328 # update is POST on edit_repo
325 329
326 330 # Settings advanced
327 331 config.add_route(
328 332 name='edit_repo_advanced',
329 333 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
330 334 config.add_route(
331 335 name='edit_repo_advanced_archive',
332 336 pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True)
333 337 config.add_route(
334 338 name='edit_repo_advanced_delete',
335 339 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
336 340 config.add_route(
337 341 name='edit_repo_advanced_locking',
338 342 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
339 343 config.add_route(
340 344 name='edit_repo_advanced_journal',
341 345 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
342 346 config.add_route(
343 347 name='edit_repo_advanced_fork',
344 348 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
345 349
346 350 config.add_route(
347 351 name='edit_repo_advanced_hooks',
348 352 pattern='/{repo_name:.*?[^/]}/settings/advanced/hooks', repo_route=True)
349 353
350 354 # Caches
351 355 config.add_route(
352 356 name='edit_repo_caches',
353 357 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
354 358
355 359 # Permissions
356 360 config.add_route(
357 361 name='edit_repo_perms',
358 362 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
359 363
360 364 # Permissions Branch (EE feature)
361 365 config.add_route(
362 366 name='edit_repo_perms_branch',
363 367 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
364 368 config.add_route(
365 369 name='edit_repo_perms_branch_delete',
366 370 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions/{rule_id}/delete',
367 371 repo_route=True)
368 372
369 373 # Maintenance
370 374 config.add_route(
371 375 name='edit_repo_maintenance',
372 376 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
373 377
374 378 config.add_route(
375 379 name='edit_repo_maintenance_execute',
376 380 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
377 381
378 382 # Fields
379 383 config.add_route(
380 384 name='edit_repo_fields',
381 385 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
382 386 config.add_route(
383 387 name='edit_repo_fields_create',
384 388 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
385 389 config.add_route(
386 390 name='edit_repo_fields_delete',
387 391 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
388 392
389 393 # Locking
390 394 config.add_route(
391 395 name='repo_edit_toggle_locking',
392 396 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
393 397
394 398 # Remote
395 399 config.add_route(
396 400 name='edit_repo_remote',
397 401 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
398 402 config.add_route(
399 403 name='edit_repo_remote_pull',
400 404 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
401 405 config.add_route(
402 406 name='edit_repo_remote_push',
403 407 pattern='/{repo_name:.*?[^/]}/settings/remote/push', repo_route=True)
404 408
405 409 # Statistics
406 410 config.add_route(
407 411 name='edit_repo_statistics',
408 412 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
409 413 config.add_route(
410 414 name='edit_repo_statistics_reset',
411 415 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
412 416
413 417 # Issue trackers
414 418 config.add_route(
415 419 name='edit_repo_issuetracker',
416 420 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
417 421 config.add_route(
418 422 name='edit_repo_issuetracker_test',
419 423 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
420 424 config.add_route(
421 425 name='edit_repo_issuetracker_delete',
422 426 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
423 427 config.add_route(
424 428 name='edit_repo_issuetracker_update',
425 429 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
426 430
427 431 # VCS Settings
428 432 config.add_route(
429 433 name='edit_repo_vcs',
430 434 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
431 435 config.add_route(
432 436 name='edit_repo_vcs_update',
433 437 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
434 438
435 439 # svn pattern
436 440 config.add_route(
437 441 name='edit_repo_vcs_svn_pattern_delete',
438 442 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
439 443
440 444 # Repo Review Rules (EE feature)
441 445 config.add_route(
442 446 name='repo_reviewers',
443 447 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
444 448
445 449 config.add_route(
446 450 name='repo_default_reviewers_data',
447 451 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
448 452
449 453 # Repo Automation (EE feature)
450 454 config.add_route(
451 455 name='repo_automation',
452 456 pattern='/{repo_name:.*?[^/]}/settings/automation', repo_route=True)
453 457
454 458 # Strip
455 459 config.add_route(
456 460 name='edit_repo_strip',
457 461 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
458 462
459 463 config.add_route(
460 464 name='strip_check',
461 465 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
462 466
463 467 config.add_route(
464 468 name='strip_execute',
465 469 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
466 470
467 471 # Audit logs
468 472 config.add_route(
469 473 name='edit_repo_audit_logs',
470 474 pattern='/{repo_name:.*?[^/]}/settings/audit_logs', repo_route=True)
471 475
472 476 # ATOM/RSS Feed
473 477 config.add_route(
474 478 name='rss_feed_home',
475 479 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
476 480
477 481 config.add_route(
478 482 name='atom_feed_home',
479 483 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
480 484
481 485 # NOTE(marcink): needs to be at the end for catch-all
482 486 add_route_with_slash(
483 487 config,
484 488 name='repo_summary',
485 489 pattern='/{repo_name:.*?[^/]}', repo_route=True)
486 490
487 491 # Scan module for configuration decorators.
488 492 config.scan('.views', ignore='.tests')
@@ -1,1403 +1,1406 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27 import urllib
28 28
29 29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 30 from pyramid.view import view_config
31 31 from pyramid.renderers import render
32 32 from pyramid.response import Response
33 33
34 34 import rhodecode
35 35 from rhodecode.apps._base import RepoAppView
36 36
37 37
38 38 from rhodecode.lib import diffs, helpers as h, rc_cache
39 39 from rhodecode.lib import audit_logger
40 40 from rhodecode.lib.view_utils import parse_path_ref
41 41 from rhodecode.lib.exceptions import NonRelativePathError
42 42 from rhodecode.lib.codeblocks import (
43 43 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 44 from rhodecode.lib.utils2 import (
45 45 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1)
46 46 from rhodecode.lib.auth import (
47 47 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 48 from rhodecode.lib.vcs import path as vcspath
49 49 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 50 from rhodecode.lib.vcs.conf import settings
51 51 from rhodecode.lib.vcs.nodes import FileNode
52 52 from rhodecode.lib.vcs.exceptions import (
53 53 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 54 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 55 NodeDoesNotExistError, CommitError, NodeError)
56 56
57 57 from rhodecode.model.scm import ScmModel
58 58 from rhodecode.model.db import Repository
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class RepoFilesView(RepoAppView):
64 64
65 65 @staticmethod
66 66 def adjust_file_path_for_svn(f_path, repo):
67 67 """
68 68 Computes the relative path of `f_path`.
69 69
70 70 This is mainly based on prefix matching of the recognized tags and
71 71 branches in the underlying repository.
72 72 """
73 73 tags_and_branches = itertools.chain(
74 74 repo.branches.iterkeys(),
75 75 repo.tags.iterkeys())
76 76 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 77
78 78 for name in tags_and_branches:
79 79 if f_path.startswith('{}/'.format(name)):
80 80 f_path = vcspath.relpath(f_path, name)
81 81 break
82 82 return f_path
83 83
84 84 def load_default_context(self):
85 85 c = self._get_local_tmpl_context(include_app_defaults=True)
86 86 c.rhodecode_repo = self.rhodecode_vcs_repo
87 87 c.enable_downloads = self.db_repo.enable_downloads
88 88 return c
89 89
90 90 def _ensure_not_locked(self):
91 91 _ = self.request.translate
92 92
93 93 repo = self.db_repo
94 94 if repo.enable_locking and repo.locked[0]:
95 95 h.flash(_('This repository has been locked by %s on %s')
96 96 % (h.person_by_id(repo.locked[0]),
97 97 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 98 'warning')
99 99 files_url = h.route_path(
100 100 'repo_files:default_path',
101 101 repo_name=self.db_repo_name, commit_id='tip')
102 102 raise HTTPFound(files_url)
103 103
104 104 def check_branch_permission(self, branch_name):
105 105 _ = self.request.translate
106 106
107 107 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
108 108 self.db_repo_name, branch_name)
109 109 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
110 110 h.flash(
111 111 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
112 112 'warning')
113 113 files_url = h.route_path(
114 114 'repo_files:default_path',
115 115 repo_name=self.db_repo_name, commit_id='tip')
116 116 raise HTTPFound(files_url)
117 117
118 118 def _get_commit_and_path(self):
119 119 default_commit_id = self.db_repo.landing_rev[1]
120 120 default_f_path = '/'
121 121
122 122 commit_id = self.request.matchdict.get(
123 123 'commit_id', default_commit_id)
124 124 f_path = self._get_f_path(self.request.matchdict, default_f_path)
125 125 return commit_id, f_path
126 126
127 127 def _get_default_encoding(self, c):
128 128 enc_list = getattr(c, 'default_encodings', [])
129 129 return enc_list[0] if enc_list else 'UTF-8'
130 130
131 131 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
132 132 """
133 133 This is a safe way to get commit. If an error occurs it redirects to
134 134 tip with proper message
135 135
136 136 :param commit_id: id of commit to fetch
137 137 :param redirect_after: toggle redirection
138 138 """
139 139 _ = self.request.translate
140 140
141 141 try:
142 142 return self.rhodecode_vcs_repo.get_commit(commit_id)
143 143 except EmptyRepositoryError:
144 144 if not redirect_after:
145 145 return None
146 146
147 147 _url = h.route_path(
148 148 'repo_files_add_file',
149 149 repo_name=self.db_repo_name, commit_id=0, f_path='',
150 150 _anchor='edit')
151 151
152 152 if h.HasRepoPermissionAny(
153 153 'repository.write', 'repository.admin')(self.db_repo_name):
154 154 add_new = h.link_to(
155 155 _('Click here to add a new file.'), _url, class_="alert-link")
156 156 else:
157 157 add_new = ""
158 158
159 159 h.flash(h.literal(
160 160 _('There are no files yet. %s') % add_new), category='warning')
161 161 raise HTTPFound(
162 162 h.route_path('repo_summary', repo_name=self.db_repo_name))
163 163
164 164 except (CommitDoesNotExistError, LookupError):
165 165 msg = _('No such commit exists for this repository')
166 166 h.flash(msg, category='error')
167 167 raise HTTPNotFound()
168 168 except RepositoryError as e:
169 169 h.flash(safe_str(h.escape(e)), category='error')
170 170 raise HTTPNotFound()
171 171
172 172 def _get_filenode_or_redirect(self, commit_obj, path):
173 173 """
174 174 Returns file_node, if error occurs or given path is directory,
175 175 it'll redirect to top level path
176 176 """
177 177 _ = self.request.translate
178 178
179 179 try:
180 180 file_node = commit_obj.get_node(path)
181 181 if file_node.is_dir():
182 182 raise RepositoryError('The given path is a directory')
183 183 except CommitDoesNotExistError:
184 184 log.exception('No such commit exists for this repository')
185 185 h.flash(_('No such commit exists for this repository'), category='error')
186 186 raise HTTPNotFound()
187 187 except RepositoryError as e:
188 188 log.warning('Repository error while fetching '
189 189 'filenode `%s`. Err:%s', path, e)
190 190 h.flash(safe_str(h.escape(e)), category='error')
191 191 raise HTTPNotFound()
192 192
193 193 return file_node
194 194
195 195 def _is_valid_head(self, commit_id, repo):
196 196 branch_name = sha_commit_id = ''
197 197 is_head = False
198 198
199 199 if h.is_svn(repo) and not repo.is_empty():
200 200 # Note: Subversion only has one head.
201 201 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
202 202 is_head = True
203 203 return branch_name, sha_commit_id, is_head
204 204
205 205 for _branch_name, branch_commit_id in repo.branches.items():
206 206 # simple case we pass in branch name, it's a HEAD
207 207 if commit_id == _branch_name:
208 208 is_head = True
209 209 branch_name = _branch_name
210 210 sha_commit_id = branch_commit_id
211 211 break
212 212 # case when we pass in full sha commit_id, which is a head
213 213 elif commit_id == branch_commit_id:
214 214 is_head = True
215 215 branch_name = _branch_name
216 216 sha_commit_id = branch_commit_id
217 217 break
218 218
219 219 # checked branches, means we only need to try to get the branch/commit_sha
220 220 if not repo.is_empty:
221 221 commit = repo.get_commit(commit_id=commit_id)
222 222 if commit:
223 223 branch_name = commit.branch
224 224 sha_commit_id = commit.raw_id
225 225
226 226 return branch_name, sha_commit_id, is_head
227 227
228 228 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
229 229
230 230 repo_id = self.db_repo.repo_id
231 231 force_recache = self.get_recache_flag()
232 232
233 233 cache_seconds = safe_int(
234 234 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
235 235 cache_on = not force_recache and cache_seconds > 0
236 236 log.debug(
237 237 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
238 238 'with caching: %s[TTL: %ss]' % (
239 239 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
240 240
241 241 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
242 242 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
243 243
244 244 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
245 245 condition=cache_on)
246 246 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
247 247 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
248 248 ver, repo_id, commit_id, f_path)
249 249
250 250 c.full_load = full_load
251 251 return render(
252 252 'rhodecode:templates/files/files_browser_tree.mako',
253 253 self._get_template_context(c), self.request)
254 254
255 255 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
256 256
257 257 def _get_archive_spec(self, fname):
258 258 log.debug('Detecting archive spec for: `%s`', fname)
259 259
260 260 fileformat = None
261 261 ext = None
262 262 content_type = None
263 263 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
264 264 content_type, extension = ext_data
265 265
266 266 if fname.endswith(extension):
267 267 fileformat = a_type
268 268 log.debug('archive is of type: %s', fileformat)
269 269 ext = extension
270 270 break
271 271
272 272 if not fileformat:
273 273 raise ValueError()
274 274
275 275 # left over part of whole fname is the commit
276 276 commit_id = fname[:-len(ext)]
277 277
278 278 return commit_id, ext, fileformat, content_type
279 279
280 280 @LoginRequired()
281 281 @HasRepoPermissionAnyDecorator(
282 282 'repository.read', 'repository.write', 'repository.admin')
283 283 @view_config(
284 284 route_name='repo_archivefile', request_method='GET',
285 285 renderer=None)
286 286 def repo_archivefile(self):
287 287 # archive cache config
288 288 from rhodecode import CONFIG
289 289 _ = self.request.translate
290 290 self.load_default_context()
291 291
292 292 fname = self.request.matchdict['fname']
293 293 subrepos = self.request.GET.get('subrepos') == 'true'
294 294 at_path = self.request.GET.get('at_path') or '/'
295 295
296 296 if not self.db_repo.enable_downloads:
297 297 return Response(_('Downloads disabled'))
298 298
299 299 try:
300 300 commit_id, ext, fileformat, content_type = \
301 301 self._get_archive_spec(fname)
302 302 except ValueError:
303 303 return Response(_('Unknown archive type for: `{}`').format(
304 304 h.escape(fname)))
305 305
306 306 try:
307 307 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
308 308 except CommitDoesNotExistError:
309 309 return Response(_('Unknown commit_id {}').format(
310 310 h.escape(commit_id)))
311 311 except EmptyRepositoryError:
312 312 return Response(_('Empty repository'))
313 313
314 314 try:
315 315 at_path = commit.get_node(at_path).path
316 316 except Exception:
317 317 return Response(_('No node at path {} for this repository').format(at_path))
318 318
319 319 path_sha = sha1(at_path)[:8]
320 320
321 321 archive_name = '{}-{}{}-{}{}'.format(
322 322 safe_str(self.db_repo_name.replace('/', '_')),
323 323 '-sub' if subrepos else '',
324 324 safe_str(commit.short_id),
325 325 path_sha,
326 326 ext)
327 327
328 328 use_cached_archive = False
329 329 archive_cache_enabled = CONFIG.get(
330 330 'archive_cache_dir') and not self.request.GET.get('no_cache')
331 331 cached_archive_path = None
332 332
333 333 if archive_cache_enabled:
334 334 # check if we it's ok to write
335 335 if not os.path.isdir(CONFIG['archive_cache_dir']):
336 336 os.makedirs(CONFIG['archive_cache_dir'])
337 337 cached_archive_path = os.path.join(
338 338 CONFIG['archive_cache_dir'], archive_name)
339 339 if os.path.isfile(cached_archive_path):
340 340 log.debug('Found cached archive in %s', cached_archive_path)
341 341 fd, archive = None, cached_archive_path
342 342 use_cached_archive = True
343 343 else:
344 344 log.debug('Archive %s is not yet cached', archive_name)
345 345
346 346 if not use_cached_archive:
347 347 # generate new archive
348 348 fd, archive = tempfile.mkstemp()
349 349 log.debug('Creating new temp archive in %s', archive)
350 350 try:
351 351 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
352 352 archive_at_path=at_path)
353 353 except ImproperArchiveTypeError:
354 354 return _('Unknown archive type')
355 355 if archive_cache_enabled:
356 356 # if we generated the archive and we have cache enabled
357 357 # let's use this for future
358 358 log.debug('Storing new archive in %s', cached_archive_path)
359 359 shutil.move(archive, cached_archive_path)
360 360 archive = cached_archive_path
361 361
362 362 # store download action
363 363 audit_logger.store_web(
364 364 'repo.archive.download', action_data={
365 365 'user_agent': self.request.user_agent,
366 366 'archive_name': archive_name,
367 367 'archive_spec': fname,
368 368 'archive_cached': use_cached_archive},
369 369 user=self._rhodecode_user,
370 370 repo=self.db_repo,
371 371 commit=True
372 372 )
373 373
374 374 def get_chunked_archive(archive_path):
375 375 with open(archive_path, 'rb') as stream:
376 376 while True:
377 377 data = stream.read(16 * 1024)
378 378 if not data:
379 379 if fd: # fd means we used temporary file
380 380 os.close(fd)
381 381 if not archive_cache_enabled:
382 382 log.debug('Destroying temp archive %s', archive_path)
383 383 os.remove(archive_path)
384 384 break
385 385 yield data
386 386
387 387 response = Response(app_iter=get_chunked_archive(archive))
388 388 response.content_disposition = str(
389 389 'attachment; filename=%s' % archive_name)
390 390 response.content_type = str(content_type)
391 391
392 392 return response
393 393
394 394 def _get_file_node(self, commit_id, f_path):
395 395 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
396 396 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
397 397 try:
398 398 node = commit.get_node(f_path)
399 399 if node.is_dir():
400 400 raise NodeError('%s path is a %s not a file'
401 401 % (node, type(node)))
402 402 except NodeDoesNotExistError:
403 403 commit = EmptyCommit(
404 404 commit_id=commit_id,
405 405 idx=commit.idx,
406 406 repo=commit.repository,
407 407 alias=commit.repository.alias,
408 408 message=commit.message,
409 409 author=commit.author,
410 410 date=commit.date)
411 411 node = FileNode(f_path, '', commit=commit)
412 412 else:
413 413 commit = EmptyCommit(
414 414 repo=self.rhodecode_vcs_repo,
415 415 alias=self.rhodecode_vcs_repo.alias)
416 416 node = FileNode(f_path, '', commit=commit)
417 417 return node
418 418
419 419 @LoginRequired()
420 420 @HasRepoPermissionAnyDecorator(
421 421 'repository.read', 'repository.write', 'repository.admin')
422 422 @view_config(
423 423 route_name='repo_files_diff', request_method='GET',
424 424 renderer=None)
425 425 def repo_files_diff(self):
426 426 c = self.load_default_context()
427 427 f_path = self._get_f_path(self.request.matchdict)
428 428 diff1 = self.request.GET.get('diff1', '')
429 429 diff2 = self.request.GET.get('diff2', '')
430 430
431 431 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
432 432
433 433 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
434 434 line_context = self.request.GET.get('context', 3)
435 435
436 436 if not any((diff1, diff2)):
437 437 h.flash(
438 438 'Need query parameter "diff1" or "diff2" to generate a diff.',
439 439 category='error')
440 440 raise HTTPBadRequest()
441 441
442 442 c.action = self.request.GET.get('diff')
443 443 if c.action not in ['download', 'raw']:
444 444 compare_url = h.route_path(
445 445 'repo_compare',
446 446 repo_name=self.db_repo_name,
447 447 source_ref_type='rev',
448 448 source_ref=diff1,
449 449 target_repo=self.db_repo_name,
450 450 target_ref_type='rev',
451 451 target_ref=diff2,
452 452 _query=dict(f_path=f_path))
453 453 # redirect to new view if we render diff
454 454 raise HTTPFound(compare_url)
455 455
456 456 try:
457 457 node1 = self._get_file_node(diff1, path1)
458 458 node2 = self._get_file_node(diff2, f_path)
459 459 except (RepositoryError, NodeError):
460 460 log.exception("Exception while trying to get node from repository")
461 461 raise HTTPFound(
462 462 h.route_path('repo_files', repo_name=self.db_repo_name,
463 463 commit_id='tip', f_path=f_path))
464 464
465 465 if all(isinstance(node.commit, EmptyCommit)
466 466 for node in (node1, node2)):
467 467 raise HTTPNotFound()
468 468
469 469 c.commit_1 = node1.commit
470 470 c.commit_2 = node2.commit
471 471
472 472 if c.action == 'download':
473 473 _diff = diffs.get_gitdiff(node1, node2,
474 474 ignore_whitespace=ignore_whitespace,
475 475 context=line_context)
476 476 diff = diffs.DiffProcessor(_diff, format='gitdiff')
477 477
478 478 response = Response(self.path_filter.get_raw_patch(diff))
479 479 response.content_type = 'text/plain'
480 480 response.content_disposition = (
481 481 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
482 482 )
483 483 charset = self._get_default_encoding(c)
484 484 if charset:
485 485 response.charset = charset
486 486 return response
487 487
488 488 elif c.action == 'raw':
489 489 _diff = diffs.get_gitdiff(node1, node2,
490 490 ignore_whitespace=ignore_whitespace,
491 491 context=line_context)
492 492 diff = diffs.DiffProcessor(_diff, format='gitdiff')
493 493
494 494 response = Response(self.path_filter.get_raw_patch(diff))
495 495 response.content_type = 'text/plain'
496 496 charset = self._get_default_encoding(c)
497 497 if charset:
498 498 response.charset = charset
499 499 return response
500 500
501 501 # in case we ever end up here
502 502 raise HTTPNotFound()
503 503
504 504 @LoginRequired()
505 505 @HasRepoPermissionAnyDecorator(
506 506 'repository.read', 'repository.write', 'repository.admin')
507 507 @view_config(
508 508 route_name='repo_files_diff_2way_redirect', request_method='GET',
509 509 renderer=None)
510 510 def repo_files_diff_2way_redirect(self):
511 511 """
512 512 Kept only to make OLD links work
513 513 """
514 514 f_path = self._get_f_path_unchecked(self.request.matchdict)
515 515 diff1 = self.request.GET.get('diff1', '')
516 516 diff2 = self.request.GET.get('diff2', '')
517 517
518 518 if not any((diff1, diff2)):
519 519 h.flash(
520 520 'Need query parameter "diff1" or "diff2" to generate a diff.',
521 521 category='error')
522 522 raise HTTPBadRequest()
523 523
524 524 compare_url = h.route_path(
525 525 'repo_compare',
526 526 repo_name=self.db_repo_name,
527 527 source_ref_type='rev',
528 528 source_ref=diff1,
529 529 target_ref_type='rev',
530 530 target_ref=diff2,
531 531 _query=dict(f_path=f_path, diffmode='sideside',
532 532 target_repo=self.db_repo_name,))
533 533 raise HTTPFound(compare_url)
534 534
535 535 @LoginRequired()
536 536 @HasRepoPermissionAnyDecorator(
537 537 'repository.read', 'repository.write', 'repository.admin')
538 538 @view_config(
539 539 route_name='repo_files', request_method='GET',
540 540 renderer=None)
541 541 @view_config(
542 542 route_name='repo_files:default_path', request_method='GET',
543 543 renderer=None)
544 544 @view_config(
545 545 route_name='repo_files:default_commit', request_method='GET',
546 546 renderer=None)
547 547 @view_config(
548 548 route_name='repo_files:rendered', request_method='GET',
549 549 renderer=None)
550 550 @view_config(
551 551 route_name='repo_files:annotated', request_method='GET',
552 552 renderer=None)
553 553 def repo_files(self):
554 554 c = self.load_default_context()
555 555
556 556 view_name = getattr(self.request.matched_route, 'name', None)
557 557
558 558 c.annotate = view_name == 'repo_files:annotated'
559 559 # default is false, but .rst/.md files later are auto rendered, we can
560 560 # overwrite auto rendering by setting this GET flag
561 561 c.renderer = view_name == 'repo_files:rendered' or \
562 562 not self.request.GET.get('no-render', False)
563 563
564 564 # redirect to given commit_id from form if given
565 565 get_commit_id = self.request.GET.get('at_rev', None)
566 566 if get_commit_id:
567 567 self._get_commit_or_redirect(get_commit_id)
568 568
569 569 commit_id, f_path = self._get_commit_and_path()
570 570 c.commit = self._get_commit_or_redirect(commit_id)
571 571 c.branch = self.request.GET.get('branch', None)
572 572 c.f_path = f_path
573 573
574 574 # prev link
575 575 try:
576 576 prev_commit = c.commit.prev(c.branch)
577 577 c.prev_commit = prev_commit
578 578 c.url_prev = h.route_path(
579 579 'repo_files', repo_name=self.db_repo_name,
580 580 commit_id=prev_commit.raw_id, f_path=f_path)
581 581 if c.branch:
582 582 c.url_prev += '?branch=%s' % c.branch
583 583 except (CommitDoesNotExistError, VCSError):
584 584 c.url_prev = '#'
585 585 c.prev_commit = EmptyCommit()
586 586
587 587 # next link
588 588 try:
589 589 next_commit = c.commit.next(c.branch)
590 590 c.next_commit = next_commit
591 591 c.url_next = h.route_path(
592 592 'repo_files', repo_name=self.db_repo_name,
593 593 commit_id=next_commit.raw_id, f_path=f_path)
594 594 if c.branch:
595 595 c.url_next += '?branch=%s' % c.branch
596 596 except (CommitDoesNotExistError, VCSError):
597 597 c.url_next = '#'
598 598 c.next_commit = EmptyCommit()
599 599
600 600 # files or dirs
601 601 try:
602 602 c.file = c.commit.get_node(f_path)
603 603 c.file_author = True
604 604 c.file_tree = ''
605 605
606 606 # load file content
607 607 if c.file.is_file():
608 608 c.lf_node = c.file.get_largefile_node()
609 609
610 610 c.file_source_page = 'true'
611 611 c.file_last_commit = c.file.last_commit
612 612 if c.file.size < c.visual.cut_off_limit_diff:
613 613 if c.annotate: # annotation has precedence over renderer
614 614 c.annotated_lines = filenode_as_annotated_lines_tokens(
615 615 c.file
616 616 )
617 617 else:
618 618 c.renderer = (
619 619 c.renderer and h.renderer_from_filename(c.file.path)
620 620 )
621 621 if not c.renderer:
622 622 c.lines = filenode_as_lines_tokens(c.file)
623 623
624 624 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
625 625 commit_id, self.rhodecode_vcs_repo)
626 626 c.on_branch_head = is_head
627 627
628 628 branch = c.commit.branch if (
629 629 c.commit.branch and '/' not in c.commit.branch) else None
630 630 c.branch_or_raw_id = branch or c.commit.raw_id
631 631 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
632 632
633 633 author = c.file_last_commit.author
634 634 c.authors = [[
635 635 h.email(author),
636 636 h.person(author, 'username_or_name_or_email'),
637 637 1
638 638 ]]
639 639
640 640 else: # load tree content at path
641 641 c.file_source_page = 'false'
642 642 c.authors = []
643 643 # this loads a simple tree without metadata to speed things up
644 644 # later via ajax we call repo_nodetree_full and fetch whole
645 645 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
646 646
647 647 except RepositoryError as e:
648 648 h.flash(safe_str(h.escape(e)), category='error')
649 649 raise HTTPNotFound()
650 650
651 651 if self.request.environ.get('HTTP_X_PJAX'):
652 652 html = render('rhodecode:templates/files/files_pjax.mako',
653 653 self._get_template_context(c), self.request)
654 654 else:
655 655 html = render('rhodecode:templates/files/files.mako',
656 656 self._get_template_context(c), self.request)
657 657 return Response(html)
658 658
659 659 @HasRepoPermissionAnyDecorator(
660 660 'repository.read', 'repository.write', 'repository.admin')
661 661 @view_config(
662 662 route_name='repo_files:annotated_previous', request_method='GET',
663 663 renderer=None)
664 664 def repo_files_annotated_previous(self):
665 665 self.load_default_context()
666 666
667 667 commit_id, f_path = self._get_commit_and_path()
668 668 commit = self._get_commit_or_redirect(commit_id)
669 669 prev_commit_id = commit.raw_id
670 670 line_anchor = self.request.GET.get('line_anchor')
671 671 is_file = False
672 672 try:
673 673 _file = commit.get_node(f_path)
674 674 is_file = _file.is_file()
675 675 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
676 676 pass
677 677
678 678 if is_file:
679 679 history = commit.get_path_history(f_path)
680 680 prev_commit_id = history[1].raw_id \
681 681 if len(history) > 1 else prev_commit_id
682 682 prev_url = h.route_path(
683 683 'repo_files:annotated', repo_name=self.db_repo_name,
684 684 commit_id=prev_commit_id, f_path=f_path,
685 685 _anchor='L{}'.format(line_anchor))
686 686
687 687 raise HTTPFound(prev_url)
688 688
689 689 @LoginRequired()
690 690 @HasRepoPermissionAnyDecorator(
691 691 'repository.read', 'repository.write', 'repository.admin')
692 692 @view_config(
693 693 route_name='repo_nodetree_full', request_method='GET',
694 694 renderer=None, xhr=True)
695 695 @view_config(
696 696 route_name='repo_nodetree_full:default_path', request_method='GET',
697 697 renderer=None, xhr=True)
698 698 def repo_nodetree_full(self):
699 699 """
700 700 Returns rendered html of file tree that contains commit date,
701 701 author, commit_id for the specified combination of
702 702 repo, commit_id and file path
703 703 """
704 704 c = self.load_default_context()
705 705
706 706 commit_id, f_path = self._get_commit_and_path()
707 707 commit = self._get_commit_or_redirect(commit_id)
708 708 try:
709 709 dir_node = commit.get_node(f_path)
710 710 except RepositoryError as e:
711 711 return Response('error: {}'.format(h.escape(safe_str(e))))
712 712
713 713 if dir_node.is_file():
714 714 return Response('')
715 715
716 716 c.file = dir_node
717 717 c.commit = commit
718 718
719 719 html = self._get_tree_at_commit(
720 720 c, commit.raw_id, dir_node.path, full_load=True)
721 721
722 722 return Response(html)
723 723
724 724 def _get_attachement_headers(self, f_path):
725 725 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
726 726 safe_path = f_name.replace('"', '\\"')
727 727 encoded_path = urllib.quote(f_name)
728 728
729 729 return "attachment; " \
730 730 "filename=\"{}\"; " \
731 731 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
732 732
733 733 @LoginRequired()
734 734 @HasRepoPermissionAnyDecorator(
735 735 'repository.read', 'repository.write', 'repository.admin')
736 736 @view_config(
737 737 route_name='repo_file_raw', request_method='GET',
738 738 renderer=None)
739 739 def repo_file_raw(self):
740 740 """
741 741 Action for show as raw, some mimetypes are "rendered",
742 742 those include images, icons.
743 743 """
744 744 c = self.load_default_context()
745 745
746 746 commit_id, f_path = self._get_commit_and_path()
747 747 commit = self._get_commit_or_redirect(commit_id)
748 748 file_node = self._get_filenode_or_redirect(commit, f_path)
749 749
750 750 raw_mimetype_mapping = {
751 751 # map original mimetype to a mimetype used for "show as raw"
752 752 # you can also provide a content-disposition to override the
753 753 # default "attachment" disposition.
754 754 # orig_type: (new_type, new_dispo)
755 755
756 756 # show images inline:
757 757 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
758 758 # for example render an SVG with javascript inside or even render
759 759 # HTML.
760 760 'image/x-icon': ('image/x-icon', 'inline'),
761 761 'image/png': ('image/png', 'inline'),
762 762 'image/gif': ('image/gif', 'inline'),
763 763 'image/jpeg': ('image/jpeg', 'inline'),
764 764 'application/pdf': ('application/pdf', 'inline'),
765 765 }
766 766
767 767 mimetype = file_node.mimetype
768 768 try:
769 769 mimetype, disposition = raw_mimetype_mapping[mimetype]
770 770 except KeyError:
771 771 # we don't know anything special about this, handle it safely
772 772 if file_node.is_binary:
773 773 # do same as download raw for binary files
774 774 mimetype, disposition = 'application/octet-stream', 'attachment'
775 775 else:
776 776 # do not just use the original mimetype, but force text/plain,
777 777 # otherwise it would serve text/html and that might be unsafe.
778 778 # Note: underlying vcs library fakes text/plain mimetype if the
779 779 # mimetype can not be determined and it thinks it is not
780 780 # binary.This might lead to erroneous text display in some
781 781 # cases, but helps in other cases, like with text files
782 782 # without extension.
783 783 mimetype, disposition = 'text/plain', 'inline'
784 784
785 785 if disposition == 'attachment':
786 786 disposition = self._get_attachement_headers(f_path)
787 787
788 788 def stream_node():
789 789 yield file_node.raw_bytes
790 790
791 791 response = Response(app_iter=stream_node())
792 792 response.content_disposition = disposition
793 793 response.content_type = mimetype
794 794
795 795 charset = self._get_default_encoding(c)
796 796 if charset:
797 797 response.charset = charset
798 798
799 799 return response
800 800
801 801 @LoginRequired()
802 802 @HasRepoPermissionAnyDecorator(
803 803 'repository.read', 'repository.write', 'repository.admin')
804 804 @view_config(
805 805 route_name='repo_file_download', request_method='GET',
806 806 renderer=None)
807 807 @view_config(
808 808 route_name='repo_file_download:legacy', request_method='GET',
809 809 renderer=None)
810 810 def repo_file_download(self):
811 811 c = self.load_default_context()
812 812
813 813 commit_id, f_path = self._get_commit_and_path()
814 814 commit = self._get_commit_or_redirect(commit_id)
815 815 file_node = self._get_filenode_or_redirect(commit, f_path)
816 816
817 817 if self.request.GET.get('lf'):
818 818 # only if lf get flag is passed, we download this file
819 819 # as LFS/Largefile
820 820 lf_node = file_node.get_largefile_node()
821 821 if lf_node:
822 822 # overwrite our pointer with the REAL large-file
823 823 file_node = lf_node
824 824
825 825 disposition = self._get_attachement_headers(f_path)
826 826
827 827 def stream_node():
828 828 yield file_node.raw_bytes
829 829
830 830 response = Response(app_iter=stream_node())
831 831 response.content_disposition = disposition
832 832 response.content_type = file_node.mimetype
833 833
834 834 charset = self._get_default_encoding(c)
835 835 if charset:
836 836 response.charset = charset
837 837
838 838 return response
839 839
840 840 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
841 841
842 842 cache_seconds = safe_int(
843 843 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
844 844 cache_on = cache_seconds > 0
845 845 log.debug(
846 846 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
847 847 'with caching: %s[TTL: %ss]' % (
848 848 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
849 849
850 850 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
851 851 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
852 852
853 853 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
854 854 condition=cache_on)
855 855 def compute_file_search(repo_id, commit_id, f_path):
856 856 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
857 857 repo_id, commit_id, f_path)
858 858 try:
859 859 _d, _f = ScmModel().get_nodes(
860 860 repo_name, commit_id, f_path, flat=False)
861 861 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
862 862 log.exception(safe_str(e))
863 863 h.flash(safe_str(h.escape(e)), category='error')
864 864 raise HTTPFound(h.route_path(
865 865 'repo_files', repo_name=self.db_repo_name,
866 866 commit_id='tip', f_path='/'))
867 867 return _d + _f
868 868
869 869 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
870 870
871 871 @LoginRequired()
872 872 @HasRepoPermissionAnyDecorator(
873 873 'repository.read', 'repository.write', 'repository.admin')
874 874 @view_config(
875 875 route_name='repo_files_nodelist', request_method='GET',
876 876 renderer='json_ext', xhr=True)
877 877 def repo_nodelist(self):
878 878 self.load_default_context()
879 879
880 880 commit_id, f_path = self._get_commit_and_path()
881 881 commit = self._get_commit_or_redirect(commit_id)
882 882
883 883 metadata = self._get_nodelist_at_commit(
884 884 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
885 885 return {'nodes': metadata}
886 886
887 887 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
888 888 items = []
889 889 for name, commit_id in branches_or_tags.items():
890 890 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
891 891 items.append((sym_ref, name, ref_type))
892 892 return items
893 893
894 894 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
895 895 return commit_id
896 896
897 897 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
898 898 new_f_path = vcspath.join(name, f_path)
899 899 return u'%s@%s' % (new_f_path, commit_id)
900 900
901 901 def _get_node_history(self, commit_obj, f_path, commits=None):
902 902 """
903 903 get commit history for given node
904 904
905 905 :param commit_obj: commit to calculate history
906 906 :param f_path: path for node to calculate history for
907 907 :param commits: if passed don't calculate history and take
908 908 commits defined in this list
909 909 """
910 910 _ = self.request.translate
911 911
912 912 # calculate history based on tip
913 913 tip = self.rhodecode_vcs_repo.get_commit()
914 914 if commits is None:
915 915 pre_load = ["author", "branch"]
916 916 try:
917 917 commits = tip.get_path_history(f_path, pre_load=pre_load)
918 918 except (NodeDoesNotExistError, CommitError):
919 919 # this node is not present at tip!
920 920 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
921 921
922 922 history = []
923 923 commits_group = ([], _("Changesets"))
924 924 for commit in commits:
925 925 branch = ' (%s)' % commit.branch if commit.branch else ''
926 926 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
927 927 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
928 928 history.append(commits_group)
929 929
930 930 symbolic_reference = self._symbolic_reference
931 931
932 932 if self.rhodecode_vcs_repo.alias == 'svn':
933 933 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
934 934 f_path, self.rhodecode_vcs_repo)
935 935 if adjusted_f_path != f_path:
936 936 log.debug(
937 937 'Recognized svn tag or branch in file "%s", using svn '
938 938 'specific symbolic references', f_path)
939 939 f_path = adjusted_f_path
940 940 symbolic_reference = self._symbolic_reference_svn
941 941
942 942 branches = self._create_references(
943 943 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
944 944 branches_group = (branches, _("Branches"))
945 945
946 946 tags = self._create_references(
947 947 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
948 948 tags_group = (tags, _("Tags"))
949 949
950 950 history.append(branches_group)
951 951 history.append(tags_group)
952 952
953 953 return history, commits
954 954
955 955 @LoginRequired()
956 956 @HasRepoPermissionAnyDecorator(
957 957 'repository.read', 'repository.write', 'repository.admin')
958 958 @view_config(
959 959 route_name='repo_file_history', request_method='GET',
960 960 renderer='json_ext')
961 961 def repo_file_history(self):
962 962 self.load_default_context()
963 963
964 964 commit_id, f_path = self._get_commit_and_path()
965 965 commit = self._get_commit_or_redirect(commit_id)
966 966 file_node = self._get_filenode_or_redirect(commit, f_path)
967 967
968 968 if file_node.is_file():
969 969 file_history, _hist = self._get_node_history(commit, f_path)
970 970
971 971 res = []
972 972 for obj in file_history:
973 973 res.append({
974 974 'text': obj[1],
975 975 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
976 976 })
977 977
978 978 data = {
979 979 'more': False,
980 980 'results': res
981 981 }
982 982 return data
983 983
984 984 log.warning('Cannot fetch history for directory')
985 985 raise HTTPBadRequest()
986 986
987 987 @LoginRequired()
988 988 @HasRepoPermissionAnyDecorator(
989 989 'repository.read', 'repository.write', 'repository.admin')
990 990 @view_config(
991 991 route_name='repo_file_authors', request_method='GET',
992 992 renderer='rhodecode:templates/files/file_authors_box.mako')
993 993 def repo_file_authors(self):
994 994 c = self.load_default_context()
995 995
996 996 commit_id, f_path = self._get_commit_and_path()
997 997 commit = self._get_commit_or_redirect(commit_id)
998 998 file_node = self._get_filenode_or_redirect(commit, f_path)
999 999
1000 1000 if not file_node.is_file():
1001 1001 raise HTTPBadRequest()
1002 1002
1003 1003 c.file_last_commit = file_node.last_commit
1004 1004 if self.request.GET.get('annotate') == '1':
1005 1005 # use _hist from annotation if annotation mode is on
1006 1006 commit_ids = set(x[1] for x in file_node.annotate)
1007 1007 _hist = (
1008 1008 self.rhodecode_vcs_repo.get_commit(commit_id)
1009 1009 for commit_id in commit_ids)
1010 1010 else:
1011 1011 _f_history, _hist = self._get_node_history(commit, f_path)
1012 1012 c.file_author = False
1013 1013
1014 1014 unique = collections.OrderedDict()
1015 1015 for commit in _hist:
1016 1016 author = commit.author
1017 1017 if author not in unique:
1018 1018 unique[commit.author] = [
1019 1019 h.email(author),
1020 1020 h.person(author, 'username_or_name_or_email'),
1021 1021 1 # counter
1022 1022 ]
1023 1023
1024 1024 else:
1025 1025 # increase counter
1026 1026 unique[commit.author][2] += 1
1027 1027
1028 1028 c.authors = [val for val in unique.values()]
1029 1029
1030 1030 return self._get_template_context(c)
1031 1031
1032 1032 @LoginRequired()
1033 1033 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1034 1034 @view_config(
1035 1035 route_name='repo_files_remove_file', request_method='GET',
1036 1036 renderer='rhodecode:templates/files/files_delete.mako')
1037 1037 def repo_files_remove_file(self):
1038 1038 _ = self.request.translate
1039 1039 c = self.load_default_context()
1040 1040 commit_id, f_path = self._get_commit_and_path()
1041 1041
1042 1042 self._ensure_not_locked()
1043 1043 _branch_name, _sha_commit_id, is_head = \
1044 1044 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1045 1045
1046 1046 if not is_head:
1047 1047 h.flash(_('You can only delete files with commit '
1048 1048 'being a valid branch head.'), category='warning')
1049 1049 raise HTTPFound(
1050 1050 h.route_path('repo_files',
1051 1051 repo_name=self.db_repo_name, commit_id='tip',
1052 1052 f_path=f_path))
1053 1053
1054 1054 self.check_branch_permission(_branch_name)
1055 1055 c.commit = self._get_commit_or_redirect(commit_id)
1056 1056 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1057 1057
1058 1058 c.default_message = _(
1059 1059 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1060 1060 c.f_path = f_path
1061 1061
1062 1062 return self._get_template_context(c)
1063 1063
1064 1064 @LoginRequired()
1065 1065 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1066 1066 @CSRFRequired()
1067 1067 @view_config(
1068 1068 route_name='repo_files_delete_file', request_method='POST',
1069 1069 renderer=None)
1070 1070 def repo_files_delete_file(self):
1071 1071 _ = self.request.translate
1072 1072
1073 1073 c = self.load_default_context()
1074 1074 commit_id, f_path = self._get_commit_and_path()
1075 1075
1076 1076 self._ensure_not_locked()
1077 1077 _branch_name, _sha_commit_id, is_head = \
1078 1078 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1079 1079
1080 1080 if not is_head:
1081 1081 h.flash(_('You can only delete files with commit '
1082 1082 'being a valid branch head.'), category='warning')
1083 1083 raise HTTPFound(
1084 1084 h.route_path('repo_files',
1085 1085 repo_name=self.db_repo_name, commit_id='tip',
1086 1086 f_path=f_path))
1087 1087 self.check_branch_permission(_branch_name)
1088 1088
1089 1089 c.commit = self._get_commit_or_redirect(commit_id)
1090 1090 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1091 1091
1092 1092 c.default_message = _(
1093 1093 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1094 1094 c.f_path = f_path
1095 1095 node_path = f_path
1096 1096 author = self._rhodecode_db_user.full_contact
1097 1097 message = self.request.POST.get('message') or c.default_message
1098 1098 try:
1099 1099 nodes = {
1100 1100 node_path: {
1101 1101 'content': ''
1102 1102 }
1103 1103 }
1104 1104 ScmModel().delete_nodes(
1105 1105 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1106 1106 message=message,
1107 1107 nodes=nodes,
1108 1108 parent_commit=c.commit,
1109 1109 author=author,
1110 1110 )
1111 1111
1112 1112 h.flash(
1113 1113 _('Successfully deleted file `{}`').format(
1114 1114 h.escape(f_path)), category='success')
1115 1115 except Exception:
1116 1116 log.exception('Error during commit operation')
1117 1117 h.flash(_('Error occurred during commit'), category='error')
1118 1118 raise HTTPFound(
1119 1119 h.route_path('repo_commit', repo_name=self.db_repo_name,
1120 1120 commit_id='tip'))
1121 1121
1122 1122 @LoginRequired()
1123 1123 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1124 1124 @view_config(
1125 1125 route_name='repo_files_edit_file', request_method='GET',
1126 1126 renderer='rhodecode:templates/files/files_edit.mako')
1127 1127 def repo_files_edit_file(self):
1128 1128 _ = self.request.translate
1129 1129 c = self.load_default_context()
1130 1130 commit_id, f_path = self._get_commit_and_path()
1131 1131
1132 1132 self._ensure_not_locked()
1133 1133 _branch_name, _sha_commit_id, is_head = \
1134 1134 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1135 1135
1136 1136 if not is_head:
1137 1137 h.flash(_('You can only edit files with commit '
1138 1138 'being a valid branch head.'), category='warning')
1139 1139 raise HTTPFound(
1140 1140 h.route_path('repo_files',
1141 1141 repo_name=self.db_repo_name, commit_id='tip',
1142 1142 f_path=f_path))
1143 1143 self.check_branch_permission(_branch_name)
1144 1144
1145 1145 c.commit = self._get_commit_or_redirect(commit_id)
1146 1146 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1147 1147
1148 1148 if c.file.is_binary:
1149 1149 files_url = h.route_path(
1150 1150 'repo_files',
1151 1151 repo_name=self.db_repo_name,
1152 1152 commit_id=c.commit.raw_id, f_path=f_path)
1153 1153 raise HTTPFound(files_url)
1154 1154
1155 1155 c.default_message = _(
1156 1156 'Edited file {} via RhodeCode Enterprise').format(f_path)
1157 1157 c.f_path = f_path
1158 1158
1159 1159 return self._get_template_context(c)
1160 1160
1161 1161 @LoginRequired()
1162 1162 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1163 1163 @CSRFRequired()
1164 1164 @view_config(
1165 1165 route_name='repo_files_update_file', request_method='POST',
1166 1166 renderer=None)
1167 1167 def repo_files_update_file(self):
1168 1168 _ = self.request.translate
1169 1169 c = self.load_default_context()
1170 1170 commit_id, f_path = self._get_commit_and_path()
1171 1171
1172 1172 self._ensure_not_locked()
1173 1173 _branch_name, _sha_commit_id, is_head = \
1174 1174 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1175 1175
1176 1176 if not is_head:
1177 1177 h.flash(_('You can only edit files with commit '
1178 1178 'being a valid branch head.'), category='warning')
1179 1179 raise HTTPFound(
1180 1180 h.route_path('repo_files',
1181 1181 repo_name=self.db_repo_name, commit_id='tip',
1182 1182 f_path=f_path))
1183 1183
1184 1184 self.check_branch_permission(_branch_name)
1185 1185
1186 1186 c.commit = self._get_commit_or_redirect(commit_id)
1187 1187 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1188 1188
1189 1189 if c.file.is_binary:
1190 1190 raise HTTPFound(
1191 1191 h.route_path('repo_files',
1192 1192 repo_name=self.db_repo_name,
1193 1193 commit_id=c.commit.raw_id,
1194 1194 f_path=f_path))
1195 1195
1196 1196 c.default_message = _(
1197 1197 'Edited file {} via RhodeCode Enterprise').format(f_path)
1198 1198 c.f_path = f_path
1199 1199 old_content = c.file.content
1200 1200 sl = old_content.splitlines(1)
1201 1201 first_line = sl[0] if sl else ''
1202 1202
1203 1203 r_post = self.request.POST
1204 1204 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1205 1205 line_ending_mode = detect_mode(first_line, 0)
1206 1206 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1207 1207
1208 1208 message = r_post.get('message') or c.default_message
1209 1209 org_f_path = c.file.unicode_path
1210 1210 filename = r_post['filename']
1211 1211 org_filename = c.file.name
1212 1212
1213 1213 if content == old_content and filename == org_filename:
1214 1214 h.flash(_('No changes'), category='warning')
1215 1215 raise HTTPFound(
1216 1216 h.route_path('repo_commit', repo_name=self.db_repo_name,
1217 1217 commit_id='tip'))
1218 1218 try:
1219 1219 mapping = {
1220 1220 org_f_path: {
1221 1221 'org_filename': org_f_path,
1222 1222 'filename': os.path.join(c.file.dir_path, filename),
1223 1223 'content': content,
1224 1224 'lexer': '',
1225 1225 'op': 'mod',
1226 1226 'mode': c.file.mode
1227 1227 }
1228 1228 }
1229 1229
1230 1230 ScmModel().update_nodes(
1231 1231 user=self._rhodecode_db_user.user_id,
1232 1232 repo=self.db_repo,
1233 1233 message=message,
1234 1234 nodes=mapping,
1235 1235 parent_commit=c.commit,
1236 1236 )
1237 1237
1238 1238 h.flash(
1239 1239 _('Successfully committed changes to file `{}`').format(
1240 1240 h.escape(f_path)), category='success')
1241 1241 except Exception:
1242 1242 log.exception('Error occurred during commit')
1243 1243 h.flash(_('Error occurred during commit'), category='error')
1244 1244 raise HTTPFound(
1245 1245 h.route_path('repo_commit', repo_name=self.db_repo_name,
1246 1246 commit_id='tip'))
1247 1247
1248 1248 @LoginRequired()
1249 1249 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1250 1250 @view_config(
1251 1251 route_name='repo_files_add_file', request_method='GET',
1252 1252 renderer='rhodecode:templates/files/files_add.mako')
1253 @view_config(
1254 route_name='repo_files_upload_file', request_method='GET',
1255 renderer='rhodecode:templates/files/files_upload.mako')
1253 1256 def repo_files_add_file(self):
1254 1257 _ = self.request.translate
1255 1258 c = self.load_default_context()
1256 1259 commit_id, f_path = self._get_commit_and_path()
1257 1260
1258 1261 self._ensure_not_locked()
1259 1262
1260 1263 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1261 1264 if c.commit is None:
1262 1265 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1263 1266 c.default_message = (_('Added file via RhodeCode Enterprise'))
1264 1267 c.f_path = f_path.lstrip('/') # ensure not relative path
1265 1268
1266 1269 if self.rhodecode_vcs_repo.is_empty:
1267 1270 # for empty repository we cannot check for current branch, we rely on
1268 1271 # c.commit.branch instead
1269 1272 _branch_name = c.commit.branch
1270 1273 is_head = True
1271 1274 else:
1272 1275 _branch_name, _sha_commit_id, is_head = \
1273 1276 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1274 1277
1275 1278 if not is_head:
1276 1279 h.flash(_('You can only add files with commit '
1277 1280 'being a valid branch head.'), category='warning')
1278 1281 raise HTTPFound(
1279 1282 h.route_path('repo_files',
1280 1283 repo_name=self.db_repo_name, commit_id='tip',
1281 1284 f_path=f_path))
1282 1285
1283 1286 self.check_branch_permission(_branch_name)
1284 1287
1285 1288 return self._get_template_context(c)
1286 1289
1287 1290 @LoginRequired()
1288 1291 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1289 1292 @CSRFRequired()
1290 1293 @view_config(
1291 1294 route_name='repo_files_create_file', request_method='POST',
1292 1295 renderer=None)
1293 1296 def repo_files_create_file(self):
1294 1297 _ = self.request.translate
1295 1298 c = self.load_default_context()
1296 1299 commit_id, f_path = self._get_commit_and_path()
1297 1300
1298 1301 self._ensure_not_locked()
1299 1302
1300 1303 r_post = self.request.POST
1301 1304
1302 1305 c.commit = self._get_commit_or_redirect(
1303 1306 commit_id, redirect_after=False)
1304 1307 if c.commit is None:
1305 1308 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1306 1309
1307 1310 if self.rhodecode_vcs_repo.is_empty:
1308 1311 # for empty repository we cannot check for current branch, we rely on
1309 1312 # c.commit.branch instead
1310 1313 _branch_name = c.commit.branch
1311 1314 is_head = True
1312 1315 else:
1313 1316 _branch_name, _sha_commit_id, is_head = \
1314 1317 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1315 1318
1316 1319 if not is_head:
1317 1320 h.flash(_('You can only add files with commit '
1318 1321 'being a valid branch head.'), category='warning')
1319 1322 raise HTTPFound(
1320 1323 h.route_path('repo_files',
1321 1324 repo_name=self.db_repo_name, commit_id='tip',
1322 1325 f_path=f_path))
1323 1326
1324 1327 self.check_branch_permission(_branch_name)
1325 1328
1326 1329 c.default_message = (_('Added file via RhodeCode Enterprise'))
1327 1330 c.f_path = f_path
1328 1331 unix_mode = 0
1329 1332 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1330 1333
1331 1334 message = r_post.get('message') or c.default_message
1332 1335 filename = r_post.get('filename')
1333 1336 location = r_post.get('location', '') # dir location
1334 1337 file_obj = r_post.get('upload_file', None)
1335 1338
1336 1339 if file_obj is not None and hasattr(file_obj, 'filename'):
1337 1340 filename = r_post.get('filename_upload')
1338 1341 content = file_obj.file
1339 1342
1340 1343 if hasattr(content, 'file'):
1341 1344 # non posix systems store real file under file attr
1342 1345 content = content.file
1343 1346
1344 1347 if self.rhodecode_vcs_repo.is_empty:
1345 1348 default_redirect_url = h.route_path(
1346 1349 'repo_summary', repo_name=self.db_repo_name)
1347 1350 else:
1348 1351 default_redirect_url = h.route_path(
1349 1352 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1350 1353
1351 1354 # If there's no commit, redirect to repo summary
1352 1355 if type(c.commit) is EmptyCommit:
1353 1356 redirect_url = h.route_path(
1354 1357 'repo_summary', repo_name=self.db_repo_name)
1355 1358 else:
1356 1359 redirect_url = default_redirect_url
1357 1360
1358 1361 if not filename:
1359 1362 h.flash(_('No filename'), category='warning')
1360 1363 raise HTTPFound(redirect_url)
1361 1364
1362 1365 # extract the location from filename,
1363 1366 # allows using foo/bar.txt syntax to create subdirectories
1364 1367 subdir_loc = filename.rsplit('/', 1)
1365 1368 if len(subdir_loc) == 2:
1366 1369 location = os.path.join(location, subdir_loc[0])
1367 1370
1368 1371 # strip all crap out of file, just leave the basename
1369 1372 filename = os.path.basename(filename)
1370 1373 node_path = os.path.join(location, filename)
1371 1374 author = self._rhodecode_db_user.full_contact
1372 1375
1373 1376 try:
1374 1377 nodes = {
1375 1378 node_path: {
1376 1379 'content': content
1377 1380 }
1378 1381 }
1379 1382 ScmModel().create_nodes(
1380 1383 user=self._rhodecode_db_user.user_id,
1381 1384 repo=self.db_repo,
1382 1385 message=message,
1383 1386 nodes=nodes,
1384 1387 parent_commit=c.commit,
1385 1388 author=author,
1386 1389 )
1387 1390
1388 1391 h.flash(
1389 1392 _('Successfully committed new file `{}`').format(
1390 1393 h.escape(node_path)), category='success')
1391 1394 except NonRelativePathError:
1392 1395 log.exception('Non Relative path found')
1393 1396 h.flash(_(
1394 1397 'The location specified must be a relative path and must not '
1395 1398 'contain .. in the path'), category='warning')
1396 1399 raise HTTPFound(default_redirect_url)
1397 1400 except (NodeError, NodeAlreadyExistsError) as e:
1398 1401 h.flash(_(h.escape(e)), category='error')
1399 1402 except Exception:
1400 1403 log.exception('Error occurred during commit')
1401 1404 h.flash(_('Error occurred during commit'), category='error')
1402 1405
1403 1406 raise HTTPFound(default_redirect_url)
@@ -1,236 +1,182 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Files Add') % c.repo_name}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_subnav()">
19 19 ${self.repo_menu(active='files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24
25 25 <div class="edit-file-title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28
28 29 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
29 30 <div class="edit-file-fieldset">
30 31 <div class="fieldset">
31 32 <div id="destination-label" class="left-label">
32 33 ${_('Path')}:
33 34 </div>
34 35 <div class="right-content">
35 <div id="specify-custom-path-container">
36 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
37 <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a>
38 </div>
39 <div id="remove-custom-path-container" style="display: none;">
40 ${c.repo_name}/
36 <div>
37 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))} /
41 38 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
42 <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a>
43 39 </div>
44 40 </div>
45 41 </div>
46 42 <div id="filename_container" class="fieldset">
47 43 <div class="filename-label left-label">
48 44 ${_('Filename')}:
49 45 </div>
50 46 <div class="right-content">
51 47 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
52 <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p>
48
53 49 </div>
54 50 </div>
55 <div id="upload_file_container" class="fieldset" style="display: none;">
56 <div class="filename-label left-label">
57 ${_('Filename')}:
58 </div>
59 <div class="right-content">
60 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
61 </div>
62 <div class="filename-label left-label file-upload-label">
63 ${_('Upload file')}:
64 </div>
65 <div class="right-content file-upload-input">
66 <label for="upload_file" class="btn btn-default">Browse</label>
67 51
68 <input type="file" name="upload_file" id="upload_file">
69 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
70 </div>
71 </div>
72 52 </div>
53
73 54 <div class="table">
74 55 <div id="files_data">
75 56 <div id="codeblock" class="codeblock">
76 57 <div class="code-header form" id="set_mode_header">
77 58 <div class="fields">
78 59 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
79 60 <label for="line_wrap">${_('line wraps')}</label>
80 61 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
81 62
82 63 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
83 64 </div>
84 65 </div>
85 66 <div id="editor_container">
86 67 <pre id="editor_pre"></pre>
87 68 <textarea id="editor" name="content" ></textarea>
88 69 <div id="editor_preview"></div>
89 70 </div>
90 71 </div>
91 72 </div>
92 73 </div>
93 74
94 75 <div class="edit-file-fieldset">
95 76 <div class="fieldset">
96 77 <div id="commit-message-label" class="commit-message-label left-label">
97 78 ${_('Commit Message')}:
98 79 </div>
99 80 <div class="right-content">
100 81 <div class="message">
101 82 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
102 83 </div>
103 84 </div>
104 85 </div>
105 86 <div class="pull-right">
106 87 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
107 88 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
108 89 </div>
109 90 </div>
110 91 ${h.end_form()}
111 92 </div>
112 93 <script type="text/javascript">
113 94
114 95 $('#commit_btn').on('click', function() {
115 96 var button = $(this);
116 97 if (button.hasClass('clicked')) {
117 98 button.attr('disabled', true);
118 99 } else {
119 100 button.addClass('clicked');
120 101 }
121 102 });
122 103
123 $('#specify-custom-path').on('click', function(e){
124 e.preventDefault();
125 $('#specify-custom-path-container').hide();
126 $('#remove-custom-path-container').show();
127 $('#destination-label').css('margin-top', '13px');
128 });
129
130 $('#remove-custom-path').on('click', function(e){
131 e.preventDefault();
132 $('#specify-custom-path-container').show();
133 $('#remove-custom-path-container').hide();
134 $('#location').val('${c.f_path}');
135 $('#destination-label').css('margin-top', '0');
136 });
137
138 104 var hide_upload = function(){
139 105 $('#files_data').show();
140 106 $('#upload_file_container').hide();
141 107 $('#filename_container').show();
142 108 };
143 109
144 110 $('#file_enable').on('click', function(e){
145 111 e.preventDefault();
146 112 hide_upload();
147 113 });
148 114
149 $('#upload_file_enable').on('click', function(e){
150 e.preventDefault();
151 $('#files_data').hide();
152 $('#upload_file_container').show();
153 $('#filename_container').hide();
154 if (detectIE() && detectIE() <= 9) {
155 $('#upload_file_container .file-upload-input label').hide();
156 $('#upload_file_container .file-upload-input span').hide();
157 $('#upload_file_container .file-upload-input input').show();
158 }
159 });
160
161 $('#upload_file').on('change', function() {
162 if (this.files && this.files[0]) {
163 $('#filename_upload').val(this.files[0].name);
164 }
165 });
166
167 hide_upload();
168
169 115 var renderer = "";
170 116 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
171 117 var myCodeMirror = initCodeMirror('editor', reset_url, false);
172 118
173 119 var modes_select = $('#set_mode');
174 120 fillCodeMirrorOptions(modes_select);
175 121
176 122 var filename_selector = '#filename';
177 123 var callback = function(filename, mimetype, mode){
178 124 CodeMirrorPreviewEnable(mode);
179 125 };
180 126 // on change of select field set mode
181 127 setCodeMirrorModeFromSelect(
182 128 modes_select, filename_selector, myCodeMirror, callback);
183 129
184 130 // on entering the new filename set mode, from given extension
185 131 setCodeMirrorModeFromInput(
186 132 modes_select, filename_selector, myCodeMirror, callback);
187 133
188 134 // if the file is renderable set line wraps automatically
189 135 if (renderer !== ""){
190 136 var line_wrap = 'on';
191 137 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
192 138 setCodeMirrorLineWrap(myCodeMirror, true);
193 139 }
194 140
195 141 // on select line wraps change the editor
196 142 $('#line_wrap').on('change', function(e){
197 143 var selected = e.currentTarget;
198 144 var line_wraps = {'on': true, 'off': false}[selected.value];
199 145 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
200 146 });
201 147
202 148 // render preview/edit button
203 149 $('#render_preview').on('click', function(e){
204 150 if($(this).hasClass('preview')){
205 151 $(this).removeClass('preview');
206 152 $(this).html("${_('Edit')}");
207 153 $('#editor_preview').show();
208 154 $(myCodeMirror.getWrapperElement()).hide();
209 155
210 156 var possible_renderer = {
211 157 'rst':'rst',
212 158 'markdown':'markdown',
213 159 'gfm': 'markdown'}[myCodeMirror.getMode().name];
214 160 var _text = myCodeMirror.getValue();
215 161 var _renderer = possible_renderer || DEFAULT_RENDERER;
216 162 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
217 163 $('#editor_preview').html(_gettext('Loading ...'));
218 164 var url = pyroutes.url('repo_commit_comment_preview',
219 165 {'repo_name': '${c.repo_name}',
220 166 'commit_id': '${c.commit.raw_id}'});
221 167
222 168 ajaxPOST(url, post_data, function(o){
223 169 $('#editor_preview').html(o);
224 170 })
225 171 }
226 172 else{
227 173 $(this).addClass('preview');
228 174 $(this).html("${_('Preview')}");
229 175 $('#editor_preview').hide();
230 176 $(myCodeMirror.getWrapperElement()).show();
231 177 }
232 178 });
233 179 $('#filename').focus();
234 180
235 181 </script>
236 182 </%def>
@@ -1,64 +1,64 b''
1 1
2 2 <div id="codeblock" class="browserblock">
3 3 <div class="browser-header">
4 4 <div class="browser-nav">
5 5
6 6 <div class="info_box">
7 7
8 8 <div class="info_box_elem previous">
9 9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
10 10 </div>
11 11
12 12 ${h.hidden('refs_filter')}
13 13
14 14 <div class="info_box_elem next">
15 15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
16 16 </div>
17 17 </div>
18 18
19 19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
20 20 <div>
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}">
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
22 22 ${_('Upload File')}
23 23 </a>
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}">
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
25 25 ${_('Add File')}
26 26 </a>
27 27 </div>
28 28 % endif
29 29
30 30 % if c.enable_downloads:
31 31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
32 32 <div class="btn btn-default new-file">
33 33 % if c.f_path == '/':
34 34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
35 35 ${_('Download full tree ZIP')}
36 36 </a>
37 37 % else:
38 38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
39 39 ${_('Download this tree ZIP')}
40 40 </a>
41 41 % endif
42 42 </div>
43 43 % endif
44 44
45 45 <div class="files-quick-filter">
46 46 <ul class="files-filter-box">
47 47 <li class="files-filter-box-path">
48 48 <i class="icon-search"></i>
49 49 </li>
50 50 <li class="files-filter-box-input">
51 51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
52 52 </li>
53 53 </ul>
54 54 </div>
55 55 </div>
56 56
57 57 </div>
58 58
59 59 ## file tree is computed from caches, and filled in
60 60 <div id="file-tree">
61 61 ${c.file_tree |n}
62 62 </div>
63 63
64 64 </div>
General Comments 0
You need to be logged in to leave comments. Login now