45
47
from bzrlib.textfile import check_text_lines
46
48
from bzrlib.trace import mutter, warning, note
47
49
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
FinalPaths, create_by_entry, unique_add,
50
conflict_pass, FinalPaths, create_by_entry,
51
unique_add, ROOT_PARENT)
50
52
from bzrlib.versionedfile import WeaveMerge
51
53
from bzrlib import ui
53
55
# TODO: Report back as changes are merged in
55
def _get_tree(treespec, local_branch=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)[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
114
79
self.ignore_zero = False
115
80
self.backup_files = False
116
81
self.interesting_ids = None
82
self.interesting_files = None
117
83
self.show_base = False
118
84
self.reprocess = False
121
87
self.recurse = recurse
122
88
self.change_reporter = change_reporter
124
def revision_tree(self, revision_id):
125
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):
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)[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)
127
116
def ensure_revision_trees(self):
128
117
if self.this_revision_tree is None:
129
self.this_basis_tree = self.this_branch.repository.revision_tree(
118
self.this_basis_tree = self.revision_tree(self.this_basis)
131
119
if self.this_basis == self.this_rev_id:
132
120
self.this_revision_tree = self.this_basis_tree
163
151
raise BzrCommandError("Working tree has uncommitted changes.")
165
153
def compare_basis(self):
166
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)
167
159
if not changes.has_changed():
168
160
self.this_rev_id = self.this_basis
170
162
def set_interesting_files(self, file_list):
172
self._set_interesting_files(file_list)
173
except NotVersionedError, e:
174
raise BzrCommandError("%s is not a source file in any"
177
def _set_interesting_files(self, file_list):
178
"""Set the list of interesting ids from a list of files."""
179
if file_list is None:
180
self.interesting_ids = None
183
interesting_ids = set()
184
for path in file_list:
186
# TODO: jam 20070226 The trees are not locked at this time,
187
# wouldn't it make merge faster if it locks everything in the
188
# beginning? It locks at do_merge time, but this happens
190
for tree in (self.this_tree, self.base_tree, self.other_tree):
191
file_id = tree.path2id(path)
192
if file_id is not None:
193
interesting_ids.add(file_id)
196
raise NotVersionedError(path=path)
197
self.interesting_ids = interesting_ids
163
self.interesting_files = file_list
199
165
def set_pending(self):
200
if not self.base_is_ancestor:
202
if self.other_rev_id is None:
204
ancestry = set(self.this_branch.repository.get_ancestry(
205
self.this_basis, topo_sorted=False))
206
if self.other_rev_id in ancestry:
208
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:
210
189
def set_other(self, other_revision):
211
190
"""Set the revision and tree to merge from.
215
194
:param other_revision: The [path, revision] list to merge from.
217
self.other_branch, self.other_tree = _get_tree(other_revision,
196
self.other_branch, self.other_tree = self._get_tree(other_revision)
219
197
if other_revision[1] == -1:
220
self.other_rev_id = self.other_branch.last_revision()
221
if self.other_rev_id is None:
198
self.other_rev_id = _mod_revision.ensure_null(
199
self.other_branch.last_revision())
200
if _mod_revision.is_null(self.other_rev_id):
222
201
raise NoCommits(self.other_branch)
223
202
self.other_basis = self.other_rev_id
224
203
elif other_revision[1] is not None:
229
208
self.other_basis = self.other_branch.last_revision()
230
209
if self.other_basis is None:
231
210
raise NoCommits(self.other_branch)
232
if self.other_branch.base != self.this_branch.base:
233
self.this_branch.fetch(self.other_branch,
234
last_revision=self.other_basis)
211
if self.other_rev_id is not None:
212
self._cached_trees[self.other_rev_id] = self.other_tree
213
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
236
215
def set_other_revision(self, revision_id, other_branch):
237
216
"""Set 'other' based on a branch and revision id
242
221
self.other_rev_id = revision_id
243
222
self.other_branch = other_branch
244
self.this_branch.fetch(other_branch, self.other_rev_id)
223
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
245
224
self.other_tree = self.revision_tree(revision_id)
246
225
self.other_basis = revision_id
254
233
self.base_rev_id = revision_id
255
234
self.base_branch = branch
256
self.this_branch.fetch(branch, revision_id)
235
self._maybe_fetch(branch, self.this_branch, revision_id)
257
236
self.base_tree = self.revision_tree(revision_id)
258
237
self.base_is_ancestor = is_ancestor(self.this_basis,
259
238
self.base_rev_id,
260
239
self.this_branch)
240
self.base_is_other_ancestor = is_ancestor(self.other_basis,
244
def _maybe_fetch(self, source, target, revision_id):
245
if (source.repository.bzrdir.root_transport.base !=
246
target.repository.bzrdir.root_transport.base):
247
target.fetch(source, revision_id)
262
249
def find_base(self):
263
self.set_base([None, None])
250
this_repo = self.this_branch.repository
251
graph = this_repo.get_graph()
252
revisions = [ensure_null(self.this_basis),
253
ensure_null(self.other_basis)]
254
if NULL_REVISION in revisions:
255
self.base_rev_id = NULL_REVISION
257
self.base_rev_id = graph.find_unique_lca(*revisions)
258
if self.base_rev_id == NULL_REVISION:
259
raise UnrelatedBranches()
260
self.base_tree = self.revision_tree(self.base_rev_id)
261
self.base_is_ancestor = True
262
self.base_is_other_ancestor = True
265
264
def set_base(self, base_revision):
266
265
"""Set the base revision to use for the merge.
270
269
mutter("doing merge() with no base_revision specified")
271
270
if base_revision == [None, None]:
273
pb = ui.ui_factory.nested_progress_bar()
275
this_repo = self.this_branch.repository
276
graph = this_repo.get_graph()
277
revisions = [ensure_null(self.this_basis),
278
ensure_null(self.other_basis)]
279
if NULL_REVISION in revisions:
280
self.base_rev_id = NULL_REVISION
282
self.base_rev_id = graph.find_unique_lca(*revisions)
283
if self.base_rev_id == NULL_REVISION:
284
raise UnrelatedBranches()
287
except NoCommonAncestor:
288
raise UnrelatedBranches()
289
self.base_tree = _get_revid_tree_from_tree(self.this_tree,
292
self.base_is_ancestor = True
294
base_branch, self.base_tree = _get_tree(base_revision)
273
base_branch, self.base_tree = self._get_tree(base_revision)
295
274
if base_revision[1] == -1:
296
275
self.base_rev_id = base_branch.last_revision()
297
276
elif base_revision[1] is None:
298
self.base_rev_id = None
277
self.base_rev_id = _mod_revision.NULL_REVISION
300
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
301
if self.this_branch.base != base_branch.base:
302
self.this_branch.fetch(base_branch)
279
self.base_rev_id = _mod_revision.ensure_null(
280
base_branch.get_rev_id(base_revision[1]))
281
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
303
282
self.base_is_ancestor = is_ancestor(self.this_basis,
304
283
self.base_rev_id,
305
284
self.this_branch)
285
self.base_is_other_ancestor = is_ancestor(self.other_basis,
307
289
def do_merge(self):
308
290
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
309
291
'other_tree': self.other_tree,
310
292
'interesting_ids': self.interesting_ids,
293
'interesting_files': self.interesting_files,
312
295
if self.merge_type.requires_base:
313
296
kwargs['base_tree'] = self.base_tree
362
345
return len(merge.cooked_conflicts)
364
def regen_inventory(self, new_entries):
365
old_entries = self.this_tree.read_working_inventory()
369
for path, file_id in new_entries:
372
new_entries_map[file_id] = path
374
def id2path(file_id):
375
path = new_entries_map.get(file_id)
378
entry = old_entries[file_id]
379
if entry.parent_id is None:
381
return pathjoin(id2path(entry.parent_id), entry.name)
383
for file_id in old_entries:
384
entry = old_entries[file_id]
385
path = id2path(file_id)
386
if file_id in self.base_tree.inventory:
387
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
389
executable = getattr(entry, 'executable', False)
390
new_inventory[file_id] = (path, file_id, entry.parent_id,
391
entry.kind, executable)
393
by_path[path] = file_id
398
for path, file_id in new_entries:
400
del new_inventory[file_id]
403
new_path_list.append((path, file_id))
404
if file_id not in old_entries:
406
# Ensure no file is added before its parent
408
for path, file_id in new_path_list:
412
parent = by_path[os.path.dirname(path)]
413
abspath = pathjoin(self.this_tree.basedir, path)
414
kind = osutils.file_kind(abspath)
415
if file_id in self.base_tree.inventory:
416
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
419
new_inventory[file_id] = (path, file_id, parent, kind, executable)
420
by_path[path] = file_id
422
# Get a list in insertion order
423
new_inventory_list = new_inventory.values()
424
mutter ("""Inventory regeneration:
425
old length: %i insertions: %i deletions: %i new_length: %i"""\
426
% (len(old_entries), insertions, deletions,
427
len(new_inventory_list)))
428
assert len(new_inventory_list) == len(old_entries) + insertions\
430
new_inventory_list.sort()
431
return new_inventory_list
434
348
class Merge3Merger(object):
435
349
"""Three-way merger that uses the merge3 text merger"""
437
351
supports_reprocess = True
438
352
supports_show_base = True
439
353
history_based = False
354
winner_idx = {"this": 2, "other": 1, "conflict": 1}
441
356
def __init__(self, working_tree, this_tree, base_tree, other_tree,
442
357
interesting_ids=None, reprocess=False, show_base=False,
443
pb=DummyProgress(), pp=None, change_reporter=None):
444
"""Initialize the merger object and perform the merge."""
358
pb=DummyProgress(), pp=None, change_reporter=None,
359
interesting_files=None):
360
"""Initialize the merger object and perform the merge.
362
:param working_tree: The working tree to apply the merge to
363
:param this_tree: The local tree in the merge operation
364
:param base_tree: The common tree in the merge operation
365
:param other_tree: The other other tree to merge changes from
366
:param interesting_ids: The file_ids of files that should be
367
participate in the merge. May not be combined with
369
:param: reprocess If True, perform conflict-reduction processing.
370
:param show_base: If True, show the base revision in text conflicts.
371
(incompatible with reprocess)
372
:param pb: A Progress bar
373
:param pp: A ProgressPhase object
374
:param change_reporter: An object that should report changes made
375
:param interesting_files: The tree-relative paths of files that should
376
participate in the merge. If these paths refer to directories,
377
the contents of those directories will also be included. May not
378
be combined with interesting_ids. If neither interesting_files nor
379
interesting_ids is specified, all files may participate in the
445
382
object.__init__(self)
383
if interesting_files is not None:
384
assert interesting_ids is None
385
self.interesting_ids = interesting_ids
386
self.interesting_files = interesting_files
446
387
self.this_tree = working_tree
447
388
self.this_tree.lock_tree_write()
448
389
self.base_tree = base_tree
459
400
if self.pp is None:
460
401
self.pp = ProgressPhase("Merge phase", 3, self.pb)
462
if interesting_ids is not None:
463
all_ids = interesting_ids
465
all_ids = set(base_tree)
466
all_ids.update(other_tree)
467
403
self.tt = TreeTransform(working_tree, self.pb)
469
405
self.pp.next_phase()
406
entries = self._entries3()
470
407
child_pb = ui.ui_factory.nested_progress_bar()
472
for num, file_id in enumerate(all_ids):
473
child_pb.update('Preparing file merge', num, len(all_ids))
474
self.merge_names(file_id)
475
file_status = self.merge_contents(file_id)
476
self.merge_executable(file_id, file_status)
409
for num, (file_id, changed, parents3, names3,
410
executable3) in enumerate(entries):
411
child_pb.update('Preparing file merge', num, len(entries))
412
self._merge_names(file_id, parents3, names3)
414
file_status = self.merge_contents(file_id)
416
file_status = 'unmodified'
417
self._merge_executable(file_id,
418
executable3, file_status)
478
420
child_pb.finished()
480
422
self.pp.next_phase()
481
423
child_pb = ui.ui_factory.nested_progress_bar()
483
fs_conflicts = resolve_conflicts(self.tt, child_pb)
425
fs_conflicts = resolve_conflicts(self.tt, child_pb,
426
lambda t, c: conflict_pass(t, c, self.other_tree))
485
428
child_pb.finished()
486
429
if change_reporter is not None:
503
446
self.this_tree.unlock()
450
"""Gather data about files modified between three trees.
452
Return a list of tuples of file_id, changed, parents3, names3,
453
executable3. changed is a boolean indicating whether the file contents
454
or kind were changed. parents3 is a tuple of parent ids for base,
455
other and this. names3 is a tuple of names for base, other and this.
456
executable3 is a tuple of execute-bit values for base, other and this.
459
iterator = self.other_tree._iter_changes(self.base_tree,
460
include_unchanged=True, specific_files=self.interesting_files,
461
extra_trees=[self.this_tree])
462
for (file_id, paths, changed, versioned, parents, names, kind,
463
executable) in iterator:
464
if (self.interesting_ids is not None and
465
file_id not in self.interesting_ids):
467
if file_id in self.this_tree.inventory:
468
entry = self.this_tree.inventory[file_id]
469
this_name = entry.name
470
this_parent = entry.parent_id
471
this_executable = entry.executable
475
this_executable = None
476
parents3 = parents + (this_parent,)
477
names3 = names + (this_name,)
478
executable3 = executable + (this_executable,)
479
result.append((file_id, changed, parents3, names3, executable3))
506
482
def fix_root(self):
508
484
self.tt.final_kind(self.tt.root)
580
556
return tree.kind(file_id)
559
def _three_way(base, other, this):
560
#if base == other, either they all agree, or only THIS has changed.
563
elif this not in (base, other):
565
# "Ambiguous clean merge" -- both sides have made the same change.
568
# this == base: only other has changed.
583
573
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
584
574
"""Do a three-way test on a scalar.
585
575
Return "this", "other" or "conflict", depending whether a value wins.
609
598
this_entry = get_entry(self.this_tree)
610
599
other_entry = get_entry(self.other_tree)
611
600
base_entry = get_entry(self.base_tree)
612
name_winner = self.scalar_three_way(this_entry, base_entry,
613
other_entry, file_id, self.name)
614
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
615
other_entry, file_id,
617
if this_entry is None:
601
entries = (base_entry, other_entry, this_entry)
604
for entry in entries:
609
names.append(entry.name)
610
parents.append(entry.parent_id)
611
return self._merge_names(file_id, parents, names)
613
def _merge_names(self, file_id, parents, names):
614
"""Perform a merge on file_id names and parents"""
615
base_name, other_name, this_name = names
616
base_parent, other_parent, this_parent = parents
618
name_winner = self._three_way(*names)
620
parent_id_winner = self._three_way(*parents)
621
if this_name is None:
618
622
if name_winner == "this":
619
623
name_winner = "other"
620
624
if parent_id_winner == "this":
624
628
if name_winner == "conflict":
625
629
trans_id = self.tt.trans_id_file_id(file_id)
626
630
self._raw_conflicts.append(('name conflict', trans_id,
627
self.name(this_entry, file_id),
628
self.name(other_entry, file_id)))
631
this_name, other_name))
629
632
if parent_id_winner == "conflict":
630
633
trans_id = self.tt.trans_id_file_id(file_id)
631
634
self._raw_conflicts.append(('parent conflict', trans_id,
632
self.parent(this_entry, file_id),
633
self.parent(other_entry, file_id)))
634
if other_entry is None:
635
this_parent, other_parent))
636
if other_name is None:
635
637
# it doesn't matter whether the result was 'other' or
636
638
# 'conflict'-- if there's no 'other', we leave it alone.
638
640
# if we get here, name_winner and parent_winner are set to safe values.
639
winner_entry = {"this": this_entry, "other": other_entry,
640
"conflict": other_entry}
641
641
trans_id = self.tt.trans_id_file_id(file_id)
642
parent_id = winner_entry[parent_id_winner].parent_id
642
parent_id = parents[self.winner_idx[parent_id_winner]]
643
643
if parent_id is not None:
644
644
parent_trans_id = self.tt.trans_id_file_id(parent_id)
645
self.tt.adjust_path(winner_entry[name_winner].name,
645
self.tt.adjust_path(names[self.winner_idx[name_winner]],
646
646
parent_trans_id, trans_id)
648
648
def merge_contents(self, file_id):
809
809
def merge_executable(self, file_id, file_status):
810
810
"""Perform a merge on the execute bit."""
811
executable = [self.executable(t, file_id) for t in (self.base_tree,
812
self.other_tree, self.this_tree)]
813
self._merge_executable(file_id, executable, file_status)
815
def _merge_executable(self, file_id, executable, file_status):
816
"""Perform a merge on the execute bit."""
817
base_executable, other_executable, this_executable = executable
811
818
if file_status == "deleted":
813
820
trans_id = self.tt.trans_id_file_id(file_id)
817
824
except NoSuchFile:
819
winner = self.scalar_three_way(self.this_tree, self.base_tree,
820
self.other_tree, file_id,
826
winner = self._three_way(*executable)
822
827
if winner == "conflict":
823
828
# There must be a None in here, if we have a conflict, but we
824
829
# need executability since file status was not deleted.
829
834
if winner == "this":
830
835
if file_status == "modified":
831
executability = self.this_tree.is_executable(file_id)
836
executability = this_executable
832
837
if executability is not None:
833
838
trans_id = self.tt.trans_id_file_id(file_id)
834
839
self.tt.set_executability(executability, trans_id)
836
841
assert winner == "other"
837
842
if file_id in self.other_tree:
838
executability = self.other_tree.is_executable(file_id)
843
executability = other_executable
839
844
elif file_id in self.this_tree:
840
executability = self.this_tree.is_executable(file_id)
845
executability = this_executable
841
846
elif file_id in self.base_tree:
842
executability = self.base_tree.is_executable(file_id)
847
executability = base_executable
843
848
if executability is not None:
844
849
trans_id = self.tt.trans_id_file_id(file_id)
845
850
self.tt.set_executability(executability, trans_id)
912
917
def __init__(self, working_tree, this_tree, base_tree, other_tree,
913
918
interesting_ids=None, pb=DummyProgress(), pp=None,
914
reprocess=False, change_reporter=None):
919
reprocess=False, change_reporter=None,
920
interesting_files=None):
915
921
self.this_revision_tree = self._get_revision_tree(this_tree)
916
922
self.other_revision_tree = self._get_revision_tree(other_tree)
917
923
super(WeaveMerger, self).__init__(working_tree, this_tree,
1045
1051
if interesting_files:
1046
1052
assert not interesting_ids, ('Only supply interesting_ids'
1047
1053
' or interesting_files')
1048
merger._set_interesting_files(interesting_files)
1054
merger.interesting_files = interesting_files
1049
1055
merger.show_base = show_base
1050
1056
merger.reprocess = reprocess
1051
1057
merger.other_rev_id = other_rev_id