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