Add status command

This commit is contained in:
Nathan McRae 2024-07-08 19:44:04 -07:00
parent b2ee6d25be
commit bad433b264

View File

@ -1064,8 +1064,136 @@ def check_ignore(rules, path):
if os.path.isabs(path):
raise Exception("This function requires path to be relative to the repository's root")
# Eh, just hardcode it
if (path.startswith(".git")):
return True
result = check_ignore_scoped(rules.scoped, path)
if result != None:
return result
return check_ignore_absolute(rules.absolute, path)
argsp = argsubparsers.add_parser("status", help="Show the working tree status.")
def cmd_status(_):
repo = repo_find()
index = index_read(repo)
cmd_status_branch(repo)
cmd_status_head_index(repo, index)
print()
cmd_status_index_worktree(repo, index)
def branch_get_active(repo):
with open(GitRepository.repo_file(repo, "HEAD"), "r") as f:
head = f.read()
if head.startswith("ref: refs/heads/"):
return(head[16:-1])
else:
return False
def cmd_status_branch(repo):
branch = branch_get_active(repo)
if branch:
print(f"On branch {branch}.")
else:
print("HEAD detached at {}".format(object_find(repo, "HEAD")))
def tree_to_dict(repo, ref, prefix=""):
ret = dict()
tree_sha = object_find(repo, ref, fmt=b"tree")
tree = object_read(repo, tree_sha)
for leaf in tree.items:
full_path = join_path(prefix, leaf.path)
# 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')
# 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))
else:
ret[full_path] = leaf.sha
return ret
def cmd_status_head_index(repo, index):
print("Changes to be commited:")
head = tree_to_dict(repo, "HEAD")
for entry in index.entries:
if entry.name in head:
if head[entry.name] != entry.sha:
print(" modified:", entry.name)
del head[entry.name]
else:
print(" added: ", entry.name)
# Keys still in HEAD are files that we haven't met in the index,
# and thus have been deleted
for entry in head.keys():
print(" deleted: ", entry)
def cmd_status_index_worktree(repo, index):
print("Changes not staged for commit:")
ignore = gitignore_read(repo)
gitdir_prefix = repo.gitdir + "/"
all_files = list()
# We begin by walking the filesystem
for (root, _, files) in os.walk(repo.worktree, True):
if root==repo.gitdir or root.startswith(gitdir_prefix):
continue
for f in files:
full_path = join_path(root, f)
rel_path = os.path.relpath(full_path, repo.worktree).replace("\\", "/")
all_files.append(rel_path)
# We now traverse the index, and compare real files with the cached
# versions.
for entry in index.entries:
full_path = join_path(repo.worktree, entry.name)
# That file *name* is in the index
if not os.path.exists(full_path):
print(" deleted: ", entry.name)
else:
stat = os.stat(full_path)
# Compare metadata
ctime_ns = entry.ctime[0] * 10**9 + entry.ctime[1]
mtime_ns = entry.mtime[0] * 10**9 + entry.mtime[1]
if (stat.st_ctime_ns != ctime_ns) or (stat.st_mtime_ns != mtime_ns):
# If different, deep compare.
# @FIXME This *will* crash on symlinks to dir.
with open(full_path, "rb") as fd:
new_sha = object_hash(fd, b"blob", None)
# If the hashes are the same, the files are actually the same.
same = entry.sha == new_sha
if not same:
print(" modified:", entry.name)
if entry.name in all_files:
all_files.remove(entry.name)
print()
print("Untracked files:")
for f in all_files:
# @TODO If a full directory is untracked, we should display
# its name without its contents.
if not check_ignore(ignore, f):
print(" ", f)