Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not immediately fail if git is not available #1074

Merged
merged 5 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion copier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from pathspec import PathSpec
from plumbum import ProcessExecutionError, colors
from plumbum.cli.terminal import ask
from plumbum.cmd import git
from plumbum.machines import local
from pydantic import ConfigDict, PositiveInt
from pydantic.dataclasses import dataclass
Expand All @@ -57,6 +56,7 @@
StrSeq,
)
from .user_data import DEFAULT_DATA, AnswersMap, Question
from .vcs import get_git


@dataclass(config=ConfigDict(extra="forbid"))
Expand Down Expand Up @@ -815,6 +815,7 @@ def run_update(self) -> None:
self._print_message(self.template.message_after_update)

def _apply_update(self):
git = get_git()
subproject_top = Path(
git(
"-C",
Expand Down Expand Up @@ -931,6 +932,7 @@ def _apply_update(self):

def _git_initialize_repo(self):
"""Initialize a git repository in the current directory."""
git = get_git()
git("init", retcode=None)
git("add", ".")
git("config", "user.name", "Copier")
Expand Down
5 changes: 2 additions & 3 deletions copier/subproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@
from typing import Callable, List, Optional

import yaml
from plumbum.cmd import git
from plumbum.machines import local
from pydantic.dataclasses import dataclass

from .template import Template
from .types import AbsolutePath, AnyByStrDict, VCSTypes
from .vcs import is_in_git_repo
from .vcs import get_git, is_in_git_repo


@dataclass
Expand All @@ -42,7 +41,7 @@ def is_dirty(self) -> bool:
"""
if self.vcs == "git":
with local.cwd(self.local_abspath):
return bool(git("status", "--porcelain").strip())
return bool(get_git()("status", "--porcelain").strip())
return False

def _cleanup(self):
Expand Down
7 changes: 3 additions & 4 deletions copier/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import yaml
from funcy import lflatten
from packaging.version import Version, parse
from plumbum.cmd import git
from plumbum.machines import local
from pydantic.dataclasses import dataclass
from yamlinclude import YamlIncludeConstructor
Expand All @@ -28,7 +27,7 @@
)
from .tools import copier_version, handle_remove_readonly
from .types import AnyByStrDict, Env, OptStr, StrSeq, Union, VCSTypes
from .vcs import checkout_latest_tag, clone, get_repo
from .vcs import checkout_latest_tag, clone, get_git, get_repo

# Default list of files in the template to exclude from the rendered project
DEFAULT_EXCLUDE: Tuple[str, ...] = (
Expand Down Expand Up @@ -249,13 +248,13 @@ def commit(self) -> OptStr:
"""If the template is VCS-tracked, get its commit description."""
if self.vcs == "git":
with local.cwd(self.local_abspath):
return git("describe", "--tags", "--always").strip()
return get_git()("describe", "--tags", "--always").strip()

@cached_property
def commit_hash(self) -> OptStr:
"""If the template is VCS-tracked, get its commit full hash."""
if self.vcs == "git":
return git("-C", self.local_abspath, "rev-parse", "HEAD").strip()
return get_git()("-C", self.local_abspath, "rev-parse", "HEAD").strip()

@cached_property
def config_data(self) -> AnyByStrDict:
Expand Down
32 changes: 24 additions & 8 deletions copier/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@
from packaging import version
from packaging.version import InvalidVersion, Version
from plumbum import TF, ProcessExecutionError, colors, local
from plumbum.cmd import git

from .errors import DirtyLocalWarning, ShallowCloneWarning
from .types import OptBool, OptStr, StrOrPath


def get_git():
"""Gets `git` command, or fails if it's not available"""
return local["git"]


def get_git_version():
git = get_git()

return Version(re.findall(r"\d+\.\d+\.\d+", git("version"))[0])


GIT_PREFIX = ("git@", "git://", "git+", "https://github.com/", "https://gitlab.com/")
GIT_POSTFIX = ".git"
GIT_VERSION = Version(re.findall(r"\d+\.\d+\.\d+", git("version"))[0])
REPLACEMENTS = (
(re.compile(r"^gh:/?(.*\.git)$"), r"https://github.com/\1"),
(re.compile(r"^gh:/?(.*)$"), r"https://github.com/\1.git"),
Expand All @@ -30,15 +40,15 @@ def is_git_repo_root(path: StrOrPath) -> bool:
"""Indicate if a given path is a git repo root directory."""
try:
with local.cwd(Path(path, ".git")):
return git("rev-parse", "--is-inside-git-dir").strip() == "true"
return get_git()("rev-parse", "--is-inside-git-dir").strip() == "true"
except OSError:
return False


def is_in_git_repo(path: StrOrPath) -> bool:
"""Indicate if a given path is in a git repo directory."""
try:
git("-C", path, "rev-parse", "--show-toplevel")
get_git()("-C", path, "rev-parse", "--show-toplevel")
return True
except (OSError, ProcessExecutionError):
return False
Expand All @@ -47,7 +57,10 @@ def is_in_git_repo(path: StrOrPath) -> bool:
def is_git_shallow_repo(path: StrOrPath) -> bool:
"""Indicate if a given path is a git shallow repo directory."""
try:
return git("-C", path, "rev-parse", "--is-shallow-repository").strip() == "true"
return (
get_git()("-C", path, "rev-parse", "--is-shallow-repository").strip()
== "true"
)
except (OSError, ProcessExecutionError):
return False

Expand All @@ -58,8 +71,8 @@ def is_git_bundle(path: Path) -> bool:
path = path.resolve()
with TemporaryDirectory(prefix=f"{__name__}.is_git_bundle.") as dirname:
with local.cwd(dirname):
git("init")
return bool(git["bundle", "verify", path] & TF)
get_git()("init")
return bool(get_git()["bundle", "verify", path] & TF)


def get_repo(url: str) -> OptStr:
Expand Down Expand Up @@ -107,6 +120,7 @@ def checkout_latest_tag(local_repo: StrOrPath, use_prereleases: OptBool = False)
use_prereleases:
If `False`, skip prerelease git tags.
"""
git = get_git()
with local.cwd(local_repo):
all_tags = filter(valid_version, git("tag").split())
if not use_prereleases:
Expand Down Expand Up @@ -140,10 +154,12 @@ def clone(url: str, ref: OptStr = None) -> str:
ref:
Reference to checkout. For Git repos, defaults to `HEAD`.
"""
git = get_git()
git_version = get_git_version()
location = mkdtemp(prefix=f"{__name__}.clone.")
_clone = git["clone", "--no-checkout", url, location]
# Faster clones if possible
if GIT_VERSION >= Version("2.27"):
if git_version >= Version("2.27"):
url_match = re.match("(file://)?(.*)", url)
if url_match is not None:
file_url = url_match.groups()[-1]
Expand Down
4 changes: 2 additions & 2 deletions tests/test_vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from copier import Worker, run_copy, run_update
from copier.errors import ShallowCloneWarning
from copier.vcs import GIT_VERSION, checkout_latest_tag, clone, get_repo
from copier.vcs import checkout_latest_tag, clone, get_git_version, get_repo


def test_get_repo() -> None:
Expand Down Expand Up @@ -93,7 +93,7 @@ def test_shallow_clone(tmp_path: Path, recwarn: pytest.WarningsRecorder) -> None
git("clone", "--depth=2", "https://github.com/copier-org/autopretty.git", src_path)
assert Path(src_path, "README.md").exists()

if GIT_VERSION >= Version("2.27"):
if get_git_version() >= Version("2.27"):
with pytest.warns(ShallowCloneWarning):
local_tmp = clone(str(src_path))
else:
Expand Down