~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import os
19
19
import errno
20
 
from tempfile import mkdtemp
21
20
import warnings
22
21
 
 
22
from bzrlib import (
 
23
    osutils,
 
24
    registry,
 
25
    )
23
26
from bzrlib.branch import Branch
24
27
from bzrlib.conflicts import ConflictList, Conflict
25
28
from bzrlib.errors import (BzrCommandError,
36
39
                           BinaryFile,
37
40
                           )
38
41
from bzrlib.merge3 import Merge3
39
 
import bzrlib.osutils
40
 
from bzrlib.osutils import rename, pathjoin, rmtree
 
42
from bzrlib.osutils import rename, pathjoin
41
43
from progress import DummyProgress, ProgressPhase
42
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
 
44
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
43
45
from bzrlib.textfile import check_text_lines
44
46
from bzrlib.trace import mutter, warning, note
45
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
46
 
                              FinalPaths, create_by_entry, unique_add)
 
48
                              FinalPaths, create_by_entry, unique_add,
 
49
                              ROOT_PARENT)
47
50
from bzrlib.versionedfile import WeaveMerge
48
51
from bzrlib import ui
49
52
 
57
60
        return tree.branch, tree
58
61
    branch = Branch.open_containing(location)[0]
59
62
    if revno == -1:
60
 
        revision = branch.last_revision()
 
63
        revision_id = branch.last_revision()
61
64
    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:
 
65
        revision_id = branch.get_rev_id(revno)
 
66
    if revision_id is None:
 
67
        revision_id = NULL_REVISION
 
68
    return branch, _get_revid_tree(branch, revision_id, local_branch)
 
69
 
 
70
 
 
71
def _get_revid_tree(branch, revision_id, local_branch):
 
72
    if revision_id is None:
70
73
        base_tree = branch.bzrdir.open_workingtree()
71
74
    else:
72
75
        if local_branch is not None:
73
76
            if local_branch.base != branch.base:
74
 
                local_branch.fetch(branch, revision)
75
 
            base_tree = local_branch.repository.revision_tree(revision)
 
77
                local_branch.fetch(branch, revision_id)
 
78
            base_tree = local_branch.repository.revision_tree(revision_id)
76
79
        else:
77
 
            base_tree = branch.repository.revision_tree(revision)
 
80
            base_tree = branch.repository.revision_tree(revision_id)
78
81
    return base_tree
79
82
 
80
83
 
 
84
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
 
85
    if revision_id is None:
 
86
        return tree
 
87
    if local_branch is not None:
 
88
        if local_branch.base != tree.branch.base:
 
89
            local_branch.fetch(tree.branch, revision_id)
 
90
        return local_branch.repository.revision_tree(revision_id)
 
91
    return tree.branch.repository.revision_tree(revision_id)
 
92
 
 
93
 
81
94
def transform_tree(from_tree, to_tree, interesting_ids=None):
82
95
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
83
96
                interesting_ids=interesting_ids, this_tree=from_tree)
84
97
 
85
98
 
86
99
class Merger(object):
87
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
88
 
                 this_tree=None, pb=DummyProgress()):
 
100
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
101
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
102
                 recurse='down'):
89
103
        object.__init__(self)
90
104
        assert this_tree is not None, "this_tree is required"
91
105
        self.this_branch = this_branch
95
109
        self.this_revision_tree = None
96
110
        self.this_basis_tree = None
97
111
        self.other_tree = other_tree
 
112
        self.other_branch = None
98
113
        self.base_tree = base_tree
99
114
        self.ignore_zero = False
100
115
        self.backup_files = False
101
116
        self.interesting_ids = None
102
117
        self.show_base = False
103
118
        self.reprocess = False
104
 
        self._pb = pb 
 
119
        self._pb = pb
105
120
        self.pp = None
106
 
 
 
121
        self.recurse = recurse
 
122
        self.change_reporter = change_reporter
107
123
 
108
124
    def revision_tree(self, revision_id):
109
125
        return self.this_branch.repository.revision_tree(revision_id)
139
155
 
140
156
    def check_basis(self, check_clean, require_commits=True):
141
157
        if self.this_basis is None and require_commits is True:
142
 
            raise BzrCommandError("This branch has no commits")
 
158
            raise BzrCommandError("This branch has no commits."
 
159
                                  " (perhaps you would prefer 'bzr pull')")
143
160
        if check_clean:
144
161
            self.compare_basis()
145
162
            if self.this_basis != self.this_rev_id:
166
183
        interesting_ids = set()
167
184
        for path in file_list:
168
185
            found_id = False
 
186
            # TODO: jam 20070226 The trees are not locked at this time,
 
187
            #       wouldn't it make merge faster if it locks everything in the
 
188
            #       beginning? It locks at do_merge time, but this happens
 
189
            #       before that.
169
190
            for tree in (self.this_tree, self.base_tree, self.other_tree):
170
 
                file_id = tree.inventory.path2id(path)
 
191
                file_id = tree.path2id(path)
171
192
                if file_id is not None:
172
193
                    interesting_ids.add(file_id)
173
194
                    found_id = True
180
201
            return
181
202
        if self.other_rev_id is None:
182
203
            return
183
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
 
204
        ancestry = set(self.this_branch.repository.get_ancestry(
 
205
            self.this_basis, topo_sorted=False))
184
206
        if self.other_rev_id in ancestry:
185
207
            return
186
208
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
192
214
 
193
215
        :param other_revision: The [path, revision] list to merge from.
194
216
        """
195
 
        other_branch, self.other_tree = _get_tree(other_revision,
 
217
        self.other_branch, self.other_tree = _get_tree(other_revision,
196
218
                                                  self.this_branch)
197
219
        if other_revision[1] == -1:
198
 
            self.other_rev_id = other_branch.last_revision()
 
220
            self.other_rev_id = self.other_branch.last_revision()
199
221
            if self.other_rev_id is None:
200
 
                raise NoCommits(other_branch)
 
222
                raise NoCommits(self.other_branch)
201
223
            self.other_basis = self.other_rev_id
202
224
        elif other_revision[1] is not None:
203
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
225
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
204
226
            self.other_basis = self.other_rev_id
205
227
        else:
206
228
            self.other_rev_id = None
207
 
            self.other_basis = other_branch.last_revision()
 
229
            self.other_basis = self.other_branch.last_revision()
208
230
            if self.other_basis is None:
209
 
                raise NoCommits(other_branch)
210
 
        if other_branch.base != self.this_branch.base:
211
 
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
231
                raise NoCommits(self.other_branch)
 
232
        if self.other_branch.base != self.this_branch.base:
 
233
            self.this_branch.fetch(self.other_branch,
 
234
                                   last_revision=self.other_basis)
 
235
 
 
236
    def set_other_revision(self, revision_id, other_branch):
 
237
        """Set 'other' based on a branch and revision id
 
238
 
 
239
        :param revision_id: The revision to use for a tree
 
240
        :param other_branch: The branch containing this tree
 
241
        """
 
242
        self.other_rev_id = revision_id
 
243
        self.other_branch = other_branch
 
244
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
245
        self.other_tree = self.revision_tree(revision_id)
 
246
        self.other_basis = revision_id
212
247
 
213
248
    def find_base(self):
214
249
        self.set_base([None, None])
221
256
        mutter("doing merge() with no base_revision specified")
222
257
        if base_revision == [None, None]:
223
258
            try:
224
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
259
                pb = ui.ui_factory.nested_progress_bar()
225
260
                try:
226
261
                    this_repo = self.this_branch.repository
227
 
                    self.base_rev_id = common_ancestor(self.this_basis, 
228
 
                                                       self.other_basis, 
229
 
                                                       this_repo, pb)
 
262
                    graph = this_repo.get_graph()
 
263
                    revisions = [ensure_null(self.this_basis),
 
264
                                 ensure_null(self.other_basis)]
 
265
                    if NULL_REVISION in revisions:
 
266
                        self.base_rev_id = NULL_REVISION
 
267
                    else:
 
268
                        self.base_rev_id = graph.find_unique_lca(*revisions)
 
269
                        if self.base_rev_id == NULL_REVISION:
 
270
                            raise UnrelatedBranches()
230
271
                finally:
231
272
                    pb.finished()
232
273
            except NoCommonAncestor:
233
274
                raise UnrelatedBranches()
234
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
235
 
                                            None)
 
275
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
 
276
                                                       self.base_rev_id,
 
277
                                                       None)
236
278
            self.base_is_ancestor = True
237
279
        else:
238
280
            base_branch, self.base_tree = _get_tree(base_revision)
249
291
                                                self.this_branch)
250
292
 
251
293
    def do_merge(self):
252
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
253
 
                  'other_tree': self.other_tree, 
 
294
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
295
                  'other_tree': self.other_tree,
254
296
                  'interesting_ids': self.interesting_ids,
255
297
                  'pp': self.pp}
256
298
        if self.merge_type.requires_base:
265
307
        elif self.show_base:
266
308
            raise BzrError("Showing base is not supported for this"
267
309
                                  " merge type. %s" % self.merge_type)
268
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
310
        self.this_tree.lock_tree_write()
 
311
        if self.base_tree is not None:
 
312
            self.base_tree.lock_read()
 
313
        if self.other_tree is not None:
 
314
            self.other_tree.lock_read()
 
315
        try:
 
316
            merge = self.merge_type(pb=self._pb,
 
317
                                    change_reporter=self.change_reporter,
 
318
                                    **kwargs)
 
319
            if self.recurse == 'down':
 
320
                for path, file_id in self.this_tree.iter_references():
 
321
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
322
                    other_revision = self.other_tree.get_reference_revision(
 
323
                        file_id, path)
 
324
                    if  other_revision == sub_tree.last_revision():
 
325
                        continue
 
326
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
327
                    sub_merge.merge_type = self.merge_type
 
328
                    relpath = self.this_tree.relpath(path)
 
329
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
330
                    sub_merge.set_other_revision(other_revision, other_branch)
 
331
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
332
                    sub_merge.base_tree = \
 
333
                        sub_tree.branch.repository.revision_tree(base_revision)
 
334
                    sub_merge.do_merge()
 
335
 
 
336
        finally:
 
337
            if self.other_tree is not None:
 
338
                self.other_tree.unlock()
 
339
            if self.base_tree is not None:
 
340
                self.base_tree.unlock()
 
341
            self.this_tree.unlock()
269
342
        if len(merge.cooked_conflicts) == 0:
270
343
            if not self.ignore_zero:
271
344
                note("All changes applied successfully.")
324
397
            else:
325
398
                parent = by_path[os.path.dirname(path)]
326
399
            abspath = pathjoin(self.this_tree.basedir, path)
327
 
            kind = bzrlib.osutils.file_kind(abspath)
 
400
            kind = osutils.file_kind(abspath)
328
401
            if file_id in self.base_tree.inventory:
329
402
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
330
403
            else:
353
426
 
354
427
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
355
428
                 interesting_ids=None, reprocess=False, show_base=False,
356
 
                 pb=DummyProgress(), pp=None):
 
429
                 pb=DummyProgress(), pp=None, change_reporter=None):
357
430
        """Initialize the merger object and perform the merge."""
358
431
        object.__init__(self)
359
432
        self.this_tree = working_tree
 
433
        self.this_tree.lock_tree_write()
360
434
        self.base_tree = base_tree
 
435
        self.base_tree.lock_read()
361
436
        self.other_tree = other_tree
 
437
        self.other_tree.lock_read()
362
438
        self._raw_conflicts = []
363
439
        self.cooked_conflicts = []
364
440
        self.reprocess = reprocess
365
441
        self.show_base = show_base
366
442
        self.pb = pb
367
443
        self.pp = pp
 
444
        self.change_reporter = change_reporter
368
445
        if self.pp is None:
369
446
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
370
447
 
373
450
        else:
374
451
            all_ids = set(base_tree)
375
452
            all_ids.update(other_tree)
376
 
        working_tree.lock_write()
377
453
        self.tt = TreeTransform(working_tree, self.pb)
378
454
        try:
379
455
            self.pp.next_phase()
386
462
                    self.merge_executable(file_id, file_status)
387
463
            finally:
388
464
                child_pb.finished()
389
 
                
 
465
            self.fix_root()
390
466
            self.pp.next_phase()
391
467
            child_pb = ui.ui_factory.nested_progress_bar()
392
468
            try:
393
469
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
394
470
            finally:
395
471
                child_pb.finished()
 
472
            if change_reporter is not None:
 
473
                from bzrlib import delta
 
474
                delta.report_changes(self.tt._iter_changes(), change_reporter)
396
475
            self.cook_conflicts(fs_conflicts)
397
476
            for conflict in self.cooked_conflicts:
398
477
                warning(conflict)
405
484
                pass
406
485
        finally:
407
486
            self.tt.finalize()
408
 
            working_tree.unlock()
 
487
            self.other_tree.unlock()
 
488
            self.base_tree.unlock()
 
489
            self.this_tree.unlock()
409
490
            self.pb.clear()
410
491
 
 
492
    def fix_root(self):
 
493
        try:
 
494
            self.tt.final_kind(self.tt.root)
 
495
        except NoSuchFile:
 
496
            self.tt.cancel_deletion(self.tt.root)
 
497
        if self.tt.final_file_id(self.tt.root) is None:
 
498
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
499
                                 self.tt.root)
 
500
        if self.other_tree.inventory.root is None:
 
501
            return
 
502
        other_root_file_id = self.other_tree.inventory.root.file_id
 
503
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
504
        if other_root == self.tt.root:
 
505
            return
 
506
        try:
 
507
            self.tt.final_kind(other_root)
 
508
        except NoSuchFile:
 
509
            return
 
510
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
511
        self.tt.cancel_creation(other_root)
 
512
        self.tt.cancel_versioning(other_root)
 
513
 
 
514
    def reparent_children(self, ie, target):
 
515
        for thing, child in ie.children.iteritems():
 
516
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
517
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
518
 
411
519
    def write_modified(self, results):
412
520
        modified_hashes = {}
413
521
        for path in results.modified_paths:
518
626
                        "conflict": other_entry}
519
627
        trans_id = self.tt.trans_id_file_id(file_id)
520
628
        parent_id = winner_entry[parent_id_winner].parent_id
521
 
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
522
 
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
523
 
                            trans_id)
 
629
        if parent_id is not None:
 
630
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
631
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
632
                                parent_trans_id, trans_id)
524
633
 
525
634
    def merge_contents(self, file_id):
526
635
        """Performa a merge on file_id contents."""
542
651
            parent_id = self.tt.final_parent(trans_id)
543
652
            if file_id in self.this_tree.inventory:
544
653
                self.tt.unversion_file(trans_id)
545
 
                self.tt.delete_contents(trans_id)
 
654
                if file_id in self.this_tree:
 
655
                    self.tt.delete_contents(trans_id)
546
656
            file_group = self._dump_conflicts(name, parent_id, file_id, 
547
657
                                              set_version=True)
548
658
            self._raw_conflicts.append(('contents conflict', file_group))
767
877
            except KeyError:
768
878
                this_name = other_name = self.tt.final_name(trans_id)
769
879
            other_path = fp.get_path(trans_id)
770
 
            if this_parent is not None:
 
880
            if this_parent is not None and this_name is not None:
771
881
                this_parent_path = \
772
882
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
773
883
                this_path = pathjoin(this_parent_path, this_name)
787
897
 
788
898
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
789
899
                 interesting_ids=None, pb=DummyProgress(), pp=None,
790
 
                 reprocess=False):
 
900
                 reprocess=False, change_reporter=None):
791
901
        self.this_revision_tree = self._get_revision_tree(this_tree)
792
902
        self.other_revision_tree = self._get_revision_tree(other_tree)
793
903
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
794
904
                                          base_tree, other_tree, 
795
905
                                          interesting_ids=interesting_ids, 
796
 
                                          pb=pb, pp=pp, reprocess=reprocess)
 
906
                                          pb=pb, pp=pp, reprocess=reprocess,
 
907
                                          change_reporter=change_reporter)
797
908
 
798
909
    def _get_revision_tree(self, tree):
799
910
        """Return a revision tree related to this tree.
868
979
        will be dumped, and a will be conflict noted.
869
980
        """
870
981
        import bzrlib.patch
871
 
        temp_dir = mkdtemp(prefix="bzr-")
 
982
        temp_dir = osutils.mkdtemp(prefix="bzr-")
872
983
        try:
873
984
            new_file = pathjoin(temp_dir, "new")
874
985
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
886
997
                name = self.tt.final_name(trans_id)
887
998
                parent_id = self.tt.final_parent(trans_id)
888
999
                self._dump_conflicts(name, parent_id, file_id)
889
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
1000
                self._raw_conflicts.append(('text conflict', trans_id))
890
1001
        finally:
891
 
            rmtree(temp_dir)
 
1002
            osutils.rmtree(temp_dir)
892
1003
 
893
1004
 
894
1005
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
895
 
                backup_files=False, 
896
 
                merge_type=Merge3Merger, 
897
 
                interesting_ids=None, 
898
 
                show_base=False, 
899
 
                reprocess=False, 
 
1006
                backup_files=False,
 
1007
                merge_type=Merge3Merger,
 
1008
                interesting_ids=None,
 
1009
                show_base=False,
 
1010
                reprocess=False,
900
1011
                other_rev_id=None,
901
1012
                interesting_files=None,
902
1013
                this_tree=None,
903
 
                pb=DummyProgress()):
 
1014
                pb=DummyProgress(),
 
1015
                change_reporter=None):
904
1016
    """Primary interface for merging. 
905
1017
 
906
1018
        typical use is probably 
908
1020
                     branch.get_revision_tree(base_revision))'
909
1021
        """
910
1022
    if this_tree is None:
911
 
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
912
 
             "bzrlib version 0.8.",
913
 
             DeprecationWarning,
914
 
             stacklevel=2)
915
 
        this_tree = this_branch.bzrdir.open_workingtree()
916
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
917
 
                    pb=pb)
 
1023
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1024
            "parameter as of bzrlib version 0.8.")
 
1025
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1026
                    pb=pb, change_reporter=change_reporter)
918
1027
    merger.backup_files = backup_files
919
1028
    merger.merge_type = merge_type
920
1029
    merger.interesting_ids = interesting_ids
929
1038
    merger.other_basis = other_rev_id
930
1039
    return merger.do_merge()
931
1040
 
932
 
 
933
 
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
934
 
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
935
 
                     'weave': (WeaveMerger, "Weave-based merge")
936
 
              }
937
 
 
938
 
 
939
 
def merge_type_help():
940
 
    templ = '%s%%7s: %%s' % (' '*12)
941
 
    lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
942
 
    return '\n'.join(lines)
 
1041
def get_merge_type_registry():
 
1042
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1043
 
 
1044
    This method provides a sanctioned way to retrieve it.
 
1045
    """
 
1046
    from bzrlib import option
 
1047
    return option._merge_type_registry