diff --git a/libwyag.py b/libwyag.py index de56aa3..7dde8f8 100644 --- a/libwyag.py +++ b/libwyag.py @@ -581,13 +581,14 @@ def ls_tree(repo, ref, recursive=None, prefix=""): type = item.mode[0:2] match type: - case b'04': type = "tree" + case b'04': raise Exception("Trees should point to subcommits, not other trees") case b'10': type = "blob" case b'12': type = "blob" # a symlink case b'16': type = "commit" # a submodule + case b'sc': type = "subcommit" case _: raise Exception(f"Weird tree leaf mode {item.mode}") - if not (recursive and type=='tree'): # This is a leaf + if not (recursive and type=='subcommit'): # This is a leaf print("{0} {1} {2}\t{3}".format( "0" * (6 - len(item.mode)) + item.mode.decode("ascii"), # Git's ls-tree displays the type @@ -596,8 +597,11 @@ def ls_tree(repo, ref, recursive=None, prefix=""): item.sha, join_path(prefix, item.path) )) - else: # This is a branch (vs. leaf), recurse - ls_tree(repo, item.sha, recursive, join_path(prefix, item.path)) + else: # This is a subcommit + commit_obj = object_read(repo, item.sha) + tree_sha = commit_obj.kvlm[b'tree'].decode("ascii") + ls_tree(repo, tree_sha, recursive, join_path(prefix, item.path)) + argsp = argsubparsers.add_parser("checkout", help="Checkout a commit inside of a directory.") @@ -1112,13 +1116,19 @@ def tree_to_dict(repo, ref, prefix=""): # We read the object to extract its type (this is uselessly # expensive: we could just open it as a file and read the # first few bytes) - is_subtree = leaf.mode.startswith(b'04') + if (leaf.mode.startswith(b'04')): + raise Exception("Tree should not be child of tree") + + is_subcommit = leaf.mode.startswitch(b'sc') # Depending on the type, we either store the path (if it's a # blob, so a regular file), or recurse (if it's another tree, # so a subdir) - if is_subtree: - ret.update(tree_to_dict(repo, leaf.sha, full_path)) + if is_subcommit: + commit_obj = object_read(repo, leaf.sha) + tree_sha = commit_obj.kvlm[b'tree'].decode("ascii") + + ret.update(tree_to_dict(repo, tree_sha, full_path)) else: ret[full_path] = leaf.sha @@ -1367,7 +1377,42 @@ def gitconfig_user_get(config): return f"{config['user']['name']} <{config['user']['email']}>" return None -def tree_from_index(repo, index): +def create_commit_map(repo, commit): + commit_map = dict() + + create_commit_map_recurse(repo, commit, commit_map, "") + + return commit_map + +def create_commit_map_recurse(repo, commit, commit_map, path): + """From a root commit, walk down the tree of subcommits. Returns a dict mapping + directory paths to commit objects. Note: doesn't include root commit.""" + + tree_sha = commit.kvlm["tree"].decode('ascii') + obj = object_read(repo, tree_sha) + for item in obj.items: + if len(item.mode) == 5: + type = item.mode[0:1] + else: + type = item.mode[0:2] + + match type: + case b'04': raise Exception("Trees should point to subcommits, not other trees") + case b'10': type = "blob" + case b'12': type = "blob" # a symlink + case b'16': type = "commit" # a submodule + case b'sc': type = "subcommit" + case _: raise Exception(f"Weird tree leaf mode {item.mode}") + + if type == "subcommit": + fullpath = f"{path}/{item.path}" + subcommit = object_read(repo, item.sha) + commit_map[fullpath] = subcommit + + create_commit_map_recurse(repo, subcommit, commit_map, fullpath) + + +def tree_from_index(repo, index, commit_map, author, commit_time, message): contents = dict() contents[""] = list() @@ -1411,6 +1456,15 @@ def tree_from_index(repo, index): sha = object_write(tree, repo) + subcommit = commit_map[path] + + new_subcommit = commit_create(repo, + sha, + subcommit, + author, + commit_time, + message) + parent = os.path.dirname(path).replace("\\", "/") base = os.path.basename(path) contents[parent].append((base, sha)) @@ -1441,13 +1495,25 @@ def commit_create(repo, tree, parent, author, timestamp, message): def cmd_commit(args): repo = repo_find() index = index_read(repo) - tree = tree_from_index(repo, index) + root_commit = object_find(repo, "HEAD") + + commit_time = datetime.now() + author = gitconfig_user_get(gitconfig_read()) + + commit_map = create_commit_map(repo, root_commit) + + tree = tree_from_index(repo, + index, + commit_map, + author, + commit_time, + args.message) commit = commit_create(repo, tree, - object_find(repo, "HEAD"), - gitconfig_user_get(gitconfig_read()), - datetime.now(), + root_commit, + author, + commit_time, args.message) # Update HEAD so our commit is now the tip of the active branch.