/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 breezy/git/workingtree.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-11 20:19:38 UTC
  • mfrom: (7526.3.2 work)
  • Revision ID: gustav.hartvigsson@gmail.com-20210111201938-omr9wjz3qdgyxe8k
MergedĀ lp:brz

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
    )
45
45
import os
46
46
import posixpath
 
47
import re
47
48
import stat
48
49
import sys
49
50
 
53
54
    errors,
54
55
    controldir as _mod_controldir,
55
56
    globbing,
56
 
    ignores,
57
57
    lock,
58
 
    merge,
59
58
    osutils,
60
59
    revision as _mod_revision,
61
60
    trace,
80
79
    MutableGitIndexTree,
81
80
    )
82
81
from .mapping import (
 
82
    encode_git_path,
 
83
    decode_git_path,
83
84
    mode_kind,
84
85
    )
85
86
 
86
87
 
 
88
CONFLICT_SUFFIXES = ['.BASE', '.OTHER', '.THIS']
 
89
 
 
90
 
 
91
# TODO: There should be a base revid attribute to better inform the user about
 
92
# how the conflicts were generated.
 
93
class TextConflict(_mod_conflicts.Conflict):
 
94
    """The merge algorithm could not resolve all differences encountered."""
 
95
 
 
96
    has_files = True
 
97
 
 
98
    typestring = 'text conflict'
 
99
 
 
100
    _conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
 
101
 
 
102
    def associated_filenames(self):
 
103
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
 
104
 
 
105
    def _resolve(self, tt, winner_suffix):
 
106
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
 
107
 
 
108
        :param tt: The TreeTransform where the conflict is resolved.
 
109
        :param winner_suffix: Either 'THIS' or 'OTHER'
 
110
 
 
111
        The resolution is symmetric, when taking THIS, item.THIS is renamed
 
112
        into item and vice-versa. This takes one of the files as a whole
 
113
        ignoring every difference that could have been merged cleanly.
 
114
        """
 
115
        # To avoid useless copies, we switch item and item.winner_suffix, only
 
116
        # item will exist after the conflict has been resolved anyway.
 
117
        item_tid = tt.trans_id_tree_path(self.path)
 
118
        item_parent_tid = tt.get_tree_parent(item_tid)
 
119
        winner_path = self.path + '.' + winner_suffix
 
120
        winner_tid = tt.trans_id_tree_path(winner_path)
 
121
        winner_parent_tid = tt.get_tree_parent(winner_tid)
 
122
        # Switch the paths to preserve the content
 
123
        tt.adjust_path(osutils.basename(self.path),
 
124
                       winner_parent_tid, winner_tid)
 
125
        tt.adjust_path(osutils.basename(winner_path),
 
126
                       item_parent_tid, item_tid)
 
127
        tt.unversion_file(item_tid)
 
128
        tt.version_file(winner_tid)
 
129
        tt.apply()
 
130
 
 
131
    def action_auto(self, tree):
 
132
        # GZ 2012-07-27: Using NotImplementedError to signal that a conflict
 
133
        #                can't be auto resolved does not seem ideal.
 
134
        try:
 
135
            kind = tree.kind(self.path)
 
136
        except errors.NoSuchFile:
 
137
            return
 
138
        if kind != 'file':
 
139
            raise NotImplementedError("Conflict is not a file")
 
140
        conflict_markers_in_line = self._conflict_re.search
 
141
        with tree.get_file(self.path) as f:
 
142
            for line in f:
 
143
                if conflict_markers_in_line(line):
 
144
                    raise NotImplementedError("Conflict markers present")
 
145
 
 
146
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
 
147
        with tree.transform() as tt:
 
148
            self._resolve(tt, *args, **kwargs)
 
149
 
 
150
    def action_take_this(self, tree):
 
151
        self._resolve_with_cleanups(tree, 'THIS')
 
152
 
 
153
    def action_take_other(self, tree):
 
154
        self._resolve_with_cleanups(tree, 'OTHER')
 
155
 
 
156
    def do(self, action, tree):
 
157
        """Apply the specified action to the conflict.
 
158
 
 
159
        :param action: The method name to call.
 
160
 
 
161
        :param tree: The tree passed as a parameter to the method.
 
162
        """
 
163
        meth = getattr(self, 'action_%s' % action, None)
 
164
        if meth is None:
 
165
            raise NotImplementedError(self.__class__.__name__ + '.' + action)
 
166
        meth(tree)
 
167
 
 
168
    def action_done(self, tree):
 
169
        """Mark the conflict as solved once it has been handled."""
 
170
        # This method does nothing but simplifies the design of upper levels.
 
171
        pass
 
172
 
 
173
    def describe(self):
 
174
        return 'Text conflict in %(path)s' % self.__dict__
 
175
 
 
176
    def __str__(self):
 
177
        return self.describe()
 
178
 
 
179
    def __repr__(self):
 
180
        return "%s(%r)" % (type(self).__name__, self.path)
 
181
 
 
182
 
87
183
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
88
184
    """A Git working tree."""
89
185
 
121
217
        try:
122
218
            info = self._submodule_info()[relpath]
123
219
        except KeyError:
124
 
            index_path = os.path.join(self.basedir, relpath.decode('utf-8'), '.git', 'index')
 
220
            index_path = os.path.join(self.basedir, decode_git_path(relpath), '.git', 'index')
125
221
        else:
126
222
            index_path = self.control_transport.local_abspath(
127
 
                posixpath.join('modules', info[1].decode('utf-8'), 'index'))
 
223
                posixpath.join('modules', decode_git_path(info[1]), 'index'))
128
224
        return Index(index_path)
129
225
 
130
226
    def lock_read(self):
221
317
        else:
222
318
            self.case_sensitive = False
223
319
 
224
 
    def get_transform(self, pb=None):
225
 
        from ..transform import TreeTransform
226
 
        return TreeTransform(self, pb=pb)
227
 
 
228
320
    def merge_modified(self):
229
321
        return {}
230
322
 
334
426
        def recurse_directory_to_add_files(directory):
335
427
            # Recurse directory and add all files
336
428
            # so we can check if they have changed.
337
 
            for parent_info, file_infos in self.walkdirs(directory):
338
 
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
 
429
            for parent_path, file_infos in self.walkdirs(directory):
 
430
                for relpath, basename, kind, lstat, kind in file_infos:
339
431
                    # Is it versioned or ignored?
340
432
                    if self.is_versioned(relpath):
341
433
                        # Add nested content for deletion.
462
554
                kind = osutils.file_kind(abspath)
463
555
                if kind in ("file", "symlink"):
464
556
                    (index, subpath) = self._lookup_index(
465
 
                        filepath.encode('utf-8'))
 
557
                        encode_git_path(filepath))
466
558
                    if subpath in index:
467
559
                        # Already present
468
560
                        continue
472
564
                    added.append(filepath)
473
565
                elif kind == "directory":
474
566
                    (index, subpath) = self._lookup_index(
475
 
                        filepath.encode('utf-8'))
 
567
                        encode_git_path(filepath))
476
568
                    if subpath not in index:
477
569
                        call_action(filepath, kind)
478
570
                    if recurse:
512
604
                        user_dirs.append(subp)
513
605
                    else:
514
606
                        (index, subpath) = self._lookup_index(
515
 
                            subp.encode('utf-8'))
 
607
                            encode_git_path(subp))
516
608
                        if subpath in index:
517
609
                            # Already present
518
610
                            continue
573
665
        """
574
666
        with self.lock_read():
575
667
            index_paths = set(
576
 
                [p.decode('utf-8') for p, i in self._recurse_index_entries()])
 
668
                [decode_git_path(p) for p, sha, mode in self.iter_git_objects()])
577
669
            all_paths = set(self._iter_files_recursive(include_dirs=False))
578
670
            return iter(all_paths - index_paths)
579
671
 
628
720
        be ignored, otherwise None.  So this can simply be used as a
629
721
        boolean if desired."""
630
722
        if getattr(self, '_global_ignoreglobster', None) is None:
 
723
            from breezy import ignores
631
724
            ignore_globs = set()
632
725
            ignore_globs.update(ignores.get_runtime_ignores())
633
726
            ignore_globs.update(ignores.get_user_ignores())
677
770
 
678
771
    def get_file_verifier(self, path, stat_value=None):
679
772
        with self.lock_read():
680
 
            (index, subpath) = self._lookup_index(path.encode('utf-8'))
 
773
            (index, subpath) = self._lookup_index(encode_git_path(path))
681
774
            try:
682
775
                return ("GIT", index[subpath].sha)
683
776
            except KeyError:
709
802
 
710
803
    def stored_kind(self, path):
711
804
        with self.lock_read():
712
 
            encoded_path = path.encode('utf-8')
 
805
            encoded_path = encode_git_path(path)
713
806
            (index, subpath) = self._lookup_index(encoded_path)
714
807
            try:
715
808
                return mode_kind(index[subpath].mode)
723
816
        return os.lstat(self.abspath(path))
724
817
 
725
818
    def _live_entry(self, path):
726
 
        encoded_path = self.abspath(path.decode('utf-8')).encode(
 
819
        encoded_path = self.abspath(decode_git_path(path)).encode(
727
820
            osutils._fs_enc)
728
821
        return index_entry_from_path(encoded_path)
729
822
 
732
825
            if self._supports_executable():
733
826
                mode = self._lstat(path).st_mode
734
827
            else:
735
 
                (index, subpath) = self._lookup_index(path.encode('utf-8'))
 
828
                (index, subpath) = self._lookup_index(encode_git_path(path))
736
829
                try:
737
830
                    mode = index[subpath].mode
738
831
                except KeyError:
777
870
                         name.decode(osutils._fs_enc))])
778
871
            for path in path_iterator:
779
872
                try:
780
 
                    encoded_path = path.encode("utf-8")
 
873
                    encoded_path = encode_git_path(path)
781
874
                except UnicodeEncodeError:
782
875
                    raise errors.BadFilenameEncoding(
783
876
                        path, osutils._fs_enc)
831
924
            for path in self.index:
832
925
                if self.mapping.is_special_file(path):
833
926
                    continue
834
 
                path = path.decode("utf-8")
 
927
                path = decode_git_path(path)
835
928
                paths.add(path)
836
929
                while path != "":
837
930
                    path = posixpath.dirname(path).strip("/")
841
934
            return paths
842
935
 
843
936
    def iter_child_entries(self, path):
844
 
        encoded_path = path.encode('utf-8')
 
937
        encoded_path = encode_git_path(path)
845
938
        with self.lock_read():
846
939
            parent_id = self.path2id(path)
847
940
            found_any = False
848
941
            for item_path, value in self.index.iteritems():
849
 
                decoded_item_path = item_path.decode('utf-8')
 
942
                decoded_item_path = decode_git_path(item_path)
850
943
                if self.mapping.is_special_file(item_path):
851
944
                    continue
852
945
                if not osutils.is_inside(path, decoded_item_path):
870
963
            conflicts = _mod_conflicts.ConflictList()
871
964
            for item_path, value in self.index.iteritems():
872
965
                if value.flags & FLAG_STAGEMASK:
873
 
                    conflicts.append(_mod_conflicts.TextConflict(
874
 
                        item_path.decode('utf-8')))
 
966
                    conflicts.append(TextConflict(decode_git_path(item_path)))
875
967
            return conflicts
876
968
 
877
969
    def set_conflicts(self, conflicts):
878
970
        by_path = set()
879
971
        for conflict in conflicts:
880
972
            if conflict.typestring in ('text conflict', 'contents conflict'):
881
 
                by_path.add(conflict.path.encode('utf-8'))
 
973
                by_path.add(encode_git_path(conflict.path))
882
974
            else:
883
975
                raise errors.UnsupportedOperation(self.set_conflicts, self)
884
976
        with self.lock_tree_write():
886
978
                self._set_conflicted(path, path in by_path)
887
979
 
888
980
    def _set_conflicted(self, path, conflicted):
889
 
        trace.mutter('change conflict: %r -> %r', path, conflicted)
890
981
        value = self.index[path]
891
982
        self._index_dirty = True
892
983
        if conflicted:
901
992
                                           'contents conflict'):
902
993
                    try:
903
994
                        self._set_conflicted(
904
 
                            conflict.path.encode('utf-8'), True)
 
995
                            encode_git_path(conflict.path), True)
905
996
                    except KeyError:
906
997
                        raise errors.UnsupportedOperation(
907
998
                            self.add_conflicts, self)
912
1003
        """Walk the directories of this tree.
913
1004
 
914
1005
        returns a generator which yields items in the form:
915
 
                ((curren_directory_path, fileid),
916
 
                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
 
1006
                (current_directory_path,
 
1007
                 [(file1_path, file1_name, file1_kind, (lstat),
917
1008
                   file1_kind), ... ])
918
1009
 
919
1010
        This API returns a generator, which is only valid during the current
977
1068
                             - (current_inv[0][0] < cur_disk_dir_relpath))
978
1069
            if direction > 0:
979
1070
                # disk is before inventory - unknown
980
 
                dirblock = [(relpath, basename, kind, stat, None, None) for
 
1071
                dirblock = [(relpath, basename, kind, stat, None) for
981
1072
                            relpath, basename, kind, stat, top_path in
982
1073
                            cur_disk_dir_content]
983
 
                yield (cur_disk_dir_relpath, None), dirblock
 
1074
                yield cur_disk_dir_relpath, dirblock
984
1075
                try:
985
1076
                    current_disk = next(disk_iterator)
986
1077
                except StopIteration:
987
1078
                    disk_finished = True
988
1079
            elif direction < 0:
989
1080
                # inventory is before disk - missing.
990
 
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
 
1081
                dirblock = [(relpath, basename, 'unknown', None, kind)
991
1082
                            for relpath, basename, dkind, stat, fileid, kind in
992
1083
                            current_inv[1]]
993
 
                yield (current_inv[0][0], current_inv[0][1]), dirblock
 
1084
                yield current_inv[0][0], dirblock
994
1085
                try:
995
1086
                    current_inv = next(inventory_iterator)
996
1087
                except StopIteration:
1008
1099
                        # versioned, present file
1009
1100
                        dirblock.append((inv_row[0],
1010
1101
                                         inv_row[1], disk_row[2],
1011
 
                                         disk_row[3], inv_row[4],
1012
 
                                         inv_row[5]))
 
1102
                                         disk_row[3], inv_row[5]))
1013
1103
                    elif len(path_elements[0]) == 5:
1014
1104
                        # unknown disk file
1015
1105
                        dirblock.append(
1016
1106
                            (path_elements[0][0], path_elements[0][1],
1017
1107
                                path_elements[0][2], path_elements[0][3],
1018
 
                                None, None))
 
1108
                                None))
1019
1109
                    elif len(path_elements[0]) == 6:
1020
1110
                        # versioned, absent file.
1021
1111
                        dirblock.append(
1022
1112
                            (path_elements[0][0], path_elements[0][1],
1023
 
                                'unknown', None, path_elements[0][4],
 
1113
                                'unknown', None,
1024
1114
                                path_elements[0][5]))
1025
1115
                    else:
1026
1116
                        raise NotImplementedError('unreachable code')
1027
 
                yield current_inv[0], dirblock
 
1117
                yield current_inv[0][0], dirblock
1028
1118
                try:
1029
1119
                    current_inv = next(inventory_iterator)
1030
1120
                except StopIteration:
1037
1127
    def _walkdirs(self, prefix=u""):
1038
1128
        if prefix != u"":
1039
1129
            prefix += u"/"
1040
 
        prefix = prefix.encode('utf-8')
 
1130
        prefix = encode_git_path(prefix)
1041
1131
        per_dir = defaultdict(set)
1042
1132
        if prefix == b"":
1043
1133
            per_dir[(u'', self.path2id(''))] = set()
1047
1137
                return
1048
1138
            (dirname, child_name) = posixpath.split(path)
1049
1139
            add_entry(dirname, 'directory')
1050
 
            dirname = dirname.decode("utf-8")
 
1140
            dirname = decode_git_path(dirname)
1051
1141
            dir_file_id = self.path2id(dirname)
1052
1142
            if not isinstance(value, tuple) or len(value) != 10:
1053
1143
                raise ValueError(value)
1054
1144
            per_dir[(dirname, dir_file_id)].add(
1055
 
                (path.decode("utf-8"), child_name.decode("utf-8"),
 
1145
                (decode_git_path(path), decode_git_path(child_name),
1056
1146
                 kind, None,
1057
 
                 self.path2id(path.decode("utf-8")),
 
1147
                 self.path2id(decode_git_path(path)),
1058
1148
                 kind))
1059
1149
        with self.lock_read():
1060
1150
            for path, value in self.index.iteritems():
1071
1161
    def store_uncommitted(self):
1072
1162
        raise errors.StoringUncommittedNotSupported(self)
1073
1163
 
1074
 
    def apply_inventory_delta(self, changes):
1075
 
        for (old_path, new_path, file_id, ie) in changes:
1076
 
            if old_path is not None:
1077
 
                (index, old_subpath) = self._lookup_index(
1078
 
                    old_path.encode('utf-8'))
1079
 
                try:
1080
 
                    self._index_del_entry(index, old_subpath)
1081
 
                except KeyError:
1082
 
                    pass
1083
 
                else:
1084
 
                    self._versioned_dirs = None
1085
 
            if new_path is not None and ie.kind != 'directory':
1086
 
                if ie.kind == 'tree-reference':
1087
 
                    self._index_add_entry(
1088
 
                        new_path, ie.kind,
1089
 
                        reference_revision=ie.reference_revision)
1090
 
                else:
1091
 
                    self._index_add_entry(new_path, ie.kind)
1092
 
        self.flush()
1093
 
 
1094
1164
    def annotate_iter(self, path,
1095
1165
                      default_revision=_mod_revision.CURRENT_REVISION):
1096
1166
        """See Tree.annotate_iter
1187
1257
                        # Let's at least try to use the working tree file:
1188
1258
                        try:
1189
1259
                            st = self._lstat(self.abspath(
1190
 
                                entry.path.decode('utf-8')))
 
1260
                                decode_git_path(entry.path)))
1191
1261
                        except OSError:
1192
1262
                            # But if it doesn't exist, we'll make something up.
1193
1263
                            obj = self.store[entry.sha]
1198
1268
                    (index, subpath) = self._lookup_index(entry.path)
1199
1269
                    index[subpath] = index_entry_from_stat(st, entry.sha, 0)
1200
1270
 
1201
 
    def _update_git_tree(self, old_revision, new_revision, change_reporter=None,
1202
 
                         show_base=False):
 
1271
    def _update_git_tree(
 
1272
            self, old_revision, new_revision, change_reporter=None,
 
1273
            show_base=False):
1203
1274
        basis_tree = self.revision_tree(old_revision)
1204
1275
        if new_revision != old_revision:
 
1276
            from .. import merge
1205
1277
            with basis_tree.lock_read():
1206
1278
                new_basis_tree = self.branch.basis_tree()
1207
1279
                merge.merge_inner(
1247
1319
    def get_reference_revision(self, path, branch=None):
1248
1320
        hexsha = self._read_submodule_head(path)
1249
1321
        if hexsha is None:
1250
 
            return _mod_revision.NULL_REVISION
 
1322
            (index, subpath) = self._lookup_index(
 
1323
                encode_git_path(path))
 
1324
            if subpath is None:
 
1325
                raise errors.NoSuchFile(path)
 
1326
            hexsha = index[subpath].sha
1251
1327
        return self.branch.lookup_foreign_revision_id(hexsha)
1252
1328
 
1253
1329
    def get_nested_tree(self, path):
1310
1386
 
1311
1387
    def copy_content_into(self, tree, revision_id=None):
1312
1388
        """Copy the current content and user files of this tree into tree."""
 
1389
        from .. import merge
1313
1390
        with self.lock_read():
1314
1391
            if revision_id is None:
1315
1392
                merge.transform_tree(tree, self)
1338
1415
 
1339
1416
    def get_reference_info(self, path):
1340
1417
        submodule_info = self._submodule_info()
1341
 
        info = submodule_info.get(path.encode('utf-8'))
 
1418
        info = submodule_info.get(encode_git_path(path))
1342
1419
        if info is None:
1343
1420
            return None
1344
 
        return info[0].decode('utf-8')
 
1421
        return decode_git_path(info[0])
1345
1422
 
1346
1423
    def set_reference_info(self, tree_path, branch_location):
1347
1424
        path = self.abspath('.gitmodules')
1352
1429
                config = GitConfigFile()
1353
1430
            else:
1354
1431
                raise
1355
 
        section = (b'submodule', tree_path.encode('utf-8'))
 
1432
        section = (b'submodule', encode_git_path(tree_path))
1356
1433
        if branch_location is None:
1357
1434
            try:
1358
1435
                del config[section]
1364
1441
                branch_location)
1365
1442
            config.set(
1366
1443
                section,
1367
 
                b'path', tree_path.encode('utf-8'))
 
1444
                b'path', encode_git_path(tree_path))
1368
1445
            config.set(
1369
1446
                section,
1370
1447
                b'url', branch_location.encode('utf-8'))
1371
1448
        config.write_to_path(path)
1372
1449
        self.add('.gitmodules')
1373
1450
 
 
1451
    _marker = object()
 
1452
 
 
1453
    def update(self, change_reporter=None, possible_transports=None,
 
1454
               revision=None, old_tip=_marker, show_base=False):
 
1455
        """Update a working tree along its branch.
 
1456
 
 
1457
        This will update the branch if its bound too, which means we have
 
1458
        multiple trees involved:
 
1459
 
 
1460
        - The new basis tree of the master.
 
1461
        - The old basis tree of the branch.
 
1462
        - The old basis tree of the working tree.
 
1463
        - The current working tree state.
 
1464
 
 
1465
        Pathologically, all three may be different, and non-ancestors of each
 
1466
        other.  Conceptually we want to:
 
1467
 
 
1468
        - Preserve the wt.basis->wt.state changes
 
1469
        - Transform the wt.basis to the new master basis.
 
1470
        - Apply a merge of the old branch basis to get any 'local' changes from
 
1471
          it into the tree.
 
1472
        - Restore the wt.basis->wt.state changes.
 
1473
 
 
1474
        There isn't a single operation at the moment to do that, so we:
 
1475
 
 
1476
        - Merge current state -> basis tree of the master w.r.t. the old tree
 
1477
          basis.
 
1478
        - Do a 'normal' merge of the old branch basis if it is relevant.
 
1479
 
 
1480
        :param revision: The target revision to update to. Must be in the
 
1481
            revision history.
 
1482
        :param old_tip: If branch.update() has already been run, the value it
 
1483
            returned (old tip of the branch or None). _marker is used
 
1484
            otherwise.
 
1485
        """
 
1486
        if self.branch.get_bound_location() is not None:
 
1487
            self.lock_write()
 
1488
            update_branch = (old_tip is self._marker)
 
1489
        else:
 
1490
            self.lock_tree_write()
 
1491
            update_branch = False
 
1492
        try:
 
1493
            if update_branch:
 
1494
                old_tip = self.branch.update(possible_transports)
 
1495
            else:
 
1496
                if old_tip is self._marker:
 
1497
                    old_tip = None
 
1498
            return self._update_tree(old_tip, change_reporter, revision, show_base)
 
1499
        finally:
 
1500
            self.unlock()
 
1501
 
 
1502
    def _update_tree(self, old_tip=None, change_reporter=None, revision=None,
 
1503
                     show_base=False):
 
1504
        """Update a tree to the master branch.
 
1505
 
 
1506
        :param old_tip: if supplied, the previous tip revision the branch,
 
1507
            before it was changed to the master branch's tip.
 
1508
        """
 
1509
        # here if old_tip is not None, it is the old tip of the branch before
 
1510
        # it was updated from the master branch. This should become a pending
 
1511
        # merge in the working tree to preserve the user existing work.  we
 
1512
        # cant set that until we update the working trees last revision to be
 
1513
        # one from the new branch, because it will just get absorbed by the
 
1514
        # parent de-duplication logic.
 
1515
        #
 
1516
        # We MUST save it even if an error occurs, because otherwise the users
 
1517
        # local work is unreferenced and will appear to have been lost.
 
1518
        #
 
1519
        with self.lock_tree_write():
 
1520
            from .. import merge
 
1521
            nb_conflicts = 0
 
1522
            try:
 
1523
                last_rev = self.get_parent_ids()[0]
 
1524
            except IndexError:
 
1525
                last_rev = _mod_revision.NULL_REVISION
 
1526
            if revision is None:
 
1527
                revision = self.branch.last_revision()
 
1528
 
 
1529
            old_tip = old_tip or _mod_revision.NULL_REVISION
 
1530
 
 
1531
            if not _mod_revision.is_null(old_tip) and old_tip != last_rev:
 
1532
                # the branch we are bound to was updated
 
1533
                # merge those changes in first
 
1534
                base_tree = self.basis_tree()
 
1535
                other_tree = self.branch.repository.revision_tree(old_tip)
 
1536
                nb_conflicts = merge.merge_inner(self.branch, other_tree,
 
1537
                                                 base_tree, this_tree=self,
 
1538
                                                 change_reporter=change_reporter,
 
1539
                                                 show_base=show_base)
 
1540
                if nb_conflicts:
 
1541
                    self.add_parent_tree((old_tip, other_tree))
 
1542
                    return nb_conflicts
 
1543
 
 
1544
            if last_rev != _mod_revision.ensure_null(revision):
 
1545
                to_tree = self.branch.repository.revision_tree(revision)
 
1546
 
 
1547
                # determine the branch point
 
1548
                graph = self.branch.repository.get_graph()
 
1549
                base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
 
1550
                                                    last_rev)
 
1551
                base_tree = self.branch.repository.revision_tree(base_rev_id)
 
1552
 
 
1553
                nb_conflicts = merge.merge_inner(self.branch, to_tree, base_tree,
 
1554
                                                 this_tree=self,
 
1555
                                                 change_reporter=change_reporter,
 
1556
                                                 show_base=show_base)
 
1557
                self.set_last_revision(revision)
 
1558
                # TODO - dedup parents list with things merged by pull ?
 
1559
                # reuse the tree we've updated to to set the basis:
 
1560
                parent_trees = [(revision, to_tree)]
 
1561
                merges = self.get_parent_ids()[1:]
 
1562
                # Ideally we ask the tree for the trees here, that way the working
 
1563
                # tree can decide whether to give us the entire tree or give us a
 
1564
                # lazy initialised tree. dirstate for instance will have the trees
 
1565
                # in ram already, whereas a last-revision + basis-inventory tree
 
1566
                # will not, but also does not need them when setting parents.
 
1567
                for parent in merges:
 
1568
                    parent_trees.append(
 
1569
                        (parent, self.branch.repository.revision_tree(parent)))
 
1570
                if not _mod_revision.is_null(old_tip):
 
1571
                    parent_trees.append(
 
1572
                        (old_tip, self.branch.repository.revision_tree(old_tip)))
 
1573
                self.set_parent_trees(parent_trees)
 
1574
                last_rev = parent_trees[0][0]
 
1575
            return nb_conflicts
 
1576
 
1374
1577
 
1375
1578
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
1376
1579