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, steps = graph.find_unique_lca(revisions[0],
319
revisions[1], count_steps=True)
320
if self.base_rev_id == NULL_REVISION:
321
raise UnrelatedBranches()
323
warning('Warning: criss-cross merge encountered. See bzr'
324
' help criss-cross.')
325
self.base_tree = self.revision_tree(self.base_rev_id)
326
self.base_is_ancestor = True
327
self.base_is_other_ancestor = True
329
def set_base(self, base_revision):
330
"""Set the base revision to use for the merge.
332
:param base_revision: A 2-list containing a path and revision number.
334
mutter("doing merge() with no base_revision specified")
335
if base_revision == [None, None]:
338
base_branch, self.base_tree = self._get_tree(base_revision)
339
if base_revision[1] == -1:
340
self.base_rev_id = base_branch.last_revision()
341
elif base_revision[1] is None:
342
self.base_rev_id = _mod_revision.NULL_REVISION
344
self.base_rev_id = _mod_revision.ensure_null(
345
base_branch.get_rev_id(base_revision[1]))
346
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
347
graph = self.this_branch.repository.get_graph()
348
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
350
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
354
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
355
'other_tree': self.other_tree,
356
'interesting_ids': self.interesting_ids,
357
'interesting_files': self.interesting_files,
359
if self.merge_type.requires_base:
360
kwargs['base_tree'] = self.base_tree
361
if self.merge_type.supports_reprocess:
362
kwargs['reprocess'] = self.reprocess
364
raise BzrError("Conflict reduction is not supported for merge"
365
" type %s." % self.merge_type)
366
if self.merge_type.supports_show_base:
367
kwargs['show_base'] = self.show_base
369
raise BzrError("Showing base is not supported for this"
370
" merge type. %s" % self.merge_type)
371
self.this_tree.lock_tree_write()
372
if self.base_tree is not None:
373
self.base_tree.lock_read()
374
if self.other_tree is not None:
375
self.other_tree.lock_read()
377
merge = self.merge_type(pb=self._pb,
378
change_reporter=self.change_reporter,
380
if self.recurse == 'down':
381
for path, file_id in self.this_tree.iter_references():
382
sub_tree = self.this_tree.get_nested_tree(file_id, path)
383
other_revision = self.other_tree.get_reference_revision(
385
if other_revision == sub_tree.last_revision():
387
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
388
sub_merge.merge_type = self.merge_type
389
relpath = self.this_tree.relpath(path)
390
other_branch = self.other_branch.reference_parent(file_id, relpath)
391
sub_merge.set_other_revision(other_revision, other_branch)
392
base_revision = self.base_tree.get_reference_revision(file_id)
393
sub_merge.base_tree = \
394
sub_tree.branch.repository.revision_tree(base_revision)
398
if self.other_tree is not None:
399
self.other_tree.unlock()
400
if self.base_tree is not None:
401
self.base_tree.unlock()
402
self.this_tree.unlock()
403
if len(merge.cooked_conflicts) == 0:
404
if not self.ignore_zero:
405
note("All changes applied successfully.")
407
note("%d conflicts encountered." % len(merge.cooked_conflicts))
409
return len(merge.cooked_conflicts)
412
class Merge3Merger(object):
413
"""Three-way merger that uses the merge3 text merger"""
415
supports_reprocess = True
416
supports_show_base = True
417
history_based = False
418
winner_idx = {"this": 2, "other": 1, "conflict": 1}
420
def __init__(self, working_tree, this_tree, base_tree, other_tree,
421
interesting_ids=None, reprocess=False, show_base=False,
422
pb=DummyProgress(), pp=None, change_reporter=None,
423
interesting_files=None):
424
"""Initialize the merger object and perform the merge.
426
:param working_tree: The working tree to apply the merge to
427
:param this_tree: The local tree in the merge operation
428
:param base_tree: The common tree in the merge operation
429
:param other_tree: The other other tree to merge changes from
430
:param interesting_ids: The file_ids of files that should be
431
participate in the merge. May not be combined with
433
:param: reprocess If True, perform conflict-reduction processing.
434
:param show_base: If True, show the base revision in text conflicts.
435
(incompatible with reprocess)
436
:param pb: A Progress bar
437
:param pp: A ProgressPhase object
438
:param change_reporter: An object that should report changes made
439
:param interesting_files: The tree-relative paths of files that should
440
participate in the merge. If these paths refer to directories,
441
the contents of those directories will also be included. May not
442
be combined with interesting_ids. If neither interesting_files nor
443
interesting_ids is specified, all files may participate in the
446
object.__init__(self)
447
if interesting_files is not None:
448
assert interesting_ids is None
449
self.interesting_ids = interesting_ids
450
self.interesting_files = interesting_files
451
self.this_tree = working_tree
452
self.this_tree.lock_tree_write()
453
self.base_tree = base_tree
454
self.base_tree.lock_read()
455
self.other_tree = other_tree
456
self.other_tree.lock_read()
457
self._raw_conflicts = []
458
self.cooked_conflicts = []
459
self.reprocess = reprocess
460
self.show_base = show_base
463
self.change_reporter = change_reporter
465
self.pp = ProgressPhase("Merge phase", 3, self.pb)
467
self.tt = TreeTransform(working_tree, self.pb)
470
entries = self._entries3()
471
child_pb = ui.ui_factory.nested_progress_bar()
473
for num, (file_id, changed, parents3, names3,
474
executable3) in enumerate(entries):
475
child_pb.update('Preparing file merge', num, len(entries))
476
self._merge_names(file_id, parents3, names3)
478
file_status = self.merge_contents(file_id)
480
file_status = 'unmodified'
481
self._merge_executable(file_id,
482
executable3, file_status)
487
child_pb = ui.ui_factory.nested_progress_bar()
489
fs_conflicts = resolve_conflicts(self.tt, child_pb,
490
lambda t, c: conflict_pass(t, c, self.other_tree))
493
if change_reporter is not None:
494
from bzrlib import delta
495
delta.report_changes(self.tt._iter_changes(), change_reporter)
496
self.cook_conflicts(fs_conflicts)
497
for conflict in self.cooked_conflicts:
500
results = self.tt.apply(no_conflicts=True)
501
self.write_modified(results)
503
working_tree.add_conflicts(self.cooked_conflicts)
504
except UnsupportedOperation:
508
self.other_tree.unlock()
509
self.base_tree.unlock()
510
self.this_tree.unlock()
514
"""Gather data about files modified between three trees.
516
Return a list of tuples of file_id, changed, parents3, names3,
517
executable3. changed is a boolean indicating whether the file contents
518
or kind were changed. parents3 is a tuple of parent ids for base,
519
other and this. names3 is a tuple of names for base, other and this.
520
executable3 is a tuple of execute-bit values for base, other and this.
523
iterator = self.other_tree._iter_changes(self.base_tree,
524
include_unchanged=True, specific_files=self.interesting_files,
525
extra_trees=[self.this_tree])
526
for (file_id, paths, changed, versioned, parents, names, kind,
527
executable) in iterator:
528
if (self.interesting_ids is not None and
529
file_id not in self.interesting_ids):
531
if file_id in self.this_tree.inventory:
532
entry = self.this_tree.inventory[file_id]
533
this_name = entry.name
534
this_parent = entry.parent_id
535
this_executable = entry.executable
539
this_executable = None
540
parents3 = parents + (this_parent,)
541
names3 = names + (this_name,)
542
executable3 = executable + (this_executable,)
543
result.append((file_id, changed, parents3, names3, executable3))
548
self.tt.final_kind(self.tt.root)
550
self.tt.cancel_deletion(self.tt.root)
551
if self.tt.final_file_id(self.tt.root) is None:
552
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
554
if self.other_tree.inventory.root is None:
556
other_root_file_id = self.other_tree.get_root_id()
557
other_root = self.tt.trans_id_file_id(other_root_file_id)
558
if other_root == self.tt.root:
561
self.tt.final_kind(other_root)
564
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
565
self.tt.cancel_creation(other_root)
566
self.tt.cancel_versioning(other_root)
568
def reparent_children(self, ie, target):
569
for thing, child in ie.children.iteritems():
570
trans_id = self.tt.trans_id_file_id(child.file_id)
571
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
573
def write_modified(self, results):
575
for path in results.modified_paths:
576
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
579
hash = self.this_tree.get_file_sha1(file_id)
582
modified_hashes[file_id] = hash
583
self.this_tree.set_merge_modified(modified_hashes)
586
def parent(entry, file_id):
587
"""Determine the parent for a file_id (used as a key method)"""
590
return entry.parent_id
593
def name(entry, file_id):
594
"""Determine the name for a file_id (used as a key method)"""
600
def contents_sha1(tree, file_id):
601
"""Determine the sha1 of the file contents (used as a key method)."""
602
if file_id not in tree:
604
return tree.get_file_sha1(file_id)
607
def executable(tree, file_id):
608
"""Determine the executability of a file-id (used as a key method)."""
609
if file_id not in tree:
611
if tree.kind(file_id) != "file":
613
return tree.is_executable(file_id)
616
def kind(tree, file_id):
617
"""Determine the kind of a file-id (used as a key method)."""
618
if file_id not in tree:
620
return tree.kind(file_id)
623
def _three_way(base, other, this):
624
#if base == other, either they all agree, or only THIS has changed.
627
elif this not in (base, other):
629
# "Ambiguous clean merge" -- both sides have made the same change.
632
# this == base: only other has changed.
637
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
638
"""Do a three-way test on a scalar.
639
Return "this", "other" or "conflict", depending whether a value wins.
641
key_base = key(base_tree, file_id)
642
key_other = key(other_tree, file_id)
643
#if base == other, either they all agree, or only THIS has changed.
644
if key_base == key_other:
646
key_this = key(this_tree, file_id)
647
if key_this not in (key_base, key_other):
649
# "Ambiguous clean merge"
650
elif key_this == key_other:
653
assert key_this == key_base
656
def merge_names(self, file_id):
658
if file_id in tree.inventory:
659
return tree.inventory[file_id]
662
this_entry = get_entry(self.this_tree)
663
other_entry = get_entry(self.other_tree)
664
base_entry = get_entry(self.base_tree)
665
entries = (base_entry, other_entry, this_entry)
668
for entry in entries:
673
names.append(entry.name)
674
parents.append(entry.parent_id)
675
return self._merge_names(file_id, parents, names)
677
def _merge_names(self, file_id, parents, names):
678
"""Perform a merge on file_id names and parents"""
679
base_name, other_name, this_name = names
680
base_parent, other_parent, this_parent = parents
682
name_winner = self._three_way(*names)
684
parent_id_winner = self._three_way(*parents)
685
if this_name is None:
686
if name_winner == "this":
687
name_winner = "other"
688
if parent_id_winner == "this":
689
parent_id_winner = "other"
690
if name_winner == "this" and parent_id_winner == "this":
692
if name_winner == "conflict":
693
trans_id = self.tt.trans_id_file_id(file_id)
694
self._raw_conflicts.append(('name conflict', trans_id,
695
this_name, other_name))
696
if parent_id_winner == "conflict":
697
trans_id = self.tt.trans_id_file_id(file_id)
698
self._raw_conflicts.append(('parent conflict', trans_id,
699
this_parent, other_parent))
700
if other_name is None:
701
# it doesn't matter whether the result was 'other' or
702
# 'conflict'-- if there's no 'other', we leave it alone.
704
# if we get here, name_winner and parent_winner are set to safe values.
705
trans_id = self.tt.trans_id_file_id(file_id)
706
parent_id = parents[self.winner_idx[parent_id_winner]]
707
if parent_id is not None:
708
parent_trans_id = self.tt.trans_id_file_id(parent_id)
709
self.tt.adjust_path(names[self.winner_idx[name_winner]],
710
parent_trans_id, trans_id)
712
def merge_contents(self, file_id):
713
"""Performa a merge on file_id contents."""
714
def contents_pair(tree):
715
if file_id not in tree:
717
kind = tree.kind(file_id)
719
contents = tree.get_file_sha1(file_id)
720
elif kind == "symlink":
721
contents = tree.get_symlink_target(file_id)
724
return kind, contents
726
def contents_conflict():
727
trans_id = self.tt.trans_id_file_id(file_id)
728
name = self.tt.final_name(trans_id)
729
parent_id = self.tt.final_parent(trans_id)
730
if file_id in self.this_tree.inventory:
731
self.tt.unversion_file(trans_id)
732
if file_id in self.this_tree:
733
self.tt.delete_contents(trans_id)
734
file_group = self._dump_conflicts(name, parent_id, file_id,
736
self._raw_conflicts.append(('contents conflict', file_group))
738
# See SPOT run. run, SPOT, run.
739
# So we're not QUITE repeating ourselves; we do tricky things with
741
base_pair = contents_pair(self.base_tree)
742
other_pair = contents_pair(self.other_tree)
743
if base_pair == other_pair:
744
# OTHER introduced no changes
746
this_pair = contents_pair(self.this_tree)
747
if this_pair == other_pair:
748
# THIS and OTHER introduced the same changes
751
trans_id = self.tt.trans_id_file_id(file_id)
752
if this_pair == base_pair:
753
# only OTHER introduced changes
754
if file_id in self.this_tree:
755
# Remove any existing contents
756
self.tt.delete_contents(trans_id)
757
if file_id in self.other_tree:
758
# OTHER changed the file
759
create_by_entry(self.tt,
760
self.other_tree.inventory[file_id],
761
self.other_tree, trans_id)
762
if file_id not in self.this_tree.inventory:
763
self.tt.version_file(file_id, trans_id)
765
elif file_id in self.this_tree.inventory:
766
# OTHER deleted the file
767
self.tt.unversion_file(trans_id)
769
#BOTH THIS and OTHER introduced changes; scalar conflict
770
elif this_pair[0] == "file" and other_pair[0] == "file":
771
# THIS and OTHER are both files, so text merge. Either
772
# BASE is a file, or both converted to files, so at least we
773
# have agreement that output should be a file.
775
self.text_merge(file_id, trans_id)
777
return contents_conflict()
778
if file_id not in self.this_tree.inventory:
779
self.tt.version_file(file_id, trans_id)
781
self.tt.tree_kind(trans_id)
782
self.tt.delete_contents(trans_id)
787
# Scalar conflict, can't text merge. Dump conflicts
788
return contents_conflict()
790
def get_lines(self, tree, file_id):
791
"""Return the lines in a file, or an empty list."""
793
return tree.get_file(file_id).readlines()
797
def text_merge(self, file_id, trans_id):
798
"""Perform a three-way text merge on a file_id"""
799
# it's possible that we got here with base as a different type.
800
# if so, we just want two-way text conflicts.
801
if file_id in self.base_tree and \
802
self.base_tree.kind(file_id) == "file":
803
base_lines = self.get_lines(self.base_tree, file_id)
806
other_lines = self.get_lines(self.other_tree, file_id)
807
this_lines = self.get_lines(self.this_tree, file_id)
808
m3 = Merge3(base_lines, this_lines, other_lines)
809
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
810
if self.show_base is True:
811
base_marker = '|' * 7
815
def iter_merge3(retval):
816
retval["text_conflicts"] = False
817
for line in m3.merge_lines(name_a = "TREE",
818
name_b = "MERGE-SOURCE",
819
name_base = "BASE-REVISION",
820
start_marker=start_marker,
821
base_marker=base_marker,
822
reprocess=self.reprocess):
823
if line.startswith(start_marker):
824
retval["text_conflicts"] = True
825
yield line.replace(start_marker, '<' * 7)
829
merge3_iterator = iter_merge3(retval)
830
self.tt.create_file(merge3_iterator, trans_id)
831
if retval["text_conflicts"] is True:
832
self._raw_conflicts.append(('text conflict', trans_id))
833
name = self.tt.final_name(trans_id)
834
parent_id = self.tt.final_parent(trans_id)
835
file_group = self._dump_conflicts(name, parent_id, file_id,
836
this_lines, base_lines,
838
file_group.append(trans_id)
840
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
841
base_lines=None, other_lines=None, set_version=False,
843
"""Emit conflict files.
844
If this_lines, base_lines, or other_lines are omitted, they will be
845
determined automatically. If set_version is true, the .OTHER, .THIS
846
or .BASE (in that order) will be created as versioned files.
848
data = [('OTHER', self.other_tree, other_lines),
849
('THIS', self.this_tree, this_lines)]
851
data.append(('BASE', self.base_tree, base_lines))
854
for suffix, tree, lines in data:
856
trans_id = self._conflict_file(name, parent_id, tree, file_id,
858
file_group.append(trans_id)
859
if set_version and not versioned:
860
self.tt.version_file(file_id, trans_id)
864
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
866
"""Emit a single conflict file."""
867
name = name + '.' + suffix
868
trans_id = self.tt.create_path(name, parent_id)
869
entry = tree.inventory[file_id]
870
create_by_entry(self.tt, entry, tree, trans_id, lines)
873
def merge_executable(self, file_id, file_status):
874
"""Perform a merge on the execute bit."""
875
executable = [self.executable(t, file_id) for t in (self.base_tree,
876
self.other_tree, self.this_tree)]
877
self._merge_executable(file_id, executable, file_status)
879
def _merge_executable(self, file_id, executable, file_status):
880
"""Perform a merge on the execute bit."""
881
base_executable, other_executable, this_executable = executable
882
if file_status == "deleted":
884
trans_id = self.tt.trans_id_file_id(file_id)
886
if self.tt.final_kind(trans_id) != "file":
890
winner = self._three_way(*executable)
891
if winner == "conflict":
892
# There must be a None in here, if we have a conflict, but we
893
# need executability since file status was not deleted.
894
if self.executable(self.other_tree, file_id) is None:
899
if file_status == "modified":
900
executability = this_executable
901
if executability is not None:
902
trans_id = self.tt.trans_id_file_id(file_id)
903
self.tt.set_executability(executability, trans_id)
905
assert winner == "other"
906
if file_id in self.other_tree:
907
executability = other_executable
908
elif file_id in self.this_tree:
909
executability = this_executable
910
elif file_id in self.base_tree:
911
executability = base_executable
912
if executability is not None:
913
trans_id = self.tt.trans_id_file_id(file_id)
914
self.tt.set_executability(executability, trans_id)
916
def cook_conflicts(self, fs_conflicts):
917
"""Convert all conflicts into a form that doesn't depend on trans_id"""
918
from conflicts import Conflict
920
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
921
fp = FinalPaths(self.tt)
922
for conflict in self._raw_conflicts:
923
conflict_type = conflict[0]
924
if conflict_type in ('name conflict', 'parent conflict'):
925
trans_id = conflict[1]
926
conflict_args = conflict[2:]
927
if trans_id not in name_conflicts:
928
name_conflicts[trans_id] = {}
929
unique_add(name_conflicts[trans_id], conflict_type,
931
if conflict_type == 'contents conflict':
932
for trans_id in conflict[1]:
933
file_id = self.tt.final_file_id(trans_id)
934
if file_id is not None:
936
path = fp.get_path(trans_id)
937
for suffix in ('.BASE', '.THIS', '.OTHER'):
938
if path.endswith(suffix):
939
path = path[:-len(suffix)]
941
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
942
self.cooked_conflicts.append(c)
943
if conflict_type == 'text conflict':
944
trans_id = conflict[1]
945
path = fp.get_path(trans_id)
946
file_id = self.tt.final_file_id(trans_id)
947
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
948
self.cooked_conflicts.append(c)
950
for trans_id, conflicts in name_conflicts.iteritems():
952
this_parent, other_parent = conflicts['parent conflict']
953
assert this_parent != other_parent
955
this_parent = other_parent = \
956
self.tt.final_file_id(self.tt.final_parent(trans_id))
958
this_name, other_name = conflicts['name conflict']
959
assert this_name != other_name
961
this_name = other_name = self.tt.final_name(trans_id)
962
other_path = fp.get_path(trans_id)
963
if this_parent is not None and this_name is not None:
965
fp.get_path(self.tt.trans_id_file_id(this_parent))
966
this_path = pathjoin(this_parent_path, this_name)
968
this_path = "<deleted>"
969
file_id = self.tt.final_file_id(trans_id)
970
c = Conflict.factory('path conflict', path=this_path,
971
conflict_path=other_path, file_id=file_id)
972
self.cooked_conflicts.append(c)
973
self.cooked_conflicts.sort(key=Conflict.sort_key)
976
class WeaveMerger(Merge3Merger):
977
"""Three-way tree merger, text weave merger."""
978
supports_reprocess = True
979
supports_show_base = False
981
def __init__(self, working_tree, this_tree, base_tree, other_tree,
982
interesting_ids=None, pb=DummyProgress(), pp=None,
983
reprocess=False, change_reporter=None,
984
interesting_files=None):
985
super(WeaveMerger, self).__init__(working_tree, this_tree,
986
base_tree, other_tree,
987
interesting_ids=interesting_ids,
988
pb=pb, pp=pp, reprocess=reprocess,
989
change_reporter=change_reporter)
991
def _merged_lines(self, file_id):
992
"""Generate the merged lines.
993
There is no distinction between lines that are meant to contain <<<<<<<
996
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
997
if 'merge' in debug.debug_flags:
999
trans_id = self.tt.trans_id_file_id(file_id)
1000
name = self.tt.final_name(trans_id) + '.plan'
1001
contents = ('%10s|%s' % l for l in plan)
1002
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1003
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1004
'>>>>>>> MERGE-SOURCE\n')
1005
return textmerge.merge_lines(self.reprocess)
1007
def text_merge(self, file_id, trans_id):
1008
"""Perform a (weave) text merge for a given file and file-id.
1009
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1010
and a conflict will be noted.
1012
lines, conflicts = self._merged_lines(file_id)
1014
# Note we're checking whether the OUTPUT is binary in this case,
1015
# because we don't want to get into weave merge guts.
1016
check_text_lines(lines)
1017
self.tt.create_file(lines, trans_id)
1019
self._raw_conflicts.append(('text conflict', trans_id))
1020
name = self.tt.final_name(trans_id)
1021
parent_id = self.tt.final_parent(trans_id)
1022
file_group = self._dump_conflicts(name, parent_id, file_id,
1024
file_group.append(trans_id)
1027
class Diff3Merger(Merge3Merger):
1028
"""Three-way merger using external diff3 for text merging"""
1030
def dump_file(self, temp_dir, name, tree, file_id):
1031
out_path = pathjoin(temp_dir, name)
1032
out_file = open(out_path, "wb")
1034
in_file = tree.get_file(file_id)
1035
for line in in_file:
1036
out_file.write(line)
1041
def text_merge(self, file_id, trans_id):
1042
"""Perform a diff3 merge using a specified file-id and trans-id.
1043
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1044
will be dumped, and a will be conflict noted.
1047
temp_dir = osutils.mkdtemp(prefix="bzr-")
1049
new_file = pathjoin(temp_dir, "new")
1050
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1051
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1052
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1053
status = bzrlib.patch.diff3(new_file, this, base, other)
1054
if status not in (0, 1):
1055
raise BzrError("Unhandled diff3 exit code")
1056
f = open(new_file, 'rb')
1058
self.tt.create_file(f, trans_id)
1062
name = self.tt.final_name(trans_id)
1063
parent_id = self.tt.final_parent(trans_id)
1064
self._dump_conflicts(name, parent_id, file_id)
1065
self._raw_conflicts.append(('text conflict', trans_id))
1067
osutils.rmtree(temp_dir)
1070
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1072
merge_type=Merge3Merger,
1073
interesting_ids=None,
1077
interesting_files=None,
1080
change_reporter=None):
1081
"""Primary interface for merging.
1083
typical use is probably
1084
'merge_inner(branch, branch.get_revision_tree(other_revision),
1085
branch.get_revision_tree(base_revision))'
1087
if this_tree is None:
1088
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1089
"parameter as of bzrlib version 0.8.")
1090
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1091
pb=pb, change_reporter=change_reporter)
1092
merger.backup_files = backup_files
1093
merger.merge_type = merge_type
1094
merger.interesting_ids = interesting_ids
1095
merger.ignore_zero = ignore_zero
1096
if interesting_files:
1097
assert not interesting_ids, ('Only supply interesting_ids'
1098
' or interesting_files')
1099
merger.interesting_files = interesting_files
1100
merger.show_base = show_base
1101
merger.reprocess = reprocess
1102
merger.other_rev_id = other_rev_id
1103
merger.other_basis = other_rev_id
1104
return merger.do_merge()
1106
def get_merge_type_registry():
1107
"""Merge type registry is in bzrlib.option to avoid circular imports.
1109
This method provides a sanctioned way to retrieve it.
1111
from bzrlib import option
1112
return option._merge_type_registry
1115
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1116
def status_a(revision, text):
1117
if revision in ancestors_b:
1118
return 'killed-b', text
1120
return 'new-a', text
1122
def status_b(revision, text):
1123
if revision in ancestors_a:
1124
return 'killed-a', text
1126
return 'new-b', text
1128
plain_a = [t for (a, t) in annotated_a]
1129
plain_b = [t for (a, t) in annotated_b]
1130
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1131
blocks = matcher.get_matching_blocks()
1134
for ai, bi, l in blocks:
1135
# process all mismatched sections
1136
# (last mismatched section is handled because blocks always
1137
# includes a 0-length last block)
1138
for revision, text in annotated_a[a_cur:ai]:
1139
yield status_a(revision, text)
1140
for revision, text in annotated_b[b_cur:bi]:
1141
yield status_b(revision, text)
1143
# and now the matched section
1146
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1147
assert text_a == text_b
1148
yield "unchanged", text_a
1151
class _PlanMerge(object):
1152
"""Plan an annotate merge using on-the-fly annotation"""
1154
def __init__(self, a_rev, b_rev, vf):
1157
:param a_rev: Revision-id of one revision to merge
1158
:param b_rev: Revision-id of the other revision to merge
1159
:param vf: A versionedfile containing both revisions
1163
self.lines_a = vf.get_lines(a_rev)
1164
self.lines_b = vf.get_lines(b_rev)
1166
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1167
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1168
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1169
self._last_lines = None
1170
self._last_lines_revision_id = None
1172
def plan_merge(self):
1173
"""Generate a 'plan' for merging the two revisions.
1175
This involves comparing their texts and determining the cause of
1176
differences. If text A has a line and text B does not, then either the
1177
line was added to text A, or it was deleted from B. Once the causes
1178
are combined, they are written out in the format described in
1179
VersionedFile.plan_merge
1181
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1182
new_a = self._find_new(self.a_rev)
1183
new_b = self._find_new(self.b_rev)
1186
a_lines = self.vf.get_lines(self.a_rev)
1187
b_lines = self.vf.get_lines(self.b_rev)
1188
for i, j, n in blocks:
1189
# determine why lines aren't common
1190
for a_index in range(last_i, i):
1191
if a_index in new_a:
1195
yield cause, a_lines[a_index]
1196
for b_index in range(last_j, j):
1197
if b_index in new_b:
1201
yield cause, b_lines[b_index]
1202
# handle common lines
1203
for a_index in range(i, i+n):
1204
yield 'unchanged', a_lines[a_index]
1208
def _get_matching_blocks(self, left_revision, right_revision):
1209
"""Return a description of which sections of two revisions match.
1211
See SequenceMatcher.get_matching_blocks
1213
if self._last_lines_revision_id == left_revision:
1214
left_lines = self._last_lines
1216
left_lines = self.vf.get_lines(left_revision)
1217
right_lines = self.vf.get_lines(right_revision)
1218
self._last_lines = right_lines
1219
self._last_lines_revision_id = right_revision
1220
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1222
return matcher.get_matching_blocks()
1224
def _unique_lines(self, matching_blocks):
1225
"""Analyse matching_blocks to determine which lines are unique
1227
:return: a tuple of (unique_left, unique_right), where the values are
1228
sets of line numbers of unique lines.
1234
for i, j, n in matching_blocks:
1235
unique_left.extend(range(last_i, i))
1236
unique_right.extend(range(last_j, j))
1239
return unique_left, unique_right
1241
def _find_new(self, version_id):
1242
"""Determine which lines are new in the ancestry of this version.
1244
If a lines is present in this version, and not present in any
1245
common ancestor, it is considered new.
1247
if version_id not in self.uncommon:
1249
parents = self.vf.get_parents(version_id)
1250
if len(parents) == 0:
1251
return set(range(len(self.vf.get_lines(version_id))))
1253
for parent in parents:
1254
blocks = self._get_matching_blocks(version_id, parent)
1255
result, unused = self._unique_lines(blocks)
1256
parent_new = self._find_new(parent)
1257
for i, j, n in blocks:
1258
for ii, jj in [(i+r, j+r) for r in range(n)]:
1259
if jj in parent_new:
1264
new.intersection_update(result)