| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Repository module to handle different types of repositories.""" |
| |
| import glob |
| import logging |
| import multiprocessing |
| import os |
| import re |
| import shutil |
| import time |
| import urllib.parse |
| |
| from chromite.cbuildbot import cbuildbot_alerts |
| from chromite.lib import constants |
| from chromite.lib import cros_build_lib |
| from chromite.lib import git |
| from chromite.lib import locking |
| from chromite.lib import metrics |
| from chromite.lib import osutils |
| from chromite.lib import parallel |
| from chromite.lib import path_util |
| from chromite.lib import repo_util |
| from chromite.lib import retry_util |
| from chromite.lib import rewrite_git_alternates |
| |
| |
| # Default sleep time(second) between retries |
| DEFAULT_SLEEP_TIME = 5 |
| |
| # Retry limit for 'repo init' |
| REPO_INIT_RETRY_LIMIT = 2 |
| |
| SELFUPDATE_WARNING = r'Skipped upgrade to unverified version' |
| |
| SELFUPDATE_WARNING_RE = re.compile(SELFUPDATE_WARNING, re.IGNORECASE) |
| |
| |
| class SrcCheckOutException(Exception): |
| """Exception gets thrown for failure to sync sources""" |
| |
| |
| def IsARepoRoot(directory): |
| """Returns True if directory is the root of a repo checkout.""" |
| return os.path.exists(os.path.join(directory, '.repo')) |
| |
| |
| def _IsLocalPath(url): |
| """Returns whether the url is a local path. |
| |
| Args: |
| url: The url string to parse. |
| |
| Returns: |
| True if the url actually refers to a local path (with prefix |
| 'file://' or '/'); else, False. |
| """ |
| o = urllib.parse.urlparse(url) |
| return o.scheme in ('file', '') |
| |
| |
| def CloneWorkingRepo(dest, url, reference, branch=None, single_branch=False): |
| """Clone a git repository with an existing local copy as a reference. |
| |
| Also copy the hooks into the new repository. |
| |
| Args: |
| dest: The directory to clone int. |
| url: The URL of the repository to clone. |
| reference: Local checkout to draw objects from. |
| branch: The branch to clone. |
| single_branch: Clone only one the requested branch. |
| """ |
| git.Clone(dest, url, reference=reference, |
| single_branch=single_branch, branch=branch) |
| for name in glob.glob(os.path.join(reference, '.git', 'hooks', '*')): |
| newname = os.path.join(dest, '.git', 'hooks', os.path.basename(name)) |
| shutil.copyfile(name, newname) |
| shutil.copystat(name, newname) |
| |
| |
| def ClearBuildRoot(buildroot, preserve_paths=()): |
| """Remove all files in the buildroot not preserved. |
| |
| Args: |
| buildroot: buildroot to clear. |
| preserve_paths: paths need to be preserved during clean. |
| """ |
| if os.path.exists(buildroot): |
| cmd = ['find', buildroot, '-mindepth', '1', '-maxdepth', '1'] |
| |
| ignores = [] |
| for path in preserve_paths: |
| if ignores: |
| ignores.append('-a') |
| ignores += ['!', '-name', path] |
| cmd.extend(ignores) |
| |
| cmd += ['-exec', 'rm', '-rf', '{}', '+'] |
| cros_build_lib.sudo_run(cmd) |
| |
| osutils.SafeMakedirs(buildroot) |
| |
| |
| class RepoRepository(object): |
| """A Class that encapsulates a repo repository.""" |
| |
| def __init__(self, manifest_repo_url, directory, branch=None, |
| referenced_repo=None, manifest=constants.DEFAULT_MANIFEST, |
| depth=None, repo_url=None, |
| repo_branch=None, groups=None, repo_cmd='repo', |
| preserve_paths=(), git_cache_dir=None): |
| """Initialize. |
| |
| Args: |
| manifest_repo_url: URL to fetch repo manifest from. |
| directory: local path where to checkout the repository. |
| branch: Branch to check out the manifest at. |
| referenced_repo: Repository to reference for git objects, if possible. |
| manifest: Which manifest.xml within the branch to use. Effectively |
| default.xml if not given. |
| depth: Mutually exclusive option to referenced_repo; this limits the |
| checkout to a max commit history of the given integer. |
| repo_url: URL to fetch repo tool from. |
| repo_branch: Branch to check out the repo tool at. |
| groups: Only sync projects that match this filter. |
| repo_cmd: Name of repo_cmd to use. |
| preserve_paths: paths need to be preserved in repo clean |
| in case we want to clean and retry repo sync. |
| git_cache_dir: If specified, use --cache-dir=git_cache_dir in repo sync. |
| """ |
| self.manifest_repo_url = manifest_repo_url |
| self.repo_url = repo_url |
| self.repo_branch = repo_branch |
| self.directory = directory |
| self.branch = branch |
| self.groups = groups |
| self.repo_cmd = repo_cmd |
| self.preserve_paths = preserve_paths |
| self.repo_rev = None |
| |
| # It's perfectly acceptable to pass in a reference pathway that isn't |
| # usable. Detect it, and suppress the setting so that any depth |
| # settings aren't disabled due to it. |
| if referenced_repo is not None: |
| if depth is not None: |
| raise ValueError('referenced_repo and depth are mutually exclusive ' |
| 'options; please pick one or the other.') |
| if git_cache_dir is not None: |
| raise ValueError('referenced_repo and git_cache_dir are mutually ' |
| 'exclusive options; please pick one or the other.') |
| if not IsARepoRoot(referenced_repo): |
| referenced_repo = None |
| |
| self.git_cache_dir = git_cache_dir |
| self._referenced_repo = referenced_repo |
| self._manifest = manifest |
| |
| # Use by Intialize to avoid updating more than once. |
| self._repo_update_needed = True |
| self._depth = int(depth) if depth is not None else None |
| |
| def _SwitchToLocalManifest(self, local_manifest): |
| """Reinitializes the repository if the manifest has changed.""" |
| logging.debug('Moving to manifest defined by %s', local_manifest) |
| # TODO: use upstream repo's manifest logic when we bump repo version. |
| manifest_path = self.GetRelativePath('.repo/manifest.xml') |
| os.unlink(manifest_path) |
| shutil.copyfile(local_manifest, manifest_path) |
| |
| def _RepoSelfupdate(self): |
| """Execute repo selfupdate command. |
| |
| 'repo selfupdate' would clean up the .repo/repo dir on certain exceptions |
| and warnings, it must be followed by the 'repo init' command, which would |
| recover .repo/repo in this circumstance. |
| """ |
| cmd = [self.repo_cmd, 'selfupdate'] |
| failed_to_selfupdate = False |
| try: |
| cmd_result = cros_build_lib.run( |
| cmd, cwd=self.directory, capture_output=True, log_output=True, |
| encoding='utf-8') |
| |
| if (cmd_result.stderr is not None and |
| SELFUPDATE_WARNING_RE.search(cmd_result.stderr)): |
| logging.warning('Unable to selfupdate because of warning "%s"', |
| SELFUPDATE_WARNING) |
| failed_to_selfupdate = True |
| except cros_build_lib.RunCommandError as e: |
| logging.warning('repo selfupdate failed with exception: %s', e) |
| failed_to_selfupdate = True |
| |
| if failed_to_selfupdate: |
| metrics.Counter(constants.MON_REPO_SELFUPDATE_FAILURE_COUNT).increment() |
| logging.warning('Failed to selfupdate repo, cleaning .repo/repo in %s', |
| self.directory) |
| osutils.RmDir(os.path.join(self.directory, '.repo', 'repo'), |
| ignore_missing=True) |
| |
| def _CleanUpRepoManifest(self, directory): |
| """Clean up the manifest and repo dirs under the '.repo' dir. |
| |
| Args: |
| directory: The directory where stores repo and manifest dirs. |
| """ |
| paths = [os.path.join(directory, '.repo', x) for x in |
| ('manifest.xml', 'manifests.git', 'manifests', 'repo')] |
| cros_build_lib.sudo_run(['rm', '-rf'] + paths) |
| |
| def _RepoInit(self, *args, **kwargs): |
| """Run 'repo init' and clean up repo manifest on init failures. |
| |
| Args: |
| args: args to pass to cros_build_lib.run. |
| kwargs: kwargs to pass to cros_build_lib.run. |
| """ |
| try: |
| kwargs.setdefault('cwd', self.directory) |
| kwargs.setdefault('input', '\n\ny\n') |
| cros_build_lib.run(*args, **kwargs) |
| except cros_build_lib.RunCommandError as e: |
| logging.warning('Wiping %r due to `repo init` failures.', self.directory) |
| self._CleanUpRepoManifest(self.directory) |
| raise e |
| |
| def CleanStaleLocks(self): |
| """Clean up stale locks left behind in any git repos. |
| |
| This might occur if earlier git commands were killed during an operation. |
| Warning: This is dangerous because these locks are intended to prevent |
| corruption. Only use this if you are sure that no other git process is |
| accessing the repo (such as at the beginning of a fresh build). |
| """ |
| logging.info('Removing stale git locks from: %s', self.directory) |
| for attrs in git.ManifestCheckout.Cached(self.directory).ListCheckouts(): |
| d = os.path.join(self.directory, attrs['path']) |
| repo_git_store = self.CalculateGitRepoLocations(attrs['name'], d)[0] |
| logging.debug('Found repo directory to clean: %s', repo_git_store) |
| git.DeleteStaleLocks(repo_git_store) |
| |
| def CalculateGitRepoLocations(self, project, path): |
| """Calculate the directory locations for git and object stores. |
| |
| These are stored separately from the git checkout location. |
| |
| Args: |
| project: String name of the manifest project to locate. |
| path: String path where the manifest checks out the project. |
| |
| Returns: |
| A tuple containing the string directory paths: (git dir, objects dir). |
| """ |
| relpath = os.path.relpath(path, self.directory) |
| projects_dir = os.path.join(self.directory, '.repo', 'projects') |
| project_objects_dir = os.path.join( |
| self.directory, '.repo', 'project-objects') |
| repo_git_store = '%s.git' % os.path.join(projects_dir, relpath) |
| repo_obj_store = '%s.git' % os.path.join(project_objects_dir, project) |
| return repo_git_store, repo_obj_store |
| |
| def BuildRootGitCleanup(self, prune_all=False): |
| """Put buildroot onto manifest branch. Delete branches created on last run. |
| |
| Args: |
| prune_all: If True, prune all loose objects regardless of gc.pruneExpire. |
| |
| Raises: |
| A variety of exceptions if the buildroot is missing/corrupt. |
| """ |
| logging.info('Resetting all repo branches: %s', self.directory) |
| lock_path = os.path.join(self.directory, '.clean_lock') |
| deleted_objdirs = multiprocessing.Event() |
| |
| def RunCleanupCommands(project, path): |
| with locking.FileLock(lock_path, verbose=False).read_lock() as lock: |
| repo_git_store, repo_obj_store = self.CalculateGitRepoLocations( |
| project, path) |
| |
| try: |
| if os.path.isdir(path): |
| git.CleanAndDetachHead(path) |
| |
| if os.path.isdir(repo_git_store): |
| git.GarbageCollection(repo_git_store, prune_all=prune_all) |
| except cros_build_lib.RunCommandError as e: |
| result = e.result |
| cbuildbot_alerts.PrintBuildbotStepWarnings() |
| logging.warning('\n%s', result.stderr) |
| |
| # If there's no repository corruption, just delete the index. |
| corrupted = git.IsGitRepositoryCorrupted(repo_git_store) |
| lock.write_lock() |
| logging.warning('Deleting %s because %s failed', path, result.cmd) |
| osutils.RmDir(path, ignore_missing=True, sudo=True) |
| if corrupted: |
| # Looks like the object dir is corrupted. Delete it. |
| deleted_objdirs.set() |
| for store in (repo_git_store, repo_obj_store): |
| logging.warning('Deleting %s as well', store) |
| osutils.RmDir(store, ignore_missing=True) |
| |
| # TODO: Make the deletions below smarter. Look to see what exists, |
| # instead of just deleting things we think might be there. |
| |
| # Delete all branches created by cbuildbot. |
| if os.path.isdir(repo_git_store): |
| cmd = ['branch', '-D'] + list(constants.CREATED_BRANCHES) |
| # Ignore errors, since we delete branches without checking existence. |
| git.RunGit(repo_git_store, cmd, check=False) |
| |
| if os.path.isdir(path): |
| # Above we deleted refs/heads/<branch> for each created branch, now |
| # we need to delete the bare ref <branch> if it was created somehow. |
| for ref in constants.CREATED_BRANCHES: |
| # Ignore errors, since we delete branches without checking |
| # existence. |
| git.RunGit(path, ['update-ref', '-d', ref], check=False) |
| |
| # Cleanup all of the directories. |
| dirs = [[attrs['name'], os.path.join(self.directory, attrs['path'])] |
| for attrs in |
| git.ManifestCheckout.Cached(self.directory).ListCheckouts()] |
| parallel.RunTasksInProcessPool(RunCleanupCommands, dirs) |
| |
| # repo shares git object directories amongst multiple project paths. If the |
| # first pass deleted an object dir for a project path, then other |
| # repositories (project paths) of that same project may now be broken. Do a |
| # second pass to clean them up as well. |
| if deleted_objdirs.is_set(): |
| parallel.RunTasksInProcessPool(RunCleanupCommands, dirs) |
| |
| def AssertNotNested(self): |
| """Assert that the current repository isn't inside another repository. |
| |
| Since repo detects it's root by looking for .repo, it can't support having |
| one repo inside another. |
| """ |
| if not IsARepoRoot(self.directory): |
| repo_root = git.FindRepoDir(self.directory) |
| if repo_root: |
| raise ValueError('%s is nested inside a repo at %s.' % |
| (self.directory, repo_root)) |
| |
| def PreLoad(self, source_repo=None): |
| """Preinitialize new .repo directory for faster initial sync. |
| |
| This is a hint that the new .repo directory can be copied from |
| source_repo/.repo to avoid network sync operations. It does nothing if the |
| .repo already exists, or source is invalid. source_repo defaults to the repo |
| of the current checkout for the script. |
| |
| This should be done before the target is cleaned, to avoid corruption, since |
| the source is in an unknown state. |
| |
| Args: |
| source_repo: Directory path to use as a template for new repo checkout. |
| """ |
| if not source_repo: |
| source_repo = constants.SOURCE_ROOT |
| |
| # If target already exist, or source is invalid, don't copy. |
| if IsARepoRoot(self.directory) or not IsARepoRoot(source_repo): |
| return |
| |
| osutils.SafeMakedirs(self.directory) |
| |
| logging.info("Preloading '%s' from '%s'.", self.directory, source_repo) |
| repo_util.Repository(source_repo).Copy(self.directory) |
| logging.info('Preload Finished.') |
| |
| |
| def Initialize(self, local_manifest=None, manifest_repo_url=None, |
| extra_args=()): |
| """Initializes a repository. Optionally forces a local manifest. |
| |
| Args: |
| local_manifest: The absolute path to a custom manifest to use. This will |
| replace .repo/manifest.xml. |
| manifest_repo_url: A new value for manifest_repo_url. |
| extra_args: Extra args to pass to 'repo init' |
| """ |
| self.AssertNotNested() |
| |
| if manifest_repo_url: |
| self.manifest_repo_url = manifest_repo_url |
| |
| # Do a sanity check on the repo; if it exists and we can't pull a |
| # manifest from it, we know it's fairly screwed up and needs a fresh |
| # rebuild. |
| if os.path.exists(os.path.join(self.directory, '.repo', 'manifest.xml')): |
| cmd = [self.repo_cmd, 'manifest'] |
| try: |
| cros_build_lib.run(cmd, cwd=self.directory, capture_output=True) |
| except cros_build_lib.RunCommandError: |
| metrics.Counter(constants.MON_REPO_MANIFEST_FAILURE_COUNT).increment() |
| logging.warning('Wiping %r due to `repo manifest` failure', |
| self.directory) |
| self._CleanUpRepoManifest(self.directory) |
| self._repo_update_needed = False |
| |
| # Wipe local_manifest.xml if it exists- it can interfere w/ things in |
| # bad ways (duplicate projects, etc); we control this repository, thus |
| # we can destroy it. |
| osutils.SafeUnlink(os.path.join(self.directory, 'local_manifest.xml')) |
| |
| # Force a repo update the first time we initialize an old repo checkout. |
| # Don't update if there is nothing to update. |
| if self._repo_update_needed: |
| if IsARepoRoot(self.directory): |
| self._RepoSelfupdate() |
| self._repo_update_needed = False |
| |
| # Use our own repo, in case android.kernel.org (the default location) is |
| # down. |
| init_cmd = [self.repo_cmd, 'init', |
| '--manifest-url', self.manifest_repo_url] |
| if self.repo_url: |
| init_cmd.extend(['--repo-url', self.repo_url]) |
| if self._referenced_repo: |
| init_cmd.extend(['--reference', self._referenced_repo]) |
| if self._manifest: |
| init_cmd.extend(['--manifest-name', self._manifest]) |
| if self._depth is not None: |
| init_cmd.extend(['--depth', str(self._depth)]) |
| init_cmd.extend(extra_args) |
| # Handle branch / manifest options. |
| if self.branch: |
| init_cmd.extend(['--manifest-branch', self.branch]) |
| if self.repo_rev: |
| init_cmd.extend(['--repo-rev', 'v2.7']) |
| if self.repo_branch: |
| init_cmd.extend(['--repo-branch', self.repo_branch]) |
| if self.groups: |
| init_cmd.extend(['--groups', self.groups]) |
| else: |
| init_cmd.extend(['--groups', 'all']) |
| |
| def _StatusCallback(attempt, _): |
| if attempt: |
| metrics.Counter(constants.MON_REPO_INIT_RETRY_COUNT).increment( |
| fields={'manifest_url': self.manifest_repo_url}) |
| |
| retry_util.RetryCommand(self._RepoInit, |
| REPO_INIT_RETRY_LIMIT, |
| init_cmd, |
| sleep=DEFAULT_SLEEP_TIME, |
| backoff_factor=2, |
| log_retries=True, |
| status_callback=_StatusCallback) |
| |
| if local_manifest and local_manifest != self._manifest: |
| self._SwitchToLocalManifest(local_manifest) |
| |
| @property |
| def _ManifestConfig(self): |
| return os.path.join(self.directory, '.repo', 'manifests.git', 'config') |
| |
| def _EnsureMirroring(self, post_sync=False): |
| """Ensure git is usable from w/in the chroot if --references is enabled |
| |
| repo init --references hardcodes the abspath to parent; this pathway |
| however isn't usable from the chroot (it doesn't exist). As such the |
| pathway is rewritten to use relative pathways pointing at the root of |
| the repo, which via I84988630 enter_chroot sets up a helper bind mount |
| allowing git/repo to access the actual referenced repo. |
| |
| This has to be invoked prior to a repo sync of the target trybot to |
| fix any pathways that may have been broken by the parent repo moving |
| on disk, and needs to be invoked after the sync has completed to rewrite |
| any new project's abspath to relative. |
| """ |
| if not self._referenced_repo: |
| return |
| |
| proj_root = os.path.join(self.directory, '.repo', 'project-objects') |
| if not os.path.exists(proj_root): |
| # Not yet synced, nothing to be done. |
| return |
| |
| rewrite_git_alternates.RebuildRepoCheckout(self.directory, |
| self._referenced_repo) |
| |
| if post_sync: |
| chroot_path = os.path.join(self._referenced_repo, '.repo', 'chroot', |
| 'external') |
| chroot_path = path_util.ToChrootPath(chroot_path) |
| rewrite_git_alternates.RebuildRepoCheckout( |
| self.directory, self._referenced_repo, chroot_path) |
| |
| # Finally, force the git config marker that enter_chroot looks for |
| # to know when to do bind mounting trickery; this normally will exist, |
| # but if we're converting a pre-existing repo checkout, it's possible |
| # that it was invoked w/out the reference arg. Note this must be |
| # an absolute path to the source repo- enter_chroot uses that to know |
| # what to bind mount into the chroot. |
| cmd = ['config', '--file', self._ManifestConfig, 'repo.reference', |
| self._referenced_repo] |
| git.RunGit('.', cmd) |
| |
| def _CleanUpAndRunCommand(self, *args, **kwargs): |
| """Clean up repository and run command. |
| |
| This is only called in repo network Sync retries. |
| """ |
| self.BuildRootGitCleanup() |
| local_manifest = kwargs.pop('local_manifest', None) |
| # Always re-initialize to the current branch. |
| self.Initialize(local_manifest) |
| # Fix existing broken mirroring configurations. |
| self._EnsureMirroring() |
| |
| fields = {'manifest_repo': self.manifest_repo_url} |
| metrics.Counter(constants.MON_REPO_SYNC_RETRY_COUNT).increment( |
| fields=fields) |
| |
| cros_build_lib.run(*args, **kwargs) |
| |
| def _RepoDebugInfo(self): |
| """Display debugging information for the repo binary.""" |
| logging.info('Repo path: %s', osutils.Which('repo')) |
| cmd = [self.repo_cmd, 'version'] |
| cros_build_lib.run(cmd, capture_output=True, encoding='utf-8', |
| log_output=True) |
| |
| def Sync(self, local_manifest=None, jobs=None, all_branches=True, |
| network_only=False, detach=False, |
| downgrade_repo: bool = False): |
| """Sync/update the source. Changes manifest if specified. |
| |
| Args: |
| local_manifest: If true, checks out source to manifest. DEFAULT_MANIFEST |
| may be used to set it back to the default manifest. |
| jobs: May be set to override the default sync parallelism defined by |
| the manifest. |
| all_branches: If False, a repo sync -c is performed; this saves on |
| sync'ing via grabbing only what is needed for the manifest specified |
| branch. Defaults to True. TODO(davidjames): Set the default back to |
| False once we've fixed https://crbug.com/368722 . |
| network_only: If true, perform only the network half of the sync; skip |
| the checkout. Primarily of use to validate a manifest (although |
| if the manifest has bad copyfile statements, via skipping checkout |
| the broken copyfile tag won't be spotted), or of use when the |
| invoking code is fine w/ operating on bare repos, ie .repo/projects/*. |
| detach: If true, throw away all local changes, even if on tracking |
| branches. |
| downgrade_repo: Whether to downgrade repo version. |
| """ |
| try: |
| if downgrade_repo: |
| self.repo_rev = 'v2.7' |
| # Repo debugging information. |
| self._RepoDebugInfo() |
| # Always re-initialize to the current branch. |
| self.Initialize(local_manifest) |
| # Fix existing broken mirroring configurations. |
| self._EnsureMirroring() |
| |
| cmd = [self.repo_cmd, '--time', 'sync', '--force-sync'] |
| if jobs: |
| cmd += ['--jobs', str(jobs)] |
| if not all_branches or self._depth is not None: |
| # Note that this option can break kernel checkouts. crbug.com/464536 |
| cmd.append('-c') |
| if self.git_cache_dir is not None: |
| cmd.append('--cache-dir=%s' % self.git_cache_dir) |
| # Do the network half of the sync; retry as necessary to get the content. |
| try: |
| if not _IsLocalPath(self.manifest_repo_url): |
| fields = {'manifest_repo': self.manifest_repo_url} |
| metrics.Counter(constants.MON_REPO_SYNC_COUNT).increment( |
| fields=fields) |
| |
| cros_build_lib.run(cmd + ['-n'], cwd=self.directory) |
| except cros_build_lib.RunCommandError: |
| if constants.SYNC_RETRIES > 0: |
| # Retry on clean up and repo sync commands, |
| # decrement max_retry for this command |
| logging.warning('cmd %s failed, clean up repository and retry sync.', |
| cmd) |
| time.sleep(DEFAULT_SLEEP_TIME) |
| retry_util.RetryCommand(self._CleanUpAndRunCommand, |
| constants.SYNC_RETRIES - 1, |
| cmd + ['-n'], |
| cwd=self.directory, |
| local_manifest=local_manifest, |
| sleep=DEFAULT_SLEEP_TIME, |
| backoff_factor=2, |
| log_retries=True) |
| else: |
| # No need to retry |
| raise |
| |
| if network_only: |
| return |
| |
| if detach: |
| cmd.append('--detach') |
| |
| # Do the local sync; note that there is a couple of corner cases where |
| # the new manifest cannot transition from the old checkout cleanly- |
| # primarily involving git submodules. Thus we intercept, and do |
| # a forced wipe, then a retry. |
| try: |
| cros_build_lib.run(cmd + ['-l'], cwd=self.directory) |
| except cros_build_lib.RunCommandError: |
| manifest = git.ManifestCheckout.Cached(self.directory) |
| targets = set(project['path'].split('/', 1)[0] |
| for project in manifest.ListCheckouts()) |
| if not targets: |
| # No directories to wipe, thus nothing we can fix. |
| raise |
| |
| cros_build_lib.sudo_run(['rm', '-rf'] + sorted(targets), |
| cwd=self.directory) |
| |
| # Retry the sync now; if it fails, let the exception propagate. |
| cros_build_lib.run(cmd + ['-l'], cwd=self.directory) |
| |
| # We do a second run to fix any new repositories created by repo to |
| # use relative object pathways. Note that cros_sdk also triggers the |
| # same cleanup- we however kick it erring on the side of caution. |
| self._EnsureMirroring(True) |
| |
| except cros_build_lib.RunCommandError as e: |
| err_msg = e.Stringify() |
| logging.error(err_msg) |
| raise SrcCheckOutException(err_msg) |
| |
| def FetchAll(self, detach=False): |
| """Run repo forall -c git fetch --all'. |
| |
| Args: |
| detach: If true, throw away all local changes, even if on tracking |
| branches. |
| """ |
| cmd = [self.repo_cmd, 'forall', '-c', 'git', 'fetch', '--all'] |
| if detach: |
| cmd.append('--detach') |
| |
| cros_build_lib.run(cmd, cwd=self.directory, check=False) |
| |
| def GetRelativePath(self, path): |
| """Returns full path including source directory of path in repo.""" |
| return os.path.join(self.directory, path) |
| |
| def ExportManifest(self, mark_revision=False, revisions=True): |
| """Export the revision locked manifest |
| |
| Args: |
| mark_revision: If True, then the sha1 of manifest.git is recorded |
| into the resultant manifest tag as a version attribute. |
| Specifically, if manifests.git is at 1234, <manifest> becomes |
| <manifest revision="1234">. |
| revisions: If True, then rewrite all branches/tags into a specific |
| sha1 revision. If False, don't. |
| |
| Returns: |
| The manifest as a string. |
| """ |
| cmd = [self.repo_cmd, 'manifest', '-o', '-'] |
| if revisions: |
| cmd += ['-r'] |
| output = cros_build_lib.run( |
| cmd, cwd=self.directory, print_cmd=False, capture_output=True, |
| encoding='utf-8', extra_env={'PAGER': 'cat'}).stdout |
| |
| if not mark_revision: |
| return output |
| modified = git.RunGit(os.path.join(self.directory, '.repo/manifests'), |
| ['rev-list', '-n1', 'HEAD']) |
| assert modified.stdout |
| return output.replace('<manifest>', '<manifest revision="%s">' % |
| modified.stdout.strip()) |
| |
| def IsManifestDifferent(self, other_manifest): |
| """Checks whether this manifest is different than another. |
| |
| May ignore certain repos as part of the diff. |
| |
| Args: |
| other_manifest: Second manifest file to compare against. |
| |
| Returns: |
| True: If the manifests are different |
| False: If the manifests are same |
| """ |
| logging.debug('Calling IsManifestDifferent against %s', other_manifest) |
| |
| ignore_list = ['="chromium/'] |
| ignore_pattern = re.compile(r'|'.join(ignore_list)) |
| manifest_revision_pattern = re.compile(r'<manifest revision="[a-f0-9]+">', |
| re.I) |
| |
| current = self.ExportManifest() |
| with open(other_manifest, 'r') as manifest2_fh: |
| for (line1, line2) in zip(current.splitlines(), manifest2_fh): |
| line1 = line1.strip() |
| line2 = line2.strip() |
| if ignore_pattern.search(line1): |
| logging.debug('%s ignored %s', line1, line2) |
| continue |
| |
| if line1 != line2: |
| logging.debug('Current and other manifest differ.') |
| logging.debug('current: "%s"', line1) |
| logging.debug('other : "%s"', line2) |
| |
| # Ignore revision differences on the manifest line. The revision of |
| # the manifest.git repo is uninteresting when determining if the |
| # current manifest describes the same sources as the other manifest. |
| if manifest_revision_pattern.search(line2): |
| logging.debug('Ignoring difference in manifest revision.') |
| continue |
| |
| return True |
| |
| return False |