~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Michael Ellerman
  • Date: 2006-02-28 14:45:51 UTC
  • mto: (1558.1.18 Aaron's integration)
  • mto: This revision was merged to the branch mainline in revision 1586.
  • Revision ID: michael@ellerman.id.au-20060228144551-3d9941ecde4a0b0a
Update contrib/pwk for -p1 diffs from bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005 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
29
29
WorkingTree.open(dir).
30
30
"""
31
31
 
32
 
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
 
CONFLICT_HEADER_1 = "BZR conflict list format 1"
 
32
 
 
33
# FIXME: I don't know if writing out the cache from the destructor is really a
 
34
# good idea, because destructors are considered poor taste in Python, and it's
 
35
# not predictable when it will be written out.
34
36
 
35
37
# TODO: Give the workingtree sole responsibility for the working inventory;
36
38
# remove the variable and references to it from the branch.  This may require
37
39
# updating the commit code so as to update the inventory within the working
38
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
39
 
# At the moment they may alias the inventory and have old copies of it in
40
 
# memory.  (Now done? -- mbp 20060309)
 
41
# At the momenthey may alias the inventory and have old copies of it in memory.
41
42
 
42
 
from binascii import hexlify
43
 
import collections
44
43
from copy import deepcopy
45
44
from cStringIO import StringIO
46
45
import errno
47
46
import fnmatch
48
47
import os
49
 
import re
50
48
import stat
51
 
from time import time
52
 
import warnings
 
49
 
53
50
 
54
 
from bzrlib import bzrdir, errors, osutils, urlutils
55
51
from bzrlib.atomicfile import AtomicFile
56
 
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
 
52
from bzrlib.branch import (Branch,
 
53
                           quotefn)
 
54
import bzrlib.bzrdir as bzrdir
57
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
56
import bzrlib.errors as errors
58
57
from bzrlib.errors import (BzrCheckError,
59
58
                           BzrError,
60
 
                           ConflictFormatError,
 
59
                           DivergedBranches,
61
60
                           WeaveRevisionNotPresent,
62
61
                           NotBranchError,
63
62
                           NoSuchFile,
64
 
                           NotVersionedError,
65
 
                           MergeModifiedFormatError,
66
 
                           UnsupportedOperation,
67
 
                           )
 
63
                           NotVersionedError)
68
64
from bzrlib.inventory import InventoryEntry, Inventory
69
 
from bzrlib.lockable_files import LockableFiles, TransportLock
70
 
from bzrlib.lockdir import LockDir
 
65
from bzrlib.lockable_files import LockableFiles
71
66
from bzrlib.merge import merge_inner, transform_tree
72
 
from bzrlib.osutils import (
73
 
                            abspath,
 
67
from bzrlib.osutils import (appendpath,
74
68
                            compact_date,
75
69
                            file_kind,
76
70
                            isdir,
79
73
                            pumpfile,
80
74
                            safe_unicode,
81
75
                            splitpath,
82
 
                            rand_chars,
 
76
                            rand_bytes,
 
77
                            abspath,
83
78
                            normpath,
84
79
                            realpath,
85
80
                            relpath,
86
81
                            rename,
87
82
                            supports_executable,
88
83
                            )
89
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
84
from bzrlib.progress import DummyProgress
90
85
from bzrlib.revision import NULL_REVISION
91
 
from bzrlib.rio import RioReader, rio_file, Stanza
92
 
from bzrlib.symbol_versioning import (deprecated_passed,
93
 
        deprecated_method,
94
 
        deprecated_function,
95
 
        DEPRECATED_PARAMETER,
96
 
        zero_eight,
97
 
        )
98
 
 
 
86
from bzrlib.symbol_versioning import *
99
87
from bzrlib.textui import show_status
100
88
import bzrlib.tree
 
89
from bzrlib.trace import mutter
101
90
from bzrlib.transform import build_tree
102
 
from bzrlib.trace import mutter, note
103
91
from bzrlib.transport import get_transport
104
92
from bzrlib.transport.local import LocalTransport
105
93
import bzrlib.ui
106
94
import bzrlib.xml5
107
95
 
108
96
 
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.]|(^\.*)')
114
 
_gen_id_suffix = None
115
 
_gen_id_serial = 0
116
 
 
117
 
 
118
 
def _next_id_suffix():
119
 
    """Create a new file id suffix that is reasonably unique.
120
 
    
121
 
    On the first call we combine the current time with 64 bits of randomness
122
 
    to give a highly probably globally unique number. Then each call in the same
123
 
    process adds 1 to a serial number we append to that unique value.
124
 
    """
125
 
    # XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather 
126
 
    # than having to move the id randomness out of the inner loop like this.
127
 
    # XXX TODO: for the global randomness this uses we should add the thread-id
128
 
    # before the serial #.
129
 
    global _gen_id_suffix, _gen_id_serial
130
 
    if _gen_id_suffix is None:
131
 
        _gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
132
 
    _gen_id_serial += 1
133
 
    return _gen_id_suffix + str(_gen_id_serial)
134
 
 
135
 
 
136
97
def gen_file_id(name):
137
 
    """Return new file id for the basename 'name'.
138
 
 
139
 
    The uniqueness is supplied from _next_id_suffix.
140
 
    """
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()
 
98
    """Return new file id.
 
99
 
 
100
    This should probably generate proper UUIDs, but for the moment we
 
101
    cope with just randomness because running uuidgen every time is
 
102
    slow."""
 
103
    import re
 
104
    from binascii import hexlify
 
105
    from time import time
 
106
 
 
107
    # get last component
 
108
    idx = name.rfind('/')
 
109
    if idx != -1:
 
110
        name = name[idx+1 : ]
 
111
    idx = name.rfind('\\')
 
112
    if idx != -1:
 
113
        name = name[idx+1 : ]
 
114
 
 
115
    # make it not a hidden file
 
116
    name = name.lstrip('.')
 
117
 
 
118
    # remove any wierd characters; we don't escape them but rather
 
119
    # just pull them out
 
120
    name = re.sub(r'[^\w.]', '', name)
 
121
 
 
122
    s = hexlify(rand_bytes(8))
 
123
    return '-'.join((name, compact_date(time()), s))
146
124
 
147
125
 
148
126
def gen_root_id():
151
129
 
152
130
 
153
131
class TreeEntry(object):
154
 
    """An entry that implements the minimum interface used by commands.
 
132
    """An entry that implements the minium interface used by commands.
155
133
 
156
134
    This needs further inspection, it may be better to have 
157
135
    InventoryEntries without ids - though that seems wrong. For now,
233
211
        self.bzrdir = _bzrdir
234
212
        if not _internal:
235
213
            # not created via open etc.
236
 
            warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
214
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
237
215
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
238
216
                 DeprecationWarning,
239
217
                 stacklevel=2)
240
218
            wt = WorkingTree.open(basedir)
241
 
            self._branch = wt.branch
 
219
            self.branch = wt.branch
242
220
            self.basedir = wt.basedir
243
221
            self._control_files = wt._control_files
244
222
            self._hashcache = wt._hashcache
253
231
        mutter("opening working tree %r", basedir)
254
232
        if deprecated_passed(branch):
255
233
            if not _internal:
256
 
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
257
 
                     " Please use bzrdir.open_workingtree() or"
258
 
                     " WorkingTree.open().",
 
234
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
235
                     " Please use bzrdir.open_workingtree() or WorkingTree.open().",
259
236
                     DeprecationWarning,
260
237
                     stacklevel=2
261
238
                     )
262
 
            self._branch = branch
 
239
            self.branch = branch
263
240
        else:
264
 
            self._branch = self.bzrdir.open_branch()
 
241
            self.branch = self.bzrdir.open_branch()
 
242
        assert isinstance(self.branch, Branch), \
 
243
            "branch %r is not a Branch" % self.branch
265
244
        self.basedir = realpath(basedir)
266
245
        # if branch is at our basedir and is a format 6 or less
267
246
        if isinstance(self._format, WorkingTreeFormat2):
268
247
            # share control object
269
248
            self._control_files = self.branch.control_files
 
249
        elif _control_files is not None:
 
250
            assert False, "not done yet"
 
251
#            self._control_files = _control_files
270
252
        else:
271
253
            # only ready for format 3
272
254
            assert isinstance(self._format, WorkingTreeFormat3)
273
 
            assert isinstance(_control_files, LockableFiles), \
274
 
                    "_control_files must be a LockableFiles, not %r" \
275
 
                    % _control_files
276
 
            self._control_files = _control_files
 
255
            self._control_files = LockableFiles(
 
256
                self.bzrdir.get_workingtree_transport(None),
 
257
                'lock')
 
258
 
277
259
        # update the whole cache up front and write to disk if anything changed;
278
260
        # in the future we might want to do this more selectively
279
261
        # two possible ways offer themselves : in self._unlock, write the cache
280
262
        # if needed, or, when the cache sees a change, append it to the hash
281
263
        # cache file, and have the parser take the most recent entry for a
282
264
        # given path only.
283
 
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
 
265
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
284
266
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
285
267
        hc.read()
286
268
        # is this scan needed ? it makes things kinda slow.
287
 
        #hc.scan()
 
269
        hc.scan()
288
270
 
289
271
        if hc.needs_write:
290
272
            mutter("write hc")
295
277
        else:
296
278
            self._set_inventory(_inventory)
297
279
 
298
 
    branch = property(
299
 
        fget=lambda self: self._branch,
300
 
        doc="""The branch this WorkingTree is connected to.
301
 
 
302
 
            This cannot be set - it is reflective of the actual disk structure
303
 
            the working tree has been constructed from.
304
 
            """)
305
 
 
306
 
    def break_lock(self):
307
 
        """Break a lock if one is present from another instance.
308
 
 
309
 
        Uses the ui factory to ask for confirmation if the lock may be from
310
 
        an active process.
311
 
 
312
 
        This will probe the repository for its lock as well.
313
 
        """
314
 
        self._control_files.break_lock()
315
 
        self.branch.break_lock()
316
 
 
317
280
    def _set_inventory(self, inv):
318
281
        self._inventory = inv
319
282
        self.path2id = self._inventory.path2id
321
284
    def is_control_filename(self, filename):
322
285
        """True if filename is the name of a control file in this tree.
323
286
        
324
 
        :param filename: A filename within the tree. This is a relative path
325
 
        from the root of this tree.
326
 
 
327
287
        This is true IF and ONLY IF the filename is part of the meta data
328
288
        that bzr controls in this tree. I.E. a random .bzr directory placed
329
289
        on disk will not be a control file for this tree.
330
290
        """
331
 
        return self.bzrdir.is_control_filename(filename)
 
291
        try:
 
292
            self.bzrdir.transport.relpath(self.abspath(filename))
 
293
            return True
 
294
        except errors.PathNotChild:
 
295
            return False
332
296
 
333
297
    @staticmethod
334
298
    def open(path=None, _unsupported=False):
350
314
        run into /.  If there isn't one, raises NotBranchError.
351
315
        TODO: give this a new exception.
352
316
        If there is one, it is returned, along with the unused portion of path.
353
 
 
354
 
        :return: The WorkingTree that contains 'path', and the rest of path
355
317
        """
356
318
        if path is None:
357
 
            path = osutils.getcwd()
 
319
            path = os.getcwdu()
358
320
        control, relpath = bzrdir.BzrDir.open_containing(path)
359
 
 
360
321
        return control.open_workingtree(), relpath
361
322
 
362
323
    @staticmethod
390
351
        revision_id = self.last_revision()
391
352
        if revision_id is not None:
392
353
            try:
393
 
                xml = self.read_basis_inventory()
 
354
                xml = self.read_basis_inventory(revision_id)
394
355
                inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
395
 
            except NoSuchFile:
396
 
                inv = None
397
 
            if inv is not None and inv.revision_id == revision_id:
398
356
                return bzrlib.tree.RevisionTree(self.branch.repository, inv,
399
357
                                                revision_id)
400
 
        # FIXME? RBC 20060403 should we cache the inventory here ?
 
358
            except NoSuchFile:
 
359
                pass
401
360
        return self.branch.repository.revision_tree(revision_id)
402
361
 
403
362
    @staticmethod
417
376
        XXX: When BzrDir is present, these should be created through that 
418
377
        interface instead.
419
378
        """
420
 
        warnings.warn('delete WorkingTree.create', stacklevel=3)
 
379
        warn('delete WorkingTree.create', stacklevel=3)
421
380
        transport = get_transport(directory)
422
381
        if branch.bzrdir.root_transport.base == transport.base:
423
382
            # same dir 
438
397
        """
439
398
        return bzrdir.BzrDir.create_standalone_workingtree(directory)
440
399
 
441
 
    def relpath(self, path):
442
 
        """Return the local path portion from a given path.
443
 
        
444
 
        The path may be absolute or relative. If its a relative path it is 
445
 
        interpreted relative to the python current working directory.
446
 
        """
447
 
        return relpath(self.basedir, path)
 
400
    def relpath(self, abs):
 
401
        """Return the local path portion from a given absolute path."""
 
402
        return relpath(self.basedir, abs)
448
403
 
449
404
    def has_filename(self, filename):
450
405
        return bzrlib.osutils.lexists(self.abspath(filename))
455
410
    def get_file_byname(self, filename):
456
411
        return file(self.abspath(filename), 'rb')
457
412
 
458
 
    def get_parent_ids(self):
459
 
        """See Tree.get_parent_ids.
460
 
        
461
 
        This implementation reads the pending merges list and last_revision
462
 
        value and uses that to decide what the parents list should be.
463
 
        """
464
 
        last_rev = self.last_revision()
465
 
        if last_rev is None:
466
 
            parents = []
467
 
        else:
468
 
            parents = [last_rev]
469
 
        other_parents = self.pending_merges()
470
 
        return parents + other_parents
471
 
 
472
413
    def get_root_id(self):
473
414
        """Return the id of this trees root"""
474
415
        inv = self.read_working_inventory()
512
453
            tree.set_last_revision(revision_id)
513
454
 
514
455
    @needs_write_lock
515
 
    def commit(self, message=None, revprops=None, *args, **kwargs):
516
 
        # avoid circular imports
 
456
    def commit(self, *args, **kwargs):
517
457
        from bzrlib.commit import Commit
518
 
        if revprops is None:
519
 
            revprops = {}
520
 
        if not 'branch-nick' in revprops:
521
 
            revprops['branch-nick'] = self.branch.nick
522
458
        # args for wt.commit start at message from the Commit.commit method,
523
459
        # but with branch a kwarg now, passing in args as is results in the
524
460
        #message being used for the branch
525
 
        args = (DEPRECATED_PARAMETER, message, ) + args
526
 
        committed_id = Commit().commit( working_tree=self, revprops=revprops,
527
 
            *args, **kwargs)
 
461
        args = (DEPRECATED_PARAMETER, ) + args
 
462
        Commit().commit(working_tree=self, *args, **kwargs)
528
463
        self._set_inventory(self.read_working_inventory())
529
 
        return committed_id
530
464
 
531
465
    def id2abspath(self, file_id):
532
466
        return self.abspath(self.id2path(file_id))
550
484
        return os.path.getsize(self.id2abspath(file_id))
551
485
 
552
486
    @needs_read_lock
553
 
    def get_file_sha1(self, file_id, path=None):
554
 
        if not path:
555
 
            path = self._inventory.id2path(file_id)
 
487
    def get_file_sha1(self, file_id):
 
488
        path = self._inventory.id2path(file_id)
556
489
        return self._hashcache.get_sha1(path)
557
490
 
558
 
    def get_file_mtime(self, file_id, path=None):
559
 
        if not path:
560
 
            path = self._inventory.id2path(file_id)
561
 
        return os.lstat(self.abspath(path)).st_mtime
562
 
 
563
 
    if not supports_executable():
564
 
        def is_executable(self, file_id, path=None):
 
491
    def is_executable(self, file_id):
 
492
        if not supports_executable():
565
493
            return self._inventory[file_id].executable
566
 
    else:
567
 
        def is_executable(self, file_id, path=None):
568
 
            if not path:
569
 
                path = self._inventory.id2path(file_id)
 
494
        else:
 
495
            path = self._inventory.id2path(file_id)
570
496
            mode = os.lstat(self.abspath(path)).st_mode
571
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
497
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
572
498
 
573
499
    @needs_write_lock
574
500
    def add(self, files, ids=None):
609
535
        inv = self.read_working_inventory()
610
536
        for f,file_id in zip(files, ids):
611
537
            if self.is_control_filename(f):
612
 
                raise errors.ForbiddenControlFileError(filename=f)
 
538
                raise BzrError("cannot add control file %s" % quotefn(f))
613
539
 
614
540
            fp = splitpath(f)
615
541
 
617
543
                raise BzrError("cannot add top-level %r" % f)
618
544
 
619
545
            fullpath = normpath(self.abspath(f))
 
546
 
620
547
            try:
621
548
                kind = file_kind(fullpath)
622
549
            except OSError, e:
623
550
                if e.errno == errno.ENOENT:
624
551
                    raise NoSuchFile(fullpath)
 
552
                # maybe something better?
 
553
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
554
 
625
555
            if not InventoryEntry.versionable_kind(kind):
626
 
                raise errors.BadFileKindError(filename=f, kind=kind)
 
556
                raise BzrError('cannot add: not a versionable file ('
 
557
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
558
 
627
559
            if file_id is None:
628
 
                inv.add_path(f, kind=kind)
629
 
            else:
630
 
                inv.add_path(f, kind=kind, file_id=file_id)
 
560
                file_id = gen_file_id(f)
 
561
            inv.add_path(f, kind=kind, file_id=file_id)
631
562
 
 
563
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
632
564
        self._write_inventory(inv)
633
565
 
634
566
    @needs_write_lock
654
586
        """
655
587
        try:
656
588
            merges_file = self._control_files.get_utf8('pending-merges')
657
 
        except NoSuchFile:
 
589
        except OSError, e:
 
590
            if e.errno != errno.ENOENT:
 
591
                raise
658
592
            return []
659
593
        p = []
660
594
        for l in merges_file.readlines():
665
599
    def set_pending_merges(self, rev_list):
666
600
        self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
667
601
 
668
 
    @needs_write_lock
669
 
    def set_merge_modified(self, modified_hashes):
670
 
        def iter_stanzas():
671
 
            for file_id, hash in modified_hashes.iteritems():
672
 
                yield Stanza(file_id=file_id, hash=hash)
673
 
        self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
674
 
 
675
 
    @needs_write_lock
676
 
    def _put_rio(self, filename, stanzas, header):
677
 
        my_file = rio_file(stanzas, header)
678
 
        self._control_files.put(filename, my_file)
679
 
 
680
 
    @needs_read_lock
681
 
    def merge_modified(self):
682
 
        try:
683
 
            hashfile = self._control_files.get('merge-hashes')
684
 
        except NoSuchFile:
685
 
            return {}
686
 
        merge_hashes = {}
687
 
        try:
688
 
            if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
689
 
                raise MergeModifiedFormatError()
690
 
        except StopIteration:
691
 
            raise MergeModifiedFormatError()
692
 
        for s in RioReader(hashfile):
693
 
            file_id = s.get("file_id")
694
 
            if file_id not in self.inventory:
695
 
                continue
696
 
            hash = s.get("hash")
697
 
            if hash == self.get_file_sha1(file_id):
698
 
                merge_hashes[file_id] = hash
699
 
        return merge_hashes
700
 
 
701
602
    def get_symlink_target(self, file_id):
702
603
        return os.readlink(self.id2abspath(file_id))
703
604
 
710
611
            return '?'
711
612
 
712
613
    def list_files(self):
713
 
        """Recursively list all files as (path, class, kind, id, entry).
 
614
        """Recursively list all files as (path, class, kind, id).
714
615
 
715
616
        Lists, but does not descend into unversioned directories.
716
617
 
720
621
        Skips the control directory.
721
622
        """
722
623
        inv = self._inventory
723
 
        # Convert these into local objects to save lookup times
724
 
        pathjoin = bzrlib.osutils.pathjoin
725
 
        file_kind = bzrlib.osutils.file_kind
726
 
 
727
 
        # transport.base ends in a slash, we want the piece
728
 
        # between the last two slashes
729
 
        transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
730
 
 
731
 
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
732
 
 
733
 
        # directory file_id, relative path, absolute path, reverse sorted children
734
 
        children = os.listdir(self.basedir)
735
 
        children.sort()
736
 
        # jam 20060527 The kernel sized tree seems equivalent whether we 
737
 
        # use a deque and popleft to keep them sorted, or if we use a plain
738
 
        # list and just reverse() them.
739
 
        children = collections.deque(children)
740
 
        stack = [(inv.root.file_id, u'', self.basedir, children)]
741
 
        while stack:
742
 
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
743
 
 
744
 
            while children:
745
 
                f = children.popleft()
 
624
 
 
625
        def descend(from_dir_relpath, from_dir_id, dp):
 
626
            ls = os.listdir(dp)
 
627
            ls.sort()
 
628
            for f in ls:
746
629
                ## TODO: If we find a subdirectory with its own .bzr
747
630
                ## directory, then that is a separate tree and we
748
631
                ## should exclude it.
749
632
 
750
633
                # the bzrdir for this tree
751
 
                if transport_base_dir == f:
 
634
                if self.bzrdir.transport.base.endswith(f + '/'):
752
635
                    continue
753
636
 
754
 
                # we know that from_dir_relpath and from_dir_abspath never end in a slash
755
 
                # and 'f' doesn't begin with one, we can do a string op, rather
756
 
                # than the checks of pathjoin(), all relative paths will have an extra slash
757
 
                # at the beginning
758
 
                fp = from_dir_relpath + '/' + f
 
637
                # path within tree
 
638
                fp = appendpath(from_dir_relpath, f)
759
639
 
760
640
                # absolute path
761
 
                fap = from_dir_abspath + '/' + f
 
641
                fap = appendpath(dp, f)
762
642
                
763
643
                f_ie = inv.get_child(from_dir_id, f)
764
644
                if f_ie:
765
645
                    c = 'V'
766
 
                elif self.is_ignored(fp[1:]):
 
646
                elif self.is_ignored(fp):
767
647
                    c = 'I'
768
648
                else:
769
 
                    # we may not have found this file, because of a unicode issue
770
 
                    f_norm, can_access = osutils.normalized_filename(f)
771
 
                    if f == f_norm or not can_access:
772
 
                        # No change, so treat this file normally
773
 
                        c = '?'
774
 
                    else:
775
 
                        # this file can be accessed by a normalized path
776
 
                        # check again if it is versioned
777
 
                        # these lines are repeated here for performance
778
 
                        f = f_norm
779
 
                        fp = from_dir_relpath + '/' + f
780
 
                        fap = from_dir_abspath + '/' + f
781
 
                        f_ie = inv.get_child(from_dir_id, f)
782
 
                        if f_ie:
783
 
                            c = 'V'
784
 
                        elif self.is_ignored(fp[1:]):
785
 
                            c = 'I'
786
 
                        else:
787
 
                            c = '?'
 
649
                    c = '?'
788
650
 
789
651
                fk = file_kind(fap)
790
652
 
796
658
 
797
659
                # make a last minute entry
798
660
                if f_ie:
799
 
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
 
661
                    entry = f_ie
800
662
                else:
801
 
                    try:
802
 
                        yield fp[1:], c, fk, None, fk_entries[fk]()
803
 
                    except KeyError:
804
 
                        yield fp[1:], c, fk, None, TreeEntry()
805
 
                    continue
 
663
                    if fk == 'directory':
 
664
                        entry = TreeDirectory()
 
665
                    elif fk == 'file':
 
666
                        entry = TreeFile()
 
667
                    elif fk == 'symlink':
 
668
                        entry = TreeLink()
 
669
                    else:
 
670
                        entry = TreeEntry()
806
671
                
 
672
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
 
673
 
807
674
                if fk != 'directory':
808
675
                    continue
809
676
 
810
 
                # But do this child first
811
 
                new_children = os.listdir(fap)
812
 
                new_children.sort()
813
 
                new_children = collections.deque(new_children)
814
 
                stack.append((f_ie.file_id, fp, fap, new_children))
815
 
                # Break out of inner loop, so that we start outer loop with child
816
 
                break
817
 
            else:
818
 
                # if we finished all children, pop it off the stack
819
 
                stack.pop()
 
677
                if c != 'V':
 
678
                    # don't descend unversioned directories
 
679
                    continue
 
680
                
 
681
                for ff in descend(fp, f_ie.file_id, fap):
 
682
                    yield ff
820
683
 
 
684
        for f in descend(u'', inv.root.file_id, self.basedir):
 
685
            yield f
821
686
 
822
687
    @needs_write_lock
823
688
    def move(self, from_paths, to_name):
859
724
            if f_id == None:
860
725
                raise BzrError("%r is not versioned" % f)
861
726
            name_tail = splitpath(f)[-1]
862
 
            dest_path = pathjoin(to_name, name_tail)
 
727
            dest_path = appendpath(to_name, name_tail)
863
728
            if self.has_filename(dest_path):
864
729
                raise BzrError("destination %r already exists" % dest_path)
865
730
            if f_id in to_idpath:
872
737
        try:
873
738
            for f in from_paths:
874
739
                name_tail = splitpath(f)[-1]
875
 
                dest_path = pathjoin(to_name, name_tail)
 
740
                dest_path = appendpath(to_name, name_tail)
876
741
                result.append((f, dest_path))
877
742
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
878
743
                try:
942
807
 
943
808
        These are files in the working directory that are not versioned or
944
809
        control files or ignored.
 
810
        
 
811
        >>> from bzrlib.bzrdir import ScratchDir
 
812
        >>> d = ScratchDir(files=['foo', 'foo~'])
 
813
        >>> b = d.open_branch()
 
814
        >>> tree = d.open_workingtree()
 
815
        >>> map(str, tree.unknowns())
 
816
        ['foo']
 
817
        >>> tree.add('foo')
 
818
        >>> list(b.unknowns())
 
819
        []
 
820
        >>> tree.remove('foo')
 
821
        >>> list(b.unknowns())
 
822
        [u'foo']
945
823
        """
946
824
        for subp in self.extras():
947
825
            if not self.is_ignored(subp):
948
826
                yield subp
949
827
 
950
 
    @deprecated_method(zero_eight)
951
828
    def iter_conflicts(self):
952
 
        """List all files in the tree that have text or content conflicts.
953
 
        DEPRECATED.  Use conflicts instead."""
954
 
        return self._iter_conflicts()
955
 
 
956
 
    def _iter_conflicts(self):
957
829
        conflicted = set()
958
 
        for info in self.list_files():
959
 
            path = info[0]
 
830
        for path in (s[0] for s in self.list_files()):
960
831
            stem = get_conflicted_stem(path)
961
832
            if stem is None:
962
833
                continue
966
837
 
967
838
    @needs_write_lock
968
839
    def pull(self, source, overwrite=False, stop_revision=None):
969
 
        top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
970
840
        source.lock_read()
971
841
        try:
972
 
            pp = ProgressPhase("Pull phase", 2, top_pb)
973
 
            pp.next_phase()
974
842
            old_revision_history = self.branch.revision_history()
975
843
            basis_tree = self.basis_tree()
976
844
            count = self.branch.pull(source, overwrite, stop_revision)
977
845
            new_revision_history = self.branch.revision_history()
978
846
            if new_revision_history != old_revision_history:
979
 
                pp.next_phase()
980
847
                if len(old_revision_history):
981
848
                    other_revision = old_revision_history[-1]
982
849
                else:
983
850
                    other_revision = None
984
851
                repository = self.branch.repository
985
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
986
 
                try:
987
 
                    merge_inner(self.branch,
988
 
                                self.branch.basis_tree(),
989
 
                                basis_tree, 
990
 
                                this_tree=self, 
991
 
                                pb=pb)
992
 
                finally:
993
 
                    pb.finished()
 
852
                merge_inner(self.branch,
 
853
                            self.branch.basis_tree(),
 
854
                            basis_tree, 
 
855
                            this_tree=self, 
 
856
                            pb=bzrlib.ui.ui_factory.progress_bar())
994
857
                self.set_last_revision(self.branch.last_revision())
995
858
            return count
996
859
        finally:
997
860
            source.unlock()
998
 
            top_pb.finished()
999
861
 
1000
862
    def extras(self):
1001
863
        """Yield all unknown files in this WorkingTree.
1016
878
 
1017
879
            fl = []
1018
880
            for subf in os.listdir(dirabs):
1019
 
                if subf == '.bzr':
1020
 
                    continue
1021
 
                if subf not in dir_entry.children:
1022
 
                    subf_norm, can_access = osutils.normalized_filename(subf)
1023
 
                    if subf_norm != subf and can_access:
1024
 
                        if subf_norm not in dir_entry.children:
1025
 
                            fl.append(subf_norm)
1026
 
                    else:
1027
 
                        fl.append(subf)
 
881
                if (subf != '.bzr'
 
882
                    and (subf not in dir_entry.children)):
 
883
                    fl.append(subf)
1028
884
            
1029
885
            fl.sort()
1030
886
            for subf in fl:
1031
 
                subp = pathjoin(path, subf)
 
887
                subp = appendpath(path, subf)
1032
888
                yield subp
1033
889
 
1034
 
    def _translate_ignore_rule(self, rule):
1035
 
        """Translate a single ignore rule to a regex.
1036
 
 
1037
 
        There are two types of ignore rules.  Those that do not contain a / are
1038
 
        matched against the tail of the filename (that is, they do not care
1039
 
        what directory the file is in.)  Rules which do contain a slash must
1040
 
        match the entire path.  As a special case, './' at the start of the
1041
 
        string counts as a slash in the string but is removed before matching
1042
 
        (e.g. ./foo.c, ./src/foo.c)
1043
 
 
1044
 
        :return: The translated regex.
1045
 
        """
1046
 
        if rule[:2] in ('./', '.\\'):
1047
 
            # rootdir rule
1048
 
            result = fnmatch.translate(rule[2:])
1049
 
        elif '/' in rule or '\\' in rule:
1050
 
            # path prefix 
1051
 
            result = fnmatch.translate(rule)
1052
 
        else:
1053
 
            # default rule style.
1054
 
            result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1055
 
        assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1056
 
        return "(" + result + ")"
1057
 
 
1058
 
    def _combine_ignore_rules(self, rules):
1059
 
        """Combine a list of ignore rules into a single regex object.
1060
 
 
1061
 
        Each individual rule is combined with | to form a big regex, which then
1062
 
        has $ added to it to form something like ()|()|()$. The group index for
1063
 
        each subregex's outermost group is placed in a dictionary mapping back 
1064
 
        to the rule. This allows quick identification of the matching rule that
1065
 
        triggered a match.
1066
 
        :return: a list of the compiled regex and the matching-group index 
1067
 
        dictionaries. We return a list because python complains if you try to 
1068
 
        combine more than 100 regexes.
1069
 
        """
1070
 
        result = []
1071
 
        groups = {}
1072
 
        next_group = 0
1073
 
        translated_rules = []
1074
 
        for rule in rules:
1075
 
            translated_rule = self._translate_ignore_rule(rule)
1076
 
            compiled_rule = re.compile(translated_rule)
1077
 
            groups[next_group] = rule
1078
 
            next_group += compiled_rule.groups
1079
 
            translated_rules.append(translated_rule)
1080
 
            if next_group == 99:
1081
 
                result.append((re.compile("|".join(translated_rules)), groups))
1082
 
                groups = {}
1083
 
                next_group = 0
1084
 
                translated_rules = []
1085
 
        if len(translated_rules):
1086
 
            result.append((re.compile("|".join(translated_rules)), groups))
1087
 
        return result
1088
890
 
1089
891
    def ignored_files(self):
1090
892
        """Yield list of PATH, IGNORE_PATTERN"""
1093
895
            if pat != None:
1094
896
                yield subp, pat
1095
897
 
 
898
 
1096
899
    def get_ignore_list(self):
1097
900
        """Return list of ignore patterns.
1098
901
 
1101
904
        if hasattr(self, '_ignorelist'):
1102
905
            return self._ignorelist
1103
906
 
1104
 
        l = []
 
907
        l = bzrlib.DEFAULT_IGNORE[:]
1105
908
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1106
909
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1107
 
            l.extend([line.rstrip("\n\r").decode('utf-8') 
1108
 
                      for line in f.readlines()])
 
910
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
1109
911
        self._ignorelist = l
1110
 
        self._ignore_regex = self._combine_ignore_rules(l)
1111
912
        return l
1112
913
 
1113
 
    def _get_ignore_rules_as_regex(self):
1114
 
        """Return a regex of the ignore rules and a mapping dict.
1115
 
 
1116
 
        :return: (ignore rules compiled regex, dictionary mapping rule group 
1117
 
        indices to original rule.)
1118
 
        """
1119
 
        if getattr(self, '_ignorelist', None) is None:
1120
 
            self.get_ignore_list()
1121
 
        return self._ignore_regex
1122
914
 
1123
915
    def is_ignored(self, filename):
1124
916
        r"""Check whether the filename matches an ignore pattern.
1138
930
        # treat dotfiles correctly and allows * to match /.
1139
931
        # Eventually it should be replaced with something more
1140
932
        # accurate.
1141
 
    
1142
 
        rules = self._get_ignore_rules_as_regex()
1143
 
        for regex, mapping in rules:
1144
 
            match = regex.match(filename)
1145
 
            if match is not None:
1146
 
                # one or more of the groups in mapping will have a non-None group 
1147
 
                # match.
1148
 
                groups = match.groups()
1149
 
                rules = [mapping[group] for group in 
1150
 
                    mapping if groups[group] is not None]
1151
 
                return rules[0]
1152
 
        return None
 
933
        
 
934
        for pat in self.get_ignore_list():
 
935
            if '/' in pat or '\\' in pat:
 
936
                
 
937
                # as a special case, you can put ./ at the start of a
 
938
                # pattern; this is good to match in the top-level
 
939
                # only;
 
940
                
 
941
                if (pat[:2] == './') or (pat[:2] == '.\\'):
 
942
                    newpat = pat[2:]
 
943
                else:
 
944
                    newpat = pat
 
945
                if fnmatch.fnmatchcase(filename, newpat):
 
946
                    return pat
 
947
            else:
 
948
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
 
949
                    return pat
 
950
        else:
 
951
            return None
1153
952
 
1154
953
    def kind(self, file_id):
1155
954
        return file_kind(self.id2abspath(file_id))
1164
963
        """
1165
964
        return self.branch.last_revision()
1166
965
 
1167
 
    def is_locked(self):
1168
 
        return self._control_files.is_locked()
1169
 
 
1170
966
    def lock_read(self):
1171
967
        """See Branch.lock_read, and WorkingTree.unlock."""
1172
968
        self.branch.lock_read()
1185
981
            self.branch.unlock()
1186
982
            raise
1187
983
 
1188
 
    def get_physical_lock_status(self):
1189
 
        return self._control_files.get_physical_lock_status()
1190
 
 
1191
 
    def _basis_inventory_name(self):
1192
 
        return 'basis-inventory'
 
984
    def _basis_inventory_name(self, revision_id):
 
985
        return 'basis-inventory.%s' % revision_id
1193
986
 
1194
987
    @needs_write_lock
1195
 
    def set_last_revision(self, new_revision):
 
988
    def set_last_revision(self, new_revision, old_revision=None):
1196
989
        """Change the last revision in the working tree."""
 
990
        self._remove_old_basis(old_revision)
1197
991
        if self._change_last_revision(new_revision):
1198
992
            self._cache_basis_inventory(new_revision)
1199
993
 
1200
994
    def _change_last_revision(self, new_revision):
1201
 
        """Template method part of set_last_revision to perform the change.
1202
 
        
1203
 
        This is used to allow WorkingTree3 instances to not affect branch
1204
 
        when their last revision is set.
1205
 
        """
 
995
        """Template method part of set_last_revision to perform the change."""
1206
996
        if new_revision is None:
1207
997
            self.branch.set_revision_history([])
1208
998
            return False
1217
1007
 
1218
1008
    def _cache_basis_inventory(self, new_revision):
1219
1009
        """Cache new_revision as the basis inventory."""
1220
 
        # TODO: this should allow the ready-to-use inventory to be passed in,
1221
 
        # as commit already has that ready-to-use [while the format is the
1222
 
        # same, that is].
1223
1010
        try:
1224
 
            # this double handles the inventory - unpack and repack - 
1225
 
            # but is easier to understand. We can/should put a conditional
1226
 
            # in here based on whether the inventory is in the latest format
1227
 
            # - perhaps we should repack all inventories on a repository
1228
 
            # upgrade ?
1229
 
            # the fast path is to copy the raw xml from the repository. If the
1230
 
            # xml contains 'revision_id="', then we assume the right 
1231
 
            # revision_id is set. We must check for this full string, because a
1232
 
            # root node id can legitimately look like 'revision_id' but cannot
1233
 
            # contain a '"'.
1234
1011
            xml = self.branch.repository.get_inventory_xml(new_revision)
1235
 
            if not 'revision_id="' in xml.split('\n', 1)[0]:
1236
 
                inv = self.branch.repository.deserialise_inventory(
1237
 
                    new_revision, xml)
1238
 
                inv.revision_id = new_revision
1239
 
                xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1240
 
            assert isinstance(xml, str), 'serialised xml must be bytestring.'
1241
 
            path = self._basis_inventory_name()
1242
 
            sio = StringIO(xml)
1243
 
            self._control_files.put(path, sio)
 
1012
            path = self._basis_inventory_name(new_revision)
 
1013
            self._control_files.put_utf8(path, xml)
1244
1014
        except WeaveRevisionNotPresent:
1245
1015
            pass
1246
1016
 
1247
 
    def read_basis_inventory(self):
 
1017
    def _remove_old_basis(self, old_revision):
 
1018
        """Remove the old basis inventory 'old_revision'."""
 
1019
        if old_revision is not None:
 
1020
            try:
 
1021
                path = self._basis_inventory_name(old_revision)
 
1022
                path = self._control_files._escape(path)
 
1023
                self._control_files._transport.delete(path)
 
1024
            except NoSuchFile:
 
1025
                pass
 
1026
 
 
1027
    def read_basis_inventory(self, revision_id):
1248
1028
        """Read the cached basis inventory."""
1249
 
        path = self._basis_inventory_name()
1250
 
        return self._control_files.get(path).read()
 
1029
        path = self._basis_inventory_name(revision_id)
 
1030
        return self._control_files.get_utf8(path).read()
1251
1031
        
1252
1032
    @needs_read_lock
1253
1033
    def read_working_inventory(self):
1260
1040
        return result
1261
1041
 
1262
1042
    @needs_write_lock
1263
 
    def remove(self, files, verbose=False, to_file=None):
 
1043
    def remove(self, files, verbose=False):
1264
1044
        """Remove nominated files from the working inventory..
1265
1045
 
1266
1046
        This does not remove their text.  This does not run on XXX on what? RBC
1288
1068
                # TODO: Perhaps make this just a warning, and continue?
1289
1069
                # This tends to happen when 
1290
1070
                raise NotVersionedError(path=f)
 
1071
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1291
1072
            if verbose:
1292
1073
                # having remove it, it must be either ignored or unknown
1293
1074
                if self.is_ignored(f):
1294
1075
                    new_status = 'I'
1295
1076
                else:
1296
1077
                    new_status = '?'
1297
 
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
 
1078
                show_status(new_status, inv[fid].kind, quotefn(f))
1298
1079
            del inv[fid]
1299
1080
 
1300
1081
        self._write_inventory(inv)
1303
1084
    def revert(self, filenames, old_tree=None, backups=True, 
1304
1085
               pb=DummyProgress()):
1305
1086
        from transform import revert
1306
 
        from conflicts import resolve
1307
1087
        if old_tree is None:
1308
1088
            old_tree = self.basis_tree()
1309
 
        conflicts = revert(self, old_tree, filenames, backups, pb)
 
1089
        revert(self, old_tree, filenames, backups, pb)
1310
1090
        if not len(filenames):
1311
1091
            self.set_pending_merges([])
1312
 
            resolve(self)
1313
 
        else:
1314
 
            resolve(self, filenames, ignore_misses=True)
1315
 
        return conflicts
1316
1092
 
1317
 
    # XXX: This method should be deprecated in favour of taking in a proper
1318
 
    # new Inventory object.
1319
1093
    @needs_write_lock
1320
1094
    def set_inventory(self, new_inventory_list):
1321
1095
        from bzrlib.inventory import (Inventory,
1367
1141
        # of a nasty hack; probably it's better to have a transaction object,
1368
1142
        # which can do some finalization when it's either successfully or
1369
1143
        # 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.
 
1144
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
 
1145
        # wrongly. Hookinh into unllock on the control files object is fine though.
1372
1146
        
1373
1147
        # TODO: split this per format so there is no ugly if block
1374
1148
        if self._hashcache.needs_write and (
1379
1153
             self._control_files._lock_count==3)):
1380
1154
            self._hashcache.write()
1381
1155
        # reverse order of locking.
 
1156
        result = self._control_files.unlock()
1382
1157
        try:
1383
 
            return self._control_files.unlock()
 
1158
            self.branch.unlock()
1384
1159
        finally:
1385
 
            self.branch.unlock()
 
1160
            return result
1386
1161
 
1387
1162
    @needs_write_lock
1388
1163
    def update(self):
1389
 
        """Update a working tree along its branch.
1390
 
 
1391
 
        This will update the branch if its bound too, which means we have multiple trees involved:
1392
 
        The new basis tree of the master.
1393
 
        The old basis tree of the branch.
1394
 
        The old basis tree of the working tree.
1395
 
        The current working tree state.
1396
 
        pathologically all three may be different, and non ancestors of each other.
1397
 
        Conceptually we want to:
1398
 
        Preserve the wt.basis->wt.state changes
1399
 
        Transform the wt.basis to the new master basis.
1400
 
        Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1401
 
        Restore the wt.basis->wt.state changes.
1402
 
 
1403
 
        There isn't a single operation at the moment to do that, so we:
1404
 
        Merge current state -> basis tree of the master w.r.t. the old tree basis.
1405
 
        Do a 'normal' merge of the old branch basis if it is relevant.
1406
 
        """
1407
 
        old_tip = self.branch.update()
1408
 
        if old_tip is not None:
1409
 
            self.add_pending_merge(old_tip)
1410
1164
        self.branch.lock_read()
1411
1165
        try:
1412
 
            result = 0
1413
 
            if self.last_revision() != self.branch.last_revision():
1414
 
                # merge tree state up to new branch tip.
1415
 
                basis = self.basis_tree()
1416
 
                to_tree = self.branch.basis_tree()
1417
 
                result += merge_inner(self.branch,
1418
 
                                      to_tree,
1419
 
                                      basis,
1420
 
                                      this_tree=self)
1421
 
                self.set_last_revision(self.branch.last_revision())
1422
 
            if old_tip and old_tip != self.last_revision():
1423
 
                # our last revision was not the prior branch last revision
1424
 
                # and we have converted that last revision to a pending merge.
1425
 
                # base is somewhere between the branch tip now
1426
 
                # and the now pending merge
1427
 
                from bzrlib.revision import common_ancestor
1428
 
                try:
1429
 
                    base_rev_id = common_ancestor(self.branch.last_revision(),
1430
 
                                                  old_tip,
1431
 
                                                  self.branch.repository)
1432
 
                except errors.NoCommonAncestor:
1433
 
                    base_rev_id = None
1434
 
                base_tree = self.branch.repository.revision_tree(base_rev_id)
1435
 
                other_tree = self.branch.repository.revision_tree(old_tip)
1436
 
                result += merge_inner(self.branch,
1437
 
                                      other_tree,
1438
 
                                      base_tree,
1439
 
                                      this_tree=self)
 
1166
            if self.last_revision() == self.branch.last_revision():
 
1167
                return
 
1168
            basis = self.basis_tree()
 
1169
            to_tree = self.branch.basis_tree()
 
1170
            result = merge_inner(self.branch,
 
1171
                                 to_tree,
 
1172
                                 basis,
 
1173
                                 this_tree=self)
 
1174
            self.set_last_revision(self.branch.last_revision())
1440
1175
            return result
1441
1176
        finally:
1442
1177
            self.branch.unlock()
1451
1186
        self._set_inventory(inv)
1452
1187
        mutter('wrote working inventory')
1453
1188
 
1454
 
    def set_conflicts(self, arg):
1455
 
        raise UnsupportedOperation(self.set_conflicts, self)
1456
 
 
1457
 
    def add_conflicts(self, arg):
1458
 
        raise UnsupportedOperation(self.add_conflicts, self)
1459
 
 
1460
 
    @needs_read_lock
1461
 
    def conflicts(self):
1462
 
        conflicts = ConflictList()
1463
 
        for conflicted in self._iter_conflicts():
1464
 
            text = True
1465
 
            try:
1466
 
                if file_kind(self.abspath(conflicted)) != "file":
1467
 
                    text = False
1468
 
            except errors.NoSuchFile:
1469
 
                text = False
1470
 
            if text is True:
1471
 
                for suffix in ('.THIS', '.OTHER'):
1472
 
                    try:
1473
 
                        kind = file_kind(self.abspath(conflicted+suffix))
1474
 
                        if kind != "file":
1475
 
                            text = False
1476
 
                    except errors.NoSuchFile:
1477
 
                        text = False
1478
 
                    if text == False:
1479
 
                        break
1480
 
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1481
 
            conflicts.append(Conflict.factory(ctype, path=conflicted,
1482
 
                             file_id=self.path2id(conflicted)))
1483
 
        return conflicts
1484
 
 
1485
1189
 
1486
1190
class WorkingTree3(WorkingTree):
1487
1191
    """This is the Format 3 working tree.
1489
1193
    This differs from the base WorkingTree by:
1490
1194
     - having its own file lock
1491
1195
     - having its own last-revision property.
1492
 
 
1493
 
    This is new in bzr 0.8
1494
1196
    """
1495
1197
 
1496
1198
    @needs_read_lock
1517
1219
            self._control_files.put_utf8('last-revision', revision_id)
1518
1220
            return True
1519
1221
 
1520
 
    @needs_write_lock
1521
 
    def set_conflicts(self, conflicts):
1522
 
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1523
 
                      CONFLICT_HEADER_1)
1524
 
 
1525
 
    @needs_write_lock
1526
 
    def add_conflicts(self, new_conflicts):
1527
 
        conflict_set = set(self.conflicts())
1528
 
        conflict_set.update(set(list(new_conflicts)))
1529
 
        self.set_conflicts(ConflictList(sorted(conflict_set,
1530
 
                                               key=Conflict.sort_key)))
1531
 
 
1532
 
    @needs_read_lock
1533
 
    def conflicts(self):
1534
 
        try:
1535
 
            confile = self._control_files.get('conflicts')
1536
 
        except NoSuchFile:
1537
 
            return ConflictList()
1538
 
        try:
1539
 
            if confile.next() != CONFLICT_HEADER_1 + '\n':
1540
 
                raise ConflictFormatError()
1541
 
        except StopIteration:
1542
 
            raise ConflictFormatError()
1543
 
        return ConflictList.from_stanzas(RioReader(confile))
1544
 
 
1545
 
 
 
1222
 
 
1223
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1546
1224
def get_conflicted_stem(path):
1547
1225
    for suffix in CONFLICT_SUFFIXES:
1548
1226
        if path.endswith(suffix):
1598
1276
        except NoSuchFile:
1599
1277
            raise errors.NoWorkingTree(base=transport.base)
1600
1278
        except KeyError:
1601
 
            raise errors.UnknownFormatError(format=format_string)
 
1279
            raise errors.UnknownFormatError(format_string)
1602
1280
 
1603
1281
    @classmethod
1604
1282
    def get_default_format(klass):
1609
1287
        """Return the ASCII format string that identifies this format."""
1610
1288
        raise NotImplementedError(self.get_format_string)
1611
1289
 
1612
 
    def get_format_description(self):
1613
 
        """Return the short description for this format."""
1614
 
        raise NotImplementedError(self.get_format_description)
1615
 
 
1616
1290
    def is_supported(self):
1617
1291
        """Is this format supported?
1618
1292
 
1643
1317
    This format modified the hash cache from the format 1 hash cache.
1644
1318
    """
1645
1319
 
1646
 
    def get_format_description(self):
1647
 
        """See WorkingTreeFormat.get_format_description()."""
1648
 
        return "Working tree format 2"
1649
 
 
1650
 
    def stub_initialize_remote(self, control_files):
1651
 
        """As a special workaround create critical control files for a remote working tree
1652
 
        
1653
 
        This ensures that it can later be updated and dealt with locally,
1654
 
        since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with 
1655
 
        no working tree.  (See bug #43064).
1656
 
        """
1657
 
        sio = StringIO()
1658
 
        inv = Inventory()
1659
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1660
 
        sio.seek(0)
1661
 
        control_files.put('inventory', sio)
1662
 
 
1663
 
        control_files.put_utf8('pending-merges', '')
1664
 
        
1665
 
 
1666
1320
    def initialize(self, a_bzrdir, revision_id=None):
1667
1321
        """See WorkingTreeFormat.initialize()."""
1668
1322
        if not isinstance(a_bzrdir.transport, LocalTransport):
1681
1335
                branch.unlock()
1682
1336
        revision = branch.last_revision()
1683
1337
        inv = Inventory() 
1684
 
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1338
        wt = WorkingTree(a_bzrdir.root_transport.base,
1685
1339
                         branch,
1686
1340
                         inv,
1687
1341
                         _internal=True,
1709
1363
            raise NotImplementedError
1710
1364
        if not isinstance(a_bzrdir.transport, LocalTransport):
1711
1365
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1712
 
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
 
1366
        return WorkingTree(a_bzrdir.root_transport.base,
1713
1367
                           _internal=True,
1714
1368
                           _format=self,
1715
1369
                           _bzrdir=a_bzrdir)
1718
1372
class WorkingTreeFormat3(WorkingTreeFormat):
1719
1373
    """The second working tree format updated to record a format marker.
1720
1374
 
1721
 
    This format:
1722
 
        - exists within a metadir controlling .bzr
1723
 
        - includes an explicit version marker for the workingtree control
1724
 
          files, separate from the BzrDir format
1725
 
        - modifies the hash cache format
1726
 
        - is new in bzr 0.8
1727
 
        - uses a LockDir to guard access to the repository
 
1375
    This format modified the hash cache from the format 1 hash cache.
1728
1376
    """
1729
1377
 
1730
1378
    def get_format_string(self):
1731
1379
        """See WorkingTreeFormat.get_format_string()."""
1732
1380
        return "Bazaar-NG Working Tree format 3"
1733
1381
 
1734
 
    def get_format_description(self):
1735
 
        """See WorkingTreeFormat.get_format_description()."""
1736
 
        return "Working tree format 3"
1737
 
 
1738
 
    _lock_file_name = 'lock'
1739
 
    _lock_class = LockDir
1740
 
 
1741
 
    def _open_control_files(self, a_bzrdir):
1742
 
        transport = a_bzrdir.get_workingtree_transport(None)
1743
 
        return LockableFiles(transport, self._lock_file_name, 
1744
 
                             self._lock_class)
1745
 
 
1746
1382
    def initialize(self, a_bzrdir, revision_id=None):
1747
1383
        """See WorkingTreeFormat.initialize().
1748
1384
        
1749
 
        revision_id allows creating a working tree at a different
 
1385
        revision_id allows creating a working tree at a differnet
1750
1386
        revision than the branch is at.
1751
1387
        """
1752
1388
        if not isinstance(a_bzrdir.transport, LocalTransport):
1753
1389
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1754
1390
        transport = a_bzrdir.get_workingtree_transport(self)
1755
 
        control_files = self._open_control_files(a_bzrdir)
1756
 
        control_files.create_lock()
1757
 
        control_files.lock_write()
 
1391
        control_files = LockableFiles(transport, 'lock')
1758
1392
        control_files.put_utf8('format', self.get_format_string())
1759
1393
        branch = a_bzrdir.open_branch()
1760
1394
        if revision_id is None:
1761
1395
            revision_id = branch.last_revision()
1762
1396
        inv = Inventory() 
1763
 
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1397
        wt = WorkingTree3(a_bzrdir.root_transport.base,
1764
1398
                         branch,
1765
1399
                         inv,
1766
1400
                         _internal=True,
1767
1401
                         _format=self,
1768
 
                         _bzrdir=a_bzrdir,
1769
 
                         _control_files=control_files)
1770
 
        wt.lock_write()
1771
 
        try:
1772
 
            wt._write_inventory(inv)
1773
 
            wt.set_root_id(inv.root.file_id)
1774
 
            wt.set_last_revision(revision_id)
1775
 
            wt.set_pending_merges([])
1776
 
            build_tree(wt.basis_tree(), wt)
1777
 
        finally:
1778
 
            wt.unlock()
1779
 
            control_files.unlock()
 
1402
                         _bzrdir=a_bzrdir)
 
1403
        wt._write_inventory(inv)
 
1404
        wt.set_root_id(inv.root.file_id)
 
1405
        wt.set_last_revision(revision_id)
 
1406
        wt.set_pending_merges([])
 
1407
        build_tree(wt.basis_tree(), wt)
1780
1408
        return wt
1781
1409
 
1782
1410
    def __init__(self):
1794
1422
            raise NotImplementedError
1795
1423
        if not isinstance(a_bzrdir.transport, LocalTransport):
1796
1424
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1797
 
        control_files = self._open_control_files(a_bzrdir)
1798
 
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
 
1425
        return WorkingTree3(a_bzrdir.root_transport.base,
1799
1426
                           _internal=True,
1800
1427
                           _format=self,
1801
 
                           _bzrdir=a_bzrdir,
1802
 
                           _control_files=control_files)
 
1428
                           _bzrdir=a_bzrdir)
1803
1429
 
1804
1430
    def __str__(self):
1805
1431
        return self.get_format_string()