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
28
revision as _mod_revision,
30
from bzrlib.branch import Branch
31
from bzrlib.conflicts import ConflictList, Conflict
32
from bzrlib.errors import (BzrCommandError,
42
WorkingTreeNotRevision,
45
from bzrlib.merge3 import Merge3
46
from bzrlib.osutils import rename, pathjoin
47
from progress import DummyProgress, ProgressPhase
48
from bzrlib.revision import (NULL_REVISION, ensure_null)
49
from bzrlib.textfile import check_text_lines
50
from bzrlib.trace import mutter, warning, note
51
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
52
conflict_pass, FinalPaths, create_by_entry,
53
unique_add, ROOT_PARENT)
54
from bzrlib.versionedfile import PlanWeaveMerge
57
# TODO: Report back as changes are merged in
60
def transform_tree(from_tree, to_tree, interesting_ids=None):
61
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
62
interesting_ids=interesting_ids, this_tree=from_tree)
66
def __init__(self, this_branch, other_tree=None, base_tree=None,
67
this_tree=None, pb=DummyProgress(), change_reporter=None,
70
assert this_tree is not None, "this_tree is required"
71
self.this_branch = this_branch
72
self.this_basis = _mod_revision.ensure_null(
73
this_branch.last_revision())
74
self.this_rev_id = None
75
self.this_tree = this_tree
76
self.this_revision_tree = None
77
self.this_basis_tree = None
78
self.other_tree = other_tree
79
self.other_branch = None
80
self.base_tree = base_tree
81
self.ignore_zero = False
82
self.backup_files = False
83
self.interesting_ids = None
84
self.interesting_files = None
85
self.show_base = False
86
self.reprocess = False
89
self.recurse = recurse
90
self.change_reporter = change_reporter
91
self._cached_trees = {}
94
def from_uncommitted(tree, other_tree, pb):
95
"""Return a Merger for uncommitted changes in other_tree.
97
:param tree: The tree to merge into
98
:param other_tree: The tree to get uncommitted changes from
99
:param pb: A progress indicator
101
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
103
merger.base_rev_id = merger.base_tree.get_revision_id()
104
merger.other_rev_id = None
108
def from_mergeable(klass, tree, mergeable, pb):
109
"""Return a Merger for a bundle or merge directive.
111
:param tree: The tree to merge changes into
112
:param mergeable: A merge directive or bundle
113
:param pb: A progress indicator
115
mergeable.install_revisions(tree.branch.repository)
116
base_revision_id, other_revision_id, verified =\
117
mergeable.get_merge_request(tree.branch.repository)
118
if (base_revision_id != _mod_revision.NULL_REVISION and
119
tree.branch.repository.get_graph().is_ancestor(
120
base_revision_id, tree.branch.last_revision())):
121
base_revision_id = None
122
merger = klass.from_revision_ids(pb, tree, other_revision_id,
124
return merger, verified
127
def from_revision_ids(pb, this, other, base=None, other_branch=None,
129
"""Return a Merger for revision-ids.
131
:param tree: The tree to merge changes into
132
:param other: The revision-id to use as OTHER
133
:param base: The revision-id to use as BASE. If not specified, will
135
:param other_branch: A branch containing the other revision-id. If
136
not supplied, this.branch is used.
137
:param base_branch: A branch containing the base revision-id. If
138
not supplied, other_branch or this.branch will be used.
139
:param pb: A progress indicator
141
merger = Merger(this.branch, this_tree=this, pb=pb)
142
if other_branch is None:
143
other_branch = this.branch
144
merger.set_other_revision(other, other_branch)
148
if base_branch is None:
149
base_branch = other_branch
150
merger.set_base_revision(base, base_branch)
153
def revision_tree(self, revision_id, branch=None):
154
if revision_id not in self._cached_trees:
156
branch = self.this_branch
158
tree = self.this_tree.revision_tree(revision_id)
159
except errors.NoSuchRevisionInTree:
160
tree = branch.repository.revision_tree(revision_id)
161
self._cached_trees[revision_id] = tree
162
return self._cached_trees[revision_id]
164
def _get_tree(self, treespec, possible_transports=None):
165
from bzrlib import workingtree
166
location, revno = treespec
168
tree = workingtree.WorkingTree.open_containing(location)[0]
169
return tree.branch, tree
170
branch = Branch.open_containing(location, possible_transports)[0]
172
revision_id = branch.last_revision()
174
revision_id = branch.get_rev_id(revno)
175
revision_id = ensure_null(revision_id)
176
return branch, self.revision_tree(revision_id, branch)
178
def ensure_revision_trees(self):
179
if self.this_revision_tree is None:
180
self.this_basis_tree = self.revision_tree(self.this_basis)
181
if self.this_basis == self.this_rev_id:
182
self.this_revision_tree = self.this_basis_tree
184
if self.other_rev_id is None:
185
other_basis_tree = self.revision_tree(self.other_basis)
186
changes = other_basis_tree.changes_from(self.other_tree)
187
if changes.has_changed():
188
raise WorkingTreeNotRevision(self.this_tree)
189
other_rev_id = self.other_basis
190
self.other_tree = other_basis_tree
192
def file_revisions(self, file_id):
193
self.ensure_revision_trees()
194
def get_id(tree, file_id):
195
revision_id = tree.inventory[file_id].revision
196
assert revision_id is not None
198
if self.this_rev_id is None:
199
if self.this_basis_tree.get_file_sha1(file_id) != \
200
self.this_tree.get_file_sha1(file_id):
201
raise WorkingTreeNotRevision(self.this_tree)
203
trees = (self.this_basis_tree, self.other_tree)
204
return [get_id(tree, file_id) for tree in trees]
206
def check_basis(self, check_clean, require_commits=True):
207
if self.this_basis is None and require_commits is True:
208
raise BzrCommandError("This branch has no commits."
209
" (perhaps you would prefer 'bzr pull')")
212
if self.this_basis != self.this_rev_id:
213
raise errors.UncommittedChanges(self.this_tree)
215
def compare_basis(self):
217
basis_tree = self.revision_tree(self.this_tree.last_revision())
218
except errors.RevisionNotPresent:
219
basis_tree = self.this_tree.basis_tree()
220
changes = self.this_tree.changes_from(basis_tree)
221
if not changes.has_changed():
222
self.this_rev_id = self.this_basis
224
def set_interesting_files(self, file_list):
225
self.interesting_files = file_list
227
def set_pending(self):
228
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
232
def _add_parent(self):
233
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
234
new_parent_trees = []
235
for revision_id in new_parents:
237
tree = self.revision_tree(revision_id)
238
except errors.RevisionNotPresent:
242
new_parent_trees.append((revision_id, tree))
244
self.this_tree.set_parent_trees(new_parent_trees,
245
allow_leftmost_as_ghost=True)
247
for _revision_id, tree in new_parent_trees:
251
def set_other(self, other_revision, possible_transports=None):
252
"""Set the revision and tree to merge from.
254
This sets the other_tree, other_rev_id, other_basis attributes.
256
:param other_revision: The [path, revision] list to merge from.
258
self.other_branch, self.other_tree = self._get_tree(other_revision,
260
if other_revision[1] == -1:
261
self.other_rev_id = _mod_revision.ensure_null(
262
self.other_branch.last_revision())
263
if _mod_revision.is_null(self.other_rev_id):
264
raise NoCommits(self.other_branch)
265
self.other_basis = self.other_rev_id
266
elif other_revision[1] is not None:
267
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
268
self.other_basis = self.other_rev_id
270
self.other_rev_id = None
271
self.other_basis = self.other_branch.last_revision()
272
if self.other_basis is None:
273
raise NoCommits(self.other_branch)
274
if self.other_rev_id is not None:
275
self._cached_trees[self.other_rev_id] = self.other_tree
276
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
278
def set_other_revision(self, revision_id, other_branch):
279
"""Set 'other' based on a branch and revision id
281
:param revision_id: The revision to use for a tree
282
:param other_branch: The branch containing this tree
284
self.other_rev_id = revision_id
285
self.other_branch = other_branch
286
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
287
self.other_tree = self.revision_tree(revision_id)
288
self.other_basis = revision_id
290
def set_base_revision(self, revision_id, branch):
291
"""Set 'base' based on a branch and revision id
293
:param revision_id: The revision to use for a tree
294
:param branch: The branch containing this tree
296
self.base_rev_id = revision_id
297
self.base_branch = branch
298
self._maybe_fetch(branch, self.this_branch, revision_id)
299
self.base_tree = self.revision_tree(revision_id)
300
graph = self.this_branch.repository.get_graph()
301
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
303
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
306
def _maybe_fetch(self, source, target, revision_id):
307
if not source.repository.has_same_location(target.repository):
308
target.fetch(source, revision_id)
311
this_repo = self.this_branch.repository
312
graph = this_repo.get_graph()
313
revisions = [ensure_null(self.this_basis),
314
ensure_null(self.other_basis)]
315
if NULL_REVISION in revisions:
316
self.base_rev_id = NULL_REVISION
318
self.base_rev_id = graph.find_unique_lca(*revisions)
319
if self.base_rev_id == NULL_REVISION:
320
raise UnrelatedBranches()
321
self.base_tree = self.revision_tree(self.base_rev_id)
322
self.base_is_ancestor = True
323
self.base_is_other_ancestor = True
325
def set_base(self, base_revision):
326
"""Set the base revision to use for the merge.
328
:param base_revision: A 2-list containing a path and revision number.
330
mutter("doing merge() with no base_revision specified")
331
if base_revision == [None, None]:
334
base_branch, self.base_tree = self._get_tree(base_revision)
335
if base_revision[1] == -1:
336
self.base_rev_id = base_branch.last_revision()
337
elif base_revision[1] is None:
338
self.base_rev_id = _mod_revision.NULL_REVISION
340
self.base_rev_id = _mod_revision.ensure_null(
341
base_branch.get_rev_id(base_revision[1]))
342
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
343
graph = self.this_branch.repository.get_graph()
344
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
346
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
350
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
351
'other_tree': self.other_tree,
352
'interesting_ids': self.interesting_ids,
353
'interesting_files': self.interesting_files,
355
if self.merge_type.requires_base:
356
kwargs['base_tree'] = self.base_tree
357
if self.merge_type.supports_reprocess:
358
kwargs['reprocess'] = self.reprocess
360
raise BzrError("Conflict reduction is not supported for merge"
361
" type %s." % self.merge_type)
362
if self.merge_type.supports_show_base:
363
kwargs['show_base'] = self.show_base
365
raise BzrError("Showing base is not supported for this"
366
" merge type. %s" % self.merge_type)
367
self.this_tree.lock_tree_write()
368
if self.base_tree is not None:
369
self.base_tree.lock_read()
370
if self.other_tree is not None:
371
self.other_tree.lock_read()
373
merge = self.merge_type(pb=self._pb,
374
change_reporter=self.change_reporter,
376
if self.recurse == 'down':
377
for path, file_id in self.this_tree.iter_references():
378
sub_tree = self.this_tree.get_nested_tree(file_id, path)
379
other_revision = self.other_tree.get_reference_revision(
381
if other_revision == sub_tree.last_revision():
383
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
384
sub_merge.merge_type = self.merge_type
385
relpath = self.this_tree.relpath(path)
386
other_branch = self.other_branch.reference_parent(file_id, relpath)
387
sub_merge.set_other_revision(other_revision, other_branch)
388
base_revision = self.base_tree.get_reference_revision(file_id)
389
sub_merge.base_tree = \
390
sub_tree.branch.repository.revision_tree(base_revision)
394
if self.other_tree is not None:
395
self.other_tree.unlock()
396
if self.base_tree is not None:
397
self.base_tree.unlock()
398
self.this_tree.unlock()
399
if len(merge.cooked_conflicts) == 0:
400
if not self.ignore_zero:
401
note("All changes applied successfully.")
403
note("%d conflicts encountered." % len(merge.cooked_conflicts))
405
return len(merge.cooked_conflicts)
408
class Merge3Merger(object):
409
"""Three-way merger that uses the merge3 text merger"""
411
supports_reprocess = True
412
supports_show_base = True
413
history_based = False
414
winner_idx = {"this": 2, "other": 1, "conflict": 1}
416
def __init__(self, working_tree, this_tree, base_tree, other_tree,
417
interesting_ids=None, reprocess=False, show_base=False,
418
pb=DummyProgress(), pp=None, change_reporter=None,
419
interesting_files=None):
420
"""Initialize the merger object and perform the merge.
422
:param working_tree: The working tree to apply the merge to
423
:param this_tree: The local tree in the merge operation
424
:param base_tree: The common tree in the merge operation
425
:param other_tree: The other other tree to merge changes from
426
:param interesting_ids: The file_ids of files that should be
427
participate in the merge. May not be combined with
429
:param: reprocess If True, perform conflict-reduction processing.
430
:param show_base: If True, show the base revision in text conflicts.
431
(incompatible with reprocess)
432
:param pb: A Progress bar
433
:param pp: A ProgressPhase object
434
:param change_reporter: An object that should report changes made
435
:param interesting_files: The tree-relative paths of files that should
436
participate in the merge. If these paths refer to directories,
437
the contents of those directories will also be included. May not
438
be combined with interesting_ids. If neither interesting_files nor
439
interesting_ids is specified, all files may participate in the
442
object.__init__(self)
443
if interesting_files is not None:
444
assert interesting_ids is None
445
self.interesting_ids = interesting_ids
446
self.interesting_files = interesting_files
447
self.this_tree = working_tree
448
self.this_tree.lock_tree_write()
449
self.base_tree = base_tree
450
self.base_tree.lock_read()
451
self.other_tree = other_tree
452
self.other_tree.lock_read()
453
self._raw_conflicts = []
454
self.cooked_conflicts = []
455
self.reprocess = reprocess
456
self.show_base = show_base
459
self.change_reporter = change_reporter
461
self.pp = ProgressPhase("Merge phase", 3, self.pb)
463
self.tt = TreeTransform(working_tree, self.pb)
466
entries = self._entries3()
467
child_pb = ui.ui_factory.nested_progress_bar()
469
for num, (file_id, changed, parents3, names3,
470
executable3) in enumerate(entries):
471
child_pb.update('Preparing file merge', num, len(entries))
472
self._merge_names(file_id, parents3, names3)
474
file_status = self.merge_contents(file_id)
476
file_status = 'unmodified'
477
self._merge_executable(file_id,
478
executable3, file_status)
483
child_pb = ui.ui_factory.nested_progress_bar()
485
fs_conflicts = resolve_conflicts(self.tt, child_pb,
486
lambda t, c: conflict_pass(t, c, self.other_tree))
489
if change_reporter is not None:
490
from bzrlib import delta
491
delta.report_changes(self.tt._iter_changes(), change_reporter)
492
self.cook_conflicts(fs_conflicts)
493
for conflict in self.cooked_conflicts:
496
results = self.tt.apply(no_conflicts=True)
497
self.write_modified(results)
499
working_tree.add_conflicts(self.cooked_conflicts)
500
except UnsupportedOperation:
504
self.other_tree.unlock()
505
self.base_tree.unlock()
506
self.this_tree.unlock()
510
"""Gather data about files modified between three trees.
512
Return a list of tuples of file_id, changed, parents3, names3,
513
executable3. changed is a boolean indicating whether the file contents
514
or kind were changed. parents3 is a tuple of parent ids for base,
515
other and this. names3 is a tuple of names for base, other and this.
516
executable3 is a tuple of execute-bit values for base, other and this.
519
iterator = self.other_tree._iter_changes(self.base_tree,
520
include_unchanged=True, specific_files=self.interesting_files,
521
extra_trees=[self.this_tree])
522
for (file_id, paths, changed, versioned, parents, names, kind,
523
executable) in iterator:
524
if (self.interesting_ids is not None and
525
file_id not in self.interesting_ids):
527
if file_id in self.this_tree.inventory:
528
entry = self.this_tree.inventory[file_id]
529
this_name = entry.name
530
this_parent = entry.parent_id
531
this_executable = entry.executable
535
this_executable = None
536
parents3 = parents + (this_parent,)
537
names3 = names + (this_name,)
538
executable3 = executable + (this_executable,)
539
result.append((file_id, changed, parents3, names3, executable3))
544
self.tt.final_kind(self.tt.root)
546
self.tt.cancel_deletion(self.tt.root)
547
if self.tt.final_file_id(self.tt.root) is None:
548
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
550
if self.other_tree.inventory.root is None:
552
other_root_file_id = self.other_tree.get_root_id()
553
other_root = self.tt.trans_id_file_id(other_root_file_id)
554
if other_root == self.tt.root:
557
self.tt.final_kind(other_root)
560
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
561
self.tt.cancel_creation(other_root)
562
self.tt.cancel_versioning(other_root)
564
def reparent_children(self, ie, target):
565
for thing, child in ie.children.iteritems():
566
trans_id = self.tt.trans_id_file_id(child.file_id)
567
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
569
def write_modified(self, results):
571
for path in results.modified_paths:
572
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
575
hash = self.this_tree.get_file_sha1(file_id)
578
modified_hashes[file_id] = hash
579
self.this_tree.set_merge_modified(modified_hashes)
582
def parent(entry, file_id):
583
"""Determine the parent for a file_id (used as a key method)"""
586
return entry.parent_id
589
def name(entry, file_id):
590
"""Determine the name for a file_id (used as a key method)"""
596
def contents_sha1(tree, file_id):
597
"""Determine the sha1 of the file contents (used as a key method)."""
598
if file_id not in tree:
600
return tree.get_file_sha1(file_id)
603
def executable(tree, file_id):
604
"""Determine the executability of a file-id (used as a key method)."""
605
if file_id not in tree:
607
if tree.kind(file_id) != "file":
609
return tree.is_executable(file_id)
612
def kind(tree, file_id):
613
"""Determine the kind of a file-id (used as a key method)."""
614
if file_id not in tree:
616
return tree.kind(file_id)
619
def _three_way(base, other, this):
620
#if base == other, either they all agree, or only THIS has changed.
623
elif this not in (base, other):
625
# "Ambiguous clean merge" -- both sides have made the same change.
628
# this == base: only other has changed.
633
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
634
"""Do a three-way test on a scalar.
635
Return "this", "other" or "conflict", depending whether a value wins.
637
key_base = key(base_tree, file_id)
638
key_other = key(other_tree, file_id)
639
#if base == other, either they all agree, or only THIS has changed.
640
if key_base == key_other:
642
key_this = key(this_tree, file_id)
643
if key_this not in (key_base, key_other):
645
# "Ambiguous clean merge"
646
elif key_this == key_other:
649
assert key_this == key_base
652
def merge_names(self, file_id):
654
if file_id in tree.inventory:
655
return tree.inventory[file_id]
658
this_entry = get_entry(self.this_tree)
659
other_entry = get_entry(self.other_tree)
660
base_entry = get_entry(self.base_tree)
661
entries = (base_entry, other_entry, this_entry)
664
for entry in entries:
669
names.append(entry.name)
670
parents.append(entry.parent_id)
671
return self._merge_names(file_id, parents, names)
673
def _merge_names(self, file_id, parents, names):
674
"""Perform a merge on file_id names and parents"""
675
base_name, other_name, this_name = names
676
base_parent, other_parent, this_parent = parents
678
name_winner = self._three_way(*names)
680
parent_id_winner = self._three_way(*parents)
681
if this_name is None:
682
if name_winner == "this":
683
name_winner = "other"
684
if parent_id_winner == "this":
685
parent_id_winner = "other"
686
if name_winner == "this" and parent_id_winner == "this":
688
if name_winner == "conflict":
689
trans_id = self.tt.trans_id_file_id(file_id)
690
self._raw_conflicts.append(('name conflict', trans_id,
691
this_name, other_name))
692
if parent_id_winner == "conflict":
693
trans_id = self.tt.trans_id_file_id(file_id)
694
self._raw_conflicts.append(('parent conflict', trans_id,
695
this_parent, other_parent))
696
if other_name is None:
697
# it doesn't matter whether the result was 'other' or
698
# 'conflict'-- if there's no 'other', we leave it alone.
700
# if we get here, name_winner and parent_winner are set to safe values.
701
trans_id = self.tt.trans_id_file_id(file_id)
702
parent_id = parents[self.winner_idx[parent_id_winner]]
703
if parent_id is not None:
704
parent_trans_id = self.tt.trans_id_file_id(parent_id)
705
self.tt.adjust_path(names[self.winner_idx[name_winner]],
706
parent_trans_id, trans_id)
708
def merge_contents(self, file_id):
709
"""Performa a merge on file_id contents."""
710
def contents_pair(tree):
711
if file_id not in tree:
713
kind = tree.kind(file_id)
715
contents = tree.get_file_sha1(file_id)
716
elif kind == "symlink":
717
contents = tree.get_symlink_target(file_id)
720
return kind, contents
722
def contents_conflict():
723
trans_id = self.tt.trans_id_file_id(file_id)
724
name = self.tt.final_name(trans_id)
725
parent_id = self.tt.final_parent(trans_id)
726
if file_id in self.this_tree.inventory:
727
self.tt.unversion_file(trans_id)
728
if file_id in self.this_tree:
729
self.tt.delete_contents(trans_id)
730
file_group = self._dump_conflicts(name, parent_id, file_id,
732
self._raw_conflicts.append(('contents conflict', file_group))
734
# See SPOT run. run, SPOT, run.
735
# So we're not QUITE repeating ourselves; we do tricky things with
737
base_pair = contents_pair(self.base_tree)
738
other_pair = contents_pair(self.other_tree)
739
if base_pair == other_pair:
740
# OTHER introduced no changes
742
this_pair = contents_pair(self.this_tree)
743
if this_pair == other_pair:
744
# THIS and OTHER introduced the same changes
747
trans_id = self.tt.trans_id_file_id(file_id)
748
if this_pair == base_pair:
749
# only OTHER introduced changes
750
if file_id in self.this_tree:
751
# Remove any existing contents
752
self.tt.delete_contents(trans_id)
753
if file_id in self.other_tree:
754
# OTHER changed the file
755
create_by_entry(self.tt,
756
self.other_tree.inventory[file_id],
757
self.other_tree, trans_id)
758
if file_id not in self.this_tree.inventory:
759
self.tt.version_file(file_id, trans_id)
761
elif file_id in self.this_tree.inventory:
762
# OTHER deleted the file
763
self.tt.unversion_file(trans_id)
765
#BOTH THIS and OTHER introduced changes; scalar conflict
766
elif this_pair[0] == "file" and other_pair[0] == "file":
767
# THIS and OTHER are both files, so text merge. Either
768
# BASE is a file, or both converted to files, so at least we
769
# have agreement that output should be a file.
771
self.text_merge(file_id, trans_id)
773
return contents_conflict()
774
if file_id not in self.this_tree.inventory:
775
self.tt.version_file(file_id, trans_id)
777
self.tt.tree_kind(trans_id)
778
self.tt.delete_contents(trans_id)
783
# Scalar conflict, can't text merge. Dump conflicts
784
return contents_conflict()
786
def get_lines(self, tree, file_id):
787
"""Return the lines in a file, or an empty list."""
789
return tree.get_file(file_id).readlines()
793
def text_merge(self, file_id, trans_id):
794
"""Perform a three-way text merge on a file_id"""
795
# it's possible that we got here with base as a different type.
796
# if so, we just want two-way text conflicts.
797
if file_id in self.base_tree and \
798
self.base_tree.kind(file_id) == "file":
799
base_lines = self.get_lines(self.base_tree, file_id)
802
other_lines = self.get_lines(self.other_tree, file_id)
803
this_lines = self.get_lines(self.this_tree, file_id)
804
m3 = Merge3(base_lines, this_lines, other_lines)
805
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
806
if self.show_base is True:
807
base_marker = '|' * 7
811
def iter_merge3(retval):
812
retval["text_conflicts"] = False
813
for line in m3.merge_lines(name_a = "TREE",
814
name_b = "MERGE-SOURCE",
815
name_base = "BASE-REVISION",
816
start_marker=start_marker,
817
base_marker=base_marker,
818
reprocess=self.reprocess):
819
if line.startswith(start_marker):
820
retval["text_conflicts"] = True
821
yield line.replace(start_marker, '<' * 7)
825
merge3_iterator = iter_merge3(retval)
826
self.tt.create_file(merge3_iterator, trans_id)
827
if retval["text_conflicts"] is True:
828
self._raw_conflicts.append(('text conflict', trans_id))
829
name = self.tt.final_name(trans_id)
830
parent_id = self.tt.final_parent(trans_id)
831
file_group = self._dump_conflicts(name, parent_id, file_id,
832
this_lines, base_lines,
834
file_group.append(trans_id)
836
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
837
base_lines=None, other_lines=None, set_version=False,
839
"""Emit conflict files.
840
If this_lines, base_lines, or other_lines are omitted, they will be
841
determined automatically. If set_version is true, the .OTHER, .THIS
842
or .BASE (in that order) will be created as versioned files.
844
data = [('OTHER', self.other_tree, other_lines),
845
('THIS', self.this_tree, this_lines)]
847
data.append(('BASE', self.base_tree, base_lines))
850
for suffix, tree, lines in data:
852
trans_id = self._conflict_file(name, parent_id, tree, file_id,
854
file_group.append(trans_id)
855
if set_version and not versioned:
856
self.tt.version_file(file_id, trans_id)
860
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
862
"""Emit a single conflict file."""
863
name = name + '.' + suffix
864
trans_id = self.tt.create_path(name, parent_id)
865
entry = tree.inventory[file_id]
866
create_by_entry(self.tt, entry, tree, trans_id, lines)
869
def merge_executable(self, file_id, file_status):
870
"""Perform a merge on the execute bit."""
871
executable = [self.executable(t, file_id) for t in (self.base_tree,
872
self.other_tree, self.this_tree)]
873
self._merge_executable(file_id, executable, file_status)
875
def _merge_executable(self, file_id, executable, file_status):
876
"""Perform a merge on the execute bit."""
877
base_executable, other_executable, this_executable = executable
878
if file_status == "deleted":
880
trans_id = self.tt.trans_id_file_id(file_id)
882
if self.tt.final_kind(trans_id) != "file":
886
winner = self._three_way(*executable)
887
if winner == "conflict":
888
# There must be a None in here, if we have a conflict, but we
889
# need executability since file status was not deleted.
890
if self.executable(self.other_tree, file_id) is None:
895
if file_status == "modified":
896
executability = this_executable
897
if executability is not None:
898
trans_id = self.tt.trans_id_file_id(file_id)
899
self.tt.set_executability(executability, trans_id)
901
assert winner == "other"
902
if file_id in self.other_tree:
903
executability = other_executable
904
elif file_id in self.this_tree:
905
executability = this_executable
906
elif file_id in self.base_tree:
907
executability = base_executable
908
if executability is not None:
909
trans_id = self.tt.trans_id_file_id(file_id)
910
self.tt.set_executability(executability, trans_id)
912
def cook_conflicts(self, fs_conflicts):
913
"""Convert all conflicts into a form that doesn't depend on trans_id"""
914
from conflicts import Conflict
916
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
917
fp = FinalPaths(self.tt)
918
for conflict in self._raw_conflicts:
919
conflict_type = conflict[0]
920
if conflict_type in ('name conflict', 'parent conflict'):
921
trans_id = conflict[1]
922
conflict_args = conflict[2:]
923
if trans_id not in name_conflicts:
924
name_conflicts[trans_id] = {}
925
unique_add(name_conflicts[trans_id], conflict_type,
927
if conflict_type == 'contents conflict':
928
for trans_id in conflict[1]:
929
file_id = self.tt.final_file_id(trans_id)
930
if file_id is not None:
932
path = fp.get_path(trans_id)
933
for suffix in ('.BASE', '.THIS', '.OTHER'):
934
if path.endswith(suffix):
935
path = path[:-len(suffix)]
937
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
938
self.cooked_conflicts.append(c)
939
if conflict_type == 'text conflict':
940
trans_id = conflict[1]
941
path = fp.get_path(trans_id)
942
file_id = self.tt.final_file_id(trans_id)
943
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
944
self.cooked_conflicts.append(c)
946
for trans_id, conflicts in name_conflicts.iteritems():
948
this_parent, other_parent = conflicts['parent conflict']
949
assert this_parent != other_parent
951
this_parent = other_parent = \
952
self.tt.final_file_id(self.tt.final_parent(trans_id))
954
this_name, other_name = conflicts['name conflict']
955
assert this_name != other_name
957
this_name = other_name = self.tt.final_name(trans_id)
958
other_path = fp.get_path(trans_id)
959
if this_parent is not None and this_name is not None:
961
fp.get_path(self.tt.trans_id_file_id(this_parent))
962
this_path = pathjoin(this_parent_path, this_name)
964
this_path = "<deleted>"
965
file_id = self.tt.final_file_id(trans_id)
966
c = Conflict.factory('path conflict', path=this_path,
967
conflict_path=other_path, file_id=file_id)
968
self.cooked_conflicts.append(c)
969
self.cooked_conflicts.sort(key=Conflict.sort_key)
972
class WeaveMerger(Merge3Merger):
973
"""Three-way tree merger, text weave merger."""
974
supports_reprocess = True
975
supports_show_base = False
977
def __init__(self, working_tree, this_tree, base_tree, other_tree,
978
interesting_ids=None, pb=DummyProgress(), pp=None,
979
reprocess=False, change_reporter=None,
980
interesting_files=None):
981
super(WeaveMerger, self).__init__(working_tree, this_tree,
982
base_tree, other_tree,
983
interesting_ids=interesting_ids,
984
pb=pb, pp=pp, reprocess=reprocess,
985
change_reporter=change_reporter)
987
def _merged_lines(self, file_id):
988
"""Generate the merged lines.
989
There is no distinction between lines that are meant to contain <<<<<<<
992
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
993
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
994
'>>>>>>> MERGE-SOURCE\n')
995
return textmerge.merge_lines(self.reprocess)
997
def text_merge(self, file_id, trans_id):
998
"""Perform a (weave) text merge for a given file and file-id.
999
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1000
and a conflict will be noted.
1002
lines, conflicts = self._merged_lines(file_id)
1004
# Note we're checking whether the OUTPUT is binary in this case,
1005
# because we don't want to get into weave merge guts.
1006
check_text_lines(lines)
1007
self.tt.create_file(lines, trans_id)
1009
self._raw_conflicts.append(('text conflict', trans_id))
1010
name = self.tt.final_name(trans_id)
1011
parent_id = self.tt.final_parent(trans_id)
1012
file_group = self._dump_conflicts(name, parent_id, file_id,
1014
file_group.append(trans_id)
1017
class Diff3Merger(Merge3Merger):
1018
"""Three-way merger using external diff3 for text merging"""
1020
def dump_file(self, temp_dir, name, tree, file_id):
1021
out_path = pathjoin(temp_dir, name)
1022
out_file = open(out_path, "wb")
1024
in_file = tree.get_file(file_id)
1025
for line in in_file:
1026
out_file.write(line)
1031
def text_merge(self, file_id, trans_id):
1032
"""Perform a diff3 merge using a specified file-id and trans-id.
1033
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1034
will be dumped, and a will be conflict noted.
1037
temp_dir = osutils.mkdtemp(prefix="bzr-")
1039
new_file = pathjoin(temp_dir, "new")
1040
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1041
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1042
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1043
status = bzrlib.patch.diff3(new_file, this, base, other)
1044
if status not in (0, 1):
1045
raise BzrError("Unhandled diff3 exit code")
1046
f = open(new_file, 'rb')
1048
self.tt.create_file(f, trans_id)
1052
name = self.tt.final_name(trans_id)
1053
parent_id = self.tt.final_parent(trans_id)
1054
self._dump_conflicts(name, parent_id, file_id)
1055
self._raw_conflicts.append(('text conflict', trans_id))
1057
osutils.rmtree(temp_dir)
1060
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1062
merge_type=Merge3Merger,
1063
interesting_ids=None,
1067
interesting_files=None,
1070
change_reporter=None):
1071
"""Primary interface for merging.
1073
typical use is probably
1074
'merge_inner(branch, branch.get_revision_tree(other_revision),
1075
branch.get_revision_tree(base_revision))'
1077
if this_tree is None:
1078
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1079
"parameter as of bzrlib version 0.8.")
1080
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1081
pb=pb, change_reporter=change_reporter)
1082
merger.backup_files = backup_files
1083
merger.merge_type = merge_type
1084
merger.interesting_ids = interesting_ids
1085
merger.ignore_zero = ignore_zero
1086
if interesting_files:
1087
assert not interesting_ids, ('Only supply interesting_ids'
1088
' or interesting_files')
1089
merger.interesting_files = interesting_files
1090
merger.show_base = show_base
1091
merger.reprocess = reprocess
1092
merger.other_rev_id = other_rev_id
1093
merger.other_basis = other_rev_id
1094
return merger.do_merge()
1096
def get_merge_type_registry():
1097
"""Merge type registry is in bzrlib.option to avoid circular imports.
1099
This method provides a sanctioned way to retrieve it.
1101
from bzrlib import option
1102
return option._merge_type_registry
1105
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1106
def status_a(revision, text):
1107
if revision in ancestors_b:
1108
return 'killed-b', text
1110
return 'new-a', text
1112
def status_b(revision, text):
1113
if revision in ancestors_a:
1114
return 'killed-a', text
1116
return 'new-b', text
1118
plain_a = [t for (a, t) in annotated_a]
1119
plain_b = [t for (a, t) in annotated_b]
1120
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1121
blocks = matcher.get_matching_blocks()
1124
for ai, bi, l in blocks:
1125
# process all mismatched sections
1126
# (last mismatched section is handled because blocks always
1127
# includes a 0-length last block)
1128
for revision, text in annotated_a[a_cur:ai]:
1129
yield status_a(revision, text)
1130
for revision, text in annotated_b[b_cur:bi]:
1131
yield status_b(revision, text)
1133
# and now the matched section
1136
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1137
assert text_a == text_b
1138
yield "unchanged", text_a
1141
class _PlanMerge(object):
1142
"""Plan an annotate merge using on-the-fly annotation"""
1144
def __init__(self, a_rev, b_rev, vf):
1147
:param a_rev: Revision-id of one revision to merge
1148
:param b_rev: Revision-id of the other revision to merge
1149
:param vf: A versionedfile containing both revisions
1153
self.lines_a = vf.get_lines(a_rev)
1154
self.lines_b = vf.get_lines(b_rev)
1156
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1157
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1158
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1159
self._last_lines = None
1160
self._last_lines_revision_id = None
1162
def plan_merge(self):
1163
"""Generate a 'plan' for merging the two revisions.
1165
This involves comparing their texts and determining the cause of
1166
differences. If text A has a line and text B does not, then either the
1167
line was added to text A, or it was deleted from B. Once the causes
1168
are combined, they are written out in the format described in
1169
VersionedFile.plan_merge
1171
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1172
new_a = self._find_new(self.a_rev)
1173
new_b = self._find_new(self.b_rev)
1176
a_lines = self.vf.get_lines(self.a_rev)
1177
b_lines = self.vf.get_lines(self.b_rev)
1178
for i, j, n in blocks:
1179
# determine why lines aren't common
1180
for a_index in range(last_i, i):
1181
if a_index in new_a:
1185
yield cause, a_lines[a_index]
1186
for b_index in range(last_j, j):
1187
if b_index in new_b:
1191
yield cause, b_lines[b_index]
1192
# handle common lines
1193
for a_index in range(i, i+n):
1194
yield 'unchanged', a_lines[a_index]
1198
def _get_matching_blocks(self, left_revision, right_revision):
1199
"""Return a description of which sections of two revisions match.
1201
See SequenceMatcher.get_matching_blocks
1203
if self._last_lines_revision_id == left_revision:
1204
left_lines = self._last_lines
1206
left_lines = self.vf.get_lines(left_revision)
1207
right_lines = self.vf.get_lines(right_revision)
1208
self._last_lines = right_lines
1209
self._last_lines_revision_id = right_revision
1210
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1212
return matcher.get_matching_blocks()
1214
def _unique_lines(self, matching_blocks):
1215
"""Analyse matching_blocks to determine which lines are unique
1217
:return: a tuple of (unique_left, unique_right), where the values are
1218
sets of line numbers of unique lines.
1224
for i, j, n in matching_blocks:
1225
unique_left.extend(range(last_i, i))
1226
unique_right.extend(range(last_j, j))
1229
return unique_left, unique_right
1231
def _find_new(self, version_id):
1232
"""Determine which lines are new in the ancestry of this version.
1234
If a lines is present in this version, and not present in any
1235
common ancestor, it is considered new.
1237
if version_id not in self.uncommon:
1239
parents = self.vf.get_parents(version_id)
1240
if len(parents) == 0:
1241
return set(range(len(self.vf.get_lines(version_id))))
1243
for parent in parents:
1244
blocks = self._get_matching_blocks(version_id, parent)
1245
result, unused = self._unique_lines(blocks)
1246
parent_new = self._find_new(parent)
1247
for i, j, n in blocks:
1248
for ii, jj in [(i+r, j+r) for r in range(n)]:
1249
if jj in parent_new:
1254
new.intersection_update(result)
1258
def _subtract_plans(old_plan, new_plan):
1259
# Can't use patience diff-- C version doesn't work with tuples
1260
matcher = difflib.SequenceMatcher(None, old_plan, new_plan)
1262
for i, j, n in matcher.get_matching_blocks():
1263
for jj in range(last_j, j):
1265
for jj in range(j, j+n):
1266
plan_line = new_plan[jj]
1267
if plan_line[0] == 'new-b':
1269
elif plan_line[0] == 'killed-b':
1270
yield 'unchanged', plan_line[1]