Skip to content

Commit fdc1567

Browse files
committed
Replace GitPython implementation with pygit2 one
Signed-off-by: Tobias Wolf <[email protected]>
1 parent a4c3480 commit fdc1567

File tree

11 files changed

+627
-563
lines changed

11 files changed

+627
-563
lines changed

.github/actions/features_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ outputs:
1111
runs:
1212
using: composite
1313
steps:
14-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.9.0
14+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.0
1515
- id: result
1616
shell: bash
1717
run: |

.github/actions/flavors_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ outputs:
1313
runs:
1414
using: composite
1515
steps:
16-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.9.0
16+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.0
1717
- id: matrix
1818
shell: bash
1919
run: |

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Installs the given GardenLinux Python library
44
inputs:
55
version:
66
description: GardenLinux Python library version
7-
default: "0.9.0"
7+
default: "0.10.0"
88
python_version:
99
description: Python version to setup
1010
default: "3.13"

poetry.lock

Lines changed: 419 additions & 437 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gardenlinux"
3-
version = "0.9.0"
3+
version = "0.10.0"
44
description = "Contains tools to work with the features directory of gardenlinux, for example deducting dependencies from feature sets or validating cnames"
55
authors = ["Garden Linux Maintainers <[email protected]>"]
66
license = "Apache-2.0"
@@ -10,23 +10,23 @@ packages = [{include = "gardenlinux", from="src"}]
1010
[tool.poetry.dependencies]
1111
python = "^3.13"
1212
apt-repo = "^0.5"
13-
boto3 = "^1.40.10"
13+
boto3 = "^1.40.30"
1414
click = "^8.2.1"
1515
cryptography = "^45.0.6"
16-
gitpython = "^3.1.45"
17-
jsonschema = "^4.25.0"
16+
jsonschema = "^4.25.1"
1817
networkx = "^3.5"
19-
oras = "^0.2.37"
18+
oras = "^0.2.38"
19+
pygit2 = "^1.18.2"
2020
pygments = "^2.19.2"
2121
PyYAML = "^6.0.2"
2222

2323
[tool.poetry.group.dev.dependencies]
2424
bandit = "^1.8.6"
2525
black = "^25.1.0"
26-
moto = "^5.1.10"
26+
moto = "^5.1.12"
2727
python-dotenv = "^1.1.1"
2828
pytest = "^8.4.1"
29-
pytest-cov = "^6.2.1"
29+
pytest-cov = "^6.3.0"
3030

3131
[tool.poetry.group.docs.dependencies]
3232
sphinx-rtd-theme = "^3.0.2"

src/gardenlinux/flavors/__main__.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .parser import Parser
1919
from ..constants import GL_REPOSITORY_URL
20-
from ..git import Git
20+
from ..git import Repository
2121

2222

2323
def _get_flavors_file_data(flavors_file):
@@ -140,21 +140,17 @@ def main():
140140
args = parse_args()
141141

142142
try:
143-
flavors_data = _get_flavors_file_data(Path(Git().root, "flavors.yaml"))
143+
flavors_data = _get_flavors_file_data(Path(Repository().root, "flavors.yaml"))
144144
except (GitError, RuntimeError):
145145
with TemporaryDirectory() as git_directory:
146-
repo = Repo.clone_from(
147-
GL_REPOSITORY_URL, git_directory, no_origin=True, sparse=True
146+
repo = Repository.checkout_repo_sparse(
147+
git_directory,
148+
["flavors.yaml"],
149+
repo_url=GL_REPOSITORY_URL,
150+
commit=args.commit,
148151
)
149152

150-
ref = repo.heads.main
151-
152-
if args.commit is not None:
153-
ref = ref.set_commit(args.commit)
154-
155-
flavors_data = _get_flavors_file_data(
156-
Path(repo.working_dir, "flavors.yaml")
157-
)
153+
flavors_data = _get_flavors_file_data(Path(repo.root, "flavors.yaml"))
158154

159155
combinations = Parser(flavors_data).filter(
160156
include_only_patterns=args.include_only,

src/gardenlinux/git/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
Git module
55
"""
66

7-
from .git import Git
7+
from .repository import Repository
88

9-
__all__ = ["Git"]
9+
__all__ = ["Repository"]

src/gardenlinux/git/git.py

Lines changed: 0 additions & 84 deletions
This file was deleted.

src/gardenlinux/git/repository.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from os import PathLike
4+
from pathlib import Path
5+
import sys
6+
7+
from pygit2 import init_repository, Oid
8+
from pygit2 import Repository as _Repository
9+
10+
from ..constants import GL_REPOSITORY_URL
11+
from ..logger import LoggerSetup
12+
13+
14+
class Repository(_Repository):
15+
"""
16+
Repository operations handler based on the given Git directory.
17+
18+
:author: Garden Linux Maintainers
19+
:copyright: Copyright 2024 SAP SE
20+
:package: gardenlinux
21+
:subpackage: git
22+
:since: 0.10.0
23+
:license: https://www.apache.org/licenses/LICENSE-2.0
24+
Apache License, Version 2.0
25+
"""
26+
27+
def __init__(self, git_directory=".", logger=None, **kwargs):
28+
"""
29+
Constructor __init__(Repository)
30+
31+
:param git_directory: Git directory
32+
:param logger: Logger instance
33+
34+
:since: 0.10.0
35+
"""
36+
37+
if logger is None or not logger.hasHandlers():
38+
logger = LoggerSetup.get_logger("gardenlinux.git")
39+
40+
if not isinstance(git_directory, PathLike):
41+
git_directory = Path(git_directory)
42+
43+
if not git_directory.exists():
44+
raise RuntimeError(f"Git directory given is invalid: {git_directory}")
45+
46+
_Repository.__init__(self, git_directory, **kwargs)
47+
48+
self._git_directory = git_directory
49+
self._logger = logger
50+
51+
@property
52+
def commit_id(self):
53+
"""
54+
Returns the commit ID for Git `HEAD`.
55+
56+
:return: (str) Git commit ID
57+
:since: 0.10.0
58+
"""
59+
60+
return str(self.root_repo.head.target)
61+
62+
@property
63+
def root(self):
64+
"""
65+
Returns the root directory of the current Git repository.
66+
67+
:return: (object) Git root directory
68+
:since: 0.10.0
69+
"""
70+
71+
root_dir = self.workdir
72+
73+
if self.is_bare:
74+
root_dir = self.path
75+
76+
usual_git_dir = Path(root_dir, ".git")
77+
78+
# Possible submodule Git repository. Validate repository containing `.git` directory.
79+
if self.path != str(usual_git_dir):
80+
try:
81+
repo = Repository(usual_git_dir, self._logger)
82+
83+
if self.path != repo.path:
84+
root_dir = repo.root
85+
except Exception as exc:
86+
self._logger.warning(f"Failed to inspect Git directory: {exc}")
87+
88+
self._logger.debug(f"Git root directory: {root_dir}")
89+
return Path(root_dir)
90+
91+
@property
92+
def root_repo(self):
93+
"""
94+
Returns the root Git `Repository` instance.
95+
96+
:return: (object) Git root `Repository` instance
97+
:since: 0.10.0
98+
"""
99+
100+
repo = self
101+
102+
if self._git_directory != self.root:
103+
repo = Repository(self.root, self._logger)
104+
105+
return repo
106+
107+
@staticmethod
108+
def checkout_repo(
109+
git_directory,
110+
repo_url=GL_REPOSITORY_URL,
111+
branch="main",
112+
commit=None,
113+
pathspecs=None,
114+
logger=None,
115+
**kwargs,
116+
):
117+
"""
118+
Returns the root Git `Repo` instance.
119+
120+
:return: (object) Git `Repo` instance
121+
:since: 0.10.0
122+
"""
123+
124+
if not isinstance(git_directory, PathLike):
125+
git_directory = Path(git_directory)
126+
127+
if not git_directory.is_dir() or git_directory.match("/*"):
128+
raise RuntimeError(
129+
"Git directory should not exist or be empty before checkout"
130+
)
131+
132+
repo = init_repository(git_directory, origin_url=repo_url)
133+
repo.remotes["origin"].fetch()
134+
135+
if commit is None:
136+
refish = f"origin/{branch}"
137+
resolved = repo.resolve_refish(refish)
138+
commit = str(resolved[0].id)
139+
140+
checkout_tree_kwargs = kwargs
141+
if pathspecs is not None:
142+
checkout_tree_kwargs["paths"] = pathspecs
143+
144+
repo.checkout_tree(repo[Oid(hex=commit)], **checkout_tree_kwargs)
145+
146+
return Repository(git_directory, logger)
147+
148+
@staticmethod
149+
def checkout_repo_sparse(
150+
git_directory,
151+
pathspecs=[],
152+
repo_url=GL_REPOSITORY_URL,
153+
branch="main",
154+
commit=None,
155+
logger=None,
156+
**kwargs,
157+
):
158+
"""
159+
Sparse checkout given Git repository and return the `Repository` instance.
160+
161+
:return: (object) Git `Repository` instance
162+
:since: 0.10.0
163+
"""
164+
165+
# @TODO: pygit2 does not support sparse checkouts. We use the `paths` parameter at the moment.
166+
return Repository.checkout_repo(
167+
git_directory,
168+
repo_url=repo_url,
169+
branch=branch,
170+
commit=commit,
171+
pathspecs=pathspecs,
172+
logger=logger,
173+
)

tests/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
from gardenlinux.git import Git
3+
from gardenlinux.git import Repository
44

55
TEST_DATA_DIR = "test-data"
66
GL_ROOT_DIR = f"{TEST_DATA_DIR}/gardenlinux"
@@ -17,6 +17,6 @@
1717
TEST_ARCHITECTURES = ["arm64", "amd64"]
1818
TEST_FEATURE_STRINGS_SHORT = ["gardener_prod"]
1919
TEST_FEATURE_SET = "_slim,base,container"
20-
TEST_COMMIT = Git(GL_ROOT_DIR).commit_id[:8]
20+
TEST_COMMIT = Repository(GL_ROOT_DIR).commit_id[:8]
2121
TEST_VERSION = "1000.0"
2222
TEST_VERSION_STABLE = "1000"

0 commit comments

Comments
 (0)