50
51
from time import time
52
54
from bzrlib.atomicfile import AtomicFile
53
from bzrlib.branch import (Branch,
55
55
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
56
56
import bzrlib.bzrdir as bzrdir
57
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
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 *
93
from bzrlib.symbol_versioning import (deprecated_passed,
96
100
from bzrlib.textui import show_status
97
101
import bzrlib.tree
98
102
from bzrlib.transform import build_tree
99
103
from bzrlib.trace import mutter, note
100
104
from bzrlib.transport import get_transport
101
105
from bzrlib.transport.local import LocalTransport
106
import bzrlib.urlutils as urlutils
103
108
import bzrlib.xml5
150
155
class TreeEntry(object):
151
"""An entry that implements the minium interface used by commands.
156
"""An entry that implements the minimum interface used by commands.
153
158
This needs further inspection, it may be better to have
154
159
InventoryEntries without ids - though that seems wrong. For now,
250
255
mutter("opening working tree %r", basedir)
251
256
if deprecated_passed(branch):
252
257
if not _internal:
253
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
258
warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
254
259
" Please use bzrdir.open_workingtree() or"
255
260
" WorkingTree.open().",
256
261
DeprecationWarning,
259
264
self._branch = branch
261
266
self._branch = self.bzrdir.open_branch()
262
assert isinstance(self.branch, Branch), \
263
"branch %r is not a Branch" % self.branch
264
267
self.basedir = realpath(basedir)
265
268
# if branch is at our basedir and is a format 6 or less
266
269
if isinstance(self._format, WorkingTreeFormat2):
279
282
# if needed, or, when the cache sees a change, append it to the hash
280
283
# cache file, and have the parser take the most recent entry for a
281
284
# given path only.
282
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
285
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
283
286
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
285
288
# is this scan needed ? it makes things kinda slow.
288
291
if hc.needs_write:
289
292
mutter("write hc")
349
352
run into /. If there isn't one, raises NotBranchError.
350
353
TODO: give this a new exception.
351
354
If there is one, it is returned, along with the unused portion of path.
356
:return: The WorkingTree that contains 'path', and the rest of path
354
359
path = os.getcwdu()
355
360
control, relpath = bzrdir.BzrDir.open_containing(path)
356
362
return control.open_workingtree(), relpath
413
419
XXX: When BzrDir is present, these should be created through that
414
420
interface instead.
416
warn('delete WorkingTree.create', stacklevel=3)
422
warnings.warn('delete WorkingTree.create', stacklevel=3)
417
423
transport = get_transport(directory)
418
424
if branch.bzrdir.root_transport.base == transport.base:
451
457
def get_file_byname(self, filename):
452
458
return file(self.abspath(filename), 'rb')
460
def get_parent_ids(self):
461
"""See Tree.get_parent_ids.
463
This implementation reads the pending merges list and last_revision
464
value and uses that to decide what the parents list should be.
466
last_rev = self.last_revision()
471
other_parents = self.pending_merges()
472
return parents + other_parents
454
474
def get_root_id(self):
455
475
"""Return the id of this trees root"""
456
476
inv = self.read_working_inventory()
505
525
# but with branch a kwarg now, passing in args as is results in the
506
526
#message being used for the branch
507
527
args = (DEPRECATED_PARAMETER, message, ) + args
508
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
528
committed_id = Commit().commit( working_tree=self, revprops=revprops,
509
530
self._set_inventory(self.read_working_inventory())
511
533
def id2abspath(self, file_id):
512
534
return self.abspath(self.id2path(file_id))
530
552
return os.path.getsize(self.id2abspath(file_id))
533
def get_file_sha1(self, file_id):
534
path = self._inventory.id2path(file_id)
555
def get_file_sha1(self, file_id, path=None):
557
path = self._inventory.id2path(file_id)
535
558
return self._hashcache.get_sha1(path)
537
def is_executable(self, file_id):
538
if not supports_executable():
560
def get_file_mtime(self, file_id, path=None):
562
path = self._inventory.id2path(file_id)
563
return os.lstat(self.abspath(path)).st_mtime
565
if not supports_executable():
566
def is_executable(self, file_id, path=None):
539
567
return self._inventory[file_id].executable
541
path = self._inventory.id2path(file_id)
569
def is_executable(self, file_id, path=None):
571
path = self._inventory.id2path(file_id)
542
572
mode = os.lstat(self.abspath(path)).st_mode
543
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
573
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
545
575
@needs_write_lock
546
576
def add(self, files, ids=None):
581
611
inv = self.read_working_inventory()
582
612
for f,file_id in zip(files, ids):
583
613
if self.is_control_filename(f):
584
raise BzrError("cannot add control file %s" % quotefn(f))
614
raise errors.ForbiddenControlFileError(filename=f)
586
616
fp = splitpath(f)
589
619
raise BzrError("cannot add top-level %r" % f)
591
621
fullpath = normpath(self.abspath(f))
594
623
kind = file_kind(fullpath)
595
624
except OSError, e:
596
625
if e.errno == errno.ENOENT:
597
626
raise NoSuchFile(fullpath)
598
# maybe something better?
599
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
601
627
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))
628
raise errors.BadFileKindError(filename=f, kind=kind)
605
629
if file_id is None:
606
630
inv.add_path(f, kind=kind)
692
714
def list_files(self):
693
"""Recursively list all files as (path, class, kind, id).
715
"""Recursively list all files as (path, class, kind, id, entry).
695
717
Lists, but does not descend into unversioned directories.
700
722
Skips the control directory.
702
724
inv = self._inventory
704
def descend(from_dir_relpath, from_dir_id, dp):
725
# Convert these into local objects to save lookup times
726
pathjoin = bzrlib.osutils.pathjoin
727
file_kind = bzrlib.osutils.file_kind
729
# transport.base ends in a slash, we want the piece
730
# between the last two slashes
731
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
733
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
735
# directory file_id, relative path, absolute path, reverse sorted children
736
children = os.listdir(self.basedir)
738
# jam 20060527 The kernel sized tree seems equivalent whether we
739
# use a deque and popleft to keep them sorted, or if we use a plain
740
# list and just reverse() them.
741
children = collections.deque(children)
742
stack = [(inv.root.file_id, u'', self.basedir, children)]
744
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
747
f = children.popleft()
708
748
## TODO: If we find a subdirectory with its own .bzr
709
749
## directory, then that is a separate tree and we
710
750
## should exclude it.
712
752
# the bzrdir for this tree
713
if self.bzrdir.transport.base.endswith(f + '/'):
753
if transport_base_dir == f:
717
fp = appendpath(from_dir_relpath, f)
756
# we know that from_dir_relpath and from_dir_abspath never end in a slash
757
# and 'f' doesn't begin with one, we can do a string op, rather
758
# than the checks of pathjoin(), all relative paths will have an extra slash
760
fp = from_dir_relpath + '/' + f
720
fap = appendpath(dp, f)
763
fap = from_dir_abspath + '/' + f
722
765
f_ie = inv.get_child(from_dir_id, f)
725
elif self.is_ignored(fp):
768
elif self.is_ignored(fp[1:]):
738
781
# make a last minute entry
783
yield fp[1:], c, fk, f_ie.file_id, f_ie
742
if fk == 'directory':
743
entry = TreeDirectory()
746
elif fk == 'symlink':
786
yield fp[1:], c, fk, None, fk_entries[fk]()
788
yield fp[1:], c, fk, None, TreeEntry()
751
yield fp, c, fk, (f_ie and f_ie.file_id), entry
753
791
if fk != 'directory':
757
# don't descend unversioned directories
760
for ff in descend(fp, f_ie.file_id, fap):
794
# But do this child first
795
new_children = os.listdir(fap)
797
new_children = collections.deque(new_children)
798
stack.append((f_ie.file_id, fp, fap, new_children))
799
# Break out of inner loop, so that we start outer loop with child
802
# if we finished all children, pop it off the stack
763
for f in descend(u'', inv.root.file_id, self.basedir):
766
806
@needs_write_lock
767
807
def move(self, from_paths, to_name):
804
844
raise BzrError("%r is not versioned" % f)
805
845
name_tail = splitpath(f)[-1]
806
dest_path = appendpath(to_name, name_tail)
846
dest_path = pathjoin(to_name, name_tail)
807
847
if self.has_filename(dest_path):
808
848
raise BzrError("destination %r already exists" % dest_path)
809
849
if f_id in to_idpath:
817
857
for f in from_paths:
818
858
name_tail = splitpath(f)[-1]
819
dest_path = appendpath(to_name, name_tail)
859
dest_path = pathjoin(to_name, name_tail)
820
860
result.append((f, dest_path))
821
861
inv.rename(inv.path2id(f), to_dir_id, name_tail)
887
927
These are files in the working directory that are not versioned or
888
928
control files or ignored.
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())
897
>>> list(b.unknowns())
899
>>> tree.remove('foo')
900
>>> list(b.unknowns())
903
930
for subp in self.extras():
904
931
if not self.is_ignored(subp):
1051
1079
if hasattr(self, '_ignorelist'):
1052
1080
return self._ignorelist
1054
l = bzrlib.DEFAULT_IGNORE[:]
1055
1083
if self.has_filename(bzrlib.IGNORE_FILENAME):
1056
1084
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1057
l.extend([line.rstrip("\n\r") for line in f.readlines()])
1085
l.extend([line.rstrip("\n\r").decode('utf-8')
1086
for line in f.readlines()])
1058
1087
self._ignorelist = l
1059
1088
self._ignore_regex = self._combine_ignore_rules(l)
1167
1196
def _cache_basis_inventory(self, new_revision):
1168
1197
"""Cache new_revision as the basis inventory."""
1198
# TODO: this should allow the ready-to-use inventory to be passed in,
1199
# as commit already has that ready-to-use [while the format is the
1170
1202
# this double handles the inventory - unpack and repack -
1171
1203
# but is easier to understand. We can/should put a conditional
1172
1204
# in here based on whether the inventory is in the latest format
1173
1205
# - perhaps we should repack all inventories on a repository
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)
1207
# the fast path is to copy the raw xml from the repository. If the
1208
# xml contains 'revision_id="', then we assume the right
1209
# revision_id is set. We must check for this full string, because a
1210
# root node id can legitimately look like 'revision_id' but cannot
1212
xml = self.branch.repository.get_inventory_xml(new_revision)
1213
if not 'revision_id="' in xml.split('\n', 1)[0]:
1214
inv = self.branch.repository.deserialise_inventory(
1216
inv.revision_id = new_revision
1217
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1218
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1179
1219
path = self._basis_inventory_name()
1180
self._control_files.put_utf8(path, xml)
1221
self._control_files.put(path, sio)
1181
1222
except WeaveRevisionNotPresent:
1184
1225
def read_basis_inventory(self):
1185
1226
"""Read the cached basis inventory."""
1186
1227
path = self._basis_inventory_name()
1187
return self._control_files.get_utf8(path).read()
1228
return self._control_files.get(path).read()
1189
1230
@needs_read_lock
1190
1231
def read_working_inventory(self):
1199
1240
@needs_write_lock
1200
def remove(self, files, verbose=False):
1241
def remove(self, files, verbose=False, to_file=None):
1201
1242
"""Remove nominated files from the working inventory..
1203
1244
This does not remove their text. This does not run on XXX on what? RBC
1225
1266
# TODO: Perhaps make this just a warning, and continue?
1226
1267
# This tends to happen when
1227
1268
raise NotVersionedError(path=f)
1228
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1230
1270
# having remove it, it must be either ignored or unknown
1231
1271
if self.is_ignored(f):
1232
1272
new_status = 'I'
1234
1274
new_status = '?'
1235
show_status(new_status, inv[fid].kind, quotefn(f))
1275
show_status(new_status, inv[fid].kind, f, to_file=to_file)
1238
1278
self._write_inventory(inv)
1305
1345
# of a nasty hack; probably it's better to have a transaction object,
1306
1346
# which can do some finalization when it's either successfully or
1307
1347
# 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.
1348
# RBC 20060206 hooking into transaction will couple lock and transaction
1349
# wrongly. Hooking into unlock on the control files object is fine though.
1311
1351
# TODO: split this per format so there is no ugly if block
1312
1352
if self._hashcache.needs_write and (
1358
1398
this_tree=self)
1359
1399
self.set_last_revision(self.branch.last_revision())
1360
1400
if old_tip and old_tip != self.last_revision():
1361
# our last revision was not the prior branch last reivison
1401
# our last revision was not the prior branch last revision
1362
1402
# and we have converted that last revision to a pending merge.
1363
1403
# base is somewhere between the branch tip now
1364
1404
# and the now pending merge
1392
1432
def set_conflicts(self, arg):
1393
1433
raise UnsupportedOperation(self.set_conflicts, self)
1435
def add_conflicts(self, arg):
1436
raise UnsupportedOperation(self.add_conflicts, self)
1395
1438
@needs_read_lock
1396
1439
def conflicts(self):
1397
1440
conflicts = ConflictList()
1401
1444
if file_kind(self.abspath(conflicted)) != "file":
1404
if e.errno == errno.ENOENT:
1446
except errors.NoSuchFile:
1408
1448
if text is True:
1409
1449
for suffix in ('.THIS', '.OTHER'):
1411
1451
kind = file_kind(self.abspath(conflicted+suffix))
1413
if e.errno == errno.ENOENT:
1454
except errors.NoSuchFile:
1421
1458
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1422
1459
conflicts.append(Conflict.factory(ctype, path=conflicted,
1463
1500
self._put_rio('conflicts', conflicts.to_stanzas(),
1464
1501
CONFLICT_HEADER_1)
1504
def add_conflicts(self, new_conflicts):
1505
conflict_set = set(self.conflicts())
1506
conflict_set.update(set(list(new_conflicts)))
1507
self.set_conflicts(ConflictList(sorted(conflict_set,
1508
key=Conflict.sort_key)))
1466
1510
@needs_read_lock
1467
1511
def conflicts(self):
1643
1687
raise NotImplementedError
1644
1688
if not isinstance(a_bzrdir.transport, LocalTransport):
1645
1689
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1646
return WorkingTree(a_bzrdir.root_transport.base,
1690
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1647
1691
_internal=True,
1649
1693
_bzrdir=a_bzrdir)
1680
1724
def initialize(self, a_bzrdir, revision_id=None):
1681
1725
"""See WorkingTreeFormat.initialize().
1683
revision_id allows creating a working tree at a differnet
1727
revision_id allows creating a working tree at a different
1684
1728
revision than the branch is at.
1686
1730
if not isinstance(a_bzrdir.transport, LocalTransport):
1729
1773
if not isinstance(a_bzrdir.transport, LocalTransport):
1730
1774
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1731
1775
control_files = self._open_control_files(a_bzrdir)
1732
return WorkingTree3(a_bzrdir.root_transport.base,
1776
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1733
1777
_internal=True,
1735
1779
_bzrdir=a_bzrdir,