Show More
@@ -1103,9 +1103,9 b' def make_map(config):' | |||||
1103 | conditions={'function': check_repo}, |
|
1103 | conditions={'function': check_repo}, | |
1104 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
1104 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
1105 |
|
1105 | |||
1106 |
rmap.connect('files_ |
|
1106 | rmap.connect('files_nodetree_full', | |
1107 |
'/{repo_name}/ |
|
1107 | '/{repo_name}/nodetree_full/{commit_id}/{f_path}', | |
1108 |
controller='files', action=' |
|
1108 | controller='files', action='nodetree_full', | |
1109 | conditions={'function': check_repo}, |
|
1109 | conditions={'function': check_repo}, | |
1110 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) |
|
1110 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
1111 |
|
1111 |
@@ -136,10 +136,12 b' class FilesController(BaseRepoController' | |||||
136 | _namespace = caches.get_repo_namespace_key(namespace_type, repo_name) |
|
136 | _namespace = caches.get_repo_namespace_key(namespace_type, repo_name) | |
137 | return caches.get_cache_manager('repo_cache_long', _namespace) |
|
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 | def _cached_tree(): |
|
141 | def _cached_tree(): | |
141 | log.debug('Generating cached file tree for %s, %s, %s', |
|
142 | log.debug('Generating cached file tree for %s, %s, %s', | |
142 | repo_name, commit_id, f_path) |
|
143 | repo_name, commit_id, f_path) | |
|
144 | c.full_load = full_load | |||
143 | return render('files/files_browser_tree.html') |
|
145 | return render('files/files_browser_tree.html') | |
144 |
|
146 | |||
145 | cache_manager = self.__get_tree_cache_manager( |
|
147 | cache_manager = self.__get_tree_cache_manager( | |
@@ -148,6 +150,10 b' class FilesController(BaseRepoController' | |||||
148 | cache_key = caches.compute_key_from_params( |
|
150 | cache_key = caches.compute_key_from_params( | |
149 | repo_name, commit_id, f_path) |
|
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 | return cache_manager.get(cache_key, createfunc=_cached_tree) |
|
157 | return cache_manager.get(cache_key, createfunc=_cached_tree) | |
152 |
|
158 | |||
153 | def _get_nodelist_at_commit(self, repo_name, commit_id, f_path): |
|
159 | def _get_nodelist_at_commit(self, repo_name, commit_id, f_path): | |
@@ -165,22 +171,6 b' class FilesController(BaseRepoController' | |||||
165 | repo_name, commit_id, f_path) |
|
171 | repo_name, commit_id, f_path) | |
166 | return cache_manager.get(cache_key, createfunc=_cached_nodes) |
|
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 | @LoginRequired() |
|
174 | @LoginRequired() | |
185 | @HasRepoPermissionAnyDecorator( |
|
175 | @HasRepoPermissionAnyDecorator( | |
186 | 'repository.read', 'repository.write', 'repository.admin') |
|
176 | 'repository.read', 'repository.write', 'repository.admin') | |
@@ -246,6 +236,7 b' class FilesController(BaseRepoController' | |||||
246 | c.authors = [] |
|
236 | c.authors = [] | |
247 | c.file_tree = self._get_tree_at_commit( |
|
237 | c.file_tree = self._get_tree_at_commit( | |
248 | repo_name, c.commit.raw_id, f_path) |
|
238 | repo_name, c.commit.raw_id, f_path) | |
|
239 | ||||
249 | except RepositoryError as e: |
|
240 | except RepositoryError as e: | |
250 | h.flash(safe_str(e), category='error') |
|
241 | h.flash(safe_str(e), category='error') | |
251 | raise HTTPNotFound() |
|
242 | raise HTTPNotFound() | |
@@ -1092,23 +1083,32 b' class FilesController(BaseRepoController' | |||||
1092 | @XHRRequired() |
|
1083 | @XHRRequired() | |
1093 | @HasRepoPermissionAnyDecorator( |
|
1084 | @HasRepoPermissionAnyDecorator( | |
1094 | 'repository.read', 'repository.write', 'repository.admin') |
|
1085 | 'repository.read', 'repository.write', 'repository.admin') | |
1095 | @jsonify |
|
1086 | def nodetree_full(self, repo_name, commit_id, f_path): | |
1096 | def metadata_list(self, repo_name, revision, f_path): |
|
|||
1097 | """ |
|
1087 | """ | |
1098 |
Returns |
|
1088 | Returns rendered html of file tree that contains commit date, | |
1099 |
a |
|
1089 | author, revision for the specified combination of | |
|
1090 | repo, commit_id and file path | |||
1100 |
|
1091 | |||
1101 | :param repo_name: name of the repository |
|
1092 | :param repo_name: name of the repository | |
1102 |
:param |
|
1093 | :param commit_id: commit_id of file tree | |
1103 | :param f_path: file path of the requested directory |
|
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 | try: |
|
1098 | try: | |
1108 |
|
|
1099 | dir_node = commit.get_node(f_path) | |
1109 | except RepositoryError as e: |
|
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( |
|
1106 | c.file = dir_node | |
1113 | repo_name, commit, file_node) |
|
1107 | c.commit = commit | |
1114 | return {'metadata': metadata} |
|
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 | SUMMARY_STATS = 'cache_summary_stats' |
|
35 | SUMMARY_STATS = 'cache_summary_stats' | |
36 |
|
36 | |||
37 | # This list of caches gets purged when invalidation happens |
|
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 | DEFAULT_CACHE_MANAGER_CONFIG = { |
|
40 | DEFAULT_CACHE_MANAGER_CONFIG = { | |
41 | 'type': 'memorylru_base', |
|
41 | 'type': 'memorylru_base', |
@@ -4,7 +4,8 b'' | |||||
4 | * DO NOT CHANGE THIS FILE MANUALLY * |
|
4 | * DO NOT CHANGE THIS FILE MANUALLY * | |
5 | * * |
|
5 | * * | |
6 | * * |
|
6 | * * | |
7 |
* This file is automatically generated when the app starts up |
|
7 | * This file is automatically generated when the app starts up with * | |
|
8 | * generate_js_files = true * | |||
8 | * * |
|
9 | * * | |
9 | * To add a route here pass jsroute=True to the route definition in the app * |
|
10 | * To add a route here pass jsroute=True to the route definition in the app * | |
10 | * * |
|
11 | * * | |
@@ -44,7 +45,7 b' function registerRCRoutes() {' | |||||
44 | pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
45 | pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); | |
45 | pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
46 | pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); | |
46 | pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); |
|
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 | pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']); |
|
49 | pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']); | |
49 | pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']); |
|
50 | pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']); | |
50 | } |
|
51 | } |
@@ -91,36 +91,26 b'' | |||||
91 | if (source_page) { |
|
91 | if (source_page) { | |
92 | return false; |
|
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 | var state = getState('metadata'); |
|
101 | var state = getState('metadata'); | |
95 | var url_data = { |
|
102 | var url_data = { | |
96 | 'repo_name': templateContext.repo_name, |
|
103 | 'repo_name': templateContext.repo_name, | |
97 |
' |
|
104 | 'commit_id': state.commit_id, | |
98 | 'f_path': state.f_path |
|
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 | metadataRequest = $.ajax({url: url}); |
|
110 | metadataRequest = $.ajax({url: url}); | |
104 |
|
111 | |||
105 | metadataRequest.done(function(data) { |
|
112 | metadataRequest.done(function(data) { | |
106 | var data = data.metadata; |
|
113 | $('#file-tree').html(data); | |
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 | } |
|
|||
124 | timeagoActivate(); |
|
114 | timeagoActivate(); | |
125 | }); |
|
115 | }); | |
126 | metadataRequest.fail(function (data, textStatus, errorThrown) { |
|
116 | metadataRequest.fail(function (data, textStatus, errorThrown) { | |
@@ -138,7 +128,7 b'' | |||||
138 | // used for history, and switch to |
|
128 | // used for history, and switch to | |
139 | var initialCommitData = { |
|
129 | var initialCommitData = { | |
140 | id: null, |
|
130 | id: null, | |
141 |
text: |
|
131 | text: '${_("Switch To Commit")}', | |
142 | type: 'sha', |
|
132 | type: 'sha', | |
143 | raw_id: null, |
|
133 | raw_id: null, | |
144 | files_url: null |
|
134 | files_url: null | |
@@ -329,5 +319,4 b'' | |||||
329 |
|
319 | |||
330 | </script> |
|
320 | </script> | |
331 |
|
321 | |||
332 |
|
||||
333 | </%def> |
|
322 | </%def> |
@@ -42,7 +42,9 b'' | |||||
42 | </div> |
|
42 | </div> | |
43 | </div> |
|
43 | </div> | |
44 | ## file tree is computed from caches, and filled in |
|
44 | ## file tree is computed from caches, and filled in | |
|
45 | <div id="file-tree"> | |||
45 | ${c.file_tree} |
|
46 | ${c.file_tree} | |
|
47 | </div> | |||
46 |
|
48 | |||
47 | </div> |
|
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 | <table class="code-browser rctable"> |
|
2 | <table class="code-browser rctable"> | |
3 | <thead> |
|
3 | <thead> | |
4 | <tr> |
|
4 | <tr> | |
@@ -40,12 +40,30 b'' | |||||
40 | %endif |
|
40 | %endif | |
41 | </td> |
|
41 | </td> | |
42 | %if node.is_file(): |
|
42 | %if node.is_file(): | |
43 |
<td class="td-size" |
|
43 | <td class="td-size" data-attr-name="size"> | |
44 | <td class="td-time" title="${'modified_at-%s' % node.name}"> |
|
44 | % if c.full_load: | |
45 | <span class="browser-loading">${_('Loading...')}</span> |
|
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 | </td> |
|
54 | </td> | |
47 |
<td class="td-hash" |
|
55 | <td class="td-hash" data-attr-name="commit_id"> | |
48 | <td class="td-user" title="${'author-%s' % node.name}"></td> |
|
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 | %else: |
|
67 | %else: | |
50 | <td></td> |
|
68 | <td></td> | |
51 | <td></td> |
|
69 | <td></td> | |
@@ -57,4 +75,4 b'' | |||||
57 | </tbody> |
|
75 | </tbody> | |
58 | <tbody id="tbody_filtered"></tbody> |
|
76 | <tbody id="tbody_filtered"></tbody> | |
59 | </table> |
|
77 | </table> | |
60 | </div> No newline at end of file |
|
78 | </div> |
@@ -54,7 +54,7 b' class TestFilesController:' | |||||
54 |
|
54 | |||
55 | params = { |
|
55 | params = { | |
56 | 'repo_name': backend.repo_name, |
|
56 | 'repo_name': backend.repo_name, | |
57 |
' |
|
57 | 'commit_id': commit.raw_id, | |
58 | 'date': commit.date |
|
58 | 'date': commit.date | |
59 | } |
|
59 | } | |
60 | assert_dirs_in_response(response, ['docs', 'vcs'], params) |
|
60 | assert_dirs_in_response(response, ['docs', 'vcs'], params) | |
@@ -135,7 +135,7 b' class TestFilesController:' | |||||
135 | files = ['README.rst'] |
|
135 | files = ['README.rst'] | |
136 | params = { |
|
136 | params = { | |
137 | 'repo_name': backend.repo_name, |
|
137 | 'repo_name': backend.repo_name, | |
138 |
' |
|
138 | 'commit_id': commit.raw_id, | |
139 | } |
|
139 | } | |
140 | assert_dirs_in_response(response, dirs, params) |
|
140 | assert_dirs_in_response(response, dirs, params) | |
141 | assert_files_in_response(response, files, params) |
|
141 | assert_files_in_response(response, files, params) | |
@@ -302,31 +302,34 b' class TestFilesController:' | |||||
302 | url('files_nodelist_home', repo_name=backend.repo_name, |
|
302 | url('files_nodelist_home', repo_name=backend.repo_name, | |
303 | f_path='/', revision='tip'), status=400) |
|
303 | f_path='/', revision='tip'), status=400) | |
304 |
|
304 | |||
305 |
def test_ |
|
305 | def test_nodetree_full_success(self, backend, xhr_header): | |
306 | commit = backend.repo.get_commit(commit_idx=173) |
|
306 | commit = backend.repo.get_commit(commit_idx=173) | |
307 | response = self.app.get( |
|
307 | response = self.app.get( | |
308 |
url('files_ |
|
308 | url('files_nodetree_full', repo_name=backend.repo_name, | |
309 |
f_path='/', |
|
309 | f_path='/', commit_id=commit.raw_id), | |
310 | extra_environ=xhr_header) |
|
310 | extra_environ=xhr_header) | |
311 |
|
311 | |||
312 | expected_keys = ['author', 'message', 'modified_at', 'modified_ts', |
|
312 | assert_response = AssertResponse(response) | |
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 |
|
|||
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 | commit = backend.repo.get_commit(commit_idx=173) |
|
322 | commit = backend.repo.get_commit(commit_idx=173) | |
320 | response = self.app.get( |
|
323 | response = self.app.get( | |
321 |
url('files_ |
|
324 | url('files_nodetree_full', repo_name=backend.repo_name, | |
322 |
f_path='README.rst', |
|
325 | f_path='README.rst', commit_id=commit.raw_id), | |
323 | extra_environ=xhr_header) |
|
326 | extra_environ=xhr_header) | |
324 |
assert response. |
|
327 | assert response.body == '' | |
325 |
|
328 | |||
326 | def test_tree_metadata_list_missing_xhr(self, backend): |
|
329 | def test_tree_metadata_list_missing_xhr(self, backend): | |
327 | self.app.get( |
|
330 | self.app.get( | |
328 |
url('files_ |
|
331 | url('files_nodetree_full', repo_name=backend.repo_name, | |
329 |
f_path='/', |
|
332 | f_path='/', commit_id='tip'), status=400) | |
330 |
|
333 | |||
331 | def test_access_empty_repo_redirect_to_summary_with_alert_write_perms( |
|
334 | def test_access_empty_repo_redirect_to_summary_with_alert_write_perms( | |
332 | self, app, backend_stub, autologin_regular_user, user_regular, |
|
335 | self, app, backend_stub, autologin_regular_user, user_regular, | |
@@ -917,13 +920,13 b' class TestChangingFiles:' | |||||
917 |
|
920 | |||
918 | def assert_files_in_response(response, files, params): |
|
921 | def assert_files_in_response(response, files, params): | |
919 | template = ( |
|
922 | template = ( | |
920 |
" |
|
923 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |
921 | _assert_items_in_response(response, files, template, params) |
|
924 | _assert_items_in_response(response, files, template, params) | |
922 |
|
925 | |||
923 |
|
926 | |||
924 | def assert_dirs_in_response(response, dirs, params): |
|
927 | def assert_dirs_in_response(response, dirs, params): | |
925 | template = ( |
|
928 | template = ( | |
926 |
" |
|
929 | 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"') | |
927 | _assert_items_in_response(response, dirs, template, params) |
|
930 | _assert_items_in_response(response, dirs, template, params) | |
928 |
|
931 | |||
929 |
|
932 |
@@ -171,6 +171,9 b' class AssertResponse(object):' | |||||
171 | assert len(elements) == 1 |
|
171 | assert len(elements) == 1 | |
172 | return elements[0] |
|
172 | return elements[0] | |
173 |
|
173 | |||
|
174 | def get_elements(self, css_selector): | |||
|
175 | return self._get_elements(css_selector) | |||
|
176 | ||||
174 | def _get_elements(self, css_selector): |
|
177 | def _get_elements(self, css_selector): | |
175 | doc = fromstring(self.response.body) |
|
178 | doc = fromstring(self.response.body) | |
176 | sel = CSSSelector(css_selector) |
|
179 | sel = CSSSelector(css_selector) |
General Comments 0
You need to be logged in to leave comments.
Login now