~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Aaron Bentley
  • Date: 2006-08-02 13:02:47 UTC
  • mfrom: (1907 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1934.
  • Revision ID: abentley@panoramicfeedback.com-20060802130247-bf2502d680abed61
Rough merge of bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
 
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
51
51
from time import time
52
52
import warnings
53
53
 
54
 
from bzrlib import bzrdir, errors, osutils, urlutils
 
54
import bzrlib
 
55
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
55
56
from bzrlib.atomicfile import AtomicFile
 
57
import bzrlib.branch
56
58
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
59
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
60
from bzrlib.errors import (BzrCheckError,
95
97
        DEPRECATED_PARAMETER,
96
98
        zero_eight,
97
99
        )
98
 
 
99
 
from bzrlib.textui import show_status
100
 
import bzrlib.tree
 
100
from bzrlib.trace import mutter, note
101
101
from bzrlib.transform import build_tree
102
 
from bzrlib.trace import mutter, note
103
102
from bzrlib.transport import get_transport
104
103
from bzrlib.transport.local import LocalTransport
 
104
from bzrlib.textui import show_status
 
105
import bzrlib.tree
105
106
import bzrlib.ui
106
107
import bzrlib.xml5
107
108
 
108
109
 
109
 
# the regex here does the following:
110
 
# 1) remove any weird characters; we don't escape them but rather
111
 
# just pull them out
112
 
 # 2) match leading '.'s to make it not hidden
113
 
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
 
110
# the regex removes any weird characters; we don't escape them 
 
111
# but rather just pull them out
 
112
_gen_file_id_re = re.compile(r'[^\w.]')
114
113
_gen_id_suffix = None
115
114
_gen_id_serial = 0
116
115
 
138
137
 
139
138
    The uniqueness is supplied from _next_id_suffix.
140
139
    """
141
 
    # XXX TODO: squash the filename to lowercase.
142
 
    # XXX TODO: truncate the filename to something like 20 or 30 chars.
143
 
    # XXX TODO: consider what to do with ids that look like illegal filepaths
144
 
    # on platforms we support.
145
 
    return _gen_file_id_re.sub('', name) + _next_id_suffix()
 
140
    # The real randomness is in the _next_id_suffix, the
 
141
    # rest of the identifier is just to be nice.
 
142
    # So we:
 
143
    # 1) Remove non-ascii word characters to keep the ids portable
 
144
    # 2) squash to lowercase, so the file id doesn't have to
 
145
    #    be escaped (case insensitive filesystems would bork for ids
 
146
    #    that only differred in case without escaping).
 
147
    # 3) truncate the filename to 20 chars. Long filenames also bork on some
 
148
    #    filesystems
 
149
    # 4) Removing starting '.' characters to prevent the file ids from
 
150
    #    being considered hidden.
 
151
    ascii_word_only = _gen_file_id_re.sub('', name.lower())
 
152
    short_no_dots = ascii_word_only.lstrip('.')[:20]
 
153
    return short_no_dots + _next_id_suffix()
146
154
 
147
155
 
148
156
def gen_root_id():
268
276
            # share control object
269
277
            self._control_files = self.branch.control_files
270
278
        else:
271
 
            # only ready for format 3
272
 
            assert isinstance(self._format, WorkingTreeFormat3)
 
279
            # assume all other formats have their own control files.
273
280
            assert isinstance(_control_files, LockableFiles), \
274
281
                    "_control_files must be a LockableFiles, not %r" \
275
282
                    % _control_files
375
382
        """
376
383
        inv = self._inventory
377
384
        for path, ie in inv.iter_entries():
378
 
            if bzrlib.osutils.lexists(self.abspath(path)):
 
385
            if osutils.lexists(self.abspath(path)):
379
386
                yield ie.file_id
380
387
 
381
388
    def __repr__(self):
447
454
        return relpath(self.basedir, path)
448
455
 
449
456
    def has_filename(self, filename):
450
 
        return bzrlib.osutils.lexists(self.abspath(filename))
 
457
        return osutils.lexists(self.abspath(filename))
451
458
 
452
459
    def get_file(self, file_id):
453
460
        return self.get_file_byname(self.id2path(file_id))
454
461
 
 
462
    def get_file_text(self, file_id):
 
463
        return self.get_file(file_id).read()
 
464
 
455
465
    def get_file_byname(self, filename):
456
466
        return file(self.abspath(filename), 'rb')
457
467
 
537
547
        if not inv.has_id(file_id):
538
548
            return False
539
549
        path = inv.id2path(file_id)
540
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
550
        return osutils.lexists(self.abspath(path))
541
551
 
542
552
    def has_or_had_id(self, file_id):
543
553
        if file_id == self.inventory.root.file_id:
721
731
        """
722
732
        inv = self._inventory
723
733
        # Convert these into local objects to save lookup times
724
 
        pathjoin = bzrlib.osutils.pathjoin
725
 
        file_kind = bzrlib.osutils.file_kind
 
734
        pathjoin = osutils.pathjoin
 
735
        file_kind = osutils.file_kind
726
736
 
727
737
        # transport.base ends in a slash, we want the piece
728
738
        # between the last two slashes
1008
1018
        """
1009
1019
        ## TODO: Work from given directory downwards
1010
1020
        for path, dir_entry in self.inventory.directories():
1011
 
            mutter("search for unknowns in %r", path)
 
1021
            # mutter("search for unknowns in %r", path)
1012
1022
            dirabs = self.abspath(path)
1013
1023
            if not isdir(dirabs):
1014
1024
                # e.g. directory deleted
1098
1108
 
1099
1109
        Cached in the Tree object after the first call.
1100
1110
        """
1101
 
        if hasattr(self, '_ignorelist'):
1102
 
            return self._ignorelist
1103
 
 
1104
 
        l = []
 
1111
        ignoreset = getattr(self, '_ignoreset', None)
 
1112
        if ignoreset is not None:
 
1113
            return ignoreset
 
1114
 
 
1115
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
 
1116
        ignore_globs.update(ignores.get_runtime_ignores())
 
1117
 
 
1118
        ignore_globs.update(ignores.get_user_ignores())
 
1119
 
1105
1120
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1106
1121
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1107
 
            l.extend([line.rstrip("\n\r").decode('utf-8') 
1108
 
                      for line in f.readlines()])
1109
 
        self._ignorelist = l
1110
 
        self._ignore_regex = self._combine_ignore_rules(l)
1111
 
        return l
 
1122
            try:
 
1123
                ignore_globs.update(ignores.parse_ignore_file(f))
 
1124
            finally:
 
1125
                f.close()
 
1126
 
 
1127
        self._ignoreset = ignore_globs
 
1128
        self._ignore_regex = self._combine_ignore_rules(ignore_globs)
 
1129
        return ignore_globs
1112
1130
 
1113
1131
    def _get_ignore_rules_as_regex(self):
1114
1132
        """Return a regex of the ignore rules and a mapping dict.
1116
1134
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1117
1135
        indices to original rule.)
1118
1136
        """
1119
 
        if getattr(self, '_ignorelist', None) is None:
 
1137
        if getattr(self, '_ignoreset', None) is None:
1120
1138
            self.get_ignore_list()
1121
1139
        return self._ignore_regex
1122
1140
 
1362
1380
        between multiple working trees, i.e. via shared storage, then we 
1363
1381
        would probably want to lock both the local tree, and the branch.
1364
1382
        """
1365
 
        # FIXME: We want to write out the hashcache only when the last lock on
1366
 
        # this working copy is released.  Peeking at the lock count is a bit
1367
 
        # of a nasty hack; probably it's better to have a transaction object,
1368
 
        # which can do some finalization when it's either successfully or
1369
 
        # unsuccessfully completed.  (Denys's original patch did that.)
1370
 
        # RBC 20060206 hooking into transaction will couple lock and transaction
1371
 
        # wrongly. Hooking into unlock on the control files object is fine though.
1372
 
        
1373
 
        # TODO: split this per format so there is no ugly if block
1374
 
        if self._hashcache.needs_write and (
1375
 
            # dedicated lock files
1376
 
            self._control_files._lock_count==1 or 
1377
 
            # shared lock files
1378
 
            (self._control_files is self.branch.control_files and 
1379
 
             self._control_files._lock_count==3)):
1380
 
            self._hashcache.write()
1381
 
        # reverse order of locking.
1382
 
        try:
1383
 
            return self._control_files.unlock()
1384
 
        finally:
1385
 
            self.branch.unlock()
 
1383
        raise NotImplementedError(self.unlock)
1386
1384
 
1387
1385
    @needs_write_lock
1388
1386
    def update(self):
1483
1481
        return conflicts
1484
1482
 
1485
1483
 
 
1484
class WorkingTree2(WorkingTree):
 
1485
    """This is the Format 2 working tree.
 
1486
 
 
1487
    This was the first weave based working tree. 
 
1488
     - uses os locks for locking.
 
1489
     - uses the branch last-revision.
 
1490
    """
 
1491
 
 
1492
    def unlock(self):
 
1493
        # we share control files:
 
1494
        if self._hashcache.needs_write and self._control_files._lock_count==3:
 
1495
            self._hashcache.write()
 
1496
        # reverse order of locking.
 
1497
        try:
 
1498
            return self._control_files.unlock()
 
1499
        finally:
 
1500
            self.branch.unlock()
 
1501
 
 
1502
 
1486
1503
class WorkingTree3(WorkingTree):
1487
1504
    """This is the Format 3 working tree.
1488
1505
 
1542
1559
            raise ConflictFormatError()
1543
1560
        return ConflictList.from_stanzas(RioReader(confile))
1544
1561
 
 
1562
    def unlock(self):
 
1563
        if self._hashcache.needs_write and self._control_files._lock_count==1:
 
1564
            self._hashcache.write()
 
1565
        # reverse order of locking.
 
1566
        try:
 
1567
            return self._control_files.unlock()
 
1568
        finally:
 
1569
            self.branch.unlock()
 
1570
 
1545
1571
 
1546
1572
def get_conflicted_stem(path):
1547
1573
    for suffix in CONFLICT_SUFFIXES:
1681
1707
                branch.unlock()
1682
1708
        revision = branch.last_revision()
1683
1709
        inv = Inventory() 
1684
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1710
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1685
1711
                         branch,
1686
1712
                         inv,
1687
1713
                         _internal=True,
1709
1735
            raise NotImplementedError
1710
1736
        if not isinstance(a_bzrdir.transport, LocalTransport):
1711
1737
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1712
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1738
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1713
1739
                           _internal=True,
1714
1740
                           _format=self,
1715
1741
                           _bzrdir=a_bzrdir)
1724
1750
          files, separate from the BzrDir format
1725
1751
        - modifies the hash cache format
1726
1752
        - is new in bzr 0.8
1727
 
        - uses a LockDir to guard access to the repository
 
1753
        - uses a LockDir to guard access for writes.
1728
1754
    """
1729
1755
 
1730
1756
    def get_format_string(self):
1794
1820
            raise NotImplementedError
1795
1821
        if not isinstance(a_bzrdir.transport, LocalTransport):
1796
1822
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1797
 
        control_files = self._open_control_files(a_bzrdir)
 
1823
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1824
 
 
1825
    def _open(self, a_bzrdir, control_files):
 
1826
        """Open the tree itself.
 
1827
        
 
1828
        :param a_bzrdir: the dir for the tree.
 
1829
        :param control_files: the control files for the tree.
 
1830
        """
1798
1831
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1799
1832
                           _internal=True,
1800
1833
                           _format=self,
1828
1861
        self._transport_readonly_server = transport_readonly_server
1829
1862
        self._formats = formats
1830
1863
    
 
1864
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
1865
        """Clone test for adaption."""
 
1866
        new_test = deepcopy(test)
 
1867
        new_test.transport_server = self._transport_server
 
1868
        new_test.transport_readonly_server = self._transport_readonly_server
 
1869
        new_test.bzrdir_format = bzrdir_format
 
1870
        new_test.workingtree_format = workingtree_format
 
1871
        def make_new_test_id():
 
1872
            new_id = "%s(%s)" % (test.id(), variation)
 
1873
            return lambda: new_id
 
1874
        new_test.id = make_new_test_id()
 
1875
        return new_test
 
1876
    
1831
1877
    def adapt(self, test):
1832
1878
        from bzrlib.tests import TestSuite
1833
1879
        result = TestSuite()
1834
1880
        for workingtree_format, bzrdir_format in self._formats:
1835
 
            new_test = deepcopy(test)
1836
 
            new_test.transport_server = self._transport_server
1837
 
            new_test.transport_readonly_server = self._transport_readonly_server
1838
 
            new_test.bzrdir_format = bzrdir_format
1839
 
            new_test.workingtree_format = workingtree_format
1840
 
            def make_new_test_id():
1841
 
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1842
 
                return lambda: new_id
1843
 
            new_test.id = make_new_test_id()
 
1881
            new_test = self._clone_test(
 
1882
                test,
 
1883
                bzrdir_format,
 
1884
                workingtree_format, workingtree_format.__class__.__name__)
1844
1885
            result.addTest(new_test)
1845
1886
        return result