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
29
revision as _mod_revision,
31
from bzrlib.branch import Branch
32
from bzrlib.conflicts import ConflictList, Conflict
33
from bzrlib.errors import (BzrCommandError,
43
WorkingTreeNotRevision,
46
from bzrlib.merge3 import Merge3
47
from bzrlib.osutils import rename, pathjoin
48
from progress import DummyProgress, ProgressPhase
49
from bzrlib.revision import (NULL_REVISION, ensure_null)
50
from bzrlib.textfile import check_text_lines
51
from bzrlib.trace import mutter, warning, note
52
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
53
conflict_pass, FinalPaths, create_by_entry,
54
unique_add, ROOT_PARENT)
55
from bzrlib.versionedfile import PlanWeaveMerge
58
# TODO: Report back as changes are merged in
61
def transform_tree(from_tree, to_tree, interesting_ids=None):
62
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
63
interesting_ids=interesting_ids, this_tree=from_tree)
67
def __init__(self, this_branch, other_tree=None, base_tree=None,
68
this_tree=None, pb=DummyProgress(), change_reporter=None,
71
assert this_tree is not None, "this_tree is required"
72
self.this_branch = this_branch
73
self.this_basis = _mod_revision.ensure_null(
74
this_branch.last_revision())
75
self.this_rev_id = None
76
self.this_tree = this_tree
77
self.this_revision_tree = None
78
self.this_basis_tree = None
79
self.other_tree = other_tree
80
self.other_branch = None
81
self.base_tree = base_tree
82
self.ignore_zero = False
83
self.backup_files = False
84
self.interesting_ids = None
85
self.interesting_files = None
86
self.show_base = False
87
self.reprocess = False
90
self.recurse = recurse
91
self.change_reporter = change_reporter
92
self._cached_trees = {}
95
def from_uncommitted(tree, other_tree, pb):
96
"""Return a Merger for uncommitted changes in other_tree.
98
:param tree: The tree to merge into
99
:param other_tree: The tree to get uncommitted changes from
100
:param pb: A progress indicator
102
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
104
merger.base_rev_id = merger.base_tree.get_revision_id()
105
merger.other_rev_id = None
109
def from_mergeable(klass, tree, mergeable, pb):
110
"""Return a Merger for a bundle or merge directive.
112
:param tree: The tree to merge changes into
113
:param mergeable: A merge directive or bundle
114
:param pb: A progress indicator
116
mergeable.install_revisions(tree.branch.repository)
117
base_revision_id, other_revision_id, verified =\
118
mergeable.get_merge_request(tree.branch.repository)
119
if (base_revision_id != _mod_revision.NULL_REVISION and
120
tree.branch.repository.get_graph().is_ancestor(
121
base_revision_id, tree.branch.last_revision())):
122
base_revision_id = None
123
merger = klass.from_revision_ids(pb, tree, other_revision_id,
125
return merger, verified
128
def from_revision_ids(pb, this, other, base=None, other_branch=None,
130
"""Return a Merger for revision-ids.
132
:param tree: The tree to merge changes into
133
:param other: The revision-id to use as OTHER
134
:param base: The revision-id to use as BASE. If not specified, will
136
:param other_branch: A branch containing the other revision-id. If
137
not supplied, this.branch is used.
138
:param base_branch: A branch containing the base revision-id. If
139
not supplied, other_branch or this.branch will be used.
140
:param pb: A progress indicator
142
merger = Merger(this.branch, this_tree=this, pb=pb)
143
if other_branch is None:
144
other_branch = this.branch
145
merger.set_other_revision(other, other_branch)
149
if base_branch is None:
150
base_branch = other_branch
151
merger.set_base_revision(base, base_branch)
154
def revision_tree(self, revision_id, branch=None):
155
if revision_id not in self._cached_trees:
157
branch = self.this_branch
159
tree = self.this_tree.revision_tree(revision_id)
160
except errors.NoSuchRevisionInTree:
161
tree = branch.repository.revision_tree(revision_id)
162
self._cached_trees[revision_id] = tree
163
return self._cached_trees[revision_id]
165
def _get_tree(self, treespec, possible_transports=None):
166
from bzrlib import workingtree
167
location, revno = treespec
169
tree = workingtree.WorkingTree.open_containing(location)[0]
170
return tree.branch, tree
171
branch = Branch.open_containing(location, possible_transports)[0]
173
revision_id = branch.last_revision()
175
revision_id = branch.get_rev_id(revno)
176
revision_id = ensure_null(revision_id)
177
return branch, self.revision_tree(revision_id, branch)
179
def ensure_revision_trees(self):
180
if self.this_revision_tree is None:
181
self.this_basis_tree = self.revision_tree(self.this_basis)
182
if self.this_basis == self.this_rev_id:
183
self.this_revision_tree = self.this_basis_tree
185
if self.other_rev_id is None:
186
other_basis_tree = self.revision_tree(self.other_basis)
187
changes = other_basis_tree.changes_from(self.other_tree)
188
if changes.has_changed():
189
raise WorkingTreeNotRevision(self.this_tree)
190
other_rev_id = self.other_basis
191
self.other_tree = other_basis_tree
193
def file_revisions(self, file_id):
194
self.ensure_revision_trees()
195
def get_id(tree, file_id):
196
revision_id = tree.inventory[file_id].revision
197
assert revision_id is not None
199
if self.this_rev_id is None:
200
if self.this_basis_tree.get_file_sha1(file_id) != \
201
self.this_tree.get_file_sha1(file_id):
202
raise WorkingTreeNotRevision(self.this_tree)
204
trees = (self.this_basis_tree, self.other_tree)
205
return [get_id(tree, file_id) for tree in trees]
207
def check_basis(self, check_clean, require_commits=True):
208
if self.this_basis is None and require_commits is True:
209
raise BzrCommandError("This branch has no commits."
210
" (perhaps you would prefer 'bzr pull')")
213
if self.this_basis != self.this_rev_id:
214
raise errors.UncommittedChanges(self.this_tree)
216
def compare_basis(self):
218
basis_tree = self.revision_tree(self.this_tree.last_revision())
219
except errors.RevisionNotPresent:
220
basis_tree = self.this_tree.basis_tree()
221
changes = self.this_tree.changes_from(basis_tree)
222
if not changes.has_changed():
223
self.this_rev_id = self.this_basis
225
def set_interesting_files(self, file_list):
226
self.interesting_files = file_list
228
def set_pending(self):
229
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
233
def _add_parent(self):
234
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
235
new_parent_trees = []
236
for revision_id in new_parents:
238
tree = self.revision_tree(revision_id)
239
except errors.RevisionNotPresent:
243
new_parent_trees.append((revision_id, tree))
245
self.this_tree.set_parent_trees(new_parent_trees,
246
allow_leftmost_as_ghost=True)
248
for _revision_id, tree in new_parent_trees:
252
def set_other(self, other_revision, possible_transports=None):
253
"""Set the revision and tree to merge from.
255
This sets the other_tree, other_rev_id, other_basis attributes.
257
:param other_revision: The [path, revision] list to merge from.
259
self.other_branch, self.other_tree = self._get_tree(other_revision,
261
if other_revision[1] == -1:
262
self.other_rev_id = _mod_revision.ensure_null(
263
self.other_branch.last_revision())
264
if _mod_revision.is_null(self.other_rev_id):
265
raise NoCommits(self.other_branch)
266
self.other_basis = self.other_rev_id
267
elif other_revision[1] is not None:
268
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
269
self.other_basis = self.other_rev_id
271
self.other_rev_id = None
272
self.other_basis = self.other_branch.last_revision()
273
if self.other_basis is None:
274
raise NoCommits(self.other_branch)
275
if self.other_rev_id is not None:
276
self._cached_trees[self.other_rev_id] = self.other_tree
277
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
279
def set_other_revision(self, revision_id, other_branch):
280
"""Set 'other' based on a branch and revision id
282
:param revision_id: The revision to use for a tree
283
:param other_branch: The branch containing this tree
285
self.other_rev_id = revision_id
286
self.other_branch = other_branch
287
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
288
self.other_tree = self.revision_tree(revision_id)
289
self.other_basis = revision_id
291
def set_base_revision(self, revision_id, branch):
292
"""Set 'base' based on a branch and revision id
294
:param revision_id: The revision to use for a tree
295
:param branch: The branch containing this tree
297
self.base_rev_id = revision_id
298
self.base_branch = branch
299
self._maybe_fetch(branch, self.this_branch, revision_id)
300
self.base_tree = self.revision_tree(revision_id)
301
graph = self.this_branch.repository.get_graph()
302
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
304
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
307
def _maybe_fetch(self, source, target, revision_id):
308
if not source.repository.has_same_location(target.repository):
309
target.fetch(source, revision_id)
312
this_repo = self.this_branch.repository
313
graph = this_repo.get_graph()
314
revisions = [ensure_null(self.this_basis),
315
ensure_null(self.other_basis)]
316
if NULL_REVISION in revisions:
317
self.base_rev_id = NULL_REVISION
319
self.base_rev_id, steps = graph.find_unique_lca(revisions[0],
320
revisions[1], count_steps=True)
321
if self.base_rev_id == NULL_REVISION:
322
raise UnrelatedBranches()
324
warning('Warning: criss-cross merge encountered. See bzr'
325
' help criss-cross.')
326
self.base_tree = self.revision_tree(self.base_rev_id)
327
self.base_is_ancestor = True
328
self.base_is_other_ancestor = True
330
def set_base(self, base_revision):
331
"""Set the base revision to use for the merge.
333
:param base_revision: A 2-list containing a path and revision number.
335
mutter("doing merge() with no base_revision specified")
336
if base_revision == [None, None]:
339
base_branch, self.base_tree = self._get_tree(base_revision)
340
if base_revision[1] == -1:
341
self.base_rev_id = base_branch.last_revision()
342
elif base_revision[1] is None:
343
self.base_rev_id = _mod_revision.NULL_REVISION
345
self.base_rev_id = _mod_revision.ensure_null(
346
base_branch.get_rev_id(base_revision[1]))
347
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
348
graph = self.this_branch.repository.get_graph()
349
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
351
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
355
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
356
'other_tree': self.other_tree,
357
'interesting_ids': self.interesting_ids,
358
'interesting_files': self.interesting_files,
360
if self.merge_type.requires_base:
361
kwargs['base_tree'] = self.base_tree
362
if self.merge_type.supports_reprocess:
363
kwargs['reprocess'] = self.reprocess
365
raise BzrError("Conflict reduction is not supported for merge"
366
" type %s." % self.merge_type)
367
if self.merge_type.supports_show_base:
368
kwargs['show_base'] = self.show_base
370
raise BzrError("Showing base is not supported for this"
371
" merge type. %s" % self.merge_type)
372
self.this_tree.lock_tree_write()
373
if self.base_tree is not None:
374
self.base_tree.lock_read()
375
if self.other_tree is not None:
376
self.other_tree.lock_read()
378
merge = self.merge_type(pb=self._pb,
379
change_reporter=self.change_reporter,
381
if self.recurse == 'down':
382
for path, file_id in self.this_tree.iter_references():
383
sub_tree = self.this_tree.get_nested_tree(file_id, path)
384
other_revision = self.other_tree.get_reference_revision(
386
if other_revision == sub_tree.last_revision():
388
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
389
sub_merge.merge_type = self.merge_type
390
relpath = self.this_tree.relpath(path)
391
other_branch = self.other_branch.reference_parent(file_id, relpath)
392
sub_merge.set_other_revision(other_revision, other_branch)
393
base_revision = self.base_tree.get_reference_revision(file_id)
394
sub_merge.base_tree = \
395
sub_tree.branch.repository.revision_tree(base_revision)
399
if self.other_tree is not None:
400
self.other_tree.unlock()
401
if self.base_tree is not None:
402
self.base_tree.unlock()
403
self.this_tree.unlock()
404
if len(merge.cooked_conflicts) == 0:
405
if not self.ignore_zero:
406
note("All changes applied successfully.")
408
note("%d conflicts encountered." % len(merge.cooked_conflicts))
410
return len(merge.cooked_conflicts)
413
class Merge3Merger(object):
414
"""Three-way merger that uses the merge3 text merger"""
416
supports_reprocess = True
417
supports_show_base = True
418
history_based = False
419
winner_idx = {"this": 2, "other": 1, "conflict": 1}
421
def __init__(self, working_tree, this_tree, base_tree, other_tree,
422
interesting_ids=None, reprocess=False, show_base=False,
423
pb=DummyProgress(), pp=None, change_reporter=None,
424
interesting_files=None):
425
"""Initialize the merger object and perform the merge.
427
:param working_tree: The working tree to apply the merge to
428
:param this_tree: The local tree in the merge operation
429
:param base_tree: The common tree in the merge operation
430
:param other_tree: The other other tree to merge changes from
431
:param interesting_ids: The file_ids of files that should be
432
participate in the merge. May not be combined with
434
:param: reprocess If True, perform conflict-reduction processing.
435
:param show_base: If True, show the base revision in text conflicts.
436
(incompatible with reprocess)
437
:param pb: A Progress bar
438
:param pp: A ProgressPhase object
439
:param change_reporter: An object that should report changes made
440
:param interesting_files: The tree-relative paths of files that should
441
participate in the merge. If these paths refer to directories,
442
the contents of those directories will also be included. May not
443
be combined with interesting_ids. If neither interesting_files nor
444
interesting_ids is specified, all files may participate in the
447
object.__init__(self)
448
if interesting_files is not None:
449
assert interesting_ids is None
450
self.interesting_ids = interesting_ids
451
self.interesting_files = interesting_files
452
self.this_tree = working_tree
453
self.this_tree.lock_tree_write()
454
self.base_tree = base_tree
455
self.base_tree.lock_read()
456
self.other_tree = other_tree
457
self.other_tree.lock_read()
458
self._raw_conflicts = []
459
self.cooked_conflicts = []
460
self.reprocess = reprocess
461
self.show_base = show_base
464
self.change_reporter = change_reporter
466
self.pp = ProgressPhase("Merge phase", 3, self.pb)
468
self.tt = TreeTransform(working_tree, self.pb)
471
entries = self._entries3()
472
child_pb = ui.ui_factory.nested_progress_bar()
474
for num, (file_id, changed, parents3, names3,
475
executable3) in enumerate(entries):
476
child_pb.update('Preparing file merge', num, len(entries))
477
self._merge_names(file_id, parents3, names3)
479
file_status = self.merge_contents(file_id)
481
file_status = 'unmodified'
482
self._merge_executable(file_id,
483
executable3, file_status)
488
child_pb = ui.ui_factory.nested_progress_bar()
490
fs_conflicts = resolve_conflicts(self.tt, child_pb,
491
lambda t, c: conflict_pass(t, c, self.other_tree))
494
if change_reporter is not None:
495
from bzrlib import delta
496
delta.report_changes(self.tt._iter_changes(), change_reporter)
497
self.cook_conflicts(fs_conflicts)
498
for conflict in self.cooked_conflicts:
501
results = self.tt.apply(no_conflicts=True)
502
self.write_modified(results)
504
working_tree.add_conflicts(self.cooked_conflicts)
505
except UnsupportedOperation:
509
self.other_tree.unlock()
510
self.base_tree.unlock()
511
self.this_tree.unlock()
515
"""Gather data about files modified between three trees.
517
Return a list of tuples of file_id, changed, parents3, names3,
518
executable3. changed is a boolean indicating whether the file contents
519
or kind were changed. parents3 is a tuple of parent ids for base,
520
other and this. names3 is a tuple of names for base, other and this.
521
executable3 is a tuple of execute-bit values for base, other and this.
524
iterator = self.other_tree._iter_changes(self.base_tree,
525
include_unchanged=True, specific_files=self.interesting_files,
526
extra_trees=[self.this_tree])
527
for (file_id, paths, changed, versioned, parents, names, kind,
528
executable) in iterator:
529
if (self.interesting_ids is not None and
530
file_id not in self.interesting_ids):
532
if file_id in self.this_tree.inventory:
533
entry = self.this_tree.inventory[file_id]
534
this_name = entry.name
535
this_parent = entry.parent_id
536
this_executable = entry.executable
540
this_executable = None
541
parents3 = parents + (this_parent,)
542
names3 = names + (this_name,)
543
executable3 = executable + (this_executable,)
544
result.append((file_id, changed, parents3, names3, executable3))
549
self.tt.final_kind(self.tt.root)
551
self.tt.cancel_deletion(self.tt.root)
552
if self.tt.final_file_id(self.tt.root) is None:
553
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
555
if self.other_tree.inventory.root is None:
557
other_root_file_id = self.other_tree.get_root_id()
558
other_root = self.tt.trans_id_file_id(other_root_file_id)
559
if other_root == self.tt.root:
562
self.tt.final_kind(other_root)
565
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
566
self.tt.cancel_creation(other_root)
567
self.tt.cancel_versioning(other_root)
569
def reparent_children(self, ie, target):
570
for thing, child in ie.children.iteritems():
571
trans_id = self.tt.trans_id_file_id(child.file_id)
572
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
574
def write_modified(self, results):
576
for path in results.modified_paths:
577
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
580
hash = self.this_tree.get_file_sha1(file_id)
583
modified_hashes[file_id] = hash
584
self.this_tree.set_merge_modified(modified_hashes)
587
def parent(entry, file_id):
588
"""Determine the parent for a file_id (used as a key method)"""
591
return entry.parent_id
594
def name(entry, file_id):
595
"""Determine the name for a file_id (used as a key method)"""
601
def contents_sha1(tree, file_id):
602
"""Determine the sha1 of the file contents (used as a key method)."""
603
if file_id not in tree:
605
return tree.get_file_sha1(file_id)
608
def executable(tree, file_id):
609
"""Determine the executability of a file-id (used as a key method)."""
610
if file_id not in tree:
612
if tree.kind(file_id) != "file":
614
return tree.is_executable(file_id)
617
def kind(tree, file_id):
618
"""Determine the kind of a file-id (used as a key method)."""
619
if file_id not in tree:
621
return tree.kind(file_id)
624
def _three_way(base, other, this):
625
#if base == other, either they all agree, or only THIS has changed.
628
elif this not in (base, other):
630
# "Ambiguous clean merge" -- both sides have made the same change.
633
# this == base: only other has changed.
638
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
639
"""Do a three-way test on a scalar.
640
Return "this", "other" or "conflict", depending whether a value wins.
642
key_base = key(base_tree, file_id)
643
key_other = key(other_tree, file_id)
644
#if base == other, either they all agree, or only THIS has changed.
645
if key_base == key_other:
647
key_this = key(this_tree, file_id)
648
if key_this not in (key_base, key_other):
650
# "Ambiguous clean merge"
651
elif key_this == key_other:
654
assert key_this == key_base
657
def merge_names(self, file_id):
659
if file_id in tree.inventory:
660
return tree.inventory[file_id]
663
this_entry = get_entry(self.this_tree)
664
other_entry = get_entry(self.other_tree)
665
base_entry = get_entry(self.base_tree)
666
entries = (base_entry, other_entry, this_entry)
669
for entry in entries:
674
names.append(entry.name)
675
parents.append(entry.parent_id)
676
return self._merge_names(file_id, parents, names)
678
def _merge_names(self, file_id, parents, names):
679
"""Perform a merge on file_id names and parents"""
680
base_name, other_name, this_name = names
681
base_parent, other_parent, this_parent = parents
683
name_winner = self._three_way(*names)
685
parent_id_winner = self._three_way(*parents)
686
if this_name is None:
687
if name_winner == "this":
688
name_winner = "other"
689
if parent_id_winner == "this":
690
parent_id_winner = "other"
691
if name_winner == "this" and parent_id_winner == "this":
693
if name_winner == "conflict":
694
trans_id = self.tt.trans_id_file_id(file_id)
695
self._raw_conflicts.append(('name conflict', trans_id,
696
this_name, other_name))
697
if parent_id_winner == "conflict":
698
trans_id = self.tt.trans_id_file_id(file_id)
699
self._raw_conflicts.append(('parent conflict', trans_id,
700
this_parent, other_parent))
701
if other_name is None:
702
# it doesn't matter whether the result was 'other' or
703
# 'conflict'-- if there's no 'other', we leave it alone.
705
# if we get here, name_winner and parent_winner are set to safe values.
706
trans_id = self.tt.trans_id_file_id(file_id)
707
parent_id = parents[self.winner_idx[parent_id_winner]]
708
if parent_id is not None:
709
parent_trans_id = self.tt.trans_id_file_id(parent_id)
710
self.tt.adjust_path(names[self.winner_idx[name_winner]],
711
parent_trans_id, trans_id)
713
def merge_contents(self, file_id):
714
"""Performa a merge on file_id contents."""
715
def contents_pair(tree):
716
if file_id not in tree:
718
kind = tree.kind(file_id)
720
contents = tree.get_file_sha1(file_id)
721
elif kind == "symlink":
722
contents = tree.get_symlink_target(file_id)
725
return kind, contents
727
def contents_conflict():
728
trans_id = self.tt.trans_id_file_id(file_id)
729
name = self.tt.final_name(trans_id)
730
parent_id = self.tt.final_parent(trans_id)
731
if file_id in self.this_tree.inventory:
732
self.tt.unversion_file(trans_id)
733
if file_id in self.this_tree:
734
self.tt.delete_contents(trans_id)
735
file_group = self._dump_conflicts(name, parent_id, file_id,
737
self._raw_conflicts.append(('contents conflict', file_group))
739
# See SPOT run. run, SPOT, run.
740
# So we're not QUITE repeating ourselves; we do tricky things with
742
base_pair = contents_pair(self.base_tree)
743
other_pair = contents_pair(self.other_tree)
744
if base_pair == other_pair:
745
# OTHER introduced no changes
747
this_pair = contents_pair(self.this_tree)
748
if this_pair == other_pair:
749
# THIS and OTHER introduced the same changes
752
trans_id = self.tt.trans_id_file_id(file_id)
753
if this_pair == base_pair:
754
# only OTHER introduced changes
755
if file_id in self.this_tree:
756
# Remove any existing contents
757
self.tt.delete_contents(trans_id)
758
if file_id in self.other_tree:
759
# OTHER changed the file
760
create_by_entry(self.tt,
761
self.other_tree.inventory[file_id],
762
self.other_tree, trans_id)
763
if file_id not in self.this_tree.inventory:
764
self.tt.version_file(file_id, trans_id)
766
elif file_id in self.this_tree.inventory:
767
# OTHER deleted the file
768
self.tt.unversion_file(trans_id)
770
#BOTH THIS and OTHER introduced changes; scalar conflict
771
elif this_pair[0] == "file" and other_pair[0] == "file":
772
# THIS and OTHER are both files, so text merge. Either
773
# BASE is a file, or both converted to files, so at least we
774
# have agreement that output should be a file.
776
self.text_merge(file_id, trans_id)
778
return contents_conflict()
779
if file_id not in self.this_tree.inventory:
780
self.tt.version_file(file_id, trans_id)
782
self.tt.tree_kind(trans_id)
783
self.tt.delete_contents(trans_id)
788
# Scalar conflict, can't text merge. Dump conflicts
789
return contents_conflict()
791
def get_lines(self, tree, file_id):
792
"""Return the lines in a file, or an empty list."""
794
return tree.get_file(file_id).readlines()
798
def text_merge(self, file_id, trans_id):
799
"""Perform a three-way text merge on a file_id"""
800
# it's possible that we got here with base as a different type.
801
# if so, we just want two-way text conflicts.
802
if file_id in self.base_tree and \
803
self.base_tree.kind(file_id) == "file":
804
base_lines = self.get_lines(self.base_tree, file_id)
807
other_lines = self.get_lines(self.other_tree, file_id)
808
this_lines = self.get_lines(self.this_tree, file_id)
809
m3 = Merge3(base_lines, this_lines, other_lines)
810
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
811
if self.show_base is True:
812
base_marker = '|' * 7
816
def iter_merge3(retval):
817
retval["text_conflicts"] = False
818
for line in m3.merge_lines(name_a = "TREE",
819
name_b = "MERGE-SOURCE",
820
name_base = "BASE-REVISION",
821
start_marker=start_marker,
822
base_marker=base_marker,
823
reprocess=self.reprocess):
824
if line.startswith(start_marker):
825
retval["text_conflicts"] = True
826
yield line.replace(start_marker, '<' * 7)
830
merge3_iterator = iter_merge3(retval)
831
self.tt.create_file(merge3_iterator, trans_id)
832
if retval["text_conflicts"] is True:
833
self._raw_conflicts.append(('text conflict', trans_id))
834
name = self.tt.final_name(trans_id)
835
parent_id = self.tt.final_parent(trans_id)
836
file_group = self._dump_conflicts(name, parent_id, file_id,
837
this_lines, base_lines,
839
file_group.append(trans_id)
841
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
842
base_lines=None, other_lines=None, set_version=False,
844
"""Emit conflict files.
845
If this_lines, base_lines, or other_lines are omitted, they will be
846
determined automatically. If set_version is true, the .OTHER, .THIS
847
or .BASE (in that order) will be created as versioned files.
849
data = [('OTHER', self.other_tree, other_lines),
850
('THIS', self.this_tree, this_lines)]
852
data.append(('BASE', self.base_tree, base_lines))
855
for suffix, tree, lines in data:
857
trans_id = self._conflict_file(name, parent_id, tree, file_id,
859
file_group.append(trans_id)
860
if set_version and not versioned:
861
self.tt.version_file(file_id, trans_id)
865
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
867
"""Emit a single conflict file."""
868
name = name + '.' + suffix
869
trans_id = self.tt.create_path(name, parent_id)
870
entry = tree.inventory[file_id]
871
create_by_entry(self.tt, entry, tree, trans_id, lines)
874
def merge_executable(self, file_id, file_status):
875
"""Perform a merge on the execute bit."""
876
executable = [self.executable(t, file_id) for t in (self.base_tree,
877
self.other_tree, self.this_tree)]
878
self._merge_executable(file_id, executable, file_status)
880
def _merge_executable(self, file_id, executable, file_status):
881
"""Perform a merge on the execute bit."""
882
base_executable, other_executable, this_executable = executable
883
if file_status == "deleted":
885
trans_id = self.tt.trans_id_file_id(file_id)
887
if self.tt.final_kind(trans_id) != "file":
891
winner = self._three_way(*executable)
892
if winner == "conflict":
893
# There must be a None in here, if we have a conflict, but we
894
# need executability since file status was not deleted.
895
if self.executable(self.other_tree, file_id) is None:
900
if file_status == "modified":
901
executability = this_executable
902
if executability is not None:
903
trans_id = self.tt.trans_id_file_id(file_id)
904
self.tt.set_executability(executability, trans_id)
906
assert winner == "other"
907
if file_id in self.other_tree:
908
executability = other_executable
909
elif file_id in self.this_tree:
910
executability = this_executable
911
elif file_id in self.base_tree:
912
executability = base_executable
913
if executability is not None:
914
trans_id = self.tt.trans_id_file_id(file_id)
915
self.tt.set_executability(executability, trans_id)
917
def cook_conflicts(self, fs_conflicts):
918
"""Convert all conflicts into a form that doesn't depend on trans_id"""
919
from conflicts import Conflict
921
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
922
fp = FinalPaths(self.tt)
923
for conflict in self._raw_conflicts:
924
conflict_type = conflict[0]
925
if conflict_type in ('name conflict', 'parent conflict'):
926
trans_id = conflict[1]
927
conflict_args = conflict[2:]
928
if trans_id not in name_conflicts:
929
name_conflicts[trans_id] = {}
930
unique_add(name_conflicts[trans_id], conflict_type,
932
if conflict_type == 'contents conflict':
933
for trans_id in conflict[1]:
934
file_id = self.tt.final_file_id(trans_id)
935
if file_id is not None:
937
path = fp.get_path(trans_id)
938
for suffix in ('.BASE', '.THIS', '.OTHER'):
939
if path.endswith(suffix):
940
path = path[:-len(suffix)]
942
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
943
self.cooked_conflicts.append(c)
944
if conflict_type == 'text conflict':
945
trans_id = conflict[1]
946
path = fp.get_path(trans_id)
947
file_id = self.tt.final_file_id(trans_id)
948
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
949
self.cooked_conflicts.append(c)
951
for trans_id, conflicts in name_conflicts.iteritems():
953
this_parent, other_parent = conflicts['parent conflict']
954
assert this_parent != other_parent
956
this_parent = other_parent = \
957
self.tt.final_file_id(self.tt.final_parent(trans_id))
959
this_name, other_name = conflicts['name conflict']
960
assert this_name != other_name
962
this_name = other_name = self.tt.final_name(trans_id)
963
other_path = fp.get_path(trans_id)
964
if this_parent is not None and this_name is not None:
966
fp.get_path(self.tt.trans_id_file_id(this_parent))
967
this_path = pathjoin(this_parent_path, this_name)
969
this_path = "<deleted>"
970
file_id = self.tt.final_file_id(trans_id)
971
c = Conflict.factory('path conflict', path=this_path,
972
conflict_path=other_path, file_id=file_id)
973
self.cooked_conflicts.append(c)
974
self.cooked_conflicts.sort(key=Conflict.sort_key)
977
class WeaveMerger(Merge3Merger):
978
"""Three-way tree merger, text weave merger."""
979
supports_reprocess = True
980
supports_show_base = False
982
def __init__(self, working_tree, this_tree, base_tree, other_tree,
983
interesting_ids=None, pb=DummyProgress(), pp=None,
984
reprocess=False, change_reporter=None,
985
interesting_files=None):
986
super(WeaveMerger, self).__init__(working_tree, this_tree,
987
base_tree, other_tree,
988
interesting_ids=interesting_ids,
989
pb=pb, pp=pp, reprocess=reprocess,
990
change_reporter=change_reporter)
992
def _merged_lines(self, file_id):
993
"""Generate the merged lines.
994
There is no distinction between lines that are meant to contain <<<<<<<
997
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
999
if 'merge' in debug.debug_flags:
1001
trans_id = self.tt.trans_id_file_id(file_id)
1002
name = self.tt.final_name(trans_id) + '.plan'
1003
contents = ('%10s|%s' % l for l in plan)
1004
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1005
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1006
'>>>>>>> MERGE-SOURCE\n')
1007
return textmerge.merge_lines(self.reprocess)
1009
def text_merge(self, file_id, trans_id):
1010
"""Perform a (weave) text merge for a given file and file-id.
1011
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1012
and a conflict will be noted.
1014
lines, conflicts = self._merged_lines(file_id)
1016
# Note we're checking whether the OUTPUT is binary in this case,
1017
# because we don't want to get into weave merge guts.
1018
check_text_lines(lines)
1019
self.tt.create_file(lines, trans_id)
1021
self._raw_conflicts.append(('text conflict', trans_id))
1022
name = self.tt.final_name(trans_id)
1023
parent_id = self.tt.final_parent(trans_id)
1024
file_group = self._dump_conflicts(name, parent_id, file_id,
1026
file_group.append(trans_id)
1029
class Diff3Merger(Merge3Merger):
1030
"""Three-way merger using external diff3 for text merging"""
1032
def dump_file(self, temp_dir, name, tree, file_id):
1033
out_path = pathjoin(temp_dir, name)
1034
out_file = open(out_path, "wb")
1036
in_file = tree.get_file(file_id)
1037
for line in in_file:
1038
out_file.write(line)
1043
def text_merge(self, file_id, trans_id):
1044
"""Perform a diff3 merge using a specified file-id and trans-id.
1045
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1046
will be dumped, and a will be conflict noted.
1049
temp_dir = osutils.mkdtemp(prefix="bzr-")
1051
new_file = pathjoin(temp_dir, "new")
1052
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1053
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1054
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1055
status = bzrlib.patch.diff3(new_file, this, base, other)
1056
if status not in (0, 1):
1057
raise BzrError("Unhandled diff3 exit code")
1058
f = open(new_file, 'rb')
1060
self.tt.create_file(f, trans_id)
1064
name = self.tt.final_name(trans_id)
1065
parent_id = self.tt.final_parent(trans_id)
1066
self._dump_conflicts(name, parent_id, file_id)
1067
self._raw_conflicts.append(('text conflict', trans_id))
1069
osutils.rmtree(temp_dir)
1072
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1074
merge_type=Merge3Merger,
1075
interesting_ids=None,
1079
interesting_files=None,
1082
change_reporter=None):
1083
"""Primary interface for merging.
1085
typical use is probably
1086
'merge_inner(branch, branch.get_revision_tree(other_revision),
1087
branch.get_revision_tree(base_revision))'
1089
if this_tree is None:
1090
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1091
"parameter as of bzrlib version 0.8.")
1092
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1093
pb=pb, change_reporter=change_reporter)
1094
merger.backup_files = backup_files
1095
merger.merge_type = merge_type
1096
merger.interesting_ids = interesting_ids
1097
merger.ignore_zero = ignore_zero
1098
if interesting_files:
1099
assert not interesting_ids, ('Only supply interesting_ids'
1100
' or interesting_files')
1101
merger.interesting_files = interesting_files
1102
merger.show_base = show_base
1103
merger.reprocess = reprocess
1104
merger.other_rev_id = other_rev_id
1105
merger.other_basis = other_rev_id
1106
return merger.do_merge()
1108
def get_merge_type_registry():
1109
"""Merge type registry is in bzrlib.option to avoid circular imports.
1111
This method provides a sanctioned way to retrieve it.
1113
from bzrlib import option
1114
return option._merge_type_registry
1117
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1118
def status_a(revision, text):
1119
if revision in ancestors_b:
1120
return 'killed-b', text
1122
return 'new-a', text
1124
def status_b(revision, text):
1125
if revision in ancestors_a:
1126
return 'killed-a', text
1128
return 'new-b', text
1130
plain_a = [t for (a, t) in annotated_a]
1131
plain_b = [t for (a, t) in annotated_b]
1132
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1133
blocks = matcher.get_matching_blocks()
1136
for ai, bi, l in blocks:
1137
# process all mismatched sections
1138
# (last mismatched section is handled because blocks always
1139
# includes a 0-length last block)
1140
for revision, text in annotated_a[a_cur:ai]:
1141
yield status_a(revision, text)
1142
for revision, text in annotated_b[b_cur:bi]:
1143
yield status_b(revision, text)
1145
# and now the matched section
1148
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1149
assert text_a == text_b
1150
yield "unchanged", text_a
1153
class _PlanMerge(object):
1154
"""Plan an annotate merge using on-the-fly annotation"""
1156
def __init__(self, a_rev, b_rev, vf):
1159
:param a_rev: Revision-id of one revision to merge
1160
:param b_rev: Revision-id of the other revision to merge
1161
:param vf: A versionedfile containing both revisions
1165
self.lines_a = vf.get_lines(a_rev)
1166
self.lines_b = vf.get_lines(b_rev)
1168
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1169
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1170
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1171
self._last_lines = None
1172
self._last_lines_revision_id = None
1174
def plan_merge(self):
1175
"""Generate a 'plan' for merging the two revisions.
1177
This involves comparing their texts and determining the cause of
1178
differences. If text A has a line and text B does not, then either the
1179
line was added to text A, or it was deleted from B. Once the causes
1180
are combined, they are written out in the format described in
1181
VersionedFile.plan_merge
1183
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1184
new_a = self._find_new(self.a_rev)
1185
new_b = self._find_new(self.b_rev)
1188
a_lines = self.vf.get_lines(self.a_rev)
1189
b_lines = self.vf.get_lines(self.b_rev)
1190
for i, j, n in blocks:
1191
# determine why lines aren't common
1192
for a_index in range(last_i, i):
1193
if a_index in new_a:
1197
yield cause, a_lines[a_index]
1198
for b_index in range(last_j, j):
1199
if b_index in new_b:
1203
yield cause, b_lines[b_index]
1204
# handle common lines
1205
for a_index in range(i, i+n):
1206
yield 'unchanged', a_lines[a_index]
1210
def _get_matching_blocks(self, left_revision, right_revision):
1211
"""Return a description of which sections of two revisions match.
1213
See SequenceMatcher.get_matching_blocks
1215
if self._last_lines_revision_id == left_revision:
1216
left_lines = self._last_lines
1218
left_lines = self.vf.get_lines(left_revision)
1219
right_lines = self.vf.get_lines(right_revision)
1220
self._last_lines = right_lines
1221
self._last_lines_revision_id = right_revision
1222
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1224
return matcher.get_matching_blocks()
1226
def _unique_lines(self, matching_blocks):
1227
"""Analyse matching_blocks to determine which lines are unique
1229
:return: a tuple of (unique_left, unique_right), where the values are
1230
sets of line numbers of unique lines.
1236
for i, j, n in matching_blocks:
1237
unique_left.extend(range(last_i, i))
1238
unique_right.extend(range(last_j, j))
1241
return unique_left, unique_right
1243
def _find_new(self, version_id):
1244
"""Determine which lines are new in the ancestry of this version.
1246
If a lines is present in this version, and not present in any
1247
common ancestor, it is considered new.
1249
if version_id not in self.uncommon:
1251
parents = self.vf.get_parents(version_id)
1252
if len(parents) == 0:
1253
return set(range(len(self.vf.get_lines(version_id))))
1255
for parent in parents:
1256
blocks = self._get_matching_blocks(version_id, parent)
1257
result, unused = self._unique_lines(blocks)
1258
parent_new = self._find_new(parent)
1259
for i, j, n in blocks:
1260
for ii, jj in [(i+r, j+r) for r in range(n)]:
1261
if jj in parent_new:
1266
new.intersection_update(result)
1270
def _subtract_plans(old_plan, new_plan):
1271
# Can't use patience diff-- C version doesn't work with tuples
1272
matcher = difflib.SequenceMatcher(None, old_plan, new_plan)
1274
for i, j, n in matcher.get_matching_blocks():
1275
for jj in range(last_j, j):
1277
for jj in range(j, j+n):
1278
plan_line = new_plan[jj]
1279
if plan_line[0] == 'new-b':
1281
elif plan_line[0] == 'killed-b':
1282
yield 'unchanged', plan_line[1]