##// END OF EJS Templates
git: allow checkout in pull command
marcink -
r551:7ca9d712 default
parent child Browse files
Show More
@@ -1,716 +1,720 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 def pull(self, wire, url, apply_refs=True, refs=None):
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 if refs:
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 else:
446 return remote_refs
445 if update_after:
446 # we want to checkout HEAD
447 repo["HEAD"] = remote_refs["HEAD"]
448 index.build_index_from_tree(repo.path, repo.index_path(),
449 repo.object_store, repo["HEAD"].tree)
450 return remote_refs
447 451
448 452 @reraise_safe_exceptions
449 453 def sync_fetch(self, wire, url, refs=None):
450 454 repo = self._factory.repo(wire)
451 455 if refs and not isinstance(refs, (list, tuple)):
452 456 refs = [refs]
453 457
454 458 # get all remote refs we'll use to fetch later
455 459 output, __ = self.run_git_command(
456 460 wire, ['ls-remote', url], fail_on_stderr=False,
457 461 _copts=['-c', 'core.askpass=""'],
458 462 extra_env={'GIT_TERMINAL_PROMPT': '0'})
459 463
460 464 remote_refs = collections.OrderedDict()
461 465 fetch_refs = []
462 466
463 467 for ref_line in output.splitlines():
464 468 sha, ref = ref_line.split('\t')
465 469 sha = sha.strip()
466 470 if ref in remote_refs:
467 471 # duplicate, skip
468 472 continue
469 473 if ref.endswith(self.peeled_ref_marker):
470 474 log.debug("Skipping peeled reference %s", ref)
471 475 continue
472 476 remote_refs[ref] = sha
473 477
474 478 if refs and sha in refs:
475 479 # we filter fetch using our specified refs
476 480 fetch_refs.append('{}:{}'.format(ref, ref))
477 481 elif not refs:
478 482 fetch_refs.append('{}:{}'.format(ref, ref))
479 483
480 484 if fetch_refs:
481 485 _out, _err = self.run_git_command(
482 486 wire, ['fetch', url, '--prune', '--'] + fetch_refs,
483 487 fail_on_stderr=False,
484 488 _copts=['-c', 'core.askpass=""'],
485 489 extra_env={'GIT_TERMINAL_PROMPT': '0'})
486 490
487 491 return remote_refs
488 492
489 493 @reraise_safe_exceptions
490 494 def sync_push(self, wire, url, refs=None):
491 495 if not self.check_url(url, wire):
492 496 return
493 497
494 498 repo = self._factory.repo(wire)
495 499 self.run_git_command(
496 500 wire, ['push', url, '--mirror'], fail_on_stderr=False,
497 501 _copts=['-c', 'core.askpass=""'],
498 502 extra_env={'GIT_TERMINAL_PROMPT': '0'})
499 503
500 504 @reraise_safe_exceptions
501 505 def get_remote_refs(self, wire, url):
502 506 repo = Repo(url)
503 507 return repo.get_refs()
504 508
505 509 @reraise_safe_exceptions
506 510 def get_description(self, wire):
507 511 repo = self._factory.repo(wire)
508 512 return repo.get_description()
509 513
510 514 @reraise_safe_exceptions
511 515 def get_file_history(self, wire, file_path, commit_id, limit):
512 516 repo = self._factory.repo(wire)
513 517 include = [commit_id]
514 518 paths = [file_path]
515 519
516 520 walker = repo.get_walker(include, paths=paths, max_entries=limit)
517 521 return [x.commit.id for x in walker]
518 522
519 523 @reraise_safe_exceptions
520 524 def get_missing_revs(self, wire, rev1, rev2, path2):
521 525 repo = self._factory.repo(wire)
522 526 LocalGitClient(thin_packs=False).fetch(path2, repo)
523 527
524 528 wire_remote = wire.copy()
525 529 wire_remote['path'] = path2
526 530 repo_remote = self._factory.repo(wire_remote)
527 531 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
528 532
529 533 revs = [
530 534 x.commit.id
531 535 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
532 536 return revs
533 537
534 538 @reraise_safe_exceptions
535 539 def get_object(self, wire, sha):
536 540 repo = self._factory.repo(wire)
537 541 obj = repo.get_object(sha)
538 542 commit_id = obj.id
539 543
540 544 if isinstance(obj, Tag):
541 545 commit_id = obj.object[1]
542 546
543 547 return {
544 548 'id': obj.id,
545 549 'type': obj.type_name,
546 550 'commit_id': commit_id
547 551 }
548 552
549 553 @reraise_safe_exceptions
550 554 def get_object_attrs(self, wire, sha, *attrs):
551 555 repo = self._factory.repo(wire)
552 556 obj = repo.get_object(sha)
553 557 return list(getattr(obj, a) for a in attrs)
554 558
555 559 @reraise_safe_exceptions
556 560 def get_refs(self, wire):
557 561 repo = self._factory.repo(wire)
558 562 result = {}
559 563 for ref, sha in repo.refs.as_dict().items():
560 564 peeled_sha = repo.get_peeled(ref)
561 565 result[ref] = peeled_sha
562 566 return result
563 567
564 568 @reraise_safe_exceptions
565 569 def get_refs_path(self, wire):
566 570 repo = self._factory.repo(wire)
567 571 return repo.refs.path
568 572
569 573 @reraise_safe_exceptions
570 574 def head(self, wire, show_exc=True):
571 575 repo = self._factory.repo(wire)
572 576 try:
573 577 return repo.head()
574 578 except Exception:
575 579 if show_exc:
576 580 raise
577 581
578 582 @reraise_safe_exceptions
579 583 def init(self, wire):
580 584 repo_path = str_to_dulwich(wire['path'])
581 585 self.repo = Repo.init(repo_path)
582 586
583 587 @reraise_safe_exceptions
584 588 def init_bare(self, wire):
585 589 repo_path = str_to_dulwich(wire['path'])
586 590 self.repo = Repo.init_bare(repo_path)
587 591
588 592 @reraise_safe_exceptions
589 593 def revision(self, wire, rev):
590 594 repo = self._factory.repo(wire)
591 595 obj = repo[rev]
592 596 obj_data = {
593 597 'id': obj.id,
594 598 }
595 599 try:
596 600 obj_data['tree'] = obj.tree
597 601 except AttributeError:
598 602 pass
599 603 return obj_data
600 604
601 605 @reraise_safe_exceptions
602 606 def commit_attribute(self, wire, rev, attr):
603 607 repo = self._factory.repo(wire)
604 608 obj = repo[rev]
605 609 return getattr(obj, attr)
606 610
607 611 @reraise_safe_exceptions
608 612 def set_refs(self, wire, key, value):
609 613 repo = self._factory.repo(wire)
610 614 repo.refs[key] = value
611 615
612 616 @reraise_safe_exceptions
613 617 def remove_ref(self, wire, key):
614 618 repo = self._factory.repo(wire)
615 619 del repo.refs[key]
616 620
617 621 @reraise_safe_exceptions
618 622 def tree_changes(self, wire, source_id, target_id):
619 623 repo = self._factory.repo(wire)
620 624 source = repo[source_id].tree if source_id else None
621 625 target = repo[target_id].tree
622 626 result = repo.object_store.tree_changes(source, target)
623 627 return list(result)
624 628
625 629 @reraise_safe_exceptions
626 630 def tree_items(self, wire, tree_id):
627 631 repo = self._factory.repo(wire)
628 632 tree = repo[tree_id]
629 633
630 634 result = []
631 635 for item in tree.iteritems():
632 636 item_sha = item.sha
633 637 item_mode = item.mode
634 638
635 639 if FILE_MODE(item_mode) == GIT_LINK:
636 640 item_type = "link"
637 641 else:
638 642 item_type = repo[item_sha].type_name
639 643
640 644 result.append((item.path, item_mode, item_sha, item_type))
641 645 return result
642 646
643 647 @reraise_safe_exceptions
644 648 def update_server_info(self, wire):
645 649 repo = self._factory.repo(wire)
646 650 update_server_info(repo)
647 651
648 652 @reraise_safe_exceptions
649 653 def discover_git_version(self):
650 654 stdout, _ = self.run_git_command(
651 655 {}, ['--version'], _bare=True, _safe=True)
652 656 prefix = 'git version'
653 657 if stdout.startswith(prefix):
654 658 stdout = stdout[len(prefix):]
655 659 return stdout.strip()
656 660
657 661 @reraise_safe_exceptions
658 662 def run_git_command(self, wire, cmd, **opts):
659 663 path = wire.get('path', None)
660 664
661 665 if path and os.path.isdir(path):
662 666 opts['cwd'] = path
663 667
664 668 if '_bare' in opts:
665 669 _copts = []
666 670 del opts['_bare']
667 671 else:
668 672 _copts = ['-c', 'core.quotepath=false', ]
669 673 safe_call = False
670 674 if '_safe' in opts:
671 675 # no exc on failure
672 676 del opts['_safe']
673 677 safe_call = True
674 678
675 679 if '_copts' in opts:
676 680 _copts.extend(opts['_copts'] or [])
677 681 del opts['_copts']
678 682
679 683 gitenv = os.environ.copy()
680 684 gitenv.update(opts.pop('extra_env', {}))
681 685 # need to clean fix GIT_DIR !
682 686 if 'GIT_DIR' in gitenv:
683 687 del gitenv['GIT_DIR']
684 688 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
685 689 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
686 690
687 691 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
688 692
689 693 try:
690 694 _opts = {'env': gitenv, 'shell': False}
691 695 _opts.update(opts)
692 696 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
693 697
694 698 return ''.join(p), ''.join(p.error)
695 699 except (EnvironmentError, OSError) as err:
696 700 cmd = ' '.join(cmd) # human friendly CMD
697 701 tb_err = ("Couldn't run git command (%s).\n"
698 702 "Original error was:%s\n" % (cmd, err))
699 703 log.exception(tb_err)
700 704 if safe_call:
701 705 return '', err
702 706 else:
703 707 raise exceptions.VcsException()(tb_err)
704 708
705 709 @reraise_safe_exceptions
706 710 def install_hooks(self, wire, force=False):
707 711 from vcsserver.hook_utils import install_git_hooks
708 712 repo = self._factory.repo(wire)
709 713 return install_git_hooks(repo.path, repo.bare, force_create=force)
710 714
711 715
712 716 def str_to_dulwich(value):
713 717 """
714 718 Dulwich 0.10.1a requires `unicode` objects to be passed in.
715 719 """
716 720 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now