53
55
# TODO: Report back as changes are merged in
55
def _get_tree(treespec, local_branch=None, possible_transports=None):
56
from bzrlib import workingtree
57
location, revno = treespec
59
tree = workingtree.WorkingTree.open_containing(location)[0]
60
return tree.branch, tree
61
branch = Branch.open_containing(location, possible_transports)[0]
63
revision_id = branch.last_revision()
65
revision_id = branch.get_rev_id(revno)
66
if revision_id is None:
67
revision_id = NULL_REVISION
68
return branch, _get_revid_tree(branch, revision_id, local_branch)
71
def _get_revid_tree(branch, revision_id, local_branch):
72
if revision_id is None:
73
base_tree = branch.bzrdir.open_workingtree()
75
if local_branch is not None:
76
if local_branch.base != branch.base:
77
local_branch.fetch(branch, revision_id)
78
base_tree = local_branch.repository.revision_tree(revision_id)
80
base_tree = branch.repository.revision_tree(revision_id)
84
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
85
if revision_id is None:
87
if local_branch is not None:
88
if local_branch.base != tree.branch.base:
89
local_branch.fetch(tree.branch, revision_id)
90
return local_branch.repository.revision_tree(revision_id)
91
return tree.branch.repository.revision_tree(revision_id)
94
58
def transform_tree(from_tree, to_tree, interesting_ids=None):
95
59
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
103
67
object.__init__(self)
104
68
assert this_tree is not None, "this_tree is required"
105
69
self.this_branch = this_branch
106
self.this_basis = this_branch.last_revision()
70
self.this_basis = _mod_revision.ensure_null(
71
this_branch.last_revision())
107
72
self.this_rev_id = None
108
73
self.this_tree = this_tree
109
74
self.this_revision_tree = None
122
87
self.recurse = recurse
123
88
self.change_reporter = change_reporter
125
def revision_tree(self, revision_id):
126
return self.this_branch.repository.revision_tree(revision_id)
89
self._cached_trees = {}
91
def revision_tree(self, revision_id, branch=None):
92
if revision_id not in self._cached_trees:
94
branch = self.this_branch
96
tree = self.this_tree.revision_tree(revision_id)
97
except errors.NoSuchRevisionInTree:
98
tree = branch.repository.revision_tree(revision_id)
99
self._cached_trees[revision_id] = tree
100
return self._cached_trees[revision_id]
102
def _get_tree(self, treespec, possible_transports=None):
103
from bzrlib import workingtree
104
location, revno = treespec
106
tree = workingtree.WorkingTree.open_containing(location)[0]
107
return tree.branch, tree
108
branch = Branch.open_containing(location, possible_transports)[0]
110
revision_id = branch.last_revision()
112
revision_id = branch.get_rev_id(revno)
113
revision_id = ensure_null(revision_id)
114
return branch, self.revision_tree(revision_id, branch)
128
116
def ensure_revision_trees(self):
129
117
if self.this_revision_tree is None:
130
self.this_basis_tree = self.this_branch.repository.revision_tree(
118
self.this_basis_tree = self.revision_tree(self.this_basis)
132
119
if self.this_basis == self.this_rev_id:
133
120
self.this_revision_tree = self.this_basis_tree
164
151
raise BzrCommandError("Working tree has uncommitted changes.")
166
153
def compare_basis(self):
167
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
155
basis_tree = self.revision_tree(self.this_tree.last_revision())
156
except errors.RevisionNotPresent:
157
basis_tree = self.this_tree.basis_tree()
158
changes = self.this_tree.changes_from(basis_tree)
168
159
if not changes.has_changed():
169
160
self.this_rev_id = self.this_basis
171
162
def set_interesting_files(self, file_list):
172
163
self.interesting_files = file_list
174
def _set_interesting_files(self, file_list):
175
"""Set the list of interesting ids from a list of files."""
176
if file_list is None:
177
self.interesting_ids = None
180
interesting_ids = set()
181
for path in file_list:
183
# TODO: jam 20070226 The trees are not locked at this time,
184
# wouldn't it make merge faster if it locks everything in the
185
# beginning? It locks at do_merge time, but this happens
187
for tree in (self.this_tree, self.base_tree, self.other_tree):
188
file_id = tree.path2id(path)
189
if file_id is not None:
190
interesting_ids.add(file_id)
193
raise NotVersionedError(path=path)
194
self.interesting_ids = interesting_ids
196
165
def set_pending(self):
197
if not self.base_is_ancestor:
199
if self.other_rev_id is None:
201
ancestry = set(self.this_branch.repository.get_ancestry(
202
self.this_basis, topo_sorted=False))
203
if self.other_rev_id in ancestry:
205
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
166
if not self.base_is_ancestor or not self.base_is_other_ancestor:
170
def _add_parent(self):
171
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
172
new_parent_trees = []
173
for revision_id in new_parents:
175
tree = self.revision_tree(revision_id)
176
except errors.RevisionNotPresent:
180
new_parent_trees.append((revision_id, tree))
182
self.this_tree.set_parent_trees(new_parent_trees,
183
allow_leftmost_as_ghost=True)
185
for _revision_id, tree in new_parent_trees:
207
189
def set_other(self, other_revision, possible_transports=None):
208
190
"""Set the revision and tree to merge from.
212
194
:param other_revision: The [path, revision] list to merge from.
214
self.other_branch, self.other_tree = _get_tree(other_revision,
196
self.other_branch, self.other_tree = self._get_tree(other_revision,
217
198
if other_revision[1] == -1:
218
self.other_rev_id = self.other_branch.last_revision()
219
if self.other_rev_id is None:
199
self.other_rev_id = _mod_revision.ensure_null(
200
self.other_branch.last_revision())
201
if _mod_revision.is_null(self.other_rev_id):
220
202
raise NoCommits(self.other_branch)
221
203
self.other_basis = self.other_rev_id
222
204
elif other_revision[1] is not None:
227
209
self.other_basis = self.other_branch.last_revision()
228
210
if self.other_basis is None:
229
211
raise NoCommits(self.other_branch)
230
if self.other_branch.base != self.this_branch.base:
231
self.this_branch.fetch(self.other_branch,
232
last_revision=self.other_basis)
212
if self.other_rev_id is not None:
213
self._cached_trees[self.other_rev_id] = self.other_tree
214
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
234
216
def set_other_revision(self, revision_id, other_branch):
235
217
"""Set 'other' based on a branch and revision id
240
222
self.other_rev_id = revision_id
241
223
self.other_branch = other_branch
242
self.this_branch.fetch(other_branch, self.other_rev_id)
224
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
243
225
self.other_tree = self.revision_tree(revision_id)
244
226
self.other_basis = revision_id
228
def _maybe_fetch(self, source, target, revision_id):
229
if (source.repository.bzrdir.root_transport.base !=
230
target.repository.bzrdir.root_transport.base):
231
target.fetch(source, revision_id)
246
233
def find_base(self):
247
self.set_base([None, None])
234
this_repo = self.this_branch.repository
235
graph = this_repo.get_graph()
236
revisions = [ensure_null(self.this_basis),
237
ensure_null(self.other_basis)]
238
if NULL_REVISION in revisions:
239
self.base_rev_id = NULL_REVISION
241
self.base_rev_id = graph.find_unique_lca(*revisions)
242
if self.base_rev_id == NULL_REVISION:
243
raise UnrelatedBranches()
244
self.base_tree = self.revision_tree(self.base_rev_id)
245
self.base_is_ancestor = True
246
self.base_is_other_ancestor = True
249
248
def set_base(self, base_revision):
250
249
"""Set the base revision to use for the merge.
254
253
mutter("doing merge() with no base_revision specified")
255
254
if base_revision == [None, None]:
257
pb = ui.ui_factory.nested_progress_bar()
259
this_repo = self.this_branch.repository
260
graph = this_repo.get_graph()
261
revisions = [ensure_null(self.this_basis),
262
ensure_null(self.other_basis)]
263
if NULL_REVISION in revisions:
264
self.base_rev_id = NULL_REVISION
266
self.base_rev_id = graph.find_unique_lca(*revisions)
267
if self.base_rev_id == NULL_REVISION:
268
raise UnrelatedBranches()
271
except NoCommonAncestor:
272
raise UnrelatedBranches()
273
self.base_tree = _get_revid_tree_from_tree(self.this_tree,
276
self.base_is_ancestor = True
278
base_branch, self.base_tree = _get_tree(base_revision)
257
base_branch, self.base_tree = self._get_tree(base_revision)
279
258
if base_revision[1] == -1:
280
259
self.base_rev_id = base_branch.last_revision()
281
260
elif base_revision[1] is None:
282
self.base_rev_id = None
261
self.base_rev_id = _mod_revision.NULL_REVISION
284
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
285
if self.this_branch.base != base_branch.base:
286
self.this_branch.fetch(base_branch)
263
self.base_rev_id = _mod_revision.ensure_null(
264
base_branch.get_rev_id(base_revision[1]))
265
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
287
266
self.base_is_ancestor = is_ancestor(self.this_basis,
288
267
self.base_rev_id,
289
268
self.this_branch)
269
self.base_is_other_ancestor = is_ancestor(self.other_basis,
291
273
def do_merge(self):
292
274
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
347
329
return len(merge.cooked_conflicts)
349
def regen_inventory(self, new_entries):
350
old_entries = self.this_tree.read_working_inventory()
354
for path, file_id in new_entries:
357
new_entries_map[file_id] = path
359
def id2path(file_id):
360
path = new_entries_map.get(file_id)
363
entry = old_entries[file_id]
364
if entry.parent_id is None:
366
return pathjoin(id2path(entry.parent_id), entry.name)
368
for file_id in old_entries:
369
entry = old_entries[file_id]
370
path = id2path(file_id)
371
if file_id in self.base_tree.inventory:
372
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
374
executable = getattr(entry, 'executable', False)
375
new_inventory[file_id] = (path, file_id, entry.parent_id,
376
entry.kind, executable)
378
by_path[path] = file_id
383
for path, file_id in new_entries:
385
del new_inventory[file_id]
388
new_path_list.append((path, file_id))
389
if file_id not in old_entries:
391
# Ensure no file is added before its parent
393
for path, file_id in new_path_list:
397
parent = by_path[os.path.dirname(path)]
398
abspath = pathjoin(self.this_tree.basedir, path)
399
kind = osutils.file_kind(abspath)
400
if file_id in self.base_tree.inventory:
401
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
404
new_inventory[file_id] = (path, file_id, parent, kind, executable)
405
by_path[path] = file_id
407
# Get a list in insertion order
408
new_inventory_list = new_inventory.values()
409
mutter ("""Inventory regeneration:
410
old length: %i insertions: %i deletions: %i new_length: %i"""\
411
% (len(old_entries), insertions, deletions,
412
len(new_inventory_list)))
413
assert len(new_inventory_list) == len(old_entries) + insertions\
415
new_inventory_list.sort()
416
return new_inventory_list
419
332
class Merge3Merger(object):
420
333
"""Three-way merger that uses the merge3 text merger"""