##// END OF EJS Templates
git: sync_fetch should fetch HEAD ref
marcink -
r558:d806d23f default
parent child Browse files
Show More
@@ -1,722 +1,726 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 self.peeled_ref_marker = '^{}'
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 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
283 283 remote_refs = self.pull(wire, url, apply_refs=False)
284 284 repo = self._factory.repo(wire)
285 285 if isinstance(valid_refs, list):
286 286 valid_refs = tuple(valid_refs)
287 287
288 288 for k in remote_refs:
289 289 # only parse heads/tags and skip so called deferred tags
290 290 if k.startswith(valid_refs) and not k.endswith(deferred):
291 291 repo[k] = remote_refs[k]
292 292
293 293 if update_after_clone:
294 294 # we want to checkout HEAD
295 295 repo["HEAD"] = remote_refs["HEAD"]
296 296 index.build_index_from_tree(repo.path, repo.index_path(),
297 297 repo.object_store, repo["HEAD"].tree)
298 298
299 299 # TODO: this is quite complex, check if that can be simplified
300 300 @reraise_safe_exceptions
301 301 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
302 302 repo = self._factory.repo(wire)
303 303 object_store = repo.object_store
304 304
305 305 # Create tree and populates it with blobs
306 306 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
307 307
308 308 for node in updated:
309 309 # Compute subdirs if needed
310 310 dirpath, nodename = vcspath.split(node['path'])
311 311 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
312 312 parent = commit_tree
313 313 ancestors = [('', parent)]
314 314
315 315 # Tries to dig for the deepest existing tree
316 316 while dirnames:
317 317 curdir = dirnames.pop(0)
318 318 try:
319 319 dir_id = parent[curdir][1]
320 320 except KeyError:
321 321 # put curdir back into dirnames and stops
322 322 dirnames.insert(0, curdir)
323 323 break
324 324 else:
325 325 # If found, updates parent
326 326 parent = repo[dir_id]
327 327 ancestors.append((curdir, parent))
328 328 # Now parent is deepest existing tree and we need to create
329 329 # subtrees for dirnames (in reverse order)
330 330 # [this only applies for nodes from added]
331 331 new_trees = []
332 332
333 333 blob = objects.Blob.from_string(node['content'])
334 334
335 335 if dirnames:
336 336 # If there are trees which should be created we need to build
337 337 # them now (in reverse order)
338 338 reversed_dirnames = list(reversed(dirnames))
339 339 curtree = objects.Tree()
340 340 curtree[node['node_path']] = node['mode'], blob.id
341 341 new_trees.append(curtree)
342 342 for dirname in reversed_dirnames[:-1]:
343 343 newtree = objects.Tree()
344 344 newtree[dirname] = (DIR_STAT, curtree.id)
345 345 new_trees.append(newtree)
346 346 curtree = newtree
347 347 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
348 348 else:
349 349 parent.add(
350 350 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
351 351
352 352 new_trees.append(parent)
353 353 # Update ancestors
354 354 reversed_ancestors = reversed(
355 355 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
356 356 for parent, tree, path in reversed_ancestors:
357 357 parent[path] = (DIR_STAT, tree.id)
358 358 object_store.add_object(tree)
359 359
360 360 object_store.add_object(blob)
361 361 for tree in new_trees:
362 362 object_store.add_object(tree)
363 363
364 364 for node_path in removed:
365 365 paths = node_path.split('/')
366 366 tree = commit_tree
367 367 trees = [tree]
368 368 # Traverse deep into the forest...
369 369 for path in paths:
370 370 try:
371 371 obj = repo[tree[path][1]]
372 372 if isinstance(obj, objects.Tree):
373 373 trees.append(obj)
374 374 tree = obj
375 375 except KeyError:
376 376 break
377 377 # Cut down the blob and all rotten trees on the way back...
378 378 for path, tree in reversed(zip(paths, trees)):
379 379 del tree[path]
380 380 if tree:
381 381 # This tree still has elements - don't remove it or any
382 382 # of it's parents
383 383 break
384 384
385 385 object_store.add_object(commit_tree)
386 386
387 387 # Create commit
388 388 commit = objects.Commit()
389 389 commit.tree = commit_tree.id
390 390 for k, v in commit_data.iteritems():
391 391 setattr(commit, k, v)
392 392 object_store.add_object(commit)
393 393
394 394 ref = 'refs/heads/%s' % branch
395 395 repo.refs[ref] = commit.id
396 396
397 397 return commit.id
398 398
399 399 @reraise_safe_exceptions
400 400 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
401 401 if url != 'default' and '://' not in url:
402 402 client = LocalGitClient(url)
403 403 else:
404 404 url_obj = url_parser(url)
405 405 o = self._build_opener(url)
406 406 url, _ = url_obj.authinfo()
407 407 client = HttpGitClient(base_url=url, opener=o)
408 408 repo = self._factory.repo(wire)
409 409
410 410 determine_wants = repo.object_store.determine_wants_all
411 411 if refs:
412 412 def determine_wants_requested(references):
413 413 return [references[r] for r in references if r in refs]
414 414 determine_wants = determine_wants_requested
415 415
416 416 try:
417 417 remote_refs = client.fetch(
418 418 path=url, target=repo, determine_wants=determine_wants)
419 419 except NotGitRepository as e:
420 420 log.warning(
421 421 'Trying to fetch from "%s" failed, not a Git repository.', url)
422 422 # Exception can contain unicode which we convert
423 423 raise exceptions.AbortException(e)(repr(e))
424 424
425 425 # mikhail: client.fetch() returns all the remote refs, but fetches only
426 426 # refs filtered by `determine_wants` function. We need to filter result
427 427 # as well
428 428 if refs:
429 429 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
430 430
431 431 if apply_refs:
432 432 # TODO: johbo: Needs proper test coverage with a git repository
433 433 # that contains a tag object, so that we would end up with
434 434 # a peeled ref at this point.
435 435 for k in remote_refs:
436 436 if k.endswith(self.peeled_ref_marker):
437 437 log.debug("Skipping peeled reference %s", k)
438 438 continue
439 439 repo[k] = remote_refs[k]
440 440
441 441 if refs and not update_after:
442 442 # mikhail: explicitly set the head to the last ref.
443 443 repo['HEAD'] = remote_refs[refs[-1]]
444 444
445 445 if update_after:
446 446 # we want to checkout HEAD
447 447 repo["HEAD"] = remote_refs["HEAD"]
448 448 index.build_index_from_tree(repo.path, repo.index_path(),
449 449 repo.object_store, repo["HEAD"].tree)
450 450 return remote_refs
451 451
452 452 @reraise_safe_exceptions
453 453 def sync_fetch(self, wire, url, refs=None):
454 454 repo = self._factory.repo(wire)
455 455 if refs and not isinstance(refs, (list, tuple)):
456 456 refs = [refs]
457 457
458 458 # get all remote refs we'll use to fetch later
459 459 output, __ = self.run_git_command(
460 460 wire, ['ls-remote', url], fail_on_stderr=False,
461 461 _copts=['-c', 'core.askpass=""'],
462 462 extra_env={'GIT_TERMINAL_PROMPT': '0'})
463 463
464 464 remote_refs = collections.OrderedDict()
465 465 fetch_refs = []
466 466
467 467 for ref_line in output.splitlines():
468 468 sha, ref = ref_line.split('\t')
469 469 sha = sha.strip()
470 470 if ref in remote_refs:
471 471 # duplicate, skip
472 472 continue
473 473 if ref.endswith(self.peeled_ref_marker):
474 474 log.debug("Skipping peeled reference %s", ref)
475 475 continue
476 # don't sync HEAD
477 if ref in ['HEAD']:
478 continue
479
476 480 remote_refs[ref] = sha
477 481
478 482 if refs and sha in refs:
479 483 # we filter fetch using our specified refs
480 484 fetch_refs.append('{}:{}'.format(ref, ref))
481 485 elif not refs:
482 486 fetch_refs.append('{}:{}'.format(ref, ref))
483 487
484 488 if fetch_refs:
485 489 _out, _err = self.run_git_command(
486 490 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs,
487 491 fail_on_stderr=False,
488 492 _copts=['-c', 'core.askpass=""'],
489 493 extra_env={'GIT_TERMINAL_PROMPT': '0'})
490 494
491 495 return remote_refs
492 496
493 497 @reraise_safe_exceptions
494 498 def sync_push(self, wire, url, refs=None):
495 499 if not self.check_url(url, wire):
496 500 return
497 501
498 502 repo = self._factory.repo(wire)
499 503 self.run_git_command(
500 504 wire, ['push', url, '--mirror'], fail_on_stderr=False,
501 505 _copts=['-c', 'core.askpass=""'],
502 506 extra_env={'GIT_TERMINAL_PROMPT': '0'})
503 507
504 508 @reraise_safe_exceptions
505 509 def get_remote_refs(self, wire, url):
506 510 repo = Repo(url)
507 511 return repo.get_refs()
508 512
509 513 @reraise_safe_exceptions
510 514 def get_description(self, wire):
511 515 repo = self._factory.repo(wire)
512 516 return repo.get_description()
513 517
514 518 @reraise_safe_exceptions
515 519 def get_file_history(self, wire, file_path, commit_id, limit):
516 520 repo = self._factory.repo(wire)
517 521 include = [commit_id]
518 522 paths = [file_path]
519 523
520 524 walker = repo.get_walker(include, paths=paths, max_entries=limit)
521 525 return [x.commit.id for x in walker]
522 526
523 527 @reraise_safe_exceptions
524 528 def get_missing_revs(self, wire, rev1, rev2, path2):
525 529 repo = self._factory.repo(wire)
526 530 LocalGitClient(thin_packs=False).fetch(path2, repo)
527 531
528 532 wire_remote = wire.copy()
529 533 wire_remote['path'] = path2
530 534 repo_remote = self._factory.repo(wire_remote)
531 535 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
532 536
533 537 revs = [
534 538 x.commit.id
535 539 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
536 540 return revs
537 541
538 542 @reraise_safe_exceptions
539 543 def get_object(self, wire, sha):
540 544 repo = self._factory.repo(wire)
541 545 obj = repo.get_object(sha)
542 546 commit_id = obj.id
543 547
544 548 if isinstance(obj, Tag):
545 549 commit_id = obj.object[1]
546 550
547 551 return {
548 552 'id': obj.id,
549 553 'type': obj.type_name,
550 554 'commit_id': commit_id
551 555 }
552 556
553 557 @reraise_safe_exceptions
554 558 def get_object_attrs(self, wire, sha, *attrs):
555 559 repo = self._factory.repo(wire)
556 560 obj = repo.get_object(sha)
557 561 return list(getattr(obj, a) for a in attrs)
558 562
559 563 @reraise_safe_exceptions
560 564 def get_refs(self, wire):
561 565 repo = self._factory.repo(wire)
562 566 result = {}
563 567 for ref, sha in repo.refs.as_dict().items():
564 568 peeled_sha = repo.get_peeled(ref)
565 569 result[ref] = peeled_sha
566 570 return result
567 571
568 572 @reraise_safe_exceptions
569 573 def get_refs_path(self, wire):
570 574 repo = self._factory.repo(wire)
571 575 return repo.refs.path
572 576
573 577 @reraise_safe_exceptions
574 578 def head(self, wire, show_exc=True):
575 579 repo = self._factory.repo(wire)
576 580 try:
577 581 return repo.head()
578 582 except Exception:
579 583 if show_exc:
580 584 raise
581 585
582 586 @reraise_safe_exceptions
583 587 def init(self, wire):
584 588 repo_path = str_to_dulwich(wire['path'])
585 589 self.repo = Repo.init(repo_path)
586 590
587 591 @reraise_safe_exceptions
588 592 def init_bare(self, wire):
589 593 repo_path = str_to_dulwich(wire['path'])
590 594 self.repo = Repo.init_bare(repo_path)
591 595
592 596 @reraise_safe_exceptions
593 597 def revision(self, wire, rev):
594 598 repo = self._factory.repo(wire)
595 599 obj = repo[rev]
596 600 obj_data = {
597 601 'id': obj.id,
598 602 }
599 603 try:
600 604 obj_data['tree'] = obj.tree
601 605 except AttributeError:
602 606 pass
603 607 return obj_data
604 608
605 609 @reraise_safe_exceptions
606 610 def commit_attribute(self, wire, rev, attr):
607 611 repo = self._factory.repo(wire)
608 612 obj = repo[rev]
609 613 return getattr(obj, attr)
610 614
611 615 @reraise_safe_exceptions
612 616 def set_refs(self, wire, key, value):
613 617 repo = self._factory.repo(wire)
614 618 repo.refs[key] = value
615 619
616 620 @reraise_safe_exceptions
617 621 def remove_ref(self, wire, key):
618 622 repo = self._factory.repo(wire)
619 623 del repo.refs[key]
620 624
621 625 @reraise_safe_exceptions
622 626 def tree_changes(self, wire, source_id, target_id):
623 627 repo = self._factory.repo(wire)
624 628 source = repo[source_id].tree if source_id else None
625 629 target = repo[target_id].tree
626 630 result = repo.object_store.tree_changes(source, target)
627 631 return list(result)
628 632
629 633 @reraise_safe_exceptions
630 634 def tree_items(self, wire, tree_id):
631 635 repo = self._factory.repo(wire)
632 636 tree = repo[tree_id]
633 637
634 638 result = []
635 639 for item in tree.iteritems():
636 640 item_sha = item.sha
637 641 item_mode = item.mode
638 642
639 643 if FILE_MODE(item_mode) == GIT_LINK:
640 644 item_type = "link"
641 645 else:
642 646 item_type = repo[item_sha].type_name
643 647
644 648 result.append((item.path, item_mode, item_sha, item_type))
645 649 return result
646 650
647 651 @reraise_safe_exceptions
648 652 def update_server_info(self, wire):
649 653 repo = self._factory.repo(wire)
650 654 update_server_info(repo)
651 655
652 656 @reraise_safe_exceptions
653 657 def discover_git_version(self):
654 658 stdout, _ = self.run_git_command(
655 659 {}, ['--version'], _bare=True, _safe=True)
656 660 prefix = 'git version'
657 661 if stdout.startswith(prefix):
658 662 stdout = stdout[len(prefix):]
659 663 return stdout.strip()
660 664
661 665 @reraise_safe_exceptions
662 666 def run_git_command(self, wire, cmd, **opts):
663 667 path = wire.get('path', None)
664 668
665 669 if path and os.path.isdir(path):
666 670 opts['cwd'] = path
667 671
668 672 if '_bare' in opts:
669 673 _copts = []
670 674 del opts['_bare']
671 675 else:
672 676 _copts = ['-c', 'core.quotepath=false', ]
673 677 safe_call = False
674 678 if '_safe' in opts:
675 679 # no exc on failure
676 680 del opts['_safe']
677 681 safe_call = True
678 682
679 683 if '_copts' in opts:
680 684 _copts.extend(opts['_copts'] or [])
681 685 del opts['_copts']
682 686
683 687 gitenv = os.environ.copy()
684 688 gitenv.update(opts.pop('extra_env', {}))
685 689 # need to clean fix GIT_DIR !
686 690 if 'GIT_DIR' in gitenv:
687 691 del gitenv['GIT_DIR']
688 692 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
689 693 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
690 694
691 695 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
692 696 _opts = {'env': gitenv, 'shell': False}
693 697
694 698 try:
695 699 _opts.update(opts)
696 700 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
697 701
698 702 return ''.join(p), ''.join(p.error)
699 703 except (EnvironmentError, OSError) as err:
700 704 cmd = ' '.join(cmd) # human friendly CMD
701 705 tb_err = ("Couldn't run git command (%s).\n"
702 706 "Original error was:%s\n"
703 707 "Call options:%s\n"
704 708 % (cmd, err, _opts))
705 709 log.exception(tb_err)
706 710 if safe_call:
707 711 return '', err
708 712 else:
709 713 raise exceptions.VcsException()(tb_err)
710 714
711 715 @reraise_safe_exceptions
712 716 def install_hooks(self, wire, force=False):
713 717 from vcsserver.hook_utils import install_git_hooks
714 718 repo = self._factory.repo(wire)
715 719 return install_git_hooks(repo.path, repo.bare, force_create=force)
716 720
717 721
718 722 def str_to_dulwich(value):
719 723 """
720 724 Dulwich 0.10.1a requires `unicode` objects to be passed in.
721 725 """
722 726 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now