/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Alexander Belchenko
  • Date: 2007-06-05 08:02:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2512.
  • Revision ID: bialix@ukr.net-20070605080204-hvhqw69njlpxcscb
sanitizeĀ developersĀ docs

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
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
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
192
213
 
193
214
        :param other_revision: The [path, revision] list to merge from.
194
215
        """
195
 
        other_branch, self.other_tree = _get_tree(other_revision,
 
216
        self.other_branch, self.other_tree = _get_tree(other_revision,
196
217
                                                  self.this_branch)
197
218
        if other_revision[1] == -1:
198
 
            self.other_rev_id = other_branch.last_revision()
 
219
            self.other_rev_id = self.other_branch.last_revision()
199
220
            if self.other_rev_id is None:
200
 
                raise NoCommits(other_branch)
 
221
                raise NoCommits(self.other_branch)
201
222
            self.other_basis = self.other_rev_id
202
223
        elif other_revision[1] is not None:
203
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
224
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
204
225
            self.other_basis = self.other_rev_id
205
226
        else:
206
227
            self.other_rev_id = None
207
 
            self.other_basis = other_branch.last_revision()
 
228
            self.other_basis = self.other_branch.last_revision()
208
229
            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)
 
230
                raise NoCommits(self.other_branch)
 
231
        if self.other_branch.base != self.this_branch.base:
 
232
            self.this_branch.fetch(self.other_branch,
 
233
                                   last_revision=self.other_basis)
 
234
 
 
235
    def set_other_revision(self, revision_id, other_branch):
 
236
        """Set 'other' based on a branch and revision id
 
237
 
 
238
        :param revision_id: The revision to use for a tree
 
239
        :param other_branch: The branch containing this tree
 
240
        """
 
241
        self.other_rev_id = revision_id
 
242
        self.other_branch = other_branch
 
243
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
244
        self.other_tree = self.revision_tree(revision_id)
 
245
        self.other_basis = revision_id
212
246
 
213
247
    def find_base(self):
214
248
        self.set_base([None, None])
221
255
        mutter("doing merge() with no base_revision specified")
222
256
        if base_revision == [None, None]:
223
257
            try:
224
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
258
                pb = ui.ui_factory.nested_progress_bar()
225
259
                try:
226
260
                    this_repo = self.this_branch.repository
227
261
                    self.base_rev_id = common_ancestor(self.this_basis, 
231
265
                    pb.finished()
232
266
            except NoCommonAncestor:
233
267
                raise UnrelatedBranches()
234
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
235
 
                                            None)
 
268
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
 
269
                                                       self.base_rev_id,
 
270
                                                       None)
236
271
            self.base_is_ancestor = True
237
272
        else:
238
273
            base_branch, self.base_tree = _get_tree(base_revision)
249
284
                                                self.this_branch)
250
285
 
251
286
    def do_merge(self):
252
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
253
 
                  'other_tree': self.other_tree, 
 
287
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
288
                  'other_tree': self.other_tree,
254
289
                  'interesting_ids': self.interesting_ids,
255
290
                  'pp': self.pp}
256
291
        if self.merge_type.requires_base:
265
300
        elif self.show_base:
266
301
            raise BzrError("Showing base is not supported for this"
267
302
                                  " merge type. %s" % self.merge_type)
268
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
303
        self.this_tree.lock_tree_write()
 
304
        if self.base_tree is not None:
 
305
            self.base_tree.lock_read()
 
306
        if self.other_tree is not None:
 
307
            self.other_tree.lock_read()
 
308
        try:
 
309
            merge = self.merge_type(pb=self._pb,
 
310
                                    change_reporter=self.change_reporter,
 
311
                                    **kwargs)
 
312
            if self.recurse == 'down':
 
313
                for path, file_id in self.this_tree.iter_references():
 
314
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
315
                    other_revision = self.other_tree.get_reference_revision(
 
316
                        file_id, path)
 
317
                    if  other_revision == sub_tree.last_revision():
 
318
                        continue
 
319
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
320
                    sub_merge.merge_type = self.merge_type
 
321
                    relpath = self.this_tree.relpath(path)
 
322
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
323
                    sub_merge.set_other_revision(other_revision, other_branch)
 
324
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
325
                    sub_merge.base_tree = \
 
326
                        sub_tree.branch.repository.revision_tree(base_revision)
 
327
                    sub_merge.do_merge()
 
328
 
 
329
        finally:
 
330
            if self.other_tree is not None:
 
331
                self.other_tree.unlock()
 
332
            if self.base_tree is not None:
 
333
                self.base_tree.unlock()
 
334
            self.this_tree.unlock()
269
335
        if len(merge.cooked_conflicts) == 0:
270
336
            if not self.ignore_zero:
271
337
                note("All changes applied successfully.")
324
390
            else:
325
391
                parent = by_path[os.path.dirname(path)]
326
392
            abspath = pathjoin(self.this_tree.basedir, path)
327
 
            kind = bzrlib.osutils.file_kind(abspath)
 
393
            kind = osutils.file_kind(abspath)
328
394
            if file_id in self.base_tree.inventory:
329
395
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
330
396
            else:
353
419
 
354
420
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
355
421
                 interesting_ids=None, reprocess=False, show_base=False,
356
 
                 pb=DummyProgress(), pp=None):
 
422
                 pb=DummyProgress(), pp=None, change_reporter=None):
357
423
        """Initialize the merger object and perform the merge."""
358
424
        object.__init__(self)
359
425
        self.this_tree = working_tree
 
426
        self.this_tree.lock_tree_write()
360
427
        self.base_tree = base_tree
 
428
        self.base_tree.lock_read()
361
429
        self.other_tree = other_tree
 
430
        self.other_tree.lock_read()
362
431
        self._raw_conflicts = []
363
432
        self.cooked_conflicts = []
364
433
        self.reprocess = reprocess
365
434
        self.show_base = show_base
366
435
        self.pb = pb
367
436
        self.pp = pp
 
437
        self.change_reporter = change_reporter
368
438
        if self.pp is None:
369
439
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
370
440
 
373
443
        else:
374
444
            all_ids = set(base_tree)
375
445
            all_ids.update(other_tree)
376
 
        working_tree.lock_write()
377
446
        self.tt = TreeTransform(working_tree, self.pb)
378
447
        try:
379
448
            self.pp.next_phase()
386
455
                    self.merge_executable(file_id, file_status)
387
456
            finally:
388
457
                child_pb.finished()
389
 
                
 
458
            self.fix_root()
390
459
            self.pp.next_phase()
391
460
            child_pb = ui.ui_factory.nested_progress_bar()
392
461
            try:
393
462
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
394
463
            finally:
395
464
                child_pb.finished()
 
465
            if change_reporter is not None:
 
466
                from bzrlib import delta
 
467
                delta.report_changes(self.tt._iter_changes(), change_reporter)
396
468
            self.cook_conflicts(fs_conflicts)
397
469
            for conflict in self.cooked_conflicts:
398
470
                warning(conflict)
405
477
                pass
406
478
        finally:
407
479
            self.tt.finalize()
408
 
            working_tree.unlock()
 
480
            self.other_tree.unlock()
 
481
            self.base_tree.unlock()
 
482
            self.this_tree.unlock()
409
483
            self.pb.clear()
410
484
 
 
485
    def fix_root(self):
 
486
        try:
 
487
            self.tt.final_kind(self.tt.root)
 
488
        except NoSuchFile:
 
489
            self.tt.cancel_deletion(self.tt.root)
 
490
        if self.tt.final_file_id(self.tt.root) is None:
 
491
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
492
                                 self.tt.root)
 
493
        if self.other_tree.inventory.root is None:
 
494
            return
 
495
        other_root_file_id = self.other_tree.inventory.root.file_id
 
496
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
497
        if other_root == self.tt.root:
 
498
            return
 
499
        try:
 
500
            self.tt.final_kind(other_root)
 
501
        except NoSuchFile:
 
502
            return
 
503
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
504
        self.tt.cancel_creation(other_root)
 
505
        self.tt.cancel_versioning(other_root)
 
506
 
 
507
    def reparent_children(self, ie, target):
 
508
        for thing, child in ie.children.iteritems():
 
509
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
510
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
511
 
411
512
    def write_modified(self, results):
412
513
        modified_hashes = {}
413
514
        for path in results.modified_paths:
518
619
                        "conflict": other_entry}
519
620
        trans_id = self.tt.trans_id_file_id(file_id)
520
621
        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)
 
622
        if parent_id is not None:
 
623
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
624
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
625
                                parent_trans_id, trans_id)
524
626
 
525
627
    def merge_contents(self, file_id):
526
628
        """Performa a merge on file_id contents."""
542
644
            parent_id = self.tt.final_parent(trans_id)
543
645
            if file_id in self.this_tree.inventory:
544
646
                self.tt.unversion_file(trans_id)
545
 
                self.tt.delete_contents(trans_id)
 
647
                if file_id in self.this_tree:
 
648
                    self.tt.delete_contents(trans_id)
546
649
            file_group = self._dump_conflicts(name, parent_id, file_id, 
547
650
                                              set_version=True)
548
651
            self._raw_conflicts.append(('contents conflict', file_group))
787
890
 
788
891
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
789
892
                 interesting_ids=None, pb=DummyProgress(), pp=None,
790
 
                 reprocess=False):
 
893
                 reprocess=False, change_reporter=None):
791
894
        self.this_revision_tree = self._get_revision_tree(this_tree)
792
895
        self.other_revision_tree = self._get_revision_tree(other_tree)
793
896
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
794
897
                                          base_tree, other_tree, 
795
898
                                          interesting_ids=interesting_ids, 
796
 
                                          pb=pb, pp=pp, reprocess=reprocess)
 
899
                                          pb=pb, pp=pp, reprocess=reprocess,
 
900
                                          change_reporter=change_reporter)
797
901
 
798
902
    def _get_revision_tree(self, tree):
799
903
        """Return a revision tree related to this tree.
868
972
        will be dumped, and a will be conflict noted.
869
973
        """
870
974
        import bzrlib.patch
871
 
        temp_dir = mkdtemp(prefix="bzr-")
 
975
        temp_dir = osutils.mkdtemp(prefix="bzr-")
872
976
        try:
873
977
            new_file = pathjoin(temp_dir, "new")
874
978
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
886
990
                name = self.tt.final_name(trans_id)
887
991
                parent_id = self.tt.final_parent(trans_id)
888
992
                self._dump_conflicts(name, parent_id, file_id)
889
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
993
                self._raw_conflicts.append(('text conflict', trans_id))
890
994
        finally:
891
 
            rmtree(temp_dir)
 
995
            osutils.rmtree(temp_dir)
892
996
 
893
997
 
894
998
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, 
 
999
                backup_files=False,
 
1000
                merge_type=Merge3Merger,
 
1001
                interesting_ids=None,
 
1002
                show_base=False,
 
1003
                reprocess=False,
900
1004
                other_rev_id=None,
901
1005
                interesting_files=None,
902
1006
                this_tree=None,
903
 
                pb=DummyProgress()):
 
1007
                pb=DummyProgress(),
 
1008
                change_reporter=None):
904
1009
    """Primary interface for merging. 
905
1010
 
906
1011
        typical use is probably 
908
1013
                     branch.get_revision_tree(base_revision))'
909
1014
        """
910
1015
    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)
 
1016
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1017
            "parameter as of bzrlib version 0.8.")
 
1018
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1019
                    pb=pb, change_reporter=change_reporter)
918
1020
    merger.backup_files = backup_files
919
1021
    merger.merge_type = merge_type
920
1022
    merger.interesting_ids = interesting_ids
929
1031
    merger.other_basis = other_rev_id
930
1032
    return merger.do_merge()
931
1033
 
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)
 
1034
def get_merge_type_registry():
 
1035
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1036
 
 
1037
    This method provides a sanctioned way to retrieve it.
 
1038
    """
 
1039
    from bzrlib import option
 
1040
    return option._merge_type_registry