113
113
>>> i.id2path('2326')
114
114
'src/wibble/wibble.c'
117
# Constants returned by describe_change()
119
# TODO: These should probably move to some kind of FileChangeDescription
120
# class; that's like what's inside a TreeDelta but we want to be able to
121
# generate them just for one file at a time.
123
MODIFIED_AND_RENAMED = 'modified and renamed'
125
117
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
126
118
'text_id', 'parent_id', 'children', 'executable',
129
121
def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
130
versionedfile = weave_store.get_weave_or_empty(self.file_id,
132
versionedfile.add_lines(self.revision, parents, new_lines)
133
versionedfile.clear_cache()
122
weave_store.add_text(self.file_id, self.revision, new_lines, parents,
135
125
def detect_changes(self, old_entry):
136
126
"""Return a (text_modified, meta_modified) from this to old_entry.
172
159
This is a map containing the file revisions in all parents
173
160
for which the file exists, and its revision is not a parent of
174
161
any other. If the file is new, the set will be empty.
176
:param versioned_file_store: A store where ancestry data on this
177
file id can be queried.
178
:param transaction: The transaction that queries to the versioned
179
file store should be completed under.
180
:param entry_vf: The entry versioned file, if its already available.
182
163
def get_ancestors(weave, entry):
183
return set(weave.get_ancestry(entry.revision))
184
# revision:ie mapping for each ie found in previous_inventories.
186
# revision:ie mapping with one revision for each head.
164
return set(map(weave.idx_to_name,
165
weave.inclusions([weave.lookup(entry.revision)])))
188
# revision: ancestor list for each head
189
167
head_ancestors = {}
190
# identify candidate head revision ids.
191
168
for inv in previous_inventories:
192
169
if self.file_id in inv:
193
170
ie = inv[self.file_id]
194
171
assert ie.file_id == self.file_id
195
if ie.revision in candidates:
196
# same revision value in two different inventories:
197
# correct possible inconsistencies:
198
# * there was a bug in revision updates with 'x' bit
172
if ie.revision in heads:
173
# fixup logic, there was a bug in revision updates.
174
# with x bit support.
201
if candidates[ie.revision].executable != ie.executable:
202
candidates[ie.revision].executable = False
176
if heads[ie.revision].executable != ie.executable:
177
heads[ie.revision].executable = False
203
178
ie.executable = False
204
179
except AttributeError:
206
# must now be the same.
207
assert candidates[ie.revision] == ie
181
assert heads[ie.revision] == ie
209
# add this revision as a candidate.
210
candidates[ie.revision] = ie
212
# common case optimisation
213
if len(candidates) == 1:
214
# if there is only one candidate revision found
215
# then we can opening the versioned file to access ancestry:
216
# there cannot be any ancestors to eliminate when there is
217
# only one revision available.
218
heads[ie.revision] = ie
221
# eliminate ancestors amongst the available candidates:
222
# heads are those that are not an ancestor of any other candidate
223
# - this provides convergence at a per-file level.
224
for ie in candidates.values():
225
# may be an ancestor of a known head:
226
already_present = 0 != len(
227
[head for head in heads
228
if ie.revision in head_ancestors[head]])
230
# an ancestor of an analyzed candidate.
232
# not an ancestor of a known head:
233
# load the versioned file for this file id if needed
235
entry_vf = versioned_file_store.get_weave_or_empty(
236
self.file_id, transaction)
237
ancestors = get_ancestors(entry_vf, ie)
238
# may knock something else out:
239
check_heads = list(heads.keys())
240
for head in check_heads:
241
if head in ancestors:
242
# this previously discovered 'head' is not
243
# really a head - its an ancestor of the newly
246
head_ancestors[ie.revision] = ancestors
247
heads[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
250
203
def get_tar_item(self, root, dp, now, tree):
357
302
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
358
303
(self.kind, rev_id))
361
307
"""Clone this inventory entry."""
362
308
raise NotImplementedError
365
def describe_change(old_entry, new_entry):
366
"""Describe the change between old_entry and this.
368
This smells of being an InterInventoryEntry situation, but as its
369
the first one, we're making it a static method for now.
371
An entry with a different parent, or different name is considered
372
to be renamed. Reparenting is an internal detail.
373
Note that renaming the parent does not trigger a rename for the
376
# TODO: Perhaps return an object rather than just a string
377
if old_entry is new_entry:
378
# also the case of both being None
380
elif old_entry is None:
310
def _get_snapshot_change(self, previous_entries):
311
if len(previous_entries) > 1:
313
elif len(previous_entries) == 0:
382
elif new_entry is None:
384
text_modified, meta_modified = new_entry.detect_changes(old_entry)
385
if text_modified or meta_modified:
389
# TODO 20060511 (mbp, rbc) factor out 'detect_rename' here.
390
if old_entry.parent_id != new_entry.parent_id:
392
elif old_entry.name != new_entry.name:
396
if renamed and not modified:
397
return InventoryEntry.RENAMED
398
if modified and not renamed:
400
if modified and renamed:
401
return InventoryEntry.MODIFIED_AND_RENAMED
316
return 'modified/renamed/reparented'
404
318
def __repr__(self):
405
319
return ("%s(%r, %r, parent_id=%r)"
424
338
mutter("found unchanged entry")
425
339
self.revision = parent_ie.revision
426
340
return "unchanged"
427
return self._snapshot_into_revision(revision, previous_entries,
428
work_tree, weave_store, transaction)
430
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
431
weave_store, transaction):
432
"""Record this revision unconditionally into a store.
434
The entry's last-changed revision property (`revision`) is updated to
435
that of the new revision.
437
:param revision: id of the new revision that is being recorded.
439
:returns: String description of the commit (e.g. "merged", "modified"), etc.
441
mutter('new revision {%s} for {%s}', revision, self.file_id)
341
return self.snapshot_revision(revision, previous_entries,
342
work_tree, weave_store, transaction)
344
def snapshot_revision(self, revision, previous_entries, work_tree,
345
weave_store, transaction):
346
"""Record this revision unconditionally."""
347
mutter('new revision for {%s}', self.file_id)
442
348
self.revision = revision
349
change = self._get_snapshot_change(previous_entries)
443
350
self._snapshot_text(previous_entries, work_tree, weave_store,
446
354
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
447
355
"""Record the 'text' of this entry, whatever form that takes.
565
470
class InventoryFile(InventoryEntry):
566
471
"""A file in an inventory."""
568
def _check(self, checker, tree_revision_id, tree):
473
def _check(self, checker, rev_id, tree):
569
474
"""See InventoryEntry._check"""
570
t = (self.file_id, self.revision)
475
revision = self.revision
476
t = (self.file_id, revision)
571
477
if t in checker.checked_texts:
572
prev_sha = checker.checked_texts[t]
478
prev_sha = checker.checked_texts[t]
573
479
if prev_sha != self.text_sha1:
574
480
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
575
(self.file_id, tree_revision_id))
481
(self.file_id, rev_id))
577
483
checker.repeated_text_cnt += 1
587
493
checker.checked_weaves[self.file_id] = True
589
w = tree.get_weave(self.file_id)
495
w = tree.get_weave_prelude(self.file_id)
591
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
592
checker.checked_text_cnt += 1
497
mutter('check version {%s} of {%s}', rev_id, self.file_id)
498
checker.checked_text_cnt += 1
593
499
# We can't check the length, because Weave doesn't store that
594
500
# information, and the whole point of looking at the weave's
595
501
# sha1sum is that we don't have to extract the text.
618
524
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
619
525
output_to, reverse=False):
620
526
"""See InventoryEntry._diff."""
622
from_text = tree.get_file(self.file_id).readlines()
624
to_text = to_tree.get_file(to_entry.file_id).readlines()
628
text_diff(from_label, from_text,
629
to_label, to_text, output_to)
631
text_diff(to_label, to_text,
632
from_label, from_text, output_to)
635
label_pair = (to_label, from_label)
637
label_pair = (from_label, to_label)
638
print >> output_to, "Binary files %s and %s differ" % label_pair
527
from_text = tree.get_file(self.file_id).readlines()
529
to_text = to_tree.get_file(to_entry.file_id).readlines()
533
text_diff(from_label, from_text,
534
to_label, to_text, output_to)
536
text_diff(to_label, to_text,
537
from_label, from_text, output_to)
640
539
def has_text(self):
641
540
"""See InventoryEntry.has_text."""
671
570
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
672
571
self.executable = work_tree.is_executable(self.file_id)
674
def _forget_tree_state(self):
675
self.text_sha1 = None
676
self.executable = None
678
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
573
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
679
574
"""See InventoryEntry._snapshot_text."""
680
mutter('storing text of file {%s} in revision {%s} into %r',
681
self.file_id, self.revision, versionedfile_store)
575
mutter('storing file {%s} in revision {%s}',
576
self.file_id, self.revision)
682
577
# special case to avoid diffing on renames or
684
579
if (len(file_parents) == 1
685
580
and self.text_sha1 == file_parents.values()[0].text_sha1
686
581
and self.text_size == file_parents.values()[0].text_size):
687
582
previous_ie = file_parents.values()[0]
688
versionedfile = versionedfile_store.get_weave(self.file_id, transaction)
689
versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
583
weave_store.add_identical_text(
584
self.file_id, previous_ie.revision,
585
self.revision, file_parents, transaction)
691
587
new_lines = work_tree.get_file(self.file_id).readlines()
692
self._add_text_to_weave(new_lines, file_parents.keys(), versionedfile_store,
588
self._add_text_to_weave(new_lines, file_parents, weave_store,
694
590
self.text_sha1 = sha_strings(new_lines)
695
591
self.text_size = sum(map(len, new_lines))
996
def add_path(self, relpath, kind, file_id=None, parent_id=None):
887
def add_path(self, relpath, kind, file_id=None):
997
888
"""Add entry from a path.
999
890
The immediate parent must already be versioned.
1001
892
Returns the new entry object."""
893
from bzrlib.workingtree import gen_file_id
1003
895
parts = bzrlib.osutils.splitpath(relpath)
898
file_id = gen_file_id(relpath)
1005
900
if len(parts) == 0:
1007
file_id = bzrlib.workingtree.gen_root_id()
1008
901
self.root = RootEntry(file_id)
1009
902
self._byid = {self.root.file_id: self.root}
1013
906
parent_id = self.path2id(parent_path)
1014
907
if parent_id == None:
1015
908
raise NotVersionedError(path=parent_path)
1016
ie = make_entry(kind, parts[-1], parent_id, file_id)
909
if kind == 'directory':
910
ie = InventoryDirectory(file_id, parts[-1], parent_id)
912
ie = InventoryFile(file_id, parts[-1], parent_id)
913
elif kind == 'symlink':
914
ie = InventoryLink(file_id, parts[-1], parent_id)
916
raise BzrError("unknown kind %r" % kind)
1017
917
return self.add(ie)
1019
920
def __delitem__(self, file_id):
1020
921
"""Remove entry by id.
1176
1078
file_ie.parent_id = new_parent_id
1179
def make_entry(kind, name, parent_id, file_id=None):
1180
"""Create an inventory entry.
1182
:param kind: the type of inventory entry to create.
1183
:param name: the basename of the entry.
1184
:param parent_id: the parent_id of the entry.
1185
:param file_id: the file_id to use. if None, one will be created.
1188
file_id = bzrlib.workingtree.gen_file_id(name)
1189
if kind == 'directory':
1190
return InventoryDirectory(file_id, name, parent_id)
1191
elif kind == 'file':
1192
return InventoryFile(file_id, name, parent_id)
1193
elif kind == 'symlink':
1194
return InventoryLink(file_id, name, parent_id)
1196
raise BzrError("unknown kind %r" % kind)
1200
1083
_NAME_RE = None