Skip to content

Instantly share code, notes, and snippets.

@robertsdotpm
Created March 13, 2022 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertsdotpm/c7efac82a143780e5ed99efaa3e9e23e to your computer and use it in GitHub Desktop.
Save robertsdotpm/c7efac82a143780e5ed99efaa3e9e23e to your computer and use it in GitHub Desktop.
Git merge conflict resolve Python script
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.
#!/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