78
81
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
79
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
82
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
80
83
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
81
InventoryFile('2323', 'hello.c', parent_id='123')
82
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
84
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
85
>>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
83
86
>>> for ix, j in enumerate(i.iter_entries()):
84
87
... print (j[0] == shouldbe[ix], j[1])
86
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
(True, InventoryFile('2323', 'hello.c', parent_id='123'))
89
(True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
90
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
91
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
88
92
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
89
93
Traceback (most recent call last):
91
95
BzrError: inventory already contains entry with id {2323}
92
96
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
93
InventoryFile('2324', 'bye.c', parent_id='123')
97
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
94
98
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
95
InventoryDirectory('2325', 'wibble', parent_id='123')
99
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
96
100
>>> i.path2id('src/wibble')
100
104
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
101
InventoryFile('2326', 'wibble.c', parent_id='2325')
105
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
103
InventoryFile('2326', 'wibble.c', parent_id='2325')
107
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
108
>>> for path, entry in i.iter_entries():
106
110
... assert i.path2id(path)
113
118
>>> i.id2path('2326')
114
119
'src/wibble/wibble.c'
122
# Constants returned by describe_change()
124
# TODO: These should probably move to some kind of FileChangeDescription
125
# class; that's like what's inside a TreeDelta but we want to be able to
126
# generate them just for one file at a time.
128
MODIFIED_AND_RENAMED = 'modified and renamed'
117
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
118
'text_id', 'parent_id', 'children', 'executable',
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,
125
132
def detect_changes(self, old_entry):
126
133
"""Return a (text_modified, meta_modified) from this to old_entry.
151
158
output_to, reverse=False):
152
159
"""Perform a diff between two entries of the same kind."""
154
def find_previous_heads(self, previous_inventories, entry_weave):
155
"""Return the revisions and entries that directly preceed this.
161
def find_previous_heads(self, previous_inventories,
162
versioned_file_store,
165
"""Return the revisions and entries that directly precede this.
157
167
Returned as a map from revision to inventory entry.
159
169
This is a map containing the file revisions in all parents
160
170
for which the file exists, and its revision is not a parent of
161
171
any other. If the file is new, the set will be empty.
173
:param versioned_file_store: A store where ancestry data on this
174
file id can be queried.
175
:param transaction: The transaction that queries to the versioned
176
file store should be completed under.
177
:param entry_vf: The entry versioned file, if its already available.
163
179
def get_ancestors(weave, entry):
164
return set(map(weave.idx_to_name,
165
weave.inclusions([weave.lookup(entry.revision)])))
180
return set(weave.get_ancestry(entry.revision))
181
# revision:ie mapping for each ie found in previous_inventories.
183
# revision:ie mapping with one revision for each head.
185
# revision: ancestor list for each head
167
186
head_ancestors = {}
187
# identify candidate head revision ids.
168
188
for inv in previous_inventories:
169
189
if self.file_id in inv:
170
190
ie = inv[self.file_id]
171
191
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.
192
if ie.revision in candidates:
193
# same revision value in two different inventories:
194
# correct possible inconsistencies:
195
# * there was a bug in revision updates with 'x' bit
176
if heads[ie.revision].executable != ie.executable:
177
heads[ie.revision].executable = False
198
if candidates[ie.revision].executable != ie.executable:
199
candidates[ie.revision].executable = False
178
200
ie.executable = False
179
201
except AttributeError:
181
assert heads[ie.revision] == ie
203
# must now be the same.
204
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
206
# add this revision as a candidate.
207
candidates[ie.revision] = ie
209
# common case optimisation
210
if len(candidates) == 1:
211
# if there is only one candidate revision found
212
# then we can opening the versioned file to access ancestry:
213
# there cannot be any ancestors to eliminate when there is
214
# only one revision available.
215
heads[ie.revision] = ie
218
# eliminate ancestors amongst the available candidates:
219
# heads are those that are not an ancestor of any other candidate
220
# - this provides convergence at a per-file level.
221
for ie in candidates.values():
222
# may be an ancestor of a known head:
223
already_present = 0 != len(
224
[head for head in heads
225
if ie.revision in head_ancestors[head]])
227
# an ancestor of an analyzed candidate.
229
# not an ancestor of a known head:
230
# load the versioned file for this file id if needed
232
entry_vf = versioned_file_store.get_weave_or_empty(
233
self.file_id, transaction)
234
ancestors = get_ancestors(entry_vf, ie)
235
# may knock something else out:
236
check_heads = list(heads.keys())
237
for head in check_heads:
238
if head in ancestors:
239
# this previously discovered 'head' is not
240
# really a head - its an ancestor of the newly
243
head_ancestors[ie.revision] = ancestors
244
heads[ie.revision] = ie
203
247
def get_tar_item(self, root, dp, now, tree):
302
352
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
303
353
(self.kind, rev_id))
307
356
"""Clone this inventory entry."""
308
357
raise NotImplementedError
310
def _get_snapshot_change(self, previous_entries):
311
if len(previous_entries) > 1:
313
elif len(previous_entries) == 0:
360
def describe_change(old_entry, new_entry):
361
"""Describe the change between old_entry and this.
363
This smells of being an InterInventoryEntry situation, but as its
364
the first one, we're making it a static method for now.
366
An entry with a different parent, or different name is considered
367
to be renamed. Reparenting is an internal detail.
368
Note that renaming the parent does not trigger a rename for the
371
# TODO: Perhaps return an object rather than just a string
372
if old_entry is new_entry:
373
# also the case of both being None
375
elif old_entry is None:
316
return 'modified/renamed/reparented'
377
elif new_entry is None:
379
text_modified, meta_modified = new_entry.detect_changes(old_entry)
380
if text_modified or meta_modified:
384
# TODO 20060511 (mbp, rbc) factor out 'detect_rename' here.
385
if old_entry.parent_id != new_entry.parent_id:
387
elif old_entry.name != new_entry.name:
391
if renamed and not modified:
392
return InventoryEntry.RENAMED
393
if modified and not renamed:
395
if modified and renamed:
396
return InventoryEntry.MODIFIED_AND_RENAMED
318
399
def __repr__(self):
319
return ("%s(%r, %r, parent_id=%r)"
400
return ("%s(%r, %r, parent_id=%r, revision=%r)"
320
401
% (self.__class__.__name__,
325
407
def snapshot(self, revision, path, previous_entries,
326
work_tree, weave_store, transaction):
408
work_tree, commit_builder):
327
409
"""Make a snapshot of this entry which may or may not have changed.
329
411
This means that all its fields are populated, that it has its
330
412
text stored in the text store or weave.
332
mutter('new parents of %s are %r', path, previous_entries)
414
# mutter('new parents of %s are %r', path, previous_entries)
333
415
self._read_tree_state(path, work_tree)
416
# TODO: Where should we determine whether to reuse a
417
# previous revision id or create a new revision? 20060606
334
418
if len(previous_entries) == 1:
335
419
# cannot be unchanged unless there is only one parent file rev.
336
420
parent_ie = previous_entries.values()[0]
337
421
if self._unchanged(parent_ie):
338
mutter("found unchanged entry")
422
# mutter("found unchanged entry")
339
423
self.revision = parent_ie.revision
340
424
return "unchanged"
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)
425
return self._snapshot_into_revision(revision, previous_entries,
426
work_tree, commit_builder)
428
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
430
"""Record this revision unconditionally into a store.
432
The entry's last-changed revision property (`revision`) is updated to
433
that of the new revision.
435
:param revision: id of the new revision that is being recorded.
437
:returns: String description of the commit (e.g. "merged", "modified"), etc.
439
# mutter('new revision {%s} for {%s}', revision, self.file_id)
348
440
self.revision = revision
349
change = self._get_snapshot_change(previous_entries)
350
self._snapshot_text(previous_entries, work_tree, weave_store,
441
self._snapshot_text(previous_entries, work_tree, commit_builder)
354
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
443
def _snapshot_text(self, file_parents, work_tree, commit_builder):
355
444
"""Record the 'text' of this entry, whatever form that takes.
357
446
This default implementation simply adds an empty text.
359
mutter('storing file {%s} in revision {%s}',
360
self.file_id, self.revision)
361
self._add_text_to_weave([], file_parents, weave_store, transaction)
448
raise NotImplementedError(self._snapshot_text)
363
450
def __eq__(self, other):
364
451
if not isinstance(other, InventoryEntry):
407
494
# first requested, or preload them if they're already known
408
495
pass # nothing to do by default
497
def _forget_tree_state(self):
411
501
class RootEntry(InventoryEntry):
503
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
504
'text_id', 'parent_id', 'children', 'executable',
505
'revision', 'symlink_target']
413
507
def _check(self, checker, rev_id, tree):
414
508
"""See InventoryEntry._check"""
416
510
def __init__(self, file_id):
417
511
self.file_id = file_id
418
512
self.children = {}
419
self.kind = 'root_directory'
513
self.kind = 'directory'
420
514
self.parent_id = None
517
warn('RootEntry is deprecated as of bzr 0.10. Please use '
518
'InventoryDirectory instead.',
519
DeprecationWarning, stacklevel=2)
423
521
def __eq__(self, other):
424
522
if not isinstance(other, RootEntry):
466
568
"""See InventoryEntry._put_on_disk."""
467
569
os.mkdir(fullpath)
571
def _snapshot_text(self, file_parents, work_tree, commit_builder):
572
"""See InventoryEntry._snapshot_text."""
573
commit_builder.modified_directory(self.file_id, file_parents)
470
576
class InventoryFile(InventoryEntry):
471
577
"""A file in an inventory."""
473
def _check(self, checker, rev_id, tree):
579
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
580
'text_id', 'parent_id', 'children', 'executable',
581
'revision', 'symlink_target']
583
def _check(self, checker, tree_revision_id, tree):
474
584
"""See InventoryEntry._check"""
475
revision = self.revision
476
t = (self.file_id, revision)
585
t = (self.file_id, self.revision)
477
586
if t in checker.checked_texts:
478
prev_sha = checker.checked_texts[t]
587
prev_sha = checker.checked_texts[t]
479
588
if prev_sha != self.text_sha1:
480
589
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
481
(self.file_id, rev_id))
590
(self.file_id, tree_revision_id))
483
592
checker.repeated_text_cnt += 1
524
633
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
525
634
output_to, reverse=False):
526
635
"""See InventoryEntry._diff."""
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)
637
from_text = tree.get_file(self.file_id).readlines()
639
to_text = to_tree.get_file(to_entry.file_id).readlines()
643
text_diff(from_label, from_text,
644
to_label, to_text, output_to)
646
text_diff(to_label, to_text,
647
from_label, from_text, output_to)
650
label_pair = (to_label, from_label)
652
label_pair = (from_label, to_label)
653
print >> output_to, "Binary files %s and %s differ" % label_pair
539
655
def has_text(self):
540
656
"""See InventoryEntry.has_text."""
568
684
def _read_tree_state(self, path, work_tree):
569
685
"""See InventoryEntry._read_tree_state."""
570
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
571
self.executable = work_tree.is_executable(self.file_id)
573
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
686
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
687
# FIXME: 20050930 probe for the text size when getting sha1
688
# in _read_tree_state
689
self.executable = work_tree.is_executable(self.file_id, path=path)
692
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
693
% (self.__class__.__name__,
700
def _forget_tree_state(self):
701
self.text_sha1 = None
703
def _snapshot_text(self, file_parents, work_tree, commit_builder):
574
704
"""See InventoryEntry._snapshot_text."""
575
mutter('storing file {%s} in revision {%s}',
576
self.file_id, self.revision)
577
# special case to avoid diffing on renames or
579
if (len(file_parents) == 1
580
and self.text_sha1 == file_parents.values()[0].text_sha1
581
and self.text_size == file_parents.values()[0].text_size):
582
previous_ie = file_parents.values()[0]
583
weave_store.add_identical_text(
584
self.file_id, previous_ie.revision,
585
self.revision, file_parents, transaction)
587
new_lines = work_tree.get_file(self.file_id).readlines()
588
self._add_text_to_weave(new_lines, file_parents, weave_store,
590
self.text_sha1 = sha_strings(new_lines)
591
self.text_size = sum(map(len, new_lines))
705
def get_content_byte_lines():
706
return work_tree.get_file(self.file_id).readlines()
707
self.text_sha1, self.text_size = commit_builder.modified_file_text(
708
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
594
710
def _unchanged(self, previous_ie):
595
711
"""See InventoryEntry._unchanged."""
608
724
class InventoryLink(InventoryEntry):
609
725
"""A file in an inventory."""
611
__slots__ = ['symlink_target']
727
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
728
'text_id', 'parent_id', 'children', 'executable',
729
'revision', 'symlink_target']
613
731
def _check(self, checker, rev_id, tree):
614
732
"""See InventoryEntry._check"""
615
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
733
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
616
734
raise BzrCheckError('symlink {%s} has text in revision {%s}'
617
735
% (self.file_id, rev_id))
618
if self.symlink_target == None:
736
if self.symlink_target is None:
619
737
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
620
738
% (self.file_id, rev_id))
735
861
The inventory is created with a default root directory, with
738
# We are letting Branch.initialize() create a unique inventory
864
# We are letting Branch.create() create a unique inventory
739
865
# root id. Rather than generating a random one here.
740
866
#if root_id is None:
741
867
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
742
self.root = RootEntry(root_id)
868
if root_id is not None:
869
self._set_root(InventoryDirectory(root_id, '', None))
873
# FIXME: this isn't ever used, changing it to self.revision may break
874
# things. TODO make everything use self.revision_id
875
self.revision_id = revision_id
877
def _set_root(self, ie):
743
879
self._byid = {self.root.file_id: self.root}
747
other = Inventory(self.root.file_id)
882
# TODO: jam 20051218 Should copy also copy the revision_id?
883
entries = self.iter_entries()
884
other = Inventory(entries.next()[1].file_id)
748
885
# copy recursively so we know directories will be added before
749
886
# their children. There are more efficient ways than this...
750
for path, entry in self.iter_entries():
751
if entry == self.root:
887
for path, entry in entries():
753
888
other.add(entry.copy())
757
891
def __iter__(self):
758
892
return iter(self._byid)
761
894
def __len__(self):
762
895
"""Returns number of entries."""
763
896
return len(self._byid)
766
898
def iter_entries(self, from_dir=None):
767
899
"""Return (path, entry) pairs, in order by name."""
771
elif isinstance(from_dir, basestring):
772
from_dir = self._byid[from_dir]
774
kids = from_dir.children.items()
776
for name, ie in kids:
778
if ie.kind == 'directory':
779
for cn, cie in self.iter_entries(from_dir=ie.file_id):
780
yield pathjoin(name, cn), cie
904
elif isinstance(from_dir, basestring):
905
from_dir = self._byid[from_dir]
907
# unrolling the recursive called changed the time from
908
# 440ms/663ms (inline/total) to 116ms/116ms
909
children = from_dir.children.items()
911
children = collections.deque(children)
912
stack = [(u'', children)]
914
from_dir_relpath, children = stack[-1]
917
name, ie = children.popleft()
919
# we know that from_dir_relpath never ends in a slash
920
# and 'f' doesn't begin with one, we can do a string op, rather
921
# than the checks of pathjoin(), though this means that all paths
923
path = from_dir_relpath + '/' + name
927
if ie.kind != 'directory':
930
# But do this child first
931
new_children = ie.children.items()
933
new_children = collections.deque(new_children)
934
stack.append((path, new_children))
935
# Break out of inner loop, so that we start outer loop with child
938
# if we finished all children, pop it off the stack
941
def iter_entries_by_dir(self, from_dir=None):
942
"""Iterate over the entries in a directory first order.
944
This returns all entries for a directory before returning
945
the entries for children of a directory. This is not
946
lexicographically sorted order, and is a hybrid between
947
depth-first and breadth-first.
949
:return: This yields (path, entry) pairs
951
# TODO? Perhaps this should return the from_dir so that the root is
952
# yielded? or maybe an option?
957
elif isinstance(from_dir, basestring):
958
from_dir = self._byid[from_dir]
960
stack = [(u'', from_dir)]
962
cur_relpath, cur_dir = stack.pop()
965
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
967
child_relpath = cur_relpath + child_name
969
yield child_relpath, child_ie
971
if child_ie.kind == 'directory':
972
child_dirs.append((child_relpath+'/', child_ie))
973
stack.extend(reversed(child_dirs))
783
975
def entries(self):
784
976
"""Return list of (path, ie) for all entries except the root.
831
1020
return file_id in self._byid
834
1022
def __getitem__(self, file_id):
835
1023
"""Return the entry for given file_id.
837
1025
>>> inv = Inventory()
838
1026
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
839
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1027
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
840
1028
>>> inv['123123'].name
844
1032
return self._byid[file_id]
845
1033
except KeyError:
847
1035
raise BzrError("can't look up file_id None")
849
1037
raise BzrError("file_id {%s} not in inventory" % file_id)
852
1039
def get_file_kind(self, file_id):
853
1040
return self._byid[file_id].kind
855
1042
def get_child(self, parent_id, filename):
856
1043
return self[parent_id].children.get(filename)
859
1045
def add(self, entry):
860
1046
"""Add entry to inventory.
883
1074
parent.children[entry.name] = entry
887
def add_path(self, relpath, kind, file_id=None):
1077
def add_path(self, relpath, kind, file_id=None, parent_id=None):
888
1078
"""Add entry from a path.
890
1080
The immediate parent must already be versioned.
892
1082
Returns the new entry object."""
893
from bzrlib.workingtree import gen_file_id
895
parts = bzrlib.osutils.splitpath(relpath)
1084
parts = osutils.splitpath(relpath)
896
1086
if len(parts) == 0:
897
raise BzrError("cannot re-add root of inventory")
900
file_id = gen_file_id(relpath)
902
parent_path = parts[:-1]
903
parent_id = self.path2id(parent_path)
904
if parent_id == None:
905
raise NotVersionedError(path=parent_path)
906
if kind == 'directory':
907
ie = InventoryDirectory(file_id, parts[-1], parent_id)
909
ie = InventoryFile(file_id, parts[-1], parent_id)
910
elif kind == 'symlink':
911
ie = InventoryLink(file_id, parts[-1], parent_id)
1088
file_id = bzrlib.workingtree.gen_root_id()
1089
self.root = InventoryDirectory(file_id, '', None)
1090
self._byid = {self.root.file_id: self.root}
913
raise BzrError("unknown kind %r" % kind)
1093
parent_path = parts[:-1]
1094
parent_id = self.path2id(parent_path)
1095
if parent_id is None:
1096
raise NotVersionedError(path=parent_path)
1097
ie = make_entry(kind, parts[-1], parent_id, file_id)
914
1098
return self.add(ie)
917
1100
def __delitem__(self, file_id):
918
1101
"""Remove entry by id.
920
1103
>>> inv = Inventory()
921
1104
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
922
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1105
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
923
1106
>>> '123' in inv
925
1108
>>> del inv['123']
951
1128
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
952
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1129
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
955
1132
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
956
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1133
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
960
1137
if not isinstance(other, Inventory):
961
1138
return NotImplemented
963
if len(self._byid) != len(other._byid):
964
# shortcut: obviously not the same
967
1140
return self._byid == other._byid
970
1142
def __ne__(self, other):
971
1143
return not self.__eq__(other)
974
1145
def __hash__(self):
975
1146
raise ValueError('not hashable')
1148
def _iter_file_id_parents(self, file_id):
1149
"""Yield the parents of file_id up to the root."""
1150
while file_id is not None:
1152
ie = self._byid[file_id]
1154
raise BzrError("file_id {%s} not found in inventory" % file_id)
1156
file_id = ie.parent_id
978
1158
def get_idpath(self, file_id):
979
1159
"""Return a list of file_ids for the path to an entry.
1080
1247
file_ie.parent_id = new_parent_id
1250
def make_entry(kind, name, parent_id, file_id=None):
1251
"""Create an inventory entry.
1253
:param kind: the type of inventory entry to create.
1254
:param name: the basename of the entry.
1255
:param parent_id: the parent_id of the entry.
1256
:param file_id: the file_id to use. if None, one will be created.
1259
file_id = bzrlib.workingtree.gen_file_id(name)
1261
norm_name, can_access = osutils.normalized_filename(name)
1262
if norm_name != name:
1266
# TODO: jam 20060701 This would probably be more useful
1267
# if the error was raised with the full path
1268
raise errors.InvalidNormalization(name)
1270
if kind == 'directory':
1271
return InventoryDirectory(file_id, name, parent_id)
1272
elif kind == 'file':
1273
return InventoryFile(file_id, name, parent_id)
1274
elif kind == 'symlink':
1275
return InventoryLink(file_id, name, parent_id)
1277
raise BzrError("unknown kind %r" % kind)
1085
1280
_NAME_RE = None
1087
1282
def is_valid_name(name):
1088
1283
global _NAME_RE
1089
if _NAME_RE == None:
1284
if _NAME_RE is None:
1090
1285
_NAME_RE = re.compile(r'^[^/\\]+$')
1092
1287
return bool(_NAME_RE.match(name))