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