mbed-os/tools/python/mbed_tools/project/_internal/git_utils.py

148 lines
4.5 KiB
Python

#
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Wrappers for git operations."""
from dataclasses import dataclass
from pathlib import Path
import git
import logging
from mbed_tools.project.exceptions import VersionControlError
from mbed_tools.project._internal.progress import ProgressReporter
from typing import Optional
logger = logging.getLogger(__name__)
@dataclass
class GitReference:
"""Git reference for a remote repository.
Attributes:
repo_url: URL of the git repository.
ref: The reference commit sha, tag or branch.
"""
repo_url: str
ref: str
def clone(url: str, dst_dir: Path, ref: Optional[str] = None, depth: int = 1) -> git.Repo:
"""Clone a library repository.
Args:
url: URL of the remote to clone.
dst_dir: Destination directory for the cloned repo.
ref: An optional git commit hash, branch or tag reference to checkout
depth: Truncate history to the specified number of commits. Defaults to
1, to make a shallow clone.
Raises:
VersionControlError: Cloning the repository failed.
"""
# Gitpython doesn't propagate the git error message when a repo is already
# cloned, so we cannot depend on git to handle the "already cloned" error.
# We must handle this ourselves instead.
if dst_dir.exists() and list(dst_dir.glob("*")):
raise VersionControlError(f"{dst_dir} exists and is not an empty directory.")
clone_from_kwargs = {"url": url, "to_path": str(dst_dir), "progress": ProgressReporter(name=url), "depth": depth}
if ref:
clone_from_kwargs["branch"] = ref
try:
return git.Repo.clone_from(**clone_from_kwargs)
except git.exc.GitCommandError as err:
raise VersionControlError(f"Cloning git repository from url '{url}' failed. Error from VCS: {err}")
def checkout(repo: git.Repo, ref: str, force: bool = False) -> None:
"""Check out a specific reference in the given repository.
Args:
repo: git.Repo object where the checkout will be performed.
ref: Git commit hash, branch or tag reference, must be a valid ref defined in the repo.
Raises:
VersionControlError: Check out failed.
"""
try:
git_args = [ref] + ["--force"] if force else [ref]
repo.git.checkout(*git_args)
except git.exc.GitCommandError as err:
raise VersionControlError(f"Failed to check out revision '{ref}'. Error from VCS: {err}")
def fetch(repo: git.Repo, ref: str) -> None:
"""Fetch from the repo's origin.
Args:
repo: git.Repo object where the checkout will be performed.
ref: Git commit hash, branch or tag reference, must be a valid ref defined in the repo.
Raises:
VersionControlError: Fetch failed.
"""
try:
repo.git.fetch("origin", ref)
except git.exc.GitCommandError as err:
raise VersionControlError(f"Failed to fetch. Error from VCS: {err}")
def init(path: Path) -> git.Repo:
"""Initialise a git repository at the given path.
Args:
path: Path where the repo will be initialised.
Returns:
Initialised git.Repo object.
Raises:
VersionControlError: initalising the repository failed.
"""
try:
return git.Repo.init(str(path))
except git.exc.GitCommandError as err:
raise VersionControlError(f"Failed to initialise git repository at path '{path}'. Error from VCS: {err}")
def get_repo(path: Path) -> git.Repo:
"""Get a git.Repo object from an existing repository path.
Args:
path: Path to the git repository.
Returns:
git.Repo object.
Raises:
VersionControlError: No valid git repository at this path.
"""
try:
return git.Repo(str(path))
except git.exc.InvalidGitRepositoryError:
raise VersionControlError(
"Could not find a valid git repository at this path. Please perform a `git init` command."
)
def get_default_branch(repo: git.Repo) -> str:
"""Get a default branch from an existing git.Repo.
Args:
repo: git.Repo object
Returns:
The default branch name as a string.
Raises:
VersionControlError: Could not find the default branch name.
"""
try:
return str(repo.git.symbolic_ref("refs/remotes/origin/HEAD").rsplit("/", maxsplit=1)[-1])
except git.exc.GitCommandError as err:
raise VersionControlError(f"Could not resolve default repository branch name. Error from VCS: {err}")