##// END OF EJS Templates
caches: invalidate cache on remote side from repo settings alongside local cache.
super-admin -
r4748:28ed66c7 default
parent child
Show More
@@ -1,83 +1,93
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 from rhodecode.lib import helpers as h, rc_cache
30 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib import system_info
31 from rhodecode.lib import system_info
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.scm import ScmModel
33 from rhodecode.model.scm import ScmModel
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class RepoCachesView(RepoAppView):
38 class RepoCachesView(RepoAppView):
39 def load_default_context(self):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.admin')
44 @HasRepoPermissionAnyDecorator('repository.admin')
45 def repo_caches(self):
45 def repo_caches(self):
46 c = self.load_default_context()
46 c = self.load_default_context()
47 c.active = 'caches'
47 c.active = 'caches'
48 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
48 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
49 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
49 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
50 c.cached_diff_size = 0
50 c.cached_diff_size = 0
51 if os.path.isdir(cached_diffs_dir):
51 if os.path.isdir(cached_diffs_dir):
52 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
52 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
53 c.shadow_repos = c.rhodecode_db_repo.shadow_repos()
53 c.shadow_repos = c.rhodecode_db_repo.shadow_repos()
54
54
55 cache_namespace_uid = 'cache_repo.{}'.format(self.db_repo.repo_id)
55 cache_namespace_uid = 'cache_repo.{}'.format(self.db_repo.repo_id)
56 c.region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
56 c.region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
57 c.backend = c.region.backend
57 c.backend = c.region.backend
58 c.repo_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
58 c.repo_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
59
59
60 return self._get_template_context(c)
60 return self._get_template_context(c)
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @HasRepoPermissionAnyDecorator('repository.admin')
63 @HasRepoPermissionAnyDecorator('repository.admin')
64 @CSRFRequired()
64 @CSRFRequired()
65 def repo_caches_purge(self):
65 def repo_caches_purge(self):
66 _ = self.request.translate
66 _ = self.request.translate
67 c = self.load_default_context()
67 c = self.load_default_context()
68 c.active = 'caches'
68 c.active = 'caches'
69 invalidated = 0
69
70
70 try:
71 try:
71 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
72 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
72
73 Session().commit()
73 Session().commit()
74
74 invalidated +=1
75 h.flash(_('Cache invalidation successful'),
76 category='success')
77 except Exception:
75 except Exception:
78 log.exception("Exception during cache invalidation")
76 log.exception("Exception during cache invalidation")
79 h.flash(_('An error occurred during cache invalidation'),
77 h.flash(_('An error occurred during cache invalidation'),
80 category='error')
78 category='error')
81
79
80 try:
81 invalidated += 1
82 self.rhodecode_vcs_repo.vcsserver_invalidate_cache(delete=True)
83 except Exception:
84 log.exception("Exception during vcsserver cache invalidation")
85 h.flash(_('An error occurred during vcsserver cache invalidation'),
86 category='error')
87
88 if invalidated:
89 h.flash(_('Cache invalidation successful. Stages {}/2').format(invalidated),
90 category='success')
91
82 raise HTTPFound(h.route_path(
92 raise HTTPFound(h.route_path(
83 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
93 'edit_repo_caches', repo_name=self.db_repo_name))
@@ -1,1938 +1,1941
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Base module for all VCS systems
22 Base module for all VCS systems
23 """
23 """
24 import os
24 import os
25 import re
25 import re
26 import time
26 import time
27 import shutil
27 import shutil
28 import datetime
28 import datetime
29 import fnmatch
29 import fnmatch
30 import itertools
30 import itertools
31 import logging
31 import logging
32 import collections
32 import collections
33 import warnings
33 import warnings
34
34
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from pyramid import compat
37 from pyramid import compat
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.translation import lazy_ugettext
40 from rhodecode.translation import lazy_ugettext
41 from rhodecode.lib.utils2 import safe_str, safe_unicode, CachedProperty
41 from rhodecode.lib.utils2 import safe_str, safe_unicode, CachedProperty
42 from rhodecode.lib.vcs import connection
42 from rhodecode.lib.vcs import connection
43 from rhodecode.lib.vcs.utils import author_name, author_email
43 from rhodecode.lib.vcs.utils import author_name, author_email
44 from rhodecode.lib.vcs.conf import settings
44 from rhodecode.lib.vcs.conf import settings
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
46 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
47 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
47 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
48 NodeDoesNotExistError, NodeNotChangedError, VCSError,
48 NodeDoesNotExistError, NodeNotChangedError, VCSError,
49 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
49 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
50 RepositoryError)
50 RepositoryError)
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 FILEMODE_DEFAULT = 0o100644
56 FILEMODE_DEFAULT = 0o100644
57 FILEMODE_EXECUTABLE = 0o100755
57 FILEMODE_EXECUTABLE = 0o100755
58 EMPTY_COMMIT_ID = '0' * 40
58 EMPTY_COMMIT_ID = '0' * 40
59
59
60 _Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
60 _Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
61
61
62
62
63 class Reference(_Reference):
63 class Reference(_Reference):
64
64
65 @property
65 @property
66 def branch(self):
66 def branch(self):
67 if self.type == 'branch':
67 if self.type == 'branch':
68 return self.name
68 return self.name
69
69
70 @property
70 @property
71 def bookmark(self):
71 def bookmark(self):
72 if self.type == 'book':
72 if self.type == 'book':
73 return self.name
73 return self.name
74
74
75 @property
75 @property
76 def to_unicode(self):
76 def to_unicode(self):
77 return reference_to_unicode(self)
77 return reference_to_unicode(self)
78
78
79
79
80 def unicode_to_reference(raw):
80 def unicode_to_reference(raw):
81 """
81 """
82 Convert a unicode (or string) to a reference object.
82 Convert a unicode (or string) to a reference object.
83 If unicode evaluates to False it returns None.
83 If unicode evaluates to False it returns None.
84 """
84 """
85 if raw:
85 if raw:
86 refs = raw.split(':')
86 refs = raw.split(':')
87 return Reference(*refs)
87 return Reference(*refs)
88 else:
88 else:
89 return None
89 return None
90
90
91
91
92 def reference_to_unicode(ref):
92 def reference_to_unicode(ref):
93 """
93 """
94 Convert a reference object to unicode.
94 Convert a reference object to unicode.
95 If reference is None it returns None.
95 If reference is None it returns None.
96 """
96 """
97 if ref:
97 if ref:
98 return u':'.join(ref)
98 return u':'.join(ref)
99 else:
99 else:
100 return None
100 return None
101
101
102
102
103 class MergeFailureReason(object):
103 class MergeFailureReason(object):
104 """
104 """
105 Enumeration with all the reasons why the server side merge could fail.
105 Enumeration with all the reasons why the server side merge could fail.
106
106
107 DO NOT change the number of the reasons, as they may be stored in the
107 DO NOT change the number of the reasons, as they may be stored in the
108 database.
108 database.
109
109
110 Changing the name of a reason is acceptable and encouraged to deprecate old
110 Changing the name of a reason is acceptable and encouraged to deprecate old
111 reasons.
111 reasons.
112 """
112 """
113
113
114 # Everything went well.
114 # Everything went well.
115 NONE = 0
115 NONE = 0
116
116
117 # An unexpected exception was raised. Check the logs for more details.
117 # An unexpected exception was raised. Check the logs for more details.
118 UNKNOWN = 1
118 UNKNOWN = 1
119
119
120 # The merge was not successful, there are conflicts.
120 # The merge was not successful, there are conflicts.
121 MERGE_FAILED = 2
121 MERGE_FAILED = 2
122
122
123 # The merge succeeded but we could not push it to the target repository.
123 # The merge succeeded but we could not push it to the target repository.
124 PUSH_FAILED = 3
124 PUSH_FAILED = 3
125
125
126 # The specified target is not a head in the target repository.
126 # The specified target is not a head in the target repository.
127 TARGET_IS_NOT_HEAD = 4
127 TARGET_IS_NOT_HEAD = 4
128
128
129 # The source repository contains more branches than the target. Pushing
129 # The source repository contains more branches than the target. Pushing
130 # the merge will create additional branches in the target.
130 # the merge will create additional branches in the target.
131 HG_SOURCE_HAS_MORE_BRANCHES = 5
131 HG_SOURCE_HAS_MORE_BRANCHES = 5
132
132
133 # The target reference has multiple heads. That does not allow to correctly
133 # The target reference has multiple heads. That does not allow to correctly
134 # identify the target location. This could only happen for mercurial
134 # identify the target location. This could only happen for mercurial
135 # branches.
135 # branches.
136 HG_TARGET_HAS_MULTIPLE_HEADS = 6
136 HG_TARGET_HAS_MULTIPLE_HEADS = 6
137
137
138 # The target repository is locked
138 # The target repository is locked
139 TARGET_IS_LOCKED = 7
139 TARGET_IS_LOCKED = 7
140
140
141 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
141 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
142 # A involved commit could not be found.
142 # A involved commit could not be found.
143 _DEPRECATED_MISSING_COMMIT = 8
143 _DEPRECATED_MISSING_COMMIT = 8
144
144
145 # The target repo reference is missing.
145 # The target repo reference is missing.
146 MISSING_TARGET_REF = 9
146 MISSING_TARGET_REF = 9
147
147
148 # The source repo reference is missing.
148 # The source repo reference is missing.
149 MISSING_SOURCE_REF = 10
149 MISSING_SOURCE_REF = 10
150
150
151 # The merge was not successful, there are conflicts related to sub
151 # The merge was not successful, there are conflicts related to sub
152 # repositories.
152 # repositories.
153 SUBREPO_MERGE_FAILED = 11
153 SUBREPO_MERGE_FAILED = 11
154
154
155
155
156 class UpdateFailureReason(object):
156 class UpdateFailureReason(object):
157 """
157 """
158 Enumeration with all the reasons why the pull request update could fail.
158 Enumeration with all the reasons why the pull request update could fail.
159
159
160 DO NOT change the number of the reasons, as they may be stored in the
160 DO NOT change the number of the reasons, as they may be stored in the
161 database.
161 database.
162
162
163 Changing the name of a reason is acceptable and encouraged to deprecate old
163 Changing the name of a reason is acceptable and encouraged to deprecate old
164 reasons.
164 reasons.
165 """
165 """
166
166
167 # Everything went well.
167 # Everything went well.
168 NONE = 0
168 NONE = 0
169
169
170 # An unexpected exception was raised. Check the logs for more details.
170 # An unexpected exception was raised. Check the logs for more details.
171 UNKNOWN = 1
171 UNKNOWN = 1
172
172
173 # The pull request is up to date.
173 # The pull request is up to date.
174 NO_CHANGE = 2
174 NO_CHANGE = 2
175
175
176 # The pull request has a reference type that is not supported for update.
176 # The pull request has a reference type that is not supported for update.
177 WRONG_REF_TYPE = 3
177 WRONG_REF_TYPE = 3
178
178
179 # Update failed because the target reference is missing.
179 # Update failed because the target reference is missing.
180 MISSING_TARGET_REF = 4
180 MISSING_TARGET_REF = 4
181
181
182 # Update failed because the source reference is missing.
182 # Update failed because the source reference is missing.
183 MISSING_SOURCE_REF = 5
183 MISSING_SOURCE_REF = 5
184
184
185
185
186 class MergeResponse(object):
186 class MergeResponse(object):
187
187
188 # uses .format(**metadata) for variables
188 # uses .format(**metadata) for variables
189 MERGE_STATUS_MESSAGES = {
189 MERGE_STATUS_MESSAGES = {
190 MergeFailureReason.NONE: lazy_ugettext(
190 MergeFailureReason.NONE: lazy_ugettext(
191 u'This pull request can be automatically merged.'),
191 u'This pull request can be automatically merged.'),
192 MergeFailureReason.UNKNOWN: lazy_ugettext(
192 MergeFailureReason.UNKNOWN: lazy_ugettext(
193 u'This pull request cannot be merged because of an unhandled exception. '
193 u'This pull request cannot be merged because of an unhandled exception. '
194 u'{exception}'),
194 u'{exception}'),
195 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
195 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
196 u'This pull request cannot be merged because of merge conflicts. {unresolved_files}'),
196 u'This pull request cannot be merged because of merge conflicts. {unresolved_files}'),
197 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
197 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
198 u'This pull request could not be merged because push to '
198 u'This pull request could not be merged because push to '
199 u'target:`{target}@{merge_commit}` failed.'),
199 u'target:`{target}@{merge_commit}` failed.'),
200 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
200 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
201 u'This pull request cannot be merged because the target '
201 u'This pull request cannot be merged because the target '
202 u'`{target_ref.name}` is not a head.'),
202 u'`{target_ref.name}` is not a head.'),
203 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
203 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
204 u'This pull request cannot be merged because the source contains '
204 u'This pull request cannot be merged because the source contains '
205 u'more branches than the target.'),
205 u'more branches than the target.'),
206 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
206 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
207 u'This pull request cannot be merged because the target `{target_ref.name}` '
207 u'This pull request cannot be merged because the target `{target_ref.name}` '
208 u'has multiple heads: `{heads}`.'),
208 u'has multiple heads: `{heads}`.'),
209 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
209 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
210 u'This pull request cannot be merged because the target repository is '
210 u'This pull request cannot be merged because the target repository is '
211 u'locked by {locked_by}.'),
211 u'locked by {locked_by}.'),
212
212
213 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
213 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
214 u'This pull request cannot be merged because the target '
214 u'This pull request cannot be merged because the target '
215 u'reference `{target_ref.name}` is missing.'),
215 u'reference `{target_ref.name}` is missing.'),
216 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
216 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
217 u'This pull request cannot be merged because the source '
217 u'This pull request cannot be merged because the source '
218 u'reference `{source_ref.name}` is missing.'),
218 u'reference `{source_ref.name}` is missing.'),
219 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
219 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
220 u'This pull request cannot be merged because of conflicts related '
220 u'This pull request cannot be merged because of conflicts related '
221 u'to sub repositories.'),
221 u'to sub repositories.'),
222
222
223 # Deprecations
223 # Deprecations
224 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
224 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
225 u'This pull request cannot be merged because the target or the '
225 u'This pull request cannot be merged because the target or the '
226 u'source reference is missing.'),
226 u'source reference is missing.'),
227
227
228 }
228 }
229
229
230 def __init__(self, possible, executed, merge_ref, failure_reason, metadata=None):
230 def __init__(self, possible, executed, merge_ref, failure_reason, metadata=None):
231 self.possible = possible
231 self.possible = possible
232 self.executed = executed
232 self.executed = executed
233 self.merge_ref = merge_ref
233 self.merge_ref = merge_ref
234 self.failure_reason = failure_reason
234 self.failure_reason = failure_reason
235 self.metadata = metadata or {}
235 self.metadata = metadata or {}
236
236
237 def __repr__(self):
237 def __repr__(self):
238 return '<MergeResponse:{} {}>'.format(self.label, self.failure_reason)
238 return '<MergeResponse:{} {}>'.format(self.label, self.failure_reason)
239
239
240 def __eq__(self, other):
240 def __eq__(self, other):
241 same_instance = isinstance(other, self.__class__)
241 same_instance = isinstance(other, self.__class__)
242 return same_instance \
242 return same_instance \
243 and self.possible == other.possible \
243 and self.possible == other.possible \
244 and self.executed == other.executed \
244 and self.executed == other.executed \
245 and self.failure_reason == other.failure_reason
245 and self.failure_reason == other.failure_reason
246
246
247 @property
247 @property
248 def label(self):
248 def label(self):
249 label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if
249 label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if
250 not k.startswith('_'))
250 not k.startswith('_'))
251 return label_dict.get(self.failure_reason)
251 return label_dict.get(self.failure_reason)
252
252
253 @property
253 @property
254 def merge_status_message(self):
254 def merge_status_message(self):
255 """
255 """
256 Return a human friendly error message for the given merge status code.
256 Return a human friendly error message for the given merge status code.
257 """
257 """
258 msg = safe_unicode(self.MERGE_STATUS_MESSAGES[self.failure_reason])
258 msg = safe_unicode(self.MERGE_STATUS_MESSAGES[self.failure_reason])
259
259
260 try:
260 try:
261 return msg.format(**self.metadata)
261 return msg.format(**self.metadata)
262 except Exception:
262 except Exception:
263 log.exception('Failed to format %s message', self)
263 log.exception('Failed to format %s message', self)
264 return msg
264 return msg
265
265
266 def asdict(self):
266 def asdict(self):
267 data = {}
267 data = {}
268 for k in ['possible', 'executed', 'merge_ref', 'failure_reason',
268 for k in ['possible', 'executed', 'merge_ref', 'failure_reason',
269 'merge_status_message']:
269 'merge_status_message']:
270 data[k] = getattr(self, k)
270 data[k] = getattr(self, k)
271 return data
271 return data
272
272
273
273
274 class TargetRefMissing(ValueError):
274 class TargetRefMissing(ValueError):
275 pass
275 pass
276
276
277
277
278 class SourceRefMissing(ValueError):
278 class SourceRefMissing(ValueError):
279 pass
279 pass
280
280
281
281
282 class BaseRepository(object):
282 class BaseRepository(object):
283 """
283 """
284 Base Repository for final backends
284 Base Repository for final backends
285
285
286 .. attribute:: DEFAULT_BRANCH_NAME
286 .. attribute:: DEFAULT_BRANCH_NAME
287
287
288 name of default branch (i.e. "trunk" for svn, "master" for git etc.
288 name of default branch (i.e. "trunk" for svn, "master" for git etc.
289
289
290 .. attribute:: commit_ids
290 .. attribute:: commit_ids
291
291
292 list of all available commit ids, in ascending order
292 list of all available commit ids, in ascending order
293
293
294 .. attribute:: path
294 .. attribute:: path
295
295
296 absolute path to the repository
296 absolute path to the repository
297
297
298 .. attribute:: bookmarks
298 .. attribute:: bookmarks
299
299
300 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
300 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
301 there are no bookmarks or the backend implementation does not support
301 there are no bookmarks or the backend implementation does not support
302 bookmarks.
302 bookmarks.
303
303
304 .. attribute:: tags
304 .. attribute:: tags
305
305
306 Mapping from name to :term:`Commit ID` of the tag.
306 Mapping from name to :term:`Commit ID` of the tag.
307
307
308 """
308 """
309
309
310 DEFAULT_BRANCH_NAME = None
310 DEFAULT_BRANCH_NAME = None
311 DEFAULT_CONTACT = u"Unknown"
311 DEFAULT_CONTACT = u"Unknown"
312 DEFAULT_DESCRIPTION = u"unknown"
312 DEFAULT_DESCRIPTION = u"unknown"
313 EMPTY_COMMIT_ID = '0' * 40
313 EMPTY_COMMIT_ID = '0' * 40
314 COMMIT_ID_PAT = re.compile(r'[0-9a-fA-F]{40}')
314 COMMIT_ID_PAT = re.compile(r'[0-9a-fA-F]{40}')
315
315
316 path = None
316 path = None
317
317
318 _is_empty = None
318 _is_empty = None
319 _commit_ids = {}
319 _commit_ids = {}
320
320
321 def __init__(self, repo_path, config=None, create=False, **kwargs):
321 def __init__(self, repo_path, config=None, create=False, **kwargs):
322 """
322 """
323 Initializes repository. Raises RepositoryError if repository could
323 Initializes repository. Raises RepositoryError if repository could
324 not be find at the given ``repo_path`` or directory at ``repo_path``
324 not be find at the given ``repo_path`` or directory at ``repo_path``
325 exists and ``create`` is set to True.
325 exists and ``create`` is set to True.
326
326
327 :param repo_path: local path of the repository
327 :param repo_path: local path of the repository
328 :param config: repository configuration
328 :param config: repository configuration
329 :param create=False: if set to True, would try to create repository.
329 :param create=False: if set to True, would try to create repository.
330 :param src_url=None: if set, should be proper url from which repository
330 :param src_url=None: if set, should be proper url from which repository
331 would be cloned; requires ``create`` parameter to be set to True -
331 would be cloned; requires ``create`` parameter to be set to True -
332 raises RepositoryError if src_url is set and create evaluates to
332 raises RepositoryError if src_url is set and create evaluates to
333 False
333 False
334 """
334 """
335 raise NotImplementedError
335 raise NotImplementedError
336
336
337 def __repr__(self):
337 def __repr__(self):
338 return '<%s at %s>' % (self.__class__.__name__, self.path)
338 return '<%s at %s>' % (self.__class__.__name__, self.path)
339
339
340 def __len__(self):
340 def __len__(self):
341 return self.count()
341 return self.count()
342
342
343 def __eq__(self, other):
343 def __eq__(self, other):
344 same_instance = isinstance(other, self.__class__)
344 same_instance = isinstance(other, self.__class__)
345 return same_instance and other.path == self.path
345 return same_instance and other.path == self.path
346
346
347 def __ne__(self, other):
347 def __ne__(self, other):
348 return not self.__eq__(other)
348 return not self.__eq__(other)
349
349
350 def get_create_shadow_cache_pr_path(self, db_repo):
350 def get_create_shadow_cache_pr_path(self, db_repo):
351 path = db_repo.cached_diffs_dir
351 path = db_repo.cached_diffs_dir
352 if not os.path.exists(path):
352 if not os.path.exists(path):
353 os.makedirs(path, 0o755)
353 os.makedirs(path, 0o755)
354 return path
354 return path
355
355
356 @classmethod
356 @classmethod
357 def get_default_config(cls, default=None):
357 def get_default_config(cls, default=None):
358 config = Config()
358 config = Config()
359 if default and isinstance(default, list):
359 if default and isinstance(default, list):
360 for section, key, val in default:
360 for section, key, val in default:
361 config.set(section, key, val)
361 config.set(section, key, val)
362 return config
362 return config
363
363
364 @LazyProperty
364 @LazyProperty
365 def _remote(self):
365 def _remote(self):
366 raise NotImplementedError
366 raise NotImplementedError
367
367
368 def _heads(self, branch=None):
368 def _heads(self, branch=None):
369 return []
369 return []
370
370
371 @LazyProperty
371 @LazyProperty
372 def EMPTY_COMMIT(self):
372 def EMPTY_COMMIT(self):
373 return EmptyCommit(self.EMPTY_COMMIT_ID)
373 return EmptyCommit(self.EMPTY_COMMIT_ID)
374
374
375 @LazyProperty
375 @LazyProperty
376 def alias(self):
376 def alias(self):
377 for k, v in settings.BACKENDS.items():
377 for k, v in settings.BACKENDS.items():
378 if v.split('.')[-1] == str(self.__class__.__name__):
378 if v.split('.')[-1] == str(self.__class__.__name__):
379 return k
379 return k
380
380
381 @LazyProperty
381 @LazyProperty
382 def name(self):
382 def name(self):
383 return safe_unicode(os.path.basename(self.path))
383 return safe_unicode(os.path.basename(self.path))
384
384
385 @LazyProperty
385 @LazyProperty
386 def description(self):
386 def description(self):
387 raise NotImplementedError
387 raise NotImplementedError
388
388
389 def refs(self):
389 def refs(self):
390 """
390 """
391 returns a `dict` with branches, bookmarks, tags, and closed_branches
391 returns a `dict` with branches, bookmarks, tags, and closed_branches
392 for this repository
392 for this repository
393 """
393 """
394 return dict(
394 return dict(
395 branches=self.branches,
395 branches=self.branches,
396 branches_closed=self.branches_closed,
396 branches_closed=self.branches_closed,
397 tags=self.tags,
397 tags=self.tags,
398 bookmarks=self.bookmarks
398 bookmarks=self.bookmarks
399 )
399 )
400
400
401 @LazyProperty
401 @LazyProperty
402 def branches(self):
402 def branches(self):
403 """
403 """
404 A `dict` which maps branch names to commit ids.
404 A `dict` which maps branch names to commit ids.
405 """
405 """
406 raise NotImplementedError
406 raise NotImplementedError
407
407
408 @LazyProperty
408 @LazyProperty
409 def branches_closed(self):
409 def branches_closed(self):
410 """
410 """
411 A `dict` which maps tags names to commit ids.
411 A `dict` which maps tags names to commit ids.
412 """
412 """
413 raise NotImplementedError
413 raise NotImplementedError
414
414
415 @LazyProperty
415 @LazyProperty
416 def bookmarks(self):
416 def bookmarks(self):
417 """
417 """
418 A `dict` which maps tags names to commit ids.
418 A `dict` which maps tags names to commit ids.
419 """
419 """
420 raise NotImplementedError
420 raise NotImplementedError
421
421
422 @LazyProperty
422 @LazyProperty
423 def tags(self):
423 def tags(self):
424 """
424 """
425 A `dict` which maps tags names to commit ids.
425 A `dict` which maps tags names to commit ids.
426 """
426 """
427 raise NotImplementedError
427 raise NotImplementedError
428
428
429 @LazyProperty
429 @LazyProperty
430 def size(self):
430 def size(self):
431 """
431 """
432 Returns combined size in bytes for all repository files
432 Returns combined size in bytes for all repository files
433 """
433 """
434 tip = self.get_commit()
434 tip = self.get_commit()
435 return tip.size
435 return tip.size
436
436
437 def size_at_commit(self, commit_id):
437 def size_at_commit(self, commit_id):
438 commit = self.get_commit(commit_id)
438 commit = self.get_commit(commit_id)
439 return commit.size
439 return commit.size
440
440
441 def _check_for_empty(self):
441 def _check_for_empty(self):
442 no_commits = len(self._commit_ids) == 0
442 no_commits = len(self._commit_ids) == 0
443 if no_commits:
443 if no_commits:
444 # check on remote to be sure
444 # check on remote to be sure
445 return self._remote.is_empty()
445 return self._remote.is_empty()
446 else:
446 else:
447 return False
447 return False
448
448
449 def is_empty(self):
449 def is_empty(self):
450 if rhodecode.is_test:
450 if rhodecode.is_test:
451 return self._check_for_empty()
451 return self._check_for_empty()
452
452
453 if self._is_empty is None:
453 if self._is_empty is None:
454 # cache empty for production, but not tests
454 # cache empty for production, but not tests
455 self._is_empty = self._check_for_empty()
455 self._is_empty = self._check_for_empty()
456
456
457 return self._is_empty
457 return self._is_empty
458
458
459 @staticmethod
459 @staticmethod
460 def check_url(url, config):
460 def check_url(url, config):
461 """
461 """
462 Function will check given url and try to verify if it's a valid
462 Function will check given url and try to verify if it's a valid
463 link.
463 link.
464 """
464 """
465 raise NotImplementedError
465 raise NotImplementedError
466
466
467 @staticmethod
467 @staticmethod
468 def is_valid_repository(path):
468 def is_valid_repository(path):
469 """
469 """
470 Check if given `path` contains a valid repository of this backend
470 Check if given `path` contains a valid repository of this backend
471 """
471 """
472 raise NotImplementedError
472 raise NotImplementedError
473
473
474 # ==========================================================================
474 # ==========================================================================
475 # COMMITS
475 # COMMITS
476 # ==========================================================================
476 # ==========================================================================
477
477
478 @CachedProperty
478 @CachedProperty
479 def commit_ids(self):
479 def commit_ids(self):
480 raise NotImplementedError
480 raise NotImplementedError
481
481
482 def append_commit_id(self, commit_id):
482 def append_commit_id(self, commit_id):
483 if commit_id not in self.commit_ids:
483 if commit_id not in self.commit_ids:
484 self._rebuild_cache(self.commit_ids + [commit_id])
484 self._rebuild_cache(self.commit_ids + [commit_id])
485
485
486 # clear cache
486 # clear cache
487 self._invalidate_prop_cache('commit_ids')
487 self._invalidate_prop_cache('commit_ids')
488 self._is_empty = False
488 self._is_empty = False
489
489
490 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
490 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
491 translate_tag=None, maybe_unreachable=False, reference_obj=None):
491 translate_tag=None, maybe_unreachable=False, reference_obj=None):
492 """
492 """
493 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
493 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
494 are both None, most recent commit is returned.
494 are both None, most recent commit is returned.
495
495
496 :param pre_load: Optional. List of commit attributes to load.
496 :param pre_load: Optional. List of commit attributes to load.
497
497
498 :raises ``EmptyRepositoryError``: if there are no commits
498 :raises ``EmptyRepositoryError``: if there are no commits
499 """
499 """
500 raise NotImplementedError
500 raise NotImplementedError
501
501
502 def __iter__(self):
502 def __iter__(self):
503 for commit_id in self.commit_ids:
503 for commit_id in self.commit_ids:
504 yield self.get_commit(commit_id=commit_id)
504 yield self.get_commit(commit_id=commit_id)
505
505
506 def get_commits(
506 def get_commits(
507 self, start_id=None, end_id=None, start_date=None, end_date=None,
507 self, start_id=None, end_id=None, start_date=None, end_date=None,
508 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
508 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
509 """
509 """
510 Returns iterator of `BaseCommit` objects from start to end
510 Returns iterator of `BaseCommit` objects from start to end
511 not inclusive. This should behave just like a list, ie. end is not
511 not inclusive. This should behave just like a list, ie. end is not
512 inclusive.
512 inclusive.
513
513
514 :param start_id: None or str, must be a valid commit id
514 :param start_id: None or str, must be a valid commit id
515 :param end_id: None or str, must be a valid commit id
515 :param end_id: None or str, must be a valid commit id
516 :param start_date:
516 :param start_date:
517 :param end_date:
517 :param end_date:
518 :param branch_name:
518 :param branch_name:
519 :param show_hidden:
519 :param show_hidden:
520 :param pre_load:
520 :param pre_load:
521 :param translate_tags:
521 :param translate_tags:
522 """
522 """
523 raise NotImplementedError
523 raise NotImplementedError
524
524
525 def __getitem__(self, key):
525 def __getitem__(self, key):
526 """
526 """
527 Allows index based access to the commit objects of this repository.
527 Allows index based access to the commit objects of this repository.
528 """
528 """
529 pre_load = ["author", "branch", "date", "message", "parents"]
529 pre_load = ["author", "branch", "date", "message", "parents"]
530 if isinstance(key, slice):
530 if isinstance(key, slice):
531 return self._get_range(key, pre_load)
531 return self._get_range(key, pre_load)
532 return self.get_commit(commit_idx=key, pre_load=pre_load)
532 return self.get_commit(commit_idx=key, pre_load=pre_load)
533
533
534 def _get_range(self, slice_obj, pre_load):
534 def _get_range(self, slice_obj, pre_load):
535 for commit_id in self.commit_ids.__getitem__(slice_obj):