43
39
from bzrlib.merge3 import Merge3
44
from bzrlib.osutils import rename, pathjoin
41
from bzrlib.osutils import rename, pathjoin, rmtree
45
42
from progress import DummyProgress, ProgressPhase
46
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
47
44
from bzrlib.textfile import check_text_lines
48
45
from bzrlib.trace import mutter, warning, note
49
46
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
50
conflict_pass, FinalPaths, create_by_entry,
51
unique_add, ROOT_PARENT)
47
FinalPaths, create_by_entry, unique_add)
52
48
from bzrlib.versionedfile import WeaveMerge
53
49
from bzrlib import ui
55
51
# TODO: Report back as changes are merged in
53
def _get_tree(treespec, local_branch=None):
54
location, revno = treespec
55
branch = Branch.open_containing(location)[0]
59
revision = branch.last_revision()
61
revision = branch.get_rev_id(revno)
63
revision = NULL_REVISION
64
return branch, _get_revid_tree(branch, revision, local_branch)
67
def _get_revid_tree(branch, revision, local_branch):
69
base_tree = branch.bzrdir.open_workingtree()
71
if local_branch is not None:
72
if local_branch.base != branch.base:
73
local_branch.fetch(branch, revision)
74
base_tree = local_branch.repository.revision_tree(revision)
76
base_tree = branch.repository.revision_tree(revision)
58
80
def transform_tree(from_tree, to_tree, interesting_ids=None):
59
81
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
63
85
class Merger(object):
64
def __init__(self, this_branch, other_tree=None, base_tree=None,
65
this_tree=None, pb=DummyProgress(), change_reporter=None,
86
def __init__(self, this_branch, other_tree=None, base_tree=None,
87
this_tree=None, pb=DummyProgress()):
67
88
object.__init__(self)
68
89
assert this_tree is not None, "this_tree is required"
69
90
self.this_branch = this_branch
70
self.this_basis = _mod_revision.ensure_null(
71
this_branch.last_revision())
91
self.this_basis = this_branch.last_revision()
72
92
self.this_rev_id = None
73
93
self.this_tree = this_tree
74
94
self.this_revision_tree = None
75
95
self.this_basis_tree = None
76
96
self.other_tree = other_tree
77
self.other_branch = None
78
97
self.base_tree = base_tree
79
98
self.ignore_zero = False
80
99
self.backup_files = False
81
100
self.interesting_ids = None
82
self.interesting_files = None
83
101
self.show_base = False
84
102
self.reprocess = False
87
self.recurse = recurse
88
self.change_reporter = change_reporter
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)
107
def revision_tree(self, revision_id):
108
return self.this_branch.repository.revision_tree(revision_id)
116
110
def ensure_revision_trees(self):
117
111
if self.this_revision_tree is None:
118
self.this_basis_tree = self.revision_tree(self.this_basis)
112
self.this_basis_tree = self.this_branch.repository.revision_tree(
119
114
if self.this_basis == self.this_rev_id:
120
115
self.this_revision_tree = self.this_basis_tree
122
117
if self.other_rev_id is None:
123
118
other_basis_tree = self.revision_tree(self.other_basis)
124
changes = other_basis_tree.changes_from(self.other_tree)
119
changes = compare_trees(self.other_tree, other_basis_tree)
125
120
if changes.has_changed():
126
121
raise WorkingTreeNotRevision(self.this_tree)
127
122
other_rev_id = self.other_basis
144
139
def check_basis(self, check_clean, require_commits=True):
145
140
if self.this_basis is None and require_commits is True:
146
raise BzrCommandError("This branch has no commits."
147
" (perhaps you would prefer 'bzr pull')")
141
raise BzrCommandError("This branch has no commits")
149
143
self.compare_basis()
150
144
if self.this_basis != self.this_rev_id:
151
145
raise BzrCommandError("Working tree has uncommitted changes.")
153
147
def compare_basis(self):
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)
148
changes = compare_trees(self.this_tree,
149
self.this_tree.basis_tree(), False)
159
150
if not changes.has_changed():
160
151
self.this_rev_id = self.this_basis
162
153
def set_interesting_files(self, file_list):
163
self.interesting_files = file_list
155
self._set_interesting_files(file_list)
156
except NotVersionedError, e:
157
raise BzrCommandError("%s is not a source file in any"
160
def _set_interesting_files(self, file_list):
161
"""Set the list of interesting ids from a list of files."""
162
if file_list is None:
163
self.interesting_ids = None
166
interesting_ids = set()
167
for path in file_list:
169
for tree in (self.this_tree, self.base_tree, self.other_tree):
170
file_id = tree.inventory.path2id(path)
171
if file_id is not None:
172
interesting_ids.add(file_id)
175
raise NotVersionedError(path=path)
176
self.interesting_ids = interesting_ids
165
178
def set_pending(self):
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:
189
def set_other(self, other_revision, possible_transports=None):
190
"""Set the revision and tree to merge from.
192
This sets the other_tree, other_rev_id, other_basis attributes.
194
:param other_revision: The [path, revision] list to merge from.
196
self.other_branch, self.other_tree = self._get_tree(other_revision,
179
if not self.base_is_ancestor:
181
if self.other_rev_id is None:
183
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
184
if self.other_rev_id in ancestry:
186
self.this_tree.add_pending_merge(self.other_rev_id)
188
def set_other(self, other_revision):
189
other_branch, self.other_tree = _get_tree(other_revision,
198
191
if other_revision[1] == -1:
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):
202
raise NoCommits(self.other_branch)
192
self.other_rev_id = other_branch.last_revision()
193
if self.other_rev_id is None:
194
raise NoCommits(other_branch)
203
195
self.other_basis = self.other_rev_id
204
196
elif other_revision[1] is not None:
205
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
197
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
206
198
self.other_basis = self.other_rev_id
208
200
self.other_rev_id = None
209
self.other_basis = self.other_branch.last_revision()
201
self.other_basis = other_branch.last_revision()
210
202
if self.other_basis is None:
211
raise NoCommits(self.other_branch)
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)
216
def set_other_revision(self, revision_id, other_branch):
217
"""Set 'other' based on a branch and revision id
219
:param revision_id: The revision to use for a tree
220
:param other_branch: The branch containing this tree
222
self.other_rev_id = revision_id
223
self.other_branch = other_branch
224
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
225
self.other_tree = self.revision_tree(revision_id)
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)
203
raise NoCommits(other_branch)
204
if other_branch.base != self.this_branch.base:
205
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
233
207
def find_base(self):
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
208
self.set_base([None, None])
248
210
def set_base(self, base_revision):
249
"""Set the base revision to use for the merge.
251
:param base_revision: A 2-list containing a path and revision number.
253
211
mutter("doing merge() with no base_revision specified")
254
212
if base_revision == [None, None]:
214
pb = bzrlib.ui.ui_factory.nested_progress_bar()
216
this_repo = self.this_branch.repository
217
self.base_rev_id = common_ancestor(self.this_basis,
222
except NoCommonAncestor:
223
raise UnrelatedBranches()
224
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
226
self.base_is_ancestor = True
257
base_branch, self.base_tree = self._get_tree(base_revision)
228
base_branch, self.base_tree = _get_tree(base_revision)
258
229
if base_revision[1] == -1:
259
230
self.base_rev_id = base_branch.last_revision()
260
231
elif base_revision[1] is None:
261
self.base_rev_id = _mod_revision.NULL_REVISION
232
self.base_rev_id = None
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)
234
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
235
if self.this_branch.base != base_branch.base:
236
self.this_branch.fetch(base_branch)
266
237
self.base_is_ancestor = is_ancestor(self.this_basis,
267
238
self.base_rev_id,
268
239
self.this_branch)
269
self.base_is_other_ancestor = is_ancestor(self.other_basis,
273
241
def do_merge(self):
274
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
275
'other_tree': self.other_tree,
242
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
243
'other_tree': self.other_tree,
276
244
'interesting_ids': self.interesting_ids,
277
'interesting_files': self.interesting_files,
279
246
if self.merge_type.requires_base:
280
247
kwargs['base_tree'] = self.base_tree
288
255
elif self.show_base:
289
256
raise BzrError("Showing base is not supported for this"
290
257
" merge type. %s" % self.merge_type)
291
self.this_tree.lock_tree_write()
292
if self.base_tree is not None:
293
self.base_tree.lock_read()
294
if self.other_tree is not None:
295
self.other_tree.lock_read()
297
merge = self.merge_type(pb=self._pb,
298
change_reporter=self.change_reporter,
300
if self.recurse == 'down':
301
for path, file_id in self.this_tree.iter_references():
302
sub_tree = self.this_tree.get_nested_tree(file_id, path)
303
other_revision = self.other_tree.get_reference_revision(
305
if other_revision == sub_tree.last_revision():
307
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
308
sub_merge.merge_type = self.merge_type
309
relpath = self.this_tree.relpath(path)
310
other_branch = self.other_branch.reference_parent(file_id, relpath)
311
sub_merge.set_other_revision(other_revision, other_branch)
312
base_revision = self.base_tree.get_reference_revision(file_id)
313
sub_merge.base_tree = \
314
sub_tree.branch.repository.revision_tree(base_revision)
318
if self.other_tree is not None:
319
self.other_tree.unlock()
320
if self.base_tree is not None:
321
self.base_tree.unlock()
322
self.this_tree.unlock()
258
merge = self.merge_type(pb=self._pb, **kwargs)
323
259
if len(merge.cooked_conflicts) == 0:
324
260
if not self.ignore_zero:
325
261
note("All changes applied successfully.")
329
265
return len(merge.cooked_conflicts)
267
def regen_inventory(self, new_entries):
268
old_entries = self.this_tree.read_working_inventory()
272
for path, file_id in new_entries:
275
new_entries_map[file_id] = path
277
def id2path(file_id):
278
path = new_entries_map.get(file_id)
281
entry = old_entries[file_id]
282
if entry.parent_id is None:
284
return pathjoin(id2path(entry.parent_id), entry.name)
286
for file_id in old_entries:
287
entry = old_entries[file_id]
288
path = id2path(file_id)
289
if file_id in self.base_tree.inventory:
290
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
292
executable = getattr(entry, 'executable', False)
293
new_inventory[file_id] = (path, file_id, entry.parent_id,
294
entry.kind, executable)
296
by_path[path] = file_id
301
for path, file_id in new_entries:
303
del new_inventory[file_id]
306
new_path_list.append((path, file_id))
307
if file_id not in old_entries:
309
# Ensure no file is added before its parent
311
for path, file_id in new_path_list:
315
parent = by_path[os.path.dirname(path)]
316
abspath = pathjoin(self.this_tree.basedir, path)
317
kind = bzrlib.osutils.file_kind(abspath)
318
if file_id in self.base_tree.inventory:
319
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
322
new_inventory[file_id] = (path, file_id, parent, kind, executable)
323
by_path[path] = file_id
325
# Get a list in insertion order
326
new_inventory_list = new_inventory.values()
327
mutter ("""Inventory regeneration:
328
old length: %i insertions: %i deletions: %i new_length: %i"""\
329
% (len(old_entries), insertions, deletions,
330
len(new_inventory_list)))
331
assert len(new_inventory_list) == len(old_entries) + insertions\
333
new_inventory_list.sort()
334
return new_inventory_list
332
337
class Merge3Merger(object):
333
338
"""Three-way merger that uses the merge3 text merger"""
335
340
supports_reprocess = True
336
341
supports_show_base = True
337
342
history_based = False
338
winner_idx = {"this": 2, "other": 1, "conflict": 1}
340
344
def __init__(self, working_tree, this_tree, base_tree, other_tree,
341
345
interesting_ids=None, reprocess=False, show_base=False,
342
pb=DummyProgress(), pp=None, change_reporter=None,
343
interesting_files=None):
344
"""Initialize the merger object and perform the merge.
346
:param working_tree: The working tree to apply the merge to
347
:param this_tree: The local tree in the merge operation
348
:param base_tree: The common tree in the merge operation
349
:param other_tree: The other other tree to merge changes from
350
:param interesting_ids: The file_ids of files that should be
351
participate in the merge. May not be combined with
353
:param: reprocess If True, perform conflict-reduction processing.
354
:param show_base: If True, show the base revision in text conflicts.
355
(incompatible with reprocess)
356
:param pb: A Progress bar
357
:param pp: A ProgressPhase object
358
:param change_reporter: An object that should report changes made
359
:param interesting_files: The tree-relative paths of files that should
360
participate in the merge. If these paths refer to directories,
361
the contents of those directories will also be included. May not
362
be combined with interesting_ids. If neither interesting_files nor
363
interesting_ids is specified, all files may participate in the
346
pb=DummyProgress(), pp=None):
347
"""Initialize the merger object and perform the merge."""
366
348
object.__init__(self)
367
if interesting_files is not None:
368
assert interesting_ids is None
369
self.interesting_ids = interesting_ids
370
self.interesting_files = interesting_files
371
349
self.this_tree = working_tree
372
self.this_tree.lock_tree_write()
373
350
self.base_tree = base_tree
374
self.base_tree.lock_read()
375
351
self.other_tree = other_tree
376
self.other_tree.lock_read()
377
352
self._raw_conflicts = []
378
353
self.cooked_conflicts = []
379
354
self.reprocess = reprocess
380
355
self.show_base = show_base
383
self.change_reporter = change_reporter
384
358
if self.pp is None:
385
359
self.pp = ProgressPhase("Merge phase", 3, self.pb)
361
if interesting_ids is not None:
362
all_ids = interesting_ids
364
all_ids = set(base_tree)
365
all_ids.update(other_tree)
366
working_tree.lock_write()
387
367
self.tt = TreeTransform(working_tree, self.pb)
389
369
self.pp.next_phase()
390
entries = self._entries3()
391
370
child_pb = ui.ui_factory.nested_progress_bar()
393
for num, (file_id, changed, parents3, names3,
394
executable3) in enumerate(entries):
395
child_pb.update('Preparing file merge', num, len(entries))
396
self._merge_names(file_id, parents3, names3)
398
file_status = self.merge_contents(file_id)
400
file_status = 'unmodified'
401
self._merge_executable(file_id,
402
executable3, file_status)
372
for num, file_id in enumerate(all_ids):
373
child_pb.update('Preparing file merge', num, len(all_ids))
374
self.merge_names(file_id)
375
file_status = self.merge_contents(file_id)
376
self.merge_executable(file_id, file_status)
404
378
child_pb.finished()
406
380
self.pp.next_phase()
407
381
child_pb = ui.ui_factory.nested_progress_bar()
409
fs_conflicts = resolve_conflicts(self.tt, child_pb,
410
lambda t, c: conflict_pass(t, c, self.other_tree))
383
fs_conflicts = resolve_conflicts(self.tt, child_pb)
412
385
child_pb.finished()
413
if change_reporter is not None:
414
from bzrlib import delta
415
delta.report_changes(self.tt._iter_changes(), change_reporter)
416
386
self.cook_conflicts(fs_conflicts)
417
387
for conflict in self.cooked_conflicts:
418
388
warning(conflict)
419
389
self.pp.next_phase()
420
results = self.tt.apply(no_conflicts=True)
390
results = self.tt.apply()
421
391
self.write_modified(results)
423
393
working_tree.add_conflicts(self.cooked_conflicts)
427
397
self.tt.finalize()
428
self.other_tree.unlock()
429
self.base_tree.unlock()
430
self.this_tree.unlock()
398
working_tree.unlock()
434
"""Gather data about files modified between three trees.
436
Return a list of tuples of file_id, changed, parents3, names3,
437
executable3. changed is a boolean indicating whether the file contents
438
or kind were changed. parents3 is a tuple of parent ids for base,
439
other and this. names3 is a tuple of names for base, other and this.
440
executable3 is a tuple of execute-bit values for base, other and this.
443
iterator = self.other_tree._iter_changes(self.base_tree,
444
include_unchanged=True, specific_files=self.interesting_files,
445
extra_trees=[self.this_tree])
446
for (file_id, paths, changed, versioned, parents, names, kind,
447
executable) in iterator:
448
if (self.interesting_ids is not None and
449
file_id not in self.interesting_ids):
451
if file_id in self.this_tree.inventory:
452
entry = self.this_tree.inventory[file_id]
453
this_name = entry.name
454
this_parent = entry.parent_id
455
this_executable = entry.executable
459
this_executable = None
460
parents3 = parents + (this_parent,)
461
names3 = names + (this_name,)
462
executable3 = executable + (this_executable,)
463
result.append((file_id, changed, parents3, names3, executable3))
468
self.tt.final_kind(self.tt.root)
470
self.tt.cancel_deletion(self.tt.root)
471
if self.tt.final_file_id(self.tt.root) is None:
472
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
474
if self.other_tree.inventory.root is None:
476
other_root_file_id = self.other_tree.inventory.root.file_id
477
other_root = self.tt.trans_id_file_id(other_root_file_id)
478
if other_root == self.tt.root:
481
self.tt.final_kind(other_root)
484
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
485
self.tt.cancel_creation(other_root)
486
self.tt.cancel_versioning(other_root)
488
def reparent_children(self, ie, target):
489
for thing, child in ie.children.iteritems():
490
trans_id = self.tt.trans_id_file_id(child.file_id)
491
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
493
401
def write_modified(self, results):
494
402
modified_hashes = {}
495
403
for path in results.modified_paths:
612
492
if name_winner == "conflict":
613
493
trans_id = self.tt.trans_id_file_id(file_id)
614
494
self._raw_conflicts.append(('name conflict', trans_id,
615
this_name, other_name))
495
self.name(this_entry, file_id),
496
self.name(other_entry, file_id)))
616
497
if parent_id_winner == "conflict":
617
498
trans_id = self.tt.trans_id_file_id(file_id)
618
499
self._raw_conflicts.append(('parent conflict', trans_id,
619
this_parent, other_parent))
620
if other_name is None:
500
self.parent(this_entry, file_id),
501
self.parent(other_entry, file_id)))
502
if other_entry is None:
621
503
# it doesn't matter whether the result was 'other' or
622
504
# 'conflict'-- if there's no 'other', we leave it alone.
624
506
# if we get here, name_winner and parent_winner are set to safe values.
507
winner_entry = {"this": this_entry, "other": other_entry,
508
"conflict": other_entry}
625
509
trans_id = self.tt.trans_id_file_id(file_id)
626
parent_id = parents[self.winner_idx[parent_id_winner]]
627
if parent_id is not None:
628
parent_trans_id = self.tt.trans_id_file_id(parent_id)
629
self.tt.adjust_path(names[self.winner_idx[name_winner]],
630
parent_trans_id, trans_id)
510
parent_id = winner_entry[parent_id_winner].parent_id
511
parent_trans_id = self.tt.trans_id_file_id(parent_id)
512
self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
632
515
def merge_contents(self, file_id):
633
516
"""Performa a merge on file_id contents."""