summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py161
1 files changed, 115 insertions, 46 deletions
diff --git a/project.py b/project.py
index 9a7128af..0d60fc6e 100644
--- a/project.py
+++ b/project.py
@@ -40,7 +40,13 @@ from trace import IsTrace, Trace
40from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 40from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
41 41
42from pyversion import is_python3 42from pyversion import is_python3
43if not is_python3(): 43if is_python3():
44 import urllib.parse
45else:
46 import imp
47 import urlparse
48 urllib = imp.new_module('urllib')
49 urllib.parse = urlparse
44 # pylint:disable=W0622 50 # pylint:disable=W0622
45 input = raw_input 51 input = raw_input
46 # pylint:enable=W0622 52 # pylint:enable=W0622
@@ -314,11 +320,13 @@ class RemoteSpec(object):
314 def __init__(self, 320 def __init__(self,
315 name, 321 name,
316 url=None, 322 url=None,
323 pushUrl=None,
317 review=None, 324 review=None,
318 revision=None, 325 revision=None,
319 orig_name=None): 326 orig_name=None):
320 self.name = name 327 self.name = name
321 self.url = url 328 self.url = url
329 self.pushUrl = pushUrl
322 self.review = review 330 self.review = review
323 self.revision = revision 331 self.revision = revision
324 self.orig_name = orig_name 332 self.orig_name = orig_name
@@ -343,6 +351,7 @@ class RepoHook(object):
343 hook_type, 351 hook_type,
344 hooks_project, 352 hooks_project,
345 topdir, 353 topdir,
354 manifest_url,
346 abort_if_user_denies=False): 355 abort_if_user_denies=False):
347 """RepoHook constructor. 356 """RepoHook constructor.
348 357
@@ -356,11 +365,13 @@ class RepoHook(object):
356 topdir: Repo's top directory (the one containing the .repo directory). 365 topdir: Repo's top directory (the one containing the .repo directory).
357 Scripts will run with CWD as this directory. If you have a manifest, 366 Scripts will run with CWD as this directory. If you have a manifest,
358 this is manifest.topdir 367 this is manifest.topdir
368 manifest_url: The URL to the manifest git repo.
359 abort_if_user_denies: If True, we'll throw a HookError() if the user 369 abort_if_user_denies: If True, we'll throw a HookError() if the user
360 doesn't allow us to run the hook. 370 doesn't allow us to run the hook.
361 """ 371 """
362 self._hook_type = hook_type 372 self._hook_type = hook_type
363 self._hooks_project = hooks_project 373 self._hooks_project = hooks_project
374 self._manifest_url = manifest_url
364 self._topdir = topdir 375 self._topdir = topdir
365 self._abort_if_user_denies = abort_if_user_denies 376 self._abort_if_user_denies = abort_if_user_denies
366 377
@@ -409,9 +420,9 @@ class RepoHook(object):
409 def _CheckForHookApproval(self): 420 def _CheckForHookApproval(self):
410 """Check to see whether this hook has been approved. 421 """Check to see whether this hook has been approved.
411 422
412 We'll look at the hash of all of the hooks. If this matches the hash that 423 We'll accept approval of manifest URLs if they're using secure transports.
413 the user last approved, we're done. If it doesn't, we'll ask the user 424 This way the user can say they trust the manifest hoster. For insecure
414 about approval. 425 hosts, we fall back to checking the hash of the hooks repo.
415 426
416 Note that we ask permission for each individual hook even though we use 427 Note that we ask permission for each individual hook even though we use
417 the hash of all hooks when detecting changes. We'd like the user to be 428 the hash of all hooks when detecting changes. We'd like the user to be
@@ -425,44 +436,58 @@ class RepoHook(object):
425 HookError: Raised if the user doesn't approve and abort_if_user_denies 436 HookError: Raised if the user doesn't approve and abort_if_user_denies
426 was passed to the consturctor. 437 was passed to the consturctor.
427 """ 438 """
428 hooks_config = self._hooks_project.config 439 if self._ManifestUrlHasSecureScheme():
429 git_approval_key = 'repo.hooks.%s.approvedhash' % self._hook_type 440 return self._CheckForHookApprovalManifest()
441 else:
442 return self._CheckForHookApprovalHash()
443
444 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
445 changed_prompt):
446 """Check for approval for a particular attribute and hook.
447
448 Args:
449 subkey: The git config key under [repo.hooks.<hook_type>] to store the
450 last approved string.
451 new_val: The new value to compare against the last approved one.
452 main_prompt: Message to display to the user to ask for approval.
453 changed_prompt: Message explaining why we're re-asking for approval.
430 454
431 # Get the last hash that the user approved for this hook; may be None. 455 Returns:
432 old_hash = hooks_config.GetString(git_approval_key) 456 True if this hook is approved to run; False otherwise.
457
458 Raises:
459 HookError: Raised if the user doesn't approve and abort_if_user_denies
460 was passed to the consturctor.
461 """
462 hooks_config = self._hooks_project.config
463 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
433 464
434 # Get the current hash so we can tell if scripts changed since approval. 465 # Get the last value that the user approved for this hook; may be None.
435 new_hash = self._GetHash() 466 old_val = hooks_config.GetString(git_approval_key)
436 467
437 if old_hash is not None: 468 if old_val is not None:
438 # User previously approved hook and asked not to be prompted again. 469 # User previously approved hook and asked not to be prompted again.
439 if new_hash == old_hash: 470 if new_val == old_val:
440 # Approval matched. We're done. 471 # Approval matched. We're done.
441 return True 472 return True
442 else: 473 else:
443 # Give the user a reason why we're prompting, since they last told 474 # Give the user a reason why we're prompting, since they last told
444 # us to "never ask again". 475 # us to "never ask again".
445 prompt = 'WARNING: Scripts have changed since %s was allowed.\n\n' % ( 476 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
446 self._hook_type)
447 else: 477 else:
448 prompt = '' 478 prompt = ''
449 479
450 # Prompt the user if we're not on a tty; on a tty we'll assume "no". 480 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
451 if sys.stdout.isatty(): 481 if sys.stdout.isatty():
452 prompt += ('Repo %s run the script:\n' 482 prompt += main_prompt + ' (yes/always/NO)? '
453 ' %s\n'
454 '\n'
455 'Do you want to allow this script to run '
456 '(yes/yes-never-ask-again/NO)? ') % (self._GetMustVerb(),
457 self._script_fullpath)
458 response = input(prompt).lower() 483 response = input(prompt).lower()
459 print() 484 print()
460 485
461 # User is doing a one-time approval. 486 # User is doing a one-time approval.
462 if response in ('y', 'yes'): 487 if response in ('y', 'yes'):
463 return True 488 return True
464 elif response == 'yes-never-ask-again': 489 elif response == 'always':
465 hooks_config.SetString(git_approval_key, new_hash) 490 hooks_config.SetString(git_approval_key, new_val)
466 return True 491 return True
467 492
468 # For anything else, we'll assume no approval. 493 # For anything else, we'll assume no approval.
@@ -472,6 +497,40 @@ class RepoHook(object):
472 497
473 return False 498 return False
474 499
500 def _ManifestUrlHasSecureScheme(self):
501 """Check if the URI for the manifest is a secure transport."""
502 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
503 parse_results = urllib.parse.urlparse(self._manifest_url)
504 return parse_results.scheme in secure_schemes
505
506 def _CheckForHookApprovalManifest(self):
507 """Check whether the user has approved this manifest host.
508
509 Returns:
510 True if this hook is approved to run; False otherwise.
511 """
512 return self._CheckForHookApprovalHelper(
513 'approvedmanifest',
514 self._manifest_url,
515 'Run hook scripts from %s' % (self._manifest_url,),
516 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
517
518 def _CheckForHookApprovalHash(self):
519 """Check whether the user has approved the hooks repo.
520
521 Returns:
522 True if this hook is approved to run; False otherwise.
523 """
524 prompt = ('Repo %s run the script:\n'
525 ' %s\n'
526 '\n'
527 'Do you want to allow this script to run')
528 return self._CheckForHookApprovalHelper(
529 'approvedhash',
530 self._GetHash(),
531 prompt % (self._GetMustVerb(), self._script_fullpath),
532 'Scripts have changed since %s was allowed.' % (self._hook_type,))
533
475 def _ExecuteHook(self, **kwargs): 534 def _ExecuteHook(self, **kwargs):
476 """Actually execute the given hook. 535 """Actually execute the given hook.
477 536
@@ -628,7 +687,7 @@ class Project(object):
628 self.gitdir = gitdir.replace('\\', '/') 687 self.gitdir = gitdir.replace('\\', '/')
629 self.objdir = objdir.replace('\\', '/') 688 self.objdir = objdir.replace('\\', '/')
630 if worktree: 689 if worktree:
631 self.worktree = worktree.replace('\\', '/') 690 self.worktree = os.path.normpath(worktree.replace('\\', '/'))
632 else: 691 else:
633 self.worktree = None 692 self.worktree = None
634 self.relpath = relpath 693 self.relpath = relpath
@@ -852,11 +911,13 @@ class Project(object):
852 else: 911 else:
853 return False 912 return False
854 913
855 def PrintWorkTreeStatus(self, output_redir=None): 914 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
856 """Prints the status of the repository to stdout. 915 """Prints the status of the repository to stdout.
857 916
858 Args: 917 Args:
859 output: If specified, redirect the output to this object. 918 output: If specified, redirect the output to this object.
919 quiet: If True then only print the project name. Do not print
920 the modified files, branch name, etc.
860 """ 921 """
861 if not os.path.isdir(self.worktree): 922 if not os.path.isdir(self.worktree):
862 if output_redir is None: 923 if output_redir is None:
@@ -882,6 +943,10 @@ class Project(object):
882 out.redirect(output_redir) 943 out.redirect(output_redir)
883 out.project('project %-40s', self.relpath + '/ ') 944 out.project('project %-40s', self.relpath + '/ ')
884 945
946 if quiet:
947 out.nl()
948 return 'DIRTY'
949
885 branch = self.CurrentBranch 950 branch = self.CurrentBranch
886 if branch is None: 951 if branch is None:
887 out.nobranch('(*** NO BRANCH ***)') 952 out.nobranch('(*** NO BRANCH ***)')
@@ -1199,13 +1264,18 @@ class Project(object):
1199 elif self.manifest.default.sync_c: 1264 elif self.manifest.default.sync_c:
1200 current_branch_only = True 1265 current_branch_only = True
1201 1266
1267 if self.clone_depth:
1268 depth = self.clone_depth
1269 else:
1270 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1271
1202 need_to_fetch = not (optimized_fetch and 1272 need_to_fetch = not (optimized_fetch and
1203 (ID_RE.match(self.revisionExpr) and 1273 (ID_RE.match(self.revisionExpr) and
1204 self._CheckForSha1())) 1274 self._CheckForSha1()))
1205 if (need_to_fetch and 1275 if (need_to_fetch and
1206 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir, 1276 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1207 current_branch_only=current_branch_only, 1277 current_branch_only=current_branch_only,
1208 no_tags=no_tags, prune=prune)): 1278 no_tags=no_tags, prune=prune, depth=depth)):
1209 return False 1279 return False
1210 1280
1211 if self.worktree: 1281 if self.worktree:
@@ -1768,6 +1838,7 @@ class Project(object):
1768 1838
1769 remote = RemoteSpec(self.remote.name, 1839 remote = RemoteSpec(self.remote.name,
1770 url=url, 1840 url=url,
1841 pushUrl=self.remote.pushUrl,
1771 review=self.remote.review, 1842 review=self.remote.review,
1772 revision=self.remote.revision) 1843 revision=self.remote.revision)
1773 subproject = Project(manifest=self.manifest, 1844 subproject = Project(manifest=self.manifest,
@@ -1777,7 +1848,7 @@ class Project(object):
1777 objdir=objdir, 1848 objdir=objdir,
1778 worktree=worktree, 1849 worktree=worktree,
1779 relpath=relpath, 1850 relpath=relpath,
1780 revisionExpr=self.revisionExpr, 1851 revisionExpr=rev,
1781 revisionId=rev, 1852 revisionId=rev,
1782 rebase=self.rebase, 1853 rebase=self.rebase,
1783 groups=self.groups, 1854 groups=self.groups,
@@ -1820,23 +1891,17 @@ class Project(object):
1820 quiet=False, 1891 quiet=False,
1821 alt_dir=None, 1892 alt_dir=None,
1822 no_tags=False, 1893 no_tags=False,
1823 prune=False): 1894 prune=False,
1895 depth=None):
1824 1896
1825 is_sha1 = False 1897 is_sha1 = False
1826 tag_name = None 1898 tag_name = None
1827 depth = None
1828
1829 # The depth should not be used when fetching to a mirror because 1899 # The depth should not be used when fetching to a mirror because
1830 # it will result in a shallow repository that cannot be cloned or 1900 # it will result in a shallow repository that cannot be cloned or
1831 # fetched from. 1901 # fetched from.
1832 if not self.manifest.IsMirror: 1902 # The repo project should also never be synced with partial depth.
1833 if self.clone_depth: 1903 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1834 depth = self.clone_depth 1904 depth = None
1835 else:
1836 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1837 # The repo project should never be synced with partial depth
1838 if self.relpath == '.repo/repo':
1839 depth = None
1840 1905
1841 if depth: 1906 if depth:
1842 current_branch_only = True 1907 current_branch_only = True
@@ -1997,21 +2062,22 @@ class Project(object):
1997 os.remove(packed_refs) 2062 os.remove(packed_refs)
1998 self.bare_git.pack_refs('--all', '--prune') 2063 self.bare_git.pack_refs('--all', '--prune')
1999 2064
2000 if is_sha1 and current_branch_only and self.upstream: 2065 if is_sha1 and current_branch_only:
2001 # We just synced the upstream given branch; verify we 2066 # We just synced the upstream given branch; verify we
2002 # got what we wanted, else trigger a second run of all 2067 # got what we wanted, else trigger a second run of all
2003 # refs. 2068 # refs.
2004 if not self._CheckForSha1(): 2069 if not self._CheckForSha1():
2005 if not depth: 2070 if current_branch_only and depth:
2006 # Avoid infinite recursion when depth is True (since depth implies 2071 # Sync the current branch only with depth set to None
2007 # current_branch_only)
2008 return self._RemoteFetch(name=name, current_branch_only=False,
2009 initial=False, quiet=quiet, alt_dir=alt_dir)
2010 if self.clone_depth:
2011 self.clone_depth = None
2012 return self._RemoteFetch(name=name, 2072 return self._RemoteFetch(name=name,
2013 current_branch_only=current_branch_only, 2073 current_branch_only=current_branch_only,
2014 initial=False, quiet=quiet, alt_dir=alt_dir) 2074 initial=False, quiet=quiet, alt_dir=alt_dir,
2075 depth=None)
2076 else:
2077 # Avoid infinite recursion: sync all branches with depth set to None
2078 return self._RemoteFetch(name=name, current_branch_only=False,
2079 initial=False, quiet=quiet, alt_dir=alt_dir,
2080 depth=None)
2015 2081
2016 return ok 2082 return ok
2017 2083
@@ -2235,6 +2301,7 @@ class Project(object):
2235 for key in ['user.name', 'user.email']: 2301 for key in ['user.name', 'user.email']:
2236 if m.Has(key, include_defaults=False): 2302 if m.Has(key, include_defaults=False):
2237 self.config.SetString(key, m.GetString(key)) 2303 self.config.SetString(key, m.GetString(key))
2304 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
2238 if self.manifest.IsMirror: 2305 if self.manifest.IsMirror:
2239 self.config.SetString('core.bare', 'true') 2306 self.config.SetString('core.bare', 'true')
2240 else: 2307 else:
@@ -2288,6 +2355,7 @@ class Project(object):
2288 if self.remote.url: 2355 if self.remote.url:
2289 remote = self.GetRemote(self.remote.name) 2356 remote = self.GetRemote(self.remote.name)
2290 remote.url = self.remote.url 2357 remote.url = self.remote.url
2358 remote.pushUrl = self.remote.pushUrl
2291 remote.review = self.remote.review 2359 remote.review = self.remote.review
2292 remote.projectname = self.name 2360 remote.projectname = self.name
2293 2361
@@ -2332,6 +2400,7 @@ class Project(object):
2332 src = os.path.realpath(os.path.join(srcdir, name)) 2400 src = os.path.realpath(os.path.join(srcdir, name))
2333 # Fail if the links are pointing to the wrong place 2401 # Fail if the links are pointing to the wrong place
2334 if src != dst: 2402 if src != dst:
2403 _error('%s is different in %s vs %s', name, destdir, srcdir)
2335 raise GitError('--force-sync not enabled; cannot overwrite a local ' 2404 raise GitError('--force-sync not enabled; cannot overwrite a local '
2336 'work tree. If you\'re comfortable with the ' 2405 'work tree. If you\'re comfortable with the '
2337 'possibility of losing the work tree\'s git metadata,' 2406 'possibility of losing the work tree\'s git metadata,'