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