51
51
from time import time
53
54
from bzrlib.atomicfile import AtomicFile
54
from bzrlib.branch import (Branch,
56
55
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
56
import bzrlib.bzrdir as bzrdir
58
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,
230
235
self.bzrdir = _bzrdir
231
236
if not _internal:
232
237
# not created via open etc.
233
warn("WorkingTree() is deprecated as of bzr version 0.8. "
238
warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
234
239
"Please use bzrdir.open_workingtree or WorkingTree.open().",
235
240
DeprecationWarning,
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.
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))
535
557
path = self._inventory.id2path(file_id)
536
558
return self._hashcache.get_sha1(path)
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
538
565
if not supports_executable():
539
566
def is_executable(self, file_id, path=None):
540
567
return self._inventory[file_id].executable
544
571
path = self._inventory.id2path(file_id)
545
572
mode = os.lstat(self.abspath(path)).st_mode
546
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
573
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
548
575
@needs_write_lock
549
576
def add(self, files, ids=None):
584
611
inv = self.read_working_inventory()
585
612
for f,file_id in zip(files, ids):
586
613
if self.is_control_filename(f):
587
raise BzrError("cannot add control file %s" % quotefn(f))
614
raise errors.ForbiddenControlFileError(filename=f)
589
616
fp = splitpath(f)
592
619
raise BzrError("cannot add top-level %r" % f)
594
621
fullpath = normpath(self.abspath(f))
597
623
kind = file_kind(fullpath)
598
624
except OSError, e:
599
625
if e.errno == errno.ENOENT:
600
626
raise NoSuchFile(fullpath)
601
# maybe something better?
602
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
604
627
if not InventoryEntry.versionable_kind(kind):
605
raise BzrError('cannot add: not a versionable file ('
606
'i.e. regular file, symlink or directory): %s' % quotefn(f))
628
raise errors.BadFileKindError(filename=f, kind=kind)
608
629
if file_id is None:
609
630
inv.add_path(f, kind=kind)
1063
1084
l = bzrlib.DEFAULT_IGNORE[:]
1064
1085
if self.has_filename(bzrlib.IGNORE_FILENAME):
1065
1086
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1066
l.extend([line.rstrip("\n\r") for line in f.readlines()])
1087
l.extend([line.rstrip("\n\r").decode('utf-8')
1088
for line in f.readlines()])
1067
1089
self._ignorelist = l
1068
1090
self._ignore_regex = self._combine_ignore_rules(l)
1176
1198
def _cache_basis_inventory(self, new_revision):
1177
1199
"""Cache new_revision as the basis inventory."""
1200
# TODO: this should allow the ready-to-use inventory to be passed in,
1201
# as commit already has that ready-to-use [while the format is the
1179
1204
# this double handles the inventory - unpack and repack -
1180
1205
# but is easier to understand. We can/should put a conditional
1181
1206
# in here based on whether the inventory is in the latest format
1182
1207
# - perhaps we should repack all inventories on a repository
1184
inv = self.branch.repository.get_inventory(new_revision)
1185
inv.revision_id = new_revision
1186
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1209
# the fast path is to copy the raw xml from the repository. If the
1210
# xml contains 'revision_id="', then we assume the right
1211
# revision_id is set. We must check for this full string, because a
1212
# root node id can legitimately look like 'revision_id' but cannot
1214
xml = self.branch.repository.get_inventory_xml(new_revision)
1215
if not 'revision_id="' in xml.split('\n', 1)[0]:
1216
inv = self.branch.repository.deserialise_inventory(
1218
inv.revision_id = new_revision
1219
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1220
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1188
1221
path = self._basis_inventory_name()
1189
self._control_files.put_utf8(path, xml)
1223
self._control_files.put(path, sio)
1190
1224
except WeaveRevisionNotPresent:
1193
1227
def read_basis_inventory(self):
1194
1228
"""Read the cached basis inventory."""
1195
1229
path = self._basis_inventory_name()
1196
return self._control_files.get_utf8(path).read()
1230
return self._control_files.get(path).read()
1198
1232
@needs_read_lock
1199
1233
def read_working_inventory(self):
1208
1242
@needs_write_lock
1209
def remove(self, files, verbose=False):
1243
def remove(self, files, verbose=False, to_file=None):
1210
1244
"""Remove nominated files from the working inventory..
1212
1246
This does not remove their text. This does not run on XXX on what? RBC
1234
1268
# TODO: Perhaps make this just a warning, and continue?
1235
1269
# This tends to happen when
1236
1270
raise NotVersionedError(path=f)
1237
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1239
1272
# having remove it, it must be either ignored or unknown
1240
1273
if self.is_ignored(f):
1241
1274
new_status = 'I'
1243
1276
new_status = '?'
1244
show_status(new_status, inv[fid].kind, quotefn(f))
1277
show_status(new_status, inv[fid].kind, f, to_file=to_file)
1247
1280
self._write_inventory(inv)
1314
1347
# of a nasty hack; probably it's better to have a transaction object,
1315
1348
# which can do some finalization when it's either successfully or
1316
1349
# unsuccessfully completed. (Denys's original patch did that.)
1317
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1318
# wrongly. Hookinh into unllock on the control files object is fine though.
1350
# RBC 20060206 hooking into transaction will couple lock and transaction
1351
# wrongly. Hooking into unlock on the control files object is fine though.
1320
1353
# TODO: split this per format so there is no ugly if block
1321
1354
if self._hashcache.needs_write and (
1367
1400
this_tree=self)
1368
1401
self.set_last_revision(self.branch.last_revision())
1369
1402
if old_tip and old_tip != self.last_revision():
1370
# our last revision was not the prior branch last reivison
1403
# our last revision was not the prior branch last revision
1371
1404
# and we have converted that last revision to a pending merge.
1372
1405
# base is somewhere between the branch tip now
1373
1406
# and the now pending merge
1410
1443
if file_kind(self.abspath(conflicted)) != "file":
1413
if e.errno == errno.ENOENT:
1445
except errors.NoSuchFile:
1417
1447
if text is True:
1418
1448
for suffix in ('.THIS', '.OTHER'):
1420
1450
kind = file_kind(self.abspath(conflicted+suffix))
1422
if e.errno == errno.ENOENT:
1453
except errors.NoSuchFile:
1430
1457
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1431
1458
conflicts.append(Conflict.factory(ctype, path=conflicted,
1652
1679
raise NotImplementedError
1653
1680
if not isinstance(a_bzrdir.transport, LocalTransport):
1654
1681
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1655
return WorkingTree(a_bzrdir.root_transport.base,
1682
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1656
1683
_internal=True,
1658
1685
_bzrdir=a_bzrdir)
1689
1716
def initialize(self, a_bzrdir, revision_id=None):
1690
1717
"""See WorkingTreeFormat.initialize().
1692
revision_id allows creating a working tree at a differnet
1719
revision_id allows creating a working tree at a different
1693
1720
revision than the branch is at.
1695
1722
if not isinstance(a_bzrdir.transport, LocalTransport):
1738
1765
if not isinstance(a_bzrdir.transport, LocalTransport):
1739
1766
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1740
1767
control_files = self._open_control_files(a_bzrdir)
1741
return WorkingTree3(a_bzrdir.root_transport.base,
1768
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1742
1769
_internal=True,
1744
1771
_bzrdir=a_bzrdir,