From 3f49d9f7db40a64e967c28ba10479985aa0fadc9 Mon Sep 17 00:00:00 2001 From: Nathan McRae Date: Thu, 11 Jul 2024 19:29:44 -0700 Subject: [PATCH] Add 'commit' command --- libwyag.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/libwyag.py b/libwyag.py index 7d43d75..de56aa3 100644 --- a/libwyag.py +++ b/libwyag.py @@ -1342,3 +1342,119 @@ def add(repo, paths, delete=True, skip_missing=False): index.entries.append(entry) index_write(repo, index) + +argsp = argsubparsers.add_parser("commit", help="Record changes to the repository.") + +argsp.add_argument("-m", + metavar="mesaage", + dest="message", + help="Message to associate with this commit.") + +def gitconfig_read(): + xdg_config_home = os.environ["XDG_CONFIG_HOME"] if "XDG_CONFIG_HOME" in os.environ else "~/.config" + config_files = [ + os.path.expanduser(join_path(xdg_config_home, "git/config")).replace("\\", "/"), + #os.path.expanduser("~/.gitconfig").replace("\\", "/") + ] + + config = configparser.ConfigParser() + config.read(config_files) + return config + +def gitconfig_user_get(config): + if "user" in config: + if "name" in config["user"] and "email" in config["user"]: + return f"{config['user']['name']} <{config['user']['email']}>" + return None + +def tree_from_index(repo, index): + contents = dict() + contents[""] = list() + + # Convert entries to dictionary where keys are directories, and values are + # lists of directory contents. + for entry in index.entries: + dirname = os.path.dirname(entry.name).replace("\\", "/") + + # We create all dictionary entries up to root (""). We need them *all* + # because even if a directory holds no files it will contain at least + # a tree. + key = dirname + while key != "": + if not key in contents: + contents[key] = list() + key = os.path.dirname(key).replace("\\", "/") + + contents[dirname].append(entry) + + # Sort keys (= directories) by length, descending. This means that we'll + # always encounter a given path before its parent, which is all we need, + # since for each directory D we'll need to modify its parent P to add + # D's tree. + sorted_paths = sorted(contents.keys(), key=len, reverse=True) + + sha = None + + for path in sorted_paths: + tree = GitTree() + + for entry in contents[path]: + # An entry can be a normal GitIndexEntry read from the index, or + # a tree we've created. + if isinstance(entry, GitIndexEntry): + leaf_mode = f"{entry.mode_type:02o}{entry.mode_perms:04o}".encode("ascii") + leaf = GitTreeLeaf(mode=leaf_mode, path=os.path.basename(entry.name), sha=entry.sha) + else: # Tree. We've stored it as a pair: (basename, SHA) + leaf = GitTreeLeaf(mode=b"040000", path=entry[0], sha=entry[1]) + + tree.items.append(leaf) + + sha = object_write(tree, repo) + + parent = os.path.dirname(path).replace("\\", "/") + base = os.path.basename(path) + contents[parent].append((base, sha)) + + return sha + +def commit_create(repo, tree, parent, author, timestamp, message): + commit = GitCommit() + commit.kvlm[b"tree"] = tree.encode("ascii") + if parent: + commit.kvlm[b'parent'] = parent.encode("ascii") + + offset = int(timestamp.astimezone().utcoffset().total_seconds()) + hours = offset // 3600 + minutes = (offset & 3600) // 60 + tz = f"{'+' if offset > 0 else '-'}{hours:02}{minutes:02}" + + if author == None: + author = "" + author = author + timestamp.strftime(" %S ") + tz + + commit.kvlm[b"author"] = author.encode("utf8") + commit.kvlm[b"committer"] = author.encode("utf8") + commit.kvlm[None] = message.encode("utf8") + + return object_write(commit, repo) + +def cmd_commit(args): + repo = repo_find() + index = index_read(repo) + tree = tree_from_index(repo, index) + + commit = commit_create(repo, + tree, + object_find(repo, "HEAD"), + gitconfig_user_get(gitconfig_read()), + datetime.now(), + args.message) + + # Update HEAD so our commit is now the tip of the active branch. + active_branch = branch_get_active(repo) + if active_branch: # If on a branch, update that branch + with open(GitRepository.repo_file(repo, join_path("refs/heads", active_branch)), "w") as fd: + fd.write(commit + "\n") + else: # Otherwise, we update HEAD itself. + with open(repo_file(repo, "HEAD"), "w") as fd: + fd.write(commit + "\n")