##// END OF EJS Templates
diff-caches: show count and size in caches view per repository.
marcink -
r2687:040668bd default
parent child Browse files
Show More

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

@@ -1,75 +1,80 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 import os
21 22 import logging
22 23
23 24 from pyramid.httpexceptions import HTTPFound
24 25 from pyramid.view import view_config
25 26
26 27 from rhodecode.apps._base import RepoAppView
27 28 from rhodecode.lib.auth import (
28 29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import system_info
30 32 from rhodecode.model.meta import Session
31 33 from rhodecode.model.scm import ScmModel
32 34
33 35 log = logging.getLogger(__name__)
34 36
35 37
36 38 class RepoCachesView(RepoAppView):
37 39 def load_default_context(self):
38 40 c = self._get_local_tmpl_context()
39
40
41 41 return c
42 42
43 43 @LoginRequired()
44 44 @HasRepoPermissionAnyDecorator('repository.admin')
45 45 @view_config(
46 46 route_name='edit_repo_caches', request_method='GET',
47 47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 48 def repo_caches(self):
49 49 c = self.load_default_context()
50 50 c.active = 'caches'
51 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
52 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
53 c.cached_diff_size = 0
54 if os.path.isdir(cached_diffs_dir):
55 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
51 56
52 57 return self._get_template_context(c)
53 58
54 59 @LoginRequired()
55 60 @HasRepoPermissionAnyDecorator('repository.admin')
56 61 @CSRFRequired()
57 62 @view_config(
58 63 route_name='edit_repo_caches', request_method='POST')
59 64 def repo_caches_purge(self):
60 65 _ = self.request.translate
61 66 c = self.load_default_context()
62 67 c.active = 'caches'
63 68
64 69 try:
65 70 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
66 71 Session().commit()
67 72 h.flash(_('Cache invalidation successful'),
68 73 category='success')
69 74 except Exception:
70 75 log.exception("Exception during cache invalidation")
71 76 h.flash(_('An error occurred during cache invalidation'),
72 77 category='error')
73 78
74 79 raise HTTPFound(h.route_path(
75 80 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
@@ -1,1712 +1,1710 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2018 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 Base module for all VCS systems
23 23 """
24 24
25 25 import collections
26 26 import datetime
27 27 import fnmatch
28 28 import itertools
29 29 import logging
30 30 import os
31 31 import re
32 32 import time
33 33 import warnings
34 34
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 37 from rhodecode.lib.utils2 import safe_str, safe_unicode
38 38 from rhodecode.lib.vcs import connection
39 39 from rhodecode.lib.vcs.utils import author_name, author_email
40 40 from rhodecode.lib.vcs.conf import settings
41 41 from rhodecode.lib.vcs.exceptions import (
42 42 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
43 43 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
44 44 NodeDoesNotExistError, NodeNotChangedError, VCSError,
45 45 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
46 46 RepositoryError)
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 FILEMODE_DEFAULT = 0100644
53 53 FILEMODE_EXECUTABLE = 0100755
54 54
55 55 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
56 56 MergeResponse = collections.namedtuple(
57 57 'MergeResponse',
58 58 ('possible', 'executed', 'merge_ref', 'failure_reason'))
59 59
60 60
61 61 class MergeFailureReason(object):
62 62 """
63 63 Enumeration with all the reasons why the server side merge could fail.
64 64
65 65 DO NOT change the number of the reasons, as they may be stored in the
66 66 database.
67 67
68 68 Changing the name of a reason is acceptable and encouraged to deprecate old
69 69 reasons.
70 70 """
71 71
72 72 # Everything went well.
73 73 NONE = 0
74 74
75 75 # An unexpected exception was raised. Check the logs for more details.
76 76 UNKNOWN = 1
77 77
78 78 # The merge was not successful, there are conflicts.
79 79 MERGE_FAILED = 2
80 80
81 81 # The merge succeeded but we could not push it to the target repository.
82 82 PUSH_FAILED = 3
83 83
84 84 # The specified target is not a head in the target repository.
85 85 TARGET_IS_NOT_HEAD = 4
86 86
87 87 # The source repository contains more branches than the target. Pushing
88 88 # the merge will create additional branches in the target.
89 89 HG_SOURCE_HAS_MORE_BRANCHES = 5
90 90
91 91 # The target reference has multiple heads. That does not allow to correctly
92 92 # identify the target location. This could only happen for mercurial
93 93 # branches.
94 94 HG_TARGET_HAS_MULTIPLE_HEADS = 6
95 95
96 96 # The target repository is locked
97 97 TARGET_IS_LOCKED = 7
98 98
99 99 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
100 100 # A involved commit could not be found.
101 101 _DEPRECATED_MISSING_COMMIT = 8
102 102
103 103 # The target repo reference is missing.
104 104 MISSING_TARGET_REF = 9
105 105
106 106 # The source repo reference is missing.
107 107 MISSING_SOURCE_REF = 10
108 108
109 109 # The merge was not successful, there are conflicts related to sub
110 110 # repositories.
111 111 SUBREPO_MERGE_FAILED = 11
112 112
113 113
114 114 class UpdateFailureReason(object):
115 115 """
116 116 Enumeration with all the reasons why the pull request update could fail.
117 117
118 118 DO NOT change the number of the reasons, as they may be stored in the
119 119 database.
120 120
121 121 Changing the name of a reason is acceptable and encouraged to deprecate old
122 122 reasons.
123 123 """
124 124
125 125 # Everything went well.
126 126 NONE = 0
127 127
128 128 # An unexpected exception was raised. Check the logs for more details.
129 129 UNKNOWN = 1
130 130
131 131 # The pull request is up to date.
132 132 NO_CHANGE = 2
133 133
134 134 # The pull request has a reference type that is not supported for update.
135 135 WRONG_REF_TYPE = 3
136 136
137 137 # Update failed because the target reference is missing.
138 138 MISSING_TARGET_REF = 4
139 139
140 140 # Update failed because the source reference is missing.
141 141 MISSING_SOURCE_REF = 5
142 142
143 143
144 144 class BaseRepository(object):
145 145 """
146 146 Base Repository for final backends
147 147
148 148 .. attribute:: DEFAULT_BRANCH_NAME
149 149
150 150 name of default branch (i.e. "trunk" for svn, "master" for git etc.
151 151
152 152 .. attribute:: commit_ids
153 153
154 154 list of all available commit ids, in ascending order
155 155
156 156 .. attribute:: path
157 157
158 158 absolute path to the repository
159 159
160 160 .. attribute:: bookmarks
161 161
162 162 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
163 163 there are no bookmarks or the backend implementation does not support
164 164 bookmarks.
165 165
166 166 .. attribute:: tags
167 167
168 168 Mapping from name to :term:`Commit ID` of the tag.
169 169
170 170 """
171 171
172 172 DEFAULT_BRANCH_NAME = None
173 173 DEFAULT_CONTACT = u"Unknown"
174 174 DEFAULT_DESCRIPTION = u"unknown"
175 175 EMPTY_COMMIT_ID = '0' * 40
176 176
177 177 path = None
178 178 _remote = None
179 179
180 180 def __init__(self, repo_path, config=None, create=False, **kwargs):
181 181 """
182 182 Initializes repository. Raises RepositoryError if repository could
183 183 not be find at the given ``repo_path`` or directory at ``repo_path``
184 184 exists and ``create`` is set to True.
185 185
186 186 :param repo_path: local path of the repository
187 187 :param config: repository configuration
188 188 :param create=False: if set to True, would try to create repository.
189 189 :param src_url=None: if set, should be proper url from which repository
190 190 would be cloned; requires ``create`` parameter to be set to True -
191 191 raises RepositoryError if src_url is set and create evaluates to
192 192 False
193 193 """
194 194 raise NotImplementedError
195 195
196 196 def __repr__(self):
197 197 return '<%s at %s>' % (self.__class__.__name__, self.path)
198 198
199 199 def __len__(self):
200 200 return self.count()
201 201
202 202 def __eq__(self, other):
203 203 same_instance = isinstance(other, self.__class__)
204 204 return same_instance and other.path == self.path
205 205
206 206 def __ne__(self, other):
207 207 return not self.__eq__(other)
208 208
209 def get_create_shadow_cache_pr_path(self, repo):
210 path = os.path.join(
211 os.path.dirname(self.path),
212 '.__shadow_diff_cache_repo_{}/'.format(repo.repo_id))
209 def get_create_shadow_cache_pr_path(self, db_repo):
210 path = db_repo.cached_diffs_dir
213 211 if not os.path.exists(path):
214 212 os.makedirs(path, 0755)
215 213 return path
216 214
217 215 @classmethod
218 216 def get_default_config(cls, default=None):
219 217 config = Config()
220 218 if default and isinstance(default, list):
221 219 for section, key, val in default:
222 220 config.set(section, key, val)
223 221 return config
224 222
225 223 @LazyProperty
226 224 def EMPTY_COMMIT(self):
227 225 return EmptyCommit(self.EMPTY_COMMIT_ID)
228 226
229 227 @LazyProperty
230 228 def alias(self):
231 229 for k, v in settings.BACKENDS.items():
232 230 if v.split('.')[-1] == str(self.__class__.__name__):
233 231 return k
234 232
235 233 @LazyProperty
236 234 def name(self):
237 235 return safe_unicode(os.path.basename(self.path))
238 236
239 237 @LazyProperty
240 238 def description(self):
241 239 raise NotImplementedError
242 240
243 241 def refs(self):
244 242 """
245 243 returns a `dict` with branches, bookmarks, tags, and closed_branches
246 244 for this repository
247 245 """
248 246 return dict(
249 247 branches=self.branches,
250 248 branches_closed=self.branches_closed,
251 249 tags=self.tags,
252 250 bookmarks=self.bookmarks
253 251 )
254 252
255 253 @LazyProperty
256 254 def branches(self):
257 255 """
258 256 A `dict` which maps branch names to commit ids.
259 257 """
260 258 raise NotImplementedError
261 259
262 260 @LazyProperty
263 261 def branches_closed(self):
264 262 """
265 263 A `dict` which maps tags names to commit ids.
266 264 """
267 265 raise NotImplementedError
268 266
269 267 @LazyProperty
270 268 def bookmarks(self):
271 269 """
272 270 A `dict` which maps tags names to commit ids.
273 271 """
274 272 raise NotImplementedError
275 273
276 274 @LazyProperty
277 275 def tags(self):
278 276 """
279 277 A `dict` which maps tags names to commit ids.
280 278 """
281 279 raise NotImplementedError
282 280
283 281 @LazyProperty
284 282 def size(self):
285 283 """
286 284 Returns combined size in bytes for all repository files
287 285 """
288 286 tip = self.get_commit()
289 287 return tip.size
290 288
291 289 def size_at_commit(self, commit_id):
292 290 commit = self.get_commit(commit_id)
293 291 return commit.size
294 292
295 293 def is_empty(self):
296 294 return not bool(self.commit_ids)
297 295
298 296 @staticmethod
299 297 def check_url(url, config):
300 298 """
301 299 Function will check given url and try to verify if it's a valid
302 300 link.
303 301 """
304 302 raise NotImplementedError
305 303
306 304 @staticmethod
307 305 def is_valid_repository(path):
308 306 """
309 307 Check if given `path` contains a valid repository of this backend
310 308 """
311 309 raise NotImplementedError
312 310
313 311 # ==========================================================================
314 312 # COMMITS
315 313 # ==========================================================================
316 314
317 315 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
318 316 """
319 317 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
320 318 are both None, most recent commit is returned.
321 319
322 320 :param pre_load: Optional. List of commit attributes to load.
323 321
324 322 :raises ``EmptyRepositoryError``: if there are no commits
325 323 """
326 324 raise NotImplementedError
327 325
328 326 def __iter__(self):
329 327 for commit_id in self.commit_ids:
330 328 yield self.get_commit(commit_id=commit_id)
331 329
332 330 def get_commits(
333 331 self, start_id=None, end_id=None, start_date=None, end_date=None,
334 332 branch_name=None, show_hidden=False, pre_load=None):
335 333 """
336 334 Returns iterator of `BaseCommit` objects from start to end
337 335 not inclusive. This should behave just like a list, ie. end is not
338 336 inclusive.
339 337
340 338 :param start_id: None or str, must be a valid commit id
341 339 :param end_id: None or str, must be a valid commit id
342 340 :param start_date:
343 341 :param end_date:
344 342 :param branch_name:
345 343 :param show_hidden:
346 344 :param pre_load:
347 345 """
348 346 raise NotImplementedError
349 347
350 348 def __getitem__(self, key):
351 349 """
352 350 Allows index based access to the commit objects of this repository.
353 351 """
354 352 pre_load = ["author", "branch", "date", "message", "parents"]
355 353 if isinstance(key, slice):
356 354 return self._get_range(key, pre_load)
357 355 return self.get_commit(commit_idx=key, pre_load=pre_load)
358 356
359 357 def _get_range(self, slice_obj, pre_load):
360 358 for commit_id in self.commit_ids.__getitem__(slice_obj):
361 359 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
362 360
363 361 def count(self):
364 362 return len(self.commit_ids)
365 363
366 364 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
367 365 """
368 366 Creates and returns a tag for the given ``commit_id``.
369 367
370 368 :param name: name for new tag
371 369 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
372 370 :param commit_id: commit id for which new tag would be created
373 371 :param message: message of the tag's commit
374 372 :param date: date of tag's commit
375 373
376 374 :raises TagAlreadyExistError: if tag with same name already exists
377 375 """
378 376 raise NotImplementedError
379 377
380 378 def remove_tag(self, name, user, message=None, date=None):
381 379 """
382 380 Removes tag with the given ``name``.
383 381
384 382 :param name: name of the tag to be removed
385 383 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
386 384 :param message: message of the tag's removal commit
387 385 :param date: date of tag's removal commit
388 386
389 387 :raises TagDoesNotExistError: if tag with given name does not exists
390 388 """
391 389 raise NotImplementedError
392 390
393 391 def get_diff(
394 392 self, commit1, commit2, path=None, ignore_whitespace=False,
395 393 context=3, path1=None):
396 394 """
397 395 Returns (git like) *diff*, as plain text. Shows changes introduced by
398 396 `commit2` since `commit1`.
399 397
400 398 :param commit1: Entry point from which diff is shown. Can be
401 399 ``self.EMPTY_COMMIT`` - in this case, patch showing all
402 400 the changes since empty state of the repository until `commit2`
403 401 :param commit2: Until which commit changes should be shown.
404 402 :param path: Can be set to a path of a file to create a diff of that
405 403 file. If `path1` is also set, this value is only associated to
406 404 `commit2`.
407 405 :param ignore_whitespace: If set to ``True``, would not show whitespace
408 406 changes. Defaults to ``False``.
409 407 :param context: How many lines before/after changed lines should be
410 408 shown. Defaults to ``3``.
411 409 :param path1: Can be set to a path to associate with `commit1`. This
412 410 parameter works only for backends which support diff generation for
413 411 different paths. Other backends will raise a `ValueError` if `path1`
414 412 is set and has a different value than `path`.
415 413 :param file_path: filter this diff by given path pattern
416 414 """
417 415 raise NotImplementedError
418 416
419 417 def strip(self, commit_id, branch=None):
420 418 """
421 419 Strip given commit_id from the repository
422 420 """
423 421 raise NotImplementedError
424 422
425 423 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
426 424 """
427 425 Return a latest common ancestor commit if one exists for this repo
428 426 `commit_id1` vs `commit_id2` from `repo2`.
429 427
430 428 :param commit_id1: Commit it from this repository to use as a
431 429 target for the comparison.
432 430 :param commit_id2: Source commit id to use for comparison.
433 431 :param repo2: Source repository to use for comparison.
434 432 """
435 433 raise NotImplementedError
436 434
437 435 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
438 436 """
439 437 Compare this repository's revision `commit_id1` with `commit_id2`.
440 438
441 439 Returns a tuple(commits, ancestor) that would be merged from
442 440 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
443 441 will be returned as ancestor.
444 442
445 443 :param commit_id1: Commit it from this repository to use as a
446 444 target for the comparison.
447 445 :param commit_id2: Source commit id to use for comparison.
448 446 :param repo2: Source repository to use for comparison.
449 447 :param merge: If set to ``True`` will do a merge compare which also
450 448 returns the common ancestor.
451 449 :param pre_load: Optional. List of commit attributes to load.
452 450 """
453 451 raise NotImplementedError
454 452
455 453 def merge(self, target_ref, source_repo, source_ref, workspace_id,
456 454 user_name='', user_email='', message='', dry_run=False,
457 455 use_rebase=False, close_branch=False):
458 456 """
459 457 Merge the revisions specified in `source_ref` from `source_repo`
460 458 onto the `target_ref` of this repository.
461 459
462 460 `source_ref` and `target_ref` are named tupls with the following
463 461 fields `type`, `name` and `commit_id`.
464 462
465 463 Returns a MergeResponse named tuple with the following fields
466 464 'possible', 'executed', 'source_commit', 'target_commit',
467 465 'merge_commit'.
468 466
469 467 :param target_ref: `target_ref` points to the commit on top of which
470 468 the `source_ref` should be merged.
471 469 :param source_repo: The repository that contains the commits to be
472 470 merged.
473 471 :param source_ref: `source_ref` points to the topmost commit from
474 472 the `source_repo` which should be merged.
475 473 :param workspace_id: `workspace_id` unique identifier.
476 474 :param user_name: Merge commit `user_name`.
477 475 :param user_email: Merge commit `user_email`.
478 476 :param message: Merge commit `message`.
479 477 :param dry_run: If `True` the merge will not take place.
480 478 :param use_rebase: If `True` commits from the source will be rebased
481 479 on top of the target instead of being merged.
482 480 :param close_branch: If `True` branch will be close before merging it
483 481 """
484 482 if dry_run:
485 483 message = message or 'dry_run_merge_message'
486 484 user_email = user_email or 'dry-run-merge@rhodecode.com'
487 485 user_name = user_name or 'Dry-Run User'
488 486 else:
489 487 if not user_name:
490 488 raise ValueError('user_name cannot be empty')
491 489 if not user_email:
492 490 raise ValueError('user_email cannot be empty')
493 491 if not message:
494 492 raise ValueError('message cannot be empty')
495 493
496 494 shadow_repository_path = self._maybe_prepare_merge_workspace(
497 495 workspace_id, target_ref, source_ref)
498 496
499 497 try:
500 498 return self._merge_repo(
501 499 shadow_repository_path, target_ref, source_repo,
502 500 source_ref, message, user_name, user_email, dry_run=dry_run,
503 501 use_rebase=use_rebase, close_branch=close_branch)
504 502 except RepositoryError:
505 503 log.exception(
506 504 'Unexpected failure when running merge, dry-run=%s',
507 505 dry_run)
508 506 return MergeResponse(
509 507 False, False, None, MergeFailureReason.UNKNOWN)
510 508
511 509 def _merge_repo(self, shadow_repository_path, target_ref,
512 510 source_repo, source_ref, merge_message,
513 511 merger_name, merger_email, dry_run=False,
514 512 use_rebase=False, close_branch=False):
515 513 """Internal implementation of merge."""
516 514 raise NotImplementedError
517 515
518 516 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref, source_ref):
519 517 """
520 518 Create the merge workspace.
521 519
522 520 :param workspace_id: `workspace_id` unique identifier.
523 521 """
524 522 raise NotImplementedError
525 523
526 524 def cleanup_merge_workspace(self, workspace_id):
527 525 """
528 526 Remove merge workspace.
529 527
530 528 This function MUST not fail in case there is no workspace associated to
531 529 the given `workspace_id`.
532 530
533 531 :param workspace_id: `workspace_id` unique identifier.
534 532 """
535 533 raise NotImplementedError
536 534
537 535 # ========== #
538 536 # COMMIT API #
539 537 # ========== #
540 538
541 539 @LazyProperty
542 540 def in_memory_commit(self):
543 541 """
544 542 Returns :class:`InMemoryCommit` object for this repository.
545 543 """
546 544 raise NotImplementedError
547 545
548 546 # ======================== #
549 547 # UTILITIES FOR SUBCLASSES #
550 548 # ======================== #
551 549
552 550 def _validate_diff_commits(self, commit1, commit2):
553 551 """
554 552 Validates that the given commits are related to this repository.
555 553
556 554 Intended as a utility for sub classes to have a consistent validation
557 555 of input parameters in methods like :meth:`get_diff`.
558 556 """
559 557 self._validate_commit(commit1)
560 558 self._validate_commit(commit2)
561 559 if (isinstance(commit1, EmptyCommit) and
562 560 isinstance(commit2, EmptyCommit)):
563 561 raise ValueError("Cannot compare two empty commits")
564 562
565 563 def _validate_commit(self, commit):
566 564 if not isinstance(commit, BaseCommit):
567 565 raise TypeError(
568 566 "%s is not of type BaseCommit" % repr(commit))
569 567 if commit.repository != self and not isinstance(commit, EmptyCommit):
570 568 raise ValueError(
571 569 "Commit %s must be a valid commit from this repository %s, "
572 570 "related to this repository instead %s." %
573 571 (commit, self, commit.repository))
574 572
575 573 def _validate_commit_id(self, commit_id):
576 574 if not isinstance(commit_id, basestring):
577 575 raise TypeError("commit_id must be a string value")
578 576
579 577 def _validate_commit_idx(self, commit_idx):
580 578 if not isinstance(commit_idx, (int, long)):
581 579 raise TypeError("commit_idx must be a numeric value")
582 580
583 581 def _validate_branch_name(self, branch_name):
584 582 if branch_name and branch_name not in self.branches_all:
585 583 msg = ("Branch %s not found in %s" % (branch_name, self))
586 584 raise BranchDoesNotExistError(msg)
587 585
588 586 #
589 587 # Supporting deprecated API parts
590 588 # TODO: johbo: consider to move this into a mixin
591 589 #
592 590
593 591 @property
594 592 def EMPTY_CHANGESET(self):
595 593 warnings.warn(
596 594 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
597 595 return self.EMPTY_COMMIT_ID
598 596
599 597 @property
600 598 def revisions(self):
601 599 warnings.warn("Use commits attribute instead", DeprecationWarning)
602 600 return self.commit_ids
603 601
604 602 @revisions.setter
605 603 def revisions(self, value):
606 604 warnings.warn("Use commits attribute instead", DeprecationWarning)
607 605 self.commit_ids = value
608 606
609 607 def get_changeset(self, revision=None, pre_load=None):
610 608 warnings.warn("Use get_commit instead", DeprecationWarning)
611 609 commit_id = None
612 610 commit_idx = None
613 611 if isinstance(revision, basestring):
614 612 commit_id = revision
615 613 else:
616 614 commit_idx = revision
617 615 return self.get_commit(
618 616 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
619 617
620 618 def get_changesets(
621 619 self, start=None, end=None, start_date=None, end_date=None,
622 620 branch_name=None, pre_load=None):
623 621 warnings.warn("Use get_commits instead", DeprecationWarning)
624 622 start_id = self._revision_to_commit(start)
625 623 end_id = self._revision_to_commit(end)
626 624 return self.get_commits(
627 625 start_id=start_id, end_id=end_id, start_date=start_date,
628 626 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
629 627
630 628 def _revision_to_commit(self, revision):
631 629 """
632 630 Translates a revision to a commit_id
633 631
634 632 Helps to support the old changeset based API which allows to use
635 633 commit ids and commit indices interchangeable.
636 634 """
637 635 if revision is None:
638 636 return revision
639 637
640 638 if isinstance(revision, basestring):
641 639 commit_id = revision
642 640 else:
643 641 commit_id = self.commit_ids[revision]
644 642 return commit_id
645 643
646 644 @property
647 645 def in_memory_changeset(self):
648 646 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
649 647 return self.in_memory_commit
650 648
651 649 def get_path_permissions(self, username):
652 650 """
653 651 Returns a path permission checker or None if not supported
654 652
655 653 :param username: session user name
656 654 :return: an instance of BasePathPermissionChecker or None
657 655 """
658 656 return None
659 657
660 658 def install_hooks(self, force=False):
661 659 return self._remote.install_hooks(force)
662 660
663 661
664 662 class BaseCommit(object):
665 663 """
666 664 Each backend should implement it's commit representation.
667 665
668 666 **Attributes**
669 667
670 668 ``repository``
671 669 repository object within which commit exists
672 670
673 671 ``id``
674 672 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
675 673 just ``tip``.
676 674
677 675 ``raw_id``
678 676 raw commit representation (i.e. full 40 length sha for git
679 677 backend)
680 678
681 679 ``short_id``
682 680 shortened (if apply) version of ``raw_id``; it would be simple
683 681 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
684 682 as ``raw_id`` for subversion
685 683
686 684 ``idx``
687 685 commit index
688 686
689 687 ``files``
690 688 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
691 689
692 690 ``dirs``
693 691 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
694 692
695 693 ``nodes``
696 694 combined list of ``Node`` objects
697 695
698 696 ``author``
699 697 author of the commit, as unicode
700 698
701 699 ``message``
702 700 message of the commit, as unicode
703 701
704 702 ``parents``
705 703 list of parent commits
706 704
707 705 """
708 706
709 707 branch = None
710 708 """
711 709 Depending on the backend this should be set to the branch name of the
712 710 commit. Backends not supporting branches on commits should leave this
713 711 value as ``None``.
714 712 """
715 713
716 714 _ARCHIVE_PREFIX_TEMPLATE = b'{repo_name}-{short_id}'
717 715 """
718 716 This template is used to generate a default prefix for repository archives
719 717 if no prefix has been specified.
720 718 """
721 719
722 720 def __str__(self):
723 721 return '<%s at %s:%s>' % (
724 722 self.__class__.__name__, self.idx, self.short_id)
725 723
726 724 def __repr__(self):
727 725 return self.__str__()
728 726
729 727 def __unicode__(self):
730 728 return u'%s:%s' % (self.idx, self.short_id)
731 729
732 730 def __eq__(self, other):
733 731 same_instance = isinstance(other, self.__class__)
734 732 return same_instance and self.raw_id == other.raw_id
735 733
736 734 def __json__(self):
737 735 parents = []
738 736 try:
739 737 for parent in self.parents:
740 738 parents.append({'raw_id': parent.raw_id})
741 739 except NotImplementedError:
742 740 # empty commit doesn't have parents implemented
743 741 pass
744 742
745 743 return {
746 744 'short_id': self.short_id,
747 745 'raw_id': self.raw_id,
748 746 'revision': self.idx,
749 747 'message': self.message,
750 748 'date': self.date,
751 749 'author': self.author,
752 750 'parents': parents,
753 751 'branch': self.branch
754 752 }
755 753
756 754 def __getstate__(self):
757 755 d = self.__dict__.copy()
758 756 d.pop('_remote', None)
759 757 d.pop('repository', None)
760 758 return d
761 759
762 760 def _get_refs(self):
763 761 return {
764 762 'branches': [self.branch] if self.branch else [],
765 763 'bookmarks': getattr(self, 'bookmarks', []),
766 764 'tags': self.tags
767 765 }
768 766
769 767 @LazyProperty
770 768 def last(self):
771 769 """
772 770 ``True`` if this is last commit in repository, ``False``
773 771 otherwise; trying to access this attribute while there is no
774 772 commits would raise `EmptyRepositoryError`
775 773 """
776 774 if self.repository is None:
777 775 raise CommitError("Cannot check if it's most recent commit")
778 776 return self.raw_id == self.repository.commit_ids[-1]
779 777
780 778 @LazyProperty
781 779 def parents(self):
782 780 """
783 781 Returns list of parent commits.
784 782 """
785 783 raise NotImplementedError
786 784
787 785 @property
788 786 def merge(self):
789 787 """
790 788 Returns boolean if commit is a merge.
791 789 """
792 790 return len(self.parents) > 1
793 791
794 792 @LazyProperty
795 793 def children(self):
796 794 """
797 795 Returns list of child commits.
798 796 """
799 797 raise NotImplementedError
800 798
801 799 @LazyProperty
802 800 def id(self):
803 801 """
804 802 Returns string identifying this commit.
805 803 """
806 804 raise NotImplementedError
807 805
808 806 @LazyProperty
809 807 def raw_id(self):
810 808 """
811 809 Returns raw string identifying this commit.
812 810 """
813 811 raise NotImplementedError
814 812
815 813 @LazyProperty
816 814 def short_id(self):
817 815 """
818 816 Returns shortened version of ``raw_id`` attribute, as string,
819 817 identifying this commit, useful for presentation to users.
820 818 """
821 819 raise NotImplementedError
822 820
823 821 @LazyProperty
824 822 def idx(self):
825 823 """
826 824 Returns integer identifying this commit.
827 825 """
828 826 raise NotImplementedError
829 827
830 828 @LazyProperty
831 829 def committer(self):
832 830 """
833 831 Returns committer for this commit
834 832 """
835 833 raise NotImplementedError
836 834
837 835 @LazyProperty
838 836 def committer_name(self):
839 837 """
840 838 Returns committer name for this commit
841 839 """
842 840
843 841 return author_name(self.committer)
844 842
845 843 @LazyProperty
846 844 def committer_email(self):
847 845 """
848 846 Returns committer email address for this commit
849 847 """
850 848
851 849 return author_email(self.committer)
852 850
853 851 @LazyProperty
854 852 def author(self):
855 853 """
856 854 Returns author for this commit
857 855 """
858 856
859 857 raise NotImplementedError
860 858
861 859 @LazyProperty
862 860 def author_name(self):
863 861 """
864 862 Returns author name for this commit
865 863 """
866 864
867 865 return author_name(self.author)
868 866
869 867 @LazyProperty
870 868 def author_email(self):
871 869 """
872 870 Returns author email address for this commit
873 871 """
874 872
875 873 return author_email(self.author)
876 874
877 875 def get_file_mode(self, path):
878 876 """
879 877 Returns stat mode of the file at `path`.
880 878 """
881 879 raise NotImplementedError
882 880
883 881 def is_link(self, path):
884 882 """
885 883 Returns ``True`` if given `path` is a symlink
886 884 """
887 885 raise NotImplementedError
888 886
889 887 def get_file_content(self, path):
890 888 """
891 889 Returns content of the file at the given `path`.
892 890 """
893 891 raise NotImplementedError
894 892
895 893 def get_file_size(self, path):
896 894 """
897 895 Returns size of the file at the given `path`.
898 896 """
899 897 raise NotImplementedError
900 898
901 899 def get_file_commit(self, path, pre_load=None):
902 900 """
903 901 Returns last commit of the file at the given `path`.
904 902
905 903 :param pre_load: Optional. List of commit attributes to load.
906 904 """
907 905 commits = self.get_file_history(path, limit=1, pre_load=pre_load)
908 906 if not commits:
909 907 raise RepositoryError(
910 908 'Failed to fetch history for path {}. '
911 909 'Please check if such path exists in your repository'.format(
912 910 path))
913 911 return commits[0]
914 912
915 913 def get_file_history(self, path, limit=None, pre_load=None):
916 914 """
917 915 Returns history of file as reversed list of :class:`BaseCommit`
918 916 objects for which file at given `path` has been modified.
919 917
920 918 :param limit: Optional. Allows to limit the size of the returned
921 919 history. This is intended as a hint to the underlying backend, so
922 920 that it can apply optimizations depending on the limit.
923 921 :param pre_load: Optional. List of commit attributes to load.
924 922 """
925 923 raise NotImplementedError
926 924
927 925 def get_file_annotate(self, path, pre_load=None):
928 926 """
929 927 Returns a generator of four element tuples with
930 928 lineno, sha, commit lazy loader and line
931 929
932 930 :param pre_load: Optional. List of commit attributes to load.
933 931 """
934 932 raise NotImplementedError
935 933
936 934 def get_nodes(self, path):
937 935 """
938 936 Returns combined ``DirNode`` and ``FileNode`` objects list representing
939 937 state of commit at the given ``path``.
940 938
941 939 :raises ``CommitError``: if node at the given ``path`` is not
942 940 instance of ``DirNode``
943 941 """
944 942 raise NotImplementedError
945 943
946 944 def get_node(self, path):
947 945 """
948 946 Returns ``Node`` object from the given ``path``.
949 947
950 948 :raises ``NodeDoesNotExistError``: if there is no node at the given
951 949 ``path``
952 950 """
953 951 raise NotImplementedError
954 952
955 953 def get_largefile_node(self, path):
956 954 """
957 955 Returns the path to largefile from Mercurial/Git-lfs storage.
958 956 or None if it's not a largefile node
959 957 """
960 958 return None
961 959
962 960 def archive_repo(self, file_path, kind='tgz', subrepos=None,
963 961 prefix=None, write_metadata=False, mtime=None):
964 962 """
965 963 Creates an archive containing the contents of the repository.
966 964
967 965 :param file_path: path to the file which to create the archive.
968 966 :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``.
969 967 :param prefix: name of root directory in archive.
970 968 Default is repository name and commit's short_id joined with dash:
971 969 ``"{repo_name}-{short_id}"``.
972 970 :param write_metadata: write a metadata file into archive.
973 971 :param mtime: custom modification time for archive creation, defaults
974 972 to time.time() if not given.
975 973
976 974 :raise VCSError: If prefix has a problem.
977 975 """
978 976 allowed_kinds = settings.ARCHIVE_SPECS.keys()
979 977 if kind not in allowed_kinds:
980 978 raise ImproperArchiveTypeError(
981 979 'Archive kind (%s) not supported use one of %s' %
982 980 (kind, allowed_kinds))
983 981
984 982 prefix = self._validate_archive_prefix(prefix)
985 983
986 984 mtime = mtime or time.mktime(self.date.timetuple())
987 985
988 986 file_info = []
989 987 cur_rev = self.repository.get_commit(commit_id=self.raw_id)
990 988 for _r, _d, files in cur_rev.walk('/'):
991 989 for f in files:
992 990 f_path = os.path.join(prefix, f.path)
993 991 file_info.append(
994 992 (f_path, f.mode, f.is_link(), f.raw_bytes))
995 993
996 994 if write_metadata:
997 995 metadata = [
998 996 ('repo_name', self.repository.name),
999 997 ('rev', self.raw_id),
1000 998 ('create_time', mtime),
1001 999 ('branch', self.branch),
1002 1000 ('tags', ','.join(self.tags)),
1003 1001 ]
1004 1002 meta = ["%s:%s" % (f_name, value) for f_name, value in metadata]
1005 1003 file_info.append(('.archival.txt', 0644, False, '\n'.join(meta)))
1006 1004
1007 1005 connection.Hg.archive_repo(file_path, mtime, file_info, kind)
1008 1006
1009 1007 def _validate_archive_prefix(self, prefix):
1010 1008 if prefix is None:
1011 1009 prefix = self._ARCHIVE_PREFIX_TEMPLATE.format(
1012 1010 repo_name=safe_str(self.repository.name),
1013 1011 short_id=self.short_id)
1014 1012 elif not isinstance(prefix, str):
1015 1013 raise ValueError("prefix not a bytes object: %s" % repr(prefix))
1016 1014 elif prefix.startswith('/'):
1017 1015 raise VCSError("Prefix cannot start with leading slash")
1018 1016 elif prefix.strip() == '':
1019 1017 raise VCSError("Prefix cannot be empty")
1020 1018 return prefix
1021 1019
1022 1020 @LazyProperty
1023 1021 def root(self):
1024 1022 """
1025 1023 Returns ``RootNode`` object for this commit.
1026 1024 """
1027 1025 return self.get_node('')
1028 1026
1029 1027 def next(self, branch=None):
1030 1028 """
1031 1029 Returns next commit from current, if branch is gives it will return
1032 1030 next commit belonging to this branch
1033 1031
1034 1032 :param branch: show commits within the given named branch
1035 1033 """
1036 1034 indexes = xrange(self.idx + 1, self.repository.count())
1037 1035 return self._find_next(indexes, branch)
1038 1036
1039 1037 def prev(self, branch=None):
1040 1038 """
1041 1039 Returns previous commit from current, if branch is gives it will
1042 1040 return previous commit belonging to this branch
1043 1041
1044 1042 :param branch: show commit within the given named branch
1045 1043 """
1046 1044 indexes = xrange(self.idx - 1, -1, -1)
1047 1045 return self._find_next(indexes, branch)
1048 1046
1049 1047 def _find_next(self, indexes, branch=None):
1050 1048 if branch and self.branch != branch:
1051 1049 raise VCSError('Branch option used on commit not belonging '
1052 1050 'to that branch')
1053 1051
1054 1052 for next_idx in indexes:
1055 1053 commit = self.repository.get_commit(commit_idx=next_idx)
1056 1054 if branch and branch != commit.branch:
1057 1055 continue
1058 1056 return commit
1059 1057 raise CommitDoesNotExistError
1060 1058
1061 1059 def diff(self, ignore_whitespace=True, context=3):
1062 1060 """
1063 1061 Returns a `Diff` object representing the change made by this commit.
1064 1062 """
1065 1063 parent = (
1066 1064 self.parents[0] if self.parents else self.repository.EMPTY_COMMIT)
1067 1065 diff = self.repository.get_diff(
1068 1066 parent, self,
1069 1067 ignore_whitespace=ignore_whitespace,
1070 1068 context=context)
1071 1069 return diff
1072 1070
1073 1071 @LazyProperty
1074 1072 def added(self):
1075 1073 """
1076 1074 Returns list of added ``FileNode`` objects.
1077 1075 """
1078 1076 raise NotImplementedError
1079 1077
1080 1078 @LazyProperty
1081 1079 def changed(self):
1082 1080 """
1083 1081 Returns list of modified ``FileNode`` objects.
1084 1082 """
1085 1083 raise NotImplementedError
1086 1084
1087 1085 @LazyProperty
1088 1086 def removed(self):
1089 1087 """
1090 1088 Returns list of removed ``FileNode`` objects.
1091 1089 """
1092 1090 raise NotImplementedError
1093 1091
1094 1092 @LazyProperty
1095 1093 def size(self):
1096 1094 """
1097 1095 Returns total number of bytes from contents of all filenodes.
1098 1096 """
1099 1097 return sum((node.size for node in self.get_filenodes_generator()))
1100 1098
1101 1099 def walk(self, topurl=''):
1102 1100 """
1103 1101 Similar to os.walk method. Insted of filesystem it walks through
1104 1102 commit starting at given ``topurl``. Returns generator of tuples
1105 1103 (topnode, dirnodes, filenodes).
1106 1104 """
1107 1105 topnode = self.get_node(topurl)
1108 1106 if not topnode.is_dir():
1109 1107 return
1110 1108 yield (topnode, topnode.dirs, topnode.files)
1111 1109 for dirnode in topnode.dirs:
1112 1110 for tup in self.walk(dirnode.path):
1113 1111 yield tup
1114 1112
1115 1113 def get_filenodes_generator(self):
1116 1114 """
1117 1115 Returns generator that yields *all* file nodes.
1118 1116 """
1119 1117 for topnode, dirs, files in self.walk():
1120 1118 for node in files:
1121 1119 yield node
1122 1120
1123 1121 #
1124 1122 # Utilities for sub classes to support consistent behavior
1125 1123 #
1126 1124
1127 1125 def no_node_at_path(self, path):
1128 1126 return NodeDoesNotExistError(
1129 1127 u"There is no file nor directory at the given path: "
1130 1128 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1131 1129
1132 1130 def _fix_path(self, path):
1133 1131 """
1134 1132 Paths are stored without trailing slash so we need to get rid off it if
1135 1133 needed.
1136 1134 """
1137 1135 return path.rstrip('/')
1138 1136
1139 1137 #
1140 1138 # Deprecated API based on changesets
1141 1139 #
1142 1140
1143 1141 @property
1144 1142 def revision(self):
1145 1143 warnings.warn("Use idx instead", DeprecationWarning)
1146 1144 return self.idx
1147 1145
1148 1146 @revision.setter
1149 1147 def revision(self, value):
1150 1148 warnings.warn("Use idx instead", DeprecationWarning)
1151 1149 self.idx = value
1152 1150
1153 1151 def get_file_changeset(self, path):
1154 1152 warnings.warn("Use get_file_commit instead", DeprecationWarning)
1155 1153 return self.get_file_commit(path)
1156 1154
1157 1155
1158 1156 class BaseChangesetClass(type):
1159 1157
1160 1158 def __instancecheck__(self, instance):
1161 1159 return isinstance(instance, BaseCommit)
1162 1160
1163 1161
1164 1162 class BaseChangeset(BaseCommit):
1165 1163
1166 1164 __metaclass__ = BaseChangesetClass
1167 1165
1168 1166 def __new__(cls, *args, **kwargs):
1169 1167 warnings.warn(
1170 1168 "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
1171 1169 return super(BaseChangeset, cls).__new__(cls, *args, **kwargs)
1172 1170
1173 1171
1174 1172 class BaseInMemoryCommit(object):
1175 1173 """
1176 1174 Represents differences between repository's state (most recent head) and
1177 1175 changes made *in place*.
1178 1176
1179 1177 **Attributes**
1180 1178
1181 1179 ``repository``
1182 1180 repository object for this in-memory-commit
1183 1181
1184 1182 ``added``
1185 1183 list of ``FileNode`` objects marked as *added*
1186 1184
1187 1185 ``changed``
1188 1186 list of ``FileNode`` objects marked as *changed*
1189 1187
1190 1188 ``removed``
1191 1189 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
1192 1190 *removed*
1193 1191
1194 1192 ``parents``
1195 1193 list of :class:`BaseCommit` instances representing parents of
1196 1194 in-memory commit. Should always be 2-element sequence.
1197 1195
1198 1196 """
1199 1197
1200 1198 def __init__(self, repository):
1201 1199 self.repository = repository
1202 1200 self.added = []
1203 1201 self.changed = []
1204 1202 self.removed = []
1205 1203 self.parents = []
1206 1204
1207 1205 def add(self, *filenodes):
1208 1206 """
1209 1207 Marks given ``FileNode`` objects as *to be committed*.
1210 1208
1211 1209 :raises ``NodeAlreadyExistsError``: if node with same path exists at
1212 1210 latest commit
1213 1211 :raises ``NodeAlreadyAddedError``: if node with same path is already
1214 1212 marked as *added*
1215 1213 """
1216 1214 # Check if not already marked as *added* first
1217 1215 for node in filenodes:
1218 1216 if node.path in (n.path for n in self.added):
1219 1217 raise NodeAlreadyAddedError(
1220 1218 "Such FileNode %s is already marked for addition"
1221 1219 % node.path)
1222 1220 for node in filenodes:
1223 1221 self.added.append(node)
1224 1222
1225 1223 def change(self, *filenodes):
1226 1224 """
1227 1225 Marks given ``FileNode`` objects to be *changed* in next commit.
1228 1226
1229 1227 :raises ``EmptyRepositoryError``: if there are no commits yet
1230 1228 :raises ``NodeAlreadyExistsError``: if node with same path is already
1231 1229 marked to be *changed*
1232 1230 :raises ``NodeAlreadyRemovedError``: if node with same path is already
1233 1231 marked to be *removed*
1234 1232 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
1235 1233 commit
1236 1234 :raises ``NodeNotChangedError``: if node hasn't really be changed
1237 1235 """
1238 1236 for node in filenodes:
1239 1237 if node.path in (n.path for n in self.removed):
1240 1238 raise NodeAlreadyRemovedError(
1241 1239 "Node at %s is already marked as removed" % node.path)
1242 1240 try:
1243 1241 self.repository.get_commit()
1244 1242 except EmptyRepositoryError:
1245 1243 raise EmptyRepositoryError(
1246 1244 "Nothing to change - try to *add* new nodes rather than "
1247 1245 "changing them")
1248 1246 for node in filenodes:
1249 1247 if node.path in (n.path for n in self.changed):
1250 1248 raise NodeAlreadyChangedError(
1251 1249 "Node at '%s' is already marked as changed" % node.path)
1252 1250 self.changed.append(node)
1253 1251
1254 1252 def remove(self, *filenodes):
1255 1253 """
1256 1254 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
1257 1255 *removed* in next commit.
1258 1256
1259 1257 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
1260 1258 be *removed*
1261 1259 :raises ``NodeAlreadyChangedError``: if node has been already marked to
1262 1260 be *changed*
1263 1261 """
1264 1262 for node in filenodes:
1265 1263 if node.path in (n.path for n in self.removed):
1266 1264 raise NodeAlreadyRemovedError(
1267 1265 "Node is already marked to for removal at %s" % node.path)
1268 1266 if node.path in (n.path for n in self.changed):
1269 1267 raise NodeAlreadyChangedError(
1270 1268 "Node is already marked to be changed at %s" % node.path)
1271 1269 # We only mark node as *removed* - real removal is done by
1272 1270 # commit method
1273 1271 self.removed.append(node)
1274 1272
1275 1273 def reset(self):
1276 1274 """
1277 1275 Resets this instance to initial state (cleans ``added``, ``changed``
1278 1276 and ``removed`` lists).
1279 1277 """
1280 1278 self.added = []
1281 1279 self.changed = []
1282 1280 self.removed = []
1283 1281 self.parents = []
1284 1282
1285 1283 def get_ipaths(self):
1286 1284 """
1287 1285 Returns generator of paths from nodes marked as added, changed or
1288 1286 removed.
1289 1287 """
1290 1288 for node in itertools.chain(self.added, self.changed, self.removed):
1291 1289 yield node.path
1292 1290
1293 1291 def get_paths(self):
1294 1292 """
1295 1293 Returns list of paths from nodes marked as added, changed or removed.
1296 1294 """
1297 1295 return list(self.get_ipaths())
1298 1296
1299 1297 def check_integrity(self, parents=None):
1300 1298 """
1301 1299 Checks in-memory commit's integrity. Also, sets parents if not
1302 1300 already set.
1303 1301
1304 1302 :raises CommitError: if any error occurs (i.e.
1305 1303 ``NodeDoesNotExistError``).
1306 1304 """
1307 1305 if not self.parents:
1308 1306 parents = parents or []
1309 1307 if len(parents) == 0:
1310 1308 try:
1311 1309 parents = [self.repository.get_commit(), None]
1312 1310 except EmptyRepositoryError:
1313 1311 parents = [None, None]
1314 1312 elif len(parents) == 1:
1315 1313 parents += [None]
1316 1314 self.parents = parents
1317 1315
1318 1316 # Local parents, only if not None
1319 1317 parents = [p for p in self.parents if p]
1320 1318
1321 1319 # Check nodes marked as added
1322 1320 for p in parents:
1323 1321 for node in self.added:
1324 1322 try:
1325 1323 p.get_node(node.path)
1326 1324 except NodeDoesNotExistError:
1327 1325 pass
1328 1326 else:
1329 1327 raise NodeAlreadyExistsError(
1330 1328 "Node `%s` already exists at %s" % (node.path, p))
1331 1329
1332 1330 # Check nodes marked as changed
1333 1331 missing = set(self.changed)
1334 1332 not_changed = set(self.changed)
1335 1333 if self.changed and not parents:
1336 1334 raise NodeDoesNotExistError(str(self.changed[0].path))
1337 1335 for p in parents:
1338 1336 for node in self.changed:
1339 1337 try:
1340 1338 old = p.get_node(node.path)
1341 1339 missing.remove(node)
1342 1340 # if content actually changed, remove node from not_changed
1343 1341 if old.content != node.content:
1344 1342 not_changed.remove(node)
1345 1343 except NodeDoesNotExistError:
1346 1344 pass
1347 1345 if self.changed and missing:
1348 1346 raise NodeDoesNotExistError(
1349 1347 "Node `%s` marked as modified but missing in parents: %s"
1350 1348 % (node.path, parents))
1351 1349
1352 1350 if self.changed and not_changed:
1353 1351 raise NodeNotChangedError(
1354 1352 "Node `%s` wasn't actually changed (parents: %s)"
1355 1353 % (not_changed.pop().path, parents))
1356 1354
1357 1355 # Check nodes marked as removed
1358 1356 if self.removed and not parents:
1359 1357 raise NodeDoesNotExistError(
1360 1358 "Cannot remove node at %s as there "
1361 1359 "were no parents specified" % self.removed[0].path)
1362 1360 really_removed = set()
1363 1361 for p in parents:
1364 1362 for node in self.removed:
1365 1363 try:
1366 1364 p.get_node(node.path)
1367 1365 really_removed.add(node)
1368 1366 except CommitError:
1369 1367 pass
1370 1368 not_removed = set(self.removed) - really_removed
1371 1369 if not_removed:
1372 1370 # TODO: johbo: This code branch does not seem to be covered
1373 1371 raise NodeDoesNotExistError(
1374 1372 "Cannot remove node at %s from "
1375 1373 "following parents: %s" % (not_removed, parents))
1376 1374
1377 1375 def commit(
1378 1376 self, message, author, parents=None, branch=None, date=None,
1379 1377 **kwargs):
1380 1378 """
1381 1379 Performs in-memory commit (doesn't check workdir in any way) and
1382 1380 returns newly created :class:`BaseCommit`. Updates repository's
1383 1381 attribute `commits`.
1384 1382
1385 1383 .. note::
1386 1384
1387 1385 While overriding this method each backend's should call
1388 1386 ``self.check_integrity(parents)`` in the first place.
1389 1387
1390 1388 :param message: message of the commit
1391 1389 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
1392 1390 :param parents: single parent or sequence of parents from which commit
1393 1391 would be derived
1394 1392 :param date: ``datetime.datetime`` instance. Defaults to
1395 1393 ``datetime.datetime.now()``.
1396 1394 :param branch: branch name, as string. If none given, default backend's
1397 1395 branch would be used.
1398 1396
1399 1397 :raises ``CommitError``: if any error occurs while committing
1400 1398 """
1401 1399 raise NotImplementedError
1402 1400
1403 1401
1404 1402 class BaseInMemoryChangesetClass(type):
1405 1403
1406 1404 def __instancecheck__(self, instance):
1407 1405 return isinstance(instance, BaseInMemoryCommit)
1408 1406
1409 1407
1410 1408 class BaseInMemoryChangeset(BaseInMemoryCommit):
1411 1409
1412 1410 __metaclass__ = BaseInMemoryChangesetClass
1413 1411
1414 1412 def __new__(cls, *args, **kwargs):
1415 1413 warnings.warn(
1416 1414 "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
1417 1415 return super(BaseInMemoryChangeset, cls).__new__(cls, *args, **kwargs)
1418 1416
1419 1417
1420 1418 class EmptyCommit(BaseCommit):
1421 1419 """
1422 1420 An dummy empty commit. It's possible to pass hash when creating
1423 1421 an EmptyCommit
1424 1422 """
1425 1423
1426 1424 def __init__(
1427 1425 self, commit_id='0' * 40, repo=None, alias=None, idx=-1,
1428 1426 message='', author='', date=None):
1429 1427 self._empty_commit_id = commit_id
1430 1428 # TODO: johbo: Solve idx parameter, default value does not make
1431 1429 # too much sense
1432 1430 self.idx = idx
1433 1431 self.message = message
1434 1432 self.author = author
1435 1433 self.date = date or datetime.datetime.fromtimestamp(0)
1436 1434 self.repository = repo
1437 1435 self.alias = alias
1438 1436
1439 1437 @LazyProperty
1440 1438 def raw_id(self):
1441 1439 """
1442 1440 Returns raw string identifying this commit, useful for web
1443 1441 representation.
1444 1442 """
1445 1443
1446 1444 return self._empty_commit_id
1447 1445
1448 1446 @LazyProperty
1449 1447 def branch(self):
1450 1448 if self.alias:
1451 1449 from rhodecode.lib.vcs.backends import get_backend
1452 1450 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1453 1451
1454 1452 @LazyProperty
1455 1453 def short_id(self):
1456 1454 return self.raw_id[:12]
1457 1455
1458 1456 @LazyProperty
1459 1457 def id(self):
1460 1458 return self.raw_id
1461 1459
1462 1460 def get_file_commit(self, path):
1463 1461 return self
1464 1462
1465 1463 def get_file_content(self, path):
1466 1464 return u''
1467 1465
1468 1466 def get_file_size(self, path):
1469 1467 return 0
1470 1468
1471 1469
1472 1470 class EmptyChangesetClass(type):
1473 1471
1474 1472 def __instancecheck__(self, instance):
1475 1473 return isinstance(instance, EmptyCommit)
1476 1474
1477 1475
1478 1476 class EmptyChangeset(EmptyCommit):
1479 1477
1480 1478 __metaclass__ = EmptyChangesetClass
1481 1479
1482 1480 def __new__(cls, *args, **kwargs):
1483 1481 warnings.warn(
1484 1482 "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
1485 1483 return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
1486 1484
1487 1485 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1488 1486 alias=None, revision=-1, message='', author='', date=None):
1489 1487 if requested_revision is not None:
1490 1488 warnings.warn(
1491 1489 "Parameter requested_revision not supported anymore",
1492 1490 DeprecationWarning)
1493 1491 super(EmptyChangeset, self).__init__(
1494 1492 commit_id=cs, repo=repo, alias=alias, idx=revision,
1495 1493 message=message, author=author, date=date)
1496 1494
1497 1495 @property
1498 1496 def revision(self):
1499 1497 warnings.warn("Use idx instead", DeprecationWarning)
1500 1498 return self.idx
1501 1499
1502 1500 @revision.setter
1503 1501 def revision(self, value):
1504 1502 warnings.warn("Use idx instead", DeprecationWarning)
1505 1503 self.idx = value
1506 1504
1507 1505
1508 1506 class EmptyRepository(BaseRepository):
1509 1507 def __init__(self, repo_path=None, config=None, create=False, **kwargs):
1510 1508 pass
1511 1509
1512 1510 def get_diff(self, *args, **kwargs):
1513 1511 from rhodecode.lib.vcs.backends.git.diff import GitDiff
1514 1512 return GitDiff('')
1515 1513
1516 1514
1517 1515 class CollectionGenerator(object):
1518 1516
1519 1517 def __init__(self, repo, commit_ids, collection_size=None, pre_load=None):
1520 1518 self.repo = repo
1521 1519 self.commit_ids = commit_ids
1522 1520 # TODO: (oliver) this isn't currently hooked up
1523 1521 self.collection_size = None
1524 1522 self.pre_load = pre_load
1525 1523
1526 1524 def __len__(self):
1527 1525 if self.collection_size is not None:
1528 1526 return self.collection_size
1529 1527 return self.commit_ids.__len__()
1530 1528
1531 1529 def __iter__(self):
1532 1530 for commit_id in self.commit_ids:
1533 1531 # TODO: johbo: Mercurial passes in commit indices or commit ids
1534 1532 yield self._commit_factory(commit_id)
1535 1533
1536 1534 def _commit_factory(self, commit_id):
1537 1535 """
1538 1536 Allows backends to override the way commits are generated.
1539 1537 """
1540 1538 return self.repo.get_commit(commit_id=commit_id,
1541 1539 pre_load=self.pre_load)
1542 1540
1543 1541 def __getslice__(self, i, j):
1544 1542 """
1545 1543 Returns an iterator of sliced repository
1546 1544 """
1547 1545 commit_ids = self.commit_ids[i:j]
1548 1546 return self.__class__(
1549 1547 self.repo, commit_ids, pre_load=self.pre_load)
1550 1548
1551 1549 def __repr__(self):
1552 1550 return '<CollectionGenerator[len:%s]>' % (self.__len__())
1553 1551
1554 1552
1555 1553 class Config(object):
1556 1554 """
1557 1555 Represents the configuration for a repository.
1558 1556
1559 1557 The API is inspired by :class:`ConfigParser.ConfigParser` from the
1560 1558 standard library. It implements only the needed subset.
1561 1559 """
1562 1560
1563 1561 def __init__(self):
1564 1562 self._values = {}
1565 1563
1566 1564 def copy(self):
1567 1565 clone = Config()
1568 1566 for section, values in self._values.items():
1569 1567 clone._values[section] = values.copy()
1570 1568 return clone
1571 1569
1572 1570 def __repr__(self):
1573 1571 return '<Config(%s sections) at %s>' % (
1574 1572 len(self._values), hex(id(self)))
1575 1573
1576 1574 def items(self, section):
1577 1575 return self._values.get(section, {}).iteritems()
1578 1576
1579 1577 def get(self, section, option):
1580 1578 return self._values.get(section, {}).get(option)
1581 1579
1582 1580 def set(self, section, option, value):
1583 1581 section_values = self._values.setdefault(section, {})
1584 1582 section_values[option] = value
1585 1583
1586 1584 def clear_section(self, section):
1587 1585 self._values[section] = {}
1588 1586
1589 1587 def serialize(self):
1590 1588 """
1591 1589 Creates a list of three tuples (section, key, value) representing
1592 1590 this config object.
1593 1591 """
1594 1592 items = []
1595 1593 for section in self._values:
1596 1594 for option, value in self._values[section].items():
1597 1595 items.append(
1598 1596 (safe_str(section), safe_str(option), safe_str(value)))
1599 1597 return items
1600 1598
1601 1599
1602 1600 class Diff(object):
1603 1601 """
1604 1602 Represents a diff result from a repository backend.
1605 1603
1606 1604 Subclasses have to provide a backend specific value for
1607 1605 :attr:`_header_re` and :attr:`_meta_re`.
1608 1606 """
1609 1607 _meta_re = None
1610 1608 _header_re = None
1611 1609
1612 1610 def __init__(self, raw_diff):
1613 1611 self.raw = raw_diff
1614 1612
1615 1613 def chunks(self):
1616 1614 """
1617 1615 split the diff in chunks of separate --git a/file b/file chunks
1618 1616 to make diffs consistent we must prepend with \n, and make sure
1619 1617 we can detect last chunk as this was also has special rule
1620 1618 """
1621 1619
1622 1620 diff_parts = ('\n' + self.raw).split('\ndiff --git')
1623 1621 header = diff_parts[0]
1624 1622
1625 1623 if self._meta_re:
1626 1624 match = self._meta_re.match(header)
1627 1625
1628 1626 chunks = diff_parts[1:]
1629 1627 total_chunks = len(chunks)
1630 1628
1631 1629 return (
1632 1630 DiffChunk(chunk, self, cur_chunk == total_chunks)
1633 1631 for cur_chunk, chunk in enumerate(chunks, start=1))
1634 1632
1635 1633
1636 1634 class DiffChunk(object):
1637 1635
1638 1636 def __init__(self, chunk, diff, last_chunk):
1639 1637 self._diff = diff
1640 1638
1641 1639 # since we split by \ndiff --git that part is lost from original diff
1642 1640 # we need to re-apply it at the end, EXCEPT ! if it's last chunk
1643 1641 if not last_chunk:
1644 1642 chunk += '\n'
1645 1643
1646 1644 match = self._diff._header_re.match(chunk)
1647 1645 self.header = match.groupdict()
1648 1646 self.diff = chunk[match.end():]
1649 1647 self.raw = chunk
1650 1648
1651 1649
1652 1650 class BasePathPermissionChecker(object):
1653 1651
1654 1652 @staticmethod
1655 1653 def create_from_patterns(includes, excludes):
1656 1654 if includes and '*' in includes and not excludes:
1657 1655 return AllPathPermissionChecker()
1658 1656 elif excludes and '*' in excludes:
1659 1657 return NonePathPermissionChecker()
1660 1658 else:
1661 1659 return PatternPathPermissionChecker(includes, excludes)
1662 1660
1663 1661 @property
1664 1662 def has_full_access(self):
1665 1663 raise NotImplemented()
1666 1664
1667 1665 def has_access(self, path):
1668 1666 raise NotImplemented()
1669 1667
1670 1668
1671 1669 class AllPathPermissionChecker(BasePathPermissionChecker):
1672 1670
1673 1671 @property
1674 1672 def has_full_access(self):
1675 1673 return True
1676 1674
1677 1675 def has_access(self, path):
1678 1676 return True
1679 1677
1680 1678
1681 1679 class NonePathPermissionChecker(BasePathPermissionChecker):
1682 1680
1683 1681 @property
1684 1682 def has_full_access(self):
1685 1683 return False
1686 1684
1687 1685 def has_access(self, path):
1688 1686 return False
1689 1687
1690 1688
1691 1689 class PatternPathPermissionChecker(BasePathPermissionChecker):
1692 1690
1693 1691 def __init__(self, includes, excludes):
1694 1692 self.includes = includes
1695 1693 self.excludes = excludes
1696 1694 self.includes_re = [] if not includes else [
1697 1695 re.compile(fnmatch.translate(pattern)) for pattern in includes]
1698 1696 self.excludes_re = [] if not excludes else [
1699 1697 re.compile(fnmatch.translate(pattern)) for pattern in excludes]
1700 1698
1701 1699 @property
1702 1700 def has_full_access(self):
1703 1701 return '*' in self.includes and not self.excludes
1704 1702
1705 1703 def has_access(self, path):
1706 1704 for regex in self.excludes_re:
1707 1705 if regex.match(path):
1708 1706 return False
1709 1707 for regex in self.includes_re:
1710 1708 if regex.match(path):
1711 1709 return True
1712 1710 return False
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,53 +1,72 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6
7 7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8 8
9 9 <p>
10 10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 11 <br/>
12 12 <code>
13 13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_name})}
14 14 </code>
15 15 </p>
16 16
17 17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), request=request)}
18 18 <div class="form">
19 19 <div class="fields">
20 20 ${h.submit('reset_cache_%s' % c.rhodecode_db_repo.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
21 21 </div>
22 22 </div>
23 23 ${h.end_form()}
24 24
25 25 </div>
26 26 </div>
27 27
28 28
29 29 <div class="panel panel-default">
30 30 <div class="panel-heading">
31 31 <h3 class="panel-title">
32 32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.rhodecode_db_repo.cache_keys)) % {'count': len(c.rhodecode_db_repo.cache_keys)})}
33 33 </h3>
34 34 </div>
35 35 <div class="panel-body">
36 36 <div class="field" >
37 37 <table class="rctable edit_cache">
38 38 <tr>
39 39 <th>${_('Prefix')}</th>
40 40 <th>${_('Key')}</th>
41 41 <th>${_('Active')}</th>
42 42 </tr>
43 43 %for cache in c.rhodecode_db_repo.cache_keys:
44 44 <tr>
45 45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
46 46 <td class="td-cachekey">${cache.cache_key}</td>
47 47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
48 48 </tr>
49 49 %endfor
50 50 </table>
51 51 </div>
52 52 </div>
53 53 </div>
54
55
56 <div class="panel panel-default">
57 <div class="panel-heading">
58 <h3 class="panel-title">${_('Diff Caches')}</h3>
59 </div>
60 <div class="panel-body">
61 <table class="rctable edit_cache">
62 <tr>
63 <td>${_('Cached diff files')}:</td>
64 <td>${c.cached_diff_count}</td>
65 </tr>
66 <tr>
67 <td>${_('Cached diff size')}:</td>
68 <td>${h.format_byte_size(c.cached_diff_size)}</td>
69 </tr>
70 </table>
71 </div>
72 </div>
General Comments 0
You need to be logged in to leave comments. Login now