1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
revision as _mod_revision,
29
from bzrlib.branch import Branch
30
from bzrlib.conflicts import ConflictList, Conflict
31
from bzrlib.errors import (BzrCommandError,
41
WorkingTreeNotRevision,
44
from bzrlib.merge3 import Merge3
45
from bzrlib.osutils import rename, pathjoin
46
from progress import DummyProgress, ProgressPhase
47
from bzrlib.revision import (NULL_REVISION, ensure_null)
48
from bzrlib.textfile import check_text_lines
49
from bzrlib.trace import mutter, warning, note
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
51
conflict_pass, FinalPaths, create_by_entry,
52
unique_add, ROOT_PARENT)
53
from bzrlib.versionedfile import PlanWeaveMerge
56
# TODO: Report back as changes are merged in
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
60
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
61
interesting_ids=interesting_ids, this_tree=from_tree)
65
def __init__(self, this_branch, other_tree=None, base_tree=None,
66
this_tree=None, pb=DummyProgress(), change_reporter=None,
69
assert this_tree is not None, "this_tree is required"
70
self.this_branch = this_branch
71
self.this_basis = _mod_revision.ensure_null(
72
this_branch.last_revision())
73
self.this_rev_id = None
74
self.this_tree = this_tree
75
self.this_revision_tree = None
76
self.this_basis_tree = None
77
self.other_tree = other_tree
78
self.other_branch = None
79
self.base_tree = base_tree
80
self.ignore_zero = False
81
self.backup_files = False
82
self.interesting_ids = None
83
self.interesting_files = None
84
self.show_base = False
85
self.reprocess = False
88
self.recurse = recurse
89
self.change_reporter = change_reporter
90
self._cached_trees = {}
93
def from_uncommitted(tree, other_tree, pb):
94
"""Return a Merger for uncommitted changes in other_tree.
96
:param tree: The tree to merge into
97
:param other_tree: The tree to get uncommitted changes from
98
:param pb: A progress indicator
100
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
102
merger.base_rev_id = merger.base_tree.get_revision_id()
103
merger.other_rev_id = None
107
def from_mergeable(klass, tree, mergeable, pb):
108
"""Return a Merger for a bundle or merge directive.
110
:param tree: The tree to merge changes into
111
:param mergeable: A merge directive or bundle
112
:param pb: A progress indicator
114
mergeable.install_revisions(tree.branch.repository)
115
base_revision_id, other_revision_id, verified =\
116
mergeable.get_merge_request(tree.branch.repository)
117
if (base_revision_id != _mod_revision.NULL_REVISION and
118
tree.branch.repository.get_graph().is_ancestor(
119
base_revision_id, tree.branch.last_revision())):
120
base_revision_id = None
121
merger = klass.from_revision_ids(pb, tree, other_revision_id,
123
return merger, verified
126
def from_revision_ids(pb, this, other, base=None, other_branch=None,
128
"""Return a Merger for revision-ids.
130
:param tree: The tree to merge changes into
131
:param other: The revision-id to use as OTHER
132
:param base: The revision-id to use as BASE. If not specified, will
134
:param other_branch: A branch containing the other revision-id. If
135
not supplied, this.branch is used.
136
:param base_branch: A branch containing the base revision-id. If
137
not supplied, other_branch or this.branch will be used.
138
:param pb: A progress indicator
140
merger = Merger(this.branch, this_tree=this, pb=pb)
141
if other_branch is None:
142
other_branch = this.branch
143
merger.set_other_revision(other, other_branch)
147
if base_branch is None:
148
base_branch = other_branch
149
merger.set_base_revision(base, base_branch)
152
def revision_tree(self, revision_id, branch=None):
153
if revision_id not in self._cached_trees:
155
branch = self.this_branch
157
tree = self.this_tree.revision_tree(revision_id)
158
except errors.NoSuchRevisionInTree:
159
tree = branch.repository.revision_tree(revision_id)
160
self._cached_trees[revision_id] = tree
161
return self._cached_trees[revision_id]
163
def _get_tree(self, treespec, possible_transports=None):
164
from bzrlib import workingtree
165
location, revno = treespec
167
tree = workingtree.WorkingTree.open_containing(location)[0]
168
return tree.branch, tree
169
branch = Branch.open_containing(location, possible_transports)[0]
171
revision_id = branch.last_revision()
173
revision_id = branch.get_rev_id(revno)
174
revision_id = ensure_null(revision_id)
175
return branch, self.revision_tree(revision_id, branch)
177
def ensure_revision_trees(self):
178
if self.this_revision_tree is None:
179
self.this_basis_tree = self.revision_tree(self.this_basis)
180
if self.this_basis == self.this_rev_id:
181
self.this_revision_tree = self.this_basis_tree
183
if self.other_rev_id is None:
184
other_basis_tree = self.revision_tree(self.other_basis)
185
changes = other_basis_tree.changes_from(self.other_tree)
186
if changes.has_changed():
187
raise WorkingTreeNotRevision(self.this_tree)
188
other_rev_id = self.other_basis
189
self.other_tree = other_basis_tree
191
def file_revisions(self, file_id):
192
self.ensure_revision_trees()
193
def get_id(tree, file_id):
194
revision_id = tree.inventory[file_id].revision
195
assert revision_id is not None
197
if self.this_rev_id is None:
198
if self.this_basis_tree.get_file_sha1(file_id) != \
199
self.this_tree.get_file_sha1(file_id):
200
raise WorkingTreeNotRevision(self.this_tree)
202
trees = (self.this_basis_tree, self.other_tree)
203
return [get_id(tree, file_id) for tree in trees]
205
def check_basis(self, check_clean, require_commits=True):
206
if self.this_basis is None and require_commits is True:
207
raise BzrCommandError("This branch has no commits."
208
" (perhaps you would prefer 'bzr pull')")
211
if self.this_basis != self.this_rev_id:
212
raise errors.UncommittedChanges(self.this_tree)
214
def compare_basis(self):
216
basis_tree = self.revision_tree(self.this_tree.last_revision())
217
except errors.RevisionNotPresent:
218
basis_tree = self.this_tree.basis_tree()
219
changes = self.this_tree.changes_from(basis_tree)
220
if not changes.has_changed():
221
self.this_rev_id = self.this_basis
223
def set_interesting_files(self, file_list):
224
self.interesting_files = file_list
226
def set_pending(self):
227
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
231
def _add_parent(self):
232
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
233
new_parent_trees = []
234
for revision_id in new_parents:
236
tree = self.revision_tree(revision_id)
237
except errors.RevisionNotPresent:
241
new_parent_trees.append((revision_id, tree))
243
self.this_tree.set_parent_trees(new_parent_trees,
244
allow_leftmost_as_ghost=True)
246
for _revision_id, tree in new_parent_trees:
250
def set_other(self, other_revision, possible_transports=None):
251
"""Set the revision and tree to merge from.
253
This sets the other_tree, other_rev_id, other_basis attributes.
255
:param other_revision: The [path, revision] list to merge from.
257
self.other_branch, self.other_tree = self._get_tree(other_revision,
259
if other_revision[1] == -1:
260
self.other_rev_id = _mod_revision.ensure_null(
261
self.other_branch.last_revision())
262
if _mod_revision.is_null(self.other_rev_id):
263
raise NoCommits(self.other_branch)
264
self.other_basis = self.other_rev_id
265
elif other_revision[1] is not None:
266
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
267
self.other_basis = self.other_rev_id
269
self.other_rev_id = None
270
self.other_basis = self.other_branch.last_revision()
271
if self.other_basis is None:
272
raise NoCommits(self.other_branch)
273
if self.other_rev_id is not None:
274
self._cached_trees[self.other_rev_id] = self.other_tree
275
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
277
def set_other_revision(self, revision_id, other_branch):
278
"""Set 'other' based on a branch and revision id
280
:param revision_id: The revision to use for a tree
281
:param other_branch: The branch containing this tree
283
self.other_rev_id = revision_id
284
self.other_branch = other_branch
285
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
286
self.other_tree = self.revision_tree(revision_id)
287
self.other_basis = revision_id
289
def set_base_revision(self, revision_id, branch):
290
"""Set 'base' based on a branch and revision id
292
:param revision_id: The revision to use for a tree
293
:param branch: The branch containing this tree
295
self.base_rev_id = revision_id
296
self.base_branch = branch
297
self._maybe_fetch(branch, self.this_branch, revision_id)
298
self.base_tree = self.revision_tree(revision_id)
299
graph = self.this_branch.repository.get_graph()
300
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
302
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
305
def _maybe_fetch(self, source, target, revision_id):
306
if not source.repository.has_same_location(target.repository):
307
target.fetch(source, revision_id)
310
this_repo = self.this_branch.repository
311
graph = this_repo.get_graph()
312
revisions = [ensure_null(self.this_basis),
313
ensure_null(self.other_basis)]
314
if NULL_REVISION in revisions:
315
self.base_rev_id = NULL_REVISION
317
self.base_rev_id = graph.find_unique_lca(*revisions)
318
if self.base_rev_id == NULL_REVISION:
319
raise UnrelatedBranches()
320
self.base_tree = self.revision_tree(self.base_rev_id)
321
self.base_is_ancestor = True
322
self.base_is_other_ancestor = True
324
def set_base(self, base_revision):
325
"""Set the base revision to use for the merge.
327
:param base_revision: A 2-list containing a path and revision number.
329
mutter("doing merge() with no base_revision specified")
330
if base_revision == [None, None]:
333
base_branch, self.base_tree = self._get_tree(base_revision)
334
if base_revision[1] == -1:
335
self.base_rev_id = base_branch.last_revision()
336
elif base_revision[1] is None:
337
self.base_rev_id = _mod_revision.NULL_REVISION
339
self.base_rev_id = _mod_revision.ensure_null(
340
base_branch.get_rev_id(base_revision[1]))
341
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
342
graph = self.this_branch.repository.get_graph()
343
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
345
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
349
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
350
'other_tree': self.other_tree,
351
'interesting_ids': self.interesting_ids,
352
'interesting_files': self.interesting_files,
354
if self.merge_type.requires_base:
355
kwargs['base_tree'] = self.base_tree
356
if self.merge_type.supports_reprocess:
357
kwargs['reprocess'] = self.reprocess
359
raise BzrError("Conflict reduction is not supported for merge"
360
" type %s." % self.merge_type)
361
if self.merge_type.supports_show_base:
362
kwargs['show_base'] = self.show_base
364
raise BzrError("Showing base is not supported for this"
365
" merge type. %s" % self.merge_type)
366
self.this_tree.lock_tree_write()
367
if self.base_tree is not None:
368
self.base_tree.lock_read()
369
if self.other_tree is not None:
370
self.other_tree.lock_read()
372
merge = self.merge_type(pb=self._pb,
373
change_reporter=self.change_reporter,
375
if self.recurse == 'down':
376
for path, file_id in self.this_tree.iter_references():
377
sub_tree = self.this_tree.get_nested_tree(file_id, path)
378
other_revision = self.other_tree.get_reference_revision(
380
if other_revision == sub_tree.last_revision():
382
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
383
sub_merge.merge_type = self.merge_type
384
relpath = self.this_tree.relpath(path)
385
other_branch = self.other_branch.reference_parent(file_id, relpath)
386
sub_merge.set_other_revision(other_revision, other_branch)
387
base_revision = self.base_tree.get_reference_revision(file_id)
388
sub_merge.base_tree = \
389
sub_tree.branch.repository.revision_tree(base_revision)
393
if self.other_tree is not None:
394
self.other_tree.unlock()
395
if self.base_tree is not None:
396
self.base_tree.unlock()
397
self.this_tree.unlock()
398
if len(merge.cooked_conflicts) == 0:
399
if not self.ignore_zero:
400
note("All changes applied successfully.")
402
note("%d conflicts encountered." % len(merge.cooked_conflicts))
404
return len(merge.cooked_conflicts)
407
class Merge3Merger(object):
408
"""Three-way merger that uses the merge3 text merger"""
410
supports_reprocess = True
411
supports_show_base = True
412
history_based = False
413
winner_idx = {"this": 2, "other": 1, "conflict": 1}
415
def __init__(self, working_tree, this_tree, base_tree, other_tree,
416
interesting_ids=None, reprocess=False, show_base=False,
417
pb=DummyProgress(), pp=None, change_reporter=None,
418
interesting_files=None):
419
"""Initialize the merger object and perform the merge.
421
:param working_tree: The working tree to apply the merge to
422
:param this_tree: The local tree in the merge operation
423
:param base_tree: The common tree in the merge operation
424
:param other_tree: The other other tree to merge changes from
425
:param interesting_ids: The file_ids of files that should be
426
participate in the merge. May not be combined with
428
:param: reprocess If True, perform conflict-reduction processing.
429
:param show_base: If True, show the base revision in text conflicts.
430
(incompatible with reprocess)
431
:param pb: A Progress bar
432
:param pp: A ProgressPhase object
433
:param change_reporter: An object that should report changes made
434
:param interesting_files: The tree-relative paths of files that should
435
participate in the merge. If these paths refer to directories,
436
the contents of those directories will also be included. May not
437
be combined with interesting_ids. If neither interesting_files nor
438
interesting_ids is specified, all files may participate in the
441
object.__init__(self)
442
if interesting_files is not None:
443
assert interesting_ids is None
444
self.interesting_ids = interesting_ids
445
self.interesting_files = interesting_files
446
self.this_tree = working_tree
447
self.this_tree.lock_tree_write()
448
self.base_tree = base_tree
449
self.base_tree.lock_read()
450
self.other_tree = other_tree
451
self.other_tree.lock_read()
452
self._raw_conflicts = []
453
self.cooked_conflicts = []
454
self.reprocess = reprocess
455
self.show_base = show_base
458
self.change_reporter = change_reporter
460
self.pp = ProgressPhase("Merge phase", 3, self.pb)
462
self.tt = TreeTransform(working_tree, self.pb)
465
entries = self._entries3()
466
child_pb = ui.ui_factory.nested_progress_bar()
468
for num, (file_id, changed, parents3, names3,
469
executable3) in enumerate(entries):
470
child_pb.update('Preparing file merge', num, len(entries))
471
self._merge_names(file_id, parents3, names3)
473
file_status = self.merge_contents(file_id)
475
file_status = 'unmodified'
476
self._merge_executable(file_id,
477
executable3, file_status)
482
child_pb = ui.ui_factory.nested_progress_bar()
484
fs_conflicts = resolve_conflicts(self.tt, child_pb,
485
lambda t, c: conflict_pass(t, c, self.other_tree))
488
if change_reporter is not None:
489
from bzrlib import delta
490
delta.report_changes(self.tt._iter_changes(), change_reporter)
491
self.cook_conflicts(fs_conflicts)
492
for conflict in self.cooked_conflicts:
495
results = self.tt.apply(no_conflicts=True)
496
self.write_modified(results)
498
working_tree.add_conflicts(self.cooked_conflicts)
499
except UnsupportedOperation:
503
self.other_tree.unlock()
504
self.base_tree.unlock()
505
self.this_tree.unlock()
509
"""Gather data about files modified between three trees.
511
Return a list of tuples of file_id, changed, parents3, names3,
512
executable3. changed is a boolean indicating whether the file contents
513
or kind were changed. parents3 is a tuple of parent ids for base,
514
other and this. names3 is a tuple of names for base, other and this.
515
executable3 is a tuple of execute-bit values for base, other and this.
518
iterator = self.other_tree._iter_changes(self.base_tree,
519
include_unchanged=True, specific_files=self.interesting_files,
520
extra_trees=[self.this_tree])
521
for (file_id, paths, changed, versioned, parents, names, kind,
522
executable) in iterator:
523
if (self.interesting_ids is not None and
524
file_id not in self.interesting_ids):
526
if file_id in self.this_tree.inventory:
527
entry = self.this_tree.inventory[file_id]
528
this_name = entry.name
529
this_parent = entry.parent_id
530
this_executable = entry.executable
534
this_executable = None
535
parents3 = parents + (this_parent,)
536
names3 = names + (this_name,)
537
executable3 = executable + (this_executable,)
538
result.append((file_id, changed, parents3, names3, executable3))
543
self.tt.final_kind(self.tt.root)
545
self.tt.cancel_deletion(self.tt.root)
546
if self.tt.final_file_id(self.tt.root) is None:
547
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
549
if self.other_tree.inventory.root is None:
551
other_root_file_id = self.other_tree.get_root_id()
552
other_root = self.tt.trans_id_file_id(other_root_file_id)
553
if other_root == self.tt.root:
556
self.tt.final_kind(other_root)
559
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
560
self.tt.cancel_creation(other_root)
561
self.tt.cancel_versioning(other_root)
563
def reparent_children(self, ie, target):
564
for thing, child in ie.children.iteritems():
565
trans_id = self.tt.trans_id_file_id(child.file_id)
566
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
568
def write_modified(self, results):
570
for path in results.modified_paths:
571
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
574
hash = self.this_tree.get_file_sha1(file_id)
577
modified_hashes[file_id] = hash
578
self.this_tree.set_merge_modified(modified_hashes)
581
def parent(entry, file_id):
582
"""Determine the parent for a file_id (used as a key method)"""
585
return entry.parent_id
588
def name(entry, file_id):
589
"""Determine the name for a file_id (used as a key method)"""
595
def contents_sha1(tree, file_id):
596
"""Determine the sha1 of the file contents (used as a key method)."""
597
if file_id not in tree:
599
return tree.get_file_sha1(file_id)
602
def executable(tree, file_id):
603
"""Determine the executability of a file-id (used as a key method)."""
604
if file_id not in tree:
606
if tree.kind(file_id) != "file":
608
return tree.is_executable(file_id)
611
def kind(tree, file_id):
612
"""Determine the kind of a file-id (used as a key method)."""
613
if file_id not in tree:
615
return tree.kind(file_id)
618
def _three_way(base, other, this):
619
#if base == other, either they all agree, or only THIS has changed.
622
elif this not in (base, other):
624
# "Ambiguous clean merge" -- both sides have made the same change.
627
# this == base: only other has changed.
632
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
633
"""Do a three-way test on a scalar.
634
Return "this", "other" or "conflict", depending whether a value wins.
636
key_base = key(base_tree, file_id)
637
key_other = key(other_tree, file_id)
638
#if base == other, either they all agree, or only THIS has changed.
639
if key_base == key_other:
641
key_this = key(this_tree, file_id)
642
if key_this not in (key_base, key_other):
644
# "Ambiguous clean merge"
645
elif key_this == key_other:
648
assert key_this == key_base
651
def merge_names(self, file_id):
653
if file_id in tree.inventory:
654
return tree.inventory[file_id]
657
this_entry = get_entry(self.this_tree)
658
other_entry = get_entry(self.other_tree)
659
base_entry = get_entry(self.base_tree)
660
entries = (base_entry, other_entry, this_entry)
663
for entry in entries:
668
names.append(entry.name)
669
parents.append(entry.parent_id)
670
return self._merge_names(file_id, parents, names)
672
def _merge_names(self, file_id, parents, names):
673
"""Perform a merge on file_id names and parents"""
674
base_name, other_name, this_name = names
675
base_parent, other_parent, this_parent = parents
677
name_winner = self._three_way(*names)
679
parent_id_winner = self._three_way(*parents)
680
if this_name is None:
681
if name_winner == "this":
682
name_winner = "other"
683
if parent_id_winner == "this":
684
parent_id_winner = "other"
685
if name_winner == "this" and parent_id_winner == "this":
687
if name_winner == "conflict":
688
trans_id = self.tt.trans_id_file_id(file_id)
689
self._raw_conflicts.append(('name conflict', trans_id,
690
this_name, other_name))
691
if parent_id_winner == "conflict":
692
trans_id = self.tt.trans_id_file_id(file_id)
693
self._raw_conflicts.append(('parent conflict', trans_id,
694
this_parent, other_parent))
695
if other_name is None:
696
# it doesn't matter whether the result was 'other' or
697
# 'conflict'-- if there's no 'other', we leave it alone.
699
# if we get here, name_winner and parent_winner are set to safe values.
700
trans_id = self.tt.trans_id_file_id(file_id)
701
parent_id = parents[self.winner_idx[parent_id_winner]]
702
if parent_id is not None:
703
parent_trans_id = self.tt.trans_id_file_id(parent_id)
704
self.tt.adjust_path(names[self.winner_idx[name_winner]],
705
parent_trans_id, trans_id)
707
def merge_contents(self, file_id):
708
"""Performa a merge on file_id contents."""
709
def contents_pair(tree):
710
if file_id not in tree:
712
kind = tree.kind(file_id)
714
contents = tree.get_file_sha1(file_id)
715
elif kind == "symlink":
716
contents = tree.get_symlink_target(file_id)
719
return kind, contents
721
def contents_conflict():
722
trans_id = self.tt.trans_id_file_id(file_id)
723
name = self.tt.final_name(trans_id)
724
parent_id = self.tt.final_parent(trans_id)
725
if file_id in self.this_tree.inventory:
726
self.tt.unversion_file(trans_id)
727
if file_id in self.this_tree:
728
self.tt.delete_contents(trans_id)
729
file_group = self._dump_conflicts(name, parent_id, file_id,
731
self._raw_conflicts.append(('contents conflict', file_group))
733
# See SPOT run. run, SPOT, run.
734
# So we're not QUITE repeating ourselves; we do tricky things with
736
base_pair = contents_pair(self.base_tree)
737
other_pair = contents_pair(self.other_tree)
738
if base_pair == other_pair:
739
# OTHER introduced no changes
741
this_pair = contents_pair(self.this_tree)
742
if this_pair == other_pair:
743
# THIS and OTHER introduced the same changes
746
trans_id = self.tt.trans_id_file_id(file_id)
747
if this_pair == base_pair:
748
# only OTHER introduced changes
749
if file_id in self.this_tree:
750
# Remove any existing contents
751
self.tt.delete_contents(trans_id)
752
if file_id in self.other_tree:
753
# OTHER changed the file
754
create_by_entry(self.tt,
755
self.other_tree.inventory[file_id],
756
self.other_tree, trans_id)
757
if file_id not in self.this_tree.inventory:
758
self.tt.version_file(file_id, trans_id)
760
elif file_id in self.this_tree.inventory:
761
# OTHER deleted the file
762
self.tt.unversion_file(trans_id)
764
#BOTH THIS and OTHER introduced changes; scalar conflict
765
elif this_pair[0] == "file" and other_pair[0] == "file":
766
# THIS and OTHER are both files, so text merge. Either
767
# BASE is a file, or both converted to files, so at least we
768
# have agreement that output should be a file.
770
self.text_merge(file_id, trans_id)
772
return contents_conflict()
773
if file_id not in self.this_tree.inventory:
774
self.tt.version_file(file_id, trans_id)
776
self.tt.tree_kind(trans_id)
777
self.tt.delete_contents(trans_id)
782
# Scalar conflict, can't text merge. Dump conflicts
783
return contents_conflict()
785
def get_lines(self, tree, file_id):
786
"""Return the lines in a file, or an empty list."""
788
return tree.get_file(file_id).readlines()
792
def text_merge(self, file_id, trans_id):
793
"""Perform a three-way text merge on a file_id"""
794
# it's possible that we got here with base as a different type.
795
# if so, we just want two-way text conflicts.
796
if file_id in self.base_tree and \
797
self.base_tree.kind(file_id) == "file":
798
base_lines = self.get_lines(self.base_tree, file_id)
801
other_lines = self.get_lines(self.other_tree, file_id)
802
this_lines = self.get_lines(self.this_tree, file_id)
803
m3 = Merge3(base_lines, this_lines, other_lines)
804
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
805
if self.show_base is True:
806
base_marker = '|' * 7
810
def iter_merge3(retval):
811
retval["text_conflicts"] = False
812
for line in m3.merge_lines(name_a = "TREE",
813
name_b = "MERGE-SOURCE",
814
name_base = "BASE-REVISION",
815
start_marker=start_marker,
816
base_marker=base_marker,
817
reprocess=self.reprocess):
818
if line.startswith(start_marker):
819
retval["text_conflicts"] = True
820
yield line.replace(start_marker, '<' * 7)
824
merge3_iterator = iter_merge3(retval)
825
self.tt.create_file(merge3_iterator, trans_id)
826
if retval["text_conflicts"] is True:
827
self._raw_conflicts.append(('text conflict', trans_id))
828
name = self.tt.final_name(trans_id)
829
parent_id = self.tt.final_parent(trans_id)
830
file_group = self._dump_conflicts(name, parent_id, file_id,
831
this_lines, base_lines,
833
file_group.append(trans_id)
835
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
836
base_lines=None, other_lines=None, set_version=False,
838
"""Emit conflict files.
839
If this_lines, base_lines, or other_lines are omitted, they will be
840
determined automatically. If set_version is true, the .OTHER, .THIS
841
or .BASE (in that order) will be created as versioned files.
843
data = [('OTHER', self.other_tree, other_lines),
844
('THIS', self.this_tree, this_lines)]
846
data.append(('BASE', self.base_tree, base_lines))
849
for suffix, tree, lines in data:
851
trans_id = self._conflict_file(name, parent_id, tree, file_id,
853
file_group.append(trans_id)
854
if set_version and not versioned:
855
self.tt.version_file(file_id, trans_id)
859
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
861
"""Emit a single conflict file."""
862
name = name + '.' + suffix
863
trans_id = self.tt.create_path(name, parent_id)
864
entry = tree.inventory[file_id]
865
create_by_entry(self.tt, entry, tree, trans_id, lines)
868
def merge_executable(self, file_id, file_status):
869
"""Perform a merge on the execute bit."""
870
executable = [self.executable(t, file_id) for t in (self.base_tree,
871
self.other_tree, self.this_tree)]
872
self._merge_executable(file_id, executable, file_status)
874
def _merge_executable(self, file_id, executable, file_status):
875
"""Perform a merge on the execute bit."""
876
base_executable, other_executable, this_executable = executable
877
if file_status == "deleted":
879
trans_id = self.tt.trans_id_file_id(file_id)
881
if self.tt.final_kind(trans_id) != "file":
885
winner = self._three_way(*executable)
886
if winner == "conflict":
887
# There must be a None in here, if we have a conflict, but we
888
# need executability since file status was not deleted.
889
if self.executable(self.other_tree, file_id) is None:
894
if file_status == "modified":
895
executability = this_executable
896
if executability is not None:
897
trans_id = self.tt.trans_id_file_id(file_id)
898
self.tt.set_executability(executability, trans_id)
900
assert winner == "other"
901
if file_id in self.other_tree:
902
executability = other_executable
903
elif file_id in self.this_tree:
904
executability = this_executable
905
elif file_id in self.base_tree:
906
executability = base_executable
907
if executability is not None:
908
trans_id = self.tt.trans_id_file_id(file_id)
909
self.tt.set_executability(executability, trans_id)
911
def cook_conflicts(self, fs_conflicts):
912
"""Convert all conflicts into a form that doesn't depend on trans_id"""
913
from conflicts import Conflict
915
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
916
fp = FinalPaths(self.tt)
917
for conflict in self._raw_conflicts:
918
conflict_type = conflict[0]
919
if conflict_type in ('name conflict', 'parent conflict'):
920
trans_id = conflict[1]
921
conflict_args = conflict[2:]
922
if trans_id not in name_conflicts:
923
name_conflicts[trans_id] = {}
924
unique_add(name_conflicts[trans_id], conflict_type,
926
if conflict_type == 'contents conflict':
927
for trans_id in conflict[1]:
928
file_id = self.tt.final_file_id(trans_id)
929
if file_id is not None:
931
path = fp.get_path(trans_id)
932
for suffix in ('.BASE', '.THIS', '.OTHER'):
933
if path.endswith(suffix):
934
path = path[:-len(suffix)]
936
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
937
self.cooked_conflicts.append(c)
938
if conflict_type == 'text conflict':
939
trans_id = conflict[1]
940
path = fp.get_path(trans_id)
941
file_id = self.tt.final_file_id(trans_id)
942
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
943
self.cooked_conflicts.append(c)
945
for trans_id, conflicts in name_conflicts.iteritems():
947
this_parent, other_parent = conflicts['parent conflict']
948
assert this_parent != other_parent
950
this_parent = other_parent = \
951
self.tt.final_file_id(self.tt.final_parent(trans_id))
953
this_name, other_name = conflicts['name conflict']
954
assert this_name != other_name
956
this_name = other_name = self.tt.final_name(trans_id)
957
other_path = fp.get_path(trans_id)
958
if this_parent is not None and this_name is not None:
960
fp.get_path(self.tt.trans_id_file_id(this_parent))
961
this_path = pathjoin(this_parent_path, this_name)
963
this_path = "<deleted>"
964
file_id = self.tt.final_file_id(trans_id)
965
c = Conflict.factory('path conflict', path=this_path,
966
conflict_path=other_path, file_id=file_id)
967
self.cooked_conflicts.append(c)
968
self.cooked_conflicts.sort(key=Conflict.sort_key)
971
class WeaveMerger(Merge3Merger):
972
"""Three-way tree merger, text weave merger."""
973
supports_reprocess = True
974
supports_show_base = False
976
def __init__(self, working_tree, this_tree, base_tree, other_tree,
977
interesting_ids=None, pb=DummyProgress(), pp=None,
978
reprocess=False, change_reporter=None,
979
interesting_files=None):
980
super(WeaveMerger, self).__init__(working_tree, this_tree,
981
base_tree, other_tree,
982
interesting_ids=interesting_ids,
983
pb=pb, pp=pp, reprocess=reprocess,
984
change_reporter=change_reporter)
986
def _merged_lines(self, file_id):
987
"""Generate the merged lines.
988
There is no distinction between lines that are meant to contain <<<<<<<
991
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
992
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
993
'>>>>>>> MERGE-SOURCE\n')
994
return textmerge.merge_lines(self.reprocess)
996
def text_merge(self, file_id, trans_id):
997
"""Perform a (weave) text merge for a given file and file-id.
998
If conflicts are encountered, .THIS and .OTHER files will be emitted,
999
and a conflict will be noted.
1001
lines, conflicts = self._merged_lines(file_id)
1003
# Note we're checking whether the OUTPUT is binary in this case,
1004
# because we don't want to get into weave merge guts.
1005
check_text_lines(lines)
1006
self.tt.create_file(lines, trans_id)
1008
self._raw_conflicts.append(('text conflict', trans_id))
1009
name = self.tt.final_name(trans_id)
1010
parent_id = self.tt.final_parent(trans_id)
1011
file_group = self._dump_conflicts(name, parent_id, file_id,
1013
file_group.append(trans_id)
1016
class Diff3Merger(Merge3Merger):
1017
"""Three-way merger using external diff3 for text merging"""
1019
def dump_file(self, temp_dir, name, tree, file_id):
1020
out_path = pathjoin(temp_dir, name)
1021
out_file = open(out_path, "wb")
1023
in_file = tree.get_file(file_id)
1024
for line in in_file:
1025
out_file.write(line)
1030
def text_merge(self, file_id, trans_id):
1031
"""Perform a diff3 merge using a specified file-id and trans-id.
1032
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1033
will be dumped, and a will be conflict noted.
1036
temp_dir = osutils.mkdtemp(prefix="bzr-")
1038
new_file = pathjoin(temp_dir, "new")
1039
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1040
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1041
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1042
status = bzrlib.patch.diff3(new_file, this, base, other)
1043
if status not in (0, 1):
1044
raise BzrError("Unhandled diff3 exit code")
1045
f = open(new_file, 'rb')
1047
self.tt.create_file(f, trans_id)
1051
name = self.tt.final_name(trans_id)
1052
parent_id = self.tt.final_parent(trans_id)
1053
self._dump_conflicts(name, parent_id, file_id)
1054
self._raw_conflicts.append(('text conflict', trans_id))
1056
osutils.rmtree(temp_dir)
1059
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1061
merge_type=Merge3Merger,
1062
interesting_ids=None,
1066
interesting_files=None,
1069
change_reporter=None):
1070
"""Primary interface for merging.
1072
typical use is probably
1073
'merge_inner(branch, branch.get_revision_tree(other_revision),
1074
branch.get_revision_tree(base_revision))'
1076
if this_tree is None:
1077
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1078
"parameter as of bzrlib version 0.8.")
1079
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1080
pb=pb, change_reporter=change_reporter)
1081
merger.backup_files = backup_files
1082
merger.merge_type = merge_type
1083
merger.interesting_ids = interesting_ids
1084
merger.ignore_zero = ignore_zero
1085
if interesting_files:
1086
assert not interesting_ids, ('Only supply interesting_ids'
1087
' or interesting_files')
1088
merger.interesting_files = interesting_files
1089
merger.show_base = show_base
1090
merger.reprocess = reprocess
1091
merger.other_rev_id = other_rev_id
1092
merger.other_basis = other_rev_id
1093
return merger.do_merge()
1095
def get_merge_type_registry():
1096
"""Merge type registry is in bzrlib.option to avoid circular imports.
1098
This method provides a sanctioned way to retrieve it.
1100
from bzrlib import option
1101
return option._merge_type_registry
1104
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1105
def status_a(revision, text):
1106
if revision in ancestors_b:
1107
return 'killed-b', text
1109
return 'new-a', text
1111
def status_b(revision, text):
1112
if revision in ancestors_a:
1113
return 'killed-a', text
1115
return 'new-b', text
1117
plain_a = [t for (a, t) in annotated_a]
1118
plain_b = [t for (a, t) in annotated_b]
1119
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1120
blocks = matcher.get_matching_blocks()
1123
for ai, bi, l in blocks:
1124
# process all mismatched sections
1125
# (last mismatched section is handled because blocks always
1126
# includes a 0-length last block)
1127
for revision, text in annotated_a[a_cur:ai]:
1128
yield status_a(revision, text)
1129
for revision, text in annotated_b[b_cur:bi]:
1130
yield status_b(revision, text)
1132
# and now the matched section
1135
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1136
assert text_a == text_b
1137
yield "unchanged", text_a