##// END OF EJS Templates

Compare Commits r1106:490ebeeb75af...r1108:ebe0247cd154

Target:

Source:

Time Author Commit Description
Martin Bornhold
r1106:490ebeeb75af
subrepo: Add merge failure reason code ad message for subrepo merge conflicts.
Martin Bornhold
r1107:6bc055e1504d
subrepo: Add exception for subrepo merge errors.
Martin Bornhold
r1108:ebe0247cd154
subrepo: Handle subrepo merge errors.
@@ -1,1545 +1,1549
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Base module for all VCS systems
22 Base module for all VCS systems
23 """
23 """
24
24
25 import collections
25 import collections
26 import datetime
26 import datetime
27 import itertools
27 import itertools
28 import logging
28 import logging
29 import os
29 import os
30 import time
30 import time
31 import warnings
31 import warnings
32
32
33 from zope.cachedescriptors.property import Lazy as LazyProperty
33 from zope.cachedescriptors.property import Lazy as LazyProperty
34
34
35 from rhodecode.lib.utils2 import safe_str, safe_unicode
35 from rhodecode.lib.utils2 import safe_str, safe_unicode
36 from rhodecode.lib.vcs import connection
36 from rhodecode.lib.vcs import connection
37 from rhodecode.lib.vcs.utils import author_name, author_email
37 from rhodecode.lib.vcs.utils import author_name, author_email
38 from rhodecode.lib.vcs.conf import settings
38 from rhodecode.lib.vcs.conf import settings
39 from rhodecode.lib.vcs.exceptions import (
39 from rhodecode.lib.vcs.exceptions import (
40 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
40 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
41 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
41 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
42 NodeDoesNotExistError, NodeNotChangedError, VCSError,
42 NodeDoesNotExistError, NodeNotChangedError, VCSError,
43 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
43 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
44 RepositoryError)
44 RepositoryError)
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 FILEMODE_DEFAULT = 0100644
50 FILEMODE_DEFAULT = 0100644
51 FILEMODE_EXECUTABLE = 0100755
51 FILEMODE_EXECUTABLE = 0100755
52
52
53 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
53 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
54 MergeResponse = collections.namedtuple(
54 MergeResponse = collections.namedtuple(
55 'MergeResponse',
55 'MergeResponse',
56 ('possible', 'executed', 'merge_ref', 'failure_reason'))
56 ('possible', 'executed', 'merge_ref', 'failure_reason'))
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
108 # repositories.
109 SUBREPO_MERGE_FAILED = 11
110
107
111
108 class UpdateFailureReason(object):
112 class UpdateFailureReason(object):
109 """
113 """
110 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.
111
115
112 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
113 database.
117 database.
114
118
115 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
116 reasons.
120 reasons.
117 """
121 """
118
122
119 # Everything went well.
123 # Everything went well.
120 NONE = 0
124 NONE = 0
121
125
122 # An unexpected exception was raised. Check the logs for more details.
126 # An unexpected exception was raised. Check the logs for more details.
123 UNKNOWN = 1
127 UNKNOWN = 1
124
128
125 # The pull request is up to date.
129 # The pull request is up to date.
126 NO_CHANGE = 2
130 NO_CHANGE = 2
127
131
128 # 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.
129 WRONG_REF_TPYE = 3
133 WRONG_REF_TPYE = 3
130
134
131 # Update failed because the target reference is missing.
135 # Update failed because the target reference is missing.
132 MISSING_TARGET_REF = 4
136 MISSING_TARGET_REF = 4
133
137
134 # Update failed because the source reference is missing.
138 # Update failed because the source reference is missing.
135 MISSING_SOURCE_REF = 5
139 MISSING_SOURCE_REF = 5
136
140
137
141
138 class BaseRepository(object):
142 class BaseRepository(object):
139 """
143 """
140 Base Repository for final backends
144 Base Repository for final backends
141
145
142 .. attribute:: DEFAULT_BRANCH_NAME
146 .. attribute:: DEFAULT_BRANCH_NAME
143
147
144 name of default branch (i.e. "trunk" for svn, "master" for git etc.
148 name of default branch (i.e. "trunk" for svn, "master" for git etc.
145
149
146 .. attribute:: commit_ids
150 .. attribute:: commit_ids
147
151
148 list of all available commit ids, in ascending order
152 list of all available commit ids, in ascending order
149
153
150 .. attribute:: path
154 .. attribute:: path
151
155
152 absolute path to the repository
156 absolute path to the repository
153
157
154 .. attribute:: bookmarks
158 .. attribute:: bookmarks
155
159
156 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
160 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
157 there are no bookmarks or the backend implementation does not support
161 there are no bookmarks or the backend implementation does not support
158 bookmarks.
162 bookmarks.
159
163
160 .. attribute:: tags
164 .. attribute:: tags
161
165
162 Mapping from name to :term:`Commit ID` of the tag.
166 Mapping from name to :term:`Commit ID` of the tag.
163
167
164 """
168 """
165
169
166 DEFAULT_BRANCH_NAME = None
170 DEFAULT_BRANCH_NAME = None
167 DEFAULT_CONTACT = u"Unknown"
171 DEFAULT_CONTACT = u"Unknown"
168 DEFAULT_DESCRIPTION = u"unknown"
172 DEFAULT_DESCRIPTION = u"unknown"
169 EMPTY_COMMIT_ID = '0' * 40
173 EMPTY_COMMIT_ID = '0' * 40
170
174
171 path = None
175 path = None
172
176
173 def __init__(self, repo_path, config=None, create=False, **kwargs):
177 def __init__(self, repo_path, config=None, create=False, **kwargs):
174 """
178 """
175 Initializes repository. Raises RepositoryError if repository could
179 Initializes repository. Raises RepositoryError if repository could
176 not be find at the given ``repo_path`` or directory at ``repo_path``
180 not be find at the given ``repo_path`` or directory at ``repo_path``
177 exists and ``create`` is set to True.
181 exists and ``create`` is set to True.
178
182
179 :param repo_path: local path of the repository
183 :param repo_path: local path of the repository
180 :param config: repository configuration
184 :param config: repository configuration
181 :param create=False: if set to True, would try to create repository.
185 :param create=False: if set to True, would try to create repository.
182 :param src_url=None: if set, should be proper url from which repository
186 :param src_url=None: if set, should be proper url from which repository
183 would be cloned; requires ``create`` parameter to be set to True -
187 would be cloned; requires ``create`` parameter to be set to True -
184 raises RepositoryError if src_url is set and create evaluates to
188 raises RepositoryError if src_url is set and create evaluates to
185 False
189 False
186 """
190 """
187 raise NotImplementedError
191 raise NotImplementedError
188
192
189 def __repr__(self):
193 def __repr__(self):
190 return '<%s at %s>' % (self.__class__.__name__, self.path)
194 return '<%s at %s>' % (self.__class__.__name__, self.path)
191
195
192 def __len__(self):
196 def __len__(self):
193 return self.count()
197 return self.count()
194
198
195 def __eq__(self, other):
199 def __eq__(self, other):
196 same_instance = isinstance(other, self.__class__)
200 same_instance = isinstance(other, self.__class__)
197 return same_instance and other.path == self.path
201 return same_instance and other.path == self.path
198
202
199 def __ne__(self, other):
203 def __ne__(self, other):
200 return not self.__eq__(other)
204 return not self.__eq__(other)
201
205
202 @LazyProperty
206 @LazyProperty
203 def EMPTY_COMMIT(self):
207 def EMPTY_COMMIT(self):
204 return EmptyCommit(self.EMPTY_COMMIT_ID)
208 return EmptyCommit(self.EMPTY_COMMIT_ID)
205
209
206 @LazyProperty
210 @LazyProperty
207 def alias(self):
211 def alias(self):
208 for k, v in settings.BACKENDS.items():
212 for k, v in settings.BACKENDS.items():
209 if v.split('.')[-1] == str(self.__class__.__name__):
213 if v.split('.')[-1] == str(self.__class__.__name__):
210 return k
214 return k
211
215
212 @LazyProperty
216 @LazyProperty
213 def name(self):
217 def name(self):
214 return safe_unicode(os.path.basename(self.path))
218 return safe_unicode(os.path.basename(self.path))
215
219
216 @LazyProperty
220 @LazyProperty
217 def description(self):
221 def description(self):
218 raise NotImplementedError
222 raise NotImplementedError
219
223
220 def refs(self):
224 def refs(self):
221 """
225 """
222 returns a `dict` with branches, bookmarks, tags, and closed_branches
226 returns a `dict` with branches, bookmarks, tags, and closed_branches
223 for this repository
227 for this repository
224 """
228 """
225 raise NotImplementedError
229 raise NotImplementedError
226
230
227 @LazyProperty
231 @LazyProperty
228 def branches(self):
232 def branches(self):
229 """
233 """
230 A `dict` which maps branch names to commit ids.
234 A `dict` which maps branch names to commit ids.
231 """
235 """
232 raise NotImplementedError
236 raise NotImplementedError
233
237
234 @LazyProperty
238 @LazyProperty
235 def size(self):
239 def size(self):
236 """
240 """
237 Returns combined size in bytes for all repository files
241 Returns combined size in bytes for all repository files
238 """
242 """
239 tip = self.get_commit()
243 tip = self.get_commit()
240 return tip.size
244 return tip.size
241
245
242 def size_at_commit(self, commit_id):
246 def size_at_commit(self, commit_id):
243 commit = self.get_commit(commit_id)
247 commit = self.get_commit(commit_id)
244 return commit.size
248 return commit.size
245
249
246 def is_empty(self):
250 def is_empty(self):
247 return not bool(self.commit_ids)
251 return not bool(self.commit_ids)
248
252
249 @staticmethod
253 @staticmethod
250 def check_url(url, config):
254 def check_url(url, config):
251 """
255 """
252 Function will check given url and try to verify if it's a valid
256 Function will check given url and try to verify if it's a valid
253 link.
257 link.
254 """
258 """
255 raise NotImplementedError
259 raise NotImplementedError
256
260
257 @staticmethod
261 @staticmethod
258 def is_valid_repository(path):
262 def is_valid_repository(path):
259 """
263 """
260 Check if given `path` contains a valid repository of this backend
264 Check if given `path` contains a valid repository of this backend
261 """
265 """
262 raise NotImplementedError
266 raise NotImplementedError
263
267
264 # ==========================================================================
268 # ==========================================================================
265 # COMMITS
269 # COMMITS
266 # ==========================================================================
270 # ==========================================================================
267
271
268 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
272 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
269 """
273 """
270 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
274 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
271 are both None, most recent commit is returned.
275 are both None, most recent commit is returned.
272
276
273 :param pre_load: Optional. List of commit attributes to load.
277 :param pre_load: Optional. List of commit attributes to load.
274
278
275 :raises ``EmptyRepositoryError``: if there are no commits
279 :raises ``EmptyRepositoryError``: if there are no commits
276 """
280 """
277 raise NotImplementedError
281 raise NotImplementedError
278
282
279 def __iter__(self):
283 def __iter__(self):
280 for commit_id in self.commit_ids:
284 for commit_id in self.commit_ids:
281 yield self.get_commit(commit_id=commit_id)
285 yield self.get_commit(commit_id=commit_id)
282
286
283 def get_commits(
287 def get_commits(
284 self, start_id=None, end_id=None, start_date=None, end_date=None,
288 self, start_id=None, end_id=None, start_date=None, end_date=None,
285 branch_name=None, pre_load=None):
289 branch_name=None, pre_load=None):
286 """
290 """
287 Returns iterator of `BaseCommit` objects from start to end
291 Returns iterator of `BaseCommit` objects from start to end
288 not inclusive. This should behave just like a list, ie. end is not
292 not inclusive. This should behave just like a list, ie. end is not
289 inclusive.
293 inclusive.
290
294
291 :param start_id: None or str, must be a valid commit id
295 :param start_id: None or str, must be a valid commit id
292 :param end_id: None or str, must be a valid commit id
296 :param end_id: None or str, must be a valid commit id
293 :param start_date:
297 :param start_date:
294 :param end_date:
298 :param end_date:
295 :param branch_name:
299 :param branch_name:
296 :param pre_load:
300 :param pre_load:
297 """
301 """
298 raise NotImplementedError
302 raise NotImplementedError
299
303
300 def __getitem__(self, key):
304 def __getitem__(self, key):
301 """
305 """
302 Allows index based access to the commit objects of this repository.
306 Allows index based access to the commit objects of this repository.
303 """
307 """
304 pre_load = ["author", "branch", "date", "message", "parents"]
308 pre_load = ["author", "branch", "date", "message", "parents"]
305 if isinstance(key, slice):
309 if isinstance(key, slice):
306 return self._get_range(key, pre_load)
310 return self._get_range(key, pre_load)
307 return self.get_commit(commit_idx=key, pre_load=pre_load)
311 return self.get_commit(commit_idx=key, pre_load=pre_load)
308
312
309 def _get_range(self, slice_obj, pre_load):
313 def _get_range(self, slice_obj, pre_load):
310 for commit_id in self.commit_ids.__getitem__(slice_obj):
314 for commit_id in self.commit_ids.__getitem__(slice_obj):
311 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
315 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
312
316
313 def count(self):
317 def count(self):
314 return len(self.commit_ids)
318 return len(self.commit_ids)
315
319
316 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
320 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
317 """
321 """
318 Creates and returns a tag for the given ``commit_id``.
322 Creates and returns a tag for the given ``commit_id``.
319
323
320 :param name: name for new tag
324 :param name: name for new tag
321 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
325 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
322 :param commit_id: commit id for which new tag would be created
326 :param commit_id: commit id for which new tag would be created
323 :param message: message of the tag's commit
327 :param message: message of the tag's commit
324 :param date: date of tag's commit
328 :param date: date of tag's commit
325
329
326 :raises TagAlreadyExistError: if tag with same name already exists
330 :raises TagAlreadyExistError: if tag with same name already exists
327 """
331 """
328 raise NotImplementedError
332 raise NotImplementedError
329
333
330 def remove_tag(self, name, user, message=None, date=None):
334 def remove_tag(self, name, user, message=None, date=None):
331 """
335 """
332 Removes tag with the given ``name``.
336 Removes tag with the given ``name``.
333
337
334 :param name: name of the tag to be removed
338 :param name: name of the tag to be removed
335 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
339 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
336 :param message: message of the tag's removal commit
340 :param message: message of the tag's removal commit
337 :param date: date of tag's removal commit
341 :param date: date of tag's removal commit
338
342
339 :raises TagDoesNotExistError: if tag with given name does not exists
343 :raises TagDoesNotExistError: if tag with given name does not exists
340 """
344 """
341 raise NotImplementedError
345 raise NotImplementedError
342
346
343 def get_diff(
347 def get_diff(
344 self, commit1, commit2, path=None, ignore_whitespace=False,
348 self, commit1, commit2, path=None, ignore_whitespace=False,
345 context=3, path1=None):
349 context=3, path1=None):
346 """
350 """
347 Returns (git like) *diff*, as plain text. Shows changes introduced by
351 Returns (git like) *diff*, as plain text. Shows changes introduced by
348 `commit2` since `commit1`.
352 `commit2` since `commit1`.
349
353
350 :param commit1: Entry point from which diff is shown. Can be
354 :param commit1: Entry point from which diff is shown. Can be
351 ``self.EMPTY_COMMIT`` - in this case, patch showing all
355 ``self.EMPTY_COMMIT`` - in this case, patch showing all
352 the changes since empty state of the repository until `commit2`
356 the changes since empty state of the repository until `commit2`
353 :param commit2: Until which commit changes should be shown.
357 :param commit2: Until which commit changes should be shown.
354 :param path: Can be set to a path of a file to create a diff of that
358 :param path: Can be set to a path of a file to create a diff of that
355 file. If `path1` is also set, this value is only associated to
359 file. If `path1` is also set, this value is only associated to
356 `commit2`.
360 `commit2`.
357 :param ignore_whitespace: If set to ``True``, would not show whitespace
361 :param ignore_whitespace: If set to ``True``, would not show whitespace
358 changes. Defaults to ``False``.
362 changes. Defaults to ``False``.
359 :param context: How many lines before/after changed lines should be
363 :param context: How many lines before/after changed lines should be
360 shown. Defaults to ``3``.
364 shown. Defaults to ``3``.
361 :param path1: Can be set to a path to associate with `commit1`. This
365 :param path1: Can be set to a path to associate with `commit1`. This
362 parameter works only for backends which support diff generation for
366 parameter works only for backends which support diff generation for
363 different paths. Other backends will raise a `ValueError` if `path1`
367 different paths. Other backends will raise a `ValueError` if `path1`
364 is set and has a different value than `path`.
368 is set and has a different value than `path`.
365 """
369 """
366 raise NotImplementedError
370 raise NotImplementedError
367
371
368 def strip(self, commit_id, branch=None):
372 def strip(self, commit_id, branch=None):
369 """
373 """
370 Strip given commit_id from the repository
374 Strip given commit_id from the repository
371 """
375 """
372 raise NotImplementedError
376 raise NotImplementedError
373
377
374 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
378 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
375 """
379 """
376 Return a latest common ancestor commit if one exists for this repo
380 Return a latest common ancestor commit if one exists for this repo
377 `commit_id1` vs `commit_id2` from `repo2`.
381 `commit_id1` vs `commit_id2` from `repo2`.
378
382
379 :param commit_id1: Commit it from this repository to use as a
383 :param commit_id1: Commit it from this repository to use as a
380 target for the comparison.
384 target for the comparison.
381 :param commit_id2: Source commit id to use for comparison.
385 :param commit_id2: Source commit id to use for comparison.
382 :param repo2: Source repository to use for comparison.
386 :param repo2: Source repository to use for comparison.
383 """
387 """
384 raise NotImplementedError
388 raise NotImplementedError
385
389
386 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
390 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
387 """
391 """
388 Compare this repository's revision `commit_id1` with `commit_id2`.
392 Compare this repository's revision `commit_id1` with `commit_id2`.
389
393
390 Returns a tuple(commits, ancestor) that would be merged from
394 Returns a tuple(commits, ancestor) that would be merged from
391 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
395 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
392 will be returned as ancestor.
396 will be returned as ancestor.
393
397
394 :param commit_id1: Commit it from this repository to use as a
398 :param commit_id1: Commit it from this repository to use as a
395 target for the comparison.
399 target for the comparison.
396 :param commit_id2: Source commit id to use for comparison.
400 :param commit_id2: Source commit id to use for comparison.
397 :param repo2: Source repository to use for comparison.
401 :param repo2: Source repository to use for comparison.
398 :param merge: If set to ``True`` will do a merge compare which also
402 :param merge: If set to ``True`` will do a merge compare which also
399 returns the common ancestor.
403 returns the common ancestor.
400 :param pre_load: Optional. List of commit attributes to load.
404 :param pre_load: Optional. List of commit attributes to load.
401 """
405 """
402 raise NotImplementedError
406 raise NotImplementedError
403
407
404 def merge(self, target_ref, source_repo, source_ref, workspace_id,
408 def merge(self, target_ref, source_repo, source_ref, workspace_id,
405 user_name='', user_email='', message='', dry_run=False,
409 user_name='', user_email='', message='', dry_run=False,
406 use_rebase=False):
410 use_rebase=False):
407 """
411 """
408 Merge the revisions specified in `source_ref` from `source_repo`
412 Merge the revisions specified in `source_ref` from `source_repo`
409 onto the `target_ref` of this repository.
413 onto the `target_ref` of this repository.
410
414
411 `source_ref` and `target_ref` are named tupls with the following
415 `source_ref` and `target_ref` are named tupls with the following
412 fields `type`, `name` and `commit_id`.
416 fields `type`, `name` and `commit_id`.
413
417
414 Returns a MergeResponse named tuple with the following fields
418 Returns a MergeResponse named tuple with the following fields
415 'possible', 'executed', 'source_commit', 'target_commit',
419 'possible', 'executed', 'source_commit', 'target_commit',
416 'merge_commit'.
420 'merge_commit'.
417
421
418 :param target_ref: `target_ref` points to the commit on top of which
422 :param target_ref: `target_ref` points to the commit on top of which
419 the `source_ref` should be merged.
423 the `source_ref` should be merged.
420 :param source_repo: The repository that contains the commits to be
424 :param source_repo: The repository that contains the commits to be
421 merged.
425 merged.
422 :param source_ref: `source_ref` points to the topmost commit from
426 :param source_ref: `source_ref` points to the topmost commit from
423 the `source_repo` which should be merged.
427 the `source_repo` which should be merged.
424 :param workspace_id: `workspace_id` unique identifier.
428 :param workspace_id: `workspace_id` unique identifier.
425 :param user_name: Merge commit `user_name`.
429 :param user_name: Merge commit `user_name`.
426 :param user_email: Merge commit `user_email`.
430 :param user_email: Merge commit `user_email`.
427 :param message: Merge commit `message`.
431 :param message: Merge commit `message`.
428 :param dry_run: If `True` the merge will not take place.
432 :param dry_run: If `True` the merge will not take place.
429 :param use_rebase: If `True` commits from the source will be rebased
433 :param use_rebase: If `True` commits from the source will be rebased
430 on top of the target instead of being merged.
434 on top of the target instead of being merged.
431 """
435 """
432 if dry_run:
436 if dry_run:
433 message = message or 'dry_run_merge_message'
437 message = message or 'dry_run_merge_message'
434 user_email = user_email or 'dry-run-merge@rhodecode.com'
438 user_email = user_email or 'dry-run-merge@rhodecode.com'
435 user_name = user_name or 'Dry-Run User'
439 user_name = user_name or 'Dry-Run User'
436 else:
440 else:
437 if not user_name:
441 if not user_name:
438 raise ValueError('user_name cannot be empty')
442 raise ValueError('user_name cannot be empty')
439 if not user_email:
443 if not user_email:
440 raise ValueError('user_email cannot be empty')
444 raise ValueError('user_email cannot be empty')
441 if not message:
445 if not message:
442 raise ValueError('message cannot be empty')
446 raise ValueError('message cannot be empty')
443
447
444 shadow_repository_path = self._maybe_prepare_merge_workspace(
448 shadow_repository_path = self._maybe_prepare_merge_workspace(
445 workspace_id, target_ref)
449 workspace_id, target_ref)
446
450
447 try:
451 try:
448 return self._merge_repo(
452 return self._merge_repo(
449 shadow_repository_path, target_ref, source_repo,
453 shadow_repository_path, target_ref, source_repo,
450 source_ref, message, user_name, user_email, dry_run=dry_run,
454 source_ref, message, user_name, user_email, dry_run=dry_run,
451 use_rebase=use_rebase)
455 use_rebase=use_rebase)
452 except RepositoryError:
456 except RepositoryError:
453 log.exception(
457 log.exception(
454 'Unexpected failure when running merge, dry-run=%s',
458 'Unexpected failure when running merge, dry-run=%s',
455 dry_run)
459 dry_run)
456 return MergeResponse(
460 return MergeResponse(
457 False, False, None, MergeFailureReason.UNKNOWN)
461 False, False, None, MergeFailureReason.UNKNOWN)
458
462
459 def _merge_repo(self, shadow_repository_path, target_ref,
463 def _merge_repo(self, shadow_repository_path, target_ref,
460 source_repo, source_ref, merge_message,
464 source_repo, source_ref, merge_message,
461 merger_name, merger_email, dry_run=False, use_rebase=False):
465 merger_name, merger_email, dry_run=False, use_rebase=False):
462 """Internal implementation of merge."""
466 """Internal implementation of merge."""
463 raise NotImplementedError
467 raise NotImplementedError
464
468
465 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref):
469 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref):
466 """
470 """
467 Create the merge workspace.
471 Create the merge workspace.
468
472
469 :param workspace_id: `workspace_id` unique identifier.
473 :param workspace_id: `workspace_id` unique identifier.
470 """
474 """
471 raise NotImplementedError
475 raise NotImplementedError
472
476
473 def cleanup_merge_workspace(self, workspace_id):
477 def cleanup_merge_workspace(self, workspace_id):
474 """
478 """
475 Remove merge workspace.
479 Remove merge workspace.
476
480
477 This function MUST not fail in case there is no workspace associated to
481 This function MUST not fail in case there is no workspace associated to
478 the given `workspace_id`.
482 the given `workspace_id`.
479
483
480 :param workspace_id: `workspace_id` unique identifier.
484 :param workspace_id: `workspace_id` unique identifier.
481 """
485 """
482 raise NotImplementedError
486 raise NotImplementedError
483
487
484 # ========== #
488 # ========== #
485 # COMMIT API #
489 # COMMIT API #
486 # ========== #
490 # ========== #
487
491
488 @LazyProperty
492 @LazyProperty
489 def in_memory_commit(self):
493 def in_memory_commit(self):
490 """
494 """
491 Returns :class:`InMemoryCommit` object for this repository.
495 Returns :class:`InMemoryCommit` object for this repository.
492 """
496 """
493 raise NotImplementedError
497 raise NotImplementedError
494
498
495 # ======================== #
499 # ======================== #
496 # UTILITIES FOR SUBCLASSES #
500 # UTILITIES FOR SUBCLASSES #
497 # ======================== #
501 # ======================== #
498
502
499 def _validate_diff_commits(self, commit1, commit2):
503 def _validate_diff_commits(self, commit1, commit2):
500 """
504 """
501 Validates that the given commits are related to this repository.
505 Validates that the given commits are related to this repository.
502
506
503 Intended as a utility for sub classes to have a consistent validation
507 Intended as a utility for sub classes to have a consistent validation
504 of input parameters in methods like :meth:`get_diff`.
508 of input parameters in methods like :meth:`get_diff`.
505 """
509 """
506 self._validate_commit(commit1)
510 self._validate_commit(commit1)
507 self._validate_commit(commit2)
511 self._validate_commit(commit2)
508 if (isinstance(commit1, EmptyCommit) and
512 if (isinstance(commit1, EmptyCommit) and
509 isinstance(commit2, EmptyCommit)):
513 isinstance(commit2, EmptyCommit)):
510 raise ValueError("Cannot compare two empty commits")
514 raise ValueError("Cannot compare two empty commits")
511
515
512 def _validate_commit(self, commit):
516 def _validate_commit(self, commit):
513 if not isinstance(commit, BaseCommit):
517 if not isinstance(commit, BaseCommit):
514 raise TypeError(
518 raise TypeError(
515 "%s is not of type BaseCommit" % repr(commit))
519 "%s is not of type BaseCommit" % repr(commit))
516 if commit.repository != self and not isinstance(commit, EmptyCommit):
520 if commit.repository != self and not isinstance(commit, EmptyCommit):
517 raise ValueError(
521 raise ValueError(
518 "Commit %s must be a valid commit from this repository %s, "
522 "Commit %s must be a valid commit from this repository %s, "
519 "related to this repository instead %s." %
523 "related to this repository instead %s." %
520 (commit, self, commit.repository))
524 (commit, self, commit.repository))
521
525
522 def _validate_commit_id(self, commit_id):
526 def _validate_commit_id(self, commit_id):
523 if not isinstance(commit_id, basestring):
527 if not isinstance(commit_id, basestring):
524 raise TypeError("commit_id must be a string value")
528 raise TypeError("commit_id must be a string value")
525
529
526 def _validate_commit_idx(self, commit_idx):
530 def _validate_commit_idx(self, commit_idx):
527 if not isinstance(commit_idx, (int, long)):
531 if not isinstance(commit_idx, (int, long)):
528 raise TypeError("commit_idx must be a numeric value")
532 raise TypeError("commit_idx must be a numeric value")
529
533
530 def _validate_branch_name(self, branch_name):
534 def _validate_branch_name(self, branch_name):
531 if branch_name and branch_name not in self.branches_all:
535 if branch_name and branch_name not in self.branches_all:
532 msg = ("Branch %s not found in %s" % (branch_name, self))
536 msg = ("Branch %s not found in %s" % (branch_name, self))
533 raise BranchDoesNotExistError(msg)
537 raise BranchDoesNotExistError(msg)
534
538
535 #
539 #
536 # Supporting deprecated API parts
540 # Supporting deprecated API parts
537 # TODO: johbo: consider to move this into a mixin
541 # TODO: johbo: consider to move this into a mixin
538 #
542 #
539
543
540 @property
544 @property
541 def EMPTY_CHANGESET(self):
545 def EMPTY_CHANGESET(self):
542 warnings.warn(
546 warnings.warn(
543 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
547 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
544 return self.EMPTY_COMMIT_ID
548 return self.EMPTY_COMMIT_ID
545
549
546 @property
550 @property
547 def revisions(self):
551 def revisions(self):
548 warnings.warn("Use commits attribute instead", DeprecationWarning)
552 warnings.warn("Use commits attribute instead", DeprecationWarning)
549 return self.commit_ids
553 return self.commit_ids
550
554
551 @revisions.setter
555 @revisions.setter
552 def revisions(self, value):
556 def revisions(self, value):
553 warnings.warn("Use commits attribute instead", DeprecationWarning)
557 warnings.warn("Use commits attribute instead", DeprecationWarning)
554 self.commit_ids = value
558 self.commit_ids = value
555
559
556 def get_changeset(self, revision=None, pre_load=None):
560 def get_changeset(self, revision=None, pre_load=None):
557 warnings.warn("Use get_commit instead", DeprecationWarning)
561 warnings.warn("Use get_commit instead", DeprecationWarning)
558 commit_id = None
562 commit_id = None
559 commit_idx = None
563 commit_idx = None
560 if isinstance(revision, basestring):
564 if isinstance(revision, basestring):
561 commit_id = revision
565 commit_id = revision
562 else:
566 else:
563 commit_idx = revision
567 commit_idx = revision
564 return self.get_commit(
568 return self.get_commit(
565 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
569 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
566
570
567 def get_changesets(
571 def get_changesets(
568 self, start=None, end=None, start_date=None, end_date=None,
572 self, start=None, end=None, start_date=None, end_date=None,
569 branch_name=None, pre_load=None):
573 branch_name=None, pre_load=None):
570 warnings.warn("Use get_commits instead", DeprecationWarning)
574 warnings.warn("Use get_commits instead", DeprecationWarning)
571 start_id = self._revision_to_commit(start)
575 start_id = self._revision_to_commit(start)
572 end_id = self._revision_to_commit(end)
576 end_id = self._revision_to_commit(end)
573 return self.get_commits(
577 return self.get_commits(
574 start_id=start_id, end_id=end_id, start_date=start_date,
578 start_id=start_id, end_id=end_id, start_date=start_date,
575 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
579 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
576
580
577 def _revision_to_commit(self, revision):
581 def _revision_to_commit(self, revision):
578 """
582 """
579 Translates a revision to a commit_id
583 Translates a revision to a commit_id
580
584
581 Helps to support the old changeset based API which allows to use
585 Helps to support the old changeset based API which allows to use
582 commit ids and commit indices interchangeable.
586 commit ids and commit indices interchangeable.
583 """
587 """
584 if revision is None:
588 if revision is None:
585 return revision
589 return revision
586
590
587 if isinstance(revision, basestring):
591 if isinstance(revision, basestring):
588 commit_id = revision
592 commit_id = revision
589 else:
593 else:
590 commit_id = self.commit_ids[revision]
594 commit_id = self.commit_ids[revision]
591 return commit_id
595 return commit_id
592
596
593 @property
597 @property
594 def in_memory_changeset(self):
598 def in_memory_changeset(self):
595 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
599 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
596 return self.in_memory_commit
600 return self.in_memory_commit
597
601
598
602
599 class BaseCommit(object):
603 class BaseCommit(object):
600 """
604 """
601 Each backend should implement it's commit representation.
605 Each backend should implement it's commit representation.
602
606
603 **Attributes**
607 **Attributes**
604
608
605 ``repository``
609 ``repository``
606 repository object within which commit exists
610 repository object within which commit exists
607
611
608 ``id``
612 ``id``
609 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
613 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
610 just ``tip``.
614 just ``tip``.
611
615
612 ``raw_id``
616 ``raw_id``
613 raw commit representation (i.e. full 40 length sha for git
617 raw commit representation (i.e. full 40 length sha for git
614 backend)
618 backend)
615
619
616 ``short_id``
620 ``short_id``
617 shortened (if apply) version of ``raw_id``; it would be simple
621 shortened (if apply) version of ``raw_id``; it would be simple
618 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
622 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
619 as ``raw_id`` for subversion
623 as ``raw_id`` for subversion
620
624
621 ``idx``
625 ``idx``
622 commit index
626 commit index
623
627
624 ``files``
628 ``files``
625 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
629 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
626
630
627 ``dirs``
631 ``dirs``
628 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
632 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
629
633
630 ``nodes``
634 ``nodes``
631 combined list of ``Node`` objects
635 combined list of ``Node`` objects
632
636
633 ``author``
637 ``author``
634 author of the commit, as unicode
638 author of the commit, as unicode
635
639
636 ``message``
640 ``message``
637 message of the commit, as unicode
641 message of the commit, as unicode
638
642