Files
endoflife-date-release-data/src/common/git.py

69 lines
2.8 KiB
Python

import logging
from hashlib import sha1
from pathlib import Path
from subprocess import run
class Git:
"""Git cli wrapper
"""
def __init__(self, url: str) -> None:
self.url: str = url
self.repo_dir: Path = Path(f"~/.cache/git/{sha1(url.encode()).hexdigest()}").expanduser()
def _run(self, cmd: str) -> list:
"""Run git command and return command result as a list of lines.
"""
try:
logging.info(f"Running 'git {cmd}' on {self.url}")
child = run(f"git {cmd}", capture_output=True, timeout=300, check=True, shell=True, cwd=self.repo_dir)
return child.stdout.decode("utf-8").strip().split("\n")
except ChildProcessError as ex:
raise RuntimeError(f"Failed to run '{cmd}': {ex}") from ex
def setup(self, bare: bool = False) -> None:
"""Creates the repository path and runs:
git init
git remote add origin $url
"""
if not Path(f"{self.repo_dir}").exists():
self.repo_dir.mkdir(parents=True, exist_ok=True)
bare = "--bare" if bare else ""
self._run(f"init {bare}")
self._run(f"remote add origin {self.url}")
# See https://stackoverflow.com/a/65746233/374236
def list_tags(self) -> list[tuple[str, str]]:
"""Fetch and return tags matching the given`pattern`"""
# See https://stackoverflow.com/a/65746233/374236
self._run("config --local extensions.partialClone true")
# Using --force to avoid error like "would clobber existing tag".
# See https://stackoverflow.com/questions/58031165/how-to-get-rid-of-would-clobber-existing-tag.
self._run("fetch --force --tags --filter=blob:none --depth=1 origin")
tags_with_date = self._run("tag --list --format='%(refname:strip=2) %(creatordate:short)'")
return [tag_with_date.split(" ") for tag_with_date in tags_with_date]
def list_branches(self, pattern: str) -> list[str]:
"""Uses ls-remote to fetch the branch names
`pattern` uses fnmatch style globbing
"""
lines = self._run(f"ls-remote origin '{pattern}'")
# this checks keeps the linter quiet, because _run returns a bool OR list
if isinstance(lines, bool):
return []
return [line.split("\t")[1][11:] for line in lines if "\t" in line]
def checkout(self, branch: str, file_list: list[str] = None) -> None:
"""Checks out a branch
If `file_list` is given, sparse-checkout is used to save bandwidth
and only download the given files
"""
if file_list:
# --skip-checks needed to avoid error when file_list contains a file
self._run(f"sparse-checkout set --skip-checks {' '.join(file_list)}")
self._run(f"fetch --filter=blob:none --depth 1 origin {branch}")
self._run(f"checkout {branch}")