17
17
"""Merge hook for bzr's NEWS file.
19
Install this as a plugin, e.g:
21
cp contrib/news-file-merge-hook.py ~/.bazaar/plugins/news_merge.py
19
To enable this plugin, add a section to your branch.conf or location.conf
23
news_merge_files = NEWS
24
news_merge_files:policy = recurse
26
The news_merge_files config option takes a list of file paths, separated by
31
* if there's a conflict in more than just bullet points, this doesn't yet know
32
how to resolve that, so bzr will fallback to the default line-based merge.
35
# Put most of the code in a separate module that we lazy-import to keep the
36
# overhead of this plugin as minimal as possible.
24
37
from bzrlib.lazy_import import lazy_import
25
38
lazy_import(globals(), """
26
from bzrlib.plugins.news_merge.parser import simple_parse
27
from bzrlib import merge3
39
from bzrlib.plugins.news_merge.news_merge import news_merger
30
42
from bzrlib.merge import Merger
33
45
def news_merge_hook(params):
46
"""Merger.merge_file_content hook function for bzr-format NEWS files."""
47
# First, check whether this custom merge logic should be used. We expect
48
# most files should not be merged by this file.
34
49
if params.winner == 'other':
35
50
# OTHER is a straight winner, rely on default merge.
36
51
return 'not_applicable', None
37
elif params.is_file_merge():
38
# THIS and OTHER are both files. That's a good start.
52
elif not params.is_file_merge():
53
# THIS and OTHER aren't both files.
54
return 'not_applicable', None
55
elif not filename_matches_config(params):
56
# The filename isn't listed in the 'news_merge_files' config option.
57
return 'not_applicable', None
58
return news_merger(params)
61
def filename_matches_config(params):
62
config = params.merger.this_branch.get_config()
63
affected_files = config.get_user_option('news_merge_files')
39
65
filename = params.merger.this_tree.id2path(params.file_id)
40
if filename != 'NEWS':
41
return 'not_applicable', None
42
return news_merger(params)
44
return 'not_applicable', None
46
magic_marker = '|NEWS-MERGE-MAGIC-MARKER|'
48
def blocks_to_fakelines(blocks):
49
for kind, text in blocks:
50
yield '%s%s%s' % (kind, magic_marker, text)
52
def fakelines_to_lines(fakelines):
53
for fakeline in fakelines:
54
yield fakeline.split(magic_marker, 1)[1] + '\n'
58
return s.replace('`', '').lower()
60
def news_merger(params):
62
return list(blocks_to_fakelines(simple_parse(''.join(lines))))
63
this_lines = munge(params.this_lines)
64
other_lines = munge(params.other_lines)
65
base_lines = munge(params.base_lines)
66
m3 = merge3.Merge3(base_lines, this_lines, other_lines)
68
for group in m3.merge_groups():
69
if group[0] == 'conflict':
71
# are all the conflicting lines bullets? If so, we can merge this.
72
for line_set in [base, a, b]:
74
if not line.startswith('bullet'):
76
# Maybe the default merge can cope.
77
return 'not_applicable', None
78
new_in_a = set(a).difference(base)
79
new_in_b = set(b).difference(base)
80
all_new = new_in_a.union(new_in_b)
81
deleted_in_a = set(base).difference(a)
82
deleted_in_b = set(base).difference(b)
83
final = all_new.difference(deleted_in_a).difference(deleted_in_b)
84
final = sorted(final, key=sort_key)
85
result_lines.extend(final)
87
result_lines.extend(group[1])
88
return 'success', list(fakelines_to_lines(result_lines))
66
if filename in affected_files:
91
71
Merger.hooks.install_named_hook(