~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: John Arbash Meinel
  • Date: 2006-08-16 22:00:19 UTC
  • mto: This revision was merged to the branch mainline in revision 1942.
  • Revision ID: john@arbash-meinel.com-20060816220019-541cb90093258ac3
Using real utf8 and cache_utf8 has similar performance, 272ms, and 363ms

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
315
322
        self.branch.break_lock()
316
323
 
317
324
    def _set_inventory(self, inv):
 
325
        assert inv.root is not None
318
326
        self._inventory = inv
319
327
        self.path2id = self._inventory.path2id
320
328
 
375
383
        """
376
384
        inv = self._inventory
377
385
        for path, ie in inv.iter_entries():
378
 
            if bzrlib.osutils.lexists(self.abspath(path)):
 
386
            if osutils.lexists(self.abspath(path)):
379
387
                yield ie.file_id
380
388
 
381
389
    def __repr__(self):
392
400
            try:
393
401
                xml = self.read_basis_inventory()
394
402
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
403
                inv.root.revision = revision_id
395
404
            except NoSuchFile:
396
405
                inv = None
397
406
            if inv is not None and inv.revision_id == revision_id:
447
456
        return relpath(self.basedir, path)
448
457
 
449
458
    def has_filename(self, filename):
450
 
        return bzrlib.osutils.lexists(self.abspath(filename))
 
459
        return osutils.lexists(self.abspath(filename))
451
460
 
452
461
    def get_file(self, file_id):
453
462
        return self.get_file_byname(self.id2path(file_id))
454
463
 
 
464
    def get_file_text(self, file_id):
 
465
        return self.get_file(file_id).read()
 
466
 
455
467
    def get_file_byname(self, filename):
456
468
        return file(self.abspath(filename), 'rb')
457
469
 
537
549
        if not inv.has_id(file_id):
538
550
            return False
539
551
        path = inv.id2path(file_id)
540
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
552
        return osutils.lexists(self.abspath(path))
541
553
 
542
554
    def has_or_had_id(self, file_id):
543
555
        if file_id == self.inventory.root.file_id:
721
733
        """
722
734
        inv = self._inventory
723
735
        # Convert these into local objects to save lookup times
724
 
        pathjoin = bzrlib.osutils.pathjoin
725
 
        file_kind = bzrlib.osutils.file_kind
 
736
        pathjoin = osutils.pathjoin
 
737
        file_kind = osutils.file_kind
726
738
 
727
739
        # transport.base ends in a slash, we want the piece
728
740
        # between the last two slashes
847
859
        if to_dir_id == None and to_name != '':
848
860
            raise BzrError("destination %r is not a versioned directory" % to_name)
849
861
        to_dir_ie = inv[to_dir_id]
850
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
862
        if to_dir_ie.kind != 'directory':
851
863
            raise BzrError("destination %r is not a directory" % to_abs)
852
864
 
853
865
        to_idpath = inv.get_idpath(to_dir_id)
1008
1020
        """
1009
1021
        ## TODO: Work from given directory downwards
1010
1022
        for path, dir_entry in self.inventory.directories():
1011
 
            mutter("search for unknowns in %r", path)
 
1023
            # mutter("search for unknowns in %r", path)
1012
1024
            dirabs = self.abspath(path)
1013
1025
            if not isdir(dirabs):
1014
1026
                # e.g. directory deleted
1098
1110
 
1099
1111
        Cached in the Tree object after the first call.
1100
1112
        """
1101
 
        if hasattr(self, '_ignorelist'):
1102
 
            return self._ignorelist
1103
 
 
1104
 
        l = []
 
1113
        ignoreset = getattr(self, '_ignoreset', None)
 
1114
        if ignoreset is not None:
 
1115
            return ignoreset
 
1116
 
 
1117
        ignore_globs = set(bzrlib.DEFAULT_IGNORE)
 
1118
        ignore_globs.update(ignores.get_runtime_ignores())
 
1119
 
 
1120
        ignore_globs.update(ignores.get_user_ignores())
 
1121
 
1105
1122
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1106
1123
            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
 
1124
            try:
 
1125
                ignore_globs.update(ignores.parse_ignore_file(f))
 
1126
            finally:
 
1127
                f.close()
 
1128
 
 
1129
        self._ignoreset = ignore_globs
 
1130
        self._ignore_regex = self._combine_ignore_rules(ignore_globs)
 
1131
        return ignore_globs
1112
1132
 
1113
1133
    def _get_ignore_rules_as_regex(self):
1114
1134
        """Return a regex of the ignore rules and a mapping dict.
1116
1136
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1117
1137
        indices to original rule.)
1118
1138
        """
1119
 
        if getattr(self, '_ignorelist', None) is None:
 
1139
        if getattr(self, '_ignoreset', None) is None:
1120
1140
            self.get_ignore_list()
1121
1141
        return self._ignore_regex
1122
1142
 
1206
1226
        if new_revision is None:
1207
1227
            self.branch.set_revision_history([])
1208
1228
            return False
1209
 
        # current format is locked in with the branch
1210
 
        revision_history = self.branch.revision_history()
1211
1229
        try:
1212
 
            position = revision_history.index(new_revision)
1213
 
        except ValueError:
1214
 
            raise errors.NoSuchRevision(self.branch, new_revision)
1215
 
        self.branch.set_revision_history(revision_history[:position + 1])
 
1230
            self.branch.generate_revision_history(new_revision)
 
1231
        except errors.NoSuchRevision:
 
1232
            # not present in the repo - dont try to set it deeper than the tip
 
1233
            self.branch.set_revision_history([new_revision])
1216
1234
        return True
1217
1235
 
1218
1236
    def _cache_basis_inventory(self, new_revision):
1241
1259
            path = self._basis_inventory_name()
1242
1260
            sio = StringIO(xml)
1243
1261
            self._control_files.put(path, sio)
1244
 
        except WeaveRevisionNotPresent:
 
1262
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1245
1263
            pass
1246
1264
 
1247
1265
    def read_basis_inventory(self):
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
 
1510
1527
                pass
1511
1528
            return False
1512
1529
        else:
1513
 
            try:
1514
 
                self.branch.revision_history().index(revision_id)
1515
 
            except ValueError:
1516
 
                raise errors.NoSuchRevision(self.branch, revision_id)
1517
1530
            self._control_files.put_utf8('last-revision', revision_id)
1518
1531
            return True
1519
1532
 
1542
1555
            raise ConflictFormatError()
1543
1556
        return ConflictList.from_stanzas(RioReader(confile))
1544
1557
 
 
1558
    def unlock(self):
 
1559
        if self._hashcache.needs_write and self._control_files._lock_count==1:
 
1560
            self._hashcache.write()
 
1561
        # reverse order of locking.
 
1562
        try:
 
1563
            return self._control_files.unlock()
 
1564
        finally:
 
1565
            self.branch.unlock()
 
1566
 
1545
1567
 
1546
1568
def get_conflicted_stem(path):
1547
1569
    for suffix in CONFLICT_SUFFIXES:
1681
1703
                branch.unlock()
1682
1704
        revision = branch.last_revision()
1683
1705
        inv = Inventory() 
1684
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1706
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1685
1707
                         branch,
1686
1708
                         inv,
1687
1709
                         _internal=True,
1709
1731
            raise NotImplementedError
1710
1732
        if not isinstance(a_bzrdir.transport, LocalTransport):
1711
1733
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1712
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1734
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1713
1735
                           _internal=True,
1714
1736
                           _format=self,
1715
1737
                           _bzrdir=a_bzrdir)
1724
1746
          files, separate from the BzrDir format
1725
1747
        - modifies the hash cache format
1726
1748
        - is new in bzr 0.8
1727
 
        - uses a LockDir to guard access to the repository
 
1749
        - uses a LockDir to guard access for writes.
1728
1750
    """
1729
1751
 
1730
1752
    def get_format_string(self):
1794
1816
            raise NotImplementedError
1795
1817
        if not isinstance(a_bzrdir.transport, LocalTransport):
1796
1818
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1797
 
        control_files = self._open_control_files(a_bzrdir)
 
1819
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1820
 
 
1821
    def _open(self, a_bzrdir, control_files):
 
1822
        """Open the tree itself.
 
1823
        
 
1824
        :param a_bzrdir: the dir for the tree.
 
1825
        :param control_files: the control files for the tree.
 
1826
        """
1798
1827
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1799
1828
                           _internal=True,
1800
1829
                           _format=self,
1828
1857
        self._transport_readonly_server = transport_readonly_server
1829
1858
        self._formats = formats
1830
1859
    
 
1860
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
1861
        """Clone test for adaption."""
 
1862
        new_test = deepcopy(test)
 
1863
        new_test.transport_server = self._transport_server
 
1864
        new_test.transport_readonly_server = self._transport_readonly_server
 
1865
        new_test.bzrdir_format = bzrdir_format
 
1866
        new_test.workingtree_format = workingtree_format
 
1867
        def make_new_test_id():
 
1868
            new_id = "%s(%s)" % (test.id(), variation)
 
1869
            return lambda: new_id
 
1870
        new_test.id = make_new_test_id()
 
1871
        return new_test
 
1872
    
1831
1873
    def adapt(self, test):
1832
1874
        from bzrlib.tests import TestSuite
1833
1875
        result = TestSuite()
1834
1876
        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()
 
1877
            new_test = self._clone_test(
 
1878
                test,
 
1879
                bzrdir_format,
 
1880
                workingtree_format, workingtree_format.__class__.__name__)
1844
1881
            result.addTest(new_test)
1845
1882
        return result