/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 bzrlib/workingtree.py

  • Committer: Jan Balster
  • Date: 2006-08-15 12:39:42 UTC
  • mfrom: (1923 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1928.
  • Revision ID: jan@merlinux.de-20060815123942-22c388c6e9a8ac91
merge bzr.dev 1923

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
import bzrlib
 
55
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
54
56
from bzrlib.atomicfile import AtomicFile
 
57
import bzrlib.branch
55
58
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
56
 
import bzrlib.bzrdir as bzrdir
57
59
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
 
import bzrlib.errors as errors
59
60
from bzrlib.errors import (BzrCheckError,
60
61
                           BzrError,
61
62
                           ConflictFormatError,
96
97
        DEPRECATED_PARAMETER,
97
98
        zero_eight,
98
99
        )
99
 
 
100
 
from bzrlib.textui import show_status
101
 
import bzrlib.tree
 
100
from bzrlib.trace import mutter, note
102
101
from bzrlib.transform import build_tree
103
 
from bzrlib.trace import mutter, note
104
102
from bzrlib.transport import get_transport
105
103
from bzrlib.transport.local import LocalTransport
106
 
import bzrlib.urlutils as urlutils
 
104
from bzrlib.textui import show_status
 
105
import bzrlib.tree
107
106
import bzrlib.ui
108
107
import bzrlib.xml5
109
108
 
110
109
 
111
 
# the regex here does the following:
112
 
# 1) remove any weird characters; we don't escape them but rather
113
 
# just pull them out
114
 
 # 2) match leading '.'s to make it not hidden
115
 
_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.]')
116
113
_gen_id_suffix = None
117
114
_gen_id_serial = 0
118
115
 
140
137
 
141
138
    The uniqueness is supplied from _next_id_suffix.
142
139
    """
143
 
    # XXX TODO: squash the filename to lowercase.
144
 
    # XXX TODO: truncate the filename to something like 20 or 30 chars.
145
 
    # XXX TODO: consider what to do with ids that look like illegal filepaths
146
 
    # on platforms we support.
147
 
    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()
148
154
 
149
155
 
150
156
def gen_root_id():
270
276
            # share control object
271
277
            self._control_files = self.branch.control_files
272
278
        else:
273
 
            # only ready for format 3
274
 
            assert isinstance(self._format, WorkingTreeFormat3)
 
279
            # assume all other formats have their own control files.
275
280
            assert isinstance(_control_files, LockableFiles), \
276
281
                    "_control_files must be a LockableFiles, not %r" \
277
282
                    % _control_files
317
322
        self.branch.break_lock()
318
323
 
319
324
    def _set_inventory(self, inv):
 
325
        assert inv.root is not None
320
326
        self._inventory = inv
321
327
        self.path2id = self._inventory.path2id
322
328
 
356
362
        :return: The WorkingTree that contains 'path', and the rest of path
357
363
        """
358
364
        if path is None:
359
 
            path = os.getcwdu()
 
365
            path = osutils.getcwd()
360
366
        control, relpath = bzrdir.BzrDir.open_containing(path)
361
367
 
362
368
        return control.open_workingtree(), relpath
377
383
        """
378
384
        inv = self._inventory
379
385
        for path, ie in inv.iter_entries():
380
 
            if bzrlib.osutils.lexists(self.abspath(path)):
 
386
            if osutils.lexists(self.abspath(path)):
381
387
                yield ie.file_id
382
388
 
383
389
    def __repr__(self):
394
400
            try:
395
401
                xml = self.read_basis_inventory()
396
402
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
403
                inv.root.revision = revision_id
397
404
            except NoSuchFile:
398
405
                inv = None
399
406
            if inv is not None and inv.revision_id == revision_id:
449
456
        return relpath(self.basedir, path)
450
457
 
451
458
    def has_filename(self, filename):
452
 
        return bzrlib.osutils.lexists(self.abspath(filename))
 
459
        return osutils.lexists(self.abspath(filename))
453
460
 
454
461
    def get_file(self, file_id):
455
462
        return self.get_file_byname(self.id2path(file_id))
456
463
 
 
464
    def get_file_text(self, file_id):
 
465
        return self.get_file(file_id).read()
 
466
 
457
467
    def get_file_byname(self, filename):
458
468
        return file(self.abspath(filename), 'rb')
459
469
 
539
549
        if not inv.has_id(file_id):
540
550
            return False
541
551
        path = inv.id2path(file_id)
542
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
552
        return osutils.lexists(self.abspath(path))
543
553
 
544
554
    def has_or_had_id(self, file_id):
545
555
        if file_id == self.inventory.root.file_id:
723
733
        """
724
734
        inv = self._inventory
725
735
        # Convert these into local objects to save lookup times
726
 
        pathjoin = bzrlib.osutils.pathjoin
727
 
        file_kind = bzrlib.osutils.file_kind
 
736
        pathjoin = osutils.pathjoin
 
737
        file_kind = osutils.file_kind
728
738
 
729
739
        # transport.base ends in a slash, we want the piece
730
740
        # between the last two slashes
768
778
                elif self.is_ignored(fp[1:]):
769
779
                    c = 'I'
770
780
                else:
771
 
                    c = '?'
 
781
                    # we may not have found this file, because of a unicode issue
 
782
                    f_norm, can_access = osutils.normalized_filename(f)
 
783
                    if f == f_norm or not can_access:
 
784
                        # No change, so treat this file normally
 
785
                        c = '?'
 
786
                    else:
 
787
                        # this file can be accessed by a normalized path
 
788
                        # check again if it is versioned
 
789
                        # these lines are repeated here for performance
 
790
                        f = f_norm
 
791
                        fp = from_dir_relpath + '/' + f
 
792
                        fap = from_dir_abspath + '/' + f
 
793
                        f_ie = inv.get_child(from_dir_id, f)
 
794
                        if f_ie:
 
795
                            c = 'V'
 
796
                        elif self.is_ignored(fp[1:]):
 
797
                            c = 'I'
 
798
                        else:
 
799
                            c = '?'
772
800
 
773
801
                fk = file_kind(fap)
774
802
 
831
859
        if to_dir_id == None and to_name != '':
832
860
            raise BzrError("destination %r is not a versioned directory" % to_name)
833
861
        to_dir_ie = inv[to_dir_id]
834
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
862
        if to_dir_ie.kind != 'directory':
835
863
            raise BzrError("destination %r is not a directory" % to_abs)
836
864
 
837
865
        to_idpath = inv.get_idpath(to_dir_id)
992
1020
        """
993
1021
        ## TODO: Work from given directory downwards
994
1022
        for path, dir_entry in self.inventory.directories():
995
 
            mutter("search for unknowns in %r", path)
 
1023
            # mutter("search for unknowns in %r", path)
996
1024
            dirabs = self.abspath(path)
997
1025
            if not isdir(dirabs):
998
1026
                # e.g. directory deleted
1000
1028
 
1001
1029
            fl = []
1002
1030
            for subf in os.listdir(dirabs):
1003
 
                if (subf != '.bzr'
1004
 
                    and (subf not in dir_entry.children)):
1005
 
                    fl.append(subf)
 
1031
                if subf == '.bzr':
 
1032
                    continue
 
1033
                if subf not in dir_entry.children:
 
1034
                    subf_norm, can_access = osutils.normalized_filename(subf)
 
1035
                    if subf_norm != subf and can_access:
 
1036
                        if subf_norm not in dir_entry.children:
 
1037
                            fl.append(subf_norm)
 
1038
                    else:
 
1039
                        fl.append(subf)
1006
1040
            
1007
1041
            fl.sort()
1008
1042
            for subf in fl:
1076
1110
 
1077
1111
        Cached in the Tree object after the first call.
1078
1112
        """
1079
 
        if hasattr(self, '_ignorelist'):
1080
 
            return self._ignorelist
1081
 
 
1082
 
        l = bzrlib.DEFAULT_IGNORE[:]
 
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
 
1083
1122
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1084
1123
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1085
 
            l.extend([line.rstrip("\n\r").decode('utf-8') 
1086
 
                      for line in f.readlines()])
1087
 
        self._ignorelist = l
1088
 
        self._ignore_regex = self._combine_ignore_rules(l)
1089
 
        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
1090
1132
 
1091
1133
    def _get_ignore_rules_as_regex(self):
1092
1134
        """Return a regex of the ignore rules and a mapping dict.
1094
1136
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1095
1137
        indices to original rule.)
1096
1138
        """
1097
 
        if getattr(self, '_ignorelist', None) is None:
 
1139
        if getattr(self, '_ignoreset', None) is None:
1098
1140
            self.get_ignore_list()
1099
1141
        return self._ignore_regex
1100
1142
 
1184
1226
        if new_revision is None:
1185
1227
            self.branch.set_revision_history([])
1186
1228
            return False
1187
 
        # current format is locked in with the branch
1188
 
        revision_history = self.branch.revision_history()
1189
1229
        try:
1190
 
            position = revision_history.index(new_revision)
1191
 
        except ValueError:
1192
 
            raise errors.NoSuchRevision(self.branch, new_revision)
1193
 
        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])
1194
1234
        return True
1195
1235
 
1196
1236
    def _cache_basis_inventory(self, new_revision):
1219
1259
            path = self._basis_inventory_name()
1220
1260
            sio = StringIO(xml)
1221
1261
            self._control_files.put(path, sio)
1222
 
        except WeaveRevisionNotPresent:
 
1262
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
1223
1263
            pass
1224
1264
 
1225
1265
    def read_basis_inventory(self):
1340
1380
        between multiple working trees, i.e. via shared storage, then we 
1341
1381
        would probably want to lock both the local tree, and the branch.
1342
1382
        """
1343
 
        # FIXME: We want to write out the hashcache only when the last lock on
1344
 
        # this working copy is released.  Peeking at the lock count is a bit
1345
 
        # of a nasty hack; probably it's better to have a transaction object,
1346
 
        # which can do some finalization when it's either successfully or
1347
 
        # unsuccessfully completed.  (Denys's original patch did that.)
1348
 
        # RBC 20060206 hooking into transaction will couple lock and transaction
1349
 
        # wrongly. Hooking into unlock on the control files object is fine though.
1350
 
        
1351
 
        # TODO: split this per format so there is no ugly if block
1352
 
        if self._hashcache.needs_write and (
1353
 
            # dedicated lock files
1354
 
            self._control_files._lock_count==1 or 
1355
 
            # shared lock files
1356
 
            (self._control_files is self.branch.control_files and 
1357
 
             self._control_files._lock_count==3)):
1358
 
            self._hashcache.write()
1359
 
        # reverse order of locking.
1360
 
        try:
1361
 
            return self._control_files.unlock()
1362
 
        finally:
1363
 
            self.branch.unlock()
 
1383
        raise NotImplementedError(self.unlock)
1364
1384
 
1365
1385
    @needs_write_lock
1366
1386
    def update(self):
1432
1452
    def set_conflicts(self, arg):
1433
1453
        raise UnsupportedOperation(self.set_conflicts, self)
1434
1454
 
 
1455
    def add_conflicts(self, arg):
 
1456
        raise UnsupportedOperation(self.add_conflicts, self)
 
1457
 
1435
1458
    @needs_read_lock
1436
1459
    def conflicts(self):
1437
1460
        conflicts = ConflictList()
1458
1481
        return conflicts
1459
1482
 
1460
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
 
1461
1503
class WorkingTree3(WorkingTree):
1462
1504
    """This is the Format 3 working tree.
1463
1505
 
1485
1527
                pass
1486
1528
            return False
1487
1529
        else:
1488
 
            try:
1489
 
                self.branch.revision_history().index(revision_id)
1490
 
            except ValueError:
1491
 
                raise errors.NoSuchRevision(self.branch, revision_id)
1492
1530
            self._control_files.put_utf8('last-revision', revision_id)
1493
1531
            return True
1494
1532
 
1497
1535
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1498
1536
                      CONFLICT_HEADER_1)
1499
1537
 
 
1538
    @needs_write_lock
 
1539
    def add_conflicts(self, new_conflicts):
 
1540
        conflict_set = set(self.conflicts())
 
1541
        conflict_set.update(set(list(new_conflicts)))
 
1542
        self.set_conflicts(ConflictList(sorted(conflict_set,
 
1543
                                               key=Conflict.sort_key)))
 
1544
 
1500
1545
    @needs_read_lock
1501
1546
    def conflicts(self):
1502
1547
        try:
1510
1555
            raise ConflictFormatError()
1511
1556
        return ConflictList.from_stanzas(RioReader(confile))
1512
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
 
1513
1567
 
1514
1568
def get_conflicted_stem(path):
1515
1569
    for suffix in CONFLICT_SUFFIXES:
1649
1703
                branch.unlock()
1650
1704
        revision = branch.last_revision()
1651
1705
        inv = Inventory() 
1652
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1706
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1653
1707
                         branch,
1654
1708
                         inv,
1655
1709
                         _internal=True,
1677
1731
            raise NotImplementedError
1678
1732
        if not isinstance(a_bzrdir.transport, LocalTransport):
1679
1733
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1680
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1734
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
1681
1735
                           _internal=True,
1682
1736
                           _format=self,
1683
1737
                           _bzrdir=a_bzrdir)
1692
1746
          files, separate from the BzrDir format
1693
1747
        - modifies the hash cache format
1694
1748
        - is new in bzr 0.8
1695
 
        - uses a LockDir to guard access to the repository
 
1749
        - uses a LockDir to guard access for writes.
1696
1750
    """
1697
1751
 
1698
1752
    def get_format_string(self):
1762
1816
            raise NotImplementedError
1763
1817
        if not isinstance(a_bzrdir.transport, LocalTransport):
1764
1818
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1765
 
        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
        """
1766
1827
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1767
1828
                           _internal=True,
1768
1829
                           _format=self,
1796
1857
        self._transport_readonly_server = transport_readonly_server
1797
1858
        self._formats = formats
1798
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
    
1799
1873
    def adapt(self, test):
1800
1874
        from bzrlib.tests import TestSuite
1801
1875
        result = TestSuite()
1802
1876
        for workingtree_format, bzrdir_format in self._formats:
1803
 
            new_test = deepcopy(test)
1804
 
            new_test.transport_server = self._transport_server
1805
 
            new_test.transport_readonly_server = self._transport_readonly_server
1806
 
            new_test.bzrdir_format = bzrdir_format
1807
 
            new_test.workingtree_format = workingtree_format
1808
 
            def make_new_test_id():
1809
 
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1810
 
                return lambda: new_id
1811
 
            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__)
1812
1881
            result.addTest(new_test)
1813
1882
        return result