Add status command
This commit is contained in:
parent
b2ee6d25be
commit
bad433b264
128
libwyag.py
128
libwyag.py
@ -1064,8 +1064,136 @@ def check_ignore(rules, path):
|
|||||||
if os.path.isabs(path):
|
if os.path.isabs(path):
|
||||||
raise Exception("This function requires path to be relative to the repository's root")
|
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)
|
result = check_ignore_scoped(rules.scoped, path)
|
||||||
if result != None:
|
if result != None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return check_ignore_absolute(rules.absolute, path)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user