Show More
@@ -325,6 +325,21 b' class RepoFilesView(RepoAppView):' | |||||
325 |
|
325 | |||
326 | return lf_enabled |
|
326 | return lf_enabled | |
327 |
|
327 | |||
|
328 | def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha=''): | |||
|
329 | # original backward compat name of archive | |||
|
330 | clean_name = safe_str(db_repo_name.replace('/', '_')) | |||
|
331 | ||||
|
332 | # e.g vcsserver.zip | |||
|
333 | # e.g vcsserver-abcdefgh.zip | |||
|
334 | # e.g vcsserver-abcdefgh-defghijk.zip | |||
|
335 | archive_name = '{}{}{}{}{}'.format( | |||
|
336 | clean_name, | |||
|
337 | '-sub' if subrepos else '', | |||
|
338 | commit_sha, | |||
|
339 | '-{}'.format(path_sha) if path_sha else '', | |||
|
340 | ext) | |||
|
341 | return archive_name | |||
|
342 | ||||
328 | @LoginRequired() |
|
343 | @LoginRequired() | |
329 | @HasRepoPermissionAnyDecorator( |
|
344 | @HasRepoPermissionAnyDecorator( | |
330 | 'repository.read', 'repository.write', 'repository.admin') |
|
345 | 'repository.read', 'repository.write', 'repository.admin') | |
@@ -339,6 +354,7 b' class RepoFilesView(RepoAppView):' | |||||
339 | default_at_path = '/' |
|
354 | default_at_path = '/' | |
340 | fname = self.request.matchdict['fname'] |
|
355 | fname = self.request.matchdict['fname'] | |
341 | subrepos = self.request.GET.get('subrepos') == 'true' |
|
356 | subrepos = self.request.GET.get('subrepos') == 'true' | |
|
357 | with_hash = str2bool(self.request.GET.get('with_hash', '1')) | |||
342 | at_path = self.request.GET.get('at_path') or default_at_path |
|
358 | at_path = self.request.GET.get('at_path') or default_at_path | |
343 |
|
359 | |||
344 | if not self.db_repo.enable_downloads: |
|
360 | if not self.db_repo.enable_downloads: | |
@@ -364,26 +380,26 b' class RepoFilesView(RepoAppView):' | |||||
364 | except Exception: |
|
380 | except Exception: | |
365 | return Response(_('No node at path {} for this repository').format(at_path)) |
|
381 | return Response(_('No node at path {} for this repository').format(at_path)) | |
366 |
|
382 | |||
|
383 | # path sha is part of subdir | |||
|
384 | path_sha = '' | |||
|
385 | if at_path != default_at_path: | |||
367 | path_sha = sha1(at_path)[:8] |
|
386 | path_sha = sha1(at_path)[:8] | |
368 |
|
387 | short_sha = '-{}'.format(safe_str(commit.short_id)) | ||
369 | # original backward compat name of archive |
|
388 | # used for cache etc | |
370 | clean_name = safe_str(self.db_repo_name.replace('/', '_')) |
|
389 | archive_name = self._get_archive_name( | |
371 | short_sha = safe_str(commit.short_id) |
|
390 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |
|
391 | path_sha=path_sha) | |||
372 |
|
392 | |||
373 | if at_path == default_at_path: |
|
393 | if not with_hash: | |
374 | archive_name = '{}-{}{}{}'.format( |
|
394 | short_sha = '' | |
375 | clean_name, |
|
395 | path_sha = '' | |
376 | '-sub' if subrepos else '', |
|
396 | ||
377 | short_sha, |
|
397 | # what end client gets served | |
378 | ext) |
|
398 | response_archive_name = self._get_archive_name( | |
379 | # custom path and new name |
|
399 | self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, | |
380 | else: |
|
400 | path_sha=path_sha) | |
381 | archive_name = '{}-{}{}-{}{}'.format( |
|
401 | # remove extension from our archive directory name | |
382 | clean_name, |
|
402 | archive_dir_name = response_archive_name[:-len(ext)] | |
383 | '-sub' if subrepos else '', |
|
|||
384 | short_sha, |
|
|||
385 | path_sha, |
|
|||
386 | ext) |
|
|||
387 |
|
403 | |||
388 | use_cached_archive = False |
|
404 | use_cached_archive = False | |
389 | archive_cache_enabled = CONFIG.get( |
|
405 | archive_cache_enabled = CONFIG.get( | |
@@ -403,13 +419,15 b' class RepoFilesView(RepoAppView):' | |||||
403 | else: |
|
419 | else: | |
404 | log.debug('Archive %s is not yet cached', archive_name) |
|
420 | log.debug('Archive %s is not yet cached', archive_name) | |
405 |
|
421 | |||
|
422 | # generate new archive, as previous was not found in the cache | |||
406 | if not use_cached_archive: |
|
423 | if not use_cached_archive: | |
407 | # generate new archive |
|
424 | ||
408 | fd, archive = tempfile.mkstemp() |
|
425 | fd, archive = tempfile.mkstemp() | |
409 | log.debug('Creating new temp archive in %s', archive) |
|
426 | log.debug('Creating new temp archive in %s', archive) | |
410 | try: |
|
427 | try: | |
411 |
commit.archive_repo(archive, |
|
428 | commit.archive_repo(archive, archive_dir_name=archive_dir_name, | |
412 |
|
|
429 | kind=fileformat, subrepos=subrepos, | |
|
430 | archive_at_path=at_path, with_hash=with_hash) | |||
413 | except ImproperArchiveTypeError: |
|
431 | except ImproperArchiveTypeError: | |
414 | return _('Unknown archive type') |
|
432 | return _('Unknown archive type') | |
415 | if archive_cache_enabled: |
|
433 | if archive_cache_enabled: | |
@@ -445,8 +463,7 b' class RepoFilesView(RepoAppView):' | |||||
445 | yield data |
|
463 | yield data | |
446 |
|
464 | |||
447 | response = Response(app_iter=get_chunked_archive(archive)) |
|
465 | response = Response(app_iter=get_chunked_archive(archive)) | |
448 | response.content_disposition = str( |
|
466 | response.content_disposition = str('attachment; filename=%s' % response_archive_name) | |
449 | 'attachment; filename=%s' % archive_name) |
|
|||
450 | response.content_type = str(content_type) |
|
467 | response.content_type = str(content_type) | |
451 |
|
468 | |||
452 | return response |
|
469 | return response |
@@ -1192,13 +1192,14 b' class BaseCommit(object):' | |||||
1192 | return None |
|
1192 | return None | |
1193 |
|
1193 | |||
1194 | def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None, |
|
1194 | def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None, | |
1195 |
|
|
1195 | archive_dir_name=None, write_metadata=False, mtime=None, | |
|
1196 | archive_at_path='/', with_hash=True): | |||
1196 | """ |
|
1197 | """ | |
1197 | Creates an archive containing the contents of the repository. |
|
1198 | Creates an archive containing the contents of the repository. | |
1198 |
|
1199 | |||
1199 | :param archive_dest_path: path to the file which to create the archive. |
|
1200 | :param archive_dest_path: path to the file which to create the archive. | |
1200 | :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``. |
|
1201 | :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``. | |
1201 |
:param |
|
1202 | :param archive_dir_name: name of root directory in archive. | |
1202 | Default is repository name and commit's short_id joined with dash: |
|
1203 | Default is repository name and commit's short_id joined with dash: | |
1203 | ``"{repo_name}-{short_id}"``. |
|
1204 | ``"{repo_name}-{short_id}"``. | |
1204 | :param write_metadata: write a metadata file into archive. |
|
1205 | :param write_metadata: write a metadata file into archive. | |
@@ -1214,7 +1215,7 b' class BaseCommit(object):' | |||||
1214 | 'Archive kind (%s) not supported use one of %s' % |
|
1215 | 'Archive kind (%s) not supported use one of %s' % | |
1215 | (kind, allowed_kinds)) |
|
1216 | (kind, allowed_kinds)) | |
1216 |
|
1217 | |||
1217 |
|
|
1218 | archive_dir_name = self._validate_archive_prefix(archive_dir_name) | |
1218 |
|
1219 | |||
1219 | mtime = mtime is not None or time.mktime(self.date.timetuple()) |
|
1220 | mtime = mtime is not None or time.mktime(self.date.timetuple()) | |
1220 |
|
1221 | |||
@@ -1222,7 +1223,7 b' class BaseCommit(object):' | |||||
1222 | cur_rev = self.repository.get_commit(commit_id=self.raw_id) |
|
1223 | cur_rev = self.repository.get_commit(commit_id=self.raw_id) | |
1223 | for _r, _d, files in cur_rev.walk(archive_at_path): |
|
1224 | for _r, _d, files in cur_rev.walk(archive_at_path): | |
1224 | for f in files: |
|
1225 | for f in files: | |
1225 |
f_path = os.path.join( |
|
1226 | f_path = os.path.join(archive_dir_name, f.path) | |
1226 | file_info.append( |
|
1227 | file_info.append( | |
1227 | (f_path, f.mode, f.is_link(), f.raw_bytes)) |
|
1228 | (f_path, f.mode, f.is_link(), f.raw_bytes)) | |
1228 |
|
1229 | |||
@@ -1239,18 +1240,18 b' class BaseCommit(object):' | |||||
1239 |
|
1240 | |||
1240 | connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind) |
|
1241 | connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind) | |
1241 |
|
1242 | |||
1242 |
def _validate_archive_prefix(self, |
|
1243 | def _validate_archive_prefix(self, archive_dir_name): | |
1243 |
if |
|
1244 | if archive_dir_name is None: | |
1244 |
|
|
1245 | archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format( | |
1245 | repo_name=safe_str(self.repository.name), |
|
1246 | repo_name=safe_str(self.repository.name), | |
1246 | short_id=self.short_id) |
|
1247 | short_id=self.short_id) | |
1247 |
elif not isinstance( |
|
1248 | elif not isinstance(archive_dir_name, str): | |
1248 |
raise ValueError("prefix not a bytes object: %s" % repr( |
|
1249 | raise ValueError("prefix not a bytes object: %s" % repr(archive_dir_name)) | |
1249 |
elif |
|
1250 | elif archive_dir_name.startswith('/'): | |
1250 | raise VCSError("Prefix cannot start with leading slash") |
|
1251 | raise VCSError("Prefix cannot start with leading slash") | |
1251 |
elif |
|
1252 | elif archive_dir_name.strip() == '': | |
1252 | raise VCSError("Prefix cannot be empty") |
|
1253 | raise VCSError("Prefix cannot be empty") | |
1253 |
return |
|
1254 | return archive_dir_name | |
1254 |
|
1255 | |||
1255 | @LazyProperty |
|
1256 | @LazyProperty | |
1256 | def root(self): |
|
1257 | def root(self): |
@@ -51,7 +51,7 b'' | |||||
51 | ${_('Download full tree ZIP')} |
|
51 | ${_('Download full tree ZIP')} | |
52 | </a> |
|
52 | </a> | |
53 | % else: |
|
53 | % else: | |
54 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}"> |
|
54 | <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path, 'with_hash': '1'})}"> | |
55 | ${_('Download this tree ZIP')} |
|
55 | ${_('Download this tree ZIP')} | |
56 | </a> |
|
56 | </a> | |
57 | % endif |
|
57 | % endif |
@@ -187,7 +187,7 b'' | |||||
187 | <div class="enabled pull-left" style="margin-right: 10px"> |
|
187 | <div class="enabled pull-left" style="margin-right: 10px"> | |
188 |
|
188 | |||
189 | <div class="btn-group btn-group-actions"> |
|
189 | <div class="btn-group btn-group-actions"> | |
190 |
<a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip' |
|
190 | <a class="archive_link btn btn-small" data-ext=".zip" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+'.zip', _query={'with_hash': '1'})}"> | |
191 | <i class="icon-download"></i> |
|
191 | <i class="icon-download"></i> | |
192 | ${c.rhodecode_db_repo.landing_ref_name}.zip |
|
192 | ${c.rhodecode_db_repo.landing_ref_name}.zip | |
193 | ## replaced by some JS on select |
|
193 | ## replaced by some JS on select | |
@@ -202,8 +202,7 b'' | |||||
202 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: |
|
202 | % for a_type, content_type, extension in h.ARCHIVE_SPECS: | |
203 | % if extension not in ['.zip']: |
|
203 | % if extension not in ['.zip']: | |
204 | <li> |
|
204 | <li> | |
205 |
|
205 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension, _query={'with_hash': '1'})}"> | ||
206 | <a class="archive_link" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension)}"> |
|
|||
207 | <i class="icon-download"></i> |
|
206 | <i class="icon-download"></i> | |
208 | ${c.rhodecode_db_repo.landing_ref_name+extension} |
|
207 | ${c.rhodecode_db_repo.landing_ref_name+extension} | |
209 | </a> |
|
208 | </a> |
General Comments 0
You need to be logged in to leave comments.
Login now