summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/merge-driver-ekeyword132
1 files changed, 132 insertions, 0 deletions
diff --git a/bin/merge-driver-ekeyword b/bin/merge-driver-ekeyword
new file mode 100755
index 0000000..2df83fc
--- /dev/null
+++ b/bin/merge-driver-ekeyword
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+#
+# Copyright 2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2 or later
+
+"""
+Custom git merge driver for handling conflicts in KEYWORDS assignments
+
+See https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver
+"""
+
+import difflib
+import os
+import sys
+import tempfile
+
+from typing import List, Optional, Tuple
+
+from gentoolkit.ekeyword import ekeyword
+
+
+def keyword_array(keyword_line: str) -> List[str]:
+ # Find indices of string inside the double-quotes
+ i1: int = keyword_line.find('"') + 1
+ i2: int = keyword_line.rfind('"')
+
+ # Split into array of KEYWORDS
+ return keyword_line[i1:i2].split(' ')
+
+
+def keyword_line_changes(old: str, new: str) -> List[Tuple[Optional[str],
+ Optional[str]]]:
+ a: List[str] = keyword_array(old)
+ b: List[str] = keyword_array(new)
+
+ s = difflib.SequenceMatcher(a=a, b=b)
+
+ changes = []
+ for tag, i1, i2, j1, j2 in s.get_opcodes():
+ if tag == 'replace':
+ changes.append((a[i1:i2], b[j1:j2]),)
+ elif tag == 'delete':
+ changes.append((a[i1:i2], None),)
+ elif tag == 'insert':
+ changes.append((None, b[j1:j2]),)
+ else:
+ assert tag == 'equal'
+ return changes
+
+
+def keyword_changes(ebuild1: str, ebuild2: str) -> List[Tuple[Optional[str],
+ Optional[str]]]:
+ with open(ebuild1) as e1, open(ebuild2) as e2:
+ lines1 = e1.readlines()
+ lines2 = e2.readlines()
+
+ diff = difflib.unified_diff(lines1, lines2, n=0)
+ assert next(diff) == '--- \n'
+ assert next(diff) == '+++ \n'
+
+ hunk: int = 0
+ old: str = ''
+ new: str = ''
+
+ for line in diff:
+ if line.startswith('@@ '):
+ if hunk > 0:
+ break
+ hunk += 1
+ elif line.startswith('-'):
+ if old or new:
+ break
+ old = line
+ elif line.startswith('+'):
+ if not old or new:
+ break
+ new = line
+ else:
+ if 'KEYWORDS=' in old and 'KEYWORDS=' in new:
+ return keyword_line_changes(old, new)
+ return None
+
+
+def apply_keyword_changes(ebuild: str, pathname: str,
+ changes: List[Tuple[Optional[str],
+ Optional[str]]]) -> int:
+ result: int = 0
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ # ekeyword will only modify files named *.ebuild, so make a symlink
+ ebuild_symlink: str = os.path.join(tmpdir, os.path.basename(pathname))
+ os.symlink(os.path.join(os.getcwd(), ebuild), ebuild_symlink)
+
+ for removals, additions in changes:
+ args = []
+ for rem in removals:
+ # Drop leading '~' and '-' characters and prepend '^'
+ i = 1 if rem[0] in ('~', '-') else 0
+ args.append('^' + rem[i:])
+ if additions:
+ args.extend(additions)
+ args.append(ebuild_symlink)
+
+ result = ekeyword.main(args)
+ if result != 0:
+ break
+
+ return result
+
+
+def main(argv):
+ if len(argv) != 5:
+ sys.exit(-1)
+
+ O = argv[1] # %O - filename of original
+ A = argv[2] # %A - filename of our current version
+ B = argv[3] # %B - filename of the other branch's version
+ P = argv[4] # %P - original path of the file
+
+ # Get changes from %O to %B
+ changes = keyword_changes(O, B)
+ if changes:
+ # Apply O -> B changes to A
+ result: int = apply_keyword_changes(A, P, changes)
+ sys.exit(result)
+ else:
+ result: int = os.system(f"git merge-file -L HEAD -L base -L ours {A} {O} {B}")
+ sys.exit(0 if result == 0 else -1)
+
+
+if __name__ == "__main__":
+ main(sys.argv)