@@ -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
326384if __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