Files
Yogstation/tools/hooks/merge_frontend.py
TheGamerdk 2e787af654 AI Rework | 2024 edition (#21266)
* fix ui

* oops

* Fixes map bibby broke

* Revert "Fixes map bibby broke"

This reverts commit af1d1c610e.

* e

* Unfucks deltastation

* stuff

* cleans things after bibby

* Fix subcontroller cache

* Fixed APCs and air alarms not working when connected to a subcontroller

* bunch of fixes

* job icon

* tcomms server

Update traffic_control.dm

* make compile

* Update synthetic.dm

* Update synthetic.dm

* Update access.dm

* er

* le runtime

* Update ai_data_core.dm

* le dupe

* wall

* Update decentralized_ai.dm

* Update decentralized_ai.dm

* e

* Update wy_synths.dm

* we do a little trolling

* e

* small cahnges

* more minor changes

* Create conflict_fixer.dmm

* Create conflict_fixer - Kopi.dmm

* minor map cahnges

* broken maps

* oops

* bon

* Update ainetworkinterface.dm

* Update ainetworkinterface.dm

* Update ai.dm

* lol

* light

* fixes

* e

* Update synth_pod.dm

* e

* Update synth_os.dm

* Update objective.dm

* fixes

* Update synthetic.dm

* e

* antag

* tweaks

* e

* Update VRSleeper.js

* e

* Update synth_pod.dm

* Update synth_pod.dm

* 1

* Update synth_pod.dm

* Update synth_os.dm

* Update synthetic.dm

* fixes

* headset and ID go boom

* Update wy_synths.dm

* Update robot_parts.dm

* cremator

* Update communications.dm

* Update synthetic.dm

* no synth control

* Update synthetic.dm

* Update ai_dashboard.dm

* Update miscellaneous.dm

* Update miscellaneous.dm

* Update headset.dm

* Update damage_procs.dm

* Update server_cabinet.dm

* minor fixes

* Update miscellaneous.dm

* Update rack_creator.dm

* Update synthetic.dm

* compiles

* fixes

* brainless

* access request

* cofnlict

* fixes

* controller

* Update yogstation.dme

* Update yogstation.dme

* s

* maps

* Update NtosSynthManager.js

* unrevert this lol

* eat

* Update wy_synths.dm

* sigh

* e

* Update wy_synths.dm

* sigh

* fixed ma

* Update synthetic.dm

* better

* Update ethernet_cable.dm

* Update computer.dm

* Update computer.dm

* i hate this

* Update ai_laws.dm

* Update hardsuit.dm

* initialize

* sigh

* Update synth_os.dm

* Update master_subcontroller.dm

* Update master_subcontroller.dm

* Update synth_requester.dm

* Update synth_requester.dm

* Update room_lockdown.dm

* small fixes

* propa sprite matey

* no latejoin lol

* Update synth_os.dm

* maps fixed

* compiles

* Update brain_item.dm

* fixes

* mappers

* Update hud.dmi

* Update passworddoor.dm

* button puzzle

* mine stuff

* Update yogstation.dme

* le map

* changes

* ye

* Update abandoned_mine.dm

* Update _puzzles.dm

* bingus

* Update abandoned_mine.dm

* maps

* e

* no more quirks

* e

* DMI merge conflict resolver (from TG)

* update yogstation map

* Minor away mission changes

* forgot something

* proper fix

* donut

* maps

* asteroid

* gax

* all maps done

* Update synthetic.dm

* makes it compile

* Update backstabs.dm

* more compile!

* fix this map

* bad item

* remove broken objects

* gax

* Update ticker.dm

* pdas

* omg save the file

* Update networking_machines.dm

* area string moment

* s

* awkward

* revival

* Update ai_dashboard.dm

* fixes

* fixed floopy

* less difficult

* this is important

* Update ai_network.dm

* maps

* fixes the maps

* wHAT

* WHAT

* wet

* start fixing conflicts

* Update yogstation.dme

* start fixing map conflicts

* Update DonutStation.dmm

* updates

* donut conflicts

* update box

* Update IceMeta.dmm

* icon diffs

* Update crew.dm

* Update unsorted.dm

* Update layers.dm

* Update YogStation.dmm

* conflicts

* Update IceMeta.dmm

* will this finally fix it

* Update IceMeta.dmm

* fix all this SHIT

* what

* more shit ugh

* what!

* Update DonutStation.dmm

* Update AsteroidStation.dmm

* Update AsteroidStation.dmm

* Update dead.dm

* Update catwalk_plating.dm

* Update catwalk_plating.dm

---------

Co-authored-by: adamsong <adamsong@users.noreply.github.com>
Co-authored-by: wejengin2 <48154165+wejengin2@users.noreply.github.com>
Co-authored-by: Byemoh <baiomurang@gmail.com>
2024-04-01 16:32:09 -05:00

169 lines
5.8 KiB
Python

# merge_frontend.py
import sys
import io
import os
import pygit2
import collections
import typing
ENCODING = 'utf-8'
class MergeReturn(typing.NamedTuple):
success: bool
merge_result: typing.Optional[object]
class MergeDriver:
driver_id: typing.Optional[str] = None
def pre_announce(self, path: str):
"""
Called before merge() is called, with a human-friendly path for output.
"""
print(f"Merging {self.driver_id}: {path}")
def merge(self, base: typing.BinaryIO, left: typing.BinaryIO, right: typing.BinaryIO) -> MergeReturn:
"""
Read from three BinaryIOs: base (common ancestor), left (ours), and
right (theirs). Perform the actual three-way merge operation. Leave
conflict markers if necessary.
Return (False, None) to indicate the merge driver totally failed.
Return (False, merge_result) if the result contains conflict markers.
Return (True, merge_result) if everything went smoothly.
"""
raise NotImplementedError
def to_file(self, output: typing.BinaryIO, merge_result: object):
"""
Save the merge() result to the given output stream.
Override this if the merge() result is not bytes or str.
"""
if isinstance(merge_result, bytes):
output.write(merge_result)
elif isinstance(merge_result, str):
with io.TextIOWrapper(output, ENCODING) as f:
f.write(merge_result)
else:
raise NotImplementedError
def post_announce(self, success: bool, merge_result: object):
"""
Called after merge() is called, to warn the user if action is needed.
"""
if not success:
print("!!! Manual merge required")
if merge_result:
print(" A best-effort merge was performed. You must finish the job yourself.")
else:
print(" No merge was possible. You must resolve the conflict yourself.")
def main(self, args: typing.List[str] = None):
return _main(self, args or sys.argv[1:])
def _main(driver: MergeDriver, args: typing.List[str]):
if len(args) > 0 and args[0] == '--posthoc':
return _posthoc_main(driver, args[1:])
else:
return _driver_main(driver, args)
def _driver_main(driver: MergeDriver, args: typing.List[str]):
"""
Act like a normal Git merge driver, called by Git during a merge.
"""
if len(args) != 5:
print("merge driver called with wrong number of arguments")
print(" usage: %P %O %A %B %L")
return 1
path, path_base, path_left, path_right, _ = args
driver.pre_announce(path)
with open(path_base, 'rb') as io_base:
with open(path_left, 'rb') as io_left:
with open(path_right, 'rb') as io_right:
success, merge_result = driver.merge(io_base, io_left, io_right)
if merge_result:
# If we got anything, write it to the working directory.
with open(path_left, 'wb') as io_output:
driver.to_file(io_output, merge_result)
driver.post_announce(success, merge_result)
if not success:
# If we were not successful, do not mark the conflict as resolved.
return 1
def _posthoc_main(driver: MergeDriver, args: typing.List[str]):
"""
Apply merge driver logic to a repository which is already in a conflicted
state, running the driver on any conflicted files.
"""
repo_dir = pygit2.discover_repository(os.getcwd())
repo = pygit2.Repository(repo_dir)
conflicts = repo.index.conflicts
if not conflicts:
print("There are no unresolved conflicts.")
return 0
all_success = True
index_changed = False
any_attempted = False
for base, left, right in list(conflicts):
if not base or not left or not right:
# (not left) or (not right): deleted in one branch, modified in the other.
# (not base): added differently in both branches.
# In either case, there's nothing we can do for now.
continue
path = left.path
if not _applies_to(repo, driver, path):
# Skip the file if it's not the right extension.
continue
any_attempted = True
driver.pre_announce(path)
io_base = io.BytesIO(repo[base.id].data)
io_left = io.BytesIO(repo[left.id].data)
io_right = io.BytesIO(repo[right.id].data)
success, merge_result = driver.merge(io_base, io_left, io_right)
if merge_result:
# If we got anything, write it to the working directory.
with open(os.path.join(repo.workdir, path), 'wb') as io_output:
driver.to_file(io_output, merge_result)
if success:
# If we were successful, mark the conflict as resolved.
with open(os.path.join(repo.workdir, path), 'rb') as io_readback:
contents = io_readback.read()
merged_id = repo.create_blob(contents)
repo.index.add(pygit2.IndexEntry(path, merged_id, left.mode))
del conflicts[path]
index_changed = True
if not success:
all_success = False
driver.post_announce(success, merge_result)
if index_changed:
repo.index.write()
if not any_attempted:
print("There are no unresolved", driver.driver_id, "conflicts.")
if not all_success:
# Not usually observed, but indicate the failure just in case.
return 1
def _applies_to(repo: pygit2.Repository, driver: MergeDriver, path: str):
"""
Check if the current merge driver is a candidate to handle a given path.
"""
if not driver.driver_id:
raise ValueError('Driver must have ID to perform post-hoc merge')
return repo.get_attr(path, 'merge') == driver.driver_id