Skip to content

Instantly share code, notes, and snippets.

@androm3da
Created July 20, 2017 20:48
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 androm3da/81d7d383751b407ab58c405fdfadfeaf to your computer and use it in GitHub Desktop.
Save androm3da/81d7d383751b407ab58c405fdfadfeaf to your computer and use it in GitHub Desktop.
#!/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