~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Aaron Bentley
  • Date: 2005-10-04 04:32:32 UTC
  • mfrom: (1185.12.6)
  • mto: (1185.12.13)
  • mto: This revision was merged to the branch mainline in revision 1419.
  • Revision ID: aaron.bentley@utoronto.ca-20051004043231-40302a149769263b
merged my own changes

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