/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Aaron Bentley
  • Date: 2007-02-21 06:06:06 UTC
  • mto: (2255.6.1 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: aaron.bentley@utoronto.ca-20070221060606-unjaailciijp12ab
rename working tree format 4 to AB1 everywhere

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
 
 
18
import os
 
19
import errno
 
20
import warnings
 
21
 
 
22
from bzrlib import (
 
23
    osutils,
 
24
    registry,
 
25
    )
 
26
from bzrlib.branch import Branch
 
27
from bzrlib.conflicts import ConflictList, Conflict
 
28
from bzrlib.errors import (BzrCommandError,
 
29
                           BzrError,
 
30
                           NoCommonAncestor,
 
31
                           NoCommits,
 
32
                           NoSuchRevision,
 
33
                           NoSuchFile,
 
34
                           NotBranchError,
 
35
                           NotVersionedError,
 
36
                           UnrelatedBranches,
 
37
                           UnsupportedOperation,
 
38
                           WorkingTreeNotRevision,
 
39
                           BinaryFile,
 
40
                           )
 
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,
 
49
                              ROOT_PARENT)
 
50
from bzrlib.versionedfile import WeaveMerge
 
51
from bzrlib import ui
 
52
 
 
53
# TODO: Report back as changes are merged in
 
54
 
 
55
def _get_tree(treespec, local_branch=None):
 
56
    from bzrlib import workingtree
 
57
    location, revno = treespec
 
58
    if revno is None:
 
59
        tree = workingtree.WorkingTree.open_containing(location)[0]
 
60
        return tree.branch, tree
 
61
    branch = Branch.open_containing(location)[0]
 
62
    if revno == -1:
 
63
        revision = branch.last_revision()
 
64
    else:
 
65
        revision = branch.get_rev_id(revno)
 
66
        if revision is None:
 
67
            revision = NULL_REVISION
 
68
    return branch, _get_revid_tree(branch, revision, local_branch)
 
69
 
 
70
 
 
71
def _get_revid_tree(branch, revision, local_branch):
 
72
    if revision is None:
 
73
        base_tree = branch.bzrdir.open_workingtree()
 
74
    else:
 
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)
 
79
        else:
 
80
            base_tree = branch.repository.revision_tree(revision)
 
81
    return base_tree
 
82
 
 
83
 
 
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)
 
87
 
 
88
 
 
89
class Merger(object):
 
90
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
 
91
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
92
                 recurse='down'):
 
93
        object.__init__(self)
 
94
        assert this_tree is not None, "this_tree is required"
 
95
        self.this_branch = this_branch
 
96
        self.this_basis = this_branch.last_revision()
 
97
        self.this_rev_id = None
 
98
        self.this_tree = this_tree
 
99
        self.this_revision_tree = None
 
100
        self.this_basis_tree = None
 
101
        self.other_tree = other_tree
 
102
        self.other_branch = None
 
103
        self.base_tree = base_tree
 
104
        self.ignore_zero = False
 
105
        self.backup_files = False
 
106
        self.interesting_ids = None
 
107
        self.show_base = False
 
108
        self.reprocess = False
 
109
        self._pb = pb 
 
110
        self.pp = None
 
111
        self.recurse = recurse
 
112
        self.change_reporter = change_reporter
 
113
 
 
114
    def revision_tree(self, revision_id):
 
115
        return self.this_branch.repository.revision_tree(revision_id)
 
116
 
 
117
    def ensure_revision_trees(self):
 
118
        if self.this_revision_tree is None:
 
119
            self.this_basis_tree = self.this_branch.repository.revision_tree(
 
120
                self.this_basis)
 
121
            if self.this_basis == self.this_rev_id:
 
122
                self.this_revision_tree = self.this_basis_tree
 
123
 
 
124
        if self.other_rev_id is None:
 
125
            other_basis_tree = self.revision_tree(self.other_basis)
 
126
            changes = other_basis_tree.changes_from(self.other_tree)
 
127
            if changes.has_changed():
 
128
                raise WorkingTreeNotRevision(self.this_tree)
 
129
            other_rev_id = self.other_basis
 
130
            self.other_tree = other_basis_tree
 
131
 
 
132
    def file_revisions(self, file_id):
 
133
        self.ensure_revision_trees()
 
134
        def get_id(tree, file_id):
 
135
            revision_id = tree.inventory[file_id].revision
 
136
            assert revision_id is not None
 
137
            return revision_id
 
138
        if self.this_rev_id is None:
 
139
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
140
                self.this_tree.get_file_sha1(file_id):
 
141
                raise WorkingTreeNotRevision(self.this_tree)
 
142
 
 
143
        trees = (self.this_basis_tree, self.other_tree)
 
144
        return [get_id(tree, file_id) for tree in trees]
 
145
 
 
146
    def check_basis(self, check_clean, require_commits=True):
 
147
        if self.this_basis is None and require_commits is True:
 
148
            raise BzrCommandError("This branch has no commits."
 
149
                                  " (perhaps you would prefer 'bzr pull')")
 
150
        if check_clean:
 
151
            self.compare_basis()
 
152
            if self.this_basis != self.this_rev_id:
 
153
                raise BzrCommandError("Working tree has uncommitted changes.")
 
154
 
 
155
    def compare_basis(self):
 
156
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
 
157
        if not changes.has_changed():
 
158
            self.this_rev_id = self.this_basis
 
159
 
 
160
    def set_interesting_files(self, file_list):
 
161
        try:
 
162
            self._set_interesting_files(file_list)
 
163
        except NotVersionedError, e:
 
164
            raise BzrCommandError("%s is not a source file in any"
 
165
                                      " tree." % e.path)
 
166
 
 
167
    def _set_interesting_files(self, file_list):
 
168
        """Set the list of interesting ids from a list of files."""
 
169
        if file_list is None:
 
170
            self.interesting_ids = None
 
171
            return
 
172
 
 
173
        interesting_ids = set()
 
174
        for path in file_list:
 
175
            found_id = False
 
176
            for tree in (self.this_tree, self.base_tree, self.other_tree):
 
177
                file_id = tree.inventory.path2id(path)
 
178
                if file_id is not None:
 
179
                    interesting_ids.add(file_id)
 
180
                    found_id = True
 
181
            if not found_id:
 
182
                raise NotVersionedError(path=path)
 
183
        self.interesting_ids = interesting_ids
 
184
 
 
185
    def set_pending(self):
 
186
        if not self.base_is_ancestor:
 
187
            return
 
188
        if self.other_rev_id is None:
 
189
            return
 
190
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
 
191
        if self.other_rev_id in ancestry:
 
192
            return
 
193
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
 
194
 
 
195
    def set_other(self, other_revision):
 
196
        """Set the revision and tree to merge from.
 
197
 
 
198
        This sets the other_tree, other_rev_id, other_basis attributes.
 
199
 
 
200
        :param other_revision: The [path, revision] list to merge from.
 
201
        """
 
202
        self.other_branch, self.other_tree = _get_tree(other_revision,
 
203
                                                  self.this_branch)
 
204
        if other_revision[1] == -1:
 
205
            self.other_rev_id = self.other_branch.last_revision()
 
206
            if self.other_rev_id is None:
 
207
                raise NoCommits(self.other_branch)
 
208
            self.other_basis = self.other_rev_id
 
209
        elif other_revision[1] is not None:
 
210
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
 
211
            self.other_basis = self.other_rev_id
 
212
        else:
 
213
            self.other_rev_id = None
 
214
            self.other_basis = self.other_branch.last_revision()
 
215
            if self.other_basis is None:
 
216
                raise NoCommits(self.other_branch)
 
217
        if self.other_branch.base != self.this_branch.base:
 
218
            self.this_branch.fetch(self.other_branch,
 
219
                                   last_revision=self.other_basis)
 
220
 
 
221
    def set_other_revision(self, revision_id, other_branch):
 
222
        """Set 'other' based on a branch and revision id
 
223
 
 
224
        :param revision_id: The revision to use for a tree
 
225
        :param other_branch: The branch containing this tree
 
226
        """
 
227
        self.other_rev_id = revision_id
 
228
        self.other_branch = other_branch
 
229
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
230
        self.other_tree = self.revision_tree(revision_id)
 
231
        self.other_basis = revision_id
 
232
 
 
233
    def find_base(self):
 
234
        self.set_base([None, None])
 
235
 
 
236
    def set_base(self, base_revision):
 
237
        """Set the base revision to use for the merge.
 
238
 
 
239
        :param base_revision: A 2-list containing a path and revision number.
 
240
        """
 
241
        mutter("doing merge() with no base_revision specified")
 
242
        if base_revision == [None, None]:
 
243
            try:
 
244
                pb = ui.ui_factory.nested_progress_bar()
 
245
                try:
 
246
                    this_repo = self.this_branch.repository
 
247
                    self.base_rev_id = common_ancestor(self.this_basis, 
 
248
                                                       self.other_basis, 
 
249
                                                       this_repo, pb)
 
250
                finally:
 
251
                    pb.finished()
 
252
            except NoCommonAncestor:
 
253
                raise UnrelatedBranches()
 
254
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
 
255
                                            None)
 
256
            self.base_is_ancestor = True
 
257
        else:
 
258
            base_branch, self.base_tree = _get_tree(base_revision)
 
259
            if base_revision[1] == -1:
 
260
                self.base_rev_id = base_branch.last_revision()
 
261
            elif base_revision[1] is None:
 
262
                self.base_rev_id = None
 
263
            else:
 
264
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
 
265
            if self.this_branch.base != base_branch.base:
 
266
                self.this_branch.fetch(base_branch)
 
267
            self.base_is_ancestor = is_ancestor(self.this_basis, 
 
268
                                                self.base_rev_id,
 
269
                                                self.this_branch)
 
270
 
 
271
    def do_merge(self):
 
272
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
 
273
                  'other_tree': self.other_tree, 
 
274
                  'interesting_ids': self.interesting_ids,
 
275
                  'pp': self.pp}
 
276
        if self.merge_type.requires_base:
 
277
            kwargs['base_tree'] = self.base_tree
 
278
        if self.merge_type.supports_reprocess:
 
279
            kwargs['reprocess'] = self.reprocess
 
280
        elif self.reprocess:
 
281
            raise BzrError("Conflict reduction is not supported for merge"
 
282
                                  " type %s." % self.merge_type)
 
283
        if self.merge_type.supports_show_base:
 
284
            kwargs['show_base'] = self.show_base
 
285
        elif self.show_base:
 
286
            raise BzrError("Showing base is not supported for this"
 
287
                                  " merge type. %s" % self.merge_type)
 
288
        merge = self.merge_type(pb=self._pb,
 
289
                                change_reporter=self.change_reporter,
 
290
                                **kwargs)
 
291
        if len(merge.cooked_conflicts) == 0:
 
292
            if not self.ignore_zero:
 
293
                note("All changes applied successfully.")
 
294
        else:
 
295
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
 
296
 
 
297
        if self.recurse == 'down':
 
298
            for path, entry in self.this_tree.iter_reference_entries():
 
299
                sub_tree = self.this_tree.get_nested_tree(entry, path)
 
300
                other_entry = self.other_tree.inventory[entry.file_id]
 
301
                other_revision = self.other_tree.get_reference_revision(
 
302
                    other_entry, path)
 
303
                if  other_revision == sub_tree.last_revision():
 
304
                    continue
 
305
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
306
                sub_merge.merge_type = self.merge_type
 
307
                other_branch = self.other_branch.reference_parent(
 
308
                    entry.file_id, path)
 
309
                sub_merge.set_other_revision(other_revision, other_branch)
 
310
                base_entry = self.base_tree.inventory[entry.file_id]
 
311
                base_revision = \
 
312
                    self.base_tree.get_reference_revision(base_entry)
 
313
                sub_merge.base_tree = \
 
314
                    sub_tree.branch.repository.revision_tree(base_revision)
 
315
                sub_merge.do_merge()
 
316
 
 
317
        return len(merge.cooked_conflicts)
 
318
 
 
319
    def regen_inventory(self, new_entries):
 
320
        old_entries = self.this_tree.read_working_inventory()
 
321
        new_inventory = {}
 
322
        by_path = {}
 
323
        new_entries_map = {} 
 
324
        for path, file_id in new_entries:
 
325
            if path is None:
 
326
                continue
 
327
            new_entries_map[file_id] = path
 
328
 
 
329
        def id2path(file_id):
 
330
            path = new_entries_map.get(file_id)
 
331
            if path is not None:
 
332
                return path
 
333
            entry = old_entries[file_id]
 
334
            if entry.parent_id is None:
 
335
                return entry.name
 
336
            return pathjoin(id2path(entry.parent_id), entry.name)
 
337
            
 
338
        for file_id in old_entries:
 
339
            entry = old_entries[file_id]
 
340
            path = id2path(file_id)
 
341
            if file_id in self.base_tree.inventory:
 
342
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
343
            else:
 
344
                executable = getattr(entry, 'executable', False)
 
345
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
 
346
                                      entry.kind, executable)
 
347
                                      
 
348
            by_path[path] = file_id
 
349
        
 
350
        deletions = 0
 
351
        insertions = 0
 
352
        new_path_list = []
 
353
        for path, file_id in new_entries:
 
354
            if path is None:
 
355
                del new_inventory[file_id]
 
356
                deletions += 1
 
357
            else:
 
358
                new_path_list.append((path, file_id))
 
359
                if file_id not in old_entries:
 
360
                    insertions += 1
 
361
        # Ensure no file is added before its parent
 
362
        new_path_list.sort()
 
363
        for path, file_id in new_path_list:
 
364
            if path == '':
 
365
                parent = None
 
366
            else:
 
367
                parent = by_path[os.path.dirname(path)]
 
368
            abspath = pathjoin(self.this_tree.basedir, path)
 
369
            kind = osutils.file_kind(abspath)
 
370
            if file_id in self.base_tree.inventory:
 
371
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
 
372
            else:
 
373
                executable = False
 
374
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
 
375
            by_path[path] = file_id 
 
376
 
 
377
        # Get a list in insertion order
 
378
        new_inventory_list = new_inventory.values()
 
379
        mutter ("""Inventory regeneration:
 
380
    old length: %i insertions: %i deletions: %i new_length: %i"""\
 
381
            % (len(old_entries), insertions, deletions, 
 
382
               len(new_inventory_list)))
 
383
        assert len(new_inventory_list) == len(old_entries) + insertions\
 
384
            - deletions
 
385
        new_inventory_list.sort()
 
386
        return new_inventory_list
 
387
 
 
388
 
 
389
class Merge3Merger(object):
 
390
    """Three-way merger that uses the merge3 text merger"""
 
391
    requires_base = True
 
392
    supports_reprocess = True
 
393
    supports_show_base = True
 
394
    history_based = False
 
395
 
 
396
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
 
397
                 interesting_ids=None, reprocess=False, show_base=False,
 
398
                 pb=DummyProgress(), pp=None, change_reporter=None):
 
399
        """Initialize the merger object and perform the merge."""
 
400
        object.__init__(self)
 
401
        self.this_tree = working_tree
 
402
        self.base_tree = base_tree
 
403
        self.other_tree = other_tree
 
404
        self._raw_conflicts = []
 
405
        self.cooked_conflicts = []
 
406
        self.reprocess = reprocess
 
407
        self.show_base = show_base
 
408
        self.pb = pb
 
409
        self.pp = pp
 
410
        self.change_reporter = change_reporter
 
411
        if self.pp is None:
 
412
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
 
413
 
 
414
        if interesting_ids is not None:
 
415
            all_ids = interesting_ids
 
416
        else:
 
417
            all_ids = set(base_tree)
 
418
            all_ids.update(other_tree)
 
419
        working_tree.lock_tree_write()
 
420
        self.tt = TreeTransform(working_tree, self.pb)
 
421
        try:
 
422
            self.pp.next_phase()
 
423
            child_pb = ui.ui_factory.nested_progress_bar()
 
424
            try:
 
425
                for num, file_id in enumerate(all_ids):
 
426
                    child_pb.update('Preparing file merge', num, len(all_ids))
 
427
                    self.merge_names(file_id)
 
428
                    file_status = self.merge_contents(file_id)
 
429
                    self.merge_executable(file_id, file_status)
 
430
            finally:
 
431
                child_pb.finished()
 
432
            self.fix_root()
 
433
            self.pp.next_phase()
 
434
            child_pb = ui.ui_factory.nested_progress_bar()
 
435
            try:
 
436
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
437
            finally:
 
438
                child_pb.finished()
 
439
            if change_reporter is not None:
 
440
                from bzrlib import delta
 
441
                delta.report_changes(self.tt._iter_changes(), change_reporter)
 
442
            self.cook_conflicts(fs_conflicts)
 
443
            for conflict in self.cooked_conflicts:
 
444
                warning(conflict)
 
445
            self.pp.next_phase()
 
446
            results = self.tt.apply()
 
447
            self.write_modified(results)
 
448
            try:
 
449
                working_tree.add_conflicts(self.cooked_conflicts)
 
450
            except UnsupportedOperation:
 
451
                pass
 
452
        finally:
 
453
            self.tt.finalize()
 
454
            working_tree.unlock()
 
455
            self.pb.clear()
 
456
 
 
457
    def fix_root(self):
 
458
        try:
 
459
            self.tt.final_kind(self.tt.root)
 
460
        except NoSuchFile:
 
461
            self.tt.cancel_deletion(self.tt.root)
 
462
        if self.tt.final_file_id(self.tt.root) is None:
 
463
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
464
                                 self.tt.root)
 
465
        if self.other_tree.inventory.root is None:
 
466
            return
 
467
        other_root_file_id = self.other_tree.inventory.root.file_id
 
468
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
469
        if other_root == self.tt.root:
 
470
            return
 
471
        try:
 
472
            self.tt.final_kind(other_root)
 
473
        except NoSuchFile:
 
474
            return
 
475
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
476
        self.tt.cancel_creation(other_root)
 
477
        self.tt.cancel_versioning(other_root)
 
478
 
 
479
    def reparent_children(self, ie, target):
 
480
        for thing, child in ie.children.iteritems():
 
481
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
482
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
483
 
 
484
    def write_modified(self, results):
 
485
        modified_hashes = {}
 
486
        for path in results.modified_paths:
 
487
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
 
488
            if file_id is None:
 
489
                continue
 
490
            hash = self.this_tree.get_file_sha1(file_id)
 
491
            if hash is None:
 
492
                continue
 
493
            modified_hashes[file_id] = hash
 
494
        self.this_tree.set_merge_modified(modified_hashes)
 
495
 
 
496
    @staticmethod
 
497
    def parent(entry, file_id):
 
498
        """Determine the parent for a file_id (used as a key method)"""
 
499
        if entry is None:
 
500
            return None
 
501
        return entry.parent_id
 
502
 
 
503
    @staticmethod
 
504
    def name(entry, file_id):
 
505
        """Determine the name for a file_id (used as a key method)"""
 
506
        if entry is None:
 
507
            return None
 
508
        return entry.name
 
509
    
 
510
    @staticmethod
 
511
    def contents_sha1(tree, file_id):
 
512
        """Determine the sha1 of the file contents (used as a key method)."""
 
513
        if file_id not in tree:
 
514
            return None
 
515
        return tree.get_file_sha1(file_id)
 
516
 
 
517
    @staticmethod
 
518
    def executable(tree, file_id):
 
519
        """Determine the executability of a file-id (used as a key method)."""
 
520
        if file_id not in tree:
 
521
            return None
 
522
        if tree.kind(file_id) != "file":
 
523
            return False
 
524
        return tree.is_executable(file_id)
 
525
 
 
526
    @staticmethod
 
527
    def kind(tree, file_id):
 
528
        """Determine the kind of a file-id (used as a key method)."""
 
529
        if file_id not in tree:
 
530
            return None
 
531
        return tree.kind(file_id)
 
532
 
 
533
    @staticmethod
 
534
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
 
535
        """Do a three-way test on a scalar.
 
536
        Return "this", "other" or "conflict", depending whether a value wins.
 
537
        """
 
538
        key_base = key(base_tree, file_id)
 
539
        key_other = key(other_tree, file_id)
 
540
        #if base == other, either they all agree, or only THIS has changed.
 
541
        if key_base == key_other:
 
542
            return "this"
 
543
        key_this = key(this_tree, file_id)
 
544
        if key_this not in (key_base, key_other):
 
545
            return "conflict"
 
546
        # "Ambiguous clean merge"
 
547
        elif key_this == key_other:
 
548
            return "this"
 
549
        else:
 
550
            assert key_this == key_base
 
551
            return "other"
 
552
 
 
553
    def merge_names(self, file_id):
 
554
        """Perform a merge on file_id names and parents"""
 
555
        def get_entry(tree):
 
556
            if file_id in tree.inventory:
 
557
                return tree.inventory[file_id]
 
558
            else:
 
559
                return None
 
560
        this_entry = get_entry(self.this_tree)
 
561
        other_entry = get_entry(self.other_tree)
 
562
        base_entry = get_entry(self.base_tree)
 
563
        name_winner = self.scalar_three_way(this_entry, base_entry, 
 
564
                                            other_entry, file_id, self.name)
 
565
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
 
566
                                                 other_entry, file_id, 
 
567
                                                 self.parent)
 
568
        if this_entry is None:
 
569
            if name_winner == "this":
 
570
                name_winner = "other"
 
571
            if parent_id_winner == "this":
 
572
                parent_id_winner = "other"
 
573
        if name_winner == "this" and parent_id_winner == "this":
 
574
            return
 
575
        if name_winner == "conflict":
 
576
            trans_id = self.tt.trans_id_file_id(file_id)
 
577
            self._raw_conflicts.append(('name conflict', trans_id, 
 
578
                                        self.name(this_entry, file_id), 
 
579
                                        self.name(other_entry, file_id)))
 
580
        if parent_id_winner == "conflict":
 
581
            trans_id = self.tt.trans_id_file_id(file_id)
 
582
            self._raw_conflicts.append(('parent conflict', trans_id, 
 
583
                                        self.parent(this_entry, file_id), 
 
584
                                        self.parent(other_entry, file_id)))
 
585
        if other_entry is None:
 
586
            # it doesn't matter whether the result was 'other' or 
 
587
            # 'conflict'-- if there's no 'other', we leave it alone.
 
588
            return
 
589
        # if we get here, name_winner and parent_winner are set to safe values.
 
590
        winner_entry = {"this": this_entry, "other": other_entry, 
 
591
                        "conflict": other_entry}
 
592
        trans_id = self.tt.trans_id_file_id(file_id)
 
593
        parent_id = winner_entry[parent_id_winner].parent_id
 
594
        if parent_id is not None:
 
595
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
596
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
597
                                parent_trans_id, trans_id)
 
598
 
 
599
    def merge_contents(self, file_id):
 
600
        """Performa a merge on file_id contents."""
 
601
        def contents_pair(tree):
 
602
            if file_id not in tree:
 
603
                return (None, None)
 
604
            kind = tree.kind(file_id)
 
605
            if kind == "file":
 
606
                contents = tree.get_file_sha1(file_id)
 
607
            elif kind == "symlink":
 
608
                contents = tree.get_symlink_target(file_id)
 
609
            else:
 
610
                contents = None
 
611
            return kind, contents
 
612
 
 
613
        def contents_conflict():
 
614
            trans_id = self.tt.trans_id_file_id(file_id)
 
615
            name = self.tt.final_name(trans_id)
 
616
            parent_id = self.tt.final_parent(trans_id)
 
617
            if file_id in self.this_tree.inventory:
 
618
                self.tt.unversion_file(trans_id)
 
619
                if file_id in self.this_tree:
 
620
                    self.tt.delete_contents(trans_id)
 
621
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
622
                                              set_version=True)
 
623
            self._raw_conflicts.append(('contents conflict', file_group))
 
624
 
 
625
        # See SPOT run.  run, SPOT, run.
 
626
        # So we're not QUITE repeating ourselves; we do tricky things with
 
627
        # file kind...
 
628
        base_pair = contents_pair(self.base_tree)
 
629
        other_pair = contents_pair(self.other_tree)
 
630
        if base_pair == other_pair:
 
631
            # OTHER introduced no changes
 
632
            return "unmodified"
 
633
        this_pair = contents_pair(self.this_tree)
 
634
        if this_pair == other_pair:
 
635
            # THIS and OTHER introduced the same changes
 
636
            return "unmodified"
 
637
        else:
 
638
            trans_id = self.tt.trans_id_file_id(file_id)
 
639
            if this_pair == base_pair:
 
640
                # only OTHER introduced changes
 
641
                if file_id in self.this_tree:
 
642
                    # Remove any existing contents
 
643
                    self.tt.delete_contents(trans_id)
 
644
                if file_id in self.other_tree:
 
645
                    # OTHER changed the file
 
646
                    create_by_entry(self.tt, 
 
647
                                    self.other_tree.inventory[file_id], 
 
648
                                    self.other_tree, trans_id)
 
649
                    if file_id not in self.this_tree.inventory:
 
650
                        self.tt.version_file(file_id, trans_id)
 
651
                    return "modified"
 
652
                elif file_id in self.this_tree.inventory:
 
653
                    # OTHER deleted the file
 
654
                    self.tt.unversion_file(trans_id)
 
655
                    return "deleted"
 
656
            #BOTH THIS and OTHER introduced changes; scalar conflict
 
657
            elif this_pair[0] == "file" and other_pair[0] == "file":
 
658
                # THIS and OTHER are both files, so text merge.  Either
 
659
                # BASE is a file, or both converted to files, so at least we
 
660
                # have agreement that output should be a file.
 
661
                try:
 
662
                    self.text_merge(file_id, trans_id)
 
663
                except BinaryFile:
 
664
                    return contents_conflict()
 
665
                if file_id not in self.this_tree.inventory:
 
666
                    self.tt.version_file(file_id, trans_id)
 
667
                try:
 
668
                    self.tt.tree_kind(trans_id)
 
669
                    self.tt.delete_contents(trans_id)
 
670
                except NoSuchFile:
 
671
                    pass
 
672
                return "modified"
 
673
            else:
 
674
                # Scalar conflict, can't text merge.  Dump conflicts
 
675
                return contents_conflict()
 
676
 
 
677
    def get_lines(self, tree, file_id):
 
678
        """Return the lines in a file, or an empty list."""
 
679
        if file_id in tree:
 
680
            return tree.get_file(file_id).readlines()
 
681
        else:
 
682
            return []
 
683
 
 
684
    def text_merge(self, file_id, trans_id):
 
685
        """Perform a three-way text merge on a file_id"""
 
686
        # it's possible that we got here with base as a different type.
 
687
        # if so, we just want two-way text conflicts.
 
688
        if file_id in self.base_tree and \
 
689
            self.base_tree.kind(file_id) == "file":
 
690
            base_lines = self.get_lines(self.base_tree, file_id)
 
691
        else:
 
692
            base_lines = []
 
693
        other_lines = self.get_lines(self.other_tree, file_id)
 
694
        this_lines = self.get_lines(self.this_tree, file_id)
 
695
        m3 = Merge3(base_lines, this_lines, other_lines)
 
696
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
 
697
        if self.show_base is True:
 
698
            base_marker = '|' * 7
 
699
        else:
 
700
            base_marker = None
 
701
 
 
702
        def iter_merge3(retval):
 
703
            retval["text_conflicts"] = False
 
704
            for line in m3.merge_lines(name_a = "TREE", 
 
705
                                       name_b = "MERGE-SOURCE", 
 
706
                                       name_base = "BASE-REVISION",
 
707
                                       start_marker=start_marker, 
 
708
                                       base_marker=base_marker,
 
709
                                       reprocess=self.reprocess):
 
710
                if line.startswith(start_marker):
 
711
                    retval["text_conflicts"] = True
 
712
                    yield line.replace(start_marker, '<' * 7)
 
713
                else:
 
714
                    yield line
 
715
        retval = {}
 
716
        merge3_iterator = iter_merge3(retval)
 
717
        self.tt.create_file(merge3_iterator, trans_id)
 
718
        if retval["text_conflicts"] is True:
 
719
            self._raw_conflicts.append(('text conflict', trans_id))
 
720
            name = self.tt.final_name(trans_id)
 
721
            parent_id = self.tt.final_parent(trans_id)
 
722
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
723
                                              this_lines, base_lines,
 
724
                                              other_lines)
 
725
            file_group.append(trans_id)
 
726
 
 
727
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, 
 
728
                        base_lines=None, other_lines=None, set_version=False,
 
729
                        no_base=False):
 
730
        """Emit conflict files.
 
731
        If this_lines, base_lines, or other_lines are omitted, they will be
 
732
        determined automatically.  If set_version is true, the .OTHER, .THIS
 
733
        or .BASE (in that order) will be created as versioned files.
 
734
        """
 
735
        data = [('OTHER', self.other_tree, other_lines), 
 
736
                ('THIS', self.this_tree, this_lines)]
 
737
        if not no_base:
 
738
            data.append(('BASE', self.base_tree, base_lines))
 
739
        versioned = False
 
740
        file_group = []
 
741
        for suffix, tree, lines in data:
 
742
            if file_id in tree:
 
743
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
 
744
                                               suffix, lines)
 
745
                file_group.append(trans_id)
 
746
                if set_version and not versioned:
 
747
                    self.tt.version_file(file_id, trans_id)
 
748
                    versioned = True
 
749
        return file_group
 
750
           
 
751
    def _conflict_file(self, name, parent_id, tree, file_id, suffix, 
 
752
                       lines=None):
 
753
        """Emit a single conflict file."""
 
754
        name = name + '.' + suffix
 
755
        trans_id = self.tt.create_path(name, parent_id)
 
756
        entry = tree.inventory[file_id]
 
757
        create_by_entry(self.tt, entry, tree, trans_id, lines)
 
758
        return trans_id
 
759
 
 
760
    def merge_executable(self, file_id, file_status):
 
761
        """Perform a merge on the execute bit."""
 
762
        if file_status == "deleted":
 
763
            return
 
764
        trans_id = self.tt.trans_id_file_id(file_id)
 
765
        try:
 
766
            if self.tt.final_kind(trans_id) != "file":
 
767
                return
 
768
        except NoSuchFile:
 
769
            return
 
770
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
 
771
                                       self.other_tree, file_id, 
 
772
                                       self.executable)
 
773
        if winner == "conflict":
 
774
        # There must be a None in here, if we have a conflict, but we
 
775
        # need executability since file status was not deleted.
 
776
            if self.executable(self.other_tree, file_id) is None:
 
777
                winner = "this"
 
778
            else:
 
779
                winner = "other"
 
780
        if winner == "this":
 
781
            if file_status == "modified":
 
782
                executability = self.this_tree.is_executable(file_id)
 
783
                if executability is not None:
 
784
                    trans_id = self.tt.trans_id_file_id(file_id)
 
785
                    self.tt.set_executability(executability, trans_id)
 
786
        else:
 
787
            assert winner == "other"
 
788
            if file_id in self.other_tree:
 
789
                executability = self.other_tree.is_executable(file_id)
 
790
            elif file_id in self.this_tree:
 
791
                executability = self.this_tree.is_executable(file_id)
 
792
            elif file_id in self.base_tree:
 
793
                executability = self.base_tree.is_executable(file_id)
 
794
            if executability is not None:
 
795
                trans_id = self.tt.trans_id_file_id(file_id)
 
796
                self.tt.set_executability(executability, trans_id)
 
797
 
 
798
    def cook_conflicts(self, fs_conflicts):
 
799
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
800
        from conflicts import Conflict
 
801
        name_conflicts = {}
 
802
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
 
803
        fp = FinalPaths(self.tt)
 
804
        for conflict in self._raw_conflicts:
 
805
            conflict_type = conflict[0]
 
806
            if conflict_type in ('name conflict', 'parent conflict'):
 
807
                trans_id = conflict[1]
 
808
                conflict_args = conflict[2:]
 
809
                if trans_id not in name_conflicts:
 
810
                    name_conflicts[trans_id] = {}
 
811
                unique_add(name_conflicts[trans_id], conflict_type, 
 
812
                           conflict_args)
 
813
            if conflict_type == 'contents conflict':
 
814
                for trans_id in conflict[1]:
 
815
                    file_id = self.tt.final_file_id(trans_id)
 
816
                    if file_id is not None:
 
817
                        break
 
818
                path = fp.get_path(trans_id)
 
819
                for suffix in ('.BASE', '.THIS', '.OTHER'):
 
820
                    if path.endswith(suffix):
 
821
                        path = path[:-len(suffix)]
 
822
                        break
 
823
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
824
                self.cooked_conflicts.append(c)
 
825
            if conflict_type == 'text conflict':
 
826
                trans_id = conflict[1]
 
827
                path = fp.get_path(trans_id)
 
828
                file_id = self.tt.final_file_id(trans_id)
 
829
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
830
                self.cooked_conflicts.append(c)
 
831
 
 
832
        for trans_id, conflicts in name_conflicts.iteritems():
 
833
            try:
 
834
                this_parent, other_parent = conflicts['parent conflict']
 
835
                assert this_parent != other_parent
 
836
            except KeyError:
 
837
                this_parent = other_parent = \
 
838
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
839
            try:
 
840
                this_name, other_name = conflicts['name conflict']
 
841
                assert this_name != other_name
 
842
            except KeyError:
 
843
                this_name = other_name = self.tt.final_name(trans_id)
 
844
            other_path = fp.get_path(trans_id)
 
845
            if this_parent is not None:
 
846
                this_parent_path = \
 
847
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
848
                this_path = pathjoin(this_parent_path, this_name)
 
849
            else:
 
850
                this_path = "<deleted>"
 
851
            file_id = self.tt.final_file_id(trans_id)
 
852
            c = Conflict.factory('path conflict', path=this_path,
 
853
                                 conflict_path=other_path, file_id=file_id)
 
854
            self.cooked_conflicts.append(c)
 
855
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
856
 
 
857
 
 
858
class WeaveMerger(Merge3Merger):
 
859
    """Three-way tree merger, text weave merger."""
 
860
    supports_reprocess = True
 
861
    supports_show_base = False
 
862
 
 
863
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
 
864
                 interesting_ids=None, pb=DummyProgress(), pp=None,
 
865
                 reprocess=False, change_reporter=None):
 
866
        self.this_revision_tree = self._get_revision_tree(this_tree)
 
867
        self.other_revision_tree = self._get_revision_tree(other_tree)
 
868
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
 
869
                                          base_tree, other_tree, 
 
870
                                          interesting_ids=interesting_ids, 
 
871
                                          pb=pb, pp=pp, reprocess=reprocess,
 
872
                                          change_reporter=change_reporter)
 
873
 
 
874
    def _get_revision_tree(self, tree):
 
875
        """Return a revision tree related to this tree.
 
876
        If the tree is a WorkingTree, the basis will be returned.
 
877
        """
 
878
        if getattr(tree, 'get_weave', False) is False:
 
879
            # If we have a WorkingTree, try using the basis
 
880
            return tree.branch.basis_tree()
 
881
        else:
 
882
            return tree
 
883
 
 
884
    def _check_file(self, file_id):
 
885
        """Check that the revision tree's version of the file matches."""
 
886
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
 
887
                         (self.other_tree, self.other_revision_tree)):
 
888
            if rt is tree:
 
889
                continue
 
890
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
 
891
                raise WorkingTreeNotRevision(self.this_tree)
 
892
 
 
893
    def _merged_lines(self, file_id):
 
894
        """Generate the merged lines.
 
895
        There is no distinction between lines that are meant to contain <<<<<<<
 
896
        and conflicts.
 
897
        """
 
898
        weave = self.this_revision_tree.get_weave(file_id)
 
899
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
 
900
        other_revision_id = \
 
901
            self.other_revision_tree.inventory[file_id].revision
 
902
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
 
903
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
 
904
        return wm.merge_lines(self.reprocess)
 
905
 
 
906
    def text_merge(self, file_id, trans_id):
 
907
        """Perform a (weave) text merge for a given file and file-id.
 
908
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
 
909
        and a conflict will be noted.
 
910
        """
 
911
        self._check_file(file_id)
 
912
        lines, conflicts = self._merged_lines(file_id)
 
913
        lines = list(lines)
 
914
        # Note we're checking whether the OUTPUT is binary in this case, 
 
915
        # because we don't want to get into weave merge guts.
 
916
        check_text_lines(lines)
 
917
        self.tt.create_file(lines, trans_id)
 
918
        if conflicts:
 
919
            self._raw_conflicts.append(('text conflict', trans_id))
 
920
            name = self.tt.final_name(trans_id)
 
921
            parent_id = self.tt.final_parent(trans_id)
 
922
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
923
                                              no_base=True)
 
924
            file_group.append(trans_id)
 
925
 
 
926
 
 
927
class Diff3Merger(Merge3Merger):
 
928
    """Three-way merger using external diff3 for text merging"""
 
929
 
 
930
    def dump_file(self, temp_dir, name, tree, file_id):
 
931
        out_path = pathjoin(temp_dir, name)
 
932
        out_file = open(out_path, "wb")
 
933
        try:
 
934
            in_file = tree.get_file(file_id)
 
935
            for line in in_file:
 
936
                out_file.write(line)
 
937
        finally:
 
938
            out_file.close()
 
939
        return out_path
 
940
 
 
941
    def text_merge(self, file_id, trans_id):
 
942
        """Perform a diff3 merge using a specified file-id and trans-id.
 
943
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
 
944
        will be dumped, and a will be conflict noted.
 
945
        """
 
946
        import bzrlib.patch
 
947
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
948
        try:
 
949
            new_file = pathjoin(temp_dir, "new")
 
950
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
 
951
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
 
952
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
 
953
            status = bzrlib.patch.diff3(new_file, this, base, other)
 
954
            if status not in (0, 1):
 
955
                raise BzrError("Unhandled diff3 exit code")
 
956
            f = open(new_file, 'rb')
 
957
            try:
 
958
                self.tt.create_file(f, trans_id)
 
959
            finally:
 
960
                f.close()
 
961
            if status == 1:
 
962
                name = self.tt.final_name(trans_id)
 
963
                parent_id = self.tt.final_parent(trans_id)
 
964
                self._dump_conflicts(name, parent_id, file_id)
 
965
                self._raw_conflicts.append(('text conflict', trans_id))
 
966
        finally:
 
967
            osutils.rmtree(temp_dir)
 
968
 
 
969
 
 
970
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
 
971
                backup_files=False, 
 
972
                merge_type=Merge3Merger, 
 
973
                interesting_ids=None, 
 
974
                show_base=False, 
 
975
                reprocess=False, 
 
976
                other_rev_id=None,
 
977
                interesting_files=None,
 
978
                this_tree=None,
 
979
                pb=DummyProgress(),
 
980
                change_reporter=None):
 
981
    """Primary interface for merging. 
 
982
 
 
983
        typical use is probably 
 
984
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
985
                     branch.get_revision_tree(base_revision))'
 
986
        """
 
987
    if this_tree is None:
 
988
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
 
989
             "bzrlib version 0.8.",
 
990
             DeprecationWarning,
 
991
             stacklevel=2)
 
992
        this_tree = this_branch.bzrdir.open_workingtree()
 
993
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
 
994
                    pb=pb, change_reporter=change_reporter)
 
995
    merger.backup_files = backup_files
 
996
    merger.merge_type = merge_type
 
997
    merger.interesting_ids = interesting_ids
 
998
    merger.ignore_zero = ignore_zero
 
999
    if interesting_files:
 
1000
        assert not interesting_ids, ('Only supply interesting_ids'
 
1001
                                     ' or interesting_files')
 
1002
        merger._set_interesting_files(interesting_files)
 
1003
    merger.show_base = show_base
 
1004
    merger.reprocess = reprocess
 
1005
    merger.other_rev_id = other_rev_id
 
1006
    merger.other_basis = other_rev_id
 
1007
    return merger.do_merge()
 
1008
 
 
1009
def get_merge_type_registry():
 
1010
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1011
 
 
1012
    This method provides a sanctioned way to retrieve it.
 
1013
    """
 
1014
    from bzrlib import option
 
1015
    return option._merge_type_registry