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, is_quiet
51
from bzrlib.transform import (TransformPreview, TreeTransform,
52
resolve_conflicts, cook_conflicts,
53
conflict_pass, FinalPaths, create_by_entry,
54
unique_add, ROOT_PARENT)
55
from bzrlib.versionedfile import PlanWeaveMerge
58
# TODO: Report back as changes are merged in
61
def transform_tree(from_tree, to_tree, interesting_ids=None):
62
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
63
interesting_ids=interesting_ids, this_tree=from_tree)
67
def __init__(self, this_branch, other_tree=None, base_tree=None,
68
this_tree=None, pb=DummyProgress(), change_reporter=None,
69
recurse='down', revision_graph=None):
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 = {}
92
self._revision_graph = revision_graph
93
self._base_is_ancestor = None
94
self._base_is_other_ancestor = None
97
def revision_graph(self):
98
if self._revision_graph is None:
99
self._revision_graph = self.this_branch.repository.get_graph()
100
return self._revision_graph
102
def _set_base_is_ancestor(self, value):
103
self._base_is_ancestor = value
105
def _get_base_is_ancestor(self):
106
if self._base_is_ancestor is None:
107
self._base_is_ancestor = self.revision_graph.is_ancestor(
108
self.base_rev_id, self.this_basis)
109
return self._base_is_ancestor
111
base_is_ancestor = property(_get_base_is_ancestor, _set_base_is_ancestor)
113
def _set_base_is_other_ancestor(self, value):
114
self._base_is_other_ancestor = value
116
def _get_base_is_other_ancestor(self):
117
if self._base_is_other_ancestor is None:
118
if self.other_basis is None:
120
self._base_is_other_ancestor = self.revision_graph.is_ancestor(
121
self.base_rev_id, self.other_basis)
122
return self._base_is_other_ancestor
124
base_is_other_ancestor = property(_get_base_is_other_ancestor,
125
_set_base_is_other_ancestor)
128
def from_uncommitted(tree, other_tree, pb):
129
"""Return a Merger for uncommitted changes in other_tree.
131
:param tree: The tree to merge into
132
:param other_tree: The tree to get uncommitted changes from
133
:param pb: A progress indicator
135
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
137
merger.base_rev_id = merger.base_tree.get_revision_id()
138
merger.other_rev_id = None
139
merger.other_basis = merger.base_rev_id
143
def from_mergeable(klass, tree, mergeable, pb):
144
"""Return a Merger for a bundle or merge directive.
146
:param tree: The tree to merge changes into
147
:param mergeable: A merge directive or bundle
148
:param pb: A progress indicator
150
mergeable.install_revisions(tree.branch.repository)
151
base_revision_id, other_revision_id, verified =\
152
mergeable.get_merge_request(tree.branch.repository)
153
revision_graph = tree.branch.repository.get_graph()
154
if (base_revision_id != _mod_revision.NULL_REVISION and
155
revision_graph.is_ancestor(
156
base_revision_id, tree.branch.last_revision())):
157
base_revision_id = None
159
warning('Performing cherrypick')
160
merger = klass.from_revision_ids(pb, tree, other_revision_id,
161
base_revision_id, revision_graph=
163
return merger, verified
166
def from_revision_ids(pb, tree, other, base=None, other_branch=None,
167
base_branch=None, revision_graph=None):
168
"""Return a Merger for revision-ids.
170
:param tree: The tree to merge changes into
171
:param other: The revision-id to use as OTHER
172
:param base: The revision-id to use as BASE. If not specified, will
174
:param other_branch: A branch containing the other revision-id. If
175
not supplied, tree.branch is used.
176
:param base_branch: A branch containing the base revision-id. If
177
not supplied, other_branch or tree.branch will be used.
178
:param revision_graph: If you have a revision_graph precomputed, pass
179
it in, otherwise it will be created for you.
180
:param pb: A progress indicator
182
merger = Merger(tree.branch, this_tree=tree, pb=pb,
183
revision_graph=revision_graph)
184
if other_branch is None:
185
other_branch = tree.branch
186
merger.set_other_revision(other, other_branch)
190
if base_branch is None:
191
base_branch = other_branch
192
merger.set_base_revision(base, base_branch)
195
def revision_tree(self, revision_id, branch=None):
196
if revision_id not in self._cached_trees:
198
branch = self.this_branch
200
tree = self.this_tree.revision_tree(revision_id)
201
except errors.NoSuchRevisionInTree:
202
tree = branch.repository.revision_tree(revision_id)
203
self._cached_trees[revision_id] = tree
204
return self._cached_trees[revision_id]
206
def _get_tree(self, treespec, possible_transports=None):
207
from bzrlib import workingtree
208
location, revno = treespec
210
tree = workingtree.WorkingTree.open_containing(location)[0]
211
return tree.branch, tree
212
branch = Branch.open_containing(location, possible_transports)[0]
214
revision_id = branch.last_revision()
216
revision_id = branch.get_rev_id(revno)
217
revision_id = ensure_null(revision_id)
218
return branch, self.revision_tree(revision_id, branch)
220
def ensure_revision_trees(self):
221
if self.this_revision_tree is None:
222
self.this_basis_tree = self.revision_tree(self.this_basis)
223
if self.this_basis == self.this_rev_id:
224
self.this_revision_tree = self.this_basis_tree
226
if self.other_rev_id is None:
227
other_basis_tree = self.revision_tree(self.other_basis)
228
changes = other_basis_tree.changes_from(self.other_tree)
229
if changes.has_changed():
230
raise WorkingTreeNotRevision(self.this_tree)
231
other_rev_id = self.other_basis
232
self.other_tree = other_basis_tree
234
def file_revisions(self, file_id):
235
self.ensure_revision_trees()
236
def get_id(tree, file_id):
237
revision_id = tree.inventory[file_id].revision
239
if self.this_rev_id is None:
240
if self.this_basis_tree.get_file_sha1(file_id) != \
241
self.this_tree.get_file_sha1(file_id):
242
raise WorkingTreeNotRevision(self.this_tree)
244
trees = (self.this_basis_tree, self.other_tree)
245
return [get_id(tree, file_id) for tree in trees]
247
def check_basis(self, check_clean, require_commits=True):
248
if self.this_basis is None and require_commits is True:
249
raise BzrCommandError("This branch has no commits."
250
" (perhaps you would prefer 'bzr pull')")
253
if self.this_basis != self.this_rev_id:
254
raise errors.UncommittedChanges(self.this_tree)
256
def compare_basis(self):
258
basis_tree = self.revision_tree(self.this_tree.last_revision())
259
except errors.RevisionNotPresent:
260
basis_tree = self.this_tree.basis_tree()
261
changes = self.this_tree.changes_from(basis_tree)
262
if not changes.has_changed():
263
self.this_rev_id = self.this_basis
265
def set_interesting_files(self, file_list):
266
self.interesting_files = file_list
268
def set_pending(self):
269
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
273
def _add_parent(self):
274
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
275
new_parent_trees = []
276
for revision_id in new_parents:
278
tree = self.revision_tree(revision_id)
279
except errors.RevisionNotPresent:
283
new_parent_trees.append((revision_id, tree))
285
self.this_tree.set_parent_trees(new_parent_trees,
286
allow_leftmost_as_ghost=True)
288
for _revision_id, tree in new_parent_trees:
292
def set_other(self, other_revision, possible_transports=None):
293
"""Set the revision and tree to merge from.
295
This sets the other_tree, other_rev_id, other_basis attributes.
297
:param other_revision: The [path, revision] list to merge from.
299
self.other_branch, self.other_tree = self._get_tree(other_revision,
301
if other_revision[1] == -1:
302
self.other_rev_id = _mod_revision.ensure_null(
303
self.other_branch.last_revision())
304
if _mod_revision.is_null(self.other_rev_id):
305
raise NoCommits(self.other_branch)
306
self.other_basis = self.other_rev_id
307
elif other_revision[1] is not None:
308
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
309
self.other_basis = self.other_rev_id
311
self.other_rev_id = None
312
self.other_basis = self.other_branch.last_revision()
313
if self.other_basis is None:
314
raise NoCommits(self.other_branch)
315
if self.other_rev_id is not None:
316
self._cached_trees[self.other_rev_id] = self.other_tree
317
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
319
def set_other_revision(self, revision_id, other_branch):
320
"""Set 'other' based on a branch and revision id
322
:param revision_id: The revision to use for a tree
323
:param other_branch: The branch containing this tree
325
self.other_rev_id = revision_id
326
self.other_branch = other_branch
327
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
328
self.other_tree = self.revision_tree(revision_id)
329
self.other_basis = revision_id
331
def set_base_revision(self, revision_id, branch):
332
"""Set 'base' based on a branch and revision id
334
:param revision_id: The revision to use for a tree
335
:param branch: The branch containing this tree
337
self.base_rev_id = revision_id
338
self.base_branch = branch
339
self._maybe_fetch(branch, self.this_branch, revision_id)
340
self.base_tree = self.revision_tree(revision_id)
342
def _maybe_fetch(self, source, target, revision_id):
343
if not source.repository.has_same_location(target.repository):
344
target.fetch(source, revision_id)
347
revisions = [ensure_null(self.this_basis),
348
ensure_null(self.other_basis)]
349
if NULL_REVISION in revisions:
350
self.base_rev_id = NULL_REVISION
352
self.base_rev_id, steps = self.revision_graph.find_unique_lca(
353
revisions[0], revisions[1], count_steps=True)
354
if self.base_rev_id == NULL_REVISION:
355
raise UnrelatedBranches()
357
warning('Warning: criss-cross merge encountered. See bzr'
358
' help criss-cross.')
359
self.base_tree = self.revision_tree(self.base_rev_id)
360
self.base_is_ancestor = True
361
self.base_is_other_ancestor = True
363
def set_base(self, base_revision):
364
"""Set the base revision to use for the merge.
366
:param base_revision: A 2-list containing a path and revision number.
368
mutter("doing merge() with no base_revision specified")
369
if base_revision == [None, None]:
372
base_branch, self.base_tree = self._get_tree(base_revision)
373
if base_revision[1] == -1:
374
self.base_rev_id = base_branch.last_revision()
375
elif base_revision[1] is None:
376
self.base_rev_id = _mod_revision.NULL_REVISION
378
self.base_rev_id = _mod_revision.ensure_null(
379
base_branch.get_rev_id(base_revision[1]))
380
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
382
def make_merger(self):
383
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
384
'other_tree': self.other_tree,
385
'interesting_ids': self.interesting_ids,
386
'interesting_files': self.interesting_files,
389
if self.merge_type.requires_base:
390
kwargs['base_tree'] = self.base_tree
391
if self.merge_type.supports_reprocess:
392
kwargs['reprocess'] = self.reprocess
394
raise BzrError("Conflict reduction is not supported for merge"
395
" type %s." % self.merge_type)
396
if self.merge_type.supports_show_base:
397
kwargs['show_base'] = self.show_base
399
raise BzrError("Showing base is not supported for this"
400
" merge type. %s" % self.merge_type)
401
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
402
and not self.base_is_other_ancestor):
403
raise errors.CannotReverseCherrypick()
404
if self.merge_type.supports_cherrypick:
405
kwargs['cherrypick'] = (not self.base_is_ancestor or
406
not self.base_is_other_ancestor)
407
return self.merge_type(pb=self._pb,
408
change_reporter=self.change_reporter,
412
self.this_tree.lock_tree_write()
413
if self.base_tree is not None:
414
self.base_tree.lock_read()
415
if self.other_tree is not None:
416
self.other_tree.lock_read()
418
merge = self.make_merger()
420
if self.recurse == 'down':
421
for path, file_id in self.this_tree.iter_references():
422
sub_tree = self.this_tree.get_nested_tree(file_id, path)
423
other_revision = self.other_tree.get_reference_revision(
425
if other_revision == sub_tree.last_revision():
427
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
428
sub_merge.merge_type = self.merge_type
429
relpath = self.this_tree.relpath(path)
430
other_branch = self.other_branch.reference_parent(file_id, relpath)
431
sub_merge.set_other_revision(other_revision, other_branch)
432
base_revision = self.base_tree.get_reference_revision(file_id)
433
sub_merge.base_tree = \
434
sub_tree.branch.repository.revision_tree(base_revision)
435
sub_merge.base_rev_id = base_revision
439
if self.other_tree is not None:
440
self.other_tree.unlock()
441
if self.base_tree is not None:
442
self.base_tree.unlock()
443
self.this_tree.unlock()
444
if len(merge.cooked_conflicts) == 0:
445
if not self.ignore_zero and not is_quiet():
446
note("All changes applied successfully.")
448
note("%d conflicts encountered." % len(merge.cooked_conflicts))
450
return len(merge.cooked_conflicts)
453
class Merge3Merger(object):
454
"""Three-way merger that uses the merge3 text merger"""
456
supports_reprocess = True
457
supports_show_base = True
458
history_based = False
459
supports_cherrypick = True
460
supports_reverse_cherrypick = True
461
winner_idx = {"this": 2, "other": 1, "conflict": 1}
463
def __init__(self, working_tree, this_tree, base_tree, other_tree,
464
interesting_ids=None, reprocess=False, show_base=False,
465
pb=DummyProgress(), pp=None, change_reporter=None,
466
interesting_files=None, do_merge=True,
468
"""Initialize the merger object and perform the merge.
470
:param working_tree: The working tree to apply the merge to
471
:param this_tree: The local tree in the merge operation
472
:param base_tree: The common tree in the merge operation
473
:param other_tree: The other other tree to merge changes from
474
:param interesting_ids: The file_ids of files that should be
475
participate in the merge. May not be combined with
477
:param: reprocess If True, perform conflict-reduction processing.
478
:param show_base: If True, show the base revision in text conflicts.
479
(incompatible with reprocess)
480
:param pb: A Progress bar
481
:param pp: A ProgressPhase object
482
:param change_reporter: An object that should report changes made
483
:param interesting_files: The tree-relative paths of files that should
484
participate in the merge. If these paths refer to directories,
485
the contents of those directories will also be included. May not
486
be combined with interesting_ids. If neither interesting_files nor
487
interesting_ids is specified, all files may participate in the
490
object.__init__(self)
491
if interesting_files is not None and interesting_ids is not None:
493
'specify either interesting_ids or interesting_files')
494
self.interesting_ids = interesting_ids
495
self.interesting_files = interesting_files
496
self.this_tree = working_tree
497
self.base_tree = base_tree
498
self.other_tree = other_tree
499
self._raw_conflicts = []
500
self.cooked_conflicts = []
501
self.reprocess = reprocess
502
self.show_base = show_base
505
self.change_reporter = change_reporter
506
self.cherrypick = cherrypick
508
self.pp = ProgressPhase("Merge phase", 3, self.pb)
513
self.this_tree.lock_tree_write()
514
self.base_tree.lock_read()
515
self.other_tree.lock_read()
516
self.tt = TreeTransform(self.this_tree, self.pb)
519
self._compute_transform()
521
results = self.tt.apply(no_conflicts=True)
522
self.write_modified(results)
524
self.this_tree.add_conflicts(self.cooked_conflicts)
525
except UnsupportedOperation:
529
self.other_tree.unlock()
530
self.base_tree.unlock()
531
self.this_tree.unlock()
534
def make_preview_transform(self):
535
self.base_tree.lock_read()
536
self.other_tree.lock_read()
537
self.tt = TransformPreview(self.this_tree)
540
self._compute_transform()
543
self.other_tree.unlock()
544
self.base_tree.unlock()
548
def _compute_transform(self):
549
entries = self._entries3()
550
child_pb = ui.ui_factory.nested_progress_bar()
552
for num, (file_id, changed, parents3, names3,
553
executable3) in enumerate(entries):
554
child_pb.update('Preparing file merge', num, len(entries))
555
self._merge_names(file_id, parents3, names3)
557
file_status = self.merge_contents(file_id)
559
file_status = 'unmodified'
560
self._merge_executable(file_id,
561
executable3, file_status)
566
child_pb = ui.ui_factory.nested_progress_bar()
568
fs_conflicts = resolve_conflicts(self.tt, child_pb,
569
lambda t, c: conflict_pass(t, c, self.other_tree))
572
if self.change_reporter is not None:
573
from bzrlib import delta
574
delta.report_changes(
575
self.tt.iter_changes(), self.change_reporter)
576
self.cook_conflicts(fs_conflicts)
577
for conflict in self.cooked_conflicts:
581
"""Gather data about files modified between three trees.
583
Return a list of tuples of file_id, changed, parents3, names3,
584
executable3. changed is a boolean indicating whether the file contents
585
or kind were changed. parents3 is a tuple of parent ids for base,
586
other and this. names3 is a tuple of names for base, other and this.
587
executable3 is a tuple of execute-bit values for base, other and this.
590
iterator = self.other_tree.iter_changes(self.base_tree,
591
include_unchanged=True, specific_files=self.interesting_files,
592
extra_trees=[self.this_tree])
593
for (file_id, paths, changed, versioned, parents, names, kind,
594
executable) in iterator:
595
if (self.interesting_ids is not None and
596
file_id not in self.interesting_ids):
598
if file_id in self.this_tree.inventory:
599
entry = self.this_tree.inventory[file_id]
600
this_name = entry.name
601
this_parent = entry.parent_id
602
this_executable = entry.executable
606
this_executable = None
607
parents3 = parents + (this_parent,)
608
names3 = names + (this_name,)
609
executable3 = executable + (this_executable,)
610
result.append((file_id, changed, parents3, names3, executable3))
615
self.tt.final_kind(self.tt.root)
617
self.tt.cancel_deletion(self.tt.root)
618
if self.tt.final_file_id(self.tt.root) is None:
619
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
621
if self.other_tree.inventory.root is None:
623
other_root_file_id = self.other_tree.get_root_id()
624
other_root = self.tt.trans_id_file_id(other_root_file_id)
625
if other_root == self.tt.root:
628
self.tt.final_kind(other_root)
631
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
632
self.tt.cancel_creation(other_root)
633
self.tt.cancel_versioning(other_root)
635
def reparent_children(self, ie, target):
636
for thing, child in ie.children.iteritems():
637
trans_id = self.tt.trans_id_file_id(child.file_id)
638
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
640
def write_modified(self, results):
642
for path in results.modified_paths:
643
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
646
hash = self.this_tree.get_file_sha1(file_id)
649
modified_hashes[file_id] = hash
650
self.this_tree.set_merge_modified(modified_hashes)
653
def parent(entry, file_id):
654
"""Determine the parent for a file_id (used as a key method)"""
657
return entry.parent_id
660
def name(entry, file_id):
661
"""Determine the name for a file_id (used as a key method)"""
667
def contents_sha1(tree, file_id):
668
"""Determine the sha1 of the file contents (used as a key method)."""
669
if file_id not in tree:
671
return tree.get_file_sha1(file_id)
674
def executable(tree, file_id):
675
"""Determine the executability of a file-id (used as a key method)."""
676
if file_id not in tree:
678
if tree.kind(file_id) != "file":
680
return tree.is_executable(file_id)
683
def kind(tree, file_id):
684
"""Determine the kind of a file-id (used as a key method)."""
685
if file_id not in tree:
687
return tree.kind(file_id)
690
def _three_way(base, other, this):
691
#if base == other, either they all agree, or only THIS has changed.
694
elif this not in (base, other):
696
# "Ambiguous clean merge" -- both sides have made the same change.
699
# this == base: only other has changed.
704
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
705
"""Do a three-way test on a scalar.
706
Return "this", "other" or "conflict", depending whether a value wins.
708
key_base = key(base_tree, file_id)
709
key_other = key(other_tree, file_id)
710
#if base == other, either they all agree, or only THIS has changed.
711
if key_base == key_other:
713
key_this = key(this_tree, file_id)
714
# "Ambiguous clean merge"
715
if key_this == key_other:
717
elif key_this == key_base:
722
def merge_names(self, file_id):
724
if file_id in tree.inventory:
725
return tree.inventory[file_id]
728
this_entry = get_entry(self.this_tree)
729
other_entry = get_entry(self.other_tree)
730
base_entry = get_entry(self.base_tree)
731
entries = (base_entry, other_entry, this_entry)
734
for entry in entries:
739
names.append(entry.name)
740
parents.append(entry.parent_id)
741
return self._merge_names(file_id, parents, names)
743
def _merge_names(self, file_id, parents, names):
744
"""Perform a merge on file_id names and parents"""
745
base_name, other_name, this_name = names
746
base_parent, other_parent, this_parent = parents
748
name_winner = self._three_way(*names)
750
parent_id_winner = self._three_way(*parents)
751
if this_name is None:
752
if name_winner == "this":
753
name_winner = "other"
754
if parent_id_winner == "this":
755
parent_id_winner = "other"
756
if name_winner == "this" and parent_id_winner == "this":
758
if name_winner == "conflict":
759
trans_id = self.tt.trans_id_file_id(file_id)
760
self._raw_conflicts.append(('name conflict', trans_id,
761
this_name, other_name))
762
if parent_id_winner == "conflict":
763
trans_id = self.tt.trans_id_file_id(file_id)
764
self._raw_conflicts.append(('parent conflict', trans_id,
765
this_parent, other_parent))
766
if other_name is None:
767
# it doesn't matter whether the result was 'other' or
768
# 'conflict'-- if there's no 'other', we leave it alone.
770
# if we get here, name_winner and parent_winner are set to safe values.
771
trans_id = self.tt.trans_id_file_id(file_id)
772
parent_id = parents[self.winner_idx[parent_id_winner]]
773
if parent_id is not None:
774
parent_trans_id = self.tt.trans_id_file_id(parent_id)
775
self.tt.adjust_path(names[self.winner_idx[name_winner]],
776
parent_trans_id, trans_id)
778
def merge_contents(self, file_id):
779
"""Performa a merge on file_id contents."""
780
def contents_pair(tree):
781
if file_id not in tree:
783
kind = tree.kind(file_id)
785
contents = tree.get_file_sha1(file_id)
786
elif kind == "symlink":
787
contents = tree.get_symlink_target(file_id)
790
return kind, contents
792
def contents_conflict():
793
trans_id = self.tt.trans_id_file_id(file_id)
794
name = self.tt.final_name(trans_id)
795
parent_id = self.tt.final_parent(trans_id)
796
if file_id in self.this_tree.inventory:
797
self.tt.unversion_file(trans_id)
798
if file_id in self.this_tree:
799
self.tt.delete_contents(trans_id)
800
file_group = self._dump_conflicts(name, parent_id, file_id,
802
self._raw_conflicts.append(('contents conflict', file_group))
804
# See SPOT run. run, SPOT, run.
805
# So we're not QUITE repeating ourselves; we do tricky things with
807
base_pair = contents_pair(self.base_tree)
808
other_pair = contents_pair(self.other_tree)
809
if base_pair == other_pair:
810
# OTHER introduced no changes
812
this_pair = contents_pair(self.this_tree)
813
if this_pair == other_pair:
814
# THIS and OTHER introduced the same changes
817
trans_id = self.tt.trans_id_file_id(file_id)
818
if this_pair == base_pair:
819
# only OTHER introduced changes
820
if file_id in self.this_tree:
821
# Remove any existing contents
822
self.tt.delete_contents(trans_id)
823
if file_id in self.other_tree:
824
# OTHER changed the file
825
create_by_entry(self.tt,
826
self.other_tree.inventory[file_id],
827
self.other_tree, trans_id)
828
if file_id not in self.this_tree.inventory:
829
self.tt.version_file(file_id, trans_id)
831
elif file_id in self.this_tree.inventory:
832
# OTHER deleted the file
833
self.tt.unversion_file(trans_id)
835
#BOTH THIS and OTHER introduced changes; scalar conflict
836
elif this_pair[0] == "file" and other_pair[0] == "file":
837
# THIS and OTHER are both files, so text merge. Either
838
# BASE is a file, or both converted to files, so at least we
839
# have agreement that output should be a file.
841
self.text_merge(file_id, trans_id)
843
return contents_conflict()
844
if file_id not in self.this_tree.inventory:
845
self.tt.version_file(file_id, trans_id)
847
self.tt.tree_kind(trans_id)
848
self.tt.delete_contents(trans_id)
853
# Scalar conflict, can't text merge. Dump conflicts
854
return contents_conflict()
856
def get_lines(self, tree, file_id):
857
"""Return the lines in a file, or an empty list."""
859
return tree.get_file(file_id).readlines()
863
def text_merge(self, file_id, trans_id):
864
"""Perform a three-way text merge on a file_id"""
865
# it's possible that we got here with base as a different type.
866
# if so, we just want two-way text conflicts.
867
if file_id in self.base_tree and \
868
self.base_tree.kind(file_id) == "file":
869
base_lines = self.get_lines(self.base_tree, file_id)
872
other_lines = self.get_lines(self.other_tree, file_id)
873
this_lines = self.get_lines(self.this_tree, file_id)
874
m3 = Merge3(base_lines, this_lines, other_lines,
875
is_cherrypick=self.cherrypick)
876
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
877
if self.show_base is True:
878
base_marker = '|' * 7
882
def iter_merge3(retval):
883
retval["text_conflicts"] = False
884
for line in m3.merge_lines(name_a = "TREE",
885
name_b = "MERGE-SOURCE",
886
name_base = "BASE-REVISION",
887
start_marker=start_marker,
888
base_marker=base_marker,
889
reprocess=self.reprocess):
890
if line.startswith(start_marker):
891
retval["text_conflicts"] = True
892
yield line.replace(start_marker, '<' * 7)
896
merge3_iterator = iter_merge3(retval)
897
self.tt.create_file(merge3_iterator, trans_id)
898
if retval["text_conflicts"] is True:
899
self._raw_conflicts.append(('text conflict', trans_id))
900
name = self.tt.final_name(trans_id)
901
parent_id = self.tt.final_parent(trans_id)
902
file_group = self._dump_conflicts(name, parent_id, file_id,
903
this_lines, base_lines,
905
file_group.append(trans_id)
907
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
908
base_lines=None, other_lines=None, set_version=False,
910
"""Emit conflict files.
911
If this_lines, base_lines, or other_lines are omitted, they will be
912
determined automatically. If set_version is true, the .OTHER, .THIS
913
or .BASE (in that order) will be created as versioned files.
915
data = [('OTHER', self.other_tree, other_lines),
916
('THIS', self.this_tree, this_lines)]
918
data.append(('BASE', self.base_tree, base_lines))
921
for suffix, tree, lines in data:
923
trans_id = self._conflict_file(name, parent_id, tree, file_id,
925
file_group.append(trans_id)
926
if set_version and not versioned:
927
self.tt.version_file(file_id, trans_id)
931
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
933
"""Emit a single conflict file."""
934
name = name + '.' + suffix
935
trans_id = self.tt.create_path(name, parent_id)
936
entry = tree.inventory[file_id]
937
create_by_entry(self.tt, entry, tree, trans_id, lines)
940
def merge_executable(self, file_id, file_status):
941
"""Perform a merge on the execute bit."""
942
executable = [self.executable(t, file_id) for t in (self.base_tree,
943
self.other_tree, self.this_tree)]
944
self._merge_executable(file_id, executable, file_status)
946
def _merge_executable(self, file_id, executable, file_status):
947
"""Perform a merge on the execute bit."""
948
base_executable, other_executable, this_executable = executable
949
if file_status == "deleted":
951
winner = self._three_way(*executable)
952
if winner == "conflict":
953
# There must be a None in here, if we have a conflict, but we
954
# need executability since file status was not deleted.
955
if self.executable(self.other_tree, file_id) is None:
959
if winner == 'this' and file_status != "modified":
961
trans_id = self.tt.trans_id_file_id(file_id)
963
if self.tt.final_kind(trans_id) != "file":
968
executability = this_executable
970
if file_id in self.other_tree:
971
executability = other_executable
972
elif file_id in self.this_tree:
973
executability = this_executable
974
elif file_id in self.base_tree:
975
executability = base_executable
976
if executability is not None:
977
trans_id = self.tt.trans_id_file_id(file_id)
978
self.tt.set_executability(executability, trans_id)
980
def cook_conflicts(self, fs_conflicts):
981
"""Convert all conflicts into a form that doesn't depend on trans_id"""
982
from conflicts import Conflict
984
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
985
fp = FinalPaths(self.tt)
986
for conflict in self._raw_conflicts:
987
conflict_type = conflict[0]
988
if conflict_type in ('name conflict', 'parent conflict'):
989
trans_id = conflict[1]
990
conflict_args = conflict[2:]
991
if trans_id not in name_conflicts:
992
name_conflicts[trans_id] = {}
993
unique_add(name_conflicts[trans_id], conflict_type,
995
if conflict_type == 'contents conflict':
996
for trans_id in conflict[1]:
997
file_id = self.tt.final_file_id(trans_id)
998
if file_id is not None:
1000
path = fp.get_path(trans_id)
1001
for suffix in ('.BASE', '.THIS', '.OTHER'):
1002
if path.endswith(suffix):
1003
path = path[:-len(suffix)]
1005
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1006
self.cooked_conflicts.append(c)
1007
if conflict_type == 'text conflict':
1008
trans_id = conflict[1]
1009
path = fp.get_path(trans_id)
1010
file_id = self.tt.final_file_id(trans_id)
1011
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1012
self.cooked_conflicts.append(c)
1014
for trans_id, conflicts in name_conflicts.iteritems():
1016
this_parent, other_parent = conflicts['parent conflict']
1017
if this_parent == other_parent:
1018
raise AssertionError()
1020
this_parent = other_parent = \
1021
self.tt.final_file_id(self.tt.final_parent(trans_id))
1023
this_name, other_name = conflicts['name conflict']
1024
if this_name == other_name:
1025
raise AssertionError()
1027
this_name = other_name = self.tt.final_name(trans_id)
1028
other_path = fp.get_path(trans_id)
1029
if this_parent is not None and this_name is not None:
1030
this_parent_path = \
1031
fp.get_path(self.tt.trans_id_file_id(this_parent))
1032
this_path = pathjoin(this_parent_path, this_name)
1034
this_path = "<deleted>"
1035
file_id = self.tt.final_file_id(trans_id)
1036
c = Conflict.factory('path conflict', path=this_path,
1037
conflict_path=other_path, file_id=file_id)
1038
self.cooked_conflicts.append(c)
1039
self.cooked_conflicts.sort(key=Conflict.sort_key)
1042
class WeaveMerger(Merge3Merger):
1043
"""Three-way tree merger, text weave merger."""
1044
supports_reprocess = True
1045
supports_show_base = False
1046
supports_reverse_cherrypick = False
1047
history_based = True
1049
def _merged_lines(self, file_id):
1050
"""Generate the merged lines.
1051
There is no distinction between lines that are meant to contain <<<<<<<
1055
base = self.base_tree
1058
plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
1060
if 'merge' in debug.debug_flags:
1062
trans_id = self.tt.trans_id_file_id(file_id)
1063
name = self.tt.final_name(trans_id) + '.plan'
1064
contents = ('%10s|%s' % l for l in plan)
1065
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1066
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1067
'>>>>>>> MERGE-SOURCE\n')
1068
return textmerge.merge_lines(self.reprocess)
1070
def text_merge(self, file_id, trans_id):
1071
"""Perform a (weave) text merge for a given file and file-id.
1072
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1073
and a conflict will be noted.
1075
lines, conflicts = self._merged_lines(file_id)
1077
# Note we're checking whether the OUTPUT is binary in this case,
1078
# because we don't want to get into weave merge guts.
1079
check_text_lines(lines)
1080
self.tt.create_file(lines, trans_id)
1082
self._raw_conflicts.append(('text conflict', trans_id))
1083
name = self.tt.final_name(trans_id)
1084
parent_id = self.tt.final_parent(trans_id)
1085
file_group = self._dump_conflicts(name, parent_id, file_id,
1087
file_group.append(trans_id)
1090
class LCAMerger(WeaveMerger):
1092
def _merged_lines(self, file_id):
1093
"""Generate the merged lines.
1094
There is no distinction between lines that are meant to contain <<<<<<<
1098
base = self.base_tree
1101
plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1103
if 'merge' in debug.debug_flags:
1105
trans_id = self.tt.trans_id_file_id(file_id)
1106
name = self.tt.final_name(trans_id) + '.plan'
1107
contents = ('%10s|%s' % l for l in plan)
1108
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1109
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1110
'>>>>>>> MERGE-SOURCE\n')
1111
return textmerge.merge_lines(self.reprocess)
1114
class Diff3Merger(Merge3Merger):
1115
"""Three-way merger using external diff3 for text merging"""
1117
def dump_file(self, temp_dir, name, tree, file_id):
1118
out_path = pathjoin(temp_dir, name)
1119
out_file = open(out_path, "wb")
1121
in_file = tree.get_file(file_id)
1122
for line in in_file:
1123
out_file.write(line)
1128
def text_merge(self, file_id, trans_id):
1129
"""Perform a diff3 merge using a specified file-id and trans-id.
1130
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1131
will be dumped, and a will be conflict noted.
1134
temp_dir = osutils.mkdtemp(prefix="bzr-")
1136
new_file = pathjoin(temp_dir, "new")
1137
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1138
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1139
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1140
status = bzrlib.patch.diff3(new_file, this, base, other)
1141
if status not in (0, 1):
1142
raise BzrError("Unhandled diff3 exit code")
1143
f = open(new_file, 'rb')
1145
self.tt.create_file(f, trans_id)
1149
name = self.tt.final_name(trans_id)
1150
parent_id = self.tt.final_parent(trans_id)
1151
self._dump_conflicts(name, parent_id, file_id)
1152
self._raw_conflicts.append(('text conflict', trans_id))
1154
osutils.rmtree(temp_dir)
1157
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1159
merge_type=Merge3Merger,
1160
interesting_ids=None,
1164
interesting_files=None,
1167
change_reporter=None):
1168
"""Primary interface for merging.
1170
typical use is probably
1171
'merge_inner(branch, branch.get_revision_tree(other_revision),
1172
branch.get_revision_tree(base_revision))'
1174
if this_tree is None:
1175
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1176
"parameter as of bzrlib version 0.8.")
1177
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1178
pb=pb, change_reporter=change_reporter)
1179
merger.backup_files = backup_files
1180
merger.merge_type = merge_type
1181
merger.interesting_ids = interesting_ids
1182
merger.ignore_zero = ignore_zero
1183
if interesting_files:
1185
raise ValueError('Only supply interesting_ids'
1186
' or interesting_files')
1187
merger.interesting_files = interesting_files
1188
merger.show_base = show_base
1189
merger.reprocess = reprocess
1190
merger.other_rev_id = other_rev_id
1191
merger.other_basis = other_rev_id
1192
get_revision_id = getattr(base_tree, 'get_revision_id', None)
1193
if get_revision_id is None:
1194
get_revision_id = base_tree.last_revision
1195
merger.set_base_revision(get_revision_id(), this_branch)
1196
return merger.do_merge()
1198
def get_merge_type_registry():
1199
"""Merge type registry is in bzrlib.option to avoid circular imports.
1201
This method provides a sanctioned way to retrieve it.
1203
from bzrlib import option
1204
return option._merge_type_registry
1207
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1208
def status_a(revision, text):
1209
if revision in ancestors_b:
1210
return 'killed-b', text
1212
return 'new-a', text
1214
def status_b(revision, text):
1215
if revision in ancestors_a:
1216
return 'killed-a', text
1218
return 'new-b', text
1220
plain_a = [t for (a, t) in annotated_a]
1221
plain_b = [t for (a, t) in annotated_b]
1222
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1223
blocks = matcher.get_matching_blocks()
1226
for ai, bi, l in blocks:
1227
# process all mismatched sections
1228
# (last mismatched section is handled because blocks always
1229
# includes a 0-length last block)
1230
for revision, text in annotated_a[a_cur:ai]:
1231
yield status_a(revision, text)
1232
for revision, text in annotated_b[b_cur:bi]:
1233
yield status_b(revision, text)
1234
# and now the matched section
1237
for text_a in plain_a[ai:a_cur]:
1238
yield "unchanged", text_a
1241
class _PlanMergeBase(object):
1243
def __init__(self, a_rev, b_rev, vf):
1246
:param a_rev: Revision-id of one revision to merge
1247
:param b_rev: Revision-id of the other revision to merge
1248
:param vf: A versionedfile containing both revisions
1252
self.lines_a = vf.get_lines(a_rev)
1253
self.lines_b = vf.get_lines(b_rev)
1255
self._last_lines = None
1256
self._last_lines_revision_id = None
1257
self._cached_matching_blocks = {}
1259
def plan_merge(self):
1260
"""Generate a 'plan' for merging the two revisions.
1262
This involves comparing their texts and determining the cause of
1263
differences. If text A has a line and text B does not, then either the
1264
line was added to text A, or it was deleted from B. Once the causes
1265
are combined, they are written out in the format described in
1266
VersionedFile.plan_merge
1268
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1269
unique_a, unique_b = self._unique_lines(blocks)
1270
new_a, killed_b = self._determine_status(self.a_rev, unique_a)
1271
new_b, killed_a = self._determine_status(self.b_rev, unique_b)
1272
return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
1274
def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
1277
for i, j, n in blocks:
1278
for a_index in range(last_i, i):
1279
if a_index in new_a:
1280
if a_index in killed_b:
1281
yield 'conflicted-a', self.lines_a[a_index]
1283
yield 'new-a', self.lines_a[a_index]
1285
yield 'killed-b', self.lines_a[a_index]
1286
for b_index in range(last_j, j):
1287
if b_index in new_b:
1288
if b_index in killed_a:
1289
yield 'conflicted-b', self.lines_b[b_index]
1291
yield 'new-b', self.lines_b[b_index]
1293
yield 'killed-a', self.lines_b[b_index]
1294
# handle common lines
1295
for a_index in range(i, i+n):
1296
yield 'unchanged', self.lines_a[a_index]
1300
def _get_matching_blocks(self, left_revision, right_revision):
1301
"""Return a description of which sections of two revisions match.
1303
See SequenceMatcher.get_matching_blocks
1305
cached = self._cached_matching_blocks.get((left_revision,
1307
if cached is not None:
1309
if self._last_lines_revision_id == left_revision:
1310
left_lines = self._last_lines
1312
left_lines = self.vf.get_lines(left_revision)
1313
right_lines = self.vf.get_lines(right_revision)
1314
self._last_lines = right_lines
1315
self._last_lines_revision_id = right_revision
1316
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1318
return matcher.get_matching_blocks()
1320
def _unique_lines(self, matching_blocks):
1321
"""Analyse matching_blocks to determine which lines are unique
1323
:return: a tuple of (unique_left, unique_right), where the values are
1324
sets of line numbers of unique lines.
1330
for i, j, n in matching_blocks:
1331
unique_left.extend(range(last_i, i))
1332
unique_right.extend(range(last_j, j))
1335
return unique_left, unique_right
1338
def _subtract_plans(old_plan, new_plan):
1339
"""Remove changes from new_plan that came from old_plan.
1341
It is assumed that the difference between the old_plan and new_plan
1342
is their choice of 'b' text.
1344
All lines from new_plan that differ from old_plan are emitted
1345
verbatim. All lines from new_plan that match old_plan but are
1346
not about the 'b' revision are emitted verbatim.
1348
Lines that match and are about the 'b' revision are the lines we
1349
don't want, so we convert 'killed-b' -> 'unchanged', and 'new-b'
1350
is skipped entirely.
1352
matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1355
for i, j, n in matcher.get_matching_blocks():
1356
for jj in range(last_j, j):
1358
for jj in range(j, j+n):
1359
plan_line = new_plan[jj]
1360
if plan_line[0] == 'new-b':
1362
elif plan_line[0] == 'killed-b':
1363
yield 'unchanged', plan_line[1]
1369
class _PlanMerge(_PlanMergeBase):
1370
"""Plan an annotate merge using on-the-fly annotation"""
1372
def __init__(self, a_rev, b_rev, vf):
1373
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1374
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1375
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1376
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1378
def _determine_status(self, revision_id, unique_line_numbers):
1379
"""Determines the status unique lines versus all lcas.
1381
Basically, determines why the line is unique to this revision.
1383
A line may be determined new or killed, but not both.
1385
:param revision_id: The id of the revision in which the lines are
1387
:param unique_line_numbers: The line numbers of unique lines.
1388
:return a tuple of (new_this, killed_other):
1390
new = self._find_new(revision_id)
1391
killed = set(unique_line_numbers).difference(new)
1394
def _find_new(self, version_id):
1395
"""Determine which lines are new in the ancestry of this version.
1397
If a lines is present in this version, and not present in any
1398
common ancestor, it is considered new.
1400
if version_id not in self.uncommon:
1402
parents = self.vf.get_parent_map([version_id])[version_id]
1403
if len(parents) == 0:
1404
return set(range(len(self.vf.get_lines(version_id))))
1406
for parent in parents:
1407
blocks = self._get_matching_blocks(version_id, parent)
1408
result, unused = self._unique_lines(blocks)
1409
parent_new = self._find_new(parent)
1410
for i, j, n in blocks:
1411
for ii, jj in [(i+r, j+r) for r in range(n)]:
1412
if jj in parent_new:
1417
new.intersection_update(result)
1421
class _PlanLCAMerge(_PlanMergeBase):
1423
This merge algorithm differs from _PlanMerge in that:
1424
1. comparisons are done against LCAs only
1425
2. cases where a contested line is new versus one LCA but old versus
1426
another are marked as conflicts, by emitting the line as conflicted-a
1429
This is faster, and hopefully produces more useful output.
1432
def __init__(self, a_rev, b_rev, vf, graph):
1433
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1434
self.lcas = graph.find_lca(a_rev, b_rev)
1435
for lca in self.lcas:
1436
lca_lines = self.vf.get_lines(lca)
1437
matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
1439
blocks = list(matcher.get_matching_blocks())
1440
self._cached_matching_blocks[(a_rev, lca)] = blocks
1441
matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_b,
1443
blocks = list(matcher.get_matching_blocks())
1444
self._cached_matching_blocks[(b_rev, lca)] = blocks
1446
def _determine_status(self, revision_id, unique_line_numbers):
1447
"""Determines the status unique lines versus all lcas.
1449
Basically, determines why the line is unique to this revision.
1451
A line may be determined new, killed, or both.
1453
If a line is determined new, that means it was not present in at least
1454
one LCA, and is not present in the other merge revision.
1456
If a line is determined killed, that means the line was present in
1459
If a line is killed and new, this indicates that the two merge
1460
revisions contain differing conflict resolutions.
1461
:param revision_id: The id of the revision in which the lines are
1463
:param unique_line_numbers: The line numbers of unique lines.
1464
:return a tuple of (new_this, killed_other):
1468
unique_line_numbers = set(unique_line_numbers)
1469
for lca in self.lcas:
1470
blocks = self._get_matching_blocks(revision_id, lca)
1471
unique_vs_lca, _ignored = self._unique_lines(blocks)
1472
new.update(unique_line_numbers.intersection(unique_vs_lca))
1473
killed.update(unique_line_numbers.difference(unique_vs_lca))