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