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
25
revision as _mod_revision,
27
from bzrlib.branch import Branch
28
from bzrlib.conflicts import ConflictList, Conflict
29
from bzrlib.errors import (BzrCommandError,
39
WorkingTreeNotRevision,
42
from bzrlib.merge3 import Merge3
43
from bzrlib.osutils import rename, pathjoin
44
from progress import DummyProgress, ProgressPhase
45
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
46
from bzrlib.textfile import check_text_lines
47
from bzrlib.trace import mutter, warning, note
48
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
49
FinalPaths, create_by_entry, unique_add,
51
from bzrlib.versionedfile import WeaveMerge
54
# TODO: Report back as changes are merged in
56
def _get_tree(treespec, local_branch=None):
57
from bzrlib import workingtree
58
location, revno = treespec
60
tree = workingtree.WorkingTree.open_containing(location)[0]
61
return tree.branch, tree
62
branch = Branch.open_containing(location)[0]
64
revision_id = branch.last_revision()
66
revision_id = branch.get_rev_id(revno)
67
if revision_id is None:
68
revision_id = NULL_REVISION
69
return branch, _get_revid_tree(branch, revision_id, local_branch)
72
def _get_revid_tree(branch, revision_id, local_branch):
73
if revision_id is None:
74
base_tree = branch.bzrdir.open_workingtree()
76
if local_branch is not None:
77
if local_branch.base != branch.base:
78
local_branch.fetch(branch, revision_id)
79
base_tree = local_branch.repository.revision_tree(revision_id)
81
base_tree = branch.repository.revision_tree(revision_id)
85
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
86
if revision_id is None:
88
if local_branch is not None:
89
if local_branch.base != tree.branch.base:
90
local_branch.fetch(tree.branch, revision_id)
91
return local_branch.repository.revision_tree(revision_id)
92
return tree.branch.repository.revision_tree(revision_id)
95
def transform_tree(from_tree, to_tree, interesting_ids=None):
96
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
97
interesting_ids=interesting_ids, this_tree=from_tree)
100
class Merger(object):
101
def __init__(self, this_branch, other_tree=None, base_tree=None,
102
this_tree=None, pb=DummyProgress(), change_reporter=None,
104
object.__init__(self)
105
assert this_tree is not None, "this_tree is required"
106
self.this_branch = this_branch
107
self.this_basis = this_branch.last_revision()
108
self.this_rev_id = None
109
self.this_tree = this_tree
110
self.this_revision_tree = None
111
self.this_basis_tree = None
112
self.other_tree = other_tree
113
self.other_branch = None
114
self.base_tree = base_tree
115
self.ignore_zero = False
116
self.backup_files = False
117
self.interesting_ids = None
118
self.show_base = False
119
self.reprocess = False
122
self.recurse = recurse
123
self.change_reporter = change_reporter
125
def revision_tree(self, revision_id):
126
return self.this_branch.repository.revision_tree(revision_id)
128
def ensure_revision_trees(self):
129
if self.this_revision_tree is None:
130
self.this_basis_tree = self.this_branch.repository.revision_tree(
132
if self.this_basis == self.this_rev_id:
133
self.this_revision_tree = self.this_basis_tree
135
if self.other_rev_id is None:
136
other_basis_tree = self.revision_tree(self.other_basis)
137
changes = other_basis_tree.changes_from(self.other_tree)
138
if changes.has_changed():
139
raise WorkingTreeNotRevision(self.this_tree)
140
other_rev_id = self.other_basis
141
self.other_tree = other_basis_tree
143
def file_revisions(self, file_id):
144
self.ensure_revision_trees()
145
def get_id(tree, file_id):
146
revision_id = tree.inventory[file_id].revision
147
assert revision_id is not None
149
if self.this_rev_id is None:
150
if self.this_basis_tree.get_file_sha1(file_id) != \
151
self.this_tree.get_file_sha1(file_id):
152
raise WorkingTreeNotRevision(self.this_tree)
154
trees = (self.this_basis_tree, self.other_tree)
155
return [get_id(tree, file_id) for tree in trees]
157
def check_basis(self, check_clean, require_commits=True):
158
if self.this_basis is None and require_commits is True:
159
raise BzrCommandError("This branch has no commits."
160
" (perhaps you would prefer 'bzr pull')")
163
if self.this_basis != self.this_rev_id:
164
raise BzrCommandError("Working tree has uncommitted changes.")
166
def compare_basis(self):
167
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
168
if not changes.has_changed():
169
self.this_rev_id = self.this_basis
171
def set_interesting_files(self, file_list):
173
self._set_interesting_files(file_list)
174
except NotVersionedError, e:
175
raise BzrCommandError("%s is not a source file in any"
178
def _set_interesting_files(self, file_list):
179
"""Set the list of interesting ids from a list of files."""
180
if file_list is None:
181
self.interesting_ids = None
184
interesting_ids = set()
185
for path in file_list:
187
# TODO: jam 20070226 The trees are not locked at this time,
188
# wouldn't it make merge faster if it locks everything in the
189
# beginning? It locks at do_merge time, but this happens
191
for tree in (self.this_tree, self.base_tree, self.other_tree):
192
file_id = tree.path2id(path)
193
if file_id is not None:
194
interesting_ids.add(file_id)
197
raise NotVersionedError(path=path)
198
self.interesting_ids = interesting_ids
200
def set_pending(self):
201
if not self.base_is_ancestor:
203
if self.other_rev_id is None:
205
ancestry = set(self.this_branch.repository.get_ancestry(
206
self.this_basis, topo_sorted=False))
207
if self.other_rev_id in ancestry:
209
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
211
def set_other(self, other_revision):
212
"""Set the revision and tree to merge from.
214
This sets the other_tree, other_rev_id, other_basis attributes.
216
:param other_revision: The [path, revision] list to merge from.
218
self.other_branch, self.other_tree = _get_tree(other_revision,
220
if other_revision[1] == -1:
221
self.other_rev_id = self.other_branch.last_revision()
222
if _mod_revision.is_null(self.other_rev_id):
223
raise NoCommits(self.other_branch)
224
self.other_basis = self.other_rev_id
225
elif other_revision[1] is not None:
226
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
227
self.other_basis = self.other_rev_id
229
self.other_rev_id = None
230
self.other_basis = self.other_branch.last_revision()
231
if self.other_basis is None:
232
raise NoCommits(self.other_branch)
233
if self.other_branch.base != self.this_branch.base:
234
self.this_branch.fetch(self.other_branch,
235
last_revision=self.other_basis)
237
def set_other_revision(self, revision_id, other_branch):
238
"""Set 'other' based on a branch and revision id
240
:param revision_id: The revision to use for a tree
241
:param other_branch: The branch containing this tree
243
self.other_rev_id = revision_id
244
self.other_branch = other_branch
245
self.this_branch.fetch(other_branch, self.other_rev_id)
246
self.other_tree = self.revision_tree(revision_id)
247
self.other_basis = revision_id
250
self.set_base([None, None])
252
def set_base(self, base_revision):
253
"""Set the base revision to use for the merge.
255
:param base_revision: A 2-list containing a path and revision number.
257
mutter("doing merge() with no base_revision specified")
258
if base_revision == [None, None]:
260
pb = ui.ui_factory.nested_progress_bar()
262
this_repo = self.this_branch.repository
263
graph = this_repo.get_graph()
264
revisions = [ensure_null(self.this_basis),
265
ensure_null(self.other_basis)]
266
if NULL_REVISION in revisions:
267
self.base_rev_id = NULL_REVISION
269
self.base_rev_id = graph.find_unique_lca(*revisions)
270
if self.base_rev_id == NULL_REVISION:
271
raise UnrelatedBranches()
274
except NoCommonAncestor:
275
raise UnrelatedBranches()
276
self.base_tree = _get_revid_tree_from_tree(self.this_tree,
279
self.base_is_ancestor = True
281
base_branch, self.base_tree = _get_tree(base_revision)
282
if base_revision[1] == -1:
283
self.base_rev_id = base_branch.last_revision()
284
elif base_revision[1] is None:
285
self.base_rev_id = _mod_revision.NULL_REVISION
287
self.base_rev_id = _mod_revision.ensure_null(
288
base_branch.get_rev_id(base_revision[1]))
289
if self.this_branch.base != base_branch.base:
290
self.this_branch.fetch(base_branch)
291
self.base_is_ancestor = is_ancestor(self.this_basis,
296
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
297
'other_tree': self.other_tree,
298
'interesting_ids': self.interesting_ids,
300
if self.merge_type.requires_base:
301
kwargs['base_tree'] = self.base_tree
302
if self.merge_type.supports_reprocess:
303
kwargs['reprocess'] = self.reprocess
305
raise BzrError("Conflict reduction is not supported for merge"
306
" type %s." % self.merge_type)
307
if self.merge_type.supports_show_base:
308
kwargs['show_base'] = self.show_base
310
raise BzrError("Showing base is not supported for this"
311
" merge type. %s" % self.merge_type)
312
self.this_tree.lock_tree_write()
313
if self.base_tree is not None:
314
self.base_tree.lock_read()
315
if self.other_tree is not None:
316
self.other_tree.lock_read()
318
merge = self.merge_type(pb=self._pb,
319
change_reporter=self.change_reporter,
321
if self.recurse == 'down':
322
for path, file_id in self.this_tree.iter_references():
323
sub_tree = self.this_tree.get_nested_tree(file_id, path)
324
other_revision = self.other_tree.get_reference_revision(
326
if other_revision == sub_tree.last_revision():
328
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
329
sub_merge.merge_type = self.merge_type
330
relpath = self.this_tree.relpath(path)
331
other_branch = self.other_branch.reference_parent(file_id, relpath)
332
sub_merge.set_other_revision(other_revision, other_branch)
333
base_revision = self.base_tree.get_reference_revision(file_id)
334
sub_merge.base_tree = \
335
sub_tree.branch.repository.revision_tree(base_revision)
339
if self.other_tree is not None:
340
self.other_tree.unlock()
341
if self.base_tree is not None:
342
self.base_tree.unlock()
343
self.this_tree.unlock()
344
if len(merge.cooked_conflicts) == 0:
345
if not self.ignore_zero:
346
note("All changes applied successfully.")
348
note("%d conflicts encountered." % len(merge.cooked_conflicts))
350
return len(merge.cooked_conflicts)
352
def regen_inventory(self, new_entries):
353
old_entries = self.this_tree.read_working_inventory()
357
for path, file_id in new_entries:
360
new_entries_map[file_id] = path
362
def id2path(file_id):
363
path = new_entries_map.get(file_id)
366
entry = old_entries[file_id]
367
if entry.parent_id is None:
369
return pathjoin(id2path(entry.parent_id), entry.name)
371
for file_id in old_entries:
372
entry = old_entries[file_id]
373
path = id2path(file_id)
374
if file_id in self.base_tree.inventory:
375
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
377
executable = getattr(entry, 'executable', False)
378
new_inventory[file_id] = (path, file_id, entry.parent_id,
379
entry.kind, executable)
381
by_path[path] = file_id
386
for path, file_id in new_entries:
388
del new_inventory[file_id]
391
new_path_list.append((path, file_id))
392
if file_id not in old_entries:
394
# Ensure no file is added before its parent
396
for path, file_id in new_path_list:
400
parent = by_path[os.path.dirname(path)]
401
abspath = pathjoin(self.this_tree.basedir, path)
402
kind = osutils.file_kind(abspath)
403
if file_id in self.base_tree.inventory:
404
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
407
new_inventory[file_id] = (path, file_id, parent, kind, executable)
408
by_path[path] = file_id
410
# Get a list in insertion order
411
new_inventory_list = new_inventory.values()
412
mutter ("""Inventory regeneration:
413
old length: %i insertions: %i deletions: %i new_length: %i"""\
414
% (len(old_entries), insertions, deletions,
415
len(new_inventory_list)))
416
assert len(new_inventory_list) == len(old_entries) + insertions\
418
new_inventory_list.sort()
419
return new_inventory_list
422
class Merge3Merger(object):
423
"""Three-way merger that uses the merge3 text merger"""
425
supports_reprocess = True
426
supports_show_base = True
427
history_based = False
429
def __init__(self, working_tree, this_tree, base_tree, other_tree,
430
interesting_ids=None, reprocess=False, show_base=False,
431
pb=DummyProgress(), pp=None, change_reporter=None):
432
"""Initialize the merger object and perform the merge."""
433
object.__init__(self)
434
self.this_tree = working_tree
435
self.this_tree.lock_tree_write()
436
self.base_tree = base_tree
437
self.base_tree.lock_read()
438
self.other_tree = other_tree
439
self.other_tree.lock_read()
440
self._raw_conflicts = []
441
self.cooked_conflicts = []
442
self.reprocess = reprocess
443
self.show_base = show_base
446
self.change_reporter = change_reporter
448
self.pp = ProgressPhase("Merge phase", 3, self.pb)
450
if interesting_ids is not None:
451
all_ids = interesting_ids
453
all_ids = set(base_tree)
454
all_ids.update(other_tree)
455
self.tt = TreeTransform(working_tree, self.pb)
458
child_pb = ui.ui_factory.nested_progress_bar()
460
for num, file_id in enumerate(all_ids):
461
child_pb.update('Preparing file merge', num, len(all_ids))
462
self.merge_names(file_id)
463
file_status = self.merge_contents(file_id)
464
self.merge_executable(file_id, file_status)
469
child_pb = ui.ui_factory.nested_progress_bar()
471
fs_conflicts = resolve_conflicts(self.tt, child_pb)
474
if change_reporter is not None:
475
from bzrlib import delta
476
delta.report_changes(self.tt._iter_changes(), change_reporter)
477
self.cook_conflicts(fs_conflicts)
478
for conflict in self.cooked_conflicts:
481
results = self.tt.apply()
482
self.write_modified(results)
484
working_tree.add_conflicts(self.cooked_conflicts)
485
except UnsupportedOperation:
489
self.other_tree.unlock()
490
self.base_tree.unlock()
491
self.this_tree.unlock()
496
self.tt.final_kind(self.tt.root)
498
self.tt.cancel_deletion(self.tt.root)
499
if self.tt.final_file_id(self.tt.root) is None:
500
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
502
if self.other_tree.inventory.root is None:
504
other_root_file_id = self.other_tree.inventory.root.file_id
505
other_root = self.tt.trans_id_file_id(other_root_file_id)
506
if other_root == self.tt.root:
509
self.tt.final_kind(other_root)
512
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
513
self.tt.cancel_creation(other_root)
514
self.tt.cancel_versioning(other_root)
516
def reparent_children(self, ie, target):
517
for thing, child in ie.children.iteritems():
518
trans_id = self.tt.trans_id_file_id(child.file_id)
519
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
521
def write_modified(self, results):
523
for path in results.modified_paths:
524
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
527
hash = self.this_tree.get_file_sha1(file_id)
530
modified_hashes[file_id] = hash
531
self.this_tree.set_merge_modified(modified_hashes)
534
def parent(entry, file_id):
535
"""Determine the parent for a file_id (used as a key method)"""
538
return entry.parent_id
541
def name(entry, file_id):
542
"""Determine the name for a file_id (used as a key method)"""
548
def contents_sha1(tree, file_id):
549
"""Determine the sha1 of the file contents (used as a key method)."""
550
if file_id not in tree:
552
return tree.get_file_sha1(file_id)
555
def executable(tree, file_id):
556
"""Determine the executability of a file-id (used as a key method)."""
557
if file_id not in tree:
559
if tree.kind(file_id) != "file":
561
return tree.is_executable(file_id)
564
def kind(tree, file_id):
565
"""Determine the kind of a file-id (used as a key method)."""
566
if file_id not in tree:
568
return tree.kind(file_id)
571
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
572
"""Do a three-way test on a scalar.
573
Return "this", "other" or "conflict", depending whether a value wins.
575
key_base = key(base_tree, file_id)
576
key_other = key(other_tree, file_id)
577
#if base == other, either they all agree, or only THIS has changed.
578
if key_base == key_other:
580
key_this = key(this_tree, file_id)
581
if key_this not in (key_base, key_other):
583
# "Ambiguous clean merge"
584
elif key_this == key_other:
587
assert key_this == key_base
590
def merge_names(self, file_id):
591
"""Perform a merge on file_id names and parents"""
593
if file_id in tree.inventory:
594
return tree.inventory[file_id]
597
this_entry = get_entry(self.this_tree)
598
other_entry = get_entry(self.other_tree)
599
base_entry = get_entry(self.base_tree)
600
name_winner = self.scalar_three_way(this_entry, base_entry,
601
other_entry, file_id, self.name)
602
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
603
other_entry, file_id,
605
if this_entry is None:
606
if name_winner == "this":
607
name_winner = "other"
608
if parent_id_winner == "this":
609
parent_id_winner = "other"
610
if name_winner == "this" and parent_id_winner == "this":
612
if name_winner == "conflict":
613
trans_id = self.tt.trans_id_file_id(file_id)
614
self._raw_conflicts.append(('name conflict', trans_id,
615
self.name(this_entry, file_id),
616
self.name(other_entry, file_id)))
617
if parent_id_winner == "conflict":
618
trans_id = self.tt.trans_id_file_id(file_id)
619
self._raw_conflicts.append(('parent conflict', trans_id,
620
self.parent(this_entry, file_id),
621
self.parent(other_entry, file_id)))
622
if other_entry is None:
623
# it doesn't matter whether the result was 'other' or
624
# 'conflict'-- if there's no 'other', we leave it alone.
626
# if we get here, name_winner and parent_winner are set to safe values.
627
winner_entry = {"this": this_entry, "other": other_entry,
628
"conflict": other_entry}
629
trans_id = self.tt.trans_id_file_id(file_id)
630
parent_id = winner_entry[parent_id_winner].parent_id
631
if parent_id is not None:
632
parent_trans_id = self.tt.trans_id_file_id(parent_id)
633
self.tt.adjust_path(winner_entry[name_winner].name,
634
parent_trans_id, trans_id)
636
def merge_contents(self, file_id):
637
"""Performa a merge on file_id contents."""
638
def contents_pair(tree):
639
if file_id not in tree:
641
kind = tree.kind(file_id)
643
contents = tree.get_file_sha1(file_id)
644
elif kind == "symlink":
645
contents = tree.get_symlink_target(file_id)
648
return kind, contents
650
def contents_conflict():
651
trans_id = self.tt.trans_id_file_id(file_id)
652
name = self.tt.final_name(trans_id)
653
parent_id = self.tt.final_parent(trans_id)
654
if file_id in self.this_tree.inventory:
655
self.tt.unversion_file(trans_id)
656
if file_id in self.this_tree:
657
self.tt.delete_contents(trans_id)
658
file_group = self._dump_conflicts(name, parent_id, file_id,
660
self._raw_conflicts.append(('contents conflict', file_group))
662
# See SPOT run. run, SPOT, run.
663
# So we're not QUITE repeating ourselves; we do tricky things with
665
base_pair = contents_pair(self.base_tree)
666
other_pair = contents_pair(self.other_tree)
667
if base_pair == other_pair:
668
# OTHER introduced no changes
670
this_pair = contents_pair(self.this_tree)
671
if this_pair == other_pair:
672
# THIS and OTHER introduced the same changes
675
trans_id = self.tt.trans_id_file_id(file_id)
676
if this_pair == base_pair:
677
# only OTHER introduced changes
678
if file_id in self.this_tree:
679
# Remove any existing contents
680
self.tt.delete_contents(trans_id)
681
if file_id in self.other_tree:
682
# OTHER changed the file
683
create_by_entry(self.tt,
684
self.other_tree.inventory[file_id],
685
self.other_tree, trans_id)
686
if file_id not in self.this_tree.inventory:
687
self.tt.version_file(file_id, trans_id)
689
elif file_id in self.this_tree.inventory:
690
# OTHER deleted the file
691
self.tt.unversion_file(trans_id)
693
#BOTH THIS and OTHER introduced changes; scalar conflict
694
elif this_pair[0] == "file" and other_pair[0] == "file":
695
# THIS and OTHER are both files, so text merge. Either
696
# BASE is a file, or both converted to files, so at least we
697
# have agreement that output should be a file.
699
self.text_merge(file_id, trans_id)
701
return contents_conflict()
702
if file_id not in self.this_tree.inventory:
703
self.tt.version_file(file_id, trans_id)
705
self.tt.tree_kind(trans_id)
706
self.tt.delete_contents(trans_id)
711
# Scalar conflict, can't text merge. Dump conflicts
712
return contents_conflict()
714
def get_lines(self, tree, file_id):
715
"""Return the lines in a file, or an empty list."""
717
return tree.get_file(file_id).readlines()
721
def text_merge(self, file_id, trans_id):
722
"""Perform a three-way text merge on a file_id"""
723
# it's possible that we got here with base as a different type.
724
# if so, we just want two-way text conflicts.
725
if file_id in self.base_tree and \
726
self.base_tree.kind(file_id) == "file":
727
base_lines = self.get_lines(self.base_tree, file_id)
730
other_lines = self.get_lines(self.other_tree, file_id)
731
this_lines = self.get_lines(self.this_tree, file_id)
732
m3 = Merge3(base_lines, this_lines, other_lines)
733
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
734
if self.show_base is True:
735
base_marker = '|' * 7
739
def iter_merge3(retval):
740
retval["text_conflicts"] = False
741
for line in m3.merge_lines(name_a = "TREE",
742
name_b = "MERGE-SOURCE",
743
name_base = "BASE-REVISION",
744
start_marker=start_marker,
745
base_marker=base_marker,
746
reprocess=self.reprocess):
747
if line.startswith(start_marker):
748
retval["text_conflicts"] = True
749
yield line.replace(start_marker, '<' * 7)
753
merge3_iterator = iter_merge3(retval)
754
self.tt.create_file(merge3_iterator, trans_id)
755
if retval["text_conflicts"] is True:
756
self._raw_conflicts.append(('text conflict', trans_id))
757
name = self.tt.final_name(trans_id)
758
parent_id = self.tt.final_parent(trans_id)
759
file_group = self._dump_conflicts(name, parent_id, file_id,
760
this_lines, base_lines,
762
file_group.append(trans_id)
764
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
765
base_lines=None, other_lines=None, set_version=False,
767
"""Emit conflict files.
768
If this_lines, base_lines, or other_lines are omitted, they will be
769
determined automatically. If set_version is true, the .OTHER, .THIS
770
or .BASE (in that order) will be created as versioned files.
772
data = [('OTHER', self.other_tree, other_lines),
773
('THIS', self.this_tree, this_lines)]
775
data.append(('BASE', self.base_tree, base_lines))
778
for suffix, tree, lines in data:
780
trans_id = self._conflict_file(name, parent_id, tree, file_id,
782
file_group.append(trans_id)
783
if set_version and not versioned:
784
self.tt.version_file(file_id, trans_id)
788
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
790
"""Emit a single conflict file."""
791
name = name + '.' + suffix
792
trans_id = self.tt.create_path(name, parent_id)
793
entry = tree.inventory[file_id]
794
create_by_entry(self.tt, entry, tree, trans_id, lines)
797
def merge_executable(self, file_id, file_status):
798
"""Perform a merge on the execute bit."""
799
if file_status == "deleted":
801
trans_id = self.tt.trans_id_file_id(file_id)
803
if self.tt.final_kind(trans_id) != "file":
807
winner = self.scalar_three_way(self.this_tree, self.base_tree,
808
self.other_tree, file_id,
810
if winner == "conflict":
811
# There must be a None in here, if we have a conflict, but we
812
# need executability since file status was not deleted.
813
if self.executable(self.other_tree, file_id) is None:
818
if file_status == "modified":
819
executability = self.this_tree.is_executable(file_id)
820
if executability is not None:
821
trans_id = self.tt.trans_id_file_id(file_id)
822
self.tt.set_executability(executability, trans_id)
824
assert winner == "other"
825
if file_id in self.other_tree:
826
executability = self.other_tree.is_executable(file_id)
827
elif file_id in self.this_tree:
828
executability = self.this_tree.is_executable(file_id)
829
elif file_id in self.base_tree:
830
executability = self.base_tree.is_executable(file_id)
831
if executability is not None:
832
trans_id = self.tt.trans_id_file_id(file_id)
833
self.tt.set_executability(executability, trans_id)
835
def cook_conflicts(self, fs_conflicts):
836
"""Convert all conflicts into a form that doesn't depend on trans_id"""
837
from conflicts import Conflict
839
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
840
fp = FinalPaths(self.tt)
841
for conflict in self._raw_conflicts:
842
conflict_type = conflict[0]
843
if conflict_type in ('name conflict', 'parent conflict'):
844
trans_id = conflict[1]
845
conflict_args = conflict[2:]
846
if trans_id not in name_conflicts:
847
name_conflicts[trans_id] = {}
848
unique_add(name_conflicts[trans_id], conflict_type,
850
if conflict_type == 'contents conflict':
851
for trans_id in conflict[1]:
852
file_id = self.tt.final_file_id(trans_id)
853
if file_id is not None:
855
path = fp.get_path(trans_id)
856
for suffix in ('.BASE', '.THIS', '.OTHER'):
857
if path.endswith(suffix):
858
path = path[:-len(suffix)]
860
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
861
self.cooked_conflicts.append(c)
862
if conflict_type == 'text conflict':
863
trans_id = conflict[1]
864
path = fp.get_path(trans_id)
865
file_id = self.tt.final_file_id(trans_id)
866
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
867
self.cooked_conflicts.append(c)
869
for trans_id, conflicts in name_conflicts.iteritems():
871
this_parent, other_parent = conflicts['parent conflict']
872
assert this_parent != other_parent
874
this_parent = other_parent = \
875
self.tt.final_file_id(self.tt.final_parent(trans_id))
877
this_name, other_name = conflicts['name conflict']
878
assert this_name != other_name
880
this_name = other_name = self.tt.final_name(trans_id)
881
other_path = fp.get_path(trans_id)
882
if this_parent is not None and this_name is not None:
884
fp.get_path(self.tt.trans_id_file_id(this_parent))
885
this_path = pathjoin(this_parent_path, this_name)
887
this_path = "<deleted>"
888
file_id = self.tt.final_file_id(trans_id)
889
c = Conflict.factory('path conflict', path=this_path,
890
conflict_path=other_path, file_id=file_id)
891
self.cooked_conflicts.append(c)
892
self.cooked_conflicts.sort(key=Conflict.sort_key)
895
class WeaveMerger(Merge3Merger):
896
"""Three-way tree merger, text weave merger."""
897
supports_reprocess = True
898
supports_show_base = False
900
def __init__(self, working_tree, this_tree, base_tree, other_tree,
901
interesting_ids=None, pb=DummyProgress(), pp=None,
902
reprocess=False, change_reporter=None):
903
self.this_revision_tree = self._get_revision_tree(this_tree)
904
self.other_revision_tree = self._get_revision_tree(other_tree)
905
super(WeaveMerger, self).__init__(working_tree, this_tree,
906
base_tree, other_tree,
907
interesting_ids=interesting_ids,
908
pb=pb, pp=pp, reprocess=reprocess,
909
change_reporter=change_reporter)
911
def _get_revision_tree(self, tree):
912
"""Return a revision tree related to this tree.
913
If the tree is a WorkingTree, the basis will be returned.
915
if getattr(tree, 'get_weave', False) is False:
916
# If we have a WorkingTree, try using the basis
917
return tree.branch.basis_tree()
921
def _check_file(self, file_id):
922
"""Check that the revision tree's version of the file matches."""
923
for tree, rt in ((self.this_tree, self.this_revision_tree),
924
(self.other_tree, self.other_revision_tree)):
927
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
928
raise WorkingTreeNotRevision(self.this_tree)
930
def _merged_lines(self, file_id):
931
"""Generate the merged lines.
932
There is no distinction between lines that are meant to contain <<<<<<<
935
weave = self.this_revision_tree.get_weave(file_id)
936
this_revision_id = self.this_revision_tree.inventory[file_id].revision
937
other_revision_id = \
938
self.other_revision_tree.inventory[file_id].revision
939
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
940
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
941
return wm.merge_lines(self.reprocess)
943
def text_merge(self, file_id, trans_id):
944
"""Perform a (weave) text merge for a given file and file-id.
945
If conflicts are encountered, .THIS and .OTHER files will be emitted,
946
and a conflict will be noted.
948
self._check_file(file_id)
949
lines, conflicts = self._merged_lines(file_id)
951
# Note we're checking whether the OUTPUT is binary in this case,
952
# because we don't want to get into weave merge guts.
953
check_text_lines(lines)
954
self.tt.create_file(lines, trans_id)
956
self._raw_conflicts.append(('text conflict', trans_id))
957
name = self.tt.final_name(trans_id)
958
parent_id = self.tt.final_parent(trans_id)
959
file_group = self._dump_conflicts(name, parent_id, file_id,
961
file_group.append(trans_id)
964
class Diff3Merger(Merge3Merger):
965
"""Three-way merger using external diff3 for text merging"""
967
def dump_file(self, temp_dir, name, tree, file_id):
968
out_path = pathjoin(temp_dir, name)
969
out_file = open(out_path, "wb")
971
in_file = tree.get_file(file_id)
978
def text_merge(self, file_id, trans_id):
979
"""Perform a diff3 merge using a specified file-id and trans-id.
980
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
981
will be dumped, and a will be conflict noted.
984
temp_dir = osutils.mkdtemp(prefix="bzr-")
986
new_file = pathjoin(temp_dir, "new")
987
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
988
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
989
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
990
status = bzrlib.patch.diff3(new_file, this, base, other)
991
if status not in (0, 1):
992
raise BzrError("Unhandled diff3 exit code")
993
f = open(new_file, 'rb')
995
self.tt.create_file(f, trans_id)
999
name = self.tt.final_name(trans_id)
1000
parent_id = self.tt.final_parent(trans_id)
1001
self._dump_conflicts(name, parent_id, file_id)
1002
self._raw_conflicts.append(('text conflict', trans_id))
1004
osutils.rmtree(temp_dir)
1007
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1009
merge_type=Merge3Merger,
1010
interesting_ids=None,
1014
interesting_files=None,
1017
change_reporter=None):
1018
"""Primary interface for merging.
1020
typical use is probably
1021
'merge_inner(branch, branch.get_revision_tree(other_revision),
1022
branch.get_revision_tree(base_revision))'
1024
if this_tree is None:
1025
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1026
"parameter as of bzrlib version 0.8.")
1027
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1028
pb=pb, change_reporter=change_reporter)
1029
merger.backup_files = backup_files
1030
merger.merge_type = merge_type
1031
merger.interesting_ids = interesting_ids
1032
merger.ignore_zero = ignore_zero
1033
if interesting_files:
1034
assert not interesting_ids, ('Only supply interesting_ids'
1035
' or interesting_files')
1036
merger._set_interesting_files(interesting_files)
1037
merger.show_base = show_base
1038
merger.reprocess = reprocess
1039
merger.other_rev_id = other_rev_id
1040
merger.other_basis = other_rev_id
1041
return merger.do_merge()
1043
def get_merge_type_registry():
1044
"""Merge type registry is in bzrlib.option to avoid circular imports.
1046
This method provides a sanctioned way to retrieve it.
1048
from bzrlib import option
1049
return option._merge_type_registry