~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Robert Collins
  • Date: 2005-10-03 01:15:02 UTC
  • mfrom: (1092.2.28)
  • Revision ID: robertc@robertcollins.net-20051003011502-f579a509a136b774
mergeĀ fromĀ baz2bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
import bzrlib
37
37
from bzrlib.errors import BzrError, BzrCheckError
38
38
 
39
 
from bzrlib.osutils import quotefn, splitpath, joinpath, appendpath
 
39
from bzrlib.osutils import quotefn, splitpath, joinpath, appendpath, sha_strings
40
40
from bzrlib.trace import mutter
41
41
from bzrlib.errors import NotVersionedError
42
42
 
58
58
    parent_id
59
59
        file_id of the parent directory, or ROOT_ID
60
60
 
61
 
    name_version
62
 
        the revision_id in which the name or parent of this file was
63
 
        last changed
 
61
    revision
 
62
        the revision_id in which this variationo f this file was 
 
63
        introduced.
64
64
 
65
65
    text_sha1
66
66
        sha-1 of the text of the file
68
68
    text_size
69
69
        size in bytes of the text of the file
70
70
        
71
 
    text_version
72
 
        the revision_id in which the text of this file was introduced
73
 
 
74
71
    (reading a version 4 tree created a text_id field.)
75
72
 
76
73
    >>> i = Inventory()
116
113
    
117
114
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
118
115
                 'text_id', 'parent_id', 'children',
119
 
                 'text_version', 'name_version', ]
 
116
                 'revision', 'symlink_target']
120
117
 
 
118
    def _add_text_to_weave(self, new_lines, parents, weave_store):
 
119
        weave_store.add_text(self.file_id, self.revision, new_lines, parents)
121
120
 
122
121
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
123
122
        """Create an InventoryEntry
138
137
        if '/' in name or '\\' in name:
139
138
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
140
139
        
141
 
        self.text_version = None
142
 
        self.name_version = None
 
140
        self.revision = None
143
141
        self.text_sha1 = None
144
142
        self.text_size = None
145
143
        self.file_id = file_id
147
145
        self.kind = kind
148
146
        self.text_id = text_id
149
147
        self.parent_id = parent_id
 
148
        self.symlink_target = None
150
149
        if kind == 'directory':
151
150
            self.children = {}
152
151
        elif kind == 'file':
153
152
            pass
 
153
        elif kind == 'symlink':
 
154
            pass
154
155
        else:
155
156
            raise BzrError("unhandled entry kind %r" % kind)
156
157
 
157
 
 
 
158
    def read_symlink_target(self, path):
 
159
        if self.kind == 'symlink':
 
160
            try:
 
161
                self.symlink_target = os.readlink(path)
 
162
            except OSError,e:
 
163
                raise BzrError("os.readlink error, %s" % e)
158
164
 
159
165
    def sorted_children(self):
160
166
        l = self.children.items()
161
167
        l.sort()
162
168
        return l
163
169
 
 
170
    def check(self, checker, rev_id, inv, tree):
 
171
        if self.parent_id != None:
 
172
            if not inv.has_id(self.parent_id):
 
173
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
174
                        % (self.parent_id, rev_id))
 
175
        if self.kind == 'file':
 
176
            revision = self.revision
 
177
            t = (self.file_id, revision)
 
178
            if t in checker.checked_texts:
 
179
                prev_sha = checker.checked_texts[t] 
 
180
                if prev_sha != self.text_sha1:
 
181
                    raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
 
182
                                        (self.file_id, rev_id))
 
183
                else:
 
184
                    checker.repeated_text_cnt += 1
 
185
                    return
 
186
            mutter('check version {%s} of {%s}', rev_id, self.file_id)
 
187
            file_lines = tree.get_file_lines(self.file_id)
 
188
            checker.checked_text_cnt += 1 
 
189
            if self.text_size != sum(map(len, file_lines)):
 
190
                raise BzrCheckError('text {%s} wrong size' % self.text_id)
 
191
            if self.text_sha1 != sha_strings(file_lines):
 
192
                raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
 
193
            checker.checked_texts[t] = self.text_sha1
 
194
        elif self.kind == 'directory':
 
195
            if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
196
                raise BzrCheckError('directory {%s} has text in revision {%s}'
 
197
                        % (self.file_id, rev_id))
 
198
        elif self.kind == 'root_directory':
 
199
            pass
 
200
        elif self.kind == 'symlink':
 
201
            if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
202
                raise BzrCheckError('symlink {%s} has text in revision {%s}'
 
203
                        % (self.file_id, rev_id))
 
204
            if self.symlink_target == None:
 
205
                raise BzrCheckError('symlink {%s} has no target in revision {%s}'
 
206
                        % (self.file_id, rev_id))
 
207
        else:
 
208
            raise BzrCheckError('unknown entry kind %r in revision {%s}' % 
 
209
                                (self.kind, rev_id))
 
210
 
164
211
 
165
212
    def copy(self):
166
213
        other = InventoryEntry(self.file_id, self.name, self.kind,
168
215
        other.text_id = self.text_id
169
216
        other.text_sha1 = self.text_sha1
170
217
        other.text_size = self.text_size
171
 
        other.text_version = self.text_version
172
 
        other.name_version = self.name_version
 
218
        other.symlink_target = self.symlink_target
 
219
        other.revision = self.revision
173
220
        # note that children are *not* copied; they're pulled across when
174
221
        # others are added
175
222
        return other
176
223
 
 
224
    def _get_snapshot_change(self, previous_entries):
 
225
        if len(previous_entries) > 1:
 
226
            return 'merged'
 
227
        elif len(previous_entries) == 0:
 
228
            return 'added'
 
229
        else:
 
230
            return 'modified/renamed/reparented'
177
231
 
178
232
    def __repr__(self):
179
233
        return ("%s(%r, %r, kind=%r, parent_id=%r)"
183
237
                   self.kind,
184
238
                   self.parent_id))
185
239
 
186
 
    
 
240
    def snapshot(self, revision, path, previous_entries, work_tree, 
 
241
                 weave_store):
 
242
        """Make a snapshot of this entry.
 
243
        
 
244
        This means that all its fields are populated, that it has its
 
245
        text stored in the text store or weave.
 
246
        """
 
247
        mutter('new parents of %s are %r', path, previous_entries)
 
248
        self._read_tree_state(path, work_tree)
 
249
        if len(previous_entries) == 1:
 
250
            # cannot be unchanged unless there is only one parent file rev.
 
251
            parent_ie = previous_entries.values()[0]
 
252
            if self._unchanged(path, parent_ie, work_tree):
 
253
                mutter("found unchanged entry")
 
254
                self.revision = parent_ie.revision
 
255
                return "unchanged"
 
256
        mutter('new revision for {%s}', self.file_id)
 
257
        self.revision = revision
 
258
        change = self._get_snapshot_change(previous_entries)
 
259
        if self.kind != 'file':
 
260
            return change
 
261
        self._snapshot_text(previous_entries, work_tree, weave_store)
 
262
        return change
 
263
 
 
264
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
 
265
        mutter('storing file {%s} in revision {%s}',
 
266
               self.file_id, self.revision)
 
267
        # special case to avoid diffing on renames or 
 
268
        # reparenting
 
269
        if (len(file_parents) == 1
 
270
            and self.text_sha1 == file_parents.values()[0].text_sha1
 
271
            and self.text_size == file_parents.values()[0].text_size):
 
272
            previous_ie = file_parents.values()[0]
 
273
            weave_store.add_identical_text(
 
274
                self.file_id, previous_ie.revision, 
 
275
                self.revision, file_parents)
 
276
        else:
 
277
            new_lines = work_tree.get_file(self.file_id).readlines()
 
278
            self._add_text_to_weave(new_lines, file_parents, weave_store)
 
279
            self.text_sha1 = sha_strings(new_lines)
 
280
            self.text_size = sum(map(len, new_lines))
 
281
 
187
282
    def __eq__(self, other):
188
283
        if not isinstance(other, InventoryEntry):
189
284
            return NotImplemented
190
285
 
191
286
        return (self.file_id == other.file_id) \
192
287
               and (self.name == other.name) \
 
288
               and (other.symlink_target == self.symlink_target) \
193
289
               and (self.text_sha1 == other.text_sha1) \
194
290
               and (self.text_size == other.text_size) \
195
291
               and (self.text_id == other.text_id) \
196
292
               and (self.parent_id == other.parent_id) \
197
293
               and (self.kind == other.kind) \
198
 
               and (self.text_version == other.text_version) \
199
 
               and (self.name_version == other.name_version)
200
 
 
 
294
               and (self.revision == other.revision)
201
295
 
202
296
    def __ne__(self, other):
203
297
        return not (self == other)
205
299
    def __hash__(self):
206
300
        raise ValueError('not hashable')
207
301
 
 
302
    def _unchanged(self, path, previous_ie, work_tree):
 
303
        compatible = True
 
304
        # different inv parent
 
305
        if previous_ie.parent_id != self.parent_id:
 
306
            compatible = False
 
307
        # renamed
 
308
        elif previous_ie.name != self.name:
 
309
            compatible = False
 
310
        if self.kind == 'symlink':
 
311
            if self.symlink_target != previous_ie.symlink_target:
 
312
                compatible = False
 
313
        if self.kind == 'file':
 
314
            if self.text_sha1 != previous_ie.text_sha1:
 
315
                compatible = False
 
316
            else:
 
317
                # FIXME: 20050930 probe for the text size when getting sha1
 
318
                # in _read_tree_state
 
319
                self.text_size = previous_ie.text_size
 
320
        return compatible
 
321
 
 
322
    def _read_tree_state(self, path, work_tree):
 
323
        if self.kind == 'symlink':
 
324
            self.read_symlink_target(work_tree.abspath(path))
 
325
        if self.kind == 'file':
 
326
            self.text_sha1 = work_tree.get_file_sha1(self.file_id)
208
327
 
209
328
 
210
329
class RootEntry(InventoryEntry):