##// END OF EJS Templates
hg: Increase logging around merge status check
johbo -
r142:f220593c default
parent child Browse files
Show More
@@ -1,777 +1,781 b''
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 HG repository module
22 HG repository module
23 """
23 """
24
24
25 import logging
25 import logging
26 import binascii
26 import binascii
27 import os
27 import os
28 import re
28 import re
29 import shutil
29 import shutil
30 import urllib
30 import urllib
31
31
32 from zope.cachedescriptors.property import Lazy as LazyProperty
32 from zope.cachedescriptors.property import Lazy as LazyProperty
33
33
34 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib.compat import OrderedDict
35 from rhodecode.lib.datelib import (
35 from rhodecode.lib.datelib import (
36 date_fromtimestamp, makedate, date_to_timestamp_plus_offset,
36 date_fromtimestamp, makedate, date_to_timestamp_plus_offset,
37 date_astimestamp)
37 date_astimestamp)
38 from rhodecode.lib.utils import safe_unicode, safe_str
38 from rhodecode.lib.utils import safe_unicode, safe_str
39 from rhodecode.lib.vcs import connection
39 from rhodecode.lib.vcs import connection
40 from rhodecode.lib.vcs.backends.base import (
40 from rhodecode.lib.vcs.backends.base import (
41 BaseRepository, CollectionGenerator, Config, MergeResponse,
41 BaseRepository, CollectionGenerator, Config, MergeResponse,
42 MergeFailureReason)
42 MergeFailureReason)
43 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
43 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
44 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
44 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
45 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
45 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import (
47 from rhodecode.lib.vcs.exceptions import (
48 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
48 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
49 TagDoesNotExistError, CommitDoesNotExistError)
49 TagDoesNotExistError, CommitDoesNotExistError)
50
50
51 hexlify = binascii.hexlify
51 hexlify = binascii.hexlify
52 nullid = "\0" * 20
52 nullid = "\0" * 20
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class MercurialRepository(BaseRepository):
57 class MercurialRepository(BaseRepository):
58 """
58 """
59 Mercurial repository backend
59 Mercurial repository backend
60 """
60 """
61 DEFAULT_BRANCH_NAME = 'default'
61 DEFAULT_BRANCH_NAME = 'default'
62
62
63 def __init__(self, repo_path, config=None, create=False, src_url=None,
63 def __init__(self, repo_path, config=None, create=False, src_url=None,
64 update_after_clone=False, with_wire=None):
64 update_after_clone=False, with_wire=None):
65 """
65 """
66 Raises RepositoryError if repository could not be find at the given
66 Raises RepositoryError if repository could not be find at the given
67 ``repo_path``.
67 ``repo_path``.
68
68
69 :param repo_path: local path of the repository
69 :param repo_path: local path of the repository
70 :param config: config object containing the repo configuration
70 :param config: config object containing the repo configuration
71 :param create=False: if set to True, would try to create repository if
71 :param create=False: if set to True, would try to create repository if
72 it does not exist rather than raising exception
72 it does not exist rather than raising exception
73 :param src_url=None: would try to clone repository from given location
73 :param src_url=None: would try to clone repository from given location
74 :param update_after_clone=False: sets update of working copy after
74 :param update_after_clone=False: sets update of working copy after
75 making a clone
75 making a clone
76 """
76 """
77 self.path = safe_str(os.path.abspath(repo_path))
77 self.path = safe_str(os.path.abspath(repo_path))
78 self.config = config if config else Config()
78 self.config = config if config else Config()
79 self._remote = connection.Hg(
79 self._remote = connection.Hg(
80 self.path, self.config, with_wire=with_wire)
80 self.path, self.config, with_wire=with_wire)
81
81
82 self._init_repo(create, src_url, update_after_clone)
82 self._init_repo(create, src_url, update_after_clone)
83
83
84 # caches
84 # caches
85 self._commit_ids = {}
85 self._commit_ids = {}
86
86
87 @LazyProperty
87 @LazyProperty
88 def commit_ids(self):
88 def commit_ids(self):
89 """
89 """
90 Returns list of commit ids, in ascending order. Being lazy
90 Returns list of commit ids, in ascending order. Being lazy
91 attribute allows external tools to inject shas from cache.
91 attribute allows external tools to inject shas from cache.
92 """
92 """
93 commit_ids = self._get_all_commit_ids()
93 commit_ids = self._get_all_commit_ids()
94 self._rebuild_cache(commit_ids)
94 self._rebuild_cache(commit_ids)
95 return commit_ids
95 return commit_ids
96
96
97 def _rebuild_cache(self, commit_ids):
97 def _rebuild_cache(self, commit_ids):
98 self._commit_ids = dict((commit_id, index)
98 self._commit_ids = dict((commit_id, index)
99 for index, commit_id in enumerate(commit_ids))
99 for index, commit_id in enumerate(commit_ids))
100
100
101 @LazyProperty
101 @LazyProperty
102 def branches(self):
102 def branches(self):
103 return self._get_branches()
103 return self._get_branches()
104
104
105 @LazyProperty
105 @LazyProperty
106 def branches_closed(self):
106 def branches_closed(self):
107 return self._get_branches(active=False, closed=True)
107 return self._get_branches(active=False, closed=True)
108
108
109 @LazyProperty
109 @LazyProperty
110 def branches_all(self):
110 def branches_all(self):
111 all_branches = {}
111 all_branches = {}
112 all_branches.update(self.branches)
112 all_branches.update(self.branches)
113 all_branches.update(self.branches_closed)
113 all_branches.update(self.branches_closed)
114 return all_branches
114 return all_branches
115
115
116 def _get_branches(self, active=True, closed=False):
116 def _get_branches(self, active=True, closed=False):
117 """
117 """
118 Gets branches for this repository
118 Gets branches for this repository
119 Returns only not closed active branches by default
119 Returns only not closed active branches by default
120
120
121 :param active: return also active branches
121 :param active: return also active branches
122 :param closed: return also closed branches
122 :param closed: return also closed branches
123
123
124 """
124 """
125 if self.is_empty():
125 if self.is_empty():
126 return {}
126 return {}
127
127
128 def get_name(ctx):
128 def get_name(ctx):
129 return ctx[0]
129 return ctx[0]
130
130
131 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
131 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
132 self._remote.branches(active, closed).items()]
132 self._remote.branches(active, closed).items()]
133
133
134 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
134 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
135
135
136 @LazyProperty
136 @LazyProperty
137 def tags(self):
137 def tags(self):
138 """
138 """
139 Gets tags for this repository
139 Gets tags for this repository
140 """
140 """
141 return self._get_tags()
141 return self._get_tags()
142
142
143 def _get_tags(self):
143 def _get_tags(self):
144 if self.is_empty():
144 if self.is_empty():
145 return {}
145 return {}
146
146
147 def get_name(ctx):
147 def get_name(ctx):
148 return ctx[0]
148 return ctx[0]
149
149
150 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
150 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
151 self._remote.tags().items()]
151 self._remote.tags().items()]
152
152
153 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
153 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
154
154
155 def tag(self, name, user, commit_id=None, message=None, date=None,
155 def tag(self, name, user, commit_id=None, message=None, date=None,
156 **kwargs):
156 **kwargs):
157 """
157 """
158 Creates and returns a tag for the given ``commit_id``.
158 Creates and returns a tag for the given ``commit_id``.
159
159
160 :param name: name for new tag
160 :param name: name for new tag
161 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
161 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
162 :param commit_id: commit id for which new tag would be created
162 :param commit_id: commit id for which new tag would be created
163 :param message: message of the tag's commit
163 :param message: message of the tag's commit
164 :param date: date of tag's commit
164 :param date: date of tag's commit
165
165
166 :raises TagAlreadyExistError: if tag with same name already exists
166 :raises TagAlreadyExistError: if tag with same name already exists
167 """
167 """
168 if name in self.tags:
168 if name in self.tags:
169 raise TagAlreadyExistError("Tag %s already exists" % name)
169 raise TagAlreadyExistError("Tag %s already exists" % name)
170 commit = self.get_commit(commit_id=commit_id)
170 commit = self.get_commit(commit_id=commit_id)
171 local = kwargs.setdefault('local', False)
171 local = kwargs.setdefault('local', False)
172
172
173 if message is None:
173 if message is None:
174 message = "Added tag %s for commit %s" % (name, commit.short_id)
174 message = "Added tag %s for commit %s" % (name, commit.short_id)
175
175
176 date, tz = date_to_timestamp_plus_offset(date)
176 date, tz = date_to_timestamp_plus_offset(date)
177
177
178 self._remote.tag(
178 self._remote.tag(
179 name, commit.raw_id, message, local, user, date, tz)
179 name, commit.raw_id, message, local, user, date, tz)
180
180
181 # Reinitialize tags
181 # Reinitialize tags
182 self.tags = self._get_tags()
182 self.tags = self._get_tags()
183 tag_id = self.tags[name]
183 tag_id = self.tags[name]
184
184
185 return self.get_commit(commit_id=tag_id)
185 return self.get_commit(commit_id=tag_id)
186
186
187 def remove_tag(self, name, user, message=None, date=None):
187 def remove_tag(self, name, user, message=None, date=None):
188 """
188 """
189 Removes tag with the given `name`.
189 Removes tag with the given `name`.
190
190
191 :param name: name of the tag to be removed
191 :param name: name of the tag to be removed
192 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
192 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
193 :param message: message of the tag's removal commit
193 :param message: message of the tag's removal commit
194 :param date: date of tag's removal commit
194 :param date: date of tag's removal commit
195
195
196 :raises TagDoesNotExistError: if tag with given name does not exists
196 :raises TagDoesNotExistError: if tag with given name does not exists
197 """
197 """
198 if name not in self.tags:
198 if name not in self.tags:
199 raise TagDoesNotExistError("Tag %s does not exist" % name)
199 raise TagDoesNotExistError("Tag %s does not exist" % name)
200 if message is None:
200 if message is None:
201 message = "Removed tag %s" % name
201 message = "Removed tag %s" % name
202 local = False
202 local = False
203
203
204 date, tz = date_to_timestamp_plus_offset(date)
204 date, tz = date_to_timestamp_plus_offset(date)
205
205
206 self._remote.tag(name, nullid, message, local, user, date, tz)
206 self._remote.tag(name, nullid, message, local, user, date, tz)
207 self.tags = self._get_tags()
207 self.tags = self._get_tags()
208
208
209 @LazyProperty
209 @LazyProperty
210 def bookmarks(self):
210 def bookmarks(self):
211 """
211 """
212 Gets bookmarks for this repository
212 Gets bookmarks for this repository
213 """
213 """
214 return self._get_bookmarks()
214 return self._get_bookmarks()
215
215
216 def _get_bookmarks(self):
216 def _get_bookmarks(self):
217 if self.is_empty():
217 if self.is_empty():
218 return {}
218 return {}
219
219
220 def get_name(ctx):
220 def get_name(ctx):
221 return ctx[0]
221 return ctx[0]
222
222
223 _bookmarks = [
223 _bookmarks = [
224 (safe_unicode(n), hexlify(h)) for n, h in
224 (safe_unicode(n), hexlify(h)) for n, h in
225 self._remote.bookmarks().items()]
225 self._remote.bookmarks().items()]
226
226
227 return OrderedDict(sorted(_bookmarks, key=get_name))
227 return OrderedDict(sorted(_bookmarks, key=get_name))
228
228
229 def _get_all_commit_ids(self):
229 def _get_all_commit_ids(self):
230 return self._remote.get_all_commit_ids('visible')
230 return self._remote.get_all_commit_ids('visible')
231
231
232 def get_diff(
232 def get_diff(
233 self, commit1, commit2, path='', ignore_whitespace=False,
233 self, commit1, commit2, path='', ignore_whitespace=False,
234 context=3, path1=None):
234 context=3, path1=None):
235 """
235 """
236 Returns (git like) *diff*, as plain text. Shows changes introduced by
236 Returns (git like) *diff*, as plain text. Shows changes introduced by
237 `commit2` since `commit1`.
237 `commit2` since `commit1`.
238
238
239 :param commit1: Entry point from which diff is shown. Can be
239 :param commit1: Entry point from which diff is shown. Can be
240 ``self.EMPTY_COMMIT`` - in this case, patch showing all
240 ``self.EMPTY_COMMIT`` - in this case, patch showing all
241 the changes since empty state of the repository until `commit2`
241 the changes since empty state of the repository until `commit2`
242 :param commit2: Until which commit changes should be shown.
242 :param commit2: Until which commit changes should be shown.
243 :param ignore_whitespace: If set to ``True``, would not show whitespace
243 :param ignore_whitespace: If set to ``True``, would not show whitespace
244 changes. Defaults to ``False``.
244 changes. Defaults to ``False``.
245 :param context: How many lines before/after changed lines should be
245 :param context: How many lines before/after changed lines should be
246 shown. Defaults to ``3``.
246 shown. Defaults to ``3``.
247 """
247 """
248 self._validate_diff_commits(commit1, commit2)
248 self._validate_diff_commits(commit1, commit2)
249 if path1 is not None and path1 != path:
249 if path1 is not None and path1 != path:
250 raise ValueError("Diff of two different paths not supported.")
250 raise ValueError("Diff of two different paths not supported.")
251
251
252 if path:
252 if path:
253 file_filter = [self.path, path]
253 file_filter = [self.path, path]
254 else:
254 else:
255 file_filter = None
255 file_filter = None
256
256
257 diff = self._remote.diff(
257 diff = self._remote.diff(
258 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
258 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
259 opt_git=True, opt_ignorews=ignore_whitespace,
259 opt_git=True, opt_ignorews=ignore_whitespace,
260 context=context)
260 context=context)
261 return MercurialDiff(diff)
261 return MercurialDiff(diff)
262
262
263 def strip(self, commit_id, branch=None):
263 def strip(self, commit_id, branch=None):
264 self._remote.strip(commit_id, update=False, backup="none")
264 self._remote.strip(commit_id, update=False, backup="none")
265
265
266 self.commit_ids = self._get_all_commit_ids()
266 self.commit_ids = self._get_all_commit_ids()
267 self._rebuild_cache(self.commit_ids)
267 self._rebuild_cache(self.commit_ids)
268
268
269 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
269 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
270 if commit_id1 == commit_id2:
270 if commit_id1 == commit_id2:
271 return commit_id1
271 return commit_id1
272
272
273 ancestors = self._remote.revs_from_revspec(
273 ancestors = self._remote.revs_from_revspec(
274 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
274 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
275 other_path=repo2.path)
275 other_path=repo2.path)
276 return repo2[ancestors[0]].raw_id if ancestors else None
276 return repo2[ancestors[0]].raw_id if ancestors else None
277
277
278 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
278 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
279 if commit_id1 == commit_id2:
279 if commit_id1 == commit_id2:
280 commits = []
280 commits = []
281 else:
281 else:
282 if merge:
282 if merge:
283 indexes = self._remote.revs_from_revspec(
283 indexes = self._remote.revs_from_revspec(
284 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
284 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
285 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
285 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
286 else:
286 else:
287 indexes = self._remote.revs_from_revspec(
287 indexes = self._remote.revs_from_revspec(
288 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
288 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
289 commit_id1, other_path=repo2.path)
289 commit_id1, other_path=repo2.path)
290
290
291 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
291 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
292 for idx in indexes]
292 for idx in indexes]
293
293
294 return commits
294 return commits
295
295
296 @staticmethod
296 @staticmethod
297 def check_url(url, config):
297 def check_url(url, config):
298 """
298 """
299 Function will check given url and try to verify if it's a valid
299 Function will check given url and try to verify if it's a valid
300 link. Sometimes it may happened that mercurial will issue basic
300 link. Sometimes it may happened that mercurial will issue basic
301 auth request that can cause whole API to hang when used from python
301 auth request that can cause whole API to hang when used from python
302 or other external calls.
302 or other external calls.
303
303
304 On failures it'll raise urllib2.HTTPError, exception is also thrown
304 On failures it'll raise urllib2.HTTPError, exception is also thrown
305 when the return code is non 200
305 when the return code is non 200
306 """
306 """
307 # check first if it's not an local url
307 # check first if it's not an local url
308 if os.path.isdir(url) or url.startswith('file:'):
308 if os.path.isdir(url) or url.startswith('file:'):
309 return True
309 return True
310
310
311 # Request the _remote to verify the url
311 # Request the _remote to verify the url
312 return connection.Hg.check_url(url, config.serialize())
312 return connection.Hg.check_url(url, config.serialize())
313
313
314 @staticmethod
314 @staticmethod
315 def is_valid_repository(path):
315 def is_valid_repository(path):
316 return os.path.isdir(os.path.join(path, '.hg'))
316 return os.path.isdir(os.path.join(path, '.hg'))
317
317
318 def _init_repo(self, create, src_url=None, update_after_clone=False):
318 def _init_repo(self, create, src_url=None, update_after_clone=False):
319 """
319 """
320 Function will check for mercurial repository in given path. If there
320 Function will check for mercurial repository in given path. If there
321 is no repository in that path it will raise an exception unless
321 is no repository in that path it will raise an exception unless
322 `create` parameter is set to True - in that case repository would
322 `create` parameter is set to True - in that case repository would
323 be created.
323 be created.
324
324
325 If `src_url` is given, would try to clone repository from the
325 If `src_url` is given, would try to clone repository from the
326 location at given clone_point. Additionally it'll make update to
326 location at given clone_point. Additionally it'll make update to
327 working copy accordingly to `update_after_clone` flag.
327 working copy accordingly to `update_after_clone` flag.
328 """
328 """
329 if create and os.path.exists(self.path):
329 if create and os.path.exists(self.path):
330 raise RepositoryError(
330 raise RepositoryError(
331 "Cannot create repository at %s, location already exist"
331 "Cannot create repository at %s, location already exist"
332 % self.path)
332 % self.path)
333
333
334 if src_url:
334 if src_url:
335 url = str(self._get_url(src_url))
335 url = str(self._get_url(src_url))
336 MercurialRepository.check_url(url, self.config)
336 MercurialRepository.check_url(url, self.config)
337
337
338 self._remote.clone(url, self.path, update_after_clone)
338 self._remote.clone(url, self.path, update_after_clone)
339
339
340 # Don't try to create if we've already cloned repo
340 # Don't try to create if we've already cloned repo
341 create = False
341 create = False
342
342
343 if create:
343 if create:
344 os.makedirs(self.path, mode=0755)
344 os.makedirs(self.path, mode=0755)
345
345
346 self._remote.localrepository(create)
346 self._remote.localrepository(create)
347
347
348 @LazyProperty
348 @LazyProperty
349 def in_memory_commit(self):
349 def in_memory_commit(self):
350 return MercurialInMemoryCommit(self)
350 return MercurialInMemoryCommit(self)
351
351
352 @LazyProperty
352 @LazyProperty
353 def description(self):
353 def description(self):
354 description = self._remote.get_config_value(
354 description = self._remote.get_config_value(
355 'web', 'description', untrusted=True)
355 'web', 'description', untrusted=True)
356 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
356 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
357
357
358 @LazyProperty
358 @LazyProperty
359 def contact(self):
359 def contact(self):
360 contact = (
360 contact = (
361 self._remote.get_config_value("web", "contact") or
361 self._remote.get_config_value("web", "contact") or
362 self._remote.get_config_value("ui", "username"))
362 self._remote.get_config_value("ui", "username"))
363 return safe_unicode(contact or self.DEFAULT_CONTACT)
363 return safe_unicode(contact or self.DEFAULT_CONTACT)
364
364
365 @LazyProperty
365 @LazyProperty
366 def last_change(self):
366 def last_change(self):
367 """
367 """
368 Returns last change made on this repository as
368 Returns last change made on this repository as
369 `datetime.datetime` object
369 `datetime.datetime` object
370 """
370 """
371 return date_fromtimestamp(self._get_mtime(), makedate()[1])
371 return date_fromtimestamp(self._get_mtime(), makedate()[1])
372
372
373 def _get_mtime(self):
373 def _get_mtime(self):
374 try:
374 try:
375 return date_astimestamp(self.get_commit().date)
375 return date_astimestamp(self.get_commit().date)
376 except RepositoryError:
376 except RepositoryError:
377 # fallback to filesystem
377 # fallback to filesystem
378 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
378 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
379 st_path = os.path.join(self.path, '.hg', "store")
379 st_path = os.path.join(self.path, '.hg', "store")
380 if os.path.exists(cl_path):
380 if os.path.exists(cl_path):
381 return os.stat(cl_path).st_mtime
381 return os.stat(cl_path).st_mtime
382 else:
382 else:
383 return os.stat(st_path).st_mtime
383 return os.stat(st_path).st_mtime
384
384
385 def _sanitize_commit_idx(self, idx):
385 def _sanitize_commit_idx(self, idx):
386 # Note: Mercurial has ``int(-1)`` reserved as not existing id_or_idx
386 # Note: Mercurial has ``int(-1)`` reserved as not existing id_or_idx
387 # number. A `long` is treated in the correct way though. So we convert
387 # number. A `long` is treated in the correct way though. So we convert
388 # `int` to `long` here to make sure it is handled correctly.
388 # `int` to `long` here to make sure it is handled correctly.
389 if isinstance(idx, int):
389 if isinstance(idx, int):
390 return long(idx)
390 return long(idx)
391 return idx
391 return idx
392
392
393 def _get_url(self, url):
393 def _get_url(self, url):
394 """
394 """
395 Returns normalized url. If schema is not given, would fall
395 Returns normalized url. If schema is not given, would fall
396 to filesystem
396 to filesystem
397 (``file:///``) schema.
397 (``file:///``) schema.
398 """
398 """
399 url = url.encode('utf8')
399 url = url.encode('utf8')
400 if url != 'default' and '://' not in url:
400 if url != 'default' and '://' not in url:
401 url = "file:" + urllib.pathname2url(url)
401 url = "file:" + urllib.pathname2url(url)
402 return url
402 return url
403
403
404 def get_hook_location(self):
404 def get_hook_location(self):
405 """
405 """
406 returns absolute path to location where hooks are stored
406 returns absolute path to location where hooks are stored
407 """
407 """
408 return os.path.join(self.path, '.hg', '.hgrc')
408 return os.path.join(self.path, '.hg', '.hgrc')
409
409
410 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
410 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
411 """
411 """
412 Returns ``MercurialCommit`` object representing repository's
412 Returns ``MercurialCommit`` object representing repository's
413 commit at the given `commit_id` or `commit_idx`.
413 commit at the given `commit_id` or `commit_idx`.
414 """
414 """
415 if self.is_empty():
415 if self.is_empty():
416 raise EmptyRepositoryError("There are no commits yet")
416 raise EmptyRepositoryError("There are no commits yet")
417
417
418 if commit_id is not None:
418 if commit_id is not None:
419 self._validate_commit_id(commit_id)
419 self._validate_commit_id(commit_id)
420 try:
420 try:
421 idx = self._commit_ids[commit_id]
421 idx = self._commit_ids[commit_id]
422 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
422 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
423 except KeyError:
423 except KeyError:
424 pass
424 pass
425 elif commit_idx is not None:
425 elif commit_idx is not None:
426 self._validate_commit_idx(commit_idx)
426 self._validate_commit_idx(commit_idx)
427 commit_idx = self._sanitize_commit_idx(commit_idx)
427 commit_idx = self._sanitize_commit_idx(commit_idx)
428 try:
428 try:
429 id_ = self.commit_ids[commit_idx]
429 id_ = self.commit_ids[commit_idx]
430 if commit_idx < 0:
430 if commit_idx < 0:
431 commit_idx += len(self.commit_ids)
431 commit_idx += len(self.commit_ids)
432 return MercurialCommit(
432 return MercurialCommit(
433 self, id_, commit_idx, pre_load=pre_load)
433 self, id_, commit_idx, pre_load=pre_load)
434 except IndexError:
434 except IndexError:
435 commit_id = commit_idx
435 commit_id = commit_idx
436 else:
436 else:
437 commit_id = "tip"
437 commit_id = "tip"
438
438
439 # TODO Paris: Ugly hack to "serialize" long for msgpack
439 # TODO Paris: Ugly hack to "serialize" long for msgpack
440 if isinstance(commit_id, long):
440 if isinstance(commit_id, long):
441 commit_id = float(commit_id)
441 commit_id = float(commit_id)
442
442
443 if isinstance(commit_id, unicode):
443 if isinstance(commit_id, unicode):
444 commit_id = safe_str(commit_id)
444 commit_id = safe_str(commit_id)
445
445
446 raw_id, idx = self._remote.lookup(commit_id, both=True)
446 raw_id, idx = self._remote.lookup(commit_id, both=True)
447
447
448 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
448 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
449
449
450 def get_commits(
450 def get_commits(
451 self, start_id=None, end_id=None, start_date=None, end_date=None,
451 self, start_id=None, end_id=None, start_date=None, end_date=None,
452 branch_name=None, pre_load=None):
452 branch_name=None, pre_load=None):
453 """
453 """
454 Returns generator of ``MercurialCommit`` objects from start to end
454 Returns generator of ``MercurialCommit`` objects from start to end
455 (both are inclusive)
455 (both are inclusive)
456
456
457 :param start_id: None, str(commit_id)
457 :param start_id: None, str(commit_id)
458 :param end_id: None, str(commit_id)
458 :param end_id: None, str(commit_id)
459 :param start_date: if specified, commits with commit date less than
459 :param start_date: if specified, commits with commit date less than
460 ``start_date`` would be filtered out from returned set
460 ``start_date`` would be filtered out from returned set
461 :param end_date: if specified, commits with commit date greater than
461 :param end_date: if specified, commits with commit date greater than
462 ``end_date`` would be filtered out from returned set
462 ``end_date`` would be filtered out from returned set
463 :param branch_name: if specified, commits not reachable from given
463 :param branch_name: if specified, commits not reachable from given
464 branch would be filtered out from returned set
464 branch would be filtered out from returned set
465
465
466 :raise BranchDoesNotExistError: If given ``branch_name`` does not
466 :raise BranchDoesNotExistError: If given ``branch_name`` does not
467 exist.
467 exist.
468 :raise CommitDoesNotExistError: If commit for given ``start`` or
468 :raise CommitDoesNotExistError: If commit for given ``start`` or
469 ``end`` could not be found.
469 ``end`` could not be found.
470 """
470 """
471 # actually we should check now if it's not an empty repo
471 # actually we should check now if it's not an empty repo
472 branch_ancestors = False
472 branch_ancestors = False
473 if self.is_empty():
473 if self.is_empty():
474 raise EmptyRepositoryError("There are no commits yet")
474 raise EmptyRepositoryError("There are no commits yet")
475 self._validate_branch_name(branch_name)
475 self._validate_branch_name(branch_name)
476
476
477 if start_id is not None:
477 if start_id is not None:
478 self._validate_commit_id(start_id)
478 self._validate_commit_id(start_id)
479 c_start = self.get_commit(commit_id=start_id)
479 c_start = self.get_commit(commit_id=start_id)
480 start_pos = self._commit_ids[c_start.raw_id]
480 start_pos = self._commit_ids[c_start.raw_id]
481 else:
481 else:
482 start_pos = None
482 start_pos = None
483
483
484 if end_id is not None:
484 if end_id is not None:
485 self._validate_commit_id(end_id)
485 self._validate_commit_id(end_id)
486 c_end = self.get_commit(commit_id=end_id)
486 c_end = self.get_commit(commit_id=end_id)
487 end_pos = max(0, self._commit_ids[c_end.raw_id])
487 end_pos = max(0, self._commit_ids[c_end.raw_id])
488 else:
488 else:
489 end_pos = None
489 end_pos = None
490
490
491 if None not in [start_id, end_id] and start_pos > end_pos:
491 if None not in [start_id, end_id] and start_pos > end_pos:
492 raise RepositoryError(
492 raise RepositoryError(
493 "Start commit '%s' cannot be after end commit '%s'" %
493 "Start commit '%s' cannot be after end commit '%s'" %
494 (start_id, end_id))
494 (start_id, end_id))
495
495
496 if end_pos is not None:
496 if end_pos is not None:
497 end_pos += 1
497 end_pos += 1
498
498
499 commit_filter = []
499 commit_filter = []
500 if branch_name and not branch_ancestors:
500 if branch_name and not branch_ancestors:
501 commit_filter.append('branch("%s")' % branch_name)
501 commit_filter.append('branch("%s")' % branch_name)
502 elif branch_name and branch_ancestors:
502 elif branch_name and branch_ancestors:
503 commit_filter.append('ancestors(branch("%s"))' % branch_name)
503 commit_filter.append('ancestors(branch("%s"))' % branch_name)
504 if start_date and not end_date:
504 if start_date and not end_date:
505 commit_filter.append('date(">%s")' % start_date)
505 commit_filter.append('date(">%s")' % start_date)
506 if end_date and not start_date:
506 if end_date and not start_date:
507 commit_filter.append('date("<%s")' % end_date)
507 commit_filter.append('date("<%s")' % end_date)
508 if start_date and end_date:
508 if start_date and end_date:
509 commit_filter.append(
509 commit_filter.append(
510 'date(">%s") and date("<%s")' % (start_date, end_date))
510 'date(">%s") and date("<%s")' % (start_date, end_date))
511
511
512 # TODO: johbo: Figure out a simpler way for this solution
512 # TODO: johbo: Figure out a simpler way for this solution
513 collection_generator = CollectionGenerator
513 collection_generator = CollectionGenerator
514 if commit_filter:
514 if commit_filter:
515 commit_filter = map(safe_str, commit_filter)
515 commit_filter = map(safe_str, commit_filter)
516 revisions = self._remote.rev_range(commit_filter)
516 revisions = self._remote.rev_range(commit_filter)
517 collection_generator = MercurialIndexBasedCollectionGenerator
517 collection_generator = MercurialIndexBasedCollectionGenerator
518 else:
518 else:
519 revisions = self.commit_ids
519 revisions = self.commit_ids
520
520
521 if start_pos or end_pos:
521 if start_pos or end_pos:
522 revisions = revisions[start_pos:end_pos]
522 revisions = revisions[start_pos:end_pos]
523
523
524 return collection_generator(self, revisions, pre_load=pre_load)
524 return collection_generator(self, revisions, pre_load=pre_load)
525
525
526 def pull(self, url, commit_ids=None):
526 def pull(self, url, commit_ids=None):
527 """
527 """
528 Tries to pull changes from external location.
528 Tries to pull changes from external location.
529
529
530 :param commit_ids: Optional. Can be set to a list of commit ids
530 :param commit_ids: Optional. Can be set to a list of commit ids
531 which shall be pulled from the other repository.
531 which shall be pulled from the other repository.
532 """
532 """
533 url = self._get_url(url)
533 url = self._get_url(url)
534 self._remote.pull(url, commit_ids=commit_ids)
534 self._remote.pull(url, commit_ids=commit_ids)
535
535
536 def _local_clone(self, clone_path):
536 def _local_clone(self, clone_path):
537 """
537 """
538 Create a local clone of the current repo.
538 Create a local clone of the current repo.
539 """
539 """
540 self._remote.clone(self.path, clone_path, update_after_clone=True,
540 self._remote.clone(self.path, clone_path, update_after_clone=True,
541 hooks=False)
541 hooks=False)
542
542
543 def _update(self, revision, clean=False):
543 def _update(self, revision, clean=False):
544 """
544 """
545 Update the working copty to the specified revision.
545 Update the working copty to the specified revision.
546 """
546 """
547 self._remote.update(revision, clean=clean)
547 self._remote.update(revision, clean=clean)
548
548
549 def _identify(self):
549 def _identify(self):
550 """
550 """
551 Return the current state of the working directory.
551 Return the current state of the working directory.
552 """
552 """
553 return self._remote.identify().strip().rstrip('+')
553 return self._remote.identify().strip().rstrip('+')
554
554
555 def _heads(self, branch=None):
555 def _heads(self, branch=None):
556 """
556 """
557 Return the commit ids of the repository heads.
557 Return the commit ids of the repository heads.
558 """
558 """
559 return self._remote.heads(branch=branch).strip().split(' ')
559 return self._remote.heads(branch=branch).strip().split(' ')
560
560
561 def _ancestor(self, revision1, revision2):
561 def _ancestor(self, revision1, revision2):
562 """
562 """
563 Return the common ancestor of the two revisions.
563 Return the common ancestor of the two revisions.
564 """
564 """
565 return self._remote.ancestor(
565 return self._remote.ancestor(
566 revision1, revision2).strip().split(':')[-1]
566 revision1, revision2).strip().split(':')[-1]
567
567
568 def _local_push(
568 def _local_push(
569 self, revision, repository_path, push_branches=False,
569 self, revision, repository_path, push_branches=False,
570 enable_hooks=False):
570 enable_hooks=False):
571 """
571 """
572 Push the given revision to the specified repository.
572 Push the given revision to the specified repository.
573
573
574 :param push_branches: allow to create branches in the target repo.
574 :param push_branches: allow to create branches in the target repo.
575 """
575 """
576 self._remote.push(
576 self._remote.push(
577 [revision], repository_path, hooks=enable_hooks,
577 [revision], repository_path, hooks=enable_hooks,
578 push_branches=push_branches)
578 push_branches=push_branches)
579
579
580 def _local_merge(self, target_ref, merge_message, user_name, user_email,
580 def _local_merge(self, target_ref, merge_message, user_name, user_email,
581 source_ref):
581 source_ref):
582 """
582 """
583 Merge the given source_revision into the checked out revision.
583 Merge the given source_revision into the checked out revision.
584
584
585 Returns the commit id of the merge and a boolean indicating if the
585 Returns the commit id of the merge and a boolean indicating if the
586 commit needs to be pushed.
586 commit needs to be pushed.
587 """
587 """
588 self._update(target_ref.commit_id)
588 self._update(target_ref.commit_id)
589
589
590 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
590 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
591 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
591 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
592
592
593 if ancestor == source_ref.commit_id:
593 if ancestor == source_ref.commit_id:
594 # Nothing to do, the changes were already integrated
594 # Nothing to do, the changes were already integrated
595 return target_ref.commit_id, False
595 return target_ref.commit_id, False
596
596
597 elif ancestor == target_ref.commit_id and is_the_same_branch:
597 elif ancestor == target_ref.commit_id and is_the_same_branch:
598 # In this case we should force a commit message
598 # In this case we should force a commit message
599 return source_ref.commit_id, True
599 return source_ref.commit_id, True
600
600
601 if settings.HG_USE_REBASE_FOR_MERGING:
601 if settings.HG_USE_REBASE_FOR_MERGING:
602 try:
602 try:
603 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
603 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
604 target_ref.commit_id)
604 target_ref.commit_id)
605 self.bookmark(bookmark_name, revision=source_ref.commit_id)
605 self.bookmark(bookmark_name, revision=source_ref.commit_id)
606 self._remote.rebase(
606 self._remote.rebase(
607 source=source_ref.commit_id, dest=target_ref.commit_id)
607 source=source_ref.commit_id, dest=target_ref.commit_id)
608 self._update(bookmark_name)
608 self._update(bookmark_name)
609 return self._identify(), True
609 return self._identify(), True
610 except RepositoryError:
610 except RepositoryError:
611 # Cleanup any rebase leftovers
611 # Cleanup any rebase leftovers
612 self._remote.rebase(abort=True)
612 self._remote.rebase(abort=True)
613 self._remote.update(clean=True)
613 self._remote.update(clean=True)
614 raise
614 raise
615 else:
615 else:
616 try:
616 try:
617 self._remote.merge(source_ref.commit_id)
617 self._remote.merge(source_ref.commit_id)
618 self._remote.commit(
618 self._remote.commit(
619 message=safe_str(merge_message),
619 message=safe_str(merge_message),
620 username=safe_str('%s <%s>' % (user_name, user_email)))
620 username=safe_str('%s <%s>' % (user_name, user_email)))
621 return self._identify(), True
621 return self._identify(), True
622 except RepositoryError:
622 except RepositoryError:
623 # Cleanup any merge leftovers
623 # Cleanup any merge leftovers
624 self._remote.update(clean=True)
624 self._remote.update(clean=True)
625 raise
625 raise
626
626
627 def _is_the_same_branch(self, target_ref, source_ref):
627 def _is_the_same_branch(self, target_ref, source_ref):
628 return (
628 return (
629 self._get_branch_name(target_ref) ==
629 self._get_branch_name(target_ref) ==
630 self._get_branch_name(source_ref))
630 self._get_branch_name(source_ref))
631
631
632 def _get_branch_name(self, ref):
632 def _get_branch_name(self, ref):
633 if ref.type == 'branch':
633 if ref.type == 'branch':
634 return ref.name
634 return ref.name
635 return self._remote.ctx_branch(ref.commit_id)
635 return self._remote.ctx_branch(ref.commit_id)
636
636
637 def _get_shadow_repository_path(self, workspace_id):
637 def _get_shadow_repository_path(self, workspace_id):
638 # The name of the shadow repository must start with '.', so it is
638 # The name of the shadow repository must start with '.', so it is
639 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
639 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
640 return os.path.join(
640 return os.path.join(
641 os.path.dirname(self.path),
641 os.path.dirname(self.path),
642 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
642 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
643
643
644 def _maybe_prepare_merge_workspace(self, workspace_id, unused_target_ref):
644 def _maybe_prepare_merge_workspace(self, workspace_id, unused_target_ref):
645 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
645 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
646 if not os.path.exists(shadow_repository_path):
646 if not os.path.exists(shadow_repository_path):
647 self._local_clone(shadow_repository_path)
647 self._local_clone(shadow_repository_path)
648 log.debug(
649 'Prepared shadow repository in %s', shadow_repository_path)
648
650
649 return shadow_repository_path
651 return shadow_repository_path
650
652
651 def cleanup_merge_workspace(self, workspace_id):
653 def cleanup_merge_workspace(self, workspace_id):
652 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
654 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
653 shutil.rmtree(shadow_repository_path, ignore_errors=True)
655 shutil.rmtree(shadow_repository_path, ignore_errors=True)
654
656
655 def _merge_repo(self, shadow_repository_path, target_ref,
657 def _merge_repo(self, shadow_repository_path, target_ref,
656 source_repo, source_ref, merge_message,
658 source_repo, source_ref, merge_message,
657 merger_name, merger_email, dry_run=False):
659 merger_name, merger_email, dry_run=False):
658 if target_ref.commit_id not in self._heads():
660 if target_ref.commit_id not in self._heads():
659 return MergeResponse(
661 return MergeResponse(
660 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
662 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
661
663
662 if (target_ref.type == 'branch' and
664 if (target_ref.type == 'branch' and
663 len(self._heads(target_ref.name)) != 1):
665 len(self._heads(target_ref.name)) != 1):
664 return MergeResponse(
666 return MergeResponse(
665 False, False, None,
667 False, False, None,
666 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
668 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
667
669
668 shadow_repo = self._get_shadow_instance(shadow_repository_path)
670 shadow_repo = self._get_shadow_instance(shadow_repository_path)
669
671
672 log.debug('Pulling in target reference %s', target_ref)
670 self._validate_pull_reference(target_ref)
673 self._validate_pull_reference(target_ref)
671 shadow_repo._local_pull(self.path, target_ref)
674 shadow_repo._local_pull(self.path, target_ref)
672 try:
675 try:
676 log.debug('Pulling in source reference %s', source_ref)
673 source_repo._validate_pull_reference(source_ref)
677 source_repo._validate_pull_reference(source_ref)
674 shadow_repo._local_pull(source_repo.path, source_ref)
678 shadow_repo._local_pull(source_repo.path, source_ref)
675 except CommitDoesNotExistError as e:
679 except CommitDoesNotExistError as e:
676 log.exception('Failure when doing local pull on hg shadow repo')
680 log.exception('Failure when doing local pull on hg shadow repo')
677 return MergeResponse(
681 return MergeResponse(
678 False, False, None, MergeFailureReason.MISSING_COMMIT)
682 False, False, None, MergeFailureReason.MISSING_COMMIT)
679
683
680 merge_commit_id = None
684 merge_commit_id = None
681 merge_failure_reason = MergeFailureReason.NONE
685 merge_failure_reason = MergeFailureReason.NONE
682
686
683 try:
687 try:
684 merge_commit_id, needs_push = shadow_repo._local_merge(
688 merge_commit_id, needs_push = shadow_repo._local_merge(
685 target_ref, merge_message, merger_name, merger_email,
689 target_ref, merge_message, merger_name, merger_email,
686 source_ref)
690 source_ref)
687 merge_possible = True
691 merge_possible = True
688 except RepositoryError as e:
692 except RepositoryError as e:
689 log.exception('Failure when doing local merge on hg shadow repo')
693 log.exception('Failure when doing local merge on hg shadow repo')
690 merge_possible = False
694 merge_possible = False
691 merge_failure_reason = MergeFailureReason.MERGE_FAILED
695 merge_failure_reason = MergeFailureReason.MERGE_FAILED
692
696
693 if merge_possible and not dry_run:
697 if merge_possible and not dry_run:
694 if needs_push:
698 if needs_push:
695 # In case the target is a bookmark, update it, so after pushing
699 # In case the target is a bookmark, update it, so after pushing
696 # the bookmarks is also updated in the target.
700 # the bookmarks is also updated in the target.
697 if target_ref.type == 'book':
701 if target_ref.type == 'book':
698 shadow_repo.bookmark(
702 shadow_repo.bookmark(
699 target_ref.name, revision=merge_commit_id)
703 target_ref.name, revision=merge_commit_id)
700
704
701 try:
705 try:
702 shadow_repo_with_hooks = self._get_shadow_instance(
706 shadow_repo_with_hooks = self._get_shadow_instance(
703 shadow_repository_path,
707 shadow_repository_path,
704 enable_hooks=True)
708 enable_hooks=True)
705 # Note: the push_branches option will push any new branch
709 # Note: the push_branches option will push any new branch
706 # defined in the source repository to the target. This may
710 # defined in the source repository to the target. This may
707 # be dangerous as branches are permanent in Mercurial.
711 # be dangerous as branches are permanent in Mercurial.
708 # This feature was requested in issue #441.
712 # This feature was requested in issue #441.
709 shadow_repo_with_hooks._local_push(
713 shadow_repo_with_hooks._local_push(
710 merge_commit_id, self.path, push_branches=True,
714 merge_commit_id, self.path, push_branches=True,
711 enable_hooks=True)
715 enable_hooks=True)
712 merge_succeeded = True
716 merge_succeeded = True
713 except RepositoryError as e:
717 except RepositoryError as e:
714 log.exception(
718 log.exception(
715 'Failure when doing local push on hg shadow repo')
719 'Failure when doing local push on hg shadow repo')
716 merge_succeeded = False
720 merge_succeeded = False
717 merge_failure_reason = MergeFailureReason.PUSH_FAILED
721 merge_failure_reason = MergeFailureReason.PUSH_FAILED
718 else:
722 else:
719 merge_succeeded = True
723 merge_succeeded = True
720 else:
724 else:
721 merge_succeeded = False
725 merge_succeeded = False
722
726
723 if dry_run:
727 if dry_run:
724 merge_commit_id = None
728 merge_commit_id = None
725
729
726 return MergeResponse(
730 return MergeResponse(
727 merge_possible, merge_succeeded, merge_commit_id,
731 merge_possible, merge_succeeded, merge_commit_id,
728 merge_failure_reason)
732 merge_failure_reason)
729
733
730 def _get_shadow_instance(
734 def _get_shadow_instance(
731 self, shadow_repository_path, enable_hooks=False):
735 self, shadow_repository_path, enable_hooks=False):
732 config = self.config.copy()
736 config = self.config.copy()
733 if not enable_hooks:
737 if not enable_hooks:
734 config.clear_section('hooks')
738 config.clear_section('hooks')
735 return MercurialRepository(shadow_repository_path, config)
739 return MercurialRepository(shadow_repository_path, config)
736
740
737 def _validate_pull_reference(self, reference):
741 def _validate_pull_reference(self, reference):
738 if not (reference.name in self.bookmarks or
742 if not (reference.name in self.bookmarks or
739 reference.name in self.branches or
743 reference.name in self.branches or
740 self.get_commit(reference.commit_id)):
744 self.get_commit(reference.commit_id)):
741 raise CommitDoesNotExistError(
745 raise CommitDoesNotExistError(
742 'Unknown branch, bookmark or commit id')
746 'Unknown branch, bookmark or commit id')
743
747
744 def _local_pull(self, repository_path, reference):
748 def _local_pull(self, repository_path, reference):
745 """
749 """
746 Fetch a branch, bookmark or commit from a local repository.
750 Fetch a branch, bookmark or commit from a local repository.
747 """
751 """
748 repository_path = os.path.abspath(repository_path)
752 repository_path = os.path.abspath(repository_path)
749 if repository_path == self.path:
753 if repository_path == self.path:
750 raise ValueError('Cannot pull from the same repository')
754 raise ValueError('Cannot pull from the same repository')
751
755
752 reference_type_to_option_name = {
756 reference_type_to_option_name = {
753 'book': 'bookmark',
757 'book': 'bookmark',
754 'branch': 'branch',
758 'branch': 'branch',
755 }
759 }
756 option_name = reference_type_to_option_name.get(
760 option_name = reference_type_to_option_name.get(
757 reference.type, 'revision')
761 reference.type, 'revision')
758
762
759 if option_name == 'revision':
763 if option_name == 'revision':
760 ref = reference.commit_id
764 ref = reference.commit_id
761 else:
765 else:
762 ref = reference.name
766 ref = reference.name
763
767
764 options = {option_name: [ref]}
768 options = {option_name: [ref]}
765 self._remote.pull_cmd(repository_path, hooks=False, **options)
769 self._remote.pull_cmd(repository_path, hooks=False, **options)
766
770
767 def bookmark(self, bookmark, revision=None):
771 def bookmark(self, bookmark, revision=None):
768 if isinstance(bookmark, unicode):
772 if isinstance(bookmark, unicode):
769 bookmark = safe_str(bookmark)
773 bookmark = safe_str(bookmark)
770 self._remote.bookmark(bookmark, revision=revision)
774 self._remote.bookmark(bookmark, revision=revision)
771
775
772
776
773 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
777 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
774
778
775 def _commit_factory(self, commit_id):
779 def _commit_factory(self, commit_id):
776 return self.repo.get_commit(
780 return self.repo.get_commit(
777 commit_idx=commit_id, pre_load=self.pre_load)
781 commit_idx=commit_id, pre_load=self.pre_load)
General Comments 0
You need to be logged in to leave comments. Login now