79
79
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
80
80
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
81
81
InventoryFile('2323', 'hello.c', parent_id='123')
82
>>> shouldbe = {0: 'src', 1: os.path.join('src','hello.c')}
82
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
83
83
>>> for ix, j in enumerate(i.iter_entries()):
84
84
... print (j[0] == shouldbe[ix], j[1])
121
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,
122
versionedfile = weave_store.get_weave_or_empty(self.file_id,
124
versionedfile.add_lines(self.revision, parents, new_lines)
125
126
def detect_changes(self, old_entry):
126
127
"""Return a (text_modified, meta_modified) from this to old_entry.
159
163
This is a map containing the file revisions in all parents
160
164
for which the file exists, and its revision is not a parent of
161
165
any other. If the file is new, the set will be empty.
167
:param versioned_file_store: A store where ancestry data on this
168
file id can be queried.
169
:param transaction: The transaction that queries to the versioned
170
file store should be completed under.
171
:param entry_vf: The entry versioned file, if its already available.
163
173
def get_ancestors(weave, entry):
164
return set(map(weave.idx_to_name,
165
weave.inclusions([weave.lookup(entry.revision)])))
174
return set(weave.get_ancestry(entry.revision))
175
# revision:ie mapping for each ie found in previous_inventories.
177
# revision:ie mapping with one revision for each head.
179
# revision: ancestor list for each head
167
180
head_ancestors = {}
181
# identify candidate head revision ids.
168
182
for inv in previous_inventories:
169
183
if self.file_id in inv:
170
184
ie = inv[self.file_id]
171
185
assert ie.file_id == self.file_id
172
if ie.revision in heads:
173
# fixup logic, there was a bug in revision updates.
174
# with x bit support.
186
if ie.revision in candidates:
187
# same revision value in two different inventories:
188
# correct possible inconsistencies:
189
# * there was a bug in revision updates with 'x' bit
176
if heads[ie.revision].executable != ie.executable:
177
heads[ie.revision].executable = False
192
if candidates[ie.revision].executable != ie.executable:
193
candidates[ie.revision].executable = False
178
194
ie.executable = False
179
195
except AttributeError:
181
assert heads[ie.revision] == ie
197
# must now be the same.
198
assert candidates[ie.revision] == ie
183
# may want to add it.
184
# may already be covered:
185
already_present = 0 != len(
186
[head for head in heads
187
if ie.revision in head_ancestors[head]])
189
# an ancestor of a known head.
192
ancestors = get_ancestors(entry_weave, ie)
193
# may knock something else out:
194
check_heads = list(heads.keys())
195
for head in check_heads:
196
if head in ancestors:
197
# this head is not really a head
199
head_ancestors[ie.revision] = ancestors
200
heads[ie.revision] = ie
200
# add this revision as a candidate.
201
candidates[ie.revision] = ie
203
# common case optimisation
204
if len(candidates) == 1:
205
# if there is only one candidate revision found
206
# then we can opening the versioned file to access ancestry:
207
# there cannot be any ancestors to eliminate when there is
208
# only one revision available.
209
heads[ie.revision] = ie
212
# eliminate ancestors amongst the available candidates:
213
# heads are those that are not an ancestor of any other candidate
214
# - this provides convergence at a per-file level.
215
for ie in candidates.values():
216
# may be an ancestor of a known head:
217
already_present = 0 != len(
218
[head for head in heads
219
if ie.revision in head_ancestors[head]])
221
# an ancestor of an analyzed candidate.
223
# not an ancestor of a known head:
224
# load the versioned file for this file id if needed
226
entry_vf = versioned_file_store.get_weave_or_empty(
227
self.file_id, transaction)
228
ancestors = get_ancestors(entry_vf, ie)
229
# may knock something else out:
230
check_heads = list(heads.keys())
231
for head in check_heads:
232
if head in ancestors:
233
# this previously discovered 'head' is not
234
# really a head - its an ancestor of the newly
237
head_ancestors[ie.revision] = ancestors
238
heads[ie.revision] = ie
203
241
def get_tar_item(self, root, dp, now, tree):
204
242
"""Get a tarfile item and a file stream for its content."""
205
item = tarfile.TarInfo(os.path.join(root, dp))
243
item = tarfile.TarInfo(pathjoin(root, dp))
206
244
# TODO: would be cool to actually set it to the timestamp of the
207
245
# revision it was last changed
470
519
class InventoryFile(InventoryEntry):
471
520
"""A file in an inventory."""
473
def _check(self, checker, rev_id, tree):
522
def _check(self, checker, tree_revision_id, tree):
474
523
"""See InventoryEntry._check"""
475
revision = self.revision
476
t = (self.file_id, revision)
524
t = (self.file_id, self.revision)
477
525
if t in checker.checked_texts:
478
prev_sha = checker.checked_texts[t]
526
prev_sha = checker.checked_texts[t]
479
527
if prev_sha != self.text_sha1:
480
528
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
481
(self.file_id, rev_id))
529
(self.file_id, tree_revision_id))
483
531
checker.repeated_text_cnt += 1
485
mutter('check version {%s} of {%s}', rev_id, self.file_id)
486
file_lines = tree.get_file_lines(self.file_id)
487
checker.checked_text_cnt += 1
488
if self.text_size != sum(map(len, file_lines)):
489
raise BzrCheckError('text {%s} wrong size' % self.text_id)
490
if self.text_sha1 != sha_strings(file_lines):
491
raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
534
if self.file_id not in checker.checked_weaves:
535
mutter('check weave {%s}', self.file_id)
536
w = tree.get_weave(self.file_id)
537
# Not passing a progress bar, because it creates a new
538
# progress, which overwrites the current progress,
539
# and doesn't look nice
541
checker.checked_weaves[self.file_id] = True
543
w = tree.get_weave(self.file_id)
545
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
546
checker.checked_text_cnt += 1
547
# We can't check the length, because Weave doesn't store that
548
# information, and the whole point of looking at the weave's
549
# sha1sum is that we don't have to extract the text.
550
if self.text_sha1 != w.get_sha1(self.revision):
551
raise BzrCheckError('text {%s} version {%s} wrong sha1'
552
% (self.file_id, self.revision))
492
553
checker.checked_texts[t] = self.text_sha1
567
632
and self.text_sha1 == file_parents.values()[0].text_sha1
568
633
and self.text_size == file_parents.values()[0].text_size):
569
634
previous_ie = file_parents.values()[0]
570
weave_store.add_identical_text(
571
self.file_id, previous_ie.revision,
572
self.revision, file_parents, transaction)
635
versionedfile = weave_store.get_weave(self.file_id, transaction)
636
versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
574
638
new_lines = work_tree.get_file(self.file_id).readlines()
575
self._add_text_to_weave(new_lines, file_parents, weave_store,
639
self._add_text_to_weave(new_lines, file_parents.keys(), weave_store,
577
641
self.text_sha1 = sha_strings(new_lines)
578
642
self.text_size = sum(map(len, new_lines))
722
789
The inventory is created with a default root directory, with
725
# We are letting Branch.initialize() create a unique inventory
792
# We are letting Branch.create() create a unique inventory
726
793
# root id. Rather than generating a random one here.
727
794
#if root_id is None:
728
795
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
729
796
self.root = RootEntry(root_id)
797
self.revision_id = revision_id
730
798
self._byid = {self.root.file_id: self.root}
802
# TODO: jam 20051218 Should copy also copy the revision_id?
734
803
other = Inventory(self.root.file_id)
735
804
# copy recursively so we know directories will be added before
736
805
# their children. There are more efficient ways than this...
880
949
from bzrlib.workingtree import gen_file_id
882
951
parts = bzrlib.osutils.splitpath(relpath)
884
raise BzrError("cannot re-add root of inventory")
886
953
if file_id == None:
887
954
file_id = gen_file_id(relpath)
889
parent_path = parts[:-1]
890
parent_id = self.path2id(parent_path)
891
if parent_id == None:
892
raise NotVersionedError(path=parent_path)
957
self.root = RootEntry(file_id)
958
self._byid = {self.root.file_id: self.root}
961
parent_path = parts[:-1]
962
parent_id = self.path2id(parent_path)
963
if parent_id == None:
964
raise NotVersionedError(path=parent_path)
893
965
if kind == 'directory':
894
966
ie = InventoryDirectory(file_id, parts[-1], parent_id)
895
967
elif kind == 'file':
971
1047
root directory as depth 1.
974
while file_id != None:
976
ie = self._byid[file_id]
978
raise BzrError("file_id {%s} not found in inventory" % file_id)
979
p.insert(0, ie.file_id)
980
file_id = ie.parent_id
1050
for parent in self._iter_file_id_parents(file_id):
1051
p.insert(0, parent.file_id)
984
1054
def id2path(self, file_id):
985
"""Return as a list the path to file_id.
1055
"""Return as a string the path to file_id.
987
1057
>>> i = Inventory()
988
1058
>>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
989
1059
>>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
990
>>> print i.id2path('foo-id').replace(os.sep, '/')
1060
>>> print i.id2path('foo-id')
993
1063
# get all names, skipping root
994
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
995
return os.sep.join(p)
1064
return '/'.join(reversed(
1065
[parent.name for parent in
1066
self._iter_file_id_parents(file_id)][:-1]))
999
1068
def path2id(self, name):
1000
1069
"""Walk down through directories to return entry of last component.