Files
CHOMPStation2/tools/mapmerge2/fixup.py

146 lines
5.6 KiB
Python

#!/usr/bin/env python3
import os
import pygit2
from . import dmm
from .mapmerge import merge_map, frontend
STATUS_INDEX = (pygit2.GIT_STATUS_INDEX_NEW
| pygit2.GIT_STATUS_INDEX_MODIFIED
| pygit2.GIT_STATUS_INDEX_DELETED
| pygit2.GIT_STATUS_INDEX_RENAMED
| pygit2.GIT_STATUS_INDEX_TYPECHANGE
)
STATUS_WT = (pygit2.GIT_STATUS_WT_NEW
| pygit2.GIT_STATUS_WT_MODIFIED
| pygit2.GIT_STATUS_WT_DELETED
| pygit2.GIT_STATUS_WT_RENAMED
| pygit2.GIT_STATUS_WT_TYPECHANGE
)
ABBREV_LEN = 12
TGM_HEADER = dmm.TGM_HEADER.encode(dmm.ENCODING)
def walk_tree(tree, *, _prefix=''):
for child in tree:
if isinstance(child, pygit2.Tree):
yield from walk_tree(child, _prefix=f'{_prefix}{child.name}/')
else:
yield f'{_prefix}{child.name}', child
def insert_into_tree(repo, tree_builder, path, blob_oid):
try:
first, rest = path.split('/', 1)
except ValueError:
tree_builder.insert(path, blob_oid, pygit2.GIT_FILEMODE_BLOB)
else:
inner = repo.TreeBuilder(tree_builder.get(first))
insert_into_tree(repo, inner, rest, blob_oid)
tree_builder.insert(first, inner.write(), pygit2.GIT_FILEMODE_TREE)
def main(repo : pygit2.Repository):
if repo.index.conflicts:
print("You need to resolve merge conflicts first.")
return 1
# Ensure the index is clean.
for path, status in repo.status().items():
if status & pygit2.GIT_STATUS_IGNORED:
continue
if status & STATUS_INDEX:
print("You have changes staged for commit. Commit them or unstage them first.")
print("If you are about to commit maps for the first time and can't use hooks, run `Run Before Committing.bat`.")
return 1
if path.endswith(".dmm") and (status & STATUS_WT):
print("You have modified maps. Commit them first.")
print("If you are about to commit maps for the first time and can't use hooks, run `Run Before Committing.bat`.")
return 1
# Set up upstream remote if needed
try:
repo.remotes.create("upstream", "https://github.com/CHOMPStation2/CHOMPStation2.git") # ChompEDIT
except ValueError:
pass
else:
print("Adding upstream remote...")
# Read the HEAD and ancestor commits.
head_commit = repo[repo.head.target]
repo.remotes["upstream"].fetch()
ancestor = repo.merge_base(repo.head.target, repo.revparse_single("refs/remotes/upstream/master").id)
if not ancestor:
print("Unable to automatically fix anything because merge base could not be determined.")
return 1
ancestor_commit = repo[ancestor]
print("Determined ancestor commit SHA to be:", ancestor)
# Walk the head commit tree and convert as needed
converted = {}
commit_message_lines = []
map_folder_to_check = os.path.join(frontend.read_settings().map_folder).replace("\\", "/").replace("../", "") # Remove this ever virgo maps aren't busted
for path, blob in walk_tree(head_commit.tree):
if path.endswith(".dmm") and path.startswith(map_folder_to_check):
print(f"Checking: {path}...")
head_data = blob.read_raw()
head_map = dmm.DMM.from_bytes(head_data)
# test: does every DMM convert cleanly (including TGM conversion)
try:
ancestor_blob = ancestor_commit.tree[path]
except KeyError:
# New map, no entry in ancestor
merged_map = merge_map(head_map, head_map)
merged_bytes = merged_map.to_bytes()
if head_data != merged_bytes:
print(f"Updating new map: {path}")
commit_message_lines.append(f"{'new':{ABBREV_LEN}}: {path}")
converted[path] = merged_map
else:
# Entry in ancestor
ancestor_map = dmm.DMM.from_bytes(ancestor_blob.read_raw())
merged_map = merge_map(head_map, ancestor_map)
merged_bytes = merged_map.to_bytes()
if head_data != merged_bytes:
print(f"Updating map: {path}")
str_id = str(ancestor_commit.id)[:ABBREV_LEN]
commit_message_lines.append(f"{str_id}: {path}")
converted[path] = merged_map
if not converted:
print("All committed maps appear to be okay.")
print("If you are about to commit maps for the first time and can't use hooks, run `Run Before Committing.bat`.")
return 1
# Okay, do the actual work.
tree_builder = repo.TreeBuilder(head_commit.tree)
for path, merged_map in converted.items():
blob_oid = repo.create_blob(merged_map.to_bytes())
insert_into_tree(repo, tree_builder, path, blob_oid)
repo.index.add(pygit2.IndexEntry(path, blob_oid, repo.index[path].mode))
merged_map.to_file(os.path.join(repo.workdir, path))
# Save the index.
repo.index.write()
# Commit the index to the current branch.
signature = pygit2.Signature(repo.config['user.name'], repo.config['user.email'])
joined = "\n".join(commit_message_lines)
repo.create_commit(
repo.head.name,
signature, # author
signature, # committer
f'Fixup maps in TGM format\n\n{joined}\n\nAutomatically commited by: {os.path.relpath(__file__, repo.workdir)}',
tree_builder.write(),
[head_commit.id],
)
# Success.
print("Successfully committed a fixup. Push as needed.")
return 0
if __name__ == '__main__':
exit(main(pygit2.Repository(pygit2.discover_repository(os.getcwd()))))