1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
revision as _mod_revision,
29
from bzrlib.branch import Branch
30
from bzrlib.conflicts import ConflictList, Conflict
31
from bzrlib.errors import (BzrCommandError,
41
WorkingTreeNotRevision,
44
from bzrlib.merge3 import Merge3
45
from bzrlib.osutils import rename, pathjoin
46
from progress import DummyProgress, ProgressPhase
47
from bzrlib.revision import (NULL_REVISION, ensure_null)
48
from bzrlib.textfile import check_text_lines
49
from bzrlib.trace import mutter, warning, note
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
51
conflict_pass, FinalPaths, create_by_entry,
52
unique_add, ROOT_PARENT)
53
from bzrlib.versionedfile import PlanWeaveMerge
56
# TODO: Report back as changes are merged in
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
60
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
61
interesting_ids=interesting_ids, this_tree=from_tree)
65
def __init__(self, this_branch, other_tree=None, base_tree=None,
66
this_tree=None, pb=DummyProgress(), change_reporter=None,
69
assert this_tree is not None, "this_tree is required"
70
self.this_branch = this_branch
71
self.this_basis = _mod_revision.ensure_null(
72
this_branch.last_revision())
73
self.this_rev_id = None
74
self.this_tree = this_tree
75
self.this_revision_tree = None
76
self.this_basis_tree = None
77
self.other_tree = other_tree
78
self.other_branch = None
79
self.base_tree = base_tree
80
self.ignore_zero = False
81
self.backup_files = False
82
self.interesting_ids = None
83
self.interesting_files = None
84
self.show_base = False
85
self.reprocess = False
88
self.recurse = recurse
89
self.change_reporter = change_reporter
90
self._cached_trees = {}
93
def from_uncommitted(tree, other_tree, pb):
94
"""Return a Merger for uncommitted changes in other_tree.
96
:param tree: The tree to merge into
97
:param other_tree: The tree to get uncommitted changes from
98
:param pb: A progress indicator
100
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
102
merger.base_rev_id = merger.base_tree.get_revision_id()
103
merger.other_rev_id = None
107
def from_mergeable(klass, tree, mergeable, pb):
108
"""Return a Merger for a bundle or merge directive.
110
:param tree: The tree to merge changes into
111
:param mergeable: A merge directive or bundle
112
:param pb: A progress indicator
114
mergeable.install_revisions(tree.branch.repository)
115
base_revision_id, other_revision_id, verified =\
116
mergeable.get_merge_request(tree.branch.repository)
117
if (base_revision_id != _mod_revision.NULL_REVISION and
118
tree.branch.repository.get_graph().is_ancestor(
119
base_revision_id, tree.branch.last_revision())):
120
base_revision_id = None
121
merger = klass.from_revision_ids(pb, tree, other_revision_id,
123
return merger, verified
126
def from_revision_ids(pb, this, other, base=None, other_branch=None,
128
"""Return a Merger for revision-ids.
130
:param tree: The tree to merge changes into
131
:param other: The revision-id to use as OTHER
132
:param base: The revision-id to use as BASE. If not specified, will
134
:param other_branch: A branch containing the other revision-id. If
135
not supplied, this.branch is used.
136
:param base_branch: A branch containing the base revision-id. If
137
not supplied, other_branch or this.branch will be used.
138
:param pb: A progress indicator
140
merger = Merger(this.branch, this_tree=this, pb=pb)
141
if other_branch is None:
142
other_branch = this.branch
143
merger.set_other_revision(other, other_branch)
147
if base_branch is None:
148
base_branch = other_branch
149
merger.set_base_revision(base, base_branch)
152
def revision_tree(self, revision_id, branch=None):
153
if revision_id not in self._cached_trees:
155
branch = self.this_branch
157
tree = self.this_tree.revision_tree(revision_id)
158
except errors.NoSuchRevisionInTree:
159
tree = branch.repository.revision_tree(revision_id)
160
self._cached_trees[revision_id] = tree
161
return self._cached_trees[revision_id]
163
def _get_tree(self, treespec, possible_transports=None):
164
from bzrlib import workingtree
165
location, revno = treespec
167
tree = workingtree.WorkingTree.open_containing(location)[0]
168
return tree.branch, tree
169
branch = Branch.open_containing(location, possible_transports)[0]
171
revision_id = branch.last_revision()
173
revision_id = branch.get_rev_id(revno)
174
revision_id = ensure_null(revision_id)
175
return branch, self.revision_tree(revision_id, branch)
177
def ensure_revision_trees(self):
178
if self.this_revision_tree is None:
179
self.this_basis_tree = self.revision_tree(self.this_basis)
180
if self.this_basis == self.this_rev_id:
181
self.this_revision_tree = self.this_basis_tree
183
if self.other_rev_id is None:
184
other_basis_tree = self.revision_tree(self.other_basis)
185
changes = other_basis_tree.changes_from(self.other_tree)
186
if changes.has_changed():
187
raise WorkingTreeNotRevision(self.this_tree)
188
other_rev_id = self.other_basis
189
self.other_tree = other_basis_tree
191
def file_revisions(self, file_id):
192
self.ensure_revision_trees()
193
def get_id(tree, file_id):
194
revision_id = tree.inventory[file_id].revision
195
assert revision_id is not None
197
if self.this_rev_id is None:
198
if self.this_basis_tree.get_file_sha1(file_id) != \
199
self.this_tree.get_file_sha1(file_id):
200
raise WorkingTreeNotRevision(self.this_tree)
202
trees = (self.this_basis_tree, self.other_tree)
203
return [get_id(tree, file_id) for tree in trees]
205
def check_basis(self, check_clean, require_commits=True):
206
if self.this_basis is None and require_commits is True:
207
raise BzrCommandError("This branch has no commits."
208
" (perhaps you would prefer 'bzr pull')")
211
if self.this_basis != self.this_rev_id:
212
raise errors.UncommittedChanges(self.this_tree)
214
def compare_basis(self):
216
basis_tree = self.revision_tree(self.this_tree.last_revision())
217
except errors.RevisionNotPresent:
218
basis_tree = self.this_tree.basis_tree()
219
changes = self.this_tree.changes_from(basis_tree)
220
if not changes.has_changed():
221
self.this_rev_id = self.this_basis
223
def set_interesting_files(self, file_list):
224
self.interesting_files = file_list
226
def set_pending(self):
227
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
231
def _add_parent(self):
232
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
233
new_parent_trees = []
234
for revision_id in new_parents:
236
tree = self.revision_tree(revision_id)
237
except errors.RevisionNotPresent:
241
new_parent_trees.append((revision_id, tree))
243
self.this_tree.set_parent_trees(new_parent_trees,
244
allow_leftmost_as_ghost=True)
246
for _revision_id, tree in new_parent_trees:
250
def set_other(self, other_revision, possible_transports=None):
251
"""Set the revision and tree to merge from.
253
This sets the other_tree, other_rev_id, other_basis attributes.
255
:param other_revision: The [path, revision] list to merge from.
257
self.other_branch, self.other_tree = self._get_tree(other_revision,
259
if other_revision[1] == -1:
260
self.other_rev_id = _mod_revision.ensure_null(
261
self.other_branch.last_revision())
262
if _mod_revision.is_null(self.other_rev_id):
263
raise NoCommits(self.other_branch)
264
self.other_basis = self.other_rev_id
265
elif other_revision[1] is not None:
266
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
267
self.other_basis = self.other_rev_id
269
self.other_rev_id = None
270
self.other_basis = self.other_branch.last_revision()
271
if self.other_basis is None:
272
raise NoCommits(self.other_branch)
273
if self.other_rev_id is not None:
274
self._cached_trees[self.other_rev_id] = self.other_tree
275
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
277
def set_other_revision(self, revision_id, other_branch):
278
"""Set 'other' based on a branch and revision id
280
:param revision_id: The revision to use for a tree
281
:param other_branch: The branch containing this tree
283
self.other_rev_id = revision_id
284
self.other_branch = other_branch
285
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
286
self.other_tree = self.revision_tree(revision_id)
287
self.other_basis = revision_id
289
def set_base_revision(self, revision_id, branch):
290
"""Set 'base' based on a branch and revision id
292
:param revision_id: The revision to use for a tree
293
:param branch: The branch containing this tree
295
self.base_rev_id = revision_id
296
self.base_branch = branch
297
self._maybe_fetch(branch, self.this_branch, revision_id)
298
self.base_tree = self.revision_tree(revision_id)
299
graph = self.this_branch.repository.get_graph()
300
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
302
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
305
def _maybe_fetch(self, source, target, revision_id):
306
if not source.repository.has_same_location(target.repository):
307
target.fetch(source, revision_id)
310
this_repo = self.this_branch.repository
311
graph = this_repo.get_graph()
312
revisions = [ensure_null(self.this_basis),
313
ensure_null(self.other_basis)]
314
if NULL_REVISION in revisions:
315
self.base_rev_id = NULL_REVISION
317
self.base_rev_id, steps = graph.find_unique_lca(revisions[0],
318
revisions[1], count_steps=True)
319
if self.base_rev_id == NULL_REVISION:
320
raise UnrelatedBranches()
322
warning('Warning: criss-cross merge encountered. See bzr'
323
' help criss-cross.')
324
self.base_tree = self.revision_tree(self.base_rev_id)
325
self.base_is_ancestor = True
326
self.base_is_other_ancestor = True
328
def set_base(self, base_revision):
329
"""Set the base revision to use for the merge.
331
:param base_revision: A 2-list containing a path and revision number.
333
mutter("doing merge() with no base_revision specified")
334
if base_revision == [None, None]:
337
base_branch, self.base_tree = self._get_tree(base_revision)
338
if base_revision[1] == -1:
339
self.base_rev_id = base_branch.last_revision()
340
elif base_revision[1] is None:
341
self.base_rev_id = _mod_revision.NULL_REVISION
343
self.base_rev_id = _mod_revision.ensure_null(
344
base_branch.get_rev_id(base_revision[1]))
345
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
346
graph = self.this_branch.repository.get_graph()
347
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
349
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
353
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
354
'other_tree': self.other_tree,
355
'interesting_ids': self.interesting_ids,
356
'interesting_files': self.interesting_files,
358
if self.merge_type.requires_base:
359
kwargs['base_tree'] = self.base_tree
360
if self.merge_type.supports_reprocess:
361
kwargs['reprocess'] = self.reprocess
363
raise BzrError("Conflict reduction is not supported for merge"
364
" type %s." % self.merge_type)
365
if self.merge_type.supports_show_base:
366
kwargs['show_base'] = self.show_base
368
raise BzrError("Showing base is not supported for this"
369
" merge type. %s" % self.merge_type)
370
self.this_tree.lock_tree_write()
371
if self.base_tree is not None:
372
self.base_tree.lock_read()
373
if self.other_tree is not None:
374
self.other_tree.lock_read()
376
merge = self.merge_type(pb=self._pb,
377
change_reporter=self.change_reporter,
379
if self.recurse == 'down':
380
for path, file_id in self.this_tree.iter_references():
381
sub_tree = self.this_tree.get_nested_tree(file_id, path)
382
other_revision = self.other_tree.get_reference_revision(
384
if other_revision == sub_tree.last_revision():
386
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
387
sub_merge.merge_type = self.merge_type
388
relpath = self.this_tree.relpath(path)
389
other_branch = self.other_branch.reference_parent(file_id, relpath)
390
sub_merge.set_other_revision(other_revision, other_branch)
391
base_revision = self.base_tree.get_reference_revision(file_id)
392
sub_merge.base_tree = \
393
sub_tree.branch.repository.revision_tree(base_revision)
397
if self.other_tree is not None:
398
self.other_tree.unlock()
399
if self.base_tree is not None:
400
self.base_tree.unlock()
401
self.this_tree.unlock()
402
if len(merge.cooked_conflicts) == 0:
403
if not self.ignore_zero:
404
note("All changes applied successfully.")
406
note("%d conflicts encountered." % len(merge.cooked_conflicts))
408
return len(merge.cooked_conflicts)
411
class Merge3Merger(object):
412
"""Three-way merger that uses the merge3 text merger"""
414
supports_reprocess = True
415
supports_show_base = True
416
history_based = False
417
winner_idx = {"this": 2, "other": 1, "conflict": 1}
419
def __init__(self, working_tree, this_tree, base_tree, other_tree,
420
interesting_ids=None, reprocess=False, show_base=False,
421
pb=DummyProgress(), pp=None, change_reporter=None,
422
interesting_files=None):
423
"""Initialize the merger object and perform the merge.
425
:param working_tree: The working tree to apply the merge to
426
:param this_tree: The local tree in the merge operation
427
:param base_tree: The common tree in the merge operation
428
:param other_tree: The other other tree to merge changes from
429
:param interesting_ids: The file_ids of files that should be
430
participate in the merge. May not be combined with
432
:param: reprocess If True, perform conflict-reduction processing.
433
:param show_base: If True, show the base revision in text conflicts.
434
(incompatible with reprocess)
435
:param pb: A Progress bar
436
:param pp: A ProgressPhase object
437
:param change_reporter: An object that should report changes made
438
:param interesting_files: The tree-relative paths of files that should
439
participate in the merge. If these paths refer to directories,
440
the contents of those directories will also be included. May not
441
be combined with interesting_ids. If neither interesting_files nor
442
interesting_ids is specified, all files may participate in the
445
object.__init__(self)
446
if interesting_files is not None:
447
assert interesting_ids is None
448
self.interesting_ids = interesting_ids
449
self.interesting_files = interesting_files
450
self.this_tree = working_tree
451
self.this_tree.lock_tree_write()
452
self.base_tree = base_tree
453
self.base_tree.lock_read()
454
self.other_tree = other_tree
455
self.other_tree.lock_read()
456
self._raw_conflicts = []
457
self.cooked_conflicts = []
458
self.reprocess = reprocess
459
self.show_base = show_base
462
self.change_reporter = change_reporter
464
self.pp = ProgressPhase("Merge phase", 3, self.pb)
466
self.tt = TreeTransform(working_tree, self.pb)
469
entries = self._entries3()
470
child_pb = ui.ui_factory.nested_progress_bar()
472
for num, (file_id, changed, parents3, names3,
473
executable3) in enumerate(entries):
474
child_pb.update('Preparing file merge', num, len(entries))
475
self._merge_names(file_id, parents3, names3)
477
file_status = self.merge_contents(file_id)
479
file_status = 'unmodified'
480
self._merge_executable(file_id,
481
executable3, file_status)
486
child_pb = ui.ui_factory.nested_progress_bar()
488
fs_conflicts = resolve_conflicts(self.tt, child_pb,
489
lambda t, c: conflict_pass(t, c, self.other_tree))
492
if change_reporter is not None:
493
from bzrlib import delta
494
delta.report_changes(self.tt._iter_changes(), change_reporter)
495
self.cook_conflicts(fs_conflicts)
496
for conflict in self.cooked_conflicts:
499
results = self.tt.apply(no_conflicts=True)
500
self.write_modified(results)
502
working_tree.add_conflicts(self.cooked_conflicts)
503
except UnsupportedOperation:
507
self.other_tree.unlock()
508
self.base_tree.unlock()
509
self.this_tree.unlock()
513
"""Gather data about files modified between three trees.
515
Return a list of tuples of file_id, changed, parents3, names3,
516
executable3. changed is a boolean indicating whether the file contents
517
or kind were changed. parents3 is a tuple of parent ids for base,
518
other and this. names3 is a tuple of names for base, other and this.
519
executable3 is a tuple of execute-bit values for base, other and this.
522
iterator = self.other_tree._iter_changes(self.base_tree,
523
include_unchanged=True, specific_files=self.interesting_files,
524
extra_trees=[self.this_tree])
525
for (file_id, paths, changed, versioned, parents, names, kind,
526
executable) in iterator:
527
if (self.interesting_ids is not None and
528
file_id not in self.interesting_ids):
530
if file_id in self.this_tree.inventory:
531
entry = self.this_tree.inventory[file_id]
532
this_name = entry.name
533
this_parent = entry.parent_id
534
this_executable = entry.executable
538
this_executable = None
539
parents3 = parents + (this_parent,)
540
names3 = names + (this_name,)
541
executable3 = executable + (this_executable,)
542
result.append((file_id, changed, parents3, names3, executable3))
547
self.tt.final_kind(self.tt.root)
549
self.tt.cancel_deletion(self.tt.root)
550
if self.tt.final_file_id(self.tt.root) is None:
551
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
553
if self.other_tree.inventory.root is None:
555
other_root_file_id = self.other_tree.get_root_id()
556
other_root = self.tt.trans_id_file_id(other_root_file_id)
557
if other_root == self.tt.root:
560
self.tt.final_kind(other_root)
563
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
564
self.tt.cancel_creation(other_root)
565
self.tt.cancel_versioning(other_root)
567
def reparent_children(self, ie, target):
568
for thing, child in ie.children.iteritems():
569
trans_id = self.tt.trans_id_file_id(child.file_id)
570
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
572
def write_modified(self, results):
574
for path in results.modified_paths:
575
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
578
hash = self.this_tree.get_file_sha1(file_id)
581
modified_hashes[file_id] = hash
582
self.this_tree.set_merge_modified(modified_hashes)
585
def parent(entry, file_id):
586
"""Determine the parent for a file_id (used as a key method)"""
589
return entry.parent_id
592
def name(entry, file_id):
593
"""Determine the name for a file_id (used as a key method)"""
599
def contents_sha1(tree, file_id):
600
"""Determine the sha1 of the file contents (used as a key method)."""
601
if file_id not in tree:
603
return tree.get_file_sha1(file_id)
606
def executable(tree, file_id):
607
"""Determine the executability of a file-id (used as a key method)."""
608
if file_id not in tree:
610
if tree.kind(file_id) != "file":
612
return tree.is_executable(file_id)
615
def kind(tree, file_id):
616
"""Determine the kind of a file-id (used as a key method)."""
617
if file_id not in tree:
619
return tree.kind(file_id)
622
def _three_way(base, other, this):
623
#if base == other, either they all agree, or only THIS has changed.
626
elif this not in (base, other):
628
# "Ambiguous clean merge" -- both sides have made the same change.
631
# this == base: only other has changed.
636
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
637
"""Do a three-way test on a scalar.
638
Return "this", "other" or "conflict", depending whether a value wins.
640
key_base = key(base_tree, file_id)
641
key_other = key(other_tree, file_id)
642
#if base == other, either they all agree, or only THIS has changed.
643
if key_base == key_other:
645
key_this = key(this_tree, file_id)
646
if key_this not in (key_base, key_other):
648
# "Ambiguous clean merge"
649
elif key_this == key_other:
652
assert key_this == key_base
655
def merge_names(self, file_id):
657
if file_id in tree.inventory:
658
return tree.inventory[file_id]
661
this_entry = get_entry(self.this_tree)
662
other_entry = get_entry(self.other_tree)
663
base_entry = get_entry(self.base_tree)
664
entries = (base_entry, other_entry, this_entry)
667
for entry in entries:
672
names.append(entry.name)
673
parents.append(entry.parent_id)
674
return self._merge_names(file_id, parents, names)
676
def _merge_names(self, file_id, parents, names):
677
"""Perform a merge on file_id names and parents"""
678
base_name, other_name, this_name = names
679
base_parent, other_parent, this_parent = parents
681
name_winner = self._three_way(*names)
683
parent_id_winner = self._three_way(*parents)
684
if this_name is None:
685
if name_winner == "this":
686
name_winner = "other"
687
if parent_id_winner == "this":
688
parent_id_winner = "other"
689
if name_winner == "this" and parent_id_winner == "this":
691
if name_winner == "conflict":
692
trans_id = self.tt.trans_id_file_id(file_id)
693
self._raw_conflicts.append(('name conflict', trans_id,
694
this_name, other_name))
695
if parent_id_winner == "conflict":
696
trans_id = self.tt.trans_id_file_id(file_id)
697
self._raw_conflicts.append(('parent conflict', trans_id,
698
this_parent, other_parent))
699
if other_name is None:
700
# it doesn't matter whether the result was 'other' or
701
# 'conflict'-- if there's no 'other', we leave it alone.
703
# if we get here, name_winner and parent_winner are set to safe values.
704
trans_id = self.tt.trans_id_file_id(file_id)
705
parent_id = parents[self.winner_idx[parent_id_winner]]
706
if parent_id is not None:
707
parent_trans_id = self.tt.trans_id_file_id(parent_id)
708
self.tt.adjust_path(names[self.winner_idx[name_winner]],
709
parent_trans_id, trans_id)
711
def merge_contents(self, file_id):
712
"""Performa a merge on file_id contents."""
713
def contents_pair(tree):
714
if file_id not in tree:
716
kind = tree.kind(file_id)
718
contents = tree.get_file_sha1(file_id)
719
elif kind == "symlink":
720
contents = tree.get_symlink_target(file_id)
723
return kind, contents
725
def contents_conflict():
726
trans_id = self.tt.trans_id_file_id(file_id)
727
name = self.tt.final_name(trans_id)
728
parent_id = self.tt.final_parent(trans_id)
729
if file_id in self.this_tree.inventory:
730
self.tt.unversion_file(trans_id)
731
if file_id in self.this_tree:
732
self.tt.delete_contents(trans_id)
733
file_group = self._dump_conflicts(name, parent_id, file_id,
735
self._raw_conflicts.append(('contents conflict', file_group))
737
# See SPOT run. run, SPOT, run.
738
# So we're not QUITE repeating ourselves; we do tricky things with
740
base_pair = contents_pair(self.base_tree)
741
other_pair = contents_pair(self.other_tree)
742
if base_pair == other_pair:
743
# OTHER introduced no changes
745
this_pair = contents_pair(self.this_tree)
746
if this_pair == other_pair:
747
# THIS and OTHER introduced the same changes
750
trans_id = self.tt.trans_id_file_id(file_id)
751
if this_pair == base_pair:
752
# only OTHER introduced changes
753
if file_id in self.this_tree:
754
# Remove any existing contents
755
self.tt.delete_contents(trans_id)
756
if file_id in self.other_tree:
757
# OTHER changed the file
758
create_by_entry(self.tt,
759
self.other_tree.inventory[file_id],
760
self.other_tree, trans_id)
761
if file_id not in self.this_tree.inventory:
762
self.tt.version_file(file_id, trans_id)
764
elif file_id in self.this_tree.inventory:
765
# OTHER deleted the file
766
self.tt.unversion_file(trans_id)
768
#BOTH THIS and OTHER introduced changes; scalar conflict
769
elif this_pair[0] == "file" and other_pair[0] == "file":
770
# THIS and OTHER are both files, so text merge. Either
771
# BASE is a file, or both converted to files, so at least we
772
# have agreement that output should be a file.
774
self.text_merge(file_id, trans_id)
776
return contents_conflict()
777
if file_id not in self.this_tree.inventory:
778
self.tt.version_file(file_id, trans_id)
780
self.tt.tree_kind(trans_id)
781
self.tt.delete_contents(trans_id)
786
# Scalar conflict, can't text merge. Dump conflicts
787
return contents_conflict()
789
def get_lines(self, tree, file_id):
790
"""Return the lines in a file, or an empty list."""
792
return tree.get_file(file_id).readlines()
796
def text_merge(self, file_id, trans_id):
797
"""Perform a three-way text merge on a file_id"""
798
# it's possible that we got here with base as a different type.
799
# if so, we just want two-way text conflicts.
800
if file_id in self.base_tree and \
801
self.base_tree.kind(file_id) == "file":
802
base_lines = self.get_lines(self.base_tree, file_id)
805
other_lines = self.get_lines(self.other_tree, file_id)
806
this_lines = self.get_lines(self.this_tree, file_id)
807
m3 = Merge3(base_lines, this_lines, other_lines)
808
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
809
if self.show_base is True:
810
base_marker = '|' * 7
814
def iter_merge3(retval):
815
retval["text_conflicts"] = False
816
for line in m3.merge_lines(name_a = "TREE",
817
name_b = "MERGE-SOURCE",
818
name_base = "BASE-REVISION",
819
start_marker=start_marker,
820
base_marker=base_marker,
821
reprocess=self.reprocess):
822
if line.startswith(start_marker):
823
retval["text_conflicts"] = True
824
yield line.replace(start_marker, '<' * 7)
828
merge3_iterator = iter_merge3(retval)
829
self.tt.create_file(merge3_iterator, trans_id)
830
if retval["text_conflicts"] is True:
831
self._raw_conflicts.append(('text conflict', trans_id))
832
name = self.tt.final_name(trans_id)
833
parent_id = self.tt.final_parent(trans_id)
834
file_group = self._dump_conflicts(name, parent_id, file_id,
835
this_lines, base_lines,
837
file_group.append(trans_id)
839
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
840
base_lines=None, other_lines=None, set_version=False,
842
"""Emit conflict files.
843
If this_lines, base_lines, or other_lines are omitted, they will be
844
determined automatically. If set_version is true, the .OTHER, .THIS
845
or .BASE (in that order) will be created as versioned files.
847
data = [('OTHER', self.other_tree, other_lines),
848
('THIS', self.this_tree, this_lines)]
850
data.append(('BASE', self.base_tree, base_lines))
853
for suffix, tree, lines in data:
855
trans_id = self._conflict_file(name, parent_id, tree, file_id,
857
file_group.append(trans_id)
858
if set_version and not versioned:
859
self.tt.version_file(file_id, trans_id)
863
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
865
"""Emit a single conflict file."""
866
name = name + '.' + suffix
867
trans_id = self.tt.create_path(name, parent_id)
868
entry = tree.inventory[file_id]
869
create_by_entry(self.tt, entry, tree, trans_id, lines)
872
def merge_executable(self, file_id, file_status):
873
"""Perform a merge on the execute bit."""
874
executable = [self.executable(t, file_id) for t in (self.base_tree,
875
self.other_tree, self.this_tree)]
876
self._merge_executable(file_id, executable, file_status)
878
def _merge_executable(self, file_id, executable, file_status):
879
"""Perform a merge on the execute bit."""
880
base_executable, other_executable, this_executable = executable
881
if file_status == "deleted":
883
trans_id = self.tt.trans_id_file_id(file_id)
885
if self.tt.final_kind(trans_id) != "file":
889
winner = self._three_way(*executable)
890
if winner == "conflict":
891
# There must be a None in here, if we have a conflict, but we
892
# need executability since file status was not deleted.
893
if self.executable(self.other_tree, file_id) is None:
898
if file_status == "modified":
899
executability = this_executable
900
if executability is not None:
901
trans_id = self.tt.trans_id_file_id(file_id)
902
self.tt.set_executability(executability, trans_id)
904
assert winner == "other"
905
if file_id in self.other_tree:
906
executability = other_executable
907
elif file_id in self.this_tree:
908
executability = this_executable
909
elif file_id in self.base_tree:
910
executability = base_executable
911
if executability is not None:
912
trans_id = self.tt.trans_id_file_id(file_id)
913
self.tt.set_executability(executability, trans_id)
915
def cook_conflicts(self, fs_conflicts):
916
"""Convert all conflicts into a form that doesn't depend on trans_id"""
917
from conflicts import Conflict
919
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
920
fp = FinalPaths(self.tt)
921
for conflict in self._raw_conflicts:
922
conflict_type = conflict[0]
923
if conflict_type in ('name conflict', 'parent conflict'):
924
trans_id = conflict[1]
925
conflict_args = conflict[2:]
926
if trans_id not in name_conflicts:
927
name_conflicts[trans_id] = {}
928
unique_add(name_conflicts[trans_id], conflict_type,
930
if conflict_type == 'contents conflict':
931
for trans_id in conflict[1]:
932
file_id = self.tt.final_file_id(trans_id)
933
if file_id is not None:
935
path = fp.get_path(trans_id)
936
for suffix in ('.BASE', '.THIS', '.OTHER'):
937
if path.endswith(suffix):
938
path = path[:-len(suffix)]
940
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
941
self.cooked_conflicts.append(c)
942
if conflict_type == 'text conflict':
943
trans_id = conflict[1]
944
path = fp.get_path(trans_id)
945
file_id = self.tt.final_file_id(trans_id)
946
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
947
self.cooked_conflicts.append(c)
949
for trans_id, conflicts in name_conflicts.iteritems():
951
this_parent, other_parent = conflicts['parent conflict']
952
assert this_parent != other_parent
954
this_parent = other_parent = \
955
self.tt.final_file_id(self.tt.final_parent(trans_id))
957
this_name, other_name = conflicts['name conflict']
958
assert this_name != other_name
960
this_name = other_name = self.tt.final_name(trans_id)
961
other_path = fp.get_path(trans_id)
962
if this_parent is not None and this_name is not None:
964
fp.get_path(self.tt.trans_id_file_id(this_parent))
965
this_path = pathjoin(this_parent_path, this_name)
967
this_path = "<deleted>"
968
file_id = self.tt.final_file_id(trans_id)
969
c = Conflict.factory('path conflict', path=this_path,
970
conflict_path=other_path, file_id=file_id)
971
self.cooked_conflicts.append(c)
972
self.cooked_conflicts.sort(key=Conflict.sort_key)
975
class WeaveMerger(Merge3Merger):
976
"""Three-way tree merger, text weave merger."""
977
supports_reprocess = True
978
supports_show_base = False
980
def __init__(self, working_tree, this_tree, base_tree, other_tree,
981
interesting_ids=None, pb=DummyProgress(), pp=None,
982
reprocess=False, change_reporter=None,
983
interesting_files=None):
984
super(WeaveMerger, self).__init__(working_tree, this_tree,
985
base_tree, other_tree,
986
interesting_ids=interesting_ids,
987
pb=pb, pp=pp, reprocess=reprocess,
988
change_reporter=change_reporter)
990
def _merged_lines(self, file_id):
991
"""Generate the merged lines.
992
There is no distinction between lines that are meant to contain <<<<<<<
995
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
996
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
997
'>>>>>>> MERGE-SOURCE\n')
998
return textmerge.merge_lines(self.reprocess)
1000
def text_merge(self, file_id, trans_id):
1001
"""Perform a (weave) text merge for a given file and file-id.
1002
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1003
and a conflict will be noted.
1005
lines, conflicts = self._merged_lines(file_id)
1007
# Note we're checking whether the OUTPUT is binary in this case,
1008
# because we don't want to get into weave merge guts.
1009
check_text_lines(lines)
1010
self.tt.create_file(lines, trans_id)
1012
self._raw_conflicts.append(('text conflict', trans_id))
1013
name = self.tt.final_name(trans_id)
1014
parent_id = self.tt.final_parent(trans_id)
1015
file_group = self._dump_conflicts(name, parent_id, file_id,
1017
file_group.append(trans_id)
1020
class Diff3Merger(Merge3Merger):
1021
"""Three-way merger using external diff3 for text merging"""
1023
def dump_file(self, temp_dir, name, tree, file_id):
1024
out_path = pathjoin(temp_dir, name)
1025
out_file = open(out_path, "wb")
1027
in_file = tree.get_file(file_id)
1028
for line in in_file:
1029
out_file.write(line)
1034
def text_merge(self, file_id, trans_id):
1035
"""Perform a diff3 merge using a specified file-id and trans-id.
1036
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1037
will be dumped, and a will be conflict noted.
1040
temp_dir = osutils.mkdtemp(prefix="bzr-")
1042
new_file = pathjoin(temp_dir, "new")
1043
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1044
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1045
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1046
status = bzrlib.patch.diff3(new_file, this, base, other)
1047
if status not in (0, 1):
1048
raise BzrError("Unhandled diff3 exit code")
1049
f = open(new_file, 'rb')
1051
self.tt.create_file(f, trans_id)
1055
name = self.tt.final_name(trans_id)
1056
parent_id = self.tt.final_parent(trans_id)
1057
self._dump_conflicts(name, parent_id, file_id)
1058
self._raw_conflicts.append(('text conflict', trans_id))
1060
osutils.rmtree(temp_dir)
1063
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1065
merge_type=Merge3Merger,
1066
interesting_ids=None,
1070
interesting_files=None,
1073
change_reporter=None):
1074
"""Primary interface for merging.
1076
typical use is probably
1077
'merge_inner(branch, branch.get_revision_tree(other_revision),
1078
branch.get_revision_tree(base_revision))'
1080
if this_tree is None:
1081
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1082
"parameter as of bzrlib version 0.8.")
1083
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1084
pb=pb, change_reporter=change_reporter)
1085
merger.backup_files = backup_files
1086
merger.merge_type = merge_type
1087
merger.interesting_ids = interesting_ids
1088
merger.ignore_zero = ignore_zero
1089
if interesting_files:
1090
assert not interesting_ids, ('Only supply interesting_ids'
1091
' or interesting_files')
1092
merger.interesting_files = interesting_files
1093
merger.show_base = show_base
1094
merger.reprocess = reprocess
1095
merger.other_rev_id = other_rev_id
1096
merger.other_basis = other_rev_id
1097
return merger.do_merge()
1099
def get_merge_type_registry():
1100
"""Merge type registry is in bzrlib.option to avoid circular imports.
1102
This method provides a sanctioned way to retrieve it.
1104
from bzrlib import option
1105
return option._merge_type_registry
1108
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1109
def status_a(revision, text):
1110
if revision in ancestors_b:
1111
return 'killed-b', text
1113
return 'new-a', text
1115
def status_b(revision, text):
1116
if revision in ancestors_a:
1117
return 'killed-a', text
1119
return 'new-b', text
1121
plain_a = [t for (a, t) in annotated_a]
1122
plain_b = [t for (a, t) in annotated_b]
1123
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1124
blocks = matcher.get_matching_blocks()
1127
for ai, bi, l in blocks:
1128
# process all mismatched sections
1129
# (last mismatched section is handled because blocks always
1130
# includes a 0-length last block)
1131
for revision, text in annotated_a[a_cur:ai]:
1132
yield status_a(revision, text)
1133
for revision, text in annotated_b[b_cur:bi]:
1134
yield status_b(revision, text)
1136
# and now the matched section
1139
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1140
assert text_a == text_b
1141
yield "unchanged", text_a
1144
class _PlanMerge(object):
1145
"""Plan an annotate merge using on-the-fly annotation"""
1147
def __init__(self, a_rev, b_rev, vf):
1150
:param a_rev: Revision-id of one revision to merge
1151
:param b_rev: Revision-id of the other revision to merge
1152
:param vf: A versionedfile containing both revisions
1156
self.lines_a = vf.get_lines(a_rev)
1157
self.lines_b = vf.get_lines(b_rev)
1159
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1160
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1161
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1162
self._last_lines = None
1163
self._last_lines_revision_id = None
1165
def plan_merge(self):
1166
"""Generate a 'plan' for merging the two revisions.
1168
This involves comparing their texts and determining the cause of
1169
differences. If text A has a line and text B does not, then either the
1170
line was added to text A, or it was deleted from B. Once the causes
1171
are combined, they are written out in the format described in
1172
VersionedFile.plan_merge
1174
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1175
new_a = self._find_new(self.a_rev)
1176
new_b = self._find_new(self.b_rev)
1179
a_lines = self.vf.get_lines(self.a_rev)
1180
b_lines = self.vf.get_lines(self.b_rev)
1181
for i, j, n in blocks:
1182
# determine why lines aren't common
1183
for a_index in range(last_i, i):
1184
if a_index in new_a:
1188
yield cause, a_lines[a_index]
1189
for b_index in range(last_j, j):
1190
if b_index in new_b:
1194
yield cause, b_lines[b_index]
1195
# handle common lines
1196
for a_index in range(i, i+n):
1197
yield 'unchanged', a_lines[a_index]
1201
def _get_matching_blocks(self, left_revision, right_revision):
1202
"""Return a description of which sections of two revisions match.
1204
See SequenceMatcher.get_matching_blocks
1206
if self._last_lines_revision_id == left_revision:
1207
left_lines = self._last_lines
1209
left_lines = self.vf.get_lines(left_revision)
1210
right_lines = self.vf.get_lines(right_revision)
1211
self._last_lines = right_lines
1212
self._last_lines_revision_id = right_revision
1213
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1215
return matcher.get_matching_blocks()
1217
def _unique_lines(self, matching_blocks):
1218
"""Analyse matching_blocks to determine which lines are unique
1220
:return: a tuple of (unique_left, unique_right), where the values are
1221
sets of line numbers of unique lines.
1227
for i, j, n in matching_blocks:
1228
unique_left.extend(range(last_i, i))
1229
unique_right.extend(range(last_j, j))
1232
return unique_left, unique_right
1234
def _find_new(self, version_id):
1235
"""Determine which lines are new in the ancestry of this version.
1237
If a lines is present in this version, and not present in any
1238
common ancestor, it is considered new.
1240
if version_id not in self.uncommon:
1242
parents = self.vf.get_parents(version_id)
1243
if len(parents) == 0:
1244
return set(range(len(self.vf.get_lines(version_id))))
1246
for parent in parents:
1247
blocks = self._get_matching_blocks(version_id, parent)
1248
result, unused = self._unique_lines(blocks)
1249
parent_new = self._find_new(parent)
1250
for i, j, n in blocks:
1251
for ii, jj in [(i+r, j+r) for r in range(n)]:
1252
if jj in parent_new:
1257
new.intersection_update(result)