~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-17 07:52:09 UTC
  • mfrom: (1910.3.4 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20060817075209-e85a1f9e05ff8b87
(andrew) Trivial fixes to NotImplemented errors.

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
 
    patiencediff,
26
 
    registry,
27
 
    revision as _mod_revision,
28
 
    )
29
23
from bzrlib.branch import Branch
30
24
from bzrlib.conflicts import ConflictList, Conflict
31
25
from bzrlib.errors import (BzrCommandError,
42
36
                           BinaryFile,
43
37
                           )
44
38
from bzrlib.merge3 import Merge3
45
 
from bzrlib.osutils import rename, pathjoin
 
39
import bzrlib.osutils
 
40
from bzrlib.osutils import rename, pathjoin, rmtree
46
41
from progress import DummyProgress, ProgressPhase
47
 
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
 
42
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
48
43
from bzrlib.textfile import check_text_lines
49
44
from bzrlib.trace import mutter, warning, note
50
45
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
51
 
                              conflict_pass, FinalPaths, create_by_entry,
52
 
                              unique_add, ROOT_PARENT)
53
 
from bzrlib.versionedfile import PlanWeaveMerge
 
46
                              FinalPaths, create_by_entry, unique_add)
 
47
from bzrlib.versionedfile import WeaveMerge
54
48
from bzrlib import ui
55
49
 
56
50
# TODO: Report back as changes are merged in
57
51
 
 
52
def _get_tree(treespec, local_branch=None):
 
53
    from bzrlib import workingtree
 
54
    location, revno = treespec
 
55
    if revno is None:
 
56
        tree = workingtree.WorkingTree.open_containing(location)[0]
 
57
        return tree.branch, tree
 
58
    branch = Branch.open_containing(location)[0]
 
59
    if revno == -1:
 
60
        revision = branch.last_revision()
 
61
    else:
 
62
        revision = branch.get_rev_id(revno)
 
63
        if revision is None:
 
64
            revision = NULL_REVISION
 
65
    return branch, _get_revid_tree(branch, revision, local_branch)
 
66
 
 
67
 
 
68
def _get_revid_tree(branch, revision, local_branch):
 
69
    if revision is None:
 
70
        base_tree = branch.bzrdir.open_workingtree()
 
71
    else:
 
72
        if local_branch is not None:
 
73
            if local_branch.base != branch.base:
 
74
                local_branch.fetch(branch, revision)
 
75
            base_tree = local_branch.repository.revision_tree(revision)
 
76
        else:
 
77
            base_tree = branch.repository.revision_tree(revision)
 
78
    return base_tree
 
79
 
58
80
 
59
81
def transform_tree(from_tree, to_tree, interesting_ids=None):
60
82
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
62
84
 
63
85
 
64
86
class Merger(object):
65
 
    def __init__(self, this_branch, other_tree=None, base_tree=None,
66
 
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
67
 
                 recurse='down'):
 
87
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
 
88
                 this_tree=None, pb=DummyProgress()):
68
89
        object.__init__(self)
69
90
        assert this_tree is not None, "this_tree is required"
70
91
        self.this_branch = this_branch
71
 
        self.this_basis = _mod_revision.ensure_null(
72
 
            this_branch.last_revision())
 
92
        self.this_basis = this_branch.last_revision()
73
93
        self.this_rev_id = None
74
94
        self.this_tree = this_tree
75
95
        self.this_revision_tree = None
76
96
        self.this_basis_tree = None
77
97
        self.other_tree = other_tree
78
 
        self.other_branch = None
79
98
        self.base_tree = base_tree
80
99
        self.ignore_zero = False
81
100
        self.backup_files = False
82
101
        self.interesting_ids = None
83
 
        self.interesting_files = None
84
102
        self.show_base = False
85
103
        self.reprocess = False
86
 
        self._pb = pb
 
104
        self._pb = pb 
87
105
        self.pp = None
88
 
        self.recurse = recurse
89
 
        self.change_reporter = change_reporter
90
 
        self._cached_trees = {}
91
 
 
92
 
    @staticmethod
93
 
    def from_uncommitted(tree, other_tree, pb):
94
 
        """Return a Merger for uncommitted changes in other_tree.
95
 
 
96
 
        :param tree: The tree to merge into
97
 
        :param other_tree: The tree to get uncommitted changes from
98
 
        :param pb: A progress indicator
99
 
        """
100
 
        merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
101
 
                        pb)
102
 
        merger.base_rev_id = merger.base_tree.get_revision_id()
103
 
        merger.other_rev_id = None
104
 
        return merger
105
 
 
106
 
    @classmethod
107
 
    def from_mergeable(klass, tree, mergeable, pb):
108
 
        """Return a Merger for a bundle or merge directive.
109
 
 
110
 
        :param tree: The tree to merge changes into
111
 
        :param mergeable: A merge directive or bundle
112
 
        :param pb: A progress indicator
113
 
        """
114
 
        mergeable.install_revisions(tree.branch.repository)
115
 
        base_revision_id, other_revision_id, verified =\
116
 
            mergeable.get_merge_request(tree.branch.repository)
117
 
        if (base_revision_id != _mod_revision.NULL_REVISION and
118
 
            tree.branch.repository.get_graph().is_ancestor(
119
 
            base_revision_id, tree.branch.last_revision())):
120
 
            base_revision_id = None
121
 
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
122
 
                                         base_revision_id)
123
 
        return merger, verified
124
 
 
125
 
    @staticmethod
126
 
    def from_revision_ids(pb, this, other, base=None, other_branch=None,
127
 
                          base_branch=None):
128
 
        """Return a Merger for revision-ids.
129
 
 
130
 
        :param tree: The tree to merge changes into
131
 
        :param other: The revision-id to use as OTHER
132
 
        :param base: The revision-id to use as BASE.  If not specified, will
133
 
            be auto-selected.
134
 
        :param other_branch: A branch containing the other revision-id.  If
135
 
            not supplied, this.branch is used.
136
 
        :param base_branch: A branch containing the base revision-id.  If
137
 
            not supplied, other_branch or this.branch will be used.
138
 
        :param pb: A progress indicator
139
 
        """
140
 
        merger = Merger(this.branch, this_tree=this, pb=pb)
141
 
        if other_branch is None:
142
 
            other_branch = this.branch
143
 
        merger.set_other_revision(other, other_branch)
144
 
        if base is None:
145
 
            merger.find_base()
146
 
        else:
147
 
            if base_branch is None:
148
 
                base_branch = other_branch
149
 
            merger.set_base_revision(base, base_branch)
150
 
        return merger
151
 
 
152
 
    def revision_tree(self, revision_id, branch=None):
153
 
        if revision_id not in self._cached_trees:
154
 
            if branch is None:
155
 
                branch = self.this_branch
156
 
            try:
157
 
                tree = self.this_tree.revision_tree(revision_id)
158
 
            except errors.NoSuchRevisionInTree:
159
 
                tree = branch.repository.revision_tree(revision_id)
160
 
            self._cached_trees[revision_id] = tree
161
 
        return self._cached_trees[revision_id]
162
 
 
163
 
    def _get_tree(self, treespec, possible_transports=None):
164
 
        from bzrlib import workingtree
165
 
        location, revno = treespec
166
 
        if revno is None:
167
 
            tree = workingtree.WorkingTree.open_containing(location)[0]
168
 
            return tree.branch, tree
169
 
        branch = Branch.open_containing(location, possible_transports)[0]
170
 
        if revno == -1:
171
 
            revision_id = branch.last_revision()
172
 
        else:
173
 
            revision_id = branch.get_rev_id(revno)
174
 
        revision_id = ensure_null(revision_id)
175
 
        return branch, self.revision_tree(revision_id, branch)
 
106
 
 
107
 
 
108
    def revision_tree(self, revision_id):
 
109
        return self.this_branch.repository.revision_tree(revision_id)
176
110
 
177
111
    def ensure_revision_trees(self):
178
112
        if self.this_revision_tree is None:
179
 
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
113
            self.this_basis_tree = self.this_branch.repository.revision_tree(
 
114
                self.this_basis)
180
115
            if self.this_basis == self.this_rev_id:
181
116
                self.this_revision_tree = self.this_basis_tree
182
117
 
204
139
 
205
140
    def check_basis(self, check_clean, require_commits=True):
206
141
        if self.this_basis is None and require_commits is True:
207
 
            raise BzrCommandError("This branch has no commits."
208
 
                                  " (perhaps you would prefer 'bzr pull')")
 
142
            raise BzrCommandError("This branch has no commits")
209
143
        if check_clean:
210
144
            self.compare_basis()
211
145
            if self.this_basis != self.this_rev_id:
212
 
                raise errors.UncommittedChanges(self.this_tree)
 
146
                raise BzrCommandError("Working tree has uncommitted changes.")
213
147
 
214
148
    def compare_basis(self):
215
 
        try:
216
 
            basis_tree = self.revision_tree(self.this_tree.last_revision())
217
 
        except errors.RevisionNotPresent:
218
 
            basis_tree = self.this_tree.basis_tree()
219
 
        changes = self.this_tree.changes_from(basis_tree)
 
149
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
220
150
        if not changes.has_changed():
221
151
            self.this_rev_id = self.this_basis
222
152
 
223
153
    def set_interesting_files(self, file_list):
224
 
        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
225
177
 
226
178
    def set_pending(self):
227
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
228
 
            return
229
 
        self._add_parent()
230
 
 
231
 
    def _add_parent(self):
232
 
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
233
 
        new_parent_trees = []
234
 
        for revision_id in new_parents:
235
 
            try:
236
 
                tree = self.revision_tree(revision_id)
237
 
            except errors.RevisionNotPresent:
238
 
                tree = None
239
 
            else:
240
 
                tree.lock_read()
241
 
            new_parent_trees.append((revision_id, tree))
242
 
        try:
243
 
            self.this_tree.set_parent_trees(new_parent_trees,
244
 
                                            allow_leftmost_as_ghost=True)
245
 
        finally:
246
 
            for _revision_id, tree in new_parent_trees:
247
 
                if tree is not None:
248
 
                    tree.unlock()
249
 
 
250
 
    def set_other(self, other_revision, possible_transports=None):
251
 
        """Set the revision and tree to merge from.
252
 
 
253
 
        This sets the other_tree, other_rev_id, other_basis attributes.
254
 
 
255
 
        :param other_revision: The [path, revision] list to merge from.
256
 
        """
257
 
        self.other_branch, self.other_tree = self._get_tree(other_revision,
258
 
                                                            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)
259
191
        if other_revision[1] == -1:
260
 
            self.other_rev_id = _mod_revision.ensure_null(
261
 
                self.other_branch.last_revision())
262
 
            if _mod_revision.is_null(self.other_rev_id):
263
 
                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)
264
195
            self.other_basis = self.other_rev_id
265
196
        elif other_revision[1] is not None:
266
 
            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])
267
198
            self.other_basis = self.other_rev_id
268
199
        else:
269
200
            self.other_rev_id = None
270
 
            self.other_basis = self.other_branch.last_revision()
 
201
            self.other_basis = other_branch.last_revision()
271
202
            if self.other_basis is None:
272
 
                raise NoCommits(self.other_branch)
273
 
        if self.other_rev_id is not None:
274
 
            self._cached_trees[self.other_rev_id] = self.other_tree
275
 
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
276
 
 
277
 
    def set_other_revision(self, revision_id, other_branch):
278
 
        """Set 'other' based on a branch and revision id
279
 
 
280
 
        :param revision_id: The revision to use for a tree
281
 
        :param other_branch: The branch containing this tree
282
 
        """
283
 
        self.other_rev_id = revision_id
284
 
        self.other_branch = other_branch
285
 
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
286
 
        self.other_tree = self.revision_tree(revision_id)
287
 
        self.other_basis = revision_id
288
 
 
289
 
    def set_base_revision(self, revision_id, branch):
290
 
        """Set 'base' based on a branch and revision id
291
 
 
292
 
        :param revision_id: The revision to use for a tree
293
 
        :param branch: The branch containing this tree
294
 
        """
295
 
        self.base_rev_id = revision_id
296
 
        self.base_branch = branch
297
 
        self._maybe_fetch(branch, self.this_branch, revision_id)
298
 
        self.base_tree = self.revision_tree(revision_id)
299
 
        self.base_is_ancestor = is_ancestor(self.this_basis,
300
 
                                            self.base_rev_id,
301
 
                                            self.this_branch)
302
 
        self.base_is_other_ancestor = is_ancestor(self.other_basis,
303
 
                                                  self.base_rev_id,
304
 
                                                  self.this_branch)
305
 
 
306
 
    def _maybe_fetch(self, source, target, revision_id):
307
 
        if not source.repository.has_same_location(target.repository):
308
 
            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)
309
206
 
310
207
    def find_base(self):
311
 
        this_repo = self.this_branch.repository
312
 
        graph = this_repo.get_graph()
313
 
        revisions = [ensure_null(self.this_basis),
314
 
                     ensure_null(self.other_basis)]
315
 
        if NULL_REVISION in revisions:
316
 
            self.base_rev_id = NULL_REVISION
317
 
        else:
318
 
            self.base_rev_id = graph.find_unique_lca(*revisions)
319
 
            if self.base_rev_id == NULL_REVISION:
320
 
                raise UnrelatedBranches()
321
 
        self.base_tree = self.revision_tree(self.base_rev_id)
322
 
        self.base_is_ancestor = True
323
 
        self.base_is_other_ancestor = True
 
208
        self.set_base([None, None])
324
209
 
325
210
    def set_base(self, base_revision):
326
 
        """Set the base revision to use for the merge.
327
 
 
328
 
        :param base_revision: A 2-list containing a path and revision number.
329
 
        """
330
211
        mutter("doing merge() with no base_revision specified")
331
212
        if base_revision == [None, None]:
332
 
            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
333
227
        else:
334
 
            base_branch, self.base_tree = self._get_tree(base_revision)
 
228
            base_branch, self.base_tree = _get_tree(base_revision)
335
229
            if base_revision[1] == -1:
336
230
                self.base_rev_id = base_branch.last_revision()
337
231
            elif base_revision[1] is None:
338
 
                self.base_rev_id = _mod_revision.NULL_REVISION
 
232
                self.base_rev_id = None
339
233
            else:
340
 
                self.base_rev_id = _mod_revision.ensure_null(
341
 
                    base_branch.get_rev_id(base_revision[1]))
342
 
            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)
343
237
            self.base_is_ancestor = is_ancestor(self.this_basis, 
344
238
                                                self.base_rev_id,
345
239
                                                self.this_branch)
346
 
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
347
 
                                                      self.base_rev_id,
348
 
                                                      self.this_branch)
349
240
 
350
241
    def do_merge(self):
351
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
352
 
                  'other_tree': self.other_tree,
 
242
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
 
243
                  'other_tree': self.other_tree, 
353
244
                  'interesting_ids': self.interesting_ids,
354
 
                  'interesting_files': self.interesting_files,
355
245
                  'pp': self.pp}
356
246
        if self.merge_type.requires_base:
357
247
            kwargs['base_tree'] = self.base_tree
365
255
        elif self.show_base:
366
256
            raise BzrError("Showing base is not supported for this"
367
257
                                  " merge type. %s" % self.merge_type)
368
 
        self.this_tree.lock_tree_write()
369
 
        if self.base_tree is not None:
370
 
            self.base_tree.lock_read()
371
 
        if self.other_tree is not None:
372
 
            self.other_tree.lock_read()
373
 
        try:
374
 
            merge = self.merge_type(pb=self._pb,
375
 
                                    change_reporter=self.change_reporter,
376
 
                                    **kwargs)
377
 
            if self.recurse == 'down':
378
 
                for path, file_id in self.this_tree.iter_references():
379
 
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
380
 
                    other_revision = self.other_tree.get_reference_revision(
381
 
                        file_id, path)
382
 
                    if  other_revision == sub_tree.last_revision():
383
 
                        continue
384
 
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
385
 
                    sub_merge.merge_type = self.merge_type
386
 
                    relpath = self.this_tree.relpath(path)
387
 
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
388
 
                    sub_merge.set_other_revision(other_revision, other_branch)
389
 
                    base_revision = self.base_tree.get_reference_revision(file_id)
390
 
                    sub_merge.base_tree = \
391
 
                        sub_tree.branch.repository.revision_tree(base_revision)
392
 
                    sub_merge.do_merge()
393
 
 
394
 
        finally:
395
 
            if self.other_tree is not None:
396
 
                self.other_tree.unlock()
397
 
            if self.base_tree is not None:
398
 
                self.base_tree.unlock()
399
 
            self.this_tree.unlock()
 
258
        merge = self.merge_type(pb=self._pb, **kwargs)
400
259
        if len(merge.cooked_conflicts) == 0:
401
260
            if not self.ignore_zero:
402
261
                note("All changes applied successfully.")
405
264
 
406
265
        return len(merge.cooked_conflicts)
407
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
 
408
336
 
409
337
class Merge3Merger(object):
410
338
    """Three-way merger that uses the merge3 text merger"""
412
340
    supports_reprocess = True
413
341
    supports_show_base = True
414
342
    history_based = False
415
 
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
416
343
 
417
344
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
418
345
                 interesting_ids=None, reprocess=False, show_base=False,
419
 
                 pb=DummyProgress(), pp=None, change_reporter=None,
420
 
                 interesting_files=None):
421
 
        """Initialize the merger object and perform the merge.
422
 
 
423
 
        :param working_tree: The working tree to apply the merge to
424
 
        :param this_tree: The local tree in the merge operation
425
 
        :param base_tree: The common tree in the merge operation
426
 
        :param other_tree: The other other tree to merge changes from
427
 
        :param interesting_ids: The file_ids of files that should be
428
 
            participate in the merge.  May not be combined with
429
 
            interesting_files.
430
 
        :param: reprocess If True, perform conflict-reduction processing.
431
 
        :param show_base: If True, show the base revision in text conflicts.
432
 
            (incompatible with reprocess)
433
 
        :param pb: A Progress bar
434
 
        :param pp: A ProgressPhase object
435
 
        :param change_reporter: An object that should report changes made
436
 
        :param interesting_files: The tree-relative paths of files that should
437
 
            participate in the merge.  If these paths refer to directories,
438
 
            the contents of those directories will also be included.  May not
439
 
            be combined with interesting_ids.  If neither interesting_files nor
440
 
            interesting_ids is specified, all files may participate in the
441
 
            merge.
442
 
        """
 
346
                 pb=DummyProgress(), pp=None):
 
347
        """Initialize the merger object and perform the merge."""
443
348
        object.__init__(self)
444
 
        if interesting_files is not None:
445
 
            assert interesting_ids is None
446
 
        self.interesting_ids = interesting_ids
447
 
        self.interesting_files = interesting_files
448
349
        self.this_tree = working_tree
449
 
        self.this_tree.lock_tree_write()
450
350
        self.base_tree = base_tree
451
 
        self.base_tree.lock_read()
452
351
        self.other_tree = other_tree
453
 
        self.other_tree.lock_read()
454
352
        self._raw_conflicts = []
455
353
        self.cooked_conflicts = []
456
354
        self.reprocess = reprocess
457
355
        self.show_base = show_base
458
356
        self.pb = pb
459
357
        self.pp = pp
460
 
        self.change_reporter = change_reporter
461
358
        if self.pp is None:
462
359
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
463
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()
464
367
        self.tt = TreeTransform(working_tree, self.pb)
465
368
        try:
466
369
            self.pp.next_phase()
467
 
            entries = self._entries3()
468
370
            child_pb = ui.ui_factory.nested_progress_bar()
469
371
            try:
470
 
                for num, (file_id, changed, parents3, names3,
471
 
                          executable3) in enumerate(entries):
472
 
                    child_pb.update('Preparing file merge', num, len(entries))
473
 
                    self._merge_names(file_id, parents3, names3)
474
 
                    if changed:
475
 
                        file_status = self.merge_contents(file_id)
476
 
                    else:
477
 
                        file_status = 'unmodified'
478
 
                    self._merge_executable(file_id,
479
 
                        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)
480
377
            finally:
481
378
                child_pb.finished()
482
 
            self.fix_root()
 
379
                
483
380
            self.pp.next_phase()
484
381
            child_pb = ui.ui_factory.nested_progress_bar()
485
382
            try:
486
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
487
 
                    lambda t, c: conflict_pass(t, c, self.other_tree))
 
383
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
488
384
            finally:
489
385
                child_pb.finished()
490
 
            if change_reporter is not None:
491
 
                from bzrlib import delta
492
 
                delta.report_changes(self.tt._iter_changes(), change_reporter)
493
386
            self.cook_conflicts(fs_conflicts)
494
387
            for conflict in self.cooked_conflicts:
495
388
                warning(conflict)
496
389
            self.pp.next_phase()
497
 
            results = self.tt.apply(no_conflicts=True)
 
390
            results = self.tt.apply()
498
391
            self.write_modified(results)
499
392
            try:
500
393
                working_tree.add_conflicts(self.cooked_conflicts)
502
395
                pass
503
396
        finally:
504
397
            self.tt.finalize()
505
 
            self.other_tree.unlock()
506
 
            self.base_tree.unlock()
507
 
            self.this_tree.unlock()
 
398
            working_tree.unlock()
508
399
            self.pb.clear()
509
400
 
510
 
    def _entries3(self):
511
 
        """Gather data about files modified between three trees.
512
 
 
513
 
        Return a list of tuples of file_id, changed, parents3, names3,
514
 
        executable3.  changed is a boolean indicating whether the file contents
515
 
        or kind were changed.  parents3 is a tuple of parent ids for base,
516
 
        other and this.  names3 is a tuple of names for base, other and this.
517
 
        executable3 is a tuple of execute-bit values for base, other and this.
518
 
        """
519
 
        result = []
520
 
        iterator = self.other_tree._iter_changes(self.base_tree,
521
 
                include_unchanged=True, specific_files=self.interesting_files,
522
 
                extra_trees=[self.this_tree])
523
 
        for (file_id, paths, changed, versioned, parents, names, kind,
524
 
             executable) in iterator:
525
 
            if (self.interesting_ids is not None and
526
 
                file_id not in self.interesting_ids):
527
 
                continue
528
 
            if file_id in self.this_tree.inventory:
529
 
                entry = self.this_tree.inventory[file_id]
530
 
                this_name = entry.name
531
 
                this_parent = entry.parent_id
532
 
                this_executable = entry.executable
533
 
            else:
534
 
                this_name = None
535
 
                this_parent = None
536
 
                this_executable = None
537
 
            parents3 = parents + (this_parent,)
538
 
            names3 = names + (this_name,)
539
 
            executable3 = executable + (this_executable,)
540
 
            result.append((file_id, changed, parents3, names3, executable3))
541
 
        return result
542
 
 
543
 
    def fix_root(self):
544
 
        try:
545
 
            self.tt.final_kind(self.tt.root)
546
 
        except NoSuchFile:
547
 
            self.tt.cancel_deletion(self.tt.root)
548
 
        if self.tt.final_file_id(self.tt.root) is None:
549
 
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
550
 
                                 self.tt.root)
551
 
        if self.other_tree.inventory.root is None:
552
 
            return
553
 
        other_root_file_id = self.other_tree.inventory.root.file_id
554
 
        other_root = self.tt.trans_id_file_id(other_root_file_id)
555
 
        if other_root == self.tt.root:
556
 
            return
557
 
        try:
558
 
            self.tt.final_kind(other_root)
559
 
        except NoSuchFile:
560
 
            return
561
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
562
 
        self.tt.cancel_creation(other_root)
563
 
        self.tt.cancel_versioning(other_root)
564
 
 
565
 
    def reparent_children(self, ie, target):
566
 
        for thing, child in ie.children.iteritems():
567
 
            trans_id = self.tt.trans_id_file_id(child.file_id)
568
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
569
 
 
570
401
    def write_modified(self, results):
571
402
        modified_hashes = {}
572
403
        for path in results.modified_paths:
617
448
        return tree.kind(file_id)
618
449
 
619
450
    @staticmethod
620
 
    def _three_way(base, other, this):
621
 
        #if base == other, either they all agree, or only THIS has changed.
622
 
        if base == other:
623
 
            return 'this'
624
 
        elif this not in (base, other):
625
 
            return 'conflict'
626
 
        # "Ambiguous clean merge" -- both sides have made the same change.
627
 
        elif this == other:
628
 
            return "this"
629
 
        # this == base: only other has changed.
630
 
        else:
631
 
            return "other"
632
 
 
633
 
    @staticmethod
634
451
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
635
452
        """Do a three-way test on a scalar.
636
453
        Return "this", "other" or "conflict", depending whether a value wins.
651
468
            return "other"
652
469
 
653
470
    def merge_names(self, file_id):
 
471
        """Perform a merge on file_id names and parents"""
654
472
        def get_entry(tree):
655
473
            if file_id in tree.inventory:
656
474
                return tree.inventory[file_id]
659
477
        this_entry = get_entry(self.this_tree)
660
478
        other_entry = get_entry(self.other_tree)
661
479
        base_entry = get_entry(self.base_tree)
662
 
        entries = (base_entry, other_entry, this_entry)
663
 
        names = []
664
 
        parents = []
665
 
        for entry in entries:
666
 
            if entry is None:
667
 
                names.append(None)
668
 
                parents.append(None)
669
 
            else:
670
 
                names.append(entry.name)
671
 
                parents.append(entry.parent_id)
672
 
        return self._merge_names(file_id, parents, names)
673
 
 
674
 
    def _merge_names(self, file_id, parents, names):
675
 
        """Perform a merge on file_id names and parents"""
676
 
        base_name, other_name, this_name = names
677
 
        base_parent, other_parent, this_parent = parents
678
 
 
679
 
        name_winner = self._three_way(*names)
680
 
 
681
 
        parent_id_winner = self._three_way(*parents)
682
 
        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:
683
486
            if name_winner == "this":
684
487
                name_winner = "other"
685
488
            if parent_id_winner == "this":
689
492
        if name_winner == "conflict":
690
493
            trans_id = self.tt.trans_id_file_id(file_id)
691
494
            self._raw_conflicts.append(('name conflict', trans_id, 
692
 
                                        this_name, other_name))
 
495
                                        self.name(this_entry, file_id), 
 
496
                                        self.name(other_entry, file_id)))
693
497
        if parent_id_winner == "conflict":
694
498
            trans_id = self.tt.trans_id_file_id(file_id)
695
499
            self._raw_conflicts.append(('parent conflict', trans_id, 
696
 
                                        this_parent, other_parent))
697
 
        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:
698
503
            # it doesn't matter whether the result was 'other' or 
699
504
            # 'conflict'-- if there's no 'other', we leave it alone.
700
505
            return
701
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}
702
509
        trans_id = self.tt.trans_id_file_id(file_id)
703
 
        parent_id = parents[self.winner_idx[parent_id_winner]]
704
 
        if parent_id is not None:
705
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
706
 
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
707
 
                                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)
708
514
 
709
515
    def merge_contents(self, file_id):
710
516
        """Performa a merge on file_id contents."""
726
532
            parent_id = self.tt.final_parent(trans_id)
727
533
            if file_id in self.this_tree.inventory:
728
534
                self.tt.unversion_file(trans_id)
729
 
                if file_id in self.this_tree:
730
 
                    self.tt.delete_contents(trans_id)
 
535
                self.tt.delete_contents(trans_id)
731
536
            file_group = self._dump_conflicts(name, parent_id, file_id, 
732
537
                                              set_version=True)
733
538
            self._raw_conflicts.append(('contents conflict', file_group))
869
674
 
870
675
    def merge_executable(self, file_id, file_status):
871
676
        """Perform a merge on the execute bit."""
872
 
        executable = [self.executable(t, file_id) for t in (self.base_tree,
873
 
                      self.other_tree, self.this_tree)]
874
 
        self._merge_executable(file_id, executable, file_status)
875
 
 
876
 
    def _merge_executable(self, file_id, executable, file_status):
877
 
        """Perform a merge on the execute bit."""
878
 
        base_executable, other_executable, this_executable = executable
879
677
        if file_status == "deleted":
880
678
            return
881
679
        trans_id = self.tt.trans_id_file_id(file_id)
884
682
                return
885
683
        except NoSuchFile:
886
684
            return
887
 
        winner = self._three_way(*executable)
 
685
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
 
686
                                       self.other_tree, file_id, 
 
687
                                       self.executable)
888
688
        if winner == "conflict":
889
689
        # There must be a None in here, if we have a conflict, but we
890
690
        # need executability since file status was not deleted.
894
694
                winner = "other"
895
695
        if winner == "this":
896
696
            if file_status == "modified":
897
 
                executability = this_executable
 
697
                executability = self.this_tree.is_executable(file_id)
898
698
                if executability is not None:
899
699
                    trans_id = self.tt.trans_id_file_id(file_id)
900
700
                    self.tt.set_executability(executability, trans_id)
901
701
        else:
902
702
            assert winner == "other"
903
703
            if file_id in self.other_tree:
904
 
                executability = other_executable
 
704
                executability = self.other_tree.is_executable(file_id)
905
705
            elif file_id in self.this_tree:
906
 
                executability = this_executable
 
706
                executability = self.this_tree.is_executable(file_id)
907
707
            elif file_id in self.base_tree:
908
 
                executability = base_executable
 
708
                executability = self.base_tree.is_executable(file_id)
909
709
            if executability is not None:
910
710
                trans_id = self.tt.trans_id_file_id(file_id)
911
711
                self.tt.set_executability(executability, trans_id)
957
757
            except KeyError:
958
758
                this_name = other_name = self.tt.final_name(trans_id)
959
759
            other_path = fp.get_path(trans_id)
960
 
            if this_parent is not None and this_name is not None:
 
760
            if this_parent is not None:
961
761
                this_parent_path = \
962
762
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
963
763
                this_path = pathjoin(this_parent_path, this_name)
977
777
 
978
778
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
979
779
                 interesting_ids=None, pb=DummyProgress(), pp=None,
980
 
                 reprocess=False, change_reporter=None,
981
 
                 interesting_files=None):
 
780
                 reprocess=False):
 
781
        self.this_revision_tree = self._get_revision_tree(this_tree)
 
782
        self.other_revision_tree = self._get_revision_tree(other_tree)
982
783
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
983
784
                                          base_tree, other_tree, 
984
785
                                          interesting_ids=interesting_ids, 
985
 
                                          pb=pb, pp=pp, reprocess=reprocess,
986
 
                                          change_reporter=change_reporter)
 
786
                                          pb=pb, pp=pp, reprocess=reprocess)
 
787
 
 
788
    def _get_revision_tree(self, tree):
 
789
        """Return a revision tree related to this tree.
 
790
        If the tree is a WorkingTree, the basis will be returned.
 
791
        """
 
792
        if getattr(tree, 'get_weave', False) is False:
 
793
            # If we have a WorkingTree, try using the basis
 
794
            return tree.branch.basis_tree()
 
795
        else:
 
796
            return tree
 
797
 
 
798
    def _check_file(self, file_id):
 
799
        """Check that the revision tree's version of the file matches."""
 
800
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
 
801
                         (self.other_tree, self.other_revision_tree)):
 
802
            if rt is tree:
 
803
                continue
 
804
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
 
805
                raise WorkingTreeNotRevision(self.this_tree)
987
806
 
988
807
    def _merged_lines(self, file_id):
989
808
        """Generate the merged lines.
990
809
        There is no distinction between lines that are meant to contain <<<<<<<
991
810
        and conflicts.
992
811
        """
993
 
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
994
 
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
995
 
            '>>>>>>> MERGE-SOURCE\n')
996
 
        return textmerge.merge_lines(self.reprocess)
 
812
        weave = self.this_revision_tree.get_weave(file_id)
 
813
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
 
814
        other_revision_id = \
 
815
            self.other_revision_tree.inventory[file_id].revision
 
816
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
 
817
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
 
818
        return wm.merge_lines(self.reprocess)
997
819
 
998
820
    def text_merge(self, file_id, trans_id):
999
821
        """Perform a (weave) text merge for a given file and file-id.
1000
822
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1001
823
        and a conflict will be noted.
1002
824
        """
 
825
        self._check_file(file_id)
1003
826
        lines, conflicts = self._merged_lines(file_id)
1004
827
        lines = list(lines)
1005
828
        # Note we're checking whether the OUTPUT is binary in this case, 
1035
858
        will be dumped, and a will be conflict noted.
1036
859
        """
1037
860
        import bzrlib.patch
1038
 
        temp_dir = osutils.mkdtemp(prefix="bzr-")
 
861
        temp_dir = mkdtemp(prefix="bzr-")
1039
862
        try:
1040
863
            new_file = pathjoin(temp_dir, "new")
1041
864
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1053
876
                name = self.tt.final_name(trans_id)
1054
877
                parent_id = self.tt.final_parent(trans_id)
1055
878
                self._dump_conflicts(name, parent_id, file_id)
1056
 
                self._raw_conflicts.append(('text conflict', trans_id))
 
879
            self._raw_conflicts.append(('text conflict', trans_id))
1057
880
        finally:
1058
 
            osutils.rmtree(temp_dir)
 
881
            rmtree(temp_dir)
1059
882
 
1060
883
 
1061
884
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1062
 
                backup_files=False,
1063
 
                merge_type=Merge3Merger,
1064
 
                interesting_ids=None,
1065
 
                show_base=False,
1066
 
                reprocess=False,
 
885
                backup_files=False, 
 
886
                merge_type=Merge3Merger, 
 
887
                interesting_ids=None, 
 
888
                show_base=False, 
 
889
                reprocess=False, 
1067
890
                other_rev_id=None,
1068
891
                interesting_files=None,
1069
892
                this_tree=None,
1070
 
                pb=DummyProgress(),
1071
 
                change_reporter=None):
 
893
                pb=DummyProgress()):
1072
894
    """Primary interface for merging. 
1073
895
 
1074
896
        typical use is probably 
1076
898
                     branch.get_revision_tree(base_revision))'
1077
899
        """
1078
900
    if this_tree is None:
1079
 
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1080
 
            "parameter as of bzrlib version 0.8.")
1081
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1082
 
                    pb=pb, change_reporter=change_reporter)
 
901
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
 
902
             "bzrlib version 0.8.",
 
903
             DeprecationWarning,
 
904
             stacklevel=2)
 
905
        this_tree = this_branch.bzrdir.open_workingtree()
 
906
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
 
907
                    pb=pb)
1083
908
    merger.backup_files = backup_files
1084
909
    merger.merge_type = merge_type
1085
910
    merger.interesting_ids = interesting_ids
1087
912
    if interesting_files:
1088
913
        assert not interesting_ids, ('Only supply interesting_ids'
1089
914
                                     ' or interesting_files')
1090
 
        merger.interesting_files = interesting_files
1091
 
    merger.show_base = show_base
 
915
        merger._set_interesting_files(interesting_files)
 
916
    merger.show_base = show_base 
1092
917
    merger.reprocess = reprocess
1093
918
    merger.other_rev_id = other_rev_id
1094
919
    merger.other_basis = other_rev_id
1095
920
    return merger.do_merge()
1096
921
 
1097
 
def get_merge_type_registry():
1098
 
    """Merge type registry is in bzrlib.option to avoid circular imports.
1099
 
 
1100
 
    This method provides a sanctioned way to retrieve it.
1101
 
    """
1102
 
    from bzrlib import option
1103
 
    return option._merge_type_registry
1104
 
 
1105
 
 
1106
 
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1107
 
    def status_a(revision, text):
1108
 
        if revision in ancestors_b:
1109
 
            return 'killed-b', text
1110
 
        else:
1111
 
            return 'new-a', text
1112
 
 
1113
 
    def status_b(revision, text):
1114
 
        if revision in ancestors_a:
1115
 
            return 'killed-a', text
1116
 
        else:
1117
 
            return 'new-b', text
1118
 
 
1119
 
    plain_a = [t for (a, t) in annotated_a]
1120
 
    plain_b = [t for (a, t) in annotated_b]
1121
 
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1122
 
    blocks = matcher.get_matching_blocks()
1123
 
    a_cur = 0
1124
 
    b_cur = 0
1125
 
    for ai, bi, l in blocks:
1126
 
        # process all mismatched sections
1127
 
        # (last mismatched section is handled because blocks always
1128
 
        # includes a 0-length last block)
1129
 
        for revision, text in annotated_a[a_cur:ai]:
1130
 
            yield status_a(revision, text)
1131
 
        for revision, text in annotated_b[b_cur:bi]:
1132
 
            yield status_b(revision, text)
1133
 
 
1134
 
        # and now the matched section
1135
 
        a_cur = ai + l
1136
 
        b_cur = bi + l
1137
 
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1138
 
            assert text_a == text_b
1139
 
            yield "unchanged", text_a
 
922
 
 
923
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
 
924
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
 
925
                     'weave': (WeaveMerger, "Weave-based merge")
 
926
              }
 
927
 
 
928
 
 
929
def merge_type_help():
 
930
    templ = '%s%%7s: %%s' % (' '*12)
 
931
    lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
932
    return '\n'.join(lines)