~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Martin Pool
  • Date: 2010-07-16 15:20:17 UTC
  • mfrom: (5346.3.1 pathnotchild)
  • mto: This revision was merged to the branch mainline in revision 5351.
  • Revision ID: mbp@canonical.com-20100716152017-t4c73h9y1uoih7fb
PathNotChild should not give a traceback.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
 
18
 
import errno
19
 
from itertools import chain
20
 
import os
21
17
import warnings
22
18
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
23
21
from bzrlib import (
 
22
    branch as _mod_branch,
 
23
    conflicts as _mod_conflicts,
24
24
    debug,
25
25
    errors,
26
26
    graph as _mod_graph,
 
27
    merge3,
27
28
    osutils,
28
29
    patiencediff,
29
 
    registry,
30
30
    revision as _mod_revision,
 
31
    textfile,
 
32
    trace,
 
33
    transform,
31
34
    tree as _mod_tree,
32
35
    tsort,
33
 
    )
34
 
from bzrlib.branch import Branch
35
 
from bzrlib.conflicts import ConflictList, Conflict
36
 
from bzrlib.errors import (BzrCommandError,
37
 
                           BzrError,
38
 
                           NoCommonAncestor,
39
 
                           NoCommits,
40
 
                           NoSuchRevision,
41
 
                           NoSuchFile,
42
 
                           NotBranchError,
43
 
                           NotVersionedError,
44
 
                           UnrelatedBranches,
45
 
                           UnsupportedOperation,
46
 
                           WorkingTreeNotRevision,
47
 
                           BinaryFile,
48
 
                           )
49
 
from bzrlib.graph import Graph
50
 
from bzrlib.merge3 import Merge3
51
 
from bzrlib.osutils import rename, pathjoin
52
 
from progress import DummyProgress, ProgressPhase
53
 
from bzrlib.revision import (NULL_REVISION, ensure_null)
54
 
from bzrlib.textfile import check_text_lines
55
 
from bzrlib.trace import mutter, warning, note, is_quiet
56
 
from bzrlib.transform import (TransformPreview, TreeTransform,
57
 
                              resolve_conflicts, cook_conflicts,
58
 
                              conflict_pass, FinalPaths, create_from_tree,
59
 
                              unique_add, ROOT_PARENT)
60
 
from bzrlib.versionedfile import PlanWeaveMerge
61
 
from bzrlib import ui
62
 
 
 
36
    ui,
 
37
    versionedfile,
 
38
    )
 
39
from bzrlib.cleanup import OperationWithCleanups
 
40
""")
 
41
from bzrlib import (
 
42
    decorators,
 
43
    hooks,
 
44
    )
 
45
from bzrlib.symbol_versioning import (
 
46
    deprecated_in,
 
47
    deprecated_method,
 
48
    )
63
49
# TODO: Report back as changes are merged in
64
50
 
65
51
 
66
52
def transform_tree(from_tree, to_tree, interesting_ids=None):
67
 
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
68
 
                interesting_ids=interesting_ids, this_tree=from_tree)
 
53
    from_tree.lock_tree_write()
 
54
    operation = OperationWithCleanups(merge_inner)
 
55
    operation.add_cleanup(from_tree.unlock)
 
56
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
57
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
 
58
 
 
59
 
 
60
class MergeHooks(hooks.Hooks):
 
61
 
 
62
    def __init__(self):
 
63
        hooks.Hooks.__init__(self)
 
64
        self.create_hook(hooks.HookPoint('merge_file_content',
 
65
            "Called with a bzrlib.merge.Merger object to create a per file "
 
66
            "merge object when starting a merge. "
 
67
            "Should return either None or a subclass of "
 
68
            "``bzrlib.merge.AbstractPerFileMerger``. "
 
69
            "Such objects will then be called per file "
 
70
            "that needs to be merged (including when one "
 
71
            "side has deleted the file and the other has changed it). "
 
72
            "See the AbstractPerFileMerger API docs for details on how it is "
 
73
            "used by merge.",
 
74
            (2, 1), None))
 
75
 
 
76
 
 
77
class AbstractPerFileMerger(object):
 
78
    """PerFileMerger objects are used by plugins extending merge for bzrlib.
 
79
 
 
80
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
81
    
 
82
    :ivar merger: The Merge3Merger performing the merge.
 
83
    """
 
84
 
 
85
    def __init__(self, merger):
 
86
        """Create a PerFileMerger for use with merger."""
 
87
        self.merger = merger
 
88
 
 
89
    def merge_contents(self, merge_params):
 
90
        """Attempt to merge the contents of a single file.
 
91
        
 
92
        :param merge_params: A bzrlib.merge.MergeHookParams
 
93
        :return : A tuple of (status, chunks), where status is one of
 
94
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
 
95
            is 'success' or 'conflicted', then chunks should be an iterable of
 
96
            strings for the new file contents.
 
97
        """
 
98
        return ('not applicable', None)
 
99
 
 
100
 
 
101
class PerFileMerger(AbstractPerFileMerger):
 
102
    """Merge individual files when self.file_matches returns True.
 
103
 
 
104
    This class is intended to be subclassed.  The file_matches and
 
105
    merge_matching methods should be overridden with concrete implementations.
 
106
    """
 
107
 
 
108
    def file_matches(self, params):
 
109
        """Return True if merge_matching should be called on this file.
 
110
 
 
111
        Only called with merges of plain files with no clear winner.
 
112
 
 
113
        Subclasses must override this.
 
114
        """
 
115
        raise NotImplementedError(self.file_matches)
 
116
 
 
117
    def get_filename(self, params, tree):
 
118
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
 
119
        self.merger.this_tree) and a MergeHookParams.
 
120
        """
 
121
        return osutils.basename(tree.id2path(params.file_id))
 
122
 
 
123
    def get_filepath(self, params, tree):
 
124
        """Calculate the path to the file in a tree.
 
125
 
 
126
        :param params: A MergeHookParams describing the file to merge
 
127
        :param tree: a Tree, e.g. self.merger.this_tree.
 
128
        """
 
129
        return tree.id2path(params.file_id)
 
130
 
 
131
    def merge_contents(self, params):
 
132
        """Merge the contents of a single file."""
 
133
        # Check whether this custom merge logic should be used.
 
134
        if (
 
135
            # OTHER is a straight winner, rely on default merge.
 
136
            params.winner == 'other' or
 
137
            # THIS and OTHER aren't both files.
 
138
            not params.is_file_merge() or
 
139
            # The filename doesn't match *.xml
 
140
            not self.file_matches(params)):
 
141
            return 'not_applicable', None
 
142
        return self.merge_matching(params)
 
143
 
 
144
    def merge_matching(self, params):
 
145
        """Merge the contents of a single file that has matched the criteria
 
146
        in PerFileMerger.merge_contents (is a conflict, is a file,
 
147
        self.file_matches is True).
 
148
 
 
149
        Subclasses must override this.
 
150
        """
 
151
        raise NotImplementedError(self.merge_matching)
 
152
 
 
153
 
 
154
class ConfigurableFileMerger(PerFileMerger):
 
155
    """Merge individual files when configured via a .conf file.
 
156
 
 
157
    This is a base class for concrete custom file merging logic. Concrete
 
158
    classes should implement ``merge_text``.
 
159
 
 
160
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
161
    
 
162
    :ivar affected_files: The configured file paths to merge.
 
163
 
 
164
    :cvar name_prefix: The prefix to use when looking up configuration
 
165
        details. <name_prefix>_merge_files describes the files targeted by the
 
166
        hook for example.
 
167
        
 
168
    :cvar default_files: The default file paths to merge when no configuration
 
169
        is present.
 
170
    """
 
171
 
 
172
    name_prefix = None
 
173
    default_files = None
 
174
 
 
175
    def __init__(self, merger):
 
176
        super(ConfigurableFileMerger, self).__init__(merger)
 
177
        self.affected_files = None
 
178
        self.default_files = self.__class__.default_files or []
 
179
        self.name_prefix = self.__class__.name_prefix
 
180
        if self.name_prefix is None:
 
181
            raise ValueError("name_prefix must be set.")
 
182
 
 
183
    def file_matches(self, params):
 
184
        """Check whether the file should call the merge hook.
 
185
 
 
186
        <name_prefix>_merge_files configuration variable is a list of files
 
187
        that should use the hook.
 
188
        """
 
189
        affected_files = self.affected_files
 
190
        if affected_files is None:
 
191
            config = self.merger.this_branch.get_config()
 
192
            # Until bzr provides a better policy for caching the config, we
 
193
            # just add the part we're interested in to the params to avoid
 
194
            # reading the config files repeatedly (bazaar.conf, location.conf,
 
195
            # branch.conf).
 
196
            config_key = self.name_prefix + '_merge_files'
 
197
            affected_files = config.get_user_option_as_list(config_key)
 
198
            if affected_files is None:
 
199
                # If nothing was specified in the config, use the default.
 
200
                affected_files = self.default_files
 
201
            self.affected_files = affected_files
 
202
        if affected_files:
 
203
            filepath = self.get_filepath(params, self.merger.this_tree)
 
204
            if filepath in affected_files:
 
205
                return True
 
206
        return False
 
207
 
 
208
    def merge_matching(self, params):
 
209
        return self.merge_text(params)
 
210
 
 
211
    def merge_text(self, params):
 
212
        """Merge the byte contents of a single file.
 
213
 
 
214
        This is called after checking that the merge should be performed in
 
215
        merge_contents, and it should behave as per
 
216
        ``bzrlib.merge.AbstractPerFileMerger.merge_contents``.
 
217
        """
 
218
        raise NotImplementedError(self.merge_text)
 
219
 
 
220
 
 
221
class MergeHookParams(object):
 
222
    """Object holding parameters passed to merge_file_content hooks.
 
223
 
 
224
    There are some fields hooks can access:
 
225
 
 
226
    :ivar file_id: the file ID of the file being merged
 
227
    :ivar trans_id: the transform ID for the merge of this file
 
228
    :ivar this_kind: kind of file_id in 'this' tree
 
229
    :ivar other_kind: kind of file_id in 'other' tree
 
230
    :ivar winner: one of 'this', 'other', 'conflict'
 
231
    """
 
232
 
 
233
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
 
234
            winner):
 
235
        self._merger = merger
 
236
        self.file_id = file_id
 
237
        self.trans_id = trans_id
 
238
        self.this_kind = this_kind
 
239
        self.other_kind = other_kind
 
240
        self.winner = winner
 
241
 
 
242
    def is_file_merge(self):
 
243
        """True if this_kind and other_kind are both 'file'."""
 
244
        return self.this_kind == 'file' and self.other_kind == 'file'
 
245
 
 
246
    @decorators.cachedproperty
 
247
    def base_lines(self):
 
248
        """The lines of the 'base' version of the file."""
 
249
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
250
 
 
251
    @decorators.cachedproperty
 
252
    def this_lines(self):
 
253
        """The lines of the 'this' version of the file."""
 
254
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
255
 
 
256
    @decorators.cachedproperty
 
257
    def other_lines(self):
 
258
        """The lines of the 'other' version of the file."""
 
259
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
69
260
 
70
261
 
71
262
class Merger(object):
 
263
 
 
264
    hooks = MergeHooks()
 
265
 
72
266
    def __init__(self, this_branch, other_tree=None, base_tree=None,
73
267
                 this_tree=None, pb=None, change_reporter=None,
74
268
                 recurse='down', revision_graph=None):
89
283
        self.interesting_files = None
90
284
        self.show_base = False
91
285
        self.reprocess = False
92
 
        if pb is None:
93
 
            pb = DummyProgress()
94
 
        self._pb = pb
 
286
        if pb is not None:
 
287
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
95
288
        self.pp = None
96
289
        self.recurse = recurse
97
290
        self.change_reporter = change_reporter
102
295
        self._is_criss_cross = None
103
296
        self._lca_trees = None
104
297
 
 
298
    def cache_trees_with_revision_ids(self, trees):
 
299
        """Cache any tree in trees if it has a revision_id."""
 
300
        for maybe_tree in trees:
 
301
            if maybe_tree is None:
 
302
                continue
 
303
            try:
 
304
                rev_id = maybe_tree.get_revision_id()
 
305
            except AttributeError:
 
306
                continue
 
307
            self._cached_trees[rev_id] = maybe_tree
 
308
 
105
309
    @property
106
310
    def revision_graph(self):
107
311
        if self._revision_graph is None:
169
373
                base_revision_id, tree.branch.last_revision())):
170
374
                base_revision_id = None
171
375
            else:
172
 
                warning('Performing cherrypick')
 
376
                trace.warning('Performing cherrypick')
173
377
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
174
378
                                         base_revision_id, revision_graph=
175
379
                                         revision_graph)
227
431
        if revno is None:
228
432
            tree = workingtree.WorkingTree.open_containing(location)[0]
229
433
            return tree.branch, tree
230
 
        branch = Branch.open_containing(location, possible_transports)[0]
 
434
        branch = _mod_branch.Branch.open_containing(
 
435
            location, possible_transports)[0]
231
436
        if revno == -1:
232
437
            revision_id = branch.last_revision()
233
438
        else:
234
439
            revision_id = branch.get_rev_id(revno)
235
 
        revision_id = ensure_null(revision_id)
 
440
        revision_id = _mod_revision.ensure_null(revision_id)
236
441
        return branch, self.revision_tree(revision_id, branch)
237
442
 
 
443
    @deprecated_method(deprecated_in((2, 1, 0)))
238
444
    def ensure_revision_trees(self):
239
445
        if self.this_revision_tree is None:
240
446
            self.this_basis_tree = self.revision_tree(self.this_basis)
243
449
 
244
450
        if self.other_rev_id is None:
245
451
            other_basis_tree = self.revision_tree(self.other_basis)
246
 
            changes = other_basis_tree.changes_from(self.other_tree)
247
 
            if changes.has_changed():
248
 
                raise WorkingTreeNotRevision(self.this_tree)
 
452
            if other_basis_tree.has_changes(self.other_tree):
 
453
                raise errors.WorkingTreeNotRevision(self.this_tree)
249
454
            other_rev_id = self.other_basis
250
455
            self.other_tree = other_basis_tree
251
456
 
 
457
    @deprecated_method(deprecated_in((2, 1, 0)))
252
458
    def file_revisions(self, file_id):
253
459
        self.ensure_revision_trees()
254
460
        def get_id(tree, file_id):
257
463
        if self.this_rev_id is None:
258
464
            if self.this_basis_tree.get_file_sha1(file_id) != \
259
465
                self.this_tree.get_file_sha1(file_id):
260
 
                raise WorkingTreeNotRevision(self.this_tree)
 
466
                raise errors.WorkingTreeNotRevision(self.this_tree)
261
467
 
262
468
        trees = (self.this_basis_tree, self.other_tree)
263
469
        return [get_id(tree, file_id) for tree in trees]
264
470
 
 
471
    @deprecated_method(deprecated_in((2, 1, 0)))
265
472
    def check_basis(self, check_clean, require_commits=True):
266
473
        if self.this_basis is None and require_commits is True:
267
 
            raise BzrCommandError("This branch has no commits."
268
 
                                  " (perhaps you would prefer 'bzr pull')")
 
474
            raise errors.BzrCommandError(
 
475
                "This branch has no commits."
 
476
                " (perhaps you would prefer 'bzr pull')")
269
477
        if check_clean:
270
478
            self.compare_basis()
271
479
            if self.this_basis != self.this_rev_id:
272
480
                raise errors.UncommittedChanges(self.this_tree)
273
481
 
 
482
    @deprecated_method(deprecated_in((2, 1, 0)))
274
483
    def compare_basis(self):
275
484
        try:
276
485
            basis_tree = self.revision_tree(self.this_tree.last_revision())
277
486
        except errors.NoSuchRevision:
278
487
            basis_tree = self.this_tree.basis_tree()
279
 
        changes = self.this_tree.changes_from(basis_tree)
280
 
        if not changes.has_changed():
 
488
        if not self.this_tree.has_changes(basis_tree):
281
489
            self.this_rev_id = self.this_basis
282
490
 
283
491
    def set_interesting_files(self, file_list):
284
492
        self.interesting_files = file_list
285
493
 
286
494
    def set_pending(self):
287
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
495
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
 
496
            or self.other_rev_id is None):
288
497
            return
289
498
        self._add_parent()
290
499
 
291
500
    def _add_parent(self):
292
501
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
293
502
        new_parent_trees = []
 
503
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
294
504
        for revision_id in new_parents:
295
505
            try:
296
506
                tree = self.revision_tree(revision_id)
298
508
                tree = None
299
509
            else:
300
510
                tree.lock_read()
 
511
                operation.add_cleanup(tree.unlock)
301
512
            new_parent_trees.append((revision_id, tree))
302
 
        try:
303
 
            self.this_tree.set_parent_trees(new_parent_trees,
304
 
                                            allow_leftmost_as_ghost=True)
305
 
        finally:
306
 
            for _revision_id, tree in new_parent_trees:
307
 
                if tree is not None:
308
 
                    tree.unlock()
 
513
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
309
514
 
310
515
    def set_other(self, other_revision, possible_transports=None):
311
516
        """Set the revision and tree to merge from.
320
525
            self.other_rev_id = _mod_revision.ensure_null(
321
526
                self.other_branch.last_revision())
322
527
            if _mod_revision.is_null(self.other_rev_id):
323
 
                raise NoCommits(self.other_branch)
 
528
                raise errors.NoCommits(self.other_branch)
324
529
            self.other_basis = self.other_rev_id
325
530
        elif other_revision[1] is not None:
326
531
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
329
534
            self.other_rev_id = None
330
535
            self.other_basis = self.other_branch.last_revision()
331
536
            if self.other_basis is None:
332
 
                raise NoCommits(self.other_branch)
 
537
                raise errors.NoCommits(self.other_branch)
333
538
        if self.other_rev_id is not None:
334
539
            self._cached_trees[self.other_rev_id] = self.other_tree
335
540
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
362
567
            target.fetch(source, revision_id)
363
568
 
364
569
    def find_base(self):
365
 
        revisions = [ensure_null(self.this_basis),
366
 
                     ensure_null(self.other_basis)]
367
 
        if NULL_REVISION in revisions:
368
 
            self.base_rev_id = NULL_REVISION
 
570
        revisions = [_mod_revision.ensure_null(self.this_basis),
 
571
                     _mod_revision.ensure_null(self.other_basis)]
 
572
        if _mod_revision.NULL_REVISION in revisions:
 
573
            self.base_rev_id = _mod_revision.NULL_REVISION
369
574
            self.base_tree = self.revision_tree(self.base_rev_id)
370
575
            self._is_criss_cross = False
371
576
        else:
372
577
            lcas = self.revision_graph.find_lca(revisions[0], revisions[1])
373
578
            self._is_criss_cross = False
374
579
            if len(lcas) == 0:
375
 
                self.base_rev_id = NULL_REVISION
 
580
                self.base_rev_id = _mod_revision.NULL_REVISION
376
581
            elif len(lcas) == 1:
377
582
                self.base_rev_id = list(lcas)[0]
378
583
            else: # len(lcas) > 1
387
592
                    self.base_rev_id = self.revision_graph.find_unique_lca(
388
593
                                            *lcas)
389
594
                self._is_criss_cross = True
390
 
            if self.base_rev_id == NULL_REVISION:
391
 
                raise UnrelatedBranches()
 
595
            if self.base_rev_id == _mod_revision.NULL_REVISION:
 
596
                raise errors.UnrelatedBranches()
392
597
            if self._is_criss_cross:
393
 
                warning('Warning: criss-cross merge encountered.  See bzr'
394
 
                        ' help criss-cross.')
395
 
                mutter('Criss-cross lcas: %r' % lcas)
 
598
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
 
599
                              ' help criss-cross.')
 
600
                trace.mutter('Criss-cross lcas: %r' % lcas)
396
601
                interesting_revision_ids = [self.base_rev_id]
397
602
                interesting_revision_ids.extend(lcas)
398
603
                interesting_trees = dict((t.get_revision_id(), t)
408
613
                self.base_tree = self.revision_tree(self.base_rev_id)
409
614
        self.base_is_ancestor = True
410
615
        self.base_is_other_ancestor = True
411
 
        mutter('Base revid: %r' % self.base_rev_id)
 
616
        trace.mutter('Base revid: %r' % self.base_rev_id)
412
617
 
413
618
    def set_base(self, base_revision):
414
619
        """Set the base revision to use for the merge.
415
620
 
416
621
        :param base_revision: A 2-list containing a path and revision number.
417
622
        """
418
 
        mutter("doing merge() with no base_revision specified")
 
623
        trace.mutter("doing merge() with no base_revision specified")
419
624
        if base_revision == [None, None]:
420
625
            self.find_base()
421
626
        else:
434
639
                  'other_tree': self.other_tree,
435
640
                  'interesting_ids': self.interesting_ids,
436
641
                  'interesting_files': self.interesting_files,
437
 
                  'pp': self.pp,
 
642
                  'this_branch': self.this_branch,
438
643
                  'do_merge': False}
439
644
        if self.merge_type.requires_base:
440
645
            kwargs['base_tree'] = self.base_tree
441
646
        if self.merge_type.supports_reprocess:
442
647
            kwargs['reprocess'] = self.reprocess
443
648
        elif self.reprocess:
444
 
            raise BzrError("Conflict reduction is not supported for merge"
445
 
                                  " type %s." % self.merge_type)
 
649
            raise errors.BzrError(
 
650
                "Conflict reduction is not supported for merge"
 
651
                " type %s." % self.merge_type)
446
652
        if self.merge_type.supports_show_base:
447
653
            kwargs['show_base'] = self.show_base
448
654
        elif self.show_base:
449
 
            raise BzrError("Showing base is not supported for this"
450
 
                           " merge type. %s" % self.merge_type)
 
655
            raise errors.BzrError("Showing base is not supported for this"
 
656
                                  " merge type. %s" % self.merge_type)
451
657
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
452
658
            and not self.base_is_other_ancestor):
453
659
            raise errors.CannotReverseCherrypick()
457
663
        if self._is_criss_cross and getattr(self.merge_type,
458
664
                                            'supports_lca_trees', False):
459
665
            kwargs['lca_trees'] = self._lca_trees
460
 
        return self.merge_type(pb=self._pb,
 
666
        return self.merge_type(pb=None,
461
667
                               change_reporter=self.change_reporter,
462
668
                               **kwargs)
463
669
 
464
 
    def _do_merge_to(self, merge):
 
670
    def _do_merge_to(self):
 
671
        merge = self.make_merger()
 
672
        if self.other_branch is not None:
 
673
            self.other_branch.update_references(self.this_branch)
465
674
        merge.do_merge()
466
675
        if self.recurse == 'down':
467
676
            for relpath, file_id in self.this_tree.iter_references():
479
688
                    sub_tree.branch.repository.revision_tree(base_revision)
480
689
                sub_merge.base_rev_id = base_revision
481
690
                sub_merge.do_merge()
 
691
        return merge
482
692
 
483
693
    def do_merge(self):
 
694
        operation = OperationWithCleanups(self._do_merge_to)
484
695
        self.this_tree.lock_tree_write()
485
 
        try:
486
 
            if self.base_tree is not None:
487
 
                self.base_tree.lock_read()
488
 
            try:
489
 
                if self.other_tree is not None:
490
 
                    self.other_tree.lock_read()
491
 
                try:
492
 
                    merge = self.make_merger()
493
 
                    self._do_merge_to(merge)
494
 
                finally:
495
 
                    if self.other_tree is not None:
496
 
                        self.other_tree.unlock()
497
 
            finally:
498
 
                if self.base_tree is not None:
499
 
                    self.base_tree.unlock()
500
 
        finally:
501
 
            self.this_tree.unlock()
 
696
        operation.add_cleanup(self.this_tree.unlock)
 
697
        if self.base_tree is not None:
 
698
            self.base_tree.lock_read()
 
699
            operation.add_cleanup(self.base_tree.unlock)
 
700
        if self.other_tree is not None:
 
701
            self.other_tree.lock_read()
 
702
            operation.add_cleanup(self.other_tree.unlock)
 
703
        merge = operation.run_simple()
502
704
        if len(merge.cooked_conflicts) == 0:
503
 
            if not self.ignore_zero and not is_quiet():
504
 
                note("All changes applied successfully.")
 
705
            if not self.ignore_zero and not trace.is_quiet():
 
706
                trace.note("All changes applied successfully.")
505
707
        else:
506
 
            note("%d conflicts encountered." % len(merge.cooked_conflicts))
 
708
            trace.note("%d conflicts encountered."
 
709
                       % len(merge.cooked_conflicts))
507
710
 
508
711
        return len(merge.cooked_conflicts)
509
712
 
538
741
 
539
742
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
540
743
                 interesting_ids=None, reprocess=False, show_base=False,
541
 
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
744
                 pb=None, pp=None, change_reporter=None,
542
745
                 interesting_files=None, do_merge=True,
543
 
                 cherrypick=False, lca_trees=None):
 
746
                 cherrypick=False, lca_trees=None, this_branch=None):
544
747
        """Initialize the merger object and perform the merge.
545
748
 
546
749
        :param working_tree: The working tree to apply the merge to
547
750
        :param this_tree: The local tree in the merge operation
548
751
        :param base_tree: The common tree in the merge operation
549
 
        :param other_tree: The other other tree to merge changes from
 
752
        :param other_tree: The other tree to merge changes from
 
753
        :param this_branch: The branch associated with this_tree.  Defaults to
 
754
            this_tree.branch if not supplied.
550
755
        :param interesting_ids: The file_ids of files that should be
551
756
            participate in the merge.  May not be combined with
552
757
            interesting_files.
553
758
        :param: reprocess If True, perform conflict-reduction processing.
554
759
        :param show_base: If True, show the base revision in text conflicts.
555
760
            (incompatible with reprocess)
556
 
        :param pb: A Progress bar
 
761
        :param pb: ignored
557
762
        :param pp: A ProgressPhase object
558
763
        :param change_reporter: An object that should report changes made
559
764
        :param interesting_files: The tree-relative paths of files that should
570
775
        if interesting_files is not None and interesting_ids is not None:
571
776
            raise ValueError(
572
777
                'specify either interesting_ids or interesting_files')
 
778
        if this_branch is None:
 
779
            this_branch = this_tree.branch
573
780
        self.interesting_ids = interesting_ids
574
781
        self.interesting_files = interesting_files
575
782
        self.this_tree = working_tree
576
783
        self.base_tree = base_tree
577
784
        self.other_tree = other_tree
 
785
        self.this_branch = this_branch
578
786
        self._raw_conflicts = []
579
787
        self.cooked_conflicts = []
580
788
        self.reprocess = reprocess
585
793
        # making sure we haven't missed any corner cases.
586
794
        # if lca_trees is None:
587
795
        #     self._lca_trees = [self.base_tree]
588
 
        self.pb = pb
589
 
        self.pp = pp
590
796
        self.change_reporter = change_reporter
591
797
        self.cherrypick = cherrypick
592
 
        if self.pp is None:
593
 
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
594
798
        if do_merge:
595
799
            self.do_merge()
 
800
        if pp is not None:
 
801
            warnings.warn("pp argument to Merge3Merger is deprecated")
 
802
        if pb is not None:
 
803
            warnings.warn("pb argument to Merge3Merger is deprecated")
596
804
 
597
805
    def do_merge(self):
 
806
        operation = OperationWithCleanups(self._do_merge)
598
807
        self.this_tree.lock_tree_write()
 
808
        operation.add_cleanup(self.this_tree.unlock)
599
809
        self.base_tree.lock_read()
 
810
        operation.add_cleanup(self.base_tree.unlock)
600
811
        self.other_tree.lock_read()
601
 
        self.tt = TreeTransform(self.this_tree, self.pb)
 
812
        operation.add_cleanup(self.other_tree.unlock)
 
813
        operation.run()
 
814
 
 
815
    def _do_merge(self, operation):
 
816
        self.tt = transform.TreeTransform(self.this_tree, None)
 
817
        operation.add_cleanup(self.tt.finalize)
 
818
        self._compute_transform()
 
819
        results = self.tt.apply(no_conflicts=True)
 
820
        self.write_modified(results)
602
821
        try:
603
 
            self.pp.next_phase()
604
 
            self._compute_transform()
605
 
            self.pp.next_phase()
606
 
            results = self.tt.apply(no_conflicts=True)
607
 
            self.write_modified(results)
608
 
            try:
609
 
                self.this_tree.add_conflicts(self.cooked_conflicts)
610
 
            except UnsupportedOperation:
611
 
                pass
612
 
        finally:
613
 
            self.tt.finalize()
614
 
            self.other_tree.unlock()
615
 
            self.base_tree.unlock()
616
 
            self.this_tree.unlock()
617
 
            self.pb.clear()
 
822
            self.this_tree.add_conflicts(self.cooked_conflicts)
 
823
        except errors.UnsupportedOperation:
 
824
            pass
618
825
 
619
826
    def make_preview_transform(self):
 
827
        operation = OperationWithCleanups(self._make_preview_transform)
620
828
        self.base_tree.lock_read()
 
829
        operation.add_cleanup(self.base_tree.unlock)
621
830
        self.other_tree.lock_read()
622
 
        self.tt = TransformPreview(self.this_tree)
623
 
        try:
624
 
            self.pp.next_phase()
625
 
            self._compute_transform()
626
 
            self.pp.next_phase()
627
 
        finally:
628
 
            self.other_tree.unlock()
629
 
            self.base_tree.unlock()
630
 
            self.pb.clear()
 
831
        operation.add_cleanup(self.other_tree.unlock)
 
832
        return operation.run_simple()
 
833
 
 
834
    def _make_preview_transform(self):
 
835
        self.tt = transform.TransformPreview(self.this_tree)
 
836
        self._compute_transform()
631
837
        return self.tt
632
838
 
633
839
    def _compute_transform(self):
639
845
            resolver = self._lca_multi_way
640
846
        child_pb = ui.ui_factory.nested_progress_bar()
641
847
        try:
 
848
            factories = Merger.hooks['merge_file_content']
 
849
            hooks = [factory(self) for factory in factories] + [self]
 
850
            self.active_hooks = [hook for hook in hooks if hook is not None]
642
851
            for num, (file_id, changed, parents3, names3,
643
852
                      executable3) in enumerate(entries):
644
853
                child_pb.update('Preparing file merge', num, len(entries))
645
854
                self._merge_names(file_id, parents3, names3, resolver=resolver)
646
855
                if changed:
647
 
                    file_status = self.merge_contents(file_id)
 
856
                    file_status = self._do_merge_contents(file_id)
648
857
                else:
649
858
                    file_status = 'unmodified'
650
859
                self._merge_executable(file_id,
652
861
        finally:
653
862
            child_pb.finished()
654
863
        self.fix_root()
655
 
        self.pp.next_phase()
656
864
        child_pb = ui.ui_factory.nested_progress_bar()
657
865
        try:
658
 
            fs_conflicts = resolve_conflicts(self.tt, child_pb,
659
 
                lambda t, c: conflict_pass(t, c, self.other_tree))
 
866
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
867
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
660
868
        finally:
661
869
            child_pb.finished()
662
870
        if self.change_reporter is not None:
665
873
                self.tt.iter_changes(), self.change_reporter)
666
874
        self.cook_conflicts(fs_conflicts)
667
875
        for conflict in self.cooked_conflicts:
668
 
            warning(conflict)
 
876
            trace.warning(conflict)
669
877
 
670
878
    def _entries3(self):
671
879
        """Gather data about files modified between three trees.
856
1064
                        continue
857
1065
                else:
858
1066
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
859
 
                # XXX: We need to handle kind == 'symlink'
860
1067
 
861
1068
            # If we have gotten this far, that means something has changed
862
1069
            result.append((file_id, content_changed,
873
1080
    def fix_root(self):
874
1081
        try:
875
1082
            self.tt.final_kind(self.tt.root)
876
 
        except NoSuchFile:
 
1083
        except errors.NoSuchFile:
877
1084
            self.tt.cancel_deletion(self.tt.root)
878
1085
        if self.tt.final_file_id(self.tt.root) is None:
879
1086
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
884
1091
        other_root = self.tt.trans_id_file_id(other_root_file_id)
885
1092
        if other_root == self.tt.root:
886
1093
            return
 
1094
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1095
            # the other tree's root is a non-root in the current tree (as when
 
1096
            # a previously unrelated branch is merged into another)
 
1097
            return
887
1098
        try:
888
1099
            self.tt.final_kind(other_root)
889
 
        except NoSuchFile:
890
 
            return
891
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
892
 
            # the other tree's root is a non-root in the current tree
893
 
            return
894
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
895
 
        self.tt.cancel_creation(other_root)
896
 
        self.tt.cancel_versioning(other_root)
897
 
 
898
 
    def reparent_children(self, ie, target):
899
 
        for thing, child in ie.children.iteritems():
 
1100
            other_root_is_present = True
 
1101
        except errors.NoSuchFile:
 
1102
            # other_root doesn't have a physical representation. We still need
 
1103
            # to move any references to the actual root of the tree.
 
1104
            other_root_is_present = False
 
1105
        # 'other_tree.inventory.root' is not present in this tree. We are
 
1106
        # calling adjust_path for children which *want* to be present with a
 
1107
        # correct place to go.
 
1108
        for thing, child in self.other_tree.inventory.root.children.iteritems():
900
1109
            trans_id = self.tt.trans_id_file_id(child.file_id)
901
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
1110
            if not other_root_is_present:
 
1111
                # FIXME: Make final_kind returns None instead of raising
 
1112
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
 
1113
                try:
 
1114
                    self.tt.final_kind(trans_id)
 
1115
                    # The item exist in the final tree and has a defined place
 
1116
                    # to go already.
 
1117
                    continue
 
1118
                except errors.NoSuchFile, e:
 
1119
                    pass
 
1120
            # Move the item into the root
 
1121
            self.tt.adjust_path(self.tt.final_name(trans_id),
 
1122
                                self.tt.root, trans_id)
 
1123
        if other_root_is_present:
 
1124
            self.tt.cancel_creation(other_root)
 
1125
            self.tt.cancel_versioning(other_root)
902
1126
 
903
1127
    def write_modified(self, results):
904
1128
        modified_hashes = {}
936
1160
    @staticmethod
937
1161
    def executable(tree, file_id):
938
1162
        """Determine the executability of a file-id (used as a key method)."""
939
 
        if file_id not in tree:
 
1163
        if not tree.has_id(file_id):
940
1164
            return None
941
1165
        if tree.kind(file_id) != "file":
942
1166
            return False
945
1169
    @staticmethod
946
1170
    def kind(tree, file_id):
947
1171
        """Determine the kind of a file-id (used as a key method)."""
948
 
        if file_id not in tree:
 
1172
        if not tree.has_id(file_id):
949
1173
            return None
950
1174
        return tree.kind(file_id)
951
1175
 
952
1176
    @staticmethod
953
1177
    def _three_way(base, other, this):
954
 
        #if base == other, either they all agree, or only THIS has changed.
955
1178
        if base == other:
 
1179
            # if 'base == other', either they all agree, or only 'this' has
 
1180
            # changed.
956
1181
            return 'this'
957
1182
        elif this not in (base, other):
 
1183
            # 'this' is neither 'base' nor 'other', so both sides changed
958
1184
            return 'conflict'
959
 
        # "Ambiguous clean merge" -- both sides have made the same change.
960
1185
        elif this == other:
 
1186
            # "Ambiguous clean merge" -- both sides have made the same change.
961
1187
            return "this"
962
 
        # this == base: only other has changed.
963
1188
        else:
 
1189
            # this == base: only other has changed.
964
1190
            return "other"
965
1191
 
966
1192
    @staticmethod
1010
1236
                # only has an lca value
1011
1237
                return 'other'
1012
1238
 
1013
 
        # At this point, the lcas disagree, and the tips disagree
 
1239
        # At this point, the lcas disagree, and the tip disagree
1014
1240
        return 'conflict'
1015
1241
 
1016
1242
    @staticmethod
 
1243
    @deprecated_method(deprecated_in((2, 2, 0)))
1017
1244
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1018
1245
        """Do a three-way test on a scalar.
1019
1246
        Return "this", "other" or "conflict", depending whether a value wins.
1034
1261
 
1035
1262
    def merge_names(self, file_id):
1036
1263
        def get_entry(tree):
1037
 
            if file_id in tree.inventory:
 
1264
            if tree.has_id(file_id):
1038
1265
                return tree.inventory[file_id]
1039
1266
            else:
1040
1267
                return None
1069
1296
                parent_id_winner = "other"
1070
1297
        if name_winner == "this" and parent_id_winner == "this":
1071
1298
            return
1072
 
        if name_winner == "conflict":
1073
 
            trans_id = self.tt.trans_id_file_id(file_id)
1074
 
            self._raw_conflicts.append(('name conflict', trans_id,
1075
 
                                        this_name, other_name))
1076
 
        if parent_id_winner == "conflict":
1077
 
            trans_id = self.tt.trans_id_file_id(file_id)
1078
 
            self._raw_conflicts.append(('parent conflict', trans_id,
1079
 
                                        this_parent, other_parent))
 
1299
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1300
            # Creating helpers (.OTHER or .THIS) here cause problems down the
 
1301
            # road if a ContentConflict needs to be created so we should not do
 
1302
            # that
 
1303
            trans_id = self.tt.trans_id_file_id(file_id)
 
1304
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1305
                                        this_parent, this_name,
 
1306
                                        other_parent, other_name))
1080
1307
        if other_name is None:
1081
1308
            # it doesn't matter whether the result was 'other' or
1082
1309
            # 'conflict'-- if there's no 'other', we leave it alone.
1083
1310
            return
1084
 
        # if we get here, name_winner and parent_winner are set to safe values.
1085
 
        trans_id = self.tt.trans_id_file_id(file_id)
1086
1311
        parent_id = parents[self.winner_idx[parent_id_winner]]
1087
1312
        if parent_id is not None:
1088
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1313
            # if we get here, name_winner and parent_winner are set to safe
 
1314
            # values.
1089
1315
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1090
 
                                parent_trans_id, trans_id)
 
1316
                                self.tt.trans_id_file_id(parent_id),
 
1317
                                self.tt.trans_id_file_id(file_id))
1091
1318
 
1092
 
    def merge_contents(self, file_id):
 
1319
    def _do_merge_contents(self, file_id):
1093
1320
        """Performs a merge on file_id contents."""
1094
1321
        def contents_pair(tree):
1095
1322
            if file_id not in tree:
1103
1330
                contents = None
1104
1331
            return kind, contents
1105
1332
 
1106
 
        def contents_conflict():
1107
 
            trans_id = self.tt.trans_id_file_id(file_id)
1108
 
            name = self.tt.final_name(trans_id)
1109
 
            parent_id = self.tt.final_parent(trans_id)
1110
 
            if file_id in self.this_tree.inventory:
1111
 
                self.tt.unversion_file(trans_id)
1112
 
                if file_id in self.this_tree:
1113
 
                    self.tt.delete_contents(trans_id)
1114
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
1115
 
                                              set_version=True)
1116
 
            self._raw_conflicts.append(('contents conflict', file_group))
1117
 
 
1118
1333
        # See SPOT run.  run, SPOT, run.
1119
1334
        # So we're not QUITE repeating ourselves; we do tricky things with
1120
1335
        # file kind...
1136
1351
        if winner == 'this':
1137
1352
            # No interesting changes introduced by OTHER
1138
1353
            return "unmodified"
 
1354
        # We have a hypothetical conflict, but if we have files, then we
 
1355
        # can try to merge the content
1139
1356
        trans_id = self.tt.trans_id_file_id(file_id)
1140
 
        if winner == 'other':
 
1357
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
 
1358
            other_pair[0], winner)
 
1359
        hooks = self.active_hooks
 
1360
        hook_status = 'not_applicable'
 
1361
        for hook in hooks:
 
1362
            hook_status, lines = hook.merge_contents(params)
 
1363
            if hook_status != 'not_applicable':
 
1364
                # Don't try any more hooks, this one applies.
 
1365
                break
 
1366
        result = "modified"
 
1367
        if hook_status == 'not_applicable':
 
1368
            # This is a contents conflict, because none of the available
 
1369
            # functions could merge it.
 
1370
            result = None
 
1371
            name = self.tt.final_name(trans_id)
 
1372
            parent_id = self.tt.final_parent(trans_id)
 
1373
            if self.this_tree.has_id(file_id):
 
1374
                self.tt.unversion_file(trans_id)
 
1375
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1376
                                              set_version=True)
 
1377
            self._raw_conflicts.append(('contents conflict', file_group))
 
1378
        elif hook_status == 'success':
 
1379
            self.tt.create_file(lines, trans_id)
 
1380
        elif hook_status == 'conflicted':
 
1381
            # XXX: perhaps the hook should be able to provide
 
1382
            # the BASE/THIS/OTHER files?
 
1383
            self.tt.create_file(lines, trans_id)
 
1384
            self._raw_conflicts.append(('text conflict', trans_id))
 
1385
            name = self.tt.final_name(trans_id)
 
1386
            parent_id = self.tt.final_parent(trans_id)
 
1387
            self._dump_conflicts(name, parent_id, file_id)
 
1388
        elif hook_status == 'delete':
 
1389
            self.tt.unversion_file(trans_id)
 
1390
            result = "deleted"
 
1391
        elif hook_status == 'done':
 
1392
            # The hook function did whatever it needs to do directly, no
 
1393
            # further action needed here.
 
1394
            pass
 
1395
        else:
 
1396
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
 
1397
        if not self.this_tree.has_id(file_id) and result == "modified":
 
1398
            self.tt.version_file(file_id, trans_id)
 
1399
        # The merge has been performed, so the old contents should not be
 
1400
        # retained.
 
1401
        try:
 
1402
            self.tt.delete_contents(trans_id)
 
1403
        except errors.NoSuchFile:
 
1404
            pass
 
1405
        return result
 
1406
 
 
1407
    def _default_other_winner_merge(self, merge_hook_params):
 
1408
        """Replace this contents with other."""
 
1409
        file_id = merge_hook_params.file_id
 
1410
        trans_id = merge_hook_params.trans_id
 
1411
        file_in_this = self.this_tree.has_id(file_id)
 
1412
        if self.other_tree.has_id(file_id):
 
1413
            # OTHER changed the file
 
1414
            wt = self.this_tree
 
1415
            if wt.supports_content_filtering():
 
1416
                # We get the path from the working tree if it exists.
 
1417
                # That fails though when OTHER is adding a file, so
 
1418
                # we fall back to the other tree to find the path if
 
1419
                # it doesn't exist locally.
 
1420
                try:
 
1421
                    filter_tree_path = wt.id2path(file_id)
 
1422
                except errors.NoSuchId:
 
1423
                    filter_tree_path = self.other_tree.id2path(file_id)
 
1424
            else:
 
1425
                # Skip the id2path lookup for older formats
 
1426
                filter_tree_path = None
 
1427
            transform.create_from_tree(self.tt, trans_id,
 
1428
                             self.other_tree, file_id,
 
1429
                             filter_tree_path=filter_tree_path)
 
1430
            return 'done', None
 
1431
        elif file_in_this:
 
1432
            # OTHER deleted the file
 
1433
            return 'delete', None
 
1434
        else:
 
1435
            raise AssertionError(
 
1436
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1437
                % (file_id,))
 
1438
 
 
1439
    def merge_contents(self, merge_hook_params):
 
1440
        """Fallback merge logic after user installed hooks."""
 
1441
        # This function is used in merge hooks as the fallback instance.
 
1442
        # Perhaps making this function and the functions it calls be a 
 
1443
        # a separate class would be better.
 
1444
        if merge_hook_params.winner == 'other':
1141
1445
            # OTHER is a straight winner, so replace this contents with other
1142
 
            file_in_this = file_id in self.this_tree
1143
 
            if file_in_this:
1144
 
                # Remove any existing contents
1145
 
                self.tt.delete_contents(trans_id)
1146
 
            if file_id in self.other_tree:
1147
 
                # OTHER changed the file
1148
 
                create_from_tree(self.tt, trans_id,
1149
 
                                 self.other_tree, file_id)
1150
 
                if not file_in_this:
1151
 
                    self.tt.version_file(file_id, trans_id)
1152
 
                return "modified"
1153
 
            elif file_in_this:
1154
 
                # OTHER deleted the file
1155
 
                self.tt.unversion_file(trans_id)
1156
 
                return "deleted"
 
1446
            return self._default_other_winner_merge(merge_hook_params)
 
1447
        elif merge_hook_params.is_file_merge():
 
1448
            # THIS and OTHER are both files, so text merge.  Either
 
1449
            # BASE is a file, or both converted to files, so at least we
 
1450
            # have agreement that output should be a file.
 
1451
            try:
 
1452
                self.text_merge(merge_hook_params.file_id,
 
1453
                    merge_hook_params.trans_id)
 
1454
            except errors.BinaryFile:
 
1455
                return 'not_applicable', None
 
1456
            return 'done', None
1157
1457
        else:
1158
 
            # We have a hypothetical conflict, but if we have files, then we
1159
 
            # can try to merge the content
1160
 
            if this_pair[0] == 'file' and other_pair[0] == 'file':
1161
 
                # THIS and OTHER are both files, so text merge.  Either
1162
 
                # BASE is a file, or both converted to files, so at least we
1163
 
                # have agreement that output should be a file.
1164
 
                try:
1165
 
                    self.text_merge(file_id, trans_id)
1166
 
                except BinaryFile:
1167
 
                    return contents_conflict()
1168
 
                if file_id not in self.this_tree:
1169
 
                    self.tt.version_file(file_id, trans_id)
1170
 
                try:
1171
 
                    self.tt.tree_kind(trans_id)
1172
 
                    self.tt.delete_contents(trans_id)
1173
 
                except NoSuchFile:
1174
 
                    pass
1175
 
                return "modified"
1176
 
            else:
1177
 
                return contents_conflict()
 
1458
            return 'not_applicable', None
1178
1459
 
1179
1460
    def get_lines(self, tree, file_id):
1180
1461
        """Return the lines in a file, or an empty list."""
1181
 
        if file_id in tree:
1182
 
            return tree.get_file(file_id).readlines()
 
1462
        if tree.has_id(file_id):
 
1463
            return tree.get_file_lines(file_id)
1183
1464
        else:
1184
1465
            return []
1185
1466
 
1187
1468
        """Perform a three-way text merge on a file_id"""
1188
1469
        # it's possible that we got here with base as a different type.
1189
1470
        # if so, we just want two-way text conflicts.
1190
 
        if file_id in self.base_tree and \
 
1471
        if self.base_tree.has_id(file_id) and \
1191
1472
            self.base_tree.kind(file_id) == "file":
1192
1473
            base_lines = self.get_lines(self.base_tree, file_id)
1193
1474
        else:
1194
1475
            base_lines = []
1195
1476
        other_lines = self.get_lines(self.other_tree, file_id)
1196
1477
        this_lines = self.get_lines(self.this_tree, file_id)
1197
 
        m3 = Merge3(base_lines, this_lines, other_lines,
1198
 
                    is_cherrypick=self.cherrypick)
 
1478
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
 
1479
                           is_cherrypick=self.cherrypick)
1199
1480
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1200
1481
        if self.show_base is True:
1201
1482
            base_marker = '|' * 7
1239
1520
                ('THIS', self.this_tree, this_lines)]
1240
1521
        if not no_base:
1241
1522
            data.append(('BASE', self.base_tree, base_lines))
 
1523
 
 
1524
        # We need to use the actual path in the working tree of the file here,
 
1525
        # ignoring the conflict suffixes
 
1526
        wt = self.this_tree
 
1527
        if wt.supports_content_filtering():
 
1528
            try:
 
1529
                filter_tree_path = wt.id2path(file_id)
 
1530
            except errors.NoSuchId:
 
1531
                # file has been deleted
 
1532
                filter_tree_path = None
 
1533
        else:
 
1534
            # Skip the id2path lookup for older formats
 
1535
            filter_tree_path = None
 
1536
 
1242
1537
        versioned = False
1243
1538
        file_group = []
1244
1539
        for suffix, tree, lines in data:
1245
 
            if file_id in tree:
 
1540
            if tree.has_id(file_id):
1246
1541
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
1247
 
                                               suffix, lines)
 
1542
                                               suffix, lines, filter_tree_path)
1248
1543
                file_group.append(trans_id)
1249
1544
                if set_version and not versioned:
1250
1545
                    self.tt.version_file(file_id, trans_id)
1252
1547
        return file_group
1253
1548
 
1254
1549
    def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1255
 
                       lines=None):
 
1550
                       lines=None, filter_tree_path=None):
1256
1551
        """Emit a single conflict file."""
1257
1552
        name = name + '.' + suffix
1258
1553
        trans_id = self.tt.create_path(name, parent_id)
1259
 
        create_from_tree(self.tt, trans_id, tree, file_id, lines)
 
1554
        transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
 
1555
            filter_tree_path)
1260
1556
        return trans_id
1261
1557
 
1262
1558
    def merge_executable(self, file_id, file_status):
1286
1582
        try:
1287
1583
            if self.tt.final_kind(trans_id) != "file":
1288
1584
                return
1289
 
        except NoSuchFile:
 
1585
        except errors.NoSuchFile:
1290
1586
            return
1291
1587
        if winner == "this":
1292
1588
            executability = this_executable
1293
1589
        else:
1294
 
            if file_id in self.other_tree:
 
1590
            if self.other_tree.has_id(file_id):
1295
1591
                executability = other_executable
1296
 
            elif file_id in self.this_tree:
 
1592
            elif self.this_tree.has_id(file_id):
1297
1593
                executability = this_executable
1298
 
            elif file_id in self.base_tree:
 
1594
            elif self.base_tree_has_id(file_id):
1299
1595
                executability = base_executable
1300
1596
        if executability is not None:
1301
1597
            trans_id = self.tt.trans_id_file_id(file_id)
1303
1599
 
1304
1600
    def cook_conflicts(self, fs_conflicts):
1305
1601
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1306
 
        from conflicts import Conflict
1307
 
        name_conflicts = {}
1308
 
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
1309
 
        fp = FinalPaths(self.tt)
 
1602
        self.cooked_conflicts.extend(transform.cook_conflicts(
 
1603
                fs_conflicts, self.tt))
 
1604
        fp = transform.FinalPaths(self.tt)
1310
1605
        for conflict in self._raw_conflicts:
1311
1606
            conflict_type = conflict[0]
1312
 
            if conflict_type in ('name conflict', 'parent conflict'):
1313
 
                trans_id = conflict[1]
1314
 
                conflict_args = conflict[2:]
1315
 
                if trans_id not in name_conflicts:
1316
 
                    name_conflicts[trans_id] = {}
1317
 
                unique_add(name_conflicts[trans_id], conflict_type,
1318
 
                           conflict_args)
1319
 
            if conflict_type == 'contents conflict':
 
1607
            if conflict_type == 'path conflict':
 
1608
                (trans_id, file_id,
 
1609
                this_parent, this_name,
 
1610
                other_parent, other_name) = conflict[1:]
 
1611
                if this_parent is None or this_name is None:
 
1612
                    this_path = '<deleted>'
 
1613
                else:
 
1614
                    parent_path =  fp.get_path(
 
1615
                        self.tt.trans_id_file_id(this_parent))
 
1616
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1617
                if other_parent is None or other_name is None:
 
1618
                    other_path = '<deleted>'
 
1619
                else:
 
1620
                    parent_path =  fp.get_path(
 
1621
                        self.tt.trans_id_file_id(other_parent))
 
1622
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1623
                c = _mod_conflicts.Conflict.factory(
 
1624
                    'path conflict', path=this_path,
 
1625
                    conflict_path=other_path,
 
1626
                    file_id=file_id)
 
1627
            elif conflict_type == 'contents conflict':
1320
1628
                for trans_id in conflict[1]:
1321
1629
                    file_id = self.tt.final_file_id(trans_id)
1322
1630
                    if file_id is not None:
1326
1634
                    if path.endswith(suffix):
1327
1635
                        path = path[:-len(suffix)]
1328
1636
                        break
1329
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1330
 
                self.cooked_conflicts.append(c)
1331
 
            if conflict_type == 'text conflict':
 
1637
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1638
                                                    path=path, file_id=file_id)
 
1639
            elif conflict_type == 'text conflict':
1332
1640
                trans_id = conflict[1]
1333
1641
                path = fp.get_path(trans_id)
1334
1642
                file_id = self.tt.final_file_id(trans_id)
1335
 
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
1336
 
                self.cooked_conflicts.append(c)
1337
 
 
1338
 
        for trans_id, conflicts in name_conflicts.iteritems():
1339
 
            try:
1340
 
                this_parent, other_parent = conflicts['parent conflict']
1341
 
                if this_parent == other_parent:
1342
 
                    raise AssertionError()
1343
 
            except KeyError:
1344
 
                this_parent = other_parent = \
1345
 
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
1346
 
            try:
1347
 
                this_name, other_name = conflicts['name conflict']
1348
 
                if this_name == other_name:
1349
 
                    raise AssertionError()
1350
 
            except KeyError:
1351
 
                this_name = other_name = self.tt.final_name(trans_id)
1352
 
            other_path = fp.get_path(trans_id)
1353
 
            if this_parent is not None and this_name is not None:
1354
 
                this_parent_path = \
1355
 
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1356
 
                this_path = pathjoin(this_parent_path, this_name)
 
1643
                c = _mod_conflicts.Conflict.factory(conflict_type,
 
1644
                                                    path=path, file_id=file_id)
1357
1645
            else:
1358
 
                this_path = "<deleted>"
1359
 
            file_id = self.tt.final_file_id(trans_id)
1360
 
            c = Conflict.factory('path conflict', path=this_path,
1361
 
                                 conflict_path=other_path, file_id=file_id)
 
1646
                raise AssertionError('bad conflict type: %r' % (conflict,))
1362
1647
            self.cooked_conflicts.append(c)
1363
 
        self.cooked_conflicts.sort(key=Conflict.sort_key)
 
1648
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1364
1649
 
1365
1650
 
1366
1651
class WeaveMerger(Merge3Merger):
1370
1655
    supports_reverse_cherrypick = False
1371
1656
    history_based = True
1372
1657
 
1373
 
    def _merged_lines(self, file_id):
1374
 
        """Generate the merged lines.
1375
 
        There is no distinction between lines that are meant to contain <<<<<<<
1376
 
        and conflicts.
1377
 
        """
1378
 
        if self.cherrypick:
1379
 
            base = self.base_tree
1380
 
        else:
1381
 
            base = None
1382
 
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree,
 
1658
    def _generate_merge_plan(self, file_id, base):
 
1659
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1383
1660
                                              base=base)
 
1661
 
 
1662
    def _merged_lines(self, file_id):
 
1663
        """Generate the merged lines.
 
1664
        There is no distinction between lines that are meant to contain <<<<<<<
 
1665
        and conflicts.
 
1666
        """
 
1667
        if self.cherrypick:
 
1668
            base = self.base_tree
 
1669
        else:
 
1670
            base = None
 
1671
        plan = self._generate_merge_plan(file_id, base)
1384
1672
        if 'merge' in debug.debug_flags:
1385
1673
            plan = list(plan)
1386
1674
            trans_id = self.tt.trans_id_file_id(file_id)
1387
1675
            name = self.tt.final_name(trans_id) + '.plan'
1388
 
            contents = ('%10s|%s' % l for l in plan)
 
1676
            contents = ('%11s|%s' % l for l in plan)
1389
1677
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1390
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1391
 
            '>>>>>>> MERGE-SOURCE\n')
1392
 
        return textmerge.merge_lines(self.reprocess)
 
1678
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1679
                                                 '>>>>>>> MERGE-SOURCE\n')
 
1680
        lines, conflicts = textmerge.merge_lines(self.reprocess)
 
1681
        if conflicts:
 
1682
            base_lines = textmerge.base_from_plan()
 
1683
        else:
 
1684
            base_lines = None
 
1685
        return lines, base_lines
1393
1686
 
1394
1687
    def text_merge(self, file_id, trans_id):
1395
1688
        """Perform a (weave) text merge for a given file and file-id.
1396
1689
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1397
1690
        and a conflict will be noted.
1398
1691
        """
1399
 
        lines, conflicts = self._merged_lines(file_id)
 
1692
        lines, base_lines = self._merged_lines(file_id)
1400
1693
        lines = list(lines)
1401
1694
        # Note we're checking whether the OUTPUT is binary in this case,
1402
1695
        # because we don't want to get into weave merge guts.
1403
 
        check_text_lines(lines)
 
1696
        textfile.check_text_lines(lines)
1404
1697
        self.tt.create_file(lines, trans_id)
1405
 
        if conflicts:
 
1698
        if base_lines is not None:
 
1699
            # Conflict
1406
1700
            self._raw_conflicts.append(('text conflict', trans_id))
1407
1701
            name = self.tt.final_name(trans_id)
1408
1702
            parent_id = self.tt.final_parent(trans_id)
1409
1703
            file_group = self._dump_conflicts(name, parent_id, file_id,
1410
 
                                              no_base=True)
 
1704
                                              no_base=False,
 
1705
                                              base_lines=base_lines)
1411
1706
            file_group.append(trans_id)
1412
1707
 
1413
1708
 
1414
1709
class LCAMerger(WeaveMerger):
1415
1710
 
1416
 
    def _merged_lines(self, file_id):
1417
 
        """Generate the merged lines.
1418
 
        There is no distinction between lines that are meant to contain <<<<<<<
1419
 
        and conflicts.
1420
 
        """
1421
 
        if self.cherrypick:
1422
 
            base = self.base_tree
1423
 
        else:
1424
 
            base = None
1425
 
        plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1711
    def _generate_merge_plan(self, file_id, base):
 
1712
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1426
1713
                                                  base=base)
1427
 
        if 'merge' in debug.debug_flags:
1428
 
            plan = list(plan)
1429
 
            trans_id = self.tt.trans_id_file_id(file_id)
1430
 
            name = self.tt.final_name(trans_id) + '.plan'
1431
 
            contents = ('%10s|%s' % l for l in plan)
1432
 
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1433
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1434
 
            '>>>>>>> MERGE-SOURCE\n')
1435
 
        return textmerge.merge_lines(self.reprocess)
1436
 
 
1437
1714
 
1438
1715
class Diff3Merger(Merge3Merger):
1439
1716
    """Three-way merger using external diff3 for text merging"""
1440
1717
 
1441
1718
    def dump_file(self, temp_dir, name, tree, file_id):
1442
 
        out_path = pathjoin(temp_dir, name)
 
1719
        out_path = osutils.pathjoin(temp_dir, name)
1443
1720
        out_file = open(out_path, "wb")
1444
1721
        try:
1445
1722
            in_file = tree.get_file(file_id)
1457
1734
        import bzrlib.patch
1458
1735
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1459
1736
        try:
1460
 
            new_file = pathjoin(temp_dir, "new")
 
1737
            new_file = osutils.pathjoin(temp_dir, "new")
1461
1738
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1462
1739
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1463
1740
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1464
1741
            status = bzrlib.patch.diff3(new_file, this, base, other)
1465
1742
            if status not in (0, 1):
1466
 
                raise BzrError("Unhandled diff3 exit code")
 
1743
                raise errors.BzrError("Unhandled diff3 exit code")
1467
1744
            f = open(new_file, 'rb')
1468
1745
            try:
1469
1746
                self.tt.create_file(f, trans_id)
1487
1764
                other_rev_id=None,
1488
1765
                interesting_files=None,
1489
1766
                this_tree=None,
1490
 
                pb=DummyProgress(),
 
1767
                pb=None,
1491
1768
                change_reporter=None):
1492
1769
    """Primary interface for merging.
1493
1770
 
1496
1773
                     branch.get_revision_tree(base_revision))'
1497
1774
        """
1498
1775
    if this_tree is None:
1499
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1500
 
            "parameter as of bzrlib version 0.8.")
 
1776
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1777
                              "parameter as of bzrlib version 0.8.")
1501
1778
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1502
1779
                    pb=pb, change_reporter=change_reporter)
1503
1780
    merger.backup_files = backup_files
1516
1793
    get_revision_id = getattr(base_tree, 'get_revision_id', None)
1517
1794
    if get_revision_id is None:
1518
1795
        get_revision_id = base_tree.last_revision
 
1796
    merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1519
1797
    merger.set_base_revision(get_revision_id(), this_branch)
1520
1798
    return merger.do_merge()
1521
1799
 
1720
1998
        super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1721
1999
        self.a_key = self._key_prefix + (self.a_rev,)
1722
2000
        self.b_key = self._key_prefix + (self.b_rev,)
1723
 
        self.graph = Graph(self.vf)
 
2001
        self.graph = _mod_graph.Graph(self.vf)
1724
2002
        heads = self.graph.heads((self.a_key, self.b_key))
1725
2003
        if len(heads) == 1:
1726
2004
            # one side dominates, so we can just return its values, yay for
1731
2009
                other = b_rev
1732
2010
            else:
1733
2011
                other = a_rev
1734
 
            mutter('found dominating revision for %s\n%s > %s', self.vf,
1735
 
                   self._head_key[-1], other)
 
2012
            trace.mutter('found dominating revision for %s\n%s > %s', self.vf,
 
2013
                         self._head_key[-1], other)
1736
2014
            self._weave = None
1737
2015
        else:
1738
2016
            self._head_key = None
1752
2030
        while True:
1753
2031
            next_lcas = self.graph.find_lca(*cur_ancestors)
1754
2032
            # Map a plain NULL_REVISION to a simple no-ancestors
1755
 
            if next_lcas == set([NULL_REVISION]):
 
2033
            if next_lcas == set([_mod_revision.NULL_REVISION]):
1756
2034
                next_lcas = ()
1757
2035
            # Order the lca's based on when they were merged into the tip
1758
2036
            # While the actual merge portion of weave merge uses a set() of
1770
2048
            elif len(next_lcas) > 2:
1771
2049
                # More than 2 lca's, fall back to grabbing all nodes between
1772
2050
                # this and the unique lca.
1773
 
                mutter('More than 2 LCAs, falling back to all nodes for:'
1774
 
                       ' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
 
2051
                trace.mutter('More than 2 LCAs, falling back to all nodes for:'
 
2052
                             ' %s, %s\n=> %s',
 
2053
                             self.a_key, self.b_key, cur_ancestors)
1775
2054
                cur_lcas = next_lcas
1776
2055
                while len(cur_lcas) > 1:
1777
2056
                    cur_lcas = self.graph.find_lca(*cur_lcas)
1780
2059
                    unique_lca = None
1781
2060
                else:
1782
2061
                    unique_lca = list(cur_lcas)[0]
1783
 
                    if unique_lca == NULL_REVISION:
 
2062
                    if unique_lca == _mod_revision.NULL_REVISION:
1784
2063
                        # find_lca will return a plain 'NULL_REVISION' rather
1785
2064
                        # than a key tuple when there is no common ancestor, we
1786
2065
                        # prefer to just use None, because it doesn't confuse
1809
2088
            # We remove NULL_REVISION because it isn't a proper tuple key, and
1810
2089
            # thus confuses things like _get_interesting_texts, and our logic
1811
2090
            # to add the texts into the memory weave.
1812
 
            if NULL_REVISION in parent_map:
1813
 
                parent_map.pop(NULL_REVISION)
 
2091
            if _mod_revision.NULL_REVISION in parent_map:
 
2092
                parent_map.pop(_mod_revision.NULL_REVISION)
1814
2093
        else:
1815
2094
            interesting = set()
1816
2095
            for tip in tip_keys:
1968
2247
        lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1969
2248
        self.lcas = set()
1970
2249
        for lca in lcas:
1971
 
            if lca == NULL_REVISION:
 
2250
            if lca == _mod_revision.NULL_REVISION:
1972
2251
                self.lcas.add(lca)
1973
2252
            else:
1974
2253
                self.lcas.add(lca[-1])