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