1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""WorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
35
# TODO: Give the workingtree sole responsibility for the working inventory;
36
# remove the variable and references to it from the branch. This may require
37
# updating the commit code so as to update the inventory within the working
38
# 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)
42
from binascii import hexlify
44
from copy import deepcopy
45
from cStringIO import StringIO
53
from bzrlib.atomicfile import AtomicFile
54
from bzrlib.branch import (Branch,
56
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
import bzrlib.bzrdir as bzrdir
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
59
import bzrlib.errors as errors
60
from bzrlib.errors import (BzrCheckError,
63
WeaveRevisionNotPresent,
67
MergeModifiedFormatError,
70
from bzrlib.inventory import InventoryEntry, Inventory
71
from bzrlib.lockable_files import LockableFiles, TransportLock
72
from bzrlib.lockdir import LockDir
73
from bzrlib.merge import merge_inner, transform_tree
74
from bzrlib.osutils import (
91
from bzrlib.progress import DummyProgress, ProgressPhase
92
from bzrlib.revision import NULL_REVISION
93
from bzrlib.rio import RioReader, rio_file, Stanza
94
from bzrlib.symbol_versioning import *
95
from bzrlib.textui import show_status
97
from bzrlib.transform import build_tree
98
from bzrlib.trace import mutter, note
99
from bzrlib.transport import get_transport
100
from bzrlib.transport.local import LocalTransport
105
# the regex here does the following:
106
# 1) remove any weird characters; we don't escape them but rather
108
# 2) match leading '.'s to make it not hidden
109
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
110
_gen_id_suffix = None
114
def _next_id_suffix():
115
"""Create a new file id suffix that is reasonably unique.
117
On the first call we combine the current time with 64 bits of randomness
118
to give a highly probably globally unique number. Then each call in the same
119
process adds 1 to a serial number we append to that unique value.
121
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
122
# than having to move the id randomness out of the inner loop like this.
123
# XXX TODO: for the global randomness this uses we should add the thread-id
124
# before the serial #.
125
global _gen_id_suffix, _gen_id_serial
126
if _gen_id_suffix is None:
127
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
129
return _gen_id_suffix + str(_gen_id_serial)
132
def gen_file_id(name):
133
"""Return new file id for the basename 'name'.
135
The uniqueness is supplied from _next_id_suffix.
137
# XXX TODO: squash the filename to lowercase.
138
# XXX TODO: truncate the filename to something like 20 or 30 chars.
139
# XXX TODO: consider what to do with ids that look like illegal filepaths
140
# on platforms we support.
141
return _gen_file_id_re.sub('', name) + _next_id_suffix()
145
"""Return a new tree-root file id."""
146
return gen_file_id('TREE_ROOT')
149
class TreeEntry(object):
150
"""An entry that implements the minium interface used by commands.
152
This needs further inspection, it may be better to have
153
InventoryEntries without ids - though that seems wrong. For now,
154
this is a parallel hierarchy to InventoryEntry, and needs to become
155
one of several things: decorates to that hierarchy, children of, or
157
Another note is that these objects are currently only used when there is
158
no InventoryEntry available - i.e. for unversioned objects.
159
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
162
def __eq__(self, other):
163
# yes, this us ugly, TODO: best practice __eq__ style.
164
return (isinstance(other, TreeEntry)
165
and other.__class__ == self.__class__)
167
def kind_character(self):
171
class TreeDirectory(TreeEntry):
172
"""See TreeEntry. This is a directory in a working tree."""
174
def __eq__(self, other):
175
return (isinstance(other, TreeDirectory)
176
and other.__class__ == self.__class__)
178
def kind_character(self):
182
class TreeFile(TreeEntry):
183
"""See TreeEntry. This is a regular file in a working tree."""
185
def __eq__(self, other):
186
return (isinstance(other, TreeFile)
187
and other.__class__ == self.__class__)
189
def kind_character(self):
193
class TreeLink(TreeEntry):
194
"""See TreeEntry. This is a symlink in a working tree."""
196
def __eq__(self, other):
197
return (isinstance(other, TreeLink)
198
and other.__class__ == self.__class__)
200
def kind_character(self):
204
class WorkingTree(bzrlib.tree.Tree):
205
"""Working copy tree.
207
The inventory is held in the `Branch` working-inventory, and the
208
files are in a directory on disk.
210
It is possible for a `WorkingTree` to have a filename which is
211
not listed in the Inventory and vice versa.
214
def __init__(self, basedir='.',
215
branch=DEPRECATED_PARAMETER,
221
"""Construct a WorkingTree for basedir.
223
If the branch is not supplied, it is opened automatically.
224
If the branch is supplied, it must be the branch for this basedir.
225
(branch.base is not cross checked, because for remote branches that
226
would be meaningless).
228
self._format = _format
229
self.bzrdir = _bzrdir
231
# not created via open etc.
232
warn("WorkingTree() is deprecated as of bzr version 0.8. "
233
"Please use bzrdir.open_workingtree or WorkingTree.open().",
236
wt = WorkingTree.open(basedir)
237
self._branch = wt.branch
238
self.basedir = wt.basedir
239
self._control_files = wt._control_files
240
self._hashcache = wt._hashcache
241
self._set_inventory(wt._inventory)
242
self._format = wt._format
243
self.bzrdir = wt.bzrdir
244
from bzrlib.hashcache import HashCache
245
from bzrlib.trace import note, mutter
246
assert isinstance(basedir, basestring), \
247
"base directory %r is not a string" % basedir
248
basedir = safe_unicode(basedir)
249
mutter("opening working tree %r", basedir)
250
if deprecated_passed(branch):
252
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
253
" Please use bzrdir.open_workingtree() or"
254
" WorkingTree.open().",
258
self._branch = branch
260
self._branch = self.bzrdir.open_branch()
261
assert isinstance(self.branch, Branch), \
262
"branch %r is not a Branch" % self.branch
263
self.basedir = realpath(basedir)
264
# if branch is at our basedir and is a format 6 or less
265
if isinstance(self._format, WorkingTreeFormat2):
266
# share control object
267
self._control_files = self.branch.control_files
269
# only ready for format 3
270
assert isinstance(self._format, WorkingTreeFormat3)
271
assert isinstance(_control_files, LockableFiles), \
272
"_control_files must be a LockableFiles, not %r" \
274
self._control_files = _control_files
275
# update the whole cache up front and write to disk if anything changed;
276
# in the future we might want to do this more selectively
277
# two possible ways offer themselves : in self._unlock, write the cache
278
# if needed, or, when the cache sees a change, append it to the hash
279
# cache file, and have the parser take the most recent entry for a
281
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
282
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
284
# is this scan needed ? it makes things kinda slow.
291
if _inventory is None:
292
self._set_inventory(self.read_working_inventory())
294
self._set_inventory(_inventory)
297
fget=lambda self: self._branch,
298
doc="""The branch this WorkingTree is connected to.
300
This cannot be set - it is reflective of the actual disk structure
301
the working tree has been constructed from.
304
def break_lock(self):
305
"""Break a lock if one is present from another instance.
307
Uses the ui factory to ask for confirmation if the lock may be from
310
This will probe the repository for its lock as well.
312
self._control_files.break_lock()
313
self.branch.break_lock()
315
def _set_inventory(self, inv):
316
self._inventory = inv
317
self.path2id = self._inventory.path2id
319
def is_control_filename(self, filename):
320
"""True if filename is the name of a control file in this tree.
322
:param filename: A filename within the tree. This is a relative path
323
from the root of this tree.
325
This is true IF and ONLY IF the filename is part of the meta data
326
that bzr controls in this tree. I.E. a random .bzr directory placed
327
on disk will not be a control file for this tree.
329
return self.bzrdir.is_control_filename(filename)
332
def open(path=None, _unsupported=False):
333
"""Open an existing working tree at path.
337
path = os.path.getcwdu()
338
control = bzrdir.BzrDir.open(path, _unsupported)
339
return control.open_workingtree(_unsupported)
342
def open_containing(path=None):
343
"""Open an existing working tree which has its root about path.
345
This probes for a working tree at path and searches upwards from there.
347
Basically we keep looking up until we find the control directory or
348
run into /. If there isn't one, raises NotBranchError.
349
TODO: give this a new exception.
350
If there is one, it is returned, along with the unused portion of path.
354
control, relpath = bzrdir.BzrDir.open_containing(path)
355
return control.open_workingtree(), relpath
358
def open_downlevel(path=None):
359
"""Open an unsupported working tree.
361
Only intended for advanced situations like upgrading part of a bzrdir.
363
return WorkingTree.open(path, _unsupported=True)
366
"""Iterate through file_ids for this tree.
368
file_ids are in a WorkingTree if they are in the working inventory
369
and the working file exists.
371
inv = self._inventory
372
for path, ie in inv.iter_entries():
373
if bzrlib.osutils.lexists(self.abspath(path)):
377
return "<%s of %s>" % (self.__class__.__name__,
378
getattr(self, 'basedir', None))
380
def abspath(self, filename):
381
return pathjoin(self.basedir, filename)
383
def basis_tree(self):
384
"""Return RevisionTree for the current last revision."""
385
revision_id = self.last_revision()
386
if revision_id is not None:
388
xml = self.read_basis_inventory()
389
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
392
if inv is not None and inv.revision_id == revision_id:
393
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
395
# FIXME? RBC 20060403 should we cache the inventory here ?
396
return self.branch.repository.revision_tree(revision_id)
399
@deprecated_method(zero_eight)
400
def create(branch, directory):
401
"""Create a workingtree for branch at directory.
403
If existing_directory already exists it must have a .bzr directory.
404
If it does not exist, it will be created.
406
This returns a new WorkingTree object for the new checkout.
408
TODO FIXME RBC 20060124 when we have checkout formats in place this
409
should accept an optional revisionid to checkout [and reject this if
410
checking out into the same dir as a pre-checkout-aware branch format.]
412
XXX: When BzrDir is present, these should be created through that
415
warn('delete WorkingTree.create', stacklevel=3)
416
transport = get_transport(directory)
417
if branch.bzrdir.root_transport.base == transport.base:
419
return branch.bzrdir.create_workingtree()
420
# different directory,
421
# create a branch reference
422
# and now a working tree.
423
raise NotImplementedError
426
@deprecated_method(zero_eight)
427
def create_standalone(directory):
428
"""Create a checkout and a branch and a repo at directory.
430
Directory must exist and be empty.
432
please use BzrDir.create_standalone_workingtree
434
return bzrdir.BzrDir.create_standalone_workingtree(directory)
436
def relpath(self, path):
437
"""Return the local path portion from a given path.
439
The path may be absolute or relative. If its a relative path it is
440
interpreted relative to the python current working directory.
442
return relpath(self.basedir, path)
444
def has_filename(self, filename):
445
return bzrlib.osutils.lexists(self.abspath(filename))
447
def get_file(self, file_id):
448
return self.get_file_byname(self.id2path(file_id))
450
def get_file_byname(self, filename):
451
return file(self.abspath(filename), 'rb')
453
def get_root_id(self):
454
"""Return the id of this trees root"""
455
inv = self.read_working_inventory()
456
return inv.root.file_id
458
def _get_store_filename(self, file_id):
459
## XXX: badly named; this is not in the store at all
460
return self.abspath(self.id2path(file_id))
463
def clone(self, to_bzrdir, revision_id=None, basis=None):
464
"""Duplicate this working tree into to_bzr, including all state.
466
Specifically modified files are kept as modified, but
467
ignored and unknown files are discarded.
469
If you want to make a new line of development, see bzrdir.sprout()
472
If not None, the cloned tree will have its last revision set to
473
revision, and and difference between the source trees last revision
474
and this one merged in.
477
If not None, a closer copy of a tree which may have some files in
478
common, and which file content should be preferentially copied from.
480
# assumes the target bzr dir format is compatible.
481
result = self._format.initialize(to_bzrdir)
482
self.copy_content_into(result, revision_id)
486
def copy_content_into(self, tree, revision_id=None):
487
"""Copy the current content and user files of this tree into tree."""
488
if revision_id is None:
489
transform_tree(tree, self)
491
# TODO now merge from tree.last_revision to revision
492
transform_tree(tree, self)
493
tree.set_last_revision(revision_id)
496
def commit(self, message=None, revprops=None, *args, **kwargs):
497
# avoid circular imports
498
from bzrlib.commit import Commit
501
if not 'branch-nick' in revprops:
502
revprops['branch-nick'] = self.branch.nick
503
# args for wt.commit start at message from the Commit.commit method,
504
# but with branch a kwarg now, passing in args as is results in the
505
#message being used for the branch
506
args = (DEPRECATED_PARAMETER, message, ) + args
507
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
508
self._set_inventory(self.read_working_inventory())
510
def id2abspath(self, file_id):
511
return self.abspath(self.id2path(file_id))
513
def has_id(self, file_id):
514
# files that have been deleted are excluded
515
inv = self._inventory
516
if not inv.has_id(file_id):
518
path = inv.id2path(file_id)
519
return bzrlib.osutils.lexists(self.abspath(path))
521
def has_or_had_id(self, file_id):
522
if file_id == self.inventory.root.file_id:
524
return self.inventory.has_id(file_id)
526
__contains__ = has_id
528
def get_file_size(self, file_id):
529
return os.path.getsize(self.id2abspath(file_id))
532
def get_file_sha1(self, file_id, path=None):
534
path = self._inventory.id2path(file_id)
535
return self._hashcache.get_sha1(path)
537
if not supports_executable():
538
def is_executable(self, file_id, path=None):
539
return self._inventory[file_id].executable
541
def is_executable(self, file_id, path=None):
543
path = self._inventory.id2path(file_id)
544
mode = os.lstat(self.abspath(path)).st_mode
545
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
548
def add(self, files, ids=None):
549
"""Make files versioned.
551
Note that the command line normally calls smart_add instead,
552
which can automatically recurse.
554
This adds the files to the inventory, so that they will be
555
recorded by the next commit.
558
List of paths to add, relative to the base of the tree.
561
If set, use these instead of automatically generated ids.
562
Must be the same length as the list of files, but may
563
contain None for ids that are to be autogenerated.
565
TODO: Perhaps have an option to add the ids even if the files do
568
TODO: Perhaps callback with the ids and paths as they're added.
570
# TODO: Re-adding a file that is removed in the working copy
571
# should probably put it back with the previous ID.
572
if isinstance(files, basestring):
573
assert(ids is None or isinstance(ids, basestring))
579
ids = [None] * len(files)
581
assert(len(ids) == len(files))
583
inv = self.read_working_inventory()
584
for f,file_id in zip(files, ids):
585
if self.is_control_filename(f):
586
raise BzrError("cannot add control file %s" % quotefn(f))
591
raise BzrError("cannot add top-level %r" % f)
593
fullpath = normpath(self.abspath(f))
596
kind = file_kind(fullpath)
598
if e.errno == errno.ENOENT:
599
raise NoSuchFile(fullpath)
600
# maybe something better?
601
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
603
if not InventoryEntry.versionable_kind(kind):
604
raise BzrError('cannot add: not a versionable file ('
605
'i.e. regular file, symlink or directory): %s' % quotefn(f))
608
inv.add_path(f, kind=kind)
610
inv.add_path(f, kind=kind, file_id=file_id)
612
self._write_inventory(inv)
615
def add_pending_merge(self, *revision_ids):
616
# TODO: Perhaps should check at this point that the
617
# history of the revision is actually present?
618
p = self.pending_merges()
620
for rev_id in revision_ids:
626
self.set_pending_merges(p)
629
def pending_merges(self):
630
"""Return a list of pending merges.
632
These are revisions that have been merged into the working
633
directory but not yet committed.
636
merges_file = self._control_files.get_utf8('pending-merges')
638
if e.errno != errno.ENOENT:
642
for l in merges_file.readlines():
643
p.append(l.rstrip('\n'))
647
def set_pending_merges(self, rev_list):
648
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
651
def set_merge_modified(self, modified_hashes):
653
for file_id, hash in modified_hashes.iteritems():
654
yield Stanza(file_id=file_id, hash=hash)
655
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
658
def _put_rio(self, filename, stanzas, header):
659
my_file = rio_file(stanzas, header)
660
self._control_files.put(filename, my_file)
663
def merge_modified(self):
665
hashfile = self._control_files.get('merge-hashes')
670
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
671
raise MergeModifiedFormatError()
672
except StopIteration:
673
raise MergeModifiedFormatError()
674
for s in RioReader(hashfile):
675
file_id = s.get("file_id")
676
if file_id not in self.inventory:
679
if hash == self.get_file_sha1(file_id):
680
merge_hashes[file_id] = hash
683
def get_symlink_target(self, file_id):
684
return os.readlink(self.id2abspath(file_id))
686
def file_class(self, filename):
687
if self.path2id(filename):
689
elif self.is_ignored(filename):
694
def list_files(self):
695
"""Recursively list all files as (path, class, kind, id, entry).
697
Lists, but does not descend into unversioned directories.
699
This does not include files that have been deleted in this
702
Skips the control directory.
704
inv = self._inventory
705
# Convert these into local objects to save lookup times
706
pathjoin = bzrlib.osutils.pathjoin
707
file_kind = bzrlib.osutils.file_kind
709
# transport.base ends in a slash, we want the piece
710
# between the last two slashes
711
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
713
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
715
# directory file_id, relative path, absolute path, reverse sorted children
716
children = os.listdir(self.basedir)
718
# jam 20060527 The kernel sized tree seems equivalent whether we
719
# use a deque and popleft to keep them sorted, or if we use a plain
720
# list and just reverse() them.
721
children = collections.deque(children)
722
stack = [(inv.root.file_id, u'', self.basedir, children)]
724
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
727
f = children.popleft()
728
## TODO: If we find a subdirectory with its own .bzr
729
## directory, then that is a separate tree and we
730
## should exclude it.
732
# the bzrdir for this tree
733
if transport_base_dir == f:
736
# we know that from_dir_relpath and from_dir_abspath never end in a slash
737
# and 'f' doesn't begin with one, we can do a string op, rather
738
# than the checks of pathjoin(), all relative paths will have an extra slash
740
fp = from_dir_relpath + '/' + f
743
fap = from_dir_abspath + '/' + f
745
f_ie = inv.get_child(from_dir_id, f)
748
elif self.is_ignored(fp[1:]):
757
raise BzrCheckError("file %r entered as kind %r id %r, "
759
% (fap, f_ie.kind, f_ie.file_id, fk))
761
# make a last minute entry
763
yield fp[1:], c, fk, f_ie.file_id, f_ie
766
yield fp[1:], c, fk, None, fk_entries[fk]()
768
yield fp[1:], c, fk, None, TreeEntry()
771
if fk != 'directory':
774
# But do this child first
775
new_children = os.listdir(fap)
777
new_children = collections.deque(new_children)
778
stack.append((f_ie.file_id, fp, fap, new_children))
779
# Break out of inner loop, so that we start outer loop with child
782
# if we finished all children, pop it off the stack
787
def move(self, from_paths, to_name):
790
to_name must exist in the inventory.
792
If to_name exists and is a directory, the files are moved into
793
it, keeping their old names.
795
Note that to_name is only the last component of the new name;
796
this doesn't change the directory.
798
This returns a list of (from_path, to_path) pairs for each
802
## TODO: Option to move IDs only
803
assert not isinstance(from_paths, basestring)
805
to_abs = self.abspath(to_name)
806
if not isdir(to_abs):
807
raise BzrError("destination %r is not a directory" % to_abs)
808
if not self.has_filename(to_name):
809
raise BzrError("destination %r not in working directory" % to_abs)
810
to_dir_id = inv.path2id(to_name)
811
if to_dir_id == None and to_name != '':
812
raise BzrError("destination %r is not a versioned directory" % to_name)
813
to_dir_ie = inv[to_dir_id]
814
if to_dir_ie.kind not in ('directory', 'root_directory'):
815
raise BzrError("destination %r is not a directory" % to_abs)
817
to_idpath = inv.get_idpath(to_dir_id)
820
if not self.has_filename(f):
821
raise BzrError("%r does not exist in working tree" % f)
822
f_id = inv.path2id(f)
824
raise BzrError("%r is not versioned" % f)
825
name_tail = splitpath(f)[-1]
826
dest_path = pathjoin(to_name, name_tail)
827
if self.has_filename(dest_path):
828
raise BzrError("destination %r already exists" % dest_path)
829
if f_id in to_idpath:
830
raise BzrError("can't move %r to a subdirectory of itself" % f)
832
# OK, so there's a race here, it's possible that someone will
833
# create a file in this interval and then the rename might be
834
# left half-done. But we should have caught most problems.
835
orig_inv = deepcopy(self.inventory)
838
name_tail = splitpath(f)[-1]
839
dest_path = pathjoin(to_name, name_tail)
840
result.append((f, dest_path))
841
inv.rename(inv.path2id(f), to_dir_id, name_tail)
843
rename(self.abspath(f), self.abspath(dest_path))
845
raise BzrError("failed to rename %r to %r: %s" %
846
(f, dest_path, e[1]),
847
["rename rolled back"])
849
# restore the inventory on error
850
self._set_inventory(orig_inv)
852
self._write_inventory(inv)
856
def rename_one(self, from_rel, to_rel):
859
This can change the directory or the filename or both.
862
if not self.has_filename(from_rel):
863
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
864
if self.has_filename(to_rel):
865
raise BzrError("can't rename: new working file %r already exists" % to_rel)
867
file_id = inv.path2id(from_rel)
869
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
872
from_parent = entry.parent_id
873
from_name = entry.name
875
if inv.path2id(to_rel):
876
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
878
to_dir, to_tail = os.path.split(to_rel)
879
to_dir_id = inv.path2id(to_dir)
880
if to_dir_id == None and to_dir != '':
881
raise BzrError("can't determine destination directory id for %r" % to_dir)
883
mutter("rename_one:")
884
mutter(" file_id {%s}" % file_id)
885
mutter(" from_rel %r" % from_rel)
886
mutter(" to_rel %r" % to_rel)
887
mutter(" to_dir %r" % to_dir)
888
mutter(" to_dir_id {%s}" % to_dir_id)
890
inv.rename(file_id, to_dir_id, to_tail)
892
from_abs = self.abspath(from_rel)
893
to_abs = self.abspath(to_rel)
895
rename(from_abs, to_abs)
897
inv.rename(file_id, from_parent, from_name)
898
raise BzrError("failed to rename %r to %r: %s"
899
% (from_abs, to_abs, e[1]),
900
["rename rolled back"])
901
self._write_inventory(inv)
905
"""Return all unknown files.
907
These are files in the working directory that are not versioned or
908
control files or ignored.
910
>>> from bzrlib.bzrdir import ScratchDir
911
>>> d = ScratchDir(files=['foo', 'foo~'])
912
>>> b = d.open_branch()
913
>>> tree = d.open_workingtree()
914
>>> map(str, tree.unknowns())
917
>>> list(b.unknowns())
919
>>> tree.remove('foo')
920
>>> list(b.unknowns())
923
for subp in self.extras():
924
if not self.is_ignored(subp):
927
@deprecated_method(zero_eight)
928
def iter_conflicts(self):
929
"""List all files in the tree that have text or content conflicts.
930
DEPRECATED. Use conflicts instead."""
931
return self._iter_conflicts()
933
def _iter_conflicts(self):
935
for info in self.list_files():
937
stem = get_conflicted_stem(path)
940
if stem not in conflicted:
945
def pull(self, source, overwrite=False, stop_revision=None):
946
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
949
pp = ProgressPhase("Pull phase", 2, top_pb)
951
old_revision_history = self.branch.revision_history()
952
basis_tree = self.basis_tree()
953
count = self.branch.pull(source, overwrite, stop_revision)
954
new_revision_history = self.branch.revision_history()
955
if new_revision_history != old_revision_history:
957
if len(old_revision_history):
958
other_revision = old_revision_history[-1]
960
other_revision = None
961
repository = self.branch.repository
962
pb = bzrlib.ui.ui_factory.nested_progress_bar()
964
merge_inner(self.branch,
965
self.branch.basis_tree(),
971
self.set_last_revision(self.branch.last_revision())
978
"""Yield all unknown files in this WorkingTree.
980
If there are any unknown directories then only the directory is
981
returned, not all its children. But if there are unknown files
982
under a versioned subdirectory, they are returned.
984
Currently returned depth-first, sorted by name within directories.
986
## TODO: Work from given directory downwards
987
for path, dir_entry in self.inventory.directories():
988
mutter("search for unknowns in %r", path)
989
dirabs = self.abspath(path)
990
if not isdir(dirabs):
991
# e.g. directory deleted
995
for subf in os.listdir(dirabs):
997
and (subf not in dir_entry.children)):
1002
subp = pathjoin(path, subf)
1005
def _translate_ignore_rule(self, rule):
1006
"""Translate a single ignore rule to a regex.
1008
There are two types of ignore rules. Those that do not contain a / are
1009
matched against the tail of the filename (that is, they do not care
1010
what directory the file is in.) Rules which do contain a slash must
1011
match the entire path. As a special case, './' at the start of the
1012
string counts as a slash in the string but is removed before matching
1013
(e.g. ./foo.c, ./src/foo.c)
1015
:return: The translated regex.
1017
if rule[:2] in ('./', '.\\'):
1019
result = fnmatch.translate(rule[2:])
1020
elif '/' in rule or '\\' in rule:
1022
result = fnmatch.translate(rule)
1024
# default rule style.
1025
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1026
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1027
return "(" + result + ")"
1029
def _combine_ignore_rules(self, rules):
1030
"""Combine a list of ignore rules into a single regex object.
1032
Each individual rule is combined with | to form a big regex, which then
1033
has $ added to it to form something like ()|()|()$. The group index for
1034
each subregex's outermost group is placed in a dictionary mapping back
1035
to the rule. This allows quick identification of the matching rule that
1037
:return: a list of the compiled regex and the matching-group index
1038
dictionaries. We return a list because python complains if you try to
1039
combine more than 100 regexes.
1044
translated_rules = []
1046
translated_rule = self._translate_ignore_rule(rule)
1047
compiled_rule = re.compile(translated_rule)
1048
groups[next_group] = rule
1049
next_group += compiled_rule.groups
1050
translated_rules.append(translated_rule)
1051
if next_group == 99:
1052
result.append((re.compile("|".join(translated_rules)), groups))
1055
translated_rules = []
1056
if len(translated_rules):
1057
result.append((re.compile("|".join(translated_rules)), groups))
1060
def ignored_files(self):
1061
"""Yield list of PATH, IGNORE_PATTERN"""
1062
for subp in self.extras():
1063
pat = self.is_ignored(subp)
1067
def get_ignore_list(self):
1068
"""Return list of ignore patterns.
1070
Cached in the Tree object after the first call.
1072
if hasattr(self, '_ignorelist'):
1073
return self._ignorelist
1075
l = bzrlib.DEFAULT_IGNORE[:]
1076
if self.has_filename(bzrlib.IGNORE_FILENAME):
1077
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1078
l.extend([line.rstrip("\n\r") for line in f.readlines()])
1079
self._ignorelist = l
1080
self._ignore_regex = self._combine_ignore_rules(l)
1083
def _get_ignore_rules_as_regex(self):
1084
"""Return a regex of the ignore rules and a mapping dict.
1086
:return: (ignore rules compiled regex, dictionary mapping rule group
1087
indices to original rule.)
1089
if getattr(self, '_ignorelist', None) is None:
1090
self.get_ignore_list()
1091
return self._ignore_regex
1093
def is_ignored(self, filename):
1094
r"""Check whether the filename matches an ignore pattern.
1096
Patterns containing '/' or '\' need to match the whole path;
1097
others match against only the last component.
1099
If the file is ignored, returns the pattern which caused it to
1100
be ignored, otherwise None. So this can simply be used as a
1101
boolean if desired."""
1103
# TODO: Use '**' to match directories, and other extended
1104
# globbing stuff from cvs/rsync.
1106
# XXX: fnmatch is actually not quite what we want: it's only
1107
# approximately the same as real Unix fnmatch, and doesn't
1108
# treat dotfiles correctly and allows * to match /.
1109
# Eventually it should be replaced with something more
1112
rules = self._get_ignore_rules_as_regex()
1113
for regex, mapping in rules:
1114
match = regex.match(filename)
1115
if match is not None:
1116
# one or more of the groups in mapping will have a non-None group
1118
groups = match.groups()
1119
rules = [mapping[group] for group in
1120
mapping if groups[group] is not None]
1124
def kind(self, file_id):
1125
return file_kind(self.id2abspath(file_id))
1128
def last_revision(self):
1129
"""Return the last revision id of this working tree.
1131
In early branch formats this was == the branch last_revision,
1132
but that cannot be relied upon - for working tree operations,
1133
always use tree.last_revision().
1135
return self.branch.last_revision()
1137
def is_locked(self):
1138
return self._control_files.is_locked()
1140
def lock_read(self):
1141
"""See Branch.lock_read, and WorkingTree.unlock."""
1142
self.branch.lock_read()
1144
return self._control_files.lock_read()
1146
self.branch.unlock()
1149
def lock_write(self):
1150
"""See Branch.lock_write, and WorkingTree.unlock."""
1151
self.branch.lock_write()
1153
return self._control_files.lock_write()
1155
self.branch.unlock()
1158
def get_physical_lock_status(self):
1159
return self._control_files.get_physical_lock_status()
1161
def _basis_inventory_name(self):
1162
return 'basis-inventory'
1165
def set_last_revision(self, new_revision):
1166
"""Change the last revision in the working tree."""
1167
if self._change_last_revision(new_revision):
1168
self._cache_basis_inventory(new_revision)
1170
def _change_last_revision(self, new_revision):
1171
"""Template method part of set_last_revision to perform the change.
1173
This is used to allow WorkingTree3 instances to not affect branch
1174
when their last revision is set.
1176
if new_revision is None:
1177
self.branch.set_revision_history([])
1179
# current format is locked in with the branch
1180
revision_history = self.branch.revision_history()
1182
position = revision_history.index(new_revision)
1184
raise errors.NoSuchRevision(self.branch, new_revision)
1185
self.branch.set_revision_history(revision_history[:position + 1])
1188
def _cache_basis_inventory(self, new_revision):
1189
"""Cache new_revision as the basis inventory."""
1191
# this double handles the inventory - unpack and repack -
1192
# but is easier to understand. We can/should put a conditional
1193
# in here based on whether the inventory is in the latest format
1194
# - perhaps we should repack all inventories on a repository
1196
inv = self.branch.repository.get_inventory(new_revision)
1197
inv.revision_id = new_revision
1198
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1200
path = self._basis_inventory_name()
1201
self._control_files.put_utf8(path, xml)
1202
except WeaveRevisionNotPresent:
1205
def read_basis_inventory(self):
1206
"""Read the cached basis inventory."""
1207
path = self._basis_inventory_name()
1208
return self._control_files.get_utf8(path).read()
1211
def read_working_inventory(self):
1212
"""Read the working inventory."""
1213
# ElementTree does its own conversion from UTF-8, so open in
1215
result = bzrlib.xml5.serializer_v5.read_inventory(
1216
self._control_files.get('inventory'))
1217
self._set_inventory(result)
1221
def remove(self, files, verbose=False):
1222
"""Remove nominated files from the working inventory..
1224
This does not remove their text. This does not run on XXX on what? RBC
1226
TODO: Refuse to remove modified files unless --force is given?
1228
TODO: Do something useful with directories.
1230
TODO: Should this remove the text or not? Tough call; not
1231
removing may be useful and the user can just use use rm, and
1232
is the opposite of add. Removing it is consistent with most
1233
other tools. Maybe an option.
1235
## TODO: Normalize names
1236
## TODO: Remove nested loops; better scalability
1237
if isinstance(files, basestring):
1240
inv = self.inventory
1242
# do this before any modifications
1244
fid = inv.path2id(f)
1246
# TODO: Perhaps make this just a warning, and continue?
1247
# This tends to happen when
1248
raise NotVersionedError(path=f)
1249
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1251
# having remove it, it must be either ignored or unknown
1252
if self.is_ignored(f):
1256
show_status(new_status, inv[fid].kind, quotefn(f))
1259
self._write_inventory(inv)
1262
def revert(self, filenames, old_tree=None, backups=True,
1263
pb=DummyProgress()):
1264
from transform import revert
1265
from conflicts import resolve
1266
if old_tree is None:
1267
old_tree = self.basis_tree()
1268
conflicts = revert(self, old_tree, filenames, backups, pb)
1269
if not len(filenames):
1270
self.set_pending_merges([])
1273
resolve(self, filenames, ignore_misses=True)
1276
# XXX: This method should be deprecated in favour of taking in a proper
1277
# new Inventory object.
1279
def set_inventory(self, new_inventory_list):
1280
from bzrlib.inventory import (Inventory,
1285
inv = Inventory(self.get_root_id())
1286
for path, file_id, parent, kind in new_inventory_list:
1287
name = os.path.basename(path)
1290
# fixme, there should be a factory function inv,add_??
1291
if kind == 'directory':
1292
inv.add(InventoryDirectory(file_id, name, parent))
1293
elif kind == 'file':
1294
inv.add(InventoryFile(file_id, name, parent))
1295
elif kind == 'symlink':
1296
inv.add(InventoryLink(file_id, name, parent))
1298
raise BzrError("unknown kind %r" % kind)
1299
self._write_inventory(inv)
1302
def set_root_id(self, file_id):
1303
"""Set the root id for this tree."""
1304
inv = self.read_working_inventory()
1305
orig_root_id = inv.root.file_id
1306
del inv._byid[inv.root.file_id]
1307
inv.root.file_id = file_id
1308
inv._byid[inv.root.file_id] = inv.root
1311
if entry.parent_id == orig_root_id:
1312
entry.parent_id = inv.root.file_id
1313
self._write_inventory(inv)
1316
"""See Branch.unlock.
1318
WorkingTree locking just uses the Branch locking facilities.
1319
This is current because all working trees have an embedded branch
1320
within them. IF in the future, we were to make branch data shareable
1321
between multiple working trees, i.e. via shared storage, then we
1322
would probably want to lock both the local tree, and the branch.
1324
# FIXME: We want to write out the hashcache only when the last lock on
1325
# this working copy is released. Peeking at the lock count is a bit
1326
# of a nasty hack; probably it's better to have a transaction object,
1327
# which can do some finalization when it's either successfully or
1328
# unsuccessfully completed. (Denys's original patch did that.)
1329
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1330
# wrongly. Hookinh into unllock on the control files object is fine though.
1332
# TODO: split this per format so there is no ugly if block
1333
if self._hashcache.needs_write and (
1334
# dedicated lock files
1335
self._control_files._lock_count==1 or
1337
(self._control_files is self.branch.control_files and
1338
self._control_files._lock_count==3)):
1339
self._hashcache.write()
1340
# reverse order of locking.
1342
return self._control_files.unlock()
1344
self.branch.unlock()
1348
"""Update a working tree along its branch.
1350
This will update the branch if its bound too, which means we have multiple trees involved:
1351
The new basis tree of the master.
1352
The old basis tree of the branch.
1353
The old basis tree of the working tree.
1354
The current working tree state.
1355
pathologically all three may be different, and non ancestors of each other.
1356
Conceptually we want to:
1357
Preserve the wt.basis->wt.state changes
1358
Transform the wt.basis to the new master basis.
1359
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1360
Restore the wt.basis->wt.state changes.
1362
There isn't a single operation at the moment to do that, so we:
1363
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1364
Do a 'normal' merge of the old branch basis if it is relevant.
1366
old_tip = self.branch.update()
1367
if old_tip is not None:
1368
self.add_pending_merge(old_tip)
1369
self.branch.lock_read()
1372
if self.last_revision() != self.branch.last_revision():
1373
# merge tree state up to new branch tip.
1374
basis = self.basis_tree()
1375
to_tree = self.branch.basis_tree()
1376
result += merge_inner(self.branch,
1380
self.set_last_revision(self.branch.last_revision())
1381
if old_tip and old_tip != self.last_revision():
1382
# our last revision was not the prior branch last reivison
1383
# and we have converted that last revision to a pending merge.
1384
# base is somewhere between the branch tip now
1385
# and the now pending merge
1386
from bzrlib.revision import common_ancestor
1388
base_rev_id = common_ancestor(self.branch.last_revision(),
1390
self.branch.repository)
1391
except errors.NoCommonAncestor:
1393
base_tree = self.branch.repository.revision_tree(base_rev_id)
1394
other_tree = self.branch.repository.revision_tree(old_tip)
1395
result += merge_inner(self.branch,
1401
self.branch.unlock()
1404
def _write_inventory(self, inv):
1405
"""Write inventory as the current inventory."""
1407
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1409
self._control_files.put('inventory', sio)
1410
self._set_inventory(inv)
1411
mutter('wrote working inventory')
1413
def set_conflicts(self, arg):
1414
raise UnsupportedOperation(self.set_conflicts, self)
1417
def conflicts(self):
1418
conflicts = ConflictList()
1419
for conflicted in self._iter_conflicts():
1422
if file_kind(self.abspath(conflicted)) != "file":
1425
if e.errno == errno.ENOENT:
1430
for suffix in ('.THIS', '.OTHER'):
1432
kind = file_kind(self.abspath(conflicted+suffix))
1434
if e.errno == errno.ENOENT:
1442
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1443
conflicts.append(Conflict.factory(ctype, path=conflicted,
1444
file_id=self.path2id(conflicted)))
1448
class WorkingTree3(WorkingTree):
1449
"""This is the Format 3 working tree.
1451
This differs from the base WorkingTree by:
1452
- having its own file lock
1453
- having its own last-revision property.
1455
This is new in bzr 0.8
1459
def last_revision(self):
1460
"""See WorkingTree.last_revision."""
1462
return self._control_files.get_utf8('last-revision').read()
1466
def _change_last_revision(self, revision_id):
1467
"""See WorkingTree._change_last_revision."""
1468
if revision_id is None or revision_id == NULL_REVISION:
1470
self._control_files._transport.delete('last-revision')
1471
except errors.NoSuchFile:
1476
self.branch.revision_history().index(revision_id)
1478
raise errors.NoSuchRevision(self.branch, revision_id)
1479
self._control_files.put_utf8('last-revision', revision_id)
1483
def set_conflicts(self, conflicts):
1484
self._put_rio('conflicts', conflicts.to_stanzas(),
1488
def conflicts(self):
1490
confile = self._control_files.get('conflicts')
1492
return ConflictList()
1494
if confile.next() != CONFLICT_HEADER_1 + '\n':
1495
raise ConflictFormatError()
1496
except StopIteration:
1497
raise ConflictFormatError()
1498
return ConflictList.from_stanzas(RioReader(confile))
1501
def get_conflicted_stem(path):
1502
for suffix in CONFLICT_SUFFIXES:
1503
if path.endswith(suffix):
1504
return path[:-len(suffix)]
1506
@deprecated_function(zero_eight)
1507
def is_control_file(filename):
1508
"""See WorkingTree.is_control_filename(filename)."""
1509
## FIXME: better check
1510
filename = normpath(filename)
1511
while filename != '':
1512
head, tail = os.path.split(filename)
1513
## mutter('check %r for control file' % ((head, tail),))
1516
if filename == head:
1522
class WorkingTreeFormat(object):
1523
"""An encapsulation of the initialization and open routines for a format.
1525
Formats provide three things:
1526
* An initialization routine,
1530
Formats are placed in an dict by their format string for reference
1531
during workingtree opening. Its not required that these be instances, they
1532
can be classes themselves with class methods - it simply depends on
1533
whether state is needed for a given format or not.
1535
Once a format is deprecated, just deprecate the initialize and open
1536
methods on the format class. Do not deprecate the object, as the
1537
object will be created every time regardless.
1540
_default_format = None
1541
"""The default format used for new trees."""
1544
"""The known formats."""
1547
def find_format(klass, a_bzrdir):
1548
"""Return the format for the working tree object in a_bzrdir."""
1550
transport = a_bzrdir.get_workingtree_transport(None)
1551
format_string = transport.get("format").read()
1552
return klass._formats[format_string]
1554
raise errors.NoWorkingTree(base=transport.base)
1556
raise errors.UnknownFormatError(format=format_string)
1559
def get_default_format(klass):
1560
"""Return the current default format."""
1561
return klass._default_format
1563
def get_format_string(self):
1564
"""Return the ASCII format string that identifies this format."""
1565
raise NotImplementedError(self.get_format_string)
1567
def get_format_description(self):
1568
"""Return the short description for this format."""
1569
raise NotImplementedError(self.get_format_description)
1571
def is_supported(self):
1572
"""Is this format supported?
1574
Supported formats can be initialized and opened.
1575
Unsupported formats may not support initialization or committing or
1576
some other features depending on the reason for not being supported.
1581
def register_format(klass, format):
1582
klass._formats[format.get_format_string()] = format
1585
def set_default_format(klass, format):
1586
klass._default_format = format
1589
def unregister_format(klass, format):
1590
assert klass._formats[format.get_format_string()] is format
1591
del klass._formats[format.get_format_string()]
1595
class WorkingTreeFormat2(WorkingTreeFormat):
1596
"""The second working tree format.
1598
This format modified the hash cache from the format 1 hash cache.
1601
def get_format_description(self):
1602
"""See WorkingTreeFormat.get_format_description()."""
1603
return "Working tree format 2"
1605
def stub_initialize_remote(self, control_files):
1606
"""As a special workaround create critical control files for a remote working tree
1608
This ensures that it can later be updated and dealt with locally,
1609
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1610
no working tree. (See bug #43064).
1614
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1616
control_files.put('inventory', sio)
1618
control_files.put_utf8('pending-merges', '')
1621
def initialize(self, a_bzrdir, revision_id=None):
1622
"""See WorkingTreeFormat.initialize()."""
1623
if not isinstance(a_bzrdir.transport, LocalTransport):
1624
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1625
branch = a_bzrdir.open_branch()
1626
if revision_id is not None:
1629
revision_history = branch.revision_history()
1631
position = revision_history.index(revision_id)
1633
raise errors.NoSuchRevision(branch, revision_id)
1634
branch.set_revision_history(revision_history[:position + 1])
1637
revision = branch.last_revision()
1639
wt = WorkingTree(a_bzrdir.root_transport.base,
1645
wt._write_inventory(inv)
1646
wt.set_root_id(inv.root.file_id)
1647
wt.set_last_revision(revision)
1648
wt.set_pending_merges([])
1649
build_tree(wt.basis_tree(), wt)
1653
super(WorkingTreeFormat2, self).__init__()
1654
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1656
def open(self, a_bzrdir, _found=False):
1657
"""Return the WorkingTree object for a_bzrdir
1659
_found is a private parameter, do not use it. It is used to indicate
1660
if format probing has already been done.
1663
# we are being called directly and must probe.
1664
raise NotImplementedError
1665
if not isinstance(a_bzrdir.transport, LocalTransport):
1666
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1667
return WorkingTree(a_bzrdir.root_transport.base,
1673
class WorkingTreeFormat3(WorkingTreeFormat):
1674
"""The second working tree format updated to record a format marker.
1677
- exists within a metadir controlling .bzr
1678
- includes an explicit version marker for the workingtree control
1679
files, separate from the BzrDir format
1680
- modifies the hash cache format
1682
- uses a LockDir to guard access to the repository
1685
def get_format_string(self):
1686
"""See WorkingTreeFormat.get_format_string()."""
1687
return "Bazaar-NG Working Tree format 3"
1689
def get_format_description(self):
1690
"""See WorkingTreeFormat.get_format_description()."""
1691
return "Working tree format 3"
1693
_lock_file_name = 'lock'
1694
_lock_class = LockDir
1696
def _open_control_files(self, a_bzrdir):
1697
transport = a_bzrdir.get_workingtree_transport(None)
1698
return LockableFiles(transport, self._lock_file_name,
1701
def initialize(self, a_bzrdir, revision_id=None):
1702
"""See WorkingTreeFormat.initialize().
1704
revision_id allows creating a working tree at a differnet
1705
revision than the branch is at.
1707
if not isinstance(a_bzrdir.transport, LocalTransport):
1708
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1709
transport = a_bzrdir.get_workingtree_transport(self)
1710
control_files = self._open_control_files(a_bzrdir)
1711
control_files.create_lock()
1712
control_files.lock_write()
1713
control_files.put_utf8('format', self.get_format_string())
1714
branch = a_bzrdir.open_branch()
1715
if revision_id is None:
1716
revision_id = branch.last_revision()
1718
wt = WorkingTree3(a_bzrdir.root_transport.base,
1724
_control_files=control_files)
1727
wt._write_inventory(inv)
1728
wt.set_root_id(inv.root.file_id)
1729
wt.set_last_revision(revision_id)
1730
wt.set_pending_merges([])
1731
build_tree(wt.basis_tree(), wt)
1734
control_files.unlock()
1738
super(WorkingTreeFormat3, self).__init__()
1739
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1741
def open(self, a_bzrdir, _found=False):
1742
"""Return the WorkingTree object for a_bzrdir
1744
_found is a private parameter, do not use it. It is used to indicate
1745
if format probing has already been done.
1748
# we are being called directly and must probe.
1749
raise NotImplementedError
1750
if not isinstance(a_bzrdir.transport, LocalTransport):
1751
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1752
control_files = self._open_control_files(a_bzrdir)
1753
return WorkingTree3(a_bzrdir.root_transport.base,
1757
_control_files=control_files)
1760
return self.get_format_string()
1763
# formats which have no format string are not discoverable
1764
# and not independently creatable, so are not registered.
1765
__default_format = WorkingTreeFormat3()
1766
WorkingTreeFormat.register_format(__default_format)
1767
WorkingTreeFormat.set_default_format(__default_format)
1768
_legacy_formats = [WorkingTreeFormat2(),
1772
class WorkingTreeTestProviderAdapter(object):
1773
"""A tool to generate a suite testing multiple workingtree formats at once.
1775
This is done by copying the test once for each transport and injecting
1776
the transport_server, transport_readonly_server, and workingtree_format
1777
classes into each copy. Each copy is also given a new id() to make it
1781
def __init__(self, transport_server, transport_readonly_server, formats):
1782
self._transport_server = transport_server
1783
self._transport_readonly_server = transport_readonly_server
1784
self._formats = formats
1786
def adapt(self, test):
1787
from bzrlib.tests import TestSuite
1788
result = TestSuite()
1789
for workingtree_format, bzrdir_format in self._formats:
1790
new_test = deepcopy(test)
1791
new_test.transport_server = self._transport_server
1792
new_test.transport_readonly_server = self._transport_readonly_server
1793
new_test.bzrdir_format = bzrdir_format
1794
new_test.workingtree_format = workingtree_format
1795
def make_new_test_id():
1796
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1797
return lambda: new_id
1798
new_test.id = make_new_test_id()
1799
result.addTest(new_test)