fluidattacks_core_git 12.0.0__tar.gz → 12.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/.gitignore +2 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/PKG-INFO +1 -1
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/download_repo.py +27 -7
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/pyproject.toml +2 -1
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/tests/test_git.py +76 -1
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/uv.lock +16 -1
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/.envrc +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/build/default.nix +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/build/filter.nix +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/flake.lock +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/flake.nix +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/__init__.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/classes.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/clone.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/codecommit_utils.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/constants.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/delete_files.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/download_file.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/https_utils.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/py.typed +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/remote.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/show_file.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/ssh_utils.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/upload_repo.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/utils.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/warp.py +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/import-linter.cfg +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/mypy.ini +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/ruff.toml +0 -0
- {fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/tests/__init__.py +0 -0
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/download_repo.py
RENAMED
|
@@ -46,13 +46,7 @@ def remove_symlinks_in_directory(directory: str) -> None:
|
|
|
46
46
|
Path(file_path).unlink(missing_ok=True)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def
|
|
50
|
-
try:
|
|
51
|
-
Path.cwd()
|
|
52
|
-
except OSError:
|
|
53
|
-
LOGGER.exception("Failed to get the working directory: %s", repo_path)
|
|
54
|
-
os.chdir(repo_path)
|
|
55
|
-
|
|
49
|
+
def _mark_safe_directory(repo_path: str) -> None:
|
|
56
50
|
try:
|
|
57
51
|
Git().execute(
|
|
58
52
|
[
|
|
@@ -67,6 +61,16 @@ def _reset_repo_sync(repo_path: str) -> bool:
|
|
|
67
61
|
except GitError:
|
|
68
62
|
LOGGER.exception("Failed to add safe directory %s", repo_path)
|
|
69
63
|
|
|
64
|
+
|
|
65
|
+
def _reset_repo_sync(repo_path: str) -> bool:
|
|
66
|
+
try:
|
|
67
|
+
Path.cwd()
|
|
68
|
+
except OSError:
|
|
69
|
+
LOGGER.exception("Failed to get the working directory: %s", repo_path)
|
|
70
|
+
os.chdir(repo_path)
|
|
71
|
+
|
|
72
|
+
_mark_safe_directory(repo_path)
|
|
73
|
+
|
|
70
74
|
try:
|
|
71
75
|
repo = Repo(repo_path)
|
|
72
76
|
repo.git.reset("--hard", "HEAD")
|
|
@@ -114,7 +118,16 @@ async def download_repo_from_s3(
|
|
|
114
118
|
git_ignore: list[str] | None = None,
|
|
115
119
|
*,
|
|
116
120
|
download_buffer_size: int = DEFAULT_DOWNLOAD_BUFFER_SIZE,
|
|
121
|
+
checkout: bool = True,
|
|
117
122
|
) -> bool:
|
|
123
|
+
"""Download the repository archived in s3 at `download_url`.
|
|
124
|
+
|
|
125
|
+
The archive holds only `.git`, so the working tree is restored with a
|
|
126
|
+
`git reset --hard`. Pass `checkout=False` to keep just the history: peak
|
|
127
|
+
disk stays at the size of the archive plus `.git`, which is what a
|
|
128
|
+
consumer that walks commits needs. `git_ignore` prunes working-tree files
|
|
129
|
+
and has no effect on a history-only download.
|
|
130
|
+
"""
|
|
118
131
|
await anyio.Path(destination_path.parent).mkdir(parents=True, exist_ok=True)
|
|
119
132
|
with tempfile.TemporaryDirectory(prefix="fluidattacks_", ignore_cleanup_errors=True) as tmpdir:
|
|
120
133
|
tmp_path = Path(tmpdir)
|
|
@@ -139,6 +152,13 @@ async def download_repo_from_s3(
|
|
|
139
152
|
)
|
|
140
153
|
return False
|
|
141
154
|
|
|
155
|
+
if not checkout:
|
|
156
|
+
# Nothing was extracted but `.git`, so there is no working tree to
|
|
157
|
+
# reset or prune. Callers still read the repository through git, which
|
|
158
|
+
# refuses a directory it considers foreign.
|
|
159
|
+
await anyio.to_thread.run_sync(_mark_safe_directory, str(destination_path))
|
|
160
|
+
return True
|
|
161
|
+
|
|
142
162
|
if not await reset_repo(str(destination_path)):
|
|
143
163
|
shutil.rmtree(destination_path, ignore_errors=True)
|
|
144
164
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fluidattacks_core_git"
|
|
3
|
-
version = "12.
|
|
3
|
+
version = "12.1.0"
|
|
4
4
|
description = "Fluid Attacks Core Git Library"
|
|
5
5
|
authors = [{ name = "Development", email = "development@fluidattacks.com" }]
|
|
6
6
|
license = { text = "MPL-2.0" }
|
|
@@ -32,6 +32,7 @@ dev = [
|
|
|
32
32
|
"deptry>=0.23.1",
|
|
33
33
|
"import-linter==2.11",
|
|
34
34
|
"mypy==1.19.1",
|
|
35
|
+
"pytest-asyncio>=1.1.0",
|
|
35
36
|
"pytest-cov>=7.0.0",
|
|
36
37
|
"pytest==9.0.3",
|
|
37
38
|
"ruff==0.15.0",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import tarfile
|
|
1
3
|
import tempfile
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
|
|
@@ -6,7 +8,7 @@ from git.repo import Repo
|
|
|
6
8
|
from pathspec import PathSpec
|
|
7
9
|
from pathspec import util as pathspec_util
|
|
8
10
|
|
|
9
|
-
from fluidattacks_core.git import rebase
|
|
11
|
+
from fluidattacks_core.git import download_repo, download_repo_from_s3, rebase
|
|
10
12
|
from fluidattacks_core.git.delete_files import delete_out_of_scope_files
|
|
11
13
|
from fluidattacks_core.git.download_repo import remove_symlinks_in_directory
|
|
12
14
|
|
|
@@ -171,3 +173,76 @@ def test_rebase_preserves_ascii_renamed_path() -> None:
|
|
|
171
173
|
assert result.path == renamed
|
|
172
174
|
assert result.line == 1
|
|
173
175
|
assert result.rev == rev_b
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _archive_git_dir(repo_path: Path, archive_path: Path, name: str) -> None:
|
|
179
|
+
# Mimics `_create_tar_archive`: the archive uploaded to s3 carries `.git`
|
|
180
|
+
# and no tracked file.
|
|
181
|
+
with tarfile.open(archive_path, "w:gz") as tar_handler:
|
|
182
|
+
tar_handler.add(str(repo_path / ".git"), arcname=f"{name}/.git", recursive=True)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _committed_repo(path: Path, tracked: str) -> str:
|
|
186
|
+
repo = _init_repo(path)
|
|
187
|
+
(path / tracked).write_text("content\n", encoding="utf-8")
|
|
188
|
+
repo.index.add([tracked])
|
|
189
|
+
revision = repo.index.commit("add tracked file").hexsha
|
|
190
|
+
repo.close()
|
|
191
|
+
return revision
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@pytest.fixture
|
|
195
|
+
def s3_archive(
|
|
196
|
+
tmp_path: Path,
|
|
197
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
198
|
+
) -> str:
|
|
199
|
+
# `git config --global` writes to $HOME, so keep it inside the test.
|
|
200
|
+
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
201
|
+
(tmp_path / "home").mkdir()
|
|
202
|
+
|
|
203
|
+
source = tmp_path / "source"
|
|
204
|
+
source.mkdir()
|
|
205
|
+
revision = _committed_repo(source, "tracked.txt")
|
|
206
|
+
archive = tmp_path / "repo.tar.gz"
|
|
207
|
+
_archive_git_dir(source, archive, "source")
|
|
208
|
+
shutil.rmtree(source)
|
|
209
|
+
|
|
210
|
+
async def _fake_download(
|
|
211
|
+
*,
|
|
212
|
+
url: str, # noqa: ARG001
|
|
213
|
+
destination_path: str,
|
|
214
|
+
download_buffer_size: int, # noqa: ARG001
|
|
215
|
+
) -> bool:
|
|
216
|
+
shutil.copyfile(archive, destination_path)
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
monkeypatch.setattr(download_repo, "download_file", _fake_download)
|
|
220
|
+
return revision
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.asyncio
|
|
224
|
+
@pytest.mark.usefixtures("s3_archive")
|
|
225
|
+
async def test_download_repo_from_s3_restores_working_tree(tmp_path: Path) -> None:
|
|
226
|
+
destination = tmp_path / "groups" / "group" / "root"
|
|
227
|
+
|
|
228
|
+
assert await download_repo_from_s3("https://s3/repo.tar.gz", destination)
|
|
229
|
+
|
|
230
|
+
assert (destination / "tracked.txt").is_file()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@pytest.mark.asyncio
|
|
234
|
+
async def test_download_repo_from_s3_without_checkout_keeps_only_history(
|
|
235
|
+
tmp_path: Path,
|
|
236
|
+
s3_archive: str,
|
|
237
|
+
) -> None:
|
|
238
|
+
destination = tmp_path / "groups" / "group" / "root"
|
|
239
|
+
|
|
240
|
+
assert await download_repo_from_s3("https://s3/repo.tar.gz", destination, checkout=False)
|
|
241
|
+
|
|
242
|
+
# The point of the flag: the history is complete and readable, and not one
|
|
243
|
+
# byte of the working tree was written.
|
|
244
|
+
repo = Repo(destination)
|
|
245
|
+
assert repo.head.commit.hexsha == s3_archive
|
|
246
|
+
repo.close()
|
|
247
|
+
assert not (destination / "tracked.txt").exists()
|
|
248
|
+
assert [entry.name for entry in destination.iterdir()] == [".git"]
|
|
@@ -432,7 +432,7 @@ wheels = [
|
|
|
432
432
|
|
|
433
433
|
[[package]]
|
|
434
434
|
name = "fluidattacks-core-git"
|
|
435
|
-
version = "12.
|
|
435
|
+
version = "12.1.0"
|
|
436
436
|
source = { editable = "." }
|
|
437
437
|
dependencies = [
|
|
438
438
|
{ name = "aiofiles" },
|
|
@@ -454,6 +454,7 @@ dev = [
|
|
|
454
454
|
{ name = "import-linter" },
|
|
455
455
|
{ name = "mypy" },
|
|
456
456
|
{ name = "pytest" },
|
|
457
|
+
{ name = "pytest-asyncio" },
|
|
457
458
|
{ name = "pytest-cov" },
|
|
458
459
|
{ name = "ruff" },
|
|
459
460
|
{ name = "types-aiofiles" },
|
|
@@ -480,6 +481,7 @@ dev = [
|
|
|
480
481
|
{ name = "import-linter", specifier = "==2.11" },
|
|
481
482
|
{ name = "mypy", specifier = "==1.19.1" },
|
|
482
483
|
{ name = "pytest", specifier = "==9.0.3" },
|
|
484
|
+
{ name = "pytest-asyncio", specifier = ">=1.1.0" },
|
|
483
485
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
|
484
486
|
{ name = "ruff", specifier = "==0.15.0" },
|
|
485
487
|
{ name = "types-aiofiles", specifier = "==24.1.0.20240626" },
|
|
@@ -1331,6 +1333,19 @@ wheels = [
|
|
|
1331
1333
|
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
|
1332
1334
|
]
|
|
1333
1335
|
|
|
1336
|
+
[[package]]
|
|
1337
|
+
name = "pytest-asyncio"
|
|
1338
|
+
version = "1.4.0"
|
|
1339
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1340
|
+
dependencies = [
|
|
1341
|
+
{ name = "pytest" },
|
|
1342
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
1343
|
+
]
|
|
1344
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
|
|
1345
|
+
wheels = [
|
|
1346
|
+
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
|
|
1347
|
+
]
|
|
1348
|
+
|
|
1334
1349
|
[[package]]
|
|
1335
1350
|
name = "pytest-cov"
|
|
1336
1351
|
version = "7.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/__init__.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/classes.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/clone.py
RENAMED
|
File without changes
|
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/constants.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/delete_files.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/download_file.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/https_utils.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/py.typed
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/remote.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/show_file.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/ssh_utils.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/upload_repo.py
RENAMED
|
File without changes
|
{fluidattacks_core_git-12.0.0 → fluidattacks_core_git-12.1.0}/fluidattacks_core/git/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|