/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

[merge] bzr.dev 2298 (broken)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""DirState objects record the state of a directory and its bzr metadata.
 
18
 
 
19
Pseudo EBNF grammar for the state file. Fields are separated by NULLs, and
 
20
lines by NL. The field delimiters are ommitted in the grammar, line delimiters
 
21
are not - this is done for clarity of reading. All string data is in utf8.
 
22
 
 
23
MINIKIND = "f" | "d" | "l" | "a" | "r";
 
24
NL = "\n";
 
25
NULL = "\0";
 
26
WHOLE_NUMBER = {digit}, digit;
 
27
BOOLEAN = "y" | "n";
 
28
REVISION_ID = a non-empty utf8 string;
 
29
 
 
30
dirstate format = header line, full checksum, row count, parent details,
 
31
 ghost_details, entries;
 
32
header line = "#bazaar dirstate flat format 2", NL;
 
33
full checksum = "adler32: ", ["-"], WHOLE_NUMBER, NL;
 
34
row count = "num_entries: ", digit, NL;
 
35
parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
 
36
ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
 
37
entries = {entry};
 
38
entry = entry_key, current_entry_details, {parent_entry_details};
 
39
entry_key = dirname,  basename, fileid;
 
40
current_entry_details = common_entry_details, working_entry_details;
 
41
parent_entry_details = common_entry_details, history_entry_details;
 
42
common_entry_details = MINIKIND, fingerprint, size, executable
 
43
working_entry_details = packed_stat
 
44
history_entry_details = REVISION_ID;
 
45
executable = BOOLEAN;
 
46
size = WHOLE_NUMBER;
 
47
fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
 
48
 
 
49
Given this definition, the following is useful to know:
 
50
entry (aka row) - all the data for a given key.
 
51
entry[0]: The key (dirname, basename, fileid)
 
52
entry[0][0]: dirname
 
53
entry[0][1]: basename
 
54
entry[0][2]: fileid
 
55
entry[1]: The tree(s) data for this path and id combination.
 
56
entry[1][0]: The current tree
 
57
entry[1][1]: The second tree
 
58
 
 
59
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate:
 
60
entry[1][0][0]: kind
 
61
entry[1][0][1]: fingerprint
 
62
entry[1][0][2]: size
 
63
entry[1][0][3]: executable
 
64
entry[1][0][4]: packed_stat
 
65
OR (for non tree-0)
 
66
entry[1][1][4]: revision_id
 
67
 
 
68
There may be multiple rows at the root, one per id present in the root, so the
 
69
in memory root row is now:
 
70
self._dirblocks[0] -> ('', [entry ...]),
 
71
and the entries in there are
 
72
entries[0][0]: ''
 
73
entries[0][1]: ''
 
74
entries[0][2]: file_id
 
75
entries[1][0]: The tree data for the current tree for this fileid at /
 
76
etc.
 
77
 
 
78
Kinds:
 
79
'r' is a relocated entry: This path is not present in this tree with this id,
 
80
    but the id can be found at another location. The fingerprint is used to
 
81
    point to the target location.
 
82
'a' is an absent entry: In that tree the id is not present at this path.
 
83
'd' is a directory entry: This path in this tree is a directory with the
 
84
    current file id. There is no fingerprint for directories.
 
85
'f' is a file entry: As for directory, but its a file. The fingerprint is a
 
86
    sha1 value.
 
87
'l' is a symlink entry: As for directory, but a symlink. The fingerprint is the
 
88
    link target.
 
89
 
 
90
 
 
91
--- Format 1 had the following different definition: ---
 
92
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
 
93
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target, 
 
94
    {PARENT ROW}
 
95
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
 
96
    basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
 
97
    SHA1
 
98
 
 
99
PARENT ROW's are emitted for every parent that is not in the ghosts details
 
100
line. That is, if the parents are foo, bar, baz, and the ghosts are bar, then
 
101
each row will have a PARENT ROW for foo and baz, but not for bar.
 
102
 
 
103
 
 
104
In any tree, a kind of 'moved' indicates that the fingerprint field
 
105
(which we treat as opaque data specific to the 'kind' anyway) has the
 
106
details for the id of this row in that tree.
 
107
 
 
108
I'm strongly tempted to add a id->path index as well, but I think that
 
109
where we need id->path mapping; we also usually read the whole file, so
 
110
I'm going to skip that for the moment, as we have the ability to locate
 
111
via bisect any path in any tree, and if we lookup things by path, we can
 
112
accumulate a id->path mapping as we go, which will tend to match what we
 
113
looked for.
 
114
 
 
115
I plan to implement this asap, so please speak up now to alter/tweak the
 
116
design - and once we stabilise on this, I'll update the wiki page for
 
117
it.
 
118
 
 
119
The rationale for all this is that we want fast operations for the
 
120
common case (diff/status/commit/merge on all files) and extremely fast
 
121
operations for the less common but still occurs a lot status/diff/commit
 
122
on specific files). Operations on specific files involve a scan for all
 
123
the children of a path, *in every involved tree*, which the current
 
124
format did not accommodate. 
 
125
----
 
126
 
 
127
Design priorities:
 
128
 1) Fast end to end use for bzr's top 5 uses cases. (commmit/diff/status/merge/???)
 
129
 2) fall back current object model as needed.
 
130
 3) scale usably to the largest trees known today - say 50K entries. (mozilla
 
131
    is an example of this)
 
132
 
 
133
 
 
134
Locking:
 
135
 Eventually reuse dirstate objects across locks IFF the dirstate file has not
 
136
 been modified, but will require that we flush/ignore cached stat-hit data
 
137
 because we wont want to restat all files on disk just because a lock was
 
138
 acquired, yet we cannot trust the data after the previous lock was released.
 
139
 
 
140
Memory representation:
 
141
 vector of all directories, and vector of the childen ?
 
142
   i.e. 
 
143
     root_entrie = (direntry for root, [parent_direntries_for_root]), 
 
144
     dirblocks = [
 
145
     ('', ['data for achild', 'data for bchild', 'data for cchild'])
 
146
     ('dir', ['achild', 'cchild', 'echild'])
 
147
     ]
 
148
    - single bisect to find N subtrees from a path spec
 
149
    - in-order for serialisation - this is 'dirblock' grouping.
 
150
    - insertion of a file '/a' affects only the '/' child-vector, that is, to
 
151
      insert 10K elements from scratch does not generates O(N^2) memoves of a
 
152
      single vector, rather each individual, which tends to be limited to a 
 
153
      manageable number. Will scale badly on trees with 10K entries in a 
 
154
      single directory. compare with Inventory.InventoryDirectory which has
 
155
      a dictionary for the children. No bisect capability, can only probe for
 
156
      exact matches, or grab all elements and sorta.
 
157
    - Whats the risk of error here? Once we have the base format being processed
 
158
      we should have a net win regardless of optimality. So we are going to 
 
159
      go with what seems reasonably.
 
160
open questions:
 
161
 
 
162
maybe we should do a test profile of these core structure - 10K simulated searches/lookups/etc?
 
163
 
 
164
Objects for each row?
 
165
The lifetime of Dirstate objects is current per lock, but see above for
 
166
possible extensions. The lifetime of a row from a dirstate is expected to be
 
167
very short in the optimistic case: which we are optimising for. For instance,
 
168
subtree status will determine from analysis of the disk data what rows need to
 
169
be examined at all, and will be able to determine from a single row whether
 
170
that file has altered or not, so we are aiming to process tens of thousands of
 
171
entries each second within the dirstate context, before exposing anything to
 
172
the larger codebase. This suggests we want the time for a single file
 
173
comparison to be < 0.1 milliseconds. That would give us 10000 paths per second
 
174
processed, and to scale to 100 thousand we'll another order of magnitude to do
 
175
that. Now, as the lifetime for all unchanged entries is the time to parse, stat
 
176
the file on disk, and then immediately discard, the overhead of object creation
 
177
becomes a significant cost.
 
178
 
 
179
Figures: Creating a tuple from from 3 elements was profiled at 0.0625
 
180
microseconds, whereas creating a object which is subclassed from tuple was
 
181
0.500 microseconds, and creating an object with 3 elements and slots was 3
 
182
microseconds long. 0.1 milliseconds is 100 microseconds, and ideally we'll get
 
183
down to 10 microseconds for the total processing - having 33% of that be object
 
184
creation is a huge overhead. There is a potential cost in using tuples within
 
185
each row which is that the conditional code to do comparisons may be slower
 
186
than method invocation, but method invocation is known to be slow due to stack
 
187
frame creation, so avoiding methods in these tight inner loops in unfortunately
 
188
desirable. We can consider a pyrex version of this with objects in future if
 
189
desired.
 
190
 
 
191
"""
 
192
 
 
193
 
 
194
import base64
 
195
import bisect
 
196
import cStringIO
 
197
import os
 
198
import sha
 
199
import struct
 
200
import zlib
 
201
 
 
202
from bzrlib import (
 
203
    errors,
 
204
    trace,
 
205
    )
 
206
import bzrlib.inventory
 
207
from bzrlib import osutils
 
208
from bzrlib.osutils import (
 
209
    pathjoin,
 
210
    sha_file,
 
211
    sha_string,
 
212
    walkdirs,
 
213
    )
 
214
 
 
215
 
 
216
class DirState(object):
 
217
    """Record directory and metadata state for fast access.
 
218
 
 
219
    A dirstate is a specialised data structure for managing local working
 
220
    tree state information. Its not yet well defined whether it is platform
 
221
    specific, and if it is how we detect/parameterise that.
 
222
    """
 
223
 
 
224
    _kind_to_minikind = {'absent':'a', 'file':'f', 'directory':'d', 'relocated':'r', 'symlink':'l'}
 
225
    _minikind_to_kind = {'a':'absent', 'f':'file', 'd':'directory', 'l':'symlink', 'r':'relocated'}
 
226
    _to_yesno = {True:'y', False: 'n'} # TODO profile the performance gain
 
227
     # of using int conversion rather than a dict here. AND BLAME ANDREW IF
 
228
     # it is faster.
 
229
 
 
230
    # TODO: jam 20070221 Make sure we handle when there are duplicated records
 
231
    #       (like when we remove + add the same path, or we have a rename)
 
232
    # TODO: jam 20070221 Figure out what to do if we have a record that exceeds
 
233
    #       the BISECT_PAGE_SIZE.
 
234
    BISECT_PAGE_SIZE = 4096
 
235
 
 
236
    NOT_IN_MEMORY = 0
 
237
    IN_MEMORY_UNMODIFIED = 1
 
238
    IN_MEMORY_MODIFIED = 2
 
239
 
 
240
    # A pack_stat (the x's) that is just noise and will never match the output
 
241
    # of base64 encode.
 
242
    NULLSTAT = 'x' * 32
 
243
    NULL_PARENT_DETAILS = ('absent', '', 0, False, '')
 
244
 
 
245
    def __init__(self):
 
246
        """Create a  DirState object.
 
247
 
 
248
        Attributes of note:
 
249
 
 
250
        :attr _root_entrie: The root row of the directory/file information,
 
251
            - contains the path to / - '', ''
 
252
            - kind of 'directory',
 
253
            - the file id of the root in utf8
 
254
            - size of 0
 
255
            - a packed state
 
256
            - and no sha information.
 
257
        """
 
258
        # _header_state and _dirblock_state represent the current state
 
259
        # of the dirstate metadata and the per-row data respectiely.
 
260
        # NOT_IN_MEMORY indicates that no data is in memory
 
261
        # IN_MEMORY_UNMODIFIED indicates that what we have in memory
 
262
        #   is the same as is on disk
 
263
        # IN_MEMORY_MODIFIED indicates that we have a modified version
 
264
        #   of what is on disk. 
 
265
        # In future we will add more granularity, for instance _dirblock_state
 
266
        # will probably support partially-in-memory as a separate variable,
 
267
        # allowing for partially-in-memory unmodified and partially-in-memory
 
268
        # modified states.
 
269
        self._header_state = DirState.NOT_IN_MEMORY
 
270
        self._dirblock_state = DirState.NOT_IN_MEMORY
 
271
        self._dirblocks = []
 
272
        self._ghosts = []
 
273
        self._parents = []
 
274
        self._state_file=None
 
275
 
 
276
    def add(self, path, file_id, kind, stat, link_or_sha1):
 
277
        """Add a path to be tracked.
 
278
 
 
279
        :param path: The path within the dirstate - '' is the root, 'foo' is the
 
280
            path foo within the root, 'foo/bar' is the path bar within foo 
 
281
            within the root.
 
282
        :param file_id: The file id of the path being added.
 
283
        :param kind: The kind of the path.
 
284
        :param stat: The output of os.lstate for the path.
 
285
        :param link_or_sha1: The sha value of the file, or the target of a
 
286
            symlink. '' for directories.
 
287
        """
 
288
        # adding a file:
 
289
        # find the block its in. 
 
290
        # find the location in the block.
 
291
        # check its not there
 
292
        # add it.
 
293
        #------- copied from bzrlib.inventory.make_entry
 
294
        # --- normalized_filename wants a unicode basename only, so get one.
 
295
        dirname, basename = os.path.split(path)
 
296
        # we dont import normalized_filename directly because we want to be
 
297
        # able to change the implementation at runtime for tests.
 
298
        norm_name, can_access = osutils.normalized_filename(basename)
 
299
        if norm_name != basename:
 
300
            if can_access:
 
301
                basename = norm_name
 
302
            else:
 
303
                raise errors.InvalidNormalization(path)
 
304
        # now that we've normalised, we need the correct utf8 path and 
 
305
        # dirname and basename elements. This single encode and split should be
 
306
        # faster than three separate encodes.
 
307
        utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
 
308
        dirname, basename = os.path.split(utf8path)
 
309
        entry_key = (dirname, basename, file_id.encode('utf8'))
 
310
        self._read_dirblocks_if_needed()
 
311
        block_index, present = self._find_block_index_from_key(entry_key)
 
312
        if not present:
 
313
            # TODO: This test is not complete - an empty directory, or a
 
314
            # directory for a parent tree will fool it.
 
315
            # some parent path has not been added - its an error to add this
 
316
            # child
 
317
            raise errors.NotVersionedError(path, str(self))
 
318
        block = self._dirblocks[block_index][1]
 
319
        if stat is None:
 
320
            size = 0
 
321
            packed_stat = DirState.NULLSTAT
 
322
        else:
 
323
            size = stat.st_size
 
324
            packed_stat = pack_stat(stat)
 
325
        parent_info = self._empty_parent_info()
 
326
        if kind == 'file':
 
327
            entry_data = entry_key, [
 
328
                (kind, link_or_sha1, size, False, packed_stat),
 
329
                ] + parent_info
 
330
        elif kind == 'directory':
 
331
            entry_data = entry_key, [
 
332
                (kind, '', 0, False, packed_stat),
 
333
                ] + parent_info
 
334
        elif kind == 'symlink':
 
335
            entry_data = entry_key, [
 
336
                (kind, link_or_sha1, size, False, packed_stat),
 
337
                ] + parent_info
 
338
        else:
 
339
            raise errors.BzrError('unknown kind %r' % kind)
 
340
        entry_index, present = self._find_entry_index(entry_key, block)
 
341
        assert not present, "basename %r already added" % basename
 
342
        block.insert(entry_index, entry_data)
 
343
 
 
344
        if kind == 'directory':
 
345
           # insert a new dirblock
 
346
           self._ensure_block(block_index, entry_index, utf8path)
 
347
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
348
 
 
349
    def _empty_parent_info(self):
 
350
        return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
 
351
                                                    len(self._ghosts))
 
352
 
 
353
    def _ensure_block(self, parent_block_index, parent_row_index, dirname):
 
354
        """Enssure a block for dirname exists.
 
355
        
 
356
        This function exists to let callers which know that there is a
 
357
        directory dirname ensure that the block for it exists. This block can
 
358
        fail to exist because of demand loading, or because a directory had no
 
359
        children. In either case it is not an error. It is however an error to
 
360
        call this if there is no parent entry for the directory, and thus the
 
361
        function requires the coordinates of such an entry to be provided.
 
362
 
 
363
        The root row is special cased and can be indicated with a parent block
 
364
        and row index of -1
 
365
 
 
366
        :param parent_block_index: The index of the block in which dirname's row
 
367
            exists.
 
368
        :param parent_row_index: The index in the parent block where the row
 
369
            exists.
 
370
        :param dirname: The utf8 dirname to ensure there is a block for.
 
371
        :return: The index for the block.
 
372
        """
 
373
        assert dirname != ''
 
374
        # the basename of the directory must be the end of its full name.
 
375
        if not (parent_block_index == -1 and
 
376
            parent_block_index == -1 and dirname == ''):
 
377
            assert dirname.endswith(
 
378
                self._dirblocks[parent_block_index][1][parent_row_index][0][1])
 
379
        block_index, present = self._find_block_index_from_key((dirname, '', ''))
 
380
        if not present:
 
381
            ## In future, when doing partial parsing, this should load and 
 
382
            # populate the entire block.
 
383
            self._dirblocks.insert(block_index, (dirname, []))
 
384
        return block_index
 
385
 
 
386
    def _entries_to_current_state(self, new_entries):
 
387
        """Load new_entries into self.dirblocks.
 
388
 
 
389
        Process new_entries into the current state object, making them the active
 
390
        state.
 
391
 
 
392
        :param new_entries: A sorted list of entries. This function does not sort
 
393
            to prevent unneeded overhead when callers have a sorted list already.
 
394
        :return: Nothing.
 
395
        """
 
396
        assert new_entries[0][0][0:2] == ('', ''), \
 
397
            "Missing root row %r" % new_entries[0][0]
 
398
        # The two blocks here are deliberate: the root block and the 
 
399
        # contents-of-root block.
 
400
        self._dirblocks = [('', []), ('', [])]
 
401
        current_block = self._dirblocks[0][1]
 
402
        current_dirname = ''
 
403
        root_key = ('', '')
 
404
        for entry in new_entries:
 
405
            if entry[0][0] != current_dirname:
 
406
                # new block - different dirname
 
407
                current_block = []
 
408
                current_dirname = entry[0][0]
 
409
                self._dirblocks.append((current_dirname, current_block))
 
410
            elif entry[0][1]:
 
411
                # this is not a root entry for a tree (it has a basename)
 
412
                current_block = self._dirblocks[-1][1]
 
413
            # append the entry to the current block
 
414
            current_block.append(entry)
 
415
    
 
416
    def _entry_to_line(self, entry):
 
417
        """Serialize entry to a NULL delimited line ready for _get_output_lines.
 
418
        
 
419
        :param entry: An entry_tuple as defined in the module docstring.
 
420
        """
 
421
        entire_entry = list(entry[0])
 
422
        for tree_number, tree_data in enumerate(entry[1]):
 
423
            # (kind, fingerprint, size, executable, tree_specific_string)
 
424
            entire_entry.extend(tree_data)
 
425
            # 3 for the key, 5 for the fields per tree.
 
426
            tree_offset = 3 + tree_number * 5
 
427
            # kind
 
428
            entire_entry[tree_offset + 0] = DirState._kind_to_minikind[tree_data[0]]
 
429
            # size
 
430
            entire_entry[tree_offset + 2] = str(tree_data[2])
 
431
            # executable
 
432
            entire_entry[tree_offset + 3] = DirState._to_yesno[tree_data[3]]
 
433
        return '\0'.join(entire_entry)
 
434
 
 
435
    def _fields_per_row(self):
 
436
        """How many null separated fields should be in each entry row.
 
437
 
 
438
        Each line now has an extra '\n' field which is not used
 
439
        so we just skip over it
 
440
        entry size:
 
441
            3 fields for the key
 
442
            + number of fields per tree_data (5) * tree count
 
443
            + newline
 
444
         """
 
445
        tree_count = 1 + self._num_present_parents()
 
446
        return 3 + 5 * tree_count + 1
 
447
 
 
448
    def _find_block(self, key, add_if_missing=False):
 
449
        """Return the block that key should be present in.
 
450
 
 
451
        :param key: A dirstate entry key.
 
452
        :return: The block tuple.
 
453
        """
 
454
        block_index, present = self._find_block_index_from_key(key)
 
455
        if not present:
 
456
            if not add_if_missing:
 
457
                # check to see if key is versioned itself - we might want to
 
458
                # add it anyway, because dirs with no entries dont get a
 
459
                # dirblock at parse time.
 
460
                # This is an uncommon branch to take: most dirs have children,
 
461
                # and most code works with versioned paths.
 
462
                parent_base, parent_name = os.path.split(key[0])
 
463
                if not self._get_block_entry_index(parent_base, parent_name, 0)[3]:
 
464
                    # some parent path has not been added - its an error to add
 
465
                    # this child
 
466
                    raise errors.NotVersionedError(key[0:2], str(self))
 
467
            self._dirblocks.insert(block_index, (key[0], []))
 
468
        return self._dirblocks[block_index]
 
469
 
 
470
    def _find_block_index_from_key(self, key):
 
471
        """Find the dirblock index for a key.
 
472
 
 
473
        :return: The block index, True if the block for the key is present.
 
474
        """
 
475
        if key[0:2] == ('', ''):
 
476
            return 0, True
 
477
        block_index = bisect.bisect_left(self._dirblocks, (key[0], []), 1)
 
478
        # _right returns one-past-where-key is so we have to subtract
 
479
        # one to use it. we use _right here because there are two
 
480
        # '' blocks - the root, and the contents of root
 
481
        # we always have a minimum of 2 in self._dirblocks: root and
 
482
        # root-contents, and for '', we get 2 back, so this is 
 
483
        # simple and correct:
 
484
        present = (block_index < len(self._dirblocks) and
 
485
            self._dirblocks[block_index][0] == key[0])
 
486
        return block_index, present
 
487
 
 
488
    def _find_entry_index(self, key, block):
 
489
        """Find the entry index for a key in a block.
 
490
 
 
491
        :return: The entry index, True if the entry for the key is present.
 
492
        """
 
493
        entry_index = bisect.bisect_left(block, (key, []))
 
494
        present = (entry_index < len(block) and
 
495
            block[entry_index][0] == key)
 
496
        return entry_index, present
 
497
 
 
498
    @staticmethod
 
499
    def from_tree(tree, dir_state_filename):
 
500
        """Create a dirstate from a bzr Tree.
 
501
 
 
502
        :param tree: The tree which should provide parent information and
 
503
            inventory ids.
 
504
        """
 
505
        result = DirState.initialize(dir_state_filename)
 
506
        tree.lock_read()
 
507
        parent_ids = tree.get_parent_ids()
 
508
        num_parents = len(parent_ids)
 
509
        parent_trees = []
 
510
        for parent_id in parent_ids:
 
511
            parent_trees.append((parent_id, tree.branch.repository.revision_tree(parent_id)))
 
512
            parent_trees[-1][1].lock_read()
 
513
        result.set_parent_trees(parent_trees, [])
 
514
        result.set_state_from_inventory(tree.inventory)
 
515
 
 
516
        for revid, parent in parent_trees:
 
517
            parent.unlock()
 
518
        tree.unlock()
 
519
        return result
 
520
 
 
521
    def get_ghosts(self):
 
522
        """Return a list of the parent tree revision ids that are ghosts."""
 
523
        self._read_header_if_needed()
 
524
        return self._ghosts
 
525
 
 
526
    def get_lines(self):
 
527
        """Serialise the entire dirstate to a sequence of lines."""
 
528
        if (self._header_state == DirState.IN_MEMORY_UNMODIFIED and
 
529
            self._dirblock_state == DirState.IN_MEMORY_UNMODIFIED):
 
530
            # read whats on disk.
 
531
            self._state_file.seek(0)
 
532
            return self._state_file.readlines()
 
533
        lines = []
 
534
        lines.append(self._get_parents_line(self.get_parent_ids()))
 
535
        lines.append(self._get_ghosts_line(self._ghosts))
 
536
        # append the root line which is special cased
 
537
        lines.extend(map(self._entry_to_line, self._iter_entries()))
 
538
        return self._get_output_lines(lines)
 
539
 
 
540
    def _get_ghosts_line(self, ghost_ids):
 
541
        """Create a line for the state file for ghost information."""
 
542
        return '\0'.join([str(len(ghost_ids))] + ghost_ids)
 
543
 
 
544
    def _get_parents_line(self, parent_ids):
 
545
        """Create a line for the state file for parents information."""
 
546
        return '\0'.join([str(len(parent_ids))] + parent_ids)
 
547
 
 
548
    def _get_fields_to_entry(self):
 
549
        """Get a function which converts entry fields into a entry record.
 
550
 
 
551
        This handles kind, size, and executable, as well as parent records.
 
552
 
 
553
        :return: A function which takes a list of fields, and returns an
 
554
            appropriate record for storing in memory.
 
555
        """
 
556
        # This is intentionally unrolled for performance
 
557
        num_present_parents = self._num_present_parents()
 
558
        if num_present_parents == 0:
 
559
            def fields_to_entry_0_parents(fields, _int=int, _tuple=tuple,
 
560
                                          _mini_to_kind=self._minikind_to_kind):
 
561
                path_name_file_id_key = _tuple(fields[:3])
 
562
                return (path_name_file_id_key, [
 
563
                    ( # Current tree
 
564
                        _mini_to_kind[fields[3]], # kind
 
565
                        fields[4],                # fingerprint
 
566
                        _int(fields[5]),          # size
 
567
                        fields[6] == 'y',         # executable
 
568
                        fields[7],                # packed_stat or revision_id
 
569
                    )])
 
570
            return fields_to_entry_0_parents
 
571
        elif num_present_parents == 1:
 
572
            def fields_to_entry_1_parent(fields, _int=int, _tuple=tuple,
 
573
                                         _mini_to_kind=self._minikind_to_kind):
 
574
                path_name_file_id_key = _tuple(fields[:3])
 
575
                return (path_name_file_id_key, [
 
576
                    ( # Current tree
 
577
                        _mini_to_kind[fields[3]], # kind
 
578
                        fields[4],                # fingerprint
 
579
                        _int(fields[5]),          # size
 
580
                        fields[6] == 'y',         # executable
 
581
                        fields[7],                # packed_stat or revision_id
 
582
                    ),
 
583
                    ( # Parent 1
 
584
                        _mini_to_kind[fields[8]], # kind
 
585
                        fields[9],                # fingerprint
 
586
                        _int(fields[10]),         # size
 
587
                        fields[11] == 'y',        # executable
 
588
                        fields[12],               # packed_stat or revision_id
 
589
                    ),
 
590
                    ])
 
591
            return fields_to_entry_1_parent
 
592
        elif num_present_parents == 2:
 
593
            def fields_to_entry_2_parents(fields, _int=int, _tuple=tuple,
 
594
                                          _mini_to_kind=self._minikind_to_kind):
 
595
                path_name_file_id_key = _tuple(fields[:3])
 
596
                return (path_name_file_id_key, [
 
597
                    ( # Current tree
 
598
                        _mini_to_kind[fields[3]], # kind
 
599
                        fields[4],                # fingerprint
 
600
                        _int(fields[5]),          # size
 
601
                        fields[6] == 'y',         # executable
 
602
                        fields[7],                # packed_stat or revision_id
 
603
                    ),
 
604
                    ( # Parent 1
 
605
                        _mini_to_kind[fields[8]], # kind
 
606
                        fields[9],                # fingerprint
 
607
                        _int(fields[10]),         # size
 
608
                        fields[11] == 'y',        # executable
 
609
                        fields[12],               # packed_stat or revision_id
 
610
                    ),
 
611
                    ( # Parent 2
 
612
                        _mini_to_kind[fields[13]],# kind
 
613
                        fields[14],               # fingerprint
 
614
                        _int(fields[15]),         # size
 
615
                        fields[16] == 'y',        # executable
 
616
                        fields[17],               # packed_stat or revision_id
 
617
                    ),
 
618
                    ])
 
619
            return fields_to_entry_2_parents
 
620
        else:
 
621
            def fields_to_entry_n_parents(fields, _int=int, _tuple=tuple,
 
622
                                          _mini_to_kind=self._minikind_to_kind):
 
623
                path_name_file_id_key = _tuple(fields[:3])
 
624
                trees = [(_mini_to_kind[fields[cur]], # kind
 
625
                          fields[cur+1],              # fingerprint
 
626
                          _int(fields[cur+2]),        # size
 
627
                          fields[cur+3] == 'y',       # executable
 
628
                          fields[cur+4],              # stat or revision_id
 
629
                         ) for cur in xrange(3, len(fields)-1, 5)]
 
630
                return path_name_file_id_key, trees
 
631
            return fields_to_entry_n_parents
 
632
 
 
633
    def get_parent_ids(self):
 
634
        """Return a list of the parent tree ids for the directory state."""
 
635
        self._read_header_if_needed()
 
636
        return list(self._parents)
 
637
 
 
638
    def _get_block_entry_index(self, dirname, basename, tree_index):
 
639
        """Get the coordinates for a path in the state structure.
 
640
 
 
641
        :param dirname: The utf8 dirname to lookup.
 
642
        :param basename: The utf8 basename to lookup.
 
643
        :param tree_index: The index of the tree for which this lookup should
 
644
            be attempted.
 
645
        :return: A tuple describing where the path is located, or should be
 
646
            inserted. The tuple contains four fields: the block index, the row
 
647
            index, anda two booleans are True when the directory is present, and
 
648
            when the entire path is present.  There is no guarantee that either
 
649
            coordinate is currently reachable unless the found field for it is
 
650
            True. For instance, a directory not present in the searched tree
 
651
            may be returned with a value one greater than the current highest
 
652
            block offset. The directory present field will always be True when
 
653
            the path present field is True. The directory present field does
 
654
            NOT indicate that the directory is present in the searched tree,
 
655
            rather it indicates that there are at least some files in some
 
656
            tree present there.
 
657
        """
 
658
        self._read_dirblocks_if_needed()
 
659
        key = dirname, basename, ''
 
660
        block_index, present = self._find_block_index_from_key(key)
 
661
        if not present:
 
662
            # no such directory - return the dir index and 0 for the row.
 
663
            return block_index, 0, False, False
 
664
        block = self._dirblocks[block_index][1] # access the entries only
 
665
        entry_index, present = self._find_entry_index(key, block)
 
666
        # linear search through present entries at this path to find the one
 
667
        # requested.
 
668
        while entry_index < len(block) and block[entry_index][0][1] == basename:
 
669
            if block[entry_index][1][tree_index][0] not in \
 
670
                       ('absent', 'relocated'):
 
671
                return block_index, entry_index, True, True
 
672
            entry_index += 1
 
673
        return block_index, entry_index, True, False
 
674
 
 
675
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None):
 
676
        """Get the dirstate entry for path in tree tree_index
 
677
 
 
678
        If either file_id or path is supplied, it is used as the key to lookup.
 
679
        If both are supplied, the fastest lookup is used, and an error is
 
680
        raised if they do not both point at the same row.
 
681
        
 
682
        :param tree_index: The index of the tree we wish to locate this path
 
683
            in. If the path is present in that tree, the entry containing its
 
684
            details is returned, otherwise (None, None) is returned
 
685
        :param fileid_utf8: A utf8 file_id to look up.
 
686
        :param path_utf8: An utf8 path to be looked up.
 
687
        :return: The dirstate entry tuple for path, or (None, None)
 
688
        """
 
689
        self._read_dirblocks_if_needed()
 
690
        if path_utf8 is not None:
 
691
            assert path_utf8.__class__ == str, 'path_utf8 is not a str: %s %s' % (type(path_utf8), path_utf8)
 
692
            # path lookups are faster
 
693
            dirname, basename = os.path.split(path_utf8)
 
694
            block_index, entry_index, dir_present, file_present = \
 
695
                self._get_block_entry_index(dirname, basename, tree_index)
 
696
            if not file_present:
 
697
                return None, None
 
698
            entry = self._dirblocks[block_index][1][entry_index]
 
699
            assert entry[0][2] and entry[1][tree_index][0] not in ('absent', 'relocated'), 'unversioned entry?!?!'
 
700
            if fileid_utf8:
 
701
                if entry[0][2] != fileid_utf8:
 
702
                    raise BzrError('integrity error ? : mismatching tree_index, file_id and path')
 
703
            return entry
 
704
        else:
 
705
            for entry in self._iter_entries():
 
706
                if entry[0][2] == fileid_utf8:
 
707
                    if entry[1][tree_index][0] == 'relocated':
 
708
                        # look up the real location directly by path
 
709
                        return self._get_entry(tree_index,
 
710
                            fileid_utf8=fileid_utf8,
 
711
                            path_utf8=entry[1][tree_index][1])
 
712
                    if entry[1][tree_index][0] == 'absent':
 
713
                        # not in the tree at all.
 
714
                        return None, None
 
715
                    return entry
 
716
            return None, None
 
717
 
 
718
    @staticmethod
 
719
    def initialize(path):
 
720
        """Create a new dirstate on path.
 
721
 
 
722
        The new dirstate will be an empty tree - that is it has no parents,
 
723
        and only a root node - which has id ROOT_ID.
 
724
 
 
725
        :param path: The name of the file for the dirstate.
 
726
        :return: A DirState object.
 
727
        """
 
728
        # This constructs a new DirState object on a path, sets the _state_file
 
729
        # to a new empty file for that path. It then calls _set_data() with our
 
730
        # stock empty dirstate information - a root with ROOT_ID, no children,
 
731
        # and no parents. Finally it calls save() to ensure that this data will
 
732
        # persist.
 
733
        result = DirState()
 
734
        result._state_file = open(path, 'wb+')
 
735
        # root dir and root dir contents with no children.
 
736
        empty_tree_dirblocks = [('', []), ('', [])]
 
737
        # a new root directory, with a NULLSTAT.
 
738
        empty_tree_dirblocks[0][1].append(
 
739
            (('', '', bzrlib.inventory.ROOT_ID), [
 
740
                ('directory', '', 0, False, DirState.NULLSTAT),
 
741
            ]))
 
742
        result._set_data([], empty_tree_dirblocks)
 
743
        try:
 
744
            result.save()
 
745
        except:
 
746
            result._state_file.close()
 
747
            raise
 
748
        return result
 
749
 
 
750
    def _inv_entry_to_details(self, inv_entry):
 
751
        """Convert an inventory entry (from a revision tree) to state details.
 
752
 
 
753
        :param inv_entry: An inventory entry whose sha1 and link targets can be
 
754
            relied upon, and which has a revision set.
 
755
        :return: A details tuple - the details for a single tree at a path +
 
756
            id.
 
757
        """
 
758
        kind = inv_entry.kind
 
759
        tree_data = inv_entry.revision.encode('utf8')
 
760
        assert len(tree_data) > 0, 'empty revision for the inv_entry.'
 
761
        if kind == 'directory':
 
762
            fingerprint = ''
 
763
            size = 0
 
764
            executable = False
 
765
        elif kind == 'symlink':
 
766
            fingerprint = inv_entry.symlink_target or ''
 
767
            size = 0
 
768
            executable = False
 
769
        elif kind == 'file':
 
770
            fingerprint = inv_entry.text_sha1 or ''
 
771
            size = inv_entry.text_size or 0
 
772
            executable = inv_entry.executable
 
773
        else:
 
774
            raise Exception
 
775
        return (kind, fingerprint, size, executable, tree_data)
 
776
 
 
777
    def _iter_entries(self):
 
778
        """Iterate over all the entries in the dirstate.
 
779
 
 
780
        Each yelt item is an entry in the standard format described in the
 
781
        docstring of bzrlib.dirstate.
 
782
        """
 
783
        self._read_dirblocks_if_needed()
 
784
        for directory in self._dirblocks:
 
785
            for entry in directory[1]:
 
786
                yield entry
 
787
 
 
788
    def _get_id_index(self):
 
789
        """Get an id index of self._dirblocks."""
 
790
        id_index = {}
 
791
        for key, tree_details in self._iter_entries():
 
792
            id_index.setdefault(key[2], set()).add(key)
 
793
        return id_index
 
794
 
 
795
    def _get_output_lines(self, lines):
 
796
        """format lines for final output.
 
797
 
 
798
        :param lines: A sequece of lines containing the parents list and the
 
799
            path lines.
 
800
        """
 
801
        output_lines = ['#bazaar dirstate flat format 2\n']
 
802
        lines.append('') # a final newline
 
803
        inventory_text = '\0\n\0'.join(lines)
 
804
        output_lines.append('adler32: %s\n' % (zlib.adler32(inventory_text),))
 
805
        # -3, 1 for num parents, 1 for ghosts, 1 for final newline
 
806
        num_entries = len(lines)-3
 
807
        output_lines.append('num_entries: %s\n' % (num_entries,))
 
808
        output_lines.append(inventory_text)
 
809
        return output_lines
 
810
 
 
811
    def _make_deleted_row(self, fileid_utf8, parents):
 
812
        """Return a deleted for for fileid_utf8."""
 
813
        return ('/', 'RECYCLED.BIN', 'file', fileid_utf8, 0, DirState.NULLSTAT,
 
814
            ''), parents
 
815
 
 
816
    def _num_present_parents(self):
 
817
        """The number of parent entries in each record row."""
 
818
        return len(self._parents) - len(self._ghosts)
 
819
 
 
820
    @staticmethod
 
821
    def on_file(path):
 
822
        """Construct a DirState on the file at path path."""
 
823
        result = DirState()
 
824
        result._state_file = open(path, 'rb+')
 
825
        return result
 
826
 
 
827
    def _read_dirblocks_if_needed(self):
 
828
        """Read in all the dirblocks from the file if they are not in memory.
 
829
        
 
830
        This populates self._dirblocks, and sets self._dirblock_state to
 
831
        IN_MEMORY_UNMODIFIED. It is not currently ready for incremental block
 
832
        loading.
 
833
        """
 
834
        self._read_header_if_needed()
 
835
        if self._dirblock_state == DirState.NOT_IN_MEMORY:
 
836
            # the _state_file pointer will be positioned at the start of the 
 
837
            # dirblocks.
 
838
            text = self._state_file.read()
 
839
            # TODO: check the adler checksums. adler_measured = zlib.adler32(text)
 
840
 
 
841
            fields = text.split('\0')
 
842
            # Remove the last blank entry
 
843
            trailing = fields.pop()
 
844
            assert trailing == ''
 
845
            # consider turning fields into a tuple.
 
846
 
 
847
            # skip the first field which is the trailing null from the header.
 
848
            cur = 1
 
849
            # Each line now has an extra '\n' field which is not used
 
850
            # so we just skip over it
 
851
            # entry size:
 
852
            #  3 fields for the key
 
853
            #  + number of fields per tree_data (5) * tree count
 
854
            #  + newline
 
855
            num_present_parents = self._num_present_parents()
 
856
            tree_count = 1 + num_present_parents
 
857
            entry_size = self._fields_per_row()
 
858
            expected_field_count = entry_size * self._num_entries
 
859
            if len(fields) - cur > expected_field_count:
 
860
                fields = fields[:expected_field_count + cur]
 
861
                trace.mutter('Unexpectedly long dirstate field count!')
 
862
                print "XXX: incorrectly truncated dirstate file bug triggered."
 
863
            field_count = len(fields)
 
864
            # this checks our adjustment, and also catches file too short.
 
865
            assert field_count - cur == expected_field_count, \
 
866
                'field count incorrect %s != %s, entry_size=%s, '\
 
867
                'num_entries=%s fields=%r' % (
 
868
                    field_count - cur, expected_field_count, entry_size,
 
869
                    self._num_entries, fields)
 
870
 
 
871
            fields_to_entry = self._get_fields_to_entry()
 
872
            entries = [fields_to_entry(fields[pos:pos+entry_size])
 
873
                       for pos in xrange(cur, field_count, entry_size)]
 
874
            self._entries_to_current_state(entries)
 
875
            self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
876
 
 
877
    def _read_header(self):
 
878
        """This reads in the metadata header, and the parent ids.
 
879
 
 
880
        After reading in, the file should be positioned at the null
 
881
        just before the start of the first record in the file.
 
882
 
 
883
        :return: (expected adler checksum, number of entries, parent list)
 
884
        """
 
885
        self._read_prelude()
 
886
        parent_line = self._state_file.readline()
 
887
        info = parent_line.split('\0')
 
888
        num_parents = int(info[0])
 
889
        assert num_parents == len(info)-2, 'incorrect parent info line'
 
890
        self._parents = info[1:-1]
 
891
 
 
892
        ghost_line = self._state_file.readline()
 
893
        info = ghost_line.split('\0')
 
894
        num_ghosts = int(info[1])
 
895
        assert num_ghosts == len(info)-3, 'incorrect ghost info line'
 
896
        self._ghosts = info[2:-1]
 
897
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
898
 
 
899
    def _read_header_if_needed(self):
 
900
        """Read the header of the dirstate file if needed."""
 
901
        if self._header_state == DirState.NOT_IN_MEMORY:
 
902
            self._read_header()
 
903
 
 
904
    def _read_prelude(self):
 
905
        """Read in the prelude header of the dirstate file
 
906
 
 
907
        This only reads in the stuff that is not connected to the adler
 
908
        checksum. The position will be correct to read in the rest of
 
909
        the file and check the checksum after this point.
 
910
        The next entry in the file should be the number of parents,
 
911
        and their ids. Followed by a newline.
 
912
        """
 
913
        header = self._state_file.readline()
 
914
        assert header == '#bazaar dirstate flat format 2\n', \
 
915
            'invalid header line: %r' % (header,)
 
916
        adler_line = self._state_file.readline()
 
917
        assert adler_line.startswith('adler32: '), 'missing adler32 checksum'
 
918
        self.adler_expected = int(adler_line[len('adler32: '):-1])
 
919
        num_entries_line = self._state_file.readline()
 
920
        assert num_entries_line.startswith('num_entries: '), 'missing num_entries line'
 
921
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
 
922
    
 
923
    def save(self):
 
924
        """Save any pending changes created during this session.
 
925
        
 
926
        We reuse the existing file, because that prevents race conditions with
 
927
        file creation, and we expect to be using oslocks on it in the near 
 
928
        future to prevent concurrent modification and reads - because dirstates
 
929
        incremental data aggretation is not compatible with reading a modified
 
930
        file, and replacing a file in use by another process is impossible on 
 
931
        windows.
 
932
 
 
933
        A dirstate in read only mode should be smart enough though to validate
 
934
        that the file has not changed, and otherwise discard its cache and
 
935
        start over, to allow for fine grained read lock duration, so 'status'
 
936
        wont block 'commit' - for example.
 
937
        """
 
938
        if (self._header_state == DirState.IN_MEMORY_MODIFIED or
 
939
            self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
 
940
            self._state_file.seek(0)
 
941
            self._state_file.writelines(self.get_lines())
 
942
            self._state_file.truncate()
 
943
            self._state_file.flush()
 
944
            self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
945
            self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
946
 
 
947
    def _set_data(self, parent_ids, dirblocks):
 
948
        """Set the full dirstate data in memory.
 
949
 
 
950
        This is an internal function used to completely replace the objects
 
951
        in memory state. It puts the dirstate into state 'full-dirty'.
 
952
 
 
953
        :param parent_ids: A list of parent tree revision ids.
 
954
        :param dirblocks: A list containing one tuple for each directory in the
 
955
            tree. Each tuple contains the directory path and a list of entries 
 
956
            found in that directory.
 
957
        """
 
958
        # our memory copy is now authoritative.
 
959
        self._dirblocks = dirblocks
 
960
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
961
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
962
        self._parents = list(parent_ids)
 
963
 
 
964
    def set_path_id(self, path, new_id):
 
965
        """Change the id of path to new_id in the current working tree.
 
966
 
 
967
        :param path: The path inside the tree to set - '' is the root, 'foo'
 
968
            is the path foo in the root.
 
969
        :param new_id: The new id to assign to the path. If unicode, it will
 
970
            be encoded to utf8. In future this will be deprecated: avoid using
 
971
            unicode ids if possible.
 
972
        """
 
973
        # TODO: start warning here.
 
974
        if new_id.__class__ == unicode:
 
975
            new_id = new_id.encode('utf8')
 
976
        self._read_dirblocks_if_needed()
 
977
        if len(path):
 
978
            import pdb;pdb.set_trace()
 
979
            # logic not written
 
980
            raise NotImplementedError(self.set_path_id)
 
981
        # TODO: check new id is unique
 
982
        entry = self._get_entry(0, path_utf8=path)
 
983
        # mark the old path absent, and insert a new root path
 
984
        present_parents = len(entry[1]) - 1
 
985
        self._make_absent(entry)
 
986
        id_index = self._get_id_index()
 
987
        self.update_minimal(('', '', new_id), 'directory', present_parents,
 
988
            path_utf8='', id_index=id_index, packed_stat=entry[1][0][4])
 
989
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
990
 
 
991
    def set_parent_trees(self, trees, ghosts):
 
992
        """Set the parent trees for the dirstate.
 
993
 
 
994
        :param trees: A list of revision_id, tree tuples. tree must be provided
 
995
            even if the revision_id refers to a ghost: supply an empty tree in 
 
996
            this case.
 
997
        :param ghosts: A list of the revision_ids that are ghosts at the time
 
998
            of setting.
 
999
        """ 
 
1000
        # TODO: generate a list of parent indexes to preserve to save 
 
1001
        # processing specific parent trees. In the common case one tree will
 
1002
        # be preserved - the left most parent.
 
1003
        # TODO: if the parent tree is a dirstate, we might want to walk them
 
1004
        # all by path in parallel for 'optimal' common-case performance.
 
1005
        # generate new root row.
 
1006
        self._read_dirblocks_if_needed()
 
1007
        # TODO future sketch: Examine the existing parents to generate a change
 
1008
        # map and then walk the new parent trees only, mapping them into the
 
1009
        # dirstate. Walk the dirstate at the same time to remove unreferenced
 
1010
        # entries.
 
1011
        # for now: 
 
1012
        # sketch: loop over all entries in the dirstate, cherry picking 
 
1013
        # entries from the parent trees, if they are not ghost trees.
 
1014
        # after we finish walking the dirstate, all entries not in the dirstate
 
1015
        # are deletes, so we want to append them to the end as per the design
 
1016
        # discussions. So do a set difference on ids with the parents to
 
1017
        # get deletes, and add them to the end.
 
1018
        # During the update process we need to answer the following questions:
 
1019
        # - find other keys containing a fileid in order to create cross-path
 
1020
        #   links. We dont't trivially use the inventory from other trees
 
1021
        #   because this leads to either double touching, or to accessing
 
1022
        #   missing keys,
 
1023
        # - find other keys containing a path 
 
1024
        # We accumulate each entry via this dictionary, including the root 
 
1025
        by_path = {}
 
1026
        id_index = {}
 
1027
        # we could do parallel iterators, but because file id data may be
 
1028
        # scattered throughout, we dont save on index overhead: we have to look
 
1029
        # at everything anyway. We can probably save cycles by reusing parent
 
1030
        # data and doing an incremental update when adding an additional
 
1031
        # parent, but for now the common cases are adding a new parent (merge),
 
1032
        # and replacing completely (commit), and commit is more common: so
 
1033
        # optimise merge later.
 
1034
        
 
1035
        # ---- start generation of full tree mapping data
 
1036
        # what trees should we use?
 
1037
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
 
1038
        # how many trees do we end up with 
 
1039
        parent_count = len(parent_trees)
 
1040
 
 
1041
        # one: the current tree
 
1042
        for entry in self._iter_entries():
 
1043
            # skip entries not in the current tree
 
1044
            if entry[1][0][0] in ('absent', 'relocated'):
 
1045
                continue
 
1046
            by_path[entry[0]] = [entry[1][0]] + \
 
1047
                [DirState.NULL_PARENT_DETAILS] * parent_count
 
1048
            id_index[entry[0][2]] = set([entry[0]])
 
1049
        
 
1050
        # now the parent trees:
 
1051
        for tree_index, tree in enumerate(parent_trees):
 
1052
            # the index is off by one, adjust it.
 
1053
            tree_index = tree_index + 1
 
1054
            # when we add new locations for a fileid we need these ranges for
 
1055
            # any fileid in this tree as we set the by_path[id] to:
 
1056
            # already_processed_tree_details + new_details + new_location_suffix
 
1057
            # the suffix is from tree_index+1:parent_count+1.
 
1058
            new_location_suffix = [DirState.NULL_PARENT_DETAILS] * (parent_count - tree_index)
 
1059
            # now stitch in all the entries from this tree
 
1060
            for path, entry in tree.inventory.iter_entries_by_dir():
 
1061
                # here we process each trees details for each item in the tree.
 
1062
                # we first update any existing entries for the id at other paths,
 
1063
                # then we either create or update the entry for the id at the
 
1064
                # right path, and finally we add (if needed) a mapping from
 
1065
                # file_id to this path. We do it in this order to allow us to
 
1066
                # avoid checking all known paths for the id when generating a
 
1067
                # new entry at this path: by adding the id->path mapping last,
 
1068
                # all the mappings are valid and have correct relocation
 
1069
                # records where needed. 
 
1070
                file_id = entry.file_id.encode('utf8')
 
1071
                path_utf8 = path.encode('utf8')
 
1072
                dirname, basename = os.path.split(path_utf8)
 
1073
                new_entry_key = (dirname, basename, file_id)
 
1074
                # tree index consistency: All other paths for this id in this tree
 
1075
                # index must point to the correct path.
 
1076
                for entry_key in id_index.setdefault(file_id, set()):
 
1077
                    # TODO:PROFILING: It might be faster to just update
 
1078
                    # rather than checking if we need to, and then overwrite
 
1079
                    # the one we are located at.
 
1080
                    if entry_key != new_entry_key:
 
1081
                        # this file id is at a different path in one of the
 
1082
                        # other trees, so put absent pointers there
 
1083
                        # This is the vertical axis in the matrix, all pointing
 
1084
                        # tot he real path.
 
1085
                        by_path[entry_key][tree_index] = ('relocated', path_utf8, 0, False, '')
 
1086
                # by path consistency: Insert into an existing path record (trivial), or 
 
1087
                # add a new one with relocation pointers for the other tree indexes.
 
1088
                if new_entry_key in id_index[file_id]:
 
1089
                    # there is already an entry where this data belongs, just insert it.
 
1090
                    by_path[new_entry_key][tree_index] = \
 
1091
                        self._inv_entry_to_details(entry)
 
1092
                else:
 
1093
                    # add relocated entries to the horizontal axis - this row
 
1094
                    # mapping from path,id. We need to look up the correct path
 
1095
                    # for the indexes from 0 to tree_index -1
 
1096
                    new_details = []
 
1097
                    for lookup_index in xrange(tree_index):
 
1098
                        # boundary case: this is the first occurence of file_id
 
1099
                        # so there are no id_indexs, possibly take this out of
 
1100
                        # the loop?
 
1101
                        if not len(id_index[file_id]):
 
1102
                            new_details.append(DirState.NULL_PARENT_DETAILS)
 
1103
                        else:
 
1104
                            # grab any one entry, use it to find the right path.
 
1105
                            # TODO: optimise this to reduce memory use in highly 
 
1106
                            # fragmented situations by reusing the relocation
 
1107
                            # records.
 
1108
                            a_key = iter(id_index[file_id]).next()
 
1109
                            if by_path[a_key][lookup_index][0] in ('relocated', 'absent'):
 
1110
                                # its a pointer or missing statement, use it as is.
 
1111
                                new_details.append(by_path[a_key][lookup_index])
 
1112
                            else:
 
1113
                                # we have the right key, make a pointer to it.
 
1114
                                real_path = ('/'.join(a_key[0:2])).strip('/')
 
1115
                                new_details.append(('relocated', real_path, 0, False, ''))
 
1116
                    new_details.append(self._inv_entry_to_details(entry))
 
1117
                    new_details.extend(new_location_suffix)
 
1118
                    by_path[new_entry_key] = new_details
 
1119
                    id_index[file_id].add(new_entry_key)
 
1120
        # --- end generation of full tree mappings
 
1121
 
 
1122
        # sort and output all the entries
 
1123
        new_entries = sorted(by_path.items())
 
1124
        self._entries_to_current_state(new_entries)
 
1125
        self._parents = [rev_id for rev_id, tree in trees]
 
1126
        self._ghosts = list(ghosts)
 
1127
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1128
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1129
 
 
1130
    def set_state_from_inventory(self, new_inv):
 
1131
        """Set new_inv as the current state. 
 
1132
 
 
1133
        This API is called by tree transform, and will usually occur with
 
1134
        existing parent trees.
 
1135
 
 
1136
        :param new_inv: The inventory object to set current state from.
 
1137
        """
 
1138
        self._read_dirblocks_if_needed()
 
1139
        # sketch:
 
1140
        #  generate a byid index of the dirstate
 
1141
        id_index = self._get_id_index()
 
1142
 
 
1143
        num_present_parents = len(self._parents) - len(self._ghosts)
 
1144
        # incremental algorithm:
 
1145
        # two iterators: current data and new data, both in dirblock order. 
 
1146
        new_iterator = new_inv.iter_entries_by_dir()
 
1147
        # we will be modifying the dirstate, so we need a stable iterator. In
 
1148
        # future we might write one, for now we just clone the state into a
 
1149
        # list - which is a shallow copy, so each 
 
1150
        old_iterator = iter(list(self._iter_entries()))
 
1151
        # both must have roots so this is safe:
 
1152
        current_new = new_iterator.next()
 
1153
        current_old = old_iterator.next()
 
1154
        def advance(iterator):
 
1155
            try:
 
1156
                return iterator.next()
 
1157
            except StopIteration:
 
1158
                return None
 
1159
        while current_new or current_old:
 
1160
            # skip entries in old that are not really there
 
1161
            if current_old and current_old[1][0][0] in ('relocated', 'absent'):
 
1162
                current_old = advance(old_iterator)
 
1163
                continue
 
1164
            if current_new:
 
1165
                # convert new into dirblock style
 
1166
                new_path_utf8 = current_new[0].encode('utf8')
 
1167
                new_dirname, new_basename = os.path.split(new_path_utf8)
 
1168
                new_id = current_new[1].file_id.encode('utf8')
 
1169
                new_entry_key = (new_dirname, new_basename, new_id)
 
1170
            else:
 
1171
                # for safety disable variables
 
1172
                new_path_utf8 = new_dirname = new_basename = new_id = new_entry_key = None
 
1173
            # 5 cases, we dont have a value that is strictly greater than everything, so
 
1174
            # we make both end conditions explicit
 
1175
            if not current_old:
 
1176
                # old is finished: insert current_new into the state.
 
1177
                self.update_minimal(new_entry_key, current_new[1].kind,
 
1178
                    num_present_parents, executable=current_new[1].executable,
 
1179
                    id_index=id_index, path_utf8=new_path_utf8)
 
1180
                current_new = advance(new_iterator)
 
1181
            elif not current_new:
 
1182
                # new is finished
 
1183
                self._make_absent(current_old, id_index)
 
1184
                current_old = advance(old_iterator)
 
1185
            elif new_entry_key == current_old[0]:
 
1186
                # same -  common case
 
1187
                # TODO: update the record if anything significant has changed.
 
1188
                # the minimal required trigger is if the execute bit or cached
 
1189
                # kind has changed.
 
1190
                if (current_old[1][0][3] != current_new[1].executable or
 
1191
                    current_old[1][0][0] != current_new[1].kind):
 
1192
                    self.update_minimal(current_old[0], current_new[1].kind,
 
1193
                        num_present_parents,
 
1194
                        executable=current_new[1].executable,
 
1195
                        id_index=id_index, path_utf8=new_path_utf8)
 
1196
                # both sides are dealt with, move on
 
1197
                current_old = advance(old_iterator)
 
1198
                current_new = advance(new_iterator)
 
1199
            elif new_entry_key < current_old[0]:
 
1200
                # new comes before:
 
1201
                # add a entry for this and advance new
 
1202
                self.update_minimal(new_entry_key, current_new[1].kind,
 
1203
                    num_present_parents, executable=current_new[1].executable,
 
1204
                    id_index=id_index, path_utf8=new_path_utf8)
 
1205
                current_new = advance(new_iterator)
 
1206
            else:
 
1207
                # old comes before:
 
1208
                self._make_absent(current_old, id_index)
 
1209
                current_old = advance(old_iterator)
 
1210
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1211
 
 
1212
    def _make_absent(self, current_old, id_index=None):
 
1213
        """Mark current_old - an entry - as absent for tree 0.
 
1214
 
 
1215
        :param id_index: An index from fileid_utf8 to sets of keys, used by
 
1216
            some functions. If provided it will be updated if needed.
 
1217
        :return: True if this was the last details entry for they entry key:
 
1218
            that is, if the underlying block has had the entry removed, thus
 
1219
            shrinking in legnth.
 
1220
        """
 
1221
        # build up paths that this id will be left at after the change is made,
 
1222
        # so we can update their cross references in tree 0
 
1223
        all_remaining_keys = set()
 
1224
        # Dont check the working tree, because its going.
 
1225
        for details in current_old[1][1:]:
 
1226
            if details[0] not in ('absent', 'relocated'):
 
1227
                all_remaining_keys.add(current_old[0])
 
1228
            elif details[0] == 'relocated':
 
1229
                # record the key for the real path.
 
1230
                all_remaining_keys.add(tuple(os.path.split(details[1])) + (current_old[0][2],))
 
1231
            # absent rows are not present at any path.
 
1232
        last_reference = current_old[0] not in all_remaining_keys
 
1233
        if last_reference:
 
1234
            # the current row consists entire of the current item (being marked
 
1235
            # absent), and relocated or absent entries for the other trees:
 
1236
            # Remove it, its meaningless.
 
1237
            block = self._find_block(current_old[0])
 
1238
            entry_index, present = self._find_entry_index(current_old[0], block[1])
 
1239
            assert present
 
1240
            block[1].pop(entry_index)
 
1241
            # if we have an id_index in use, remove this key from it for this id.
 
1242
            if id_index is not None:
 
1243
                id_index[current_old[0][2]].remove(current_old[0])
 
1244
        # update all remaining keys for this id to record it as absent. The
 
1245
        # existing details may either be the record we are making as deleted
 
1246
        # (if there were other trees with the id present at this path), or may
 
1247
        # be relocations.
 
1248
        for update_key in all_remaining_keys:
 
1249
            update_block_index, present = \
 
1250
                self._find_block_index_from_key(update_key)
 
1251
            assert present
 
1252
            update_entry_index, present = \
 
1253
                self._find_entry_index(update_key, self._dirblocks[update_block_index][1])
 
1254
            assert present
 
1255
            update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
 
1256
            # it must not be absent at the moment
 
1257
            assert update_tree_details[0][0] != 'absent'
 
1258
            update_tree_details[0] = DirState.NULL_PARENT_DETAILS
 
1259
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1260
        return last_reference
 
1261
 
 
1262
    def update_minimal(self, key, kind, num_present_parents, executable=False,
 
1263
        fingerprint='', packed_stat=None, size=0, id_index=None,
 
1264
        path_utf8=None):
 
1265
        """Update an entry to the state in tree 0."""
 
1266
        block = self._find_block(key)[1]
 
1267
        if packed_stat is None:
 
1268
            packed_stat = DirState.NULLSTAT
 
1269
        entry_index, present = self._find_entry_index(key, block)
 
1270
        new_details = (kind, fingerprint, size, executable, packed_stat)
 
1271
        assert id_index is not None, 'need an id index to do updates for now !'
 
1272
        if not present:
 
1273
            # new entry, synthesis cross reference here,
 
1274
            existing_keys = id_index.setdefault(key[2], set())
 
1275
            if not existing_keys:
 
1276
                # not currently in the state, simplest case
 
1277
                new_entry = key, [new_details] + self._empty_parent_info()
 
1278
            else:
 
1279
                # present at one or more existing other paths.
 
1280
                # grab one of them and use it to generate parent
 
1281
                # relocation/absent entries.
 
1282
                new_entry = key, [new_details]
 
1283
                for other_key in existing_keys:
 
1284
                    # change the record at other to be a pointer to this new
 
1285
                    # record. The loop looks similar to the change to
 
1286
                    # relocations when updating an existing record but its not:
 
1287
                    # the test for existing kinds is different: this can be
 
1288
                    # factored out to a helper though.
 
1289
                    other_block_index, present = self._find_block_index_from_key(other_key)
 
1290
                    assert present
 
1291
                    other_entry_index, present = self._find_entry_index(other_key, self._dirblocks[other_block_index][1])
 
1292
                    assert present
 
1293
                    assert path_utf8 is not None
 
1294
                    self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
 
1295
                        ('relocated', path_utf8, 0, False, '')
 
1296
 
 
1297
                for lookup_index in xrange(1, num_present_parents + 1):
 
1298
                    # grab any one entry, use it to find the right path.
 
1299
                    # TODO: optimise this to reduce memory use in highly 
 
1300
                    # fragmented situations by reusing the relocation
 
1301
                    # records.
 
1302
                    update_block_index, present = \
 
1303
                        self._find_block_index_from_key(other_key)
 
1304
                    assert present
 
1305
                    update_entry_index, present = \
 
1306
                        self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
 
1307
                    assert present
 
1308
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
 
1309
                    if update_details[0] in ('relocated', 'absent'):
 
1310
                        # its a pointer or absent in lookup_index's tree, use
 
1311
                        # it as is.
 
1312
                        new_entry[1].append(update_details)
 
1313
                    else:
 
1314
                        # we have the right key, make a pointer to it.
 
1315
                        pointer_path = os.path.join(*other_key[0:2])
 
1316
                        new_entry[1].append(('relocated', pointer_path, 0, False, ''))
 
1317
            block.insert(entry_index, new_entry)
 
1318
            existing_keys.add(key)
 
1319
        else:
 
1320
            # Does the new state matter? 
 
1321
            block[entry_index][1][0] = new_details
 
1322
            # parents cannot be affected by what we do.
 
1323
            # other occurences of this id can be found 
 
1324
            # from the id index.
 
1325
            # ---
 
1326
            # tree index consistency: All other paths for this id in this tree
 
1327
            # index must point to the correct path. We have to loop here because
 
1328
            # we may have passed entries in the state with this file id already
 
1329
            # that were absent - where parent entries are - and they need to be
 
1330
            # converted to relocated.
 
1331
            assert path_utf8 is not None
 
1332
            for entry_key in id_index.setdefault(key[2], set()):
 
1333
                # TODO:PROFILING: It might be faster to just update
 
1334
                # rather than checking if we need to, and then overwrite
 
1335
                # the one we are located at.
 
1336
                if entry_key != key:
 
1337
                    # this file id is at a different path in one of the
 
1338
                    # other trees, so put absent pointers there
 
1339
                    # This is the vertical axis in the matrix, all pointing
 
1340
                    # to the real path.
 
1341
                    block_index, present = self._find_block_index_from_key(entry_key)
 
1342
                    assert present
 
1343
                    entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
 
1344
                    assert present
 
1345
                    self._dirblocks[block_index][1][entry_index][1][0] = \
 
1346
                        ('relocated', path_utf8, 0, False, '')
 
1347
        # add a containing dirblock if needed.
 
1348
        if new_details[0] == 'directory':
 
1349
            subdir_key = (os.path.join(*key[0:2]), '', '')
 
1350
            block_index, present = self._find_block_index_from_key(subdir_key)
 
1351
            if not present:
 
1352
                self._dirblocks.insert(block_index, (subdir_key[0], []))
 
1353
 
 
1354
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1355
 
 
1356
 
 
1357
 
 
1358
def pack_stat(st, _encode=base64.encodestring, _pack=struct.pack):
 
1359
    """Convert stat values into a packed representation."""
 
1360
    # jam 20060614 it isn't really worth removing more entries if we
 
1361
    # are going to leave it in packed form.
 
1362
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
 
1363
    # With all entries filesize is 5.9M and read time is mabye 280ms
 
1364
    # well within the noise margin
 
1365
 
 
1366
    # base64.encode always adds a final newline, so strip it off
 
1367
    return _encode(_pack('>llllll'
 
1368
        , st.st_size, st.st_mtime, st.st_ctime
 
1369
        , st.st_dev, st.st_ino, st.st_mode))[:-1]
 
1370