mirror of https://github.com/ARMmbed/mbed-os.git
148 lines
4.5 KiB
Python
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}")
|