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