##// END OF EJS Templates
files: fixed SVN refs switcher that used old format of diff between files....
dan -
r4293:78ed0439 default
parent child Browse files
Show More
@@ -1,1552 +1,1555 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 import pathlib2
28 import pathlib2
29
29
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.apps._base import RepoAppView
36 from rhodecode.apps._base import RepoAppView
37
37
38
38
39 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 from rhodecode.lib import audit_logger
40 from rhodecode.lib import audit_logger
41 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.view_utils import parse_path_ref
42 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.exceptions import NonRelativePathError
43 from rhodecode.lib.codeblocks import (
43 from rhodecode.lib.codeblocks import (
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 from rhodecode.lib.auth import (
47 from rhodecode.lib.auth import (
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs import path as vcspath
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.conf import settings
52 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.nodes import FileNode
53 from rhodecode.lib.vcs.exceptions import (
53 from rhodecode.lib.vcs.exceptions import (
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 NodeDoesNotExistError, CommitError, NodeError)
56 NodeDoesNotExistError, CommitError, NodeError)
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.db import Repository
59 from rhodecode.model.db import Repository
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class RepoFilesView(RepoAppView):
64 class RepoFilesView(RepoAppView):
65
65
66 @staticmethod
66 @staticmethod
67 def adjust_file_path_for_svn(f_path, repo):
67 def adjust_file_path_for_svn(f_path, repo):
68 """
68 """
69 Computes the relative path of `f_path`.
69 Computes the relative path of `f_path`.
70
70
71 This is mainly based on prefix matching of the recognized tags and
71 This is mainly based on prefix matching of the recognized tags and
72 branches in the underlying repository.
72 branches in the underlying repository.
73 """
73 """
74 tags_and_branches = itertools.chain(
74 tags_and_branches = itertools.chain(
75 repo.branches.iterkeys(),
75 repo.branches.iterkeys(),
76 repo.tags.iterkeys())
76 repo.tags.iterkeys())
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78
78
79 for name in tags_and_branches:
79 for name in tags_and_branches:
80 if f_path.startswith('{}/'.format(name)):
80 if f_path.startswith('{}/'.format(name)):
81 f_path = vcspath.relpath(f_path, name)
81 f_path = vcspath.relpath(f_path, name)
82 break
82 break
83 return f_path
83 return f_path
84
84
85 def load_default_context(self):
85 def load_default_context(self):
86 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 c.enable_downloads = self.db_repo.enable_downloads
88 c.enable_downloads = self.db_repo.enable_downloads
89 return c
89 return c
90
90
91 def _ensure_not_locked(self, commit_id='tip'):
91 def _ensure_not_locked(self, commit_id='tip'):
92 _ = self.request.translate
92 _ = self.request.translate
93
93
94 repo = self.db_repo
94 repo = self.db_repo
95 if repo.enable_locking and repo.locked[0]:
95 if repo.enable_locking and repo.locked[0]:
96 h.flash(_('This repository has been locked by %s on %s')
96 h.flash(_('This repository has been locked by %s on %s')
97 % (h.person_by_id(repo.locked[0]),
97 % (h.person_by_id(repo.locked[0]),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 'warning')
99 'warning')
100 files_url = h.route_path(
100 files_url = h.route_path(
101 'repo_files:default_path',
101 'repo_files:default_path',
102 repo_name=self.db_repo_name, commit_id=commit_id)
102 repo_name=self.db_repo_name, commit_id=commit_id)
103 raise HTTPFound(files_url)
103 raise HTTPFound(files_url)
104
104
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 _ = self.request.translate
106 _ = self.request.translate
107
107
108 if not is_head:
108 if not is_head:
109 message = _('Cannot modify file. '
109 message = _('Cannot modify file. '
110 'Given commit `{}` is not head of a branch.').format(commit_id)
110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 h.flash(message, category='warning')
111 h.flash(message, category='warning')
112
112
113 if json_mode:
113 if json_mode:
114 return message
114 return message
115
115
116 files_url = h.route_path(
116 files_url = h.route_path(
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 f_path=f_path)
118 f_path=f_path)
119 raise HTTPFound(files_url)
119 raise HTTPFound(files_url)
120
120
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 _ = self.request.translate
122 _ = self.request.translate
123
123
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 self.db_repo_name, branch_name)
125 self.db_repo_name, branch_name)
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 branch_name, rule)
128 branch_name, rule)
129 h.flash(message, 'warning')
129 h.flash(message, 'warning')
130
130
131 if json_mode:
131 if json_mode:
132 return message
132 return message
133
133
134 files_url = h.route_path(
134 files_url = h.route_path(
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136
136
137 raise HTTPFound(files_url)
137 raise HTTPFound(files_url)
138
138
139 def _get_commit_and_path(self):
139 def _get_commit_and_path(self):
140 default_commit_id = self.db_repo.landing_rev[1]
140 default_commit_id = self.db_repo.landing_rev[1]
141 default_f_path = '/'
141 default_f_path = '/'
142
142
143 commit_id = self.request.matchdict.get(
143 commit_id = self.request.matchdict.get(
144 'commit_id', default_commit_id)
144 'commit_id', default_commit_id)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 return commit_id, f_path
146 return commit_id, f_path
147
147
148 def _get_default_encoding(self, c):
148 def _get_default_encoding(self, c):
149 enc_list = getattr(c, 'default_encodings', [])
149 enc_list = getattr(c, 'default_encodings', [])
150 return enc_list[0] if enc_list else 'UTF-8'
150 return enc_list[0] if enc_list else 'UTF-8'
151
151
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 """
153 """
154 This is a safe way to get commit. If an error occurs it redirects to
154 This is a safe way to get commit. If an error occurs it redirects to
155 tip with proper message
155 tip with proper message
156
156
157 :param commit_id: id of commit to fetch
157 :param commit_id: id of commit to fetch
158 :param redirect_after: toggle redirection
158 :param redirect_after: toggle redirection
159 """
159 """
160 _ = self.request.translate
160 _ = self.request.translate
161
161
162 try:
162 try:
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 except EmptyRepositoryError:
164 except EmptyRepositoryError:
165 if not redirect_after:
165 if not redirect_after:
166 return None
166 return None
167
167
168 _url = h.route_path(
168 _url = h.route_path(
169 'repo_files_add_file',
169 'repo_files_add_file',
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171
171
172 if h.HasRepoPermissionAny(
172 if h.HasRepoPermissionAny(
173 'repository.write', 'repository.admin')(self.db_repo_name):
173 'repository.write', 'repository.admin')(self.db_repo_name):
174 add_new = h.link_to(
174 add_new = h.link_to(
175 _('Click here to add a new file.'), _url, class_="alert-link")
175 _('Click here to add a new file.'), _url, class_="alert-link")
176 else:
176 else:
177 add_new = ""
177 add_new = ""
178
178
179 h.flash(h.literal(
179 h.flash(h.literal(
180 _('There are no files yet. %s') % add_new), category='warning')
180 _('There are no files yet. %s') % add_new), category='warning')
181 raise HTTPFound(
181 raise HTTPFound(
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183
183
184 except (CommitDoesNotExistError, LookupError):
184 except (CommitDoesNotExistError, LookupError):
185 msg = _('No such commit exists for this repository')
185 msg = _('No such commit exists for this repository')
186 h.flash(msg, category='error')
186 h.flash(msg, category='error')
187 raise HTTPNotFound()
187 raise HTTPNotFound()
188 except RepositoryError as e:
188 except RepositoryError as e:
189 h.flash(safe_str(h.escape(e)), category='error')
189 h.flash(safe_str(h.escape(e)), category='error')
190 raise HTTPNotFound()
190 raise HTTPNotFound()
191
191
192 def _get_filenode_or_redirect(self, commit_obj, path):
192 def _get_filenode_or_redirect(self, commit_obj, path):
193 """
193 """
194 Returns file_node, if error occurs or given path is directory,
194 Returns file_node, if error occurs or given path is directory,
195 it'll redirect to top level path
195 it'll redirect to top level path
196 """
196 """
197 _ = self.request.translate
197 _ = self.request.translate
198
198
199 try:
199 try:
200 file_node = commit_obj.get_node(path)
200 file_node = commit_obj.get_node(path)
201 if file_node.is_dir():
201 if file_node.is_dir():
202 raise RepositoryError('The given path is a directory')
202 raise RepositoryError('The given path is a directory')
203 except CommitDoesNotExistError:
203 except CommitDoesNotExistError:
204 log.exception('No such commit exists for this repository')
204 log.exception('No such commit exists for this repository')
205 h.flash(_('No such commit exists for this repository'), category='error')
205 h.flash(_('No such commit exists for this repository'), category='error')
206 raise HTTPNotFound()
206 raise HTTPNotFound()
207 except RepositoryError as e:
207 except RepositoryError as e:
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 h.flash(safe_str(h.escape(e)), category='error')
209 h.flash(safe_str(h.escape(e)), category='error')
210 raise HTTPNotFound()
210 raise HTTPNotFound()
211
211
212 return file_node
212 return file_node
213
213
214 def _is_valid_head(self, commit_id, repo):
214 def _is_valid_head(self, commit_id, repo):
215 branch_name = sha_commit_id = ''
215 branch_name = sha_commit_id = ''
216 is_head = False
216 is_head = False
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218
218
219 for _branch_name, branch_commit_id in repo.branches.items():
219 for _branch_name, branch_commit_id in repo.branches.items():
220 # simple case we pass in branch name, it's a HEAD
220 # simple case we pass in branch name, it's a HEAD
221 if commit_id == _branch_name:
221 if commit_id == _branch_name:
222 is_head = True
222 is_head = True
223 branch_name = _branch_name
223 branch_name = _branch_name
224 sha_commit_id = branch_commit_id
224 sha_commit_id = branch_commit_id
225 break
225 break
226 # case when we pass in full sha commit_id, which is a head
226 # case when we pass in full sha commit_id, which is a head
227 elif commit_id == branch_commit_id:
227 elif commit_id == branch_commit_id:
228 is_head = True
228 is_head = True
229 branch_name = _branch_name
229 branch_name = _branch_name
230 sha_commit_id = branch_commit_id
230 sha_commit_id = branch_commit_id
231 break
231 break
232
232
233 if h.is_svn(repo) and not repo.is_empty():
233 if h.is_svn(repo) and not repo.is_empty():
234 # Note: Subversion only has one head.
234 # Note: Subversion only has one head.
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 is_head = True
236 is_head = True
237 return branch_name, sha_commit_id, is_head
237 return branch_name, sha_commit_id, is_head
238
238
239 # checked branches, means we only need to try to get the branch/commit_sha
239 # checked branches, means we only need to try to get the branch/commit_sha
240 if not repo.is_empty():
240 if not repo.is_empty():
241 commit = repo.get_commit(commit_id=commit_id)
241 commit = repo.get_commit(commit_id=commit_id)
242 if commit:
242 if commit:
243 branch_name = commit.branch
243 branch_name = commit.branch
244 sha_commit_id = commit.raw_id
244 sha_commit_id = commit.raw_id
245
245
246 return branch_name, sha_commit_id, is_head
246 return branch_name, sha_commit_id, is_head
247
247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
249
249
250 repo_id = self.db_repo.repo_id
250 repo_id = self.db_repo.repo_id
251 force_recache = self.get_recache_flag()
251 force_recache = self.get_recache_flag()
252
252
253 cache_seconds = safe_int(
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = not force_recache and cache_seconds > 0
255 cache_on = not force_recache and cache_seconds > 0
256 log.debug(
256 log.debug(
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 'with caching: %s[TTL: %ss]' % (
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260
260
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
263
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 condition=cache_on)
265 condition=cache_on)
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 ver, repo_id, commit_id, f_path)
268 ver, repo_id, commit_id, f_path)
269
269
270 c.full_load = full_load
270 c.full_load = full_load
271 return render(
271 return render(
272 'rhodecode:templates/files/files_browser_tree.mako',
272 'rhodecode:templates/files/files_browser_tree.mako',
273 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request)
274
274
275 return compute_file_tree(
275 return compute_file_tree(
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
277
277
278 def _get_archive_spec(self, fname):
278 def _get_archive_spec(self, fname):
279 log.debug('Detecting archive spec for: `%s`', fname)
279 log.debug('Detecting archive spec for: `%s`', fname)
280
280
281 fileformat = None
281 fileformat = None
282 ext = None
282 ext = None
283 content_type = None
283 content_type = None
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285
285
286 if fname.endswith(extension):
286 if fname.endswith(extension):
287 fileformat = a_type
287 fileformat = a_type
288 log.debug('archive is of type: %s', fileformat)
288 log.debug('archive is of type: %s', fileformat)
289 ext = extension
289 ext = extension
290 break
290 break
291
291
292 if not fileformat:
292 if not fileformat:
293 raise ValueError()
293 raise ValueError()
294
294
295 # left over part of whole fname is the commit
295 # left over part of whole fname is the commit
296 commit_id = fname[:-len(ext)]
296 commit_id = fname[:-len(ext)]
297
297
298 return commit_id, ext, fileformat, content_type
298 return commit_id, ext, fileformat, content_type
299
299
300 def create_pure_path(self, *parts):
300 def create_pure_path(self, *parts):
301 # Split paths and sanitize them, removing any ../ etc
301 # Split paths and sanitize them, removing any ../ etc
302 sanitized_path = [
302 sanitized_path = [
303 x for x in pathlib2.PurePath(*parts).parts
303 x for x in pathlib2.PurePath(*parts).parts
304 if x not in ['.', '..']]
304 if x not in ['.', '..']]
305
305
306 pure_path = pathlib2.PurePath(*sanitized_path)
306 pure_path = pathlib2.PurePath(*sanitized_path)
307 return pure_path
307 return pure_path
308
308
309 def _is_lf_enabled(self, target_repo):
309 def _is_lf_enabled(self, target_repo):
310 lf_enabled = False
310 lf_enabled = False
311
311
312 lf_key_for_vcs_map = {
312 lf_key_for_vcs_map = {
313 'hg': 'extensions_largefiles',
313 'hg': 'extensions_largefiles',
314 'git': 'vcs_git_lfs_enabled'
314 'git': 'vcs_git_lfs_enabled'
315 }
315 }
316
316
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318
318
319 if lf_key_for_vcs:
319 if lf_key_for_vcs:
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321
321
322 return lf_enabled
322 return lf_enabled
323
323
324 @LoginRequired()
324 @LoginRequired()
325 @HasRepoPermissionAnyDecorator(
325 @HasRepoPermissionAnyDecorator(
326 'repository.read', 'repository.write', 'repository.admin')
326 'repository.read', 'repository.write', 'repository.admin')
327 @view_config(
327 @view_config(
328 route_name='repo_archivefile', request_method='GET',
328 route_name='repo_archivefile', request_method='GET',
329 renderer=None)
329 renderer=None)
330 def repo_archivefile(self):
330 def repo_archivefile(self):
331 # archive cache config
331 # archive cache config
332 from rhodecode import CONFIG
332 from rhodecode import CONFIG
333 _ = self.request.translate
333 _ = self.request.translate
334 self.load_default_context()
334 self.load_default_context()
335 default_at_path = '/'
335 default_at_path = '/'
336 fname = self.request.matchdict['fname']
336 fname = self.request.matchdict['fname']
337 subrepos = self.request.GET.get('subrepos') == 'true'
337 subrepos = self.request.GET.get('subrepos') == 'true'
338 at_path = self.request.GET.get('at_path') or default_at_path
338 at_path = self.request.GET.get('at_path') or default_at_path
339
339
340 if not self.db_repo.enable_downloads:
340 if not self.db_repo.enable_downloads:
341 return Response(_('Downloads disabled'))
341 return Response(_('Downloads disabled'))
342
342
343 try:
343 try:
344 commit_id, ext, fileformat, content_type = \
344 commit_id, ext, fileformat, content_type = \
345 self._get_archive_spec(fname)
345 self._get_archive_spec(fname)
346 except ValueError:
346 except ValueError:
347 return Response(_('Unknown archive type for: `{}`').format(
347 return Response(_('Unknown archive type for: `{}`').format(
348 h.escape(fname)))
348 h.escape(fname)))
349
349
350 try:
350 try:
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 except CommitDoesNotExistError:
352 except CommitDoesNotExistError:
353 return Response(_('Unknown commit_id {}').format(
353 return Response(_('Unknown commit_id {}').format(
354 h.escape(commit_id)))
354 h.escape(commit_id)))
355 except EmptyRepositoryError:
355 except EmptyRepositoryError:
356 return Response(_('Empty repository'))
356 return Response(_('Empty repository'))
357
357
358 try:
358 try:
359 at_path = commit.get_node(at_path).path or default_at_path
359 at_path = commit.get_node(at_path).path or default_at_path
360 except Exception:
360 except Exception:
361 return Response(_('No node at path {} for this repository').format(at_path))
361 return Response(_('No node at path {} for this repository').format(at_path))
362
362
363 path_sha = sha1(at_path)[:8]
363 path_sha = sha1(at_path)[:8]
364
364
365 # original backward compat name of archive
365 # original backward compat name of archive
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 short_sha = safe_str(commit.short_id)
367 short_sha = safe_str(commit.short_id)
368
368
369 if at_path == default_at_path:
369 if at_path == default_at_path:
370 archive_name = '{}-{}{}{}'.format(
370 archive_name = '{}-{}{}{}'.format(
371 clean_name,
371 clean_name,
372 '-sub' if subrepos else '',
372 '-sub' if subrepos else '',
373 short_sha,
373 short_sha,
374 ext)
374 ext)
375 # custom path and new name
375 # custom path and new name
376 else:
376 else:
377 archive_name = '{}-{}{}-{}{}'.format(
377 archive_name = '{}-{}{}-{}{}'.format(
378 clean_name,
378 clean_name,
379 '-sub' if subrepos else '',
379 '-sub' if subrepos else '',
380 short_sha,
380 short_sha,
381 path_sha,
381 path_sha,
382 ext)
382 ext)
383
383
384 use_cached_archive = False
384 use_cached_archive = False
385 archive_cache_enabled = CONFIG.get(
385 archive_cache_enabled = CONFIG.get(
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 cached_archive_path = None
387 cached_archive_path = None
388
388
389 if archive_cache_enabled:
389 if archive_cache_enabled:
390 # check if we it's ok to write
390 # check if we it's ok to write
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 os.makedirs(CONFIG['archive_cache_dir'])
392 os.makedirs(CONFIG['archive_cache_dir'])
393 cached_archive_path = os.path.join(
393 cached_archive_path = os.path.join(
394 CONFIG['archive_cache_dir'], archive_name)
394 CONFIG['archive_cache_dir'], archive_name)
395 if os.path.isfile(cached_archive_path):
395 if os.path.isfile(cached_archive_path):
396 log.debug('Found cached archive in %s', cached_archive_path)
396 log.debug('Found cached archive in %s', cached_archive_path)
397 fd, archive = None, cached_archive_path
397 fd, archive = None, cached_archive_path
398 use_cached_archive = True
398 use_cached_archive = True
399 else:
399 else:
400 log.debug('Archive %s is not yet cached', archive_name)
400 log.debug('Archive %s is not yet cached', archive_name)
401
401
402 if not use_cached_archive:
402 if not use_cached_archive:
403 # generate new archive
403 # generate new archive
404 fd, archive = tempfile.mkstemp()
404 fd, archive = tempfile.mkstemp()
405 log.debug('Creating new temp archive in %s', archive)
405 log.debug('Creating new temp archive in %s', archive)
406 try:
406 try:
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 archive_at_path=at_path)
408 archive_at_path=at_path)
409 except ImproperArchiveTypeError:
409 except ImproperArchiveTypeError:
410 return _('Unknown archive type')
410 return _('Unknown archive type')
411 if archive_cache_enabled:
411 if archive_cache_enabled:
412 # if we generated the archive and we have cache enabled
412 # if we generated the archive and we have cache enabled
413 # let's use this for future
413 # let's use this for future
414 log.debug('Storing new archive in %s', cached_archive_path)
414 log.debug('Storing new archive in %s', cached_archive_path)
415 shutil.move(archive, cached_archive_path)
415 shutil.move(archive, cached_archive_path)
416 archive = cached_archive_path
416 archive = cached_archive_path
417
417
418 # store download action
418 # store download action
419 audit_logger.store_web(
419 audit_logger.store_web(
420 'repo.archive.download', action_data={
420 'repo.archive.download', action_data={
421 'user_agent': self.request.user_agent,
421 'user_agent': self.request.user_agent,
422 'archive_name': archive_name,
422 'archive_name': archive_name,
423 'archive_spec': fname,
423 'archive_spec': fname,
424 'archive_cached': use_cached_archive},
424 'archive_cached': use_cached_archive},
425 user=self._rhodecode_user,
425 user=self._rhodecode_user,
426 repo=self.db_repo,
426 repo=self.db_repo,
427 commit=True
427 commit=True
428 )
428 )
429
429
430 def get_chunked_archive(archive_path):
430 def get_chunked_archive(archive_path):
431 with open(archive_path, 'rb') as stream:
431 with open(archive_path, 'rb') as stream:
432 while True:
432 while True:
433 data = stream.read(16 * 1024)
433 data = stream.read(16 * 1024)
434 if not data:
434 if not data:
435 if fd: # fd means we used temporary file
435 if fd: # fd means we used temporary file
436 os.close(fd)
436 os.close(fd)
437 if not archive_cache_enabled:
437 if not archive_cache_enabled:
438 log.debug('Destroying temp archive %s', archive_path)
438 log.debug('Destroying temp archive %s', archive_path)
439 os.remove(archive_path)
439 os.remove(archive_path)
440 break
440 break
441 yield data
441 yield data
442
442
443 response = Response(app_iter=get_chunked_archive(archive))
443 response = Response(app_iter=get_chunked_archive(archive))
444 response.content_disposition = str(
444 response.content_disposition = str(
445 'attachment; filename=%s' % archive_name)
445 'attachment; filename=%s' % archive_name)
446 response.content_type = str(content_type)
446 response.content_type = str(content_type)
447
447
448 return response
448 return response
449
449
450 def _get_file_node(self, commit_id, f_path):
450 def _get_file_node(self, commit_id, f_path):
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 try:
453 try:
454 node = commit.get_node(f_path)
454 node = commit.get_node(f_path)
455 if node.is_dir():
455 if node.is_dir():
456 raise NodeError('%s path is a %s not a file'
456 raise NodeError('%s path is a %s not a file'
457 % (node, type(node)))
457 % (node, type(node)))
458 except NodeDoesNotExistError:
458 except NodeDoesNotExistError:
459 commit = EmptyCommit(
459 commit = EmptyCommit(
460 commit_id=commit_id,
460 commit_id=commit_id,
461 idx=commit.idx,
461 idx=commit.idx,
462 repo=commit.repository,
462 repo=commit.repository,
463 alias=commit.repository.alias,
463 alias=commit.repository.alias,
464 message=commit.message,
464 message=commit.message,
465 author=commit.author,
465 author=commit.author,
466 date=commit.date)
466 date=commit.date)
467 node = FileNode(f_path, '', commit=commit)
467 node = FileNode(f_path, '', commit=commit)
468 else:
468 else:
469 commit = EmptyCommit(
469 commit = EmptyCommit(
470 repo=self.rhodecode_vcs_repo,
470 repo=self.rhodecode_vcs_repo,
471 alias=self.rhodecode_vcs_repo.alias)
471 alias=self.rhodecode_vcs_repo.alias)
472 node = FileNode(f_path, '', commit=commit)
472 node = FileNode(f_path, '', commit=commit)
473 return node
473 return node
474
474
475 @LoginRequired()
475 @LoginRequired()
476 @HasRepoPermissionAnyDecorator(
476 @HasRepoPermissionAnyDecorator(
477 'repository.read', 'repository.write', 'repository.admin')
477 'repository.read', 'repository.write', 'repository.admin')
478 @view_config(
478 @view_config(
479 route_name='repo_files_diff', request_method='GET',
479 route_name='repo_files_diff', request_method='GET',
480 renderer=None)
480 renderer=None)
481 def repo_files_diff(self):
481 def repo_files_diff(self):
482 c = self.load_default_context()
482 c = self.load_default_context()
483 f_path = self._get_f_path(self.request.matchdict)
483 f_path = self._get_f_path(self.request.matchdict)
484 diff1 = self.request.GET.get('diff1', '')
484 diff1 = self.request.GET.get('diff1', '')
485 diff2 = self.request.GET.get('diff2', '')
485 diff2 = self.request.GET.get('diff2', '')
486
486
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488
488
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 line_context = self.request.GET.get('context', 3)
490 line_context = self.request.GET.get('context', 3)
491
491
492 if not any((diff1, diff2)):
492 if not any((diff1, diff2)):
493 h.flash(
493 h.flash(
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 category='error')
495 category='error')
496 raise HTTPBadRequest()
496 raise HTTPBadRequest()
497
497
498 c.action = self.request.GET.get('diff')
498 c.action = self.request.GET.get('diff')
499 if c.action not in ['download', 'raw']:
499 if c.action not in ['download', 'raw']:
500 compare_url = h.route_path(
500 compare_url = h.route_path(
501 'repo_compare',
501 'repo_compare',
502 repo_name=self.db_repo_name,
502 repo_name=self.db_repo_name,
503 source_ref_type='rev',
503 source_ref_type='rev',
504 source_ref=diff1,
504 source_ref=diff1,
505 target_repo=self.db_repo_name,
505 target_repo=self.db_repo_name,
506 target_ref_type='rev',
506 target_ref_type='rev',
507 target_ref=diff2,
507 target_ref=diff2,
508 _query=dict(f_path=f_path))
508 _query=dict(f_path=f_path))
509 # redirect to new view if we render diff
509 # redirect to new view if we render diff
510 raise HTTPFound(compare_url)
510 raise HTTPFound(compare_url)
511
511
512 try:
512 try:
513 node1 = self._get_file_node(diff1, path1)
513 node1 = self._get_file_node(diff1, path1)
514 node2 = self._get_file_node(diff2, f_path)
514 node2 = self._get_file_node(diff2, f_path)
515 except (RepositoryError, NodeError):
515 except (RepositoryError, NodeError):
516 log.exception("Exception while trying to get node from repository")
516 log.exception("Exception while trying to get node from repository")
517 raise HTTPFound(
517 raise HTTPFound(
518 h.route_path('repo_files', repo_name=self.db_repo_name,
518 h.route_path('repo_files', repo_name=self.db_repo_name,
519 commit_id='tip', f_path=f_path))
519 commit_id='tip', f_path=f_path))
520
520
521 if all(isinstance(node.commit, EmptyCommit)
521 if all(isinstance(node.commit, EmptyCommit)
522 for node in (node1, node2)):
522 for node in (node1, node2)):
523 raise HTTPNotFound()
523 raise HTTPNotFound()
524
524
525 c.commit_1 = node1.commit
525 c.commit_1 = node1.commit
526 c.commit_2 = node2.commit
526 c.commit_2 = node2.commit
527
527
528 if c.action == 'download':
528 if c.action == 'download':
529 _diff = diffs.get_gitdiff(node1, node2,
529 _diff = diffs.get_gitdiff(node1, node2,
530 ignore_whitespace=ignore_whitespace,
530 ignore_whitespace=ignore_whitespace,
531 context=line_context)
531 context=line_context)
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533
533
534 response = Response(self.path_filter.get_raw_patch(diff))
534 response = Response(self.path_filter.get_raw_patch(diff))
535 response.content_type = 'text/plain'
535 response.content_type = 'text/plain'
536 response.content_disposition = (
536 response.content_disposition = (
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 )
538 )
539 charset = self._get_default_encoding(c)
539 charset = self._get_default_encoding(c)
540 if charset:
540 if charset:
541 response.charset = charset
541 response.charset = charset
542 return response
542 return response
543
543
544 elif c.action == 'raw':
544 elif c.action == 'raw':
545 _diff = diffs.get_gitdiff(node1, node2,
545 _diff = diffs.get_gitdiff(node1, node2,
546 ignore_whitespace=ignore_whitespace,
546 ignore_whitespace=ignore_whitespace,
547 context=line_context)
547 context=line_context)
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549
549
550 response = Response(self.path_filter.get_raw_patch(diff))
550 response = Response(self.path_filter.get_raw_patch(diff))
551 response.content_type = 'text/plain'
551 response.content_type = 'text/plain'
552 charset = self._get_default_encoding(c)
552 charset = self._get_default_encoding(c)
553 if charset:
553 if charset:
554 response.charset = charset
554 response.charset = charset
555 return response
555 return response
556
556
557 # in case we ever end up here
557 # in case we ever end up here
558 raise HTTPNotFound()
558 raise HTTPNotFound()
559
559
560 @LoginRequired()
560 @LoginRequired()
561 @HasRepoPermissionAnyDecorator(
561 @HasRepoPermissionAnyDecorator(
562 'repository.read', 'repository.write', 'repository.admin')
562 'repository.read', 'repository.write', 'repository.admin')
563 @view_config(
563 @view_config(
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 renderer=None)
565 renderer=None)
566 def repo_files_diff_2way_redirect(self):
566 def repo_files_diff_2way_redirect(self):
567 """
567 """
568 Kept only to make OLD links work
568 Kept only to make OLD links work
569 """
569 """
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 diff1 = self.request.GET.get('diff1', '')
571 diff1 = self.request.GET.get('diff1', '')
572 diff2 = self.request.GET.get('diff2', '')
572 diff2 = self.request.GET.get('diff2', '')
573
573
574 if not any((diff1, diff2)):
574 if not any((diff1, diff2)):
575 h.flash(
575 h.flash(
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 category='error')
577 category='error')
578 raise HTTPBadRequest()
578 raise HTTPBadRequest()
579
579
580 compare_url = h.route_path(
580 compare_url = h.route_path(
581 'repo_compare',
581 'repo_compare',
582 repo_name=self.db_repo_name,
582 repo_name=self.db_repo_name,
583 source_ref_type='rev',
583 source_ref_type='rev',
584 source_ref=diff1,
584 source_ref=diff1,
585 target_ref_type='rev',
585 target_ref_type='rev',
586 target_ref=diff2,
586 target_ref=diff2,
587 _query=dict(f_path=f_path, diffmode='sideside',
587 _query=dict(f_path=f_path, diffmode='sideside',
588 target_repo=self.db_repo_name,))
588 target_repo=self.db_repo_name,))
589 raise HTTPFound(compare_url)
589 raise HTTPFound(compare_url)
590
590
591 @LoginRequired()
591 @LoginRequired()
592 @HasRepoPermissionAnyDecorator(
592 @HasRepoPermissionAnyDecorator(
593 'repository.read', 'repository.write', 'repository.admin')
593 'repository.read', 'repository.write', 'repository.admin')
594 @view_config(
594 @view_config(
595 route_name='repo_files', request_method='GET',
595 route_name='repo_files', request_method='GET',
596 renderer=None)
596 renderer=None)
597 @view_config(
597 @view_config(
598 route_name='repo_files:default_path', request_method='GET',
598 route_name='repo_files:default_path', request_method='GET',
599 renderer=None)
599 renderer=None)
600 @view_config(
600 @view_config(
601 route_name='repo_files:default_commit', request_method='GET',
601 route_name='repo_files:default_commit', request_method='GET',
602 renderer=None)
602 renderer=None)
603 @view_config(
603 @view_config(
604 route_name='repo_files:rendered', request_method='GET',
604 route_name='repo_files:rendered', request_method='GET',
605 renderer=None)
605 renderer=None)
606 @view_config(
606 @view_config(
607 route_name='repo_files:annotated', request_method='GET',
607 route_name='repo_files:annotated', request_method='GET',
608 renderer=None)
608 renderer=None)
609 def repo_files(self):
609 def repo_files(self):
610 c = self.load_default_context()
610 c = self.load_default_context()
611
611
612 view_name = getattr(self.request.matched_route, 'name', None)
612 view_name = getattr(self.request.matched_route, 'name', None)
613
613
614 c.annotate = view_name == 'repo_files:annotated'
614 c.annotate = view_name == 'repo_files:annotated'
615 # default is false, but .rst/.md files later are auto rendered, we can
615 # default is false, but .rst/.md files later are auto rendered, we can
616 # overwrite auto rendering by setting this GET flag
616 # overwrite auto rendering by setting this GET flag
617 c.renderer = view_name == 'repo_files:rendered' or \
617 c.renderer = view_name == 'repo_files:rendered' or \
618 not self.request.GET.get('no-render', False)
618 not self.request.GET.get('no-render', False)
619
619
620 # redirect to given commit_id from form if given
620 # redirect to given commit_id from form if given
621 get_commit_id = self.request.GET.get('at_rev', None)
621 get_commit_id = self.request.GET.get('at_rev', None)
622 if get_commit_id:
622 if get_commit_id:
623 self._get_commit_or_redirect(get_commit_id)
623 self._get_commit_or_redirect(get_commit_id)
624
624
625 commit_id, f_path = self._get_commit_and_path()
625 commit_id, f_path = self._get_commit_and_path()
626 c.commit = self._get_commit_or_redirect(commit_id)
626 c.commit = self._get_commit_or_redirect(commit_id)
627 c.branch = self.request.GET.get('branch', None)
627 c.branch = self.request.GET.get('branch', None)
628 c.f_path = f_path
628 c.f_path = f_path
629
629
630 # prev link
630 # prev link
631 try:
631 try:
632 prev_commit = c.commit.prev(c.branch)
632 prev_commit = c.commit.prev(c.branch)
633 c.prev_commit = prev_commit
633 c.prev_commit = prev_commit
634 c.url_prev = h.route_path(
634 c.url_prev = h.route_path(
635 'repo_files', repo_name=self.db_repo_name,
635 'repo_files', repo_name=self.db_repo_name,
636 commit_id=prev_commit.raw_id, f_path=f_path)
636 commit_id=prev_commit.raw_id, f_path=f_path)
637 if c.branch:
637 if c.branch:
638 c.url_prev += '?branch=%s' % c.branch
638 c.url_prev += '?branch=%s' % c.branch
639 except (CommitDoesNotExistError, VCSError):
639 except (CommitDoesNotExistError, VCSError):
640 c.url_prev = '#'
640 c.url_prev = '#'
641 c.prev_commit = EmptyCommit()
641 c.prev_commit = EmptyCommit()
642
642
643 # next link
643 # next link
644 try:
644 try:
645 next_commit = c.commit.next(c.branch)
645 next_commit = c.commit.next(c.branch)
646 c.next_commit = next_commit
646 c.next_commit = next_commit
647 c.url_next = h.route_path(
647 c.url_next = h.route_path(
648 'repo_files', repo_name=self.db_repo_name,
648 'repo_files', repo_name=self.db_repo_name,
649 commit_id=next_commit.raw_id, f_path=f_path)
649 commit_id=next_commit.raw_id, f_path=f_path)
650 if c.branch:
650 if c.branch:
651 c.url_next += '?branch=%s' % c.branch
651 c.url_next += '?branch=%s' % c.branch
652 except (CommitDoesNotExistError, VCSError):
652 except (CommitDoesNotExistError, VCSError):
653 c.url_next = '#'
653 c.url_next = '#'
654 c.next_commit = EmptyCommit()
654 c.next_commit = EmptyCommit()
655
655
656 # files or dirs
656 # files or dirs
657 try:
657 try:
658 c.file = c.commit.get_node(f_path)
658 c.file = c.commit.get_node(f_path)
659 c.file_author = True
659 c.file_author = True
660 c.file_tree = ''
660 c.file_tree = ''
661
661
662 # load file content
662 # load file content
663 if c.file.is_file():
663 if c.file.is_file():
664 c.lf_node = {}
664 c.lf_node = {}
665
665
666 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 has_lf_enabled = self._is_lf_enabled(self.db_repo)
667 if has_lf_enabled:
667 if has_lf_enabled:
668 c.lf_node = c.file.get_largefile_node()
668 c.lf_node = c.file.get_largefile_node()
669
669
670 c.file_source_page = 'true'
670 c.file_source_page = 'true'
671 c.file_last_commit = c.file.last_commit
671 c.file_last_commit = c.file.last_commit
672
672
673 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
674
674
675 if not (c.file_size_too_big or c.file.is_binary):
675 if not (c.file_size_too_big or c.file.is_binary):
676 if c.annotate: # annotation has precedence over renderer
676 if c.annotate: # annotation has precedence over renderer
677 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 c.annotated_lines = filenode_as_annotated_lines_tokens(
678 c.file
678 c.file
679 )
679 )
680 else:
680 else:
681 c.renderer = (
681 c.renderer = (
682 c.renderer and h.renderer_from_filename(c.file.path)
682 c.renderer and h.renderer_from_filename(c.file.path)
683 )
683 )
684 if not c.renderer:
684 if not c.renderer:
685 c.lines = filenode_as_lines_tokens(c.file)
685 c.lines = filenode_as_lines_tokens(c.file)
686
686
687 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
688 commit_id, self.rhodecode_vcs_repo)
688 commit_id, self.rhodecode_vcs_repo)
689 c.on_branch_head = is_head
689 c.on_branch_head = is_head
690
690
691 branch = c.commit.branch if (
691 branch = c.commit.branch if (
692 c.commit.branch and '/' not in c.commit.branch) else None
692 c.commit.branch and '/' not in c.commit.branch) else None
693 c.branch_or_raw_id = branch or c.commit.raw_id
693 c.branch_or_raw_id = branch or c.commit.raw_id
694 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
695
695
696 author = c.file_last_commit.author
696 author = c.file_last_commit.author
697 c.authors = [[
697 c.authors = [[
698 h.email(author),
698 h.email(author),
699 h.person(author, 'username_or_name_or_email'),
699 h.person(author, 'username_or_name_or_email'),
700 1
700 1
701 ]]
701 ]]
702
702
703 else: # load tree content at path
703 else: # load tree content at path
704 c.file_source_page = 'false'
704 c.file_source_page = 'false'
705 c.authors = []
705 c.authors = []
706 # this loads a simple tree without metadata to speed things up
706 # this loads a simple tree without metadata to speed things up
707 # later via ajax we call repo_nodetree_full and fetch whole
707 # later via ajax we call repo_nodetree_full and fetch whole
708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
709
709
710 c.readme_data, c.readme_file = \
710 c.readme_data, c.readme_file = \
711 self._get_readme_data(self.db_repo, c.visual.default_renderer,
711 self._get_readme_data(self.db_repo, c.visual.default_renderer,
712 c.commit.raw_id, f_path)
712 c.commit.raw_id, f_path)
713
713
714 except RepositoryError as e:
714 except RepositoryError as e:
715 h.flash(safe_str(h.escape(e)), category='error')
715 h.flash(safe_str(h.escape(e)), category='error')
716 raise HTTPNotFound()
716 raise HTTPNotFound()
717
717
718 if self.request.environ.get('HTTP_X_PJAX'):
718 if self.request.environ.get('HTTP_X_PJAX'):
719 html = render('rhodecode:templates/files/files_pjax.mako',
719 html = render('rhodecode:templates/files/files_pjax.mako',
720 self._get_template_context(c), self.request)
720 self._get_template_context(c), self.request)
721 else:
721 else:
722 html = render('rhodecode:templates/files/files.mako',
722 html = render('rhodecode:templates/files/files.mako',
723 self._get_template_context(c), self.request)
723 self._get_template_context(c), self.request)
724 return Response(html)
724 return Response(html)
725
725
726 @HasRepoPermissionAnyDecorator(
726 @HasRepoPermissionAnyDecorator(
727 'repository.read', 'repository.write', 'repository.admin')
727 'repository.read', 'repository.write', 'repository.admin')
728 @view_config(
728 @view_config(
729 route_name='repo_files:annotated_previous', request_method='GET',
729 route_name='repo_files:annotated_previous', request_method='GET',
730 renderer=None)
730 renderer=None)
731 def repo_files_annotated_previous(self):
731 def repo_files_annotated_previous(self):
732 self.load_default_context()
732 self.load_default_context()
733
733
734 commit_id, f_path = self._get_commit_and_path()
734 commit_id, f_path = self._get_commit_and_path()
735 commit = self._get_commit_or_redirect(commit_id)
735 commit = self._get_commit_or_redirect(commit_id)
736 prev_commit_id = commit.raw_id
736 prev_commit_id = commit.raw_id
737 line_anchor = self.request.GET.get('line_anchor')
737 line_anchor = self.request.GET.get('line_anchor')
738 is_file = False
738 is_file = False
739 try:
739 try:
740 _file = commit.get_node(f_path)
740 _file = commit.get_node(f_path)
741 is_file = _file.is_file()
741 is_file = _file.is_file()
742 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
742 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
743 pass
743 pass
744
744
745 if is_file:
745 if is_file:
746 history = commit.get_path_history(f_path)
746 history = commit.get_path_history(f_path)
747 prev_commit_id = history[1].raw_id \
747 prev_commit_id = history[1].raw_id \
748 if len(history) > 1 else prev_commit_id
748 if len(history) > 1 else prev_commit_id
749 prev_url = h.route_path(
749 prev_url = h.route_path(
750 'repo_files:annotated', repo_name=self.db_repo_name,
750 'repo_files:annotated', repo_name=self.db_repo_name,
751 commit_id=prev_commit_id, f_path=f_path,
751 commit_id=prev_commit_id, f_path=f_path,
752 _anchor='L{}'.format(line_anchor))
752 _anchor='L{}'.format(line_anchor))
753
753
754 raise HTTPFound(prev_url)
754 raise HTTPFound(prev_url)
755
755
756 @LoginRequired()
756 @LoginRequired()
757 @HasRepoPermissionAnyDecorator(
757 @HasRepoPermissionAnyDecorator(
758 'repository.read', 'repository.write', 'repository.admin')
758 'repository.read', 'repository.write', 'repository.admin')
759 @view_config(
759 @view_config(
760 route_name='repo_nodetree_full', request_method='GET',
760 route_name='repo_nodetree_full', request_method='GET',
761 renderer=None, xhr=True)
761 renderer=None, xhr=True)
762 @view_config(
762 @view_config(
763 route_name='repo_nodetree_full:default_path', request_method='GET',
763 route_name='repo_nodetree_full:default_path', request_method='GET',
764 renderer=None, xhr=True)
764 renderer=None, xhr=True)
765 def repo_nodetree_full(self):
765 def repo_nodetree_full(self):
766 """
766 """
767 Returns rendered html of file tree that contains commit date,
767 Returns rendered html of file tree that contains commit date,
768 author, commit_id for the specified combination of
768 author, commit_id for the specified combination of
769 repo, commit_id and file path
769 repo, commit_id and file path
770 """
770 """
771 c = self.load_default_context()
771 c = self.load_default_context()
772
772
773 commit_id, f_path = self._get_commit_and_path()
773 commit_id, f_path = self._get_commit_and_path()
774 commit = self._get_commit_or_redirect(commit_id)
774 commit = self._get_commit_or_redirect(commit_id)
775 try:
775 try:
776 dir_node = commit.get_node(f_path)
776 dir_node = commit.get_node(f_path)
777 except RepositoryError as e:
777 except RepositoryError as e:
778 return Response('error: {}'.format(h.escape(safe_str(e))))
778 return Response('error: {}'.format(h.escape(safe_str(e))))
779
779
780 if dir_node.is_file():
780 if dir_node.is_file():
781 return Response('')
781 return Response('')
782
782
783 c.file = dir_node
783 c.file = dir_node
784 c.commit = commit
784 c.commit = commit
785
785
786 html = self._get_tree_at_commit(
786 html = self._get_tree_at_commit(
787 c, commit.raw_id, dir_node.path, full_load=True)
787 c, commit.raw_id, dir_node.path, full_load=True)
788
788
789 return Response(html)
789 return Response(html)
790
790
791 def _get_attachement_headers(self, f_path):
791 def _get_attachement_headers(self, f_path):
792 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
792 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
793 safe_path = f_name.replace('"', '\\"')
793 safe_path = f_name.replace('"', '\\"')
794 encoded_path = urllib.quote(f_name)
794 encoded_path = urllib.quote(f_name)
795
795
796 return "attachment; " \
796 return "attachment; " \
797 "filename=\"{}\"; " \
797 "filename=\"{}\"; " \
798 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
798 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
799
799
800 @LoginRequired()
800 @LoginRequired()
801 @HasRepoPermissionAnyDecorator(
801 @HasRepoPermissionAnyDecorator(
802 'repository.read', 'repository.write', 'repository.admin')
802 'repository.read', 'repository.write', 'repository.admin')
803 @view_config(
803 @view_config(
804 route_name='repo_file_raw', request_method='GET',
804 route_name='repo_file_raw', request_method='GET',
805 renderer=None)
805 renderer=None)
806 def repo_file_raw(self):
806 def repo_file_raw(self):
807 """
807 """
808 Action for show as raw, some mimetypes are "rendered",
808 Action for show as raw, some mimetypes are "rendered",
809 those include images, icons.
809 those include images, icons.
810 """
810 """
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 raw_mimetype_mapping = {
817 raw_mimetype_mapping = {
818 # map original mimetype to a mimetype used for "show as raw"
818 # map original mimetype to a mimetype used for "show as raw"
819 # you can also provide a content-disposition to override the
819 # you can also provide a content-disposition to override the
820 # default "attachment" disposition.
820 # default "attachment" disposition.
821 # orig_type: (new_type, new_dispo)
821 # orig_type: (new_type, new_dispo)
822
822
823 # show images inline:
823 # show images inline:
824 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
824 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
825 # for example render an SVG with javascript inside or even render
825 # for example render an SVG with javascript inside or even render
826 # HTML.
826 # HTML.
827 'image/x-icon': ('image/x-icon', 'inline'),
827 'image/x-icon': ('image/x-icon', 'inline'),
828 'image/png': ('image/png', 'inline'),
828 'image/png': ('image/png', 'inline'),
829 'image/gif': ('image/gif', 'inline'),
829 'image/gif': ('image/gif', 'inline'),
830 'image/jpeg': ('image/jpeg', 'inline'),
830 'image/jpeg': ('image/jpeg', 'inline'),
831 'application/pdf': ('application/pdf', 'inline'),
831 'application/pdf': ('application/pdf', 'inline'),
832 }
832 }
833
833
834 mimetype = file_node.mimetype
834 mimetype = file_node.mimetype
835 try:
835 try:
836 mimetype, disposition = raw_mimetype_mapping[mimetype]
836 mimetype, disposition = raw_mimetype_mapping[mimetype]
837 except KeyError:
837 except KeyError:
838 # we don't know anything special about this, handle it safely
838 # we don't know anything special about this, handle it safely
839 if file_node.is_binary:
839 if file_node.is_binary:
840 # do same as download raw for binary files
840 # do same as download raw for binary files
841 mimetype, disposition = 'application/octet-stream', 'attachment'
841 mimetype, disposition = 'application/octet-stream', 'attachment'
842 else:
842 else:
843 # do not just use the original mimetype, but force text/plain,
843 # do not just use the original mimetype, but force text/plain,
844 # otherwise it would serve text/html and that might be unsafe.
844 # otherwise it would serve text/html and that might be unsafe.
845 # Note: underlying vcs library fakes text/plain mimetype if the
845 # Note: underlying vcs library fakes text/plain mimetype if the
846 # mimetype can not be determined and it thinks it is not
846 # mimetype can not be determined and it thinks it is not
847 # binary.This might lead to erroneous text display in some
847 # binary.This might lead to erroneous text display in some
848 # cases, but helps in other cases, like with text files
848 # cases, but helps in other cases, like with text files
849 # without extension.
849 # without extension.
850 mimetype, disposition = 'text/plain', 'inline'
850 mimetype, disposition = 'text/plain', 'inline'
851
851
852 if disposition == 'attachment':
852 if disposition == 'attachment':
853 disposition = self._get_attachement_headers(f_path)
853 disposition = self._get_attachement_headers(f_path)
854
854
855 stream_content = file_node.stream_bytes()
855 stream_content = file_node.stream_bytes()
856
856
857 response = Response(app_iter=stream_content)
857 response = Response(app_iter=stream_content)
858 response.content_disposition = disposition
858 response.content_disposition = disposition
859 response.content_type = mimetype
859 response.content_type = mimetype
860
860
861 charset = self._get_default_encoding(c)
861 charset = self._get_default_encoding(c)
862 if charset:
862 if charset:
863 response.charset = charset
863 response.charset = charset
864
864
865 return response
865 return response
866
866
867 @LoginRequired()
867 @LoginRequired()
868 @HasRepoPermissionAnyDecorator(
868 @HasRepoPermissionAnyDecorator(
869 'repository.read', 'repository.write', 'repository.admin')
869 'repository.read', 'repository.write', 'repository.admin')
870 @view_config(
870 @view_config(
871 route_name='repo_file_download', request_method='GET',
871 route_name='repo_file_download', request_method='GET',
872 renderer=None)
872 renderer=None)
873 @view_config(
873 @view_config(
874 route_name='repo_file_download:legacy', request_method='GET',
874 route_name='repo_file_download:legacy', request_method='GET',
875 renderer=None)
875 renderer=None)
876 def repo_file_download(self):
876 def repo_file_download(self):
877 c = self.load_default_context()
877 c = self.load_default_context()
878
878
879 commit_id, f_path = self._get_commit_and_path()
879 commit_id, f_path = self._get_commit_and_path()
880 commit = self._get_commit_or_redirect(commit_id)
880 commit = self._get_commit_or_redirect(commit_id)
881 file_node = self._get_filenode_or_redirect(commit, f_path)
881 file_node = self._get_filenode_or_redirect(commit, f_path)
882
882
883 if self.request.GET.get('lf'):
883 if self.request.GET.get('lf'):
884 # only if lf get flag is passed, we download this file
884 # only if lf get flag is passed, we download this file
885 # as LFS/Largefile
885 # as LFS/Largefile
886 lf_node = file_node.get_largefile_node()
886 lf_node = file_node.get_largefile_node()
887 if lf_node:
887 if lf_node:
888 # overwrite our pointer with the REAL large-file
888 # overwrite our pointer with the REAL large-file
889 file_node = lf_node
889 file_node = lf_node
890
890
891 disposition = self._get_attachement_headers(f_path)
891 disposition = self._get_attachement_headers(f_path)
892
892
893 stream_content = file_node.stream_bytes()
893 stream_content = file_node.stream_bytes()
894
894
895 response = Response(app_iter=stream_content)
895 response = Response(app_iter=stream_content)
896 response.content_disposition = disposition
896 response.content_disposition = disposition
897 response.content_type = file_node.mimetype
897 response.content_type = file_node.mimetype
898
898
899 charset = self._get_default_encoding(c)
899 charset = self._get_default_encoding(c)
900 if charset:
900 if charset:
901 response.charset = charset
901 response.charset = charset
902
902
903 return response
903 return response
904
904
905 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
905 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
906
906
907 cache_seconds = safe_int(
907 cache_seconds = safe_int(
908 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
908 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
909 cache_on = cache_seconds > 0
909 cache_on = cache_seconds > 0
910 log.debug(
910 log.debug(
911 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
911 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
912 'with caching: %s[TTL: %ss]' % (
912 'with caching: %s[TTL: %ss]' % (
913 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
913 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
914
914
915 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
915 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
916 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
916 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
917
917
918 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
918 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
919 condition=cache_on)
919 condition=cache_on)
920 def compute_file_search(repo_id, commit_id, f_path):
920 def compute_file_search(repo_id, commit_id, f_path):
921 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
921 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
922 repo_id, commit_id, f_path)
922 repo_id, commit_id, f_path)
923 try:
923 try:
924 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
924 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
926 log.exception(safe_str(e))
926 log.exception(safe_str(e))
927 h.flash(safe_str(h.escape(e)), category='error')
927 h.flash(safe_str(h.escape(e)), category='error')
928 raise HTTPFound(h.route_path(
928 raise HTTPFound(h.route_path(
929 'repo_files', repo_name=self.db_repo_name,
929 'repo_files', repo_name=self.db_repo_name,
930 commit_id='tip', f_path='/'))
930 commit_id='tip', f_path='/'))
931
931
932 return _d + _f
932 return _d + _f
933
933
934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
936
936
937 @LoginRequired()
937 @LoginRequired()
938 @HasRepoPermissionAnyDecorator(
938 @HasRepoPermissionAnyDecorator(
939 'repository.read', 'repository.write', 'repository.admin')
939 'repository.read', 'repository.write', 'repository.admin')
940 @view_config(
940 @view_config(
941 route_name='repo_files_nodelist', request_method='GET',
941 route_name='repo_files_nodelist', request_method='GET',
942 renderer='json_ext', xhr=True)
942 renderer='json_ext', xhr=True)
943 def repo_nodelist(self):
943 def repo_nodelist(self):
944 self.load_default_context()
944 self.load_default_context()
945
945
946 commit_id, f_path = self._get_commit_and_path()
946 commit_id, f_path = self._get_commit_and_path()
947 commit = self._get_commit_or_redirect(commit_id)
947 commit = self._get_commit_or_redirect(commit_id)
948
948
949 metadata = self._get_nodelist_at_commit(
949 metadata = self._get_nodelist_at_commit(
950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
951 return {'nodes': metadata}
951 return {'nodes': metadata}
952
952
953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
954 items = []
954 items = []
955 for name, commit_id in branches_or_tags.items():
955 for name, commit_id in branches_or_tags.items():
956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
957 items.append((sym_ref, name, ref_type))
957 items.append((sym_ref, name, ref_type))
958 return items
958 return items
959
959
960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
961 return commit_id
961 return commit_id
962
962
963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
964 return commit_id
965
966 # NOTE(dan): old code we used in "diff" mode compare
964 new_f_path = vcspath.join(name, f_path)
967 new_f_path = vcspath.join(name, f_path)
965 return u'%s@%s' % (new_f_path, commit_id)
968 return u'%s@%s' % (new_f_path, commit_id)
966
969
967 def _get_node_history(self, commit_obj, f_path, commits=None):
970 def _get_node_history(self, commit_obj, f_path, commits=None):
968 """
971 """
969 get commit history for given node
972 get commit history for given node
970
973
971 :param commit_obj: commit to calculate history
974 :param commit_obj: commit to calculate history
972 :param f_path: path for node to calculate history for
975 :param f_path: path for node to calculate history for
973 :param commits: if passed don't calculate history and take
976 :param commits: if passed don't calculate history and take
974 commits defined in this list
977 commits defined in this list
975 """
978 """
976 _ = self.request.translate
979 _ = self.request.translate
977
980
978 # calculate history based on tip
981 # calculate history based on tip
979 tip = self.rhodecode_vcs_repo.get_commit()
982 tip = self.rhodecode_vcs_repo.get_commit()
980 if commits is None:
983 if commits is None:
981 pre_load = ["author", "branch"]
984 pre_load = ["author", "branch"]
982 try:
985 try:
983 commits = tip.get_path_history(f_path, pre_load=pre_load)
986 commits = tip.get_path_history(f_path, pre_load=pre_load)
984 except (NodeDoesNotExistError, CommitError):
987 except (NodeDoesNotExistError, CommitError):
985 # this node is not present at tip!
988 # this node is not present at tip!
986 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
989 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
987
990
988 history = []
991 history = []
989 commits_group = ([], _("Changesets"))
992 commits_group = ([], _("Changesets"))
990 for commit in commits:
993 for commit in commits:
991 branch = ' (%s)' % commit.branch if commit.branch else ''
994 branch = ' (%s)' % commit.branch if commit.branch else ''
992 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
995 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
993 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
996 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
994 history.append(commits_group)
997 history.append(commits_group)
995
998
996 symbolic_reference = self._symbolic_reference
999 symbolic_reference = self._symbolic_reference
997
1000
998 if self.rhodecode_vcs_repo.alias == 'svn':
1001 if self.rhodecode_vcs_repo.alias == 'svn':
999 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1002 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1000 f_path, self.rhodecode_vcs_repo)
1003 f_path, self.rhodecode_vcs_repo)
1001 if adjusted_f_path != f_path:
1004 if adjusted_f_path != f_path:
1002 log.debug(
1005 log.debug(
1003 'Recognized svn tag or branch in file "%s", using svn '
1006 'Recognized svn tag or branch in file "%s", using svn '
1004 'specific symbolic references', f_path)
1007 'specific symbolic references', f_path)
1005 f_path = adjusted_f_path
1008 f_path = adjusted_f_path
1006 symbolic_reference = self._symbolic_reference_svn
1009 symbolic_reference = self._symbolic_reference_svn
1007
1010
1008 branches = self._create_references(
1011 branches = self._create_references(
1009 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1012 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1010 branches_group = (branches, _("Branches"))
1013 branches_group = (branches, _("Branches"))
1011
1014
1012 tags = self._create_references(
1015 tags = self._create_references(
1013 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1016 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1014 tags_group = (tags, _("Tags"))
1017 tags_group = (tags, _("Tags"))
1015
1018
1016 history.append(branches_group)
1019 history.append(branches_group)
1017 history.append(tags_group)
1020 history.append(tags_group)
1018
1021
1019 return history, commits
1022 return history, commits
1020
1023
1021 @LoginRequired()
1024 @LoginRequired()
1022 @HasRepoPermissionAnyDecorator(
1025 @HasRepoPermissionAnyDecorator(
1023 'repository.read', 'repository.write', 'repository.admin')
1026 'repository.read', 'repository.write', 'repository.admin')
1024 @view_config(
1027 @view_config(
1025 route_name='repo_file_history', request_method='GET',
1028 route_name='repo_file_history', request_method='GET',
1026 renderer='json_ext')
1029 renderer='json_ext')
1027 def repo_file_history(self):
1030 def repo_file_history(self):
1028 self.load_default_context()
1031 self.load_default_context()
1029
1032
1030 commit_id, f_path = self._get_commit_and_path()
1033 commit_id, f_path = self._get_commit_and_path()
1031 commit = self._get_commit_or_redirect(commit_id)
1034 commit = self._get_commit_or_redirect(commit_id)
1032 file_node = self._get_filenode_or_redirect(commit, f_path)
1035 file_node = self._get_filenode_or_redirect(commit, f_path)
1033
1036
1034 if file_node.is_file():
1037 if file_node.is_file():
1035 file_history, _hist = self._get_node_history(commit, f_path)
1038 file_history, _hist = self._get_node_history(commit, f_path)
1036
1039
1037 res = []
1040 res = []
1038 for obj in file_history:
1041 for obj in file_history:
1039 res.append({
1042 res.append({
1040 'text': obj[1],
1043 'text': obj[1],
1041 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1044 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1042 })
1045 })
1043
1046
1044 data = {
1047 data = {
1045 'more': False,
1048 'more': False,
1046 'results': res
1049 'results': res
1047 }
1050 }
1048 return data
1051 return data
1049
1052
1050 log.warning('Cannot fetch history for directory')
1053 log.warning('Cannot fetch history for directory')
1051 raise HTTPBadRequest()
1054 raise HTTPBadRequest()
1052
1055
1053 @LoginRequired()
1056 @LoginRequired()
1054 @HasRepoPermissionAnyDecorator(
1057 @HasRepoPermissionAnyDecorator(
1055 'repository.read', 'repository.write', 'repository.admin')
1058 'repository.read', 'repository.write', 'repository.admin')
1056 @view_config(
1059 @view_config(
1057 route_name='repo_file_authors', request_method='GET',
1060 route_name='repo_file_authors', request_method='GET',
1058 renderer='rhodecode:templates/files/file_authors_box.mako')
1061 renderer='rhodecode:templates/files/file_authors_box.mako')
1059 def repo_file_authors(self):
1062 def repo_file_authors(self):
1060 c = self.load_default_context()
1063 c = self.load_default_context()
1061
1064
1062 commit_id, f_path = self._get_commit_and_path()
1065 commit_id, f_path = self._get_commit_and_path()
1063 commit = self._get_commit_or_redirect(commit_id)
1066 commit = self._get_commit_or_redirect(commit_id)
1064 file_node = self._get_filenode_or_redirect(commit, f_path)
1067 file_node = self._get_filenode_or_redirect(commit, f_path)
1065
1068
1066 if not file_node.is_file():
1069 if not file_node.is_file():
1067 raise HTTPBadRequest()
1070 raise HTTPBadRequest()
1068
1071
1069 c.file_last_commit = file_node.last_commit
1072 c.file_last_commit = file_node.last_commit
1070 if self.request.GET.get('annotate') == '1':
1073 if self.request.GET.get('annotate') == '1':
1071 # use _hist from annotation if annotation mode is on
1074 # use _hist from annotation if annotation mode is on
1072 commit_ids = set(x[1] for x in file_node.annotate)
1075 commit_ids = set(x[1] for x in file_node.annotate)
1073 _hist = (
1076 _hist = (
1074 self.rhodecode_vcs_repo.get_commit(commit_id)
1077 self.rhodecode_vcs_repo.get_commit(commit_id)
1075 for commit_id in commit_ids)
1078 for commit_id in commit_ids)
1076 else:
1079 else:
1077 _f_history, _hist = self._get_node_history(commit, f_path)
1080 _f_history, _hist = self._get_node_history(commit, f_path)
1078 c.file_author = False
1081 c.file_author = False
1079
1082
1080 unique = collections.OrderedDict()
1083 unique = collections.OrderedDict()
1081 for commit in _hist:
1084 for commit in _hist:
1082 author = commit.author
1085 author = commit.author
1083 if author not in unique:
1086 if author not in unique:
1084 unique[commit.author] = [
1087 unique[commit.author] = [
1085 h.email(author),
1088 h.email(author),
1086 h.person(author, 'username_or_name_or_email'),
1089 h.person(author, 'username_or_name_or_email'),
1087 1 # counter
1090 1 # counter
1088 ]
1091 ]
1089
1092
1090 else:
1093 else:
1091 # increase counter
1094 # increase counter
1092 unique[commit.author][2] += 1
1095 unique[commit.author][2] += 1
1093
1096
1094 c.authors = [val for val in unique.values()]
1097 c.authors = [val for val in unique.values()]
1095
1098
1096 return self._get_template_context(c)
1099 return self._get_template_context(c)
1097
1100
1098 @LoginRequired()
1101 @LoginRequired()
1099 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1102 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1100 @view_config(
1103 @view_config(
1101 route_name='repo_files_remove_file', request_method='GET',
1104 route_name='repo_files_remove_file', request_method='GET',
1102 renderer='rhodecode:templates/files/files_delete.mako')
1105 renderer='rhodecode:templates/files/files_delete.mako')
1103 def repo_files_remove_file(self):
1106 def repo_files_remove_file(self):
1104 _ = self.request.translate
1107 _ = self.request.translate
1105 c = self.load_default_context()
1108 c = self.load_default_context()
1106 commit_id, f_path = self._get_commit_and_path()
1109 commit_id, f_path = self._get_commit_and_path()
1107
1110
1108 self._ensure_not_locked()
1111 self._ensure_not_locked()
1109 _branch_name, _sha_commit_id, is_head = \
1112 _branch_name, _sha_commit_id, is_head = \
1110 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1113 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1111
1114
1112 self.forbid_non_head(is_head, f_path)
1115 self.forbid_non_head(is_head, f_path)
1113 self.check_branch_permission(_branch_name)
1116 self.check_branch_permission(_branch_name)
1114
1117
1115 c.commit = self._get_commit_or_redirect(commit_id)
1118 c.commit = self._get_commit_or_redirect(commit_id)
1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1119 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1117
1120
1118 c.default_message = _(
1121 c.default_message = _(
1119 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1122 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1120 c.f_path = f_path
1123 c.f_path = f_path
1121
1124
1122 return self._get_template_context(c)
1125 return self._get_template_context(c)
1123
1126
1124 @LoginRequired()
1127 @LoginRequired()
1125 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1128 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1126 @CSRFRequired()
1129 @CSRFRequired()
1127 @view_config(
1130 @view_config(
1128 route_name='repo_files_delete_file', request_method='POST',
1131 route_name='repo_files_delete_file', request_method='POST',
1129 renderer=None)
1132 renderer=None)
1130 def repo_files_delete_file(self):
1133 def repo_files_delete_file(self):
1131 _ = self.request.translate
1134 _ = self.request.translate
1132
1135
1133 c = self.load_default_context()
1136 c = self.load_default_context()
1134 commit_id, f_path = self._get_commit_and_path()
1137 commit_id, f_path = self._get_commit_and_path()
1135
1138
1136 self._ensure_not_locked()
1139 self._ensure_not_locked()
1137 _branch_name, _sha_commit_id, is_head = \
1140 _branch_name, _sha_commit_id, is_head = \
1138 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1141 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1139
1142
1140 self.forbid_non_head(is_head, f_path)
1143 self.forbid_non_head(is_head, f_path)
1141 self.check_branch_permission(_branch_name)
1144 self.check_branch_permission(_branch_name)
1142
1145
1143 c.commit = self._get_commit_or_redirect(commit_id)
1146 c.commit = self._get_commit_or_redirect(commit_id)
1144 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1147 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1145
1148
1146 c.default_message = _(
1149 c.default_message = _(
1147 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1150 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1148 c.f_path = f_path
1151 c.f_path = f_path
1149 node_path = f_path
1152 node_path = f_path
1150 author = self._rhodecode_db_user.full_contact
1153 author = self._rhodecode_db_user.full_contact
1151 message = self.request.POST.get('message') or c.default_message
1154 message = self.request.POST.get('message') or c.default_message
1152 try:
1155 try:
1153 nodes = {
1156 nodes = {
1154 node_path: {
1157 node_path: {
1155 'content': ''
1158 'content': ''
1156 }
1159 }
1157 }
1160 }
1158 ScmModel().delete_nodes(
1161 ScmModel().delete_nodes(
1159 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1162 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1160 message=message,
1163 message=message,
1161 nodes=nodes,
1164 nodes=nodes,
1162 parent_commit=c.commit,
1165 parent_commit=c.commit,
1163 author=author,
1166 author=author,
1164 )
1167 )
1165
1168
1166 h.flash(
1169 h.flash(
1167 _('Successfully deleted file `{}`').format(
1170 _('Successfully deleted file `{}`').format(
1168 h.escape(f_path)), category='success')
1171 h.escape(f_path)), category='success')
1169 except Exception:
1172 except Exception:
1170 log.exception('Error during commit operation')
1173 log.exception('Error during commit operation')
1171 h.flash(_('Error occurred during commit'), category='error')
1174 h.flash(_('Error occurred during commit'), category='error')
1172 raise HTTPFound(
1175 raise HTTPFound(
1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1176 h.route_path('repo_commit', repo_name=self.db_repo_name,
1174 commit_id='tip'))
1177 commit_id='tip'))
1175
1178
1176 @LoginRequired()
1179 @LoginRequired()
1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1180 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1178 @view_config(
1181 @view_config(
1179 route_name='repo_files_edit_file', request_method='GET',
1182 route_name='repo_files_edit_file', request_method='GET',
1180 renderer='rhodecode:templates/files/files_edit.mako')
1183 renderer='rhodecode:templates/files/files_edit.mako')
1181 def repo_files_edit_file(self):
1184 def repo_files_edit_file(self):
1182 _ = self.request.translate
1185 _ = self.request.translate
1183 c = self.load_default_context()
1186 c = self.load_default_context()
1184 commit_id, f_path = self._get_commit_and_path()
1187 commit_id, f_path = self._get_commit_and_path()
1185
1188
1186 self._ensure_not_locked()
1189 self._ensure_not_locked()
1187 _branch_name, _sha_commit_id, is_head = \
1190 _branch_name, _sha_commit_id, is_head = \
1188 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1191 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1189
1192
1190 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1193 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1191 self.check_branch_permission(_branch_name, commit_id=commit_id)
1194 self.check_branch_permission(_branch_name, commit_id=commit_id)
1192
1195
1193 c.commit = self._get_commit_or_redirect(commit_id)
1196 c.commit = self._get_commit_or_redirect(commit_id)
1194 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1197 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1195
1198
1196 if c.file.is_binary:
1199 if c.file.is_binary:
1197 files_url = h.route_path(
1200 files_url = h.route_path(
1198 'repo_files',
1201 'repo_files',
1199 repo_name=self.db_repo_name,
1202 repo_name=self.db_repo_name,
1200 commit_id=c.commit.raw_id, f_path=f_path)
1203 commit_id=c.commit.raw_id, f_path=f_path)
1201 raise HTTPFound(files_url)
1204 raise HTTPFound(files_url)
1202
1205
1203 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1206 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1204 c.f_path = f_path
1207 c.f_path = f_path
1205
1208
1206 return self._get_template_context(c)
1209 return self._get_template_context(c)
1207
1210
1208 @LoginRequired()
1211 @LoginRequired()
1209 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1212 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1210 @CSRFRequired()
1213 @CSRFRequired()
1211 @view_config(
1214 @view_config(
1212 route_name='repo_files_update_file', request_method='POST',
1215 route_name='repo_files_update_file', request_method='POST',
1213 renderer=None)
1216 renderer=None)
1214 def repo_files_update_file(self):
1217 def repo_files_update_file(self):
1215 _ = self.request.translate
1218 _ = self.request.translate
1216 c = self.load_default_context()
1219 c = self.load_default_context()
1217 commit_id, f_path = self._get_commit_and_path()
1220 commit_id, f_path = self._get_commit_and_path()
1218
1221
1219 self._ensure_not_locked()
1222 self._ensure_not_locked()
1220
1223
1221 c.commit = self._get_commit_or_redirect(commit_id)
1224 c.commit = self._get_commit_or_redirect(commit_id)
1222 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1225 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1223
1226
1224 if c.file.is_binary:
1227 if c.file.is_binary:
1225 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1228 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1226 commit_id=c.commit.raw_id, f_path=f_path))
1229 commit_id=c.commit.raw_id, f_path=f_path))
1227
1230
1228 _branch_name, _sha_commit_id, is_head = \
1231 _branch_name, _sha_commit_id, is_head = \
1229 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1232 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1230
1233
1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1234 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1235 self.check_branch_permission(_branch_name, commit_id=commit_id)
1233
1236
1234 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1237 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1235 c.f_path = f_path
1238 c.f_path = f_path
1236
1239
1237 old_content = c.file.content
1240 old_content = c.file.content
1238 sl = old_content.splitlines(1)
1241 sl = old_content.splitlines(1)
1239 first_line = sl[0] if sl else ''
1242 first_line = sl[0] if sl else ''
1240
1243
1241 r_post = self.request.POST
1244 r_post = self.request.POST
1242 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1245 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1243 line_ending_mode = detect_mode(first_line, 0)
1246 line_ending_mode = detect_mode(first_line, 0)
1244 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1247 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1245
1248
1246 message = r_post.get('message') or c.default_message
1249 message = r_post.get('message') or c.default_message
1247 org_node_path = c.file.unicode_path
1250 org_node_path = c.file.unicode_path
1248 filename = r_post['filename']
1251 filename = r_post['filename']
1249
1252
1250 root_path = c.file.dir_path
1253 root_path = c.file.dir_path
1251 pure_path = self.create_pure_path(root_path, filename)
1254 pure_path = self.create_pure_path(root_path, filename)
1252 node_path = safe_unicode(bytes(pure_path))
1255 node_path = safe_unicode(bytes(pure_path))
1253
1256
1254 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1257 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1255 commit_id=commit_id)
1258 commit_id=commit_id)
1256 if content == old_content and node_path == org_node_path:
1259 if content == old_content and node_path == org_node_path:
1257 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1260 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1258 category='warning')
1261 category='warning')
1259 raise HTTPFound(default_redirect_url)
1262 raise HTTPFound(default_redirect_url)
1260
1263
1261 try:
1264 try:
1262 mapping = {
1265 mapping = {
1263 org_node_path: {
1266 org_node_path: {
1264 'org_filename': org_node_path,
1267 'org_filename': org_node_path,
1265 'filename': node_path,
1268 'filename': node_path,
1266 'content': content,
1269 'content': content,
1267 'lexer': '',
1270 'lexer': '',
1268 'op': 'mod',
1271 'op': 'mod',
1269 'mode': c.file.mode
1272 'mode': c.file.mode
1270 }
1273 }
1271 }
1274 }
1272
1275
1273 commit = ScmModel().update_nodes(
1276 commit = ScmModel().update_nodes(
1274 user=self._rhodecode_db_user.user_id,
1277 user=self._rhodecode_db_user.user_id,
1275 repo=self.db_repo,
1278 repo=self.db_repo,
1276 message=message,
1279 message=message,
1277 nodes=mapping,
1280 nodes=mapping,
1278 parent_commit=c.commit,
1281 parent_commit=c.commit,
1279 )
1282 )
1280
1283
1281 h.flash(_('Successfully committed changes to file `{}`').format(
1284 h.flash(_('Successfully committed changes to file `{}`').format(
1282 h.escape(f_path)), category='success')
1285 h.escape(f_path)), category='success')
1283 default_redirect_url = h.route_path(
1286 default_redirect_url = h.route_path(
1284 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1287 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1285
1288
1286 except Exception:
1289 except Exception:
1287 log.exception('Error occurred during commit')
1290 log.exception('Error occurred during commit')
1288 h.flash(_('Error occurred during commit'), category='error')
1291 h.flash(_('Error occurred during commit'), category='error')
1289
1292
1290 raise HTTPFound(default_redirect_url)
1293 raise HTTPFound(default_redirect_url)
1291
1294
1292 @LoginRequired()
1295 @LoginRequired()
1293 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1296 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1294 @view_config(
1297 @view_config(
1295 route_name='repo_files_add_file', request_method='GET',
1298 route_name='repo_files_add_file', request_method='GET',
1296 renderer='rhodecode:templates/files/files_add.mako')
1299 renderer='rhodecode:templates/files/files_add.mako')
1297 @view_config(
1300 @view_config(
1298 route_name='repo_files_upload_file', request_method='GET',
1301 route_name='repo_files_upload_file', request_method='GET',
1299 renderer='rhodecode:templates/files/files_upload.mako')
1302 renderer='rhodecode:templates/files/files_upload.mako')
1300 def repo_files_add_file(self):
1303 def repo_files_add_file(self):
1301 _ = self.request.translate
1304 _ = self.request.translate
1302 c = self.load_default_context()
1305 c = self.load_default_context()
1303 commit_id, f_path = self._get_commit_and_path()
1306 commit_id, f_path = self._get_commit_and_path()
1304
1307
1305 self._ensure_not_locked()
1308 self._ensure_not_locked()
1306
1309
1307 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1310 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1308 if c.commit is None:
1311 if c.commit is None:
1309 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1312 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1310
1313
1311 if self.rhodecode_vcs_repo.is_empty():
1314 if self.rhodecode_vcs_repo.is_empty():
1312 # for empty repository we cannot check for current branch, we rely on
1315 # for empty repository we cannot check for current branch, we rely on
1313 # c.commit.branch instead
1316 # c.commit.branch instead
1314 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1317 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1315 else:
1318 else:
1316 _branch_name, _sha_commit_id, is_head = \
1319 _branch_name, _sha_commit_id, is_head = \
1317 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1320 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1318
1321
1319 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1322 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1320 self.check_branch_permission(_branch_name, commit_id=commit_id)
1323 self.check_branch_permission(_branch_name, commit_id=commit_id)
1321
1324
1322 c.default_message = (_('Added file via RhodeCode Enterprise'))
1325 c.default_message = (_('Added file via RhodeCode Enterprise'))
1323 c.f_path = f_path.lstrip('/') # ensure not relative path
1326 c.f_path = f_path.lstrip('/') # ensure not relative path
1324
1327
1325 return self._get_template_context(c)
1328 return self._get_template_context(c)
1326
1329
1327 @LoginRequired()
1330 @LoginRequired()
1328 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1331 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1329 @CSRFRequired()
1332 @CSRFRequired()
1330 @view_config(
1333 @view_config(
1331 route_name='repo_files_create_file', request_method='POST',
1334 route_name='repo_files_create_file', request_method='POST',
1332 renderer=None)
1335 renderer=None)
1333 def repo_files_create_file(self):
1336 def repo_files_create_file(self):
1334 _ = self.request.translate
1337 _ = self.request.translate
1335 c = self.load_default_context()
1338 c = self.load_default_context()
1336 commit_id, f_path = self._get_commit_and_path()
1339 commit_id, f_path = self._get_commit_and_path()
1337
1340
1338 self._ensure_not_locked()
1341 self._ensure_not_locked()
1339
1342
1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1343 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1341 if c.commit is None:
1344 if c.commit is None:
1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1345 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1343
1346
1344 # calculate redirect URL
1347 # calculate redirect URL
1345 if self.rhodecode_vcs_repo.is_empty():
1348 if self.rhodecode_vcs_repo.is_empty():
1346 default_redirect_url = h.route_path(
1349 default_redirect_url = h.route_path(
1347 'repo_summary', repo_name=self.db_repo_name)
1350 'repo_summary', repo_name=self.db_repo_name)
1348 else:
1351 else:
1349 default_redirect_url = h.route_path(
1352 default_redirect_url = h.route_path(
1350 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1353 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1351
1354
1352 if self.rhodecode_vcs_repo.is_empty():
1355 if self.rhodecode_vcs_repo.is_empty():
1353 # for empty repository we cannot check for current branch, we rely on
1356 # for empty repository we cannot check for current branch, we rely on
1354 # c.commit.branch instead
1357 # c.commit.branch instead
1355 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1358 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1356 else:
1359 else:
1357 _branch_name, _sha_commit_id, is_head = \
1360 _branch_name, _sha_commit_id, is_head = \
1358 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1361 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1359
1362
1360 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1363 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1361 self.check_branch_permission(_branch_name, commit_id=commit_id)
1364 self.check_branch_permission(_branch_name, commit_id=commit_id)
1362
1365
1363 c.default_message = (_('Added file via RhodeCode Enterprise'))
1366 c.default_message = (_('Added file via RhodeCode Enterprise'))
1364 c.f_path = f_path
1367 c.f_path = f_path
1365
1368
1366 r_post = self.request.POST
1369 r_post = self.request.POST
1367 message = r_post.get('message') or c.default_message
1370 message = r_post.get('message') or c.default_message
1368 filename = r_post.get('filename')
1371 filename = r_post.get('filename')
1369 unix_mode = 0
1372 unix_mode = 0
1370 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1373 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1371
1374
1372 if not filename:
1375 if not filename:
1373 # If there's no commit, redirect to repo summary
1376 # If there's no commit, redirect to repo summary
1374 if type(c.commit) is EmptyCommit:
1377 if type(c.commit) is EmptyCommit:
1375 redirect_url = h.route_path(
1378 redirect_url = h.route_path(
1376 'repo_summary', repo_name=self.db_repo_name)
1379 'repo_summary', repo_name=self.db_repo_name)
1377 else:
1380 else:
1378 redirect_url = default_redirect_url
1381 redirect_url = default_redirect_url
1379 h.flash(_('No filename specified'), category='warning')
1382 h.flash(_('No filename specified'), category='warning')
1380 raise HTTPFound(redirect_url)
1383 raise HTTPFound(redirect_url)
1381
1384
1382 root_path = f_path
1385 root_path = f_path
1383 pure_path = self.create_pure_path(root_path, filename)
1386 pure_path = self.create_pure_path(root_path, filename)
1384 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1387 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1385
1388
1386 author = self._rhodecode_db_user.full_contact
1389 author = self._rhodecode_db_user.full_contact
1387 nodes = {
1390 nodes = {
1388 node_path: {
1391 node_path: {
1389 'content': content
1392 'content': content
1390 }
1393 }
1391 }
1394 }
1392
1395
1393 try:
1396 try:
1394
1397
1395 commit = ScmModel().create_nodes(
1398 commit = ScmModel().create_nodes(
1396 user=self._rhodecode_db_user.user_id,
1399 user=self._rhodecode_db_user.user_id,
1397 repo=self.db_repo,
1400 repo=self.db_repo,
1398 message=message,
1401 message=message,
1399 nodes=nodes,
1402 nodes=nodes,
1400 parent_commit=c.commit,
1403 parent_commit=c.commit,
1401 author=author,
1404 author=author,
1402 )
1405 )
1403
1406
1404 h.flash(_('Successfully committed new file `{}`').format(
1407 h.flash(_('Successfully committed new file `{}`').format(
1405 h.escape(node_path)), category='success')
1408 h.escape(node_path)), category='success')
1406
1409
1407 default_redirect_url = h.route_path(
1410 default_redirect_url = h.route_path(
1408 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1411 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1409
1412
1410 except NonRelativePathError:
1413 except NonRelativePathError:
1411 log.exception('Non Relative path found')
1414 log.exception('Non Relative path found')
1412 h.flash(_('The location specified must be a relative path and must not '
1415 h.flash(_('The location specified must be a relative path and must not '
1413 'contain .. in the path'), category='warning')
1416 'contain .. in the path'), category='warning')
1414 raise HTTPFound(default_redirect_url)
1417 raise HTTPFound(default_redirect_url)
1415 except (NodeError, NodeAlreadyExistsError) as e:
1418 except (NodeError, NodeAlreadyExistsError) as e:
1416 h.flash(_(h.escape(e)), category='error')
1419 h.flash(_(h.escape(e)), category='error')
1417 except Exception:
1420 except Exception:
1418 log.exception('Error occurred during commit')
1421 log.exception('Error occurred during commit')
1419 h.flash(_('Error occurred during commit'), category='error')
1422 h.flash(_('Error occurred during commit'), category='error')
1420
1423
1421 raise HTTPFound(default_redirect_url)
1424 raise HTTPFound(default_redirect_url)
1422
1425
1423 @LoginRequired()
1426 @LoginRequired()
1424 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1427 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1425 @CSRFRequired()
1428 @CSRFRequired()
1426 @view_config(
1429 @view_config(
1427 route_name='repo_files_upload_file', request_method='POST',
1430 route_name='repo_files_upload_file', request_method='POST',
1428 renderer='json_ext')
1431 renderer='json_ext')
1429 def repo_files_upload_file(self):
1432 def repo_files_upload_file(self):
1430 _ = self.request.translate
1433 _ = self.request.translate
1431 c = self.load_default_context()
1434 c = self.load_default_context()
1432 commit_id, f_path = self._get_commit_and_path()
1435 commit_id, f_path = self._get_commit_and_path()
1433
1436
1434 self._ensure_not_locked()
1437 self._ensure_not_locked()
1435
1438
1436 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1439 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1437 if c.commit is None:
1440 if c.commit is None:
1438 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1441 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1439
1442
1440 # calculate redirect URL
1443 # calculate redirect URL
1441 if self.rhodecode_vcs_repo.is_empty():
1444 if self.rhodecode_vcs_repo.is_empty():
1442 default_redirect_url = h.route_path(
1445 default_redirect_url = h.route_path(
1443 'repo_summary', repo_name=self.db_repo_name)
1446 'repo_summary', repo_name=self.db_repo_name)
1444 else:
1447 else:
1445 default_redirect_url = h.route_path(
1448 default_redirect_url = h.route_path(
1446 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1449 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1447
1450
1448 if self.rhodecode_vcs_repo.is_empty():
1451 if self.rhodecode_vcs_repo.is_empty():
1449 # for empty repository we cannot check for current branch, we rely on
1452 # for empty repository we cannot check for current branch, we rely on
1450 # c.commit.branch instead
1453 # c.commit.branch instead
1451 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1454 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1452 else:
1455 else:
1453 _branch_name, _sha_commit_id, is_head = \
1456 _branch_name, _sha_commit_id, is_head = \
1454 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1457 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1455
1458
1456 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1459 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1457 if error:
1460 if error:
1458 return {
1461 return {
1459 'error': error,
1462 'error': error,
1460 'redirect_url': default_redirect_url
1463 'redirect_url': default_redirect_url
1461 }
1464 }
1462 error = self.check_branch_permission(_branch_name, json_mode=True)
1465 error = self.check_branch_permission(_branch_name, json_mode=True)
1463 if error:
1466 if error:
1464 return {
1467 return {
1465 'error': error,
1468 'error': error,
1466 'redirect_url': default_redirect_url
1469 'redirect_url': default_redirect_url
1467 }
1470 }
1468
1471
1469 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1472 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1470 c.f_path = f_path
1473 c.f_path = f_path
1471
1474
1472 r_post = self.request.POST
1475 r_post = self.request.POST
1473
1476
1474 message = c.default_message
1477 message = c.default_message
1475 user_message = r_post.getall('message')
1478 user_message = r_post.getall('message')
1476 if isinstance(user_message, list) and user_message:
1479 if isinstance(user_message, list) and user_message:
1477 # we take the first from duplicated results if it's not empty
1480 # we take the first from duplicated results if it's not empty
1478 message = user_message[0] if user_message[0] else message
1481 message = user_message[0] if user_message[0] else message
1479
1482
1480 nodes = {}
1483 nodes = {}
1481
1484
1482 for file_obj in r_post.getall('files_upload') or []:
1485 for file_obj in r_post.getall('files_upload') or []:
1483 content = file_obj.file
1486 content = file_obj.file
1484 filename = file_obj.filename
1487 filename = file_obj.filename
1485
1488
1486 root_path = f_path
1489 root_path = f_path
1487 pure_path = self.create_pure_path(root_path, filename)
1490 pure_path = self.create_pure_path(root_path, filename)
1488 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1491 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1489
1492
1490 nodes[node_path] = {
1493 nodes[node_path] = {
1491 'content': content
1494 'content': content
1492 }
1495 }
1493
1496
1494 if not nodes:
1497 if not nodes:
1495 error = 'missing files'
1498 error = 'missing files'
1496 return {
1499 return {
1497 'error': error,
1500 'error': error,
1498 'redirect_url': default_redirect_url
1501 'redirect_url': default_redirect_url
1499 }
1502 }
1500
1503
1501 author = self._rhodecode_db_user.full_contact
1504 author = self._rhodecode_db_user.full_contact
1502
1505
1503 try:
1506 try:
1504 commit = ScmModel().create_nodes(
1507 commit = ScmModel().create_nodes(
1505 user=self._rhodecode_db_user.user_id,
1508 user=self._rhodecode_db_user.user_id,
1506 repo=self.db_repo,
1509 repo=self.db_repo,
1507 message=message,
1510 message=message,
1508 nodes=nodes,
1511 nodes=nodes,
1509 parent_commit=c.commit,
1512 parent_commit=c.commit,
1510 author=author,
1513 author=author,
1511 )
1514 )
1512 if len(nodes) == 1:
1515 if len(nodes) == 1:
1513 flash_message = _('Successfully committed {} new files').format(len(nodes))
1516 flash_message = _('Successfully committed {} new files').format(len(nodes))
1514 else:
1517 else:
1515 flash_message = _('Successfully committed 1 new file')
1518 flash_message = _('Successfully committed 1 new file')
1516
1519
1517 h.flash(flash_message, category='success')
1520 h.flash(flash_message, category='success')
1518
1521
1519 default_redirect_url = h.route_path(
1522 default_redirect_url = h.route_path(
1520 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1523 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1521
1524
1522 except NonRelativePathError:
1525 except NonRelativePathError:
1523 log.exception('Non Relative path found')
1526 log.exception('Non Relative path found')
1524 error = _('The location specified must be a relative path and must not '
1527 error = _('The location specified must be a relative path and must not '
1525 'contain .. in the path')
1528 'contain .. in the path')
1526 h.flash(error, category='warning')
1529 h.flash(error, category='warning')
1527
1530
1528 return {
1531 return {
1529 'error': error,
1532 'error': error,
1530 'redirect_url': default_redirect_url
1533 'redirect_url': default_redirect_url
1531 }
1534 }
1532 except (NodeError, NodeAlreadyExistsError) as e:
1535 except (NodeError, NodeAlreadyExistsError) as e:
1533 error = h.escape(e)
1536 error = h.escape(e)
1534 h.flash(error, category='error')
1537 h.flash(error, category='error')
1535
1538
1536 return {
1539 return {
1537 'error': error,
1540 'error': error,
1538 'redirect_url': default_redirect_url
1541 'redirect_url': default_redirect_url
1539 }
1542 }
1540 except Exception:
1543 except Exception:
1541 log.exception('Error occurred during commit')
1544 log.exception('Error occurred during commit')
1542 error = _('Error occurred during commit')
1545 error = _('Error occurred during commit')
1543 h.flash(error, category='error')
1546 h.flash(error, category='error')
1544 return {
1547 return {
1545 'error': error,
1548 'error': error,
1546 'redirect_url': default_redirect_url
1549 'redirect_url': default_redirect_url
1547 }
1550 }
1548
1551
1549 return {
1552 return {
1550 'error': None,
1553 'error': None,
1551 'redirect_url': default_redirect_url
1554 'redirect_url': default_redirect_url
1552 }
1555 }
@@ -1,1 +1,1 b''
1 {"results": [{"text": "Changesets", "children": [{"text": "r625:ead8f28a4bc2", "type": "sha", "id": "ead8f28a4bc2f45ecfb148a6b8a89758b9654a84"}, {"text": "r535:c093f94d6d35", "type": "sha", "id": "c093f94d6d358f13c55a687da66c30c41cca4153"}, {"text": "r534:559f640ec08b", "type": "sha", "id": "559f640ec08b2a14c4a9ac863d8ca273545b8885"}, {"text": "r490:02a940b4ee37", "type": "sha", "id": "02a940b4ee371ec64ef5b4c4870a5c89dc7fb98a"}, {"text": "r464:b45a4153a2d7", "type": "sha", "id": "b45a4153a2d7adb8a78b63d35d39fac44a4320a6"}, {"text": "r460:0a54e66b9450", "type": "sha", "id": "0a54e66b94502409074b163cd93c1233dcc0413f"}, {"text": "r457:a7bf2f6bf3d5", "type": "sha", "id": "a7bf2f6bf3d5273da4bcd2032a891acae5a45e2b"}, {"text": "r456:7266de0154b4", "type": "sha", "id": "7266de0154b4da7c42ba3d788876056dbf116b5a"}, {"text": "r455:666de4ee6507", "type": "sha", "id": "666de4ee65074cd3e37ea01e75f65bd3e4c336bb"}, {"text": "r453:91acc599141c", "type": "sha", "id": "91acc599141c87f03e0e3551dcaacf4492632e58"}, {"text": "r442:40a2d5d71b75", "type": "sha", "id": "40a2d5d71b758e7eafc84a324ed55142cba22f42"}, {"text": "r440:d1f898326327", "type": "sha", "id": "d1f898326327e20524fe22417c22d71064fe54a1"}, {"text": "r420:162a36830c23", "type": "sha", "id": "162a36830c23ccf1bf1873157fd0c8d0dfc7c817"}, {"text": "r345:c994f0de03b2", "type": "sha", "id": "c994f0de03b2a0aa848a04fc2c0d7e737dba31fc"}, {"text": "r340:5d3d4d2c262e", "type": "sha", "id": "5d3d4d2c262e17b247d405feceeb09ff7408c940"}, {"text": "r334:4d4278a6390e", "type": "sha", "id": "4d4278a6390e42f4fc777ecf1b9b628e77da8e22"}, {"text": "r298:00dffb625166", "type": "sha", "id": "00dffb62516650bc5050d818eb47ea1ca207954d"}, {"text": "r297:47b6be9a812e", "type": "sha", "id": "47b6be9a812ec3ed0384001a458a759f0f583fe2"}, {"text": "r289:1589fed841cd", "type": "sha", "id": "1589fed841cd9ef33155f8560727809ac3ada2c8"}, {"text": "r285:afafd0ee2821", "type": "sha", "id": "afafd0ee28218ab979678213cb96e9e4dbd7359b"}, {"text": "r284:639b115ed2b0", "type": "sha", "id": "639b115ed2b02017824005b5ae66282c6e25eba8"}, {"text": "r283:fcf7562d7305", "type": "sha", "id": "fcf7562d7305affc94fe20dc89a34aefd2b8aa1e"}, {"text": "r256:ec8cbdb5f364", "type": "sha", "id": "ec8cbdb5f364fce7843cbf148c3d95d86f935339"}, {"text": "r255:0d74d2e2bdf3", "type": "sha", "id": "0d74d2e2bdf3dcd5ee9fe4fcfe9016c5c6486f35"}, {"text": "r243:6894ad7d8223", "type": "sha", "id": "6894ad7d8223b1e6853e9fdaa2c38d3f0cef1e38"}, {"text": "r231:31b3f4b599fa", "type": "sha", "id": "31b3f4b599fae5f12cf438c73403679cdf923d75"}, {"text": "r220:3d2515dd21fb", "type": "sha", "id": "3d2515dd21fb34fe6c5d0029075a863f3e92f5f6"}, {"text": "r186:f804e27aa496", "type": "sha", "id": "f804e27aa4961f2e327f2a10ee373235df20ee21"}, {"text": "r182:7f00513785a1", "type": "sha", "id": "7f00513785a13f273a4387ef086bb795b37f013c"}, {"text": "r181:6efcdc61028c", "type": "sha", "id": "6efcdc61028c8edd1c787b3439fae71b77a17357"}, {"text": "r175:6c0ce52b229a", "type": "sha", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "r165:09788a0b8a54", "type": "sha", "id": "09788a0b8a5455e9678c3959214246574e546d4f"}, {"text": "r163:0164ee729def", "type": "sha", "id": "0164ee729def0a253d6dcb594b5ee2a52fef4748"}, {"text": "r140:33fa32233551", "type": "sha", "id": "33fa3223355104431402a888fa77a4e9956feb3e"}, {"text": "r126:fa014c12c26d", "type": "sha", "id": "fa014c12c26d10ba682fadb78f2a11c24c8118e1"}, {"text": "r111:e686b958768e", "type": "sha", "id": "e686b958768ee96af8029fe19c6050b1a8dd3b2b"}, {"text": "r109:ab5721ca0a08", "type": "sha", "id": "ab5721ca0a081f26bf43d9051e615af2cc99952f"}, {"text": "r108:c877b68d18e7", "type": "sha", "id": "c877b68d18e792a66b7f4c529ea02c8f80801542"}, {"text": "r107:4313566d2e41", "type": "sha", "id": "4313566d2e417cb382948f8d9d7c765330356054"}, {"text": "r104:6c2303a79367", "type": "sha", "id": "6c2303a793671e807d1cfc70134c9ca0767d98c2"}, {"text": "r102:54386793436c", "type": "sha", "id": "54386793436c938cff89326944d4c2702340037d"}, {"text": "r101:54000345d2e7", "type": "sha", "id": "54000345d2e78b03a99d561399e8e548de3f3203"}, {"text": "r99:1c6b3677b37e", "type": "sha", "id": "1c6b3677b37ea064cb4b51714d8f7498f93f4b2b"}, {"text": "r93:2d03ca750a44", "type": "sha", "id": "2d03ca750a44440fb5ea8b751176d1f36f8e8f46"}, {"text": "r92:2a08b128c206", "type": "sha", "id": "2a08b128c206db48c2f0b8f70df060e6db0ae4f8"}, {"text": "r91:30c26513ff1e", "type": "sha", "id": "30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b"}, {"text": "r82:ac71e9503c2c", "type": "sha", "id": "ac71e9503c2ca95542839af0ce7b64011b72ea7c"}, {"text": "r81:12669288fd13", "type": "sha", "id": "12669288fd13adba2a9b7dd5b870cc23ffab92d2"}, {"text": "r76:5a0c84f3e6fe", "type": "sha", "id": "5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382"}, {"text": "r73:12f2f5e2b38e", "type": "sha", "id": "12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5"}, {"text": "r61:5eab1222a7cd", "type": "sha", "id": "5eab1222a7cd4bfcbabc218ca6d04276d4e27378"}, {"text": "r60:f50f42baeed5", "type": "sha", "id": "f50f42baeed5af6518ef4b0cb2f1423f3851a941"}, {"text": "r59:d7e390a45f6a", "type": "sha", "id": "d7e390a45f6aa96f04f5e7f583ad4f867431aa25"}, {"text": "r58:f15c21f97864", "type": "sha", "id": "f15c21f97864b4f071cddfbf2750ec2e23859414"}, {"text": "r57:e906ef056cf5", "type": "sha", "id": "e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade"}, {"text": "r56:ea2b108b48aa", "type": "sha", "id": "ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b"}, {"text": "r50:84dec09632a4", "type": "sha", "id": "84dec09632a4458f79f50ddbbd155506c460b4f9"}, {"text": "r48:0115510b70c7", "type": "sha", "id": "0115510b70c7229dbc5dc49036b32e7d91d23acd"}, {"text": "r46:2a13f185e452", "type": "sha", "id": "2a13f185e4525f9d4b59882791a2d397b90d5ddc"}, {"text": "r30:3bf1c5868e57", "type": "sha", "id": "3bf1c5868e570e39569d094f922d33ced2fa3b2b"}, {"text": "r26:b8d040125747", "type": "sha", "id": "b8d04012574729d2c29886e53b1a43ef16dd00a1"}, {"text": "r24:6970b057cffe", "type": "sha", "id": "6970b057cffe4aab0a792aa634c89f4bebf01441"}, {"text": "r8:dd80b0f6cf50", "type": "sha", "id": "dd80b0f6cf5052f17cc738c2951c4f2070200d7f"}, {"text": "r7:ff7ca51e58c5", "type": "sha", "id": "ff7ca51e58c505fec0dd2491de52c622bb7a806b"}]}, {"text": "Branches", "children": [{"text": "master", "type": "branch", "id": "fd627b9e0dd80b47be81af07c4a98518244ed2f7"}]}, {"text": "Tags", "children": [{"text": "v0.2.2", "type": "tag", "id": "137fea89f304a42321d40488091ee2ed419a3686"}, {"text": "v0.2.1", "type": "tag", "id": "5051d0fa344d4408a2659d9a0348eb2d41868ecf"}, {"text": "v0.2.0", "type": "tag", "id": "599ba911aa24d2981225f3966eb659dfae9e9f30"}, {"text": "v0.1.9", "type": "tag", "id": "341d28f0eec5ddf0b6b77871e13c2bbd6bec685c"}, {"text": "v0.1.8", "type": "tag", "id": "74ebce002c088b8a5ecf40073db09375515ecd68"}, {"text": "v0.1.7", "type": "tag", "id": "4d78bf73b5c22c82b68f902f138f7881b4fffa2c"}, {"text": "v0.1.6", "type": "tag", "id": "0205cb3f44223fb3099d12a77a69c81b798772d9"}, {"text": "v0.1.5", "type": "tag", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "v0.1.4", "type": "tag", "id": "7d735150934cd7645ac3051903add952390324a5"}, {"text": "v0.1.3", "type": "tag", "id": "5a3a8fb005554692b16e21dee62bf02667d8dc3e"}, {"text": "v0.1.2", "type": "tag", "id": "0ba5f8a4660034ff25c0cac2a5baabf5d2791d63"}, {"text": "v0.1.11", "type": "tag", "id": "c60f01b77c42dce653d6b1d3b04689862c261929"}, {"text": "v0.1.10", "type": "tag", "id": "10cddef6b794696066fb346434014f0a56810218"}, {"text": "v0.1.1", "type": "tag", "id": "e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0"}]}], "more": false}
1 {"results": [{"text": "Changesets", "children": [{"text": "r625:ead8f28a4bc2", "at_rev": "", "type": "sha", "id": "ead8f28a4bc2f45ecfb148a6b8a89758b9654a84"}, {"text": "r535:c093f94d6d35", "at_rev": "", "type": "sha", "id": "c093f94d6d358f13c55a687da66c30c41cca4153"}, {"text": "r534:559f640ec08b", "at_rev": "", "type": "sha", "id": "559f640ec08b2a14c4a9ac863d8ca273545b8885"}, {"text": "r490:02a940b4ee37", "at_rev": "", "type": "sha", "id": "02a940b4ee371ec64ef5b4c4870a5c89dc7fb98a"}, {"text": "r464:b45a4153a2d7", "at_rev": "", "type": "sha", "id": "b45a4153a2d7adb8a78b63d35d39fac44a4320a6"}, {"text": "r460:0a54e66b9450", "at_rev": "", "type": "sha", "id": "0a54e66b94502409074b163cd93c1233dcc0413f"}, {"text": "r457:a7bf2f6bf3d5", "at_rev": "", "type": "sha", "id": "a7bf2f6bf3d5273da4bcd2032a891acae5a45e2b"}, {"text": "r456:7266de0154b4", "at_rev": "", "type": "sha", "id": "7266de0154b4da7c42ba3d788876056dbf116b5a"}, {"text": "r455:666de4ee6507", "at_rev": "", "type": "sha", "id": "666de4ee65074cd3e37ea01e75f65bd3e4c336bb"}, {"text": "r453:91acc599141c", "at_rev": "", "type": "sha", "id": "91acc599141c87f03e0e3551dcaacf4492632e58"}, {"text": "r442:40a2d5d71b75", "at_rev": "", "type": "sha", "id": "40a2d5d71b758e7eafc84a324ed55142cba22f42"}, {"text": "r440:d1f898326327", "at_rev": "", "type": "sha", "id": "d1f898326327e20524fe22417c22d71064fe54a1"}, {"text": "r420:162a36830c23", "at_rev": "", "type": "sha", "id": "162a36830c23ccf1bf1873157fd0c8d0dfc7c817"}, {"text": "r345:c994f0de03b2", "at_rev": "", "type": "sha", "id": "c994f0de03b2a0aa848a04fc2c0d7e737dba31fc"}, {"text": "r340:5d3d4d2c262e", "at_rev": "", "type": "sha", "id": "5d3d4d2c262e17b247d405feceeb09ff7408c940"}, {"text": "r334:4d4278a6390e", "at_rev": "", "type": "sha", "id": "4d4278a6390e42f4fc777ecf1b9b628e77da8e22"}, {"text": "r298:00dffb625166", "at_rev": "", "type": "sha", "id": "00dffb62516650bc5050d818eb47ea1ca207954d"}, {"text": "r297:47b6be9a812e", "at_rev": "", "type": "sha", "id": "47b6be9a812ec3ed0384001a458a759f0f583fe2"}, {"text": "r289:1589fed841cd", "at_rev": "", "type": "sha", "id": "1589fed841cd9ef33155f8560727809ac3ada2c8"}, {"text": "r285:afafd0ee2821", "at_rev": "", "type": "sha", "id": "afafd0ee28218ab979678213cb96e9e4dbd7359b"}, {"text": "r284:639b115ed2b0", "at_rev": "", "type": "sha", "id": "639b115ed2b02017824005b5ae66282c6e25eba8"}, {"text": "r283:fcf7562d7305", "at_rev": "", "type": "sha", "id": "fcf7562d7305affc94fe20dc89a34aefd2b8aa1e"}, {"text": "r256:ec8cbdb5f364", "at_rev": "", "type": "sha", "id": "ec8cbdb5f364fce7843cbf148c3d95d86f935339"}, {"text": "r255:0d74d2e2bdf3", "at_rev": "", "type": "sha", "id": "0d74d2e2bdf3dcd5ee9fe4fcfe9016c5c6486f35"}, {"text": "r243:6894ad7d8223", "at_rev": "", "type": "sha", "id": "6894ad7d8223b1e6853e9fdaa2c38d3f0cef1e38"}, {"text": "r231:31b3f4b599fa", "at_rev": "", "type": "sha", "id": "31b3f4b599fae5f12cf438c73403679cdf923d75"}, {"text": "r220:3d2515dd21fb", "at_rev": "", "type": "sha", "id": "3d2515dd21fb34fe6c5d0029075a863f3e92f5f6"}, {"text": "r186:f804e27aa496", "at_rev": "", "type": "sha", "id": "f804e27aa4961f2e327f2a10ee373235df20ee21"}, {"text": "r182:7f00513785a1", "at_rev": "", "type": "sha", "id": "7f00513785a13f273a4387ef086bb795b37f013c"}, {"text": "r181:6efcdc61028c", "at_rev": "", "type": "sha", "id": "6efcdc61028c8edd1c787b3439fae71b77a17357"}, {"text": "r175:6c0ce52b229a", "at_rev": "", "type": "sha", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "r165:09788a0b8a54", "at_rev": "", "type": "sha", "id": "09788a0b8a5455e9678c3959214246574e546d4f"}, {"text": "r163:0164ee729def", "at_rev": "", "type": "sha", "id": "0164ee729def0a253d6dcb594b5ee2a52fef4748"}, {"text": "r140:33fa32233551", "at_rev": "", "type": "sha", "id": "33fa3223355104431402a888fa77a4e9956feb3e"}, {"text": "r126:fa014c12c26d", "at_rev": "", "type": "sha", "id": "fa014c12c26d10ba682fadb78f2a11c24c8118e1"}, {"text": "r111:e686b958768e", "at_rev": "", "type": "sha", "id": "e686b958768ee96af8029fe19c6050b1a8dd3b2b"}, {"text": "r109:ab5721ca0a08", "at_rev": "", "type": "sha", "id": "ab5721ca0a081f26bf43d9051e615af2cc99952f"}, {"text": "r108:c877b68d18e7", "at_rev": "", "type": "sha", "id": "c877b68d18e792a66b7f4c529ea02c8f80801542"}, {"text": "r107:4313566d2e41", "at_rev": "", "type": "sha", "id": "4313566d2e417cb382948f8d9d7c765330356054"}, {"text": "r104:6c2303a79367", "at_rev": "", "type": "sha", "id": "6c2303a793671e807d1cfc70134c9ca0767d98c2"}, {"text": "r102:54386793436c", "at_rev": "", "type": "sha", "id": "54386793436c938cff89326944d4c2702340037d"}, {"text": "r101:54000345d2e7", "at_rev": "", "type": "sha", "id": "54000345d2e78b03a99d561399e8e548de3f3203"}, {"text": "r99:1c6b3677b37e", "at_rev": "", "type": "sha", "id": "1c6b3677b37ea064cb4b51714d8f7498f93f4b2b"}, {"text": "r93:2d03ca750a44", "at_rev": "", "type": "sha", "id": "2d03ca750a44440fb5ea8b751176d1f36f8e8f46"}, {"text": "r92:2a08b128c206", "at_rev": "", "type": "sha", "id": "2a08b128c206db48c2f0b8f70df060e6db0ae4f8"}, {"text": "r91:30c26513ff1e", "at_rev": "", "type": "sha", "id": "30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b"}, {"text": "r82:ac71e9503c2c", "at_rev": "", "type": "sha", "id": "ac71e9503c2ca95542839af0ce7b64011b72ea7c"}, {"text": "r81:12669288fd13", "at_rev": "", "type": "sha", "id": "12669288fd13adba2a9b7dd5b870cc23ffab92d2"}, {"text": "r76:5a0c84f3e6fe", "at_rev": "", "type": "sha", "id": "5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382"}, {"text": "r73:12f2f5e2b38e", "at_rev": "", "type": "sha", "id": "12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5"}, {"text": "r61:5eab1222a7cd", "at_rev": "", "type": "sha", "id": "5eab1222a7cd4bfcbabc218ca6d04276d4e27378"}, {"text": "r60:f50f42baeed5", "at_rev": "", "type": "sha", "id": "f50f42baeed5af6518ef4b0cb2f1423f3851a941"}, {"text": "r59:d7e390a45f6a", "at_rev": "", "type": "sha", "id": "d7e390a45f6aa96f04f5e7f583ad4f867431aa25"}, {"text": "r58:f15c21f97864", "at_rev": "", "type": "sha", "id": "f15c21f97864b4f071cddfbf2750ec2e23859414"}, {"text": "r57:e906ef056cf5", "at_rev": "", "type": "sha", "id": "e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade"}, {"text": "r56:ea2b108b48aa", "at_rev": "", "type": "sha", "id": "ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b"}, {"text": "r50:84dec09632a4", "at_rev": "", "type": "sha", "id": "84dec09632a4458f79f50ddbbd155506c460b4f9"}, {"text": "r48:0115510b70c7", "at_rev": "", "type": "sha", "id": "0115510b70c7229dbc5dc49036b32e7d91d23acd"}, {"text": "r46:2a13f185e452", "at_rev": "", "type": "sha", "id": "2a13f185e4525f9d4b59882791a2d397b90d5ddc"}, {"text": "r30:3bf1c5868e57", "at_rev": "", "type": "sha", "id": "3bf1c5868e570e39569d094f922d33ced2fa3b2b"}, {"text": "r26:b8d040125747", "at_rev": "", "type": "sha", "id": "b8d04012574729d2c29886e53b1a43ef16dd00a1"}, {"text": "r24:6970b057cffe", "at_rev": "", "type": "sha", "id": "6970b057cffe4aab0a792aa634c89f4bebf01441"}, {"text": "r8:dd80b0f6cf50", "at_rev": "", "type": "sha", "id": "dd80b0f6cf5052f17cc738c2951c4f2070200d7f"}, {"text": "r7:ff7ca51e58c5", "at_rev": "", "type": "sha", "id": "ff7ca51e58c505fec0dd2491de52c622bb7a806b"}]}, {"text": "Branches", "children": [{"text": "master", "at_rev": "master", "type": "branch", "id": "fd627b9e0dd80b47be81af07c4a98518244ed2f7"}]}, {"text": "Tags", "children": [{"text": "v0.2.2", "at_rev": "v0.2.2", "type": "tag", "id": "137fea89f304a42321d40488091ee2ed419a3686"}, {"text": "v0.2.1", "at_rev": "v0.2.1", "type": "tag", "id": "5051d0fa344d4408a2659d9a0348eb2d41868ecf"}, {"text": "v0.2.0", "at_rev": "v0.2.0", "type": "tag", "id": "599ba911aa24d2981225f3966eb659dfae9e9f30"}, {"text": "v0.1.9", "at_rev": "v0.1.9", "type": "tag", "id": "341d28f0eec5ddf0b6b77871e13c2bbd6bec685c"}, {"text": "v0.1.8", "at_rev": "v0.1.8", "type": "tag", "id": "74ebce002c088b8a5ecf40073db09375515ecd68"}, {"text": "v0.1.7", "at_rev": "v0.1.7", "type": "tag", "id": "4d78bf73b5c22c82b68f902f138f7881b4fffa2c"}, {"text": "v0.1.6", "at_rev": "v0.1.6", "type": "tag", "id": "0205cb3f44223fb3099d12a77a69c81b798772d9"}, {"text": "v0.1.5", "at_rev": "v0.1.5", "type": "tag", "id": "6c0ce52b229aa978889e91b38777f800e85f330b"}, {"text": "v0.1.4", "at_rev": "v0.1.4", "type": "tag", "id": "7d735150934cd7645ac3051903add952390324a5"}, {"text": "v0.1.3", "at_rev": "v0.1.3", "type": "tag", "id": "5a3a8fb005554692b16e21dee62bf02667d8dc3e"}, {"text": "v0.1.2", "at_rev": "v0.1.2", "type": "tag", "id": "0ba5f8a4660034ff25c0cac2a5baabf5d2791d63"}, {"text": "v0.1.11", "at_rev": "v0.1.11", "type": "tag", "id": "c60f01b77c42dce653d6b1d3b04689862c261929"}, {"text": "v0.1.10", "at_rev": "v0.1.10", "type": "tag", "id": "10cddef6b794696066fb346434014f0a56810218"}, {"text": "v0.1.1", "at_rev": "v0.1.1", "type": "tag", "id": "e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0"}]}], "more": false}
@@ -1,1 +1,1 b''
1 {"results": [{"text": "Changesets", "children": [{"text": "r648:dbec37a0d5ca (default)", "type": "sha", "id": "dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6"}, {"text": "r639:1d20ed9eda94 (default)", "type": "sha", "id": "1d20ed9eda9482d46ff0a6af5812550218b3ff15"}, {"text": "r547:0173395e8227 (default)", "type": "sha", "id": "0173395e822797f098799ed95c1a81b6a547a9ad"}, {"text": "r546:afbb45ade933 (default)", "type": "sha", "id": "afbb45ade933a8182f1d8ec5d4d1bb2de2572043"}, {"text": "r502:6f093e30cac3 (default)", "type": "sha", "id": "6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7"}, {"text": "r476:c7e2212dd2ae (default)", "type": "sha", "id": "c7e2212dd2ae975d1d06534a3d7e317165c06960"}, {"text": "r472:45477506df79 (default)", "type": "sha", "id": "45477506df79f701bf69419aac3e1f0fed3c5bcf"}, {"text": "r469:5fc76cb25d11 (default)", "type": "sha", "id": "5fc76cb25d11e07c60de040f78b8cd265ff10d53"}, {"text": "r468:b073433cf899 (default)", "type": "sha", "id": "b073433cf8994969ee5cd7cce84cbe587bb880b2"}, {"text": "r467:7a74dbfcacd1 (default)", "type": "sha", "id": "7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96"}, {"text": "r465:71ee52cc4d62 (default)", "type": "sha", "id": "71ee52cc4d629096bdbee036325975dac2af4501"}, {"text": "r452:a5b217d26c5f (default)", "type": "sha", "id": "a5b217d26c5f111e72bae4de672b084ee0fbf75c"}, {"text": "r450:47aedd538bf6 (default)", "type": "sha", "id": "47aedd538bf616eedcb0e7d630ea476df0e159c7"}, {"text": "r432:8e4915fa32d7 (default)", "type": "sha", "id": "8e4915fa32d727dcbf09746f637a5f82e539511e"}, {"text": "r356:25213a5fbb04 (default)", "type": "sha", "id": "25213a5fbb048dff8ba65d21e466a835536e5b70"}, {"text": "r351:23debcedddc1 (default)", "type": "sha", "id": "23debcedddc1c23c14be33e713e7786d4a9de471"}, {"text": "r342:61e25b2a90a1 (default)", "type": "sha", "id": "61e25b2a90a19e7fffd75dea1e4c7e20df526bbe"}, {"text": "r318:fb95b340e0d0 (webvcs)", "type": "sha", "id": "fb95b340e0d03fa51f33c56c991c08077c99303e"}, {"text": "r303:bda35e0e564f (default)", "type": "sha", "id": "bda35e0e564fbbc5cd26fe0a37fb647a254c99fe"}, {"text": "r302:97ff74896d7d (default)", "type": "sha", "id": "97ff74896d7dbf3115a337a421d44b55154acc89"}, {"text": "r293:cec3473c3fdb (default)", "type": "sha", "id": "cec3473c3fdb9599c98067182a075b49bde570f9"}, {"text": "r289:0e86c43eef86 (default)", "type": "sha", "id": "0e86c43eef866a013a587666a877c879899599bb"}, {"text": "r288:91a27c312808 (default)", "type": "sha", "id": "91a27c312808100cf20a602f78befbbff9d89bfd"}, {"text": "r287:400e36a1670a (default)", "type": "sha", "id": "400e36a1670a57d11e3edcb5b07bf82c30006d0b"}, {"text": "r261:014fb17dfc95 (default)", "type": "sha", "id": "014fb17dfc95b0995e838c565376bf9a993e230a"}, {"text": "r260:cca7aebbc4d6 (default)", "type": "sha", "id": "cca7aebbc4d6125798446b11e69dc8847834a982"}, {"text": "r258:14cdb2957c01 (workdir)", "type": "sha", "id": "14cdb2957c011a5feba36f50d960d9832ba0f0c1"}, {"text": "r245:34df20118ed7 (default)", "type": "sha", "id": "34df20118ed74b5987d22a579e8a60e903da5bf8"}, {"text": "r233:0375d9042a64 (workdir)", "type": "sha", "id": "0375d9042a64a1ac1641528f0f0668f9a339e86d"}, {"text": "r222:94aa45fc1806 (workdir)", "type": "sha", "id": "94aa45fc1806c04d4ba640933edf682c22478453"}, {"text": "r188:7ed99bc73881 (default)", "type": "sha", "id": "7ed99bc738818879941e3ce20243f8856a7cfc84"}, {"text": "r184:1e85975528bc (default)", "type": "sha", "id": "1e85975528bcebe853732a9e5fb8dbf4461f6bb2"}, {"text": "r183:ed30beddde7b (default)", "type": "sha", "id": "ed30beddde7bbddb26042625be19bcd11576c1dd"}, {"text": "r177:a6664e18181c (default)", "type": "sha", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "r167:8911406ad776 (default)", "type": "sha", "id": "8911406ad776fdd3d0b9932a2e89677e57405a48"}, {"text": "r165:aa957ed78c35 (default)", "type": "sha", "id": "aa957ed78c35a1541f508d2ec90e501b0a9e3167"}, {"text": "r140:48e11b73e94c (default)", "type": "sha", "id": "48e11b73e94c0db33e736eaeea692f990cb0b5f1"}, {"text": "r126:adf3cbf48329 (default)", "type": "sha", "id": "adf3cbf483298563b968a6c673cd5bde5f7d5eea"}, {"text": "r113:6249fd0fb2cf (git)", "type": "sha", "id": "6249fd0fb2cfb1411e764129f598e2cf0de79a6f"}, {"text": "r109:75feb4c33e81 (default)", "type": "sha", "id": "75feb4c33e81186c87eac740cee2447330288412"}, {"text": "r108:9a4dc232ecdc (default)", "type": "sha", "id": "9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d"}, {"text": "r107:595cce4efa21 (default)", "type": "sha", "id": "595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d"}, {"text": "r104:4a8bd421fbc2 (default)", "type": "sha", "id": "4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da"}, {"text": "r102:57be63fc8f85 (default)", "type": "sha", "id": "57be63fc8f85e65a0106a53187f7316f8c487ffa"}, {"text": "r101:5530bd87f7e2 (git)", "type": "sha", "id": "5530bd87f7e2e124a64d07cb2654c997682128be"}, {"text": "r99:e516008b1c93 (default)", "type": "sha", "id": "e516008b1c93f142263dc4b7961787cbad654ce1"}, {"text": "r93:41f43fc74b8b (default)", "type": "sha", "id": "41f43fc74b8b285984554532eb105ac3be5c434f"}, {"text": "r92:cc66b61b8455 (default)", "type": "sha", "id": "cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e"}, {"text": "r91:73ab5b616b32 (default)", "type": "sha", "id": "73ab5b616b3271b0518682fb4988ce421de8099f"}, {"text": "r82:e0da75f308c0 (default)", "type": "sha", "id": "e0da75f308c0f18f98e9ce6257626009fdda2b39"}, {"text": "r81:fb2e41e0f081 (default)", "type": "sha", "id": "fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611"}, {"text": "r76:602ae2f5e7ad (default)", "type": "sha", "id": "602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028"}, {"text": "r73:a066b25d5df7 (default)", "type": "sha", "id": "a066b25d5df7016b45a41b7e2a78c33b57adc235"}, {"text": "r61:637a933c9059 (web)", "type": "sha", "id": "637a933c905958ce5151f154147c25c1c7b68832"}, {"text": "r60:0c21004effeb (web)", "type": "sha", "id": "0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc"}, {"text": "r59:a1f39c56d3f1 (web)", "type": "sha", "id": "a1f39c56d3f1d52d5fb5920370a2a2716cd9a444"}, {"text": "r58:97d32df05c71 (web)", "type": "sha", "id": "97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f"}, {"text": "r57:08eaf1451771 (web)", "type": "sha", "id": "08eaf14517718dccea4b67755a93368341aca919"}, {"text": "r56:22f71ad26526 (web)", "type": "sha", "id": "22f71ad265265a53238359c883aa976e725aa07d"}, {"text": "r49:97501f02b7b4 (web)", "type": "sha", "id": "97501f02b7b4330924b647755663a2d90a5e638d"}, {"text": "r47:86ede6754f2b (web)", "type": "sha", "id": "86ede6754f2b27309452bb11f997386ae01d0e5a"}, {"text": "r45:014c40c0203c (web)", "type": "sha", "id": "014c40c0203c423dc19ecf94644f7cac9d4cdce0"}, {"text": "r30:ee87846a61c1 (default)", "type": "sha", "id": "ee87846a61c12153b51543bf860e1026c6d3dcba"}, {"text": "r26:9bb326a04ae5 (default)", "type": "sha", "id": "9bb326a04ae5d98d437dece54be04f830cf1edd9"}, {"text": "r24:536c1a194283 (default)", "type": "sha", "id": "536c1a19428381cfea92ac44985304f6a8049569"}, {"text": "r8:dc5d2c0661b6 (default)", "type": "sha", "id": "dc5d2c0661b61928834a785d3e64a3f80d3aad9c"}, {"text": "r7:3803844fdbd3 (default)", "type": "sha", "id": "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"}]}, {"text": "Branches", "children": [{"text": "default", "type": "branch", "id": "2062ec7beeeaf9f44a1c25c41479565040b930b2"}, {"text": "stable", "type": "branch", "id": "4f7e2131323e0749a740c0a56ab68ae9269c562a"}]}, {"text": "Tags", "children": [{"text": "v0.2.0", "type": "tag", "id": "2c96c02def9a7c997f33047761a53943e6254396"}, {"text": "v0.1.9", "type": "tag", "id": "8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9"}, {"text": "v0.1.8", "type": "tag", "id": "ecb25ba9c96faf1e65a0bc3fd914918420a2f116"}, {"text": "v0.1.7", "type": "tag", "id": "f67633a2894edaf28513706d558205fa93df9209"}, {"text": "v0.1.6", "type": "tag", "id": "02b38c0eb6f982174750c0e309ff9faddc0c7e12"}, {"text": "v0.1.5", "type": "tag", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "v0.1.4", "type": "tag", "id": "fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200"}, {"text": "v0.1.3", "type": "tag", "id": "17544fbfcd33ffb439e2b728b5d526b1ef30bfcf"}, {"text": "v0.1.2", "type": "tag", "id": "a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720"}, {"text": "v0.1.11", "type": "tag", "id": "fef5bfe1dc17611d5fb59a7f6f95c55c3606f933"}, {"text": "v0.1.10", "type": "tag", "id": "92831aebf2f8dd4879e897024b89d09af214df1c"}, {"text": "v0.1.1", "type": "tag", "id": "eb3a60fc964309c1a318b8dfe26aa2d1586c85ae"}, {"text": "tip", "type": "tag", "id": "2062ec7beeeaf9f44a1c25c41479565040b930b2"}]}], "more": false} No newline at end of file
1 {"results": [{"text": "Changesets", "children": [{"text": "r648:dbec37a0d5ca (default)", "at_rev": "", "type": "sha", "id": "dbec37a0d5cab8ff39af4cfc4a4cd3996e4acfc6"}, {"text": "r639:1d20ed9eda94 (default)", "at_rev": "", "type": "sha", "id": "1d20ed9eda9482d46ff0a6af5812550218b3ff15"}, {"text": "r547:0173395e8227 (default)", "at_rev": "", "type": "sha", "id": "0173395e822797f098799ed95c1a81b6a547a9ad"}, {"text": "r546:afbb45ade933 (default)", "at_rev": "", "type": "sha", "id": "afbb45ade933a8182f1d8ec5d4d1bb2de2572043"}, {"text": "r502:6f093e30cac3 (default)", "at_rev": "", "type": "sha", "id": "6f093e30cac34e6b4b11275a9f22f80c5d7ad1f7"}, {"text": "r476:c7e2212dd2ae (default)", "at_rev": "", "type": "sha", "id": "c7e2212dd2ae975d1d06534a3d7e317165c06960"}, {"text": "r472:45477506df79 (default)", "at_rev": "", "type": "sha", "id": "45477506df79f701bf69419aac3e1f0fed3c5bcf"}, {"text": "r469:5fc76cb25d11 (default)", "at_rev": "", "type": "sha", "id": "5fc76cb25d11e07c60de040f78b8cd265ff10d53"}, {"text": "r468:b073433cf899 (default)", "at_rev": "", "type": "sha", "id": "b073433cf8994969ee5cd7cce84cbe587bb880b2"}, {"text": "r467:7a74dbfcacd1 (default)", "at_rev": "", "type": "sha", "id": "7a74dbfcacd1dbcb58bb9c860b2f29fbb22c4c96"}, {"text": "r465:71ee52cc4d62 (default)", "at_rev": "", "type": "sha", "id": "71ee52cc4d629096bdbee036325975dac2af4501"}, {"text": "r452:a5b217d26c5f (default)", "at_rev": "", "type": "sha", "id": "a5b217d26c5f111e72bae4de672b084ee0fbf75c"}, {"text": "r450:47aedd538bf6 (default)", "at_rev": "", "type": "sha", "id": "47aedd538bf616eedcb0e7d630ea476df0e159c7"}, {"text": "r432:8e4915fa32d7 (default)", "at_rev": "", "type": "sha", "id": "8e4915fa32d727dcbf09746f637a5f82e539511e"}, {"text": "r356:25213a5fbb04 (default)", "at_rev": "", "type": "sha", "id": "25213a5fbb048dff8ba65d21e466a835536e5b70"}, {"text": "r351:23debcedddc1 (default)", "at_rev": "", "type": "sha", "id": "23debcedddc1c23c14be33e713e7786d4a9de471"}, {"text": "r342:61e25b2a90a1 (default)", "at_rev": "", "type": "sha", "id": "61e25b2a90a19e7fffd75dea1e4c7e20df526bbe"}, {"text": "r318:fb95b340e0d0 (webvcs)", "at_rev": "", "type": "sha", "id": "fb95b340e0d03fa51f33c56c991c08077c99303e"}, {"text": "r303:bda35e0e564f (default)", "at_rev": "", "type": "sha", "id": "bda35e0e564fbbc5cd26fe0a37fb647a254c99fe"}, {"text": "r302:97ff74896d7d (default)", "at_rev": "", "type": "sha", "id": "97ff74896d7dbf3115a337a421d44b55154acc89"}, {"text": "r293:cec3473c3fdb (default)", "at_rev": "", "type": "sha", "id": "cec3473c3fdb9599c98067182a075b49bde570f9"}, {"text": "r289:0e86c43eef86 (default)", "at_rev": "", "type": "sha", "id": "0e86c43eef866a013a587666a877c879899599bb"}, {"text": "r288:91a27c312808 (default)", "at_rev": "", "type": "sha", "id": "91a27c312808100cf20a602f78befbbff9d89bfd"}, {"text": "r287:400e36a1670a (default)", "at_rev": "", "type": "sha", "id": "400e36a1670a57d11e3edcb5b07bf82c30006d0b"}, {"text": "r261:014fb17dfc95 (default)", "at_rev": "", "type": "sha", "id": "014fb17dfc95b0995e838c565376bf9a993e230a"}, {"text": "r260:cca7aebbc4d6 (default)", "at_rev": "", "type": "sha", "id": "cca7aebbc4d6125798446b11e69dc8847834a982"}, {"text": "r258:14cdb2957c01 (workdir)", "at_rev": "", "type": "sha", "id": "14cdb2957c011a5feba36f50d960d9832ba0f0c1"}, {"text": "r245:34df20118ed7 (default)", "at_rev": "", "type": "sha", "id": "34df20118ed74b5987d22a579e8a60e903da5bf8"}, {"text": "r233:0375d9042a64 (workdir)", "at_rev": "", "type": "sha", "id": "0375d9042a64a1ac1641528f0f0668f9a339e86d"}, {"text": "r222:94aa45fc1806 (workdir)", "at_rev": "", "type": "sha", "id": "94aa45fc1806c04d4ba640933edf682c22478453"}, {"text": "r188:7ed99bc73881 (default)", "at_rev": "", "type": "sha", "id": "7ed99bc738818879941e3ce20243f8856a7cfc84"}, {"text": "r184:1e85975528bc (default)", "at_rev": "", "type": "sha", "id": "1e85975528bcebe853732a9e5fb8dbf4461f6bb2"}, {"text": "r183:ed30beddde7b (default)", "at_rev": "", "type": "sha", "id": "ed30beddde7bbddb26042625be19bcd11576c1dd"}, {"text": "r177:a6664e18181c (default)", "at_rev": "", "type": "sha", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "r167:8911406ad776 (default)", "at_rev": "", "type": "sha", "id": "8911406ad776fdd3d0b9932a2e89677e57405a48"}, {"text": "r165:aa957ed78c35 (default)", "at_rev": "", "type": "sha", "id": "aa957ed78c35a1541f508d2ec90e501b0a9e3167"}, {"text": "r140:48e11b73e94c (default)", "at_rev": "", "type": "sha", "id": "48e11b73e94c0db33e736eaeea692f990cb0b5f1"}, {"text": "r126:adf3cbf48329 (default)", "at_rev": "", "type": "sha", "id": "adf3cbf483298563b968a6c673cd5bde5f7d5eea"}, {"text": "r113:6249fd0fb2cf (git)", "at_rev": "", "type": "sha", "id": "6249fd0fb2cfb1411e764129f598e2cf0de79a6f"}, {"text": "r109:75feb4c33e81 (default)", "at_rev": "", "type": "sha", "id": "75feb4c33e81186c87eac740cee2447330288412"}, {"text": "r108:9a4dc232ecdc (default)", "at_rev": "", "type": "sha", "id": "9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d"}, {"text": "r107:595cce4efa21 (default)", "at_rev": "", "type": "sha", "id": "595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d"}, {"text": "r104:4a8bd421fbc2 (default)", "at_rev": "", "type": "sha", "id": "4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da"}, {"text": "r102:57be63fc8f85 (default)", "at_rev": "", "type": "sha", "id": "57be63fc8f85e65a0106a53187f7316f8c487ffa"}, {"text": "r101:5530bd87f7e2 (git)", "at_rev": "", "type": "sha", "id": "5530bd87f7e2e124a64d07cb2654c997682128be"}, {"text": "r99:e516008b1c93 (default)", "at_rev": "", "type": "sha", "id": "e516008b1c93f142263dc4b7961787cbad654ce1"}, {"text": "r93:41f43fc74b8b (default)", "at_rev": "", "type": "sha", "id": "41f43fc74b8b285984554532eb105ac3be5c434f"}, {"text": "r92:cc66b61b8455 (default)", "at_rev": "", "type": "sha", "id": "cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e"}, {"text": "r91:73ab5b616b32 (default)", "at_rev": "", "type": "sha", "id": "73ab5b616b3271b0518682fb4988ce421de8099f"}, {"text": "r82:e0da75f308c0 (default)", "at_rev": "", "type": "sha", "id": "e0da75f308c0f18f98e9ce6257626009fdda2b39"}, {"text": "r81:fb2e41e0f081 (default)", "at_rev": "", "type": "sha", "id": "fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611"}, {"text": "r76:602ae2f5e7ad (default)", "at_rev": "", "type": "sha", "id": "602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028"}, {"text": "r73:a066b25d5df7 (default)", "at_rev": "", "type": "sha", "id": "a066b25d5df7016b45a41b7e2a78c33b57adc235"}, {"text": "r61:637a933c9059 (web)", "at_rev": "", "type": "sha", "id": "637a933c905958ce5151f154147c25c1c7b68832"}, {"text": "r60:0c21004effeb (web)", "at_rev": "", "type": "sha", "id": "0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc"}, {"text": "r59:a1f39c56d3f1 (web)", "at_rev": "", "type": "sha", "id": "a1f39c56d3f1d52d5fb5920370a2a2716cd9a444"}, {"text": "r58:97d32df05c71 (web)", "at_rev": "", "type": "sha", "id": "97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f"}, {"text": "r57:08eaf1451771 (web)", "at_rev": "", "type": "sha", "id": "08eaf14517718dccea4b67755a93368341aca919"}, {"text": "r56:22f71ad26526 (web)", "at_rev": "", "type": "sha", "id": "22f71ad265265a53238359c883aa976e725aa07d"}, {"text": "r49:97501f02b7b4 (web)", "at_rev": "", "type": "sha", "id": "97501f02b7b4330924b647755663a2d90a5e638d"}, {"text": "r47:86ede6754f2b (web)", "at_rev": "", "type": "sha", "id": "86ede6754f2b27309452bb11f997386ae01d0e5a"}, {"text": "r45:014c40c0203c (web)", "at_rev": "", "type": "sha", "id": "014c40c0203c423dc19ecf94644f7cac9d4cdce0"}, {"text": "r30:ee87846a61c1 (default)", "at_rev": "", "type": "sha", "id": "ee87846a61c12153b51543bf860e1026c6d3dcba"}, {"text": "r26:9bb326a04ae5 (default)", "at_rev": "", "type": "sha", "id": "9bb326a04ae5d98d437dece54be04f830cf1edd9"}, {"text": "r24:536c1a194283 (default)", "at_rev": "", "type": "sha", "id": "536c1a19428381cfea92ac44985304f6a8049569"}, {"text": "r8:dc5d2c0661b6 (default)", "at_rev": "", "type": "sha", "id": "dc5d2c0661b61928834a785d3e64a3f80d3aad9c"}, {"text": "r7:3803844fdbd3 (default)", "at_rev": "", "type": "sha", "id": "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"}]}, {"text": "Branches", "children": [{"text": "default", "at_rev": "default", "type": "branch", "id": "2062ec7beeeaf9f44a1c25c41479565040b930b2"}, {"text": "stable", "at_rev": "stable", "type": "branch", "id": "4f7e2131323e0749a740c0a56ab68ae9269c562a"}]}, {"text": "Tags", "children": [{"text": "v0.2.0", "at_rev": "v0.2.0", "type": "tag", "id": "2c96c02def9a7c997f33047761a53943e6254396"}, {"text": "v0.1.9", "at_rev": "v0.1.9", "type": "tag", "id": "8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9"}, {"text": "v0.1.8", "at_rev": "v0.1.8", "type": "tag", "id": "ecb25ba9c96faf1e65a0bc3fd914918420a2f116"}, {"text": "v0.1.7", "at_rev": "v0.1.7", "type": "tag", "id": "f67633a2894edaf28513706d558205fa93df9209"}, {"text": "v0.1.6", "at_rev": "v0.1.6", "type": "tag", "id": "02b38c0eb6f982174750c0e309ff9faddc0c7e12"}, {"text": "v0.1.5", "at_rev": "v0.1.5", "type": "tag", "id": "a6664e18181c6fc81b751a8d01474e7e1a3fe7fc"}, {"text": "v0.1.4", "at_rev": "v0.1.4", "type": "tag", "id": "fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200"}, {"text": "v0.1.3", "at_rev": "v0.1.3", "type": "tag", "id": "17544fbfcd33ffb439e2b728b5d526b1ef30bfcf"}, {"text": "v0.1.2", "at_rev": "v0.1.2", "type": "tag", "id": "a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720"}, {"text": "v0.1.11", "at_rev": "v0.1.11", "type": "tag", "id": "fef5bfe1dc17611d5fb59a7f6f95c55c3606f933"}, {"text": "v0.1.10", "at_rev": "v0.1.10", "type": "tag", "id": "92831aebf2f8dd4879e897024b89d09af214df1c"}, {"text": "v0.1.1", "at_rev": "v0.1.1", "type": "tag", "id": "eb3a60fc964309c1a318b8dfe26aa2d1586c85ae"}, {"text": "tip", "at_rev": "tip", "type": "tag", "id": "2062ec7beeeaf9f44a1c25c41479565040b930b2"}]}], "more": false} No newline at end of file
@@ -1,1 +1,1 b''
1 {"results": [{"text": "Changesets", "children": [{"text": "r15:16", "type": "sha", "id": "16"}, {"text": "r12:13", "type": "sha", "id": "13"}, {"text": "r7:8", "type": "sha", "id": "8"}, {"text": "r3:4", "type": "sha", "id": "4"}, {"text": "r2:3", "type": "sha", "id": "3"}]}, {"text": "Branches", "children": [{"text": "branches/add-docs", "type": "branch", "id": "branches/add-docs/example.py@26"}, {"text": "branches/argparse", "type": "branch", "id": "branches/argparse/example.py@26"}, {"text": "trunk", "type": "branch", "id": "trunk/example.py@26"}]}, {"text": "Tags", "children": [{"text": "tags/v0.1", "type": "tag", "id": "tags/v0.1/example.py@26"}, {"text": "tags/v0.2", "type": "tag", "id": "tags/v0.2/example.py@26"}, {"text": "tags/v0.3", "type": "tag", "id": "tags/v0.3/example.py@26"}, {"text": "tags/v0.5", "type": "tag", "id": "tags/v0.5/example.py@26"}]}], "more": false} No newline at end of file
1 {"results": [{"text": "Changesets", "children": [{"text": "r15:16", "at_rev": "", "type": "sha", "id": "16"}, {"text": "r12:13", "at_rev": "", "type": "sha", "id": "13"}, {"text": "r7:8", "at_rev": "", "type": "sha", "id": "8"}, {"text": "r3:4", "at_rev": "", "type": "sha", "id": "4"}, {"text": "r2:3", "at_rev": "", "type": "sha", "id": "3"}]}, {"text": "Branches", "children": [{"text": "branches/add-docs", "at_rev": "branches/add-docs", "type": "branch", "id": "26"}, {"text": "branches/argparse", "at_rev": "branches/argparse", "type": "branch", "id": "26"}, {"text": "trunk", "at_rev": "trunk", "type": "branch", "id": "26"}]}, {"text": "Tags", "children": [{"text": "tags/v0.1", "at_rev": "tags/v0.1", "type": "tag", "id": "26"}, {"text": "tags/v0.2", "at_rev": "tags/v0.2", "type": "tag", "id": "26"}, {"text": "tags/v0.3", "at_rev": "tags/v0.3", "type": "tag", "id": "26"}, {"text": "tags/v0.5", "at_rev": "tags/v0.5", "type": "tag", "id": "26"}]}], "more": false} No newline at end of file
@@ -1,1 +1,1 b''
1 {"results": [{"text": "Changesets", "children": [{"text": "r382:383", "type": "sha", "id": "383"}, {"text": "r323:324", "type": "sha", "id": "324"}, {"text": "r322:323", "type": "sha", "id": "323"}, {"text": "r299:300", "type": "sha", "id": "300"}, {"text": "r277:278", "type": "sha", "id": "278"}, {"text": "r273:274", "type": "sha", "id": "274"}, {"text": "r270:271", "type": "sha", "id": "271"}, {"text": "r269:270", "type": "sha", "id": "270"}, {"text": "r263:264", "type": "sha", "id": "264"}, {"text": "r261:262", "type": "sha", "id": "262"}, {"text": "r251:252", "type": "sha", "id": "252"}, {"text": "r208:209", "type": "sha", "id": "209"}, {"text": "r202:203", "type": "sha", "id": "203"}, {"text": "r173:174", "type": "sha", "id": "174"}, {"text": "r172:173", "type": "sha", "id": "173"}, {"text": "r171:172", "type": "sha", "id": "172"}, {"text": "r145:146", "type": "sha", "id": "146"}, {"text": "r144:145", "type": "sha", "id": "145"}, {"text": "r140:141", "type": "sha", "id": "141"}, {"text": "r134:135", "type": "sha", "id": "135"}, {"text": "r107:108", "type": "sha", "id": "108"}, {"text": "r106:107", "type": "sha", "id": "107"}, {"text": "r100:101", "type": "sha", "id": "101"}, {"text": "r94:95", "type": "sha", "id": "95"}, {"text": "r85:86", "type": "sha", "id": "86"}, {"text": "r73:74", "type": "sha", "id": "74"}, {"text": "r72:73", "type": "sha", "id": "73"}, {"text": "r71:72", "type": "sha", "id": "72"}, {"text": "r69:70", "type": "sha", "id": "70"}, {"text": "r67:68", "type": "sha", "id": "68"}, {"text": "r63:64", "type": "sha", "id": "64"}, {"text": "r62:63", "type": "sha", "id": "63"}, {"text": "r61:62", "type": "sha", "id": "62"}, {"text": "r50:51", "type": "sha", "id": "51"}, {"text": "r49:50", "type": "sha", "id": "50"}, {"text": "r48:49", "type": "sha", "id": "49"}, {"text": "r47:48", "type": "sha", "id": "48"}, {"text": "r46:47", "type": "sha", "id": "47"}, {"text": "r45:46", "type": "sha", "id": "46"}, {"text": "r41:42", "type": "sha", "id": "42"}, {"text": "r39:40", "type": "sha", "id": "40"}, {"text": "r37:38", "type": "sha", "id": "38"}, {"text": "r25:26", "type": "sha", "id": "26"}, {"text": "r23:24", "type": "sha", "id": "24"}, {"text": "r8:9", "type": "sha", "id": "9"}, {"text": "r7:8", "type": "sha", "id": "8"}]}, {"text": "Branches", "children": []}, {"text": "Tags", "children": []}], "more": false} No newline at end of file
1 {"results": [{"text": "Changesets", "children": [{"text": "r382:383", "at_rev": "", "type": "sha", "id": "383"}, {"text": "r323:324", "at_rev": "", "type": "sha", "id": "324"}, {"text": "r322:323", "at_rev": "", "type": "sha", "id": "323"}, {"text": "r299:300", "at_rev": "", "type": "sha", "id": "300"}, {"text": "r277:278", "at_rev": "", "type": "sha", "id": "278"}, {"text": "r273:274", "at_rev": "", "type": "sha", "id": "274"}, {"text": "r270:271", "at_rev": "", "type": "sha", "id": "271"}, {"text": "r269:270", "at_rev": "", "type": "sha", "id": "270"}, {"text": "r263:264", "at_rev": "", "type": "sha", "id": "264"}, {"text": "r261:262", "at_rev": "", "type": "sha", "id": "262"}, {"text": "r251:252", "at_rev": "", "type": "sha", "id": "252"}, {"text": "r208:209", "at_rev": "", "type": "sha", "id": "209"}, {"text": "r202:203", "at_rev": "", "type": "sha", "id": "203"}, {"text": "r173:174", "at_rev": "", "type": "sha", "id": "174"}, {"text": "r172:173", "at_rev": "", "type": "sha", "id": "173"}, {"text": "r171:172", "at_rev": "", "type": "sha", "id": "172"}, {"text": "r145:146", "at_rev": "", "type": "sha", "id": "146"}, {"text": "r144:145", "at_rev": "", "type": "sha", "id": "145"}, {"text": "r140:141", "at_rev": "", "type": "sha", "id": "141"}, {"text": "r134:135", "at_rev": "", "type": "sha", "id": "135"}, {"text": "r107:108", "at_rev": "", "type": "sha", "id": "108"}, {"text": "r106:107", "at_rev": "", "type": "sha", "id": "107"}, {"text": "r100:101", "at_rev": "", "type": "sha", "id": "101"}, {"text": "r94:95", "at_rev": "", "type": "sha", "id": "95"}, {"text": "r85:86", "at_rev": "", "type": "sha", "id": "86"}, {"text": "r73:74", "at_rev": "", "type": "sha", "id": "74"}, {"text": "r72:73", "at_rev": "", "type": "sha", "id": "73"}, {"text": "r71:72", "at_rev": "", "type": "sha", "id": "72"}, {"text": "r69:70", "at_rev": "", "type": "sha", "id": "70"}, {"text": "r67:68", "at_rev": "", "type": "sha", "id": "68"}, {"text": "r63:64", "at_rev": "", "type": "sha", "id": "64"}, {"text": "r62:63", "at_rev": "", "type": "sha", "id": "63"}, {"text": "r61:62", "at_rev": "", "type": "sha", "id": "62"}, {"text": "r50:51", "at_rev": "", "type": "sha", "id": "51"}, {"text": "r49:50", "at_rev": "", "type": "sha", "id": "50"}, {"text": "r48:49", "at_rev": "", "type": "sha", "id": "49"}, {"text": "r47:48", "at_rev": "", "type": "sha", "id": "48"}, {"text": "r46:47", "at_rev": "", "type": "sha", "id": "47"}, {"text": "r45:46", "at_rev": "", "type": "sha", "id": "46"}, {"text": "r41:42", "at_rev": "", "type": "sha", "id": "42"}, {"text": "r39:40", "at_rev": "", "type": "sha", "id": "40"}, {"text": "r37:38", "at_rev": "", "type": "sha", "id": "38"}, {"text": "r25:26", "at_rev": "", "type": "sha", "id": "26"}, {"text": "r23:24", "at_rev": "", "type": "sha", "id": "24"}, {"text": "r8:9", "at_rev": "", "type": "sha", "id": "9"}, {"text": "r7:8", "at_rev": "", "type": "sha", "id": "8"}]}, {"text": "Branches", "children": []}, {"text": "Tags", "children": []}], "more": false} No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now