##// END OF EJS Templates
git: allow setting symbolic ref for repos without master branch....
super-admin -
r965:7597323f default
parent child Browse files
Show More
@@ -1,1250 +1,1257 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 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
18 18 import collections
19 19 import logging
20 20 import os
21 21 import posixpath as vcspath
22 22 import re
23 23 import stat
24 24 import traceback
25 25 import urllib
26 26 import urllib2
27 27 from functools import wraps
28 28
29 29 import more_itertools
30 30 import pygit2
31 31 from pygit2 import Repository as LibGit2Repo
32 32 from pygit2 import index as LibGit2Index
33 33 from dulwich import index, objects
34 34 from dulwich.client import HttpGitClient, LocalGitClient
35 35 from dulwich.errors import (
36 36 NotGitRepository, ChecksumMismatch, WrongObjectException,
37 37 MissingCommitError, ObjectMissing, HangupException,
38 38 UnexpectedCommandError)
39 39 from dulwich.repo import Repo as DulwichRepo
40 40 from dulwich.server import update_server_info
41 41
42 42 from vcsserver import exceptions, settings, subprocessio
43 43 from vcsserver.utils import safe_str, safe_int, safe_unicode
44 44 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, archive_repo
45 45 from vcsserver.hgcompat import (
46 46 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
47 47 from vcsserver.git_lfs.lib import LFSOidStore
48 48 from vcsserver.vcs_base import RemoteBase
49 49
50 50 DIR_STAT = stat.S_IFDIR
51 51 FILE_MODE = stat.S_IFMT
52 52 GIT_LINK = objects.S_IFGITLINK
53 53 PEELED_REF_MARKER = '^{}'
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 def str_to_dulwich(value):
60 60 """
61 61 Dulwich 0.10.1a requires `unicode` objects to be passed in.
62 62 """
63 63 return value.decode(settings.WIRE_ENCODING)
64 64
65 65
66 66 def reraise_safe_exceptions(func):
67 67 """Converts Dulwich exceptions to something neutral."""
68 68
69 69 @wraps(func)
70 70 def wrapper(*args, **kwargs):
71 71 try:
72 72 return func(*args, **kwargs)
73 73 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
74 74 exc = exceptions.LookupException(org_exc=e)
75 75 raise exc(safe_str(e))
76 76 except (HangupException, UnexpectedCommandError) as e:
77 77 exc = exceptions.VcsException(org_exc=e)
78 78 raise exc(safe_str(e))
79 79 except Exception as e:
80 80 # NOTE(marcink): becuase of how dulwich handles some exceptions
81 81 # (KeyError on empty repos), we cannot track this and catch all
82 82 # exceptions, it's an exceptions from other handlers
83 83 #if not hasattr(e, '_vcs_kind'):
84 84 #log.exception("Unhandled exception in git remote call")
85 85 #raise_from_original(exceptions.UnhandledException)
86 86 raise
87 87 return wrapper
88 88
89 89
90 90 class Repo(DulwichRepo):
91 91 """
92 92 A wrapper for dulwich Repo class.
93 93
94 94 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
95 95 "Too many open files" error. We need to close all opened file descriptors
96 96 once the repo object is destroyed.
97 97 """
98 98 def __del__(self):
99 99 if hasattr(self, 'object_store'):
100 100 self.close()
101 101
102 102
103 103 class Repository(LibGit2Repo):
104 104
105 105 def __enter__(self):
106 106 return self
107 107
108 108 def __exit__(self, exc_type, exc_val, exc_tb):
109 109 self.free()
110 110
111 111
112 112 class GitFactory(RepoFactory):
113 113 repo_type = 'git'
114 114
115 115 def _create_repo(self, wire, create, use_libgit2=False):
116 116 if use_libgit2:
117 117 return Repository(wire['path'])
118 118 else:
119 119 repo_path = str_to_dulwich(wire['path'])
120 120 return Repo(repo_path)
121 121
122 122 def repo(self, wire, create=False, use_libgit2=False):
123 123 """
124 124 Get a repository instance for the given path.
125 125 """
126 126 return self._create_repo(wire, create, use_libgit2)
127 127
128 128 def repo_libgit2(self, wire):
129 129 return self.repo(wire, use_libgit2=True)
130 130
131 131
132 132 class GitRemote(RemoteBase):
133 133
134 134 def __init__(self, factory):
135 135 self._factory = factory
136 136 self._bulk_methods = {
137 137 "date": self.date,
138 138 "author": self.author,
139 139 "branch": self.branch,
140 140 "message": self.message,
141 141 "parents": self.parents,
142 142 "_commit": self.revision,
143 143 }
144 144
145 145 def _wire_to_config(self, wire):
146 146 if 'config' in wire:
147 147 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
148 148 return {}
149 149
150 150 def _remote_conf(self, config):
151 151 params = [
152 152 '-c', 'core.askpass=""',
153 153 ]
154 154 ssl_cert_dir = config.get('vcs_ssl_dir')
155 155 if ssl_cert_dir:
156 156 params.extend(['-c', 'http.sslCAinfo={}'.format(ssl_cert_dir)])
157 157 return params
158 158
159 159 @reraise_safe_exceptions
160 160 def discover_git_version(self):
161 161 stdout, _ = self.run_git_command(
162 162 {}, ['--version'], _bare=True, _safe=True)
163 163 prefix = 'git version'
164 164 if stdout.startswith(prefix):
165 165 stdout = stdout[len(prefix):]
166 166 return stdout.strip()
167 167
168 168 @reraise_safe_exceptions
169 169 def is_empty(self, wire):
170 170 repo_init = self._factory.repo_libgit2(wire)
171 171 with repo_init as repo:
172 172
173 173 try:
174 174 has_head = repo.head.name
175 175 if has_head:
176 176 return False
177 177
178 178 # NOTE(marcink): check again using more expensive method
179 179 return repo.is_empty
180 180 except Exception:
181 181 pass
182 182
183 183 return True
184 184
185 185 @reraise_safe_exceptions
186 186 def assert_correct_path(self, wire):
187 187 cache_on, context_uid, repo_id = self._cache_on(wire)
188 188 region = self._region(wire)
189 189 @region.conditional_cache_on_arguments(condition=cache_on)
190 190 def _assert_correct_path(_context_uid, _repo_id):
191 191 try:
192 192 repo_init = self._factory.repo_libgit2(wire)
193 193 with repo_init as repo:
194 194 pass
195 195 except pygit2.GitError:
196 196 path = wire.get('path')
197 197 tb = traceback.format_exc()
198 198 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
199 199 return False
200 200
201 201 return True
202 202 return _assert_correct_path(context_uid, repo_id)
203 203
204 204 @reraise_safe_exceptions
205 205 def bare(self, wire):
206 206 repo_init = self._factory.repo_libgit2(wire)
207 207 with repo_init as repo:
208 208 return repo.is_bare
209 209
210 210 @reraise_safe_exceptions
211 211 def blob_as_pretty_string(self, wire, sha):
212 212 repo_init = self._factory.repo_libgit2(wire)
213 213 with repo_init as repo:
214 214 blob_obj = repo[sha]
215 215 blob = blob_obj.data
216 216 return blob
217 217
218 218 @reraise_safe_exceptions
219 219 def blob_raw_length(self, wire, sha):
220 220 cache_on, context_uid, repo_id = self._cache_on(wire)
221 221 region = self._region(wire)
222 222 @region.conditional_cache_on_arguments(condition=cache_on)
223 223 def _blob_raw_length(_repo_id, _sha):
224 224
225 225 repo_init = self._factory.repo_libgit2(wire)
226 226 with repo_init as repo:
227 227 blob = repo[sha]
228 228 return blob.size
229 229
230 230 return _blob_raw_length(repo_id, sha)
231 231
232 232 def _parse_lfs_pointer(self, raw_content):
233 233
234 234 spec_string = 'version https://git-lfs.github.com/spec'
235 235 if raw_content and raw_content.startswith(spec_string):
236 236 pattern = re.compile(r"""
237 237 (?:\n)?
238 238 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
239 239 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
240 240 ^size[ ](?P<oid_size>[0-9]+)\n
241 241 (?:\n)?
242 242 """, re.VERBOSE | re.MULTILINE)
243 243 match = pattern.match(raw_content)
244 244 if match:
245 245 return match.groupdict()
246 246
247 247 return {}
248 248
249 249 @reraise_safe_exceptions
250 250 def is_large_file(self, wire, commit_id):
251 251 cache_on, context_uid, repo_id = self._cache_on(wire)
252 252
253 253 region = self._region(wire)
254 254 @region.conditional_cache_on_arguments(condition=cache_on)
255 255 def _is_large_file(_repo_id, _sha):
256 256 repo_init = self._factory.repo_libgit2(wire)
257 257 with repo_init as repo:
258 258 blob = repo[commit_id]
259 259 if blob.is_binary:
260 260 return {}
261 261
262 262 return self._parse_lfs_pointer(blob.data)
263 263
264 264 return _is_large_file(repo_id, commit_id)
265 265
266 266 @reraise_safe_exceptions
267 267 def is_binary(self, wire, tree_id):
268 268 cache_on, context_uid, repo_id = self._cache_on(wire)
269 269
270 270 region = self._region(wire)
271 271 @region.conditional_cache_on_arguments(condition=cache_on)
272 272 def _is_binary(_repo_id, _tree_id):
273 273 repo_init = self._factory.repo_libgit2(wire)
274 274 with repo_init as repo:
275 275 blob_obj = repo[tree_id]
276 276 return blob_obj.is_binary
277 277
278 278 return _is_binary(repo_id, tree_id)
279 279
280 280 @reraise_safe_exceptions
281 281 def in_largefiles_store(self, wire, oid):
282 282 conf = self._wire_to_config(wire)
283 283 repo_init = self._factory.repo_libgit2(wire)
284 284 with repo_init as repo:
285 285 repo_name = repo.path
286 286
287 287 store_location = conf.get('vcs_git_lfs_store_location')
288 288 if store_location:
289 289
290 290 store = LFSOidStore(
291 291 oid=oid, repo=repo_name, store_location=store_location)
292 292 return store.has_oid()
293 293
294 294 return False
295 295
296 296 @reraise_safe_exceptions
297 297 def store_path(self, wire, oid):
298 298 conf = self._wire_to_config(wire)
299 299 repo_init = self._factory.repo_libgit2(wire)
300 300 with repo_init as repo:
301 301 repo_name = repo.path
302 302
303 303 store_location = conf.get('vcs_git_lfs_store_location')
304 304 if store_location:
305 305 store = LFSOidStore(
306 306 oid=oid, repo=repo_name, store_location=store_location)
307 307 return store.oid_path
308 308 raise ValueError('Unable to fetch oid with path {}'.format(oid))
309 309
310 310 @reraise_safe_exceptions
311 311 def bulk_request(self, wire, rev, pre_load):
312 312 cache_on, context_uid, repo_id = self._cache_on(wire)
313 313 region = self._region(wire)
314 314 @region.conditional_cache_on_arguments(condition=cache_on)
315 315 def _bulk_request(_repo_id, _rev, _pre_load):
316 316 result = {}
317 317 for attr in pre_load:
318 318 try:
319 319 method = self._bulk_methods[attr]
320 320 args = [wire, rev]
321 321 result[attr] = method(*args)
322 322 except KeyError as e:
323 323 raise exceptions.VcsException(e)(
324 324 "Unknown bulk attribute: %s" % attr)
325 325 return result
326 326
327 327 return _bulk_request(repo_id, rev, sorted(pre_load))
328 328
329 329 def _build_opener(self, url):
330 330 handlers = []
331 331 url_obj = url_parser(url)
332 332 _, authinfo = url_obj.authinfo()
333 333
334 334 if authinfo:
335 335 # create a password manager
336 336 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
337 337 passmgr.add_password(*authinfo)
338 338
339 339 handlers.extend((httpbasicauthhandler(passmgr),
340 340 httpdigestauthhandler(passmgr)))
341 341
342 342 return urllib2.build_opener(*handlers)
343 343
344 344 def _type_id_to_name(self, type_id):
345 345 return {
346 346 1: b'commit',
347 347 2: b'tree',
348 348 3: b'blob',
349 349 4: b'tag'
350 350 }[type_id]
351 351
352 352 @reraise_safe_exceptions
353 353 def check_url(self, url, config):
354 354 url_obj = url_parser(url)
355 355 test_uri, _ = url_obj.authinfo()
356 356 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
357 357 url_obj.query = obfuscate_qs(url_obj.query)
358 358 cleaned_uri = str(url_obj)
359 359 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
360 360
361 361 if not test_uri.endswith('info/refs'):
362 362 test_uri = test_uri.rstrip('/') + '/info/refs'
363 363
364 364 o = self._build_opener(url)
365 365 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
366 366
367 367 q = {"service": 'git-upload-pack'}
368 368 qs = '?%s' % urllib.urlencode(q)
369 369 cu = "%s%s" % (test_uri, qs)
370 370 req = urllib2.Request(cu, None, {})
371 371
372 372 try:
373 373 log.debug("Trying to open URL %s", cleaned_uri)
374 374 resp = o.open(req)
375 375 if resp.code != 200:
376 376 raise exceptions.URLError()('Return Code is not 200')
377 377 except Exception as e:
378 378 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
379 379 # means it cannot be cloned
380 380 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
381 381
382 382 # now detect if it's proper git repo
383 383 gitdata = resp.read()
384 384 if 'service=git-upload-pack' in gitdata:
385 385 pass
386 386 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
387 387 # old style git can return some other format !
388 388 pass
389 389 else:
390 390 raise exceptions.URLError()(
391 391 "url [%s] does not look like an git" % (cleaned_uri,))
392 392
393 393 return True
394 394
395 395 @reraise_safe_exceptions
396 396 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
397 397 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
398 398 remote_refs = self.pull(wire, url, apply_refs=False)
399 399 repo = self._factory.repo(wire)
400 400 if isinstance(valid_refs, list):
401 401 valid_refs = tuple(valid_refs)
402 402
403 403 for k in remote_refs:
404 404 # only parse heads/tags and skip so called deferred tags
405 405 if k.startswith(valid_refs) and not k.endswith(deferred):
406 406 repo[k] = remote_refs[k]
407 407
408 408 if update_after_clone:
409 409 # we want to checkout HEAD
410 410 repo["HEAD"] = remote_refs["HEAD"]
411 411 index.build_index_from_tree(repo.path, repo.index_path(),
412 412 repo.object_store, repo["HEAD"].tree)
413 413
414 414 @reraise_safe_exceptions
415 415 def branch(self, wire, commit_id):
416 416 cache_on, context_uid, repo_id = self._cache_on(wire)
417 417 region = self._region(wire)
418 418 @region.conditional_cache_on_arguments(condition=cache_on)
419 419 def _branch(_context_uid, _repo_id, _commit_id):
420 420 regex = re.compile('^refs/heads')
421 421
422 422 def filter_with(ref):
423 423 return regex.match(ref[0]) and ref[1] == _commit_id
424 424
425 425 branches = filter(filter_with, self.get_refs(wire).items())
426 426 return [x[0].split('refs/heads/')[-1] for x in branches]
427 427
428 428 return _branch(context_uid, repo_id, commit_id)
429 429
430 430 @reraise_safe_exceptions
431 431 def commit_branches(self, wire, commit_id):
432 432 cache_on, context_uid, repo_id = self._cache_on(wire)
433 433 region = self._region(wire)
434 434 @region.conditional_cache_on_arguments(condition=cache_on)
435 435 def _commit_branches(_context_uid, _repo_id, _commit_id):
436 436 repo_init = self._factory.repo_libgit2(wire)
437 437 with repo_init as repo:
438 438 branches = [x for x in repo.branches.with_commit(_commit_id)]
439 439 return branches
440 440
441 441 return _commit_branches(context_uid, repo_id, commit_id)
442 442
443 443 @reraise_safe_exceptions
444 444 def add_object(self, wire, content):
445 445 repo_init = self._factory.repo_libgit2(wire)
446 446 with repo_init as repo:
447 447 blob = objects.Blob()
448 448 blob.set_raw_string(content)
449 449 repo.object_store.add_object(blob)
450 450 return blob.id
451 451
452 452 # TODO: this is quite complex, check if that can be simplified
453 453 @reraise_safe_exceptions
454 454 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
455 455 repo = self._factory.repo(wire)
456 456 object_store = repo.object_store
457 457
458 458 # Create tree and populates it with blobs
459 459 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
460 460
461 461 for node in updated:
462 462 # Compute subdirs if needed
463 463 dirpath, nodename = vcspath.split(node['path'])
464 464 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
465 465 parent = commit_tree
466 466 ancestors = [('', parent)]
467 467
468 468 # Tries to dig for the deepest existing tree
469 469 while dirnames:
470 470 curdir = dirnames.pop(0)
471 471 try:
472 472 dir_id = parent[curdir][1]
473 473 except KeyError:
474 474 # put curdir back into dirnames and stops
475 475 dirnames.insert(0, curdir)
476 476 break
477 477 else:
478 478 # If found, updates parent
479 479 parent = repo[dir_id]
480 480 ancestors.append((curdir, parent))
481 481 # Now parent is deepest existing tree and we need to create
482 482 # subtrees for dirnames (in reverse order)
483 483 # [this only applies for nodes from added]
484 484 new_trees = []
485 485
486 486 blob = objects.Blob.from_string(node['content'])
487 487
488 488 if dirnames:
489 489 # If there are trees which should be created we need to build
490 490 # them now (in reverse order)
491 491 reversed_dirnames = list(reversed(dirnames))
492 492 curtree = objects.Tree()
493 493 curtree[node['node_path']] = node['mode'], blob.id
494 494 new_trees.append(curtree)
495 495 for dirname in reversed_dirnames[:-1]:
496 496 newtree = objects.Tree()
497 497 newtree[dirname] = (DIR_STAT, curtree.id)
498 498 new_trees.append(newtree)
499 499 curtree = newtree
500 500 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
501 501 else:
502 502 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
503 503
504 504 new_trees.append(parent)
505 505 # Update ancestors
506 506 reversed_ancestors = reversed(
507 507 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
508 508 for parent, tree, path in reversed_ancestors:
509 509 parent[path] = (DIR_STAT, tree.id)
510 510 object_store.add_object(tree)
511 511
512 512 object_store.add_object(blob)
513 513 for tree in new_trees:
514 514 object_store.add_object(tree)
515 515
516 516 for node_path in removed:
517 517 paths = node_path.split('/')
518 518 tree = commit_tree
519 519 trees = [tree]
520 520 # Traverse deep into the forest...
521 521 for path in paths:
522 522 try:
523 523 obj = repo[tree[path][1]]
524 524 if isinstance(obj, objects.Tree):
525 525 trees.append(obj)
526 526 tree = obj
527 527 except KeyError:
528 528 break
529 529 # Cut down the blob and all rotten trees on the way back...
530 530 for path, tree in reversed(zip(paths, trees)):
531 531 del tree[path]
532 532 if tree:
533 533 # This tree still has elements - don't remove it or any
534 534 # of it's parents
535 535 break
536 536
537 537 object_store.add_object(commit_tree)
538 538
539 539 # Create commit
540 540 commit = objects.Commit()
541 541 commit.tree = commit_tree.id
542 542 for k, v in commit_data.iteritems():
543 543 setattr(commit, k, v)
544 544 object_store.add_object(commit)
545 545
546 546 self.create_branch(wire, branch, commit.id)
547 547
548 548 # dulwich set-ref
549 549 ref = 'refs/heads/%s' % branch
550 550 repo.refs[ref] = commit.id
551 551
552 552 return commit.id
553 553
554 554 @reraise_safe_exceptions
555 555 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
556 556 if url != 'default' and '://' not in url:
557 557 client = LocalGitClient(url)
558 558 else:
559 559 url_obj = url_parser(url)
560 560 o = self._build_opener(url)
561 561 url, _ = url_obj.authinfo()
562 562 client = HttpGitClient(base_url=url, opener=o)
563 563 repo = self._factory.repo(wire)
564 564
565 565 determine_wants = repo.object_store.determine_wants_all
566 566 if refs:
567 567 def determine_wants_requested(references):
568 568 return [references[r] for r in references if r in refs]
569 569 determine_wants = determine_wants_requested
570 570
571 571 try:
572 572 remote_refs = client.fetch(
573 573 path=url, target=repo, determine_wants=determine_wants)
574 574 except NotGitRepository as e:
575 575 log.warning(
576 576 'Trying to fetch from "%s" failed, not a Git repository.', url)
577 577 # Exception can contain unicode which we convert
578 578 raise exceptions.AbortException(e)(repr(e))
579 579
580 580 # mikhail: client.fetch() returns all the remote refs, but fetches only
581 581 # refs filtered by `determine_wants` function. We need to filter result
582 582 # as well
583 583 if refs:
584 584 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
585 585
586 586 if apply_refs:
587 587 # TODO: johbo: Needs proper test coverage with a git repository
588 588 # that contains a tag object, so that we would end up with
589 589 # a peeled ref at this point.
590 590 for k in remote_refs:
591 591 if k.endswith(PEELED_REF_MARKER):
592 592 log.debug("Skipping peeled reference %s", k)
593 593 continue
594 594 repo[k] = remote_refs[k]
595 595
596 596 if refs and not update_after:
597 597 # mikhail: explicitly set the head to the last ref.
598 repo['HEAD'] = remote_refs[refs[-1]]
598 repo["HEAD"] = remote_refs[refs[-1]]
599 599
600 600 if update_after:
601 601 # we want to checkout HEAD
602 602 repo["HEAD"] = remote_refs["HEAD"]
603 603 index.build_index_from_tree(repo.path, repo.index_path(),
604 604 repo.object_store, repo["HEAD"].tree)
605 605 return remote_refs
606 606
607 607 @reraise_safe_exceptions
608 608 def sync_fetch(self, wire, url, refs=None, all_refs=False):
609 609 repo = self._factory.repo(wire)
610 610 if refs and not isinstance(refs, (list, tuple)):
611 611 refs = [refs]
612 612
613 613 config = self._wire_to_config(wire)
614 614 # get all remote refs we'll use to fetch later
615 615 cmd = ['ls-remote']
616 616 if not all_refs:
617 617 cmd += ['--heads', '--tags']
618 618 cmd += [url]
619 619 output, __ = self.run_git_command(
620 620 wire, cmd, fail_on_stderr=False,
621 621 _copts=self._remote_conf(config),
622 622 extra_env={'GIT_TERMINAL_PROMPT': '0'})
623 623
624 624 remote_refs = collections.OrderedDict()
625 625 fetch_refs = []
626 626
627 627 for ref_line in output.splitlines():
628 628 sha, ref = ref_line.split('\t')
629 629 sha = sha.strip()
630 630 if ref in remote_refs:
631 631 # duplicate, skip
632 632 continue
633 633 if ref.endswith(PEELED_REF_MARKER):
634 634 log.debug("Skipping peeled reference %s", ref)
635 635 continue
636 636 # don't sync HEAD
637 637 if ref in ['HEAD']:
638 638 continue
639 639
640 640 remote_refs[ref] = sha
641 641
642 642 if refs and sha in refs:
643 643 # we filter fetch using our specified refs
644 644 fetch_refs.append('{}:{}'.format(ref, ref))
645 645 elif not refs:
646 646 fetch_refs.append('{}:{}'.format(ref, ref))
647 647 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
648 648
649 649 if fetch_refs:
650 650 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
651 651 fetch_refs_chunks = list(chunk)
652 652 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
653 653 _out, _err = self.run_git_command(
654 654 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
655 655 fail_on_stderr=False,
656 656 _copts=self._remote_conf(config),
657 657 extra_env={'GIT_TERMINAL_PROMPT': '0'})
658 658
659 659 return remote_refs
660 660
661 661 @reraise_safe_exceptions
662 662 def sync_push(self, wire, url, refs=None):
663 663 if not self.check_url(url, wire):
664 664 return
665 665 config = self._wire_to_config(wire)
666 666 self._factory.repo(wire)
667 667 self.run_git_command(
668 668 wire, ['push', url, '--mirror'], fail_on_stderr=False,
669 669 _copts=self._remote_conf(config),
670 670 extra_env={'GIT_TERMINAL_PROMPT': '0'})
671 671
672 672 @reraise_safe_exceptions
673 673 def get_remote_refs(self, wire, url):
674 674 repo = Repo(url)
675 675 return repo.get_refs()
676 676
677 677 @reraise_safe_exceptions
678 678 def get_description(self, wire):
679 679 repo = self._factory.repo(wire)
680 680 return repo.get_description()
681 681
682 682 @reraise_safe_exceptions
683 683 def get_missing_revs(self, wire, rev1, rev2, path2):
684 684 repo = self._factory.repo(wire)
685 685 LocalGitClient(thin_packs=False).fetch(path2, repo)
686 686
687 687 wire_remote = wire.copy()
688 688 wire_remote['path'] = path2
689 689 repo_remote = self._factory.repo(wire_remote)
690 690 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
691 691
692 692 revs = [
693 693 x.commit.id
694 694 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
695 695 return revs
696 696
697 697 @reraise_safe_exceptions
698 698 def get_object(self, wire, sha, maybe_unreachable=False):
699 699 cache_on, context_uid, repo_id = self._cache_on(wire)
700 700 region = self._region(wire)
701 701 @region.conditional_cache_on_arguments(condition=cache_on)
702 702 def _get_object(_context_uid, _repo_id, _sha):
703 703 repo_init = self._factory.repo_libgit2(wire)
704 704 with repo_init as repo:
705 705
706 706 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
707 707 try:
708 708 commit = repo.revparse_single(sha)
709 709 except KeyError:
710 710 # NOTE(marcink): KeyError doesn't give us any meaningful information
711 711 # here, we instead give something more explicit
712 712 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
713 713 raise exceptions.LookupException(e)(missing_commit_err)
714 714 except ValueError as e:
715 715 raise exceptions.LookupException(e)(missing_commit_err)
716 716
717 717 is_tag = False
718 718 if isinstance(commit, pygit2.Tag):
719 719 commit = repo.get(commit.target)
720 720 is_tag = True
721 721
722 722 check_dangling = True
723 723 if is_tag:
724 724 check_dangling = False
725 725
726 726 if check_dangling and maybe_unreachable:
727 727 check_dangling = False
728 728
729 729 # we used a reference and it parsed means we're not having a dangling commit
730 730 if sha != commit.hex:
731 731 check_dangling = False
732 732
733 733 if check_dangling:
734 734 # check for dangling commit
735 735 for branch in repo.branches.with_commit(commit.hex):
736 736 if branch:
737 737 break
738 738 else:
739 739 # NOTE(marcink): Empty error doesn't give us any meaningful information
740 740 # here, we instead give something more explicit
741 741 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
742 742 raise exceptions.LookupException(e)(missing_commit_err)
743 743
744 744 commit_id = commit.hex
745 745 type_id = commit.type
746 746
747 747 return {
748 748 'id': commit_id,
749 749 'type': self._type_id_to_name(type_id),
750 750 'commit_id': commit_id,
751 751 'idx': 0
752 752 }
753 753
754 754 return _get_object(context_uid, repo_id, sha)
755 755
756 756 @reraise_safe_exceptions
757 757 def get_refs(self, wire):
758 758 cache_on, context_uid, repo_id = self._cache_on(wire)
759 759 region = self._region(wire)
760 760 @region.conditional_cache_on_arguments(condition=cache_on)
761 761 def _get_refs(_context_uid, _repo_id):
762 762
763 763 repo_init = self._factory.repo_libgit2(wire)
764 764 with repo_init as repo:
765 765 regex = re.compile('^refs/(heads|tags)/')
766 766 return {x.name: x.target.hex for x in
767 767 filter(lambda ref: regex.match(ref.name) ,repo.listall_reference_objects())}
768 768
769 769 return _get_refs(context_uid, repo_id)
770 770
771 771 @reraise_safe_exceptions
772 772 def get_branch_pointers(self, wire):
773 773 cache_on, context_uid, repo_id = self._cache_on(wire)
774 774 region = self._region(wire)
775 775 @region.conditional_cache_on_arguments(condition=cache_on)
776 776 def _get_branch_pointers(_context_uid, _repo_id):
777 777
778 778 repo_init = self._factory.repo_libgit2(wire)
779 779 regex = re.compile('^refs/heads')
780 780 with repo_init as repo:
781 781 branches = filter(lambda ref: regex.match(ref.name), repo.listall_reference_objects())
782 782 return {x.target.hex: x.shorthand for x in branches}
783 783
784 784 return _get_branch_pointers(context_uid, repo_id)
785 785
786 786 @reraise_safe_exceptions
787 787 def head(self, wire, show_exc=True):
788 788 cache_on, context_uid, repo_id = self._cache_on(wire)
789 789 region = self._region(wire)
790 790 @region.conditional_cache_on_arguments(condition=cache_on)
791 791 def _head(_context_uid, _repo_id, _show_exc):
792 792 repo_init = self._factory.repo_libgit2(wire)
793 793 with repo_init as repo:
794 794 try:
795 795 return repo.head.peel().hex
796 796 except Exception:
797 797 if show_exc:
798 798 raise
799 799 return _head(context_uid, repo_id, show_exc)
800 800
801 801 @reraise_safe_exceptions
802 802 def init(self, wire):
803 803 repo_path = str_to_dulwich(wire['path'])
804 804 self.repo = Repo.init(repo_path)
805 805
806 806 @reraise_safe_exceptions
807 807 def init_bare(self, wire):
808 808 repo_path = str_to_dulwich(wire['path'])
809 809 self.repo = Repo.init_bare(repo_path)
810 810
811 811 @reraise_safe_exceptions
812 812 def revision(self, wire, rev):
813 813
814 814 cache_on, context_uid, repo_id = self._cache_on(wire)
815 815 region = self._region(wire)
816 816 @region.conditional_cache_on_arguments(condition=cache_on)
817 817 def _revision(_context_uid, _repo_id, _rev):
818 818 repo_init = self._factory.repo_libgit2(wire)
819 819 with repo_init as repo:
820 820 commit = repo[rev]
821 821 obj_data = {
822 822 'id': commit.id.hex,
823 823 }
824 824 # tree objects itself don't have tree_id attribute
825 825 if hasattr(commit, 'tree_id'):
826 826 obj_data['tree'] = commit.tree_id.hex
827 827
828 828 return obj_data
829 829 return _revision(context_uid, repo_id, rev)
830 830
831 831 @reraise_safe_exceptions
832 832 def date(self, wire, commit_id):
833 833 cache_on, context_uid, repo_id = self._cache_on(wire)
834 834 region = self._region(wire)
835 835 @region.conditional_cache_on_arguments(condition=cache_on)
836 836 def _date(_repo_id, _commit_id):
837 837 repo_init = self._factory.repo_libgit2(wire)
838 838 with repo_init as repo:
839 839 commit = repo[commit_id]
840 840
841 841 if hasattr(commit, 'commit_time'):
842 842 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
843 843 else:
844 844 commit = commit.get_object()
845 845 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
846 846
847 847 # TODO(marcink): check dulwich difference of offset vs timezone
848 848 return [commit_time, commit_time_offset]
849 849 return _date(repo_id, commit_id)
850 850
851 851 @reraise_safe_exceptions
852 852 def author(self, wire, commit_id):
853 853 cache_on, context_uid, repo_id = self._cache_on(wire)
854 854 region = self._region(wire)
855 855 @region.conditional_cache_on_arguments(condition=cache_on)
856 856 def _author(_repo_id, _commit_id):
857 857 repo_init = self._factory.repo_libgit2(wire)
858 858 with repo_init as repo:
859 859 commit = repo[commit_id]
860 860
861 861 if hasattr(commit, 'author'):
862 862 author = commit.author
863 863 else:
864 864 author = commit.get_object().author
865 865
866 866 if author.email:
867 867 return u"{} <{}>".format(author.name, author.email)
868 868
869 869 try:
870 870 return u"{}".format(author.name)
871 871 except Exception:
872 872 return u"{}".format(safe_unicode(author.raw_name))
873 873
874 874 return _author(repo_id, commit_id)
875 875
876 876 @reraise_safe_exceptions
877 877 def message(self, wire, commit_id):
878 878 cache_on, context_uid, repo_id = self._cache_on(wire)
879 879 region = self._region(wire)
880 880 @region.conditional_cache_on_arguments(condition=cache_on)
881 881 def _message(_repo_id, _commit_id):
882 882 repo_init = self._factory.repo_libgit2(wire)
883 883 with repo_init as repo:
884 884 commit = repo[commit_id]
885 885 return commit.message
886 886 return _message(repo_id, commit_id)
887 887
888 888 @reraise_safe_exceptions
889 889 def parents(self, wire, commit_id):
890 890 cache_on, context_uid, repo_id = self._cache_on(wire)
891 891 region = self._region(wire)
892 892 @region.conditional_cache_on_arguments(condition=cache_on)
893 893 def _parents(_repo_id, _commit_id):
894 894 repo_init = self._factory.repo_libgit2(wire)
895 895 with repo_init as repo:
896 896 commit = repo[commit_id]
897 897 if hasattr(commit, 'parent_ids'):
898 898 parent_ids = commit.parent_ids
899 899 else:
900 900 parent_ids = commit.get_object().parent_ids
901 901
902 902 return [x.hex for x in parent_ids]
903 903 return _parents(repo_id, commit_id)
904 904
905 905 @reraise_safe_exceptions
906 906 def children(self, wire, commit_id):
907 907 cache_on, context_uid, repo_id = self._cache_on(wire)
908 908 region = self._region(wire)
909 909 @region.conditional_cache_on_arguments(condition=cache_on)
910 910 def _children(_repo_id, _commit_id):
911 911 output, __ = self.run_git_command(
912 912 wire, ['rev-list', '--all', '--children'])
913 913
914 914 child_ids = []
915 915 pat = re.compile(r'^%s' % commit_id)
916 916 for l in output.splitlines():
917 917 if pat.match(l):
918 918 found_ids = l.split(' ')[1:]
919 919 child_ids.extend(found_ids)
920 920
921 921 return child_ids
922 922 return _children(repo_id, commit_id)
923 923
924 924 @reraise_safe_exceptions
925 925 def set_refs(self, wire, key, value):
926 926 repo_init = self._factory.repo_libgit2(wire)
927 927 with repo_init as repo:
928 928 repo.references.create(key, value, force=True)
929 929
930 930 @reraise_safe_exceptions
931 931 def create_branch(self, wire, branch_name, commit_id, force=False):
932 932 repo_init = self._factory.repo_libgit2(wire)
933 933 with repo_init as repo:
934 934 commit = repo[commit_id]
935 935
936 936 if force:
937 937 repo.branches.local.create(branch_name, commit, force=force)
938 938 elif not repo.branches.get(branch_name):
939 939 # create only if that branch isn't existing
940 940 repo.branches.local.create(branch_name, commit, force=force)
941 941
942 942 @reraise_safe_exceptions
943 943 def remove_ref(self, wire, key):
944 944 repo_init = self._factory.repo_libgit2(wire)
945 945 with repo_init as repo:
946 946 repo.references.delete(key)
947 947
948 948 @reraise_safe_exceptions
949 949 def tag_remove(self, wire, tag_name):
950 950 repo_init = self._factory.repo_libgit2(wire)
951 951 with repo_init as repo:
952 952 key = 'refs/tags/{}'.format(tag_name)
953 953 repo.references.delete(key)
954 954
955 955 @reraise_safe_exceptions
956 956 def tree_changes(self, wire, source_id, target_id):
957 957 # TODO(marcink): remove this seems it's only used by tests
958 958 repo = self._factory.repo(wire)
959 959 source = repo[source_id].tree if source_id else None
960 960 target = repo[target_id].tree
961 961 result = repo.object_store.tree_changes(source, target)
962 962 return list(result)
963 963
964 964 @reraise_safe_exceptions
965 965 def tree_and_type_for_path(self, wire, commit_id, path):
966 966
967 967 cache_on, context_uid, repo_id = self._cache_on(wire)
968 968 region = self._region(wire)
969 969 @region.conditional_cache_on_arguments(condition=cache_on)
970 970 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
971 971 repo_init = self._factory.repo_libgit2(wire)
972 972
973 973 with repo_init as repo:
974 974 commit = repo[commit_id]
975 975 try:
976 976 tree = commit.tree[path]
977 977 except KeyError:
978 978 return None, None, None
979 979
980 980 return tree.id.hex, tree.type, tree.filemode
981 981 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
982 982
983 983 @reraise_safe_exceptions
984 984 def tree_items(self, wire, tree_id):
985 985 cache_on, context_uid, repo_id = self._cache_on(wire)
986 986 region = self._region(wire)
987 987 @region.conditional_cache_on_arguments(condition=cache_on)
988 988 def _tree_items(_repo_id, _tree_id):
989 989
990 990 repo_init = self._factory.repo_libgit2(wire)
991 991 with repo_init as repo:
992 992 try:
993 993 tree = repo[tree_id]
994 994 except KeyError:
995 995 raise ObjectMissing('No tree with id: {}'.format(tree_id))
996 996
997 997 result = []
998 998 for item in tree:
999 999 item_sha = item.hex
1000 1000 item_mode = item.filemode
1001 1001 item_type = item.type
1002 1002
1003 1003 if item_type == 'commit':
1004 1004 # NOTE(marcink): submodules we translate to 'link' for backward compat
1005 1005 item_type = 'link'
1006 1006
1007 1007 result.append((item.name, item_mode, item_sha, item_type))
1008 1008 return result
1009 1009 return _tree_items(repo_id, tree_id)
1010 1010
1011 1011 @reraise_safe_exceptions
1012 1012 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1013 1013 """
1014 1014 Old version that uses subprocess to call diff
1015 1015 """
1016 1016
1017 1017 flags = [
1018 1018 '-U%s' % context, '--patch',
1019 1019 '--binary',
1020 1020 '--find-renames',
1021 1021 '--no-indent-heuristic',
1022 1022 # '--indent-heuristic',
1023 1023 #'--full-index',
1024 1024 #'--abbrev=40'
1025 1025 ]
1026 1026
1027 1027 if opt_ignorews:
1028 1028 flags.append('--ignore-all-space')
1029 1029
1030 1030 if commit_id_1 == self.EMPTY_COMMIT:
1031 1031 cmd = ['show'] + flags + [commit_id_2]
1032 1032 else:
1033 1033 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1034 1034
1035 1035 if file_filter:
1036 1036 cmd.extend(['--', file_filter])
1037 1037
1038 1038 diff, __ = self.run_git_command(wire, cmd)
1039 1039 # If we used 'show' command, strip first few lines (until actual diff
1040 1040 # starts)
1041 1041 if commit_id_1 == self.EMPTY_COMMIT:
1042 1042 lines = diff.splitlines()
1043 1043 x = 0
1044 1044 for line in lines:
1045 1045 if line.startswith('diff'):
1046 1046 break
1047 1047 x += 1
1048 1048 # Append new line just like 'diff' command do
1049 1049 diff = '\n'.join(lines[x:]) + '\n'
1050 1050 return diff
1051 1051
1052 1052 @reraise_safe_exceptions
1053 1053 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1054 1054 repo_init = self._factory.repo_libgit2(wire)
1055 1055 with repo_init as repo:
1056 1056 swap = True
1057 1057 flags = 0
1058 1058 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1059 1059
1060 1060 if opt_ignorews:
1061 1061 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1062 1062
1063 1063 if commit_id_1 == self.EMPTY_COMMIT:
1064 1064 comm1 = repo[commit_id_2]
1065 1065 diff_obj = comm1.tree.diff_to_tree(
1066 1066 flags=flags, context_lines=context, swap=swap)
1067 1067
1068 1068 else:
1069 1069 comm1 = repo[commit_id_2]
1070 1070 comm2 = repo[commit_id_1]
1071 1071 diff_obj = comm1.tree.diff_to_tree(
1072 1072 comm2.tree, flags=flags, context_lines=context, swap=swap)
1073 1073 similar_flags = 0
1074 1074 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1075 1075 diff_obj.find_similar(flags=similar_flags)
1076 1076
1077 1077 if file_filter:
1078 1078 for p in diff_obj:
1079 1079 if p.delta.old_file.path == file_filter:
1080 1080 return p.patch or ''
1081 1081 # fo matching path == no diff
1082 1082 return ''
1083 1083 return diff_obj.patch or ''
1084 1084
1085 1085 @reraise_safe_exceptions
1086 1086 def node_history(self, wire, commit_id, path, limit):
1087 1087 cache_on, context_uid, repo_id = self._cache_on(wire)
1088 1088 region = self._region(wire)
1089 1089 @region.conditional_cache_on_arguments(condition=cache_on)
1090 1090 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1091 1091 # optimize for n==1, rev-list is much faster for that use-case
1092 1092 if limit == 1:
1093 1093 cmd = ['rev-list', '-1', commit_id, '--', path]
1094 1094 else:
1095 1095 cmd = ['log']
1096 1096 if limit:
1097 1097 cmd.extend(['-n', str(safe_int(limit, 0))])
1098 1098 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1099 1099
1100 1100 output, __ = self.run_git_command(wire, cmd)
1101 1101 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
1102 1102
1103 1103 return [x for x in commit_ids]
1104 1104 return _node_history(context_uid, repo_id, commit_id, path, limit)
1105 1105
1106 1106 @reraise_safe_exceptions
1107 1107 def node_annotate(self, wire, commit_id, path):
1108 1108
1109 1109 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1110 1110 # -l ==> outputs long shas (and we need all 40 characters)
1111 1111 # --root ==> doesn't put '^' character for boundaries
1112 1112 # -r commit_id ==> blames for the given commit
1113 1113 output, __ = self.run_git_command(wire, cmd)
1114 1114
1115 1115 result = []
1116 1116 for i, blame_line in enumerate(output.split('\n')[:-1]):
1117 1117 line_no = i + 1
1118 1118 commit_id, line = re.split(r' ', blame_line, 1)
1119 1119 result.append((line_no, commit_id, line))
1120 1120 return result
1121 1121
1122 1122 @reraise_safe_exceptions
1123 1123 def update_server_info(self, wire):
1124 1124 repo = self._factory.repo(wire)
1125 1125 update_server_info(repo)
1126 1126
1127 1127 @reraise_safe_exceptions
1128 1128 def get_all_commit_ids(self, wire):
1129 1129
1130 1130 cache_on, context_uid, repo_id = self._cache_on(wire)
1131 1131 region = self._region(wire)
1132 1132 @region.conditional_cache_on_arguments(condition=cache_on)
1133 1133 def _get_all_commit_ids(_context_uid, _repo_id):
1134 1134
1135 1135 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1136 1136 try:
1137 1137 output, __ = self.run_git_command(wire, cmd)
1138 1138 return output.splitlines()
1139 1139 except Exception:
1140 1140 # Can be raised for empty repositories
1141 1141 return []
1142 1142 return _get_all_commit_ids(context_uid, repo_id)
1143 1143
1144 1144 @reraise_safe_exceptions
1145 1145 def run_git_command(self, wire, cmd, **opts):
1146 1146 path = wire.get('path', None)
1147 1147
1148 1148 if path and os.path.isdir(path):
1149 1149 opts['cwd'] = path
1150 1150
1151 1151 if '_bare' in opts:
1152 1152 _copts = []
1153 1153 del opts['_bare']
1154 1154 else:
1155 1155 _copts = ['-c', 'core.quotepath=false', ]
1156 1156 safe_call = False
1157 1157 if '_safe' in opts:
1158 1158 # no exc on failure
1159 1159 del opts['_safe']
1160 1160 safe_call = True
1161 1161
1162 1162 if '_copts' in opts:
1163 1163 _copts.extend(opts['_copts'] or [])
1164 1164 del opts['_copts']
1165 1165
1166 1166 gitenv = os.environ.copy()
1167 1167 gitenv.update(opts.pop('extra_env', {}))
1168 1168 # need to clean fix GIT_DIR !
1169 1169 if 'GIT_DIR' in gitenv:
1170 1170 del gitenv['GIT_DIR']
1171 1171 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1172 1172 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1173 1173
1174 1174 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1175 1175 _opts = {'env': gitenv, 'shell': False}
1176 1176
1177 1177 proc = None
1178 1178 try:
1179 1179 _opts.update(opts)
1180 1180 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1181 1181
1182 1182 return ''.join(proc), ''.join(proc.error)
1183 1183 except (EnvironmentError, OSError) as err:
1184 1184 cmd = ' '.join(cmd) # human friendly CMD
1185 1185 tb_err = ("Couldn't run git command (%s).\n"
1186 1186 "Original error was:%s\n"
1187 1187 "Call options:%s\n"
1188 1188 % (cmd, err, _opts))
1189 1189 log.exception(tb_err)
1190 1190 if safe_call:
1191 1191 return '', err
1192 1192 else:
1193 1193 raise exceptions.VcsException()(tb_err)
1194 1194 finally:
1195 1195 if proc:
1196 1196 proc.close()
1197 1197
1198 1198 @reraise_safe_exceptions
1199 1199 def install_hooks(self, wire, force=False):
1200 1200 from vcsserver.hook_utils import install_git_hooks
1201 1201 bare = self.bare(wire)
1202 1202 path = wire['path']
1203 1203 return install_git_hooks(path, bare, force_create=force)
1204 1204
1205 1205 @reraise_safe_exceptions
1206 def set_head_ref(self, wire, head_name):
1207 log.debug('Setting refs/head to `%s`', head_name)
1208 cmd = ['symbolic-ref', 'HEAD', 'refs/heads/%s' % head_name]
1209 output, __ = self.run_git_command(wire, cmd)
1210 return [head_name] + output.splitlines()
1211
1212 @reraise_safe_exceptions
1206 1213 def get_hooks_info(self, wire):
1207 1214 from vcsserver.hook_utils import (
1208 1215 get_git_pre_hook_version, get_git_post_hook_version)
1209 1216 bare = self.bare(wire)
1210 1217 path = wire['path']
1211 1218 return {
1212 1219 'pre_version': get_git_pre_hook_version(path, bare),
1213 1220 'post_version': get_git_post_hook_version(path, bare),
1214 1221 }
1215 1222
1216 1223 @reraise_safe_exceptions
1217 1224 def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
1218 1225 archive_dir_name, commit_id):
1219 1226
1220 1227 def file_walker(_commit_id, path):
1221 1228 repo_init = self._factory.repo_libgit2(wire)
1222 1229
1223 1230 with repo_init as repo:
1224 1231 commit = repo[commit_id]
1225 1232
1226 1233 if path in ['', '/']:
1227 1234 tree = commit.tree
1228 1235 else:
1229 1236 tree = commit.tree[path.rstrip('/')]
1230 1237 tree_id = tree.id.hex
1231 1238 try:
1232 1239 tree = repo[tree_id]
1233 1240 except KeyError:
1234 1241 raise ObjectMissing('No tree with id: {}'.format(tree_id))
1235 1242
1236 1243 index = LibGit2Index.Index()
1237 1244 index.read_tree(tree)
1238 1245 file_iter = index
1239 1246
1240 1247 for fn in file_iter:
1241 1248 file_path = fn.path
1242 1249 mode = fn.mode
1243 1250 is_link = stat.S_ISLNK(mode)
1244 1251 if mode == pygit2.GIT_FILEMODE_COMMIT:
1245 1252 log.debug('Skipping path %s as a commit node', file_path)
1246 1253 continue
1247 1254 yield ArchiveNode(file_path, mode, is_link, repo[fn.hex].read_raw)
1248 1255
1249 1256 return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
1250 1257 archive_dir_name, commit_id)
General Comments 0
You need to be logged in to leave comments. Login now