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 (is_ancestor, 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 = {}
92
def revision_tree(self, revision_id, branch=None):
93
if revision_id not in self._cached_trees:
95
branch = self.this_branch
97
tree = self.this_tree.revision_tree(revision_id)
98
except errors.NoSuchRevisionInTree:
99
tree = branch.repository.revision_tree(revision_id)
100
self._cached_trees[revision_id] = tree
101
return self._cached_trees[revision_id]
103
def _get_tree(self, treespec):
104
from bzrlib import workingtree
105
location, revno = treespec
107
tree = workingtree.WorkingTree.open_containing(location)[0]
108
return tree.branch, tree
109
branch = Branch.open_containing(location)[0]
111
revision_id = branch.last_revision()
113
revision_id = branch.get_rev_id(revno)
114
revision_id = ensure_null(revision_id)
115
return branch, self.revision_tree(revision_id, branch)
117
def ensure_revision_trees(self):
118
if self.this_revision_tree is None:
119
self.this_basis_tree = self.revision_tree(self.this_basis)
120
if self.this_basis == self.this_rev_id:
121
self.this_revision_tree = self.this_basis_tree
123
if self.other_rev_id is None:
124
other_basis_tree = self.revision_tree(self.other_basis)
125
changes = other_basis_tree.changes_from(self.other_tree)
126
if changes.has_changed():
127
raise WorkingTreeNotRevision(self.this_tree)
128
other_rev_id = self.other_basis
129
self.other_tree = other_basis_tree
131
def file_revisions(self, file_id):
132
self.ensure_revision_trees()
133
def get_id(tree, file_id):
134
revision_id = tree.inventory[file_id].revision
135
assert revision_id is not None
137
if self.this_rev_id is None:
138
if self.this_basis_tree.get_file_sha1(file_id) != \
139
self.this_tree.get_file_sha1(file_id):
140
raise WorkingTreeNotRevision(self.this_tree)
142
trees = (self.this_basis_tree, self.other_tree)
143
return [get_id(tree, file_id) for tree in trees]
145
def check_basis(self, check_clean, require_commits=True):
146
if self.this_basis is None and require_commits is True:
147
raise BzrCommandError("This branch has no commits."
148
" (perhaps you would prefer 'bzr pull')")
151
if self.this_basis != self.this_rev_id:
152
raise BzrCommandError("Working tree has uncommitted changes.")
154
def compare_basis(self):
156
basis_tree = self.revision_tree(self.this_tree.last_revision())
157
except errors.RevisionNotPresent:
158
basis_tree = self.this_tree.basis_tree()
159
changes = self.this_tree.changes_from(basis_tree)
160
if not changes.has_changed():
161
self.this_rev_id = self.this_basis
163
def set_interesting_files(self, file_list):
164
self.interesting_files = file_list
166
def set_pending(self):
167
if not self.base_is_ancestor or not self.base_is_other_ancestor:
171
def _add_parent(self):
172
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
173
new_parent_trees = []
174
for revision_id in new_parents:
176
tree = self.revision_tree(revision_id)
177
except errors.RevisionNotPresent:
181
new_parent_trees.append((revision_id, tree))
183
self.this_tree.set_parent_trees(new_parent_trees,
184
allow_leftmost_as_ghost=True)
186
for _revision_id, tree in new_parent_trees:
190
def set_other(self, other_revision):
191
"""Set the revision and tree to merge from.
193
This sets the other_tree, other_rev_id, other_basis attributes.
195
:param other_revision: The [path, revision] list to merge from.
197
self.other_branch, self.other_tree = self._get_tree(other_revision)
198
if other_revision[1] == -1:
199
self.other_rev_id = _mod_revision.ensure_null(
200
self.other_branch.last_revision())
201
if _mod_revision.is_null(self.other_rev_id):
202
raise NoCommits(self.other_branch)
203
self.other_basis = self.other_rev_id
204
elif other_revision[1] is not None:
205
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
206
self.other_basis = self.other_rev_id
208
self.other_rev_id = None
209
self.other_basis = self.other_branch.last_revision()
210
if self.other_basis is None:
211
raise NoCommits(self.other_branch)
212
if self.other_rev_id is not None:
213
self._cached_trees[self.other_rev_id] = self.other_tree
214
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
216
def set_other_revision(self, revision_id, other_branch):
217
"""Set 'other' based on a branch and revision id
219
:param revision_id: The revision to use for a tree
220
:param other_branch: The branch containing this tree
222
self.other_rev_id = revision_id
223
self.other_branch = other_branch
224
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
225
self.other_tree = self.revision_tree(revision_id)
226
self.other_basis = revision_id
228
def set_base_revision(self, revision_id, branch):
229
"""Set 'base' based on a branch and revision id
231
:param revision_id: The revision to use for a tree
232
:param branch: The branch containing this tree
234
self.base_rev_id = revision_id
235
self.base_branch = branch
236
self._maybe_fetch(branch, self.this_branch, revision_id)
237
self.base_tree = self.revision_tree(revision_id)
238
self.base_is_ancestor = is_ancestor(self.this_basis,
241
self.base_is_other_ancestor = is_ancestor(self.other_basis,
245
def _maybe_fetch(self, source, target, revision_id):
246
if (source.repository.bzrdir.root_transport.base !=
247
target.repository.bzrdir.root_transport.base):
248
target.fetch(source, revision_id)
251
this_repo = self.this_branch.repository
252
graph = this_repo.get_graph()
253
revisions = [ensure_null(self.this_basis),
254
ensure_null(self.other_basis)]
255
if NULL_REVISION in revisions:
256
self.base_rev_id = NULL_REVISION
258
self.base_rev_id = graph.find_unique_lca(*revisions)
259
if self.base_rev_id == NULL_REVISION:
260
raise UnrelatedBranches()
261
self.base_tree = self.revision_tree(self.base_rev_id)
262
self.base_is_ancestor = True
263
self.base_is_other_ancestor = True
265
def set_base(self, base_revision):
266
"""Set the base revision to use for the merge.
268
:param base_revision: A 2-list containing a path and revision number.
270
mutter("doing merge() with no base_revision specified")
271
if base_revision == [None, None]:
274
base_branch, self.base_tree = self._get_tree(base_revision)
275
if base_revision[1] == -1:
276
self.base_rev_id = base_branch.last_revision()
277
elif base_revision[1] is None:
278
self.base_rev_id = _mod_revision.NULL_REVISION
280
self.base_rev_id = _mod_revision.ensure_null(
281
base_branch.get_rev_id(base_revision[1]))
282
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
283
self.base_is_ancestor = is_ancestor(self.this_basis,
286
self.base_is_other_ancestor = is_ancestor(self.other_basis,
291
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
292
'other_tree': self.other_tree,
293
'interesting_ids': self.interesting_ids,
294
'interesting_files': self.interesting_files,
296
if self.merge_type.requires_base:
297
kwargs['base_tree'] = self.base_tree
298
if self.merge_type.supports_reprocess:
299
kwargs['reprocess'] = self.reprocess
301
raise BzrError("Conflict reduction is not supported for merge"
302
" type %s." % self.merge_type)
303
if self.merge_type.supports_show_base:
304
kwargs['show_base'] = self.show_base
306
raise BzrError("Showing base is not supported for this"
307
" merge type. %s" % self.merge_type)
308
self.this_tree.lock_tree_write()
309
if self.base_tree is not None:
310
self.base_tree.lock_read()
311
if self.other_tree is not None:
312
self.other_tree.lock_read()
314
merge = self.merge_type(pb=self._pb,
315
change_reporter=self.change_reporter,
317
if self.recurse == 'down':
318
for path, file_id in self.this_tree.iter_references():
319
sub_tree = self.this_tree.get_nested_tree(file_id, path)
320
other_revision = self.other_tree.get_reference_revision(
322
if other_revision == sub_tree.last_revision():
324
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
325
sub_merge.merge_type = self.merge_type
326
relpath = self.this_tree.relpath(path)
327
other_branch = self.other_branch.reference_parent(file_id, relpath)
328
sub_merge.set_other_revision(other_revision, other_branch)
329
base_revision = self.base_tree.get_reference_revision(file_id)
330
sub_merge.base_tree = \
331
sub_tree.branch.repository.revision_tree(base_revision)
335
if self.other_tree is not None:
336
self.other_tree.unlock()
337
if self.base_tree is not None:
338
self.base_tree.unlock()
339
self.this_tree.unlock()
340
if len(merge.cooked_conflicts) == 0:
341
if not self.ignore_zero:
342
note("All changes applied successfully.")
344
note("%d conflicts encountered." % len(merge.cooked_conflicts))
346
return len(merge.cooked_conflicts)
349
class Merge3Merger(object):
350
"""Three-way merger that uses the merge3 text merger"""
352
supports_reprocess = True
353
supports_show_base = True
354
history_based = False
355
winner_idx = {"this": 2, "other": 1, "conflict": 1}
357
def __init__(self, working_tree, this_tree, base_tree, other_tree,
358
interesting_ids=None, reprocess=False, show_base=False,
359
pb=DummyProgress(), pp=None, change_reporter=None,
360
interesting_files=None):
361
"""Initialize the merger object and perform the merge.
363
:param working_tree: The working tree to apply the merge to
364
:param this_tree: The local tree in the merge operation
365
:param base_tree: The common tree in the merge operation
366
:param other_tree: The other other tree to merge changes from
367
:param interesting_ids: The file_ids of files that should be
368
participate in the merge. May not be combined with
370
:param: reprocess If True, perform conflict-reduction processing.
371
:param show_base: If True, show the base revision in text conflicts.
372
(incompatible with reprocess)
373
:param pb: A Progress bar
374
:param pp: A ProgressPhase object
375
:param change_reporter: An object that should report changes made
376
:param interesting_files: The tree-relative paths of files that should
377
participate in the merge. If these paths refer to directories,
378
the contents of those directories will also be included. May not
379
be combined with interesting_ids. If neither interesting_files nor
380
interesting_ids is specified, all files may participate in the
383
object.__init__(self)
384
if interesting_files is not None:
385
assert interesting_ids is None
386
self.interesting_ids = interesting_ids
387
self.interesting_files = interesting_files
388
self.this_tree = working_tree
389
self.this_tree.lock_tree_write()
390
self.base_tree = base_tree
391
self.base_tree.lock_read()
392
self.other_tree = other_tree
393
self.other_tree.lock_read()
394
self._raw_conflicts = []
395
self.cooked_conflicts = []
396
self.reprocess = reprocess
397
self.show_base = show_base
400
self.change_reporter = change_reporter
402
self.pp = ProgressPhase("Merge phase", 3, self.pb)
404
self.tt = TreeTransform(working_tree, self.pb)
407
entries = self._entries3()
408
child_pb = ui.ui_factory.nested_progress_bar()
410
for num, (file_id, changed, parents3, names3,
411
executable3) in enumerate(entries):
412
child_pb.update('Preparing file merge', num, len(entries))
413
self._merge_names(file_id, parents3, names3)
415
file_status = self.merge_contents(file_id)
417
file_status = 'unmodified'
418
self._merge_executable(file_id,
419
executable3, file_status)
424
child_pb = ui.ui_factory.nested_progress_bar()
426
fs_conflicts = resolve_conflicts(self.tt, child_pb,
427
lambda t, c: conflict_pass(t, c, self.other_tree))
430
if change_reporter is not None:
431
from bzrlib import delta
432
delta.report_changes(self.tt._iter_changes(), change_reporter)
433
self.cook_conflicts(fs_conflicts)
434
for conflict in self.cooked_conflicts:
437
results = self.tt.apply(no_conflicts=True)
438
self.write_modified(results)
440
working_tree.add_conflicts(self.cooked_conflicts)
441
except UnsupportedOperation:
445
self.other_tree.unlock()
446
self.base_tree.unlock()
447
self.this_tree.unlock()
451
"""Gather data about files modified between three trees.
453
Return a list of tuples of file_id, changed, parents3, names3,
454
executable3. changed is a boolean indicating whether the file contents
455
or kind were changed. parents3 is a tuple of parent ids for base,
456
other and this. names3 is a tuple of names for base, other and this.
457
executable3 is a tuple of execute-bit values for base, other and this.
460
iterator = self.other_tree._iter_changes(self.base_tree,
461
include_unchanged=True, specific_files=self.interesting_files,
462
extra_trees=[self.this_tree])
463
for (file_id, paths, changed, versioned, parents, names, kind,
464
executable) in iterator:
465
if (self.interesting_ids is not None and
466
file_id not in self.interesting_ids):
468
if file_id in self.this_tree.inventory:
469
entry = self.this_tree.inventory[file_id]
470
this_name = entry.name
471
this_parent = entry.parent_id
472
this_executable = entry.executable
476
this_executable = None
477
parents3 = parents + (this_parent,)
478
names3 = names + (this_name,)
479
executable3 = executable + (this_executable,)
480
result.append((file_id, changed, parents3, names3, executable3))
485
self.tt.final_kind(self.tt.root)
487
self.tt.cancel_deletion(self.tt.root)
488
if self.tt.final_file_id(self.tt.root) is None:
489
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
491
if self.other_tree.inventory.root is None:
493
other_root_file_id = self.other_tree.inventory.root.file_id
494
other_root = self.tt.trans_id_file_id(other_root_file_id)
495
if other_root == self.tt.root:
498
self.tt.final_kind(other_root)
501
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
502
self.tt.cancel_creation(other_root)
503
self.tt.cancel_versioning(other_root)
505
def reparent_children(self, ie, target):
506
for thing, child in ie.children.iteritems():
507
trans_id = self.tt.trans_id_file_id(child.file_id)
508
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
510
def write_modified(self, results):
512
for path in results.modified_paths:
513
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
516
hash = self.this_tree.get_file_sha1(file_id)
519
modified_hashes[file_id] = hash
520
self.this_tree.set_merge_modified(modified_hashes)
523
def parent(entry, file_id):
524
"""Determine the parent for a file_id (used as a key method)"""
527
return entry.parent_id
530
def name(entry, file_id):
531
"""Determine the name for a file_id (used as a key method)"""
537
def contents_sha1(tree, file_id):
538
"""Determine the sha1 of the file contents (used as a key method)."""
539
if file_id not in tree:
541
return tree.get_file_sha1(file_id)
544
def executable(tree, file_id):
545
"""Determine the executability of a file-id (used as a key method)."""
546
if file_id not in tree:
548
if tree.kind(file_id) != "file":
550
return tree.is_executable(file_id)
553
def kind(tree, file_id):
554
"""Determine the kind of a file-id (used as a key method)."""
555
if file_id not in tree:
557
return tree.kind(file_id)
560
def _three_way(base, other, this):
561
#if base == other, either they all agree, or only THIS has changed.
564
elif this not in (base, other):
566
# "Ambiguous clean merge" -- both sides have made the same change.
569
# this == base: only other has changed.
574
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
575
"""Do a three-way test on a scalar.
576
Return "this", "other" or "conflict", depending whether a value wins.
578
key_base = key(base_tree, file_id)
579
key_other = key(other_tree, file_id)
580
#if base == other, either they all agree, or only THIS has changed.
581
if key_base == key_other:
583
key_this = key(this_tree, file_id)
584
if key_this not in (key_base, key_other):
586
# "Ambiguous clean merge"
587
elif key_this == key_other:
590
assert key_this == key_base
593
def merge_names(self, file_id):
595
if file_id in tree.inventory:
596
return tree.inventory[file_id]
599
this_entry = get_entry(self.this_tree)
600
other_entry = get_entry(self.other_tree)
601
base_entry = get_entry(self.base_tree)
602
entries = (base_entry, other_entry, this_entry)
605
for entry in entries:
610
names.append(entry.name)
611
parents.append(entry.parent_id)
612
return self._merge_names(file_id, parents, names)
614
def _merge_names(self, file_id, parents, names):
615
"""Perform a merge on file_id names and parents"""
616
base_name, other_name, this_name = names
617
base_parent, other_parent, this_parent = parents
619
name_winner = self._three_way(*names)
621
parent_id_winner = self._three_way(*parents)
622
if this_name is None:
623
if name_winner == "this":
624
name_winner = "other"
625
if parent_id_winner == "this":
626
parent_id_winner = "other"
627
if name_winner == "this" and parent_id_winner == "this":
629
if name_winner == "conflict":
630
trans_id = self.tt.trans_id_file_id(file_id)
631
self._raw_conflicts.append(('name conflict', trans_id,
632
this_name, other_name))
633
if parent_id_winner == "conflict":
634
trans_id = self.tt.trans_id_file_id(file_id)
635
self._raw_conflicts.append(('parent conflict', trans_id,
636
this_parent, other_parent))
637
if other_name is None:
638
# it doesn't matter whether the result was 'other' or
639
# 'conflict'-- if there's no 'other', we leave it alone.
641
# if we get here, name_winner and parent_winner are set to safe values.
642
trans_id = self.tt.trans_id_file_id(file_id)
643
parent_id = parents[self.winner_idx[parent_id_winner]]
644
if parent_id is not None:
645
parent_trans_id = self.tt.trans_id_file_id(parent_id)
646
self.tt.adjust_path(names[self.winner_idx[name_winner]],
647
parent_trans_id, trans_id)
649
def merge_contents(self, file_id):
650
"""Performa a merge on file_id contents."""
651
def contents_pair(tree):
652
if file_id not in tree:
654
kind = tree.kind(file_id)
656
contents = tree.get_file_sha1(file_id)
657
elif kind == "symlink":
658
contents = tree.get_symlink_target(file_id)
661
return kind, contents
663
def contents_conflict():
664
trans_id = self.tt.trans_id_file_id(file_id)
665
name = self.tt.final_name(trans_id)
666
parent_id = self.tt.final_parent(trans_id)
667
if file_id in self.this_tree.inventory:
668
self.tt.unversion_file(trans_id)
669
if file_id in self.this_tree:
670
self.tt.delete_contents(trans_id)
671
file_group = self._dump_conflicts(name, parent_id, file_id,
673
self._raw_conflicts.append(('contents conflict', file_group))
675
# See SPOT run. run, SPOT, run.
676
# So we're not QUITE repeating ourselves; we do tricky things with
678
base_pair = contents_pair(self.base_tree)
679
other_pair = contents_pair(self.other_tree)
680
if base_pair == other_pair:
681
# OTHER introduced no changes
683
this_pair = contents_pair(self.this_tree)
684
if this_pair == other_pair:
685
# THIS and OTHER introduced the same changes
688
trans_id = self.tt.trans_id_file_id(file_id)
689
if this_pair == base_pair:
690
# only OTHER introduced changes
691
if file_id in self.this_tree:
692
# Remove any existing contents
693
self.tt.delete_contents(trans_id)
694
if file_id in self.other_tree:
695
# OTHER changed the file
696
create_by_entry(self.tt,
697
self.other_tree.inventory[file_id],
698
self.other_tree, trans_id)
699
if file_id not in self.this_tree.inventory:
700
self.tt.version_file(file_id, trans_id)
702
elif file_id in self.this_tree.inventory:
703
# OTHER deleted the file
704
self.tt.unversion_file(trans_id)
706
#BOTH THIS and OTHER introduced changes; scalar conflict
707
elif this_pair[0] == "file" and other_pair[0] == "file":
708
# THIS and OTHER are both files, so text merge. Either
709
# BASE is a file, or both converted to files, so at least we
710
# have agreement that output should be a file.
712
self.text_merge(file_id, trans_id)
714
return contents_conflict()
715
if file_id not in self.this_tree.inventory:
716
self.tt.version_file(file_id, trans_id)
718
self.tt.tree_kind(trans_id)
719
self.tt.delete_contents(trans_id)
724
# Scalar conflict, can't text merge. Dump conflicts
725
return contents_conflict()
727
def get_lines(self, tree, file_id):
728
"""Return the lines in a file, or an empty list."""
730
return tree.get_file(file_id).readlines()
734
def text_merge(self, file_id, trans_id):
735
"""Perform a three-way text merge on a file_id"""
736
# it's possible that we got here with base as a different type.
737
# if so, we just want two-way text conflicts.
738
if file_id in self.base_tree and \
739
self.base_tree.kind(file_id) == "file":
740
base_lines = self.get_lines(self.base_tree, file_id)
743
other_lines = self.get_lines(self.other_tree, file_id)
744
this_lines = self.get_lines(self.this_tree, file_id)
745
m3 = Merge3(base_lines, this_lines, other_lines)
746
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
747
if self.show_base is True:
748
base_marker = '|' * 7
752
def iter_merge3(retval):
753
retval["text_conflicts"] = False
754
for line in m3.merge_lines(name_a = "TREE",
755
name_b = "MERGE-SOURCE",
756
name_base = "BASE-REVISION",
757
start_marker=start_marker,
758
base_marker=base_marker,
759
reprocess=self.reprocess):
760
if line.startswith(start_marker):
761
retval["text_conflicts"] = True
762
yield line.replace(start_marker, '<' * 7)
766
merge3_iterator = iter_merge3(retval)
767
self.tt.create_file(merge3_iterator, trans_id)
768
if retval["text_conflicts"] is True:
769
self._raw_conflicts.append(('text conflict', trans_id))
770
name = self.tt.final_name(trans_id)
771
parent_id = self.tt.final_parent(trans_id)
772
file_group = self._dump_conflicts(name, parent_id, file_id,
773
this_lines, base_lines,
775
file_group.append(trans_id)
777
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
778
base_lines=None, other_lines=None, set_version=False,
780
"""Emit conflict files.
781
If this_lines, base_lines, or other_lines are omitted, they will be
782
determined automatically. If set_version is true, the .OTHER, .THIS
783
or .BASE (in that order) will be created as versioned files.
785
data = [('OTHER', self.other_tree, other_lines),
786
('THIS', self.this_tree, this_lines)]
788
data.append(('BASE', self.base_tree, base_lines))
791
for suffix, tree, lines in data:
793
trans_id = self._conflict_file(name, parent_id, tree, file_id,
795
file_group.append(trans_id)
796
if set_version and not versioned:
797
self.tt.version_file(file_id, trans_id)
801
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
803
"""Emit a single conflict file."""
804
name = name + '.' + suffix
805
trans_id = self.tt.create_path(name, parent_id)
806
entry = tree.inventory[file_id]
807
create_by_entry(self.tt, entry, tree, trans_id, lines)
810
def merge_executable(self, file_id, file_status):
811
"""Perform a merge on the execute bit."""
812
executable = [self.executable(t, file_id) for t in (self.base_tree,
813
self.other_tree, self.this_tree)]
814
self._merge_executable(file_id, executable, file_status)
816
def _merge_executable(self, file_id, executable, file_status):
817
"""Perform a merge on the execute bit."""
818
base_executable, other_executable, this_executable = executable
819
if file_status == "deleted":
821
trans_id = self.tt.trans_id_file_id(file_id)
823
if self.tt.final_kind(trans_id) != "file":
827
winner = self._three_way(*executable)
828
if winner == "conflict":
829
# There must be a None in here, if we have a conflict, but we
830
# need executability since file status was not deleted.
831
if self.executable(self.other_tree, file_id) is None:
836
if file_status == "modified":
837
executability = this_executable
838
if executability is not None:
839
trans_id = self.tt.trans_id_file_id(file_id)
840
self.tt.set_executability(executability, trans_id)
842
assert winner == "other"
843
if file_id in self.other_tree:
844
executability = other_executable
845
elif file_id in self.this_tree:
846
executability = this_executable
847
elif file_id in self.base_tree:
848
executability = base_executable
849
if executability is not None:
850
trans_id = self.tt.trans_id_file_id(file_id)
851
self.tt.set_executability(executability, trans_id)
853
def cook_conflicts(self, fs_conflicts):
854
"""Convert all conflicts into a form that doesn't depend on trans_id"""
855
from conflicts import Conflict
857
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
858
fp = FinalPaths(self.tt)
859
for conflict in self._raw_conflicts:
860
conflict_type = conflict[0]
861
if conflict_type in ('name conflict', 'parent conflict'):
862
trans_id = conflict[1]
863
conflict_args = conflict[2:]
864
if trans_id not in name_conflicts:
865
name_conflicts[trans_id] = {}
866
unique_add(name_conflicts[trans_id], conflict_type,
868
if conflict_type == 'contents conflict':
869
for trans_id in conflict[1]:
870
file_id = self.tt.final_file_id(trans_id)
871
if file_id is not None:
873
path = fp.get_path(trans_id)
874
for suffix in ('.BASE', '.THIS', '.OTHER'):
875
if path.endswith(suffix):
876
path = path[:-len(suffix)]
878
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
879
self.cooked_conflicts.append(c)
880
if conflict_type == 'text conflict':
881
trans_id = conflict[1]
882
path = fp.get_path(trans_id)
883
file_id = self.tt.final_file_id(trans_id)
884
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
885
self.cooked_conflicts.append(c)
887
for trans_id, conflicts in name_conflicts.iteritems():
889
this_parent, other_parent = conflicts['parent conflict']
890
assert this_parent != other_parent
892
this_parent = other_parent = \
893
self.tt.final_file_id(self.tt.final_parent(trans_id))
895
this_name, other_name = conflicts['name conflict']
896
assert this_name != other_name
898
this_name = other_name = self.tt.final_name(trans_id)
899
other_path = fp.get_path(trans_id)
900
if this_parent is not None and this_name is not None:
902
fp.get_path(self.tt.trans_id_file_id(this_parent))
903
this_path = pathjoin(this_parent_path, this_name)
905
this_path = "<deleted>"
906
file_id = self.tt.final_file_id(trans_id)
907
c = Conflict.factory('path conflict', path=this_path,
908
conflict_path=other_path, file_id=file_id)
909
self.cooked_conflicts.append(c)
910
self.cooked_conflicts.sort(key=Conflict.sort_key)
913
class WeaveMerger(Merge3Merger):
914
"""Three-way tree merger, text weave merger."""
915
supports_reprocess = True
916
supports_show_base = False
918
def __init__(self, working_tree, this_tree, base_tree, other_tree,
919
interesting_ids=None, pb=DummyProgress(), pp=None,
920
reprocess=False, change_reporter=None,
921
interesting_files=None):
922
super(WeaveMerger, self).__init__(working_tree, this_tree,
923
base_tree, other_tree,
924
interesting_ids=interesting_ids,
925
pb=pb, pp=pp, reprocess=reprocess,
926
change_reporter=change_reporter)
928
def _merged_lines(self, file_id):
929
"""Generate the merged lines.
930
There is no distinction between lines that are meant to contain <<<<<<<
933
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
934
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
935
'>>>>>>> MERGE-SOURCE\n')
936
return textmerge.merge_lines(self.reprocess)
938
def text_merge(self, file_id, trans_id):
939
"""Perform a (weave) text merge for a given file and file-id.
940
If conflicts are encountered, .THIS and .OTHER files will be emitted,
941
and a conflict will be noted.
943
lines, conflicts = self._merged_lines(file_id)
945
# Note we're checking whether the OUTPUT is binary in this case,
946
# because we don't want to get into weave merge guts.
947
check_text_lines(lines)
948
self.tt.create_file(lines, trans_id)
950
self._raw_conflicts.append(('text conflict', trans_id))
951
name = self.tt.final_name(trans_id)
952
parent_id = self.tt.final_parent(trans_id)
953
file_group = self._dump_conflicts(name, parent_id, file_id,
955
file_group.append(trans_id)
958
class Diff3Merger(Merge3Merger):
959
"""Three-way merger using external diff3 for text merging"""
961
def dump_file(self, temp_dir, name, tree, file_id):
962
out_path = pathjoin(temp_dir, name)
963
out_file = open(out_path, "wb")
965
in_file = tree.get_file(file_id)
972
def text_merge(self, file_id, trans_id):
973
"""Perform a diff3 merge using a specified file-id and trans-id.
974
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
975
will be dumped, and a will be conflict noted.
978
temp_dir = osutils.mkdtemp(prefix="bzr-")
980
new_file = pathjoin(temp_dir, "new")
981
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
982
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
983
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
984
status = bzrlib.patch.diff3(new_file, this, base, other)
985
if status not in (0, 1):
986
raise BzrError("Unhandled diff3 exit code")
987
f = open(new_file, 'rb')
989
self.tt.create_file(f, trans_id)
993
name = self.tt.final_name(trans_id)
994
parent_id = self.tt.final_parent(trans_id)
995
self._dump_conflicts(name, parent_id, file_id)
996
self._raw_conflicts.append(('text conflict', trans_id))
998
osutils.rmtree(temp_dir)
1001
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1003
merge_type=Merge3Merger,
1004
interesting_ids=None,
1008
interesting_files=None,
1011
change_reporter=None):
1012
"""Primary interface for merging.
1014
typical use is probably
1015
'merge_inner(branch, branch.get_revision_tree(other_revision),
1016
branch.get_revision_tree(base_revision))'
1018
if this_tree is None:
1019
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1020
"parameter as of bzrlib version 0.8.")
1021
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1022
pb=pb, change_reporter=change_reporter)
1023
merger.backup_files = backup_files
1024
merger.merge_type = merge_type
1025
merger.interesting_ids = interesting_ids
1026
merger.ignore_zero = ignore_zero
1027
if interesting_files:
1028
assert not interesting_ids, ('Only supply interesting_ids'
1029
' or interesting_files')
1030
merger.interesting_files = interesting_files
1031
merger.show_base = show_base
1032
merger.reprocess = reprocess
1033
merger.other_rev_id = other_rev_id
1034
merger.other_basis = other_rev_id
1035
return merger.do_merge()
1037
def get_merge_type_registry():
1038
"""Merge type registry is in bzrlib.option to avoid circular imports.
1040
This method provides a sanctioned way to retrieve it.
1042
from bzrlib import option
1043
return option._merge_type_registry
1046
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1047
def status_a(revision, text):
1048
if revision in ancestors_b:
1049
return 'killed-b', text
1051
return 'new-a', text
1053
def status_b(revision, text):
1054
if revision in ancestors_a:
1055
return 'killed-a', text
1057
return 'new-b', text
1059
plain_a = [t for (a, t) in annotated_a]
1060
plain_b = [t for (a, t) in annotated_b]
1061
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1062
blocks = matcher.get_matching_blocks()
1065
for ai, bi, l in blocks:
1066
# process all mismatched sections
1067
# (last mismatched section is handled because blocks always
1068
# includes a 0-length last block)
1069
for revision, text in annotated_a[a_cur:ai]:
1070
yield status_a(revision, text)
1071
for revision, text in annotated_b[b_cur:bi]:
1072
yield status_b(revision, text)
1074
# and now the matched section
1077
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1078
assert text_a == text_b
1079
yield "unchanged", text_a