Merging Branches with Binaries
A. S. Budden
abudden at gmail.com
Thu Mar 4 16:12:50 GMT 2010
On 4 March 2010 15:12, Neil Martinsen-Burrell <nmb at wartburg.edu> wrote:
> On 2010-03-04 01:52 , A. S. Budden wrote:
>> On 19 February 2010 19:12, John Arbash Meinel <john at arbash-meinel.com> wrote:
>> [snip]
>>> With bzr 2.1.0+ you can create a per-file merge plugin, that checks for
>>> any file ending in .axf and just always selects OTHER.
>>
>> This works like a charm, thanks for the suggestion.
>
> If the code is short enough, would you be willing to post it to the list
> (or a blog if you're into that sort of thing) so others can see how this
> might be done?
>
I don't think there's anything too sensitive in there, so here it is
(spread across two files, __init__.py and custom_merge.py). Feel free
to comment on it if you think there is room for improvement! Oh, and
apologies if any of the lines get broken.
Al
======= __init__.py ========
from bzrlib.merge import Merger
"""Merge hook for axf and build number files.
"""
from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
from bzrlib.plugins.custom_merge import custom_merge as
_mod_custom_merge
""")
def custom_merge_hook(merger):
"""Merger.merge_file_content hook for axf and Build number files."""
return _mod_custom_merge.CustomMerger(merger)
def install_hook():
Merger.hooks.install_named_hook(
'merge_file_content', custom_merge_hook, 'AXF and Build Number
file merge')
install_hook()
======= custom_merge.py ========
"""Merge logic for custom_merge plugin."""
from bzrlib import merge, merge3
import sys
import os
class CustomMerger(merge.AbstractPerFileMerger):
def merge_contents(self, merge_params):
# First, check whether this custom merge logic should be used. We
# expect most files should not be merged by this handler.
if (
# OTHER is a straight winner, rely on default merge.
merge_params.winner == 'other' or
# THIS and OTHER aren't both files.
not merge_params.is_file_merge()):
return 'not_applicable', None
filename = self.merger.this_tree.id2path(merge_params.file_id)
def GetNewBuildNumber(base_num, this_num, other_num):
new_num = base_num + (this_num - base_num) + (other_num - base_num)
return ("%d" % new_num)
basename = os.path.basename(filename)
if basename.endswith('.axf'):
# AXF File
return ('success', merge_params.other_lines)
elif basename == 'Build.txt':
# Build number (simple format): check contents are reasonable
if len(merge_params.other_lines) \
== len(merge_params.this_lines) \
== len(merge_params.base_lines) \
== 1:
# This might be correct, check that both are just numbers
if merge_params.this_lines[0].strip().isdigit() \
and merge_params.other_lines[0].strip().isdigit() \
and merge_params.base_lines[0].strip().isdigit():
# This sounds reasonable
this_num = int(merge_params.this_lines[0].strip())
other_num = int(merge_params.other_lines[0].strip())
base_num = int(merge_params.base_lines[0].strip())
if (this_num >= base_num) and (other_num >= base_num):
new_lines = GetNewBuildNumber(base_num,
this_num, other_num) + "\n"
return ('success', new_lines)
elif basename == 'BuildNumber.h':
# Build number (complex format): check contents are reasonable
if len(merge_params.other_lines) == len(merge_params.this_lines):
# Must be the same length
# Check that the lines only vary by an embedded number
import re
# This is the format definition for the acceptable
differing lines
BuildNumberRE =
re.compile(r'^(?P<start>#define\s+BUILDNUMBER_\S+\s+.*?)(?P<number>\d+)(?P<end>\D+)$')
# Build a new set of lines
new_lines = []
for index in range(len(merge_params.this_lines)):
if merge_params.this_lines[index] \
== merge_params.other_lines[index] \
== merge_params.base_lines[index]:
# If the lines match, that's fine:
new_lines.append(merge_params.this_lines[index])
else:
# Otherwise, check whether they match the
regular expression
m_this =
BuildNumberRE.match(merge_params.this_lines[index])
m_other =
BuildNumberRE.match(merge_params.other_lines[index])
m_base =
BuildNumberRE.match(merge_params.base_lines[index])
if m_this is None or m_other is None or m_base is None:
# No match, not relevant
return ('not_applicable', None)
if (m_this.group('start') != m_other.group('start')) \
or (m_this.group('start') !=
m_base.group('start')) \
or (m_other.group('start') !=
m_base.group('start')):
# Start string is different, don't risk it
return ('not_applicable', None)
if (m_this.group('end') != m_other.group('end')) \
or (m_this.group('end') !=
m_base.group('end')) \
or (m_other.group('end') !=
m_base.group('end')):
# End string is different, don't risk it
return ('not_applicable', None)
# Retrieve the numbers
this_num = int(m_this.group('number'))
other_num = int(m_other.group('number'))
base_num = int(m_base.group('number'))
# Make a new line using the combined build count
new_lines.append(m_this.group('start') \
+ GetNewBuildNumber(base_num,
this_num, other_num) \
+ m_this.group('end'))
return ('success', new_lines)
else:
return ('not_applicable', None)
return ('not_applicable', None)
More information about the bazaar
mailing list