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
 
 
26
from bzrlib.branch import Branch
 
 
27
from bzrlib.conflicts import ConflictList, Conflict
 
 
28
from bzrlib.errors import (BzrCommandError,
 
 
38
                           WorkingTreeNotRevision,
 
 
41
from bzrlib.merge3 import Merge3
 
 
42
from bzrlib.osutils import rename, pathjoin
 
 
43
from progress import DummyProgress, ProgressPhase
 
 
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
 
 
45
from bzrlib.textfile import check_text_lines
 
 
46
from bzrlib.trace import mutter, warning, note
 
 
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
 
 
48
                              FinalPaths, create_by_entry, unique_add,
 
 
50
from bzrlib.versionedfile import WeaveMerge
 
 
53
# TODO: Report back as changes are merged in
 
 
55
def _get_tree(treespec, local_branch=None):
 
 
56
    from bzrlib import workingtree
 
 
57
    location, revno = treespec
 
 
59
        tree = workingtree.WorkingTree.open_containing(location)[0]
 
 
60
        return tree.branch, tree
 
 
61
    branch = Branch.open_containing(location)[0]
 
 
63
        revision = branch.last_revision()
 
 
65
        revision = branch.get_rev_id(revno)
 
 
67
            revision = NULL_REVISION
 
 
68
    return branch, _get_revid_tree(branch, revision, local_branch)
 
 
71
def _get_revid_tree(branch, revision, local_branch):
 
 
73
        base_tree = branch.bzrdir.open_workingtree()
 
 
75
        if local_branch is not None:
 
 
76
            if local_branch.base != branch.base:
 
 
77
                local_branch.fetch(branch, revision)
 
 
78
            base_tree = local_branch.repository.revision_tree(revision)
 
 
80
            base_tree = branch.repository.revision_tree(revision)
 
 
84
def transform_tree(from_tree, to_tree, interesting_ids=None):
 
 
85
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
 
86
                interesting_ids=interesting_ids, this_tree=from_tree)
 
 
90
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
 
91
                 this_tree=None, pb=DummyProgress(), change_reporter=None):
 
 
93
        assert this_tree is not None, "this_tree is required"
 
 
94
        self.this_branch = this_branch
 
 
95
        self.this_basis = this_branch.last_revision()
 
 
96
        self.this_rev_id = None
 
 
97
        self.this_tree = this_tree
 
 
98
        self.this_revision_tree = None
 
 
99
        self.this_basis_tree = None
 
 
100
        self.other_tree = other_tree
 
 
101
        self.base_tree = base_tree
 
 
102
        self.ignore_zero = False
 
 
103
        self.backup_files = False
 
 
104
        self.interesting_ids = None
 
 
105
        self.show_base = False
 
 
106
        self.reprocess = False
 
 
109
        self.change_reporter = change_reporter
 
 
111
    def revision_tree(self, revision_id):
 
 
112
        return self.this_branch.repository.revision_tree(revision_id)
 
 
114
    def ensure_revision_trees(self):
 
 
115
        if self.this_revision_tree is None:
 
 
116
            self.this_basis_tree = self.this_branch.repository.revision_tree(
 
 
118
            if self.this_basis == self.this_rev_id:
 
 
119
                self.this_revision_tree = self.this_basis_tree
 
 
121
        if self.other_rev_id is None:
 
 
122
            other_basis_tree = self.revision_tree(self.other_basis)
 
 
123
            changes = other_basis_tree.changes_from(self.other_tree)
 
 
124
            if changes.has_changed():
 
 
125
                raise WorkingTreeNotRevision(self.this_tree)
 
 
126
            other_rev_id = self.other_basis
 
 
127
            self.other_tree = other_basis_tree
 
 
129
    def file_revisions(self, file_id):
 
 
130
        self.ensure_revision_trees()
 
 
131
        def get_id(tree, file_id):
 
 
132
            revision_id = tree.inventory[file_id].revision
 
 
133
            assert revision_id is not None
 
 
135
        if self.this_rev_id is None:
 
 
136
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
 
137
                self.this_tree.get_file_sha1(file_id):
 
 
138
                raise WorkingTreeNotRevision(self.this_tree)
 
 
140
        trees = (self.this_basis_tree, self.other_tree)
 
 
141
        return [get_id(tree, file_id) for tree in trees]
 
 
143
    def check_basis(self, check_clean, require_commits=True):
 
 
144
        if self.this_basis is None and require_commits is True:
 
 
145
            raise BzrCommandError("This branch has no commits."
 
 
146
                                  " (perhaps you would prefer 'bzr pull')")
 
 
149
            if self.this_basis != self.this_rev_id:
 
 
150
                raise BzrCommandError("Working tree has uncommitted changes.")
 
 
152
    def compare_basis(self):
 
 
153
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
 
 
154
        if not changes.has_changed():
 
 
155
            self.this_rev_id = self.this_basis
 
 
157
    def set_interesting_files(self, file_list):
 
 
159
            self._set_interesting_files(file_list)
 
 
160
        except NotVersionedError, e:
 
 
161
            raise BzrCommandError("%s is not a source file in any"
 
 
164
    def _set_interesting_files(self, file_list):
 
 
165
        """Set the list of interesting ids from a list of files."""
 
 
166
        if file_list is None:
 
 
167
            self.interesting_ids = None
 
 
170
        interesting_ids = set()
 
 
171
        for path in file_list:
 
 
173
            # TODO: jam 20070226 The trees are not locked at this time,
 
 
174
            #       wouldn't it make merge faster if it locks everything in the
 
 
175
            #       beginning? It locks at do_merge time, but this happens
 
 
177
            for tree in (self.this_tree, self.base_tree, self.other_tree):
 
 
178
                file_id = tree.path2id(path)
 
 
179
                if file_id is not None:
 
 
180
                    interesting_ids.add(file_id)
 
 
183
                raise NotVersionedError(path=path)
 
 
184
        self.interesting_ids = interesting_ids
 
 
186
    def set_pending(self):
 
 
187
        if not self.base_is_ancestor:
 
 
189
        if self.other_rev_id is None:
 
 
191
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
 
 
192
        if self.other_rev_id in ancestry:
 
 
194
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
 
 
196
    def set_other(self, other_revision):
 
 
197
        """Set the revision and tree to merge from.
 
 
199
        This sets the other_tree, other_rev_id, other_basis attributes.
 
 
201
        :param other_revision: The [path, revision] list to merge from.
 
 
203
        other_branch, self.other_tree = _get_tree(other_revision,
 
 
205
        if other_revision[1] == -1:
 
 
206
            self.other_rev_id = other_branch.last_revision()
 
 
207
            if self.other_rev_id is None:
 
 
208
                raise NoCommits(other_branch)
 
 
209
            self.other_basis = self.other_rev_id
 
 
210
        elif other_revision[1] is not None:
 
 
211
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
 
212
            self.other_basis = self.other_rev_id
 
 
214
            self.other_rev_id = None
 
 
215
            self.other_basis = other_branch.last_revision()
 
 
216
            if self.other_basis is None:
 
 
217
                raise NoCommits(other_branch)
 
 
218
        if other_branch.base != self.this_branch.base:
 
 
219
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
 
222
        self.set_base([None, None])
 
 
224
    def set_base(self, base_revision):
 
 
225
        """Set the base revision to use for the merge.
 
 
227
        :param base_revision: A 2-list containing a path and revision number.
 
 
229
        mutter("doing merge() with no base_revision specified")
 
 
230
        if base_revision == [None, None]:
 
 
232
                pb = ui.ui_factory.nested_progress_bar()
 
 
234
                    this_repo = self.this_branch.repository
 
 
235
                    self.base_rev_id = common_ancestor(self.this_basis, 
 
 
240
            except NoCommonAncestor:
 
 
241
                raise UnrelatedBranches()
 
 
242
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
 
 
244
            self.base_is_ancestor = True
 
 
246
            base_branch, self.base_tree = _get_tree(base_revision)
 
 
247
            if base_revision[1] == -1:
 
 
248
                self.base_rev_id = base_branch.last_revision()
 
 
249
            elif base_revision[1] is None:
 
 
250
                self.base_rev_id = None
 
 
252
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
 
 
253
            if self.this_branch.base != base_branch.base:
 
 
254
                self.this_branch.fetch(base_branch)
 
 
255
            self.base_is_ancestor = is_ancestor(self.this_basis, 
 
 
260
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
 
261
                  'other_tree': self.other_tree,
 
 
262
                  'interesting_ids': self.interesting_ids,
 
 
264
        if self.merge_type.requires_base:
 
 
265
            kwargs['base_tree'] = self.base_tree
 
 
266
        if self.merge_type.supports_reprocess:
 
 
267
            kwargs['reprocess'] = self.reprocess
 
 
269
            raise BzrError("Conflict reduction is not supported for merge"
 
 
270
                                  " type %s." % self.merge_type)
 
 
271
        if self.merge_type.supports_show_base:
 
 
272
            kwargs['show_base'] = self.show_base
 
 
274
            raise BzrError("Showing base is not supported for this"
 
 
275
                                  " merge type. %s" % self.merge_type)
 
 
276
        self.this_tree.lock_tree_write()
 
 
277
        if self.base_tree is not None:
 
 
278
            self.base_tree.lock_read()
 
 
279
        if self.other_tree is not None:
 
 
280
            self.other_tree.lock_read()
 
 
282
            merge = self.merge_type(pb=self._pb,
 
 
283
                                    change_reporter=self.change_reporter,
 
 
286
            if self.other_tree is not None:
 
 
287
                self.other_tree.unlock()
 
 
288
            if self.base_tree is not None:
 
 
289
                self.base_tree.unlock()
 
 
290
            self.this_tree.unlock()
 
 
291
        if len(merge.cooked_conflicts) == 0:
 
 
292
            if not self.ignore_zero:
 
 
293
                note("All changes applied successfully.")
 
 
295
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
 
 
297
        return len(merge.cooked_conflicts)
 
 
299
    def regen_inventory(self, new_entries):
 
 
300
        old_entries = self.this_tree.read_working_inventory()
 
 
304
        for path, file_id in new_entries:
 
 
307
            new_entries_map[file_id] = path
 
 
309
        def id2path(file_id):
 
 
310
            path = new_entries_map.get(file_id)
 
 
313
            entry = old_entries[file_id]
 
 
314
            if entry.parent_id is None:
 
 
316
            return pathjoin(id2path(entry.parent_id), entry.name)
 
 
318
        for file_id in old_entries:
 
 
319
            entry = old_entries[file_id]
 
 
320
            path = id2path(file_id)
 
 
321
            if file_id in self.base_tree.inventory:
 
 
322
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
 
324
                executable = getattr(entry, 'executable', False)
 
 
325
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
 
 
326
                                      entry.kind, executable)
 
 
328
            by_path[path] = file_id
 
 
333
        for path, file_id in new_entries:
 
 
335
                del new_inventory[file_id]
 
 
338
                new_path_list.append((path, file_id))
 
 
339
                if file_id not in old_entries:
 
 
341
        # Ensure no file is added before its parent
 
 
343
        for path, file_id in new_path_list:
 
 
347
                parent = by_path[os.path.dirname(path)]
 
 
348
            abspath = pathjoin(self.this_tree.basedir, path)
 
 
349
            kind = osutils.file_kind(abspath)
 
 
350
            if file_id in self.base_tree.inventory:
 
 
351
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
 
354
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
 
 
355
            by_path[path] = file_id 
 
 
357
        # Get a list in insertion order
 
 
358
        new_inventory_list = new_inventory.values()
 
 
359
        mutter ("""Inventory regeneration:
 
 
360
    old length: %i insertions: %i deletions: %i new_length: %i"""\
 
 
361
            % (len(old_entries), insertions, deletions, 
 
 
362
               len(new_inventory_list)))
 
 
363
        assert len(new_inventory_list) == len(old_entries) + insertions\
 
 
365
        new_inventory_list.sort()
 
 
366
        return new_inventory_list
 
 
369
class Merge3Merger(object):
 
 
370
    """Three-way merger that uses the merge3 text merger"""
 
 
372
    supports_reprocess = True
 
 
373
    supports_show_base = True
 
 
374
    history_based = False
 
 
376
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
 
 
377
                 interesting_ids=None, reprocess=False, show_base=False,
 
 
378
                 pb=DummyProgress(), pp=None, change_reporter=None):
 
 
379
        """Initialize the merger object and perform the merge."""
 
 
380
        object.__init__(self)
 
 
381
        self.this_tree = working_tree
 
 
382
        self.this_tree.lock_tree_write()
 
 
383
        self.base_tree = base_tree
 
 
384
        self.base_tree.lock_read()
 
 
385
        self.other_tree = other_tree
 
 
386
        self.other_tree.lock_read()
 
 
387
        self._raw_conflicts = []
 
 
388
        self.cooked_conflicts = []
 
 
389
        self.reprocess = reprocess
 
 
390
        self.show_base = show_base
 
 
393
        self.change_reporter = change_reporter
 
 
395
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
 
397
        if interesting_ids is not None:
 
 
398
            all_ids = interesting_ids
 
 
400
            all_ids = set(base_tree)
 
 
401
            all_ids.update(other_tree)
 
 
402
        self.tt = TreeTransform(working_tree, self.pb)
 
 
405
            child_pb = ui.ui_factory.nested_progress_bar()
 
 
407
                for num, file_id in enumerate(all_ids):
 
 
408
                    child_pb.update('Preparing file merge', num, len(all_ids))
 
 
409
                    self.merge_names(file_id)
 
 
410
                    file_status = self.merge_contents(file_id)
 
 
411
                    self.merge_executable(file_id, file_status)
 
 
416
            child_pb = ui.ui_factory.nested_progress_bar()
 
 
418
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
 
421
            if change_reporter is not None:
 
 
422
                from bzrlib import delta
 
 
423
                delta.report_changes(self.tt._iter_changes(), change_reporter)
 
 
424
            self.cook_conflicts(fs_conflicts)
 
 
425
            for conflict in self.cooked_conflicts:
 
 
428
            results = self.tt.apply()
 
 
429
            self.write_modified(results)
 
 
431
                working_tree.add_conflicts(self.cooked_conflicts)
 
 
432
            except UnsupportedOperation:
 
 
436
            self.other_tree.unlock()
 
 
437
            self.base_tree.unlock()
 
 
438
            self.this_tree.unlock()
 
 
443
            self.tt.final_kind(self.tt.root)
 
 
445
            self.tt.cancel_deletion(self.tt.root)
 
 
446
        if self.tt.final_file_id(self.tt.root) is None:
 
 
447
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
 
449
        if self.other_tree.inventory.root is None:
 
 
451
        other_root_file_id = self.other_tree.inventory.root.file_id
 
 
452
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
 
453
        if other_root == self.tt.root:
 
 
456
            self.tt.final_kind(other_root)
 
 
459
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
 
460
        self.tt.cancel_creation(other_root)
 
 
461
        self.tt.cancel_versioning(other_root)
 
 
463
    def reparent_children(self, ie, target):
 
 
464
        for thing, child in ie.children.iteritems():
 
 
465
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
 
466
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
 
468
    def write_modified(self, results):
 
 
470
        for path in results.modified_paths:
 
 
471
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
 
 
474
            hash = self.this_tree.get_file_sha1(file_id)
 
 
477
            modified_hashes[file_id] = hash
 
 
478
        self.this_tree.set_merge_modified(modified_hashes)
 
 
481
    def parent(entry, file_id):
 
 
482
        """Determine the parent for a file_id (used as a key method)"""
 
 
485
        return entry.parent_id
 
 
488
    def name(entry, file_id):
 
 
489
        """Determine the name for a file_id (used as a key method)"""
 
 
495
    def contents_sha1(tree, file_id):
 
 
496
        """Determine the sha1 of the file contents (used as a key method)."""
 
 
497
        if file_id not in tree:
 
 
499
        return tree.get_file_sha1(file_id)
 
 
502
    def executable(tree, file_id):
 
 
503
        """Determine the executability of a file-id (used as a key method)."""
 
 
504
        if file_id not in tree:
 
 
506
        if tree.kind(file_id) != "file":
 
 
508
        return tree.is_executable(file_id)
 
 
511
    def kind(tree, file_id):
 
 
512
        """Determine the kind of a file-id (used as a key method)."""
 
 
513
        if file_id not in tree:
 
 
515
        return tree.kind(file_id)
 
 
518
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
 
 
519
        """Do a three-way test on a scalar.
 
 
520
        Return "this", "other" or "conflict", depending whether a value wins.
 
 
522
        key_base = key(base_tree, file_id)
 
 
523
        key_other = key(other_tree, file_id)
 
 
524
        #if base == other, either they all agree, or only THIS has changed.
 
 
525
        if key_base == key_other:
 
 
527
        key_this = key(this_tree, file_id)
 
 
528
        if key_this not in (key_base, key_other):
 
 
530
        # "Ambiguous clean merge"
 
 
531
        elif key_this == key_other:
 
 
534
            assert key_this == key_base
 
 
537
    def merge_names(self, file_id):
 
 
538
        """Perform a merge on file_id names and parents"""
 
 
540
            if file_id in tree.inventory:
 
 
541
                return tree.inventory[file_id]
 
 
544
        this_entry = get_entry(self.this_tree)
 
 
545
        other_entry = get_entry(self.other_tree)
 
 
546
        base_entry = get_entry(self.base_tree)
 
 
547
        name_winner = self.scalar_three_way(this_entry, base_entry, 
 
 
548
                                            other_entry, file_id, self.name)
 
 
549
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
 
 
550
                                                 other_entry, file_id, 
 
 
552
        if this_entry is None:
 
 
553
            if name_winner == "this":
 
 
554
                name_winner = "other"
 
 
555
            if parent_id_winner == "this":
 
 
556
                parent_id_winner = "other"
 
 
557
        if name_winner == "this" and parent_id_winner == "this":
 
 
559
        if name_winner == "conflict":
 
 
560
            trans_id = self.tt.trans_id_file_id(file_id)
 
 
561
            self._raw_conflicts.append(('name conflict', trans_id, 
 
 
562
                                        self.name(this_entry, file_id), 
 
 
563
                                        self.name(other_entry, file_id)))
 
 
564
        if parent_id_winner == "conflict":
 
 
565
            trans_id = self.tt.trans_id_file_id(file_id)
 
 
566
            self._raw_conflicts.append(('parent conflict', trans_id, 
 
 
567
                                        self.parent(this_entry, file_id), 
 
 
568
                                        self.parent(other_entry, file_id)))
 
 
569
        if other_entry is None:
 
 
570
            # it doesn't matter whether the result was 'other' or 
 
 
571
            # 'conflict'-- if there's no 'other', we leave it alone.
 
 
573
        # if we get here, name_winner and parent_winner are set to safe values.
 
 
574
        winner_entry = {"this": this_entry, "other": other_entry, 
 
 
575
                        "conflict": other_entry}
 
 
576
        trans_id = self.tt.trans_id_file_id(file_id)
 
 
577
        parent_id = winner_entry[parent_id_winner].parent_id
 
 
578
        if parent_id is not None:
 
 
579
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
 
580
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
 
581
                                parent_trans_id, trans_id)
 
 
583
    def merge_contents(self, file_id):
 
 
584
        """Performa a merge on file_id contents."""
 
 
585
        def contents_pair(tree):
 
 
586
            if file_id not in tree:
 
 
588
            kind = tree.kind(file_id)
 
 
590
                contents = tree.get_file_sha1(file_id)
 
 
591
            elif kind == "symlink":
 
 
592
                contents = tree.get_symlink_target(file_id)
 
 
595
            return kind, contents
 
 
597
        def contents_conflict():
 
 
598
            trans_id = self.tt.trans_id_file_id(file_id)
 
 
599
            name = self.tt.final_name(trans_id)
 
 
600
            parent_id = self.tt.final_parent(trans_id)
 
 
601
            if file_id in self.this_tree.inventory:
 
 
602
                self.tt.unversion_file(trans_id)
 
 
603
                if file_id in self.this_tree:
 
 
604
                    self.tt.delete_contents(trans_id)
 
 
605
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
 
607
            self._raw_conflicts.append(('contents conflict', file_group))
 
 
609
        # See SPOT run.  run, SPOT, run.
 
 
610
        # So we're not QUITE repeating ourselves; we do tricky things with
 
 
612
        base_pair = contents_pair(self.base_tree)
 
 
613
        other_pair = contents_pair(self.other_tree)
 
 
614
        if base_pair == other_pair:
 
 
615
            # OTHER introduced no changes
 
 
617
        this_pair = contents_pair(self.this_tree)
 
 
618
        if this_pair == other_pair:
 
 
619
            # THIS and OTHER introduced the same changes
 
 
622
            trans_id = self.tt.trans_id_file_id(file_id)
 
 
623
            if this_pair == base_pair:
 
 
624
                # only OTHER introduced changes
 
 
625
                if file_id in self.this_tree:
 
 
626
                    # Remove any existing contents
 
 
627
                    self.tt.delete_contents(trans_id)
 
 
628
                if file_id in self.other_tree:
 
 
629
                    # OTHER changed the file
 
 
630
                    create_by_entry(self.tt, 
 
 
631
                                    self.other_tree.inventory[file_id], 
 
 
632
                                    self.other_tree, trans_id)
 
 
633
                    if file_id not in self.this_tree.inventory:
 
 
634
                        self.tt.version_file(file_id, trans_id)
 
 
636
                elif file_id in self.this_tree.inventory:
 
 
637
                    # OTHER deleted the file
 
 
638
                    self.tt.unversion_file(trans_id)
 
 
640
            #BOTH THIS and OTHER introduced changes; scalar conflict
 
 
641
            elif this_pair[0] == "file" and other_pair[0] == "file":
 
 
642
                # THIS and OTHER are both files, so text merge.  Either
 
 
643
                # BASE is a file, or both converted to files, so at least we
 
 
644
                # have agreement that output should be a file.
 
 
646
                    self.text_merge(file_id, trans_id)
 
 
648
                    return contents_conflict()
 
 
649
                if file_id not in self.this_tree.inventory:
 
 
650
                    self.tt.version_file(file_id, trans_id)
 
 
652
                    self.tt.tree_kind(trans_id)
 
 
653
                    self.tt.delete_contents(trans_id)
 
 
658
                # Scalar conflict, can't text merge.  Dump conflicts
 
 
659
                return contents_conflict()
 
 
661
    def get_lines(self, tree, file_id):
 
 
662
        """Return the lines in a file, or an empty list."""
 
 
664
            return tree.get_file(file_id).readlines()
 
 
668
    def text_merge(self, file_id, trans_id):
 
 
669
        """Perform a three-way text merge on a file_id"""
 
 
670
        # it's possible that we got here with base as a different type.
 
 
671
        # if so, we just want two-way text conflicts.
 
 
672
        if file_id in self.base_tree and \
 
 
673
            self.base_tree.kind(file_id) == "file":
 
 
674
            base_lines = self.get_lines(self.base_tree, file_id)
 
 
677
        other_lines = self.get_lines(self.other_tree, file_id)
 
 
678
        this_lines = self.get_lines(self.this_tree, file_id)
 
 
679
        m3 = Merge3(base_lines, this_lines, other_lines)
 
 
680
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
 
 
681
        if self.show_base is True:
 
 
682
            base_marker = '|' * 7
 
 
686
        def iter_merge3(retval):
 
 
687
            retval["text_conflicts"] = False
 
 
688
            for line in m3.merge_lines(name_a = "TREE", 
 
 
689
                                       name_b = "MERGE-SOURCE", 
 
 
690
                                       name_base = "BASE-REVISION",
 
 
691
                                       start_marker=start_marker, 
 
 
692
                                       base_marker=base_marker,
 
 
693
                                       reprocess=self.reprocess):
 
 
694
                if line.startswith(start_marker):
 
 
695
                    retval["text_conflicts"] = True
 
 
696
                    yield line.replace(start_marker, '<' * 7)
 
 
700
        merge3_iterator = iter_merge3(retval)
 
 
701
        self.tt.create_file(merge3_iterator, trans_id)
 
 
702
        if retval["text_conflicts"] is True:
 
 
703
            self._raw_conflicts.append(('text conflict', trans_id))
 
 
704
            name = self.tt.final_name(trans_id)
 
 
705
            parent_id = self.tt.final_parent(trans_id)
 
 
706
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
 
707
                                              this_lines, base_lines,
 
 
709
            file_group.append(trans_id)
 
 
711
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, 
 
 
712
                        base_lines=None, other_lines=None, set_version=False,
 
 
714
        """Emit conflict files.
 
 
715
        If this_lines, base_lines, or other_lines are omitted, they will be
 
 
716
        determined automatically.  If set_version is true, the .OTHER, .THIS
 
 
717
        or .BASE (in that order) will be created as versioned files.
 
 
719
        data = [('OTHER', self.other_tree, other_lines), 
 
 
720
                ('THIS', self.this_tree, this_lines)]
 
 
722
            data.append(('BASE', self.base_tree, base_lines))
 
 
725
        for suffix, tree, lines in data:
 
 
727
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
 
 
729
                file_group.append(trans_id)
 
 
730
                if set_version and not versioned:
 
 
731
                    self.tt.version_file(file_id, trans_id)
 
 
735
    def _conflict_file(self, name, parent_id, tree, file_id, suffix, 
 
 
737
        """Emit a single conflict file."""
 
 
738
        name = name + '.' + suffix
 
 
739
        trans_id = self.tt.create_path(name, parent_id)
 
 
740
        entry = tree.inventory[file_id]
 
 
741
        create_by_entry(self.tt, entry, tree, trans_id, lines)
 
 
744
    def merge_executable(self, file_id, file_status):
 
 
745
        """Perform a merge on the execute bit."""
 
 
746
        if file_status == "deleted":
 
 
748
        trans_id = self.tt.trans_id_file_id(file_id)
 
 
750
            if self.tt.final_kind(trans_id) != "file":
 
 
754
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
 
 
755
                                       self.other_tree, file_id, 
 
 
757
        if winner == "conflict":
 
 
758
        # There must be a None in here, if we have a conflict, but we
 
 
759
        # need executability since file status was not deleted.
 
 
760
            if self.executable(self.other_tree, file_id) is None:
 
 
765
            if file_status == "modified":
 
 
766
                executability = self.this_tree.is_executable(file_id)
 
 
767
                if executability is not None:
 
 
768
                    trans_id = self.tt.trans_id_file_id(file_id)
 
 
769
                    self.tt.set_executability(executability, trans_id)
 
 
771
            assert winner == "other"
 
 
772
            if file_id in self.other_tree:
 
 
773
                executability = self.other_tree.is_executable(file_id)
 
 
774
            elif file_id in self.this_tree:
 
 
775
                executability = self.this_tree.is_executable(file_id)
 
 
776
            elif file_id in self.base_tree:
 
 
777
                executability = self.base_tree.is_executable(file_id)
 
 
778
            if executability is not None:
 
 
779
                trans_id = self.tt.trans_id_file_id(file_id)
 
 
780
                self.tt.set_executability(executability, trans_id)
 
 
782
    def cook_conflicts(self, fs_conflicts):
 
 
783
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
 
784
        from conflicts import Conflict
 
 
786
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
 
 
787
        fp = FinalPaths(self.tt)
 
 
788
        for conflict in self._raw_conflicts:
 
 
789
            conflict_type = conflict[0]
 
 
790
            if conflict_type in ('name conflict', 'parent conflict'):
 
 
791
                trans_id = conflict[1]
 
 
792
                conflict_args = conflict[2:]
 
 
793
                if trans_id not in name_conflicts:
 
 
794
                    name_conflicts[trans_id] = {}
 
 
795
                unique_add(name_conflicts[trans_id], conflict_type, 
 
 
797
            if conflict_type == 'contents conflict':
 
 
798
                for trans_id in conflict[1]:
 
 
799
                    file_id = self.tt.final_file_id(trans_id)
 
 
800
                    if file_id is not None:
 
 
802
                path = fp.get_path(trans_id)
 
 
803
                for suffix in ('.BASE', '.THIS', '.OTHER'):
 
 
804
                    if path.endswith(suffix):
 
 
805
                        path = path[:-len(suffix)]
 
 
807
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
 
808
                self.cooked_conflicts.append(c)
 
 
809
            if conflict_type == 'text conflict':
 
 
810
                trans_id = conflict[1]
 
 
811
                path = fp.get_path(trans_id)
 
 
812
                file_id = self.tt.final_file_id(trans_id)
 
 
813
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
 
814
                self.cooked_conflicts.append(c)
 
 
816
        for trans_id, conflicts in name_conflicts.iteritems():
 
 
818
                this_parent, other_parent = conflicts['parent conflict']
 
 
819
                assert this_parent != other_parent
 
 
821
                this_parent = other_parent = \
 
 
822
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
 
824
                this_name, other_name = conflicts['name conflict']
 
 
825
                assert this_name != other_name
 
 
827
                this_name = other_name = self.tt.final_name(trans_id)
 
 
828
            other_path = fp.get_path(trans_id)
 
 
829
            if this_parent is not None:
 
 
831
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
 
832
                this_path = pathjoin(this_parent_path, this_name)
 
 
834
                this_path = "<deleted>"
 
 
835
            file_id = self.tt.final_file_id(trans_id)
 
 
836
            c = Conflict.factory('path conflict', path=this_path,
 
 
837
                                 conflict_path=other_path, file_id=file_id)
 
 
838
            self.cooked_conflicts.append(c)
 
 
839
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
 
842
class WeaveMerger(Merge3Merger):
 
 
843
    """Three-way tree merger, text weave merger."""
 
 
844
    supports_reprocess = True
 
 
845
    supports_show_base = False
 
 
847
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
 
 
848
                 interesting_ids=None, pb=DummyProgress(), pp=None,
 
 
849
                 reprocess=False, change_reporter=None):
 
 
850
        self.this_revision_tree = self._get_revision_tree(this_tree)
 
 
851
        self.other_revision_tree = self._get_revision_tree(other_tree)
 
 
852
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
 
 
853
                                          base_tree, other_tree, 
 
 
854
                                          interesting_ids=interesting_ids, 
 
 
855
                                          pb=pb, pp=pp, reprocess=reprocess,
 
 
856
                                          change_reporter=change_reporter)
 
 
858
    def _get_revision_tree(self, tree):
 
 
859
        """Return a revision tree related to this tree.
 
 
860
        If the tree is a WorkingTree, the basis will be returned.
 
 
862
        if getattr(tree, 'get_weave', False) is False:
 
 
863
            # If we have a WorkingTree, try using the basis
 
 
864
            return tree.branch.basis_tree()
 
 
868
    def _check_file(self, file_id):
 
 
869
        """Check that the revision tree's version of the file matches."""
 
 
870
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
 
 
871
                         (self.other_tree, self.other_revision_tree)):
 
 
874
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
 
 
875
                raise WorkingTreeNotRevision(self.this_tree)
 
 
877
    def _merged_lines(self, file_id):
 
 
878
        """Generate the merged lines.
 
 
879
        There is no distinction between lines that are meant to contain <<<<<<<
 
 
882
        weave = self.this_revision_tree.get_weave(file_id)
 
 
883
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
 
 
884
        other_revision_id = \
 
 
885
            self.other_revision_tree.inventory[file_id].revision
 
 
886
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
 
 
887
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
 
 
888
        return wm.merge_lines(self.reprocess)
 
 
890
    def text_merge(self, file_id, trans_id):
 
 
891
        """Perform a (weave) text merge for a given file and file-id.
 
 
892
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
 
 
893
        and a conflict will be noted.
 
 
895
        self._check_file(file_id)
 
 
896
        lines, conflicts = self._merged_lines(file_id)
 
 
898
        # Note we're checking whether the OUTPUT is binary in this case, 
 
 
899
        # because we don't want to get into weave merge guts.
 
 
900
        check_text_lines(lines)
 
 
901
        self.tt.create_file(lines, trans_id)
 
 
903
            self._raw_conflicts.append(('text conflict', trans_id))
 
 
904
            name = self.tt.final_name(trans_id)
 
 
905
            parent_id = self.tt.final_parent(trans_id)
 
 
906
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
 
908
            file_group.append(trans_id)
 
 
911
class Diff3Merger(Merge3Merger):
 
 
912
    """Three-way merger using external diff3 for text merging"""
 
 
914
    def dump_file(self, temp_dir, name, tree, file_id):
 
 
915
        out_path = pathjoin(temp_dir, name)
 
 
916
        out_file = open(out_path, "wb")
 
 
918
            in_file = tree.get_file(file_id)
 
 
925
    def text_merge(self, file_id, trans_id):
 
 
926
        """Perform a diff3 merge using a specified file-id and trans-id.
 
 
927
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
 
 
928
        will be dumped, and a will be conflict noted.
 
 
931
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
 
933
            new_file = pathjoin(temp_dir, "new")
 
 
934
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
 
 
935
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
 
 
936
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
 
 
937
            status = bzrlib.patch.diff3(new_file, this, base, other)
 
 
938
            if status not in (0, 1):
 
 
939
                raise BzrError("Unhandled diff3 exit code")
 
 
940
            f = open(new_file, 'rb')
 
 
942
                self.tt.create_file(f, trans_id)
 
 
946
                name = self.tt.final_name(trans_id)
 
 
947
                parent_id = self.tt.final_parent(trans_id)
 
 
948
                self._dump_conflicts(name, parent_id, file_id)
 
 
949
                self._raw_conflicts.append(('text conflict', trans_id))
 
 
951
            osutils.rmtree(temp_dir)
 
 
954
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
 
 
956
                merge_type=Merge3Merger,
 
 
957
                interesting_ids=None,
 
 
961
                interesting_files=None,
 
 
964
                change_reporter=None):
 
 
965
    """Primary interface for merging. 
 
 
967
        typical use is probably 
 
 
968
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
 
969
                     branch.get_revision_tree(base_revision))'
 
 
971
    if this_tree is None:
 
 
972
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
 
 
973
             "bzrlib version 0.8.",
 
 
976
        this_tree = this_branch.bzrdir.open_workingtree()
 
 
977
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
 
978
                    pb=pb, change_reporter=change_reporter)
 
 
979
    merger.backup_files = backup_files
 
 
980
    merger.merge_type = merge_type
 
 
981
    merger.interesting_ids = interesting_ids
 
 
982
    merger.ignore_zero = ignore_zero
 
 
983
    if interesting_files:
 
 
984
        assert not interesting_ids, ('Only supply interesting_ids'
 
 
985
                                     ' or interesting_files')
 
 
986
        merger._set_interesting_files(interesting_files)
 
 
987
    merger.show_base = show_base
 
 
988
    merger.reprocess = reprocess
 
 
989
    merger.other_rev_id = other_rev_id
 
 
990
    merger.other_basis = other_rev_id
 
 
991
    return merger.do_merge()
 
 
993
def get_merge_type_registry():
 
 
994
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
 
996
    This method provides a sanctioned way to retrieve it.
 
 
998
    from bzrlib import option
 
 
999
    return option._merge_type_registry