Skip to content

Commit b3545d8

Browse files
committed
Add write-tree and ls-tree scaffolding to Git class
- Implemented `write_tree` method in `Git` class to recursively hash directories and files. - Added scaffolding for `ls-tree` command with placeholder logic. - Updated CLI parser to support `ls-tree` with `--name-only` flag. - Removed `create_blob` from `utils` in favor of `Git.create_blob`. - Refactored tests to use new `Git` class methods and added initial tests for `write_tree`. - Updated `.gitignore` to exclude PyCharm `.idea` directory.
1 parent ff29be8 commit b3545d8

5 files changed

Lines changed: 95 additions & 36 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,6 @@ dmypy.json
151151

152152
# Cython debug symbols
153153
cython_debug/
154+
155+
# Pycharm
156+
.idea

app/models/git.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44

55
__all__ = ["Git"]
66

7-
from app.utils import create_blob
8-
97
NULL_BYTE = b"\x00"
108

119

1210
class Git:
13-
@classmethod
14-
def init_repo(cls):
11+
ignore_patterns = {".git", "__pycache__", ".pytest_cache", ".venv", "HEAD"}
12+
13+
def __init__(self):
14+
self.git_folder = pathlib.Path(".git")
15+
self.objects_folder = self.git_folder / "objects"
16+
17+
@staticmethod
18+
def init_repo():
1519
path = pathlib.Path(".")
1620
dirs = [".git", ".git/objects", ".git/refs"]
1721
for _dir in dirs:
@@ -21,22 +25,62 @@ def init_repo(cls):
2125
f.write("ref: refs/heads/main\n")
2226
return
2327

24-
@classmethod
25-
def cat_file(cls, hash_: str, *, pretty_print: bool = False):
28+
def cat_file(self, hash_: str, *, pretty_print: bool = False):
2629
from app.models import Blob
2730

28-
path = pathlib.Path(".git", "objects", hash_[:2], hash_[2:])
31+
path = self.objects_folder / hash_[:2] / hash_[2:]
2932
with path.open("rb") as f:
3033
data = zlib.decompress(f.read())
3134
header, _, body = data.partition(NULL_BYTE)
3235
if pretty_print:
3336
sys.stdout.write(body.decode())
3437
return Blob(header=header, body=body)
3538

36-
@classmethod
37-
def hash_object(cls, path: pathlib.Path, *, write: bool = False, pretty_print: bool = True):
39+
def create_blob(self, content: str, *, write: bool = True) -> str:
40+
# Format the blob object
41+
blob = f"blob {len(content)}\0{content}"
42+
# Convert to bytes
43+
blob_bytes = blob.encode()
44+
# Compress using zlib
45+
compressed = zlib.compress(blob_bytes)
46+
47+
# Calculate SHA-1 hash
48+
import hashlib
49+
50+
hash_object = hashlib.sha1(blob_bytes)
51+
hash_value = hash_object.hexdigest()
52+
53+
if write:
54+
path = self.objects_folder / hash_value[:2]
55+
path.mkdir(exist_ok=True)
56+
57+
with (path / hash_value[2:]).open("wb") as f:
58+
f.write(compressed)
59+
return hash_value
60+
61+
def hash_object(
62+
self, path: pathlib.Path, *, write: bool = False, pretty_print: bool = True
63+
):
3864
with path.open("r") as f:
39-
hash_value = create_blob(f.read(), write=write)
65+
hash_value = self.create_blob(f.read(), write=write)
4066
if pretty_print:
4167
sys.stdout.write(hash_value)
4268
return hash_value
69+
70+
@classmethod
71+
def ls_tree(cls, hash_value: str, *, name_only: bool = False):
72+
raise NotImplementedError
73+
74+
def write_tree(self, working_directory: pathlib.Path = None):
75+
working_directory = working_directory or pathlib.Path(".")
76+
hash_values = []
77+
78+
for path in working_directory.iterdir():
79+
if path.name in self.ignore_patterns:
80+
continue
81+
if path.is_dir():
82+
hash_values.extend(self.write_tree(path))
83+
if path.is_file():
84+
hash_values.append(self.hash_object(path))
85+
86+
return hash_values

app/utils.py

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,10 @@ def get_parser():
2323
hash_object_parser = subparsers.add_parser("hash-object")
2424
hash_object_parser.add_argument("path", type=pathlib.Path)
2525
hash_object_parser.add_argument("-w", "--write", action="store_true")
26-
return parser
27-
28-
29-
def create_blob(content: str, *, write: bool = True) -> str:
30-
# Format the blob object
31-
blob = f"blob {len(content)}\0{content}"
32-
# Convert to bytes
33-
blob_bytes = blob.encode()
34-
# Compress using zlib
35-
compressed = zlib.compress(blob_bytes)
3626

37-
# Calculate SHA-1 hash
38-
import hashlib
27+
# ls-tree
28+
ls_tree_parser = subparsers.add_parser("ls-tree")
29+
ls_tree_parser.add_argument("--name-only", action="store_true")
30+
ls_tree_parser.add_argument("hash_value")
3931

40-
hash_object = hashlib.sha1(blob_bytes)
41-
hash_value = hash_object.hexdigest()
42-
43-
if write:
44-
# Create directory structure
45-
path = pathlib.Path(".git/objects", hash_value[:2])
46-
path.mkdir(exist_ok=True)
47-
48-
with (path / hash_value[2:]).open("wb") as f:
49-
f.write(compressed)
50-
return hash_value
32+
return parser

tests/test_git.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import pytest
55

66
from app.main import Git
7-
from app.utils import create_blob
87

98

109
@pytest.fixture
@@ -25,7 +24,7 @@ def test_init_repo(self, change_to_tmp_dir):
2524
def test_cat_file(self, change_to_tmp_dir):
2625
git = Git()
2726
git.init_repo()
28-
hash_value = create_blob("some content")
27+
hash_value = git.create_blob("some content")
2928
blob = git.cat_file(hash_value)
3029
assert blob.header == f"blob {len(blob.body)}".encode()
3130
assert blob.body == b"some content"
@@ -35,7 +34,9 @@ def test_cat_file(self, change_to_tmp_dir):
3534
"content, expected_hash_value",
3635
[("hello world\n", "3b18e512dba79e4c8300dd08aeb37f8e728b8dad")],
3736
)
38-
def test_hash_object(self, change_to_tmp_dir, content, expected_hash_value, write, capsys):
37+
def test_hash_object(
38+
self, change_to_tmp_dir, content, expected_hash_value, write, capsys
39+
):
3940
git = Git()
4041
git.init_repo()
4142
tmp_file = change_to_tmp_dir / "file.txt"
@@ -48,3 +49,20 @@ def test_hash_object(self, change_to_tmp_dir, content, expected_hash_value, writ
4849
)
4950
assert expected_path.exists() == write
5051
assert capsys.readouterr().out == hash_value
52+
53+
def test_write_tree(self, change_to_tmp_dir):
54+
git = Git()
55+
git.init_repo()
56+
#
57+
parent_dir = change_to_tmp_dir / "parent_dir"
58+
parent_dir.mkdir()
59+
file1 = parent_dir / "file1.txt"
60+
file1.write_text("hello")
61+
#
62+
child_dir = parent_dir / "child_dir"
63+
child_dir.mkdir()
64+
file2 = child_dir / "file2.txt"
65+
file2.write_text("World")
66+
#
67+
hash_values = git.write_tree()
68+
assert 0, "Finish writing tree"

tests/test_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@
3030
command="hash-object", path=pathlib.Path("some_file.txt"), write=True
3131
),
3232
),
33+
(
34+
["ls-tree", "some_hash"],
35+
Namespace(command="ls-tree", name_only=False, hash_value="some_hash"),
36+
),
37+
(
38+
[
39+
"ls-tree",
40+
"--name-only",
41+
"some_hash",
42+
],
43+
Namespace(command="ls-tree", name_only=True, hash_value="some_hash"),
44+
),
3345
],
3446
)
3547
def test_parser(params, expected):

0 commit comments

Comments
 (0)