1
# bzr-builder: a bzr plugin to constuct trees based on recipes
2
# Copyright 2010 Canonical Ltd.
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU General Public License version 3, as published
6
# by the Free Software Foundation.
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranties of
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
# PURPOSE. See the GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License along
14
# with this program. If not, see <http://www.gnu.org/licenses/>.
16
"""Copies/backports features from recent bzrlib versions.
18
This allows bzr-builder to continue to work with older bzrlib versions while
19
using features from newer versions.
22
# NOTE FOR DEVELOPERS: with each backport please include a comment saying which
23
# version of bzr you are backporting from, to make it easier to copy bugfixes
24
# made in bzr (and so that we know which things we can drop from this module if
25
# bzr-builder ever raises which version of bzr it depends on).
27
from bzrlib.merge import (
35
revision as _mod_revision,
40
# Backport of bzrlib.merge.MergeIntoMerger, introduced in bzr 2.2rc1.
41
# (Also backports PathNotInTree, _MergeTypeParameterizer, MergeIntoMergeType)
42
class PathNotInTree(errors.BzrError):
44
_fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
46
def __init__(self, path, tree):
47
errors.BzrError.__init__(self, path=path, tree=tree)
50
class MergeIntoMerger(Merger):
51
"""Merger that understands other_tree will be merged into a subdir.
53
This also changes the Merger api so that it uses real Branch, revision_id,
54
and RevisonTree objects, rather than using revision specs.
57
def __init__(self, this_tree, other_branch, other_tree, target_subdir,
58
source_subpath, other_rev_id=None):
59
"""Create a new MergeIntoMerger object.
61
source_subpath in other_tree will be effectively copied to
62
target_subdir in this_tree.
64
:param this_tree: The tree that we will be merging into.
65
:param other_branch: The Branch we will be merging from.
66
:param other_tree: The RevisionTree object we want to merge.
67
:param target_subdir: The relative path where we want to merge
68
other_tree into this_tree
69
:param source_subpath: The relative path specifying the subtree of
70
other_tree to merge into this_tree.
72
# It is assumed that we are merging a tree that is not in our current
73
# ancestry, which means we are using the "EmptyTree" as our basis.
74
null_ancestor_tree = this_tree.branch.repository.revision_tree(
75
_mod_revision.NULL_REVISION)
76
super(MergeIntoMerger, self).__init__(
77
this_branch=this_tree.branch,
79
other_tree=other_tree,
80
base_tree=null_ancestor_tree,
82
self._target_subdir = target_subdir
83
self._source_subpath = source_subpath
84
self.other_branch = other_branch
85
if other_rev_id is None:
86
other_rev_id = other_tree.get_revision_id()
87
self.other_rev_id = self.other_basis = other_rev_id
88
self.base_is_ancestor = True
89
self.backup_files = True
90
self.merge_type = Merge3Merger
91
self.show_base = False
92
self.reprocess = False
93
self.interesting_ids = None
94
self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
95
target_subdir=self._target_subdir,
96
source_subpath=self._source_subpath)
97
if self._source_subpath != '':
98
# If this isn't a partial merge make sure the revisions will be
100
self._maybe_fetch(self.other_branch, self.this_branch,
103
def set_pending(self):
104
if self._source_subpath != '':
106
Merger.set_pending(self)
109
class _MergeTypeParameterizer(object):
110
"""Wrap a merge-type class to provide extra parameters.
112
This is hack used by MergeIntoMerger to pass some extra parameters to its
113
merge_type. Merger.do_merge() sets up its own set of parameters to pass to
114
the 'merge_type' member. It is difficult override do_merge without
115
re-writing the whole thing, so instead we create a wrapper which will pass
116
the extra parameters.
119
def __init__(self, merge_type, **kwargs):
120
self._extra_kwargs = kwargs
121
self._merge_type = merge_type
123
def __call__(self, *args, **kwargs):
124
kwargs.update(self._extra_kwargs)
125
return self._merge_type(*args, **kwargs)
127
def __getattr__(self, name):
128
return getattr(self._merge_type, name)
131
class MergeIntoMergeType(Merge3Merger):
132
"""Merger that incorporates a tree (or part of a tree) into another."""
134
# Backport note: the definition of _finish_computing_transform is copied
135
# from Merge3Merger in bzr 2.2 (it is supposed to be inherited from
136
# Merge3Merger, but was only introduced in 2.2).
137
def _finish_computing_transform(self):
138
"""Finalize the transform and report the changes.
140
This is the second half of _compute_transform.
142
child_pb = ui.ui_factory.nested_progress_bar()
144
fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
145
lambda t, c: transform.conflict_pass(t, c, self.other_tree))
148
if self.change_reporter is not None:
149
from bzrlib import delta
150
delta.report_changes(
151
self.tt.iter_changes(), self.change_reporter)
152
self.cook_conflicts(fs_conflicts)
153
from bzrlib import trace
154
for conflict in self.cooked_conflicts:
155
trace.warning(conflict)
157
def __init__(self, *args, **kwargs):
158
"""Initialize the merger object.
160
:param args: See Merge3Merger.__init__'s args.
161
:param kwargs: See Merge3Merger.__init__'s keyword args, except for
162
source_subpath and target_subdir.
163
:keyword source_subpath: The relative path specifying the subtree of
164
other_tree to merge into this_tree.
165
:keyword target_subdir: The relative path where we want to merge
166
other_tree into this_tree
168
# All of the interesting work happens during Merge3Merger.__init__(),
169
# so we have have to hack in to get our extra parameters set.
170
self._source_subpath = kwargs.pop('source_subpath')
171
self._target_subdir = kwargs.pop('target_subdir')
172
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
174
def _compute_transform(self):
175
child_pb = ui.ui_factory.nested_progress_bar()
177
entries = self._entries_to_incorporate()
178
entries = list(entries)
179
for num, (entry, parent_id) in enumerate(entries):
180
child_pb.update('Preparing file merge', num, len(entries))
181
parent_trans_id = self.tt.trans_id_file_id(parent_id)
182
trans_id = transform.new_by_entry(self.tt, entry,
183
parent_trans_id, self.other_tree)
186
self._finish_computing_transform()
188
def _entries_to_incorporate(self):
189
"""Yields pairs of (inventory_entry, new_parent)."""
190
other_inv = self.other_tree.inventory
191
subdir_id = other_inv.path2id(self._source_subpath)
192
if subdir_id is None:
193
# XXX: The error would be clearer if it gave the URL of the source
194
# branch, but we don't have a reference to that here.
195
raise PathNotInTree(self._source_subpath, "Source tree")
196
subdir = other_inv[subdir_id]
197
parent_in_target = osutils.dirname(self._target_subdir)
198
target_id = self.this_tree.inventory.path2id(parent_in_target)
199
if target_id is None:
200
raise PathNotInTree(self._target_subdir, "Target tree")
201
name_in_target = osutils.basename(self._target_subdir)
202
merge_into_root = subdir.copy()
203
merge_into_root.name = name_in_target
204
if merge_into_root.file_id in self.this_tree.inventory:
205
# Give the root a new file-id.
206
# This can happen fairly easily if the directory we are
207
# incorporating is the root, and both trees have 'TREE_ROOT' as
208
# their root_id. Users will expect this to Just Work, so we
209
# change the file-id here.
210
# Non-root file-ids could potentially conflict too. That's really
211
# an edge case, so we don't do anything special for those. We let
212
# them cause conflicts.
213
merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
214
yield (merge_into_root, target_id)
215
if subdir.kind != 'directory':
216
# No children, so we are done.
218
for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
219
parent_id = entry.parent_id
220
if parent_id == subdir.file_id:
221
# The root's parent ID has changed, so make sure children of
222
# the root refer to the new ID.
223
parent_id = merge_into_root.file_id
224
yield (entry, parent_id)