##// END OF EJS Templates
exceptions: use python3 compatible exception handling
marcink -
r566:0cd102ec default
parent child Browse files
Show More
@@ -1,726 +1,728 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 raise exceptions.LookupException(e)(e.message)
59 exc = exceptions.LookupException(e)
60 raise exc(e)
60 61 except (HangupException, UnexpectedCommandError) as e:
61 raise exceptions.VcsException(e)(e.message)
62 exc = exceptions.VcsException(e)
63 raise exc(e)
62 64 except Exception as e:
63 65 # NOTE(marcink): becuase of how dulwich handles some exceptions
64 66 # (KeyError on empty repos), we cannot track this and catch all
65 67 # exceptions, it's an exceptions from other handlers
66 68 #if not hasattr(e, '_vcs_kind'):
67 69 #log.exception("Unhandled exception in git remote call")
68 70 #raise_from_original(exceptions.UnhandledException)
69 71 raise
70 72 return wrapper
71 73
72 74
73 75 class Repo(DulwichRepo):
74 76 """
75 77 A wrapper for dulwich Repo class.
76 78
77 79 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
78 80 "Too many open files" error. We need to close all opened file descriptors
79 81 once the repo object is destroyed.
80 82
81 83 TODO: mikhail: please check if we need this wrapper after updating dulwich
82 84 to 0.12.0 +
83 85 """
84 86 def __del__(self):
85 87 if hasattr(self, 'object_store'):
86 88 self.close()
87 89
88 90
89 91 class GitFactory(RepoFactory):
90 92 repo_type = 'git'
91 93
92 94 def _create_repo(self, wire, create):
93 95 repo_path = str_to_dulwich(wire['path'])
94 96 return Repo(repo_path)
95 97
96 98
97 99 class GitRemote(object):
98 100
99 101 def __init__(self, factory):
100 102 self._factory = factory
101 103 self.peeled_ref_marker = '^{}'
102 104 self._bulk_methods = {
103 105 "author": self.commit_attribute,
104 106 "date": self.get_object_attrs,
105 107 "message": self.commit_attribute,
106 108 "parents": self.commit_attribute,
107 109 "_commit": self.revision,
108 110 }
109 111
110 112 def _wire_to_config(self, wire):
111 113 if 'config' in wire:
112 114 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
113 115 return {}
114 116
115 117 def _assign_ref(self, wire, ref, commit_id):
116 118 repo = self._factory.repo(wire)
117 119 repo[ref] = commit_id
118 120
119 121 @reraise_safe_exceptions
120 122 def add_object(self, wire, content):
121 123 repo = self._factory.repo(wire)
122 124 blob = objects.Blob()
123 125 blob.set_raw_string(content)
124 126 repo.object_store.add_object(blob)
125 127 return blob.id
126 128
127 129 @reraise_safe_exceptions
128 130 def assert_correct_path(self, wire):
129 131 path = wire.get('path')
130 132 try:
131 133 self._factory.repo(wire)
132 134 except NotGitRepository as e:
133 135 tb = traceback.format_exc()
134 136 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
135 137 return False
136 138
137 139 return True
138 140
139 141 @reraise_safe_exceptions
140 142 def bare(self, wire):
141 143 repo = self._factory.repo(wire)
142 144 return repo.bare
143 145
144 146 @reraise_safe_exceptions
145 147 def blob_as_pretty_string(self, wire, sha):
146 148 repo = self._factory.repo(wire)
147 149 return repo[sha].as_pretty_string()
148 150
149 151 @reraise_safe_exceptions
150 152 def blob_raw_length(self, wire, sha):
151 153 repo = self._factory.repo(wire)
152 154 blob = repo[sha]
153 155 return blob.raw_length()
154 156
155 157 def _parse_lfs_pointer(self, raw_content):
156 158
157 159 spec_string = 'version https://git-lfs.github.com/spec'
158 160 if raw_content and raw_content.startswith(spec_string):
159 161 pattern = re.compile(r"""
160 162 (?:\n)?
161 163 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
162 164 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
163 165 ^size[ ](?P<oid_size>[0-9]+)\n
164 166 (?:\n)?
165 167 """, re.VERBOSE | re.MULTILINE)
166 168 match = pattern.match(raw_content)
167 169 if match:
168 170 return match.groupdict()
169 171
170 172 return {}
171 173
172 174 @reraise_safe_exceptions
173 175 def is_large_file(self, wire, sha):
174 176 repo = self._factory.repo(wire)
175 177 blob = repo[sha]
176 178 return self._parse_lfs_pointer(blob.as_raw_string())
177 179
178 180 @reraise_safe_exceptions
179 181 def in_largefiles_store(self, wire, oid):
180 182 repo = self._factory.repo(wire)
181 183 conf = self._wire_to_config(wire)
182 184
183 185 store_location = conf.get('vcs_git_lfs_store_location')
184 186 if store_location:
185 187 repo_name = repo.path
186 188 store = LFSOidStore(
187 189 oid=oid, repo=repo_name, store_location=store_location)
188 190 return store.has_oid()
189 191
190 192 return False
191 193
192 194 @reraise_safe_exceptions
193 195 def store_path(self, wire, oid):
194 196 repo = self._factory.repo(wire)
195 197 conf = self._wire_to_config(wire)
196 198
197 199 store_location = conf.get('vcs_git_lfs_store_location')
198 200 if store_location:
199 201 repo_name = repo.path
200 202 store = LFSOidStore(
201 203 oid=oid, repo=repo_name, store_location=store_location)
202 204 return store.oid_path
203 205 raise ValueError('Unable to fetch oid with path {}'.format(oid))
204 206
205 207 @reraise_safe_exceptions
206 208 def bulk_request(self, wire, rev, pre_load):
207 209 result = {}
208 210 for attr in pre_load:
209 211 try:
210 212 method = self._bulk_methods[attr]
211 213 args = [wire, rev]
212 214 if attr == "date":
213 215 args.extend(["commit_time", "commit_timezone"])
214 216 elif attr in ["author", "message", "parents"]:
215 217 args.append(attr)
216 218 result[attr] = method(*args)
217 219 except KeyError as e:
218 220 raise exceptions.VcsException(e)(
219 221 "Unknown bulk attribute: %s" % attr)
220 222 return result
221 223
222 224 def _build_opener(self, url):
223 225 handlers = []
224 226 url_obj = url_parser(url)
225 227 _, authinfo = url_obj.authinfo()
226 228
227 229 if authinfo:
228 230 # create a password manager
229 231 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
230 232 passmgr.add_password(*authinfo)
231 233
232 234 handlers.extend((httpbasicauthhandler(passmgr),
233 235 httpdigestauthhandler(passmgr)))
234 236
235 237 return urllib2.build_opener(*handlers)
236 238
237 239 @reraise_safe_exceptions
238 240 def check_url(self, url, config):
239 241 url_obj = url_parser(url)
240 242 test_uri, _ = url_obj.authinfo()
241 243 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
242 244 url_obj.query = obfuscate_qs(url_obj.query)
243 245 cleaned_uri = str(url_obj)
244 246 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
245 247
246 248 if not test_uri.endswith('info/refs'):
247 249 test_uri = test_uri.rstrip('/') + '/info/refs'
248 250
249 251 o = self._build_opener(url)
250 252 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
251 253
252 254 q = {"service": 'git-upload-pack'}
253 255 qs = '?%s' % urllib.urlencode(q)
254 256 cu = "%s%s" % (test_uri, qs)
255 257 req = urllib2.Request(cu, None, {})
256 258
257 259 try:
258 260 log.debug("Trying to open URL %s", cleaned_uri)
259 261 resp = o.open(req)
260 262 if resp.code != 200:
261 263 raise exceptions.URLError()('Return Code is not 200')
262 264 except Exception as e:
263 265 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
264 266 # means it cannot be cloned
265 267 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
266 268
267 269 # now detect if it's proper git repo
268 270 gitdata = resp.read()
269 271 if 'service=git-upload-pack' in gitdata:
270 272 pass
271 273 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
272 274 # old style git can return some other format !
273 275 pass
274 276 else:
275 277 raise exceptions.URLError()(
276 278 "url [%s] does not look like an git" % (cleaned_uri,))
277 279
278 280 return True
279 281
280 282 @reraise_safe_exceptions
281 283 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
282 284 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
283 285 remote_refs = self.pull(wire, url, apply_refs=False)
284 286 repo = self._factory.repo(wire)
285 287 if isinstance(valid_refs, list):
286 288 valid_refs = tuple(valid_refs)
287 289
288 290 for k in remote_refs:
289 291 # only parse heads/tags and skip so called deferred tags
290 292 if k.startswith(valid_refs) and not k.endswith(deferred):
291 293 repo[k] = remote_refs[k]
292 294
293 295 if update_after_clone:
294 296 # we want to checkout HEAD
295 297 repo["HEAD"] = remote_refs["HEAD"]
296 298 index.build_index_from_tree(repo.path, repo.index_path(),
297 299 repo.object_store, repo["HEAD"].tree)
298 300
299 301 # TODO: this is quite complex, check if that can be simplified
300 302 @reraise_safe_exceptions
301 303 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
302 304 repo = self._factory.repo(wire)
303 305 object_store = repo.object_store
304 306
305 307 # Create tree and populates it with blobs
306 308 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
307 309
308 310 for node in updated:
309 311 # Compute subdirs if needed
310 312 dirpath, nodename = vcspath.split(node['path'])
311 313 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
312 314 parent = commit_tree
313 315 ancestors = [('', parent)]
314 316
315 317 # Tries to dig for the deepest existing tree
316 318 while dirnames:
317 319 curdir = dirnames.pop(0)
318 320 try:
319 321 dir_id = parent[curdir][1]
320 322 except KeyError:
321 323 # put curdir back into dirnames and stops
322 324 dirnames.insert(0, curdir)
323 325 break
324 326 else:
325 327 # If found, updates parent
326 328 parent = repo[dir_id]
327 329 ancestors.append((curdir, parent))
328 330 # Now parent is deepest existing tree and we need to create
329 331 # subtrees for dirnames (in reverse order)
330 332 # [this only applies for nodes from added]
331 333 new_trees = []
332 334
333 335 blob = objects.Blob.from_string(node['content'])
334 336
335 337 if dirnames:
336 338 # If there are trees which should be created we need to build
337 339 # them now (in reverse order)
338 340 reversed_dirnames = list(reversed(dirnames))
339 341 curtree = objects.Tree()
340 342 curtree[node['node_path']] = node['mode'], blob.id
341 343 new_trees.append(curtree)
342 344 for dirname in reversed_dirnames[:-1]:
343 345 newtree = objects.Tree()
344 346 newtree[dirname] = (DIR_STAT, curtree.id)
345 347 new_trees.append(newtree)
346 348 curtree = newtree
347 349 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
348 350 else:
349 351 parent.add(
350 352 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
351 353
352 354 new_trees.append(parent)
353 355 # Update ancestors
354 356 reversed_ancestors = reversed(
355 357 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
356 358 for parent, tree, path in reversed_ancestors:
357 359 parent[path] = (DIR_STAT, tree.id)
358 360 object_store.add_object(tree)
359 361
360 362 object_store.add_object(blob)
361 363 for tree in new_trees:
362 364 object_store.add_object(tree)
363 365
364 366 for node_path in removed:
365 367 paths = node_path.split('/')
366 368 tree = commit_tree
367 369 trees = [tree]
368 370 # Traverse deep into the forest...
369 371 for path in paths:
370 372 try:
371 373 obj = repo[tree[path][1]]
372 374 if isinstance(obj, objects.Tree):
373 375 trees.append(obj)
374 376 tree = obj
375 377 except KeyError:
376 378 break
377 379 # Cut down the blob and all rotten trees on the way back...
378 380 for path, tree in reversed(zip(paths, trees)):
379 381 del tree[path]
380 382 if tree:
381 383 # This tree still has elements - don't remove it or any
382 384 # of it's parents
383 385 break
384 386
385 387 object_store.add_object(commit_tree)
386 388
387 389 # Create commit
388 390 commit = objects.Commit()
389 391 commit.tree = commit_tree.id
390 392 for k, v in commit_data.iteritems():
391 393 setattr(commit, k, v)
392 394 object_store.add_object(commit)
393 395
394 396 ref = 'refs/heads/%s' % branch
395 397 repo.refs[ref] = commit.id
396 398
397 399 return commit.id
398 400
399 401 @reraise_safe_exceptions
400 402 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
401 403 if url != 'default' and '://' not in url:
402 404 client = LocalGitClient(url)
403 405 else:
404 406 url_obj = url_parser(url)
405 407 o = self._build_opener(url)
406 408 url, _ = url_obj.authinfo()
407 409 client = HttpGitClient(base_url=url, opener=o)
408 410 repo = self._factory.repo(wire)
409 411
410 412 determine_wants = repo.object_store.determine_wants_all
411 413 if refs:
412 414 def determine_wants_requested(references):
413 415 return [references[r] for r in references if r in refs]
414 416 determine_wants = determine_wants_requested
415 417
416 418 try:
417 419 remote_refs = client.fetch(
418 420 path=url, target=repo, determine_wants=determine_wants)
419 421 except NotGitRepository as e:
420 422 log.warning(
421 423 'Trying to fetch from "%s" failed, not a Git repository.', url)
422 424 # Exception can contain unicode which we convert
423 425 raise exceptions.AbortException(e)(repr(e))
424 426
425 427 # mikhail: client.fetch() returns all the remote refs, but fetches only
426 428 # refs filtered by `determine_wants` function. We need to filter result
427 429 # as well
428 430 if refs:
429 431 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
430 432
431 433 if apply_refs:
432 434 # TODO: johbo: Needs proper test coverage with a git repository
433 435 # that contains a tag object, so that we would end up with
434 436 # a peeled ref at this point.
435 437 for k in remote_refs:
436 438 if k.endswith(self.peeled_ref_marker):
437 439 log.debug("Skipping peeled reference %s", k)
438 440 continue
439 441 repo[k] = remote_refs[k]
440 442
441 443 if refs and not update_after:
442 444 # mikhail: explicitly set the head to the last ref.
443 445 repo['HEAD'] = remote_refs[refs[-1]]
444 446
445 447 if update_after:
446 448 # we want to checkout HEAD
447 449 repo["HEAD"] = remote_refs["HEAD"]
448 450 index.build_index_from_tree(repo.path, repo.index_path(),
449 451 repo.object_store, repo["HEAD"].tree)
450 452 return remote_refs
451 453
452 454 @reraise_safe_exceptions
453 455 def sync_fetch(self, wire, url, refs=None):
454 456 repo = self._factory.repo(wire)
455 457 if refs and not isinstance(refs, (list, tuple)):
456 458 refs = [refs]
457 459
458 460 # get all remote refs we'll use to fetch later
459 461 output, __ = self.run_git_command(
460 462 wire, ['ls-remote', url], fail_on_stderr=False,
461 463 _copts=['-c', 'core.askpass=""'],
462 464 extra_env={'GIT_TERMINAL_PROMPT': '0'})
463 465
464 466 remote_refs = collections.OrderedDict()
465 467 fetch_refs = []
466 468
467 469 for ref_line in output.splitlines():
468 470 sha, ref = ref_line.split('\t')
469 471 sha = sha.strip()
470 472 if ref in remote_refs:
471 473 # duplicate, skip
472 474 continue
473 475 if ref.endswith(self.peeled_ref_marker):
474 476 log.debug("Skipping peeled reference %s", ref)
475 477 continue
476 478 # don't sync HEAD
477 479 if ref in ['HEAD']:
478 480 continue
479 481
480 482 remote_refs[ref] = sha
481 483
482 484 if refs and sha in refs:
483 485 # we filter fetch using our specified refs
484 486 fetch_refs.append('{}:{}'.format(ref, ref))
485 487 elif not refs:
486 488 fetch_refs.append('{}:{}'.format(ref, ref))
487 489
488 490 if fetch_refs:
489 491 _out, _err = self.run_git_command(
490 492 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs,
491 493 fail_on_stderr=False,
492 494 _copts=['-c', 'core.askpass=""'],
493 495 extra_env={'GIT_TERMINAL_PROMPT': '0'})
494 496
495 497 return remote_refs
496 498
497 499 @reraise_safe_exceptions
498 500 def sync_push(self, wire, url, refs=None):
499 501 if not self.check_url(url, wire):
500 502 return
501 503
502 504 repo = self._factory.repo(wire)
503 505 self.run_git_command(
504 506 wire, ['push', url, '--mirror'], fail_on_stderr=False,
505 507 _copts=['-c', 'core.askpass=""'],
506 508 extra_env={'GIT_TERMINAL_PROMPT': '0'})
507 509
508 510 @reraise_safe_exceptions
509 511 def get_remote_refs(self, wire, url):
510 512 repo = Repo(url)
511 513 return repo.get_refs()
512 514
513 515 @reraise_safe_exceptions
514 516 def get_description(self, wire):
515 517 repo = self._factory.repo(wire)
516 518 return repo.get_description()
517 519
518 520 @reraise_safe_exceptions
519 521 def get_file_history(self, wire, file_path, commit_id, limit):
520 522 repo = self._factory.repo(wire)
521 523 include = [commit_id]
522 524 paths = [file_path]
523 525
524 526 walker = repo.get_walker(include, paths=paths, max_entries=limit)
525 527 return [x.commit.id for x in walker]
526 528
527 529 @reraise_safe_exceptions
528 530 def get_missing_revs(self, wire, rev1, rev2, path2):
529 531 repo = self._factory.repo(wire)
530 532 LocalGitClient(thin_packs=False).fetch(path2, repo)
531 533
532 534 wire_remote = wire.copy()
533 535 wire_remote['path'] = path2
534 536 repo_remote = self._factory.repo(wire_remote)
535 537 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
536 538
537 539 revs = [
538 540 x.commit.id
539 541 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
540 542 return revs
541 543
542 544 @reraise_safe_exceptions
543 545 def get_object(self, wire, sha):
544 546 repo = self._factory.repo(wire)
545 547 obj = repo.get_object(sha)
546 548 commit_id = obj.id
547 549
548 550 if isinstance(obj, Tag):
549 551 commit_id = obj.object[1]
550 552
551 553 return {
552 554 'id': obj.id,
553 555 'type': obj.type_name,
554 556 'commit_id': commit_id
555 557 }
556 558
557 559 @reraise_safe_exceptions
558 560 def get_object_attrs(self, wire, sha, *attrs):
559 561 repo = self._factory.repo(wire)
560 562 obj = repo.get_object(sha)
561 563 return list(getattr(obj, a) for a in attrs)
562 564
563 565 @reraise_safe_exceptions
564 566 def get_refs(self, wire):
565 567 repo = self._factory.repo(wire)
566 568 result = {}
567 569 for ref, sha in repo.refs.as_dict().items():
568 570 peeled_sha = repo.get_peeled(ref)
569 571 result[ref] = peeled_sha
570 572 return result
571 573
572 574 @reraise_safe_exceptions
573 575 def get_refs_path(self, wire):
574 576 repo = self._factory.repo(wire)
575 577 return repo.refs.path
576 578
577 579 @reraise_safe_exceptions
578 580 def head(self, wire, show_exc=True):
579 581 repo = self._factory.repo(wire)
580 582 try:
581 583 return repo.head()
582 584 except Exception:
583 585 if show_exc:
584 586 raise
585 587
586 588 @reraise_safe_exceptions
587 589 def init(self, wire):
588 590 repo_path = str_to_dulwich(wire['path'])
589 591 self.repo = Repo.init(repo_path)
590 592
591 593 @reraise_safe_exceptions
592 594 def init_bare(self, wire):
593 595 repo_path = str_to_dulwich(wire['path'])
594 596 self.repo = Repo.init_bare(repo_path)
595 597
596 598 @reraise_safe_exceptions
597 599 def revision(self, wire, rev):
598 600 repo = self._factory.repo(wire)
599 601 obj = repo[rev]
600 602 obj_data = {
601 603 'id': obj.id,
602 604 }
603 605 try:
604 606 obj_data['tree'] = obj.tree
605 607 except AttributeError:
606 608 pass
607 609 return obj_data
608 610
609 611 @reraise_safe_exceptions
610 612 def commit_attribute(self, wire, rev, attr):
611 613 repo = self._factory.repo(wire)
612 614 obj = repo[rev]
613 615 return getattr(obj, attr)
614 616
615 617 @reraise_safe_exceptions
616 618 def set_refs(self, wire, key, value):
617 619 repo = self._factory.repo(wire)
618 620 repo.refs[key] = value
619 621
620 622 @reraise_safe_exceptions
621 623 def remove_ref(self, wire, key):
622 624 repo = self._factory.repo(wire)
623 625 del repo.refs[key]
624 626
625 627 @reraise_safe_exceptions
626 628 def tree_changes(self, wire, source_id, target_id):
627 629 repo = self._factory.repo(wire)
628 630 source = repo[source_id].tree if source_id else None
629 631 target = repo[target_id].tree
630 632 result = repo.object_store.tree_changes(source, target)
631 633 return list(result)
632 634
633 635 @reraise_safe_exceptions
634 636 def tree_items(self, wire, tree_id):
635 637 repo = self._factory.repo(wire)
636 638 tree = repo[tree_id]
637 639
638 640 result = []
639 641 for item in tree.iteritems():
640 642 item_sha = item.sha
641 643 item_mode = item.mode
642 644
643 645 if FILE_MODE(item_mode) == GIT_LINK:
644 646 item_type = "link"
645 647 else:
646 648 item_type = repo[item_sha].type_name
647 649
648 650 result.append((item.path, item_mode, item_sha, item_type))
649 651 return result
650 652
651 653 @reraise_safe_exceptions
652 654 def update_server_info(self, wire):
653 655 repo = self._factory.repo(wire)
654 656 update_server_info(repo)
655 657
656 658 @reraise_safe_exceptions
657 659 def discover_git_version(self):
658 660 stdout, _ = self.run_git_command(
659 661 {}, ['--version'], _bare=True, _safe=True)
660 662 prefix = 'git version'
661 663 if stdout.startswith(prefix):
662 664 stdout = stdout[len(prefix):]
663 665 return stdout.strip()
664 666
665 667 @reraise_safe_exceptions
666 668 def run_git_command(self, wire, cmd, **opts):
667 669 path = wire.get('path', None)
668 670
669 671 if path and os.path.isdir(path):
670 672 opts['cwd'] = path
671 673
672 674 if '_bare' in opts:
673 675 _copts = []
674 676 del opts['_bare']
675 677 else:
676 678 _copts = ['-c', 'core.quotepath=false', ]
677 679 safe_call = False
678 680 if '_safe' in opts:
679 681 # no exc on failure
680 682 del opts['_safe']
681 683 safe_call = True
682 684
683 685 if '_copts' in opts:
684 686 _copts.extend(opts['_copts'] or [])
685 687 del opts['_copts']
686 688
687 689 gitenv = os.environ.copy()
688 690 gitenv.update(opts.pop('extra_env', {}))
689 691 # need to clean fix GIT_DIR !
690 692 if 'GIT_DIR' in gitenv:
691 693 del gitenv['GIT_DIR']
692 694 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
693 695 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
694 696
695 697 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
696 698 _opts = {'env': gitenv, 'shell': False}
697 699
698 700 try:
699 701 _opts.update(opts)
700 702 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
701 703
702 704 return ''.join(p), ''.join(p.error)
703 705 except (EnvironmentError, OSError) as err:
704 706 cmd = ' '.join(cmd) # human friendly CMD
705 707 tb_err = ("Couldn't run git command (%s).\n"
706 708 "Original error was:%s\n"
707 709 "Call options:%s\n"
708 710 % (cmd, err, _opts))
709 711 log.exception(tb_err)
710 712 if safe_call:
711 713 return '', err
712 714 else:
713 715 raise exceptions.VcsException()(tb_err)
714 716
715 717 @reraise_safe_exceptions
716 718 def install_hooks(self, wire, force=False):
717 719 from vcsserver.hook_utils import install_git_hooks
718 720 repo = self._factory.repo(wire)
719 721 return install_git_hooks(repo.path, repo.bare, force_create=force)
720 722
721 723
722 724 def str_to_dulwich(value):
723 725 """
724 726 Dulwich 0.10.1a requires `unicode` objects to be passed in.
725 727 """
726 728 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now