##// END OF EJS Templates
remote-clones: make sure we always use obfuscated url inside logs....
marcink -
r105:859d7a10 default
parent child Browse files
Show More
@@ -1,576 +1,579 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import logging
19 19 import os
20 20 import posixpath as vcspath
21 21 import re
22 22 import stat
23 23 import urllib
24 24 import urllib2
25 25 from functools import wraps
26 26
27 27 from dulwich import index, objects
28 28 from dulwich.client import HttpGitClient, LocalGitClient
29 29 from dulwich.errors import (
30 30 NotGitRepository, ChecksumMismatch, WrongObjectException,
31 31 MissingCommitError, ObjectMissing, HangupException,
32 32 UnexpectedCommandError)
33 33 from dulwich.repo import Repo as DulwichRepo, Tag
34 34 from dulwich.server import update_server_info
35 35
36 36 from vcsserver import exceptions, settings, subprocessio
37 37 from vcsserver.utils import safe_str
38 38 from vcsserver.base import RepoFactory
39 39 from vcsserver.hgcompat import (
40 hg_url, httpbasicauthhandler, httpdigestauthhandler)
40 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
41 41
42 42
43 43 DIR_STAT = stat.S_IFDIR
44 44 FILE_MODE = stat.S_IFMT
45 45 GIT_LINK = objects.S_IFGITLINK
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 def reraise_safe_exceptions(func):
51 51 """Converts Dulwich exceptions to something neutral."""
52 52 @wraps(func)
53 53 def wrapper(*args, **kwargs):
54 54 try:
55 55 return func(*args, **kwargs)
56 56 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
57 57 ObjectMissing) as e:
58 58 raise exceptions.LookupException(e.message)
59 59 except (HangupException, UnexpectedCommandError) as e:
60 60 raise exceptions.VcsException(e.message)
61 61 return wrapper
62 62
63 63
64 64 class Repo(DulwichRepo):
65 65 """
66 66 A wrapper for dulwich Repo class.
67 67
68 68 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
69 69 "Too many open files" error. We need to close all opened file descriptors
70 70 once the repo object is destroyed.
71 71
72 72 TODO: mikhail: please check if we need this wrapper after updating dulwich
73 73 to 0.12.0 +
74 74 """
75 75 def __del__(self):
76 76 if hasattr(self, 'object_store'):
77 77 self.close()
78 78
79 79
80 80 class GitFactory(RepoFactory):
81 81
82 82 def _create_repo(self, wire, create):
83 83 repo_path = str_to_dulwich(wire['path'])
84 84 return Repo(repo_path)
85 85
86 86
87 87 class GitRemote(object):
88 88
89 89 def __init__(self, factory):
90 90 self._factory = factory
91 91
92 92 self._bulk_methods = {
93 93 "author": self.commit_attribute,
94 94 "date": self.get_object_attrs,
95 95 "message": self.commit_attribute,
96 96 "parents": self.commit_attribute,
97 97 "_commit": self.revision,
98 98 }
99 99
100 100 def _assign_ref(self, wire, ref, commit_id):
101 101 repo = self._factory.repo(wire)
102 102 repo[ref] = commit_id
103 103
104 104 @reraise_safe_exceptions
105 105 def add_object(self, wire, content):
106 106 repo = self._factory.repo(wire)
107 107 blob = objects.Blob()
108 108 blob.set_raw_string(content)
109 109 repo.object_store.add_object(blob)
110 110 return blob.id
111 111
112 112 @reraise_safe_exceptions
113 113 def assert_correct_path(self, wire):
114 114 try:
115 115 self._factory.repo(wire)
116 116 except NotGitRepository as e:
117 117 # Exception can contain unicode which we convert
118 118 raise exceptions.AbortException(repr(e))
119 119
120 120 @reraise_safe_exceptions
121 121 def bare(self, wire):
122 122 repo = self._factory.repo(wire)
123 123 return repo.bare
124 124
125 125 @reraise_safe_exceptions
126 126 def blob_as_pretty_string(self, wire, sha):
127 127 repo = self._factory.repo(wire)
128 128 return repo[sha].as_pretty_string()
129 129
130 130 @reraise_safe_exceptions
131 131 def blob_raw_length(self, wire, sha):
132 132 repo = self._factory.repo(wire)
133 133 blob = repo[sha]
134 134 return blob.raw_length()
135 135
136 136 @reraise_safe_exceptions
137 137 def bulk_request(self, wire, rev, pre_load):
138 138 result = {}
139 139 for attr in pre_load:
140 140 try:
141 141 method = self._bulk_methods[attr]
142 142 args = [wire, rev]
143 143 if attr == "date":
144 144 args.extend(["commit_time", "commit_timezone"])
145 145 elif attr in ["author", "message", "parents"]:
146 146 args.append(attr)
147 147 result[attr] = method(*args)
148 148 except KeyError:
149 149 raise exceptions.VcsException(
150 150 "Unknown bulk attribute: %s" % attr)
151 151 return result
152 152
153 153 def _build_opener(self, url):
154 154 handlers = []
155 url_obj = hg_url(url)
155 url_obj = url_parser(url)
156 156 _, authinfo = url_obj.authinfo()
157 157
158 158 if authinfo:
159 159 # create a password manager
160 160 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
161 161 passmgr.add_password(*authinfo)
162 162
163 163 handlers.extend((httpbasicauthhandler(passmgr),
164 164 httpdigestauthhandler(passmgr)))
165 165
166 166 return urllib2.build_opener(*handlers)
167 167
168 168 @reraise_safe_exceptions
169 169 def check_url(self, url, config):
170 url_obj = hg_url(url)
170 url_obj = url_parser(url)
171 171 test_uri, _ = url_obj.authinfo()
172 172 url_obj.passwd = '*****'
173 173 cleaned_uri = str(url_obj)
174 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
174 175
175 176 if not test_uri.endswith('info/refs'):
176 177 test_uri = test_uri.rstrip('/') + '/info/refs'
177 178
178 179 o = self._build_opener(url)
179 180 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
180 181
181 182 q = {"service": 'git-upload-pack'}
182 183 qs = '?%s' % urllib.urlencode(q)
183 184 cu = "%s%s" % (test_uri, qs)
184 185 req = urllib2.Request(cu, None, {})
185 186
186 187 try:
188 log.debug("Trying to open URL %s", cleaned_uri)
187 189 resp = o.open(req)
188 190 if resp.code != 200:
189 raise Exception('Return Code is not 200')
191 raise exceptions.URLError('Return Code is not 200')
190 192 except Exception as e:
193 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
191 194 # means it cannot be cloned
192 raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
195 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
193 196
194 197 # now detect if it's proper git repo
195 198 gitdata = resp.read()
196 199 if 'service=git-upload-pack' in gitdata:
197 200 pass
198 201 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
199 202 # old style git can return some other format !
200 203 pass
201 204 else:
202 raise urllib2.URLError(
205 raise exceptions.URLError(
203 206 "url [%s] does not look like an git" % (cleaned_uri,))
204 207
205 208 return True
206 209
207 210 @reraise_safe_exceptions
208 211 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
209 212 remote_refs = self.fetch(wire, url, apply_refs=False)
210 213 repo = self._factory.repo(wire)
211 214 if isinstance(valid_refs, list):
212 215 valid_refs = tuple(valid_refs)
213 216
214 217 for k in remote_refs:
215 218 # only parse heads/tags and skip so called deferred tags
216 219 if k.startswith(valid_refs) and not k.endswith(deferred):
217 220 repo[k] = remote_refs[k]
218 221
219 222 if update_after_clone:
220 223 # we want to checkout HEAD
221 224 repo["HEAD"] = remote_refs["HEAD"]
222 225 index.build_index_from_tree(repo.path, repo.index_path(),
223 226 repo.object_store, repo["HEAD"].tree)
224 227
225 228 # TODO: this is quite complex, check if that can be simplified
226 229 @reraise_safe_exceptions
227 230 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
228 231 repo = self._factory.repo(wire)
229 232 object_store = repo.object_store
230 233
231 234 # Create tree and populates it with blobs
232 235 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
233 236
234 237 for node in updated:
235 238 # Compute subdirs if needed
236 239 dirpath, nodename = vcspath.split(node['path'])
237 240 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
238 241 parent = commit_tree
239 242 ancestors = [('', parent)]
240 243
241 244 # Tries to dig for the deepest existing tree
242 245 while dirnames:
243 246 curdir = dirnames.pop(0)
244 247 try:
245 248 dir_id = parent[curdir][1]
246 249 except KeyError:
247 250 # put curdir back into dirnames and stops
248 251 dirnames.insert(0, curdir)
249 252 break
250 253 else:
251 254 # If found, updates parent
252 255 parent = repo[dir_id]
253 256 ancestors.append((curdir, parent))
254 257 # Now parent is deepest existing tree and we need to create
255 258 # subtrees for dirnames (in reverse order)
256 259 # [this only applies for nodes from added]
257 260 new_trees = []
258 261
259 262 blob = objects.Blob.from_string(node['content'])
260 263
261 264 if dirnames:
262 265 # If there are trees which should be created we need to build
263 266 # them now (in reverse order)
264 267 reversed_dirnames = list(reversed(dirnames))
265 268 curtree = objects.Tree()
266 269 curtree[node['node_path']] = node['mode'], blob.id
267 270 new_trees.append(curtree)
268 271 for dirname in reversed_dirnames[:-1]:
269 272 newtree = objects.Tree()
270 273 newtree[dirname] = (DIR_STAT, curtree.id)
271 274 new_trees.append(newtree)
272 275 curtree = newtree
273 276 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
274 277 else:
275 278 parent.add(
276 279 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
277 280
278 281 new_trees.append(parent)
279 282 # Update ancestors
280 283 reversed_ancestors = reversed(
281 284 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
282 285 for parent, tree, path in reversed_ancestors:
283 286 parent[path] = (DIR_STAT, tree.id)
284 287 object_store.add_object(tree)
285 288
286 289 object_store.add_object(blob)
287 290 for tree in new_trees:
288 291 object_store.add_object(tree)
289 292
290 293 for node_path in removed:
291 294 paths = node_path.split('/')
292 295 tree = commit_tree
293 296 trees = [tree]
294 297 # Traverse deep into the forest...
295 298 for path in paths:
296 299 try:
297 300 obj = repo[tree[path][1]]
298 301 if isinstance(obj, objects.Tree):
299 302 trees.append(obj)
300 303 tree = obj
301 304 except KeyError:
302 305 break
303 306 # Cut down the blob and all rotten trees on the way back...
304 307 for path, tree in reversed(zip(paths, trees)):
305 308 del tree[path]
306 309 if tree:
307 310 # This tree still has elements - don't remove it or any
308 311 # of it's parents
309 312 break
310 313
311 314 object_store.add_object(commit_tree)
312 315
313 316 # Create commit
314 317 commit = objects.Commit()
315 318 commit.tree = commit_tree.id
316 319 for k, v in commit_data.iteritems():
317 320 setattr(commit, k, v)
318 321 object_store.add_object(commit)
319 322
320 323 ref = 'refs/heads/%s' % branch
321 324 repo.refs[ref] = commit.id
322 325
323 326 return commit.id
324 327
325 328 @reraise_safe_exceptions
326 329 def fetch(self, wire, url, apply_refs=True, refs=None):
327 330 if url != 'default' and '://' not in url:
328 331 client = LocalGitClient(url)
329 332 else:
330 url_obj = hg_url(url)
333 url_obj = url_parser(url)
331 334 o = self._build_opener(url)
332 335 url, _ = url_obj.authinfo()
333 336 client = HttpGitClient(base_url=url, opener=o)
334 337 repo = self._factory.repo(wire)
335 338
336 339 determine_wants = repo.object_store.determine_wants_all
337 340 if refs:
338 341 def determine_wants_requested(references):
339 342 return [references[r] for r in references if r in refs]
340 343 determine_wants = determine_wants_requested
341 344
342 345 try:
343 346 remote_refs = client.fetch(
344 347 path=url, target=repo, determine_wants=determine_wants)
345 348 except NotGitRepository:
346 349 log.warning(
347 350 'Trying to fetch from "%s" failed, not a Git repository.', url)
348 351 raise exceptions.AbortException()
349 352
350 353 # mikhail: client.fetch() returns all the remote refs, but fetches only
351 354 # refs filtered by `determine_wants` function. We need to filter result
352 355 # as well
353 356 if refs:
354 357 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
355 358
356 359 if apply_refs:
357 360 # TODO: johbo: Needs proper test coverage with a git repository
358 361 # that contains a tag object, so that we would end up with
359 362 # a peeled ref at this point.
360 363 PEELED_REF_MARKER = '^{}'
361 364 for k in remote_refs:
362 365 if k.endswith(PEELED_REF_MARKER):
363 366 log.info("Skipping peeled reference %s", k)
364 367 continue
365 368 repo[k] = remote_refs[k]
366 369
367 370 if refs:
368 371 # mikhail: explicitly set the head to the last ref.
369 372 repo['HEAD'] = remote_refs[refs[-1]]
370 373
371 374 # TODO: mikhail: should we return remote_refs here to be
372 375 # consistent?
373 376 else:
374 377 return remote_refs
375 378
376 379 @reraise_safe_exceptions
377 380 def get_remote_refs(self, wire, url):
378 381 repo = Repo(url)
379 382 return repo.get_refs()
380 383
381 384 @reraise_safe_exceptions
382 385 def get_description(self, wire):
383 386 repo = self._factory.repo(wire)
384 387 return repo.get_description()
385 388
386 389 @reraise_safe_exceptions
387 390 def get_file_history(self, wire, file_path, commit_id, limit):
388 391 repo = self._factory.repo(wire)
389 392 include = [commit_id]
390 393 paths = [file_path]
391 394
392 395 walker = repo.get_walker(include, paths=paths, max_entries=limit)
393 396 return [x.commit.id for x in walker]
394 397
395 398 @reraise_safe_exceptions
396 399 def get_missing_revs(self, wire, rev1, rev2, path2):
397 400 repo = self._factory.repo(wire)
398 401 LocalGitClient(thin_packs=False).fetch(path2, repo)
399 402
400 403 wire_remote = wire.copy()
401 404 wire_remote['path'] = path2
402 405 repo_remote = self._factory.repo(wire_remote)
403 406 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
404 407
405 408 revs = [
406 409 x.commit.id
407 410 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
408 411 return revs
409 412
410 413 @reraise_safe_exceptions
411 414 def get_object(self, wire, sha):
412 415 repo = self._factory.repo(wire)
413 416 obj = repo.get_object(sha)
414 417 commit_id = obj.id
415 418
416 419 if isinstance(obj, Tag):
417 420 commit_id = obj.object[1]
418 421
419 422 return {
420 423 'id': obj.id,
421 424 'type': obj.type_name,
422 425 'commit_id': commit_id
423 426 }
424 427
425 428 @reraise_safe_exceptions
426 429 def get_object_attrs(self, wire, sha, *attrs):
427 430 repo = self._factory.repo(wire)
428 431 obj = repo.get_object(sha)
429 432 return list(getattr(obj, a) for a in attrs)
430 433
431 434 @reraise_safe_exceptions
432 435 def get_refs(self, wire):
433 436 repo = self._factory.repo(wire)
434 437 result = {}
435 438 for ref, sha in repo.refs.as_dict().items():
436 439 peeled_sha = repo.get_peeled(ref)
437 440 result[ref] = peeled_sha
438 441 return result
439 442
440 443 @reraise_safe_exceptions
441 444 def get_refs_path(self, wire):
442 445 repo = self._factory.repo(wire)
443 446 return repo.refs.path
444 447
445 448 @reraise_safe_exceptions
446 449 def head(self, wire):
447 450 repo = self._factory.repo(wire)
448 451 return repo.head()
449 452
450 453 @reraise_safe_exceptions
451 454 def init(self, wire):
452 455 repo_path = str_to_dulwich(wire['path'])
453 456 self.repo = Repo.init(repo_path)
454 457
455 458 @reraise_safe_exceptions
456 459 def init_bare(self, wire):
457 460 repo_path = str_to_dulwich(wire['path'])
458 461 self.repo = Repo.init_bare(repo_path)
459 462
460 463 @reraise_safe_exceptions
461 464 def revision(self, wire, rev):
462 465 repo = self._factory.repo(wire)
463 466 obj = repo[rev]
464 467 obj_data = {
465 468 'id': obj.id,
466 469 }
467 470 try:
468 471 obj_data['tree'] = obj.tree
469 472 except AttributeError:
470 473 pass
471 474 return obj_data
472 475
473 476 @reraise_safe_exceptions
474 477 def commit_attribute(self, wire, rev, attr):
475 478 repo = self._factory.repo(wire)
476 479 obj = repo[rev]
477 480 return getattr(obj, attr)
478 481
479 482 @reraise_safe_exceptions
480 483 def set_refs(self, wire, key, value):
481 484 repo = self._factory.repo(wire)
482 485 repo.refs[key] = value
483 486
484 487 @reraise_safe_exceptions
485 488 def remove_ref(self, wire, key):
486 489 repo = self._factory.repo(wire)
487 490 del repo.refs[key]
488 491
489 492 @reraise_safe_exceptions
490 493 def tree_changes(self, wire, source_id, target_id):
491 494 repo = self._factory.repo(wire)
492 495 source = repo[source_id].tree if source_id else None
493 496 target = repo[target_id].tree
494 497 result = repo.object_store.tree_changes(source, target)
495 498 return list(result)
496 499
497 500 @reraise_safe_exceptions
498 501 def tree_items(self, wire, tree_id):
499 502 repo = self._factory.repo(wire)
500 503 tree = repo[tree_id]
501 504
502 505 result = []
503 506 for item in tree.iteritems():
504 507 item_sha = item.sha
505 508 item_mode = item.mode
506 509
507 510 if FILE_MODE(item_mode) == GIT_LINK:
508 511 item_type = "link"
509 512 else:
510 513 item_type = repo[item_sha].type_name
511 514
512 515 result.append((item.path, item_mode, item_sha, item_type))
513 516 return result
514 517
515 518 @reraise_safe_exceptions
516 519 def update_server_info(self, wire):
517 520 repo = self._factory.repo(wire)
518 521 update_server_info(repo)
519 522
520 523 @reraise_safe_exceptions
521 524 def discover_git_version(self):
522 525 stdout, _ = self.run_git_command(
523 526 {}, ['--version'], _bare=True, _safe=True)
524 527 prefix = 'git version'
525 528 if stdout.startswith(prefix):
526 529 stdout = stdout[len(prefix):]
527 530 return stdout.strip()
528 531
529 532 @reraise_safe_exceptions
530 533 def run_git_command(self, wire, cmd, **opts):
531 534 path = wire.get('path', None)
532 535
533 536 if path and os.path.isdir(path):
534 537 opts['cwd'] = path
535 538
536 539 if '_bare' in opts:
537 540 _copts = []
538 541 del opts['_bare']
539 542 else:
540 543 _copts = ['-c', 'core.quotepath=false', ]
541 544 safe_call = False
542 545 if '_safe' in opts:
543 546 # no exc on failure
544 547 del opts['_safe']
545 548 safe_call = True
546 549
547 550 gitenv = os.environ.copy()
548 551 gitenv.update(opts.pop('extra_env', {}))
549 552 # need to clean fix GIT_DIR !
550 553 if 'GIT_DIR' in gitenv:
551 554 del gitenv['GIT_DIR']
552 555 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
553 556
554 557 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
555 558
556 559 try:
557 560 _opts = {'env': gitenv, 'shell': False}
558 561 _opts.update(opts)
559 562 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
560 563
561 564 return ''.join(p), ''.join(p.error)
562 565 except (EnvironmentError, OSError) as err:
563 566 tb_err = ("Couldn't run git command (%s).\n"
564 567 "Original error was:%s\n" % (cmd, err))
565 568 log.exception(tb_err)
566 569 if safe_call:
567 570 return '', err
568 571 else:
569 572 raise exceptions.VcsException(tb_err)
570 573
571 574
572 575 def str_to_dulwich(value):
573 576 """
574 577 Dulwich 0.10.1a requires `unicode` objects to be passed in.
575 578 """
576 579 return value.decode(settings.WIRE_ENCODING)
@@ -1,719 +1,721 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import io
19 19 import logging
20 20 import stat
21 21 import sys
22 22 import urllib
23 23 import urllib2
24 24
25 25 from hgext import largefiles, rebase
26 26 from hgext.strip import strip as hgext_strip
27 27 from mercurial import commands
28 28 from mercurial import unionrepo
29 29
30 30 from vcsserver import exceptions
31 31 from vcsserver.base import RepoFactory
32 32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex, hg_url,
34 httpbasicauthhandler, httpdigestauthhandler, httppeer, localrepository,
35 match, memctx, exchange, memfilectx, nullrev, patch, peer, revrange, ui,
36 Abort, LookupError, RepoError, RepoLookupError, InterventionRequired,
37 RequirementError)
33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
37 InterventionRequired, RequirementError)
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 def make_ui_from_config(repo_config):
43 43 baseui = ui.ui()
44 44
45 45 # clean the baseui object
46 46 baseui._ocfg = hgconfig.config()
47 47 baseui._ucfg = hgconfig.config()
48 48 baseui._tcfg = hgconfig.config()
49 49
50 50 for section, option, value in repo_config:
51 51 baseui.setconfig(section, option, value)
52 52
53 53 # make our hgweb quiet so it doesn't print output
54 54 baseui.setconfig('ui', 'quiet', 'true')
55 55
56 56 # force mercurial to only use 1 thread, otherwise it may try to set a
57 57 # signal in a non-main thread, thus generating a ValueError.
58 58 baseui.setconfig('worker', 'numcpus', 1)
59 59
60 60 # If there is no config for the largefiles extension, we explicitly disable
61 61 # it here. This overrides settings from repositories hgrc file. Recent
62 62 # mercurial versions enable largefiles in hgrc on clone from largefile
63 63 # repo.
64 64 if not baseui.hasconfig('extensions', 'largefiles'):
65 65 log.debug('Explicitly disable largefiles extension for repo.')
66 66 baseui.setconfig('extensions', 'largefiles', '!')
67 67
68 68 return baseui
69 69
70 70
71 71 def reraise_safe_exceptions(func):
72 72 """Decorator for converting mercurial exceptions to something neutral."""
73 73 def wrapper(*args, **kwargs):
74 74 try:
75 75 return func(*args, **kwargs)
76 76 except (Abort, InterventionRequired):
77 77 raise_from_original(exceptions.AbortException)
78 78 except RepoLookupError:
79 79 raise_from_original(exceptions.LookupException)
80 80 except RequirementError:
81 81 raise_from_original(exceptions.RequirementException)
82 82 except RepoError:
83 83 raise_from_original(exceptions.VcsException)
84 84 except LookupError:
85 85 raise_from_original(exceptions.LookupException)
86 86 except Exception as e:
87 87 if not hasattr(e, '_vcs_kind'):
88 88 log.exception("Unhandled exception in hg remote call")
89 89 raise_from_original(exceptions.UnhandledException)
90 90 raise
91 91 return wrapper
92 92
93 93
94 94 def raise_from_original(new_type):
95 95 """
96 96 Raise a new exception type with original args and traceback.
97 97 """
98 98 _, original, traceback = sys.exc_info()
99 99 try:
100 100 raise new_type(*original.args), None, traceback
101 101 finally:
102 102 del traceback
103 103
104 104
105 105 class MercurialFactory(RepoFactory):
106 106
107 107 def _create_config(self, config, hooks=True):
108 108 if not hooks:
109 109 hooks_to_clean = frozenset((
110 110 'changegroup.repo_size', 'preoutgoing.pre_pull',
111 111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
112 112 new_config = []
113 113 for section, option, value in config:
114 114 if section == 'hooks' and option in hooks_to_clean:
115 115 continue
116 116 new_config.append((section, option, value))
117 117 config = new_config
118 118
119 119 baseui = make_ui_from_config(config)
120 120 return baseui
121 121
122 122 def _create_repo(self, wire, create):
123 123 baseui = self._create_config(wire["config"])
124 124 return localrepository(baseui, wire["path"], create)
125 125
126 126
127 127 class HgRemote(object):
128 128
129 129 def __init__(self, factory):
130 130 self._factory = factory
131 131
132 132 self._bulk_methods = {
133 133 "affected_files": self.ctx_files,
134 134 "author": self.ctx_user,
135 135 "branch": self.ctx_branch,
136 136 "children": self.ctx_children,
137 137 "date": self.ctx_date,
138 138 "message": self.ctx_description,
139 139 "parents": self.ctx_parents,
140 140 "status": self.ctx_status,
141 141 "_file_paths": self.ctx_list,
142 142 }
143 143
144 144 @reraise_safe_exceptions
145 145 def discover_hg_version(self):
146 146 from mercurial import util
147 147 return util.version()
148 148
149 149 @reraise_safe_exceptions
150 150 def archive_repo(self, archive_path, mtime, file_info, kind):
151 151 if kind == "tgz":
152 152 archiver = archival.tarit(archive_path, mtime, "gz")
153 153 elif kind == "tbz2":
154 154 archiver = archival.tarit(archive_path, mtime, "bz2")
155 155 elif kind == 'zip':
156 156 archiver = archival.zipit(archive_path, mtime)
157 157 else:
158 158 raise exceptions.ArchiveException(
159 159 'Remote does not support: "%s".' % kind)
160 160
161 161 for f_path, f_mode, f_is_link, f_content in file_info:
162 162 archiver.addfile(f_path, f_mode, f_is_link, f_content)
163 163 archiver.done()
164 164
165 165 @reraise_safe_exceptions
166 166 def bookmarks(self, wire):
167 167 repo = self._factory.repo(wire)
168 168 return dict(repo._bookmarks)
169 169
170 170 @reraise_safe_exceptions
171 171 def branches(self, wire, normal, closed):
172 172 repo = self._factory.repo(wire)
173 173 iter_branches = repo.branchmap().iterbranches()
174 174 bt = {}
175 175 for branch_name, _heads, tip, is_closed in iter_branches:
176 176 if normal and not is_closed:
177 177 bt[branch_name] = tip
178 178 if closed and is_closed:
179 179 bt[branch_name] = tip
180 180
181 181 return bt
182 182
183 183 @reraise_safe_exceptions
184 184 def bulk_request(self, wire, rev, pre_load):
185 185 result = {}
186 186 for attr in pre_load:
187 187 try:
188 188 method = self._bulk_methods[attr]
189 189 result[attr] = method(wire, rev)
190 190 except KeyError:
191 191 raise exceptions.VcsException(
192 192 'Unknown bulk attribute: "%s"' % attr)
193 193 return result
194 194
195 195 @reraise_safe_exceptions
196 196 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
197 197 baseui = self._factory._create_config(wire["config"], hooks=hooks)
198 198 clone(baseui, source, dest, noupdate=not update_after_clone)
199 199
200 200 @reraise_safe_exceptions
201 201 def commitctx(
202 202 self, wire, message, parents, commit_time, commit_timezone,
203 203 user, files, extra, removed, updated):
204 204
205 205 def _filectxfn(_repo, memctx, path):
206 206 """
207 207 Marks given path as added/changed/removed in a given _repo. This is
208 208 for internal mercurial commit function.
209 209 """
210 210
211 211 # check if this path is removed
212 212 if path in removed:
213 213 # returning None is a way to mark node for removal
214 214 return None
215 215
216 216 # check if this path is added
217 217 for node in updated:
218 218 if node['path'] == path:
219 219 return memfilectx(
220 220 _repo,
221 221 path=node['path'],
222 222 data=node['content'],
223 223 islink=False,
224 224 isexec=bool(node['mode'] & stat.S_IXUSR),
225 225 copied=False,
226 226 memctx=memctx)
227 227
228 228 raise exceptions.AbortException(
229 229 "Given path haven't been marked as added, "
230 230 "changed or removed (%s)" % path)
231 231
232 232 repo = self._factory.repo(wire)
233 233
234 234 commit_ctx = memctx(
235 235 repo=repo,
236 236 parents=parents,
237 237 text=message,
238 238 files=files,
239 239 filectxfn=_filectxfn,
240 240 user=user,
241 241 date=(commit_time, commit_timezone),
242 242 extra=extra)
243 243
244 244 n = repo.commitctx(commit_ctx)
245 245 new_id = hex(n)
246 246
247 247 return new_id
248 248
249 249 @reraise_safe_exceptions
250 250 def ctx_branch(self, wire, revision):
251 251 repo = self._factory.repo(wire)
252 252 ctx = repo[revision]
253 253 return ctx.branch()
254 254
255 255 @reraise_safe_exceptions
256 256 def ctx_children(self, wire, revision):
257 257 repo = self._factory.repo(wire)
258 258 ctx = repo[revision]
259 259 return [child.rev() for child in ctx.children()]
260 260
261 261 @reraise_safe_exceptions
262 262 def ctx_date(self, wire, revision):
263 263 repo = self._factory.repo(wire)
264 264 ctx = repo[revision]
265 265 return ctx.date()
266 266
267 267 @reraise_safe_exceptions
268 268 def ctx_description(self, wire, revision):
269 269 repo = self._factory.repo(wire)
270 270 ctx = repo[revision]
271 271 return ctx.description()
272 272
273 273 @reraise_safe_exceptions
274 274 def ctx_diff(
275 275 self, wire, revision, git=True, ignore_whitespace=True, context=3):
276 276 repo = self._factory.repo(wire)
277 277 ctx = repo[revision]
278 278 result = ctx.diff(
279 279 git=git, ignore_whitespace=ignore_whitespace, context=context)
280 280 return list(result)
281 281
282 282 @reraise_safe_exceptions
283 283 def ctx_files(self, wire, revision):
284 284 repo = self._factory.repo(wire)
285 285 ctx = repo[revision]
286 286 return ctx.files()
287 287
288 288 @reraise_safe_exceptions
289 289 def ctx_list(self, path, revision):
290 290 repo = self._factory.repo(path)
291 291 ctx = repo[revision]
292 292 return list(ctx)
293 293
294 294 @reraise_safe_exceptions
295 295 def ctx_parents(self, wire, revision):
296 296 repo = self._factory.repo(wire)
297 297 ctx = repo[revision]
298 298 return [parent.rev() for parent in ctx.parents()]
299 299
300 300 @reraise_safe_exceptions
301 301 def ctx_substate(self, wire, revision):
302 302 repo = self._factory.repo(wire)
303 303 ctx = repo[revision]
304 304 return ctx.substate
305 305
306 306 @reraise_safe_exceptions
307 307 def ctx_status(self, wire, revision):
308 308 repo = self._factory.repo(wire)
309 309 ctx = repo[revision]
310 310 status = repo[ctx.p1().node()].status(other=ctx.node())
311 311 # object of status (odd, custom named tuple in mercurial) is not
312 312 # correctly serializable via Pyro, we make it a list, as the underling
313 313 # API expects this to be a list
314 314 return list(status)
315 315
316 316 @reraise_safe_exceptions
317 317 def ctx_user(self, wire, revision):
318 318 repo = self._factory.repo(wire)
319 319 ctx = repo[revision]
320 320 return ctx.user()
321 321
322 322 @reraise_safe_exceptions
323 323 def check_url(self, url, config):
324 log.info("Checking URL for remote cloning/import: %s", url)
325 324 _proto = None
326 325 if '+' in url[:url.find('://')]:
327 326 _proto = url[0:url.find('+')]
328 327 url = url[url.find('+') + 1:]
329 328 handlers = []
330 url_obj = hg_url(url)
329 url_obj = url_parser(url)
331 330 test_uri, authinfo = url_obj.authinfo()
332 331 url_obj.passwd = '*****'
333 332 cleaned_uri = str(url_obj)
333 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
334 334
335 335 if authinfo:
336 336 # create a password manager
337 337 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
338 338 passmgr.add_password(*authinfo)
339 339
340 340 handlers.extend((httpbasicauthhandler(passmgr),
341 341 httpdigestauthhandler(passmgr)))
342 342
343 343 o = urllib2.build_opener(*handlers)
344 344 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
345 345 ('Accept', 'application/mercurial-0.1')]
346 346
347 347 q = {"cmd": 'between'}
348 348 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
349 349 qs = '?%s' % urllib.urlencode(q)
350 350 cu = "%s%s" % (test_uri, qs)
351 351 req = urllib2.Request(cu, None, {})
352 352
353 353 try:
354 log.debug("Trying to open URL %s", url)
354 log.debug("Trying to open URL %s", cleaned_uri)
355 355 resp = o.open(req)
356 356 if resp.code != 200:
357 357 raise exceptions.URLError('Return Code is not 200')
358 358 except Exception as e:
359 log.warning("URL cannot be opened: %s", url, exc_info=True)
359 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
360 360 # means it cannot be cloned
361 361 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
362 362
363 363 # now check if it's a proper hg repo, but don't do it for svn
364 364 try:
365 365 if _proto == 'svn':
366 366 pass
367 367 else:
368 368 # check for pure hg repos
369 369 log.debug(
370 "Verifying if URL is a Mercurial repository: %s", url)
370 "Verifying if URL is a Mercurial repository: %s",
371 cleaned_uri)
371 372 httppeer(make_ui_from_config(config), url).lookup('tip')
372 373 except Exception as e:
373 log.warning("URL is not a valid Mercurial repository: %s", url)
374 log.warning("URL is not a valid Mercurial repository: %s",
375 cleaned_uri)
374 376 raise exceptions.URLError(
375 377 "url [%s] does not look like an hg repo org_exc: %s"
376 378 % (cleaned_uri, e))
377 379
378 log.info("URL is a valid Mercurial repository: %s", url)
380 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
379 381 return True
380 382
381 383 @reraise_safe_exceptions
382 384 def diff(
383 385 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
384 386 context):
385 387 repo = self._factory.repo(wire)
386 388
387 389 if file_filter:
388 390 filter = match(file_filter[0], '', [file_filter[1]])
389 391 else:
390 392 filter = file_filter
391 393 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
392 394
393 395 try:
394 396 return "".join(patch.diff(
395 397 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
396 398 except RepoLookupError:
397 399 raise exceptions.LookupException()
398 400
399 401 @reraise_safe_exceptions
400 402 def file_history(self, wire, revision, path, limit):
401 403 repo = self._factory.repo(wire)
402 404
403 405 ctx = repo[revision]
404 406 fctx = ctx.filectx(path)
405 407
406 408 def history_iter():
407 409 limit_rev = fctx.rev()
408 410 for obj in reversed(list(fctx.filelog())):
409 411 obj = fctx.filectx(obj)
410 412 if limit_rev >= obj.rev():
411 413 yield obj
412 414
413 415 history = []
414 416 for cnt, obj in enumerate(history_iter()):
415 417 if limit and cnt >= limit:
416 418 break
417 419 history.append(hex(obj.node()))
418 420
419 421 return [x for x in history]
420 422
421 423 @reraise_safe_exceptions
422 424 def file_history_untill(self, wire, revision, path, limit):
423 425 repo = self._factory.repo(wire)
424 426 ctx = repo[revision]
425 427 fctx = ctx.filectx(path)
426 428
427 429 file_log = list(fctx.filelog())
428 430 if limit:
429 431 # Limit to the last n items
430 432 file_log = file_log[-limit:]
431 433
432 434 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
433 435
434 436 @reraise_safe_exceptions
435 437 def fctx_annotate(self, wire, revision, path):
436 438 repo = self._factory.repo(wire)
437 439 ctx = repo[revision]
438 440 fctx = ctx.filectx(path)
439 441
440 442 result = []
441 443 for i, annotate_data in enumerate(fctx.annotate()):
442 444 ln_no = i + 1
443 445 sha = hex(annotate_data[0].node())
444 446 result.append((ln_no, sha, annotate_data[1]))
445 447 return result
446 448
447 449 @reraise_safe_exceptions
448 450 def fctx_data(self, wire, revision, path):
449 451 repo = self._factory.repo(wire)
450 452 ctx = repo[revision]
451 453 fctx = ctx.filectx(path)
452 454 return fctx.data()
453 455
454 456 @reraise_safe_exceptions
455 457 def fctx_flags(self, wire, revision, path):
456 458 repo = self._factory.repo(wire)
457 459 ctx = repo[revision]
458 460 fctx = ctx.filectx(path)
459 461 return fctx.flags()
460 462
461 463 @reraise_safe_exceptions
462 464 def fctx_size(self, wire, revision, path):
463 465 repo = self._factory.repo(wire)
464 466 ctx = repo[revision]
465 467 fctx = ctx.filectx(path)
466 468 return fctx.size()
467 469
468 470 @reraise_safe_exceptions
469 471 def get_all_commit_ids(self, wire, name):
470 472 repo = self._factory.repo(wire)
471 473 revs = repo.filtered(name).changelog.index
472 474 return map(lambda x: hex(x[7]), revs)[:-1]
473 475
474 476 @reraise_safe_exceptions
475 477 def get_config_value(self, wire, section, name, untrusted=False):
476 478 repo = self._factory.repo(wire)
477 479 return repo.ui.config(section, name, untrusted=untrusted)
478 480
479 481 @reraise_safe_exceptions
480 482 def get_config_bool(self, wire, section, name, untrusted=False):
481 483 repo = self._factory.repo(wire)
482 484 return repo.ui.configbool(section, name, untrusted=untrusted)
483 485
484 486 @reraise_safe_exceptions
485 487 def get_config_list(self, wire, section, name, untrusted=False):
486 488 repo = self._factory.repo(wire)
487 489 return repo.ui.configlist(section, name, untrusted=untrusted)
488 490
489 491 @reraise_safe_exceptions
490 492 def is_large_file(self, wire, path):
491 493 return largefiles.lfutil.isstandin(path)
492 494
493 495 @reraise_safe_exceptions
494 496 def in_store(self, wire, sha):
495 497 repo = self._factory.repo(wire)
496 498 return largefiles.lfutil.instore(repo, sha)
497 499
498 500 @reraise_safe_exceptions
499 501 def in_user_cache(self, wire, sha):
500 502 repo = self._factory.repo(wire)
501 503 return largefiles.lfutil.inusercache(repo.ui, sha)
502 504
503 505 @reraise_safe_exceptions
504 506 def store_path(self, wire, sha):
505 507 repo = self._factory.repo(wire)
506 508 return largefiles.lfutil.storepath(repo, sha)
507 509
508 510 @reraise_safe_exceptions
509 511 def link(self, wire, sha, path):
510 512 repo = self._factory.repo(wire)
511 513 largefiles.lfutil.link(
512 514 largefiles.lfutil.usercachepath(repo.ui, sha), path)
513 515
514 516 @reraise_safe_exceptions
515 517 def localrepository(self, wire, create=False):
516 518 self._factory.repo(wire, create=create)
517 519
518 520 @reraise_safe_exceptions
519 521 def lookup(self, wire, revision, both):
520 522 # TODO Paris: Ugly hack to "deserialize" long for msgpack
521 523 if isinstance(revision, float):
522 524 revision = long(revision)
523 525 repo = self._factory.repo(wire)
524 526 try:
525 527 ctx = repo[revision]
526 528 except RepoLookupError:
527 529 raise exceptions.LookupException(revision)
528 530 except LookupError as e:
529 531 raise exceptions.LookupException(e.name)
530 532
531 533 if not both:
532 534 return ctx.hex()
533 535
534 536 ctx = repo[ctx.hex()]
535 537 return ctx.hex(), ctx.rev()
536 538
537 539 @reraise_safe_exceptions
538 540 def pull(self, wire, url, commit_ids=None):
539 541 repo = self._factory.repo(wire)
540 542 remote = peer(repo, {}, url)
541 543 if commit_ids:
542 544 commit_ids = [bin(commit_id) for commit_id in commit_ids]
543 545
544 546 return exchange.pull(
545 547 repo, remote, heads=commit_ids, force=None).cgresult
546 548
547 549 @reraise_safe_exceptions
548 550 def revision(self, wire, rev):
549 551 repo = self._factory.repo(wire)
550 552 ctx = repo[rev]
551 553 return ctx.rev()
552 554
553 555 @reraise_safe_exceptions
554 556 def rev_range(self, wire, filter):
555 557 repo = self._factory.repo(wire)
556 558 revisions = [rev for rev in revrange(repo, filter)]
557 559 return revisions
558 560
559 561 @reraise_safe_exceptions
560 562 def rev_range_hash(self, wire, node):
561 563 repo = self._factory.repo(wire)
562 564
563 565 def get_revs(repo, rev_opt):
564 566 if rev_opt:
565 567 revs = revrange(repo, rev_opt)
566 568 if len(revs) == 0:
567 569 return (nullrev, nullrev)
568 570 return max(revs), min(revs)
569 571 else:
570 572 return len(repo) - 1, 0
571 573
572 574 stop, start = get_revs(repo, [node + ':'])
573 575 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
574 576 return revs
575 577
576 578 @reraise_safe_exceptions
577 579 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
578 580 other_path = kwargs.pop('other_path', None)
579 581
580 582 # case when we want to compare two independent repositories
581 583 if other_path and other_path != wire["path"]:
582 584 baseui = self._factory._create_config(wire["config"])
583 585 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
584 586 else:
585 587 repo = self._factory.repo(wire)
586 588 return list(repo.revs(rev_spec, *args))
587 589
588 590 @reraise_safe_exceptions
589 591 def strip(self, wire, revision, update, backup):
590 592 repo = self._factory.repo(wire)
591 593 ctx = repo[revision]
592 594 hgext_strip(
593 595 repo.baseui, repo, ctx.node(), update=update, backup=backup)
594 596
595 597 @reraise_safe_exceptions
596 598 def tag(self, wire, name, revision, message, local, user,
597 599 tag_time, tag_timezone):
598 600 repo = self._factory.repo(wire)
599 601 ctx = repo[revision]
600 602 node = ctx.node()
601 603
602 604 date = (tag_time, tag_timezone)
603 605 try:
604 606 repo.tag(name, node, message, local, user, date)
605 607 except Abort:
606 608 log.exception("Tag operation aborted")
607 609 raise exceptions.AbortException()
608 610
609 611 @reraise_safe_exceptions
610 612 def tags(self, wire):
611 613 repo = self._factory.repo(wire)
612 614 return repo.tags()
613 615
614 616 @reraise_safe_exceptions
615 617 def update(self, wire, node=None, clean=False):
616 618 repo = self._factory.repo(wire)
617 619 baseui = self._factory._create_config(wire['config'])
618 620 commands.update(baseui, repo, node=node, clean=clean)
619 621
620 622 @reraise_safe_exceptions
621 623 def identify(self, wire):
622 624 repo = self._factory.repo(wire)
623 625 baseui = self._factory._create_config(wire['config'])
624 626 output = io.BytesIO()
625 627 baseui.write = output.write
626 628 # This is required to get a full node id
627 629 baseui.debugflag = True
628 630 commands.identify(baseui, repo, id=True)
629 631
630 632 return output.getvalue()
631 633
632 634 @reraise_safe_exceptions
633 635 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
634 636 hooks=True):
635 637 repo = self._factory.repo(wire)
636 638 baseui = self._factory._create_config(wire['config'], hooks=hooks)
637 639
638 640 # Mercurial internally has a lot of logic that checks ONLY if
639 641 # option is defined, we just pass those if they are defined then
640 642 opts = {}
641 643 if bookmark:
642 644 opts['bookmark'] = bookmark
643 645 if branch:
644 646 opts['branch'] = branch
645 647 if revision:
646 648 opts['rev'] = revision
647 649
648 650 commands.pull(baseui, repo, source, **opts)
649 651
650 652 @reraise_safe_exceptions
651 653 def heads(self, wire, branch=None):
652 654 repo = self._factory.repo(wire)
653 655 baseui = self._factory._create_config(wire['config'])
654 656 output = io.BytesIO()
655 657
656 658 def write(data, **unused_kwargs):
657 659 output.write(data)
658 660
659 661 baseui.write = write
660 662 if branch:
661 663 args = [branch]
662 664 else:
663 665 args = []
664 666 commands.heads(baseui, repo, template='{node} ', *args)
665 667
666 668 return output.getvalue()
667 669
668 670 @reraise_safe_exceptions
669 671 def ancestor(self, wire, revision1, revision2):
670 672 repo = self._factory.repo(wire)
671 673 baseui = self._factory._create_config(wire['config'])
672 674 output = io.BytesIO()
673 675 baseui.write = output.write
674 676 commands.debugancestor(baseui, repo, revision1, revision2)
675 677
676 678 return output.getvalue()
677 679
678 680 @reraise_safe_exceptions
679 681 def push(self, wire, revisions, dest_path, hooks=True,
680 682 push_branches=False):
681 683 repo = self._factory.repo(wire)
682 684 baseui = self._factory._create_config(wire['config'], hooks=hooks)
683 685 commands.push(baseui, repo, dest=dest_path, rev=revisions,
684 686 new_branch=push_branches)
685 687
686 688 @reraise_safe_exceptions
687 689 def merge(self, wire, revision):
688 690 repo = self._factory.repo(wire)
689 691 baseui = self._factory._create_config(wire['config'])
690 692 repo.ui.setconfig('ui', 'merge', 'internal:dump')
691 693
692 694 # In case of sub repositories are used mercurial prompts the user in
693 695 # case of merge conflicts or different sub repository sources. By
694 696 # setting the interactive flag to `False` mercurial doesn't prompt the
695 697 # used but instead uses a default value.
696 698 repo.ui.setconfig('ui', 'interactive', False)
697 699
698 700 commands.merge(baseui, repo, rev=revision)
699 701
700 702 @reraise_safe_exceptions
701 703 def commit(self, wire, message, username):
702 704 repo = self._factory.repo(wire)
703 705 baseui = self._factory._create_config(wire['config'])
704 706 repo.ui.setconfig('ui', 'username', username)
705 707 commands.commit(baseui, repo, message=message)
706 708
707 709 @reraise_safe_exceptions
708 710 def rebase(self, wire, source=None, dest=None, abort=False):
709 711 repo = self._factory.repo(wire)
710 712 baseui = self._factory._create_config(wire['config'])
711 713 repo.ui.setconfig('ui', 'merge', 'internal:dump')
712 714 rebase.rebase(
713 715 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
714 716
715 717 @reraise_safe_exceptions
716 718 def bookmark(self, wire, bookmark, revision=None):
717 719 repo = self._factory.repo(wire)
718 720 baseui = self._factory._create_config(wire['config'])
719 721 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
General Comments 0
You need to be logged in to leave comments. Login now