Merge lp:~jelmer/brz/plan-merge into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: Vincent Ladeuil
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/plan-merge
Merge into: lp:brz
Diff against target: 318 lines (+84/-74)
6 files modified
breezy/builtins.py (+10/-1)
breezy/bzr/inventorytree.py (+57/-0)
breezy/bzr/versionedfile.py (+1/-1)
breezy/merge.py (+14/-15)
breezy/tests/per_tree/test_tree.py (+2/-0)
breezy/tree.py (+0/-57)
To merge this branch: bzr merge lp:~jelmer/brz/plan-merge
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+339452@code.launchpad.net

Commit message

Move Tree.plan_file_merge to InventoryTree.plan_file_merge.

Description of the change

Move Tree.plan_file_merge to InventoryTree.plan_file_merge.

Also, mark merge types that require a merge plan as such so that 'bzr merge'
can error out when trying to use --weave or --lca merge with git trees.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/builtins.py'
--- breezy/builtins.py 2018-02-21 13:19:44 +0000
+++ breezy/builtins.py 2018-02-24 18:57:47 +0000
@@ -4381,7 +4381,7 @@
4381 committed to record the result of the merge.4381 committed to record the result of the merge.
43824382
4383 merge refuses to run if there are any uncommitted changes, unless4383 merge refuses to run if there are any uncommitted changes, unless
4384 --force is given. If --force is given, then the changes from the source 4384 --force is given. If --force is given, then the changes from the source
4385 will be merged with the current working tree, including any uncommitted4385 will be merged with the current working tree, including any uncommitted
4386 changes in the tree. The --force option can also be used to create a4386 changes in the tree. The --force option can also be used to create a
4387 merge revision which has more than two parents.4387 merge revision which has more than two parents.
@@ -4610,6 +4610,15 @@
4610 raise errors.BzrCommandError(gettext("Cannot do conflict reduction and"4610 raise errors.BzrCommandError(gettext("Cannot do conflict reduction and"
4611 " show base."))4611 " show base."))
46124612
4613 if (merger.merge_type.requires_file_merge_plan and
4614 (not getattr(merger.this_tree, 'plan_file_merge', None) or
4615 not getattr(merger.other_tree, 'plan_file_merge', None) or
4616 (merger.base_tree is not None and
4617 not getattr(merger.base_tree, 'plan_file_merge', None)))):
4618 raise errors.BzrCommandError(
4619 gettext('Plan file merge unsupported: '
4620 'Merge type incompatible with tree formats.'))
4621
4613 def _get_merger_from_branch(self, tree, location, revision, remember,4622 def _get_merger_from_branch(self, tree, location, revision, remember,
4614 possible_transports, pb):4623 possible_transports, pb):
4615 """Produce a merger from a location, assuming it refers to a branch."""4624 """Produce a merger from a location, assuming it refers to a branch."""
46164625
=== modified file 'breezy/bzr/inventorytree.py'
--- breezy/bzr/inventorytree.py 2018-01-12 08:52:43 +0000
+++ breezy/bzr/inventorytree.py 2018-02-24 18:57:47 +0000
@@ -258,6 +258,63 @@
258 for child in viewvalues(getattr(entry, 'children', {})):258 for child in viewvalues(getattr(entry, 'children', {})):
259 yield child.file_id259 yield child.file_id
260260
261 def _get_plan_merge_data(self, file_id, other, base):
262 from . import versionedfile
263 vf = versionedfile._PlanMergeVersionedFile(file_id)
264 last_revision_a = self._get_file_revision(
265 self.id2path(file_id), file_id, vf, 'this:')
266 last_revision_b = other._get_file_revision(
267 other.id2path(file_id), file_id, vf, 'other:')
268 if base is None:
269 last_revision_base = None
270 else:
271 last_revision_base = base._get_file_revision(
272 base.id2path(file_id), file_id, vf, 'base:')
273 return vf, last_revision_a, last_revision_b, last_revision_base
274
275 def plan_file_merge(self, file_id, other, base=None):
276 """Generate a merge plan based on annotations.
277
278 If the file contains uncommitted changes in this tree, they will be
279 attributed to the 'current:' pseudo-revision. If the file contains
280 uncommitted changes in the other tree, they will be assigned to the
281 'other:' pseudo-revision.
282 """
283 data = self._get_plan_merge_data(file_id, other, base)
284 vf, last_revision_a, last_revision_b, last_revision_base = data
285 return vf.plan_merge(last_revision_a, last_revision_b,
286 last_revision_base)
287
288 def plan_file_lca_merge(self, file_id, other, base=None):
289 """Generate a merge plan based lca-newness.
290
291 If the file contains uncommitted changes in this tree, they will be
292 attributed to the 'current:' pseudo-revision. If the file contains
293 uncommitted changes in the other tree, they will be assigned to the
294 'other:' pseudo-revision.
295 """
296 data = self._get_plan_merge_data(file_id, other, base)
297 vf, last_revision_a, last_revision_b, last_revision_base = data
298 return vf.plan_lca_merge(last_revision_a, last_revision_b,
299 last_revision_base)
300
301 def _get_file_revision(self, path, file_id, vf, tree_revision):
302 """Ensure that file_id, tree_revision is in vf to plan the merge."""
303 if getattr(self, '_repository', None) is None:
304 last_revision = tree_revision
305 parent_keys = [(file_id, t.get_file_revision(path, file_id)) for t in
306 self._iter_parent_trees()]
307 vf.add_lines((file_id, last_revision), parent_keys,
308 self.get_file_lines(path, file_id))
309 repo = self.branch.repository
310 base_vf = repo.texts
311 else:
312 last_revision = self.get_file_revision(path, file_id)
313 base_vf = self._repository.texts
314 if base_vf not in vf.fallback_versionedfiles:
315 vf.fallback_versionedfiles.append(base_vf)
316 return last_revision
317
261318
262class MutableInventoryTree(MutableTree, InventoryTree):319class MutableInventoryTree(MutableTree, InventoryTree):
263320
264321
=== modified file 'breezy/bzr/versionedfile.py'
--- breezy/bzr/versionedfile.py 2017-12-04 23:25:45 +0000
+++ breezy/bzr/versionedfile.py 2018-02-24 18:57:47 +0000
@@ -669,7 +669,7 @@
669 """669 """
670 raise NotImplementedError(self.iter_lines_added_or_present_in_versions)670 raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
671671
672 def plan_merge(self, ver_a, ver_b):672 def plan_merge(self, ver_a, ver_b, base=None):
673 """Return pseudo-annotation indicating how the two versions merge.673 """Return pseudo-annotation indicating how the two versions merge.
674674
675 This is computed between versions a and b and their common675 This is computed between versions a and b and their common
676676
=== modified file 'breezy/merge.py'
--- breezy/merge.py 2017-11-13 22:51:34 +0000
+++ breezy/merge.py 2018-02-24 18:57:47 +0000
@@ -94,7 +94,7 @@
94 """PerFileMerger objects are used by plugins extending merge for breezy.94 """PerFileMerger objects are used by plugins extending merge for breezy.
9595
96 See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.96 See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
97 97
98 :ivar merger: The Merge3Merger performing the merge.98 :ivar merger: The Merge3Merger performing the merge.
99 """99 """
100100
@@ -104,7 +104,7 @@
104104
105 def merge_contents(self, merge_params):105 def merge_contents(self, merge_params):
106 """Attempt to merge the contents of a single file.106 """Attempt to merge the contents of a single file.
107 107
108 :param merge_params: A breezy.merge.MergeFileHookParams108 :param merge_params: A breezy.merge.MergeFileHookParams
109 :return: A tuple of (status, chunks), where status is one of109 :return: A tuple of (status, chunks), where status is one of
110 'not_applicable', 'success', 'conflicted', or 'delete'. If status110 'not_applicable', 'success', 'conflicted', or 'delete'. If status
@@ -174,13 +174,13 @@
174 classes should implement ``merge_text``.174 classes should implement ``merge_text``.
175175
176 See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.176 See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
177 177
178 :ivar affected_files: The configured file paths to merge.178 :ivar affected_files: The configured file paths to merge.
179179
180 :cvar name_prefix: The prefix to use when looking up configuration180 :cvar name_prefix: The prefix to use when looking up configuration
181 details. <name_prefix>_merge_files describes the files targeted by the181 details. <name_prefix>_merge_files describes the files targeted by the
182 hook for example.182 hook for example.
183 183
184 :cvar default_files: The default file paths to merge when no configuration184 :cvar default_files: The default file paths to merge when no configuration
185 is present.185 is present.
186 """186 """
@@ -718,6 +718,7 @@
718 supports_reverse_cherrypick = True718 supports_reverse_cherrypick = True
719 winner_idx = {"this": 2, "other": 1, "conflict": 1}719 winner_idx = {"this": 2, "other": 1, "conflict": 1}
720 supports_lca_trees = True720 supports_lca_trees = True
721 requires_file_merge_plan = False
721722
722 def __init__(self, working_tree, this_tree, base_tree, other_tree,723 def __init__(self, working_tree, this_tree, base_tree, other_tree,
723 interesting_ids=None, reprocess=False, show_base=False,724 interesting_ids=None, reprocess=False, show_base=False,
@@ -1408,7 +1409,7 @@
1408 def merge_contents(self, merge_hook_params):1409 def merge_contents(self, merge_hook_params):
1409 """Fallback merge logic after user installed hooks."""1410 """Fallback merge logic after user installed hooks."""
1410 # This function is used in merge hooks as the fallback instance.1411 # This function is used in merge hooks as the fallback instance.
1411 # Perhaps making this function and the functions it calls be a 1412 # Perhaps making this function and the functions it calls be a
1412 # a separate class would be better.1413 # a separate class would be better.
1413 if merge_hook_params.winner == 'other':1414 if merge_hook_params.winner == 'other':
1414 # OTHER is a straight winner, so replace this contents with other1415 # OTHER is a straight winner, so replace this contents with other
@@ -1483,7 +1484,6 @@
1483 other_lines)1484 other_lines)
1484 file_group.append(trans_id)1485 file_group.append(trans_id)
14851486
1486
1487 def _get_filter_tree_path(self, file_id):1487 def _get_filter_tree_path(self, file_id):
1488 if self.this_tree.supports_content_filtering():1488 if self.this_tree.supports_content_filtering():
1489 # We get the path from the working tree if it exists.1489 # We get the path from the working tree if it exists.
@@ -1667,6 +1667,7 @@
1667 supports_show_base = False1667 supports_show_base = False
1668 supports_reverse_cherrypick = False1668 supports_reverse_cherrypick = False
1669 history_based = True1669 history_based = True
1670 requires_file_merge_plan = True
16701671
1671 def _generate_merge_plan(self, file_id, base):1672 def _generate_merge_plan(self, file_id, base):
1672 return self.this_tree.plan_file_merge(file_id, self.other_tree,1673 return self.this_tree.plan_file_merge(file_id, self.other_tree,
@@ -1721,6 +1722,8 @@
17211722
1722class LCAMerger(WeaveMerger):1723class LCAMerger(WeaveMerger):
17231724
1725 requires_file_merge_plan = True
1726
1724 def _generate_merge_plan(self, file_id, base):1727 def _generate_merge_plan(self, file_id, base):
1725 return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,1728 return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1726 base=base)1729 base=base)
@@ -1728,15 +1731,14 @@
1728class Diff3Merger(Merge3Merger):1731class Diff3Merger(Merge3Merger):
1729 """Three-way merger using external diff3 for text merging"""1732 """Three-way merger using external diff3 for text merging"""
17301733
1734 requires_file_merge_plan = False
1735
1731 def dump_file(self, temp_dir, name, tree, file_id):1736 def dump_file(self, temp_dir, name, tree, file_id):
1732 out_path = osutils.pathjoin(temp_dir, name)1737 out_path = osutils.pathjoin(temp_dir, name)
1733 out_file = open(out_path, "wb")1738 with open(out_path, "wb") as out_file:
1734 try:
1735 in_file = tree.get_file(tree.id2path(file_id), file_id)1739 in_file = tree.get_file(tree.id2path(file_id), file_id)
1736 for line in in_file:1740 for line in in_file:
1737 out_file.write(line)1741 out_file.write(line)
1738 finally:
1739 out_file.close()
1740 return out_path1742 return out_path
17411743
1742 def text_merge(self, file_id, trans_id):1744 def text_merge(self, file_id, trans_id):
@@ -1754,11 +1756,8 @@
1754 status = breezy.patch.diff3(new_file, this, base, other)1756 status = breezy.patch.diff3(new_file, this, base, other)
1755 if status not in (0, 1):1757 if status not in (0, 1):
1756 raise errors.BzrError("Unhandled diff3 exit code")1758 raise errors.BzrError("Unhandled diff3 exit code")
1757 f = open(new_file, 'rb')1759 with open(new_file, 'rb') as f:
1758 try:
1759 self.tt.create_file(f, trans_id)1760 self.tt.create_file(f, trans_id)
1760 finally:
1761 f.close()
1762 if status == 1:1761 if status == 1:
1763 name = self.tt.final_name(trans_id)1762 name = self.tt.final_name(trans_id)
1764 parent_id = self.tt.final_parent(trans_id)1763 parent_id = self.tt.final_parent(trans_id)
@@ -1837,7 +1836,7 @@
18371836
1838class _MergeTypeParameterizer(object):1837class _MergeTypeParameterizer(object):
1839 """Wrap a merge-type class to provide extra parameters.1838 """Wrap a merge-type class to provide extra parameters.
1840 1839
1841 This is hack used by MergeIntoMerger to pass some extra parameters to its1840 This is hack used by MergeIntoMerger to pass some extra parameters to its
1842 merge_type. Merger.do_merge() sets up its own set of parameters to pass to1841 merge_type. Merger.do_merge() sets up its own set of parameters to pass to
1843 the 'merge_type' member. It is difficult override do_merge without1842 the 'merge_type' member. It is difficult override do_merge without
18441843
=== modified file 'breezy/tests/per_tree/test_tree.py'
--- breezy/tests/per_tree/test_tree.py 2018-02-16 19:38:39 +0000
+++ breezy/tests/per_tree/test_tree.py 2018-02-24 18:57:47 +0000
@@ -56,6 +56,8 @@
56 work_b = work_a.controldir.sprout('wtb').open_workingtree()56 work_b = work_a.controldir.sprout('wtb').open_workingtree()
57 self.build_tree_contents([('wta/file', 'b\nc\nd\ne\n')])57 self.build_tree_contents([('wta/file', 'b\nc\nd\ne\n')])
58 tree_a = self.workingtree_to_test_tree(work_a)58 tree_a = self.workingtree_to_test_tree(work_a)
59 if getattr(tree_a, 'plan_file_merge', None) is None:
60 raise tests.TestNotApplicable('Tree does not support plan_file_merge')
59 tree_a.lock_read()61 tree_a.lock_read()
60 self.addCleanup(tree_a.unlock)62 self.addCleanup(tree_a.unlock)
61 self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])63 self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
6264
=== modified file 'breezy/tree.py'
--- breezy/tree.py 2018-02-17 02:57:14 +0000
+++ breezy/tree.py 2018-02-24 18:57:47 +0000
@@ -476,46 +476,6 @@
476 """476 """
477 raise NotImplementedError(self.annotate_iter)477 raise NotImplementedError(self.annotate_iter)
478478
479 def _get_plan_merge_data(self, file_id, other, base):
480 from .bzr import versionedfile
481 vf = versionedfile._PlanMergeVersionedFile(file_id)
482 last_revision_a = self._get_file_revision(
483 self.id2path(file_id), file_id, vf, 'this:')
484 last_revision_b = other._get_file_revision(
485 other.id2path(file_id), file_id, vf, 'other:')
486 if base is None:
487 last_revision_base = None
488 else:
489 last_revision_base = base._get_file_revision(
490 base.id2path(file_id), file_id, vf, 'base:')
491 return vf, last_revision_a, last_revision_b, last_revision_base
492
493 def plan_file_merge(self, file_id, other, base=None):
494 """Generate a merge plan based on annotations.
495
496 If the file contains uncommitted changes in this tree, they will be
497 attributed to the 'current:' pseudo-revision. If the file contains
498 uncommitted changes in the other tree, they will be assigned to the
499 'other:' pseudo-revision.
500 """
501 data = self._get_plan_merge_data(file_id, other, base)
502 vf, last_revision_a, last_revision_b, last_revision_base = data
503 return vf.plan_merge(last_revision_a, last_revision_b,
504 last_revision_base)
505
506 def plan_file_lca_merge(self, file_id, other, base=None):
507 """Generate a merge plan based lca-newness.
508
509 If the file contains uncommitted changes in this tree, they will be
510 attributed to the 'current:' pseudo-revision. If the file contains
511 uncommitted changes in the other tree, they will be assigned to the
512 'other:' pseudo-revision.
513 """
514 data = self._get_plan_merge_data(file_id, other, base)
515 vf, last_revision_a, last_revision_b, last_revision_base = data
516 return vf.plan_lca_merge(last_revision_a, last_revision_b,
517 last_revision_base)
518
519 def _iter_parent_trees(self):479 def _iter_parent_trees(self):
520 """Iterate through parent trees, defaulting to Tree.revision_tree."""480 """Iterate through parent trees, defaulting to Tree.revision_tree."""
521 for revision_id in self.get_parent_ids():481 for revision_id in self.get_parent_ids():
@@ -524,23 +484,6 @@
524 except errors.NoSuchRevisionInTree:484 except errors.NoSuchRevisionInTree:
525 yield self.repository.revision_tree(revision_id)485 yield self.repository.revision_tree(revision_id)
526486
527 def _get_file_revision(self, path, file_id, vf, tree_revision):
528 """Ensure that file_id, tree_revision is in vf to plan the merge."""
529 if getattr(self, '_repository', None) is None:
530 last_revision = tree_revision
531 parent_keys = [(file_id, t.get_file_revision(path, file_id)) for t in
532 self._iter_parent_trees()]
533 vf.add_lines((file_id, last_revision), parent_keys,
534 self.get_file_lines(path, file_id))
535 repo = self.branch.repository
536 base_vf = repo.texts
537 else:
538 last_revision = self.get_file_revision(path, file_id)
539 base_vf = self._repository.texts
540 if base_vf not in vf.fallback_versionedfiles:
541 vf.fallback_versionedfiles.append(base_vf)
542 return last_revision
543
544 def _check_retrieved(self, ie, f):487 def _check_retrieved(self, ie, f):
545 if not __debug__:488 if not __debug__:
546 return489 return

Subscribers

People subscribed via source and target branches