##// END OF EJS Templates
mercurial: implemented verify command support.
marcink -
r179:d21df11c default
parent child Browse files
Show More
@@ -1,711 +1,727 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2017 RodeCode GmbH
2 # Copyright (C) 2014-2017 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import urllib
21 import urllib
22 import urllib2
22 import urllib2
23
23
24 from hgext import largefiles, rebase
24 from hgext import largefiles, rebase
25 from hgext.strip import strip as hgext_strip
25 from hgext.strip import strip as hgext_strip
26 from mercurial import commands
26 from mercurial import commands
27 from mercurial import unionrepo
27 from mercurial import unionrepo
28 from mercurial import verify
28
29
29 from vcsserver import exceptions
30 from vcsserver import exceptions
30 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
31 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
31 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
32 archival, bin, clone, config as hgconfig, diffopts, hex,
33 archival, bin, clone, config as hgconfig, diffopts, hex,
33 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
34 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
35 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
36 InterventionRequired, RequirementError)
37 InterventionRequired, RequirementError)
37
38
38 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
39
40
40
41
41 def make_ui_from_config(repo_config):
42 def make_ui_from_config(repo_config):
42 baseui = ui.ui()
43 baseui = ui.ui()
43
44
44 # clean the baseui object
45 # clean the baseui object
45 baseui._ocfg = hgconfig.config()
46 baseui._ocfg = hgconfig.config()
46 baseui._ucfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
47 baseui._tcfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
48
49
49 for section, option, value in repo_config:
50 for section, option, value in repo_config:
50 baseui.setconfig(section, option, value)
51 baseui.setconfig(section, option, value)
51
52
52 # make our hgweb quiet so it doesn't print output
53 # make our hgweb quiet so it doesn't print output
53 baseui.setconfig('ui', 'quiet', 'true')
54 baseui.setconfig('ui', 'quiet', 'true')
54
55
55 # force mercurial to only use 1 thread, otherwise it may try to set a
56 # force mercurial to only use 1 thread, otherwise it may try to set a
56 # signal in a non-main thread, thus generating a ValueError.
57 # signal in a non-main thread, thus generating a ValueError.
57 baseui.setconfig('worker', 'numcpus', 1)
58 baseui.setconfig('worker', 'numcpus', 1)
58
59
59 # If there is no config for the largefiles extension, we explicitly disable
60 # If there is no config for the largefiles extension, we explicitly disable
60 # it here. This overrides settings from repositories hgrc file. Recent
61 # it here. This overrides settings from repositories hgrc file. Recent
61 # mercurial versions enable largefiles in hgrc on clone from largefile
62 # mercurial versions enable largefiles in hgrc on clone from largefile
62 # repo.
63 # repo.
63 if not baseui.hasconfig('extensions', 'largefiles'):
64 if not baseui.hasconfig('extensions', 'largefiles'):
64 log.debug('Explicitly disable largefiles extension for repo.')
65 log.debug('Explicitly disable largefiles extension for repo.')
65 baseui.setconfig('extensions', 'largefiles', '!')
66 baseui.setconfig('extensions', 'largefiles', '!')
66
67
67 return baseui
68 return baseui
68
69
69
70
70 def reraise_safe_exceptions(func):
71 def reraise_safe_exceptions(func):
71 """Decorator for converting mercurial exceptions to something neutral."""
72 """Decorator for converting mercurial exceptions to something neutral."""
72 def wrapper(*args, **kwargs):
73 def wrapper(*args, **kwargs):
73 try:
74 try:
74 return func(*args, **kwargs)
75 return func(*args, **kwargs)
75 except (Abort, InterventionRequired):
76 except (Abort, InterventionRequired):
76 raise_from_original(exceptions.AbortException)
77 raise_from_original(exceptions.AbortException)
77 except RepoLookupError:
78 except RepoLookupError:
78 raise_from_original(exceptions.LookupException)
79 raise_from_original(exceptions.LookupException)
79 except RequirementError:
80 except RequirementError:
80 raise_from_original(exceptions.RequirementException)
81 raise_from_original(exceptions.RequirementException)
81 except RepoError:
82 except RepoError:
82 raise_from_original(exceptions.VcsException)
83 raise_from_original(exceptions.VcsException)
83 except LookupError:
84 except LookupError:
84 raise_from_original(exceptions.LookupException)
85 raise_from_original(exceptions.LookupException)
85 except Exception as e:
86 except Exception as e:
86 if not hasattr(e, '_vcs_kind'):
87 if not hasattr(e, '_vcs_kind'):
87 log.exception("Unhandled exception in hg remote call")
88 log.exception("Unhandled exception in hg remote call")
88 raise_from_original(exceptions.UnhandledException)
89 raise_from_original(exceptions.UnhandledException)
89 raise
90 raise
90 return wrapper
91 return wrapper
91
92
92
93
93 class MercurialFactory(RepoFactory):
94 class MercurialFactory(RepoFactory):
94
95
95 def _create_config(self, config, hooks=True):
96 def _create_config(self, config, hooks=True):
96 if not hooks:
97 if not hooks:
97 hooks_to_clean = frozenset((
98 hooks_to_clean = frozenset((
98 'changegroup.repo_size', 'preoutgoing.pre_pull',
99 'changegroup.repo_size', 'preoutgoing.pre_pull',
99 'outgoing.pull_logger', 'prechangegroup.pre_push'))
100 'outgoing.pull_logger', 'prechangegroup.pre_push'))
100 new_config = []
101 new_config = []
101 for section, option, value in config:
102 for section, option, value in config:
102 if section == 'hooks' and option in hooks_to_clean:
103 if section == 'hooks' and option in hooks_to_clean:
103 continue
104 continue
104 new_config.append((section, option, value))
105 new_config.append((section, option, value))
105 config = new_config
106 config = new_config
106
107
107 baseui = make_ui_from_config(config)
108 baseui = make_ui_from_config(config)
108 return baseui
109 return baseui
109
110
110 def _create_repo(self, wire, create):
111 def _create_repo(self, wire, create):
111 baseui = self._create_config(wire["config"])
112 baseui = self._create_config(wire["config"])
112 return localrepository(baseui, wire["path"], create)
113 return localrepository(baseui, wire["path"], create)
113
114
114
115
115 class HgRemote(object):
116 class HgRemote(object):
116
117
117 def __init__(self, factory):
118 def __init__(self, factory):
118 self._factory = factory
119 self._factory = factory
119
120
120 self._bulk_methods = {
121 self._bulk_methods = {
121 "affected_files": self.ctx_files,
122 "affected_files": self.ctx_files,
122 "author": self.ctx_user,
123 "author": self.ctx_user,
123 "branch": self.ctx_branch,
124 "branch": self.ctx_branch,
124 "children": self.ctx_children,
125 "children": self.ctx_children,
125 "date": self.ctx_date,
126 "date": self.ctx_date,
126 "message": self.ctx_description,
127 "message": self.ctx_description,
127 "parents": self.ctx_parents,
128 "parents": self.ctx_parents,
128 "status": self.ctx_status,
129 "status": self.ctx_status,
129 "_file_paths": self.ctx_list,
130 "_file_paths": self.ctx_list,
130 }
131 }
131
132
132 @reraise_safe_exceptions
133 @reraise_safe_exceptions
133 def discover_hg_version(self):
134 def discover_hg_version(self):
134 from mercurial import util
135 from mercurial import util
135 return util.version()
136 return util.version()
136
137
137 @reraise_safe_exceptions
138 @reraise_safe_exceptions
138 def archive_repo(self, archive_path, mtime, file_info, kind):
139 def archive_repo(self, archive_path, mtime, file_info, kind):
139 if kind == "tgz":
140 if kind == "tgz":
140 archiver = archival.tarit(archive_path, mtime, "gz")
141 archiver = archival.tarit(archive_path, mtime, "gz")
141 elif kind == "tbz2":
142 elif kind == "tbz2":
142 archiver = archival.tarit(archive_path, mtime, "bz2")
143 archiver = archival.tarit(archive_path, mtime, "bz2")
143 elif kind == 'zip':
144 elif kind == 'zip':
144 archiver = archival.zipit(archive_path, mtime)
145 archiver = archival.zipit(archive_path, mtime)
145 else:
146 else:
146 raise exceptions.ArchiveException(
147 raise exceptions.ArchiveException(
147 'Remote does not support: "%s".' % kind)
148 'Remote does not support: "%s".' % kind)
148
149
149 for f_path, f_mode, f_is_link, f_content in file_info:
150 for f_path, f_mode, f_is_link, f_content in file_info:
150 archiver.addfile(f_path, f_mode, f_is_link, f_content)
151 archiver.addfile(f_path, f_mode, f_is_link, f_content)
151 archiver.done()
152 archiver.done()
152
153
153 @reraise_safe_exceptions
154 @reraise_safe_exceptions
154 def bookmarks(self, wire):
155 def bookmarks(self, wire):
155 repo = self._factory.repo(wire)
156 repo = self._factory.repo(wire)
156 return dict(repo._bookmarks)
157 return dict(repo._bookmarks)
157
158
158 @reraise_safe_exceptions
159 @reraise_safe_exceptions
159 def branches(self, wire, normal, closed):
160 def branches(self, wire, normal, closed):
160 repo = self._factory.repo(wire)
161 repo = self._factory.repo(wire)
161 iter_branches = repo.branchmap().iterbranches()
162 iter_branches = repo.branchmap().iterbranches()
162 bt = {}
163 bt = {}
163 for branch_name, _heads, tip, is_closed in iter_branches:
164 for branch_name, _heads, tip, is_closed in iter_branches:
164 if normal and not is_closed:
165 if normal and not is_closed:
165 bt[branch_name] = tip
166 bt[branch_name] = tip
166 if closed and is_closed:
167 if closed and is_closed:
167 bt[branch_name] = tip
168 bt[branch_name] = tip
168
169
169 return bt
170 return bt
170
171
171 @reraise_safe_exceptions
172 @reraise_safe_exceptions
172 def bulk_request(self, wire, rev, pre_load):
173 def bulk_request(self, wire, rev, pre_load):
173 result = {}
174 result = {}
174 for attr in pre_load:
175 for attr in pre_load:
175 try:
176 try:
176 method = self._bulk_methods[attr]
177 method = self._bulk_methods[attr]
177 result[attr] = method(wire, rev)
178 result[attr] = method(wire, rev)
178 except KeyError:
179 except KeyError:
179 raise exceptions.VcsException(
180 raise exceptions.VcsException(
180 'Unknown bulk attribute: "%s"' % attr)
181 'Unknown bulk attribute: "%s"' % attr)
181 return result
182 return result
182
183
183 @reraise_safe_exceptions
184 @reraise_safe_exceptions
184 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
185 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
185 baseui = self._factory._create_config(wire["config"], hooks=hooks)
186 baseui = self._factory._create_config(wire["config"], hooks=hooks)
186 clone(baseui, source, dest, noupdate=not update_after_clone)
187 clone(baseui, source, dest, noupdate=not update_after_clone)
187
188
188 @reraise_safe_exceptions
189 @reraise_safe_exceptions
189 def commitctx(
190 def commitctx(
190 self, wire, message, parents, commit_time, commit_timezone,
191 self, wire, message, parents, commit_time, commit_timezone,
191 user, files, extra, removed, updated):
192 user, files, extra, removed, updated):
192
193
193 def _filectxfn(_repo, memctx, path):
194 def _filectxfn(_repo, memctx, path):
194 """
195 """
195 Marks given path as added/changed/removed in a given _repo. This is
196 Marks given path as added/changed/removed in a given _repo. This is
196 for internal mercurial commit function.
197 for internal mercurial commit function.
197 """
198 """
198
199
199 # check if this path is removed
200 # check if this path is removed
200 if path in removed:
201 if path in removed:
201 # returning None is a way to mark node for removal
202 # returning None is a way to mark node for removal
202 return None
203 return None
203
204
204 # check if this path is added
205 # check if this path is added
205 for node in updated:
206 for node in updated:
206 if node['path'] == path:
207 if node['path'] == path:
207 return memfilectx(
208 return memfilectx(
208 _repo,
209 _repo,
209 path=node['path'],
210 path=node['path'],
210 data=node['content'],
211 data=node['content'],
211 islink=False,
212 islink=False,
212 isexec=bool(node['mode'] & stat.S_IXUSR),
213 isexec=bool(node['mode'] & stat.S_IXUSR),
213 copied=False,
214 copied=False,
214 memctx=memctx)
215 memctx=memctx)
215
216
216 raise exceptions.AbortException(
217 raise exceptions.AbortException(
217 "Given path haven't been marked as added, "
218 "Given path haven't been marked as added, "
218 "changed or removed (%s)" % path)
219 "changed or removed (%s)" % path)
219
220
220 repo = self._factory.repo(wire)
221 repo = self._factory.repo(wire)
221
222
222 commit_ctx = memctx(
223 commit_ctx = memctx(
223 repo=repo,
224 repo=repo,
224 parents=parents,
225 parents=parents,
225 text=message,
226 text=message,
226 files=files,
227 files=files,
227 filectxfn=_filectxfn,
228 filectxfn=_filectxfn,
228 user=user,
229 user=user,
229 date=(commit_time, commit_timezone),
230 date=(commit_time, commit_timezone),
230 extra=extra)
231 extra=extra)
231
232
232 n = repo.commitctx(commit_ctx)
233 n = repo.commitctx(commit_ctx)
233 new_id = hex(n)
234 new_id = hex(n)
234
235
235 return new_id
236 return new_id
236
237
237 @reraise_safe_exceptions
238 @reraise_safe_exceptions
238 def ctx_branch(self, wire, revision):
239 def ctx_branch(self, wire, revision):
239 repo = self._factory.repo(wire)
240 repo = self._factory.repo(wire)
240 ctx = repo[revision]
241 ctx = repo[revision]
241 return ctx.branch()
242 return ctx.branch()
242
243
243 @reraise_safe_exceptions
244 @reraise_safe_exceptions
244 def ctx_children(self, wire, revision):
245 def ctx_children(self, wire, revision):
245 repo = self._factory.repo(wire)
246 repo = self._factory.repo(wire)
246 ctx = repo[revision]
247 ctx = repo[revision]
247 return [child.rev() for child in ctx.children()]
248 return [child.rev() for child in ctx.children()]
248
249
249 @reraise_safe_exceptions
250 @reraise_safe_exceptions
250 def ctx_date(self, wire, revision):
251 def ctx_date(self, wire, revision):
251 repo = self._factory.repo(wire)
252 repo = self._factory.repo(wire)
252 ctx = repo[revision]
253 ctx = repo[revision]
253 return ctx.date()
254 return ctx.date()
254
255
255 @reraise_safe_exceptions
256 @reraise_safe_exceptions
256 def ctx_description(self, wire, revision):
257 def ctx_description(self, wire, revision):
257 repo = self._factory.repo(wire)
258 repo = self._factory.repo(wire)
258 ctx = repo[revision]
259 ctx = repo[revision]
259 return ctx.description()
260 return ctx.description()
260
261
261 @reraise_safe_exceptions
262 @reraise_safe_exceptions
262 def ctx_diff(
263 def ctx_diff(
263 self, wire, revision, git=True, ignore_whitespace=True, context=3):
264 self, wire, revision, git=True, ignore_whitespace=True, context=3):
264 repo = self._factory.repo(wire)
265 repo = self._factory.repo(wire)
265 ctx = repo[revision]
266 ctx = repo[revision]
266 result = ctx.diff(
267 result = ctx.diff(
267 git=git, ignore_whitespace=ignore_whitespace, context=context)
268 git=git, ignore_whitespace=ignore_whitespace, context=context)
268 return list(result)
269 return list(result)
269
270
270 @reraise_safe_exceptions
271 @reraise_safe_exceptions
271 def ctx_files(self, wire, revision):
272 def ctx_files(self, wire, revision):
272 repo = self._factory.repo(wire)
273 repo = self._factory.repo(wire)
273 ctx = repo[revision]
274 ctx = repo[revision]
274 return ctx.files()
275 return ctx.files()
275
276
276 @reraise_safe_exceptions
277 @reraise_safe_exceptions
277 def ctx_list(self, path, revision):
278 def ctx_list(self, path, revision):
278 repo = self._factory.repo(path)
279 repo = self._factory.repo(path)
279 ctx = repo[revision]
280 ctx = repo[revision]
280 return list(ctx)
281 return list(ctx)
281
282
282 @reraise_safe_exceptions
283 @reraise_safe_exceptions
283 def ctx_parents(self, wire, revision):
284 def ctx_parents(self, wire, revision):
284 repo = self._factory.repo(wire)
285 repo = self._factory.repo(wire)
285 ctx = repo[revision]
286 ctx = repo[revision]
286 return [parent.rev() for parent in ctx.parents()]
287 return [parent.rev() for parent in ctx.parents()]
287
288
288 @reraise_safe_exceptions
289 @reraise_safe_exceptions
289 def ctx_substate(self, wire, revision):
290 def ctx_substate(self, wire, revision):
290 repo = self._factory.repo(wire)
291 repo = self._factory.repo(wire)
291 ctx = repo[revision]
292 ctx = repo[revision]
292 return ctx.substate
293 return ctx.substate
293
294
294 @reraise_safe_exceptions
295 @reraise_safe_exceptions
295 def ctx_status(self, wire, revision):
296 def ctx_status(self, wire, revision):
296 repo = self._factory.repo(wire)
297 repo = self._factory.repo(wire)
297 ctx = repo[revision]
298 ctx = repo[revision]
298 status = repo[ctx.p1().node()].status(other=ctx.node())
299 status = repo[ctx.p1().node()].status(other=ctx.node())
299 # object of status (odd, custom named tuple in mercurial) is not
300 # object of status (odd, custom named tuple in mercurial) is not
300 # correctly serializable via Pyro, we make it a list, as the underling
301 # correctly serializable via Pyro, we make it a list, as the underling
301 # API expects this to be a list
302 # API expects this to be a list
302 return list(status)
303 return list(status)
303
304
304 @reraise_safe_exceptions
305 @reraise_safe_exceptions
305 def ctx_user(self, wire, revision):
306 def ctx_user(self, wire, revision):
306 repo = self._factory.repo(wire)
307 repo = self._factory.repo(wire)
307 ctx = repo[revision]
308 ctx = repo[revision]
308 return ctx.user()
309 return ctx.user()
309
310
310 @reraise_safe_exceptions
311 @reraise_safe_exceptions
311 def check_url(self, url, config):
312 def check_url(self, url, config):
312 _proto = None
313 _proto = None
313 if '+' in url[:url.find('://')]:
314 if '+' in url[:url.find('://')]:
314 _proto = url[0:url.find('+')]
315 _proto = url[0:url.find('+')]
315 url = url[url.find('+') + 1:]
316 url = url[url.find('+') + 1:]
316 handlers = []
317 handlers = []
317 url_obj = url_parser(url)
318 url_obj = url_parser(url)
318 test_uri, authinfo = url_obj.authinfo()
319 test_uri, authinfo = url_obj.authinfo()
319 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
320 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
320 url_obj.query = obfuscate_qs(url_obj.query)
321 url_obj.query = obfuscate_qs(url_obj.query)
321
322
322 cleaned_uri = str(url_obj)
323 cleaned_uri = str(url_obj)
323 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
324 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
324
325
325 if authinfo:
326 if authinfo:
326 # create a password manager
327 # create a password manager
327 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
328 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
328 passmgr.add_password(*authinfo)
329 passmgr.add_password(*authinfo)
329
330
330 handlers.extend((httpbasicauthhandler(passmgr),
331 handlers.extend((httpbasicauthhandler(passmgr),
331 httpdigestauthhandler(passmgr)))
332 httpdigestauthhandler(passmgr)))
332
333
333 o = urllib2.build_opener(*handlers)
334 o = urllib2.build_opener(*handlers)
334 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
335 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
335 ('Accept', 'application/mercurial-0.1')]
336 ('Accept', 'application/mercurial-0.1')]
336
337
337 q = {"cmd": 'between'}
338 q = {"cmd": 'between'}
338 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
339 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
339 qs = '?%s' % urllib.urlencode(q)
340 qs = '?%s' % urllib.urlencode(q)
340 cu = "%s%s" % (test_uri, qs)
341 cu = "%s%s" % (test_uri, qs)
341 req = urllib2.Request(cu, None, {})
342 req = urllib2.Request(cu, None, {})
342
343
343 try:
344 try:
344 log.debug("Trying to open URL %s", cleaned_uri)
345 log.debug("Trying to open URL %s", cleaned_uri)
345 resp = o.open(req)
346 resp = o.open(req)
346 if resp.code != 200:
347 if resp.code != 200:
347 raise exceptions.URLError('Return Code is not 200')
348 raise exceptions.URLError('Return Code is not 200')
348 except Exception as e:
349 except Exception as e:
349 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
350 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
350 # means it cannot be cloned
351 # means it cannot be cloned
351 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
352 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
352
353
353 # now check if it's a proper hg repo, but don't do it for svn
354 # now check if it's a proper hg repo, but don't do it for svn
354 try:
355 try:
355 if _proto == 'svn':
356 if _proto == 'svn':
356 pass
357 pass
357 else:
358 else:
358 # check for pure hg repos
359 # check for pure hg repos
359 log.debug(
360 log.debug(
360 "Verifying if URL is a Mercurial repository: %s",
361 "Verifying if URL is a Mercurial repository: %s",
361 cleaned_uri)
362 cleaned_uri)
362 httppeer(make_ui_from_config(config), url).lookup('tip')
363 httppeer(make_ui_from_config(config), url).lookup('tip')
363 except Exception as e:
364 except Exception as e:
364 log.warning("URL is not a valid Mercurial repository: %s",
365 log.warning("URL is not a valid Mercurial repository: %s",
365 cleaned_uri)
366 cleaned_uri)
366 raise exceptions.URLError(
367 raise exceptions.URLError(
367 "url [%s] does not look like an hg repo org_exc: %s"
368 "url [%s] does not look like an hg repo org_exc: %s"
368 % (cleaned_uri, e))
369 % (cleaned_uri, e))
369
370
370 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
371 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
371 return True
372 return True
372
373
373 @reraise_safe_exceptions
374 @reraise_safe_exceptions
374 def diff(
375 def diff(
375 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
376 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
376 context):
377 context):
377 repo = self._factory.repo(wire)
378 repo = self._factory.repo(wire)
378
379
379 if file_filter:
380 if file_filter:
380 match_filter = match(file_filter[0], '', [file_filter[1]])
381 match_filter = match(file_filter[0], '', [file_filter[1]])
381 else:
382 else:
382 match_filter = file_filter
383 match_filter = file_filter
383 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
384 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
384
385
385 try:
386 try:
386 return "".join(patch.diff(
387 return "".join(patch.diff(
387 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
388 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
388 except RepoLookupError:
389 except RepoLookupError:
389 raise exceptions.LookupException()
390 raise exceptions.LookupException()
390
391
391 @reraise_safe_exceptions
392 @reraise_safe_exceptions
392 def file_history(self, wire, revision, path, limit):
393 def file_history(self, wire, revision, path, limit):
393 repo = self._factory.repo(wire)
394 repo = self._factory.repo(wire)
394
395
395 ctx = repo[revision]
396 ctx = repo[revision]
396 fctx = ctx.filectx(path)
397 fctx = ctx.filectx(path)
397
398
398 def history_iter():
399 def history_iter():
399 limit_rev = fctx.rev()
400 limit_rev = fctx.rev()
400 for obj in reversed(list(fctx.filelog())):
401 for obj in reversed(list(fctx.filelog())):
401 obj = fctx.filectx(obj)
402 obj = fctx.filectx(obj)
402 if limit_rev >= obj.rev():
403 if limit_rev >= obj.rev():
403 yield obj
404 yield obj
404
405
405 history = []
406 history = []
406 for cnt, obj in enumerate(history_iter()):
407 for cnt, obj in enumerate(history_iter()):
407 if limit and cnt >= limit:
408 if limit and cnt >= limit:
408 break
409 break
409 history.append(hex(obj.node()))
410 history.append(hex(obj.node()))
410
411
411 return [x for x in history]
412 return [x for x in history]
412
413
413 @reraise_safe_exceptions
414 @reraise_safe_exceptions
414 def file_history_untill(self, wire, revision, path, limit):
415 def file_history_untill(self, wire, revision, path, limit):
415 repo = self._factory.repo(wire)
416 repo = self._factory.repo(wire)
416 ctx = repo[revision]
417 ctx = repo[revision]
417 fctx = ctx.filectx(path)
418 fctx = ctx.filectx(path)
418
419
419 file_log = list(fctx.filelog())
420 file_log = list(fctx.filelog())
420 if limit:
421 if limit:
421 # Limit to the last n items
422 # Limit to the last n items
422 file_log = file_log[-limit:]
423 file_log = file_log[-limit:]
423
424
424 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
425 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
425
426
426 @reraise_safe_exceptions
427 @reraise_safe_exceptions
427 def fctx_annotate(self, wire, revision, path):
428 def fctx_annotate(self, wire, revision, path):
428 repo = self._factory.repo(wire)
429 repo = self._factory.repo(wire)
429 ctx = repo[revision]
430 ctx = repo[revision]
430 fctx = ctx.filectx(path)
431 fctx = ctx.filectx(path)
431
432
432 result = []
433 result = []
433 for i, annotate_data in enumerate(fctx.annotate()):
434 for i, annotate_data in enumerate(fctx.annotate()):
434 ln_no = i + 1
435 ln_no = i + 1
435 node_info, content = annotate_data
436 node_info, content = annotate_data
436 sha = hex(node_info[0].node())
437 sha = hex(node_info[0].node())
437 result.append((ln_no, sha, content))
438 result.append((ln_no, sha, content))
438 return result
439 return result
439
440
440 @reraise_safe_exceptions
441 @reraise_safe_exceptions
441 def fctx_data(self, wire, revision, path):
442 def fctx_data(self, wire, revision, path):
442 repo = self._factory.repo(wire)
443 repo = self._factory.repo(wire)
443 ctx = repo[revision]
444 ctx = repo[revision]
444 fctx = ctx.filectx(path)
445 fctx = ctx.filectx(path)
445 return fctx.data()
446 return fctx.data()
446
447
447 @reraise_safe_exceptions
448 @reraise_safe_exceptions
448 def fctx_flags(self, wire, revision, path):
449 def fctx_flags(self, wire, revision, path):
449 repo = self._factory.repo(wire)
450 repo = self._factory.repo(wire)
450 ctx = repo[revision]
451 ctx = repo[revision]
451 fctx = ctx.filectx(path)
452 fctx = ctx.filectx(path)
452 return fctx.flags()
453 return fctx.flags()
453
454
454 @reraise_safe_exceptions
455 @reraise_safe_exceptions
455 def fctx_size(self, wire, revision, path):
456 def fctx_size(self, wire, revision, path):
456 repo = self._factory.repo(wire)
457 repo = self._factory.repo(wire)
457 ctx = repo[revision]
458 ctx = repo[revision]
458 fctx = ctx.filectx(path)
459 fctx = ctx.filectx(path)
459 return fctx.size()
460 return fctx.size()
460
461
461 @reraise_safe_exceptions
462 @reraise_safe_exceptions
462 def get_all_commit_ids(self, wire, name):
463 def get_all_commit_ids(self, wire, name):
463 repo = self._factory.repo(wire)
464 repo = self._factory.repo(wire)
464 revs = repo.filtered(name).changelog.index
465 revs = repo.filtered(name).changelog.index
465 return map(lambda x: hex(x[7]), revs)[:-1]
466 return map(lambda x: hex(x[7]), revs)[:-1]
466
467
467 @reraise_safe_exceptions
468 @reraise_safe_exceptions
468 def get_config_value(self, wire, section, name, untrusted=False):
469 def get_config_value(self, wire, section, name, untrusted=False):
469 repo = self._factory.repo(wire)
470 repo = self._factory.repo(wire)
470 return repo.ui.config(section, name, untrusted=untrusted)
471 return repo.ui.config(section, name, untrusted=untrusted)
471
472
472 @reraise_safe_exceptions
473 @reraise_safe_exceptions
473 def get_config_bool(self, wire, section, name, untrusted=False):
474 def get_config_bool(self, wire, section, name, untrusted=False):
474 repo = self._factory.repo(wire)
475 repo = self._factory.repo(wire)
475 return repo.ui.configbool(section, name, untrusted=untrusted)
476 return repo.ui.configbool(section, name, untrusted=untrusted)
476
477
477 @reraise_safe_exceptions
478 @reraise_safe_exceptions
478 def get_config_list(self, wire, section, name, untrusted=False):
479 def get_config_list(self, wire, section, name, untrusted=False):
479 repo = self._factory.repo(wire)
480 repo = self._factory.repo(wire)
480 return repo.ui.configlist(section, name, untrusted=untrusted)
481 return repo.ui.configlist(section, name, untrusted=untrusted)
481
482
482 @reraise_safe_exceptions
483 @reraise_safe_exceptions
483 def is_large_file(self, wire, path):
484 def is_large_file(self, wire, path):
484 return largefiles.lfutil.isstandin(path)
485 return largefiles.lfutil.isstandin(path)
485
486
486 @reraise_safe_exceptions
487 @reraise_safe_exceptions
487 def in_store(self, wire, sha):
488 def in_store(self, wire, sha):
488 repo = self._factory.repo(wire)
489 repo = self._factory.repo(wire)
489 return largefiles.lfutil.instore(repo, sha)
490 return largefiles.lfutil.instore(repo, sha)
490
491
491 @reraise_safe_exceptions
492 @reraise_safe_exceptions
492 def in_user_cache(self, wire, sha):
493 def in_user_cache(self, wire, sha):
493 repo = self._factory.repo(wire)
494 repo = self._factory.repo(wire)
494 return largefiles.lfutil.inusercache(repo.ui, sha)
495 return largefiles.lfutil.inusercache(repo.ui, sha)
495
496
496 @reraise_safe_exceptions
497 @reraise_safe_exceptions
497 def store_path(self, wire, sha):
498 def store_path(self, wire, sha):
498 repo = self._factory.repo(wire)
499 repo = self._factory.repo(wire)
499 return largefiles.lfutil.storepath(repo, sha)
500 return largefiles.lfutil.storepath(repo, sha)
500
501
501 @reraise_safe_exceptions
502 @reraise_safe_exceptions
502 def link(self, wire, sha, path):
503 def link(self, wire, sha, path):
503 repo = self._factory.repo(wire)
504 repo = self._factory.repo(wire)
504 largefiles.lfutil.link(
505 largefiles.lfutil.link(
505 largefiles.lfutil.usercachepath(repo.ui, sha), path)
506 largefiles.lfutil.usercachepath(repo.ui, sha), path)
506
507
507 @reraise_safe_exceptions
508 @reraise_safe_exceptions
508 def localrepository(self, wire, create=False):
509 def localrepository(self, wire, create=False):
509 self._factory.repo(wire, create=create)
510 self._factory.repo(wire, create=create)
510
511
511 @reraise_safe_exceptions
512 @reraise_safe_exceptions
512 def lookup(self, wire, revision, both):
513 def lookup(self, wire, revision, both):
513 # TODO Paris: Ugly hack to "deserialize" long for msgpack
514 # TODO Paris: Ugly hack to "deserialize" long for msgpack
514 if isinstance(revision, float):
515 if isinstance(revision, float):
515 revision = long(revision)
516 revision = long(revision)
516 repo = self._factory.repo(wire)
517 repo = self._factory.repo(wire)
517 try:
518 try:
518 ctx = repo[revision]
519 ctx = repo[revision]
519 except RepoLookupError:
520 except RepoLookupError:
520 raise exceptions.LookupException(revision)
521 raise exceptions.LookupException(revision)
521 except LookupError as e:
522 except LookupError as e:
522 raise exceptions.LookupException(e.name)
523 raise exceptions.LookupException(e.name)
523
524
524 if not both:
525 if not both:
525 return ctx.hex()
526 return ctx.hex()
526
527
527 ctx = repo[ctx.hex()]
528 ctx = repo[ctx.hex()]
528 return ctx.hex(), ctx.rev()
529 return ctx.hex(), ctx.rev()
529
530
530 @reraise_safe_exceptions
531 @reraise_safe_exceptions
531 def pull(self, wire, url, commit_ids=None):
532 def pull(self, wire, url, commit_ids=None):
532 repo = self._factory.repo(wire)
533 repo = self._factory.repo(wire)
533 remote = peer(repo, {}, url)
534 remote = peer(repo, {}, url)
534 if commit_ids:
535 if commit_ids:
535 commit_ids = [bin(commit_id) for commit_id in commit_ids]
536 commit_ids = [bin(commit_id) for commit_id in commit_ids]
536
537
537 return exchange.pull(
538 return exchange.pull(
538 repo, remote, heads=commit_ids, force=None).cgresult
539 repo, remote, heads=commit_ids, force=None).cgresult
539
540
540 @reraise_safe_exceptions
541 @reraise_safe_exceptions
541 def revision(self, wire, rev):
542 def revision(self, wire, rev):
542 repo = self._factory.repo(wire)
543 repo = self._factory.repo(wire)
543 ctx = repo[rev]
544 ctx = repo[rev]
544 return ctx.rev()
545 return ctx.rev()
545
546
546 @reraise_safe_exceptions
547 @reraise_safe_exceptions
547 def rev_range(self, wire, filter):
548 def rev_range(self, wire, filter):
548 repo = self._factory.repo(wire)
549 repo = self._factory.repo(wire)
549 revisions = [rev for rev in revrange(repo, filter)]
550 revisions = [rev for rev in revrange(repo, filter)]
550 return revisions
551 return revisions
551
552
552 @reraise_safe_exceptions
553 @reraise_safe_exceptions
553 def rev_range_hash(self, wire, node):
554 def rev_range_hash(self, wire, node):
554 repo = self._factory.repo(wire)
555 repo = self._factory.repo(wire)
555
556
556 def get_revs(repo, rev_opt):
557 def get_revs(repo, rev_opt):
557 if rev_opt:
558 if rev_opt:
558 revs = revrange(repo, rev_opt)
559 revs = revrange(repo, rev_opt)
559 if len(revs) == 0:
560 if len(revs) == 0:
560 return (nullrev, nullrev)
561 return (nullrev, nullrev)
561 return max(revs), min(revs)
562 return max(revs), min(revs)
562 else:
563 else:
563 return len(repo) - 1, 0
564 return len(repo) - 1, 0
564
565
565 stop, start = get_revs(repo, [node + ':'])
566 stop, start = get_revs(repo, [node + ':'])
566 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
567 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
567 return revs
568 return revs
568
569
569 @reraise_safe_exceptions
570 @reraise_safe_exceptions
570 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
571 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
571 other_path = kwargs.pop('other_path', None)
572 other_path = kwargs.pop('other_path', None)
572
573
573 # case when we want to compare two independent repositories
574 # case when we want to compare two independent repositories
574 if other_path and other_path != wire["path"]:
575 if other_path and other_path != wire["path"]:
575 baseui = self._factory._create_config(wire["config"])
576 baseui = self._factory._create_config(wire["config"])
576 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
577 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
577 else:
578 else:
578 repo = self._factory.repo(wire)
579 repo = self._factory.repo(wire)
579 return list(repo.revs(rev_spec, *args))
580 return list(repo.revs(rev_spec, *args))
580
581
581 @reraise_safe_exceptions
582 @reraise_safe_exceptions
582 def strip(self, wire, revision, update, backup):
583 def strip(self, wire, revision, update, backup):
583 repo = self._factory.repo(wire)
584 repo = self._factory.repo(wire)
584 ctx = repo[revision]
585 ctx = repo[revision]
585 hgext_strip(
586 hgext_strip(
586 repo.baseui, repo, ctx.node(), update=update, backup=backup)
587 repo.baseui, repo, ctx.node(), update=update, backup=backup)
587
588
588 @reraise_safe_exceptions
589 @reraise_safe_exceptions
590 def verify(self, wire,):
591 repo = self._factory.repo(wire)
592 baseui = self._factory._create_config(wire['config'])
593 baseui.setconfig('ui', 'quiet', 'false')
594 output = io.BytesIO()
595
596 def write(data, **unused_kwargs):
597 output.write(data)
598 baseui.write = write
599
600 repo.ui = baseui
601 verify.verify(repo)
602 return output.getvalue()
603
604 @reraise_safe_exceptions
589 def tag(self, wire, name, revision, message, local, user,
605 def tag(self, wire, name, revision, message, local, user,
590 tag_time, tag_timezone):
606 tag_time, tag_timezone):
591 repo = self._factory.repo(wire)
607 repo = self._factory.repo(wire)
592 ctx = repo[revision]
608 ctx = repo[revision]
593 node = ctx.node()
609 node = ctx.node()
594
610
595 date = (tag_time, tag_timezone)
611 date = (tag_time, tag_timezone)
596 try:
612 try:
597 repo.tag(name, node, message, local, user, date)
613 repo.tag(name, node, message, local, user, date)
598 except Abort as e:
614 except Abort as e:
599 log.exception("Tag operation aborted")
615 log.exception("Tag operation aborted")
600 # Exception can contain unicode which we convert
616 # Exception can contain unicode which we convert
601 raise exceptions.AbortException(repr(e))
617 raise exceptions.AbortException(repr(e))
602
618
603 @reraise_safe_exceptions
619 @reraise_safe_exceptions
604 def tags(self, wire):
620 def tags(self, wire):
605 repo = self._factory.repo(wire)
621 repo = self._factory.repo(wire)
606 return repo.tags()
622 return repo.tags()
607
623
608 @reraise_safe_exceptions
624 @reraise_safe_exceptions
609 def update(self, wire, node=None, clean=False):
625 def update(self, wire, node=None, clean=False):
610 repo = self._factory.repo(wire)
626 repo = self._factory.repo(wire)
611 baseui = self._factory._create_config(wire['config'])
627 baseui = self._factory._create_config(wire['config'])
612 commands.update(baseui, repo, node=node, clean=clean)
628 commands.update(baseui, repo, node=node, clean=clean)
613
629
614 @reraise_safe_exceptions
630 @reraise_safe_exceptions
615 def identify(self, wire):
631 def identify(self, wire):
616 repo = self._factory.repo(wire)
632 repo = self._factory.repo(wire)
617 baseui = self._factory._create_config(wire['config'])
633 baseui = self._factory._create_config(wire['config'])
618 output = io.BytesIO()
634 output = io.BytesIO()
619 baseui.write = output.write
635 baseui.write = output.write
620 # This is required to get a full node id
636 # This is required to get a full node id
621 baseui.debugflag = True
637 baseui.debugflag = True
622 commands.identify(baseui, repo, id=True)
638 commands.identify(baseui, repo, id=True)
623
639
624 return output.getvalue()
640 return output.getvalue()
625
641
626 @reraise_safe_exceptions
642 @reraise_safe_exceptions
627 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
643 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
628 hooks=True):
644 hooks=True):
629 repo = self._factory.repo(wire)
645 repo = self._factory.repo(wire)
630 baseui = self._factory._create_config(wire['config'], hooks=hooks)
646 baseui = self._factory._create_config(wire['config'], hooks=hooks)
631
647
632 # Mercurial internally has a lot of logic that checks ONLY if
648 # Mercurial internally has a lot of logic that checks ONLY if
633 # option is defined, we just pass those if they are defined then
649 # option is defined, we just pass those if they are defined then
634 opts = {}
650 opts = {}
635 if bookmark:
651 if bookmark:
636 opts['bookmark'] = bookmark
652 opts['bookmark'] = bookmark
637 if branch:
653 if branch:
638 opts['branch'] = branch
654 opts['branch'] = branch
639 if revision:
655 if revision:
640 opts['rev'] = revision
656 opts['rev'] = revision
641
657
642 commands.pull(baseui, repo, source, **opts)
658 commands.pull(baseui, repo, source, **opts)
643
659
644 @reraise_safe_exceptions
660 @reraise_safe_exceptions
645 def heads(self, wire, branch=None):
661 def heads(self, wire, branch=None):
646 repo = self._factory.repo(wire)
662 repo = self._factory.repo(wire)
647 baseui = self._factory._create_config(wire['config'])
663 baseui = self._factory._create_config(wire['config'])
648 output = io.BytesIO()
664 output = io.BytesIO()
649
665
650 def write(data, **unused_kwargs):
666 def write(data, **unused_kwargs):
651 output.write(data)
667 output.write(data)
652
668
653 baseui.write = write
669 baseui.write = write
654 if branch:
670 if branch:
655 args = [branch]
671 args = [branch]
656 else:
672 else:
657 args = []
673 args = []
658 commands.heads(baseui, repo, template='{node} ', *args)
674 commands.heads(baseui, repo, template='{node} ', *args)
659
675
660 return output.getvalue()
676 return output.getvalue()
661
677
662 @reraise_safe_exceptions
678 @reraise_safe_exceptions
663 def ancestor(self, wire, revision1, revision2):
679 def ancestor(self, wire, revision1, revision2):
664 repo = self._factory.repo(wire)
680 repo = self._factory.repo(wire)
665 changelog = repo.changelog
681 changelog = repo.changelog
666 lookup = repo.lookup
682 lookup = repo.lookup
667 a = changelog.ancestor(lookup(revision1), lookup(revision2))
683 a = changelog.ancestor(lookup(revision1), lookup(revision2))
668 return hex(a)
684 return hex(a)
669
685
670 @reraise_safe_exceptions
686 @reraise_safe_exceptions
671 def push(self, wire, revisions, dest_path, hooks=True,
687 def push(self, wire, revisions, dest_path, hooks=True,
672 push_branches=False):
688 push_branches=False):
673 repo = self._factory.repo(wire)
689 repo = self._factory.repo(wire)
674 baseui = self._factory._create_config(wire['config'], hooks=hooks)
690 baseui = self._factory._create_config(wire['config'], hooks=hooks)
675 commands.push(baseui, repo, dest=dest_path, rev=revisions,
691 commands.push(baseui, repo, dest=dest_path, rev=revisions,
676 new_branch=push_branches)
692 new_branch=push_branches)
677
693
678 @reraise_safe_exceptions
694 @reraise_safe_exceptions
679 def merge(self, wire, revision):
695 def merge(self, wire, revision):
680 repo = self._factory.repo(wire)
696 repo = self._factory.repo(wire)
681 baseui = self._factory._create_config(wire['config'])
697 baseui = self._factory._create_config(wire['config'])
682 repo.ui.setconfig('ui', 'merge', 'internal:dump')
698 repo.ui.setconfig('ui', 'merge', 'internal:dump')
683
699
684 # In case of sub repositories are used mercurial prompts the user in
700 # In case of sub repositories are used mercurial prompts the user in
685 # case of merge conflicts or different sub repository sources. By
701 # case of merge conflicts or different sub repository sources. By
686 # setting the interactive flag to `False` mercurial doesn't prompt the
702 # setting the interactive flag to `False` mercurial doesn't prompt the
687 # used but instead uses a default value.
703 # used but instead uses a default value.
688 repo.ui.setconfig('ui', 'interactive', False)
704 repo.ui.setconfig('ui', 'interactive', False)
689
705
690 commands.merge(baseui, repo, rev=revision)
706 commands.merge(baseui, repo, rev=revision)
691
707
692 @reraise_safe_exceptions
708 @reraise_safe_exceptions
693 def commit(self, wire, message, username):
709 def commit(self, wire, message, username):
694 repo = self._factory.repo(wire)
710 repo = self._factory.repo(wire)
695 baseui = self._factory._create_config(wire['config'])
711 baseui = self._factory._create_config(wire['config'])
696 repo.ui.setconfig('ui', 'username', username)
712 repo.ui.setconfig('ui', 'username', username)
697 commands.commit(baseui, repo, message=message)
713 commands.commit(baseui, repo, message=message)
698
714
699 @reraise_safe_exceptions
715 @reraise_safe_exceptions
700 def rebase(self, wire, source=None, dest=None, abort=False):
716 def rebase(self, wire, source=None, dest=None, abort=False):
701 repo = self._factory.repo(wire)
717 repo = self._factory.repo(wire)
702 baseui = self._factory._create_config(wire['config'])
718 baseui = self._factory._create_config(wire['config'])
703 repo.ui.setconfig('ui', 'merge', 'internal:dump')
719 repo.ui.setconfig('ui', 'merge', 'internal:dump')
704 rebase.rebase(
720 rebase.rebase(
705 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
721 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
706
722
707 @reraise_safe_exceptions
723 @reraise_safe_exceptions
708 def bookmark(self, wire, bookmark, revision=None):
724 def bookmark(self, wire, bookmark, revision=None):
709 repo = self._factory.repo(wire)
725 repo = self._factory.repo(wire)
710 baseui = self._factory._create_config(wire['config'])
726 baseui = self._factory._create_config(wire['config'])
711 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
727 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
General Comments 0
You need to be logged in to leave comments. Login now