~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Jamie Wilkinson
  • Date: 2006-07-18 23:59:52 UTC
  • mfrom: (1868 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1874.
  • Revision ID: jaq@spacepants.org-20060718235952-1e362401a7858958
merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
# memory.  (Now done? -- mbp 20060309)
41
41
 
42
42
from binascii import hexlify
 
43
import collections
43
44
from copy import deepcopy
44
45
from cStringIO import StringIO
45
46
import errno
48
49
import re
49
50
import stat
50
51
from time import time
 
52
import warnings
51
53
 
 
54
from bzrlib import bzrdir, errors, osutils, urlutils
52
55
from bzrlib.atomicfile import AtomicFile
53
 
from bzrlib.branch import (Branch,
54
 
                           quotefn)
 
56
import bzrlib.branch
55
57
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
56
 
import bzrlib.bzrdir as bzrdir
57
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
 
import bzrlib.errors as errors
59
59
from bzrlib.errors import (BzrCheckError,
60
60
                           BzrError,
61
61
                           ConflictFormatError,
62
 
                           DivergedBranches,
63
62
                           WeaveRevisionNotPresent,
64
63
                           NotBranchError,
65
64
                           NoSuchFile,
73
72
from bzrlib.merge import merge_inner, transform_tree
74
73
from bzrlib.osutils import (
75
74
                            abspath,
76
 
                            appendpath,
77
75
                            compact_date,
78
76
                            file_kind,
79
77
                            isdir,
92
90
from bzrlib.progress import DummyProgress, ProgressPhase
93
91
from bzrlib.revision import NULL_REVISION
94
92
from bzrlib.rio import RioReader, rio_file, Stanza
95
 
from bzrlib.symbol_versioning import *
96
 
from bzrlib.textui import show_status
97
 
import bzrlib.tree
 
93
from bzrlib.symbol_versioning import (deprecated_passed,
 
94
        deprecated_method,
 
95
        deprecated_function,
 
96
        DEPRECATED_PARAMETER,
 
97
        zero_eight,
 
98
        )
 
99
from bzrlib.trace import mutter, note
98
100
from bzrlib.transform import build_tree
99
 
from bzrlib.trace import mutter, note
100
101
from bzrlib.transport import get_transport
101
102
from bzrlib.transport.local import LocalTransport
 
103
from bzrlib.textui import show_status
 
104
import bzrlib.tree
102
105
import bzrlib.ui
103
106
import bzrlib.xml5
104
107
 
148
151
 
149
152
 
150
153
class TreeEntry(object):
151
 
    """An entry that implements the minium interface used by commands.
 
154
    """An entry that implements the minimum interface used by commands.
152
155
 
153
156
    This needs further inspection, it may be better to have 
154
157
    InventoryEntries without ids - though that seems wrong. For now,
230
233
        self.bzrdir = _bzrdir
231
234
        if not _internal:
232
235
            # not created via open etc.
233
 
            warn("WorkingTree() is deprecated as of bzr version 0.8. "
 
236
            warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
234
237
                 "Please use bzrdir.open_workingtree or WorkingTree.open().",
235
238
                 DeprecationWarning,
236
239
                 stacklevel=2)
250
253
        mutter("opening working tree %r", basedir)
251
254
        if deprecated_passed(branch):
252
255
            if not _internal:
253
 
                warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
 
256
                warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
254
257
                     " Please use bzrdir.open_workingtree() or"
255
258
                     " WorkingTree.open().",
256
259
                     DeprecationWarning,
259
262
            self._branch = branch
260
263
        else:
261
264
            self._branch = self.bzrdir.open_branch()
262
 
        assert isinstance(self.branch, Branch), \
263
 
            "branch %r is not a Branch" % self.branch
264
265
        self.basedir = realpath(basedir)
265
266
        # if branch is at our basedir and is a format 6 or less
266
267
        if isinstance(self._format, WorkingTreeFormat2):
267
268
            # share control object
268
269
            self._control_files = self.branch.control_files
269
270
        else:
270
 
            # only ready for format 3
271
 
            assert isinstance(self._format, WorkingTreeFormat3)
 
271
            # assume all other formats have their own control files.
272
272
            assert isinstance(_control_files, LockableFiles), \
273
273
                    "_control_files must be a LockableFiles, not %r" \
274
274
                    % _control_files
279
279
        # if needed, or, when the cache sees a change, append it to the hash
280
280
        # cache file, and have the parser take the most recent entry for a
281
281
        # given path only.
282
 
        cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
 
282
        cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
283
283
        hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
284
284
        hc.read()
285
285
        # is this scan needed ? it makes things kinda slow.
286
 
        hc.scan()
 
286
        #hc.scan()
287
287
 
288
288
        if hc.needs_write:
289
289
            mutter("write hc")
349
349
        run into /.  If there isn't one, raises NotBranchError.
350
350
        TODO: give this a new exception.
351
351
        If there is one, it is returned, along with the unused portion of path.
 
352
 
 
353
        :return: The WorkingTree that contains 'path', and the rest of path
352
354
        """
353
355
        if path is None:
354
 
            path = os.getcwdu()
 
356
            path = osutils.getcwd()
355
357
        control, relpath = bzrdir.BzrDir.open_containing(path)
 
358
 
356
359
        return control.open_workingtree(), relpath
357
360
 
358
361
    @staticmethod
413
416
        XXX: When BzrDir is present, these should be created through that 
414
417
        interface instead.
415
418
        """
416
 
        warn('delete WorkingTree.create', stacklevel=3)
 
419
        warnings.warn('delete WorkingTree.create', stacklevel=3)
417
420
        transport = get_transport(directory)
418
421
        if branch.bzrdir.root_transport.base == transport.base:
419
422
            # same dir 
451
454
    def get_file_byname(self, filename):
452
455
        return file(self.abspath(filename), 'rb')
453
456
 
 
457
    def get_parent_ids(self):
 
458
        """See Tree.get_parent_ids.
 
459
        
 
460
        This implementation reads the pending merges list and last_revision
 
461
        value and uses that to decide what the parents list should be.
 
462
        """
 
463
        last_rev = self.last_revision()
 
464
        if last_rev is None:
 
465
            parents = []
 
466
        else:
 
467
            parents = [last_rev]
 
468
        other_parents = self.pending_merges()
 
469
        return parents + other_parents
 
470
 
454
471
    def get_root_id(self):
455
472
        """Return the id of this trees root"""
456
473
        inv = self.read_working_inventory()
505
522
        # but with branch a kwarg now, passing in args as is results in the
506
523
        #message being used for the branch
507
524
        args = (DEPRECATED_PARAMETER, message, ) + args
508
 
        Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
 
525
        committed_id = Commit().commit( working_tree=self, revprops=revprops,
 
526
            *args, **kwargs)
509
527
        self._set_inventory(self.read_working_inventory())
 
528
        return committed_id
510
529
 
511
530
    def id2abspath(self, file_id):
512
531
        return self.abspath(self.id2path(file_id))
530
549
        return os.path.getsize(self.id2abspath(file_id))
531
550
 
532
551
    @needs_read_lock
533
 
    def get_file_sha1(self, file_id):
534
 
        path = self._inventory.id2path(file_id)
 
552
    def get_file_sha1(self, file_id, path=None):
 
553
        if not path:
 
554
            path = self._inventory.id2path(file_id)
535
555
        return self._hashcache.get_sha1(path)
536
556
 
537
 
    def is_executable(self, file_id):
538
 
        if not supports_executable():
 
557
    def get_file_mtime(self, file_id, path=None):
 
558
        if not path:
 
559
            path = self._inventory.id2path(file_id)
 
560
        return os.lstat(self.abspath(path)).st_mtime
 
561
 
 
562
    if not supports_executable():
 
563
        def is_executable(self, file_id, path=None):
539
564
            return self._inventory[file_id].executable
540
 
        else:
541
 
            path = self._inventory.id2path(file_id)
 
565
    else:
 
566
        def is_executable(self, file_id, path=None):
 
567
            if not path:
 
568
                path = self._inventory.id2path(file_id)
542
569
            mode = os.lstat(self.abspath(path)).st_mode
543
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
570
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
544
571
 
545
572
    @needs_write_lock
546
573
    def add(self, files, ids=None):
581
608
        inv = self.read_working_inventory()
582
609
        for f,file_id in zip(files, ids):
583
610
            if self.is_control_filename(f):
584
 
                raise BzrError("cannot add control file %s" % quotefn(f))
 
611
                raise errors.ForbiddenControlFileError(filename=f)
585
612
 
586
613
            fp = splitpath(f)
587
614
 
589
616
                raise BzrError("cannot add top-level %r" % f)
590
617
 
591
618
            fullpath = normpath(self.abspath(f))
592
 
 
593
619
            try:
594
620
                kind = file_kind(fullpath)
595
621
            except OSError, e:
596
622
                if e.errno == errno.ENOENT:
597
623
                    raise NoSuchFile(fullpath)
598
 
                # maybe something better?
599
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
600
 
 
601
624
            if not InventoryEntry.versionable_kind(kind):
602
 
                raise BzrError('cannot add: not a versionable file ('
603
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
604
 
 
 
625
                raise errors.BadFileKindError(filename=f, kind=kind)
605
626
            if file_id is None:
606
627
                inv.add_path(f, kind=kind)
607
628
            else:
632
653
        """
633
654
        try:
634
655
            merges_file = self._control_files.get_utf8('pending-merges')
635
 
        except OSError, e:
636
 
            if e.errno != errno.ENOENT:
637
 
                raise
 
656
        except NoSuchFile:
638
657
            return []
639
658
        p = []
640
659
        for l in merges_file.readlines():
690
709
            return '?'
691
710
 
692
711
    def list_files(self):
693
 
        """Recursively list all files as (path, class, kind, id).
 
712
        """Recursively list all files as (path, class, kind, id, entry).
694
713
 
695
714
        Lists, but does not descend into unversioned directories.
696
715
 
700
719
        Skips the control directory.
701
720
        """
702
721
        inv = self._inventory
703
 
 
704
 
        def descend(from_dir_relpath, from_dir_id, dp):
705
 
            ls = os.listdir(dp)
706
 
            ls.sort()
707
 
            for f in ls:
 
722
        # Convert these into local objects to save lookup times
 
723
        pathjoin = bzrlib.osutils.pathjoin
 
724
        file_kind = bzrlib.osutils.file_kind
 
725
 
 
726
        # transport.base ends in a slash, we want the piece
 
727
        # between the last two slashes
 
728
        transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
 
729
 
 
730
        fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
 
731
 
 
732
        # directory file_id, relative path, absolute path, reverse sorted children
 
733
        children = os.listdir(self.basedir)
 
734
        children.sort()
 
735
        # jam 20060527 The kernel sized tree seems equivalent whether we 
 
736
        # use a deque and popleft to keep them sorted, or if we use a plain
 
737
        # list and just reverse() them.
 
738
        children = collections.deque(children)
 
739
        stack = [(inv.root.file_id, u'', self.basedir, children)]
 
740
        while stack:
 
741
            from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
 
742
 
 
743
            while children:
 
744
                f = children.popleft()
708
745
                ## TODO: If we find a subdirectory with its own .bzr
709
746
                ## directory, then that is a separate tree and we
710
747
                ## should exclude it.
711
748
 
712
749
                # the bzrdir for this tree
713
 
                if self.bzrdir.transport.base.endswith(f + '/'):
 
750
                if transport_base_dir == f:
714
751
                    continue
715
752
 
716
 
                # path within tree
717
 
                fp = appendpath(from_dir_relpath, f)
 
753
                # we know that from_dir_relpath and from_dir_abspath never end in a slash
 
754
                # and 'f' doesn't begin with one, we can do a string op, rather
 
755
                # than the checks of pathjoin(), all relative paths will have an extra slash
 
756
                # at the beginning
 
757
                fp = from_dir_relpath + '/' + f
718
758
 
719
759
                # absolute path
720
 
                fap = appendpath(dp, f)
 
760
                fap = from_dir_abspath + '/' + f
721
761
                
722
762
                f_ie = inv.get_child(from_dir_id, f)
723
763
                if f_ie:
724
764
                    c = 'V'
725
 
                elif self.is_ignored(fp):
 
765
                elif self.is_ignored(fp[1:]):
726
766
                    c = 'I'
727
767
                else:
728
 
                    c = '?'
 
768
                    # we may not have found this file, because of a unicode issue
 
769
                    f_norm, can_access = osutils.normalized_filename(f)
 
770
                    if f == f_norm or not can_access:
 
771
                        # No change, so treat this file normally
 
772
                        c = '?'
 
773
                    else:
 
774
                        # this file can be accessed by a normalized path
 
775
                        # check again if it is versioned
 
776
                        # these lines are repeated here for performance
 
777
                        f = f_norm
 
778
                        fp = from_dir_relpath + '/' + f
 
779
                        fap = from_dir_abspath + '/' + f
 
780
                        f_ie = inv.get_child(from_dir_id, f)
 
781
                        if f_ie:
 
782
                            c = 'V'
 
783
                        elif self.is_ignored(fp[1:]):
 
784
                            c = 'I'
 
785
                        else:
 
786
                            c = '?'
729
787
 
730
788
                fk = file_kind(fap)
731
789
 
737
795
 
738
796
                # make a last minute entry
739
797
                if f_ie:
740
 
                    entry = f_ie
 
798
                    yield fp[1:], c, fk, f_ie.file_id, f_ie
741
799
                else:
742
 
                    if fk == 'directory':
743
 
                        entry = TreeDirectory()
744
 
                    elif fk == 'file':
745
 
                        entry = TreeFile()
746
 
                    elif fk == 'symlink':
747
 
                        entry = TreeLink()
748
 
                    else:
749
 
                        entry = TreeEntry()
 
800
                    try:
 
801
                        yield fp[1:], c, fk, None, fk_entries[fk]()
 
802
                    except KeyError:
 
803
                        yield fp[1:], c, fk, None, TreeEntry()
 
804
                    continue
750
805
                
751
 
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
752
 
 
753
806
                if fk != 'directory':
754
807
                    continue
755
808
 
756
 
                if c != 'V':
757
 
                    # don't descend unversioned directories
758
 
                    continue
759
 
                
760
 
                for ff in descend(fp, f_ie.file_id, fap):
761
 
                    yield ff
 
809
                # But do this child first
 
810
                new_children = os.listdir(fap)
 
811
                new_children.sort()
 
812
                new_children = collections.deque(new_children)
 
813
                stack.append((f_ie.file_id, fp, fap, new_children))
 
814
                # Break out of inner loop, so that we start outer loop with child
 
815
                break
 
816
            else:
 
817
                # if we finished all children, pop it off the stack
 
818
                stack.pop()
762
819
 
763
 
        for f in descend(u'', inv.root.file_id, self.basedir):
764
 
            yield f
765
820
 
766
821
    @needs_write_lock
767
822
    def move(self, from_paths, to_name):
803
858
            if f_id == None:
804
859
                raise BzrError("%r is not versioned" % f)
805
860
            name_tail = splitpath(f)[-1]
806
 
            dest_path = appendpath(to_name, name_tail)
 
861
            dest_path = pathjoin(to_name, name_tail)
807
862
            if self.has_filename(dest_path):
808
863
                raise BzrError("destination %r already exists" % dest_path)
809
864
            if f_id in to_idpath:
816
871
        try:
817
872
            for f in from_paths:
818
873
                name_tail = splitpath(f)[-1]
819
 
                dest_path = appendpath(to_name, name_tail)
 
874
                dest_path = pathjoin(to_name, name_tail)
820
875
                result.append((f, dest_path))
821
876
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
822
877
                try:
886
941
 
887
942
        These are files in the working directory that are not versioned or
888
943
        control files or ignored.
889
 
        
890
 
        >>> from bzrlib.bzrdir import ScratchDir
891
 
        >>> d = ScratchDir(files=['foo', 'foo~'])
892
 
        >>> b = d.open_branch()
893
 
        >>> tree = d.open_workingtree()
894
 
        >>> map(str, tree.unknowns())
895
 
        ['foo']
896
 
        >>> tree.add('foo')
897
 
        >>> list(b.unknowns())
898
 
        []
899
 
        >>> tree.remove('foo')
900
 
        >>> list(b.unknowns())
901
 
        [u'foo']
902
944
        """
903
945
        for subp in self.extras():
904
946
            if not self.is_ignored(subp):
912
954
 
913
955
    def _iter_conflicts(self):
914
956
        conflicted = set()
915
 
        for path in (s[0] for s in self.list_files()):
 
957
        for info in self.list_files():
 
958
            path = info[0]
916
959
            stem = get_conflicted_stem(path)
917
960
            if stem is None:
918
961
                continue
972
1015
 
973
1016
            fl = []
974
1017
            for subf in os.listdir(dirabs):
975
 
                if (subf != '.bzr'
976
 
                    and (subf not in dir_entry.children)):
977
 
                    fl.append(subf)
 
1018
                if subf == '.bzr':
 
1019
                    continue
 
1020
                if subf not in dir_entry.children:
 
1021
                    subf_norm, can_access = osutils.normalized_filename(subf)
 
1022
                    if subf_norm != subf and can_access:
 
1023
                        if subf_norm not in dir_entry.children:
 
1024
                            fl.append(subf_norm)
 
1025
                    else:
 
1026
                        fl.append(subf)
978
1027
            
979
1028
            fl.sort()
980
1029
            for subf in fl:
981
 
                subp = appendpath(path, subf)
 
1030
                subp = pathjoin(path, subf)
982
1031
                yield subp
983
1032
 
984
1033
    def _translate_ignore_rule(self, rule):
1051
1100
        if hasattr(self, '_ignorelist'):
1052
1101
            return self._ignorelist
1053
1102
 
1054
 
        l = bzrlib.DEFAULT_IGNORE[:]
 
1103
        l = []
1055
1104
        if self.has_filename(bzrlib.IGNORE_FILENAME):
1056
1105
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1057
 
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
 
1106
            l.extend([line.rstrip("\n\r").decode('utf-8') 
 
1107
                      for line in f.readlines()])
1058
1108
        self._ignorelist = l
1059
1109
        self._ignore_regex = self._combine_ignore_rules(l)
1060
1110
        return l
1166
1216
 
1167
1217
    def _cache_basis_inventory(self, new_revision):
1168
1218
        """Cache new_revision as the basis inventory."""
 
1219
        # TODO: this should allow the ready-to-use inventory to be passed in,
 
1220
        # as commit already has that ready-to-use [while the format is the
 
1221
        # same, that is].
1169
1222
        try:
1170
1223
            # this double handles the inventory - unpack and repack - 
1171
1224
            # but is easier to understand. We can/should put a conditional
1172
1225
            # in here based on whether the inventory is in the latest format
1173
1226
            # - perhaps we should repack all inventories on a repository
1174
1227
            # upgrade ?
1175
 
            inv = self.branch.repository.get_inventory(new_revision)
1176
 
            inv.revision_id = new_revision
1177
 
            xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1178
 
 
 
1228
            # the fast path is to copy the raw xml from the repository. If the
 
1229
            # xml contains 'revision_id="', then we assume the right 
 
1230
            # revision_id is set. We must check for this full string, because a
 
1231
            # root node id can legitimately look like 'revision_id' but cannot
 
1232
            # contain a '"'.
 
1233
            xml = self.branch.repository.get_inventory_xml(new_revision)
 
1234
            if not 'revision_id="' in xml.split('\n', 1)[0]:
 
1235
                inv = self.branch.repository.deserialise_inventory(
 
1236
                    new_revision, xml)
 
1237
                inv.revision_id = new_revision
 
1238
                xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1239
            assert isinstance(xml, str), 'serialised xml must be bytestring.'
1179
1240
            path = self._basis_inventory_name()
1180
 
            self._control_files.put_utf8(path, xml)
 
1241
            sio = StringIO(xml)
 
1242
            self._control_files.put(path, sio)
1181
1243
        except WeaveRevisionNotPresent:
1182
1244
            pass
1183
1245
 
1184
1246
    def read_basis_inventory(self):
1185
1247
        """Read the cached basis inventory."""
1186
1248
        path = self._basis_inventory_name()
1187
 
        return self._control_files.get_utf8(path).read()
 
1249
        return self._control_files.get(path).read()
1188
1250
        
1189
1251
    @needs_read_lock
1190
1252
    def read_working_inventory(self):
1197
1259
        return result
1198
1260
 
1199
1261
    @needs_write_lock
1200
 
    def remove(self, files, verbose=False):
 
1262
    def remove(self, files, verbose=False, to_file=None):
1201
1263
        """Remove nominated files from the working inventory..
1202
1264
 
1203
1265
        This does not remove their text.  This does not run on XXX on what? RBC
1225
1287
                # TODO: Perhaps make this just a warning, and continue?
1226
1288
                # This tends to happen when 
1227
1289
                raise NotVersionedError(path=f)
1228
 
            mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1229
1290
            if verbose:
1230
1291
                # having remove it, it must be either ignored or unknown
1231
1292
                if self.is_ignored(f):
1232
1293
                    new_status = 'I'
1233
1294
                else:
1234
1295
                    new_status = '?'
1235
 
                show_status(new_status, inv[fid].kind, quotefn(f))
 
1296
                show_status(new_status, inv[fid].kind, f, to_file=to_file)
1236
1297
            del inv[fid]
1237
1298
 
1238
1299
        self._write_inventory(inv)
1305
1366
        # of a nasty hack; probably it's better to have a transaction object,
1306
1367
        # which can do some finalization when it's either successfully or
1307
1368
        # unsuccessfully completed.  (Denys's original patch did that.)
1308
 
        # RBC 20060206 hookinhg into transaction will couple lock and transaction
1309
 
        # wrongly. Hookinh into unllock on the control files object is fine though.
 
1369
        # RBC 20060206 hooking into transaction will couple lock and transaction
 
1370
        # wrongly. Hooking into unlock on the control files object is fine though.
1310
1371
        
1311
1372
        # TODO: split this per format so there is no ugly if block
1312
1373
        if self._hashcache.needs_write and (
1358
1419
                                      this_tree=self)
1359
1420
                self.set_last_revision(self.branch.last_revision())
1360
1421
            if old_tip and old_tip != self.last_revision():
1361
 
                # our last revision was not the prior branch last reivison
 
1422
                # our last revision was not the prior branch last revision
1362
1423
                # and we have converted that last revision to a pending merge.
1363
1424
                # base is somewhere between the branch tip now
1364
1425
                # and the now pending merge
1392
1453
    def set_conflicts(self, arg):
1393
1454
        raise UnsupportedOperation(self.set_conflicts, self)
1394
1455
 
 
1456
    def add_conflicts(self, arg):
 
1457
        raise UnsupportedOperation(self.add_conflicts, self)
 
1458
 
1395
1459
    @needs_read_lock
1396
1460
    def conflicts(self):
1397
1461
        conflicts = ConflictList()
1400
1464
            try:
1401
1465
                if file_kind(self.abspath(conflicted)) != "file":
1402
1466
                    text = False
1403
 
            except OSError, e:
1404
 
                if e.errno == errno.ENOENT:
1405
 
                    text = False
1406
 
                else:
1407
 
                    raise
 
1467
            except errors.NoSuchFile:
 
1468
                text = False
1408
1469
            if text is True:
1409
1470
                for suffix in ('.THIS', '.OTHER'):
1410
1471
                    try:
1411
1472
                        kind = file_kind(self.abspath(conflicted+suffix))
1412
 
                    except OSError, e:
1413
 
                        if e.errno == errno.ENOENT:
 
1473
                        if kind != "file":
1414
1474
                            text = False
1415
 
                            break
1416
 
                        else:
1417
 
                            raise
1418
 
                    if kind != "file":
 
1475
                    except errors.NoSuchFile:
1419
1476
                        text = False
 
1477
                    if text == False:
1420
1478
                        break
1421
1479
            ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1422
1480
            conflicts.append(Conflict.factory(ctype, path=conflicted,
1463
1521
        self._put_rio('conflicts', conflicts.to_stanzas(), 
1464
1522
                      CONFLICT_HEADER_1)
1465
1523
 
 
1524
    @needs_write_lock
 
1525
    def add_conflicts(self, new_conflicts):
 
1526
        conflict_set = set(self.conflicts())
 
1527
        conflict_set.update(set(list(new_conflicts)))
 
1528
        self.set_conflicts(ConflictList(sorted(conflict_set,
 
1529
                                               key=Conflict.sort_key)))
 
1530
 
1466
1531
    @needs_read_lock
1467
1532
    def conflicts(self):
1468
1533
        try:
1532
1597
        except NoSuchFile:
1533
1598
            raise errors.NoWorkingTree(base=transport.base)
1534
1599
        except KeyError:
1535
 
            raise errors.UnknownFormatError(format_string)
 
1600
            raise errors.UnknownFormatError(format=format_string)
1536
1601
 
1537
1602
    @classmethod
1538
1603
    def get_default_format(klass):
1615
1680
                branch.unlock()
1616
1681
        revision = branch.last_revision()
1617
1682
        inv = Inventory() 
1618
 
        wt = WorkingTree(a_bzrdir.root_transport.base,
 
1683
        wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1619
1684
                         branch,
1620
1685
                         inv,
1621
1686
                         _internal=True,
1643
1708
            raise NotImplementedError
1644
1709
        if not isinstance(a_bzrdir.transport, LocalTransport):
1645
1710
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1646
 
        return WorkingTree(a_bzrdir.root_transport.base,
 
1711
        return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1647
1712
                           _internal=True,
1648
1713
                           _format=self,
1649
1714
                           _bzrdir=a_bzrdir)
1658
1723
          files, separate from the BzrDir format
1659
1724
        - modifies the hash cache format
1660
1725
        - is new in bzr 0.8
1661
 
        - uses a LockDir to guard access to the repository
 
1726
        - uses a LockDir to guard access for writes.
1662
1727
    """
1663
1728
 
1664
1729
    def get_format_string(self):
1680
1745
    def initialize(self, a_bzrdir, revision_id=None):
1681
1746
        """See WorkingTreeFormat.initialize().
1682
1747
        
1683
 
        revision_id allows creating a working tree at a differnet
 
1748
        revision_id allows creating a working tree at a different
1684
1749
        revision than the branch is at.
1685
1750
        """
1686
1751
        if not isinstance(a_bzrdir.transport, LocalTransport):
1694
1759
        if revision_id is None:
1695
1760
            revision_id = branch.last_revision()
1696
1761
        inv = Inventory() 
1697
 
        wt = WorkingTree3(a_bzrdir.root_transport.base,
 
1762
        wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1698
1763
                         branch,
1699
1764
                         inv,
1700
1765
                         _internal=True,
1728
1793
            raise NotImplementedError
1729
1794
        if not isinstance(a_bzrdir.transport, LocalTransport):
1730
1795
            raise errors.NotLocalUrl(a_bzrdir.transport.base)
1731
 
        control_files = self._open_control_files(a_bzrdir)
1732
 
        return WorkingTree3(a_bzrdir.root_transport.base,
 
1796
        return self._open(a_bzrdir, self._open_control_files(a_bzrdir))
 
1797
 
 
1798
    def _open(self, a_bzrdir, control_files):
 
1799
        """Open the tree itself.
 
1800
        
 
1801
        :param a_bzrdir: the dir for the tree.
 
1802
        :param control_files: the control files for the tree.
 
1803
        """
 
1804
        return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1733
1805
                           _internal=True,
1734
1806
                           _format=self,
1735
1807
                           _bzrdir=a_bzrdir,