~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Martin Pool
  • Date: 2006-05-17 04:01:38 UTC
  • mto: (1704.2.17 bzr.mbp.integration)
  • mto: This revision was merged to the branch mainline in revision 1710.
  • Revision ID: mbp@sourcefrog.net-20060517040138-2cdf4e74bbc40afc
Fix setup.py to install launchpad plugin

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
39
39
# At the moment they may alias the inventory and have old copies of it in
40
40
# memory.  (Now done? -- mbp 20060309)
41
41
 
42
 
from binascii import hexlify
43
 
import collections
44
42
from copy import deepcopy
45
43
from cStringIO import StringIO
46
44
import errno
47
45
import fnmatch
48
46
import os
49
 
import re
50
47
import stat
51
 
from time import time
52
 
import warnings
 
48
 
53
49
 
54
 
import bzrlib
55
 
from bzrlib import bzrdir, errors, ignores, osutils, urlutils
56
50
from bzrlib.atomicfile import AtomicFile
57
 
import bzrlib.branch
 
51
from bzrlib.branch import (Branch,
 
52
                           quotefn)
58
53
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
54
import bzrlib.bzrdir as bzrdir
59
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
56
import bzrlib.errors as errors
60
57
from bzrlib.errors import (BzrCheckError,
61
58
                           BzrError,
62
59
                           ConflictFormatError,
 
60
                           DivergedBranches,
63
61
                           WeaveRevisionNotPresent,
64
62
                           NotBranchError,
65
63
                           NoSuchFile,
73
71
from bzrlib.merge import merge_inner, transform_tree
74
72
from bzrlib.osutils import (
75
73
                            abspath,
 
74
                            appendpath,
76
75
                            compact_date,
77
76
                            file_kind,
78
77
                            isdir,
81
80
                            pumpfile,
82
81
                            safe_unicode,
83
82
                            splitpath,
84
 
                            rand_chars,
 
83
                            rand_bytes,
85
84
                            normpath,
86
85
                            realpath,
87
86
                            relpath,
91
90
from bzrlib.progress import DummyProgress, ProgressPhase
92
91
from bzrlib.revision import NULL_REVISION
93
92
from bzrlib.rio import RioReader, rio_file, Stanza
94
 
from bzrlib.symbol_versioning import (deprecated_passed,
95
 
        deprecated_method,
96
 
        deprecated_function,
97
 
        DEPRECATED_PARAMETER,
98
 
        zero_eight,
99
 
        )
 
93
from bzrlib.symbol_versioning import *
 
94
from bzrlib.textui import show_status
 
95
import bzrlib.tree
 
96
from bzrlib.transform import build_tree
100
97
from bzrlib.trace import mutter, note
101
 
from bzrlib.transform import build_tree
102
98
from bzrlib.transport import get_transport
103
99
from bzrlib.transport.local import LocalTransport
104
 
from bzrlib.textui import show_status
105
 
import bzrlib.tree
106
100
import bzrlib.ui
107
101
import bzrlib.xml5
108
102
 
109
103
 
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.]')
113
 
_gen_id_suffix = None
114
 
_gen_id_serial = 0
115
 
 
116
 
 
117
 
def _next_id_suffix():
118
 
    """Create a new file id suffix that is reasonably unique.
119
 
    
120
 
    On the first call we combine the current time with 64 bits of randomness
121
 
    to give a highly probably globally unique number. Then each call in the same
122
 
    process adds 1 to a serial number we append to that unique value.
123
 
    """
124
 
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
125
 
    # than having to move the id randomness out of the inner loop like this.
126
 
    # XXX TODO: for the global randomness this uses we should add the thread-id
127
 
    # before the serial #.
128
 
    global _gen_id_suffix, _gen_id_serial
129
 
    if _gen_id_suffix is None:
130
 
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
131
 
    _gen_id_serial += 1
132
 
    return _gen_id_suffix + str(_gen_id_serial)
133
 
 
134
 
 
135
104
def gen_file_id(name):
136
 
    """Return new file id for the basename 'name'.
137
 
 
138
 
    The uniqueness is supplied from _next_id_suffix.
139
 
    """
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()
 
105
    """Return new file id.
 
106
 
 
107
    This should probably generate proper UUIDs, but for the moment we
 
108
    cope with just randomness because running uuidgen every time is
 
109
    slow."""
 
110
    import re
 
111
    from binascii import hexlify
 
112
    from time import time
 
113
 
 
114
    # get last component
 
115
    idx = name.rfind('/')
 
116
    if idx != -1:
 
117
        name = name[idx+1 : ]
 
118
    idx = name.rfind('\\')
 
119
    if idx != -1:
 
120
        name = name[idx+1 : ]
 
121
 
 
122
    # make it not a hidden file
 
123
    name = name.lstrip('.')
 
124
 
 
125
    # remove any wierd characters; we don't escape them but rather
 
126
    # just pull them out
 
127
    name = re.sub(r'[^\w.]', '', name)
 
128
 
 
129
    s = hexlify(rand_bytes(8))
 
130
    return '-'.join((name, compact_date(time()), s))
154
131
 
155
132
 
156
133
def gen_root_id():
159
136
 
160
137
 
161
138
class TreeEntry(object):
162
 
    """An entry that implements the minimum interface used by commands.
 
139
    """An entry that implements the minium interface used by commands.
163
140
 
164
141
    This needs further inspection, it may be better to have 
165
142
    InventoryEntries without ids - though that seems wrong. For now,
241
218
        self.bzrdir = _bzrdir
242
219
        if not _internal:
243
220
            # not created via open etc.
244
 
            warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
221
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
245
222
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
246
223
                 DeprecationWarning,
247
224
                 stacklevel=2)
261
238
        mutter("opening working tree %r", basedir)
262
239
        if deprecated_passed(branch):
263
240
            if not _internal:
264
 
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
241
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
265
242
                     " Please use bzrdir.open_workingtree() or"
266
243
                     " WorkingTree.open().",
267
244
                     DeprecationWarning,
270
247
            self._branch = branch
271
248
        else:
272
249
            self._branch = self.bzrdir.open_branch()
 
250
        assert isinstance(self.branch, Branch), \
 
251
            "branch %r is not a Branch" % self.branch
273
252
        self.basedir = realpath(basedir)
274
253
        # if branch is at our basedir and is a format 6 or less
275
254
        if isinstance(self._format, WorkingTreeFormat2):
276
255
            # share control object
277
256
            self._control_files = self.branch.control_files
278
257
        else:
279
 
            # assume all other formats have their own control files.
 
258
            # only ready for format 3
 
259
            assert isinstance(self._format, WorkingTreeFormat3)
280
260
            assert isinstance(_control_files, LockableFiles), \
281
261
                    "_control_files must be a LockableFiles, not %r" \
282
262
                    % _control_files
287
267
        # if needed, or, when the cache sees a change, append it to the hash
288
268
        # cache file, and have the parser take the most recent entry for a
289
269
        # given path only.
290
 
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
 
270
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
291
271
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
292
272
        hc.read()
293
273
        # is this scan needed ? it makes things kinda slow.
294
 
        #hc.scan()
 
274
        hc.scan()
295
275
 
296
276
        if hc.needs_write:
297
277
            mutter("write hc")
328
308
    def is_control_filename(self, filename):
329
309
        """True if filename is the name of a control file in this tree.
330
310
        
331
 
        :param filename: A filename within the tree. This is a relative path
332
 
        from the root of this tree.
333
 
 
334
311
        This is true IF and ONLY IF the filename is part of the meta data
335
312
        that bzr controls in this tree. I.E. a random .bzr directory placed
336
313
        on disk will not be a control file for this tree.
337
314
        """
338
 
        return self.bzrdir.is_control_filename(filename)
 
315
        try:
 
316
            self.bzrdir.transport.relpath(self.abspath(filename))
 
317
            return True
 
318
        except errors.PathNotChild:
 
319
            return False
339
320
 
340
321
    @staticmethod
341
322
    def open(path=None, _unsupported=False):
357
338
        run into /.  If there isn't one, raises NotBranchError.
358
339
        TODO: give this a new exception.
359
340
        If there is one, it is returned, along with the unused portion of path.
360
 
 
361
 
        :return: The WorkingTree that contains 'path', and the rest of path
362
341
        """
363
342
        if path is None:
364
 
            path = osutils.getcwd()
 
343
            path = os.getcwdu()
365
344
        control, relpath = bzrdir.BzrDir.open_containing(path)
366
 
 
367
345
        return control.open_workingtree(), relpath
368
346
 
369
347
    @staticmethod
382
360
        """
383
361
        inv = self._inventory
384
362
        for path, ie in inv.iter_entries():
385
 
            if osutils.lexists(self.abspath(path)):
 
363
            if bzrlib.osutils.lexists(self.abspath(path)):
386
364
                yield ie.file_id
387
365
 
388
366
    def __repr__(self):
424
402
        XXX: When BzrDir is present, these should be created through that 
425
403
        interface instead.
426
404
        """
427
 
        warnings.warn('delete WorkingTree.create', stacklevel=3)
 
405
        warn('delete WorkingTree.create', stacklevel=3)
428
406
        transport = get_transport(directory)
429
407
        if branch.bzrdir.root_transport.base == transport.base:
430
408
            # same dir 
445
423
        """
446
424
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
447
425
 
448
 
    def relpath(self, path):
449
 
        """Return the local path portion from a given path.
450
 
        
451
 
        The path may be absolute or relative. If its a relative path it is 
452
 
        interpreted relative to the python current working directory.
453
 
        """
454
 
        return relpath(self.basedir, path)
 
426
    def relpath(self, abs):
 
427
        """Return the local path portion from a given absolute path."""
 
428
        return relpath(self.basedir, abs)
455
429
 
456
430
    def has_filename(self, filename):
457
 
        return osutils.lexists(self.abspath(filename))
 
431
        return bzrlib.osutils.lexists(self.abspath(filename))
458
432
 
459
433
    def get_file(self, file_id):
460
434
        return self.get_file_byname(self.id2path(file_id))
461
435
 
462
 
    def get_file_text(self, file_id):
463
 
        return self.get_file(file_id).read()
464
 
 
465
436
    def get_file_byname(self, filename):
466
437
        return file(self.abspath(filename), 'rb')
467
438
 
468
 
    def get_parent_ids(self):
469
 
        """See Tree.get_parent_ids.
470
 
        
471
 
        This implementation reads the pending merges list and last_revision
472
 
        value and uses that to decide what the parents list should be.
473
 
        """
474
 
        last_rev = self.last_revision()
475
 
        if last_rev is None:
476
 
            parents = []
477
 
        else:
478
 
            parents = [last_rev]
479
 
        other_parents = self.pending_merges()
480
 
        return parents + other_parents
481
 
 
482
439
    def get_root_id(self):
483
440
        """Return the id of this trees root"""
484
441
        inv = self.read_working_inventory()
533
490
        # but with branch a kwarg now, passing in args as is results in the
534
491
        #message being used for the branch
535
492
        args = (DEPRECATED_PARAMETER, message, ) + args
536
 
        committed_id = Commit().commit( working_tree=self, revprops=revprops,
537
 
            *args, **kwargs)
 
493
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
538
494
        self._set_inventory(self.read_working_inventory())
539
 
        return committed_id
540
495
 
541
496
    def id2abspath(self, file_id):
542
497
        return self.abspath(self.id2path(file_id))
547
502
        if not inv.has_id(file_id):
548
503
            return False
549
504
        path = inv.id2path(file_id)
550
 
        return osutils.lexists(self.abspath(path))
 
505
        return bzrlib.osutils.lexists(self.abspath(path))
551
506
 
552
507
    def has_or_had_id(self, file_id):
553
508
        if file_id == self.inventory.root.file_id:
560
515
        return os.path.getsize(self.id2abspath(file_id))
561
516
 
562
517
    @needs_read_lock
563
 
    def get_file_sha1(self, file_id, path=None):
564
 
        if not path:
565
 
            path = self._inventory.id2path(file_id)
 
518
    def get_file_sha1(self, file_id):
 
519
        path = self._inventory.id2path(file_id)
566
520
        return self._hashcache.get_sha1(path)
567
521
 
568
 
    def get_file_mtime(self, file_id, path=None):
569
 
        if not path:
570
 
            path = self._inventory.id2path(file_id)
571
 
        return os.lstat(self.abspath(path)).st_mtime
572
 
 
573
 
    if not supports_executable():
574
 
        def is_executable(self, file_id, path=None):
 
522
    def is_executable(self, file_id):
 
523
        if not supports_executable():
575
524
            return self._inventory[file_id].executable
576
 
    else:
577
 
        def is_executable(self, file_id, path=None):
578
 
            if not path:
579
 
                path = self._inventory.id2path(file_id)
 
525
        else:
 
526
            path = self._inventory.id2path(file_id)
580
527
            mode = os.lstat(self.abspath(path)).st_mode
581
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
528
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
582
529
 
583
530
    @needs_write_lock
584
531
    def add(self, files, ids=None):
619
566
        inv = self.read_working_inventory()
620
567
        for f,file_id in zip(files, ids):
621
568
            if self.is_control_filename(f):
622
 
                raise errors.ForbiddenControlFileError(filename=f)
 
569
                raise BzrError("cannot add control file %s" % quotefn(f))
623
570
 
624
571
            fp = splitpath(f)
625
572
 
627
574
                raise BzrError("cannot add top-level %r" % f)
628
575
 
629
576
            fullpath = normpath(self.abspath(f))
 
577
 
630
578
            try:
631
579
                kind = file_kind(fullpath)
632
580
            except OSError, e:
633
581
                if e.errno == errno.ENOENT:
634
582
                    raise NoSuchFile(fullpath)
 
583
                # maybe something better?
 
584
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
585
 
635
586
            if not InventoryEntry.versionable_kind(kind):
636
 
                raise errors.BadFileKindError(filename=f, kind=kind)
 
587
                raise BzrError('cannot add: not a versionable file ('
 
588
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
589
 
637
590
            if file_id is None:
638
 
                inv.add_path(f, kind=kind)
639
 
            else:
640
 
                inv.add_path(f, kind=kind, file_id=file_id)
 
591
                file_id = gen_file_id(f)
 
592
            inv.add_path(f, kind=kind, file_id=file_id)
641
593
 
 
594
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
642
595
        self._write_inventory(inv)
643
596
 
644
597
    @needs_write_lock
664
617
        """
665
618
        try:
666
619
            merges_file = self._control_files.get_utf8('pending-merges')
667
 
        except NoSuchFile:
 
620
        except OSError, e:
 
621
            if e.errno != errno.ENOENT:
 
622
                raise
668
623
            return []
669
624
        p = []
670
625
        for l in merges_file.readlines():
720
675
            return '?'
721
676
 
722
677
    def list_files(self):
723
 
        """Recursively list all files as (path, class, kind, id, entry).
 
678
        """Recursively list all files as (path, class, kind, id).
724
679
 
725
680
        Lists, but does not descend into unversioned directories.
726
681
 
730
685
        Skips the control directory.
731
686
        """
732
687
        inv = self._inventory
733
 
        # Convert these into local objects to save lookup times
734
 
        pathjoin = osutils.pathjoin
735
 
        file_kind = osutils.file_kind
736
 
 
737
 
        # transport.base ends in a slash, we want the piece
738
 
        # between the last two slashes
739
 
        transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
740
 
 
741
 
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
742
 
 
743
 
        # directory file_id, relative path, absolute path, reverse sorted children
744
 
        children = os.listdir(self.basedir)
745
 
        children.sort()
746
 
        # jam 20060527 The kernel sized tree seems equivalent whether we 
747
 
        # use a deque and popleft to keep them sorted, or if we use a plain
748
 
        # list and just reverse() them.
749
 
        children = collections.deque(children)
750
 
        stack = [(inv.root.file_id, u'', self.basedir, children)]
751
 
        while stack:
752
 
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
753
 
 
754
 
            while children:
755
 
                f = children.popleft()
 
688
 
 
689
        def descend(from_dir_relpath, from_dir_id, dp):
 
690
            ls = os.listdir(dp)
 
691
            ls.sort()
 
692
            for f in ls:
756
693
                ## TODO: If we find a subdirectory with its own .bzr
757
694
                ## directory, then that is a separate tree and we
758
695
                ## should exclude it.
759
696
 
760
697
                # the bzrdir for this tree
761
 
                if transport_base_dir == f:
 
698
                if self.bzrdir.transport.base.endswith(f + '/'):
762
699
                    continue
763
700
 
764
 
                # we know that from_dir_relpath and from_dir_abspath never end in a slash
765
 
                # and 'f' doesn't begin with one, we can do a string op, rather
766
 
                # than the checks of pathjoin(), all relative paths will have an extra slash
767
 
                # at the beginning
768
 
                fp = from_dir_relpath + '/' + f
 
701
                # path within tree
 
702
                fp = appendpath(from_dir_relpath, f)
769
703
 
770
704
                # absolute path
771
 
                fap = from_dir_abspath + '/' + f
 
705
                fap = appendpath(dp, f)
772
706
                
773
707
                f_ie = inv.get_child(from_dir_id, f)
774
708
                if f_ie:
775
709
                    c = 'V'
776
 
                elif self.is_ignored(fp[1:]):
 
710
                elif self.is_ignored(fp):
777
711
                    c = 'I'
778
712
                else:
779
 
                    # we may not have found this file, because of a unicode issue
780
 
                    f_norm, can_access = osutils.normalized_filename(f)
781
 
                    if f == f_norm or not can_access:
782
 
                        # No change, so treat this file normally
783
 
                        c = '?'
784
 
                    else:
785
 
                        # this file can be accessed by a normalized path
786
 
                        # check again if it is versioned
787
 
                        # these lines are repeated here for performance
788
 
                        f = f_norm
789
 
                        fp = from_dir_relpath + '/' + f
790
 
                        fap = from_dir_abspath + '/' + f
791
 
                        f_ie = inv.get_child(from_dir_id, f)
792
 
                        if f_ie:
793
 
                            c = 'V'
794
 
                        elif self.is_ignored(fp[1:]):
795
 
                            c = 'I'
796
 
                        else:
797
 
                            c = '?'
 
713
                    c = '?'
798
714
 
799
715
                fk = file_kind(fap)
800
716
 
806
722
 
807
723
                # make a last minute entry
808
724
                if f_ie:
809
 
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
 
725
                    entry = f_ie
810
726
                else:
811
 
                    try:
812
 
                        yield fp[1:], c, fk, None, fk_entries[fk]()
813
 
                    except KeyError:
814
 
                        yield fp[1:], c, fk, None, TreeEntry()
815
 
                    continue
 
727
                    if fk == 'directory':
 
728
                        entry = TreeDirectory()
 
729
                    elif fk == 'file':
 
730
                        entry = TreeFile()
 
731
                    elif fk == 'symlink':
 
732
                        entry = TreeLink()
 
733
                    else:
 
734
                        entry = TreeEntry()
816
735
                
 
736
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
 
737
 
817
738
                if fk != 'directory':
818
739
                    continue
819
740
 
820
 
                # But do this child first
821
 
                new_children = os.listdir(fap)
822
 
                new_children.sort()
823
 
                new_children = collections.deque(new_children)
824
 
                stack.append((f_ie.file_id, fp, fap, new_children))
825
 
                # Break out of inner loop, so that we start outer loop with child
826
 
                break
827
 
            else:
828
 
                # if we finished all children, pop it off the stack
829
 
                stack.pop()
 
741
                if c != 'V':
 
742
                    # don't descend unversioned directories
 
743
                    continue
 
744
                
 
745
                for ff in descend(fp, f_ie.file_id, fap):
 
746
                    yield ff
830
747
 
 
748
        for f in descend(u'', inv.root.file_id, self.basedir):
 
749
            yield f
831
750
 
832
751
    @needs_write_lock
833
752
    def move(self, from_paths, to_name):
857
776
        if to_dir_id == None and to_name != '':
858
777
            raise BzrError("destination %r is not a versioned directory" % to_name)
859
778
        to_dir_ie = inv[to_dir_id]
860
 
        if to_dir_ie.kind != 'directory':
 
779
        if to_dir_ie.kind not in ('directory', 'root_directory'):
861
780
            raise BzrError("destination %r is not a directory" % to_abs)
862
781
 
863
782
        to_idpath = inv.get_idpath(to_dir_id)
869
788
            if f_id == None:
870
789
                raise BzrError("%r is not versioned" % f)
871
790
            name_tail = splitpath(f)[-1]
872
 
            dest_path = pathjoin(to_name, name_tail)
 
791
            dest_path = appendpath(to_name, name_tail)
873
792
            if self.has_filename(dest_path):
874
793
                raise BzrError("destination %r already exists" % dest_path)
875
794
            if f_id in to_idpath:
882
801
        try:
883
802
            for f in from_paths:
884
803
                name_tail = splitpath(f)[-1]
885
 
                dest_path = pathjoin(to_name, name_tail)
 
804
                dest_path = appendpath(to_name, name_tail)
886
805
                result.append((f, dest_path))
887
806
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
888
807
                try:
952
871
 
953
872
        These are files in the working directory that are not versioned or
954
873
        control files or ignored.
 
874
        
 
875
        >>> from bzrlib.bzrdir import ScratchDir
 
876
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
877
        >>> b = d.open_branch()
 
878
        >>> tree = d.open_workingtree()
 
879
        >>> map(str, tree.unknowns())
 
880
        ['foo']
 
881
        >>> tree.add('foo')
 
882
        >>> list(b.unknowns())
 
883
        []
 
884
        >>> tree.remove('foo')
 
885
        >>> list(b.unknowns())
 
886
        [u'foo']
955
887
        """
956
888
        for subp in self.extras():
957
889
            if not self.is_ignored(subp):
965
897
 
966
898
    def _iter_conflicts(self):
967
899
        conflicted = set()
968
 
        for info in self.list_files():
969
 
            path = info[0]
 
900
        for path in (s[0] for s in self.list_files()):
970
901
            stem = get_conflicted_stem(path)
971
902
            if stem is None:
972
903
                continue
1018
949
        """
1019
950
        ## TODO: Work from given directory downwards
1020
951
        for path, dir_entry in self.inventory.directories():
1021
 
            # mutter("search for unknowns in %r", path)
 
952
            mutter("search for unknowns in %r", path)
1022
953
            dirabs = self.abspath(path)
1023
954
            if not isdir(dirabs):
1024
955
                # e.g. directory deleted
1026
957
 
1027
958
            fl = []
1028
959
            for subf in os.listdir(dirabs):
1029
 
                if subf == '.bzr':
1030
 
                    continue
1031
 
                if subf not in dir_entry.children:
1032
 
                    subf_norm, can_access = osutils.normalized_filename(subf)
1033
 
                    if subf_norm != subf and can_access:
1034
 
                        if subf_norm not in dir_entry.children:
1035
 
                            fl.append(subf_norm)
1036
 
                    else:
1037
 
                        fl.append(subf)
 
960
                if (subf != '.bzr'
 
961
                    and (subf not in dir_entry.children)):
 
962
                    fl.append(subf)
1038
963
            
1039
964
            fl.sort()
1040
965
            for subf in fl:
1041
 
                subp = pathjoin(path, subf)
 
966
                subp = appendpath(path, subf)
1042
967
                yield subp
1043
968
 
1044
 
    def _translate_ignore_rule(self, rule):
1045
 
        """Translate a single ignore rule to a regex.
1046
 
 
1047
 
        There are two types of ignore rules.  Those that do not contain a / are
1048
 
        matched against the tail of the filename (that is, they do not care
1049
 
        what directory the file is in.)  Rules which do contain a slash must
1050
 
        match the entire path.  As a special case, './' at the start of the
1051
 
        string counts as a slash in the string but is removed before matching
1052
 
        (e.g. ./foo.c, ./src/foo.c)
1053
 
 
1054
 
        :return: The translated regex.
1055
 
        """
1056
 
        if rule[:2] in ('./', '.\\'):
1057
 
            # rootdir rule
1058
 
            result = fnmatch.translate(rule[2:])
1059
 
        elif '/' in rule or '\\' in rule:
1060
 
            # path prefix 
1061
 
            result = fnmatch.translate(rule)
1062
 
        else:
1063
 
            # default rule style.
1064
 
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1065
 
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1066
 
        return "(" + result + ")"
1067
 
 
1068
 
    def _combine_ignore_rules(self, rules):
1069
 
        """Combine a list of ignore rules into a single regex object.
1070
 
 
1071
 
        Each individual rule is combined with | to form a big regex, which then
1072
 
        has $ added to it to form something like ()|()|()$. The group index for
1073
 
        each subregex's outermost group is placed in a dictionary mapping back 
1074
 
        to the rule. This allows quick identification of the matching rule that
1075
 
        triggered a match.
1076
 
        :return: a list of the compiled regex and the matching-group index 
1077
 
        dictionaries. We return a list because python complains if you try to 
1078
 
        combine more than 100 regexes.
1079
 
        """
1080
 
        result = []
1081
 
        groups = {}
1082
 
        next_group = 0
1083
 
        translated_rules = []
1084
 
        for rule in rules:
1085
 
            translated_rule = self._translate_ignore_rule(rule)
1086
 
            compiled_rule = re.compile(translated_rule)
1087
 
            groups[next_group] = rule
1088
 
            next_group += compiled_rule.groups
1089
 
            translated_rules.append(translated_rule)
1090
 
            if next_group == 99:
1091
 
                result.append((re.compile("|".join(translated_rules)), groups))
1092
 
                groups = {}
1093
 
                next_group = 0
1094
 
                translated_rules = []
1095
 
        if len(translated_rules):
1096
 
            result.append((re.compile("|".join(translated_rules)), groups))
1097
 
        return result
1098
969
 
1099
970
    def ignored_files(self):
1100
971
        """Yield list of PATH, IGNORE_PATTERN"""
1103
974
            if pat != None:
1104
975
                yield subp, pat
1105
976
 
 
977
 
1106
978
    def get_ignore_list(self):
1107
979
        """Return list of ignore patterns.
1108
980
 
1109
981
        Cached in the Tree object after the first call.
1110
982
        """
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
 
 
 
983
        if hasattr(self, '_ignorelist'):
 
984
            return self._ignorelist
 
985
 
 
986
        l = bzrlib.DEFAULT_IGNORE[:]
1120
987
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1121
988
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
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
1130
 
 
1131
 
    def _get_ignore_rules_as_regex(self):
1132
 
        """Return a regex of the ignore rules and a mapping dict.
1133
 
 
1134
 
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1135
 
        indices to original rule.)
1136
 
        """
1137
 
        if getattr(self, '_ignoreset', None) is None:
1138
 
            self.get_ignore_list()
1139
 
        return self._ignore_regex
 
989
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
 
990
        self._ignorelist = l
 
991
        return l
 
992
 
1140
993
 
1141
994
    def is_ignored(self, filename):
1142
995
        r"""Check whether the filename matches an ignore pattern.
1156
1009
        # treat dotfiles correctly and allows * to match /.
1157
1010
        # Eventually it should be replaced with something more
1158
1011
        # accurate.
1159
 
    
1160
 
        rules = self._get_ignore_rules_as_regex()
1161
 
        for regex, mapping in rules:
1162
 
            match = regex.match(filename)
1163
 
            if match is not None:
1164
 
                # one or more of the groups in mapping will have a non-None group 
1165
 
                # match.
1166
 
                groups = match.groups()
1167
 
                rules = [mapping[group] for group in 
1168
 
                    mapping if groups[group] is not None]
1169
 
                return rules[0]
1170
 
        return None
 
1012
        
 
1013
        for pat in self.get_ignore_list():
 
1014
            if '/' in pat or '\\' in pat:
 
1015
                
 
1016
                # as a special case, you can put ./ at the start of a
 
1017
                # pattern; this is good to match in the top-level
 
1018
                # only;
 
1019
                
 
1020
                if (pat[:2] == './') or (pat[:2] == '.\\'):
 
1021
                    newpat = pat[2:]
 
1022
                else:
 
1023
                    newpat = pat
 
1024
                if fnmatch.fnmatchcase(filename, newpat):
 
1025
                    return pat
 
1026
            else:
 
1027
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
 
1028
                    return pat
 
1029
        else:
 
1030
            return None
1171
1031
 
1172
1032
    def kind(self, file_id):
1173
1033
        return file_kind(self.id2abspath(file_id))
1224
1084
        if new_revision is None:
1225
1085
            self.branch.set_revision_history([])
1226
1086
            return False
 
1087
        # current format is locked in with the branch
 
1088
        revision_history = self.branch.revision_history()
1227
1089
        try:
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])
 
1090
            position = revision_history.index(new_revision)
 
1091
        except ValueError:
 
1092
            raise errors.NoSuchRevision(self.branch, new_revision)
 
1093
        self.branch.set_revision_history(revision_history[:position + 1])
1232
1094
        return True
1233
1095
 
1234
1096
    def _cache_basis_inventory(self, new_revision):
1235
1097
        """Cache new_revision as the basis inventory."""
1236
 
        # TODO: this should allow the ready-to-use inventory to be passed in,
1237
 
        # as commit already has that ready-to-use [while the format is the
1238
 
        # same, that is].
1239
1098
        try:
1240
1099
            # this double handles the inventory - unpack and repack - 
1241
1100
            # but is easier to understand. We can/should put a conditional
1242
1101
            # in here based on whether the inventory is in the latest format
1243
1102
            # - perhaps we should repack all inventories on a repository
1244
1103
            # upgrade ?
1245
 
            # the fast path is to copy the raw xml from the repository. If the
1246
 
            # xml contains 'revision_id="', then we assume the right 
1247
 
            # revision_id is set. We must check for this full string, because a
1248
 
            # root node id can legitimately look like 'revision_id' but cannot
1249
 
            # contain a '"'.
1250
 
            xml = self.branch.repository.get_inventory_xml(new_revision)
1251
 
            if not 'revision_id="' in xml.split('\n', 1)[0]:
1252
 
                inv = self.branch.repository.deserialise_inventory(
1253
 
                    new_revision, xml)
1254
 
                inv.revision_id = new_revision
1255
 
                xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1256
 
            assert isinstance(xml, str), 'serialised xml must be bytestring.'
 
1104
            inv = self.branch.repository.get_inventory(new_revision)
 
1105
            inv.revision_id = new_revision
 
1106
            xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1107
 
1257
1108
            path = self._basis_inventory_name()
1258
 
            sio = StringIO(xml)
1259
 
            self._control_files.put(path, sio)
1260
 
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
 
1109
            self._control_files.put_utf8(path, xml)
 
1110
        except WeaveRevisionNotPresent:
1261
1111
            pass
1262
1112
 
1263
1113
    def read_basis_inventory(self):
1264
1114
        """Read the cached basis inventory."""
1265
1115
        path = self._basis_inventory_name()
1266
 
        return self._control_files.get(path).read()
 
1116
        return self._control_files.get_utf8(path).read()
1267
1117
        
1268
1118
    @needs_read_lock
1269
1119
    def read_working_inventory(self):
1276
1126
        return result
1277
1127
 
1278
1128
    @needs_write_lock
1279
 
    def remove(self, files, verbose=False, to_file=None):
 
1129
    def remove(self, files, verbose=False):
1280
1130
        """Remove nominated files from the working inventory..
1281
1131
 
1282
1132
        This does not remove their text.  This does not run on XXX on what? RBC
1304
1154
                # TODO: Perhaps make this just a warning, and continue?
1305
1155
                # This tends to happen when 
1306
1156
                raise NotVersionedError(path=f)
 
1157
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1307
1158
            if verbose:
1308
1159
                # having remove it, it must be either ignored or unknown
1309
1160
                if self.is_ignored(f):
1310
1161
                    new_status = 'I'
1311
1162
                else:
1312
1163
                    new_status = '?'
1313
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1164
                show_status(new_status, inv[fid].kind, quotefn(f))
1314
1165
            del inv[fid]
1315
1166
 
1316
1167
        self._write_inventory(inv)
1378
1229
        between multiple working trees, i.e. via shared storage, then we 
1379
1230
        would probably want to lock both the local tree, and the branch.
1380
1231
        """
1381
 
        raise NotImplementedError(self.unlock)
 
1232
        # FIXME: We want to write out the hashcache only when the last lock on
 
1233
        # this working copy is released.  Peeking at the lock count is a bit
 
1234
        # of a nasty hack; probably it's better to have a transaction object,
 
1235
        # which can do some finalization when it's either successfully or
 
1236
        # unsuccessfully completed.  (Denys's original patch did that.)
 
1237
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1238
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1239
        
 
1240
        # TODO: split this per format so there is no ugly if block
 
1241
        if self._hashcache.needs_write and (
 
1242
            # dedicated lock files
 
1243
            self._control_files._lock_count==1 or 
 
1244
            # shared lock files
 
1245
            (self._control_files is self.branch.control_files and 
 
1246
             self._control_files._lock_count==3)):
 
1247
            self._hashcache.write()
 
1248
        # reverse order of locking.
 
1249
        try:
 
1250
            return self._control_files.unlock()
 
1251
        finally:
 
1252
            self.branch.unlock()
1382
1253
 
1383
1254
    @needs_write_lock
1384
1255
    def update(self):
1416
1287
                                      this_tree=self)
1417
1288
                self.set_last_revision(self.branch.last_revision())
1418
1289
            if old_tip and old_tip != self.last_revision():
1419
 
                # our last revision was not the prior branch last revision
 
1290
                # our last revision was not the prior branch last reivison
1420
1291
                # and we have converted that last revision to a pending merge.
1421
1292
                # base is somewhere between the branch tip now
1422
1293
                # and the now pending merge
1450
1321
    def set_conflicts(self, arg):
1451
1322
        raise UnsupportedOperation(self.set_conflicts, self)
1452
1323
 
1453
 
    def add_conflicts(self, arg):
1454
 
        raise UnsupportedOperation(self.add_conflicts, self)
1455
 
 
1456
1324
    @needs_read_lock
1457
1325
    def conflicts(self):
1458
1326
        conflicts = ConflictList()
1461
1329
            try:
1462
1330
                if file_kind(self.abspath(conflicted)) != "file":
1463
1331
                    text = False
1464
 
            except errors.NoSuchFile:
1465
 
                text = False
 
1332
            except OSError, e:
 
1333
                if e.errno == errno.ENOENT:
 
1334
                    text = False
 
1335
                else:
 
1336
                    raise
1466
1337
            if text is True:
1467
1338
                for suffix in ('.THIS', '.OTHER'):
1468
1339
                    try:
1469
1340
                        kind = file_kind(self.abspath(conflicted+suffix))
1470
 
                        if kind != "file":
 
1341
                    except OSError, e:
 
1342
                        if e.errno == errno.ENOENT:
1471
1343
                            text = False
1472
 
                    except errors.NoSuchFile:
 
1344
                            break
 
1345
                        else:
 
1346
                            raise
 
1347
                    if kind != "file":
1473
1348
                        text = False
1474
 
                    if text == False:
1475
1349
                        break
1476
1350
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1477
1351
            conflicts.append(Conflict.factory(ctype, path=conflicted,
1479
1353
        return conflicts
1480
1354
 
1481
1355
 
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
 
 
1501
1356
class WorkingTree3(WorkingTree):
1502
1357
    """This is the Format 3 working tree.
1503
1358
 
1525
1380
                pass
1526
1381
            return False
1527
1382
        else:
 
1383
            try:
 
1384
                self.branch.revision_history().index(revision_id)
 
1385
            except ValueError:
 
1386
                raise errors.NoSuchRevision(self.branch, revision_id)
1528
1387
            self._control_files.put_utf8('last-revision', revision_id)
1529
1388
            return True
1530
1389
 
1533
1392
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1534
1393
                      CONFLICT_HEADER_1)
1535
1394
 
1536
 
    @needs_write_lock
1537
 
    def add_conflicts(self, new_conflicts):
1538
 
        conflict_set = set(self.conflicts())
1539
 
        conflict_set.update(set(list(new_conflicts)))
1540
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1541
 
                                               key=Conflict.sort_key)))
1542
 
 
1543
1395
    @needs_read_lock
1544
1396
    def conflicts(self):
1545
1397
        try:
1553
1405
            raise ConflictFormatError()
1554
1406
        return ConflictList.from_stanzas(RioReader(confile))
1555
1407
 
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
 
 
1565
1408
 
1566
1409
def get_conflicted_stem(path):
1567
1410
    for suffix in CONFLICT_SUFFIXES:
1618
1461
        except NoSuchFile:
1619
1462
            raise errors.NoWorkingTree(base=transport.base)
1620
1463
        except KeyError:
1621
 
            raise errors.UnknownFormatError(format=format_string)
 
1464
            raise errors.UnknownFormatError(format_string)
1622
1465
 
1623
1466
    @classmethod
1624
1467
    def get_default_format(klass):
1701
1544
                branch.unlock()
1702
1545
        revision = branch.last_revision()
1703
1546
        inv = Inventory() 
1704
 
        wt = WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
 
1547
        wt = WorkingTree(a_bzrdir.root_transport.base,
1705
1548
                         branch,
1706
1549
                         inv,
1707
1550
                         _internal=True,
1729
1572
            raise NotImplementedError
1730
1573
        if not isinstance(a_bzrdir.transport, LocalTransport):
1731
1574
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1732
 
        return WorkingTree2(a_bzrdir.root_transport.local_abspath('.'),
 
1575
        return WorkingTree(a_bzrdir.root_transport.base,
1733
1576
                           _internal=True,
1734
1577
                           _format=self,
1735
1578
                           _bzrdir=a_bzrdir)
1744
1587
          files, separate from the BzrDir format
1745
1588
        - modifies the hash cache format
1746
1589
        - is new in bzr 0.8
1747
 
        - uses a LockDir to guard access for writes.
 
1590
        - uses a LockDir to guard access to the repository
1748
1591
    """
1749
1592
 
1750
1593
    def get_format_string(self):
1766
1609
    def initialize(self, a_bzrdir, revision_id=None):
1767
1610
        """See WorkingTreeFormat.initialize().
1768
1611
        
1769
 
        revision_id allows creating a working tree at a different
 
1612
        revision_id allows creating a working tree at a differnet
1770
1613
        revision than the branch is at.
1771
1614
        """
1772
1615
        if not isinstance(a_bzrdir.transport, LocalTransport):
1780
1623
        if revision_id is None:
1781
1624
            revision_id = branch.last_revision()
1782
1625
        inv = Inventory() 
1783
 
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1626
        wt = WorkingTree3(a_bzrdir.root_transport.base,
1784
1627
                         branch,
1785
1628
                         inv,
1786
1629
                         _internal=True,
1814
1657
            raise NotImplementedError
1815
1658
        if not isinstance(a_bzrdir.transport, LocalTransport):
1816
1659
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
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
 
        """
1825
 
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1660
        control_files = self._open_control_files(a_bzrdir)
 
1661
        return WorkingTree3(a_bzrdir.root_transport.base,
1826
1662
                           _internal=True,
1827
1663
                           _format=self,
1828
1664
                           _bzrdir=a_bzrdir,
1855
1691
        self._transport_readonly_server = transport_readonly_server
1856
1692
        self._formats = formats
1857
1693
    
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
 
    
1871
1694
    def adapt(self, test):
1872
1695
        from bzrlib.tests import TestSuite
1873
1696
        result = TestSuite()
1874
1697
        for workingtree_format, bzrdir_format in self._formats:
1875
 
            new_test = self._clone_test(
1876
 
                test,
1877
 
                bzrdir_format,
1878
 
                workingtree_format, workingtree_format.__class__.__name__)
 
1698
            new_test = deepcopy(test)
 
1699
            new_test.transport_server = self._transport_server
 
1700
            new_test.transport_readonly_server = self._transport_readonly_server
 
1701
            new_test.bzrdir_format = bzrdir_format
 
1702
            new_test.workingtree_format = workingtree_format
 
1703
            def make_new_test_id():
 
1704
                new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
 
1705
                return lambda: new_id
 
1706
            new_test.id = make_new_test_id()
1879
1707
            result.addTest(new_test)
1880
1708
        return result