Created
March 13, 2022 18:52
-
-
Save robertsdotpm/c7efac82a143780e5ed99efaa3e9e23e to your computer and use it in GitHub Desktop.
Git merge conflict resolve Python script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Git's merge tool is kind of shitty with respect to rebase. It doesn't seem to acknowledge the working directory? So I made a simple Python script that lets you easily resolve merge conflicts. The diff tool it uses is Visual Studio Code (which is cross-platform.) This was necessary due to Visual Studios merge diffs being read-only. | |
To use: install the script as /usr/bin/resres, set execute perms, mkdir ~/resres_merge , and on a conflicted file type: resres file.name. This will pop-up a diff view in Visual Studio Code. The changes you want to choose are on the left side split-view. | |
Once you're done resolving the merge conflict, simply run the tool again on the same file to incorporate your changes. Run the tool multiple times if necessary to resolve all conflicts. It will tell you when you're done. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import sys | |
import os | |
import re | |
RESULT_PATH = os.path.expanduser("~/resres_merge/") | |
DIFF_TOOL = "code --diff" | |
# <<< not new line, space type, anything not greedy == anything not greedy >> not new line, windows end line or linux | |
CONFLICT_MARKER = r"""<<<<<<<[^\r\n]+\s*([\s\S]*?)=======([\s\S]*?)>>>>>>>[^\r\n]+((\r\n)|[\n])""" | |
CONFLICT_PROCESSING = "\/\/\/\/\/\/\/\/\/\/\/\/" | |
# Return two paths to files used for the dif. | |
def mk_cmp_paths(input_file): | |
pwd = os.getcwd() | |
input_path = os.path.join(pwd, input_file) | |
input_path = input_path.replace(os.sep, "^") | |
path_a = os.path.join(RESULT_PATH, input_path + ".a") | |
path_b = os.path.join(RESULT_PATH, input_path + ".b") | |
path_c = os.path.join(RESULT_PATH, input_path + ".bk") | |
return [path_a, path_b, path_c] | |
# Write content to path and truncate any existing data. | |
def do_write(path, content): | |
f = open(path, "w") | |
f.seek(0) | |
f.write(content) | |
f.truncate() | |
f.close() | |
# Open diff tool for two file paths. | |
def diff_view(path_a, path_b): | |
cmd_line = "%s %s %s" % (DIFF_TOOL, path_a, path_b) | |
os.system(cmd_line) | |
# Replace merge markers in src file with .a file changes. | |
def apply_path_a_changes(mode): | |
# Look for .a file in result path. | |
for root, dirs, files in os.walk(RESULT_PATH): | |
for filename in files: | |
# .a file is resolved conflict. | |
if filename[-2:] == ".a": | |
res_path = os.path.join(RESULT_PATH, filename) | |
with open(res_path) as res_f: | |
# Replace the section in the src file with the result. | |
res_content = res_f.read() | |
src_path = filename[:-2].replace("^", os.sep) | |
with open(src_path) as src_f: | |
src_content = src_f.read() | |
src_content = src_content.replace(CONFLICT_PROCESSING, res_content) | |
do_write(src_path, src_content) | |
# Cleanup .a file. | |
os.remove(res_path) | |
# Cleanup .b file. | |
if filename[-2:] == ".b": | |
res_path = os.path.join(RESULT_PATH, filename) | |
os.remove(res_path) | |
# Other file types are left alone here ... | |
# Exit -- no conflicts left. | |
def no_conflict(): | |
print("No conflicts found.") | |
exit() | |
# Main program logic here. | |
def main(): | |
# No files provided to command line program. | |
if len(sys.argv) < 2: | |
print("Usage: resres file_name") | |
exit() | |
# Undo. | |
mode = "create" | |
if len(sys.argv) == 3: | |
mode = "undo" | |
# Apply any existing merges. | |
apply_path_a_changes(mode) | |
# Write files for diffing. | |
input_file = sys.argv[1] | |
content = None | |
with open(input_file) as f: | |
content = f.read() | |
# Find conflict in file. | |
ret = re.findall(CONFLICT_MARKER, content) | |
if ret != None: | |
if len(ret) >= 1: | |
# Use first conflict results. | |
str_a, str_b = ret[0][0:2] | |
# Build diff files given a specific src file. | |
path_a, path_b, path_c = mk_cmp_paths(input_file) | |
# Write the files that will be compared. | |
do_write(path_a, str_a) | |
do_write(path_b, str_b) | |
# Write backup file. | |
do_write(path_c, content) | |
# Start the program that does the diffs. | |
diff_view(path_a, path_b) | |
else: | |
no_conflict() | |
else: | |
no_conflict() | |
# Replace conflict with a marker showing its being processed. | |
# Program will exit before here if there's no conflicts. | |
content = re.sub(CONFLICT_MARKER, CONFLICT_PROCESSING, content, 1) | |
# Write patched markers to file. | |
do_write(input_file, content) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment