/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: Jelmer Vernooij
  • Date: 2020-07-05 12:50:01 UTC
  • mfrom: (7490.40.46 work)
  • mto: (7490.40.48 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200705125001-7s3vo0p55szbbws7
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
"""An adapter between a Git index and a Bazaar Working Tree"""
19
19
 
 
20
from __future__ import absolute_import
 
21
 
20
22
import itertools
21
23
from collections import defaultdict
22
24
import errno
44
46
    )
45
47
import os
46
48
import posixpath
47
 
import re
48
49
import stat
49
50
import sys
50
51
 
70
71
    BadReferenceTarget,
71
72
    MutableTree,
72
73
    )
 
74
from ..sixish import text_type
73
75
 
74
76
 
75
77
from .dir import (
85
87
    )
86
88
 
87
89
 
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
 
 
183
90
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
184
91
    """A Git working tree."""
185
92
 
426
333
        def recurse_directory_to_add_files(directory):
427
334
            # Recurse directory and add all files
428
335
            # so we can check if they have changed.
429
 
            for parent_path, file_infos in self.walkdirs(directory):
430
 
                for relpath, basename, kind, lstat, kind in file_infos:
 
336
            for parent_info, file_infos in self.walkdirs(directory):
 
337
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
431
338
                    # Is it versioned or ignored?
432
339
                    if self.is_versioned(relpath):
433
340
                        # Add nested content for deletion.
623
530
                              recurse_nested=False):
624
531
        if from_dir is None:
625
532
            from_dir = u""
626
 
        if not isinstance(from_dir, str):
 
533
        if not isinstance(from_dir, text_type):
627
534
            raise TypeError(from_dir)
628
535
        encoded_from_dir = self.abspath(from_dir).encode(osutils._fs_enc)
629
536
        for (dirpath, dirnames, filenames) in os.walk(encoded_from_dir):
665
572
        """
666
573
        with self.lock_read():
667
574
            index_paths = set(
668
 
                [decode_git_path(p) for p, sha, mode in self.iter_git_objects()])
 
575
                [decode_git_path(p) for p, i in self._recurse_index_entries()])
669
576
            all_paths = set(self._iter_files_recursive(include_dirs=False))
670
577
            return iter(all_paths - index_paths)
671
578
 
963
870
            conflicts = _mod_conflicts.ConflictList()
964
871
            for item_path, value in self.index.iteritems():
965
872
                if value.flags & FLAG_STAGEMASK:
966
 
                    conflicts.append(TextConflict(decode_git_path(item_path)))
 
873
                    conflicts.append(_mod_conflicts.TextConflict(
 
874
                        decode_git_path(item_path)))
967
875
            return conflicts
968
876
 
969
877
    def set_conflicts(self, conflicts):
978
886
                self._set_conflicted(path, path in by_path)
979
887
 
980
888
    def _set_conflicted(self, path, conflicted):
 
889
        trace.mutter('change conflict: %r -> %r', path, conflicted)
981
890
        value = self.index[path]
982
891
        self._index_dirty = True
983
892
        if conflicted:
1003
912
        """Walk the directories of this tree.
1004
913
 
1005
914
        returns a generator which yields items in the form:
1006
 
                (current_directory_path,
1007
 
                 [(file1_path, file1_name, file1_kind, (lstat),
 
915
                ((curren_directory_path, fileid),
 
916
                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
1008
917
                   file1_kind), ... ])
1009
918
 
1010
919
        This API returns a generator, which is only valid during the current
1068
977
                             - (current_inv[0][0] < cur_disk_dir_relpath))
1069
978
            if direction > 0:
1070
979
                # disk is before inventory - unknown
1071
 
                dirblock = [(relpath, basename, kind, stat, None) for
 
980
                dirblock = [(relpath, basename, kind, stat, None, None) for
1072
981
                            relpath, basename, kind, stat, top_path in
1073
982
                            cur_disk_dir_content]
1074
 
                yield cur_disk_dir_relpath, dirblock
 
983
                yield (cur_disk_dir_relpath, None), dirblock
1075
984
                try:
1076
985
                    current_disk = next(disk_iterator)
1077
986
                except StopIteration:
1078
987
                    disk_finished = True
1079
988
            elif direction < 0:
1080
989
                # inventory is before disk - missing.
1081
 
                dirblock = [(relpath, basename, 'unknown', None, kind)
 
990
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1082
991
                            for relpath, basename, dkind, stat, fileid, kind in
1083
992
                            current_inv[1]]
1084
 
                yield current_inv[0][0], dirblock
 
993
                yield (current_inv[0][0], current_inv[0][1]), dirblock
1085
994
                try:
1086
995
                    current_inv = next(inventory_iterator)
1087
996
                except StopIteration:
1099
1008
                        # versioned, present file
1100
1009
                        dirblock.append((inv_row[0],
1101
1010
                                         inv_row[1], disk_row[2],
1102
 
                                         disk_row[3], inv_row[5]))
 
1011
                                         disk_row[3], inv_row[4],
 
1012
                                         inv_row[5]))
1103
1013
                    elif len(path_elements[0]) == 5:
1104
1014
                        # unknown disk file
1105
1015
                        dirblock.append(
1106
1016
                            (path_elements[0][0], path_elements[0][1],
1107
1017
                                path_elements[0][2], path_elements[0][3],
1108
 
                                None))
 
1018
                                None, None))
1109
1019
                    elif len(path_elements[0]) == 6:
1110
1020
                        # versioned, absent file.
1111
1021
                        dirblock.append(
1112
1022
                            (path_elements[0][0], path_elements[0][1],
1113
 
                                'unknown', None,
 
1023
                                'unknown', None, path_elements[0][4],
1114
1024
                                path_elements[0][5]))
1115
1025
                    else:
1116
1026
                        raise NotImplementedError('unreachable code')
1117
 
                yield current_inv[0][0], dirblock
 
1027
                yield current_inv[0], dirblock
1118
1028
                try:
1119
1029
                    current_inv = next(inventory_iterator)
1120
1030
                except StopIteration:
1161
1071
    def store_uncommitted(self):
1162
1072
        raise errors.StoringUncommittedNotSupported(self)
1163
1073
 
 
1074
    def _apply_transform_delta(self, changes):
 
1075
        for (old_path, new_path, ie) in changes:
 
1076
            if old_path is not None:
 
1077
                (index, old_subpath) = self._lookup_index(
 
1078
                    encode_git_path(old_path))
 
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
 
1164
1094
    def annotate_iter(self, path,
1165
1095
                      default_revision=_mod_revision.CURRENT_REVISION):
1166
1096
        """See Tree.annotate_iter
1319
1249
    def get_reference_revision(self, path, branch=None):
1320
1250
        hexsha = self._read_submodule_head(path)
1321
1251
        if hexsha is None:
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
 
1252
            return _mod_revision.NULL_REVISION
1327
1253
        return self.branch.lookup_foreign_revision_id(hexsha)
1328
1254
 
1329
1255
    def get_nested_tree(self, path):
1448
1374
        config.write_to_path(path)
1449
1375
        self.add('.gitmodules')
1450
1376
 
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
 
 
1577
1377
 
1578
1378
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
1579
1379