42
42
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
43
# At the momenthey may alias the inventory and have old copies of it in memory.
45
from copy import deepcopy
49
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
50
from bzrlib.branch import (Branch,
55
from bzrlib.errors import (BzrCheckError,
60
from bzrlib.inventory import InventoryEntry
51
61
from bzrlib.osutils import (appendpath,
57
from bzrlib.errors import BzrCheckError, DivergedBranches, NotVersionedError
58
72
from bzrlib.trace import mutter
76
def gen_file_id(name):
77
"""Return new file id.
79
This should probably generate proper UUIDs, but for the moment we
80
cope with just randomness because running uuidgen every time is
83
from binascii import hexlify
90
idx = name.rfind('\\')
94
# make it not a hidden file
95
name = name.lstrip('.')
97
# remove any wierd characters; we don't escape them but rather
99
name = re.sub(r'[^\w.]', '', name)
101
s = hexlify(rand_bytes(8))
102
return '-'.join((name, compact_date(time()), s))
106
"""Return a new tree-root file id."""
107
return gen_file_id('TREE_ROOT')
62
110
class TreeEntry(object):
63
111
"""An entry that implements the minium interface used by commands.
159
207
mutter("write hc")
210
def _set_inventory(self, inv):
211
self._inventory = inv
212
self.path2id = self._inventory.path2id
215
def open_containing(path=None):
216
"""Open an existing working tree which has its root about path.
218
This probes for a working tree at path and searches upwards from there.
220
Basically we keep looking up until we find the control directory or
221
run into /. If there isn't one, raises NotBranchError.
222
TODO: give this a new exception.
223
If there is one, it is returned, along with the unused portion of path.
229
if path.find('://') != -1:
230
raise NotBranchError(path=path)
231
path = os.path.abspath(path)
235
return WorkingTree(path), tail
236
except NotBranchError:
239
tail = os.path.join(os.path.basename(path), tail)
241
tail = os.path.basename(path)
242
path = os.path.dirname(path)
243
# FIXME: top in windows is indicated how ???
244
if path == os.path.sep:
245
# reached the root, whatever that may be
246
raise NotBranchError(path=path)
162
248
def __iter__(self):
163
249
"""Iterate through file_ids for this tree.
199
282
return inv.root.file_id
201
284
def _get_store_filename(self, file_id):
202
## XXX: badly named; this isn't in the store at all
285
## XXX: badly named; this is not in the store at all
203
286
return self.abspath(self.id2path(file_id))
205
288
@needs_write_lock
206
289
def commit(self, *args, **kw):
207
290
from bzrlib.commit import Commit
208
291
Commit().commit(self.branch, *args, **kw)
209
self._inventory = self.read_working_inventory()
292
self._set_inventory(self.read_working_inventory())
211
294
def id2abspath(self, file_id):
212
295
return self.abspath(self.id2path(file_id))
215
297
def has_id(self, file_id):
216
298
# files that have been deleted are excluded
217
299
inv = self._inventory
245
325
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
247
327
@needs_write_lock
328
def add(self, files, ids=None):
329
"""Make files versioned.
331
Note that the command line normally calls smart_add instead,
332
which can automatically recurse.
334
This adds the files to the inventory, so that they will be
335
recorded by the next commit.
338
List of paths to add, relative to the base of the tree.
341
If set, use these instead of automatically generated ids.
342
Must be the same length as the list of files, but may
343
contain None for ids that are to be autogenerated.
345
TODO: Perhaps have an option to add the ids even if the files do
348
TODO: Perhaps callback with the ids and paths as they're added.
350
# TODO: Re-adding a file that is removed in the working copy
351
# should probably put it back with the previous ID.
352
if isinstance(files, basestring):
353
assert(ids is None or isinstance(ids, basestring))
359
ids = [None] * len(files)
361
assert(len(ids) == len(files))
363
inv = self.read_working_inventory()
364
for f,file_id in zip(files, ids):
365
if is_control_file(f):
366
raise BzrError("cannot add control file %s" % quotefn(f))
371
raise BzrError("cannot add top-level %r" % f)
373
fullpath = os.path.normpath(self.abspath(f))
376
kind = file_kind(fullpath)
378
# maybe something better?
379
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
381
if not InventoryEntry.versionable_kind(kind):
382
raise BzrError('cannot add: not a versionable file ('
383
'i.e. regular file, symlink or directory): %s' % quotefn(f))
386
file_id = gen_file_id(f)
387
inv.add_path(f, kind=kind, file_id=file_id)
389
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
390
self._write_inventory(inv)
248
393
def add_pending_merge(self, *revision_ids):
249
394
# TODO: Perhaps should check at this point that the
250
395
# history of the revision is actually present?
360
505
for f in descend('', inv.root.file_id, self.basedir):
509
def move(self, from_paths, to_name):
512
to_name must exist in the inventory.
514
If to_name exists and is a directory, the files are moved into
515
it, keeping their old names.
517
Note that to_name is only the last component of the new name;
518
this doesn't change the directory.
520
This returns a list of (from_path, to_path) pairs for each
524
## TODO: Option to move IDs only
525
assert not isinstance(from_paths, basestring)
527
to_abs = self.abspath(to_name)
528
if not isdir(to_abs):
529
raise BzrError("destination %r is not a directory" % to_abs)
530
if not self.has_filename(to_name):
531
raise BzrError("destination %r not in working directory" % to_abs)
532
to_dir_id = inv.path2id(to_name)
533
if to_dir_id == None and to_name != '':
534
raise BzrError("destination %r is not a versioned directory" % to_name)
535
to_dir_ie = inv[to_dir_id]
536
if to_dir_ie.kind not in ('directory', 'root_directory'):
537
raise BzrError("destination %r is not a directory" % to_abs)
539
to_idpath = inv.get_idpath(to_dir_id)
542
if not self.has_filename(f):
543
raise BzrError("%r does not exist in working tree" % f)
544
f_id = inv.path2id(f)
546
raise BzrError("%r is not versioned" % f)
547
name_tail = splitpath(f)[-1]
548
dest_path = appendpath(to_name, name_tail)
549
if self.has_filename(dest_path):
550
raise BzrError("destination %r already exists" % dest_path)
551
if f_id in to_idpath:
552
raise BzrError("can't move %r to a subdirectory of itself" % f)
554
# OK, so there's a race here, it's possible that someone will
555
# create a file in this interval and then the rename might be
556
# left half-done. But we should have caught most problems.
557
orig_inv = deepcopy(self.inventory)
560
name_tail = splitpath(f)[-1]
561
dest_path = appendpath(to_name, name_tail)
562
result.append((f, dest_path))
563
inv.rename(inv.path2id(f), to_dir_id, name_tail)
565
rename(self.abspath(f), self.abspath(dest_path))
567
raise BzrError("failed to rename %r to %r: %s" %
568
(f, dest_path, e[1]),
569
["rename rolled back"])
571
# restore the inventory on error
572
self._set_inventory(orig_inv)
574
self._write_inventory(inv)
578
def rename_one(self, from_rel, to_rel):
581
This can change the directory or the filename or both.
584
if not self.has_filename(from_rel):
585
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
586
if self.has_filename(to_rel):
587
raise BzrError("can't rename: new working file %r already exists" % to_rel)
589
file_id = inv.path2id(from_rel)
591
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
594
from_parent = entry.parent_id
595
from_name = entry.name
597
if inv.path2id(to_rel):
598
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
600
to_dir, to_tail = os.path.split(to_rel)
601
to_dir_id = inv.path2id(to_dir)
602
if to_dir_id == None and to_dir != '':
603
raise BzrError("can't determine destination directory id for %r" % to_dir)
605
mutter("rename_one:")
606
mutter(" file_id {%s}" % file_id)
607
mutter(" from_rel %r" % from_rel)
608
mutter(" to_rel %r" % to_rel)
609
mutter(" to_dir %r" % to_dir)
610
mutter(" to_dir_id {%s}" % to_dir_id)
612
inv.rename(file_id, to_dir_id, to_tail)
614
from_abs = self.abspath(from_rel)
615
to_abs = self.abspath(to_rel)
617
rename(from_abs, to_abs)
619
inv.rename(file_id, from_parent, from_name)
620
raise BzrError("failed to rename %r to %r: %s"
621
% (from_abs, to_abs, e[1]),
622
["rename rolled back"])
623
self._write_inventory(inv)
365
626
def unknowns(self):
627
"""Return all unknown files.
629
These are files in the working directory that are not versioned or
630
control files or ignored.
632
>>> from bzrlib.branch import ScratchBranch
633
>>> b = ScratchBranch(files=['foo', 'foo~'])
634
>>> tree = WorkingTree(b.base, b)
635
>>> map(str, tree.unknowns())
638
>>> list(b.unknowns())
640
>>> tree.remove('foo')
641
>>> list(b.unknowns())
366
644
for subp in self.extras():
367
645
if not self.is_ignored(subp):