Skip to content

Commit d993034

Browse files
committed
Add commit parsing and checkout functionality to GitClone
- Implemented `parse_commit` and `parse_tree` methods for parsing commit and tree objects. - Added `checkout` method to create working directory from repository tree. - Updated `__main__` script to checkout repository tree after cloning.
1 parent af78fe2 commit d993034

1 file changed

Lines changed: 73 additions & 4 deletions

File tree

app/models/clone.py

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,68 @@ def store_objects(self, objects: list[PackObject], git_dir: Path) -> dict[str, P
322322
stored[sha1] = obj
323323
return stored
324324

325+
@staticmethod
326+
def parse_commit(data: bytes) -> dict:
327+
"""Parse commit object, return dict with tree, parent(s), author, etc."""
328+
text = data.decode()
329+
result = {"parents": []}
330+
lines = text.split("\n")
331+
for line in lines:
332+
if line.startswith("tree "):
333+
result["tree"] = line[5:]
334+
elif line.startswith("parent "):
335+
result["parents"].append(line[7:])
336+
elif line.startswith("author "):
337+
result["author"] = line[7:]
338+
elif line.startswith("committer "):
339+
result["committer"] = line[10:]
340+
elif line == "":
341+
break
342+
return result
343+
344+
@staticmethod
345+
def parse_tree(data: bytes) -> list[tuple[str, str, str]]:
346+
"""Parse tree object, return list of (mode, name, sha1)."""
347+
entries = []
348+
offset = 0
349+
while offset < len(data):
350+
# Find space after mode
351+
space_idx = data.index(b" ", offset)
352+
mode = data[offset:space_idx].decode()
353+
354+
# Find null after name
355+
null_idx = data.index(b"\x00", space_idx)
356+
name = data[space_idx + 1:null_idx].decode()
357+
358+
# Read 20-byte SHA
359+
sha1 = data[null_idx + 1:null_idx + 21].hex()
360+
offset = null_idx + 21
361+
362+
entries.append((mode, name, sha1))
363+
return entries
364+
365+
def checkout(self, tree_sha: str, objects: dict[str, PackObject], dest: Path):
366+
"""Checkout tree to destination directory."""
367+
tree_obj = objects[tree_sha]
368+
entries = self.parse_tree(tree_obj.data)
369+
370+
for mode, name, sha1 in entries:
371+
obj = objects[sha1]
372+
path = dest / name
373+
374+
if obj.type == OBJ_BLOB:
375+
path.write_bytes(obj.data)
376+
# Set executable if mode is 100755
377+
if mode == "100755":
378+
path.chmod(0o755)
379+
elif obj.type == OBJ_TREE:
380+
path.mkdir(exist_ok=True)
381+
self.checkout(sha1, objects, path)
382+
325383

326384
if __name__ == "__main__":
327-
git_dir = Path("/tmp/test-clone/.git")
385+
work_dir = Path("/tmp/test-clone")
386+
git_dir = work_dir / ".git"
328387
git_dir.mkdir(parents=True, exist_ok=True)
329388

330389
with GitClone(DEFAULT_URL) as clone:
@@ -333,6 +392,16 @@ def store_objects(self, objects: list[PackObject], git_dir: Path) -> dict[str, P
333392
objects = clone.parse_pack_objects(pack_data, pack_header.num_objects)
334393
stored = clone.store_objects(objects, git_dir)
335394

336-
print(f"Stored {len(stored)} objects:")
337-
for sha1, obj in stored.items():
338-
print(f" {sha1} {TYPE_NAMES[obj.type]}")
395+
# Find HEAD commit and checkout
396+
head_sha = clone.refs["HEAD"].sha1
397+
commit_obj = stored[head_sha]
398+
commit_info = clone.parse_commit(commit_obj.data)
399+
tree_sha = commit_info["tree"]
400+
401+
clone.checkout(tree_sha, stored, work_dir)
402+
403+
print(f"Cloned to {work_dir}")
404+
print(f"Files:")
405+
for f in work_dir.iterdir():
406+
if f.name != ".git":
407+
print(f" {f.name}")

0 commit comments

Comments
 (0)