/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/transform.py

  • Committer: Jelmer Vernooij
  • Date: 2020-08-08 19:05:29 UTC
  • mto: (7490.40.103 work)
  • mto: This revision was merged to the branch mainline in revision 7521.
  • Revision ID: jelmer@jelmer.uk-20200808190529-o5m9889e6ojn8a06
More improvements to the Git transform implementation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
import errno
21
21
import os
22
 
from stat import S_ISREG
 
22
from stat import S_IEXEC, S_ISREG
23
23
import time
24
24
 
25
 
from .. import errors, multiparent, osutils, trace, ui, urlutils
 
25
from .tree import MutableGitIndexTree
 
26
 
 
27
from .. import (
 
28
    annotate,
 
29
    errors,
 
30
    multiparent,
 
31
    osutils,
 
32
    revision as _mod_revision,
 
33
    trace,
 
34
    ui,
 
35
    urlutils,
 
36
    )
26
37
from ..i18n import gettext
27
38
from ..mutabletree import MutableTree
28
 
from ..sixish import viewitems, viewvalues
 
39
from ..tree import InterTree
 
40
from ..sixish import text_type, viewitems, viewvalues
29
41
from ..transform import (
30
42
    PreviewTree,
31
43
    TreeTransform,
64
76
        self.root = self.trans_id_tree_path('')
65
77
        # Whether the target is case sensitive
66
78
        self._case_sensitive_target = case_sensitive
 
79
        self._symlink_target = {}
67
80
 
68
81
    @property
69
82
    def mapping(self):
153
166
 
154
167
    def version_file(self, trans_id, file_id=None):
155
168
        """Schedule a file to become versioned."""
 
169
        if trans_id in self._versioned:
 
170
            raise errors.DuplicateKey(key=trans_id)
156
171
        self._versioned.add(trans_id)
157
172
 
158
173
    def cancel_versioning(self, trans_id):
191
206
        return self._tree.path2id(path)
192
207
 
193
208
    def final_is_versioned(self, trans_id):
194
 
        return self._final_file_id(trans_id) is not None
 
209
        if trans_id in self._removed_id:
 
210
            return False
 
211
        if trans_id in self._versioned:
 
212
            return True
 
213
        orig_path = self.tree_path(trans_id)
 
214
        if orig_path is None:
 
215
            return False
 
216
        return self._tree.is_versioned(orig_path)
195
217
 
196
218
    def _final_file_id(self, trans_id):
197
219
        """Determine the file id after any changes are applied, or None.
217
239
        # all children of non-existent parents are known, by definition.
218
240
        self._add_tree_children()
219
241
        by_parent = self.by_parent()
220
 
        conflicts.extend(self._unversioned_parents(by_parent))
221
242
        conflicts.extend(self._parent_loops())
222
243
        conflicts.extend(self._duplicate_entries(by_parent))
223
244
        conflicts.extend(self._parent_type_conflicts(by_parent))
313
334
                    break
314
335
        return conflicts
315
336
 
316
 
    def _unversioned_parents(self, by_parent):
317
 
        """If parent directories are versioned, children must be versioned."""
318
 
        conflicts = []
319
 
        for parent_id, children in viewitems(by_parent):
320
 
            if parent_id == ROOT_PARENT:
321
 
                continue
322
 
            if self.final_is_versioned(parent_id):
323
 
                continue
324
 
            for child_id in children:
325
 
                if self.final_is_versioned(child_id):
326
 
                    conflicts.append(('unversioned parent', parent_id))
327
 
                    break
328
 
        return conflicts
329
 
 
330
337
    def _improper_versioning(self):
331
338
        """Cannot version a file with no contents, or a bad type.
332
339
 
1016
1023
                path = None
1017
1024
            trace.warning(
1018
1025
                'Unable to create symlink "%s" on this filesystem.' % (path,))
 
1026
            self._symlink_target[trans_id] = target
1019
1027
        # We add symlink to _new_contents even if they are unsupported
1020
1028
        # and not created. These entries are subsequently used to avoid
1021
1029
        # conflicts on platforms that don't support symlink
1431
1439
                    child_pb.update(gettext('removing file'),
1432
1440
                                    num, total_entries)
1433
1441
                path = self._tree_id_paths[trans_id]
1434
 
                changes.append((path, None, None, None, None))
 
1442
                changes.append((path, None, None, None, None, None))
1435
1443
            for num, (path, trans_id) in enumerate(new_paths):
1436
1444
                if (num % 10) == 0:
1437
1445
                    child_pb.update(gettext('adding file'),
1442
1450
                    kind = self._tree.stored_kind(self.tree_path(trans_id))
1443
1451
                executability = self._new_executability.get(trans_id)
1444
1452
                reference_revision = self._new_reference_revision.get(trans_id)
 
1453
                symlink_target = self._symlink_target.get(trans_id)
1445
1454
                changes.append(
1446
 
                    (None, path, kind, executability, reference_revision))
 
1455
                    (None, path, kind, executability, reference_revision,
 
1456
                     symlink_target))
1447
1457
        return changes
1448
1458
 
1449
1459
 
1464
1474
        return path
1465
1475
 
1466
1476
    def tree_kind(self, trans_id):
1467
 
        path = self._tree_id_paths.get(trans_id)
 
1477
        path = self.tree_path(trans_id)
1468
1478
        if path is None:
1469
1479
            return None
1470
1480
        kind = self._tree.path_content_summary(path)[0]
1488
1498
        except KeyError:
1489
1499
            return
1490
1500
        try:
1491
 
            entry = next(self._tree.iter_entries_by_dir(
1492
 
                specific_files=[path]))[1]
1493
 
        except StopIteration:
 
1501
            for child in self._tree.iter_child_entries(path):
 
1502
                childpath = joinpath(path, child.name)
 
1503
                yield self.trans_id_tree_path(childpath)
 
1504
        except errors.NoSuchFile:
1494
1505
            return
1495
 
        children = getattr(entry, 'children', {})
1496
 
        for child in children:
1497
 
            childpath = joinpath(path, child)
1498
 
            yield self.trans_id_tree_path(childpath)
1499
1506
 
1500
1507
    def new_orphan(self, trans_id, parent_id):
1501
1508
        raise NotImplementedError(self.new_orphan)
1502
1509
 
1503
1510
 
1504
 
class GitPreviewTree(PreviewTree):
 
1511
class GitPreviewTree(PreviewTree, MutableGitIndexTree):
1505
1512
    """Partial implementation of Tree to support show_diff_trees"""
1506
1513
 
1507
1514
    def __init__(self, transform):
1508
1515
        PreviewTree.__init__(self, transform)
 
1516
        MutableGitIndexTree.__init__(self)
1509
1517
        self._final_paths = FinalPaths(transform)
 
1518
        self.store = transform._tree.store
 
1519
 
 
1520
    def _supports_executable(self):
 
1521
        return self._transform._limbo_supports_executable()
1510
1522
 
1511
1523
    def walkdirs(self, prefix=''):
1512
1524
        pending = [self._transform.root]
1535
1547
                yield parent_path, children
1536
1548
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
1537
1549
                                  reverse=True))
 
1550
 
 
1551
    def iter_changes(self, from_tree, include_unchanged=False,
 
1552
                     specific_files=None, pb=None, extra_trees=None,
 
1553
                     require_versioned=True, want_unversioned=False):
 
1554
        """See InterTree.iter_changes.
 
1555
 
 
1556
        This has a fast path that is only used when the from_tree matches
 
1557
        the transform tree, and no fancy options are supplied.
 
1558
        """
 
1559
        return InterTree.get(from_tree, self).iter_changes(
 
1560
            include_unchanged=include_unchanged,
 
1561
            specific_files=specific_files,
 
1562
            pb=pb,
 
1563
            extra_trees=extra_trees,
 
1564
            require_versioned=require_versioned,
 
1565
            want_unversioned=want_unversioned)
 
1566
 
 
1567
    def get_file(self, path):
 
1568
        """See Tree.get_file"""
 
1569
        trans_id = self._path2trans_id(path)
 
1570
        if trans_id is None:
 
1571
            raise errors.NoSuchFile(path)
 
1572
        if trans_id in self._transform._removed_contents:
 
1573
            raise errors.NoSuchFile(path)
 
1574
        if trans_id not in self._transform._new_contents:
 
1575
            orig_path = self._transform.tree_path(trans_id)
 
1576
            return self._transform._tree.get_file(orig_path)
 
1577
        name = self._transform._limbo_name(trans_id)
 
1578
        return open(name, 'rb')
 
1579
 
 
1580
    def get_symlink_target(self, path):
 
1581
        """See Tree.get_symlink_target"""
 
1582
        trans_id = self._path2trans_id(path)
 
1583
        if trans_id is None:
 
1584
            raise errors.NoSuchFile(path)
 
1585
        if trans_id not in self._transform._new_contents:
 
1586
            orig_path = self._transform.tree_path(trans_id)
 
1587
            return self._transform._tree.get_symlink_target(orig_path)
 
1588
        name = self._transform._limbo_name(trans_id)
 
1589
        return osutils.readlink(name)
 
1590
 
 
1591
    def annotate_iter(self, path, default_revision=_mod_revision.CURRENT_REVISION):
 
1592
        trans_id = self._path2trans_id(path)
 
1593
        if trans_id is None:
 
1594
            raise errors.NoSuchFile(path)
 
1595
        orig_path = self._transform.tree_path(trans_id)
 
1596
        if orig_path is not None:
 
1597
            old_annotation = self._transform._tree.annotate_iter(
 
1598
                orig_path, default_revision=default_revision)
 
1599
        else:
 
1600
            old_annotation = []
 
1601
        try:
 
1602
            lines = self.get_file_lines(path)
 
1603
        except errors.NoSuchFile:
 
1604
            return None
 
1605
        return annotate.reannotate([old_annotation], lines, default_revision)
 
1606
 
 
1607
    def get_file_text(self, path):
 
1608
        """Return the byte content of a file.
 
1609
 
 
1610
        :param path: The path of the file.
 
1611
 
 
1612
        :returns: A single byte string for the whole file.
 
1613
        """
 
1614
        with self.get_file(path) as my_file:
 
1615
            return my_file.read()
 
1616
 
 
1617
    def get_file_lines(self, path):
 
1618
        """Return the content of a file, as lines.
 
1619
 
 
1620
        :param path: The path of the file.
 
1621
        """
 
1622
        return osutils.split_lines(self.get_file_text(path))
 
1623
 
 
1624
    def extras(self):
 
1625
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
 
1626
                              in self._transform._tree.extras())
 
1627
        possible_extras.update(self._transform._new_contents)
 
1628
        possible_extras.update(self._transform._removed_id)
 
1629
        for trans_id in possible_extras:
 
1630
            if not self._transform.final_is_versioned(trans_id):
 
1631
                yield self._final_paths._determine_path(trans_id)
 
1632
 
 
1633
    def path_content_summary(self, path):
 
1634
        trans_id = self._path2trans_id(path)
 
1635
        tt = self._transform
 
1636
        tree_path = tt.tree_path(trans_id)
 
1637
        kind = tt._new_contents.get(trans_id)
 
1638
        if kind is None:
 
1639
            if tree_path is None or trans_id in tt._removed_contents:
 
1640
                return 'missing', None, None, None
 
1641
            summary = tt._tree.path_content_summary(tree_path)
 
1642
            kind, size, executable, link_or_sha1 = summary
 
1643
        else:
 
1644
            link_or_sha1 = None
 
1645
            limbo_name = tt._limbo_name(trans_id)
 
1646
            if trans_id in tt._new_reference_revision:
 
1647
                kind = 'tree-reference'
 
1648
            if kind == 'file':
 
1649
                statval = os.lstat(limbo_name)
 
1650
                size = statval.st_size
 
1651
                if not tt._limbo_supports_executable():
 
1652
                    executable = False
 
1653
                else:
 
1654
                    executable = statval.st_mode & S_IEXEC
 
1655
            else:
 
1656
                size = None
 
1657
                executable = None
 
1658
            if kind == 'symlink':
 
1659
                link_or_sha1 = os.readlink(limbo_name)
 
1660
                if not isinstance(link_or_sha1, text_type):
 
1661
                    link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
 
1662
        executable = tt._new_executability.get(trans_id, executable)
 
1663
        return kind, size, executable, link_or_sha1
 
1664
 
 
1665
    def get_file_mtime(self, path):
 
1666
        """See Tree.get_file_mtime"""
 
1667
        trans_id = self._path2trans_id(path)
 
1668
        if trans_id is None:
 
1669
            raise errors.NoSuchFile(path)
 
1670
        if trans_id not in self._transform._new_contents:
 
1671
            return self._transform._tree.get_file_mtime(
 
1672
                self._transform.tree_path(trans_id))
 
1673
        name = self._transform._limbo_name(trans_id)
 
1674
        statval = os.lstat(name)
 
1675
        return statval.st_mtime
 
1676
 
 
1677
    def is_versioned(self, path):
 
1678
        trans_id = self._path2trans_id(path)
 
1679
        if trans_id is None:
 
1680
            # It doesn't exist, so it's not versioned.
 
1681
            return False
 
1682
        if trans_id in self._transform._versioned:
 
1683
            return True
 
1684
        orig_path = self._transform.tree_path(trans_id)
 
1685
        return self._transform._tree.is_versioned(orig_path)
 
1686
 
 
1687
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
 
1688
        if recurse_nested:
 
1689
            raise NotImplementedError(
 
1690
                'follow tree references not yet supported')
 
1691
 
 
1692
        # This may not be a maximally efficient implementation, but it is
 
1693
        # reasonably straightforward.  An implementation that grafts the
 
1694
        # TreeTransform changes onto the tree's iter_entries_by_dir results
 
1695
        # might be more efficient, but requires tricky inferences about stack
 
1696
        # position.
 
1697
        ordered_ids = self._list_files_by_dir()
 
1698
        for trans_id in ordered_ids:
 
1699
            entry = None
 
1700
            yield self._final_paths.get_path(trans_id), entry
 
1701
 
 
1702
    def _list_files_by_dir(self):
 
1703
        todo = [ROOT_PARENT]
 
1704
        ordered_ids = []
 
1705
        while len(todo) > 0:
 
1706
            parent = todo.pop()
 
1707
            children = list(self._all_children(parent))
 
1708
            paths = dict(zip(children, self._final_paths.get_paths(children)))
 
1709
            children.sort(key=paths.get)
 
1710
            todo.extend(reversed(children))
 
1711
            for trans_id in children:
 
1712
                ordered_ids.append(trans_id)
 
1713
        return ordered_ids
 
1714
 
 
1715
    def revision_tree(self, revision_id):
 
1716
        return self._transform._tree.revision_tree(revision_id)
 
1717
 
 
1718
    def _stat_limbo_file(self, trans_id):
 
1719
        name = self._transform._limbo_name(trans_id)
 
1720
        return os.lstat(name)