Show More
@@ -1103,9 +1103,9 b' def make_map(config):' | |||
|
1103 | 1103 | conditions={'function': check_repo}, |
|
1104 | 1104 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
1105 | 1105 | |
|
1106 |
rmap.connect('files_ |
|
|
1107 |
'/{repo_name}/ |
|
|
1108 |
controller='files', action=' |
|
|
1106 | rmap.connect('files_nodetree_full', | |
|
1107 | '/{repo_name}/nodetree_full/{commit_id}/{f_path}', | |
|
1108 | controller='files', action='nodetree_full', | |
|
1109 | 1109 | conditions={'function': check_repo}, |
|
1110 | 1110 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
1111 | 1111 |
@@ -136,10 +136,12 b' class FilesController(BaseRepoController' | |||
|
136 | 136 | _namespace = caches.get_repo_namespace_key(namespace_type, repo_name) |
|
137 | 137 | return caches.get_cache_manager('repo_cache_long', _namespace) |
|
138 | 138 | |
|
139 |
def _get_tree_at_commit(self, repo_name, commit_id, f_path |
|
|
139 | def _get_tree_at_commit(self, repo_name, commit_id, f_path, | |
|
140 | full_load=False, force=False): | |
|
140 | 141 | def _cached_tree(): |
|
141 | 142 | log.debug('Generating cached file tree for %s, %s, %s', |
|
142 | 143 | repo_name, commit_id, f_path) |
|
144 | c.full_load = full_load | |
|
143 | 145 | return render('files/files_browser_tree.html') |
|
144 | 146 | |
|
145 | 147 | cache_manager = self.__get_tree_cache_manager( |
@@ -148,6 +150,10 b' class FilesController(BaseRepoController' | |||
|
148 | 150 | cache_key = caches.compute_key_from_params( |
|
149 | 151 | repo_name, commit_id, f_path) |
|
150 | 152 | |
|
153 | if force: | |
|
154 | # we want to force recompute of caches | |
|
155 | cache_manager.remove_value(cache_key) | |
|
156 | ||
|
151 | 157 | return cache_manager.get(cache_key, createfunc=_cached_tree) |
|
152 | 158 | |
|
153 | 159 | def _get_nodelist_at_commit(self, repo_name, commit_id, f_path): |
@@ -165,22 +171,6 b' class FilesController(BaseRepoController' | |||
|
165 | 171 | repo_name, commit_id, f_path) |
|
166 | 172 | return cache_manager.get(cache_key, createfunc=_cached_nodes) |
|
167 | 173 | |
|
168 | def _get_metadata_at_commit(self, repo_name, commit, dir_node): | |
|
169 | def _cached_metadata(): | |
|
170 | log.debug('Generating cached metadata for %s, %s, %s', | |
|
171 | repo_name, commit.raw_id, safe_str(dir_node.path)) | |
|
172 | ||
|
173 | data = ScmModel().get_dirnode_metadata(commit, dir_node) | |
|
174 | return data | |
|
175 | ||
|
176 | cache_manager = self.__get_tree_cache_manager( | |
|
177 | repo_name, caches.FILE_TREE_META) | |
|
178 | ||
|
179 | cache_key = caches.compute_key_from_params( | |
|
180 | repo_name, commit.raw_id, safe_str(dir_node.path)) | |
|
181 | ||
|
182 | return cache_manager.get(cache_key, createfunc=_cached_metadata) | |
|
183 | ||
|
184 | 174 | @LoginRequired() |
|
185 | 175 | @HasRepoPermissionAnyDecorator( |
|
186 | 176 | 'repository.read', 'repository.write', 'repository.admin') |
@@ -246,6 +236,7 b' class FilesController(BaseRepoController' | |||
|
246 | 236 | c.authors = [] |
|
247 | 237 | c.file_tree = self._get_tree_at_commit( |
|
248 | 238 | repo_name, c.commit.raw_id, f_path) |
|
239 | ||
|
249 | 240 | except RepositoryError as e: |
|
250 | 241 | h.flash(safe_str(e), category='error') |
|
251 | 242 | raise HTTPNotFound() |
@@ -1092,23 +1083,32 b' class FilesController(BaseRepoController' | |||
|
1092 | 1083 | @XHRRequired() |
|
1093 | 1084 | @HasRepoPermissionAnyDecorator( |
|
1094 | 1085 | 'repository.read', 'repository.write', 'repository.admin') |
|
1095 | @jsonify | |
|
1096 | def metadata_list(self, repo_name, revision, f_path): | |
|
1086 | def nodetree_full(self, repo_name, commit_id, f_path): | |
|
1097 | 1087 | """ |
|
1098 |
Returns |
|
|
1099 |
a |
|
|
1088 | Returns rendered html of file tree that contains commit date, | |
|
1089 | author, revision for the specified combination of | |
|
1090 | repo, commit_id and file path | |
|
1100 | 1091 | |
|
1101 | 1092 | :param repo_name: name of the repository |
|
1102 |
:param |
|
|
1093 | :param commit_id: commit_id of file tree | |
|
1103 | 1094 | :param f_path: file path of the requested directory |
|
1104 | 1095 | """ |
|
1105 | 1096 | |
|
1106 |
commit = self.__get_commit_or_redirect( |
|
|
1097 | commit = self.__get_commit_or_redirect(commit_id, repo_name) | |
|
1107 | 1098 | try: |
|
1108 |
|
|
|
1099 | dir_node = commit.get_node(f_path) | |
|
1109 | 1100 | except RepositoryError as e: |
|
1110 |
return |
|
|
1101 | return 'error {}'.format(safe_str(e)) | |
|
1102 | ||
|
1103 | if dir_node.is_file(): | |
|
1104 | return '' | |
|
1111 | 1105 | |
|
1112 | metadata = self._get_metadata_at_commit( | |
|
1113 | repo_name, commit, file_node) | |
|
1114 | return {'metadata': metadata} | |
|
1106 | c.file = dir_node | |
|
1107 | c.commit = commit | |
|
1108 | ||
|
1109 | # using force=True here, make a little trick. We flush the cache and | |
|
1110 | # compute it using the same key as without full_load, so the fully | |
|
1111 | # loaded cached tree is now returned instead of partial | |
|
1112 | return self._get_tree_at_commit( | |
|
1113 | repo_name, commit.raw_id, dir_node.path, full_load=True, | |
|
1114 | force=True) |
@@ -35,7 +35,7 b" FILE_SEARCH_TREE_META = 'cache_file_sear" | |||
|
35 | 35 | SUMMARY_STATS = 'cache_summary_stats' |
|
36 | 36 | |
|
37 | 37 | # This list of caches gets purged when invalidation happens |
|
38 |
USED_REPO_CACHES = (FILE_TREE, FILE_ |
|
|
38 | USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META) | |
|
39 | 39 | |
|
40 | 40 | DEFAULT_CACHE_MANAGER_CONFIG = { |
|
41 | 41 | 'type': 'memorylru_base', |
@@ -4,7 +4,8 b'' | |||
|
4 | 4 | * DO NOT CHANGE THIS FILE MANUALLY * |
|
5 | 5 | * * |
|
6 | 6 | * * |
|
7 |
* This file is automatically generated when the app starts up |
|
|
7 | * This file is automatically generated when the app starts up with * | |
|
8 | * generate_js_files = true * | |
|
8 | 9 | * * |
|
9 | 10 | * To add a route here pass jsroute=True to the route definition in the app * |
|
10 | 11 | * * |
@@ -44,7 +45,7 b' function registerRCRoutes() {' | |||
|
44 | 45 | pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
45 | 46 | pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
46 | 47 | pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
47 |
pyroutes.register('files_ |
|
|
48 | pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); | |
|
48 | 49 | pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']); |
|
49 | 50 | pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']); |
|
50 | 51 | } |
@@ -91,36 +91,26 b'' | |||
|
91 | 91 | if (source_page) { |
|
92 | 92 | return false; |
|
93 | 93 | } |
|
94 | ||
|
95 | if ($('#file-tree-wrapper').hasClass('full-load')) { | |
|
96 | // in case our HTML wrapper has full-load class we don't | |
|
97 | // trigger the async load of metadata | |
|
98 | return false; | |
|
99 | } | |
|
100 | ||
|
94 | 101 | var state = getState('metadata'); |
|
95 | 102 | var url_data = { |
|
96 | 103 | 'repo_name': templateContext.repo_name, |
|
97 |
' |
|
|
104 | 'commit_id': state.commit_id, | |
|
98 | 105 | 'f_path': state.f_path |
|
99 | 106 | }; |
|
100 | 107 | |
|
101 |
var url = pyroutes.url('files_ |
|
|
108 | var url = pyroutes.url('files_nodetree_full', url_data); | |
|
102 | 109 | |
|
103 | 110 | metadataRequest = $.ajax({url: url}); |
|
104 | 111 | |
|
105 | 112 | metadataRequest.done(function(data) { |
|
106 | var data = data.metadata; | |
|
107 | var dataLength = data.length; | |
|
108 | for (var i = 0; i < dataLength; i++) { | |
|
109 | var rowData = data[i]; | |
|
110 | var name = rowData.name.replace('\\', '\\\\'); | |
|
111 | ||
|
112 | $('td[title="size-' + name + '"]').html(rowData.size); | |
|
113 | var timeComponent = AgeModule.createTimeComponent( | |
|
114 | rowData.modified_ts, rowData.modified_at); | |
|
115 | $('td[title="modified_at-' + name + '"]').html(timeComponent); | |
|
116 | ||
|
117 | $('td[title="revision-' + name + '"]').html( | |
|
118 | '<div class="tooltip" title="{0}"><pre>r{1}:{2}</pre></div>'.format( | |
|
119 | data[i].message, data[i].revision, data[i].short_id)); | |
|
120 | $('td[title="author-' + name + '"]').html( | |
|
121 | '<span title="{0}">{1}</span>'.format( | |
|
122 | data[i].author, data[i].user_profile)); | |
|
123 | } | |
|
113 | $('#file-tree').html(data); | |
|
124 | 114 | timeagoActivate(); |
|
125 | 115 | }); |
|
126 | 116 | metadataRequest.fail(function (data, textStatus, errorThrown) { |
@@ -138,7 +128,7 b'' | |||
|
138 | 128 | // used for history, and switch to |
|
139 | 129 | var initialCommitData = { |
|
140 | 130 | id: null, |
|
141 |
text: |
|
|
131 | text: '${_("Switch To Commit")}', | |
|
142 | 132 | type: 'sha', |
|
143 | 133 | raw_id: null, |
|
144 | 134 | files_url: null |
@@ -329,5 +319,4 b'' | |||
|
329 | 319 | |
|
330 | 320 | </script> |
|
331 | 321 | |
|
332 | ||
|
333 | 322 | </%def> |
@@ -42,7 +42,9 b'' | |||
|
42 | 42 | </div> |
|
43 | 43 | </div> |
|
44 | 44 | ## file tree is computed from caches, and filled in |
|
45 | <div id="file-tree"> | |
|
45 | 46 | ${c.file_tree} |
|
47 | </div> | |
|
46 | 48 | |
|
47 | 49 | </div> |
|
48 | 50 |
@@ -1,4 +1,4 b'' | |||
|
1 | <div class="browser-body"> | |
|
1 | <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}"> | |
|
2 | 2 | <table class="code-browser rctable"> |
|
3 | 3 | <thead> |
|
4 | 4 | <tr> |
@@ -31,7 +31,7 b'' | |||
|
31 | 31 | <span class="submodule-dir"> |
|
32 | 32 | ${h.link_to_if( |
|
33 | 33 | node.url.startswith('http://') or node.url.startswith('https://'), |
|
34 | node.name,node.url)} | |
|
34 | node.name, node.url)} | |
|
35 | 35 | </span> |
|
36 | 36 | %else: |
|
37 | 37 | <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link"> |
@@ -40,12 +40,30 b'' | |||
|
40 | 40 | %endif |
|
41 | 41 | </td> |
|
42 | 42 | %if node.is_file(): |
|
43 |
<td class="td-size" |
|
|
44 | <td class="td-time" title="${'modified_at-%s' % node.name}"> | |
|
45 | <span class="browser-loading">${_('Loading...')}</span> | |
|
43 | <td class="td-size" data-attr-name="size"> | |
|
44 | % if c.full_load: | |
|
45 | <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span> | |
|
46 | % else: | |
|
47 | ${_('Loading ...')} | |
|
48 | % endif | |
|
49 | </td> | |
|
50 | <td class="td-time" data-attr-name="modified_at"> | |
|
51 | % if c.full_load: | |
|
52 | <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span> | |
|
53 | % endif | |
|
46 | 54 | </td> |
|
47 |
<td class="td-hash" |
|
|
48 | <td class="td-user" title="${'author-%s' % node.name}"></td> | |
|
55 | <td class="td-hash" data-attr-name="commit_id"> | |
|
56 | % if c.full_load: | |
|
57 | <div class="tooltip" title="${node.last_commit.message}"> | |
|
58 | <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre> | |
|
59 | </div> | |
|
60 | % endif | |
|
61 | </td> | |
|
62 | <td class="td-user" data-attr-name="author"> | |
|
63 | % if c.full_load: | |
|
64 | <span data-author="${node.last_commit.author}" title="${node.last_commit.author}">${h.gravatar_with_user(node.last_commit.author)|n}</span> | |
|
65 | % endif | |
|
66 | </td> | |
|
49 | 67 | %else: |
|
50 | 68 | <td></td> |
|
51 | 69 | <td></td> |
@@ -57,4 +75,4 b'' | |||
|
57 | 75 | </tbody> |
|
58 | 76 | <tbody id="tbody_filtered"></tbody> |
|
59 | 77 | </table> |
|
60 | </div> No newline at end of file | |
|
78 | </div> |
@@ -54,7 +54,7 b' class TestFilesController:' | |||
|
54 | 54 | |
|
55 | 55 | params = { |
|
56 | 56 | 'repo_name': backend.repo_name, |
|
57 |
' |
|
|
57 | 'commit_id': commit.raw_id, | |
|
58 | 58 | 'date': commit.date |
|
59 | 59 | } |
|
60 | 60 | assert_dirs_in_response(response, ['docs', 'vcs'], params) |
@@ -135,7 +135,7 b' class TestFilesController:' | |||
|
135 | 135 | files = ['README.rst'] |
|
136 | 136 | params = { |
|
137 | 137 | 'repo_name': backend.repo_name, |
|
138 |
' |
|
|
138 | 'commit_id': commit.raw_id, | |
|
139 | 139 | } |
|
140 | 140 | assert_dirs_in_response(response, dirs, params) |
|
141 | 141 | assert_files_in_response(response, files, params) |
@@ -302,31 +302,34 b' class TestFilesController:' | |||
|
302 | 302 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
303 | 303 | f_path='/', revision='tip'), status=400) |
|
304 | 304 | |
|
305 |
def test_ |
|
|
305 | def test_nodetree_full_success(self, backend, xhr_header): | |
|
306 | 306 | commit = backend.repo.get_commit(commit_idx=173) |
|
307 | 307 | response = self.app.get( |
|
308 |
url('files_ |
|
|
309 |
f_path='/', |
|
|
308 | url('files_nodetree_full', repo_name=backend.repo_name, | |
|
309 | f_path='/', commit_id=commit.raw_id), | |
|
310 | 310 | extra_environ=xhr_header) |
|
311 | 311 | |
|
312 | expected_keys = ['author', 'message', 'modified_at', 'modified_ts', | |
|
313 | 'name', 'revision', 'short_id', 'size'] | |
|
314 | for filename in response.json.get('metadata'): | |
|
315 | for key in expected_keys: | |
|
316 | assert key in filename | |
|
312 | assert_response = AssertResponse(response) | |
|
317 | 313 | |
|
318 | def test_tree_metadata_list_if_file(self, backend, xhr_header): | |
|
314 | for attr in ['data-commit-id', 'data-date', 'data-author']: | |
|
315 | elements = assert_response.get_elements('[{}]'.format(attr)) | |
|
316 | assert len(elements) > 1 | |
|
317 | ||
|
318 | for element in elements: | |
|
319 | assert element.get(attr) | |
|
320 | ||
|
321 | def test_nodetree_full_if_file(self, backend, xhr_header): | |
|
319 | 322 | commit = backend.repo.get_commit(commit_idx=173) |
|
320 | 323 | response = self.app.get( |
|
321 |
url('files_ |
|
|
322 |
f_path='README.rst', |
|
|
324 | url('files_nodetree_full', repo_name=backend.repo_name, | |
|
325 | f_path='README.rst', commit_id=commit.raw_id), | |
|
323 | 326 | extra_environ=xhr_header) |
|
324 |
assert response. |
|
|
327 | assert response.body == '' | |
|
325 | 328 | |
|
326 | 329 | def test_tree_metadata_list_missing_xhr(self, backend): |
|
327 | 330 | self.app.get( |
|
328 |
url('files_ |
|
|
329 |
f_path='/', |
|
|
331 | url('files_nodetree_full', repo_name=backend.repo_name, | |
|
332 | f_path='/', commit_id='tip'), status=400) | |
|
330 | 333 | |
|
331 | 334 | def test_access_empty_repo_redirect_to_summary_with_alert_write_perms( |
|
332 | 335 | self, app, backend_stub, autologin_regular_user, user_regular, |
@@ -917,13 +920,13 b' class TestChangingFiles:' | |||
|
917 | 920 | |
|
918 | 921 | def assert_files_in_response(response, files, params): |
|
919 | 922 | template = ( |
|
920 |
" |
|
|
923 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |
|
921 | 924 | _assert_items_in_response(response, files, template, params) |
|
922 | 925 | |
|
923 | 926 | |
|
924 | 927 | def assert_dirs_in_response(response, dirs, params): |
|
925 | 928 | template = ( |
|
926 |
" |
|
|
929 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |
|
927 | 930 | _assert_items_in_response(response, dirs, template, params) |
|
928 | 931 | |
|
929 | 932 |
@@ -171,6 +171,9 b' class AssertResponse(object):' | |||
|
171 | 171 | assert len(elements) == 1 |
|
172 | 172 | return elements[0] |
|
173 | 173 | |
|
174 | def get_elements(self, css_selector): | |
|
175 | return self._get_elements(css_selector) | |
|
176 | ||
|
174 | 177 | def _get_elements(self, css_selector): |
|
175 | 178 | doc = fromstring(self.response.body) |
|
176 | 179 | sel = CSSSelector(css_selector) |
General Comments 0
You need to be logged in to leave comments.
Login now