~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Vincent Ladeuil
  • Date: 2007-07-15 11:24:18 UTC
  • mfrom: (2617 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070715112418-9nn4n6esxv60ny4b
merge bzr.dev@1617

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import os
19
19
import errno
20
 
from tempfile import mkdtemp
21
20
import warnings
22
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    osutils,
 
25
    registry,
 
26
    revision as _mod_revision,
 
27
    )
23
28
from bzrlib.branch import Branch
24
29
from bzrlib.conflicts import ConflictList, Conflict
25
 
from bzrlib.delta import compare_trees
26
30
from bzrlib.errors import (BzrCommandError,
27
31
                           BzrError,
28
32
                           NoCommonAncestor,
37
41
                           BinaryFile,
38
42
                           )
39
43
from bzrlib.merge3 import Merge3
40
 
import bzrlib.osutils
41
 
from bzrlib.osutils import rename, pathjoin, rmtree
 
44
from bzrlib.osutils import rename, pathjoin
42
45
from progress import DummyProgress, ProgressPhase
43
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
 
46
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
44
47
from bzrlib.textfile import check_text_lines
45
48
from bzrlib.trace import mutter, warning, note
46
49
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
47
 
                              FinalPaths, create_by_entry, unique_add)
 
50
                              conflict_pass, FinalPaths, create_by_entry,
 
51
                              unique_add, ROOT_PARENT)
48
52
from bzrlib.versionedfile import WeaveMerge
49
53
from bzrlib import ui
50
54
 
51
55
# TODO: Report back as changes are merged in
52
56
 
53
 
def _get_tree(treespec, local_branch=None):
54
 
    location, revno = treespec
55
 
    branch = Branch.open_containing(location)[0]
56
 
    if revno is None:
57
 
        revision = None
58
 
    elif revno == -1:
59
 
        revision = branch.last_revision()
60
 
    else:
61
 
        revision = branch.get_rev_id(revno)
62
 
        if revision is None:
63
 
            revision = NULL_REVISION
64
 
    return branch, _get_revid_tree(branch, revision, local_branch)
65
 
 
66
 
 
67
 
def _get_revid_tree(branch, revision, local_branch):
68
 
    if revision is None:
69
 
        base_tree = branch.bzrdir.open_workingtree()
70
 
    else:
71
 
        if local_branch is not None:
72
 
            if local_branch.base != branch.base:
73
 
                local_branch.fetch(branch, revision)
74
 
            base_tree = local_branch.repository.revision_tree(revision)
75
 
        else:
76
 
            base_tree = branch.repository.revision_tree(revision)
77
 
    return base_tree
78
 
 
79
57
 
80
58
def transform_tree(from_tree, to_tree, interesting_ids=None):
81
59
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
83
61
 
84
62
 
85
63
class Merger(object):
86
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
87
 
                 this_tree=None, pb=DummyProgress()):
 
64
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
65
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
66
                 recurse='down'):
88
67
        object.__init__(self)
89
68
        assert this_tree is not None, "this_tree is required"
90
69
        self.this_branch = this_branch
91
 
        self.this_basis = this_branch.last_revision()
 
70
        self.this_basis = _mod_revision.ensure_null(
 
71
            this_branch.last_revision())
92
72
        self.this_rev_id = None
93
73
        self.this_tree = this_tree
94
74
        self.this_revision_tree = None
95
75
        self.this_basis_tree = None
96
76
        self.other_tree = other_tree
 
77
        self.other_branch = None
97
78
        self.base_tree = base_tree
98
79
        self.ignore_zero = False
99
80
        self.backup_files = False
100
81
        self.interesting_ids = None
 
82
        self.interesting_files = None
101
83
        self.show_base = False
102
84
        self.reprocess = False
103
 
        self._pb = pb 
 
85
        self._pb = pb
104
86
        self.pp = None
105
 
 
106
 
 
107
 
    def revision_tree(self, revision_id):
108
 
        return self.this_branch.repository.revision_tree(revision_id)
 
87
        self.recurse = recurse
 
88
        self.change_reporter = change_reporter
 
89
        self._cached_trees = {}
 
90
 
 
91
    def revision_tree(self, revision_id, branch=None):
 
92
        if revision_id not in self._cached_trees:
 
93
            if branch is None:
 
94
                branch = self.this_branch
 
95
            try:
 
96
                tree = self.this_tree.revision_tree(revision_id)
 
97
            except errors.NoSuchRevisionInTree:
 
98
                tree = branch.repository.revision_tree(revision_id)
 
99
            self._cached_trees[revision_id] = tree
 
100
        return self._cached_trees[revision_id]
 
101
 
 
102
    def _get_tree(self, treespec, possible_transports=None):
 
103
        from bzrlib import workingtree
 
104
        location, revno = treespec
 
105
        if revno is None:
 
106
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
107
            return tree.branch, tree
 
108
        branch = Branch.open_containing(location, possible_transports)[0]
 
109
        if revno == -1:
 
110
            revision_id = branch.last_revision()
 
111
        else:
 
112
            revision_id = branch.get_rev_id(revno)
 
113
        revision_id = ensure_null(revision_id)
 
114
        return branch, self.revision_tree(revision_id, branch)
109
115
 
110
116
    def ensure_revision_trees(self):
111
117
        if self.this_revision_tree is None:
112
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
113
 
                self.this_basis)
 
118
            self.this_basis_tree = self.revision_tree(self.this_basis)
114
119
            if self.this_basis == self.this_rev_id:
115
120
                self.this_revision_tree = self.this_basis_tree
116
121
 
117
122
        if self.other_rev_id is None:
118
123
            other_basis_tree = self.revision_tree(self.other_basis)
119
 
            changes = compare_trees(self.other_tree, other_basis_tree)
 
124
            changes = other_basis_tree.changes_from(self.other_tree)
120
125
            if changes.has_changed():
121
126
                raise WorkingTreeNotRevision(self.this_tree)
122
127
            other_rev_id = self.other_basis
138
143
 
139
144
    def check_basis(self, check_clean, require_commits=True):
140
145
        if self.this_basis is None and require_commits is True:
141
 
            raise BzrCommandError("This branch has no commits")
 
146
            raise BzrCommandError("This branch has no commits."
 
147
                                  " (perhaps you would prefer 'bzr pull')")
142
148
        if check_clean:
143
149
            self.compare_basis()
144
150
            if self.this_basis != self.this_rev_id:
145
151
                raise BzrCommandError("Working tree has uncommitted changes.")
146
152
 
147
153
    def compare_basis(self):
148
 
        changes = compare_trees(self.this_tree, 
149
 
                                self.this_tree.basis_tree(), False)
 
154
        try:
 
155
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
156
        except errors.RevisionNotPresent:
 
157
            basis_tree = self.this_tree.basis_tree()
 
158
        changes = self.this_tree.changes_from(basis_tree)
150
159
        if not changes.has_changed():
151
160
            self.this_rev_id = self.this_basis
152
161
 
153
162
    def set_interesting_files(self, file_list):
154
 
        try:
155
 
            self._set_interesting_files(file_list)
156
 
        except NotVersionedError, e:
157
 
            raise BzrCommandError("%s is not a source file in any"
158
 
                                      " tree." % e.path)
159
 
 
160
 
    def _set_interesting_files(self, file_list):
161
 
        """Set the list of interesting ids from a list of files."""
162
 
        if file_list is None:
163
 
            self.interesting_ids = None
164
 
            return
165
 
 
166
 
        interesting_ids = set()
167
 
        for path in file_list:
168
 
            found_id = False
169
 
            for tree in (self.this_tree, self.base_tree, self.other_tree):
170
 
                file_id = tree.inventory.path2id(path)
171
 
                if file_id is not None:
172
 
                    interesting_ids.add(file_id)
173
 
                    found_id = True
174
 
            if not found_id:
175
 
                raise NotVersionedError(path=path)
176
 
        self.interesting_ids = interesting_ids
 
163
        self.interesting_files = file_list
177
164
 
178
165
    def set_pending(self):
179
 
        if not self.base_is_ancestor:
180
 
            return
181
 
        if self.other_rev_id is None:
182
 
            return
183
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
184
 
        if self.other_rev_id in ancestry:
185
 
            return
186
 
        self.this_tree.add_pending_merge(self.other_rev_id)
187
 
 
188
 
    def set_other(self, other_revision):
189
 
        other_branch, self.other_tree = _get_tree(other_revision, 
190
 
                                                  self.this_branch)
 
166
        if not self.base_is_ancestor or not self.base_is_other_ancestor:
 
167
            return
 
168
        self._add_parent()
 
169
 
 
170
    def _add_parent(self):
 
171
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
172
        new_parent_trees = []
 
173
        for revision_id in new_parents:
 
174
            try:
 
175
                tree = self.revision_tree(revision_id)
 
176
            except errors.RevisionNotPresent:
 
177
                tree = None
 
178
            else:
 
179
                tree.lock_read()
 
180
            new_parent_trees.append((revision_id, tree))
 
181
        try:
 
182
            self.this_tree.set_parent_trees(new_parent_trees,
 
183
                                            allow_leftmost_as_ghost=True)
 
184
        finally:
 
185
            for _revision_id, tree in new_parent_trees:
 
186
                if tree is not None:
 
187
                    tree.unlock()
 
188
 
 
189
    def set_other(self, other_revision, possible_transports=None):
 
190
        """Set the revision and tree to merge from.
 
191
 
 
192
        This sets the other_tree, other_rev_id, other_basis attributes.
 
193
 
 
194
        :param other_revision: The [path, revision] list to merge from.
 
195
        """
 
196
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
197
                                                            possible_transports)
191
198
        if other_revision[1] == -1:
192
 
            self.other_rev_id = other_branch.last_revision()
193
 
            if self.other_rev_id is None:
194
 
                raise NoCommits(other_branch)
 
199
            self.other_rev_id = _mod_revision.ensure_null(
 
200
                self.other_branch.last_revision())
 
201
            if _mod_revision.is_null(self.other_rev_id):
 
202
                raise NoCommits(self.other_branch)
195
203
            self.other_basis = self.other_rev_id
196
204
        elif other_revision[1] is not None:
197
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
205
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
198
206
            self.other_basis = self.other_rev_id
199
207
        else:
200
208
            self.other_rev_id = None
201
 
            self.other_basis = other_branch.last_revision()
 
209
            self.other_basis = self.other_branch.last_revision()
202
210
            if self.other_basis is None:
203
 
                raise NoCommits(other_branch)
204
 
        if other_branch.base != self.this_branch.base:
205
 
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
211
                raise NoCommits(self.other_branch)
 
212
        if self.other_rev_id is not None:
 
213
            self._cached_trees[self.other_rev_id] = self.other_tree
 
214
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
 
215
 
 
216
    def set_other_revision(self, revision_id, other_branch):
 
217
        """Set 'other' based on a branch and revision id
 
218
 
 
219
        :param revision_id: The revision to use for a tree
 
220
        :param other_branch: The branch containing this tree
 
221
        """
 
222
        self.other_rev_id = revision_id
 
223
        self.other_branch = other_branch
 
224
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
225
        self.other_tree = self.revision_tree(revision_id)
 
226
        self.other_basis = revision_id
 
227
 
 
228
    def _maybe_fetch(self, source, target, revision_id):
 
229
        if (source.repository.bzrdir.root_transport.base !=
 
230
            target.repository.bzrdir.root_transport.base):
 
231
            target.fetch(source, revision_id)
206
232
 
207
233
    def find_base(self):
208
 
        self.set_base([None, None])
 
234
        this_repo = self.this_branch.repository
 
235
        graph = this_repo.get_graph()
 
236
        revisions = [ensure_null(self.this_basis),
 
237
                     ensure_null(self.other_basis)]
 
238
        if NULL_REVISION in revisions:
 
239
            self.base_rev_id = NULL_REVISION
 
240
        else:
 
241
            self.base_rev_id = graph.find_unique_lca(*revisions)
 
242
            if self.base_rev_id == NULL_REVISION:
 
243
                raise UnrelatedBranches()
 
244
        self.base_tree = self.revision_tree(self.base_rev_id)
 
245
        self.base_is_ancestor = True
 
246
        self.base_is_other_ancestor = True
209
247
 
210
248
    def set_base(self, base_revision):
 
249
        """Set the base revision to use for the merge.
 
250
 
 
251
        :param base_revision: A 2-list containing a path and revision number.
 
252
        """
211
253
        mutter("doing merge() with no base_revision specified")
212
254
        if base_revision == [None, None]:
213
 
            try:
214
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
215
 
                try:
216
 
                    this_repo = self.this_branch.repository
217
 
                    self.base_rev_id = common_ancestor(self.this_basis, 
218
 
                                                       self.other_basis, 
219
 
                                                       this_repo, pb)
220
 
                finally:
221
 
                    pb.finished()
222
 
            except NoCommonAncestor:
223
 
                raise UnrelatedBranches()
224
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
225
 
                                            None)
226
 
            self.base_is_ancestor = True
 
255
            self.find_base()
227
256
        else:
228
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
257
            base_branch, self.base_tree = self._get_tree(base_revision)
229
258
            if base_revision[1] == -1:
230
259
                self.base_rev_id = base_branch.last_revision()
231
260
            elif base_revision[1] is None:
232
 
                self.base_rev_id = None
 
261
                self.base_rev_id = _mod_revision.NULL_REVISION
233
262
            else:
234
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
235
 
            if self.this_branch.base != base_branch.base:
236
 
                self.this_branch.fetch(base_branch)
 
263
                self.base_rev_id = _mod_revision.ensure_null(
 
264
                    base_branch.get_rev_id(base_revision[1]))
 
265
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
237
266
            self.base_is_ancestor = is_ancestor(self.this_basis, 
238
267
                                                self.base_rev_id,
239
268
                                                self.this_branch)
 
269
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
270
                                                      self.base_rev_id,
 
271
                                                      self.this_branch)
240
272
 
241
273
    def do_merge(self):
242
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
243
 
                  'other_tree': self.other_tree, 
 
274
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
275
                  'other_tree': self.other_tree,
244
276
                  'interesting_ids': self.interesting_ids,
 
277
                  'interesting_files': self.interesting_files,
245
278
                  'pp': self.pp}
246
279
        if self.merge_type.requires_base:
247
280
            kwargs['base_tree'] = self.base_tree
255
288
        elif self.show_base:
256
289
            raise BzrError("Showing base is not supported for this"
257
290
                                  " merge type. %s" % self.merge_type)
258
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
291
        self.this_tree.lock_tree_write()
 
292
        if self.base_tree is not None:
 
293
            self.base_tree.lock_read()
 
294
        if self.other_tree is not None:
 
295
            self.other_tree.lock_read()
 
296
        try:
 
297
            merge = self.merge_type(pb=self._pb,
 
298
                                    change_reporter=self.change_reporter,
 
299
                                    **kwargs)
 
300
            if self.recurse == 'down':
 
301
                for path, file_id in self.this_tree.iter_references():
 
302
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
303
                    other_revision = self.other_tree.get_reference_revision(
 
304
                        file_id, path)
 
305
                    if  other_revision == sub_tree.last_revision():
 
306
                        continue
 
307
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
308
                    sub_merge.merge_type = self.merge_type
 
309
                    relpath = self.this_tree.relpath(path)
 
310
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
311
                    sub_merge.set_other_revision(other_revision, other_branch)
 
312
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
313
                    sub_merge.base_tree = \
 
314
                        sub_tree.branch.repository.revision_tree(base_revision)
 
315
                    sub_merge.do_merge()
 
316
 
 
317
        finally:
 
318
            if self.other_tree is not None:
 
319
                self.other_tree.unlock()
 
320
            if self.base_tree is not None:
 
321
                self.base_tree.unlock()
 
322
            self.this_tree.unlock()
259
323
        if len(merge.cooked_conflicts) == 0:
260
324
            if not self.ignore_zero:
261
325
                note("All changes applied successfully.")
264
328
 
265
329
        return len(merge.cooked_conflicts)
266
330
 
267
 
    def regen_inventory(self, new_entries):
268
 
        old_entries = self.this_tree.read_working_inventory()
269
 
        new_inventory = {}
270
 
        by_path = {}
271
 
        new_entries_map = {} 
272
 
        for path, file_id in new_entries:
273
 
            if path is None:
274
 
                continue
275
 
            new_entries_map[file_id] = path
276
 
 
277
 
        def id2path(file_id):
278
 
            path = new_entries_map.get(file_id)
279
 
            if path is not None:
280
 
                return path
281
 
            entry = old_entries[file_id]
282
 
            if entry.parent_id is None:
283
 
                return entry.name
284
 
            return pathjoin(id2path(entry.parent_id), entry.name)
285
 
            
286
 
        for file_id in old_entries:
287
 
            entry = old_entries[file_id]
288
 
            path = id2path(file_id)
289
 
            if file_id in self.base_tree.inventory:
290
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
291
 
            else:
292
 
                executable = getattr(entry, 'executable', False)
293
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
294
 
                                      entry.kind, executable)
295
 
                                      
296
 
            by_path[path] = file_id
297
 
        
298
 
        deletions = 0
299
 
        insertions = 0
300
 
        new_path_list = []
301
 
        for path, file_id in new_entries:
302
 
            if path is None:
303
 
                del new_inventory[file_id]
304
 
                deletions += 1
305
 
            else:
306
 
                new_path_list.append((path, file_id))
307
 
                if file_id not in old_entries:
308
 
                    insertions += 1
309
 
        # Ensure no file is added before its parent
310
 
        new_path_list.sort()
311
 
        for path, file_id in new_path_list:
312
 
            if path == '':
313
 
                parent = None
314
 
            else:
315
 
                parent = by_path[os.path.dirname(path)]
316
 
            abspath = pathjoin(self.this_tree.basedir, path)
317
 
            kind = bzrlib.osutils.file_kind(abspath)
318
 
            if file_id in self.base_tree.inventory:
319
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
320
 
            else:
321
 
                executable = False
322
 
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
323
 
            by_path[path] = file_id 
324
 
 
325
 
        # Get a list in insertion order
326
 
        new_inventory_list = new_inventory.values()
327
 
        mutter ("""Inventory regeneration:
328
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
329
 
            % (len(old_entries), insertions, deletions, 
330
 
               len(new_inventory_list)))
331
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
332
 
            - deletions
333
 
        new_inventory_list.sort()
334
 
        return new_inventory_list
335
 
 
336
331
 
337
332
class Merge3Merger(object):
338
333
    """Three-way merger that uses the merge3 text merger"""
340
335
    supports_reprocess = True
341
336
    supports_show_base = True
342
337
    history_based = False
 
338
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
343
339
 
344
340
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
345
341
                 interesting_ids=None, reprocess=False, show_base=False,
346
 
                 pb=DummyProgress(), pp=None):
347
 
        """Initialize the merger object and perform the merge."""
 
342
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
343
                 interesting_files=None):
 
344
        """Initialize the merger object and perform the merge.
 
345
 
 
346
        :param working_tree: The working tree to apply the merge to
 
347
        :param this_tree: The local tree in the merge operation
 
348
        :param base_tree: The common tree in the merge operation
 
349
        :param other_tree: The other other tree to merge changes from
 
350
        :param interesting_ids: The file_ids of files that should be
 
351
            participate in the merge.  May not be combined with
 
352
            interesting_files.
 
353
        :param: reprocess If True, perform conflict-reduction processing.
 
354
        :param show_base: If True, show the base revision in text conflicts.
 
355
            (incompatible with reprocess)
 
356
        :param pb: A Progress bar
 
357
        :param pp: A ProgressPhase object
 
358
        :param change_reporter: An object that should report changes made
 
359
        :param interesting_files: The tree-relative paths of files that should
 
360
            participate in the merge.  If these paths refer to directories,
 
361
            the contents of those directories will also be included.  May not
 
362
            be combined with interesting_ids.  If neither interesting_files nor
 
363
            interesting_ids is specified, all files may participate in the
 
364
            merge.
 
365
        """
348
366
        object.__init__(self)
 
367
        if interesting_files is not None:
 
368
            assert interesting_ids is None
 
369
        self.interesting_ids = interesting_ids
 
370
        self.interesting_files = interesting_files
349
371
        self.this_tree = working_tree
 
372
        self.this_tree.lock_tree_write()
350
373
        self.base_tree = base_tree
 
374
        self.base_tree.lock_read()
351
375
        self.other_tree = other_tree
 
376
        self.other_tree.lock_read()
352
377
        self._raw_conflicts = []
353
378
        self.cooked_conflicts = []
354
379
        self.reprocess = reprocess
355
380
        self.show_base = show_base
356
381
        self.pb = pb
357
382
        self.pp = pp
 
383
        self.change_reporter = change_reporter
358
384
        if self.pp is None:
359
385
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
360
386
 
361
 
        if interesting_ids is not None:
362
 
            all_ids = interesting_ids
363
 
        else:
364
 
            all_ids = set(base_tree)
365
 
            all_ids.update(other_tree)
366
 
        working_tree.lock_write()
367
387
        self.tt = TreeTransform(working_tree, self.pb)
368
388
        try:
369
389
            self.pp.next_phase()
 
390
            entries = self._entries3()
370
391
            child_pb = ui.ui_factory.nested_progress_bar()
371
392
            try:
372
 
                for num, file_id in enumerate(all_ids):
373
 
                    child_pb.update('Preparing file merge', num, len(all_ids))
374
 
                    self.merge_names(file_id)
375
 
                    file_status = self.merge_contents(file_id)
376
 
                    self.merge_executable(file_id, file_status)
 
393
                for num, (file_id, changed, parents3, names3,
 
394
                          executable3) in enumerate(entries):
 
395
                    child_pb.update('Preparing file merge', num, len(entries))
 
396
                    self._merge_names(file_id, parents3, names3)
 
397
                    if changed:
 
398
                        file_status = self.merge_contents(file_id)
 
399
                    else:
 
400
                        file_status = 'unmodified'
 
401
                    self._merge_executable(file_id,
 
402
                        executable3, file_status)
377
403
            finally:
378
404
                child_pb.finished()
379
 
                
 
405
            self.fix_root()
380
406
            self.pp.next_phase()
381
407
            child_pb = ui.ui_factory.nested_progress_bar()
382
408
            try:
383
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
409
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
410
                    lambda t, c: conflict_pass(t, c, self.other_tree))
384
411
            finally:
385
412
                child_pb.finished()
 
413
            if change_reporter is not None:
 
414
                from bzrlib import delta
 
415
                delta.report_changes(self.tt._iter_changes(), change_reporter)
386
416
            self.cook_conflicts(fs_conflicts)
387
417
            for conflict in self.cooked_conflicts:
388
418
                warning(conflict)
389
419
            self.pp.next_phase()
390
 
            results = self.tt.apply()
 
420
            results = self.tt.apply(no_conflicts=True)
391
421
            self.write_modified(results)
392
422
            try:
393
423
                working_tree.add_conflicts(self.cooked_conflicts)
395
425
                pass
396
426
        finally:
397
427
            self.tt.finalize()
398
 
            working_tree.unlock()
 
428
            self.other_tree.unlock()
 
429
            self.base_tree.unlock()
 
430
            self.this_tree.unlock()
399
431
            self.pb.clear()
400
432
 
 
433
    def _entries3(self):
 
434
        """Gather data about files modified between three trees.
 
435
 
 
436
        Return a list of tuples of file_id, changed, parents3, names3,
 
437
        executable3.  changed is a boolean indicating whether the file contents
 
438
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
439
        other and this.  names3 is a tuple of names for base, other and this.
 
440
        executable3 is a tuple of execute-bit values for base, other and this.
 
441
        """
 
442
        result = []
 
443
        iterator = self.other_tree._iter_changes(self.base_tree,
 
444
                include_unchanged=True, specific_files=self.interesting_files,
 
445
                extra_trees=[self.this_tree])
 
446
        for (file_id, paths, changed, versioned, parents, names, kind,
 
447
             executable) in iterator:
 
448
            if (self.interesting_ids is not None and
 
449
                file_id not in self.interesting_ids):
 
450
                continue
 
451
            if file_id in self.this_tree.inventory:
 
452
                entry = self.this_tree.inventory[file_id]
 
453
                this_name = entry.name
 
454
                this_parent = entry.parent_id
 
455
                this_executable = entry.executable
 
456
            else:
 
457
                this_name = None
 
458
                this_parent = None
 
459
                this_executable = None
 
460
            parents3 = parents + (this_parent,)
 
461
            names3 = names + (this_name,)
 
462
            executable3 = executable + (this_executable,)
 
463
            result.append((file_id, changed, parents3, names3, executable3))
 
464
        return result
 
465
 
 
466
    def fix_root(self):
 
467
        try:
 
468
            self.tt.final_kind(self.tt.root)
 
469
        except NoSuchFile:
 
470
            self.tt.cancel_deletion(self.tt.root)
 
471
        if self.tt.final_file_id(self.tt.root) is None:
 
472
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
473
                                 self.tt.root)
 
474
        if self.other_tree.inventory.root is None:
 
475
            return
 
476
        other_root_file_id = self.other_tree.inventory.root.file_id
 
477
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
478
        if other_root == self.tt.root:
 
479
            return
 
480
        try:
 
481
            self.tt.final_kind(other_root)
 
482
        except NoSuchFile:
 
483
            return
 
484
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
485
        self.tt.cancel_creation(other_root)
 
486
        self.tt.cancel_versioning(other_root)
 
487
 
 
488
    def reparent_children(self, ie, target):
 
489
        for thing, child in ie.children.iteritems():
 
490
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
491
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
492
 
401
493
    def write_modified(self, results):
402
494
        modified_hashes = {}
403
495
        for path in results.modified_paths:
448
540
        return tree.kind(file_id)
449
541
 
450
542
    @staticmethod
 
543
    def _three_way(base, other, this):
 
544
        #if base == other, either they all agree, or only THIS has changed.
 
545
        if base == other:
 
546
            return 'this'
 
547
        elif this not in (base, other):
 
548
            return 'conflict'
 
549
        # "Ambiguous clean merge" -- both sides have made the same change.
 
550
        elif this == other:
 
551
            return "this"
 
552
        # this == base: only other has changed.
 
553
        else:
 
554
            return "other"
 
555
 
 
556
    @staticmethod
451
557
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
452
558
        """Do a three-way test on a scalar.
453
559
        Return "this", "other" or "conflict", depending whether a value wins.
468
574
            return "other"
469
575
 
470
576
    def merge_names(self, file_id):
471
 
        """Perform a merge on file_id names and parents"""
472
577
        def get_entry(tree):
473
578
            if file_id in tree.inventory:
474
579
                return tree.inventory[file_id]
477
582
        this_entry = get_entry(self.this_tree)
478
583
        other_entry = get_entry(self.other_tree)
479
584
        base_entry = get_entry(self.base_tree)
480
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
481
 
                                            other_entry, file_id, self.name)
482
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
483
 
                                                 other_entry, file_id, 
484
 
                                                 self.parent)
485
 
        if this_entry is None:
 
585
        entries = (base_entry, other_entry, this_entry)
 
586
        names = []
 
587
        parents = []
 
588
        for entry in entries:
 
589
            if entry is None:
 
590
                names.append(None)
 
591
                parents.append(None)
 
592
            else:
 
593
                names.append(entry.name)
 
594
                parents.append(entry.parent_id)
 
595
        return self._merge_names(file_id, parents, names)
 
596
 
 
597
    def _merge_names(self, file_id, parents, names):
 
598
        """Perform a merge on file_id names and parents"""
 
599
        base_name, other_name, this_name = names
 
600
        base_parent, other_parent, this_parent = parents
 
601
 
 
602
        name_winner = self._three_way(*names)
 
603
 
 
604
        parent_id_winner = self._three_way(*parents)
 
605
        if this_name is None:
486
606
            if name_winner == "this":
487
607
                name_winner = "other"
488
608
            if parent_id_winner == "this":
492
612
        if name_winner == "conflict":
493
613
            trans_id = self.tt.trans_id_file_id(file_id)
494
614
            self._raw_conflicts.append(('name conflict', trans_id, 
495
 
                                        self.name(this_entry, file_id), 
496
 
                                        self.name(other_entry, file_id)))
 
615
                                        this_name, other_name))
497
616
        if parent_id_winner == "conflict":
498
617
            trans_id = self.tt.trans_id_file_id(file_id)
499
618
            self._raw_conflicts.append(('parent conflict', trans_id, 
500
 
                                        self.parent(this_entry, file_id), 
501
 
                                        self.parent(other_entry, file_id)))
502
 
        if other_entry is None:
 
619
                                        this_parent, other_parent))
 
620
        if other_name is None:
503
621
            # it doesn't matter whether the result was 'other' or 
504
622
            # 'conflict'-- if there's no 'other', we leave it alone.
505
623
            return
506
624
        # if we get here, name_winner and parent_winner are set to safe values.
507
 
        winner_entry = {"this": this_entry, "other": other_entry, 
508
 
                        "conflict": other_entry}
509
625
        trans_id = self.tt.trans_id_file_id(file_id)
510
 
        parent_id = winner_entry[parent_id_winner].parent_id
511
 
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
512
 
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
513
 
                            trans_id)
 
626
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
627
        if parent_id is not None:
 
628
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
629
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
630
                                parent_trans_id, trans_id)
514
631
 
515
632
    def merge_contents(self, file_id):
516
633
        """Performa a merge on file_id contents."""
518
635
            if file_id not in tree:
519
636
                return (None, None)
520
637
            kind = tree.kind(file_id)
521
 
            if kind == "root_directory":
522
 
                kind = "directory"
523
638
            if kind == "file":
524
639
                contents = tree.get_file_sha1(file_id)
525
640
            elif kind == "symlink":
534
649
            parent_id = self.tt.final_parent(trans_id)
535
650
            if file_id in self.this_tree.inventory:
536
651
                self.tt.unversion_file(trans_id)
537
 
                self.tt.delete_contents(trans_id)
 
652
                if file_id in self.this_tree:
 
653
                    self.tt.delete_contents(trans_id)
538
654
            file_group = self._dump_conflicts(name, parent_id, file_id, 
539
655
                                              set_version=True)
540
656
            self._raw_conflicts.append(('contents conflict', file_group))
676
792
 
677
793
    def merge_executable(self, file_id, file_status):
678
794
        """Perform a merge on the execute bit."""
 
795
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
796
                      self.other_tree, self.this_tree)]
 
797
        self._merge_executable(file_id, executable, file_status)
 
798
 
 
799
    def _merge_executable(self, file_id, executable, file_status):
 
800
        """Perform a merge on the execute bit."""
 
801
        base_executable, other_executable, this_executable = executable
679
802
        if file_status == "deleted":
680
803
            return
681
804
        trans_id = self.tt.trans_id_file_id(file_id)
684
807
                return
685
808
        except NoSuchFile:
686
809
            return
687
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
688
 
                                       self.other_tree, file_id, 
689
 
                                       self.executable)
 
810
        winner = self._three_way(*executable)
690
811
        if winner == "conflict":
691
812
        # There must be a None in here, if we have a conflict, but we
692
813
        # need executability since file status was not deleted.
696
817
                winner = "other"
697
818
        if winner == "this":
698
819
            if file_status == "modified":
699
 
                executability = self.this_tree.is_executable(file_id)
 
820
                executability = this_executable
700
821
                if executability is not None:
701
822
                    trans_id = self.tt.trans_id_file_id(file_id)
702
823
                    self.tt.set_executability(executability, trans_id)
703
824
        else:
704
825
            assert winner == "other"
705
826
            if file_id in self.other_tree:
706
 
                executability = self.other_tree.is_executable(file_id)
 
827
                executability = other_executable
707
828
            elif file_id in self.this_tree:
708
 
                executability = self.this_tree.is_executable(file_id)
 
829
                executability = this_executable
709
830
            elif file_id in self.base_tree:
710
 
                executability = self.base_tree.is_executable(file_id)
 
831
                executability = base_executable
711
832
            if executability is not None:
712
833
                trans_id = self.tt.trans_id_file_id(file_id)
713
834
                self.tt.set_executability(executability, trans_id)
759
880
            except KeyError:
760
881
                this_name = other_name = self.tt.final_name(trans_id)
761
882
            other_path = fp.get_path(trans_id)
762
 
            if this_parent is not None:
 
883
            if this_parent is not None and this_name is not None:
763
884
                this_parent_path = \
764
885
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
765
886
                this_path = pathjoin(this_parent_path, this_name)
779
900
 
780
901
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
781
902
                 interesting_ids=None, pb=DummyProgress(), pp=None,
782
 
                 reprocess=False):
 
903
                 reprocess=False, change_reporter=None,
 
904
                 interesting_files=None):
783
905
        self.this_revision_tree = self._get_revision_tree(this_tree)
784
906
        self.other_revision_tree = self._get_revision_tree(other_tree)
785
907
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
786
908
                                          base_tree, other_tree, 
787
909
                                          interesting_ids=interesting_ids, 
788
 
                                          pb=pb, pp=pp, reprocess=reprocess)
 
910
                                          pb=pb, pp=pp, reprocess=reprocess,
 
911
                                          change_reporter=change_reporter)
789
912
 
790
913
    def _get_revision_tree(self, tree):
791
914
        """Return a revision tree related to this tree.
860
983
        will be dumped, and a will be conflict noted.
861
984
        """
862
985
        import bzrlib.patch
863
 
        temp_dir = mkdtemp(prefix="bzr-")
 
986
        temp_dir = osutils.mkdtemp(prefix="bzr-")
864
987
        try:
865
988
            new_file = pathjoin(temp_dir, "new")
866
989
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
878
1001
                name = self.tt.final_name(trans_id)
879
1002
                parent_id = self.tt.final_parent(trans_id)
880
1003
                self._dump_conflicts(name, parent_id, file_id)
881
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
1004
                self._raw_conflicts.append(('text conflict', trans_id))
882
1005
        finally:
883
 
            rmtree(temp_dir)
 
1006
            osutils.rmtree(temp_dir)
884
1007
 
885
1008
 
886
1009
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
887
 
                backup_files=False, 
888
 
                merge_type=Merge3Merger, 
889
 
                interesting_ids=None, 
890
 
                show_base=False, 
891
 
                reprocess=False, 
 
1010
                backup_files=False,
 
1011
                merge_type=Merge3Merger,
 
1012
                interesting_ids=None,
 
1013
                show_base=False,
 
1014
                reprocess=False,
892
1015
                other_rev_id=None,
893
1016
                interesting_files=None,
894
1017
                this_tree=None,
895
 
                pb=DummyProgress()):
 
1018
                pb=DummyProgress(),
 
1019
                change_reporter=None):
896
1020
    """Primary interface for merging. 
897
1021
 
898
1022
        typical use is probably 
900
1024
                     branch.get_revision_tree(base_revision))'
901
1025
        """
902
1026
    if this_tree is None:
903
 
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
904
 
             "bzrlib version 0.8.",
905
 
             DeprecationWarning,
906
 
             stacklevel=2)
907
 
        this_tree = this_branch.bzrdir.open_workingtree()
908
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
909
 
                    pb=pb)
 
1027
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1028
            "parameter as of bzrlib version 0.8.")
 
1029
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1030
                    pb=pb, change_reporter=change_reporter)
910
1031
    merger.backup_files = backup_files
911
1032
    merger.merge_type = merge_type
912
1033
    merger.interesting_ids = interesting_ids
914
1035
    if interesting_files:
915
1036
        assert not interesting_ids, ('Only supply interesting_ids'
916
1037
                                     ' or interesting_files')
917
 
        merger._set_interesting_files(interesting_files)
918
 
    merger.show_base = show_base 
 
1038
        merger.interesting_files = interesting_files
 
1039
    merger.show_base = show_base
919
1040
    merger.reprocess = reprocess
920
1041
    merger.other_rev_id = other_rev_id
921
1042
    merger.other_basis = other_rev_id
922
1043
    return merger.do_merge()
923
1044
 
924
 
 
925
 
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
926
 
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
927
 
                     'weave': (WeaveMerger, "Weave-based merge")
928
 
              }
929
 
 
930
 
 
931
 
def merge_type_help():
932
 
    templ = '%s%%7s: %%s' % (' '*12)
933
 
    lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
934
 
    return '\n'.join(lines)
 
1045
def get_merge_type_registry():
 
1046
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1047
 
 
1048
    This method provides a sanctioned way to retrieve it.
 
1049
    """
 
1050
    from bzrlib import option
 
1051
    return option._merge_type_registry