~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-10 00:43:37 UTC
  • mto: This revision was merged to the branch mainline in revision 1926.
  • Revision ID: john@arbash-meinel.com-20060810004337-6aa4d7ea80e85093
Moving everything into a new location so that we can cache more than just revision ids

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
847
857
        if to_dir_id == None and to_name != '':
848
858
            raise BzrError("destination %r is not a versioned directory" % to_name)
849
859
        to_dir_ie = inv[to_dir_id]
850
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
860
        if to_dir_ie.kind != 'directory':
851
861
            raise BzrError("destination %r is not a directory" % to_abs)
852
862
 
853
863
        to_idpath = inv.get_idpath(to_dir_id)
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
 
1206
1224
        if new_revision is None:
1207
1225
            self.branch.set_revision_history([])
1208
1226
            return False
1209
 
        # current format is locked in with the branch
1210
 
        revision_history = self.branch.revision_history()
1211
1227
        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])
 
1228
            self.branch.generate_revision_history(new_revision)
 
1229
        except errors.NoSuchRevision:
 
1230
            # not present in the repo - dont try to set it deeper than the tip
 
1231
            self.branch.set_revision_history([new_revision])
1216
1232
        return True
1217
1233
 
1218
1234
    def _cache_basis_inventory(self, new_revision):
1241
1257
            path = self._basis_inventory_name()
1242
1258
            sio = StringIO(xml)
1243
1259
            self._control_files.put(path, sio)
1244
 
        except WeaveRevisionNotPresent:
 
1260
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1245
1261
            pass
1246
1262
 
1247
1263
    def read_basis_inventory(self):
1362
1378
        between multiple working trees, i.e. via shared storage, then we 
1363
1379
        would probably want to lock both the local tree, and the branch.
1364
1380
        """
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()
 
1381
        raise NotImplementedError(self.unlock)
1386
1382
 
1387
1383
    @needs_write_lock
1388
1384
    def update(self):
1483
1479
        return conflicts
1484
1480
 
1485
1481
 
 
1482
class WorkingTree2(WorkingTree):
 
1483
    """This is the Format 2 working tree.
 
1484
 
 
1485
    This was the first weave based working tree. 
 
1486
     - uses os locks for locking.
 
1487
     - uses the branch last-revision.
 
1488
    """
 
1489
 
 
1490
    def unlock(self):
 
1491
        # we share control files:
 
1492
        if self._hashcache.needs_write and self._control_files._lock_count==3:
 
1493
            self._hashcache.write()
 
1494
        # reverse order of locking.
 
1495
        try:
 
1496
            return self._control_files.unlock()
 
1497
        finally:
 
1498
            self.branch.unlock()
 
1499
 
 
1500
 
1486
1501
class WorkingTree3(WorkingTree):
1487
1502
    """This is the Format 3 working tree.
1488
1503
 
1510
1525
                pass
1511
1526
            return False
1512
1527
        else:
1513
 
            try:
1514
 
                self.branch.revision_history().index(revision_id)
1515
 
            except ValueError:
1516
 
                raise errors.NoSuchRevision(self.branch, revision_id)
1517
1528
            self._control_files.put_utf8('last-revision', revision_id)
1518
1529
            return True
1519
1530
 
1542
1553
            raise ConflictFormatError()
1543
1554
        return ConflictList.from_stanzas(RioReader(confile))
1544
1555
 
 
1556
    def unlock(self):
 
1557
        if self._hashcache.needs_write and self._control_files._lock_count==1:
 
1558
            self._hashcache.write()
 
1559
        # reverse order of locking.
 
1560
        try:
 
1561
            return self._control_files.unlock()
 
1562
        finally:
 
1563
            self.branch.unlock()
 
1564
 
1545
1565
 
1546
1566
def get_conflicted_stem(path):
1547
1567
    for suffix in CONFLICT_SUFFIXES:
1681
1701
                branch.unlock()
1682
1702
        revision = branch.last_revision()
1683
1703
        inv = Inventory() 
1684
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1704
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1685
1705
                         branch,
1686
1706
                         inv,
1687
1707
                         _internal=True,
1709
1729
            raise NotImplementedError
1710
1730
        if not isinstance(a_bzrdir.transport, LocalTransport):
1711
1731
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1712
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1732
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1713
1733
                           _internal=True,
1714
1734
                           _format=self,
1715
1735
                           _bzrdir=a_bzrdir)
1724
1744
          files, separate from the BzrDir format
1725
1745
        - modifies the hash cache format
1726
1746
        - is new in bzr 0.8
1727
 
        - uses a LockDir to guard access to the repository
 
1747
        - uses a LockDir to guard access for writes.
1728
1748
    """
1729
1749
 
1730
1750
    def get_format_string(self):
1794
1814
            raise NotImplementedError
1795
1815
        if not isinstance(a_bzrdir.transport, LocalTransport):
1796
1816
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1797
 
        control_files = self._open_control_files(a_bzrdir)
 
1817
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1818
 
 
1819
    def _open(self, a_bzrdir, control_files):
 
1820
        """Open the tree itself.
 
1821
        
 
1822
        :param a_bzrdir: the dir for the tree.
 
1823
        :param control_files: the control files for the tree.
 
1824
        """
1798
1825
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1799
1826
                           _internal=True,
1800
1827
                           _format=self,
1828
1855
        self._transport_readonly_server = transport_readonly_server
1829
1856
        self._formats = formats
1830
1857
    
 
1858
    def _clone_test(self, test, bzrdir_format, workingtree_format, variation):
 
1859
        """Clone test for adaption."""
 
1860
        new_test = deepcopy(test)
 
1861
        new_test.transport_server = self._transport_server
 
1862
        new_test.transport_readonly_server = self._transport_readonly_server
 
1863
        new_test.bzrdir_format = bzrdir_format
 
1864
        new_test.workingtree_format = workingtree_format
 
1865
        def make_new_test_id():
 
1866
            new_id = "%s(%s)" % (test.id(), variation)
 
1867
            return lambda: new_id
 
1868
        new_test.id = make_new_test_id()
 
1869
        return new_test
 
1870
    
1831
1871
    def adapt(self, test):
1832
1872
        from bzrlib.tests import TestSuite
1833
1873
        result = TestSuite()
1834
1874
        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()
 
1875
            new_test = self._clone_test(
 
1876
                test,
 
1877
                bzrdir_format,
 
1878
                workingtree_format, workingtree_format.__class__.__name__)
1844
1879
            result.addTest(new_test)
1845
1880
        return result