From 7ef46efb82c69561cd630e342cadd833b3a9b63e Mon Sep 17 00:00:00 2001 From: Nathan McRae Date: Sat, 6 Jul 2024 12:12:01 -0700 Subject: [PATCH] Add log --- libwyag.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/libwyag.py b/libwyag.py index 29395eb..6ea702f 100644 --- a/libwyag.py +++ b/libwyag.py @@ -327,3 +327,133 @@ def object_hash(fd, fmt, repo=None): case _ : raise Exception(f"Unknown type {fmt}") return object_write(obj, repo) + +def kvlm_parse(raw, start=0, dct=None): + if not dct: + dct = collections.OrderedDict() + # You CANNOT declare the argument as dct=OrderedDict() or all + # call to the functions will endlessly grow the same dict. + + # This function is recursive: it reads a key/value pair, then call + # itself back with the new position. So we first need to know + # where we are: at a keyword, or already in the messageQ + + # We search for the next space and the next newline. + spc = raw.find(b' ', start) + nl = raw.find(b'\n', start) + + # If space appears before newline, we have a keyword. Otherwise, + # it's the final message, which we just read to the end of the file. + + # Base case + # ========= + # + # If newline appears first (or there's no space at all), we asume + # a blank line. A blank line means the remainder of the data is the + # message. We store it in the dictionary, with None as the key, and + # return. + if (spc < 0) or (nl < spc): + assert nl == start + dct[None] = raw[start+1:] + return dct + + # Recursive case + # ============== + # + # We read a key-value pair and recurse for the next. + key = raw[start:spc] + + # Find the end of the value. Continuation lines begin with a + # space, so we loop until we find a "\n" not followed by a space. + end = start + while True: + end = raw.find(b'\n', end+1) + if raw[end+1] != ord(' '): break + + value = raw[spc+1:end].replace(b'\n ', b'\n') + + # Don't overwrite existing data contents + if key in dct: + if type(dct[key]) == list: + dct[key].append(value) + else: + dct[key] = [ dct[key], value ] + else: + dct[key] = value + + return kvlm_parse(raw, start=end+1, dct=dct) + +def kvlm_serialize(kvlm): + ret = b'' + + # Output fiels + for k in kvlm.keys(): + # Skip the message itself + if k == None: continue + val = kvlm[k] + # Normalize to a list + if type(val) != list: + val = [ val ] + + for v in val: + ret += k + b' ' + (v.replace(b'\n', b'\n ')) + b'\n' + + ret += b'\n' + kvlm[None] + b'\n' + + return ret + +class GitCommit(GitObject): + fmt = b'commit' + + def deserialize(self, data): + self.kvlm = kvlm_parse(data) + + def serialize(self): + return kvlm_serialize(self.kvlm) + + def init(self): + self.kvlm = dict() + +argsp = argsubparsers.add_parser("log", help="Display history of a given commit.") +argsp.add_argument("commit", + default="HEAD", + nargs="?", + help="Commit to start at.") + +def cmd_log(args): + repo = repo_find() + + print("digraph wyaglog{") + print(" node[shape=rect]") + log_graphviz(repo, object_find(repo, args.commit), set()) + print("}") + +def log_graphviz(repo, sha, seen): + if sha in seen: + return + seen.add(sha) + + commit = object_read(repo, sha) + short_hash = sha[0:8] + message = commit.kvlm[None].decode("utf8").strip() + message = message.replace("\\", "\\\\") + message = message.replace("\"", "\\\"") + + if "\n" in message: # keep only the first line + message = message[:message.index("\n")] + print(f" c_{sha} [label=\"{sha[0:7]}: {message}\"]") + assert commit.fmt==b'commit' + + if not b'parent' in commit.kvlm.keys(): + # Base case: the initial commit. + return + + parents = commit.kvlm[b'parent'] + + if type(parents) != list: + parents = [ parents ] + + for p in parents: + p = p.decode("ascii") + print (f" c_{sha} -> c_{p}") + log_graphviz(repo, p, seen)