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
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
373
and not self.base_is_other_ancestor):
374
raise errors.CannotReverseCherrypick()
375
if self.merge_type.history_based:
376
kwargs['cherrypick'] = (not self.base_is_ancestor or
377
not self.base_is_other_ancestor)
378
self.this_tree.lock_tree_write()
379
if self.base_tree is not None:
380
self.base_tree.lock_read()
381
if self.other_tree is not None:
382
self.other_tree.lock_read()
384
merge = self.merge_type(pb=self._pb,
385
change_reporter=self.change_reporter,
387
if self.recurse == 'down':
388
for path, file_id in self.this_tree.iter_references():
389
sub_tree = self.this_tree.get_nested_tree(file_id, path)
390
other_revision = self.other_tree.get_reference_revision(
392
if other_revision == sub_tree.last_revision():
394
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
395
sub_merge.merge_type = self.merge_type
396
relpath = self.this_tree.relpath(path)
397
other_branch = self.other_branch.reference_parent(file_id, relpath)
398
sub_merge.set_other_revision(other_revision, other_branch)
399
base_revision = self.base_tree.get_reference_revision(file_id)
400
sub_merge.base_tree = \
401
sub_tree.branch.repository.revision_tree(base_revision)
405
if self.other_tree is not None:
406
self.other_tree.unlock()
407
if self.base_tree is not None:
408
self.base_tree.unlock()
409
self.this_tree.unlock()
410
if len(merge.cooked_conflicts) == 0:
411
if not self.ignore_zero:
412
note("All changes applied successfully.")
414
note("%d conflicts encountered." % len(merge.cooked_conflicts))
416
return len(merge.cooked_conflicts)
419
class Merge3Merger(object):
420
"""Three-way merger that uses the merge3 text merger"""
422
supports_reprocess = True
423
supports_show_base = True
424
history_based = False
425
supports_reverse_cherrypick = True
426
winner_idx = {"this": 2, "other": 1, "conflict": 1}
428
def __init__(self, working_tree, this_tree, base_tree, other_tree,
429
interesting_ids=None, reprocess=False, show_base=False,
430
pb=DummyProgress(), pp=None, change_reporter=None,
431
interesting_files=None):
432
"""Initialize the merger object and perform the merge.
434
:param working_tree: The working tree to apply the merge to
435
:param this_tree: The local tree in the merge operation
436
:param base_tree: The common tree in the merge operation
437
:param other_tree: The other other tree to merge changes from
438
:param interesting_ids: The file_ids of files that should be
439
participate in the merge. May not be combined with
441
:param: reprocess If True, perform conflict-reduction processing.
442
:param show_base: If True, show the base revision in text conflicts.
443
(incompatible with reprocess)
444
:param pb: A Progress bar
445
:param pp: A ProgressPhase object
446
:param change_reporter: An object that should report changes made
447
:param interesting_files: The tree-relative paths of files that should
448
participate in the merge. If these paths refer to directories,
449
the contents of those directories will also be included. May not
450
be combined with interesting_ids. If neither interesting_files nor
451
interesting_ids is specified, all files may participate in the
454
object.__init__(self)
455
if interesting_files is not None:
456
assert interesting_ids is None
457
self.interesting_ids = interesting_ids
458
self.interesting_files = interesting_files
459
self.this_tree = working_tree
460
self.this_tree.lock_tree_write()
461
self.base_tree = base_tree
462
self.base_tree.lock_read()
463
self.other_tree = other_tree
464
self.other_tree.lock_read()
465
self._raw_conflicts = []
466
self.cooked_conflicts = []
467
self.reprocess = reprocess
468
self.show_base = show_base
471
self.change_reporter = change_reporter
473
self.pp = ProgressPhase("Merge phase", 3, self.pb)
475
self.tt = TreeTransform(working_tree, self.pb)
478
entries = self._entries3()
479
child_pb = ui.ui_factory.nested_progress_bar()
481
for num, (file_id, changed, parents3, names3,
482
executable3) in enumerate(entries):
483
child_pb.update('Preparing file merge', num, len(entries))
484
self._merge_names(file_id, parents3, names3)
486
file_status = self.merge_contents(file_id)
488
file_status = 'unmodified'
489
self._merge_executable(file_id,
490
executable3, file_status)
495
child_pb = ui.ui_factory.nested_progress_bar()
497
fs_conflicts = resolve_conflicts(self.tt, child_pb,
498
lambda t, c: conflict_pass(t, c, self.other_tree))
501
if change_reporter is not None:
502
from bzrlib import delta
503
delta.report_changes(self.tt._iter_changes(), change_reporter)
504
self.cook_conflicts(fs_conflicts)
505
for conflict in self.cooked_conflicts:
508
results = self.tt.apply(no_conflicts=True)
509
self.write_modified(results)
511
working_tree.add_conflicts(self.cooked_conflicts)
512
except UnsupportedOperation:
516
self.other_tree.unlock()
517
self.base_tree.unlock()
518
self.this_tree.unlock()
522
"""Gather data about files modified between three trees.
524
Return a list of tuples of file_id, changed, parents3, names3,
525
executable3. changed is a boolean indicating whether the file contents
526
or kind were changed. parents3 is a tuple of parent ids for base,
527
other and this. names3 is a tuple of names for base, other and this.
528
executable3 is a tuple of execute-bit values for base, other and this.
531
iterator = self.other_tree._iter_changes(self.base_tree,
532
include_unchanged=True, specific_files=self.interesting_files,
533
extra_trees=[self.this_tree])
534
for (file_id, paths, changed, versioned, parents, names, kind,
535
executable) in iterator:
536
if (self.interesting_ids is not None and
537
file_id not in self.interesting_ids):
539
if file_id in self.this_tree.inventory:
540
entry = self.this_tree.inventory[file_id]
541
this_name = entry.name
542
this_parent = entry.parent_id
543
this_executable = entry.executable
547
this_executable = None
548
parents3 = parents + (this_parent,)
549
names3 = names + (this_name,)
550
executable3 = executable + (this_executable,)
551
result.append((file_id, changed, parents3, names3, executable3))
556
self.tt.final_kind(self.tt.root)
558
self.tt.cancel_deletion(self.tt.root)
559
if self.tt.final_file_id(self.tt.root) is None:
560
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
562
if self.other_tree.inventory.root is None:
564
other_root_file_id = self.other_tree.get_root_id()
565
other_root = self.tt.trans_id_file_id(other_root_file_id)
566
if other_root == self.tt.root:
569
self.tt.final_kind(other_root)
572
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
573
self.tt.cancel_creation(other_root)
574
self.tt.cancel_versioning(other_root)
576
def reparent_children(self, ie, target):
577
for thing, child in ie.children.iteritems():
578
trans_id = self.tt.trans_id_file_id(child.file_id)
579
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
581
def write_modified(self, results):
583
for path in results.modified_paths:
584
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
587
hash = self.this_tree.get_file_sha1(file_id)
590
modified_hashes[file_id] = hash
591
self.this_tree.set_merge_modified(modified_hashes)
594
def parent(entry, file_id):
595
"""Determine the parent for a file_id (used as a key method)"""
598
return entry.parent_id
601
def name(entry, file_id):
602
"""Determine the name for a file_id (used as a key method)"""
608
def contents_sha1(tree, file_id):
609
"""Determine the sha1 of the file contents (used as a key method)."""
610
if file_id not in tree:
612
return tree.get_file_sha1(file_id)
615
def executable(tree, file_id):
616
"""Determine the executability of a file-id (used as a key method)."""
617
if file_id not in tree:
619
if tree.kind(file_id) != "file":
621
return tree.is_executable(file_id)
624
def kind(tree, file_id):
625
"""Determine the kind of a file-id (used as a key method)."""
626
if file_id not in tree:
628
return tree.kind(file_id)
631
def _three_way(base, other, this):
632
#if base == other, either they all agree, or only THIS has changed.
635
elif this not in (base, other):
637
# "Ambiguous clean merge" -- both sides have made the same change.
640
# this == base: only other has changed.
645
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
646
"""Do a three-way test on a scalar.
647
Return "this", "other" or "conflict", depending whether a value wins.
649
key_base = key(base_tree, file_id)
650
key_other = key(other_tree, file_id)
651
#if base == other, either they all agree, or only THIS has changed.
652
if key_base == key_other:
654
key_this = key(this_tree, file_id)
655
if key_this not in (key_base, key_other):
657
# "Ambiguous clean merge"
658
elif key_this == key_other:
661
assert key_this == key_base
664
def merge_names(self, file_id):
666
if file_id in tree.inventory:
667
return tree.inventory[file_id]
670
this_entry = get_entry(self.this_tree)
671
other_entry = get_entry(self.other_tree)
672
base_entry = get_entry(self.base_tree)
673
entries = (base_entry, other_entry, this_entry)
676
for entry in entries:
681
names.append(entry.name)
682
parents.append(entry.parent_id)
683
return self._merge_names(file_id, parents, names)
685
def _merge_names(self, file_id, parents, names):
686
"""Perform a merge on file_id names and parents"""
687
base_name, other_name, this_name = names
688
base_parent, other_parent, this_parent = parents
690
name_winner = self._three_way(*names)
692
parent_id_winner = self._three_way(*parents)
693
if this_name is None:
694
if name_winner == "this":
695
name_winner = "other"
696
if parent_id_winner == "this":
697
parent_id_winner = "other"
698
if name_winner == "this" and parent_id_winner == "this":
700
if name_winner == "conflict":
701
trans_id = self.tt.trans_id_file_id(file_id)
702
self._raw_conflicts.append(('name conflict', trans_id,
703
this_name, other_name))
704
if parent_id_winner == "conflict":
705
trans_id = self.tt.trans_id_file_id(file_id)
706
self._raw_conflicts.append(('parent conflict', trans_id,
707
this_parent, other_parent))
708
if other_name is None:
709
# it doesn't matter whether the result was 'other' or
710
# 'conflict'-- if there's no 'other', we leave it alone.
712
# if we get here, name_winner and parent_winner are set to safe values.
713
trans_id = self.tt.trans_id_file_id(file_id)
714
parent_id = parents[self.winner_idx[parent_id_winner]]
715
if parent_id is not None:
716
parent_trans_id = self.tt.trans_id_file_id(parent_id)
717
self.tt.adjust_path(names[self.winner_idx[name_winner]],
718
parent_trans_id, trans_id)
720
def merge_contents(self, file_id):
721
"""Performa a merge on file_id contents."""
722
def contents_pair(tree):
723
if file_id not in tree:
725
kind = tree.kind(file_id)
727
contents = tree.get_file_sha1(file_id)
728
elif kind == "symlink":
729
contents = tree.get_symlink_target(file_id)
732
return kind, contents
734
def contents_conflict():
735
trans_id = self.tt.trans_id_file_id(file_id)
736
name = self.tt.final_name(trans_id)
737
parent_id = self.tt.final_parent(trans_id)
738
if file_id in self.this_tree.inventory:
739
self.tt.unversion_file(trans_id)
740
if file_id in self.this_tree:
741
self.tt.delete_contents(trans_id)
742
file_group = self._dump_conflicts(name, parent_id, file_id,
744
self._raw_conflicts.append(('contents conflict', file_group))
746
# See SPOT run. run, SPOT, run.
747
# So we're not QUITE repeating ourselves; we do tricky things with
749
base_pair = contents_pair(self.base_tree)
750
other_pair = contents_pair(self.other_tree)
751
if base_pair == other_pair:
752
# OTHER introduced no changes
754
this_pair = contents_pair(self.this_tree)
755
if this_pair == other_pair:
756
# THIS and OTHER introduced the same changes
759
trans_id = self.tt.trans_id_file_id(file_id)
760
if this_pair == base_pair:
761
# only OTHER introduced changes
762
if file_id in self.this_tree:
763
# Remove any existing contents
764
self.tt.delete_contents(trans_id)
765
if file_id in self.other_tree:
766
# OTHER changed the file
767
create_by_entry(self.tt,
768
self.other_tree.inventory[file_id],
769
self.other_tree, trans_id)
770
if file_id not in self.this_tree.inventory:
771
self.tt.version_file(file_id, trans_id)
773
elif file_id in self.this_tree.inventory:
774
# OTHER deleted the file
775
self.tt.unversion_file(trans_id)
777
#BOTH THIS and OTHER introduced changes; scalar conflict
778
elif this_pair[0] == "file" and other_pair[0] == "file":
779
# THIS and OTHER are both files, so text merge. Either
780
# BASE is a file, or both converted to files, so at least we
781
# have agreement that output should be a file.
783
self.text_merge(file_id, trans_id)
785
return contents_conflict()
786
if file_id not in self.this_tree.inventory:
787
self.tt.version_file(file_id, trans_id)
789
self.tt.tree_kind(trans_id)
790
self.tt.delete_contents(trans_id)
795
# Scalar conflict, can't text merge. Dump conflicts
796
return contents_conflict()
798
def get_lines(self, tree, file_id):
799
"""Return the lines in a file, or an empty list."""
801
return tree.get_file(file_id).readlines()
805
def text_merge(self, file_id, trans_id):
806
"""Perform a three-way text merge on a file_id"""
807
# it's possible that we got here with base as a different type.
808
# if so, we just want two-way text conflicts.
809
if file_id in self.base_tree and \
810
self.base_tree.kind(file_id) == "file":
811
base_lines = self.get_lines(self.base_tree, file_id)
814
other_lines = self.get_lines(self.other_tree, file_id)
815
this_lines = self.get_lines(self.this_tree, file_id)
816
m3 = Merge3(base_lines, this_lines, other_lines)
817
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
818
if self.show_base is True:
819
base_marker = '|' * 7
823
def iter_merge3(retval):
824
retval["text_conflicts"] = False
825
for line in m3.merge_lines(name_a = "TREE",
826
name_b = "MERGE-SOURCE",
827
name_base = "BASE-REVISION",
828
start_marker=start_marker,
829
base_marker=base_marker,
830
reprocess=self.reprocess):
831
if line.startswith(start_marker):
832
retval["text_conflicts"] = True
833
yield line.replace(start_marker, '<' * 7)
837
merge3_iterator = iter_merge3(retval)
838
self.tt.create_file(merge3_iterator, trans_id)
839
if retval["text_conflicts"] is True:
840
self._raw_conflicts.append(('text conflict', trans_id))
841
name = self.tt.final_name(trans_id)
842
parent_id = self.tt.final_parent(trans_id)
843
file_group = self._dump_conflicts(name, parent_id, file_id,
844
this_lines, base_lines,
846
file_group.append(trans_id)
848
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
849
base_lines=None, other_lines=None, set_version=False,
851
"""Emit conflict files.
852
If this_lines, base_lines, or other_lines are omitted, they will be
853
determined automatically. If set_version is true, the .OTHER, .THIS
854
or .BASE (in that order) will be created as versioned files.
856
data = [('OTHER', self.other_tree, other_lines),
857
('THIS', self.this_tree, this_lines)]
859
data.append(('BASE', self.base_tree, base_lines))
862
for suffix, tree, lines in data:
864
trans_id = self._conflict_file(name, parent_id, tree, file_id,
866
file_group.append(trans_id)
867
if set_version and not versioned:
868
self.tt.version_file(file_id, trans_id)
872
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
874
"""Emit a single conflict file."""
875
name = name + '.' + suffix
876
trans_id = self.tt.create_path(name, parent_id)
877
entry = tree.inventory[file_id]
878
create_by_entry(self.tt, entry, tree, trans_id, lines)
881
def merge_executable(self, file_id, file_status):
882
"""Perform a merge on the execute bit."""
883
executable = [self.executable(t, file_id) for t in (self.base_tree,
884
self.other_tree, self.this_tree)]
885
self._merge_executable(file_id, executable, file_status)
887
def _merge_executable(self, file_id, executable, file_status):
888
"""Perform a merge on the execute bit."""
889
base_executable, other_executable, this_executable = executable
890
if file_status == "deleted":
892
trans_id = self.tt.trans_id_file_id(file_id)
894
if self.tt.final_kind(trans_id) != "file":
898
winner = self._three_way(*executable)
899
if winner == "conflict":
900
# There must be a None in here, if we have a conflict, but we
901
# need executability since file status was not deleted.
902
if self.executable(self.other_tree, file_id) is None:
907
if file_status == "modified":
908
executability = this_executable
909
if executability is not None:
910
trans_id = self.tt.trans_id_file_id(file_id)
911
self.tt.set_executability(executability, trans_id)
913
assert winner == "other"
914
if file_id in self.other_tree:
915
executability = other_executable
916
elif file_id in self.this_tree:
917
executability = this_executable
918
elif file_id in self.base_tree:
919
executability = base_executable
920
if executability is not None:
921
trans_id = self.tt.trans_id_file_id(file_id)
922
self.tt.set_executability(executability, trans_id)
924
def cook_conflicts(self, fs_conflicts):
925
"""Convert all conflicts into a form that doesn't depend on trans_id"""
926
from conflicts import Conflict
928
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
929
fp = FinalPaths(self.tt)
930
for conflict in self._raw_conflicts:
931
conflict_type = conflict[0]
932
if conflict_type in ('name conflict', 'parent conflict'):
933
trans_id = conflict[1]
934
conflict_args = conflict[2:]
935
if trans_id not in name_conflicts:
936
name_conflicts[trans_id] = {}
937
unique_add(name_conflicts[trans_id], conflict_type,
939
if conflict_type == 'contents conflict':
940
for trans_id in conflict[1]:
941
file_id = self.tt.final_file_id(trans_id)
942
if file_id is not None:
944
path = fp.get_path(trans_id)
945
for suffix in ('.BASE', '.THIS', '.OTHER'):
946
if path.endswith(suffix):
947
path = path[:-len(suffix)]
949
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
950
self.cooked_conflicts.append(c)
951
if conflict_type == 'text conflict':
952
trans_id = conflict[1]
953
path = fp.get_path(trans_id)
954
file_id = self.tt.final_file_id(trans_id)
955
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
956
self.cooked_conflicts.append(c)
958
for trans_id, conflicts in name_conflicts.iteritems():
960
this_parent, other_parent = conflicts['parent conflict']
961
assert this_parent != other_parent
963
this_parent = other_parent = \
964
self.tt.final_file_id(self.tt.final_parent(trans_id))
966
this_name, other_name = conflicts['name conflict']
967
assert this_name != other_name
969
this_name = other_name = self.tt.final_name(trans_id)
970
other_path = fp.get_path(trans_id)
971
if this_parent is not None and this_name is not None:
973
fp.get_path(self.tt.trans_id_file_id(this_parent))
974
this_path = pathjoin(this_parent_path, this_name)
976
this_path = "<deleted>"
977
file_id = self.tt.final_file_id(trans_id)
978
c = Conflict.factory('path conflict', path=this_path,
979
conflict_path=other_path, file_id=file_id)
980
self.cooked_conflicts.append(c)
981
self.cooked_conflicts.sort(key=Conflict.sort_key)
984
class WeaveMerger(Merge3Merger):
985
"""Three-way tree merger, text weave merger."""
986
supports_reprocess = True
987
supports_show_base = False
988
supports_reverse_cherrypick = False
991
def __init__(self, working_tree, this_tree, base_tree, other_tree,
992
interesting_ids=None, pb=DummyProgress(), pp=None,
993
reprocess=False, change_reporter=None,
994
interesting_files=None, cherrypick=False):
995
self.cherrypick = cherrypick
996
super(WeaveMerger, self).__init__(working_tree, this_tree,
997
base_tree, other_tree,
998
interesting_ids=interesting_ids,
999
pb=pb, pp=pp, reprocess=reprocess,
1000
change_reporter=change_reporter)
1002
def _merged_lines(self, file_id):
1003
"""Generate the merged lines.
1004
There is no distinction between lines that are meant to contain <<<<<<<
1008
base = self.base_tree
1011
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1013
if 'merge' in debug.debug_flags:
1015
trans_id = self.tt.trans_id_file_id(file_id)
1016
name = self.tt.final_name(trans_id) + '.plan'
1017
contents = ('%10s|%s' % l for l in plan)
1018
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1019
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1020
'>>>>>>> MERGE-SOURCE\n')
1021
return textmerge.merge_lines(self.reprocess)
1023
def text_merge(self, file_id, trans_id):
1024
"""Perform a (weave) text merge for a given file and file-id.
1025
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1026
and a conflict will be noted.
1028
lines, conflicts = self._merged_lines(file_id)
1030
# Note we're checking whether the OUTPUT is binary in this case,
1031
# because we don't want to get into weave merge guts.
1032
check_text_lines(lines)
1033
self.tt.create_file(lines, trans_id)
1035
self._raw_conflicts.append(('text conflict', trans_id))
1036
name = self.tt.final_name(trans_id)
1037
parent_id = self.tt.final_parent(trans_id)
1038
file_group = self._dump_conflicts(name, parent_id, file_id,
1040
file_group.append(trans_id)
1043
class Diff3Merger(Merge3Merger):
1044
"""Three-way merger using external diff3 for text merging"""
1046
def dump_file(self, temp_dir, name, tree, file_id):
1047
out_path = pathjoin(temp_dir, name)
1048
out_file = open(out_path, "wb")
1050
in_file = tree.get_file(file_id)
1051
for line in in_file:
1052
out_file.write(line)
1057
def text_merge(self, file_id, trans_id):
1058
"""Perform a diff3 merge using a specified file-id and trans-id.
1059
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1060
will be dumped, and a will be conflict noted.
1063
temp_dir = osutils.mkdtemp(prefix="bzr-")
1065
new_file = pathjoin(temp_dir, "new")
1066
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1067
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1068
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1069
status = bzrlib.patch.diff3(new_file, this, base, other)
1070
if status not in (0, 1):
1071
raise BzrError("Unhandled diff3 exit code")
1072
f = open(new_file, 'rb')
1074
self.tt.create_file(f, trans_id)
1078
name = self.tt.final_name(trans_id)
1079
parent_id = self.tt.final_parent(trans_id)
1080
self._dump_conflicts(name, parent_id, file_id)
1081
self._raw_conflicts.append(('text conflict', trans_id))
1083
osutils.rmtree(temp_dir)
1086
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1088
merge_type=Merge3Merger,
1089
interesting_ids=None,
1093
interesting_files=None,
1096
change_reporter=None):
1097
"""Primary interface for merging.
1099
typical use is probably
1100
'merge_inner(branch, branch.get_revision_tree(other_revision),
1101
branch.get_revision_tree(base_revision))'
1103
if this_tree is None:
1104
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1105
"parameter as of bzrlib version 0.8.")
1106
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1107
pb=pb, change_reporter=change_reporter)
1108
merger.backup_files = backup_files
1109
merger.merge_type = merge_type
1110
merger.interesting_ids = interesting_ids
1111
merger.ignore_zero = ignore_zero
1112
if interesting_files:
1113
assert not interesting_ids, ('Only supply interesting_ids'
1114
' or interesting_files')
1115
merger.interesting_files = interesting_files
1116
merger.show_base = show_base
1117
merger.reprocess = reprocess
1118
merger.other_rev_id = other_rev_id
1119
merger.other_basis = other_rev_id
1120
return merger.do_merge()
1122
def get_merge_type_registry():
1123
"""Merge type registry is in bzrlib.option to avoid circular imports.
1125
This method provides a sanctioned way to retrieve it.
1127
from bzrlib import option
1128
return option._merge_type_registry
1131
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1132
def status_a(revision, text):
1133
if revision in ancestors_b:
1134
return 'killed-b', text
1136
return 'new-a', text
1138
def status_b(revision, text):
1139
if revision in ancestors_a:
1140
return 'killed-a', text
1142
return 'new-b', text
1144
plain_a = [t for (a, t) in annotated_a]
1145
plain_b = [t for (a, t) in annotated_b]
1146
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1147
blocks = matcher.get_matching_blocks()
1150
for ai, bi, l in blocks:
1151
# process all mismatched sections
1152
# (last mismatched section is handled because blocks always
1153
# includes a 0-length last block)
1154
for revision, text in annotated_a[a_cur:ai]:
1155
yield status_a(revision, text)
1156
for revision, text in annotated_b[b_cur:bi]:
1157
yield status_b(revision, text)
1159
# and now the matched section
1162
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1163
assert text_a == text_b
1164
yield "unchanged", text_a
1167
class _PlanMerge(object):
1168
"""Plan an annotate merge using on-the-fly annotation"""
1170
def __init__(self, a_rev, b_rev, vf):
1173
:param a_rev: Revision-id of one revision to merge
1174
:param b_rev: Revision-id of the other revision to merge
1175
:param vf: A versionedfile containing both revisions
1179
self.lines_a = vf.get_lines(a_rev)
1180
self.lines_b = vf.get_lines(b_rev)
1182
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1183
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1184
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1185
self._last_lines = None
1186
self._last_lines_revision_id = None
1188
def plan_merge(self):
1189
"""Generate a 'plan' for merging the two revisions.
1191
This involves comparing their texts and determining the cause of
1192
differences. If text A has a line and text B does not, then either the
1193
line was added to text A, or it was deleted from B. Once the causes
1194
are combined, they are written out in the format described in
1195
VersionedFile.plan_merge
1197
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1198
new_a = self._find_new(self.a_rev)
1199
new_b = self._find_new(self.b_rev)
1202
a_lines = self.vf.get_lines(self.a_rev)
1203
b_lines = self.vf.get_lines(self.b_rev)
1204
for i, j, n in blocks:
1205
# determine why lines aren't common
1206
for a_index in range(last_i, i):
1207
if a_index in new_a:
1211
yield cause, a_lines[a_index]
1212
for b_index in range(last_j, j):
1213
if b_index in new_b:
1217
yield cause, b_lines[b_index]
1218
# handle common lines
1219
for a_index in range(i, i+n):
1220
yield 'unchanged', a_lines[a_index]
1224
def _get_matching_blocks(self, left_revision, right_revision):
1225
"""Return a description of which sections of two revisions match.
1227
See SequenceMatcher.get_matching_blocks
1229
if self._last_lines_revision_id == left_revision:
1230
left_lines = self._last_lines
1232
left_lines = self.vf.get_lines(left_revision)
1233
right_lines = self.vf.get_lines(right_revision)
1234
self._last_lines = right_lines
1235
self._last_lines_revision_id = right_revision
1236
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1238
return matcher.get_matching_blocks()
1240
def _unique_lines(self, matching_blocks):
1241
"""Analyse matching_blocks to determine which lines are unique
1243
:return: a tuple of (unique_left, unique_right), where the values are
1244
sets of line numbers of unique lines.
1250
for i, j, n in matching_blocks:
1251
unique_left.extend(range(last_i, i))
1252
unique_right.extend(range(last_j, j))
1255
return unique_left, unique_right
1257
def _find_new(self, version_id):
1258
"""Determine which lines are new in the ancestry of this version.
1260
If a lines is present in this version, and not present in any
1261
common ancestor, it is considered new.
1263
if version_id not in self.uncommon:
1265
parents = self.vf.get_parents(version_id)
1266
if len(parents) == 0:
1267
return set(range(len(self.vf.get_lines(version_id))))
1269
for parent in parents:
1270
blocks = self._get_matching_blocks(version_id, parent)
1271
result, unused = self._unique_lines(blocks)
1272
parent_new = self._find_new(parent)
1273
for i, j, n in blocks:
1274
for ii, jj in [(i+r, j+r) for r in range(n)]:
1275
if jj in parent_new:
1280
new.intersection_update(result)
1284
def _subtract_plans(old_plan, new_plan):
1285
# Can't use patience diff-- C version doesn't work with tuples
1286
matcher = difflib.SequenceMatcher(None, old_plan, new_plan)
1288
for i, j, n in matcher.get_matching_blocks():
1289
for jj in range(last_j, j):
1291
for jj in range(j, j+n):
1292
plan_line = new_plan[jj]
1293
if plan_line[0] == 'new-b':
1295
elif plan_line[0] == 'killed-b':
1296
yield 'unchanged', plan_line[1]