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
28
revision as _mod_revision,
30
from bzrlib.branch import Branch
31
from bzrlib.conflicts import ConflictList, Conflict
32
from bzrlib.errors import (BzrCommandError,
42
WorkingTreeNotRevision,
45
from bzrlib.merge3 import Merge3
46
from bzrlib.osutils import rename, pathjoin
47
from progress import DummyProgress, ProgressPhase
48
from bzrlib.revision import (NULL_REVISION, ensure_null)
49
from bzrlib.textfile import check_text_lines
50
from bzrlib.trace import mutter, warning, note
51
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
52
conflict_pass, FinalPaths, create_by_entry,
53
unique_add, ROOT_PARENT)
54
from bzrlib.versionedfile import PlanWeaveMerge
57
# TODO: Report back as changes are merged in
60
def transform_tree(from_tree, to_tree, interesting_ids=None):
61
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
62
interesting_ids=interesting_ids, this_tree=from_tree)
66
def __init__(self, this_branch, other_tree=None, base_tree=None,
67
this_tree=None, pb=DummyProgress(), change_reporter=None,
70
assert this_tree is not None, "this_tree is required"
71
self.this_branch = this_branch
72
self.this_basis = _mod_revision.ensure_null(
73
this_branch.last_revision())
74
self.this_rev_id = None
75
self.this_tree = this_tree
76
self.this_revision_tree = None
77
self.this_basis_tree = None
78
self.other_tree = other_tree
79
self.other_branch = None
80
self.base_tree = base_tree
81
self.ignore_zero = False
82
self.backup_files = False
83
self.interesting_ids = None
84
self.interesting_files = None
85
self.show_base = False
86
self.reprocess = False
89
self.recurse = recurse
90
self.change_reporter = change_reporter
91
self._cached_trees = {}
94
def from_uncommitted(tree, other_tree, pb):
95
"""Return a Merger for uncommitted changes in other_tree.
97
:param tree: The tree to merge into
98
:param other_tree: The tree to get uncommitted changes from
99
:param pb: A progress indicator
101
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
103
merger.base_rev_id = merger.base_tree.get_revision_id()
104
merger.other_rev_id = None
108
def from_mergeable(klass, tree, mergeable, pb):
109
"""Return a Merger for a bundle or merge directive.
111
:param tree: The tree to merge changes into
112
:param mergeable: A merge directive or bundle
113
:param pb: A progress indicator
115
mergeable.install_revisions(tree.branch.repository)
116
base_revision_id, other_revision_id, verified =\
117
mergeable.get_merge_request(tree.branch.repository)
118
if (base_revision_id != _mod_revision.NULL_REVISION and
119
tree.branch.repository.get_graph().is_ancestor(
120
base_revision_id, tree.branch.last_revision())):
121
base_revision_id = None
122
merger = klass.from_revision_ids(pb, tree, other_revision_id,
124
return merger, verified
127
def from_revision_ids(pb, this, other, base=None, other_branch=None,
129
"""Return a Merger for revision-ids.
131
:param tree: The tree to merge changes into
132
:param other: The revision-id to use as OTHER
133
:param base: The revision-id to use as BASE. If not specified, will
135
:param other_branch: A branch containing the other revision-id. If
136
not supplied, this.branch is used.
137
:param base_branch: A branch containing the base revision-id. If
138
not supplied, other_branch or this.branch will be used.
139
:param pb: A progress indicator
141
merger = Merger(this.branch, this_tree=this, pb=pb)
142
if other_branch is None:
143
other_branch = this.branch
144
merger.set_other_revision(other, other_branch)
148
if base_branch is None:
149
base_branch = other_branch
150
merger.set_base_revision(base, base_branch)
153
def revision_tree(self, revision_id, branch=None):
154
if revision_id not in self._cached_trees:
156
branch = self.this_branch
158
tree = self.this_tree.revision_tree(revision_id)
159
except errors.NoSuchRevisionInTree:
160
tree = branch.repository.revision_tree(revision_id)
161
self._cached_trees[revision_id] = tree
162
return self._cached_trees[revision_id]
164
def _get_tree(self, treespec, possible_transports=None):
165
from bzrlib import workingtree
166
location, revno = treespec
168
tree = workingtree.WorkingTree.open_containing(location)[0]
169
return tree.branch, tree
170
branch = Branch.open_containing(location, possible_transports)[0]
172
revision_id = branch.last_revision()
174
revision_id = branch.get_rev_id(revno)
175
revision_id = ensure_null(revision_id)
176
return branch, self.revision_tree(revision_id, branch)
178
def ensure_revision_trees(self):
179
if self.this_revision_tree is None:
180
self.this_basis_tree = self.revision_tree(self.this_basis)
181
if self.this_basis == self.this_rev_id:
182
self.this_revision_tree = self.this_basis_tree
184
if self.other_rev_id is None:
185
other_basis_tree = self.revision_tree(self.other_basis)
186
changes = other_basis_tree.changes_from(self.other_tree)
187
if changes.has_changed():
188
raise WorkingTreeNotRevision(self.this_tree)
189
other_rev_id = self.other_basis
190
self.other_tree = other_basis_tree
192
def file_revisions(self, file_id):
193
self.ensure_revision_trees()
194
def get_id(tree, file_id):
195
revision_id = tree.inventory[file_id].revision
196
assert revision_id is not None
198
if self.this_rev_id is None:
199
if self.this_basis_tree.get_file_sha1(file_id) != \
200
self.this_tree.get_file_sha1(file_id):
201
raise WorkingTreeNotRevision(self.this_tree)
203
trees = (self.this_basis_tree, self.other_tree)
204
return [get_id(tree, file_id) for tree in trees]
206
def check_basis(self, check_clean, require_commits=True):
207
if self.this_basis is None and require_commits is True:
208
raise BzrCommandError("This branch has no commits."
209
" (perhaps you would prefer 'bzr pull')")
212
if self.this_basis != self.this_rev_id:
213
raise errors.UncommittedChanges(self.this_tree)
215
def compare_basis(self):
217
basis_tree = self.revision_tree(self.this_tree.last_revision())
218
except errors.RevisionNotPresent:
219
basis_tree = self.this_tree.basis_tree()
220
changes = self.this_tree.changes_from(basis_tree)
221
if not changes.has_changed():
222
self.this_rev_id = self.this_basis
224
def set_interesting_files(self, file_list):
225
self.interesting_files = file_list
227
def set_pending(self):
228
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
232
def _add_parent(self):
233
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
234
new_parent_trees = []
235
for revision_id in new_parents:
237
tree = self.revision_tree(revision_id)
238
except errors.RevisionNotPresent:
242
new_parent_trees.append((revision_id, tree))
244
self.this_tree.set_parent_trees(new_parent_trees,
245
allow_leftmost_as_ghost=True)
247
for _revision_id, tree in new_parent_trees:
251
def set_other(self, other_revision, possible_transports=None):
252
"""Set the revision and tree to merge from.
254
This sets the other_tree, other_rev_id, other_basis attributes.
256
:param other_revision: The [path, revision] list to merge from.
258
self.other_branch, self.other_tree = self._get_tree(other_revision,
260
if other_revision[1] == -1:
261
self.other_rev_id = _mod_revision.ensure_null(
262
self.other_branch.last_revision())
263
if _mod_revision.is_null(self.other_rev_id):
264
raise NoCommits(self.other_branch)
265
self.other_basis = self.other_rev_id
266
elif other_revision[1] is not None:
267
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
268
self.other_basis = self.other_rev_id
270
self.other_rev_id = None
271
self.other_basis = self.other_branch.last_revision()
272
if self.other_basis is None:
273
raise NoCommits(self.other_branch)
274
if self.other_rev_id is not None:
275
self._cached_trees[self.other_rev_id] = self.other_tree
276
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
278
def set_other_revision(self, revision_id, other_branch):
279
"""Set 'other' based on a branch and revision id
281
:param revision_id: The revision to use for a tree
282
:param other_branch: The branch containing this tree
284
self.other_rev_id = revision_id
285
self.other_branch = other_branch
286
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
287
self.other_tree = self.revision_tree(revision_id)
288
self.other_basis = revision_id
290
def set_base_revision(self, revision_id, branch):
291
"""Set 'base' based on a branch and revision id
293
:param revision_id: The revision to use for a tree
294
:param branch: The branch containing this tree
296
self.base_rev_id = revision_id
297
self.base_branch = branch
298
self._maybe_fetch(branch, self.this_branch, revision_id)
299
self.base_tree = self.revision_tree(revision_id)
300
graph = self.this_branch.repository.get_graph()
301
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
303
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
306
def _maybe_fetch(self, source, target, revision_id):
307
if not source.repository.has_same_location(target.repository):
308
target.fetch(source, revision_id)
311
this_repo = self.this_branch.repository
312
graph = this_repo.get_graph()
313
revisions = [ensure_null(self.this_basis),
314
ensure_null(self.other_basis)]
315
if NULL_REVISION in revisions:
316
self.base_rev_id = NULL_REVISION
318
self.base_rev_id, steps = graph.find_unique_lca(revisions[0],
319
revisions[1], count_steps=True)
320
if self.base_rev_id == NULL_REVISION:
321
raise UnrelatedBranches()
323
warning('Warning: criss-cross merge encountered. See bzr'
324
' help criss-cross.')
325
self.base_tree = self.revision_tree(self.base_rev_id)
326
self.base_is_ancestor = True
327
self.base_is_other_ancestor = True
329
def set_base(self, base_revision):
330
"""Set the base revision to use for the merge.
332
:param base_revision: A 2-list containing a path and revision number.
334
mutter("doing merge() with no base_revision specified")
335
if base_revision == [None, None]:
338
base_branch, self.base_tree = self._get_tree(base_revision)
339
if base_revision[1] == -1:
340
self.base_rev_id = base_branch.last_revision()
341
elif base_revision[1] is None:
342
self.base_rev_id = _mod_revision.NULL_REVISION
344
self.base_rev_id = _mod_revision.ensure_null(
345
base_branch.get_rev_id(base_revision[1]))
346
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
347
graph = self.this_branch.repository.get_graph()
348
self.base_is_ancestor = graph.is_ancestor(self.base_rev_id,
350
self.base_is_other_ancestor = graph.is_ancestor(self.base_rev_id,
354
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
355
'other_tree': self.other_tree,
356
'interesting_ids': self.interesting_ids,
357
'interesting_files': self.interesting_files,
359
if self.merge_type.requires_base:
360
kwargs['base_tree'] = self.base_tree
361
if self.merge_type.supports_reprocess:
362
kwargs['reprocess'] = self.reprocess
364
raise BzrError("Conflict reduction is not supported for merge"
365
" type %s." % self.merge_type)
366
if self.merge_type.supports_show_base:
367
kwargs['show_base'] = self.show_base
369
raise BzrError("Showing base is not supported for this"
370
" merge type. %s" % self.merge_type)
371
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
372
and not self.base_is_other_ancestor):
373
raise errors.CannotReverseCherrypick()
374
if self.merge_type.history_based:
375
kwargs['cherrypick'] = (not self.base_is_ancestor or
376
not self.base_is_other_ancestor)
377
self.this_tree.lock_tree_write()
378
if self.base_tree is not None:
379
self.base_tree.lock_read()
380
if self.other_tree is not None:
381
self.other_tree.lock_read()
383
merge = self.merge_type(pb=self._pb,
384
change_reporter=self.change_reporter,
386
if self.recurse == 'down':
387
for path, file_id in self.this_tree.iter_references():
388
sub_tree = self.this_tree.get_nested_tree(file_id, path)
389
other_revision = self.other_tree.get_reference_revision(
391
if other_revision == sub_tree.last_revision():
393
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
394
sub_merge.merge_type = self.merge_type
395
relpath = self.this_tree.relpath(path)
396
other_branch = self.other_branch.reference_parent(file_id, relpath)
397
sub_merge.set_other_revision(other_revision, other_branch)
398
base_revision = self.base_tree.get_reference_revision(file_id)
399
sub_merge.base_tree = \
400
sub_tree.branch.repository.revision_tree(base_revision)
404
if self.other_tree is not None:
405
self.other_tree.unlock()
406
if self.base_tree is not None:
407
self.base_tree.unlock()
408
self.this_tree.unlock()
409
if len(merge.cooked_conflicts) == 0:
410
if not self.ignore_zero:
411
note("All changes applied successfully.")
413
note("%d conflicts encountered." % len(merge.cooked_conflicts))
415
return len(merge.cooked_conflicts)
418
class Merge3Merger(object):
419
"""Three-way merger that uses the merge3 text merger"""
421
supports_reprocess = True
422
supports_show_base = True
423
history_based = False
424
supports_reverse_cherrypick = True
425
winner_idx = {"this": 2, "other": 1, "conflict": 1}
427
def __init__(self, working_tree, this_tree, base_tree, other_tree,
428
interesting_ids=None, reprocess=False, show_base=False,
429
pb=DummyProgress(), pp=None, change_reporter=None,
430
interesting_files=None):
431
"""Initialize the merger object and perform the merge.
433
:param working_tree: The working tree to apply the merge to
434
:param this_tree: The local tree in the merge operation
435
:param base_tree: The common tree in the merge operation
436
:param other_tree: The other other tree to merge changes from
437
:param interesting_ids: The file_ids of files that should be
438
participate in the merge. May not be combined with
440
:param: reprocess If True, perform conflict-reduction processing.
441
:param show_base: If True, show the base revision in text conflicts.
442
(incompatible with reprocess)
443
:param pb: A Progress bar
444
:param pp: A ProgressPhase object
445
:param change_reporter: An object that should report changes made
446
:param interesting_files: The tree-relative paths of files that should
447
participate in the merge. If these paths refer to directories,
448
the contents of those directories will also be included. May not
449
be combined with interesting_ids. If neither interesting_files nor
450
interesting_ids is specified, all files may participate in the
453
object.__init__(self)
454
if interesting_files is not None:
455
assert interesting_ids is None
456
self.interesting_ids = interesting_ids
457
self.interesting_files = interesting_files
458
self.this_tree = working_tree
459
self.this_tree.lock_tree_write()
460
self.base_tree = base_tree
461
self.base_tree.lock_read()
462
self.other_tree = other_tree
463
self.other_tree.lock_read()
464
self._raw_conflicts = []
465
self.cooked_conflicts = []
466
self.reprocess = reprocess
467
self.show_base = show_base
470
self.change_reporter = change_reporter
472
self.pp = ProgressPhase("Merge phase", 3, self.pb)
474
self.tt = TreeTransform(working_tree, self.pb)
477
entries = self._entries3()
478
child_pb = ui.ui_factory.nested_progress_bar()
480
for num, (file_id, changed, parents3, names3,
481
executable3) in enumerate(entries):
482
child_pb.update('Preparing file merge', num, len(entries))
483
self._merge_names(file_id, parents3, names3)
485
file_status = self.merge_contents(file_id)
487
file_status = 'unmodified'
488
self._merge_executable(file_id,
489
executable3, file_status)
494
child_pb = ui.ui_factory.nested_progress_bar()
496
fs_conflicts = resolve_conflicts(self.tt, child_pb,
497
lambda t, c: conflict_pass(t, c, self.other_tree))
500
if change_reporter is not None:
501
from bzrlib import delta
502
delta.report_changes(self.tt._iter_changes(), change_reporter)
503
self.cook_conflicts(fs_conflicts)
504
for conflict in self.cooked_conflicts:
507
results = self.tt.apply(no_conflicts=True)
508
self.write_modified(results)
510
working_tree.add_conflicts(self.cooked_conflicts)
511
except UnsupportedOperation:
515
self.other_tree.unlock()
516
self.base_tree.unlock()
517
self.this_tree.unlock()
521
"""Gather data about files modified between three trees.
523
Return a list of tuples of file_id, changed, parents3, names3,
524
executable3. changed is a boolean indicating whether the file contents
525
or kind were changed. parents3 is a tuple of parent ids for base,
526
other and this. names3 is a tuple of names for base, other and this.
527
executable3 is a tuple of execute-bit values for base, other and this.
530
iterator = self.other_tree._iter_changes(self.base_tree,
531
include_unchanged=True, specific_files=self.interesting_files,
532
extra_trees=[self.this_tree])
533
for (file_id, paths, changed, versioned, parents, names, kind,
534
executable) in iterator:
535
if (self.interesting_ids is not None and
536
file_id not in self.interesting_ids):
538
if file_id in self.this_tree.inventory:
539
entry = self.this_tree.inventory[file_id]
540
this_name = entry.name
541
this_parent = entry.parent_id
542
this_executable = entry.executable
546
this_executable = None
547
parents3 = parents + (this_parent,)
548
names3 = names + (this_name,)
549
executable3 = executable + (this_executable,)
550
result.append((file_id, changed, parents3, names3, executable3))
555
self.tt.final_kind(self.tt.root)
557
self.tt.cancel_deletion(self.tt.root)
558
if self.tt.final_file_id(self.tt.root) is None:
559
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
561
if self.other_tree.inventory.root is None:
563
other_root_file_id = self.other_tree.get_root_id()
564
other_root = self.tt.trans_id_file_id(other_root_file_id)
565
if other_root == self.tt.root:
568
self.tt.final_kind(other_root)
571
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
572
self.tt.cancel_creation(other_root)
573
self.tt.cancel_versioning(other_root)
575
def reparent_children(self, ie, target):
576
for thing, child in ie.children.iteritems():
577
trans_id = self.tt.trans_id_file_id(child.file_id)
578
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
580
def write_modified(self, results):
582
for path in results.modified_paths:
583
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
586
hash = self.this_tree.get_file_sha1(file_id)
589
modified_hashes[file_id] = hash
590
self.this_tree.set_merge_modified(modified_hashes)
593
def parent(entry, file_id):
594
"""Determine the parent for a file_id (used as a key method)"""
597
return entry.parent_id
600
def name(entry, file_id):
601
"""Determine the name for a file_id (used as a key method)"""
607
def contents_sha1(tree, file_id):
608
"""Determine the sha1 of the file contents (used as a key method)."""
609
if file_id not in tree:
611
return tree.get_file_sha1(file_id)
614
def executable(tree, file_id):
615
"""Determine the executability of a file-id (used as a key method)."""
616
if file_id not in tree:
618
if tree.kind(file_id) != "file":
620
return tree.is_executable(file_id)
623
def kind(tree, file_id):
624
"""Determine the kind of a file-id (used as a key method)."""
625
if file_id not in tree:
627
return tree.kind(file_id)
630
def _three_way(base, other, this):
631
#if base == other, either they all agree, or only THIS has changed.
634
elif this not in (base, other):
636
# "Ambiguous clean merge" -- both sides have made the same change.
639
# this == base: only other has changed.
644
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
645
"""Do a three-way test on a scalar.
646
Return "this", "other" or "conflict", depending whether a value wins.
648
key_base = key(base_tree, file_id)
649
key_other = key(other_tree, file_id)
650
#if base == other, either they all agree, or only THIS has changed.
651
if key_base == key_other:
653
key_this = key(this_tree, file_id)
654
if key_this not in (key_base, key_other):
656
# "Ambiguous clean merge"
657
elif key_this == key_other:
660
assert key_this == key_base
663
def merge_names(self, file_id):
665
if file_id in tree.inventory:
666
return tree.inventory[file_id]
669
this_entry = get_entry(self.this_tree)
670
other_entry = get_entry(self.other_tree)
671
base_entry = get_entry(self.base_tree)
672
entries = (base_entry, other_entry, this_entry)
675
for entry in entries:
680
names.append(entry.name)
681
parents.append(entry.parent_id)
682
return self._merge_names(file_id, parents, names)
684
def _merge_names(self, file_id, parents, names):
685
"""Perform a merge on file_id names and parents"""
686
base_name, other_name, this_name = names
687
base_parent, other_parent, this_parent = parents
689
name_winner = self._three_way(*names)
691
parent_id_winner = self._three_way(*parents)
692
if this_name is None:
693
if name_winner == "this":
694
name_winner = "other"
695
if parent_id_winner == "this":
696
parent_id_winner = "other"
697
if name_winner == "this" and parent_id_winner == "this":
699
if name_winner == "conflict":
700
trans_id = self.tt.trans_id_file_id(file_id)
701
self._raw_conflicts.append(('name conflict', trans_id,
702
this_name, other_name))
703
if parent_id_winner == "conflict":
704
trans_id = self.tt.trans_id_file_id(file_id)
705
self._raw_conflicts.append(('parent conflict', trans_id,
706
this_parent, other_parent))
707
if other_name is None:
708
# it doesn't matter whether the result was 'other' or
709
# 'conflict'-- if there's no 'other', we leave it alone.
711
# if we get here, name_winner and parent_winner are set to safe values.
712
trans_id = self.tt.trans_id_file_id(file_id)
713
parent_id = parents[self.winner_idx[parent_id_winner]]
714
if parent_id is not None:
715
parent_trans_id = self.tt.trans_id_file_id(parent_id)
716
self.tt.adjust_path(names[self.winner_idx[name_winner]],
717
parent_trans_id, trans_id)
719
def merge_contents(self, file_id):
720
"""Performa a merge on file_id contents."""
721
def contents_pair(tree):
722
if file_id not in tree:
724
kind = tree.kind(file_id)
726
contents = tree.get_file_sha1(file_id)
727
elif kind == "symlink":
728
contents = tree.get_symlink_target(file_id)
731
return kind, contents
733
def contents_conflict():
734
trans_id = self.tt.trans_id_file_id(file_id)
735
name = self.tt.final_name(trans_id)
736
parent_id = self.tt.final_parent(trans_id)
737
if file_id in self.this_tree.inventory:
738
self.tt.unversion_file(trans_id)
739
if file_id in self.this_tree:
740
self.tt.delete_contents(trans_id)
741
file_group = self._dump_conflicts(name, parent_id, file_id,
743
self._raw_conflicts.append(('contents conflict', file_group))
745
# See SPOT run. run, SPOT, run.
746
# So we're not QUITE repeating ourselves; we do tricky things with
748
base_pair = contents_pair(self.base_tree)
749
other_pair = contents_pair(self.other_tree)
750
if base_pair == other_pair:
751
# OTHER introduced no changes
753
this_pair = contents_pair(self.this_tree)
754
if this_pair == other_pair:
755
# THIS and OTHER introduced the same changes
758
trans_id = self.tt.trans_id_file_id(file_id)
759
if this_pair == base_pair:
760
# only OTHER introduced changes
761
if file_id in self.this_tree:
762
# Remove any existing contents
763
self.tt.delete_contents(trans_id)
764
if file_id in self.other_tree:
765
# OTHER changed the file
766
create_by_entry(self.tt,
767
self.other_tree.inventory[file_id],
768
self.other_tree, trans_id)
769
if file_id not in self.this_tree.inventory:
770
self.tt.version_file(file_id, trans_id)
772
elif file_id in self.this_tree.inventory:
773
# OTHER deleted the file
774
self.tt.unversion_file(trans_id)
776
#BOTH THIS and OTHER introduced changes; scalar conflict
777
elif this_pair[0] == "file" and other_pair[0] == "file":
778
# THIS and OTHER are both files, so text merge. Either
779
# BASE is a file, or both converted to files, so at least we
780
# have agreement that output should be a file.
782
self.text_merge(file_id, trans_id)
784
return contents_conflict()
785
if file_id not in self.this_tree.inventory:
786
self.tt.version_file(file_id, trans_id)
788
self.tt.tree_kind(trans_id)
789
self.tt.delete_contents(trans_id)
794
# Scalar conflict, can't text merge. Dump conflicts
795
return contents_conflict()
797
def get_lines(self, tree, file_id):
798
"""Return the lines in a file, or an empty list."""
800
return tree.get_file(file_id).readlines()
804
def text_merge(self, file_id, trans_id):
805
"""Perform a three-way text merge on a file_id"""
806
# it's possible that we got here with base as a different type.
807
# if so, we just want two-way text conflicts.
808
if file_id in self.base_tree and \
809
self.base_tree.kind(file_id) == "file":
810
base_lines = self.get_lines(self.base_tree, file_id)
813
other_lines = self.get_lines(self.other_tree, file_id)
814
this_lines = self.get_lines(self.this_tree, file_id)
815
m3 = Merge3(base_lines, this_lines, other_lines)
816
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
817
if self.show_base is True:
818
base_marker = '|' * 7
822
def iter_merge3(retval):
823
retval["text_conflicts"] = False
824
for line in m3.merge_lines(name_a = "TREE",
825
name_b = "MERGE-SOURCE",
826
name_base = "BASE-REVISION",
827
start_marker=start_marker,
828
base_marker=base_marker,
829
reprocess=self.reprocess):
830
if line.startswith(start_marker):
831
retval["text_conflicts"] = True
832
yield line.replace(start_marker, '<' * 7)
836
merge3_iterator = iter_merge3(retval)
837
self.tt.create_file(merge3_iterator, trans_id)
838
if retval["text_conflicts"] is True:
839
self._raw_conflicts.append(('text conflict', trans_id))
840
name = self.tt.final_name(trans_id)
841
parent_id = self.tt.final_parent(trans_id)
842
file_group = self._dump_conflicts(name, parent_id, file_id,
843
this_lines, base_lines,
845
file_group.append(trans_id)
847
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
848
base_lines=None, other_lines=None, set_version=False,
850
"""Emit conflict files.
851
If this_lines, base_lines, or other_lines are omitted, they will be
852
determined automatically. If set_version is true, the .OTHER, .THIS
853
or .BASE (in that order) will be created as versioned files.
855
data = [('OTHER', self.other_tree, other_lines),
856
('THIS', self.this_tree, this_lines)]
858
data.append(('BASE', self.base_tree, base_lines))
861
for suffix, tree, lines in data:
863
trans_id = self._conflict_file(name, parent_id, tree, file_id,
865
file_group.append(trans_id)
866
if set_version and not versioned:
867
self.tt.version_file(file_id, trans_id)
871
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
873
"""Emit a single conflict file."""
874
name = name + '.' + suffix
875
trans_id = self.tt.create_path(name, parent_id)
876
entry = tree.inventory[file_id]
877
create_by_entry(self.tt, entry, tree, trans_id, lines)
880
def merge_executable(self, file_id, file_status):
881
"""Perform a merge on the execute bit."""
882
executable = [self.executable(t, file_id) for t in (self.base_tree,
883
self.other_tree, self.this_tree)]
884
self._merge_executable(file_id, executable, file_status)
886
def _merge_executable(self, file_id, executable, file_status):
887
"""Perform a merge on the execute bit."""
888
base_executable, other_executable, this_executable = executable
889
if file_status == "deleted":
891
trans_id = self.tt.trans_id_file_id(file_id)
893
if self.tt.final_kind(trans_id) != "file":
897
winner = self._three_way(*executable)
898
if winner == "conflict":
899
# There must be a None in here, if we have a conflict, but we
900
# need executability since file status was not deleted.
901
if self.executable(self.other_tree, file_id) is None:
906
if file_status == "modified":
907
executability = this_executable
908
if executability is not None:
909
trans_id = self.tt.trans_id_file_id(file_id)
910
self.tt.set_executability(executability, trans_id)
912
assert winner == "other"
913
if file_id in self.other_tree:
914
executability = other_executable
915
elif file_id in self.this_tree:
916
executability = this_executable
917
elif file_id in self.base_tree:
918
executability = base_executable
919
if executability is not None:
920
trans_id = self.tt.trans_id_file_id(file_id)
921
self.tt.set_executability(executability, trans_id)
923
def cook_conflicts(self, fs_conflicts):
924
"""Convert all conflicts into a form that doesn't depend on trans_id"""
925
from conflicts import Conflict
927
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
928
fp = FinalPaths(self.tt)
929
for conflict in self._raw_conflicts:
930
conflict_type = conflict[0]
931
if conflict_type in ('name conflict', 'parent conflict'):
932
trans_id = conflict[1]
933
conflict_args = conflict[2:]
934
if trans_id not in name_conflicts:
935
name_conflicts[trans_id] = {}
936
unique_add(name_conflicts[trans_id], conflict_type,
938
if conflict_type == 'contents conflict':
939
for trans_id in conflict[1]:
940
file_id = self.tt.final_file_id(trans_id)
941
if file_id is not None:
943
path = fp.get_path(trans_id)
944
for suffix in ('.BASE', '.THIS', '.OTHER'):
945
if path.endswith(suffix):
946
path = path[:-len(suffix)]
948
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
949
self.cooked_conflicts.append(c)
950
if conflict_type == 'text conflict':
951
trans_id = conflict[1]
952
path = fp.get_path(trans_id)
953
file_id = self.tt.final_file_id(trans_id)
954
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
955
self.cooked_conflicts.append(c)
957
for trans_id, conflicts in name_conflicts.iteritems():
959
this_parent, other_parent = conflicts['parent conflict']
960
assert this_parent != other_parent
962
this_parent = other_parent = \
963
self.tt.final_file_id(self.tt.final_parent(trans_id))
965
this_name, other_name = conflicts['name conflict']
966
assert this_name != other_name
968
this_name = other_name = self.tt.final_name(trans_id)
969
other_path = fp.get_path(trans_id)
970
if this_parent is not None and this_name is not None:
972
fp.get_path(self.tt.trans_id_file_id(this_parent))
973
this_path = pathjoin(this_parent_path, this_name)
975
this_path = "<deleted>"
976
file_id = self.tt.final_file_id(trans_id)
977
c = Conflict.factory('path conflict', path=this_path,
978
conflict_path=other_path, file_id=file_id)
979
self.cooked_conflicts.append(c)
980
self.cooked_conflicts.sort(key=Conflict.sort_key)
983
class WeaveMerger(Merge3Merger):
984
"""Three-way tree merger, text weave merger."""
985
supports_reprocess = True
986
supports_show_base = False
987
supports_reverse_cherrypick = False
990
def __init__(self, working_tree, this_tree, base_tree, other_tree,
991
interesting_ids=None, pb=DummyProgress(), pp=None,
992
reprocess=False, change_reporter=None,
993
interesting_files=None, cherrypick=False):
994
self.cherrypick = cherrypick
995
super(WeaveMerger, self).__init__(working_tree, this_tree,
996
base_tree, other_tree,
997
interesting_ids=interesting_ids,
998
pb=pb, pp=pp, reprocess=reprocess,
999
change_reporter=change_reporter)
1001
def _merged_lines(self, file_id):
1002
"""Generate the merged lines.
1003
There is no distinction between lines that are meant to contain <<<<<<<
1007
base = self.base_tree
1010
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1012
if 'merge' in debug.debug_flags:
1014
trans_id = self.tt.trans_id_file_id(file_id)
1015
name = self.tt.final_name(trans_id) + '.plan'
1016
contents = ('%10s|%s' % l for l in plan)
1017
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1018
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1019
'>>>>>>> MERGE-SOURCE\n')
1020
return textmerge.merge_lines(self.reprocess)
1022
def text_merge(self, file_id, trans_id):
1023
"""Perform a (weave) text merge for a given file and file-id.
1024
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1025
and a conflict will be noted.
1027
lines, conflicts = self._merged_lines(file_id)
1029
# Note we're checking whether the OUTPUT is binary in this case,
1030
# because we don't want to get into weave merge guts.
1031
check_text_lines(lines)
1032
self.tt.create_file(lines, trans_id)
1034
self._raw_conflicts.append(('text conflict', trans_id))
1035
name = self.tt.final_name(trans_id)
1036
parent_id = self.tt.final_parent(trans_id)
1037
file_group = self._dump_conflicts(name, parent_id, file_id,
1039
file_group.append(trans_id)
1042
class Diff3Merger(Merge3Merger):
1043
"""Three-way merger using external diff3 for text merging"""
1045
def dump_file(self, temp_dir, name, tree, file_id):
1046
out_path = pathjoin(temp_dir, name)
1047
out_file = open(out_path, "wb")
1049
in_file = tree.get_file(file_id)
1050
for line in in_file:
1051
out_file.write(line)
1056
def text_merge(self, file_id, trans_id):
1057
"""Perform a diff3 merge using a specified file-id and trans-id.
1058
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1059
will be dumped, and a will be conflict noted.
1062
temp_dir = osutils.mkdtemp(prefix="bzr-")
1064
new_file = pathjoin(temp_dir, "new")
1065
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1066
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1067
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1068
status = bzrlib.patch.diff3(new_file, this, base, other)
1069
if status not in (0, 1):
1070
raise BzrError("Unhandled diff3 exit code")
1071
f = open(new_file, 'rb')
1073
self.tt.create_file(f, trans_id)
1077
name = self.tt.final_name(trans_id)
1078
parent_id = self.tt.final_parent(trans_id)
1079
self._dump_conflicts(name, parent_id, file_id)
1080
self._raw_conflicts.append(('text conflict', trans_id))
1082
osutils.rmtree(temp_dir)
1085
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1087
merge_type=Merge3Merger,
1088
interesting_ids=None,
1092
interesting_files=None,
1095
change_reporter=None):
1096
"""Primary interface for merging.
1098
typical use is probably
1099
'merge_inner(branch, branch.get_revision_tree(other_revision),
1100
branch.get_revision_tree(base_revision))'
1102
if this_tree is None:
1103
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1104
"parameter as of bzrlib version 0.8.")
1105
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1106
pb=pb, change_reporter=change_reporter)
1107
merger.backup_files = backup_files
1108
merger.merge_type = merge_type
1109
merger.interesting_ids = interesting_ids
1110
merger.ignore_zero = ignore_zero
1111
if interesting_files:
1112
assert not interesting_ids, ('Only supply interesting_ids'
1113
' or interesting_files')
1114
merger.interesting_files = interesting_files
1115
merger.show_base = show_base
1116
merger.reprocess = reprocess
1117
merger.other_rev_id = other_rev_id
1118
merger.other_basis = other_rev_id
1119
return merger.do_merge()
1121
def get_merge_type_registry():
1122
"""Merge type registry is in bzrlib.option to avoid circular imports.
1124
This method provides a sanctioned way to retrieve it.
1126
from bzrlib import option
1127
return option._merge_type_registry
1130
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1131
def status_a(revision, text):
1132
if revision in ancestors_b:
1133
return 'killed-b', text
1135
return 'new-a', text
1137
def status_b(revision, text):
1138
if revision in ancestors_a:
1139
return 'killed-a', text
1141
return 'new-b', text
1143
plain_a = [t for (a, t) in annotated_a]
1144
plain_b = [t for (a, t) in annotated_b]
1145
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1146
blocks = matcher.get_matching_blocks()
1149
for ai, bi, l in blocks:
1150
# process all mismatched sections
1151
# (last mismatched section is handled because blocks always
1152
# includes a 0-length last block)
1153
for revision, text in annotated_a[a_cur:ai]:
1154
yield status_a(revision, text)
1155
for revision, text in annotated_b[b_cur:bi]:
1156
yield status_b(revision, text)
1158
# and now the matched section
1161
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1162
assert text_a == text_b
1163
yield "unchanged", text_a
1166
class _PlanMerge(object):
1167
"""Plan an annotate merge using on-the-fly annotation"""
1169
def __init__(self, a_rev, b_rev, vf):
1172
:param a_rev: Revision-id of one revision to merge
1173
:param b_rev: Revision-id of the other revision to merge
1174
:param vf: A versionedfile containing both revisions
1178
self.lines_a = vf.get_lines(a_rev)
1179
self.lines_b = vf.get_lines(b_rev)
1181
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1182
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1183
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1184
self._last_lines = None
1185
self._last_lines_revision_id = None
1187
def plan_merge(self):
1188
"""Generate a 'plan' for merging the two revisions.
1190
This involves comparing their texts and determining the cause of
1191
differences. If text A has a line and text B does not, then either the
1192
line was added to text A, or it was deleted from B. Once the causes
1193
are combined, they are written out in the format described in
1194
VersionedFile.plan_merge
1196
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1197
new_a = self._find_new(self.a_rev)
1198
new_b = self._find_new(self.b_rev)
1201
a_lines = self.vf.get_lines(self.a_rev)
1202
b_lines = self.vf.get_lines(self.b_rev)
1203
for i, j, n in blocks:
1204
# determine why lines aren't common
1205
for a_index in range(last_i, i):
1206
if a_index in new_a:
1210
yield cause, a_lines[a_index]
1211
for b_index in range(last_j, j):
1212
if b_index in new_b:
1216
yield cause, b_lines[b_index]
1217
# handle common lines
1218
for a_index in range(i, i+n):
1219
yield 'unchanged', a_lines[a_index]
1223
def _get_matching_blocks(self, left_revision, right_revision):
1224
"""Return a description of which sections of two revisions match.
1226
See SequenceMatcher.get_matching_blocks
1228
if self._last_lines_revision_id == left_revision:
1229
left_lines = self._last_lines
1231
left_lines = self.vf.get_lines(left_revision)
1232
right_lines = self.vf.get_lines(right_revision)
1233
self._last_lines = right_lines
1234
self._last_lines_revision_id = right_revision
1235
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1237
return matcher.get_matching_blocks()
1239
def _unique_lines(self, matching_blocks):
1240
"""Analyse matching_blocks to determine which lines are unique
1242
:return: a tuple of (unique_left, unique_right), where the values are
1243
sets of line numbers of unique lines.
1249
for i, j, n in matching_blocks:
1250
unique_left.extend(range(last_i, i))
1251
unique_right.extend(range(last_j, j))
1254
return unique_left, unique_right
1256
def _find_new(self, version_id):
1257
"""Determine which lines are new in the ancestry of this version.
1259
If a lines is present in this version, and not present in any
1260
common ancestor, it is considered new.
1262
if version_id not in self.uncommon:
1264
parents = self.vf.get_parents(version_id)
1265
if len(parents) == 0:
1266
return set(range(len(self.vf.get_lines(version_id))))
1268
for parent in parents:
1269
blocks = self._get_matching_blocks(version_id, parent)
1270
result, unused = self._unique_lines(blocks)
1271
parent_new = self._find_new(parent)
1272
for i, j, n in blocks:
1273
for ii, jj in [(i+r, j+r) for r in range(n)]:
1274
if jj in parent_new:
1279
new.intersection_update(result)
1283
def _subtract_plans(old_plan, new_plan):
1284
matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1287
for i, j, n in matcher.get_matching_blocks():
1288
for jj in range(last_j, j):
1290
for jj in range(j, j+n):
1291
plan_line = new_plan[jj]
1292
if plan_line[0] == 'new-b':
1294
elif plan_line[0] == 'killed-b':
1295
yield 'unchanged', plan_line[1]