Created
July 20, 2017 20:48
-
-
Save androm3da/81d7d383751b407ab58c405fdfadfeaf to your computer and use it in GitHub Desktop.
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/env python | |
"""mergelog | |
This is a custom merge driver for git for the libcxx repo. It should be called from git | |
with a stanza in .git.config like this: | |
[merge "mergecxx"] | |
name = A custom merge driver for libcxx | |
driver = ~/src/merge_cxx %O %A %B %P | |
recursive = binary | |
To make git use the custom merge driver you also need to put this in | |
.git/info/attributes or repo's .gitattributes: | |
* merge=mergecxx | |
This tells git to use the 'mergecxx' merge driver to merge files in this repo | |
""" | |
import sys, difflib | |
import subprocess | |
from subprocess import Popen | |
import shlex | |
import logging | |
logger = logging.getLogger() | |
from collections import namedtuple | |
DiffResult = namedtuple('DiffResult', 'diff added removed common other'.split()) | |
def get_diff(a, b): | |
d = difflib.Differ() | |
diff = d.compare(a, b) | |
added = [l for l in diff if l.startswith('+ ')] | |
removed = [l for l in diff if l.startswith('- ')] | |
common = [l for l in diff if l.startswith(' ')] | |
other = [l for l in diff if l.startswith('? ')] | |
return DiffResult(diff, added, removed, common, other) | |
def main(): | |
# The arguments from git are the names of temporary files that | |
# hold the contents of the different versions of the log.txt | |
# file. | |
ancestor_path, current_path, other_path, repo_path = sys.argv[1:] | |
paths = (ancestor_path, current_path, other_path, repo_path) | |
logger.debug('considering {paths}'.format(paths=paths)) | |
# The version of the file from the common ancestor of the two branches. | |
# This constitutes the 'base' version of the file. | |
ancestor = open(ancestor_path, 'r').readlines() | |
# The version of the file at the HEAD of the current branch. | |
# The result of the merge should be left in this file by overwriting it. | |
current = open(current_path, 'r').readlines() | |
# The version of the file at the HEAD of the other branch. | |
other = open(other_path, 'r').readlines() | |
our_changes = get_diff(current, ancestor) | |
their_changes = get_diff(current, other) | |
def take_other(): | |
logger.info('taking other for {path}'.format(path=repo_path)) | |
with open(current_path, 'wt') as f: | |
f.write('\n'.join(other)) | |
def take_current(): | |
logger.info('taking current for {path}'.format(path=repo_path)) | |
# with open(current_path, 'wt') as f: | |
# f.write('\n'.join(current)) | |
def take_both(): | |
logger.info('taking both for {path}'.format(path=repo_path)) | |
with open(current_path, 'wt') as f: | |
f.write('\n'.join(other)) | |
def leave_conflict(): | |
logger.info('leaving conflict for {path}'.format(path=repo_path)) | |
# cmd = 'diff3 {current} {ancestor} {other}'.format( | |
cmd = 'merge -p {current} {ancestor} {other}'.format( | |
current=current_path, | |
ancestor=ancestor_path, | |
other=other_path, | |
) | |
p = Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
out, err = p.communicate() | |
if not p.returncode in (0, 1): | |
raise ValueError('failed during diff3: "{}"'.format(err)) | |
logger.info('Result from "merge" for {path}: {res}'.format( | |
path=repo_path, | |
res='conflicts' if p.returncode == 1 else 'no conflicts')) | |
with open(current_path, 'wt') as f: | |
f.write(out) | |
return p.returncode | |
never_touched = ( | |
'utils', | |
'www', | |
) | |
if any(repo_path.startswith(t) for t in never_touched): | |
take_other() | |
sys.exit(0) | |
paths_touched = ( | |
# contains feature for organizing link flags | |
'test/libcxx/compiler.py', | |
# contain QUIC-specific config items: | |
'test/lit.site.cfg.in', | |
'src/locale.cpp', | |
'test/CMakeLists.txt', | |
) | |
hits_important_changes = repo_path in paths_touched | |
if any('hexagon' in line.lower() for line in our_changes.added) or \ | |
hits_important_changes: | |
ret = leave_conflict() | |
sys.exit(ret) | |
elif repo_path.startswith('test/libcxx/quic') or \ | |
repo_path.startswith('quic'): | |
take_current() | |
sys.exit(0) | |
elif repo_path.startswith('test') or \ | |
repo_path.startswith('include') or \ | |
repo_path.startswith('src'): | |
take_other() | |
sys.exit(0) | |
else: | |
ret = leave_conflict() | |
sys.exit(ret) | |
if __name__ == '__main__': | |
fh = logging.FileHandler('cxx_merge.log') | |
fh.setLevel(logging.DEBUG) | |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
fh.setFormatter(formatter) | |
logger_ = logging.getLogger() | |
logger_.addHandler(fh) | |
logger.setLevel(logging.INFO) | |
try: | |
main() | |
except Exception as e: | |
logger.error('error: {err}'.format(err=e)) | |
raise |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment