39
43
from bzrlib.merge3 import Merge3
41
from bzrlib.osutils import rename, pathjoin, rmtree
44
from bzrlib.osutils import rename, pathjoin
42
45
from progress import DummyProgress, ProgressPhase
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
46
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
44
47
from bzrlib.textfile import check_text_lines
45
48
from bzrlib.trace import mutter, warning, note
46
49
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
47
FinalPaths, create_by_entry, unique_add)
50
conflict_pass, FinalPaths, create_by_entry,
51
unique_add, ROOT_PARENT)
48
52
from bzrlib.versionedfile import WeaveMerge
49
53
from bzrlib import ui
51
55
# 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)
80
58
def transform_tree(from_tree, to_tree, interesting_ids=None):
81
59
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
85
63
class Merger(object):
86
def __init__(self, this_branch, other_tree=None, base_tree=None,
87
this_tree=None, pb=DummyProgress()):
64
def __init__(self, this_branch, other_tree=None, base_tree=None,
65
this_tree=None, pb=DummyProgress(), change_reporter=None,
88
67
object.__init__(self)
89
68
assert this_tree is not None, "this_tree is required"
90
69
self.this_branch = this_branch
91
self.this_basis = this_branch.last_revision()
70
self.this_basis = _mod_revision.ensure_null(
71
this_branch.last_revision())
92
72
self.this_rev_id = None
93
73
self.this_tree = this_tree
94
74
self.this_revision_tree = None
95
75
self.this_basis_tree = None
96
76
self.other_tree = other_tree
77
self.other_branch = None
97
78
self.base_tree = base_tree
98
79
self.ignore_zero = False
99
80
self.backup_files = False
100
81
self.interesting_ids = None
82
self.interesting_files = None
101
83
self.show_base = False
102
84
self.reprocess = False
107
def revision_tree(self, revision_id):
108
return self.this_branch.repository.revision_tree(revision_id)
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)
110
116
def ensure_revision_trees(self):
111
117
if self.this_revision_tree is None:
112
self.this_basis_tree = self.this_branch.repository.revision_tree(
118
self.this_basis_tree = self.revision_tree(self.this_basis)
114
119
if self.this_basis == self.this_rev_id:
115
120
self.this_revision_tree = self.this_basis_tree
117
122
if self.other_rev_id is None:
118
123
other_basis_tree = self.revision_tree(self.other_basis)
119
changes = compare_trees(self.other_tree, other_basis_tree)
124
changes = other_basis_tree.changes_from(self.other_tree)
120
125
if changes.has_changed():
121
126
raise WorkingTreeNotRevision(self.this_tree)
122
127
other_rev_id = self.other_basis
139
144
def check_basis(self, check_clean, require_commits=True):
140
145
if self.this_basis is None and require_commits is True:
141
raise BzrCommandError("This branch has no commits")
146
raise BzrCommandError("This branch has no commits."
147
" (perhaps you would prefer 'bzr pull')")
143
149
self.compare_basis()
144
150
if self.this_basis != self.this_rev_id:
145
151
raise BzrCommandError("Working tree has uncommitted changes.")
147
153
def compare_basis(self):
148
changes = compare_trees(self.this_tree,
149
self.this_tree.basis_tree(), False)
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)
150
159
if not changes.has_changed():
151
160
self.this_rev_id = self.this_basis
153
162
def set_interesting_files(self, 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
163
self.interesting_files = file_list
178
165
def set_pending(self):
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,
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,
191
198
if other_revision[1] == -1:
192
self.other_rev_id = other_branch.last_revision()
193
if self.other_rev_id is None:
194
raise NoCommits(other_branch)
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)
195
203
self.other_basis = self.other_rev_id
196
204
elif other_revision[1] is not None:
197
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
205
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
198
206
self.other_basis = self.other_rev_id
200
208
self.other_rev_id = None
201
self.other_basis = other_branch.last_revision()
209
self.other_basis = self.other_branch.last_revision()
202
210
if self.other_basis is None:
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)
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)
207
233
def find_base(self):
208
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
210
248
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.
211
253
mutter("doing merge() with no base_revision specified")
212
254
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
228
base_branch, self.base_tree = _get_tree(base_revision)
257
base_branch, self.base_tree = self._get_tree(base_revision)
229
258
if base_revision[1] == -1:
230
259
self.base_rev_id = base_branch.last_revision()
231
260
elif base_revision[1] is None:
232
self.base_rev_id = None
261
self.base_rev_id = _mod_revision.NULL_REVISION
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)
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)
237
266
self.base_is_ancestor = is_ancestor(self.this_basis,
238
267
self.base_rev_id,
239
268
self.this_branch)
269
self.base_is_other_ancestor = is_ancestor(self.other_basis,
241
273
def do_merge(self):
242
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
243
'other_tree': self.other_tree,
274
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
275
'other_tree': self.other_tree,
244
276
'interesting_ids': self.interesting_ids,
277
'interesting_files': self.interesting_files,
246
279
if self.merge_type.requires_base:
247
280
kwargs['base_tree'] = self.base_tree
255
288
elif self.show_base:
256
289
raise BzrError("Showing base is not supported for this"
257
290
" merge type. %s" % self.merge_type)
258
merge = self.merge_type(pb=self._pb, **kwargs)
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()
259
323
if len(merge.cooked_conflicts) == 0:
260
324
if not self.ignore_zero:
261
325
note("All changes applied successfully.")
265
329
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
337
332
class Merge3Merger(object):
338
333
"""Three-way merger that uses the merge3 text merger"""
340
335
supports_reprocess = True
341
336
supports_show_base = True
342
337
history_based = False
338
winner_idx = {"this": 2, "other": 1, "conflict": 1}
344
340
def __init__(self, working_tree, this_tree, base_tree, other_tree,
345
341
interesting_ids=None, reprocess=False, show_base=False,
346
pb=DummyProgress(), pp=None):
347
"""Initialize the merger object and perform the merge."""
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
348
366
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
349
371
self.this_tree = working_tree
372
self.this_tree.lock_tree_write()
350
373
self.base_tree = base_tree
374
self.base_tree.lock_read()
351
375
self.other_tree = other_tree
376
self.other_tree.lock_read()
352
377
self._raw_conflicts = []
353
378
self.cooked_conflicts = []
354
379
self.reprocess = reprocess
355
380
self.show_base = show_base
383
self.change_reporter = change_reporter
358
384
if self.pp is None:
359
385
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()
367
387
self.tt = TreeTransform(working_tree, self.pb)
369
389
self.pp.next_phase()
390
entries = self._entries3()
370
391
child_pb = ui.ui_factory.nested_progress_bar()
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)
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)
378
404
child_pb.finished()
380
406
self.pp.next_phase()
381
407
child_pb = ui.ui_factory.nested_progress_bar()
383
fs_conflicts = resolve_conflicts(self.tt, child_pb)
409
fs_conflicts = resolve_conflicts(self.tt, child_pb,
410
lambda t, c: conflict_pass(t, c, self.other_tree))
385
412
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)
386
416
self.cook_conflicts(fs_conflicts)
387
417
for conflict in self.cooked_conflicts:
388
418
warning(conflict)
389
419
self.pp.next_phase()
390
results = self.tt.apply()
420
results = self.tt.apply(no_conflicts=True)
391
421
self.write_modified(results)
393
423
working_tree.add_conflicts(self.cooked_conflicts)
397
427
self.tt.finalize()
398
working_tree.unlock()
428
self.other_tree.unlock()
429
self.base_tree.unlock()
430
self.this_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)
401
493
def write_modified(self, results):
402
494
modified_hashes = {}
403
495
for path in results.modified_paths:
492
612
if name_winner == "conflict":
493
613
trans_id = self.tt.trans_id_file_id(file_id)
494
614
self._raw_conflicts.append(('name conflict', trans_id,
495
self.name(this_entry, file_id),
496
self.name(other_entry, file_id)))
615
this_name, other_name))
497
616
if parent_id_winner == "conflict":
498
617
trans_id = self.tt.trans_id_file_id(file_id)
499
618
self._raw_conflicts.append(('parent conflict', trans_id,
500
self.parent(this_entry, file_id),
501
self.parent(other_entry, file_id)))
502
if other_entry is None:
619
this_parent, other_parent))
620
if other_name is None:
503
621
# it doesn't matter whether the result was 'other' or
504
622
# 'conflict'-- if there's no 'other', we leave it alone.
506
624
# 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}
509
625
trans_id = self.tt.trans_id_file_id(file_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,
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)
515
632
def merge_contents(self, file_id):
516
633
"""Performa a merge on file_id contents."""