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