~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2006-07-27 18:14:48 UTC
  • mto: (1946.2.6 reduce-knit-churn)
  • mto: This revision was merged to the branch mainline in revision 1887.
  • Revision ID: john@arbash-meinel.com-20060727181448-62962f823be5d3cc
minor typo fix

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
20
21
import warnings
21
22
 
22
 
from bzrlib import (
23
 
    errors,
24
 
    osutils,
25
 
    registry,
26
 
    revision as _mod_revision,
27
 
    )
28
23
from bzrlib.branch import Branch
29
24
from bzrlib.conflicts import ConflictList, Conflict
 
25
from bzrlib.delta import compare_trees
30
26
from bzrlib.errors import (BzrCommandError,
31
27
                           BzrError,
32
28
                           NoCommonAncestor,
41
37
                           BinaryFile,
42
38
                           )
43
39
from bzrlib.merge3 import Merge3
44
 
from bzrlib.osutils import rename, pathjoin
 
40
import bzrlib.osutils
 
41
from bzrlib.osutils import rename, pathjoin, rmtree
45
42
from progress import DummyProgress, ProgressPhase
46
 
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
 
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
47
44
from bzrlib.textfile import check_text_lines
48
45
from bzrlib.trace import mutter, warning, note
49
46
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
50
 
                              conflict_pass, FinalPaths, create_by_entry,
51
 
                              unique_add, ROOT_PARENT)
 
47
                              FinalPaths, create_by_entry, unique_add)
52
48
from bzrlib.versionedfile import WeaveMerge
53
49
from bzrlib import ui
54
50
 
55
51
# TODO: Report back as changes are merged in
56
52
 
 
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
 
57
79
 
58
80
def transform_tree(from_tree, to_tree, interesting_ids=None):
59
81
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
61
83
 
62
84
 
63
85
class Merger(object):
64
 
    def __init__(self, this_branch, other_tree=None, base_tree=None,
65
 
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
66
 
                 recurse='down'):
 
86
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
 
87
                 this_tree=None, pb=DummyProgress()):
67
88
        object.__init__(self)
68
89
        assert this_tree is not None, "this_tree is required"
69
90
        self.this_branch = this_branch
70
 
        self.this_basis = _mod_revision.ensure_null(
71
 
            this_branch.last_revision())
 
91
        self.this_basis = this_branch.last_revision()
72
92
        self.this_rev_id = None
73
93
        self.this_tree = this_tree
74
94
        self.this_revision_tree = None
75
95
        self.this_basis_tree = None
76
96
        self.other_tree = other_tree
77
 
        self.other_branch = None
78
97
        self.base_tree = base_tree
79
98
        self.ignore_zero = False
80
99
        self.backup_files = False
81
100
        self.interesting_ids = None
82
 
        self.interesting_files = None
83
101
        self.show_base = False
84
102
        self.reprocess = False
85
 
        self._pb = pb
 
103
        self._pb = pb 
86
104
        self.pp = None
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)
 
105
 
 
106
 
 
107
    def revision_tree(self, revision_id):
 
108
        return self.this_branch.repository.revision_tree(revision_id)
115
109
 
116
110
    def ensure_revision_trees(self):
117
111
        if self.this_revision_tree is None:
118
 
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
112
            self.this_basis_tree = self.this_branch.repository.revision_tree(
 
113
                self.this_basis)
119
114
            if self.this_basis == self.this_rev_id:
120
115
                self.this_revision_tree = self.this_basis_tree
121
116
 
122
117
        if self.other_rev_id is None:
123
118
            other_basis_tree = self.revision_tree(self.other_basis)
124
 
            changes = other_basis_tree.changes_from(self.other_tree)
 
119
            changes = compare_trees(self.other_tree, other_basis_tree)
125
120
            if changes.has_changed():
126
121
                raise WorkingTreeNotRevision(self.this_tree)
127
122
            other_rev_id = self.other_basis
143
138
 
144
139
    def check_basis(self, check_clean, require_commits=True):
145
140
        if self.this_basis is None and require_commits is True:
146
 
            raise BzrCommandError("This branch has no commits."
147
 
                                  " (perhaps you would prefer 'bzr pull')")
 
141
            raise BzrCommandError("This branch has no commits")
148
142
        if check_clean:
149
143
            self.compare_basis()
150
144
            if self.this_basis != self.this_rev_id:
151
145
                raise BzrCommandError("Working tree has uncommitted changes.")
152
146
 
153
147
    def compare_basis(self):
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)
 
148
        changes = compare_trees(self.this_tree, 
 
149
                                self.this_tree.basis_tree(), False)
159
150
        if not changes.has_changed():
160
151
            self.this_rev_id = self.this_basis
161
152
 
162
153
    def set_interesting_files(self, file_list):
163
 
        self.interesting_files = 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
164
177
 
165
178
    def set_pending(self):
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)
 
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)
198
191
        if other_revision[1] == -1:
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)
 
192
            self.other_rev_id = other_branch.last_revision()
 
193
            if self.other_rev_id is None:
 
194
                raise NoCommits(other_branch)
203
195
            self.other_basis = self.other_rev_id
204
196
        elif other_revision[1] is not None:
205
 
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
 
197
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
206
198
            self.other_basis = self.other_rev_id
207
199
        else:
208
200
            self.other_rev_id = None
209
 
            self.other_basis = self.other_branch.last_revision()
 
201
            self.other_basis = other_branch.last_revision()
210
202
            if self.other_basis is None:
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)
 
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)
232
206
 
233
207
    def find_base(self):
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
 
208
        self.set_base([None, None])
247
209
 
248
210
    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
 
        """
253
211
        mutter("doing merge() with no base_revision specified")
254
212
        if base_revision == [None, None]:
255
 
            self.find_base()
 
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
256
227
        else:
257
 
            base_branch, self.base_tree = self._get_tree(base_revision)
 
228
            base_branch, self.base_tree = _get_tree(base_revision)
258
229
            if base_revision[1] == -1:
259
230
                self.base_rev_id = base_branch.last_revision()
260
231
            elif base_revision[1] is None:
261
 
                self.base_rev_id = _mod_revision.NULL_REVISION
 
232
                self.base_rev_id = None
262
233
            else:
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)
 
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)
266
237
            self.base_is_ancestor = is_ancestor(self.this_basis, 
267
238
                                                self.base_rev_id,
268
239
                                                self.this_branch)
269
 
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
270
 
                                                      self.base_rev_id,
271
 
                                                      self.this_branch)
272
240
 
273
241
    def do_merge(self):
274
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
275
 
                  'other_tree': self.other_tree,
 
242
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
 
243
                  'other_tree': self.other_tree, 
276
244
                  'interesting_ids': self.interesting_ids,
277
 
                  'interesting_files': self.interesting_files,
278
245
                  'pp': self.pp}
279
246
        if self.merge_type.requires_base:
280
247
            kwargs['base_tree'] = self.base_tree
288
255
        elif self.show_base:
289
256
            raise BzrError("Showing base is not supported for this"
290
257
                                  " merge type. %s" % self.merge_type)
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()
 
258
        merge = self.merge_type(pb=self._pb, **kwargs)
323
259
        if len(merge.cooked_conflicts) == 0:
324
260
            if not self.ignore_zero:
325
261
                note("All changes applied successfully.")
328
264
 
329
265
        return len(merge.cooked_conflicts)
330
266
 
 
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
 
331
336
 
332
337
class Merge3Merger(object):
333
338
    """Three-way merger that uses the merge3 text merger"""
335
340
    supports_reprocess = True
336
341
    supports_show_base = True
337
342
    history_based = False
338
 
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
339
343
 
340
344
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
341
345
                 interesting_ids=None, reprocess=False, show_base=False,
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
 
        """
 
346
                 pb=DummyProgress(), pp=None):
 
347
        """Initialize the merger object and perform the merge."""
366
348
        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
371
349
        self.this_tree = working_tree
372
 
        self.this_tree.lock_tree_write()
373
350
        self.base_tree = base_tree
374
 
        self.base_tree.lock_read()
375
351
        self.other_tree = other_tree
376
 
        self.other_tree.lock_read()
377
352
        self._raw_conflicts = []
378
353
        self.cooked_conflicts = []
379
354
        self.reprocess = reprocess
380
355
        self.show_base = show_base
381
356
        self.pb = pb
382
357
        self.pp = pp
383
 
        self.change_reporter = change_reporter
384
358
        if self.pp is None:
385
359
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
386
360
 
 
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()
387
367
        self.tt = TreeTransform(working_tree, self.pb)
388
368
        try:
389
369
            self.pp.next_phase()
390
 
            entries = self._entries3()
391
370
            child_pb = ui.ui_factory.nested_progress_bar()
392
371
            try:
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)
 
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)
403
377
            finally:
404
378
                child_pb.finished()
405
 
            self.fix_root()
 
379
                
406
380
            self.pp.next_phase()
407
381
            child_pb = ui.ui_factory.nested_progress_bar()
408
382
            try:
409
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
410
 
                    lambda t, c: conflict_pass(t, c, self.other_tree))
 
383
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
411
384
            finally:
412
385
                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)
416
386
            self.cook_conflicts(fs_conflicts)
417
387
            for conflict in self.cooked_conflicts:
418
388
                warning(conflict)
419
389
            self.pp.next_phase()
420
 
            results = self.tt.apply(no_conflicts=True)
 
390
            results = self.tt.apply()
421
391
            self.write_modified(results)
422
392
            try:
423
393
                working_tree.add_conflicts(self.cooked_conflicts)
425
395
                pass
426
396
        finally:
427
397
            self.tt.finalize()
428
 
            self.other_tree.unlock()
429
 
            self.base_tree.unlock()
430
 
            self.this_tree.unlock()
 
398
            working_tree.unlock()
431
399
            self.pb.clear()
432
400
 
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
 
 
493
401
    def write_modified(self, results):
494
402
        modified_hashes = {}
495
403
        for path in results.modified_paths:
540
448
        return tree.kind(file_id)
541
449
 
542
450
    @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
557
451
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
558
452
        """Do a three-way test on a scalar.
559
453
        Return "this", "other" or "conflict", depending whether a value wins.
574
468
            return "other"
575
469
 
576
470
    def merge_names(self, file_id):
 
471
        """Perform a merge on file_id names and parents"""
577
472
        def get_entry(tree):
578
473
            if file_id in tree.inventory:
579
474
                return tree.inventory[file_id]
582
477
        this_entry = get_entry(self.this_tree)
583
478
        other_entry = get_entry(self.other_tree)
584
479
        base_entry = get_entry(self.base_tree)
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:
 
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:
606
486
            if name_winner == "this":
607
487
                name_winner = "other"
608
488
            if parent_id_winner == "this":
612
492
        if name_winner == "conflict":
613
493
            trans_id = self.tt.trans_id_file_id(file_id)
614
494
            self._raw_conflicts.append(('name conflict', trans_id, 
615
 
                                        this_name, other_name))
 
495
                                        self.name(this_entry, file_id), 
 
496
                                        self.name(other_entry, file_id)))
616
497
        if parent_id_winner == "conflict":
617
498
            trans_id = self.tt.trans_id_file_id(file_id)
618
499
            self._raw_conflicts.append(('parent conflict', trans_id, 
619
 
                                        this_parent, other_parent))
620
 
        if other_name is None:
 
500
                                        self.parent(this_entry, file_id), 
 
501
                                        self.parent(other_entry, file_id)))
 
502
        if other_entry is None:
621
503
            # it doesn't matter whether the result was 'other' or 
622
504
            # 'conflict'-- if there's no 'other', we leave it alone.
623
505
            return
624
506
        # 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}
625
509
        trans_id = self.tt.trans_id_file_id(file_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)
 
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)
631
514
 
632
515
    def merge_contents(self, file_id):
633
516
        """Performa a merge on file_id contents."""
635
518
            if file_id not in tree:
636
519
                return (None, None)
637
520
            kind = tree.kind(file_id)
 
521
            if kind == "root_directory":
 
522
                kind = "directory"
638
523
            if kind == "file":
639
524
                contents = tree.get_file_sha1(file_id)
640
525
            elif kind == "symlink":
649
534
            parent_id = self.tt.final_parent(trans_id)
650
535
            if file_id in self.this_tree.inventory:
651
536
                self.tt.unversion_file(trans_id)
652
 
                if file_id in self.this_tree:
653
 
                    self.tt.delete_contents(trans_id)
 
537
                self.tt.delete_contents(trans_id)
654
538
            file_group = self._dump_conflicts(name, parent_id, file_id, 
655
539
                                              set_version=True)
656
540
            self._raw_conflicts.append(('contents conflict', file_group))
792
676
 
793
677
    def merge_executable(self, file_id, file_status):
794
678
        """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
802
679
        if file_status == "deleted":
803
680
            return
804
681
        trans_id = self.tt.trans_id_file_id(file_id)
807
684
                return
808
685
        except NoSuchFile:
809
686
            return
810
 
        winner = self._three_way(*executable)
 
687
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
 
688
                                       self.other_tree, file_id, 
 
689
                                       self.executable)
811
690
        if winner == "conflict":
812
691
        # There must be a None in here, if we have a conflict, but we
813
692
        # need executability since file status was not deleted.
817
696
                winner = "other"
818
697
        if winner == "this":
819
698
            if file_status == "modified":
820
 
                executability = this_executable
 
699
                executability = self.this_tree.is_executable(file_id)
821
700
                if executability is not None:
822
701
                    trans_id = self.tt.trans_id_file_id(file_id)
823
702
                    self.tt.set_executability(executability, trans_id)
824
703
        else:
825
704
            assert winner == "other"
826
705
            if file_id in self.other_tree:
827
 
                executability = other_executable
 
706
                executability = self.other_tree.is_executable(file_id)
828
707
            elif file_id in self.this_tree:
829
 
                executability = this_executable
 
708
                executability = self.this_tree.is_executable(file_id)
830
709
            elif file_id in self.base_tree:
831
 
                executability = base_executable
 
710
                executability = self.base_tree.is_executable(file_id)
832
711
            if executability is not None:
833
712
                trans_id = self.tt.trans_id_file_id(file_id)
834
713
                self.tt.set_executability(executability, trans_id)
880
759
            except KeyError:
881
760
                this_name = other_name = self.tt.final_name(trans_id)
882
761
            other_path = fp.get_path(trans_id)
883
 
            if this_parent is not None and this_name is not None:
 
762
            if this_parent is not None:
884
763
                this_parent_path = \
885
764
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
886
765
                this_path = pathjoin(this_parent_path, this_name)
900
779
 
901
780
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
902
781
                 interesting_ids=None, pb=DummyProgress(), pp=None,
903
 
                 reprocess=False, change_reporter=None,
904
 
                 interesting_files=None):
 
782
                 reprocess=False):
905
783
        self.this_revision_tree = self._get_revision_tree(this_tree)
906
784
        self.other_revision_tree = self._get_revision_tree(other_tree)
907
785
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
908
786
                                          base_tree, other_tree, 
909
787
                                          interesting_ids=interesting_ids, 
910
 
                                          pb=pb, pp=pp, reprocess=reprocess,
911
 
                                          change_reporter=change_reporter)
 
788
                                          pb=pb, pp=pp, reprocess=reprocess)
912
789
 
913
790
    def _get_revision_tree(self, tree):
914
791
        """Return a revision tree related to this tree.
983
860
        will be dumped, and a will be conflict noted.
984
861
        """
985
862
        import bzrlib.patch
986
 
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
863
        temp_dir = mkdtemp(prefix="bzr-")
987
864
        try:
988
865
            new_file = pathjoin(temp_dir, "new")
989
866
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1001
878
                name = self.tt.final_name(trans_id)
1002
879
                parent_id = self.tt.final_parent(trans_id)
1003
880
                self._dump_conflicts(name, parent_id, file_id)
1004
 
                self._raw_conflicts.append(('text conflict', trans_id))
 
881
            self._raw_conflicts.append(('text conflict', trans_id))
1005
882
        finally:
1006
 
            osutils.rmtree(temp_dir)
 
883
            rmtree(temp_dir)
1007
884
 
1008
885
 
1009
886
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1010
 
                backup_files=False,
1011
 
                merge_type=Merge3Merger,
1012
 
                interesting_ids=None,
1013
 
                show_base=False,
1014
 
                reprocess=False,
 
887
                backup_files=False, 
 
888
                merge_type=Merge3Merger, 
 
889
                interesting_ids=None, 
 
890
                show_base=False, 
 
891
                reprocess=False, 
1015
892
                other_rev_id=None,
1016
893
                interesting_files=None,
1017
894
                this_tree=None,
1018
 
                pb=DummyProgress(),
1019
 
                change_reporter=None):
 
895
                pb=DummyProgress()):
1020
896
    """Primary interface for merging. 
1021
897
 
1022
898
        typical use is probably 
1024
900
                     branch.get_revision_tree(base_revision))'
1025
901
        """
1026
902
    if this_tree is None:
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)
 
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)
1031
910
    merger.backup_files = backup_files
1032
911
    merger.merge_type = merge_type
1033
912
    merger.interesting_ids = interesting_ids
1035
914
    if interesting_files:
1036
915
        assert not interesting_ids, ('Only supply interesting_ids'
1037
916
                                     ' or interesting_files')
1038
 
        merger.interesting_files = interesting_files
1039
 
    merger.show_base = show_base
 
917
        merger._set_interesting_files(interesting_files)
 
918
    merger.show_base = show_base 
1040
919
    merger.reprocess = reprocess
1041
920
    merger.other_rev_id = other_rev_id
1042
921
    merger.other_basis = other_rev_id
1043
922
    return merger.do_merge()
1044
923
 
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
 
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)