##// END OF EJS Templates
repo-mapper: make it more resilient to errors, it's better it executes and skip certain repositories, rather then crash whole mapper.
milka -
r4547:dc47f7cc default
parent child Browse files
Show More
@@ -1,1020 +1,1024 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 """
22 22 Scm model for RhodeCode
23 23 """
24 24
25 25 import os.path
26 26 import traceback
27 27 import logging
28 28 import cStringIO
29 29
30 30 from sqlalchemy import func
31 31 from zope.cachedescriptors.property import Lazy as LazyProperty
32 32
33 33 import rhodecode
34 34 from rhodecode.lib.vcs import get_backend
35 35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 36 from rhodecode.lib.vcs.nodes import FileNode
37 37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 38 from rhodecode.lib import helpers as h, rc_cache
39 39 from rhodecode.lib.auth import (
40 40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 41 HasUserGroupPermissionAny)
42 42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 43 from rhodecode.lib import hooks_utils
44 44 from rhodecode.lib.utils import (
45 45 get_filesystem_repos, make_db_config)
46 46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 47 from rhodecode.lib.system_info import get_system_info
48 48 from rhodecode.model import BaseModel
49 49 from rhodecode.model.db import (
50 50 or_, false,
51 51 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
52 52 PullRequest, FileStore)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class UserTemp(object):
60 60 def __init__(self, user_id):
61 61 self.user_id = user_id
62 62
63 63 def __repr__(self):
64 64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65 65
66 66
67 67 class RepoTemp(object):
68 68 def __init__(self, repo_id):
69 69 self.repo_id = repo_id
70 70
71 71 def __repr__(self):
72 72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
73 73
74 74
75 75 class SimpleCachedRepoList(object):
76 76 """
77 77 Lighter version of of iteration of repos without the scm initialisation,
78 78 and with cache usage
79 79 """
80 80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 81 self.db_repo_list = db_repo_list
82 82 self.repos_path = repos_path
83 83 self.order_by = order_by
84 84 self.reversed = (order_by or '').startswith('-')
85 85 if not perm_set:
86 86 perm_set = ['repository.read', 'repository.write',
87 87 'repository.admin']
88 88 self.perm_set = perm_set
89 89
90 90 def __len__(self):
91 91 return len(self.db_repo_list)
92 92
93 93 def __repr__(self):
94 94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
95 95
96 96 def __iter__(self):
97 97 for dbr in self.db_repo_list:
98 98 # check permission at this level
99 99 has_perm = HasRepoPermissionAny(*self.perm_set)(
100 100 dbr.repo_name, 'SimpleCachedRepoList check')
101 101 if not has_perm:
102 102 continue
103 103
104 104 tmp_d = {
105 105 'name': dbr.repo_name,
106 106 'dbrepo': dbr.get_dict(),
107 107 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
108 108 }
109 109 yield tmp_d
110 110
111 111
112 112 class _PermCheckIterator(object):
113 113
114 114 def __init__(
115 115 self, obj_list, obj_attr, perm_set, perm_checker,
116 116 extra_kwargs=None):
117 117 """
118 118 Creates iterator from given list of objects, additionally
119 119 checking permission for them from perm_set var
120 120
121 121 :param obj_list: list of db objects
122 122 :param obj_attr: attribute of object to pass into perm_checker
123 123 :param perm_set: list of permissions to check
124 124 :param perm_checker: callable to check permissions against
125 125 """
126 126 self.obj_list = obj_list
127 127 self.obj_attr = obj_attr
128 128 self.perm_set = perm_set
129 129 self.perm_checker = perm_checker(*self.perm_set)
130 130 self.extra_kwargs = extra_kwargs or {}
131 131
132 132 def __len__(self):
133 133 return len(self.obj_list)
134 134
135 135 def __repr__(self):
136 136 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
137 137
138 138 def __iter__(self):
139 139 for db_obj in self.obj_list:
140 140 # check permission at this level
141 141 # NOTE(marcink): the __dict__.get() is ~4x faster then getattr()
142 142 name = db_obj.__dict__.get(self.obj_attr, None)
143 143 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
144 144 continue
145 145
146 146 yield db_obj
147 147
148 148
149 149 class RepoList(_PermCheckIterator):
150 150
151 151 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
152 152 if not perm_set:
153 153 perm_set = ['repository.read', 'repository.write', 'repository.admin']
154 154
155 155 super(RepoList, self).__init__(
156 156 obj_list=db_repo_list,
157 157 obj_attr='_repo_name', perm_set=perm_set,
158 158 perm_checker=HasRepoPermissionAny,
159 159 extra_kwargs=extra_kwargs)
160 160
161 161
162 162 class RepoGroupList(_PermCheckIterator):
163 163
164 164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 165 if not perm_set:
166 166 perm_set = ['group.read', 'group.write', 'group.admin']
167 167
168 168 super(RepoGroupList, self).__init__(
169 169 obj_list=db_repo_group_list,
170 170 obj_attr='_group_name', perm_set=perm_set,
171 171 perm_checker=HasRepoGroupPermissionAny,
172 172 extra_kwargs=extra_kwargs)
173 173
174 174
175 175 class UserGroupList(_PermCheckIterator):
176 176
177 177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 178 if not perm_set:
179 179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180 180
181 181 super(UserGroupList, self).__init__(
182 182 obj_list=db_user_group_list,
183 183 obj_attr='users_group_name', perm_set=perm_set,
184 184 perm_checker=HasUserGroupPermissionAny,
185 185 extra_kwargs=extra_kwargs)
186 186
187 187
188 188 class ScmModel(BaseModel):
189 189 """
190 190 Generic Scm Model
191 191 """
192 192
193 193 @LazyProperty
194 194 def repos_path(self):
195 195 """
196 196 Gets the repositories root path from database
197 197 """
198 198
199 199 settings_model = VcsSettingsModel(sa=self.sa)
200 200 return settings_model.get_repos_location()
201 201
202 202 def repo_scan(self, repos_path=None):
203 203 """
204 204 Listing of repositories in given path. This path should not be a
205 205 repository itself. Return a dictionary of repository objects
206 206
207 207 :param repos_path: path to directory containing repositories
208 208 """
209 209
210 210 if repos_path is None:
211 211 repos_path = self.repos_path
212 212
213 213 log.info('scanning for repositories in %s', repos_path)
214 214
215 215 config = make_db_config()
216 216 config.set('extensions', 'largefiles', '')
217 217 repos = {}
218 218
219 219 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 220 # name need to be decomposed and put back together using the /
221 221 # since this is internal storage separator for rhodecode
222 222 name = Repository.normalize_repo_name(name)
223 223
224 224 try:
225 225 if name in repos:
226 226 raise RepositoryError('Duplicate repository name %s '
227 227 'found in %s' % (name, path))
228 228 elif path[0] in rhodecode.BACKENDS:
229 229 backend = get_backend(path[0])
230 230 repos[name] = backend(path[1], config=config,
231 231 with_wire={"cache": False})
232 232 except OSError:
233 233 continue
234 except RepositoryError:
235 log.exception('Failed to create a repo')
236 continue
237
234 238 log.debug('found %s paths with repositories', len(repos))
235 239 return repos
236 240
237 241 def get_repos(self, all_repos=None, sort_key=None):
238 242 """
239 243 Get all repositories from db and for each repo create it's
240 244 backend instance and fill that backed with information from database
241 245
242 246 :param all_repos: list of repository names as strings
243 247 give specific repositories list, good for filtering
244 248
245 249 :param sort_key: initial sorting of repositories
246 250 """
247 251 if all_repos is None:
248 252 all_repos = self.sa.query(Repository)\
249 253 .filter(Repository.group_id == None)\
250 254 .order_by(func.lower(Repository.repo_name)).all()
251 255 repo_iter = SimpleCachedRepoList(
252 256 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 257 return repo_iter
254 258
255 259 def get_repo_groups(self, all_groups=None):
256 260 if all_groups is None:
257 261 all_groups = RepoGroup.query()\
258 262 .filter(RepoGroup.group_parent_id == None).all()
259 263 return [x for x in RepoGroupList(all_groups)]
260 264
261 265 def mark_for_invalidation(self, repo_name, delete=False):
262 266 """
263 267 Mark caches of this repo invalid in the database. `delete` flag
264 268 removes the cache entries
265 269
266 270 :param repo_name: the repo_name for which caches should be marked
267 271 invalid, or deleted
268 272 :param delete: delete the entry keys instead of setting bool
269 273 flag on them, and also purge caches used by the dogpile
270 274 """
271 275 repo = Repository.get_by_repo_name(repo_name)
272 276
273 277 if repo:
274 278 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 279 repo_id=repo.repo_id)
276 280 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277 281
278 282 repo_id = repo.repo_id
279 283 config = repo._config
280 284 config.set('extensions', 'largefiles', '')
281 285 repo.update_commit_cache(config=config, cs_cache=None)
282 286 if delete:
283 287 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 288 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285 289
286 290 def toggle_following_repo(self, follow_repo_id, user_id):
287 291
288 292 f = self.sa.query(UserFollowing)\
289 293 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 294 .filter(UserFollowing.user_id == user_id).scalar()
291 295
292 296 if f is not None:
293 297 try:
294 298 self.sa.delete(f)
295 299 return
296 300 except Exception:
297 301 log.error(traceback.format_exc())
298 302 raise
299 303
300 304 try:
301 305 f = UserFollowing()
302 306 f.user_id = user_id
303 307 f.follows_repo_id = follow_repo_id
304 308 self.sa.add(f)
305 309 except Exception:
306 310 log.error(traceback.format_exc())
307 311 raise
308 312
309 313 def toggle_following_user(self, follow_user_id, user_id):
310 314 f = self.sa.query(UserFollowing)\
311 315 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 316 .filter(UserFollowing.user_id == user_id).scalar()
313 317
314 318 if f is not None:
315 319 try:
316 320 self.sa.delete(f)
317 321 return
318 322 except Exception:
319 323 log.error(traceback.format_exc())
320 324 raise
321 325
322 326 try:
323 327 f = UserFollowing()
324 328 f.user_id = user_id
325 329 f.follows_user_id = follow_user_id
326 330 self.sa.add(f)
327 331 except Exception:
328 332 log.error(traceback.format_exc())
329 333 raise
330 334
331 335 def is_following_repo(self, repo_name, user_id, cache=False):
332 336 r = self.sa.query(Repository)\
333 337 .filter(Repository.repo_name == repo_name).scalar()
334 338
335 339 f = self.sa.query(UserFollowing)\
336 340 .filter(UserFollowing.follows_repository == r)\
337 341 .filter(UserFollowing.user_id == user_id).scalar()
338 342
339 343 return f is not None
340 344
341 345 def is_following_user(self, username, user_id, cache=False):
342 346 u = User.get_by_username(username)
343 347
344 348 f = self.sa.query(UserFollowing)\
345 349 .filter(UserFollowing.follows_user == u)\
346 350 .filter(UserFollowing.user_id == user_id).scalar()
347 351
348 352 return f is not None
349 353
350 354 def get_followers(self, repo):
351 355 repo = self._get_repo(repo)
352 356
353 357 return self.sa.query(UserFollowing)\
354 358 .filter(UserFollowing.follows_repository == repo).count()
355 359
356 360 def get_forks(self, repo):
357 361 repo = self._get_repo(repo)
358 362 return self.sa.query(Repository)\
359 363 .filter(Repository.fork == repo).count()
360 364
361 365 def get_pull_requests(self, repo):
362 366 repo = self._get_repo(repo)
363 367 return self.sa.query(PullRequest)\
364 368 .filter(PullRequest.target_repo == repo)\
365 369 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366 370
367 371 def get_artifacts(self, repo):
368 372 repo = self._get_repo(repo)
369 373 return self.sa.query(FileStore)\
370 374 .filter(FileStore.repo == repo)\
371 375 .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count()
372 376
373 377 def mark_as_fork(self, repo, fork, user):
374 378 repo = self._get_repo(repo)
375 379 fork = self._get_repo(fork)
376 380 if fork and repo.repo_id == fork.repo_id:
377 381 raise Exception("Cannot set repository as fork of itself")
378 382
379 383 if fork and repo.repo_type != fork.repo_type:
380 384 raise RepositoryError(
381 385 "Cannot set repository as fork of repository with other type")
382 386
383 387 repo.fork = fork
384 388 self.sa.add(repo)
385 389 return repo
386 390
387 391 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
388 392 dbrepo = self._get_repo(repo)
389 393 remote_uri = remote_uri or dbrepo.clone_uri
390 394 if not remote_uri:
391 395 raise Exception("This repository doesn't have a clone uri")
392 396
393 397 repo = dbrepo.scm_instance(cache=False)
394 398 repo.config.clear_section('hooks')
395 399
396 400 try:
397 401 # NOTE(marcink): add extra validation so we skip invalid urls
398 402 # this is due this tasks can be executed via scheduler without
399 403 # proper validation of remote_uri
400 404 if validate_uri:
401 405 config = make_db_config(clear_session=False)
402 406 url_validator(remote_uri, dbrepo.repo_type, config)
403 407 except InvalidCloneUrl:
404 408 raise
405 409
406 410 repo_name = dbrepo.repo_name
407 411 try:
408 412 # TODO: we need to make sure those operations call proper hooks !
409 413 repo.fetch(remote_uri)
410 414
411 415 self.mark_for_invalidation(repo_name)
412 416 except Exception:
413 417 log.error(traceback.format_exc())
414 418 raise
415 419
416 420 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
417 421 dbrepo = self._get_repo(repo)
418 422 remote_uri = remote_uri or dbrepo.push_uri
419 423 if not remote_uri:
420 424 raise Exception("This repository doesn't have a clone uri")
421 425
422 426 repo = dbrepo.scm_instance(cache=False)
423 427 repo.config.clear_section('hooks')
424 428
425 429 try:
426 430 # NOTE(marcink): add extra validation so we skip invalid urls
427 431 # this is due this tasks can be executed via scheduler without
428 432 # proper validation of remote_uri
429 433 if validate_uri:
430 434 config = make_db_config(clear_session=False)
431 435 url_validator(remote_uri, dbrepo.repo_type, config)
432 436 except InvalidCloneUrl:
433 437 raise
434 438
435 439 try:
436 440 repo.push(remote_uri)
437 441 except Exception:
438 442 log.error(traceback.format_exc())
439 443 raise
440 444
441 445 def commit_change(self, repo, repo_name, commit, user, author, message,
442 446 content, f_path):
443 447 """
444 448 Commits changes
445 449
446 450 :param repo: SCM instance
447 451
448 452 """
449 453 user = self._get_user(user)
450 454
451 455 # decoding here will force that we have proper encoded values
452 456 # in any other case this will throw exceptions and deny commit
453 457 content = safe_str(content)
454 458 path = safe_str(f_path)
455 459 # message and author needs to be unicode
456 460 # proper backend should then translate that into required type
457 461 message = safe_unicode(message)
458 462 author = safe_unicode(author)
459 463 imc = repo.in_memory_commit
460 464 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
461 465 try:
462 466 # TODO: handle pre-push action !
463 467 tip = imc.commit(
464 468 message=message, author=author, parents=[commit],
465 469 branch=commit.branch)
466 470 except Exception as e:
467 471 log.error(traceback.format_exc())
468 472 raise IMCCommitError(str(e))
469 473 finally:
470 474 # always clear caches, if commit fails we want fresh object also
471 475 self.mark_for_invalidation(repo_name)
472 476
473 477 # We trigger the post-push action
474 478 hooks_utils.trigger_post_push_hook(
475 479 username=user.username, action='push_local', hook_type='post_push',
476 480 repo_name=repo_name, repo_type=repo.alias, commit_ids=[tip.raw_id])
477 481 return tip
478 482
479 483 def _sanitize_path(self, f_path):
480 484 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
481 485 raise NonRelativePathError('%s is not an relative path' % f_path)
482 486 if f_path:
483 487 f_path = os.path.normpath(f_path)
484 488 return f_path
485 489
486 490 def get_dirnode_metadata(self, request, commit, dir_node):
487 491 if not dir_node.is_dir():
488 492 return []
489 493
490 494 data = []
491 495 for node in dir_node:
492 496 if not node.is_file():
493 497 # we skip file-nodes
494 498 continue
495 499
496 500 last_commit = node.last_commit
497 501 last_commit_date = last_commit.date
498 502 data.append({
499 503 'name': node.name,
500 504 'size': h.format_byte_size_binary(node.size),
501 505 'modified_at': h.format_date(last_commit_date),
502 506 'modified_ts': last_commit_date.isoformat(),
503 507 'revision': last_commit.revision,
504 508 'short_id': last_commit.short_id,
505 509 'message': h.escape(last_commit.message),
506 510 'author': h.escape(last_commit.author),
507 511 'user_profile': h.gravatar_with_user(
508 512 request, last_commit.author),
509 513 })
510 514
511 515 return data
512 516
513 517 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
514 518 extended_info=False, content=False, max_file_bytes=None):
515 519 """
516 520 recursive walk in root dir and return a set of all path in that dir
517 521 based on repository walk function
518 522
519 523 :param repo_name: name of repository
520 524 :param commit_id: commit id for which to list nodes
521 525 :param root_path: root path to list
522 526 :param flat: return as a list, if False returns a dict with description
523 527 :param extended_info: show additional info such as md5, binary, size etc
524 528 :param content: add nodes content to the return data
525 529 :param max_file_bytes: will not return file contents over this limit
526 530
527 531 """
528 532 _files = list()
529 533 _dirs = list()
530 534 try:
531 535 _repo = self._get_repo(repo_name)
532 536 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
533 537 root_path = root_path.lstrip('/')
534 538 for __, dirs, files in commit.walk(root_path):
535 539
536 540 for f in files:
537 541 _content = None
538 542 _data = f_name = f.unicode_path
539 543
540 544 if not flat:
541 545 _data = {
542 546 "name": h.escape(f_name),
543 547 "type": "file",
544 548 }
545 549 if extended_info:
546 550 _data.update({
547 551 "md5": f.md5,
548 552 "binary": f.is_binary,
549 553 "size": f.size,
550 554 "extension": f.extension,
551 555 "mimetype": f.mimetype,
552 556 "lines": f.lines()[0]
553 557 })
554 558
555 559 if content:
556 560 over_size_limit = (max_file_bytes is not None
557 561 and f.size > max_file_bytes)
558 562 full_content = None
559 563 if not f.is_binary and not over_size_limit:
560 564 full_content = safe_str(f.content)
561 565
562 566 _data.update({
563 567 "content": full_content,
564 568 })
565 569 _files.append(_data)
566 570
567 571 for d in dirs:
568 572 _data = d_name = d.unicode_path
569 573 if not flat:
570 574 _data = {
571 575 "name": h.escape(d_name),
572 576 "type": "dir",
573 577 }
574 578 if extended_info:
575 579 _data.update({
576 580 "md5": None,
577 581 "binary": None,
578 582 "size": None,
579 583 "extension": None,
580 584 })
581 585 if content:
582 586 _data.update({
583 587 "content": None
584 588 })
585 589 _dirs.append(_data)
586 590 except RepositoryError:
587 591 log.exception("Exception in get_nodes")
588 592 raise
589 593
590 594 return _dirs, _files
591 595
592 596 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
593 597 """
594 598 Generate files for quick filter in files view
595 599 """
596 600
597 601 _files = list()
598 602 _dirs = list()
599 603 try:
600 604 _repo = self._get_repo(repo_name)
601 605 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
602 606 root_path = root_path.lstrip('/')
603 607 for __, dirs, files in commit.walk(root_path):
604 608
605 609 for f in files:
606 610
607 611 _data = {
608 612 "name": h.escape(f.unicode_path),
609 613 "type": "file",
610 614 }
611 615
612 616 _files.append(_data)
613 617
614 618 for d in dirs:
615 619
616 620 _data = {
617 621 "name": h.escape(d.unicode_path),
618 622 "type": "dir",
619 623 }
620 624
621 625 _dirs.append(_data)
622 626 except RepositoryError:
623 627 log.exception("Exception in get_quick_filter_nodes")
624 628 raise
625 629
626 630 return _dirs, _files
627 631
628 632 def get_node(self, repo_name, commit_id, file_path,
629 633 extended_info=False, content=False, max_file_bytes=None, cache=True):
630 634 """
631 635 retrieve single node from commit
632 636 """
633 637 try:
634 638
635 639 _repo = self._get_repo(repo_name)
636 640 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
637 641
638 642 file_node = commit.get_node(file_path)
639 643 if file_node.is_dir():
640 644 raise RepositoryError('The given path is a directory')
641 645
642 646 _content = None
643 647 f_name = file_node.unicode_path
644 648
645 649 file_data = {
646 650 "name": h.escape(f_name),
647 651 "type": "file",
648 652 }
649 653
650 654 if extended_info:
651 655 file_data.update({
652 656 "extension": file_node.extension,
653 657 "mimetype": file_node.mimetype,
654 658 })
655 659
656 660 if cache:
657 661 md5 = file_node.md5
658 662 is_binary = file_node.is_binary
659 663 size = file_node.size
660 664 else:
661 665 is_binary, md5, size, _content = file_node.metadata_uncached()
662 666
663 667 file_data.update({
664 668 "md5": md5,
665 669 "binary": is_binary,
666 670 "size": size,
667 671 })
668 672
669 673 if content and cache:
670 674 # get content + cache
671 675 size = file_node.size
672 676 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
673 677 full_content = None
674 678 all_lines = 0
675 679 if not file_node.is_binary and not over_size_limit:
676 680 full_content = safe_unicode(file_node.content)
677 681 all_lines, empty_lines = file_node.count_lines(full_content)
678 682
679 683 file_data.update({
680 684 "content": full_content,
681 685 "lines": all_lines
682 686 })
683 687 elif content:
684 688 # get content *without* cache
685 689 if _content is None:
686 690 is_binary, md5, size, _content = file_node.metadata_uncached()
687 691
688 692 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
689 693 full_content = None
690 694 all_lines = 0
691 695 if not is_binary and not over_size_limit:
692 696 full_content = safe_unicode(_content)
693 697 all_lines, empty_lines = file_node.count_lines(full_content)
694 698
695 699 file_data.update({
696 700 "content": full_content,
697 701 "lines": all_lines
698 702 })
699 703
700 704 except RepositoryError:
701 705 log.exception("Exception in get_node")
702 706 raise
703 707
704 708 return file_data
705 709
706 710 def get_fts_data(self, repo_name, commit_id, root_path='/'):
707 711 """
708 712 Fetch node tree for usage in full text search
709 713 """
710 714
711 715 tree_info = list()
712 716
713 717 try:
714 718 _repo = self._get_repo(repo_name)
715 719 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
716 720 root_path = root_path.lstrip('/')
717 721 for __, dirs, files in commit.walk(root_path):
718 722
719 723 for f in files:
720 724 is_binary, md5, size, _content = f.metadata_uncached()
721 725 _data = {
722 726 "name": f.unicode_path,
723 727 "md5": md5,
724 728 "extension": f.extension,
725 729 "binary": is_binary,
726 730 "size": size
727 731 }
728 732
729 733 tree_info.append(_data)
730 734
731 735 except RepositoryError:
732 736 log.exception("Exception in get_nodes")
733 737 raise
734 738
735 739 return tree_info
736 740
737 741 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
738 742 author=None, trigger_push_hook=True):
739 743 """
740 744 Commits given multiple nodes into repo
741 745
742 746 :param user: RhodeCode User object or user_id, the commiter
743 747 :param repo: RhodeCode Repository object
744 748 :param message: commit message
745 749 :param nodes: mapping {filename:{'content':content},...}
746 750 :param parent_commit: parent commit, can be empty than it's
747 751 initial commit
748 752 :param author: author of commit, cna be different that commiter
749 753 only for git
750 754 :param trigger_push_hook: trigger push hooks
751 755
752 756 :returns: new commited commit
753 757 """
754 758
755 759 user = self._get_user(user)
756 760 scm_instance = repo.scm_instance(cache=False)
757 761
758 762 processed_nodes = []
759 763 for f_path in nodes:
760 764 f_path = self._sanitize_path(f_path)
761 765 content = nodes[f_path]['content']
762 766 f_path = safe_str(f_path)
763 767 # decoding here will force that we have proper encoded values
764 768 # in any other case this will throw exceptions and deny commit
765 769 if isinstance(content, (basestring,)):
766 770 content = safe_str(content)
767 771 elif isinstance(content, (file, cStringIO.OutputType,)):
768 772 content = content.read()
769 773 else:
770 774 raise Exception('Content is of unrecognized type %s' % (
771 775 type(content)
772 776 ))
773 777 processed_nodes.append((f_path, content))
774 778
775 779 message = safe_unicode(message)
776 780 commiter = user.full_contact
777 781 author = safe_unicode(author) if author else commiter
778 782
779 783 imc = scm_instance.in_memory_commit
780 784
781 785 if not parent_commit:
782 786 parent_commit = EmptyCommit(alias=scm_instance.alias)
783 787
784 788 if isinstance(parent_commit, EmptyCommit):
785 789 # EmptyCommit means we we're editing empty repository
786 790 parents = None
787 791 else:
788 792 parents = [parent_commit]
789 793 # add multiple nodes
790 794 for path, content in processed_nodes:
791 795 imc.add(FileNode(path, content=content))
792 796 # TODO: handle pre push scenario
793 797 tip = imc.commit(message=message,
794 798 author=author,
795 799 parents=parents,
796 800 branch=parent_commit.branch)
797 801
798 802 self.mark_for_invalidation(repo.repo_name)
799 803 if trigger_push_hook:
800 804 hooks_utils.trigger_post_push_hook(
801 805 username=user.username, action='push_local',
802 806 repo_name=repo.repo_name, repo_type=scm_instance.alias,
803 807 hook_type='post_push',
804 808 commit_ids=[tip.raw_id])
805 809 return tip
806 810
807 811 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
808 812 author=None, trigger_push_hook=True):
809 813 user = self._get_user(user)
810 814 scm_instance = repo.scm_instance(cache=False)
811 815
812 816 message = safe_unicode(message)
813 817 commiter = user.full_contact
814 818 author = safe_unicode(author) if author else commiter
815 819
816 820 imc = scm_instance.in_memory_commit
817 821
818 822 if not parent_commit:
819 823 parent_commit = EmptyCommit(alias=scm_instance.alias)
820 824
821 825 if isinstance(parent_commit, EmptyCommit):
822 826 # EmptyCommit means we we're editing empty repository
823 827 parents = None
824 828 else:
825 829 parents = [parent_commit]
826 830
827 831 # add multiple nodes
828 832 for _filename, data in nodes.items():
829 833 # new filename, can be renamed from the old one, also sanitaze
830 834 # the path for any hack around relative paths like ../../ etc.
831 835 filename = self._sanitize_path(data['filename'])
832 836 old_filename = self._sanitize_path(_filename)
833 837 content = data['content']
834 838 file_mode = data.get('mode')
835 839 filenode = FileNode(old_filename, content=content, mode=file_mode)
836 840 op = data['op']
837 841 if op == 'add':
838 842 imc.add(filenode)
839 843 elif op == 'del':
840 844 imc.remove(filenode)
841 845 elif op == 'mod':
842 846 if filename != old_filename:
843 847 # TODO: handle renames more efficient, needs vcs lib changes
844 848 imc.remove(filenode)
845 849 imc.add(FileNode(filename, content=content, mode=file_mode))
846 850 else:
847 851 imc.change(filenode)
848 852
849 853 try:
850 854 # TODO: handle pre push scenario commit changes
851 855 tip = imc.commit(message=message,
852 856 author=author,
853 857 parents=parents,
854 858 branch=parent_commit.branch)
855 859 except NodeNotChangedError:
856 860 raise
857 861 except Exception as e:
858 862 log.exception("Unexpected exception during call to imc.commit")
859 863 raise IMCCommitError(str(e))
860 864 finally:
861 865 # always clear caches, if commit fails we want fresh object also
862 866 self.mark_for_invalidation(repo.repo_name)
863 867
864 868 if trigger_push_hook:
865 869 hooks_utils.trigger_post_push_hook(
866 870 username=user.username, action='push_local', hook_type='post_push',
867 871 repo_name=repo.repo_name, repo_type=scm_instance.alias,
868 872 commit_ids=[tip.raw_id])
869 873
870 874 return tip
871 875
872 876 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
873 877 author=None, trigger_push_hook=True):
874 878 """
875 879 Deletes given multiple nodes into `repo`
876 880
877 881 :param user: RhodeCode User object or user_id, the committer
878 882 :param repo: RhodeCode Repository object
879 883 :param message: commit message
880 884 :param nodes: mapping {filename:{'content':content},...}
881 885 :param parent_commit: parent commit, can be empty than it's initial
882 886 commit
883 887 :param author: author of commit, cna be different that commiter only
884 888 for git
885 889 :param trigger_push_hook: trigger push hooks
886 890
887 891 :returns: new commit after deletion
888 892 """
889 893
890 894 user = self._get_user(user)
891 895 scm_instance = repo.scm_instance(cache=False)
892 896
893 897 processed_nodes = []
894 898 for f_path in nodes:
895 899 f_path = self._sanitize_path(f_path)
896 900 # content can be empty but for compatabilty it allows same dicts
897 901 # structure as add_nodes
898 902 content = nodes[f_path].get('content')
899 903 processed_nodes.append((f_path, content))
900 904
901 905 message = safe_unicode(message)
902 906 commiter = user.full_contact
903 907 author = safe_unicode(author) if author else commiter
904 908
905 909 imc = scm_instance.in_memory_commit
906 910
907 911 if not parent_commit:
908 912 parent_commit = EmptyCommit(alias=scm_instance.alias)
909 913
910 914 if isinstance(parent_commit, EmptyCommit):
911 915 # EmptyCommit means we we're editing empty repository
912 916 parents = None
913 917 else:
914 918 parents = [parent_commit]
915 919 # add multiple nodes
916 920 for path, content in processed_nodes:
917 921 imc.remove(FileNode(path, content=content))
918 922
919 923 # TODO: handle pre push scenario
920 924 tip = imc.commit(message=message,
921 925 author=author,
922 926 parents=parents,
923 927 branch=parent_commit.branch)
924 928
925 929 self.mark_for_invalidation(repo.repo_name)
926 930 if trigger_push_hook:
927 931 hooks_utils.trigger_post_push_hook(
928 932 username=user.username, action='push_local', hook_type='post_push',
929 933 repo_name=repo.repo_name, repo_type=scm_instance.alias,
930 934 commit_ids=[tip.raw_id])
931 935 return tip
932 936
933 937 def strip(self, repo, commit_id, branch):
934 938 scm_instance = repo.scm_instance(cache=False)
935 939 scm_instance.config.clear_section('hooks')
936 940 scm_instance.strip(commit_id, branch)
937 941 self.mark_for_invalidation(repo.repo_name)
938 942
939 943 def get_unread_journal(self):
940 944 return self.sa.query(UserLog).count()
941 945
942 946 @classmethod
943 947 def backend_landing_ref(cls, repo_type):
944 948 """
945 949 Return a default landing ref based on a repository type.
946 950 """
947 951
948 952 landing_ref = {
949 953 'hg': ('branch:default', 'default'),
950 954 'git': ('branch:master', 'master'),
951 955 'svn': ('rev:tip', 'latest tip'),
952 956 'default': ('rev:tip', 'latest tip'),
953 957 }
954 958
955 959 return landing_ref.get(repo_type) or landing_ref['default']
956 960
957 961 def get_repo_landing_revs(self, translator, repo=None):
958 962 """
959 963 Generates select option with tags branches and bookmarks (for hg only)
960 964 grouped by type
961 965
962 966 :param repo:
963 967 """
964 968 _ = translator
965 969 repo = self._get_repo(repo)
966 970
967 971 if repo:
968 972 repo_type = repo.repo_type
969 973 else:
970 974 repo_type = 'default'
971 975
972 976 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
973 977
974 978 default_ref_options = [
975 979 [default_landing_ref, landing_ref_lbl]
976 980 ]
977 981 default_choices = [
978 982 default_landing_ref
979 983 ]
980 984
981 985 if not repo:
982 986 return default_choices, default_ref_options
983 987
984 988 repo = repo.scm_instance()
985 989
986 990 ref_options = [('rev:tip', 'latest tip')]
987 991 choices = ['rev:tip']
988 992
989 993 # branches
990 994 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
991 995 if not branch_group:
992 996 # new repo, or without maybe a branch?
993 997 branch_group = default_ref_options
994 998
995 999 branches_group = (branch_group, _("Branches"))
996 1000 ref_options.append(branches_group)
997 1001 choices.extend([x[0] for x in branches_group[0]])
998 1002
999 1003 # bookmarks for HG
1000 1004 if repo.alias == 'hg':
1001 1005 bookmarks_group = (
1002 1006 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
1003 1007 for b in repo.bookmarks],
1004 1008 _("Bookmarks"))
1005 1009 ref_options.append(bookmarks_group)
1006 1010 choices.extend([x[0] for x in bookmarks_group[0]])
1007 1011
1008 1012 # tags
1009 1013 tags_group = (
1010 1014 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1011 1015 for t in repo.tags],
1012 1016 _("Tags"))
1013 1017 ref_options.append(tags_group)
1014 1018 choices.extend([x[0] for x in tags_group[0]])
1015 1019
1016 1020 return choices, ref_options
1017 1021
1018 1022 def get_server_info(self, environ=None):
1019 1023 server_info = get_system_info(environ)
1020 1024 return server_info
General Comments 0
You need to be logged in to leave comments. Login now