##// END OF EJS Templates
landing-refs: create helpers for landing ref to make clear indication about type/name
marcink -
r4370:ef6d7bca stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1070 +1,1070 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.apps.repository.tests.test_repo_compare import ComparePage
27 27 from rhodecode.apps.repository.views.repo_files import RepoFilesView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.compat import OrderedDict
30 30 from rhodecode.lib.ext_json import json
31 31 from rhodecode.lib.vcs import nodes
32 32
33 33 from rhodecode.lib.vcs.conf import settings
34 34 from rhodecode.tests import assert_session_flash
35 35 from rhodecode.tests.fixture import Fixture
36 36 from rhodecode.model.db import Session
37 37
38 38 fixture = Fixture()
39 39
40 40
41 41 def get_node_history(backend_type):
42 42 return {
43 43 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')),
44 44 'git': json.loads(fixture.load_resource('git_node_history_response.json')),
45 45 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')),
46 46 }[backend_type]
47 47
48 48
49 49 def route_path(name, params=None, **kwargs):
50 50 import urllib
51 51
52 52 base_url = {
53 53 'repo_summary': '/{repo_name}',
54 54 'repo_archivefile': '/{repo_name}/archive/{fname}',
55 55 'repo_files_diff': '/{repo_name}/diff/{f_path}',
56 56 'repo_files_diff_2way_redirect': '/{repo_name}/diff-2way/{f_path}',
57 57 'repo_files': '/{repo_name}/files/{commit_id}/{f_path}',
58 58 'repo_files:default_path': '/{repo_name}/files/{commit_id}/',
59 59 'repo_files:default_commit': '/{repo_name}/files',
60 60 'repo_files:rendered': '/{repo_name}/render/{commit_id}/{f_path}',
61 61 'repo_files:annotated': '/{repo_name}/annotate/{commit_id}/{f_path}',
62 62 'repo_files:annotated_previous': '/{repo_name}/annotate-previous/{commit_id}/{f_path}',
63 63 'repo_files_nodelist': '/{repo_name}/nodelist/{commit_id}/{f_path}',
64 64 'repo_file_raw': '/{repo_name}/raw/{commit_id}/{f_path}',
65 65 'repo_file_download': '/{repo_name}/download/{commit_id}/{f_path}',
66 66 'repo_file_history': '/{repo_name}/history/{commit_id}/{f_path}',
67 67 'repo_file_authors': '/{repo_name}/authors/{commit_id}/{f_path}',
68 68 'repo_files_remove_file': '/{repo_name}/remove_file/{commit_id}/{f_path}',
69 69 'repo_files_delete_file': '/{repo_name}/delete_file/{commit_id}/{f_path}',
70 70 'repo_files_edit_file': '/{repo_name}/edit_file/{commit_id}/{f_path}',
71 71 'repo_files_update_file': '/{repo_name}/update_file/{commit_id}/{f_path}',
72 72 'repo_files_add_file': '/{repo_name}/add_file/{commit_id}/{f_path}',
73 73 'repo_files_create_file': '/{repo_name}/create_file/{commit_id}/{f_path}',
74 74 'repo_nodetree_full': '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
75 75 'repo_nodetree_full:default_path': '/{repo_name}/nodetree_full/{commit_id}/',
76 76 }[name].format(**kwargs)
77 77
78 78 if params:
79 79 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
80 80 return base_url
81 81
82 82
83 83 def assert_files_in_response(response, files, params):
84 84 template = (
85 85 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
86 86 _assert_items_in_response(response, files, template, params)
87 87
88 88
89 89 def assert_dirs_in_response(response, dirs, params):
90 90 template = (
91 91 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
92 92 _assert_items_in_response(response, dirs, template, params)
93 93
94 94
95 95 def _assert_items_in_response(response, items, template, params):
96 96 for item in items:
97 97 item_params = {'name': item}
98 98 item_params.update(params)
99 99 response.mustcontain(template % item_params)
100 100
101 101
102 102 def assert_timeago_in_response(response, items, params):
103 103 for item in items:
104 104 response.mustcontain(h.age_component(params['date']))
105 105
106 106
107 107 @pytest.mark.usefixtures("app")
108 108 class TestFilesViews(object):
109 109
110 110 def test_show_files(self, backend):
111 111 response = self.app.get(
112 112 route_path('repo_files',
113 113 repo_name=backend.repo_name,
114 114 commit_id='tip', f_path='/'))
115 115 commit = backend.repo.get_commit()
116 116
117 117 params = {
118 118 'repo_name': backend.repo_name,
119 119 'commit_id': commit.raw_id,
120 120 'date': commit.date
121 121 }
122 122 assert_dirs_in_response(response, ['docs', 'vcs'], params)
123 123 files = [
124 124 '.gitignore',
125 125 '.hgignore',
126 126 '.hgtags',
127 127 # TODO: missing in Git
128 128 # '.travis.yml',
129 129 'MANIFEST.in',
130 130 'README.rst',
131 131 # TODO: File is missing in svn repository
132 132 # 'run_test_and_report.sh',
133 133 'setup.cfg',
134 134 'setup.py',
135 135 'test_and_report.sh',
136 136 'tox.ini',
137 137 ]
138 138 assert_files_in_response(response, files, params)
139 139 assert_timeago_in_response(response, files, params)
140 140
141 141 def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
142 142 repo = backend_hg['subrepos']
143 143 response = self.app.get(
144 144 route_path('repo_files',
145 145 repo_name=repo.repo_name,
146 146 commit_id='tip', f_path='/'))
147 147 assert_response = response.assert_response()
148 148 assert_response.contains_one_link(
149 149 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
150 150
151 151 def test_show_files_links_submodules_with_absolute_url_subpaths(
152 152 self, backend_hg):
153 153 repo = backend_hg['subrepos']
154 154 response = self.app.get(
155 155 route_path('repo_files',
156 156 repo_name=repo.repo_name,
157 157 commit_id='tip', f_path='/'))
158 158 assert_response = response.assert_response()
159 159 assert_response.contains_one_link(
160 160 'subpaths-path @ 000000000000',
161 161 'http://sub-base.example.com/subpaths-path')
162 162
163 163 @pytest.mark.xfail_backends("svn", reason="Depends on branch support")
164 164 def test_files_menu(self, backend):
165 165 new_branch = "temp_branch_name"
166 166 commits = [
167 167 {'message': 'a'},
168 168 {'message': 'b', 'branch': new_branch}
169 169 ]
170 170 backend.create_repo(commits)
171 171 backend.repo.landing_rev = "branch:%s" % new_branch
172 172 Session().commit()
173 173
174 174 # get response based on tip and not new commit
175 175 response = self.app.get(
176 176 route_path('repo_files',
177 177 repo_name=backend.repo_name,
178 178 commit_id='tip', f_path='/'))
179 179
180 180 # make sure Files menu url is not tip but new commit
181 landing_rev = backend.repo.landing_rev[1]
181 landing_rev = backend.repo.landing_ref_name
182 182 files_url = route_path('repo_files:default_path',
183 183 repo_name=backend.repo_name,
184 184 commit_id=landing_rev)
185 185
186 186 assert landing_rev != 'tip'
187 187 response.mustcontain(
188 188 '<li class="active"><a class="menulink" href="%s">' % files_url)
189 189
190 190 def test_show_files_commit(self, backend):
191 191 commit = backend.repo.get_commit(commit_idx=32)
192 192
193 193 response = self.app.get(
194 194 route_path('repo_files',
195 195 repo_name=backend.repo_name,
196 196 commit_id=commit.raw_id, f_path='/'))
197 197
198 198 dirs = ['docs', 'tests']
199 199 files = ['README.rst']
200 200 params = {
201 201 'repo_name': backend.repo_name,
202 202 'commit_id': commit.raw_id,
203 203 }
204 204 assert_dirs_in_response(response, dirs, params)
205 205 assert_files_in_response(response, files, params)
206 206
207 207 def test_show_files_different_branch(self, backend):
208 208 branches = dict(
209 209 hg=(150, ['git']),
210 210 # TODO: Git test repository does not contain other branches
211 211 git=(633, ['master']),
212 212 # TODO: Branch support in Subversion
213 213 svn=(150, [])
214 214 )
215 215 idx, branches = branches[backend.alias]
216 216 commit = backend.repo.get_commit(commit_idx=idx)
217 217 response = self.app.get(
218 218 route_path('repo_files',
219 219 repo_name=backend.repo_name,
220 220 commit_id=commit.raw_id, f_path='/'))
221 221
222 222 assert_response = response.assert_response()
223 223 for branch in branches:
224 224 assert_response.element_contains('.tags .branchtag', branch)
225 225
226 226 def test_show_files_paging(self, backend):
227 227 repo = backend.repo
228 228 indexes = [73, 92, 109, 1, 0]
229 229 idx_map = [(rev, repo.get_commit(commit_idx=rev).raw_id)
230 230 for rev in indexes]
231 231
232 232 for idx in idx_map:
233 233 response = self.app.get(
234 234 route_path('repo_files',
235 235 repo_name=backend.repo_name,
236 236 commit_id=idx[1], f_path='/'))
237 237
238 238 response.mustcontain("""r%s:%s""" % (idx[0], idx[1][:8]))
239 239
240 240 def test_file_source(self, backend):
241 241 commit = backend.repo.get_commit(commit_idx=167)
242 242 response = self.app.get(
243 243 route_path('repo_files',
244 244 repo_name=backend.repo_name,
245 245 commit_id=commit.raw_id, f_path='vcs/nodes.py'))
246 246
247 247 msgbox = """<div class="commit">%s</div>"""
248 248 response.mustcontain(msgbox % (commit.message, ))
249 249
250 250 assert_response = response.assert_response()
251 251 if commit.branch:
252 252 assert_response.element_contains(
253 253 '.tags.tags-main .branchtag', commit.branch)
254 254 if commit.tags:
255 255 for tag in commit.tags:
256 256 assert_response.element_contains('.tags.tags-main .tagtag', tag)
257 257
258 258 def test_file_source_annotated(self, backend):
259 259 response = self.app.get(
260 260 route_path('repo_files:annotated',
261 261 repo_name=backend.repo_name,
262 262 commit_id='tip', f_path='vcs/nodes.py'))
263 263 expected_commits = {
264 264 'hg': 'r356',
265 265 'git': 'r345',
266 266 'svn': 'r208',
267 267 }
268 268 response.mustcontain(expected_commits[backend.alias])
269 269
270 270 def test_file_source_authors(self, backend):
271 271 response = self.app.get(
272 272 route_path('repo_file_authors',
273 273 repo_name=backend.repo_name,
274 274 commit_id='tip', f_path='vcs/nodes.py'))
275 275 expected_authors = {
276 276 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
277 277 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
278 278 'svn': ('marcin', 'lukasz'),
279 279 }
280 280
281 281 for author in expected_authors[backend.alias]:
282 282 response.mustcontain(author)
283 283
284 284 def test_file_source_authors_with_annotation(self, backend):
285 285 response = self.app.get(
286 286 route_path('repo_file_authors',
287 287 repo_name=backend.repo_name,
288 288 commit_id='tip', f_path='vcs/nodes.py',
289 289 params=dict(annotate=1)))
290 290 expected_authors = {
291 291 'hg': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
292 292 'git': ('Marcin Kuzminski', 'Lukasz Balcerzak'),
293 293 'svn': ('marcin', 'lukasz'),
294 294 }
295 295
296 296 for author in expected_authors[backend.alias]:
297 297 response.mustcontain(author)
298 298
299 299 def test_file_source_history(self, backend, xhr_header):
300 300 response = self.app.get(
301 301 route_path('repo_file_history',
302 302 repo_name=backend.repo_name,
303 303 commit_id='tip', f_path='vcs/nodes.py'),
304 304 extra_environ=xhr_header)
305 305 assert get_node_history(backend.alias) == json.loads(response.body)
306 306
307 307 def test_file_source_history_svn(self, backend_svn, xhr_header):
308 308 simple_repo = backend_svn['svn-simple-layout']
309 309 response = self.app.get(
310 310 route_path('repo_file_history',
311 311 repo_name=simple_repo.repo_name,
312 312 commit_id='tip', f_path='trunk/example.py'),
313 313 extra_environ=xhr_header)
314 314
315 315 expected_data = json.loads(
316 316 fixture.load_resource('svn_node_history_branches.json'))
317 317
318 318 assert expected_data == response.json
319 319
320 320 def test_file_source_history_with_annotation(self, backend, xhr_header):
321 321 response = self.app.get(
322 322 route_path('repo_file_history',
323 323 repo_name=backend.repo_name,
324 324 commit_id='tip', f_path='vcs/nodes.py',
325 325 params=dict(annotate=1)),
326 326
327 327 extra_environ=xhr_header)
328 328 assert get_node_history(backend.alias) == json.loads(response.body)
329 329
330 330 def test_tree_search_top_level(self, backend, xhr_header):
331 331 commit = backend.repo.get_commit(commit_idx=173)
332 332 response = self.app.get(
333 333 route_path('repo_files_nodelist',
334 334 repo_name=backend.repo_name,
335 335 commit_id=commit.raw_id, f_path='/'),
336 336 extra_environ=xhr_header)
337 337 assert 'nodes' in response.json
338 338 assert {'name': 'docs', 'type': 'dir'} in response.json['nodes']
339 339
340 340 def test_tree_search_missing_xhr(self, backend):
341 341 self.app.get(
342 342 route_path('repo_files_nodelist',
343 343 repo_name=backend.repo_name,
344 344 commit_id='tip', f_path='/'),
345 345 status=404)
346 346
347 347 def test_tree_search_at_path(self, backend, xhr_header):
348 348 commit = backend.repo.get_commit(commit_idx=173)
349 349 response = self.app.get(
350 350 route_path('repo_files_nodelist',
351 351 repo_name=backend.repo_name,
352 352 commit_id=commit.raw_id, f_path='/docs'),
353 353 extra_environ=xhr_header)
354 354 assert 'nodes' in response.json
355 355 nodes = response.json['nodes']
356 356 assert {'name': 'docs/api', 'type': 'dir'} in nodes
357 357 assert {'name': 'docs/index.rst', 'type': 'file'} in nodes
358 358
359 359 def test_tree_search_at_path_2nd_level(self, backend, xhr_header):
360 360 commit = backend.repo.get_commit(commit_idx=173)
361 361 response = self.app.get(
362 362 route_path('repo_files_nodelist',
363 363 repo_name=backend.repo_name,
364 364 commit_id=commit.raw_id, f_path='/docs/api'),
365 365 extra_environ=xhr_header)
366 366 assert 'nodes' in response.json
367 367 nodes = response.json['nodes']
368 368 assert {'name': 'docs/api/index.rst', 'type': 'file'} in nodes
369 369
370 370 def test_tree_search_at_path_missing_xhr(self, backend):
371 371 self.app.get(
372 372 route_path('repo_files_nodelist',
373 373 repo_name=backend.repo_name,
374 374 commit_id='tip', f_path='/docs'),
375 375 status=404)
376 376
377 377 def test_nodetree(self, backend, xhr_header):
378 378 commit = backend.repo.get_commit(commit_idx=173)
379 379 response = self.app.get(
380 380 route_path('repo_nodetree_full',
381 381 repo_name=backend.repo_name,
382 382 commit_id=commit.raw_id, f_path='/'),
383 383 extra_environ=xhr_header)
384 384
385 385 assert_response = response.assert_response()
386 386
387 387 for attr in ['data-commit-id', 'data-date', 'data-author']:
388 388 elements = assert_response.get_elements('[{}]'.format(attr))
389 389 assert len(elements) > 1
390 390
391 391 for element in elements:
392 392 assert element.get(attr)
393 393
394 394 def test_nodetree_if_file(self, backend, xhr_header):
395 395 commit = backend.repo.get_commit(commit_idx=173)
396 396 response = self.app.get(
397 397 route_path('repo_nodetree_full',
398 398 repo_name=backend.repo_name,
399 399 commit_id=commit.raw_id, f_path='README.rst'),
400 400 extra_environ=xhr_header)
401 401 assert response.body == ''
402 402
403 403 def test_nodetree_wrong_path(self, backend, xhr_header):
404 404 commit = backend.repo.get_commit(commit_idx=173)
405 405 response = self.app.get(
406 406 route_path('repo_nodetree_full',
407 407 repo_name=backend.repo_name,
408 408 commit_id=commit.raw_id, f_path='/dont-exist'),
409 409 extra_environ=xhr_header)
410 410
411 411 err = 'error: There is no file nor ' \
412 412 'directory at the given path'
413 413 assert err in response.body
414 414
415 415 def test_nodetree_missing_xhr(self, backend):
416 416 self.app.get(
417 417 route_path('repo_nodetree_full',
418 418 repo_name=backend.repo_name,
419 419 commit_id='tip', f_path='/'),
420 420 status=404)
421 421
422 422
423 423 @pytest.mark.usefixtures("app", "autologin_user")
424 424 class TestRawFileHandling(object):
425 425
426 426 def test_download_file(self, backend):
427 427 commit = backend.repo.get_commit(commit_idx=173)
428 428 response = self.app.get(
429 429 route_path('repo_file_download',
430 430 repo_name=backend.repo_name,
431 431 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
432 432
433 433 assert response.content_disposition == 'attachment; filename="nodes.py"; filename*=UTF-8\'\'nodes.py'
434 434 assert response.content_type == "text/x-python"
435 435
436 436 def test_download_file_wrong_cs(self, backend):
437 437 raw_id = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
438 438
439 439 response = self.app.get(
440 440 route_path('repo_file_download',
441 441 repo_name=backend.repo_name,
442 442 commit_id=raw_id, f_path='vcs/nodes.svg'),
443 443 status=404)
444 444
445 445 msg = """No such commit exists for this repository"""
446 446 response.mustcontain(msg)
447 447
448 448 def test_download_file_wrong_f_path(self, backend):
449 449 commit = backend.repo.get_commit(commit_idx=173)
450 450 f_path = 'vcs/ERRORnodes.py'
451 451
452 452 response = self.app.get(
453 453 route_path('repo_file_download',
454 454 repo_name=backend.repo_name,
455 455 commit_id=commit.raw_id, f_path=f_path),
456 456 status=404)
457 457
458 458 msg = (
459 459 "There is no file nor directory at the given path: "
460 460 "`%s` at commit %s" % (f_path, commit.short_id))
461 461 response.mustcontain(msg)
462 462
463 463 def test_file_raw(self, backend):
464 464 commit = backend.repo.get_commit(commit_idx=173)
465 465 response = self.app.get(
466 466 route_path('repo_file_raw',
467 467 repo_name=backend.repo_name,
468 468 commit_id=commit.raw_id, f_path='vcs/nodes.py'),)
469 469
470 470 assert response.content_type == "text/plain"
471 471
472 472 def test_file_raw_binary(self, backend):
473 473 commit = backend.repo.get_commit()
474 474 response = self.app.get(
475 475 route_path('repo_file_raw',
476 476 repo_name=backend.repo_name,
477 477 commit_id=commit.raw_id,
478 478 f_path='docs/theme/ADC/static/breadcrumb_background.png'),)
479 479
480 480 assert response.content_disposition == 'inline'
481 481
482 482 def test_raw_file_wrong_cs(self, backend):
483 483 raw_id = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
484 484
485 485 response = self.app.get(
486 486 route_path('repo_file_raw',
487 487 repo_name=backend.repo_name,
488 488 commit_id=raw_id, f_path='vcs/nodes.svg'),
489 489 status=404)
490 490
491 491 msg = """No such commit exists for this repository"""
492 492 response.mustcontain(msg)
493 493
494 494 def test_raw_wrong_f_path(self, backend):
495 495 commit = backend.repo.get_commit(commit_idx=173)
496 496 f_path = 'vcs/ERRORnodes.py'
497 497 response = self.app.get(
498 498 route_path('repo_file_raw',
499 499 repo_name=backend.repo_name,
500 500 commit_id=commit.raw_id, f_path=f_path),
501 501 status=404)
502 502
503 503 msg = (
504 504 "There is no file nor directory at the given path: "
505 505 "`%s` at commit %s" % (f_path, commit.short_id))
506 506 response.mustcontain(msg)
507 507
508 508 def test_raw_svg_should_not_be_rendered(self, backend):
509 509 backend.create_repo()
510 510 backend.ensure_file("xss.svg")
511 511 response = self.app.get(
512 512 route_path('repo_file_raw',
513 513 repo_name=backend.repo_name,
514 514 commit_id='tip', f_path='xss.svg'),)
515 515 # If the content type is image/svg+xml then it allows to render HTML
516 516 # and malicious SVG.
517 517 assert response.content_type == "text/plain"
518 518
519 519
520 520 @pytest.mark.usefixtures("app")
521 521 class TestRepositoryArchival(object):
522 522
523 523 def test_archival(self, backend):
524 524 backend.enable_downloads()
525 525 commit = backend.repo.get_commit(commit_idx=173)
526 526 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
527 527
528 528 short = commit.short_id + extension
529 529 fname = commit.raw_id + extension
530 530 filename = '%s-%s' % (backend.repo_name, short)
531 531 response = self.app.get(
532 532 route_path('repo_archivefile',
533 533 repo_name=backend.repo_name,
534 534 fname=fname))
535 535
536 536 assert response.status == '200 OK'
537 537 headers = [
538 538 ('Content-Disposition', 'attachment; filename=%s' % filename),
539 539 ('Content-Type', '%s' % content_type),
540 540 ]
541 541
542 542 for header in headers:
543 543 assert header in response.headers.items()
544 544
545 545 @pytest.mark.parametrize('arch_ext',[
546 546 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
547 547 def test_archival_wrong_ext(self, backend, arch_ext):
548 548 backend.enable_downloads()
549 549 commit = backend.repo.get_commit(commit_idx=173)
550 550
551 551 fname = commit.raw_id + '.' + arch_ext
552 552
553 553 response = self.app.get(
554 554 route_path('repo_archivefile',
555 555 repo_name=backend.repo_name,
556 556 fname=fname))
557 557 response.mustcontain(
558 558 'Unknown archive type for: `{}`'.format(fname))
559 559
560 560 @pytest.mark.parametrize('commit_id', [
561 561 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
562 562 def test_archival_wrong_commit_id(self, backend, commit_id):
563 563 backend.enable_downloads()
564 564 fname = '%s.zip' % commit_id
565 565
566 566 response = self.app.get(
567 567 route_path('repo_archivefile',
568 568 repo_name=backend.repo_name,
569 569 fname=fname))
570 570 response.mustcontain('Unknown commit_id')
571 571
572 572
573 573 @pytest.mark.usefixtures("app")
574 574 class TestFilesDiff(object):
575 575
576 576 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
577 577 def test_file_full_diff(self, backend, diff):
578 578 commit1 = backend.repo.get_commit(commit_idx=-1)
579 579 commit2 = backend.repo.get_commit(commit_idx=-2)
580 580
581 581 response = self.app.get(
582 582 route_path('repo_files_diff',
583 583 repo_name=backend.repo_name,
584 584 f_path='README'),
585 585 params={
586 586 'diff1': commit2.raw_id,
587 587 'diff2': commit1.raw_id,
588 588 'fulldiff': '1',
589 589 'diff': diff,
590 590 })
591 591
592 592 if diff == 'diff':
593 593 # use redirect since this is OLD view redirecting to compare page
594 594 response = response.follow()
595 595
596 596 # It's a symlink to README.rst
597 597 response.mustcontain('README.rst')
598 598 response.mustcontain('No newline at end of file')
599 599
600 600 def test_file_binary_diff(self, backend):
601 601 commits = [
602 602 {'message': 'First commit'},
603 603 {'message': 'Commit with binary',
604 604 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]},
605 605 ]
606 606 repo = backend.create_repo(commits=commits)
607 607
608 608 response = self.app.get(
609 609 route_path('repo_files_diff',
610 610 repo_name=backend.repo_name,
611 611 f_path='file.bin'),
612 612 params={
613 613 'diff1': repo.get_commit(commit_idx=0).raw_id,
614 614 'diff2': repo.get_commit(commit_idx=1).raw_id,
615 615 'fulldiff': '1',
616 616 'diff': 'diff',
617 617 })
618 618 # use redirect since this is OLD view redirecting to compare page
619 619 response = response.follow()
620 620 response.mustcontain('Collapse 1 commit')
621 621 file_changes = (1, 0, 0)
622 622
623 623 compare_page = ComparePage(response)
624 624 compare_page.contains_change_summary(*file_changes)
625 625
626 626 if backend.alias == 'svn':
627 627 response.mustcontain('new file 10644')
628 628 # TODO(marcink): SVN doesn't yet detect binary changes
629 629 else:
630 630 response.mustcontain('new file 100644')
631 631 response.mustcontain('binary diff hidden')
632 632
633 633 def test_diff_2way(self, backend):
634 634 commit1 = backend.repo.get_commit(commit_idx=-1)
635 635 commit2 = backend.repo.get_commit(commit_idx=-2)
636 636 response = self.app.get(
637 637 route_path('repo_files_diff_2way_redirect',
638 638 repo_name=backend.repo_name,
639 639 f_path='README'),
640 640 params={
641 641 'diff1': commit2.raw_id,
642 642 'diff2': commit1.raw_id,
643 643 })
644 644 # use redirect since this is OLD view redirecting to compare page
645 645 response = response.follow()
646 646
647 647 # It's a symlink to README.rst
648 648 response.mustcontain('README.rst')
649 649 response.mustcontain('No newline at end of file')
650 650
651 651 def test_requires_one_commit_id(self, backend, autologin_user):
652 652 response = self.app.get(
653 653 route_path('repo_files_diff',
654 654 repo_name=backend.repo_name,
655 655 f_path='README.rst'),
656 656 status=400)
657 657 response.mustcontain(
658 658 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
659 659
660 660 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
661 661 repo = vcsbackend.repo
662 662 response = self.app.get(
663 663 route_path('repo_files_diff',
664 664 repo_name=repo.name,
665 665 f_path='does-not-exist-in-any-commit'),
666 666 params={
667 667 'diff1': repo[0].raw_id,
668 668 'diff2': repo[1].raw_id
669 669 })
670 670
671 671 response = response.follow()
672 672 response.mustcontain('No files')
673 673
674 674 def test_returns_redirect_if_file_not_changed(self, backend):
675 675 commit = backend.repo.get_commit(commit_idx=-1)
676 676 response = self.app.get(
677 677 route_path('repo_files_diff_2way_redirect',
678 678 repo_name=backend.repo_name,
679 679 f_path='README'),
680 680 params={
681 681 'diff1': commit.raw_id,
682 682 'diff2': commit.raw_id,
683 683 })
684 684
685 685 response = response.follow()
686 686 response.mustcontain('No files')
687 687 response.mustcontain('No commits in this compare')
688 688
689 689 def test_supports_diff_to_different_path_svn(self, backend_svn):
690 690 #TODO: check this case
691 691 return
692 692
693 693 repo = backend_svn['svn-simple-layout'].scm_instance()
694 694 commit_id_1 = '24'
695 695 commit_id_2 = '26'
696 696
697 697 response = self.app.get(
698 698 route_path('repo_files_diff',
699 699 repo_name=backend_svn.repo_name,
700 700 f_path='trunk/example.py'),
701 701 params={
702 702 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
703 703 'diff2': commit_id_2,
704 704 })
705 705
706 706 response = response.follow()
707 707 response.mustcontain(
708 708 # diff contains this
709 709 "Will print out a useful message on invocation.")
710 710
711 711 # Note: Expecting that we indicate the user what's being compared
712 712 response.mustcontain("trunk/example.py")
713 713 response.mustcontain("tags/v0.2/example.py")
714 714
715 715 def test_show_rev_redirects_to_svn_path(self, backend_svn):
716 716 #TODO: check this case
717 717 return
718 718
719 719 repo = backend_svn['svn-simple-layout'].scm_instance()
720 720 commit_id = repo[-1].raw_id
721 721
722 722 response = self.app.get(
723 723 route_path('repo_files_diff',
724 724 repo_name=backend_svn.repo_name,
725 725 f_path='trunk/example.py'),
726 726 params={
727 727 'diff1': 'branches/argparse/example.py@' + commit_id,
728 728 'diff2': commit_id,
729 729 },
730 730 status=302)
731 731 response = response.follow()
732 732 assert response.headers['Location'].endswith(
733 733 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
734 734
735 735 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
736 736 #TODO: check this case
737 737 return
738 738
739 739 repo = backend_svn['svn-simple-layout'].scm_instance()
740 740 commit_id = repo[-1].raw_id
741 741 response = self.app.get(
742 742 route_path('repo_files_diff',
743 743 repo_name=backend_svn.repo_name,
744 744 f_path='trunk/example.py'),
745 745 params={
746 746 'diff1': 'branches/argparse/example.py@' + commit_id,
747 747 'diff2': commit_id,
748 748 'show_rev': 'Show at Revision',
749 749 'annotate': 'true',
750 750 },
751 751 status=302)
752 752 response = response.follow()
753 753 assert response.headers['Location'].endswith(
754 754 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
755 755
756 756
757 757 @pytest.mark.usefixtures("app", "autologin_user")
758 758 class TestModifyFilesWithWebInterface(object):
759 759
760 760 def test_add_file_view(self, backend):
761 761 self.app.get(
762 762 route_path('repo_files_add_file',
763 763 repo_name=backend.repo_name,
764 764 commit_id='tip', f_path='/')
765 765 )
766 766
767 767 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
768 768 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
769 769 backend.create_repo()
770 770 filename = 'init.py'
771 771 response = self.app.post(
772 772 route_path('repo_files_create_file',
773 773 repo_name=backend.repo_name,
774 774 commit_id='tip', f_path='/'),
775 775 params={
776 776 'content': "",
777 777 'filename': filename,
778 778 'csrf_token': csrf_token,
779 779 },
780 780 status=302)
781 781 expected_msg = 'Successfully committed new file `{}`'.format(os.path.join(filename))
782 782 assert_session_flash(response, expected_msg)
783 783
784 784 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
785 785 commit_id = backend.repo.get_commit().raw_id
786 786 response = self.app.post(
787 787 route_path('repo_files_create_file',
788 788 repo_name=backend.repo_name,
789 789 commit_id=commit_id, f_path='/'),
790 790 params={
791 791 'content': "foo",
792 792 'csrf_token': csrf_token,
793 793 },
794 794 status=302)
795 795
796 796 assert_session_flash(response, 'No filename specified')
797 797
798 798 def test_add_file_into_repo_errors_and_no_commits(
799 799 self, backend, csrf_token):
800 800 repo = backend.create_repo()
801 801 # Create a file with no filename, it will display an error but
802 802 # the repo has no commits yet
803 803 response = self.app.post(
804 804 route_path('repo_files_create_file',
805 805 repo_name=repo.repo_name,
806 806 commit_id='tip', f_path='/'),
807 807 params={
808 808 'content': "foo",
809 809 'csrf_token': csrf_token,
810 810 },
811 811 status=302)
812 812
813 813 assert_session_flash(response, 'No filename specified')
814 814
815 815 # Not allowed, redirect to the summary
816 816 redirected = response.follow()
817 817 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
818 818
819 819 # As there are no commits, displays the summary page with the error of
820 820 # creating a file with no filename
821 821
822 822 assert redirected.request.path == summary_url
823 823
824 824 @pytest.mark.parametrize("filename, clean_filename", [
825 825 ('/abs/foo', 'abs/foo'),
826 826 ('../rel/foo', 'rel/foo'),
827 827 ('file/../foo/foo', 'file/foo/foo'),
828 828 ])
829 829 def test_add_file_into_repo_bad_filenames(self, filename, clean_filename, backend, csrf_token):
830 830 repo = backend.create_repo()
831 831 commit_id = repo.get_commit().raw_id
832 832
833 833 response = self.app.post(
834 834 route_path('repo_files_create_file',
835 835 repo_name=repo.repo_name,
836 836 commit_id=commit_id, f_path='/'),
837 837 params={
838 838 'content': "foo",
839 839 'filename': filename,
840 840 'csrf_token': csrf_token,
841 841 },
842 842 status=302)
843 843
844 844 expected_msg = 'Successfully committed new file `{}`'.format(clean_filename)
845 845 assert_session_flash(response, expected_msg)
846 846
847 847 @pytest.mark.parametrize("cnt, filename, content", [
848 848 (1, 'foo.txt', "Content"),
849 849 (2, 'dir/foo.rst', "Content"),
850 850 (3, 'dir/foo-second.rst', "Content"),
851 851 (4, 'rel/dir/foo.bar', "Content"),
852 852 ])
853 853 def test_add_file_into_empty_repo(self, cnt, filename, content, backend, csrf_token):
854 854 repo = backend.create_repo()
855 855 commit_id = repo.get_commit().raw_id
856 856 response = self.app.post(
857 857 route_path('repo_files_create_file',
858 858 repo_name=repo.repo_name,
859 859 commit_id=commit_id, f_path='/'),
860 860 params={
861 861 'content': content,
862 862 'filename': filename,
863 863 'csrf_token': csrf_token,
864 864 },
865 865 status=302)
866 866
867 867 expected_msg = 'Successfully committed new file `{}`'.format(filename)
868 868 assert_session_flash(response, expected_msg)
869 869
870 870 def test_edit_file_view(self, backend):
871 871 response = self.app.get(
872 872 route_path('repo_files_edit_file',
873 873 repo_name=backend.repo_name,
874 874 commit_id=backend.default_head_id,
875 875 f_path='vcs/nodes.py'),
876 876 status=200)
877 877 response.mustcontain("Module holding everything related to vcs nodes.")
878 878
879 879 def test_edit_file_view_not_on_branch(self, backend):
880 880 repo = backend.create_repo()
881 881 backend.ensure_file("vcs/nodes.py")
882 882
883 883 response = self.app.get(
884 884 route_path('repo_files_edit_file',
885 885 repo_name=repo.repo_name,
886 886 commit_id='tip',
887 887 f_path='vcs/nodes.py'),
888 888 status=302)
889 889 assert_session_flash(
890 890 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
891 891
892 892 def test_edit_file_view_commit_changes(self, backend, csrf_token):
893 893 repo = backend.create_repo()
894 894 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
895 895
896 896 response = self.app.post(
897 897 route_path('repo_files_update_file',
898 898 repo_name=repo.repo_name,
899 899 commit_id=backend.default_head_id,
900 900 f_path='vcs/nodes.py'),
901 901 params={
902 902 'content': "print 'hello world'",
903 903 'message': 'I committed',
904 904 'filename': "vcs/nodes.py",
905 905 'csrf_token': csrf_token,
906 906 },
907 907 status=302)
908 908 assert_session_flash(
909 909 response, 'Successfully committed changes to file `vcs/nodes.py`')
910 910 tip = repo.get_commit(commit_idx=-1)
911 911 assert tip.message == 'I committed'
912 912
913 913 def test_edit_file_view_commit_changes_default_message(self, backend,
914 914 csrf_token):
915 915 repo = backend.create_repo()
916 916 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
917 917
918 918 commit_id = (
919 919 backend.default_branch_name or
920 920 backend.repo.scm_instance().commit_ids[-1])
921 921
922 922 response = self.app.post(
923 923 route_path('repo_files_update_file',
924 924 repo_name=repo.repo_name,
925 925 commit_id=commit_id,
926 926 f_path='vcs/nodes.py'),
927 927 params={
928 928 'content': "print 'hello world'",
929 929 'message': '',
930 930 'filename': "vcs/nodes.py",
931 931 'csrf_token': csrf_token,
932 932 },
933 933 status=302)
934 934 assert_session_flash(
935 935 response, 'Successfully committed changes to file `vcs/nodes.py`')
936 936 tip = repo.get_commit(commit_idx=-1)
937 937 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
938 938
939 939 def test_delete_file_view(self, backend):
940 940 self.app.get(
941 941 route_path('repo_files_remove_file',
942 942 repo_name=backend.repo_name,
943 943 commit_id=backend.default_head_id,
944 944 f_path='vcs/nodes.py'),
945 945 status=200)
946 946
947 947 def test_delete_file_view_not_on_branch(self, backend):
948 948 repo = backend.create_repo()
949 949 backend.ensure_file('vcs/nodes.py')
950 950
951 951 response = self.app.get(
952 952 route_path('repo_files_remove_file',
953 953 repo_name=repo.repo_name,
954 954 commit_id='tip',
955 955 f_path='vcs/nodes.py'),
956 956 status=302)
957 957 assert_session_flash(
958 958 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
959 959
960 960 def test_delete_file_view_commit_changes(self, backend, csrf_token):
961 961 repo = backend.create_repo()
962 962 backend.ensure_file("vcs/nodes.py")
963 963
964 964 response = self.app.post(
965 965 route_path('repo_files_delete_file',
966 966 repo_name=repo.repo_name,
967 967 commit_id=backend.default_head_id,
968 968 f_path='vcs/nodes.py'),
969 969 params={
970 970 'message': 'i commited',
971 971 'csrf_token': csrf_token,
972 972 },
973 973 status=302)
974 974 assert_session_flash(
975 975 response, 'Successfully deleted file `vcs/nodes.py`')
976 976
977 977
978 978 @pytest.mark.usefixtures("app")
979 979 class TestFilesViewOtherCases(object):
980 980
981 981 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
982 982 self, backend_stub, autologin_regular_user, user_regular,
983 983 user_util):
984 984
985 985 repo = backend_stub.create_repo()
986 986 user_util.grant_user_permission_to_repo(
987 987 repo, user_regular, 'repository.write')
988 988 response = self.app.get(
989 989 route_path('repo_files',
990 990 repo_name=repo.repo_name,
991 991 commit_id='tip', f_path='/'))
992 992
993 993 repo_file_add_url = route_path(
994 994 'repo_files_add_file',
995 995 repo_name=repo.repo_name,
996 996 commit_id=0, f_path='')
997 997
998 998 assert_session_flash(
999 999 response,
1000 1000 'There are no files yet. <a class="alert-link" '
1001 1001 'href="{}">Click here to add a new file.</a>'
1002 1002 .format(repo_file_add_url))
1003 1003
1004 1004 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1005 1005 self, backend_stub, autologin_regular_user):
1006 1006 repo = backend_stub.create_repo()
1007 1007 # init session for anon user
1008 1008 route_path('repo_summary', repo_name=repo.repo_name)
1009 1009
1010 1010 repo_file_add_url = route_path(
1011 1011 'repo_files_add_file',
1012 1012 repo_name=repo.repo_name,
1013 1013 commit_id=0, f_path='')
1014 1014
1015 1015 response = self.app.get(
1016 1016 route_path('repo_files',
1017 1017 repo_name=repo.repo_name,
1018 1018 commit_id='tip', f_path='/'))
1019 1019
1020 1020 assert_session_flash(response, no_=repo_file_add_url)
1021 1021
1022 1022 @pytest.mark.parametrize('file_node', [
1023 1023 'archive/file.zip',
1024 1024 'diff/my-file.txt',
1025 1025 'render.py',
1026 1026 'render',
1027 1027 'remove_file',
1028 1028 'remove_file/to-delete.txt',
1029 1029 ])
1030 1030 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1031 1031 backend.create_repo()
1032 1032 backend.ensure_file(file_node)
1033 1033
1034 1034 self.app.get(
1035 1035 route_path('repo_files',
1036 1036 repo_name=backend.repo_name,
1037 1037 commit_id='tip', f_path=file_node),
1038 1038 status=200)
1039 1039
1040 1040
1041 1041 class TestAdjustFilePathForSvn(object):
1042 1042 """
1043 1043 SVN specific adjustments of node history in RepoFilesView.
1044 1044 """
1045 1045
1046 1046 def test_returns_path_relative_to_matched_reference(self):
1047 1047 repo = self._repo(branches=['trunk'])
1048 1048 self.assert_file_adjustment('trunk/file', 'file', repo)
1049 1049
1050 1050 def test_does_not_modify_file_if_no_reference_matches(self):
1051 1051 repo = self._repo(branches=['trunk'])
1052 1052 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1053 1053
1054 1054 def test_does_not_adjust_partial_directory_names(self):
1055 1055 repo = self._repo(branches=['trun'])
1056 1056 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1057 1057
1058 1058 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1059 1059 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1060 1060 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1061 1061
1062 1062 def assert_file_adjustment(self, f_path, expected, repo):
1063 1063 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1064 1064 assert result == expected
1065 1065
1066 1066 def _repo(self, branches=None):
1067 1067 repo = mock.Mock()
1068 1068 repo.branches = OrderedDict((name, '0') for name in branches or [])
1069 1069 repo.tags = {}
1070 1070 return repo
@@ -1,1603 +1,1603 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27 import urllib
28 28 import pathlib2
29 29
30 30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 31 from pyramid.view import view_config
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 import rhodecode
36 36 from rhodecode.apps._base import RepoAppView
37 37
38 38
39 39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 40 from rhodecode.lib import audit_logger
41 41 from rhodecode.lib.view_utils import parse_path_ref
42 42 from rhodecode.lib.exceptions import NonRelativePathError
43 43 from rhodecode.lib.codeblocks import (
44 44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 45 from rhodecode.lib.utils2 import (
46 46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 47 from rhodecode.lib.auth import (
48 48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 49 from rhodecode.lib.vcs import path as vcspath
50 50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 51 from rhodecode.lib.vcs.conf import settings
52 52 from rhodecode.lib.vcs.nodes import FileNode
53 53 from rhodecode.lib.vcs.exceptions import (
54 54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 56 NodeDoesNotExistError, CommitError, NodeError)
57 57
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.db import Repository
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class RepoFilesView(RepoAppView):
65 65
66 66 @staticmethod
67 67 def adjust_file_path_for_svn(f_path, repo):
68 68 """
69 69 Computes the relative path of `f_path`.
70 70
71 71 This is mainly based on prefix matching of the recognized tags and
72 72 branches in the underlying repository.
73 73 """
74 74 tags_and_branches = itertools.chain(
75 75 repo.branches.iterkeys(),
76 76 repo.tags.iterkeys())
77 77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78 78
79 79 for name in tags_and_branches:
80 80 if f_path.startswith('{}/'.format(name)):
81 81 f_path = vcspath.relpath(f_path, name)
82 82 break
83 83 return f_path
84 84
85 85 def load_default_context(self):
86 86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 88 c.enable_downloads = self.db_repo.enable_downloads
89 89 return c
90 90
91 91 def _ensure_not_locked(self, commit_id='tip'):
92 92 _ = self.request.translate
93 93
94 94 repo = self.db_repo
95 95 if repo.enable_locking and repo.locked[0]:
96 96 h.flash(_('This repository has been locked by %s on %s')
97 97 % (h.person_by_id(repo.locked[0]),
98 98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 99 'warning')
100 100 files_url = h.route_path(
101 101 'repo_files:default_path',
102 102 repo_name=self.db_repo_name, commit_id=commit_id)
103 103 raise HTTPFound(files_url)
104 104
105 105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 106 _ = self.request.translate
107 107
108 108 if not is_head:
109 109 message = _('Cannot modify file. '
110 110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 111 h.flash(message, category='warning')
112 112
113 113 if json_mode:
114 114 return message
115 115
116 116 files_url = h.route_path(
117 117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 118 f_path=f_path)
119 119 raise HTTPFound(files_url)
120 120
121 121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 122 _ = self.request.translate
123 123
124 124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 125 self.db_repo_name, branch_name)
126 126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 128 branch_name, rule)
129 129 h.flash(message, 'warning')
130 130
131 131 if json_mode:
132 132 return message
133 133
134 134 files_url = h.route_path(
135 135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136 136
137 137 raise HTTPFound(files_url)
138 138
139 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_ref_name
141 141 default_f_path = '/'
142 142
143 143 commit_id = self.request.matchdict.get(
144 144 'commit_id', default_commit_id)
145 145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 146 return commit_id, f_path
147 147
148 148 def _get_default_encoding(self, c):
149 149 enc_list = getattr(c, 'default_encodings', [])
150 150 return enc_list[0] if enc_list else 'UTF-8'
151 151
152 152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 153 """
154 154 This is a safe way to get commit. If an error occurs it redirects to
155 155 tip with proper message
156 156
157 157 :param commit_id: id of commit to fetch
158 158 :param redirect_after: toggle redirection
159 159 """
160 160 _ = self.request.translate
161 161
162 162 try:
163 163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 164 except EmptyRepositoryError:
165 165 if not redirect_after:
166 166 return None
167 167
168 168 _url = h.route_path(
169 169 'repo_files_add_file',
170 170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171 171
172 172 if h.HasRepoPermissionAny(
173 173 'repository.write', 'repository.admin')(self.db_repo_name):
174 174 add_new = h.link_to(
175 175 _('Click here to add a new file.'), _url, class_="alert-link")
176 176 else:
177 177 add_new = ""
178 178
179 179 h.flash(h.literal(
180 180 _('There are no files yet. %s') % add_new), category='warning')
181 181 raise HTTPFound(
182 182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183 183
184 184 except (CommitDoesNotExistError, LookupError):
185 185 msg = _('No such commit exists for this repository')
186 186 h.flash(msg, category='error')
187 187 raise HTTPNotFound()
188 188 except RepositoryError as e:
189 189 h.flash(safe_str(h.escape(e)), category='error')
190 190 raise HTTPNotFound()
191 191
192 192 def _get_filenode_or_redirect(self, commit_obj, path):
193 193 """
194 194 Returns file_node, if error occurs or given path is directory,
195 195 it'll redirect to top level path
196 196 """
197 197 _ = self.request.translate
198 198
199 199 try:
200 200 file_node = commit_obj.get_node(path)
201 201 if file_node.is_dir():
202 202 raise RepositoryError('The given path is a directory')
203 203 except CommitDoesNotExistError:
204 204 log.exception('No such commit exists for this repository')
205 205 h.flash(_('No such commit exists for this repository'), category='error')
206 206 raise HTTPNotFound()
207 207 except RepositoryError as e:
208 208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 209 h.flash(safe_str(h.escape(e)), category='error')
210 210 raise HTTPNotFound()
211 211
212 212 return file_node
213 213
214 214 def _is_valid_head(self, commit_id, repo):
215 215 branch_name = sha_commit_id = ''
216 216 is_head = False
217 217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218 218
219 219 for _branch_name, branch_commit_id in repo.branches.items():
220 220 # simple case we pass in branch name, it's a HEAD
221 221 if commit_id == _branch_name:
222 222 is_head = True
223 223 branch_name = _branch_name
224 224 sha_commit_id = branch_commit_id
225 225 break
226 226 # case when we pass in full sha commit_id, which is a head
227 227 elif commit_id == branch_commit_id:
228 228 is_head = True
229 229 branch_name = _branch_name
230 230 sha_commit_id = branch_commit_id
231 231 break
232 232
233 233 if h.is_svn(repo) and not repo.is_empty():
234 234 # Note: Subversion only has one head.
235 235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 236 is_head = True
237 237 return branch_name, sha_commit_id, is_head
238 238
239 239 # checked branches, means we only need to try to get the branch/commit_sha
240 240 if not repo.is_empty():
241 241 commit = repo.get_commit(commit_id=commit_id)
242 242 if commit:
243 243 branch_name = commit.branch
244 244 sha_commit_id = commit.raw_id
245 245
246 246 return branch_name, sha_commit_id, is_head
247 247
248 248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
249 249
250 250 repo_id = self.db_repo.repo_id
251 251 force_recache = self.get_recache_flag()
252 252
253 253 cache_seconds = safe_int(
254 254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 255 cache_on = not force_recache and cache_seconds > 0
256 256 log.debug(
257 257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 258 'with caching: %s[TTL: %ss]' % (
259 259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260 260
261 261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263 263
264 264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
265 265 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
266 266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 267 ver, _repo_id, _commit_id, _f_path)
268 268
269 269 c.full_load = _full_load
270 270 return render(
271 271 'rhodecode:templates/files/files_browser_tree.mako',
272 272 self._get_template_context(c), self.request, _at_rev)
273 273
274 274 return compute_file_tree(
275 275 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
276 276 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
277 277
278 278 def _get_archive_spec(self, fname):
279 279 log.debug('Detecting archive spec for: `%s`', fname)
280 280
281 281 fileformat = None
282 282 ext = None
283 283 content_type = None
284 284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285 285
286 286 if fname.endswith(extension):
287 287 fileformat = a_type
288 288 log.debug('archive is of type: %s', fileformat)
289 289 ext = extension
290 290 break
291 291
292 292 if not fileformat:
293 293 raise ValueError()
294 294
295 295 # left over part of whole fname is the commit
296 296 commit_id = fname[:-len(ext)]
297 297
298 298 return commit_id, ext, fileformat, content_type
299 299
300 300 def create_pure_path(self, *parts):
301 301 # Split paths and sanitize them, removing any ../ etc
302 302 sanitized_path = [
303 303 x for x in pathlib2.PurePath(*parts).parts
304 304 if x not in ['.', '..']]
305 305
306 306 pure_path = pathlib2.PurePath(*sanitized_path)
307 307 return pure_path
308 308
309 309 def _is_lf_enabled(self, target_repo):
310 310 lf_enabled = False
311 311
312 312 lf_key_for_vcs_map = {
313 313 'hg': 'extensions_largefiles',
314 314 'git': 'vcs_git_lfs_enabled'
315 315 }
316 316
317 317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318 318
319 319 if lf_key_for_vcs:
320 320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321 321
322 322 return lf_enabled
323 323
324 324 @LoginRequired()
325 325 @HasRepoPermissionAnyDecorator(
326 326 'repository.read', 'repository.write', 'repository.admin')
327 327 @view_config(
328 328 route_name='repo_archivefile', request_method='GET',
329 329 renderer=None)
330 330 def repo_archivefile(self):
331 331 # archive cache config
332 332 from rhodecode import CONFIG
333 333 _ = self.request.translate
334 334 self.load_default_context()
335 335 default_at_path = '/'
336 336 fname = self.request.matchdict['fname']
337 337 subrepos = self.request.GET.get('subrepos') == 'true'
338 338 at_path = self.request.GET.get('at_path') or default_at_path
339 339
340 340 if not self.db_repo.enable_downloads:
341 341 return Response(_('Downloads disabled'))
342 342
343 343 try:
344 344 commit_id, ext, fileformat, content_type = \
345 345 self._get_archive_spec(fname)
346 346 except ValueError:
347 347 return Response(_('Unknown archive type for: `{}`').format(
348 348 h.escape(fname)))
349 349
350 350 try:
351 351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 352 except CommitDoesNotExistError:
353 353 return Response(_('Unknown commit_id {}').format(
354 354 h.escape(commit_id)))
355 355 except EmptyRepositoryError:
356 356 return Response(_('Empty repository'))
357 357
358 358 try:
359 359 at_path = commit.get_node(at_path).path or default_at_path
360 360 except Exception:
361 361 return Response(_('No node at path {} for this repository').format(at_path))
362 362
363 363 path_sha = sha1(at_path)[:8]
364 364
365 365 # original backward compat name of archive
366 366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 367 short_sha = safe_str(commit.short_id)
368 368
369 369 if at_path == default_at_path:
370 370 archive_name = '{}-{}{}{}'.format(
371 371 clean_name,
372 372 '-sub' if subrepos else '',
373 373 short_sha,
374 374 ext)
375 375 # custom path and new name
376 376 else:
377 377 archive_name = '{}-{}{}-{}{}'.format(
378 378 clean_name,
379 379 '-sub' if subrepos else '',
380 380 short_sha,
381 381 path_sha,
382 382 ext)
383 383
384 384 use_cached_archive = False
385 385 archive_cache_enabled = CONFIG.get(
386 386 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 387 cached_archive_path = None
388 388
389 389 if archive_cache_enabled:
390 390 # check if we it's ok to write
391 391 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 392 os.makedirs(CONFIG['archive_cache_dir'])
393 393 cached_archive_path = os.path.join(
394 394 CONFIG['archive_cache_dir'], archive_name)
395 395 if os.path.isfile(cached_archive_path):
396 396 log.debug('Found cached archive in %s', cached_archive_path)
397 397 fd, archive = None, cached_archive_path
398 398 use_cached_archive = True
399 399 else:
400 400 log.debug('Archive %s is not yet cached', archive_name)
401 401
402 402 if not use_cached_archive:
403 403 # generate new archive
404 404 fd, archive = tempfile.mkstemp()
405 405 log.debug('Creating new temp archive in %s', archive)
406 406 try:
407 407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 408 archive_at_path=at_path)
409 409 except ImproperArchiveTypeError:
410 410 return _('Unknown archive type')
411 411 if archive_cache_enabled:
412 412 # if we generated the archive and we have cache enabled
413 413 # let's use this for future
414 414 log.debug('Storing new archive in %s', cached_archive_path)
415 415 shutil.move(archive, cached_archive_path)
416 416 archive = cached_archive_path
417 417
418 418 # store download action
419 419 audit_logger.store_web(
420 420 'repo.archive.download', action_data={
421 421 'user_agent': self.request.user_agent,
422 422 'archive_name': archive_name,
423 423 'archive_spec': fname,
424 424 'archive_cached': use_cached_archive},
425 425 user=self._rhodecode_user,
426 426 repo=self.db_repo,
427 427 commit=True
428 428 )
429 429
430 430 def get_chunked_archive(archive_path):
431 431 with open(archive_path, 'rb') as stream:
432 432 while True:
433 433 data = stream.read(16 * 1024)
434 434 if not data:
435 435 if fd: # fd means we used temporary file
436 436 os.close(fd)
437 437 if not archive_cache_enabled:
438 438 log.debug('Destroying temp archive %s', archive_path)
439 439 os.remove(archive_path)
440 440 break
441 441 yield data
442 442
443 443 response = Response(app_iter=get_chunked_archive(archive))
444 444 response.content_disposition = str(
445 445 'attachment; filename=%s' % archive_name)
446 446 response.content_type = str(content_type)
447 447
448 448 return response
449 449
450 450 def _get_file_node(self, commit_id, f_path):
451 451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 453 try:
454 454 node = commit.get_node(f_path)
455 455 if node.is_dir():
456 456 raise NodeError('%s path is a %s not a file'
457 457 % (node, type(node)))
458 458 except NodeDoesNotExistError:
459 459 commit = EmptyCommit(
460 460 commit_id=commit_id,
461 461 idx=commit.idx,
462 462 repo=commit.repository,
463 463 alias=commit.repository.alias,
464 464 message=commit.message,
465 465 author=commit.author,
466 466 date=commit.date)
467 467 node = FileNode(f_path, '', commit=commit)
468 468 else:
469 469 commit = EmptyCommit(
470 470 repo=self.rhodecode_vcs_repo,
471 471 alias=self.rhodecode_vcs_repo.alias)
472 472 node = FileNode(f_path, '', commit=commit)
473 473 return node
474 474
475 475 @LoginRequired()
476 476 @HasRepoPermissionAnyDecorator(
477 477 'repository.read', 'repository.write', 'repository.admin')
478 478 @view_config(
479 479 route_name='repo_files_diff', request_method='GET',
480 480 renderer=None)
481 481 def repo_files_diff(self):
482 482 c = self.load_default_context()
483 483 f_path = self._get_f_path(self.request.matchdict)
484 484 diff1 = self.request.GET.get('diff1', '')
485 485 diff2 = self.request.GET.get('diff2', '')
486 486
487 487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488 488
489 489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 490 line_context = self.request.GET.get('context', 3)
491 491
492 492 if not any((diff1, diff2)):
493 493 h.flash(
494 494 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 495 category='error')
496 496 raise HTTPBadRequest()
497 497
498 498 c.action = self.request.GET.get('diff')
499 499 if c.action not in ['download', 'raw']:
500 500 compare_url = h.route_path(
501 501 'repo_compare',
502 502 repo_name=self.db_repo_name,
503 503 source_ref_type='rev',
504 504 source_ref=diff1,
505 505 target_repo=self.db_repo_name,
506 506 target_ref_type='rev',
507 507 target_ref=diff2,
508 508 _query=dict(f_path=f_path))
509 509 # redirect to new view if we render diff
510 510 raise HTTPFound(compare_url)
511 511
512 512 try:
513 513 node1 = self._get_file_node(diff1, path1)
514 514 node2 = self._get_file_node(diff2, f_path)
515 515 except (RepositoryError, NodeError):
516 516 log.exception("Exception while trying to get node from repository")
517 517 raise HTTPFound(
518 518 h.route_path('repo_files', repo_name=self.db_repo_name,
519 519 commit_id='tip', f_path=f_path))
520 520
521 521 if all(isinstance(node.commit, EmptyCommit)
522 522 for node in (node1, node2)):
523 523 raise HTTPNotFound()
524 524
525 525 c.commit_1 = node1.commit
526 526 c.commit_2 = node2.commit
527 527
528 528 if c.action == 'download':
529 529 _diff = diffs.get_gitdiff(node1, node2,
530 530 ignore_whitespace=ignore_whitespace,
531 531 context=line_context)
532 532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533 533
534 534 response = Response(self.path_filter.get_raw_patch(diff))
535 535 response.content_type = 'text/plain'
536 536 response.content_disposition = (
537 537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 538 )
539 539 charset = self._get_default_encoding(c)
540 540 if charset:
541 541 response.charset = charset
542 542 return response
543 543
544 544 elif c.action == 'raw':
545 545 _diff = diffs.get_gitdiff(node1, node2,
546 546 ignore_whitespace=ignore_whitespace,
547 547 context=line_context)
548 548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549 549
550 550 response = Response(self.path_filter.get_raw_patch(diff))
551 551 response.content_type = 'text/plain'
552 552 charset = self._get_default_encoding(c)
553 553 if charset:
554 554 response.charset = charset
555 555 return response
556 556
557 557 # in case we ever end up here
558 558 raise HTTPNotFound()
559 559
560 560 @LoginRequired()
561 561 @HasRepoPermissionAnyDecorator(
562 562 'repository.read', 'repository.write', 'repository.admin')
563 563 @view_config(
564 564 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 565 renderer=None)
566 566 def repo_files_diff_2way_redirect(self):
567 567 """
568 568 Kept only to make OLD links work
569 569 """
570 570 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 571 diff1 = self.request.GET.get('diff1', '')
572 572 diff2 = self.request.GET.get('diff2', '')
573 573
574 574 if not any((diff1, diff2)):
575 575 h.flash(
576 576 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 577 category='error')
578 578 raise HTTPBadRequest()
579 579
580 580 compare_url = h.route_path(
581 581 'repo_compare',
582 582 repo_name=self.db_repo_name,
583 583 source_ref_type='rev',
584 584 source_ref=diff1,
585 585 target_ref_type='rev',
586 586 target_ref=diff2,
587 587 _query=dict(f_path=f_path, diffmode='sideside',
588 588 target_repo=self.db_repo_name,))
589 589 raise HTTPFound(compare_url)
590 590
591 591 @LoginRequired()
592 592 @HasRepoPermissionAnyDecorator(
593 593 'repository.read', 'repository.write', 'repository.admin')
594 594 @view_config(
595 595 route_name='repo_files', request_method='GET',
596 596 renderer=None)
597 597 @view_config(
598 598 route_name='repo_files:default_path', request_method='GET',
599 599 renderer=None)
600 600 @view_config(
601 601 route_name='repo_files:default_commit', request_method='GET',
602 602 renderer=None)
603 603 @view_config(
604 604 route_name='repo_files:rendered', request_method='GET',
605 605 renderer=None)
606 606 @view_config(
607 607 route_name='repo_files:annotated', request_method='GET',
608 608 renderer=None)
609 609 def repo_files(self):
610 610 c = self.load_default_context()
611 611
612 612 view_name = getattr(self.request.matched_route, 'name', None)
613 613
614 614 c.annotate = view_name == 'repo_files:annotated'
615 615 # default is false, but .rst/.md files later are auto rendered, we can
616 616 # overwrite auto rendering by setting this GET flag
617 617 c.renderer = view_name == 'repo_files:rendered' or \
618 618 not self.request.GET.get('no-render', False)
619 619
620 620 commit_id, f_path = self._get_commit_and_path()
621 621
622 622 c.commit = self._get_commit_or_redirect(commit_id)
623 623 c.branch = self.request.GET.get('branch', None)
624 624 c.f_path = f_path
625 625 at_rev = self.request.GET.get('at')
626 626
627 627 # prev link
628 628 try:
629 629 prev_commit = c.commit.prev(c.branch)
630 630 c.prev_commit = prev_commit
631 631 c.url_prev = h.route_path(
632 632 'repo_files', repo_name=self.db_repo_name,
633 633 commit_id=prev_commit.raw_id, f_path=f_path)
634 634 if c.branch:
635 635 c.url_prev += '?branch=%s' % c.branch
636 636 except (CommitDoesNotExistError, VCSError):
637 637 c.url_prev = '#'
638 638 c.prev_commit = EmptyCommit()
639 639
640 640 # next link
641 641 try:
642 642 next_commit = c.commit.next(c.branch)
643 643 c.next_commit = next_commit
644 644 c.url_next = h.route_path(
645 645 'repo_files', repo_name=self.db_repo_name,
646 646 commit_id=next_commit.raw_id, f_path=f_path)
647 647 if c.branch:
648 648 c.url_next += '?branch=%s' % c.branch
649 649 except (CommitDoesNotExistError, VCSError):
650 650 c.url_next = '#'
651 651 c.next_commit = EmptyCommit()
652 652
653 653 # files or dirs
654 654 try:
655 655 c.file = c.commit.get_node(f_path)
656 656 c.file_author = True
657 657 c.file_tree = ''
658 658
659 659 # load file content
660 660 if c.file.is_file():
661 661 c.lf_node = {}
662 662
663 663 has_lf_enabled = self._is_lf_enabled(self.db_repo)
664 664 if has_lf_enabled:
665 665 c.lf_node = c.file.get_largefile_node()
666 666
667 667 c.file_source_page = 'true'
668 668 c.file_last_commit = c.file.last_commit
669 669
670 670 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
671 671
672 672 if not (c.file_size_too_big or c.file.is_binary):
673 673 if c.annotate: # annotation has precedence over renderer
674 674 c.annotated_lines = filenode_as_annotated_lines_tokens(
675 675 c.file
676 676 )
677 677 else:
678 678 c.renderer = (
679 679 c.renderer and h.renderer_from_filename(c.file.path)
680 680 )
681 681 if not c.renderer:
682 682 c.lines = filenode_as_lines_tokens(c.file)
683 683
684 684 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
685 685 commit_id, self.rhodecode_vcs_repo)
686 686 c.on_branch_head = is_head
687 687
688 688 branch = c.commit.branch if (
689 689 c.commit.branch and '/' not in c.commit.branch) else None
690 690 c.branch_or_raw_id = branch or c.commit.raw_id
691 691 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
692 692
693 693 author = c.file_last_commit.author
694 694 c.authors = [[
695 695 h.email(author),
696 696 h.person(author, 'username_or_name_or_email'),
697 697 1
698 698 ]]
699 699
700 700 else: # load tree content at path
701 701 c.file_source_page = 'false'
702 702 c.authors = []
703 703 # this loads a simple tree without metadata to speed things up
704 704 # later via ajax we call repo_nodetree_full and fetch whole
705 705 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
706 706
707 707 c.readme_data, c.readme_file = \
708 708 self._get_readme_data(self.db_repo, c.visual.default_renderer,
709 709 c.commit.raw_id, f_path)
710 710
711 711 except RepositoryError as e:
712 712 h.flash(safe_str(h.escape(e)), category='error')
713 713 raise HTTPNotFound()
714 714
715 715 if self.request.environ.get('HTTP_X_PJAX'):
716 716 html = render('rhodecode:templates/files/files_pjax.mako',
717 717 self._get_template_context(c), self.request)
718 718 else:
719 719 html = render('rhodecode:templates/files/files.mako',
720 720 self._get_template_context(c), self.request)
721 721 return Response(html)
722 722
723 723 @HasRepoPermissionAnyDecorator(
724 724 'repository.read', 'repository.write', 'repository.admin')
725 725 @view_config(
726 726 route_name='repo_files:annotated_previous', request_method='GET',
727 727 renderer=None)
728 728 def repo_files_annotated_previous(self):
729 729 self.load_default_context()
730 730
731 731 commit_id, f_path = self._get_commit_and_path()
732 732 commit = self._get_commit_or_redirect(commit_id)
733 733 prev_commit_id = commit.raw_id
734 734 line_anchor = self.request.GET.get('line_anchor')
735 735 is_file = False
736 736 try:
737 737 _file = commit.get_node(f_path)
738 738 is_file = _file.is_file()
739 739 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
740 740 pass
741 741
742 742 if is_file:
743 743 history = commit.get_path_history(f_path)
744 744 prev_commit_id = history[1].raw_id \
745 745 if len(history) > 1 else prev_commit_id
746 746 prev_url = h.route_path(
747 747 'repo_files:annotated', repo_name=self.db_repo_name,
748 748 commit_id=prev_commit_id, f_path=f_path,
749 749 _anchor='L{}'.format(line_anchor))
750 750
751 751 raise HTTPFound(prev_url)
752 752
753 753 @LoginRequired()
754 754 @HasRepoPermissionAnyDecorator(
755 755 'repository.read', 'repository.write', 'repository.admin')
756 756 @view_config(
757 757 route_name='repo_nodetree_full', request_method='GET',
758 758 renderer=None, xhr=True)
759 759 @view_config(
760 760 route_name='repo_nodetree_full:default_path', request_method='GET',
761 761 renderer=None, xhr=True)
762 762 def repo_nodetree_full(self):
763 763 """
764 764 Returns rendered html of file tree that contains commit date,
765 765 author, commit_id for the specified combination of
766 766 repo, commit_id and file path
767 767 """
768 768 c = self.load_default_context()
769 769
770 770 commit_id, f_path = self._get_commit_and_path()
771 771 commit = self._get_commit_or_redirect(commit_id)
772 772 try:
773 773 dir_node = commit.get_node(f_path)
774 774 except RepositoryError as e:
775 775 return Response('error: {}'.format(h.escape(safe_str(e))))
776 776
777 777 if dir_node.is_file():
778 778 return Response('')
779 779
780 780 c.file = dir_node
781 781 c.commit = commit
782 782 at_rev = self.request.GET.get('at')
783 783
784 784 html = self._get_tree_at_commit(
785 785 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
786 786
787 787 return Response(html)
788 788
789 789 def _get_attachement_headers(self, f_path):
790 790 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
791 791 safe_path = f_name.replace('"', '\\"')
792 792 encoded_path = urllib.quote(f_name)
793 793
794 794 return "attachment; " \
795 795 "filename=\"{}\"; " \
796 796 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
797 797
798 798 @LoginRequired()
799 799 @HasRepoPermissionAnyDecorator(
800 800 'repository.read', 'repository.write', 'repository.admin')
801 801 @view_config(
802 802 route_name='repo_file_raw', request_method='GET',
803 803 renderer=None)
804 804 def repo_file_raw(self):
805 805 """
806 806 Action for show as raw, some mimetypes are "rendered",
807 807 those include images, icons.
808 808 """
809 809 c = self.load_default_context()
810 810
811 811 commit_id, f_path = self._get_commit_and_path()
812 812 commit = self._get_commit_or_redirect(commit_id)
813 813 file_node = self._get_filenode_or_redirect(commit, f_path)
814 814
815 815 raw_mimetype_mapping = {
816 816 # map original mimetype to a mimetype used for "show as raw"
817 817 # you can also provide a content-disposition to override the
818 818 # default "attachment" disposition.
819 819 # orig_type: (new_type, new_dispo)
820 820
821 821 # show images inline:
822 822 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
823 823 # for example render an SVG with javascript inside or even render
824 824 # HTML.
825 825 'image/x-icon': ('image/x-icon', 'inline'),
826 826 'image/png': ('image/png', 'inline'),
827 827 'image/gif': ('image/gif', 'inline'),
828 828 'image/jpeg': ('image/jpeg', 'inline'),
829 829 'application/pdf': ('application/pdf', 'inline'),
830 830 }
831 831
832 832 mimetype = file_node.mimetype
833 833 try:
834 834 mimetype, disposition = raw_mimetype_mapping[mimetype]
835 835 except KeyError:
836 836 # we don't know anything special about this, handle it safely
837 837 if file_node.is_binary:
838 838 # do same as download raw for binary files
839 839 mimetype, disposition = 'application/octet-stream', 'attachment'
840 840 else:
841 841 # do not just use the original mimetype, but force text/plain,
842 842 # otherwise it would serve text/html and that might be unsafe.
843 843 # Note: underlying vcs library fakes text/plain mimetype if the
844 844 # mimetype can not be determined and it thinks it is not
845 845 # binary.This might lead to erroneous text display in some
846 846 # cases, but helps in other cases, like with text files
847 847 # without extension.
848 848 mimetype, disposition = 'text/plain', 'inline'
849 849
850 850 if disposition == 'attachment':
851 851 disposition = self._get_attachement_headers(f_path)
852 852
853 853 stream_content = file_node.stream_bytes()
854 854
855 855 response = Response(app_iter=stream_content)
856 856 response.content_disposition = disposition
857 857 response.content_type = mimetype
858 858
859 859 charset = self._get_default_encoding(c)
860 860 if charset:
861 861 response.charset = charset
862 862
863 863 return response
864 864
865 865 @LoginRequired()
866 866 @HasRepoPermissionAnyDecorator(
867 867 'repository.read', 'repository.write', 'repository.admin')
868 868 @view_config(
869 869 route_name='repo_file_download', request_method='GET',
870 870 renderer=None)
871 871 @view_config(
872 872 route_name='repo_file_download:legacy', request_method='GET',
873 873 renderer=None)
874 874 def repo_file_download(self):
875 875 c = self.load_default_context()
876 876
877 877 commit_id, f_path = self._get_commit_and_path()
878 878 commit = self._get_commit_or_redirect(commit_id)
879 879 file_node = self._get_filenode_or_redirect(commit, f_path)
880 880
881 881 if self.request.GET.get('lf'):
882 882 # only if lf get flag is passed, we download this file
883 883 # as LFS/Largefile
884 884 lf_node = file_node.get_largefile_node()
885 885 if lf_node:
886 886 # overwrite our pointer with the REAL large-file
887 887 file_node = lf_node
888 888
889 889 disposition = self._get_attachement_headers(f_path)
890 890
891 891 stream_content = file_node.stream_bytes()
892 892
893 893 response = Response(app_iter=stream_content)
894 894 response.content_disposition = disposition
895 895 response.content_type = file_node.mimetype
896 896
897 897 charset = self._get_default_encoding(c)
898 898 if charset:
899 899 response.charset = charset
900 900
901 901 return response
902 902
903 903 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
904 904
905 905 cache_seconds = safe_int(
906 906 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
907 907 cache_on = cache_seconds > 0
908 908 log.debug(
909 909 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
910 910 'with caching: %s[TTL: %ss]' % (
911 911 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
912 912
913 913 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
914 914 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
915 915
916 916 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
917 917 def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
918 918 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
919 919 _repo_id, commit_id, f_path)
920 920 try:
921 921 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
922 922 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
923 923 log.exception(safe_str(e))
924 924 h.flash(safe_str(h.escape(e)), category='error')
925 925 raise HTTPFound(h.route_path(
926 926 'repo_files', repo_name=self.db_repo_name,
927 927 commit_id='tip', f_path='/'))
928 928
929 929 return _d + _f
930 930
931 931 result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
932 932 commit_id, f_path)
933 933 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
934 934
935 935 @LoginRequired()
936 936 @HasRepoPermissionAnyDecorator(
937 937 'repository.read', 'repository.write', 'repository.admin')
938 938 @view_config(
939 939 route_name='repo_files_nodelist', request_method='GET',
940 940 renderer='json_ext', xhr=True)
941 941 def repo_nodelist(self):
942 942 self.load_default_context()
943 943
944 944 commit_id, f_path = self._get_commit_and_path()
945 945 commit = self._get_commit_or_redirect(commit_id)
946 946
947 947 metadata = self._get_nodelist_at_commit(
948 948 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
949 949 return {'nodes': metadata}
950 950
951 951 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
952 952 items = []
953 953 for name, commit_id in branches_or_tags.items():
954 954 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
955 955 items.append((sym_ref, name, ref_type))
956 956 return items
957 957
958 958 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
959 959 return commit_id
960 960
961 961 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
962 962 return commit_id
963 963
964 964 # NOTE(dan): old code we used in "diff" mode compare
965 965 new_f_path = vcspath.join(name, f_path)
966 966 return u'%s@%s' % (new_f_path, commit_id)
967 967
968 968 def _get_node_history(self, commit_obj, f_path, commits=None):
969 969 """
970 970 get commit history for given node
971 971
972 972 :param commit_obj: commit to calculate history
973 973 :param f_path: path for node to calculate history for
974 974 :param commits: if passed don't calculate history and take
975 975 commits defined in this list
976 976 """
977 977 _ = self.request.translate
978 978
979 979 # calculate history based on tip
980 980 tip = self.rhodecode_vcs_repo.get_commit()
981 981 if commits is None:
982 982 pre_load = ["author", "branch"]
983 983 try:
984 984 commits = tip.get_path_history(f_path, pre_load=pre_load)
985 985 except (NodeDoesNotExistError, CommitError):
986 986 # this node is not present at tip!
987 987 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
988 988
989 989 history = []
990 990 commits_group = ([], _("Changesets"))
991 991 for commit in commits:
992 992 branch = ' (%s)' % commit.branch if commit.branch else ''
993 993 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
994 994 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
995 995 history.append(commits_group)
996 996
997 997 symbolic_reference = self._symbolic_reference
998 998
999 999 if self.rhodecode_vcs_repo.alias == 'svn':
1000 1000 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1001 1001 f_path, self.rhodecode_vcs_repo)
1002 1002 if adjusted_f_path != f_path:
1003 1003 log.debug(
1004 1004 'Recognized svn tag or branch in file "%s", using svn '
1005 1005 'specific symbolic references', f_path)
1006 1006 f_path = adjusted_f_path
1007 1007 symbolic_reference = self._symbolic_reference_svn
1008 1008
1009 1009 branches = self._create_references(
1010 1010 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1011 1011 branches_group = (branches, _("Branches"))
1012 1012
1013 1013 tags = self._create_references(
1014 1014 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1015 1015 tags_group = (tags, _("Tags"))
1016 1016
1017 1017 history.append(branches_group)
1018 1018 history.append(tags_group)
1019 1019
1020 1020 return history, commits
1021 1021
1022 1022 @LoginRequired()
1023 1023 @HasRepoPermissionAnyDecorator(
1024 1024 'repository.read', 'repository.write', 'repository.admin')
1025 1025 @view_config(
1026 1026 route_name='repo_file_history', request_method='GET',
1027 1027 renderer='json_ext')
1028 1028 def repo_file_history(self):
1029 1029 self.load_default_context()
1030 1030
1031 1031 commit_id, f_path = self._get_commit_and_path()
1032 1032 commit = self._get_commit_or_redirect(commit_id)
1033 1033 file_node = self._get_filenode_or_redirect(commit, f_path)
1034 1034
1035 1035 if file_node.is_file():
1036 1036 file_history, _hist = self._get_node_history(commit, f_path)
1037 1037
1038 1038 res = []
1039 1039 for section_items, section in file_history:
1040 1040 items = []
1041 1041 for obj_id, obj_text, obj_type in section_items:
1042 1042 at_rev = ''
1043 1043 if obj_type in ['branch', 'bookmark', 'tag']:
1044 1044 at_rev = obj_text
1045 1045 entry = {
1046 1046 'id': obj_id,
1047 1047 'text': obj_text,
1048 1048 'type': obj_type,
1049 1049 'at_rev': at_rev
1050 1050 }
1051 1051
1052 1052 items.append(entry)
1053 1053
1054 1054 res.append({
1055 1055 'text': section,
1056 1056 'children': items
1057 1057 })
1058 1058
1059 1059 data = {
1060 1060 'more': False,
1061 1061 'results': res
1062 1062 }
1063 1063 return data
1064 1064
1065 1065 log.warning('Cannot fetch history for directory')
1066 1066 raise HTTPBadRequest()
1067 1067
1068 1068 @LoginRequired()
1069 1069 @HasRepoPermissionAnyDecorator(
1070 1070 'repository.read', 'repository.write', 'repository.admin')
1071 1071 @view_config(
1072 1072 route_name='repo_file_authors', request_method='GET',
1073 1073 renderer='rhodecode:templates/files/file_authors_box.mako')
1074 1074 def repo_file_authors(self):
1075 1075 c = self.load_default_context()
1076 1076
1077 1077 commit_id, f_path = self._get_commit_and_path()
1078 1078 commit = self._get_commit_or_redirect(commit_id)
1079 1079 file_node = self._get_filenode_or_redirect(commit, f_path)
1080 1080
1081 1081 if not file_node.is_file():
1082 1082 raise HTTPBadRequest()
1083 1083
1084 1084 c.file_last_commit = file_node.last_commit
1085 1085 if self.request.GET.get('annotate') == '1':
1086 1086 # use _hist from annotation if annotation mode is on
1087 1087 commit_ids = set(x[1] for x in file_node.annotate)
1088 1088 _hist = (
1089 1089 self.rhodecode_vcs_repo.get_commit(commit_id)
1090 1090 for commit_id in commit_ids)
1091 1091 else:
1092 1092 _f_history, _hist = self._get_node_history(commit, f_path)
1093 1093 c.file_author = False
1094 1094
1095 1095 unique = collections.OrderedDict()
1096 1096 for commit in _hist:
1097 1097 author = commit.author
1098 1098 if author not in unique:
1099 1099 unique[commit.author] = [
1100 1100 h.email(author),
1101 1101 h.person(author, 'username_or_name_or_email'),
1102 1102 1 # counter
1103 1103 ]
1104 1104
1105 1105 else:
1106 1106 # increase counter
1107 1107 unique[commit.author][2] += 1
1108 1108
1109 1109 c.authors = [val for val in unique.values()]
1110 1110
1111 1111 return self._get_template_context(c)
1112 1112
1113 1113 @LoginRequired()
1114 1114 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1115 1115 @view_config(
1116 1116 route_name='repo_files_check_head', request_method='POST',
1117 1117 renderer='json_ext', xhr=True)
1118 1118 def repo_files_check_head(self):
1119 1119 self.load_default_context()
1120 1120
1121 1121 commit_id, f_path = self._get_commit_and_path()
1122 1122 _branch_name, _sha_commit_id, is_head = \
1123 1123 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1124 1124
1125 1125 new_path = self.request.POST.get('path')
1126 1126 operation = self.request.POST.get('operation')
1127 1127 path_exist = ''
1128 1128
1129 1129 if new_path and operation in ['create', 'upload']:
1130 1130 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1131 1131 try:
1132 1132 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1133 1133 # NOTE(dan): construct whole path without leading /
1134 1134 file_node = commit_obj.get_node(new_f_path)
1135 1135 if file_node is not None:
1136 1136 path_exist = new_f_path
1137 1137 except EmptyRepositoryError:
1138 1138 pass
1139 1139 except Exception:
1140 1140 pass
1141 1141
1142 1142 return {
1143 1143 'branch': _branch_name,
1144 1144 'sha': _sha_commit_id,
1145 1145 'is_head': is_head,
1146 1146 'path_exists': path_exist
1147 1147 }
1148 1148
1149 1149 @LoginRequired()
1150 1150 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1151 1151 @view_config(
1152 1152 route_name='repo_files_remove_file', request_method='GET',
1153 1153 renderer='rhodecode:templates/files/files_delete.mako')
1154 1154 def repo_files_remove_file(self):
1155 1155 _ = self.request.translate
1156 1156 c = self.load_default_context()
1157 1157 commit_id, f_path = self._get_commit_and_path()
1158 1158
1159 1159 self._ensure_not_locked()
1160 1160 _branch_name, _sha_commit_id, is_head = \
1161 1161 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1162 1162
1163 1163 self.forbid_non_head(is_head, f_path)
1164 1164 self.check_branch_permission(_branch_name)
1165 1165
1166 1166 c.commit = self._get_commit_or_redirect(commit_id)
1167 1167 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1168 1168
1169 1169 c.default_message = _(
1170 1170 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1171 1171 c.f_path = f_path
1172 1172
1173 1173 return self._get_template_context(c)
1174 1174
1175 1175 @LoginRequired()
1176 1176 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 1177 @CSRFRequired()
1178 1178 @view_config(
1179 1179 route_name='repo_files_delete_file', request_method='POST',
1180 1180 renderer=None)
1181 1181 def repo_files_delete_file(self):
1182 1182 _ = self.request.translate
1183 1183
1184 1184 c = self.load_default_context()
1185 1185 commit_id, f_path = self._get_commit_and_path()
1186 1186
1187 1187 self._ensure_not_locked()
1188 1188 _branch_name, _sha_commit_id, is_head = \
1189 1189 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1190 1190
1191 1191 self.forbid_non_head(is_head, f_path)
1192 1192 self.check_branch_permission(_branch_name)
1193 1193
1194 1194 c.commit = self._get_commit_or_redirect(commit_id)
1195 1195 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1196 1196
1197 1197 c.default_message = _(
1198 1198 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1199 1199 c.f_path = f_path
1200 1200 node_path = f_path
1201 1201 author = self._rhodecode_db_user.full_contact
1202 1202 message = self.request.POST.get('message') or c.default_message
1203 1203 try:
1204 1204 nodes = {
1205 1205 node_path: {
1206 1206 'content': ''
1207 1207 }
1208 1208 }
1209 1209 ScmModel().delete_nodes(
1210 1210 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1211 1211 message=message,
1212 1212 nodes=nodes,
1213 1213 parent_commit=c.commit,
1214 1214 author=author,
1215 1215 )
1216 1216
1217 1217 h.flash(
1218 1218 _('Successfully deleted file `{}`').format(
1219 1219 h.escape(f_path)), category='success')
1220 1220 except Exception:
1221 1221 log.exception('Error during commit operation')
1222 1222 h.flash(_('Error occurred during commit'), category='error')
1223 1223 raise HTTPFound(
1224 1224 h.route_path('repo_commit', repo_name=self.db_repo_name,
1225 1225 commit_id='tip'))
1226 1226
1227 1227 @LoginRequired()
1228 1228 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1229 1229 @view_config(
1230 1230 route_name='repo_files_edit_file', request_method='GET',
1231 1231 renderer='rhodecode:templates/files/files_edit.mako')
1232 1232 def repo_files_edit_file(self):
1233 1233 _ = self.request.translate
1234 1234 c = self.load_default_context()
1235 1235 commit_id, f_path = self._get_commit_and_path()
1236 1236
1237 1237 self._ensure_not_locked()
1238 1238 _branch_name, _sha_commit_id, is_head = \
1239 1239 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1240 1240
1241 1241 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1242 1242 self.check_branch_permission(_branch_name, commit_id=commit_id)
1243 1243
1244 1244 c.commit = self._get_commit_or_redirect(commit_id)
1245 1245 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1246 1246
1247 1247 if c.file.is_binary:
1248 1248 files_url = h.route_path(
1249 1249 'repo_files',
1250 1250 repo_name=self.db_repo_name,
1251 1251 commit_id=c.commit.raw_id, f_path=f_path)
1252 1252 raise HTTPFound(files_url)
1253 1253
1254 1254 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1255 1255 c.f_path = f_path
1256 1256
1257 1257 return self._get_template_context(c)
1258 1258
1259 1259 @LoginRequired()
1260 1260 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1261 1261 @CSRFRequired()
1262 1262 @view_config(
1263 1263 route_name='repo_files_update_file', request_method='POST',
1264 1264 renderer=None)
1265 1265 def repo_files_update_file(self):
1266 1266 _ = self.request.translate
1267 1267 c = self.load_default_context()
1268 1268 commit_id, f_path = self._get_commit_and_path()
1269 1269
1270 1270 self._ensure_not_locked()
1271 1271
1272 1272 c.commit = self._get_commit_or_redirect(commit_id)
1273 1273 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1274 1274
1275 1275 if c.file.is_binary:
1276 1276 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1277 1277 commit_id=c.commit.raw_id, f_path=f_path))
1278 1278
1279 1279 _branch_name, _sha_commit_id, is_head = \
1280 1280 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1281 1281
1282 1282 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1283 1283 self.check_branch_permission(_branch_name, commit_id=commit_id)
1284 1284
1285 1285 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1286 1286 c.f_path = f_path
1287 1287
1288 1288 old_content = c.file.content
1289 1289 sl = old_content.splitlines(1)
1290 1290 first_line = sl[0] if sl else ''
1291 1291
1292 1292 r_post = self.request.POST
1293 1293 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1294 1294 line_ending_mode = detect_mode(first_line, 0)
1295 1295 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1296 1296
1297 1297 message = r_post.get('message') or c.default_message
1298 1298 org_node_path = c.file.unicode_path
1299 1299 filename = r_post['filename']
1300 1300
1301 1301 root_path = c.file.dir_path
1302 1302 pure_path = self.create_pure_path(root_path, filename)
1303 1303 node_path = safe_unicode(bytes(pure_path))
1304 1304
1305 1305 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1306 1306 commit_id=commit_id)
1307 1307 if content == old_content and node_path == org_node_path:
1308 1308 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1309 1309 category='warning')
1310 1310 raise HTTPFound(default_redirect_url)
1311 1311
1312 1312 try:
1313 1313 mapping = {
1314 1314 org_node_path: {
1315 1315 'org_filename': org_node_path,
1316 1316 'filename': node_path,
1317 1317 'content': content,
1318 1318 'lexer': '',
1319 1319 'op': 'mod',
1320 1320 'mode': c.file.mode
1321 1321 }
1322 1322 }
1323 1323
1324 1324 commit = ScmModel().update_nodes(
1325 1325 user=self._rhodecode_db_user.user_id,
1326 1326 repo=self.db_repo,
1327 1327 message=message,
1328 1328 nodes=mapping,
1329 1329 parent_commit=c.commit,
1330 1330 )
1331 1331
1332 1332 h.flash(_('Successfully committed changes to file `{}`').format(
1333 1333 h.escape(f_path)), category='success')
1334 1334 default_redirect_url = h.route_path(
1335 1335 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1336 1336
1337 1337 except Exception:
1338 1338 log.exception('Error occurred during commit')
1339 1339 h.flash(_('Error occurred during commit'), category='error')
1340 1340
1341 1341 raise HTTPFound(default_redirect_url)
1342 1342
1343 1343 @LoginRequired()
1344 1344 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1345 1345 @view_config(
1346 1346 route_name='repo_files_add_file', request_method='GET',
1347 1347 renderer='rhodecode:templates/files/files_add.mako')
1348 1348 @view_config(
1349 1349 route_name='repo_files_upload_file', request_method='GET',
1350 1350 renderer='rhodecode:templates/files/files_upload.mako')
1351 1351 def repo_files_add_file(self):
1352 1352 _ = self.request.translate
1353 1353 c = self.load_default_context()
1354 1354 commit_id, f_path = self._get_commit_and_path()
1355 1355
1356 1356 self._ensure_not_locked()
1357 1357
1358 1358 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1359 1359 if c.commit is None:
1360 1360 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1361 1361
1362 1362 if self.rhodecode_vcs_repo.is_empty():
1363 1363 # for empty repository we cannot check for current branch, we rely on
1364 1364 # c.commit.branch instead
1365 1365 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1366 1366 else:
1367 1367 _branch_name, _sha_commit_id, is_head = \
1368 1368 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1369 1369
1370 1370 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1371 1371 self.check_branch_permission(_branch_name, commit_id=commit_id)
1372 1372
1373 1373 c.default_message = (_('Added file via RhodeCode Enterprise'))
1374 1374 c.f_path = f_path.lstrip('/') # ensure not relative path
1375 1375
1376 1376 return self._get_template_context(c)
1377 1377
1378 1378 @LoginRequired()
1379 1379 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1380 1380 @CSRFRequired()
1381 1381 @view_config(
1382 1382 route_name='repo_files_create_file', request_method='POST',
1383 1383 renderer=None)
1384 1384 def repo_files_create_file(self):
1385 1385 _ = self.request.translate
1386 1386 c = self.load_default_context()
1387 1387 commit_id, f_path = self._get_commit_and_path()
1388 1388
1389 1389 self._ensure_not_locked()
1390 1390
1391 1391 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1392 1392 if c.commit is None:
1393 1393 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1394 1394
1395 1395 # calculate redirect URL
1396 1396 if self.rhodecode_vcs_repo.is_empty():
1397 1397 default_redirect_url = h.route_path(
1398 1398 'repo_summary', repo_name=self.db_repo_name)
1399 1399 else:
1400 1400 default_redirect_url = h.route_path(
1401 1401 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1402 1402
1403 1403 if self.rhodecode_vcs_repo.is_empty():
1404 1404 # for empty repository we cannot check for current branch, we rely on
1405 1405 # c.commit.branch instead
1406 1406 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1407 1407 else:
1408 1408 _branch_name, _sha_commit_id, is_head = \
1409 1409 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1410 1410
1411 1411 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1412 1412 self.check_branch_permission(_branch_name, commit_id=commit_id)
1413 1413
1414 1414 c.default_message = (_('Added file via RhodeCode Enterprise'))
1415 1415 c.f_path = f_path
1416 1416
1417 1417 r_post = self.request.POST
1418 1418 message = r_post.get('message') or c.default_message
1419 1419 filename = r_post.get('filename')
1420 1420 unix_mode = 0
1421 1421 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1422 1422
1423 1423 if not filename:
1424 1424 # If there's no commit, redirect to repo summary
1425 1425 if type(c.commit) is EmptyCommit:
1426 1426 redirect_url = h.route_path(
1427 1427 'repo_summary', repo_name=self.db_repo_name)
1428 1428 else:
1429 1429 redirect_url = default_redirect_url
1430 1430 h.flash(_('No filename specified'), category='warning')
1431 1431 raise HTTPFound(redirect_url)
1432 1432
1433 1433 root_path = f_path
1434 1434 pure_path = self.create_pure_path(root_path, filename)
1435 1435 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1436 1436
1437 1437 author = self._rhodecode_db_user.full_contact
1438 1438 nodes = {
1439 1439 node_path: {
1440 1440 'content': content
1441 1441 }
1442 1442 }
1443 1443
1444 1444 try:
1445 1445
1446 1446 commit = ScmModel().create_nodes(
1447 1447 user=self._rhodecode_db_user.user_id,
1448 1448 repo=self.db_repo,
1449 1449 message=message,
1450 1450 nodes=nodes,
1451 1451 parent_commit=c.commit,
1452 1452 author=author,
1453 1453 )
1454 1454
1455 1455 h.flash(_('Successfully committed new file `{}`').format(
1456 1456 h.escape(node_path)), category='success')
1457 1457
1458 1458 default_redirect_url = h.route_path(
1459 1459 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1460 1460
1461 1461 except NonRelativePathError:
1462 1462 log.exception('Non Relative path found')
1463 1463 h.flash(_('The location specified must be a relative path and must not '
1464 1464 'contain .. in the path'), category='warning')
1465 1465 raise HTTPFound(default_redirect_url)
1466 1466 except (NodeError, NodeAlreadyExistsError) as e:
1467 1467 h.flash(_(h.escape(e)), category='error')
1468 1468 except Exception:
1469 1469 log.exception('Error occurred during commit')
1470 1470 h.flash(_('Error occurred during commit'), category='error')
1471 1471
1472 1472 raise HTTPFound(default_redirect_url)
1473 1473
1474 1474 @LoginRequired()
1475 1475 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1476 1476 @CSRFRequired()
1477 1477 @view_config(
1478 1478 route_name='repo_files_upload_file', request_method='POST',
1479 1479 renderer='json_ext')
1480 1480 def repo_files_upload_file(self):
1481 1481 _ = self.request.translate
1482 1482 c = self.load_default_context()
1483 1483 commit_id, f_path = self._get_commit_and_path()
1484 1484
1485 1485 self._ensure_not_locked()
1486 1486
1487 1487 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1488 1488 if c.commit is None:
1489 1489 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1490 1490
1491 1491 # calculate redirect URL
1492 1492 if self.rhodecode_vcs_repo.is_empty():
1493 1493 default_redirect_url = h.route_path(
1494 1494 'repo_summary', repo_name=self.db_repo_name)
1495 1495 else:
1496 1496 default_redirect_url = h.route_path(
1497 1497 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1498 1498
1499 1499 if self.rhodecode_vcs_repo.is_empty():
1500 1500 # for empty repository we cannot check for current branch, we rely on
1501 1501 # c.commit.branch instead
1502 1502 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1503 1503 else:
1504 1504 _branch_name, _sha_commit_id, is_head = \
1505 1505 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1506 1506
1507 1507 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1508 1508 if error:
1509 1509 return {
1510 1510 'error': error,
1511 1511 'redirect_url': default_redirect_url
1512 1512 }
1513 1513 error = self.check_branch_permission(_branch_name, json_mode=True)
1514 1514 if error:
1515 1515 return {
1516 1516 'error': error,
1517 1517 'redirect_url': default_redirect_url
1518 1518 }
1519 1519
1520 1520 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1521 1521 c.f_path = f_path
1522 1522
1523 1523 r_post = self.request.POST
1524 1524
1525 1525 message = c.default_message
1526 1526 user_message = r_post.getall('message')
1527 1527 if isinstance(user_message, list) and user_message:
1528 1528 # we take the first from duplicated results if it's not empty
1529 1529 message = user_message[0] if user_message[0] else message
1530 1530
1531 1531 nodes = {}
1532 1532
1533 1533 for file_obj in r_post.getall('files_upload') or []:
1534 1534 content = file_obj.file
1535 1535 filename = file_obj.filename
1536 1536
1537 1537 root_path = f_path
1538 1538 pure_path = self.create_pure_path(root_path, filename)
1539 1539 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1540 1540
1541 1541 nodes[node_path] = {
1542 1542 'content': content
1543 1543 }
1544 1544
1545 1545 if not nodes:
1546 1546 error = 'missing files'
1547 1547 return {
1548 1548 'error': error,
1549 1549 'redirect_url': default_redirect_url
1550 1550 }
1551 1551
1552 1552 author = self._rhodecode_db_user.full_contact
1553 1553
1554 1554 try:
1555 1555 commit = ScmModel().create_nodes(
1556 1556 user=self._rhodecode_db_user.user_id,
1557 1557 repo=self.db_repo,
1558 1558 message=message,
1559 1559 nodes=nodes,
1560 1560 parent_commit=c.commit,
1561 1561 author=author,
1562 1562 )
1563 1563 if len(nodes) == 1:
1564 1564 flash_message = _('Successfully committed {} new files').format(len(nodes))
1565 1565 else:
1566 1566 flash_message = _('Successfully committed 1 new file')
1567 1567
1568 1568 h.flash(flash_message, category='success')
1569 1569
1570 1570 default_redirect_url = h.route_path(
1571 1571 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1572 1572
1573 1573 except NonRelativePathError:
1574 1574 log.exception('Non Relative path found')
1575 1575 error = _('The location specified must be a relative path and must not '
1576 1576 'contain .. in the path')
1577 1577 h.flash(error, category='warning')
1578 1578
1579 1579 return {
1580 1580 'error': error,
1581 1581 'redirect_url': default_redirect_url
1582 1582 }
1583 1583 except (NodeError, NodeAlreadyExistsError) as e:
1584 1584 error = h.escape(e)
1585 1585 h.flash(error, category='error')
1586 1586
1587 1587 return {
1588 1588 'error': error,
1589 1589 'redirect_url': default_redirect_url
1590 1590 }
1591 1591 except Exception:
1592 1592 log.exception('Error occurred during commit')
1593 1593 error = _('Error occurred during commit')
1594 1594 h.flash(error, category='error')
1595 1595 return {
1596 1596 'error': error,
1597 1597 'redirect_url': default_redirect_url
1598 1598 }
1599 1599
1600 1600 return {
1601 1601 'error': None,
1602 1602 'redirect_url': default_redirect_url
1603 1603 }
@@ -1,266 +1,266 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 36 import rhodecode.lib.helpers as h
37 37 from rhodecode.lib.celerylib.utils import get_task_id
38 38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.forms import RepoForkForm
42 42 from rhodecode.model.scm import ScmModel, RepoGroupList
43 43 from rhodecode.lib.utils2 import safe_int, safe_unicode
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoForksView(RepoAppView, DataGridAppView):
49 49
50 50 def load_default_context(self):
51 51 c = self._get_local_tmpl_context(include_app_defaults=True)
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53
54 54 acl_groups = RepoGroupList(
55 55 RepoGroup.query().all(),
56 56 perm_set=['group.write', 'group.admin'])
57 57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 59
60 60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
61 61
62 62 return c
63 63
64 64 @LoginRequired()
65 65 @HasRepoPermissionAnyDecorator(
66 66 'repository.read', 'repository.write', 'repository.admin')
67 67 @view_config(
68 68 route_name='repo_forks_show_all', request_method='GET',
69 69 renderer='rhodecode:templates/forks/forks.mako')
70 70 def repo_forks_show_all(self):
71 71 c = self.load_default_context()
72 72 return self._get_template_context(c)
73 73
74 74 @LoginRequired()
75 75 @HasRepoPermissionAnyDecorator(
76 76 'repository.read', 'repository.write', 'repository.admin')
77 77 @view_config(
78 78 route_name='repo_forks_data', request_method='GET',
79 79 renderer='json_ext', xhr=True)
80 80 def repo_forks_data(self):
81 81 _ = self.request.translate
82 82 self.load_default_context()
83 83 column_map = {
84 84 'fork_name': 'repo_name',
85 85 'fork_date': 'created_on',
86 86 'last_activity': 'updated_on'
87 87 }
88 88 draw, start, limit = self._extract_chunk(self.request)
89 89 search_q, order_by, order_dir = self._extract_ordering(
90 90 self.request, column_map=column_map)
91 91
92 92 acl_check = HasRepoPermissionAny(
93 93 'repository.read', 'repository.write', 'repository.admin')
94 94 repo_id = self.db_repo.repo_id
95 95 allowed_ids = [-1]
96 96 for f in Repository.query().filter(Repository.fork_id == repo_id):
97 97 if acl_check(f.repo_name, 'get forks check'):
98 98 allowed_ids.append(f.repo_id)
99 99
100 100 forks_data_total_count = Repository.query()\
101 101 .filter(Repository.fork_id == repo_id)\
102 102 .filter(Repository.repo_id.in_(allowed_ids))\
103 103 .count()
104 104
105 105 # json generate
106 106 base_q = Repository.query()\
107 107 .filter(Repository.fork_id == repo_id)\
108 108 .filter(Repository.repo_id.in_(allowed_ids))\
109 109
110 110 if search_q:
111 111 like_expression = u'%{}%'.format(safe_unicode(search_q))
112 112 base_q = base_q.filter(or_(
113 113 Repository.repo_name.ilike(like_expression),
114 114 Repository.description.ilike(like_expression),
115 115 ))
116 116
117 117 forks_data_total_filtered_count = base_q.count()
118 118
119 119 sort_col = getattr(Repository, order_by, None)
120 120 if sort_col:
121 121 if order_dir == 'asc':
122 122 # handle null values properly to order by NULL last
123 123 if order_by in ['last_activity']:
124 124 sort_col = coalesce(sort_col, datetime.date.max)
125 125 sort_col = sort_col.asc()
126 126 else:
127 127 # handle null values properly to order by NULL last
128 128 if order_by in ['last_activity']:
129 129 sort_col = coalesce(sort_col, datetime.date.min)
130 130 sort_col = sort_col.desc()
131 131
132 132 base_q = base_q.order_by(sort_col)
133 133 base_q = base_q.offset(start).limit(limit)
134 134
135 135 fork_list = base_q.all()
136 136
137 137 def fork_actions(fork):
138 138 url_link = h.route_path(
139 139 'repo_compare',
140 140 repo_name=fork.repo_name,
141 source_ref_type=self.db_repo.landing_rev[0],
142 source_ref=self.db_repo.landing_rev[1],
143 target_ref_type=self.db_repo.landing_rev[0],
144 target_ref=self.db_repo.landing_rev[1],
141 source_ref_type=self.db_repo.landing_ref_type,
142 source_ref=self.db_repo.landing_ref_name,
143 target_ref_type=self.db_repo.landing_ref_type,
144 target_ref=self.db_repo.landing_ref_name,
145 145 _query=dict(merge=1, target_repo=f.repo_name))
146 146 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
147 147
148 148 def fork_name(fork):
149 149 return h.link_to(fork.repo_name,
150 150 h.route_path('repo_summary', repo_name=fork.repo_name))
151 151
152 152 forks_data = []
153 153 for fork in fork_list:
154 154 forks_data.append({
155 155 "username": h.gravatar_with_user(self.request, fork.user.username),
156 156 "fork_name": fork_name(fork),
157 157 "description": fork.description_safe,
158 158 "fork_date": h.age_component(fork.created_on, time_is_local=True),
159 159 "last_activity": h.format_date(fork.updated_on),
160 160 "action": fork_actions(fork),
161 161 })
162 162
163 163 data = ({
164 164 'draw': draw,
165 165 'data': forks_data,
166 166 'recordsTotal': forks_data_total_count,
167 167 'recordsFiltered': forks_data_total_filtered_count,
168 168 })
169 169
170 170 return data
171 171
172 172 @LoginRequired()
173 173 @NotAnonymous()
174 174 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
175 175 @HasRepoPermissionAnyDecorator(
176 176 'repository.read', 'repository.write', 'repository.admin')
177 177 @view_config(
178 178 route_name='repo_fork_new', request_method='GET',
179 179 renderer='rhodecode:templates/forks/forks.mako')
180 180 def repo_fork_new(self):
181 181 c = self.load_default_context()
182 182
183 183 defaults = RepoModel()._get_defaults(self.db_repo_name)
184 184 # alter the description to indicate a fork
185 185 defaults['description'] = (
186 186 'fork of repository: %s \n%s' % (
187 187 defaults['repo_name'], defaults['description']))
188 188 # add suffix to fork
189 189 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
190 190
191 191 data = render('rhodecode:templates/forks/fork.mako',
192 192 self._get_template_context(c), self.request)
193 193 html = formencode.htmlfill.render(
194 194 data,
195 195 defaults=defaults,
196 196 encoding="UTF-8",
197 197 force_defaults=False
198 198 )
199 199 return Response(html)
200 200
201 201 @LoginRequired()
202 202 @NotAnonymous()
203 203 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
204 204 @HasRepoPermissionAnyDecorator(
205 205 'repository.read', 'repository.write', 'repository.admin')
206 206 @CSRFRequired()
207 207 @view_config(
208 208 route_name='repo_fork_create', request_method='POST',
209 209 renderer='rhodecode:templates/forks/fork.mako')
210 210 def repo_fork_create(self):
211 211 _ = self.request.translate
212 212 c = self.load_default_context()
213 213
214 214 _form = RepoForkForm(self.request.translate,
215 215 old_data={'repo_type': self.db_repo.repo_type},
216 216 repo_groups=c.repo_groups_choices)()
217 217 post_data = dict(self.request.POST)
218 218
219 219 # forbid injecting other repo by forging a request
220 220 post_data['fork_parent_id'] = self.db_repo.repo_id
221 221 post_data['landing_rev'] = self.db_repo._landing_revision
222 222
223 223 form_result = {}
224 224 task_id = None
225 225 try:
226 226 form_result = _form.to_python(post_data)
227 227 copy_permissions = form_result.get('copy_permissions')
228 228 # create fork is done sometimes async on celery, db transaction
229 229 # management is handled there.
230 230 task = RepoModel().create_fork(
231 231 form_result, c.rhodecode_user.user_id)
232 232
233 233 task_id = get_task_id(task)
234 234 except formencode.Invalid as errors:
235 235 c.rhodecode_db_repo = self.db_repo
236 236
237 237 data = render('rhodecode:templates/forks/fork.mako',
238 238 self._get_template_context(c), self.request)
239 239 html = formencode.htmlfill.render(
240 240 data,
241 241 defaults=errors.value,
242 242 errors=errors.error_dict or {},
243 243 prefix_error=False,
244 244 encoding="UTF-8",
245 245 force_defaults=False
246 246 )
247 247 return Response(html)
248 248 except Exception:
249 249 log.exception(
250 250 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 251 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 252 h.flash(msg, category='error')
253 253 raise HTTPFound(h.route_path('home'))
254 254
255 255 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256 256
257 257 affected_user_ids = [self._rhodecode_user.user_id]
258 258 if copy_permissions:
259 259 # permission flush is done in repo creating
260 260 pass
261 261
262 262 PermissionModel().trigger_permission_flush(affected_user_ids)
263 263
264 264 raise HTTPFound(
265 265 h.route_path('repo_creating', repo_name=repo_name,
266 266 _query=dict(task_id=task_id)))
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1201 +1,1201 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%!
4 4 ## base64 filter e.g ${ example | base64 }
5 5 def base64(text):
6 6 import base64
7 7 from rhodecode.lib.helpers import safe_str
8 8 return base64.encodestring(safe_str(text))
9 9 %>
10 10
11 11 <%inherit file="root.mako"/>
12 12
13 13 <%include file="/ejs_templates/templates.html"/>
14 14
15 15 <div class="outerwrapper">
16 16 <!-- HEADER -->
17 17 <div class="header">
18 18 <div id="header-inner" class="wrapper">
19 19 <div id="logo">
20 20 <div class="logo-wrapper">
21 21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
22 22 </div>
23 23 % if c.rhodecode_name:
24 24 <div class="branding">
25 25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
26 26 </div>
27 27 % endif
28 28 </div>
29 29 <!-- MENU BAR NAV -->
30 30 ${self.menu_bar_nav()}
31 31 <!-- END MENU BAR NAV -->
32 32 </div>
33 33 </div>
34 34 ${self.menu_bar_subnav()}
35 35 <!-- END HEADER -->
36 36
37 37 <!-- CONTENT -->
38 38 <div id="content" class="wrapper">
39 39
40 40 <rhodecode-toast id="notifications"></rhodecode-toast>
41 41
42 42 <div class="main">
43 43 ${next.main()}
44 44 </div>
45 45 </div>
46 46 <!-- END CONTENT -->
47 47
48 48 </div>
49 49 <!-- FOOTER -->
50 50 <div id="footer">
51 51 <div id="footer-inner" class="title wrapper">
52 52 <div>
53 53 <p class="footer-link-right">
54 54 % if c.visual.show_version:
55 55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
56 56 % endif
57 57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
58 58 % if c.visual.rhodecode_support_url:
59 59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
60 60 % endif
61 61 </p>
62 62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
63 63 <p class="server-instance" style="display:${sid}">
64 64 ## display hidden instance ID if specially defined
65 65 % if c.rhodecode_instanceid:
66 66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
67 67 % endif
68 68 </p>
69 69 </div>
70 70 </div>
71 71 </div>
72 72
73 73 <!-- END FOOTER -->
74 74
75 75 ### MAKO DEFS ###
76 76
77 77 <%def name="menu_bar_subnav()">
78 78 </%def>
79 79
80 80 <%def name="breadcrumbs(class_='breadcrumbs')">
81 81 <div class="${class_}">
82 82 ${self.breadcrumbs_links()}
83 83 </div>
84 84 </%def>
85 85
86 86 <%def name="admin_menu(active=None)">
87 87
88 88 <div id="context-bar">
89 89 <div class="wrapper">
90 90 <div class="title">
91 91 <div class="title-content">
92 92 <div class="title-main">
93 93 % if c.is_super_admin:
94 94 ${_('Super-admin Panel')}
95 95 % else:
96 96 ${_('Delegated Admin Panel')}
97 97 % endif
98 98 </div>
99 99 </div>
100 100 </div>
101 101
102 102 <ul id="context-pages" class="navigation horizontal-list">
103 103
104 104 ## super-admin case
105 105 % if c.is_super_admin:
106 106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
107 107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
108 108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
109 109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
110 110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
111 111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
112 112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
113 113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
114 114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
115 115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
116 116
117 117 ## delegated admin
118 118 % elif c.is_delegated_admin:
119 119 <%
120 120 repositories=c.auth_user.repositories_admin or c.can_create_repo
121 121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
122 122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
123 123 %>
124 124
125 125 %if repositories:
126 126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
127 127 %endif
128 128 %if repository_groups:
129 129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
130 130 %endif
131 131 %if user_groups:
132 132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
133 133 %endif
134 134 % endif
135 135 </ul>
136 136
137 137 </div>
138 138 <div class="clear"></div>
139 139 </div>
140 140 </%def>
141 141
142 142 <%def name="dt_info_panel(elements)">
143 143 <dl class="dl-horizontal">
144 144 %for dt, dd, title, show_items in elements:
145 145 <dt>${dt}:</dt>
146 146 <dd title="${h.tooltip(title)}">
147 147 %if callable(dd):
148 148 ## allow lazy evaluation of elements
149 149 ${dd()}
150 150 %else:
151 151 ${dd}
152 152 %endif
153 153 %if show_items:
154 154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
155 155 %endif
156 156 </dd>
157 157
158 158 %if show_items:
159 159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
160 160 %for item in show_items:
161 161 <dt></dt>
162 162 <dd>${item}</dd>
163 163 %endfor
164 164 </div>
165 165 %endif
166 166
167 167 %endfor
168 168 </dl>
169 169 </%def>
170 170
171 171 <%def name="tr_info_entry(element)">
172 172 <% key, val, title, show_items = element %>
173 173
174 174 <tr>
175 175 <td style="vertical-align: top">${key}</td>
176 176 <td title="${h.tooltip(title)}">
177 177 %if callable(val):
178 178 ## allow lazy evaluation of elements
179 179 ${val()}
180 180 %else:
181 181 ${val}
182 182 %endif
183 183 %if show_items:
184 184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
185 185 % for item in show_items:
186 186 <dt></dt>
187 187 <dd>${item}</dd>
188 188 % endfor
189 189 </div>
190 190 %endif
191 191 </td>
192 192 <td style="vertical-align: top">
193 193 %if show_items:
194 194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
195 195 %endif
196 196 </td>
197 197 </tr>
198 198
199 199 </%def>
200 200
201 201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
202 202 <%
203 203 if size > 16:
204 204 gravatar_class = ['gravatar','gravatar-large']
205 205 else:
206 206 gravatar_class = ['gravatar']
207 207
208 208 data_hovercard_url = ''
209 209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
210 210
211 211 if tooltip:
212 212 gravatar_class += ['tooltip-hovercard']
213 213 if extra_class:
214 214 gravatar_class += extra_class
215 215 if tooltip and user:
216 216 if user.username == h.DEFAULT_USER:
217 217 gravatar_class.pop(-1)
218 218 else:
219 219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
220 220 gravatar_class = ' '.join(gravatar_class)
221 221
222 222 %>
223 223 <%doc>
224 224 TODO: johbo: For now we serve double size images to make it smooth
225 225 for retina. This is how it worked until now. Should be replaced
226 226 with a better solution at some point.
227 227 </%doc>
228 228
229 229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
230 230 </%def>
231 231
232 232
233 233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
234 234 <%
235 235 email = h.email_or_none(contact)
236 236 rc_user = h.discover_user(contact)
237 237 %>
238 238
239 239 <div class="${_class}">
240 240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
241 241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
242 242 </div>
243 243 </%def>
244 244
245 245
246 246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
247 247 <%
248 248 if (size > 16):
249 249 gravatar_class = 'icon-user-group-alt'
250 250 else:
251 251 gravatar_class = 'icon-user-group-alt'
252 252
253 253 if tooltip:
254 254 gravatar_class += ' tooltip-hovercard'
255 255
256 256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
257 257 %>
258 258 <%doc>
259 259 TODO: johbo: For now we serve double size images to make it smooth
260 260 for retina. This is how it worked until now. Should be replaced
261 261 with a better solution at some point.
262 262 </%doc>
263 263
264 264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
265 265 </%def>
266 266
267 267 <%def name="repo_page_title(repo_instance)">
268 268 <div class="title-content repo-title">
269 269
270 270 <div class="title-main">
271 271 ## SVN/HG/GIT icons
272 272 %if h.is_hg(repo_instance):
273 273 <i class="icon-hg"></i>
274 274 %endif
275 275 %if h.is_git(repo_instance):
276 276 <i class="icon-git"></i>
277 277 %endif
278 278 %if h.is_svn(repo_instance):
279 279 <i class="icon-svn"></i>
280 280 %endif
281 281
282 282 ## public/private
283 283 %if repo_instance.private:
284 284 <i class="icon-repo-private"></i>
285 285 %else:
286 286 <i class="icon-repo-public"></i>
287 287 %endif
288 288
289 289 ## repo name with group name
290 290 ${h.breadcrumb_repo_link(repo_instance)}
291 291
292 292 ## Context Actions
293 293 <div class="pull-right">
294 294 %if c.rhodecode_user.username != h.DEFAULT_USER:
295 295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
296 296
297 297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
298 298 % if c.repository_is_user_following:
299 299 <i class="icon-eye-off"></i>${_('Unwatch')}
300 300 % else:
301 301 <i class="icon-eye"></i>${_('Watch')}
302 302 % endif
303 303
304 304 </a>
305 305 %else:
306 306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
307 307 %endif
308 308 </div>
309 309
310 310 </div>
311 311
312 312 ## FORKED
313 313 %if repo_instance.fork:
314 314 <p class="discreet">
315 315 <i class="icon-code-fork"></i> ${_('Fork of')}
316 316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
317 317 </p>
318 318 %endif
319 319
320 320 ## IMPORTED FROM REMOTE
321 321 %if repo_instance.clone_uri:
322 322 <p class="discreet">
323 323 <i class="icon-code-fork"></i> ${_('Clone from')}
324 324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
325 325 </p>
326 326 %endif
327 327
328 328 ## LOCKING STATUS
329 329 %if repo_instance.locked[0]:
330 330 <p class="locking_locked discreet">
331 331 <i class="icon-repo-lock"></i>
332 332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
333 333 </p>
334 334 %elif repo_instance.enable_locking:
335 335 <p class="locking_unlocked discreet">
336 336 <i class="icon-repo-unlock"></i>
337 337 ${_('Repository not locked. Pull repository to lock it.')}
338 338 </p>
339 339 %endif
340 340
341 341 </div>
342 342 </%def>
343 343
344 344 <%def name="repo_menu(active=None)">
345 345 <%
346 346 ## determine if we have "any" option available
347 347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
348 348 has_actions = can_lock
349 349
350 350 %>
351 351 % if c.rhodecode_db_repo.archived:
352 352 <div class="alert alert-warning text-center">
353 353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
354 354 </div>
355 355 % endif
356 356
357 357 <!--- REPO CONTEXT BAR -->
358 358 <div id="context-bar">
359 359 <div class="wrapper">
360 360
361 361 <div class="title">
362 362 ${self.repo_page_title(c.rhodecode_db_repo)}
363 363 </div>
364 364
365 365 <ul id="context-pages" class="navigation horizontal-list">
366 366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
367 367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_ref_name, f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
369 369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
370 370
371 371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
372 372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
373 373 <li class="${h.is_active('showpullrequest', active)}">
374 374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
375 375 <div class="menulabel">
376 376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
377 377 </div>
378 378 </a>
379 379 </li>
380 380 %endif
381 381
382 382 <li class="${h.is_active('artifacts', active)}">
383 383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
384 384 <div class="menulabel">
385 385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
386 386 </div>
387 387 </a>
388 388 </li>
389 389
390 390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
391 391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
392 392 %endif
393 393
394 394 <li class="${h.is_active('options', active)}">
395 395 % if has_actions:
396 396 <a class="menulink dropdown">
397 397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
398 398 </a>
399 399 <ul class="submenu">
400 400 %if can_lock:
401 401 %if c.rhodecode_db_repo.locked[0]:
402 402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
403 403 %else:
404 404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
405 405 %endif
406 406 %endif
407 407 </ul>
408 408 % endif
409 409 </li>
410 410
411 411 </ul>
412 412 </div>
413 413 <div class="clear"></div>
414 414 </div>
415 415
416 416 <!--- REPO END CONTEXT BAR -->
417 417
418 418 </%def>
419 419
420 420 <%def name="repo_group_page_title(repo_group_instance)">
421 421 <div class="title-content">
422 422 <div class="title-main">
423 423 ## Repository Group icon
424 424 <i class="icon-repo-group"></i>
425 425
426 426 ## repo name with group name
427 427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
428 428 </div>
429 429
430 430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
431 431 <div class="repo-group-desc discreet">
432 432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
433 433 </div>
434 434
435 435 </div>
436 436 </%def>
437 437
438 438
439 439 <%def name="repo_group_menu(active=None)">
440 440 <%
441 441 gr_name = c.repo_group.group_name if c.repo_group else None
442 442 # create repositories with write permission on group is set to true
443 443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
444 444
445 445 %>
446 446
447 447
448 448 <!--- REPO GROUP CONTEXT BAR -->
449 449 <div id="context-bar">
450 450 <div class="wrapper">
451 451 <div class="title">
452 452 ${self.repo_group_page_title(c.repo_group)}
453 453 </div>
454 454
455 455 <ul id="context-pages" class="navigation horizontal-list">
456 456 <li class="${h.is_active('home', active)}">
457 457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
458 458 </li>
459 459 % if c.is_super_admin or group_admin:
460 460 <li class="${h.is_active('settings', active)}">
461 461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
462 462 </li>
463 463 % endif
464 464
465 465 </ul>
466 466 </div>
467 467 <div class="clear"></div>
468 468 </div>
469 469
470 470 <!--- REPO GROUP CONTEXT BAR -->
471 471
472 472 </%def>
473 473
474 474
475 475 <%def name="usermenu(active=False)">
476 476 <%
477 477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
478 478
479 479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
480 480 # create repositories with write permission on group is set to true
481 481
482 482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
483 483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
484 484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
485 485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
486 486
487 487 can_create_repos = c.is_super_admin or c.can_create_repo
488 488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
489 489
490 490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
491 491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
492 492 %>
493 493
494 494 % if not_anonymous:
495 495 <%
496 496 default_target_group = dict()
497 497 if c.rhodecode_user.personal_repo_group:
498 498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
499 499 %>
500 500
501 501 ## create action
502 502 <li>
503 503 <a href="#create-actions" onclick="return false;" class="menulink childs">
504 504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
505 505 </a>
506 506
507 507 <div class="action-menu submenu">
508 508
509 509 <ol>
510 510 ## scope of within a repository
511 511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
512 512 <li class="submenu-title">${_('This Repository')}</li>
513 513 <li>
514 514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
515 515 </li>
516 516 % if can_fork:
517 517 <li>
518 518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
519 519 </li>
520 520 % endif
521 521 % endif
522 522
523 523 ## scope of within repository groups
524 524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
525 525 <li class="submenu-title">${_('This Repository Group')}</li>
526 526
527 527 % if can_create_repos_in_group:
528 528 <li>
529 529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
530 530 </li>
531 531 % endif
532 532
533 533 % if can_create_repo_groups_in_group:
534 534 <li>
535 535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
536 536 </li>
537 537 % endif
538 538 % endif
539 539
540 540 ## personal group
541 541 % if c.rhodecode_user.personal_repo_group:
542 542 <li class="submenu-title">Personal Group</li>
543 543
544 544 <li>
545 545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
546 546 </li>
547 547
548 548 <li>
549 549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
550 550 </li>
551 551 % endif
552 552
553 553 ## Global actions
554 554 <li class="submenu-title">RhodeCode</li>
555 555 % if can_create_repos:
556 556 <li>
557 557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
558 558 </li>
559 559 % endif
560 560
561 561 % if can_create_repo_groups:
562 562 <li>
563 563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
564 564 </li>
565 565 % endif
566 566
567 567 <li>
568 568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
569 569 </li>
570 570
571 571 </ol>
572 572
573 573 </div>
574 574 </li>
575 575
576 576 ## notifications
577 577 <li>
578 578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
579 579 ${c.unread_notifications}
580 580 </a>
581 581 </li>
582 582 % endif
583 583
584 584 ## USER MENU
585 585 <li id="quick_login_li" class="${'active' if active else ''}">
586 586 % if c.rhodecode_user.username == h.DEFAULT_USER:
587 587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
588 588 ${gravatar(c.rhodecode_user.email, 20)}
589 589 <span class="user">
590 590 <span>${_('Sign in')}</span>
591 591 </span>
592 592 </a>
593 593 % else:
594 594 ## logged in user
595 595 <a id="quick_login_link" class="menulink childs">
596 596 ${gravatar(c.rhodecode_user.email, 20)}
597 597 <span class="user">
598 598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
599 599 <div class="show_more"></div>
600 600 </span>
601 601 </a>
602 602 ## subnav with menu for logged in user
603 603 <div class="user-menu submenu">
604 604 <div id="quick_login">
605 605 %if c.rhodecode_user.username != h.DEFAULT_USER:
606 606 <div class="">
607 607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
608 608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
609 609 <div class="email">${c.rhodecode_user.email}</div>
610 610 </div>
611 611 <div class="">
612 612 <ol class="links">
613 613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
614 614 % if c.rhodecode_user.personal_repo_group:
615 615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
616 616 % endif
617 617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
618 618
619 619 % if c.debug_style:
620 620 <li>
621 621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
622 622 <div class="menulabel">${_('[Style]')}</div>
623 623 </a>
624 624 </li>
625 625 % endif
626 626
627 627 ## bookmark-items
628 628 <li class="bookmark-items">
629 629 ${_('Bookmarks')}
630 630 <div class="pull-right">
631 631 <a href="${h.route_path('my_account_bookmarks')}">
632 632
633 633 <i class="icon-cog"></i>
634 634 </a>
635 635 </div>
636 636 </li>
637 637 % if not c.bookmark_items:
638 638 <li>
639 639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
640 640 </li>
641 641 % endif
642 642 % for item in c.bookmark_items:
643 643 <li>
644 644 % if item.repository:
645 645 <div>
646 646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
647 647 <code>${item.position}</code>
648 648 % if item.repository.repo_type == 'hg':
649 649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
650 650 % elif item.repository.repo_type == 'git':
651 651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
652 652 % elif item.repository.repo_type == 'svn':
653 653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
654 654 % endif
655 655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
656 656 </a>
657 657 </div>
658 658 % elif item.repository_group:
659 659 <div>
660 660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
661 661 <code>${item.position}</code>
662 662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
663 663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
664 664 </a>
665 665 </div>
666 666 % else:
667 667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
668 668 <code>${item.position}</code>
669 669 ${item.title}
670 670 </a>
671 671 % endif
672 672 </li>
673 673 % endfor
674 674
675 675 <li class="logout">
676 676 ${h.secure_form(h.route_path('logout'), request=request)}
677 677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
678 678 ${h.end_form()}
679 679 </li>
680 680 </ol>
681 681 </div>
682 682 %endif
683 683 </div>
684 684 </div>
685 685
686 686 % endif
687 687 </li>
688 688 </%def>
689 689
690 690 <%def name="menu_items(active=None)">
691 691 <%
692 692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 694 %>
695 695 <style>
696 696
697 697 </style>
698 698
699 699 <ul id="quick" class="main_nav navigation horizontal-list">
700 700 ## notice box for important system messages
701 701 <li style="display: ${notice_display}">
702 702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
703 703 <div class="menulabel-notice ${notice_level}" >
704 704 ${len(notice_messages)}
705 705 </div>
706 706 </a>
707 707 </li>
708 708 <div class="notice-messages-container" style="display: none">
709 709 <div class="notice-messages">
710 710 <table class="rctable">
711 711 % for notice in notice_messages:
712 712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 713 <td style="vertical-align: text-top; width: 20px">
714 714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 715 </td>
716 716 <td>
717 717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 718 ${notice['subject']}
719 719
720 720 <div id="notice-${notice['msg_id']}" style="display: none">
721 721 ${h.render(notice['body'], renderer='markdown')}
722 722 </div>
723 723 </td>
724 724 <td style="vertical-align: text-top; width: 35px;">
725 725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 726 <i class="icon-remove icon-filled-red"></i>
727 727 </a>
728 728 </td>
729 729 </tr>
730 730
731 731 % endfor
732 732 </table>
733 733 </div>
734 734 </div>
735 735 ## Main filter
736 736 <li>
737 737 <div class="menulabel main_filter_box">
738 738 <div class="main_filter_input_box">
739 739 <ul class="searchItems">
740 740
741 741 <li class="searchTag searchTagIcon">
742 742 <i class="icon-search"></i>
743 743 </li>
744 744
745 745 % if c.template_context['search_context']['repo_id']:
746 746 <li class="searchTag searchTagFilter searchTagHidable" >
747 747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
748 748 <span class="tag">
749 749 This repo
750 750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
751 751 </span>
752 752 ##</a>
753 753 </li>
754 754 % elif c.template_context['search_context']['repo_group_id']:
755 755 <li class="searchTag searchTagFilter searchTagHidable">
756 756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
757 757 <span class="tag">
758 758 This group
759 759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
760 760 </span>
761 761 ##</a>
762 762 </li>
763 763 % endif
764 764
765 765 <li class="searchTagInput">
766 766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
767 767 </li>
768 768 <li class="searchTag searchTagHelp">
769 769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
770 770 </li>
771 771 </ul>
772 772 </div>
773 773 </div>
774 774
775 775 <div id="main_filter_help" style="display: none">
776 776 - Use '/' key to quickly access this field.
777 777
778 778 - Enter a name of repository, or repository group for quick search.
779 779
780 780 - Prefix query to allow special search:
781 781
782 782 user:admin, to search for usernames, always global
783 783
784 784 user_group:devops, to search for user groups, always global
785 785
786 786 pr:303, to search for pull request number, title, or description, always global
787 787
788 788 commit:efced4, to search for commits, scoped to repositories or groups
789 789
790 790 file:models.py, to search for file paths, scoped to repositories or groups
791 791
792 792 % if c.template_context['search_context']['repo_id']:
793 793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
794 794 % elif c.template_context['search_context']['repo_group_id']:
795 795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
796 796 % else:
797 797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
798 798 % endif
799 799 </div>
800 800 </li>
801 801
802 802 ## ROOT MENU
803 803 <li class="${h.is_active('home', active)}">
804 804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
805 805 <div class="menulabel">${_('Home')}</div>
806 806 </a>
807 807 </li>
808 808
809 809 %if c.rhodecode_user.username != h.DEFAULT_USER:
810 810 <li class="${h.is_active('journal', active)}">
811 811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
812 812 <div class="menulabel">${_('Journal')}</div>
813 813 </a>
814 814 </li>
815 815 %else:
816 816 <li class="${h.is_active('journal', active)}">
817 817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
818 818 <div class="menulabel">${_('Public journal')}</div>
819 819 </a>
820 820 </li>
821 821 %endif
822 822
823 823 <li class="${h.is_active('gists', active)}">
824 824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
825 825 <div class="menulabel">${_('Gists')}</div>
826 826 </a>
827 827 </li>
828 828
829 829 % if c.is_super_admin or c.is_delegated_admin:
830 830 <li class="${h.is_active('admin', active)}">
831 831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
832 832 <div class="menulabel">${_('Admin')} </div>
833 833 </a>
834 834 </li>
835 835 % endif
836 836
837 837 ## render extra user menu
838 838 ${usermenu(active=(active=='my_account'))}
839 839
840 840 </ul>
841 841
842 842 <script type="text/javascript">
843 843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
844 844
845 845 var formatRepoResult = function(result, container, query, escapeMarkup) {
846 846 return function(data, escapeMarkup) {
847 847 if (!data.repo_id){
848 848 return data.text; // optgroup text Repositories
849 849 }
850 850
851 851 var tmpl = '';
852 852 var repoType = data['repo_type'];
853 853 var repoName = data['text'];
854 854
855 855 if(data && data.type == 'repo'){
856 856 if(repoType === 'hg'){
857 857 tmpl += '<i class="icon-hg"></i> ';
858 858 }
859 859 else if(repoType === 'git'){
860 860 tmpl += '<i class="icon-git"></i> ';
861 861 }
862 862 else if(repoType === 'svn'){
863 863 tmpl += '<i class="icon-svn"></i> ';
864 864 }
865 865 if(data['private']){
866 866 tmpl += '<i class="icon-lock" ></i> ';
867 867 }
868 868 else if(visualShowPublicIcon){
869 869 tmpl += '<i class="icon-unlock-alt"></i> ';
870 870 }
871 871 }
872 872 tmpl += escapeMarkup(repoName);
873 873 return tmpl;
874 874
875 875 }(result, escapeMarkup);
876 876 };
877 877
878 878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
879 879 return function(data, escapeMarkup) {
880 880 if (!data.repo_group_id){
881 881 return data.text; // optgroup text Repositories
882 882 }
883 883
884 884 var tmpl = '';
885 885 var repoGroupName = data['text'];
886 886
887 887 if(data){
888 888
889 889 tmpl += '<i class="icon-repo-group"></i> ';
890 890
891 891 }
892 892 tmpl += escapeMarkup(repoGroupName);
893 893 return tmpl;
894 894
895 895 }(result, escapeMarkup);
896 896 };
897 897
898 898 var escapeRegExChars = function (value) {
899 899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
900 900 };
901 901
902 902 var getRepoIcon = function(repo_type) {
903 903 if (repo_type === 'hg') {
904 904 return '<i class="icon-hg"></i> ';
905 905 }
906 906 else if (repo_type === 'git') {
907 907 return '<i class="icon-git"></i> ';
908 908 }
909 909 else if (repo_type === 'svn') {
910 910 return '<i class="icon-svn"></i> ';
911 911 }
912 912 return ''
913 913 };
914 914
915 915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
916 916
917 917 if (value.split(':').length === 2) {
918 918 value = value.split(':')[1]
919 919 }
920 920
921 921 var searchType = data['type'];
922 922 var searchSubType = data['subtype'];
923 923 var valueDisplay = data['value_display'];
924 924 var valueIcon = data['value_icon'];
925 925
926 926 var pattern = '(' + escapeRegExChars(value) + ')';
927 927
928 928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
929 929
930 930 // highlight match
931 931 if (searchType != 'text') {
932 932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
933 933 }
934 934
935 935 var icon = '';
936 936
937 937 if (searchType === 'hint') {
938 938 icon += '<i class="icon-repo-group"></i> ';
939 939 }
940 940 // full text search/hints
941 941 else if (searchType === 'search') {
942 942 if (valueIcon === undefined) {
943 943 icon += '<i class="icon-more"></i> ';
944 944 } else {
945 945 icon += valueIcon + ' ';
946 946 }
947 947
948 948 if (searchSubType !== undefined && searchSubType == 'repo') {
949 949 valueDisplay += '<div class="pull-right tag">repository</div>';
950 950 }
951 951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
952 952 valueDisplay += '<div class="pull-right tag">repo group</div>';
953 953 }
954 954 }
955 955 // repository
956 956 else if (searchType === 'repo') {
957 957
958 958 var repoIcon = getRepoIcon(data['repo_type']);
959 959 icon += repoIcon;
960 960
961 961 if (data['private']) {
962 962 icon += '<i class="icon-lock" ></i> ';
963 963 }
964 964 else if (visualShowPublicIcon) {
965 965 icon += '<i class="icon-unlock-alt"></i> ';
966 966 }
967 967 }
968 968 // repository groups
969 969 else if (searchType === 'repo_group') {
970 970 icon += '<i class="icon-repo-group"></i> ';
971 971 }
972 972 // user group
973 973 else if (searchType === 'user_group') {
974 974 icon += '<i class="icon-group"></i> ';
975 975 }
976 976 // user
977 977 else if (searchType === 'user') {
978 978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
979 979 }
980 980 // pull request
981 981 else if (searchType === 'pull_request') {
982 982 icon += '<i class="icon-merge"></i> ';
983 983 }
984 984 // commit
985 985 else if (searchType === 'commit') {
986 986 var repo_data = data['repo_data'];
987 987 var repoIcon = getRepoIcon(repo_data['repository_type']);
988 988 if (repoIcon) {
989 989 icon += repoIcon;
990 990 } else {
991 991 icon += '<i class="icon-tag"></i>';
992 992 }
993 993 }
994 994 // file
995 995 else if (searchType === 'file') {
996 996 var repo_data = data['repo_data'];
997 997 var repoIcon = getRepoIcon(repo_data['repository_type']);
998 998 if (repoIcon) {
999 999 icon += repoIcon;
1000 1000 } else {
1001 1001 icon += '<i class="icon-tag"></i>';
1002 1002 }
1003 1003 }
1004 1004 // generic text
1005 1005 else if (searchType === 'text') {
1006 1006 icon = '';
1007 1007 }
1008 1008
1009 1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1010 1010 return tmpl.format(icon, valueDisplay);
1011 1011 };
1012 1012
1013 1013 var handleSelect = function(element, suggestion) {
1014 1014 if (suggestion.type === "hint") {
1015 1015 // we skip action
1016 1016 $('#main_filter').focus();
1017 1017 }
1018 1018 else if (suggestion.type === "text") {
1019 1019 // we skip action
1020 1020 $('#main_filter').focus();
1021 1021
1022 1022 } else {
1023 1023 window.location = suggestion['url'];
1024 1024 }
1025 1025 };
1026 1026
1027 1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1028 1028 if (queryLowerCase.split(':').length === 2) {
1029 1029 queryLowerCase = queryLowerCase.split(':')[1]
1030 1030 }
1031 1031 if (suggestion.type === "text") {
1032 1032 // special case we don't want to "skip" display for
1033 1033 return true
1034 1034 }
1035 1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1036 1036 };
1037 1037
1038 1038 var cleanContext = {
1039 1039 repo_view_type: null,
1040 1040
1041 1041 repo_id: null,
1042 1042 repo_name: "",
1043 1043
1044 1044 repo_group_id: null,
1045 1045 repo_group_name: null
1046 1046 };
1047 1047 var removeGoToFilter = function () {
1048 1048 $('.searchTagHidable').hide();
1049 1049 $('#main_filter').autocomplete(
1050 1050 'setOptions', {params:{search_context: cleanContext}});
1051 1051 };
1052 1052
1053 1053 $('#main_filter').autocomplete({
1054 1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1055 1055 params: {
1056 1056 "search_context": templateContext.search_context
1057 1057 },
1058 1058 minChars:2,
1059 1059 maxHeight:400,
1060 1060 deferRequestBy: 300, //miliseconds
1061 1061 tabDisabled: true,
1062 1062 autoSelectFirst: false,
1063 1063 containerClass: 'autocomplete-qfilter-suggestions',
1064 1064 formatResult: autocompleteMainFilterFormatResult,
1065 1065 lookupFilter: autocompleteMainFilterResult,
1066 1066 onSelect: function (element, suggestion) {
1067 1067 handleSelect(element, suggestion);
1068 1068 return false;
1069 1069 },
1070 1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1071 1071 if (jqXHR !== 'abort') {
1072 1072 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1073 1073 SwalNoAnimation.fire({
1074 1074 icon: 'error',
1075 1075 title: _gettext('Error during search operation'),
1076 1076 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1077 1077 }).then(function(result) {
1078 1078 window.location.reload();
1079 1079 })
1080 1080 }
1081 1081 },
1082 1082 onSearchStart: function (params) {
1083 1083 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1084 1084 },
1085 1085 onSearchComplete: function (query, suggestions) {
1086 1086 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1087 1087 },
1088 1088 });
1089 1089
1090 1090 showMainFilterBox = function () {
1091 1091 $('#main_filter_help').toggle();
1092 1092 };
1093 1093
1094 1094 $('#main_filter').on('keydown.autocomplete', function (e) {
1095 1095
1096 1096 var BACKSPACE = 8;
1097 1097 var el = $(e.currentTarget);
1098 1098 if(e.which === BACKSPACE){
1099 1099 var inputVal = el.val();
1100 1100 if (inputVal === ""){
1101 1101 removeGoToFilter()
1102 1102 }
1103 1103 }
1104 1104 });
1105 1105
1106 1106 var dismissNotice = function(noticeId) {
1107 1107
1108 1108 var url = pyroutes.url('user_notice_dismiss',
1109 1109 {"user_id": templateContext.rhodecode_user.user_id});
1110 1110
1111 1111 var postData = {
1112 1112 'csrf_token': CSRF_TOKEN,
1113 1113 'notice_id': noticeId,
1114 1114 };
1115 1115
1116 1116 var success = function(response) {
1117 1117 $('#notice-message-' + noticeId).remove();
1118 1118 return false;
1119 1119 };
1120 1120 var failure = function(data, textStatus, xhr) {
1121 1121 alert("error processing request: " + textStatus);
1122 1122 return false;
1123 1123 };
1124 1124 ajaxPOST(url, postData, success, failure);
1125 1125 }
1126 1126 </script>
1127 1127 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1128 1128 </%def>
1129 1129
1130 1130 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1131 1131 <div class="modal-dialog">
1132 1132 <div class="modal-content">
1133 1133 <div class="modal-header">
1134 1134 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1135 1135 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1136 1136 </div>
1137 1137 <div class="modal-body">
1138 1138 <div class="block-left">
1139 1139 <table class="keyboard-mappings">
1140 1140 <tbody>
1141 1141 <tr>
1142 1142 <th></th>
1143 1143 <th>${_('Site-wide shortcuts')}</th>
1144 1144 </tr>
1145 1145 <%
1146 1146 elems = [
1147 1147 ('/', 'Use quick search box'),
1148 1148 ('g h', 'Goto home page'),
1149 1149 ('g g', 'Goto my private gists page'),
1150 1150 ('g G', 'Goto my public gists page'),
1151 1151 ('g 0-9', 'Goto bookmarked items from 0-9'),
1152 1152 ('n r', 'New repository page'),
1153 1153 ('n g', 'New gist page'),
1154 1154 ]
1155 1155 %>
1156 1156 %for key, desc in elems:
1157 1157 <tr>
1158 1158 <td class="keys">
1159 1159 <span class="key tag">${key}</span>
1160 1160 </td>
1161 1161 <td>${desc}</td>
1162 1162 </tr>
1163 1163 %endfor
1164 1164 </tbody>
1165 1165 </table>
1166 1166 </div>
1167 1167 <div class="block-left">
1168 1168 <table class="keyboard-mappings">
1169 1169 <tbody>
1170 1170 <tr>
1171 1171 <th></th>
1172 1172 <th>${_('Repositories')}</th>
1173 1173 </tr>
1174 1174 <%
1175 1175 elems = [
1176 1176 ('g s', 'Goto summary page'),
1177 1177 ('g c', 'Goto changelog page'),
1178 1178 ('g f', 'Goto files page'),
1179 1179 ('g F', 'Goto files page with file search activated'),
1180 1180 ('g p', 'Goto pull requests page'),
1181 1181 ('g o', 'Goto repository settings'),
1182 1182 ('g O', 'Goto repository access permissions settings'),
1183 1183 ]
1184 1184 %>
1185 1185 %for key, desc in elems:
1186 1186 <tr>
1187 1187 <td class="keys">
1188 1188 <span class="key tag">${key}</span>
1189 1189 </td>
1190 1190 <td>${desc}</td>
1191 1191 </tr>
1192 1192 %endfor
1193 1193 </tbody>
1194 1194 </table>
1195 1195 </div>
1196 1196 </div>
1197 1197 <div class="modal-footer">
1198 1198 </div>
1199 1199 </div><!-- /.modal-content -->
1200 1200 </div><!-- /.modal-dialog -->
1201 1201 </div><!-- /.modal -->
@@ -1,166 +1,166 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3
4 4 <%
5 5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 6 go_import_header = ''
7 7 if hasattr(c, 'rhodecode_db_repo'):
8 8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_ref_name
10 10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
11 11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
12 12
13 13 if getattr(c, 'repo_group', None):
14 14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 15 c.template_context['repo_group_name'] = c.repo_group.group_name
16 16
17 17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 18 c.template_context['rhodecode_user']['user_id'] = c.rhodecode_user.user_id
19 19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
20 20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
21 21 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
22 22 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
23 23 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
24 24
25 25 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
26 26 c.template_context['default_user'] = {
27 27 'username': h.DEFAULT_USER,
28 28 'user_id': 1
29 29 }
30 30 c.template_context['search_context'] = {
31 31 'repo_group_id': c.template_context.get('repo_group_id'),
32 32 'repo_group_name': c.template_context.get('repo_group_name'),
33 33 'repo_id': c.template_context.get('repo_id'),
34 34 'repo_name': c.template_context.get('repo_name'),
35 35 'repo_view_type': c.template_context.get('repo_view_type'),
36 36 }
37 37
38 38 c.template_context['attachment_store'] = {
39 39 'max_file_size_mb': 10,
40 40 'image_ext': ["png", "jpg", "gif", "jpeg"]
41 41 }
42 42
43 43 %>
44 44 <html xmlns="http://www.w3.org/1999/xhtml">
45 45 <head>
46 46 <title>${self.title()}</title>
47 47 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
48 48
49 49 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
50 50
51 51 % if 'safari' in (request.user_agent or '').lower():
52 52 <meta name="referrer" content="origin">
53 53 % else:
54 54 <meta name="referrer" content="origin-when-cross-origin">
55 55 % endif
56 56
57 57 <%def name="robots()">
58 58 <meta name="robots" content="index, nofollow"/>
59 59 </%def>
60 60 ${self.robots()}
61 61 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
62 62 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
63 63 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
64 64
65 65 ## CSS definitions
66 66 <%def name="css()">
67 67 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
68 68 ## EXTRA FOR CSS
69 69 ${self.css_extra()}
70 70 </%def>
71 71 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
72 72 <%def name="css_extra()">
73 73 </%def>
74 74
75 75 ${self.css()}
76 76
77 77 ## JAVASCRIPT
78 78 <%def name="js()">
79 79
80 80 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
81 81 <script type="text/javascript">
82 82 // register templateContext to pass template variables to JS
83 83 var templateContext = ${h.json.dumps(c.template_context)|n};
84 84
85 85 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
86 86 var APPLICATION_PLUGINS = [];
87 87 var ASSET_URL = "${h.asset('')}";
88 88 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
89 89 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
90 90
91 91 var APPENLIGHT = {
92 92 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
93 93 key: '${getattr(c, "appenlight_api_public_key", "")}',
94 94 % if getattr(c, 'appenlight_server_url', None):
95 95 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
96 96 % endif
97 97 requestInfo: {
98 98 % if getattr(c, 'rhodecode_user', None):
99 99 ip: '${c.rhodecode_user.ip_addr}',
100 100 username: '${c.rhodecode_user.username}'
101 101 % endif
102 102 },
103 103 tags: {
104 104 rhodecode_version: '${c.rhodecode_version}',
105 105 rhodecode_edition: '${c.rhodecode_edition}'
106 106 }
107 107 };
108 108
109 109 </script>
110 110 <%include file="/base/plugins_base.mako"/>
111 111 <!--[if lt IE 9]>
112 112 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
113 113 <![endif]-->
114 114 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
115 115 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
116 116 ## avoide escaping the %N
117 117 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
118 118 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
119 119
120 120
121 121 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
122 122 ${self.js_extra()}
123 123
124 124 <script type="text/javascript">
125 125 Rhodecode = (function() {
126 126 function _Rhodecode() {
127 127 this.comments = new CommentsController();
128 128 }
129 129 return new _Rhodecode();
130 130 })();
131 131
132 132 $(document).ready(function(){
133 133 show_more_event();
134 134 timeagoActivate();
135 135 tooltipActivate();
136 136 clipboardActivate();
137 137 })
138 138 </script>
139 139
140 140 </%def>
141 141
142 142 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
143 143 <%def name="js_extra()"></%def>
144 144 ${self.js()}
145 145
146 146 <%def name="head_extra()"></%def>
147 147 ${self.head_extra()}
148 148 ## extra stuff
149 149 %if c.pre_code:
150 150 ${c.pre_code|n}
151 151 %endif
152 152 </head>
153 153 <body id="body">
154 154 <noscript>
155 155 <div class="noscript-error">
156 156 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
157 157 </div>
158 158 </noscript>
159 159
160 160 ${next.body()}
161 161 %if c.post_code:
162 162 ${c.post_code|n}
163 163 %endif
164 164 <rhodecode-app></rhodecode-app>
165 165 </body>
166 166 </html>
@@ -1,326 +1,326 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.mako"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name}
7 7 %if c.changelog_for_path:
8 8 /${c.changelog_for_path}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.changelog_for_path:
17 17 /${c.changelog_for_path}
18 18 %endif
19 19 </%def>
20 20
21 21 <%def name="menu_bar_nav()">
22 22 ${self.menu_items(active='repositories')}
23 23 </%def>
24 24
25 25 <%def name="menu_bar_subnav()">
26 26 ${self.repo_menu(active='commits')}
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30
31 31 <div class="box">
32 32
33 33 <div class="title">
34 34 <div id="filter_changelog">
35 35 ${h.hidden('branch_filter')}
36 36 %if c.selected_name:
37 37 <div class="btn btn-default" id="clear_filter" >
38 38 ${_('Clear filter')}
39 39 </div>
40 40 %endif
41 41 </div>
42 42 <div class="pull-left obsolete-toggle">
43 43 % if h.is_hg(c.rhodecode_repo):
44 44 % if c.show_hidden:
45 45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
46 46 % else:
47 47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
48 48 % endif
49 49 % else:
50 50 <span class="action-link disabled">${_('Show hidden')}</span>
51 51 % endif
52 52 </div>
53 53 <ul class="links">
54 54 <li>
55 55
56 56 %if c.rhodecode_db_repo.fork:
57 57 <span>
58 58 <a id="compare_fork_button"
59 59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
60 60 class="btn btn-small"
61 61 href="${h.route_path('repo_compare',
62 62 repo_name=c.rhodecode_db_repo.fork.repo_name,
63 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
64 source_ref=c.rhodecode_db_repo.landing_rev[1],
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
63 source_ref_type=c.rhodecode_db_repo.landing_ref_type,
64 source_ref=c.rhodecode_db_repo.landing_ref_name,
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_ref_type,
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_ref_name,
67 67 _query=dict(merge=1, target_repo=c.repo_name))}"
68 68 >
69 69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
70 70 </a>
71 71 </span>
72 72 %endif
73 73
74 74 ## pr open link
75 75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
76 76 <span>
77 77 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
78 78 ${_('Open new pull request')}
79 79 </a>
80 80 </span>
81 81 %endif
82 82
83 83 </li>
84 84 </ul>
85 85 </div>
86 86
87 87 % if c.pagination:
88 88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
89 89
90 90 <div class="graph-header">
91 91 ${self.breadcrumbs('breadcrumbs_light')}
92 92 </div>
93 93
94 94 <div id="graph">
95 95 <div class="graph-col-wrapper">
96 96 <div id="graph_nodes">
97 97 <div id="graph_canvas"></div>
98 98 </div>
99 99 <div id="graph_content" class="graph_full_width">
100 100
101 101 <div class="table">
102 102 <table id="changesets" class="rctable">
103 103 <tr>
104 104 ## checkbox
105 105 <th colspan="4">
106 106 ## clear selection
107 107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
108 108 <i class="icon-cancel-circled2"></i>
109 109 </div>
110 110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
111 111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
112 112 </th>
113 113
114 114 ## commit message expand arrow
115 115 <th></th>
116 116 <th>${_('Commit Message')}</th>
117 117
118 118 <th>${_('Age')}</th>
119 119 <th>${_('Author')}</th>
120 120
121 121 <th>${_('Refs')}</th>
122 122 ## comments
123 123 <th></th>
124 124 </tr>
125 125
126 126 <tbody class="commits-range">
127 127 <%include file='changelog_elements.mako'/>
128 128 </tbody>
129 129 </table>
130 130 </div>
131 131 </div>
132 132 <div class="pagination-wh pagination-left">
133 133 ${c.pagination.render()}
134 134 </div>
135 135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
136 136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
137 137 </div>
138 138 </div>
139 139
140 140 <script type="text/javascript">
141 141 var cache = {};
142 142 $(function(){
143 143
144 144 // Create links to commit ranges when range checkboxes are selected
145 145 var $commitCheckboxes = $('.commit-range');
146 146 // cache elements
147 147 var $commitRangeMore = $('#rev_range_more');
148 148 var $commitRangeContainer = $('#rev_range_container');
149 149 var $commitRangeClear = $('#rev_range_clear');
150 150
151 151 var checkboxRangeSelector = function(e){
152 152 var selectedCheckboxes = [];
153 153 for (pos in $commitCheckboxes){
154 154 if($commitCheckboxes[pos].checked){
155 155 selectedCheckboxes.push($commitCheckboxes[pos]);
156 156 }
157 157 }
158 158 var open_new_pull_request = $('#open_new_pull_request');
159 159
160 160 if (open_new_pull_request) {
161 161 var selected_changes = selectedCheckboxes.length;
162 162 open_new_pull_request.hide();
163 163 if (selected_changes == 1) {
164 164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
165 165 } else {
166 166 open_new_pull_request.html(_gettext('Open new pull request'));
167 167 }
168 168 open_new_pull_request.show();
169 169 }
170 170
171 171 if (selectedCheckboxes.length > 0) {
172 172 $('#compare_fork_button').hide();
173 173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
174 174
175 175 var revStart = commitStart.commitId;
176 176
177 177 var commitEnd = $(selectedCheckboxes[0]).data();
178 178 var revEnd = commitEnd.commitId;
179 179
180 180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
181 181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
182 182
183 183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
184 184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
185 185
186 186 if (selectedCheckboxes.length > 1) {
187 187 $commitRangeClear.show();
188 188 $commitRangeMore.hide();
189 189
190 190 $commitRangeContainer
191 191 .attr('href',url)
192 192 .html(link)
193 193 .show();
194 194
195 195
196 196 } else {
197 197 $commitRangeContainer.hide();
198 198 $commitRangeClear.show();
199 199 $commitRangeMore.show();
200 200 }
201 201
202 202 // pull-request link
203 203 if (selectedCheckboxes.length == 1){
204 204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
205 205 open_new_pull_request.attr('href', _url);
206 206 } else {
207 207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
208 208 open_new_pull_request.attr('href', _url);
209 209 }
210 210
211 211 } else {
212 212 $commitRangeContainer.hide();
213 213 $commitRangeClear.hide();
214 214 $commitRangeMore.hide();
215 215
216 216 %if c.branch_name:
217 217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
218 218 open_new_pull_request.attr('href', _url);
219 219 %else:
220 220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
221 221 open_new_pull_request.attr('href', _url);
222 222 %endif
223 223 $('#compare_fork_button').show();
224 224 }
225 225 };
226 226
227 227 $commitCheckboxes.on('click', checkboxRangeSelector);
228 228
229 229 $commitRangeClear.on('click',function(e) {
230 230 $commitCheckboxes.attr('checked', false);
231 231 checkboxRangeSelector();
232 232 e.preventDefault();
233 233 });
234 234
235 235 // make sure the buttons are consistent when navigate back and forth
236 236 checkboxRangeSelector();
237 237
238 238 var msgs = $('.message');
239 239 // get first element height
240 240 var el = $('#graph_content .container')[0];
241 241 var row_h = el.clientHeight;
242 242 for (var i=0; i < msgs.length; i++) {
243 243 var m = msgs[i];
244 244
245 245 var h = m.clientHeight;
246 246 var pad = $(m).css('padding');
247 247 if (h > row_h) {
248 248 var offset = row_h - (h+12);
249 249 $(m.nextElementSibling).css('display','block');
250 250 $(m.nextElementSibling).css('margin-top',offset+'px');
251 251 }
252 252 }
253 253
254 254 $("#clear_filter").on("click", function() {
255 255 var filter = {'repo_name': '${c.repo_name}'};
256 256 window.location = pyroutes.url('repo_commits', filter);
257 257 });
258 258
259 259 $("#branch_filter").select2({
260 260 'dropdownAutoWidth': true,
261 261 'width': 'resolve',
262 262 'placeholder': "${c.selected_name or _('Branch filter')}",
263 263 containerCssClass: "drop-menu",
264 264 dropdownCssClass: "drop-menu-dropdown",
265 265 query: function(query){
266 266 var key = 'cache';
267 267 var cached = cache[key] ;
268 268 if(cached) {
269 269 var data = {results: []};
270 270 //filter results
271 271 $.each(cached.results, function(){
272 272 var section = this.text;
273 273 var children = [];
274 274 $.each(this.children, function(){
275 275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
276 276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
277 277 }
278 278 });
279 279 data.results.push({'text': section, 'children': children});
280 280 query.callback({results: data.results});
281 281 });
282 282 }else{
283 283 $.ajax({
284 284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
285 285 data: {},
286 286 dataType: 'json',
287 287 type: 'GET',
288 288 success: function(data) {
289 289 cache[key] = data;
290 290 query.callback({results: data.results});
291 291 }
292 292 })
293 293 }
294 294 }
295 295 });
296 296 $('#branch_filter').on('change', function(e){
297 297 var data = $('#branch_filter').select2('data');
298 298 //type: branch_closed
299 299 var selected = data.text;
300 300 var filter = {'repo_name': '${c.repo_name}'};
301 301 if(data.type == 'branch' || data.type == 'branch_closed'){
302 302 filter["branch"] = selected;
303 303 if (data.type == 'branch_closed') {
304 304 filter["evolve"] = '1';
305 305 }
306 306 }
307 307 else if (data.type == 'book'){
308 308 filter["bookmark"] = selected;
309 309 }
310 310 window.location = pyroutes.url('repo_commits', filter);
311 311 });
312 312
313 313 commitsController = new CommitsController();
314 314 % if not c.changelog_for_path:
315 315 commitsController.reloadGraph();
316 316 % endif
317 317
318 318 });
319 319
320 320 </script>
321 321 </div>
322 322 % else:
323 323 ${_('There are no changes yet')}
324 324 % endif
325 325 </div>
326 326 </%def>
@@ -1,308 +1,308 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
4 4
5 5 <%def name="title()">
6 6 %if c.compare_home:
7 7 ${_('%s Compare') % c.repo_name}
8 8 %else:
9 9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
10 10 %endif
11 11 %if c.rhodecode_name:
12 12 &middot; ${h.branding(c.rhodecode_name)}
13 13 %endif
14 14 </%def>
15 15
16 16 <%def name="breadcrumbs_links()"></%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='compare')}
24 24 </%def>
25 25
26 26 <%def name="main()">
27 27 <script type="text/javascript">
28 28 // set fake commitId on this commit-range page
29 29 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
30 30 </script>
31 31
32 32 <div class="box">
33 33 <div class="summary changeset">
34 34 <div class="summary-detail">
35 35 <div class="summary-detail-header">
36 36 <span class="breadcrumbs files_location">
37 37 <h4>
38 38 ${_('Compare Commits')}
39 39 % if c.file_path:
40 40 ${_('for file')} <a href="#${('a_' + h.FID('',c.file_path))}">${c.file_path}</a>
41 41 % endif
42 42
43 43 % if c.commit_ranges:
44 44 <code>
45 45 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
46 46 </code>
47 47 % endif
48 48 </h4>
49 49 </span>
50 50
51 51 <div class="clear-fix"></div>
52 52 </div>
53 53
54 54 <div class="fieldset">
55 55 <div class="left-label-summary">
56 56 <p class="spacing">${_('Target')}:</p>
57 57 <div class="right-label-summary">
58 58 <div class="code-header" >
59 59 <div class="compare_header">
60 60 ## The hidden elements are replaced with a select2 widget
61 61 ${h.hidden('compare_source')}
62 62 </div>
63 63 </div>
64 64 </div>
65 65 </div>
66 66 </div>
67 67
68 68 <div class="fieldset">
69 69 <div class="left-label-summary">
70 70 <p class="spacing">${_('Source')}:</p>
71 71 <div class="right-label-summary">
72 72 <div class="code-header" >
73 73 <div class="compare_header">
74 74 ## The hidden elements are replaced with a select2 widget
75 75 ${h.hidden('compare_target')}
76 76 </div>
77 77 </div>
78 78 </div>
79 79 </div>
80 80 </div>
81 81
82 82 <div class="fieldset">
83 83 <div class="left-label-summary">
84 84 <p class="spacing">${_('Actions')}:</p>
85 85 <div class="right-label-summary">
86 86 <div class="code-header" >
87 87 <div class="compare_header">
88 88 <div class="compare-buttons">
89 89 % if c.compare_home:
90 90 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
91 91 %if c.rhodecode_db_repo.fork:
92 92
93 93 <a class="btn btn-default" title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
94 94 href="${h.route_path('repo_compare',
95 95 repo_name=c.rhodecode_db_repo.fork.repo_name,
96 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
97 source_ref=c.rhodecode_db_repo.landing_rev[1],
98 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
99 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
96 source_ref_type=c.rhodecode_db_repo.landing_ref_type,
97 source_ref=c.rhodecode_db_repo.landing_ref_name,
98 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_ref_type,
99 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_ref_name,
100 100 _query=dict(merge=1))}"
101 101 >
102 102 ${_('Compare with origin')}
103 103 </a>
104 104
105 105 %endif
106 106
107 107 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
108 108 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
109 109 <div id="changeset_compare_view_content">
110 110 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
111 111 </div>
112 112
113 113 % elif c.preview_mode:
114 114 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
115 115 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
116 116 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
117 117
118 118 % else:
119 119 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
120 120 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
121 121
122 122 ## allow comment only if there are commits to comment on
123 123 % if c.diffset and c.diffset.files and c.commit_ranges:
124 124 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
125 125 % else:
126 126 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
127 127 % endif
128 128 % endif
129 129 </div>
130 130 </div>
131 131 </div>
132 132 </div>
133 133 </div>
134 134 </div>
135 135
136 136 ## commit status form
137 137 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
138 138 <div class="left-label-summary">
139 139 <p class="spacing">${_('Commit status')}:</p>
140 140 <div class="right-label-summary">
141 141 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
142 142 ## main comment form and it status
143 143 <%
144 144 def revs(_revs):
145 145 form_inputs = []
146 146 for cs in _revs:
147 147 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
148 148 form_inputs.append(tmpl)
149 149 return form_inputs
150 150 %>
151 151 <div>
152 152 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
153 153 </div>
154 154 </div>
155 155 </div>
156 156 </div>
157 157 <div class="clear-fix"></div>
158 158 </div> <!-- end summary-detail -->
159 159 </div> <!-- end summary -->
160 160
161 161 ## use JS script to load it quickly before potentially large diffs render long time
162 162 ## this prevents from situation when large diffs block rendering of select2 fields
163 163 <script type="text/javascript">
164 164
165 165 var cache = {};
166 166
167 167 var formatSelection = function(repoName){
168 168 return function(data, container, escapeMarkup) {
169 169 var selection = data ? this.text(data) : "";
170 170 return escapeMarkup('{0}@{1}'.format(repoName, selection));
171 171 }
172 172 };
173 173
174 174 var feedCompareData = function(query, cachedValue){
175 175 var data = {results: []};
176 176 //filter results
177 177 $.each(cachedValue.results, function() {
178 178 var section = this.text;
179 179 var children = [];
180 180 $.each(this.children, function() {
181 181 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
182 182 children.push({
183 183 'id': this.id,
184 184 'text': this.text,
185 185 'type': this.type
186 186 })
187 187 }
188 188 });
189 189 data.results.push({
190 190 'text': section,
191 191 'children': children
192 192 })
193 193 });
194 194 //push the typed in changeset
195 195 data.results.push({
196 196 'text': _gettext('specify commit'),
197 197 'children': [{
198 198 'id': query.term,
199 199 'text': query.term,
200 200 'type': 'rev'
201 201 }]
202 202 });
203 203 query.callback(data);
204 204 };
205 205
206 206 var loadCompareData = function(repoName, query, cache){
207 207 $.ajax({
208 208 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
209 209 data: {},
210 210 dataType: 'json',
211 211 type: 'GET',
212 212 success: function(data) {
213 213 cache[repoName] = data;
214 214 query.callback({results: data.results});
215 215 }
216 216 })
217 217 };
218 218
219 219 var enable_fields = ${"false" if c.preview_mode else "true"};
220 220 $("#compare_source").select2({
221 221 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
222 222 containerCssClass: "drop-menu",
223 223 dropdownCssClass: "drop-menu-dropdown",
224 224 formatSelection: formatSelection("${c.source_repo.repo_name}"),
225 225 dropdownAutoWidth: true,
226 226 query: function(query) {
227 227 var repoName = '${c.source_repo.repo_name}';
228 228 var cachedValue = cache[repoName];
229 229
230 230 if (cachedValue){
231 231 feedCompareData(query, cachedValue);
232 232 }
233 233 else {
234 234 loadCompareData(repoName, query, cache);
235 235 }
236 236 }
237 237 }).select2("enable", enable_fields);
238 238
239 239 $("#compare_target").select2({
240 240 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
241 241 dropdownAutoWidth: true,
242 242 containerCssClass: "drop-menu",
243 243 dropdownCssClass: "drop-menu-dropdown",
244 244 formatSelection: formatSelection("${c.target_repo.repo_name}"),
245 245 query: function(query) {
246 246 var repoName = '${c.target_repo.repo_name}';
247 247 var cachedValue = cache[repoName];
248 248
249 249 if (cachedValue){
250 250 feedCompareData(query, cachedValue);
251 251 }
252 252 else {
253 253 loadCompareData(repoName, query, cache);
254 254 }
255 255 }
256 256 }).select2("enable", enable_fields);
257 257 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
258 258 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
259 259
260 260 $('#compare_revs').on('click', function(e) {
261 261 var source = $('#compare_source').select2('data') || initial_compare_source;
262 262 var target = $('#compare_target').select2('data') || initial_compare_target;
263 263 if (source && target) {
264 264 var url_data = {
265 265 repo_name: "${c.repo_name}",
266 266 source_ref: source.id,
267 267 source_ref_type: source.type,
268 268 target_ref: target.id,
269 269 target_ref_type: target.type
270 270 };
271 271 window.location = pyroutes.url('repo_compare', url_data);
272 272 }
273 273 });
274 274 $('#compare_changeset_status_toggle').on('click', function(e) {
275 275 $('#compare_changeset_status').toggle();
276 276 });
277 277
278 278 </script>
279 279
280 280 ## table diff data
281 281 <div class="table">
282 282 % if not c.compare_home:
283 283 <div id="changeset_compare_view_content">
284 284 <div class="pull-left">
285 285 <div class="btn-group">
286 286 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
287 287 % if c.collapse_all_commits:
288 288 <i class="icon-plus-squared-alt icon-no-margin"></i>
289 289 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
290 290 % else:
291 291 <i class="icon-minus-squared-alt icon-no-margin"></i>
292 292 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
293 293 % endif
294 294 </a>
295 295 </div>
296 296 </div>
297 297 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
298 298 ## commit compare generated below
299 299 <%include file="compare_commits.mako"/>
300 300 ${cbdiffs.render_diffset_menu(c.diffset)}
301 301 ${cbdiffs.render_diffset(c.diffset)}
302 302 </div>
303 303 % endif
304 304
305 305 </div>
306 306 </div>
307 307
308 308 </%def>
@@ -1,84 +1,84 b''
1 1
2 2 <div id="codeblock" class="browserblock">
3 3 <div class="browser-header">
4 4 <div class="browser-nav">
5 5
6 6 <div class="info_box">
7 7
8 8 <div class="info_box_elem previous">
9 9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
10 10 </div>
11 11
12 12 ${h.hidden('refs_filter')}
13 13
14 14 <div class="info_box_elem next">
15 15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
16 16 </div>
17 17 </div>
18 18
19 19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
20 20 <div>
21 21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
22 22 ${_('Upload File')}
23 23 </a>
24 24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
25 25 ${_('Add File')}
26 26 </a>
27 27 </div>
28 28 % endif
29 29
30 30 % if c.enable_downloads:
31 31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
32 32 <div class="btn btn-default new-file">
33 33 % if c.f_path == '/':
34 34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
35 35 ${_('Download full tree ZIP')}
36 36 </a>
37 37 % else:
38 38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
39 39 ${_('Download this tree ZIP')}
40 40 </a>
41 41 % endif
42 42 </div>
43 43 % endif
44 44
45 45 <div class="files-quick-filter">
46 46 <ul class="files-filter-box">
47 47 <li class="files-filter-box-path">
48 48 <i class="icon-search"></i>
49 49 </li>
50 50 <li class="files-filter-box-input">
51 51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
52 52 </li>
53 53 </ul>
54 54 </div>
55 55 </div>
56 56
57 57 </div>
58 58
59 59 ## file tree is computed from caches, and filled in
60 60 <div id="file-tree">
61 61 ${c.file_tree |n}
62 62 </div>
63 63
64 64 %if c.readme_data:
65 65 <div id="readme" class="anchor">
66 66 <div class="box">
67 <div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
67 <div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_ref_type, c.rhodecode_db_repo.landing_ref_name))}">
68 68 <div>
69 69 <i class="icon-file-text"></i>
70 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">
70 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_ref_name,f_path=c.readme_file)}">
71 71 ${c.readme_file}
72 72 </a>
73 73 </div>
74 74 </div>
75 75 <div class="readme codeblock">
76 76 <div class="readme_box">
77 77 ${c.readme_data|n}
78 78 </div>
79 79 </div>
80 80 </div>
81 81 </div>
82 82 %endif
83 83
84 84 </div>
@@ -1,118 +1,118 b''
1 1 <%inherit file="/summary/summary_base.mako"/>
2 2
3 3 <%namespace name="components" file="/summary/components.mako"/>
4 4
5 5
6 6 <%def name="menu_bar_subnav()">
7 7 ${self.repo_menu(active='summary')}
8 8 </%def>
9 9
10 10 <%def name="main()">
11 11
12 12 <div id="repo-summary" class="summary">
13 13 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
14 14 </div><!--end repo-summary-->
15 15
16 16
17 17 <div class="box">
18 18 %if not c.repo_commits:
19 19 <div class="empty-repo">
20 20 <div class="title">
21 21 <h3>${_('Quick start')}</h3>
22 22 </div>
23 23 <div class="clear-fix"></div>
24 24 </div>
25 25 %endif
26 26 <div class="table">
27 27 <div id="shortlog_data">
28 28 <%include file='summary_commits.mako'/>
29 29 </div>
30 30 </div>
31 31 </div>
32 32
33 33 %if c.readme_data:
34 34 <div id="readme" class="anchor">
35 35 <div class="box">
36 36
37 <div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
37 <div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_ref_type, c.rhodecode_db_repo.landing_ref_name))}">
38 38 <div>
39 39 <i class="icon-file-text"></i>
40 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">
40 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_ref_name,f_path=c.readme_file)}">
41 41 ${c.readme_file}
42 42 </a>
43 43 </div>
44 44 </div>
45 45 <div class="readme codeblock">
46 46 <div class="readme_box">
47 47 ${c.readme_data|n}
48 48 </div>
49 49 </div>
50 50 </div>
51 51 </div>
52 52 %endif
53 53
54 54 <script type="text/javascript">
55 55 $(document).ready(function(){
56 56
57 57 var showCloneField = function(clone_url_format){
58 58 $.each(['http', 'http_id', 'ssh'], function (idx, val) {
59 59 if(val === clone_url_format){
60 60 $('#clone_option_' + val).show();
61 61 $('#clone_option').val(val)
62 62 } else {
63 63 $('#clone_option_' + val).hide();
64 64 }
65 65 });
66 66 };
67 67 // default taken from session
68 68 showCloneField(templateContext.session_attrs.clone_url_format);
69 69
70 70 $('#clone_option').on('change', function(e) {
71 71 var selected = $(this).val();
72 72
73 73 storeUserSessionAttr('rc_user_session_attr.clone_url_format', selected);
74 74 showCloneField(selected)
75 75 });
76 76
77 77 var initialCommitData = {
78 78 id: null,
79 79 text: 'tip',
80 80 type: 'tag',
81 81 raw_id: null,
82 82 files_url: null
83 83 };
84 84
85 85 select2RefSwitcher('#download_options', initialCommitData);
86 86
87 87 // on change of download options
88 88 $('#download_options').on('change', function(e) {
89 89 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
90 90 var ext = '.zip';
91 91 var selected_cs = e.added;
92 92 var fname = e.added.raw_id + ext;
93 93 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
94 94 // set new label
95 95 $('#archive_link').html('{0}{1}'.format(escapeHtml(e.added.text), ext));
96 96
97 97 // set new url to button,
98 98 $('#archive_link').attr('href', href)
99 99 });
100 100
101 101
102 102 // calculate size of repository
103 103 calculateSize = function () {
104 104
105 105 var callback = function (data) {
106 106 % if c.show_stats:
107 107 showRepoStats('lang_stats', data);
108 108 % endif
109 109 };
110 110
111 111 showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
112 112
113 113 }
114 114
115 115 })
116 116 </script>
117 117
118 118 </%def>
General Comments 0
You need to be logged in to leave comments. Login now