blob: 171fcbd1b1039736e7bafe5f5aeefa16bf7878ca [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 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.
"""Common functions for interacting with repo."""
from __future__ import print_function
import os
from chromite.lib import cros_build_lib
from chromite.lib import cros_logging as logging
from chromite.lib import git
class Error(Exception):
"""An error related to repo."""
class NotInRepoError(Error):
"""A repo operation was attempted outside of a repo repository."""
class Repository(object):
"""Repository represents an initialized repo repository."""
def __init__(self, root):
"""Initialize Repository.
Args:
root: Path to the root of a repo repository; must contain a .repo subdir.
Raises:
NotInRepoError: root didn't contain a .repo subdir.
"""
self.root = os.path.abspath(root)
self._repo_dir = os.path.join(self.root, '.repo')
self._ValidateRepoDir()
def _ValidateRepoDir(self):
"""Validate that the repo dir exists."""
if not os.path.isdir(self._repo_dir):
raise NotInRepoError('no .repo dir in %r' % self.root)
@classmethod
def Initialize(cls, root, manifest_url, manifest_branch=None,
manifest_name=None, mirror=False, reference=None, depth=None,
groups=None, repo_url=None):
"""Initialize and return a new Repository with `repo init`.
Args:
root: Path to the new repo root. Must not be within an existing
repo repository.
manifest_url: Manifest repository URL.
manifest_branch: Manifest branch or revision.
manifest_name: Initial manifest file from manifest repository.
mirror: If True, create a repo mirror instead of a checkout.
reference: Location of a mirror directory to reference.
depth: Create shallow git clones with the given depth.
groups: Restrict manifest projects to the given groups.
repo_url: Repo command repository URL.
Raises:
Error: root already contained a .repo subdir.
RunCommandError: `repo init` failed.
"""
existing_root = git.FindRepoCheckoutRoot(root)
if existing_root is not None:
raise Error('cannot init in existing repo %r.' % existing_root)
# TODO(lannm): Use 'chromite/bootstrap/repo'?
cmd = ['repo', 'init', '--manifest-url', manifest_url]
if manifest_branch is not None:
cmd += ['--manifest-branch', manifest_branch]
if manifest_name is not None:
cmd += ['--manifest-name', manifest_name]
if mirror:
cmd += ['--mirror']
if reference is not None:
cmd += ['--reference', reference]
if depth is not None:
cmd += ['--depth', str(depth)]
if groups is not None:
cmd += ['--groups', groups]
if repo_url is not None:
cmd += ['--repo-url', repo_url]
try:
cros_build_lib.RunCommand(cmd, cwd=root)
except cros_build_lib.RunCommandError as e:
repo_dir = os.path.join(root, '.repo')
if os.path.exists(repo_dir):
logging.warning('Removing %r due to `repo init` failures.', repo_dir)
try:
cros_build_lib.RunCommand(['rm', '-rf', repo_dir])
except cros_build_lib.RunCommandError, rm_err:
logging.warning('Error removing repo dir: %s', rm_err)
raise e
return cls(root)
@classmethod
def Find(cls, path):
"""Searches for a repo directory and returns a Repository if found.
Args:
path: The path where the search starts.
"""
repo_root = git.FindRepoCheckoutRoot(path)
if repo_root is None:
return None
return cls(repo_root)
@classmethod
def MustFind(cls, path):
"""Searches for a repo directory and returns a Repository if found.
Args:
path: The path where the search starts.
Raises:
NotInRepoError: if no Repository is found.
"""
repo = cls.Find(path)
if repo is None:
raise NotInRepoError('no repo found from %r', path)
return repo
def _Run(self, repo_cmd, cwd=None):
"""RunCommand wrapper for `repo`.
Args:
repo_cmd: List of arguments to pass to `repo`.
cwd: The path to run the command in. Defaults to Repository root.
Must be within the root.
Returns:
A CommandResult object.
Raises:
NotInRepoError: if cwd is not within the Repository root.
RunCommandError: if the command failed.
"""
# Use the checkout's copy of repo so that it doesn't have to be in PATH.
cmd = [os.path.join(self._repo_dir, 'repo', 'repo')] + repo_cmd
if cwd is None:
cwd = self.root
elif git.FindRepoCheckoutRoot(cwd) != self.root:
raise NotInRepoError('cannot run `repo` outside of Repository root '
'(cwd=%r root=%r)' % (cwd, self.root))
return cros_build_lib.RunCommand(cmd, cwd=cwd)
def Sync(self, projects=None, jobs=None, cwd=None):
"""Run `repo sync`.
Args:
projects: A list of project names to sync.
jobs: Number of projects to sync in parallel.
cwd: The path to run the command in. Defaults to Repository root.
Raises:
NotInRepoError: if cwd is not within the Repository root.
RunCommandError: if the command failed.
"""
args = _ListArg(projects)
if jobs is not None:
args += ['--jobs', str(jobs)]
self._Run(['sync'] + args, cwd=cwd)
def StartBranch(self, name, projects=None, cwd=None):
"""Run `repo start`.
Args:
name: The name of the branch to create.
projects: A list of projects to create the branch in. Defaults to
creating in all projects.
cwd: The path to run the command in. Defaults to Repository root.
Raises:
NotInRepoError: if cwd is not within the Repository root.
RunCommandError: if `repo start` failed.
"""
if projects is None:
projects = ['--all']
else:
projects = _ListArg(projects)
self._Run(['start', name] + projects, cwd=cwd)
def _ListArg(arg):
"""Return a new list from arg.
Args:
arg: If a non-basestring iterable, return a new list with its contents. If
None, return an empty list.
Raises:
TypeError: if arg is a basestring or non-iterable (except None).
"""
if isinstance(arg, basestring):
raise TypeError('string not allowed')
if arg is None:
return []
return list(arg)