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