1
# Copyright (C) 2005 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 Branch.working_tree():
32
# TODO: Don't allow WorkingTrees to be constructed for remote branches if
35
# FIXME: I don't know if writing out the cache from the destructor is really a
36
# good idea, because destructors are considered poor taste in Python, and it's
37
# not predictable when it will be written out.
39
# TODO: Give the workingtree sole responsibility for the working inventory;
40
# remove the variable and references to it from the branch. This may require
41
# updating the commit code so as to update the inventory within the working
42
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
# At the momenthey may alias the inventory and have old copies of it in memory.
45
from copy import deepcopy
50
from bzrlib.branch import (Branch,
55
from bzrlib.errors import (BzrCheckError,
60
from bzrlib.inventory import InventoryEntry
61
from bzrlib.osutils import (appendpath,
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')
110
class TreeEntry(object):
111
"""An entry that implements the minium interface used by commands.
113
This needs further inspection, it may be better to have
114
InventoryEntries without ids - though that seems wrong. For now,
115
this is a parallel hierarchy to InventoryEntry, and needs to become
116
one of several things: decorates to that hierarchy, children of, or
118
Another note is that these objects are currently only used when there is
119
no InventoryEntry available - i.e. for unversioned objects.
120
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
123
def __eq__(self, other):
124
# yes, this us ugly, TODO: best practice __eq__ style.
125
return (isinstance(other, TreeEntry)
126
and other.__class__ == self.__class__)
128
def kind_character(self):
132
class TreeDirectory(TreeEntry):
133
"""See TreeEntry. This is a directory in a working tree."""
135
def __eq__(self, other):
136
return (isinstance(other, TreeDirectory)
137
and other.__class__ == self.__class__)
139
def kind_character(self):
143
class TreeFile(TreeEntry):
144
"""See TreeEntry. This is a regular file in a working tree."""
146
def __eq__(self, other):
147
return (isinstance(other, TreeFile)
148
and other.__class__ == self.__class__)
150
def kind_character(self):
154
class TreeLink(TreeEntry):
155
"""See TreeEntry. This is a symlink in a working tree."""
157
def __eq__(self, other):
158
return (isinstance(other, TreeLink)
159
and other.__class__ == self.__class__)
161
def kind_character(self):
165
class WorkingTree(bzrlib.tree.Tree):
166
"""Working copy tree.
168
The inventory is held in the `Branch` working-inventory, and the
169
files are in a directory on disk.
171
It is possible for a `WorkingTree` to have a filename which is
172
not listed in the Inventory and vice versa.
175
def __init__(self, basedir='.', branch=None):
176
"""Construct a WorkingTree for basedir.
178
If the branch is not supplied, it is opened automatically.
179
If the branch is supplied, it must be the branch for this basedir.
180
(branch.base is not cross checked, because for remote branches that
181
would be meaningless).
183
from bzrlib.hashcache import HashCache
184
from bzrlib.trace import note, mutter
185
assert isinstance(basedir, basestring), \
186
"base directory %r is not a string" % basedir
188
branch = Branch.open(basedir)
189
assert isinstance(branch, Branch), \
190
"branch %r is not a Branch" % branch
192
self.basedir = realpath(basedir)
194
self._set_inventory(self.read_working_inventory())
196
# update the whole cache up front and write to disk if anything changed;
197
# in the future we might want to do this more selectively
198
# two possible ways offer themselves : in self._unlock, write the cache
199
# if needed, or, when the cache sees a change, append it to the hash
200
# cache file, and have the parser take the most recent entry for a
202
hc = self._hashcache = HashCache(basedir)
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)
249
"""Iterate through file_ids for this tree.
251
file_ids are in a WorkingTree if they are in the working inventory
252
and the working file exists.
254
inv = self._inventory
255
for path, ie in inv.iter_entries():
256
if bzrlib.osutils.lexists(self.abspath(path)):
260
return "<%s of %s>" % (self.__class__.__name__,
261
getattr(self, 'basedir', None))
263
def abspath(self, filename):
264
return os.path.join(self.basedir, filename)
266
def relpath(self, abspath):
267
"""Return the local path portion from a given absolute path."""
268
return relpath(self.basedir, abspath)
270
def has_filename(self, filename):
271
return bzrlib.osutils.lexists(self.abspath(filename))
273
def get_file(self, file_id):
274
return self.get_file_byname(self.id2path(file_id))
276
def get_file_byname(self, filename):
277
return file(self.abspath(filename), 'rb')
279
def get_root_id(self):
280
"""Return the id of this trees root"""
281
inv = self.read_working_inventory()
282
return inv.root.file_id
284
def _get_store_filename(self, file_id):
285
## XXX: badly named; this is not in the store at all
286
return self.abspath(self.id2path(file_id))
289
def commit(self, *args, **kw):
290
from bzrlib.commit import Commit
291
Commit().commit(self.branch, *args, **kw)
292
self._set_inventory(self.read_working_inventory())
294
def id2abspath(self, file_id):
295
return self.abspath(self.id2path(file_id))
297
def has_id(self, file_id):
298
# files that have been deleted are excluded
299
inv = self._inventory
300
if not inv.has_id(file_id):
302
path = inv.id2path(file_id)
303
return bzrlib.osutils.lexists(self.abspath(path))
305
def has_or_had_id(self, file_id):
306
if file_id == self.inventory.root.file_id:
308
return self.inventory.has_id(file_id)
310
__contains__ = has_id
312
def get_file_size(self, file_id):
313
return os.path.getsize(self.id2abspath(file_id))
315
def get_file_sha1(self, file_id):
316
path = self._inventory.id2path(file_id)
317
return self._hashcache.get_sha1(path)
319
def is_executable(self, file_id):
321
return self._inventory[file_id].executable
323
path = self._inventory.id2path(file_id)
324
mode = os.lstat(self.abspath(path)).st_mode
325
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
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)
393
def add_pending_merge(self, *revision_ids):
394
# TODO: Perhaps should check at this point that the
395
# history of the revision is actually present?
396
p = self.pending_merges()
398
for rev_id in revision_ids:
404
self.set_pending_merges(p)
406
def pending_merges(self):
407
"""Return a list of pending merges.
409
These are revisions that have been merged into the working
410
directory but not yet committed.
412
cfn = self.branch._rel_controlfilename('pending-merges')
413
if not self.branch._transport.has(cfn):
416
for l in self.branch.controlfile('pending-merges', 'r').readlines():
417
p.append(l.rstrip('\n'))
421
def set_pending_merges(self, rev_list):
422
self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
424
def get_symlink_target(self, file_id):
425
return os.readlink(self.id2abspath(file_id))
427
def file_class(self, filename):
428
if self.path2id(filename):
430
elif self.is_ignored(filename):
436
def list_files(self):
437
"""Recursively list all files as (path, class, kind, id).
439
Lists, but does not descend into unversioned directories.
441
This does not include files that have been deleted in this
444
Skips the control directory.
446
inv = self._inventory
448
def descend(from_dir_relpath, from_dir_id, dp):
452
## TODO: If we find a subdirectory with its own .bzr
453
## directory, then that is a separate tree and we
454
## should exclude it.
455
if bzrlib.BZRDIR == f:
459
fp = appendpath(from_dir_relpath, f)
462
fap = appendpath(dp, f)
464
f_ie = inv.get_child(from_dir_id, f)
467
elif self.is_ignored(fp):
476
raise BzrCheckError("file %r entered as kind %r id %r, "
478
% (fap, f_ie.kind, f_ie.file_id, fk))
480
# make a last minute entry
484
if fk == 'directory':
485
entry = TreeDirectory()
488
elif fk == 'symlink':
493
yield fp, c, fk, (f_ie and f_ie.file_id), entry
495
if fk != 'directory':
499
# don't descend unversioned directories
502
for ff in descend(fp, f_ie.file_id, fap):
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)
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())
644
for subp in self.extras():
645
if not self.is_ignored(subp):
648
def iter_conflicts(self):
650
for path in (s[0] for s in self.list_files()):
651
stem = get_conflicted_stem(path)
654
if stem not in conflicted:
659
def pull(self, source, overwrite=False):
660
from bzrlib.merge import merge_inner
663
old_revision_history = self.branch.revision_history()
664
count = self.branch.pull(source, overwrite)
665
new_revision_history = self.branch.revision_history()
666
if new_revision_history != old_revision_history:
667
if len(old_revision_history):
668
other_revision = old_revision_history[-1]
670
other_revision = None
671
merge_inner(self.branch,
672
self.branch.basis_tree(),
673
self.branch.revision_tree(other_revision))
679
"""Yield all unknown files in this WorkingTree.
681
If there are any unknown directories then only the directory is
682
returned, not all its children. But if there are unknown files
683
under a versioned subdirectory, they are returned.
685
Currently returned depth-first, sorted by name within directories.
687
## TODO: Work from given directory downwards
688
for path, dir_entry in self.inventory.directories():
689
mutter("search for unknowns in %r", path)
690
dirabs = self.abspath(path)
691
if not isdir(dirabs):
692
# e.g. directory deleted
696
for subf in os.listdir(dirabs):
698
and (subf not in dir_entry.children)):
703
subp = appendpath(path, subf)
707
def ignored_files(self):
708
"""Yield list of PATH, IGNORE_PATTERN"""
709
for subp in self.extras():
710
pat = self.is_ignored(subp)
715
def get_ignore_list(self):
716
"""Return list of ignore patterns.
718
Cached in the Tree object after the first call.
720
if hasattr(self, '_ignorelist'):
721
return self._ignorelist
723
l = bzrlib.DEFAULT_IGNORE[:]
724
if self.has_filename(bzrlib.IGNORE_FILENAME):
725
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
726
l.extend([line.rstrip("\n\r") for line in f.readlines()])
731
def is_ignored(self, filename):
732
r"""Check whether the filename matches an ignore pattern.
734
Patterns containing '/' or '\' need to match the whole path;
735
others match against only the last component.
737
If the file is ignored, returns the pattern which caused it to
738
be ignored, otherwise None. So this can simply be used as a
739
boolean if desired."""
741
# TODO: Use '**' to match directories, and other extended
742
# globbing stuff from cvs/rsync.
744
# XXX: fnmatch is actually not quite what we want: it's only
745
# approximately the same as real Unix fnmatch, and doesn't
746
# treat dotfiles correctly and allows * to match /.
747
# Eventually it should be replaced with something more
750
for pat in self.get_ignore_list():
751
if '/' in pat or '\\' in pat:
753
# as a special case, you can put ./ at the start of a
754
# pattern; this is good to match in the top-level
757
if (pat[:2] == './') or (pat[:2] == '.\\'):
761
if fnmatch.fnmatchcase(filename, newpat):
764
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
769
def kind(self, file_id):
770
return file_kind(self.id2abspath(file_id))
773
"""See Branch.lock_read, and WorkingTree.unlock."""
774
return self.branch.lock_read()
776
def lock_write(self):
777
"""See Branch.lock_write, and WorkingTree.unlock."""
778
return self.branch.lock_write()
781
def read_working_inventory(self):
782
"""Read the working inventory."""
783
# ElementTree does its own conversion from UTF-8, so open in
785
f = self.branch.controlfile('inventory', 'rb')
786
return bzrlib.xml5.serializer_v5.read_inventory(f)
789
def remove(self, files, verbose=False):
790
"""Remove nominated files from the working inventory..
792
This does not remove their text. This does not run on XXX on what? RBC
794
TODO: Refuse to remove modified files unless --force is given?
796
TODO: Do something useful with directories.
798
TODO: Should this remove the text or not? Tough call; not
799
removing may be useful and the user can just use use rm, and
800
is the opposite of add. Removing it is consistent with most
801
other tools. Maybe an option.
803
## TODO: Normalize names
804
## TODO: Remove nested loops; better scalability
805
if isinstance(files, basestring):
810
# do this before any modifications
814
# TODO: Perhaps make this just a warning, and continue?
815
# This tends to happen when
816
raise NotVersionedError(path=f)
817
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
819
# having remove it, it must be either ignored or unknown
820
if self.is_ignored(f):
824
show_status(new_status, inv[fid].kind, quotefn(f))
827
self._write_inventory(inv)
830
def revert(self, filenames, old_tree=None, backups=True):
831
from bzrlib.merge import merge_inner
833
old_tree = self.branch.basis_tree()
834
merge_inner(self.branch, old_tree,
835
self, ignore_zero=True,
836
backup_files=backups,
837
interesting_files=filenames)
838
if not len(filenames):
839
self.set_pending_merges([])
842
def set_inventory(self, new_inventory_list):
843
from bzrlib.inventory import (Inventory,
848
inv = Inventory(self.get_root_id())
849
for path, file_id, parent, kind in new_inventory_list:
850
name = os.path.basename(path)
853
# fixme, there should be a factory function inv,add_??
854
if kind == 'directory':
855
inv.add(InventoryDirectory(file_id, name, parent))
857
inv.add(InventoryFile(file_id, name, parent))
858
elif kind == 'symlink':
859
inv.add(InventoryLink(file_id, name, parent))
861
raise BzrError("unknown kind %r" % kind)
862
self._write_inventory(inv)
865
def set_root_id(self, file_id):
866
"""Set the root id for this tree."""
867
inv = self.read_working_inventory()
868
orig_root_id = inv.root.file_id
869
del inv._byid[inv.root.file_id]
870
inv.root.file_id = file_id
871
inv._byid[inv.root.file_id] = inv.root
874
if entry.parent_id in (None, orig_root_id):
875
entry.parent_id = inv.root.file_id
876
self._write_inventory(inv)
879
"""See Branch.unlock.
881
WorkingTree locking just uses the Branch locking facilities.
882
This is current because all working trees have an embedded branch
883
within them. IF in the future, we were to make branch data shareable
884
between multiple working trees, i.e. via shared storage, then we
885
would probably want to lock both the local tree, and the branch.
887
return self.branch.unlock()
890
def _write_inventory(self, inv):
891
"""Write inventory as the current inventory."""
892
from cStringIO import StringIO
893
from bzrlib.atomicfile import AtomicFile
895
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
897
f = AtomicFile(self.branch.controlfilename('inventory'))
903
self._set_inventory(inv)
904
mutter('wrote working inventory')
907
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
908
def get_conflicted_stem(path):
909
for suffix in CONFLICT_SUFFIXES:
910
if path.endswith(suffix):
911
return path[:-len(suffix)]