~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_dirstate_helpers_c.pyx

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-04-07 07:52:50 UTC
  • mfrom: (3340.1.1 208418-1.4)
  • Revision ID: pqm@pqm.ubuntu.com-20080407075250-phs53xnslo8boaeo
Return the correct knit serialisation method in _StreamAccess.
        (Andrew Bennetts, Martin Pool, Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from bzrlib.dirstate import DirState
23
23
 
24
24
 
 
25
# Give Pyrex some function definitions for it to understand.
 
26
# All of these are just hints to Pyrex, so that it can try to convert python
 
27
# objects into similar C objects. (such as PyInt => int).
 
28
# In anything defined 'cdef extern from XXX' the real C header will be
 
29
# imported, and the real definition will be used from there. So these are just
 
30
# hints, and do not need to match exactly to the C definitions.
 
31
 
25
32
cdef extern from *:
26
 
    ctypedef int size_t
27
 
 
 
33
    ctypedef unsigned long size_t
 
34
 
 
35
cdef extern from "_dirstate_helpers_c.h":
 
36
    ctypedef int intptr_t
 
37
 
 
38
 
 
39
cdef extern from "stdlib.h":
 
40
    unsigned long int strtoul(char *nptr, char **endptr, int base)
 
41
 
 
42
 
 
43
# These functions allow us access to a bit of the 'bare metal' of python
 
44
# objects, rather than going through the object abstraction. (For example,
 
45
# PyList_Append, rather than getting the 'append' attribute of the object, and
 
46
# creating a tuple, and then using PyCallObject).
 
47
# Functions that return (or take) a void* are meant to grab a C PyObject*. This
 
48
# differs from the Pyrex 'object'. If you declare a variable as 'object' Pyrex
 
49
# will automatically Py_INCREF and Py_DECREF when appropriate. But for some
 
50
# inner loops, we don't need to do that at all, as the reference only lasts for
 
51
# a very short time.
28
52
cdef extern from "Python.h":
29
 
    # GetItem returns a borrowed reference
30
 
    void *PyDict_GetItem(object p, object key)
31
 
    int PyDict_SetItem(object p, object key, object val) except -1
32
 
 
33
53
    int PyList_Append(object lst, object item) except -1
34
54
    void *PyList_GetItem_object_void "PyList_GET_ITEM" (object lst, int index)
35
 
    object PyList_GET_ITEM (object lst, int index)
36
55
    int PyList_CheckExact(object)
37
56
 
38
 
    int PyTuple_CheckExact(object)
39
57
    void *PyTuple_GetItem_void_void "PyTuple_GET_ITEM" (void* tpl, int index)
40
 
    object PyTuple_New(int)
41
 
    int PyTuple_SetItem(object tpl, int offset, object val)
42
 
    void PyTuple_SET_ITEM(object tpl, int offset, object val)
43
 
    object PyTuple_Pack(int n, ...)
44
58
 
45
59
    char *PyString_AsString(object p)
46
60
    char *PyString_AS_STRING_void "PyString_AS_STRING" (void *p)
 
61
    object PyString_FromString(char *)
 
62
    object PyString_FromStringAndSize(char *, int)
47
63
    int PyString_Size(object p)
48
64
    int PyString_GET_SIZE_void "PyString_GET_SIZE" (void *p)
49
65
    int PyString_CheckExact(object p)
50
66
 
51
 
    void Py_INCREF(object)
52
 
    void Py_DECREF(object)
53
 
 
54
 
 
55
 
cdef extern from "stdlib.h":
56
 
    unsigned long int strtoul(char *nptr, char **endptr, int base)
57
67
 
58
68
cdef extern from "string.h":
59
 
    char *strchr(char *s1, char c)
60
 
 
61
 
 
62
 
cdef object _split_from_path(object cache, object path):
63
 
    """get the dirblock tuple for a given path.
64
 
 
65
 
    :param cache: A Dictionary mapping string paths to tuples
66
 
    :param path: The path we care about.
67
 
    :return: A borrowed reference to a tuple stored in cache.
68
 
        You do not need to Py_DECREF() when you are done, unless you plan on
69
 
        using it for a while.
70
 
    """
71
 
    cdef void* value_ptr
72
 
    cdef object value
73
 
 
74
 
    value_ptr = PyDict_GetItem(cache, path)
75
 
    if value_ptr == NULL:
76
 
        value = path.split('/')
77
 
        cache[path] = value
78
 
    else:
79
 
        value = <object>value_ptr
80
 
 
81
 
    return value
82
 
 
83
 
 
84
 
cdef int _cmp_dirblock_strings(char *path1, int size1, char *path2, int size2):
85
 
    cdef char *cur1
86
 
    cdef char *cur2
87
 
    cdef char *end1
88
 
    cdef char *end2
 
69
    int strncmp(char *s1, char *s2, int len)
 
70
    void *memchr(void *s, int c, size_t len)
 
71
    int memcmp(void *b1, void *b2, size_t len)
 
72
    # ??? memrchr is a GNU extension :(
 
73
    # void *memrchr(void *s, int c, size_t len)
 
74
 
 
75
 
 
76
cdef void* _my_memrchr(void *s, int c, size_t n):
 
77
    # memrchr seems to be a GNU extension, so we have to implement it ourselves
 
78
    cdef char *pos
 
79
    cdef char *start
 
80
 
 
81
    start = <char*>s
 
82
    pos = start + n - 1
 
83
    while pos >= start:
 
84
        if pos[0] == c:
 
85
            return <void*>pos
 
86
        pos = pos - 1
 
87
    return NULL
 
88
 
 
89
 
 
90
def _py_memrchr(s, c):
 
91
    """Just to expose _my_memrchr for testing.
 
92
 
 
93
    :param s: The Python string to search
 
94
    :param c: The character to search for
 
95
    :return: The offset to the last instance of 'c' in s
 
96
    """
 
97
    cdef void *_s
 
98
    cdef void *found
 
99
    cdef int length
 
100
    cdef char *_c
 
101
 
 
102
    _s = PyString_AsString(s)
 
103
    length = PyString_Size(s)
 
104
 
 
105
    _c = PyString_AsString(c)
 
106
    assert PyString_Size(c) == 1,\
 
107
        'Must be a single character string, not %s' % (c,)
 
108
    found = _my_memrchr(_s, _c[0], length)
 
109
    if found == NULL:
 
110
        return None
 
111
    return <char*>found - <char*>_s
 
112
 
 
113
 
 
114
cdef int _is_aligned(void *ptr):
 
115
    """Is this pointer aligned to an integer size offset?
 
116
 
 
117
    :return: 1 if this pointer is aligned, 0 otherwise.
 
118
    """
 
119
    return ((<intptr_t>ptr) & ((sizeof(int))-1)) == 0
 
120
 
 
121
 
 
122
cdef int _cmp_by_dirs(char *path1, int size1, char *path2, int size2):
 
123
    cdef unsigned char *cur1
 
124
    cdef unsigned char *cur2
 
125
    cdef unsigned char *end1
 
126
    cdef unsigned char *end2
89
127
    cdef int *cur_int1
90
128
    cdef int *cur_int2
91
129
    cdef int *end_int1
92
130
    cdef int *end_int2
93
131
 
94
 
    cur_int1 = <int*>path1
95
 
    cur_int2 = <int*>path2
96
 
    end_int1 = <int*>(path1 + size1 - (size1%4))
97
 
    end_int2 = <int*>(path2 + size2 - (size2%4))
98
 
    end1 = path1+size1
99
 
    end2 = path2+size2
 
132
    if path1 == path2 and size1 == size2:
 
133
        return 0
 
134
 
 
135
    end1 = <unsigned char*>path1+size1
 
136
    end2 = <unsigned char*>path2+size2
100
137
 
101
138
    # Use 32-bit comparisons for the matching portion of the string.
102
139
    # Almost all CPU's are faster at loading and comparing 32-bit integers,
103
140
    # than they are at 8-bit integers.
104
 
    while cur_int1 < end_int1 and cur_int2 < end_int2:
105
 
        if cur_int1[0] != cur_int2[0]:
106
 
            break
107
 
        cur_int1 = cur_int1 + 1
108
 
        cur_int2 = cur_int2 + 1
109
 
 
110
 
    cur1 = <char*>cur_int1
111
 
    cur2 = <char*>cur_int2
 
141
    # 99% of the time, these will be aligned, but in case they aren't just skip
 
142
    # this loop
 
143
    if _is_aligned(path1) and _is_aligned(path2):
 
144
        cur_int1 = <int*>path1
 
145
        cur_int2 = <int*>path2
 
146
        end_int1 = <int*>(path1 + size1 - (size1 % sizeof(int)))
 
147
        end_int2 = <int*>(path2 + size2 - (size2 % sizeof(int)))
 
148
 
 
149
        while cur_int1 < end_int1 and cur_int2 < end_int2:
 
150
            if cur_int1[0] != cur_int2[0]:
 
151
                break
 
152
            cur_int1 = cur_int1 + 1
 
153
            cur_int2 = cur_int2 + 1
 
154
 
 
155
        cur1 = <unsigned char*>cur_int1
 
156
        cur2 = <unsigned char*>cur_int2
 
157
    else:
 
158
        cur1 = <unsigned char*>path1
 
159
        cur2 = <unsigned char*>path2
112
160
 
113
161
    while cur1 < end1 and cur2 < end2:
114
162
        if cur1[0] == cur2[0]:
135
183
    return 0
136
184
 
137
185
 
138
 
def cmp_dirblock_strings(path1, path2):
139
 
    """Compare to python strings in dirblock fashion."""
140
 
    return _cmp_dirblock_strings(PyString_AsString(path1),
 
186
def cmp_by_dirs_c(path1, path2):
 
187
    """Compare two paths directory by directory.
 
188
 
 
189
    This is equivalent to doing::
 
190
 
 
191
       cmp(path1.split('/'), path2.split('/'))
 
192
 
 
193
    The idea is that you should compare path components separately. This
 
194
    differs from plain ``cmp(path1, path2)`` for paths like ``'a-b'`` and
 
195
    ``a/b``. "a-b" comes after "a" but would come before "a/b" lexically.
 
196
 
 
197
    :param path1: first path
 
198
    :param path2: second path
 
199
    :return: negative number if ``path1`` comes first,
 
200
        0 if paths are equal,
 
201
        and positive number if ``path2`` sorts first
 
202
    """
 
203
    if not PyString_CheckExact(path1):
 
204
        raise TypeError("'path1' must be a plain string, not %s: %r"
 
205
                        % (type(path1), path1))
 
206
    if not PyString_CheckExact(path2):
 
207
        raise TypeError("'path2' must be a plain string, not %s: %r"
 
208
                        % (type(path2), path2))
 
209
    return _cmp_by_dirs(PyString_AsString(path1),
 
210
                        PyString_Size(path1),
 
211
                        PyString_AsString(path2),
 
212
                        PyString_Size(path2))
 
213
 
 
214
 
 
215
def _cmp_path_by_dirblock_c(path1, path2):
 
216
    """Compare two paths based on what directory they are in.
 
217
 
 
218
    This generates a sort order, such that all children of a directory are
 
219
    sorted together, and grandchildren are in the same order as the
 
220
    children appear. But all grandchildren come after all children.
 
221
 
 
222
    In other words, all entries in a directory are sorted together, and
 
223
    directorys are sorted in cmp_by_dirs order.
 
224
 
 
225
    :param path1: first path
 
226
    :param path2: the second path
 
227
    :return: negative number if ``path1`` comes first,
 
228
        0 if paths are equal
 
229
        and a positive number if ``path2`` sorts first
 
230
    """
 
231
    if not PyString_CheckExact(path1):
 
232
        raise TypeError("'path1' must be a plain string, not %s: %r"
 
233
                        % (type(path1), path1))
 
234
    if not PyString_CheckExact(path2):
 
235
        raise TypeError("'path2' must be a plain string, not %s: %r"
 
236
                        % (type(path2), path2))
 
237
    return _cmp_path_by_dirblock(PyString_AsString(path1),
141
238
                                 PyString_Size(path1),
142
239
                                 PyString_AsString(path2),
143
240
                                 PyString_Size(path2))
144
241
 
145
242
 
146
 
def c_bisect_dirblock(dirblocks, dirname, lo=0, hi=None, cache=None):
 
243
cdef int _cmp_path_by_dirblock(char *path1, int path1_len,
 
244
                               char *path2, int path2_len):
 
245
    """Compare two paths by what directory they are in.
 
246
 
 
247
    see ``_cmp_path_by_dirblock_c`` for details.
 
248
    """
 
249
    cdef char *dirname1
 
250
    cdef int dirname1_len
 
251
    cdef char *dirname2
 
252
    cdef int dirname2_len
 
253
    cdef char *basename1
 
254
    cdef int basename1_len
 
255
    cdef char *basename2
 
256
    cdef int basename2_len
 
257
    cdef int cur_len
 
258
    cdef int cmp_val
 
259
 
 
260
    if path1_len == 0 and path2_len == 0:
 
261
        return 0
 
262
 
 
263
    if path1 == path2 and path1_len == path2_len:
 
264
        return 0
 
265
 
 
266
    if path1_len == 0:
 
267
        return -1
 
268
 
 
269
    if path2_len == 0:
 
270
        return 1
 
271
 
 
272
    basename1 = <char*>_my_memrchr(path1, c'/', path1_len)
 
273
 
 
274
    if basename1 == NULL:
 
275
        basename1 = path1
 
276
        basename1_len = path1_len
 
277
        dirname1 = ''
 
278
        dirname1_len = 0
 
279
    else:
 
280
        dirname1 = path1
 
281
        dirname1_len = basename1 - path1
 
282
        basename1 = basename1 + 1
 
283
        basename1_len = path1_len - dirname1_len - 1
 
284
 
 
285
    basename2 = <char*>_my_memrchr(path2, c'/', path2_len)
 
286
 
 
287
    if basename2 == NULL:
 
288
        basename2 = path2
 
289
        basename2_len = path2_len
 
290
        dirname2 = ''
 
291
        dirname2_len = 0
 
292
    else:
 
293
        dirname2 = path2
 
294
        dirname2_len = basename2 - path2
 
295
        basename2 = basename2 + 1
 
296
        basename2_len = path2_len - dirname2_len - 1
 
297
 
 
298
    cmp_val = _cmp_by_dirs(dirname1, dirname1_len,
 
299
                           dirname2, dirname2_len)
 
300
    if cmp_val != 0:
 
301
        return cmp_val
 
302
 
 
303
    cur_len = basename1_len
 
304
    if basename2_len < basename1_len:
 
305
        cur_len = basename2_len
 
306
 
 
307
    cmp_val = memcmp(basename1, basename2, cur_len)
 
308
    if cmp_val != 0:
 
309
        return cmp_val
 
310
    if basename1_len == basename2_len:
 
311
        return 0
 
312
    if basename1_len < basename2_len:
 
313
        return -1
 
314
    return 1
 
315
 
 
316
 
 
317
def _bisect_path_left_c(paths, path):
 
318
    """Return the index where to insert path into paths.
 
319
 
 
320
    This uses a path-wise comparison so we get::
 
321
        a
 
322
        a-b
 
323
        a=b
 
324
        a/b
 
325
    Rather than::
 
326
        a
 
327
        a-b
 
328
        a/b
 
329
        a=b
 
330
    :param paths: A list of paths to search through
 
331
    :param path: A single path to insert
 
332
    :return: An offset where 'path' can be inserted.
 
333
    :seealso: bisect.bisect_left
 
334
    """
 
335
    cdef int _lo
 
336
    cdef int _hi
 
337
    cdef int _mid
 
338
    cdef char *path_cstr
 
339
    cdef int path_size
 
340
    cdef char *cur_cstr
 
341
    cdef int cur_size
 
342
    cdef void *cur
 
343
 
 
344
    if not PyList_CheckExact(paths):
 
345
        raise TypeError("you must pass a python list for 'paths' not: %s %r"
 
346
                        % (type(paths), paths))
 
347
    if not PyString_CheckExact(path):
 
348
        raise TypeError("you must pass a string for 'path' not: %s %r"
 
349
                        % (type(path), path))
 
350
 
 
351
    _hi = len(paths)
 
352
    _lo = 0
 
353
 
 
354
    path_cstr = PyString_AsString(path)
 
355
    path_size = PyString_Size(path)
 
356
 
 
357
    while _lo < _hi:
 
358
        _mid = (_lo + _hi) / 2
 
359
        cur = PyList_GetItem_object_void(paths, _mid)
 
360
        cur_cstr = PyString_AS_STRING_void(cur)
 
361
        cur_size = PyString_GET_SIZE_void(cur)
 
362
        if _cmp_path_by_dirblock(cur_cstr, cur_size, path_cstr, path_size) < 0:
 
363
            _lo = _mid + 1
 
364
        else:
 
365
            _hi = _mid
 
366
    return _lo
 
367
 
 
368
 
 
369
def _bisect_path_right_c(paths, path):
 
370
    """Return the index where to insert path into paths.
 
371
 
 
372
    This uses a path-wise comparison so we get::
 
373
        a
 
374
        a-b
 
375
        a=b
 
376
        a/b
 
377
    Rather than::
 
378
        a
 
379
        a-b
 
380
        a/b
 
381
        a=b
 
382
    :param paths: A list of paths to search through
 
383
    :param path: A single path to insert
 
384
    :return: An offset where 'path' can be inserted.
 
385
    :seealso: bisect.bisect_right
 
386
    """
 
387
    cdef int _lo
 
388
    cdef int _hi
 
389
    cdef int _mid
 
390
    cdef char *path_cstr
 
391
    cdef int path_size
 
392
    cdef char *cur_cstr
 
393
    cdef int cur_size
 
394
    cdef void *cur
 
395
 
 
396
    if not PyList_CheckExact(paths):
 
397
        raise TypeError("you must pass a python list for 'paths' not: %s %r"
 
398
                        % (type(paths), paths))
 
399
    if not PyString_CheckExact(path):
 
400
        raise TypeError("you must pass a string for 'path' not: %s %r"
 
401
                        % (type(path), path))
 
402
 
 
403
    _hi = len(paths)
 
404
    _lo = 0
 
405
 
 
406
    path_cstr = PyString_AsString(path)
 
407
    path_size = PyString_Size(path)
 
408
 
 
409
    while _lo < _hi:
 
410
        _mid = (_lo + _hi) / 2
 
411
        cur = PyList_GetItem_object_void(paths, _mid)
 
412
        cur_cstr = PyString_AS_STRING_void(cur)
 
413
        cur_size = PyString_GET_SIZE_void(cur)
 
414
        if _cmp_path_by_dirblock(path_cstr, path_size, cur_cstr, cur_size) < 0:
 
415
            _hi = _mid
 
416
        else:
 
417
            _lo = _mid + 1
 
418
    return _lo
 
419
 
 
420
 
 
421
def bisect_dirblock_c(dirblocks, dirname, lo=0, hi=None, cache=None):
147
422
    """Return the index where to insert dirname into the dirblocks.
148
423
 
149
424
    The return value idx is such that all directories blocks in dirblock[:idx]
156
431
    cdef int _lo
157
432
    cdef int _hi
158
433
    cdef int _mid
159
 
    cdef char *dirname_str
 
434
    cdef char *dirname_cstr
160
435
    cdef int dirname_size
161
 
    cdef char *cur_str
 
436
    cdef char *cur_cstr
162
437
    cdef int cur_size
163
438
    cdef void *cur
164
439
 
 
440
    if not PyList_CheckExact(dirblocks):
 
441
        raise TypeError("you must pass a python list for 'dirblocks' not: %s %r"
 
442
                        % (type(dirblocks), dirblocks))
 
443
    if not PyString_CheckExact(dirname):
 
444
        raise TypeError("you must pass a string for dirname not: %s %r"
 
445
                        % (type(dirname), dirname))
165
446
    if hi is None:
166
447
        _hi = len(dirblocks)
167
448
    else:
168
449
        _hi = hi
169
450
 
170
 
    if not PyList_CheckExact(dirblocks):
171
 
        raise TypeError('you must pass a python list for dirblocks')
172
451
    _lo = lo
173
 
    if not PyString_CheckExact(dirname):
174
 
        raise TypeError('you must pass a string for dirname')
175
 
    dirname_str = PyString_AsString(dirname)
 
452
    dirname_cstr = PyString_AsString(dirname)
176
453
    dirname_size = PyString_Size(dirname)
177
454
 
178
455
    while _lo < _hi:
179
 
        _mid = (_lo+_hi)/2
 
456
        _mid = (_lo + _hi) / 2
180
457
        # Grab the dirname for the current dirblock
181
458
        # cur = dirblocks[_mid][0]
182
459
        cur = PyTuple_GetItem_void_void(
183
460
                PyList_GetItem_object_void(dirblocks, _mid), 0)
184
 
        cur_str = PyString_AS_STRING_void(cur)
 
461
        cur_cstr = PyString_AS_STRING_void(cur)
185
462
        cur_size = PyString_GET_SIZE_void(cur)
186
 
        if _cmp_dirblock_strings(cur_str, cur_size,
187
 
                                 dirname_str, dirname_size) < 0:
188
 
            _lo = _mid+1
 
463
        if _cmp_by_dirs(cur_cstr, cur_size, dirname_cstr, dirname_size) < 0:
 
464
            _lo = _mid + 1
189
465
        else:
190
466
            _hi = _mid
191
467
    return _lo
192
468
 
193
469
 
194
 
cdef object _List_GetItem_Incref(object lst, int offset):
195
 
    """Get an item, and increment a reference to it.
196
 
 
197
 
    The caller must have checked that the object really is a list.
198
 
    """
199
 
    cdef object cur
200
 
    cur = PyList_GET_ITEM(lst, offset)
201
 
    Py_INCREF(cur)
202
 
    return cur
203
 
 
204
 
 
205
 
cdef object _fields_to_entry_0_parents(object fields):
206
 
    cdef object path_name_file_id_key
207
 
    cdef char *size_str
208
 
    cdef unsigned long int size
209
 
    cdef char* executable_str
210
 
    cdef object is_executable
211
 
    if not PyList_CheckExact(fields):
212
 
        raise TypeError('fields must be a list')
213
 
    path_name_file_id_key = (_List_GetItem_Incref(fields, 0),
214
 
                             _List_GetItem_Incref(fields, 1),
215
 
                             _List_GetItem_Incref(fields, 2),
216
 
                            )
217
 
 
218
 
    size_str = PyString_AS_STRING_void(
219
 
                PyList_GetItem_object_void(fields, 5))
220
 
    size = strtoul(size_str, NULL, 10)
221
 
    executable_str = PyString_AS_STRING_void(
222
 
                        PyList_GetItem_object_void(fields, 6))
223
 
    if executable_str[0] == c'y':
224
 
        is_executable = True
225
 
    else:
226
 
        is_executable = False
227
 
    return (path_name_file_id_key, [
228
 
        ( # Current tree
229
 
            _List_GetItem_Incref(fields, 3),# minikind
230
 
            _List_GetItem_Incref(fields, 4),# fingerprint
231
 
            size,                           # size
232
 
            is_executable,                  # executable
233
 
            _List_GetItem_Incref(fields, 7),# packed_stat or revision_id
234
 
        )])
235
 
 
236
 
 
237
 
def _c_read_dirblocks(state):
 
470
cdef class Reader:
 
471
    """Maintain the current location, and return fields as you parse them."""
 
472
 
 
473
    cdef object text # The overall string object
 
474
    cdef char *text_cstr # Pointer to the beginning of text
 
475
    cdef int text_size # Length of text
 
476
 
 
477
    cdef char *end_cstr # End of text
 
478
    cdef char *cur_cstr # Pointer to the current record
 
479
    cdef char *next # Pointer to the end of this record
 
480
 
 
481
    def __init__(self, text):
 
482
        self.text = text
 
483
        self.text_cstr = PyString_AsString(text)
 
484
        self.text_size = PyString_Size(text)
 
485
        self.end_cstr = self.text_cstr + self.text_size
 
486
        self.cur_cstr = self.text_cstr
 
487
 
 
488
    cdef char *get_next(self, int *size):
 
489
        """Return a pointer to the start of the next field."""
 
490
        cdef char *next
 
491
        next = self.cur_cstr
 
492
        self.cur_cstr = <char*>memchr(next, c'\0', self.end_cstr-next)
 
493
        size[0] = self.cur_cstr - next
 
494
        self.cur_cstr = self.cur_cstr + 1
 
495
        return next
 
496
 
 
497
    cdef object get_next_str(self):
 
498
        """Get the next field as a Python string."""
 
499
        cdef int size
 
500
        cdef char *next
 
501
        next = self.get_next(&size)
 
502
        return PyString_FromStringAndSize(next, size)
 
503
 
 
504
    cdef int _init(self) except -1:
 
505
        """Get the pointer ready.
 
506
 
 
507
        This assumes that the dirstate header has already been read, and we
 
508
        already have the dirblock string loaded into memory.
 
509
        This just initializes our memory pointers, etc for parsing of the
 
510
        dirblock string.
 
511
        """
 
512
        cdef char *first
 
513
        cdef int size
 
514
        # The first field should be an empty string left over from the Header
 
515
        first = self.get_next(&size)
 
516
        if first[0] != c'\0' and size == 0:
 
517
            raise AssertionError('First character should be null not: %s'
 
518
                                 % (first,))
 
519
        return 0
 
520
 
 
521
    cdef object _get_entry(self, int num_trees, void **p_current_dirname,
 
522
                           int *new_block):
 
523
        """Extract the next entry.
 
524
 
 
525
        This parses the next entry based on the current location in
 
526
        ``self.cur_cstr``.
 
527
        Each entry can be considered a "row" in the total table. And each row
 
528
        has a fixed number of columns. It is generally broken up into "key"
 
529
        columns, then "current" columns, and then "parent" columns.
 
530
 
 
531
        :param num_trees: How many parent trees need to be parsed
 
532
        :param p_current_dirname: A pointer to the current PyString
 
533
            representing the directory name.
 
534
            We pass this in as a void * so that pyrex doesn't have to
 
535
            increment/decrement the PyObject reference counter for each
 
536
            _get_entry call.
 
537
            We use a pointer so that _get_entry can update it with the new
 
538
            value.
 
539
        :param new_block: This is to let the caller know that it needs to
 
540
            create a new directory block to store the next entry.
 
541
        """
 
542
        cdef object path_name_file_id_key
 
543
        cdef char *entry_size_cstr
 
544
        cdef unsigned long int entry_size
 
545
        cdef char* executable_cstr
 
546
        cdef int is_executable
 
547
        cdef char* dirname_cstr
 
548
        cdef char* trailing
 
549
        cdef int cur_size
 
550
        cdef int i
 
551
        cdef object minikind
 
552
        cdef object fingerprint
 
553
        cdef object info
 
554
 
 
555
        # Read the 'key' information (dirname, name, file_id)
 
556
        dirname_cstr = self.get_next(&cur_size)
 
557
        # Check to see if we have started a new directory block.
 
558
        # If so, then we need to create a new dirname PyString, so that it can
 
559
        # be used in all of the tuples. This saves time and memory, by re-using
 
560
        # the same object repeatedly.
 
561
 
 
562
        # Do the cheap 'length of string' check first. If the string is a
 
563
        # different length, then we *have* to be a different directory.
 
564
        if (cur_size != PyString_GET_SIZE_void(p_current_dirname[0])
 
565
            or strncmp(dirname_cstr,
 
566
                       # Extract the char* from our current dirname string.  We
 
567
                       # know it is a PyString, so we can use
 
568
                       # PyString_AS_STRING, we use the _void version because
 
569
                       # we are tricking Pyrex by using a void* rather than an
 
570
                       # <object>
 
571
                       PyString_AS_STRING_void(p_current_dirname[0]),
 
572
                       cur_size+1) != 0):
 
573
            dirname = PyString_FromStringAndSize(dirname_cstr, cur_size)
 
574
            p_current_dirname[0] = <void*>dirname
 
575
            new_block[0] = 1
 
576
        else:
 
577
            new_block[0] = 0
 
578
 
 
579
        # Build up the key that will be used.
 
580
        # By using <object>(void *) Pyrex will automatically handle the
 
581
        # Py_INCREF that we need.
 
582
        path_name_file_id_key = (<object>p_current_dirname[0],
 
583
                                 self.get_next_str(),
 
584
                                 self.get_next_str(),
 
585
                                )
 
586
 
 
587
        # Parse all of the per-tree information. current has the information in
 
588
        # the same location as parent trees. The only difference is that 'info'
 
589
        # is a 'packed_stat' for current, while it is a 'revision_id' for
 
590
        # parent trees.
 
591
        # minikind, fingerprint, and info will be returned as regular python
 
592
        # strings
 
593
        # entry_size and is_executable will be parsed into a python Long and
 
594
        # python Boolean, respectively.
 
595
        # TODO: jam 20070718 Consider changin the entry_size conversion to
 
596
        #       prefer python Int when possible. They are generally faster to
 
597
        #       work with, and it will be rare that we have a file >2GB.
 
598
        #       Especially since this code is pretty much fixed at a max of
 
599
        #       4GB.
 
600
        trees = []
 
601
        for i from 0 <= i < num_trees:
 
602
            minikind = self.get_next_str()
 
603
            fingerprint = self.get_next_str()
 
604
            entry_size_cstr = self.get_next(&cur_size)
 
605
            entry_size = strtoul(entry_size_cstr, NULL, 10)
 
606
            executable_cstr = self.get_next(&cur_size)
 
607
            is_executable = (executable_cstr[0] == c'y')
 
608
            info = self.get_next_str()
 
609
            PyList_Append(trees, (
 
610
                minikind,     # minikind
 
611
                fingerprint,  # fingerprint
 
612
                entry_size,   # size
 
613
                is_executable,# executable
 
614
                info,         # packed_stat or revision_id
 
615
            ))
 
616
 
 
617
        # The returned tuple is (key, [trees])
 
618
        ret = (path_name_file_id_key, trees)
 
619
        # Ignore the trailing newline, but assert that it does exist, this
 
620
        # ensures that we always finish parsing a line on an end-of-entry
 
621
        # marker.
 
622
        trailing = self.get_next(&cur_size)
 
623
        if cur_size != 1 or trailing[0] != c'\n':
 
624
            raise AssertionError(
 
625
                'Bad parse, we expected to end on \\n, not: %d %s: %s'
 
626
                % (cur_size, PyString_FromStringAndSize(trailing, cur_size),
 
627
                   ret))
 
628
        return ret
 
629
 
 
630
    def _parse_dirblocks(self, state):
 
631
        """Parse all dirblocks in the state file."""
 
632
        cdef int num_trees
 
633
        cdef object current_block
 
634
        cdef object entry
 
635
        cdef void * current_dirname
 
636
        cdef int new_block
 
637
        cdef int expected_entry_count
 
638
        cdef int entry_count
 
639
 
 
640
        num_trees = state._num_present_parents() + 1
 
641
        expected_entry_count = state._num_entries
 
642
 
 
643
        # Ignore the first record
 
644
        self._init()
 
645
 
 
646
        current_block = []
 
647
        state._dirblocks = [('', current_block), ('', [])]
 
648
        obj = ''
 
649
        current_dirname = <void*>obj
 
650
        new_block = 0
 
651
        entry_count = 0
 
652
 
 
653
        # TODO: jam 2007-05-07 Consider pre-allocating some space for the
 
654
        #       members, and then growing and shrinking from there. If most
 
655
        #       directories have close to 10 entries in them, it would save a
 
656
        #       few mallocs if we default our list size to something
 
657
        #       reasonable. Or we could malloc it to something large (100 or
 
658
        #       so), and then truncate. That would give us a malloc + realloc,
 
659
        #       rather than lots of reallocs.
 
660
        while self.cur_cstr < self.end_cstr:
 
661
            entry = self._get_entry(num_trees, &current_dirname, &new_block)
 
662
            if new_block:
 
663
                # new block - different dirname
 
664
                current_block = []
 
665
                PyList_Append(state._dirblocks,
 
666
                              (<object>current_dirname, current_block))
 
667
            PyList_Append(current_block, entry)
 
668
            entry_count = entry_count + 1
 
669
        if entry_count != expected_entry_count:
 
670
            raise AssertionError('We read the wrong number of entries.'
 
671
                    ' We expected to read %s, but read %s'
 
672
                    % (expected_entry_count, entry_count))
 
673
        state._split_root_dirblock_into_contents()
 
674
 
 
675
 
 
676
def _read_dirblocks_c(state):
238
677
    """Read in the dirblocks for the given DirState object.
239
678
 
240
679
    This is tightly bound to the DirState internal representation. It should be
243
682
 
244
683
    :param state: A DirState object.
245
684
    :return: None
 
685
    :postcondition: The dirblocks will be loaded into the appropriate fields in
 
686
        the DirState object.
246
687
    """
247
 
    cdef int cur
248
 
    cdef int pos
249
 
    cdef int entry_size
250
 
    cdef int field_count
251
 
    cdef int num_present_parents
252
 
 
253
688
    state._state_file.seek(state._end_of_header)
254
689
    text = state._state_file.read()
255
690
    # TODO: check the crc checksums. crc_measured = zlib.crc32(text)
256
691
 
257
 
    fields = text.split('\0')
258
 
    # Remove the last blank entry
259
 
    trailing = fields.pop()
260
 
    assert trailing == ''
261
 
    # consider turning fields into a tuple.
262
 
 
263
 
    # skip the first field which is the trailing null from the header.
264
 
    cur = 1
265
 
    # Each line now has an extra '\n' field which is not used
266
 
    # so we just skip over it
267
 
    # entry size:
268
 
    #  3 fields for the key
269
 
    #  + number of fields per tree_data (5) * tree count
270
 
    #  + newline
271
 
    num_present_parents = state._num_present_parents()
272
 
    tree_count = 1 + num_present_parents
273
 
    entry_size = state._fields_per_entry()
274
 
    expected_field_count = entry_size * state._num_entries
275
 
    field_count = len(fields)
276
 
    # this checks our adjustment, and also catches file too short.
277
 
    assert field_count - cur == expected_field_count, \
278
 
        'field count incorrect %s != %s, entry_size=%s, '\
279
 
        'num_entries=%s fields=%r' % (
280
 
            field_count - cur, expected_field_count, entry_size,
281
 
            state._num_entries, fields)
282
 
 
283
 
    if num_present_parents == 1:
284
 
        # Bind external functions to local names
285
 
        _int = int
286
 
        # We access all fields in order, so we can just iterate over
287
 
        # them. Grab an straight iterator over the fields. (We use an
288
 
        # iterator because we don't want to do a lot of additions, nor
289
 
        # do we want to do a lot of slicing)
290
 
        next = iter(fields).next
291
 
        # Move the iterator to the current position
292
 
        for x in xrange(cur):
293
 
            next()
294
 
        # The two blocks here are deliberate: the root block and the
295
 
        # contents-of-root block.
296
 
        state._dirblocks = [('', []), ('', [])]
297
 
        current_block = state._dirblocks[0][1]
298
 
        current_dirname = ''
299
 
        append_entry = current_block.append
300
 
        for count in xrange(state._num_entries):
301
 
            dirname = next()
302
 
            name = next()
303
 
            file_id = next()
304
 
            if dirname != current_dirname:
305
 
                # new block - different dirname
306
 
                current_block = []
307
 
                current_dirname = dirname
308
 
                state._dirblocks.append((current_dirname, current_block))
309
 
                append_entry = current_block.append
310
 
            # we know current_dirname == dirname, so re-use it to avoid
311
 
            # creating new strings
312
 
            entry = ((current_dirname, name, file_id),
313
 
                     [(# Current Tree
314
 
                         next(),                # minikind
315
 
                         next(),                # fingerprint
316
 
                         _int(next()),          # size
317
 
                         next() == 'y',         # executable
318
 
                         next(),                # packed_stat or revision_id
319
 
                     ),
320
 
                     ( # Parent 1
321
 
                         next(),                # minikind
322
 
                         next(),                # fingerprint
323
 
                         _int(next()),          # size
324
 
                         next() == 'y',         # executable
325
 
                         next(),                # packed_stat or revision_id
326
 
                     ),
327
 
                     ])
328
 
            trailing = next()
329
 
            assert trailing == '\n'
330
 
            # append the entry to the current block
331
 
            append_entry(entry)
332
 
        state._split_root_dirblock_into_contents()
333
 
    elif num_present_parents == 0:
334
 
        entries = []
335
 
        pos = cur
336
 
        while pos < field_count:
337
 
            PyList_Append(entries,
338
 
                _fields_to_entry_0_parents(fields[pos:pos+entry_size]))
339
 
            pos = pos + entry_size
340
 
        state._entries_to_current_state(entries)
341
 
    else:
342
 
        fields_to_entry = state._get_fields_to_entry()
343
 
        entries = []
344
 
        entries_append = entries.append
345
 
        pos = cur
346
 
        while pos < field_count:
347
 
            entries_append(fields_to_entry(fields[pos:pos+entry_size]))
348
 
            pos = pos + entry_size
349
 
        state._entries_to_current_state(entries)
350
 
    # To convert from format 2  => format 3
351
 
    # state._dirblocks = sorted(state._dirblocks,
352
 
    #                          key=lambda blk:blk[0].split('/'))
353
 
    # To convert from format 3 => format 2
354
 
    # state._dirblocks = sorted(state._dirblocks)
 
692
    reader = Reader(text)
 
693
 
 
694
    reader._parse_dirblocks(state)
355
695
    state._dirblock_state = DirState.IN_MEMORY_UNMODIFIED