1
1
# (C) 2005 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# FIXME: This refactoring of the workingtree code doesn't seem to keep
18
# the WorkingTree's copy of the inventory in sync with the branch. The
19
# branch modifies its working inventory when it does a commit to make
20
# missing files permanently removed.
18
22
# TODO: Maybe also keep the full path of the entry, and the children?
19
23
# But those depend on its position within a particular inventory, and
34
from bzrlib.errors import BzrError, BzrCheckError
36
38
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
37
appendpath, sha_strings)
39
pathjoin, sha_strings)
38
40
from bzrlib.trace import mutter
39
from bzrlib.errors import NotVersionedError
41
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
BzrError, BzrCheckError)
42
45
class InventoryEntry(object):
76
79
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
77
80
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
78
81
InventoryFile('2323', 'hello.c', parent_id='123')
79
>>> for j in i.iter_entries():
82
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
83
>>> for ix, j in enumerate(i.iter_entries()):
84
... print (j[0] == shouldbe[ix], j[1])
82
('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
83
('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
86
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
(True, InventoryFile('2323', 'hello.c', parent_id='123'))
84
88
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
85
89
Traceback (most recent call last):
99
103
InventoryFile('2326', 'wibble.c', parent_id='2325')
100
104
>>> for path, entry in i.iter_entries():
101
... print path.replace('\\\\', '/') # for win32 os.sep
102
106
... assert i.path2id(path)
114
118
'text_id', 'parent_id', 'children', 'executable',
117
def _add_text_to_weave(self, new_lines, parents, weave_store):
118
weave_store.add_text(self.file_id, self.revision, new_lines, parents)
121
def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
122
weave_store.add_text(self.file_id, self.revision, new_lines, parents,
120
125
def detect_changes(self, old_entry):
121
126
"""Return a (text_modified, meta_modified) from this to old_entry.
165
170
ie = inv[self.file_id]
166
171
assert ie.file_id == self.file_id
167
172
if ie.revision in heads:
173
# fixup logic, there was a bug in revision updates.
174
# with x bit support.
176
if heads[ie.revision].executable != ie.executable:
177
heads[ie.revision].executable = False
178
ie.executable = False
179
except AttributeError:
168
181
assert heads[ie.revision] == ie
170
183
# may want to add it.
190
203
def get_tar_item(self, root, dp, now, tree):
191
204
"""Get a tarfile item and a file stream for its content."""
192
item = tarfile.TarInfo(os.path.join(root, dp))
205
item = tarfile.TarInfo(pathjoin(root, dp))
193
206
# TODO: would be cool to actually set it to the timestamp of the
194
207
# revision it was last changed
221
234
>>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
222
235
Traceback (most recent call last):
223
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
236
InvalidEntryName: Invalid entry name: src/hello.c
225
238
assert isinstance(name, basestring), name
226
239
if '/' in name or '\\' in name:
227
raise BzrCheckError('InventoryEntry name %r is invalid' % name)
240
raise InvalidEntryName(name=name)
229
241
self.executable = False
230
242
self.revision = None
231
243
self.text_sha1 = None
256
268
This is a template method - implement _put_on_disk in subclasses.
258
fullpath = appendpath(dest, dp)
270
fullpath = pathjoin(dest, dp)
259
271
self._put_on_disk(fullpath, tree)
260
mutter(" export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
272
mutter(" export {%s} kind %s to %s", self.file_id,
262
275
def _put_on_disk(self, fullpath, tree):
263
276
"""Put this entry onto disk at fullpath, from tree tree."""
312
325
def snapshot(self, revision, path, previous_entries,
313
work_tree, weave_store):
326
work_tree, weave_store, transaction):
314
327
"""Make a snapshot of this entry which may or may not have changed.
316
329
This means that all its fields are populated, that it has its
326
339
self.revision = parent_ie.revision
327
340
return "unchanged"
328
341
return self.snapshot_revision(revision, previous_entries,
329
work_tree, weave_store)
342
work_tree, weave_store, transaction)
331
344
def snapshot_revision(self, revision, previous_entries, work_tree,
345
weave_store, transaction):
333
346
"""Record this revision unconditionally."""
334
347
mutter('new revision for {%s}', self.file_id)
335
348
self.revision = revision
336
349
change = self._get_snapshot_change(previous_entries)
337
self._snapshot_text(previous_entries, work_tree, weave_store)
350
self._snapshot_text(previous_entries, work_tree, weave_store,
340
def _snapshot_text(self, file_parents, work_tree, weave_store):
354
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
341
355
"""Record the 'text' of this entry, whatever form that takes.
343
357
This default implementation simply adds an empty text.
345
359
mutter('storing file {%s} in revision {%s}',
346
360
self.file_id, self.revision)
347
self._add_text_to_weave([], file_parents, weave_store)
361
self._add_text_to_weave([], file_parents, weave_store, transaction)
349
363
def __eq__(self, other):
350
364
if not isinstance(other, InventoryEntry):
388
402
Note that this should be modified to be a noop on virtual trees
389
403
as all entries created there are prepopulated.
405
# TODO: Rather than running this manually, we should check the
406
# working sha1 and other expensive properties when they're
407
# first requested, or preload them if they're already known
408
pass # nothing to do by default
393
411
class RootEntry(InventoryEntry):
465
483
checker.repeated_text_cnt += 1
486
if self.file_id not in checker.checked_weaves:
487
mutter('check weave {%s}', self.file_id)
488
w = tree.get_weave(self.file_id)
489
# Not passing a progress bar, because it creates a new
490
# progress, which overwrites the current progress,
491
# and doesn't look nice
493
checker.checked_weaves[self.file_id] = True
495
w = tree.get_weave_prelude(self.file_id)
467
497
mutter('check version {%s} of {%s}', rev_id, self.file_id)
468
file_lines = tree.get_file_lines(self.file_id)
469
498
checker.checked_text_cnt += 1
470
if self.text_size != sum(map(len, file_lines)):
471
raise BzrCheckError('text {%s} wrong size' % self.text_id)
472
if self.text_sha1 != sha_strings(file_lines):
473
raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
499
# We can't check the length, because Weave doesn't store that
500
# information, and the whole point of looking at the weave's
501
# sha1sum is that we don't have to extract the text.
502
if self.text_sha1 != w.get_sha1(self.revision):
503
raise BzrCheckError('text {%s} version {%s} wrong sha1'
504
% (self.file_id, self.revision))
474
505
checker.checked_texts[t] = self.text_sha1
539
570
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
540
571
self.executable = work_tree.is_executable(self.file_id)
542
def _snapshot_text(self, file_parents, work_tree, weave_store):
573
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
543
574
"""See InventoryEntry._snapshot_text."""
544
575
mutter('storing file {%s} in revision {%s}',
545
576
self.file_id, self.revision)
551
582
previous_ie = file_parents.values()[0]
552
583
weave_store.add_identical_text(
553
584
self.file_id, previous_ie.revision,
554
self.revision, file_parents)
585
self.revision, file_parents, transaction)
556
587
new_lines = work_tree.get_file(self.file_id).readlines()
557
self._add_text_to_weave(new_lines, file_parents, weave_store)
588
self._add_text_to_weave(new_lines, file_parents, weave_store,
558
590
self.text_sha1 = sha_strings(new_lines)
559
591
self.text_size = sum(map(len, new_lines))
628
662
def _put_in_tar(self, item, tree):
629
663
"""See InventoryEntry._put_in_tar."""
630
iterm.type = tarfile.SYMTYPE
664
item.type = tarfile.SYMTYPE
744
778
if ie.kind == 'directory':
745
779
for cn, cie in self.iter_entries(from_dir=ie.file_id):
746
yield os.path.join(name, cn), cie
780
yield pathjoin(name, cn), cie
749
783
def entries(self):
756
790
kids = dir_ie.children.items()
758
792
for name, ie in kids:
759
child_path = os.path.join(dir_path, name)
793
child_path = pathjoin(dir_path, name)
760
794
accum.append((child_path, ie))
761
795
if ie.kind == 'directory':
762
796
descend(ie, child_path)
764
descend(self.root, '')
798
descend(self.root, u'')
778
812
for name, child_ie in kids:
779
child_path = os.path.join(parent_path, name)
813
child_path = pathjoin(parent_path, name)
780
814
descend(child_ie, child_path)
781
descend(self.root, '')
815
descend(self.root, u'')
844
878
if parent.children.has_key(entry.name):
845
879
raise BzrError("%s is already versioned" %
846
appendpath(self.id2path(parent.file_id), entry.name))
880
pathjoin(self.id2path(parent.file_id), entry.name))
848
882
self._byid[entry.file_id] = entry
849
883
parent.children[entry.name] = entry
856
890
The immediate parent must already be versioned.
858
892
Returns the new entry object."""
859
from bzrlib.branch import gen_file_id
893
from bzrlib.workingtree import gen_file_id
861
895
parts = bzrlib.osutils.splitpath(relpath)
862
896
if len(parts) == 0:
868
902
parent_path = parts[:-1]
869
903
parent_id = self.path2id(parent_path)
870
904
if parent_id == None:
871
raise NotVersionedError(parent_path)
905
raise NotVersionedError(path=parent_path)
873
906
if kind == 'directory':
874
907
ie = InventoryDirectory(file_id, parts[-1], parent_id)
875
908
elif kind == 'file':
967
1000
>>> i = Inventory()
968
1001
>>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
969
1002
>>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
970
>>> print i.id2path('foo-id').replace(os.sep, '/')
1003
>>> print i.id2path('foo-id')
973
1006
# get all names, skipping root
974
1007
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
975
return os.sep.join(p)