Skip to content

Commit e641f11

Browse files
authored
Fix relative paths in dstack apply --repo (#2733)
1 parent 8988c9d commit e641f11

File tree

2 files changed

+82
-3
lines changed

2 files changed

+82
-3
lines changed

src/dstack/_internal/core/models/repos/local.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ def write_code_file(self, fp: BinaryIO) -> str:
8484
.add_custom_ignore_filename(".dstackignore")
8585
.build()
8686
):
87-
path = entry.path().relative_to(repo_path.absolute())
88-
if path != Path("."):
89-
t.add(path, recursive=False)
87+
entry_path_within_repo = entry.path().relative_to(repo_path)
88+
if entry_path_within_repo != Path("."):
89+
t.add(entry.path(), arcname=entry_path_within_repo, recursive=False)
9090
logger.debug("Code file size: %s", sizeof_fmt(fp.tell()))
9191
return get_sha256(fp)
9292

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import io
2+
import tarfile
3+
from pathlib import Path
4+
5+
import pytest
6+
7+
from dstack._internal.core.models.repos.local import LocalRepo
8+
9+
10+
class TestRepoPathType:
11+
REPO_DIR_NAME = "repo"
12+
13+
@pytest.fixture
14+
def repo_parent_dir(self, tmp_path: Path) -> Path:
15+
repo_dir = tmp_path / self.REPO_DIR_NAME
16+
repo_dir.mkdir()
17+
(repo_dir / "file.txt").touch()
18+
(repo_dir / "inner").mkdir()
19+
(repo_dir / "inner" / "file.txt").mkdir()
20+
return tmp_path
21+
22+
@staticmethod
23+
def check(repo: LocalRepo) -> None:
24+
fp = io.BytesIO()
25+
repo.write_code_file(fp)
26+
fp.seek(0)
27+
with tarfile.open(fileobj=fp, mode="r") as tar:
28+
names = tar.getnames()
29+
assert "file.txt" in names
30+
assert "inner/file.txt" in names
31+
32+
def test_absolute(self, repo_parent_dir: Path):
33+
repo = LocalRepo.from_dir(repo_parent_dir.resolve() / self.REPO_DIR_NAME)
34+
self.check(repo)
35+
36+
def test_relative(self, repo_parent_dir: Path, monkeypatch):
37+
monkeypatch.chdir(repo_parent_dir)
38+
repo = LocalRepo.from_dir(self.REPO_DIR_NAME)
39+
self.check(repo)
40+
41+
def test_cwd(self, repo_parent_dir: Path, monkeypatch):
42+
monkeypatch.chdir(repo_parent_dir / self.REPO_DIR_NAME)
43+
repo = LocalRepo.from_dir(".")
44+
self.check(repo)
45+
46+
def test_with_parent_reference(self, repo_parent_dir: Path, monkeypatch):
47+
cwd = repo_parent_dir / "test"
48+
cwd.mkdir()
49+
monkeypatch.chdir(cwd)
50+
repo = LocalRepo.from_dir(Path("..") / self.REPO_DIR_NAME)
51+
self.check(repo)
52+
53+
54+
def test_ignore_rules(tmp_path: Path):
55+
(tmp_path / "file1.txt").touch()
56+
(tmp_path / "file2.py").touch()
57+
(tmp_path / ".hidden").touch()
58+
(tmp_path / ".dstackignore").write_text("file2.py\n")
59+
(tmp_path / "inner").mkdir()
60+
(tmp_path / "inner" / "file3.txt").touch()
61+
(tmp_path / "inner" / "file4.py").touch()
62+
(tmp_path / "inner" / ".gitignore").write_text("*.txt")
63+
(tmp_path / ".git").mkdir()
64+
(tmp_path / ".git" / "config").touch()
65+
66+
repo = LocalRepo.from_dir(tmp_path)
67+
fp = io.BytesIO()
68+
repo.write_code_file(fp)
69+
fp.seek(0)
70+
71+
with tarfile.open(fileobj=fp, mode="r") as tar:
72+
names = tar.getnames()
73+
assert "file1.txt" in names
74+
assert "file2.py" not in names # ignored by .dstackignore
75+
assert ".hidden" in names
76+
assert ".dstackignore" in names
77+
assert "inner/file3.txt" not in names # ignored by inner/.gitignore
78+
assert "inner/file4.py" in names
79+
assert ".git/config" not in names # .git always ignored

0 commit comments

Comments
 (0)