diff --git a/libwyag.py b/libwyag.py index b123d73..d391b12 100644 --- a/libwyag.py +++ b/libwyag.py @@ -23,22 +23,23 @@ argsubparsers.required = True def main(argv=sys.argv[1:]): args = argparser.parse_args(argv) match args.command: - case "add" : cmd_add(args) - case "cat-file" : cmd_cat_file(args) - case "check-ignore" : cmd_check_ignore(args) - case "checkout" : cmd_checkout(args) - case "commit" : cmd_commit(args) - case "hash-object" : cmd_hash_object(args) - case "init" : cmd_init(args) - case "log" : cmd_log(args) - case "ls-files" : cmd_ls_files(args) - case "ls-tree" : cmd_ls_tree(args) - case "rev-parse" : cmd_rev_parse(args) - case "rm" : cmd_rm(args) - case "show-ref" : cmd_show_ref(args) - case "status" : cmd_status(args) - case "tag" : cmd_tag(args) - case _ : print("Bad command.") + case "add" : cmd_add(args) + case "cat-file" : cmd_cat_file(args) + case "check-ignore" : cmd_check_ignore(args) + case "checkout" : cmd_checkout(args) + case "commit" : cmd_commit(args) + case "hash-object" : cmd_hash_object(args) + case "init" : cmd_init(args) + case "log" : cmd_log(args) + case "ls-files" : cmd_ls_files(args) + case "ls-tree" : cmd_ls_tree(args) + case "rev-parse" : cmd_rev_parse(args) + case "rm" : cmd_rm(args) + case "show-ref" : cmd_show_ref(args) + case "status" : cmd_status(args) + case "tag" : cmd_tag(args) + case "graph-objects" : cmd_graph_objects(args) + case _ : print("Bad command.") class GitRepository (object): """A git repo""" @@ -1542,3 +1543,78 @@ def cmd_commit(args): else: # Otherwise, we update HEAD itself. with open(repo_file(repo, "HEAD"), "w") as fd: fd.write(commit + "\n") + +argsp = argsubparsers.add_parser("graph-objects", help="Show git objects in a dot graph") + +argsp.add_argument("object", + nargs="?", + metavar="object", + help="The object the graph will start from") + +def graph_objects_tree(repo, sha, seen): + if sha in seen: + return + seen.append(sha) + + yield f"t_{sha} [label=\"tree {sha[0:7]}\"]" + # TODO: inefficient + obj = object_read(repo, 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': type = "tree" + 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 == "tree": + yield f"t_{sha} -> t_{item.sha}" + yield from graph_objects_tree(repo, item.sha, seen) + if type == "commit" or type == "subcommit": + yield f"t_{sha} -> c_{item.sha}" + yield from graph_objects(repo, item.sha, seen) + if type == "blob": + yield f"b_{item.sha} [label=\"{item.sha[0:7]} {item.path}\"]" + yield f"t_{sha} -> b_{item.sha}" + + +def graph_objects(repo, sha, seen): + if sha in seen: + return + seen.append(sha) + + obj = object_read(repo, sha) + + match obj.fmt: + case b"commit": # + message = obj.kvlm[None] + yield f"c_{sha} [label=\"{sha[0:7]}: {message.decode('utf8')}\"]" + tree_sha = obj.kvlm[b"tree"].decode("ascii") + yield f"c_{sha} -> t_{tree_sha}" + yield from graph_objects_tree(repo, tree_sha, seen) + case b"tree": + yield from graph_objects_tree(repo, sha, seen) + +def cmd_graph_objects(args): + repo = repo_find() + + if args.object == None: + obj_name = "HEAD" + else: + obj_name = args.object + + obj_sha = object_find(repo, obj_name) + graph = "digraph objectgraph{\n" + graph += " node[shape=rect]\n" + for str in graph_objects(repo, obj_sha, list()): + graph += " " + str + "\n" + graph += "}" + + print(graph)