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