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