##// END OF EJS Templates
dirstate-entry: turn dirstate tuple into a real object (like in C)...
marmoute -
r48296:d4c79557 default
parent child Browse files
Show More
@@ -1,437 +1,456 b''
1 1 # parsers.py - Python implementation of parsers.c
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import struct
11 11 import zlib
12 12
13 13 from ..node import (
14 14 nullrev,
15 15 sha1nodeconstants,
16 16 )
17 17 from .. import (
18 18 error,
19 19 pycompat,
20 20 revlogutils,
21 21 util,
22 22 )
23 23
24 24 from ..revlogutils import nodemap as nodemaputil
25 25 from ..revlogutils import constants as revlog_constants
26 26
27 27 stringio = pycompat.bytesio
28 28
29 29
30 30 _pack = struct.pack
31 31 _unpack = struct.unpack
32 32 _compress = zlib.compress
33 33 _decompress = zlib.decompress
34 34
35 # Some code below makes tuples directly because it's more convenient. However,
36 # code outside this module should always use dirstatetuple.
37 def dirstatetuple(*x):
38 """the four items are:
35
36 class dirstatetuple(object):
37 """represent a dirstate entry
38
39 It contains:
40
39 41 - state (one of 'n', 'a', 'r', 'm')
40 42 - mode,
41 43 - size,
42 44 - mtime,
43 45 """
44 46
45 # x is a tuple
46 return x
47 __slot__ = ('_state', '_mode', '_size', '_mtime')
48
49 def __init__(self, state, mode, size, mtime):
50 self._state = state
51 self._mode = mode
52 self._size = size
53 self._mtime = mtime
54
55 def __getitem__(self, idx):
56 if idx == 0 or idx == -4:
57 return self._state
58 elif idx == 1 or idx == -3:
59 return self._mode
60 elif idx == 2 or idx == -2:
61 return self._size
62 elif idx == 3 or idx == -1:
63 return self._mtime
64 else:
65 raise IndexError(idx)
47 66
48 67
49 68 def gettype(q):
50 69 return int(q & 0xFFFF)
51 70
52 71
53 72 class BaseIndexObject(object):
54 73 # Can I be passed to an algorithme implemented in Rust ?
55 74 rust_ext_compat = 0
56 75 # Format of an index entry according to Python's `struct` language
57 76 index_format = revlog_constants.INDEX_ENTRY_V1
58 77 # Size of a C unsigned long long int, platform independent
59 78 big_int_size = struct.calcsize(b'>Q')
60 79 # Size of a C long int, platform independent
61 80 int_size = struct.calcsize(b'>i')
62 81 # An empty index entry, used as a default value to be overridden, or nullrev
63 82 null_item = (
64 83 0,
65 84 0,
66 85 0,
67 86 -1,
68 87 -1,
69 88 -1,
70 89 -1,
71 90 sha1nodeconstants.nullid,
72 91 0,
73 92 0,
74 93 revlog_constants.COMP_MODE_INLINE,
75 94 revlog_constants.COMP_MODE_INLINE,
76 95 )
77 96
78 97 @util.propertycache
79 98 def entry_size(self):
80 99 return self.index_format.size
81 100
82 101 @property
83 102 def nodemap(self):
84 103 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
85 104 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
86 105 return self._nodemap
87 106
88 107 @util.propertycache
89 108 def _nodemap(self):
90 109 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
91 110 for r in range(0, len(self)):
92 111 n = self[r][7]
93 112 nodemap[n] = r
94 113 return nodemap
95 114
96 115 def has_node(self, node):
97 116 """return True if the node exist in the index"""
98 117 return node in self._nodemap
99 118
100 119 def rev(self, node):
101 120 """return a revision for a node
102 121
103 122 If the node is unknown, raise a RevlogError"""
104 123 return self._nodemap[node]
105 124
106 125 def get_rev(self, node):
107 126 """return a revision for a node
108 127
109 128 If the node is unknown, return None"""
110 129 return self._nodemap.get(node)
111 130
112 131 def _stripnodes(self, start):
113 132 if '_nodemap' in vars(self):
114 133 for r in range(start, len(self)):
115 134 n = self[r][7]
116 135 del self._nodemap[n]
117 136
118 137 def clearcaches(self):
119 138 self.__dict__.pop('_nodemap', None)
120 139
121 140 def __len__(self):
122 141 return self._lgt + len(self._extra)
123 142
124 143 def append(self, tup):
125 144 if '_nodemap' in vars(self):
126 145 self._nodemap[tup[7]] = len(self)
127 146 data = self._pack_entry(len(self), tup)
128 147 self._extra.append(data)
129 148
130 149 def _pack_entry(self, rev, entry):
131 150 assert entry[8] == 0
132 151 assert entry[9] == 0
133 152 return self.index_format.pack(*entry[:8])
134 153
135 154 def _check_index(self, i):
136 155 if not isinstance(i, int):
137 156 raise TypeError(b"expecting int indexes")
138 157 if i < 0 or i >= len(self):
139 158 raise IndexError
140 159
141 160 def __getitem__(self, i):
142 161 if i == -1:
143 162 return self.null_item
144 163 self._check_index(i)
145 164 if i >= self._lgt:
146 165 data = self._extra[i - self._lgt]
147 166 else:
148 167 index = self._calculate_index(i)
149 168 data = self._data[index : index + self.entry_size]
150 169 r = self._unpack_entry(i, data)
151 170 if self._lgt and i == 0:
152 171 offset = revlogutils.offset_type(0, gettype(r[0]))
153 172 r = (offset,) + r[1:]
154 173 return r
155 174
156 175 def _unpack_entry(self, rev, data):
157 176 r = self.index_format.unpack(data)
158 177 r = r + (
159 178 0,
160 179 0,
161 180 revlog_constants.COMP_MODE_INLINE,
162 181 revlog_constants.COMP_MODE_INLINE,
163 182 )
164 183 return r
165 184
166 185 def pack_header(self, header):
167 186 """pack header information as binary"""
168 187 v_fmt = revlog_constants.INDEX_HEADER
169 188 return v_fmt.pack(header)
170 189
171 190 def entry_binary(self, rev):
172 191 """return the raw binary string representing a revision"""
173 192 entry = self[rev]
174 193 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
175 194 if rev == 0:
176 195 p = p[revlog_constants.INDEX_HEADER.size :]
177 196 return p
178 197
179 198
180 199 class IndexObject(BaseIndexObject):
181 200 def __init__(self, data):
182 201 assert len(data) % self.entry_size == 0, (
183 202 len(data),
184 203 self.entry_size,
185 204 len(data) % self.entry_size,
186 205 )
187 206 self._data = data
188 207 self._lgt = len(data) // self.entry_size
189 208 self._extra = []
190 209
191 210 def _calculate_index(self, i):
192 211 return i * self.entry_size
193 212
194 213 def __delitem__(self, i):
195 214 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
196 215 raise ValueError(b"deleting slices only supports a:-1 with step 1")
197 216 i = i.start
198 217 self._check_index(i)
199 218 self._stripnodes(i)
200 219 if i < self._lgt:
201 220 self._data = self._data[: i * self.entry_size]
202 221 self._lgt = i
203 222 self._extra = []
204 223 else:
205 224 self._extra = self._extra[: i - self._lgt]
206 225
207 226
208 227 class PersistentNodeMapIndexObject(IndexObject):
209 228 """a Debug oriented class to test persistent nodemap
210 229
211 230 We need a simple python object to test API and higher level behavior. See
212 231 the Rust implementation for more serious usage. This should be used only
213 232 through the dedicated `devel.persistent-nodemap` config.
214 233 """
215 234
216 235 def nodemap_data_all(self):
217 236 """Return bytes containing a full serialization of a nodemap
218 237
219 238 The nodemap should be valid for the full set of revisions in the
220 239 index."""
221 240 return nodemaputil.persistent_data(self)
222 241
223 242 def nodemap_data_incremental(self):
224 243 """Return bytes containing a incremental update to persistent nodemap
225 244
226 245 This containst the data for an append-only update of the data provided
227 246 in the last call to `update_nodemap_data`.
228 247 """
229 248 if self._nm_root is None:
230 249 return None
231 250 docket = self._nm_docket
232 251 changed, data = nodemaputil.update_persistent_data(
233 252 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
234 253 )
235 254
236 255 self._nm_root = self._nm_max_idx = self._nm_docket = None
237 256 return docket, changed, data
238 257
239 258 def update_nodemap_data(self, docket, nm_data):
240 259 """provide full block of persisted binary data for a nodemap
241 260
242 261 The data are expected to come from disk. See `nodemap_data_all` for a
243 262 produceur of such data."""
244 263 if nm_data is not None:
245 264 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
246 265 if self._nm_root:
247 266 self._nm_docket = docket
248 267 else:
249 268 self._nm_root = self._nm_max_idx = self._nm_docket = None
250 269
251 270
252 271 class InlinedIndexObject(BaseIndexObject):
253 272 def __init__(self, data, inline=0):
254 273 self._data = data
255 274 self._lgt = self._inline_scan(None)
256 275 self._inline_scan(self._lgt)
257 276 self._extra = []
258 277
259 278 def _inline_scan(self, lgt):
260 279 off = 0
261 280 if lgt is not None:
262 281 self._offsets = [0] * lgt
263 282 count = 0
264 283 while off <= len(self._data) - self.entry_size:
265 284 start = off + self.big_int_size
266 285 (s,) = struct.unpack(
267 286 b'>i',
268 287 self._data[start : start + self.int_size],
269 288 )
270 289 if lgt is not None:
271 290 self._offsets[count] = off
272 291 count += 1
273 292 off += self.entry_size + s
274 293 if off != len(self._data):
275 294 raise ValueError(b"corrupted data")
276 295 return count
277 296
278 297 def __delitem__(self, i):
279 298 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
280 299 raise ValueError(b"deleting slices only supports a:-1 with step 1")
281 300 i = i.start
282 301 self._check_index(i)
283 302 self._stripnodes(i)
284 303 if i < self._lgt:
285 304 self._offsets = self._offsets[:i]
286 305 self._lgt = i
287 306 self._extra = []
288 307 else:
289 308 self._extra = self._extra[: i - self._lgt]
290 309
291 310 def _calculate_index(self, i):
292 311 return self._offsets[i]
293 312
294 313
295 314 def parse_index2(data, inline, revlogv2=False):
296 315 if not inline:
297 316 cls = IndexObject2 if revlogv2 else IndexObject
298 317 return cls(data), None
299 318 cls = InlinedIndexObject
300 319 return cls(data, inline), (0, data)
301 320
302 321
303 322 def parse_index_cl_v2(data):
304 323 return IndexChangelogV2(data), None
305 324
306 325
307 326 class IndexObject2(IndexObject):
308 327 index_format = revlog_constants.INDEX_ENTRY_V2
309 328
310 329 def replace_sidedata_info(
311 330 self,
312 331 rev,
313 332 sidedata_offset,
314 333 sidedata_length,
315 334 offset_flags,
316 335 compression_mode,
317 336 ):
318 337 """
319 338 Replace an existing index entry's sidedata offset and length with new
320 339 ones.
321 340 This cannot be used outside of the context of sidedata rewriting,
322 341 inside the transaction that creates the revision `rev`.
323 342 """
324 343 if rev < 0:
325 344 raise KeyError
326 345 self._check_index(rev)
327 346 if rev < self._lgt:
328 347 msg = b"cannot rewrite entries outside of this transaction"
329 348 raise KeyError(msg)
330 349 else:
331 350 entry = list(self[rev])
332 351 entry[0] = offset_flags
333 352 entry[8] = sidedata_offset
334 353 entry[9] = sidedata_length
335 354 entry[11] = compression_mode
336 355 entry = tuple(entry)
337 356 new = self._pack_entry(rev, entry)
338 357 self._extra[rev - self._lgt] = new
339 358
340 359 def _unpack_entry(self, rev, data):
341 360 data = self.index_format.unpack(data)
342 361 entry = data[:10]
343 362 data_comp = data[10] & 3
344 363 sidedata_comp = (data[10] & (3 << 2)) >> 2
345 364 return entry + (data_comp, sidedata_comp)
346 365
347 366 def _pack_entry(self, rev, entry):
348 367 data = entry[:10]
349 368 data_comp = entry[10] & 3
350 369 sidedata_comp = (entry[11] & 3) << 2
351 370 data += (data_comp | sidedata_comp,)
352 371
353 372 return self.index_format.pack(*data)
354 373
355 374 def entry_binary(self, rev):
356 375 """return the raw binary string representing a revision"""
357 376 entry = self[rev]
358 377 return self._pack_entry(rev, entry)
359 378
360 379 def pack_header(self, header):
361 380 """pack header information as binary"""
362 381 msg = 'version header should go in the docket, not the index: %d'
363 382 msg %= header
364 383 raise error.ProgrammingError(msg)
365 384
366 385
367 386 class IndexChangelogV2(IndexObject2):
368 387 index_format = revlog_constants.INDEX_ENTRY_CL_V2
369 388
370 389 def _unpack_entry(self, rev, data, r=True):
371 390 items = self.index_format.unpack(data)
372 391 entry = items[:3] + (rev, rev) + items[3:8]
373 392 data_comp = items[8] & 3
374 393 sidedata_comp = (items[8] >> 2) & 3
375 394 return entry + (data_comp, sidedata_comp)
376 395
377 396 def _pack_entry(self, rev, entry):
378 397 assert entry[3] == rev, entry[3]
379 398 assert entry[4] == rev, entry[4]
380 399 data = entry[:3] + entry[5:10]
381 400 data_comp = entry[10] & 3
382 401 sidedata_comp = (entry[11] & 3) << 2
383 402 data += (data_comp | sidedata_comp,)
384 403 return self.index_format.pack(*data)
385 404
386 405
387 406 def parse_index_devel_nodemap(data, inline):
388 407 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
389 408 return PersistentNodeMapIndexObject(data), None
390 409
391 410
392 411 def parse_dirstate(dmap, copymap, st):
393 412 parents = [st[:20], st[20:40]]
394 413 # dereference fields so they will be local in loop
395 414 format = b">cllll"
396 415 e_size = struct.calcsize(format)
397 416 pos1 = 40
398 417 l = len(st)
399 418
400 419 # the inner loop
401 420 while pos1 < l:
402 421 pos2 = pos1 + e_size
403 422 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
404 423 pos1 = pos2 + e[4]
405 424 f = st[pos2:pos1]
406 425 if b'\0' in f:
407 426 f, c = f.split(b'\0')
408 427 copymap[f] = c
409 428 dmap[f] = e[:4]
410 429 return parents
411 430
412 431
413 432 def pack_dirstate(dmap, copymap, pl, now):
414 433 now = int(now)
415 434 cs = stringio()
416 435 write = cs.write
417 436 write(b"".join(pl))
418 437 for f, e in pycompat.iteritems(dmap):
419 438 if e[0] == b'n' and e[3] == now:
420 439 # The file was last modified "simultaneously" with the current
421 440 # write to dirstate (i.e. within the same second for file-
422 441 # systems with a granularity of 1 sec). This commonly happens
423 442 # for at least a couple of files on 'update'.
424 443 # The user could change the file without changing its size
425 444 # within the same second. Invalidate the file's mtime in
426 445 # dirstate, forcing future 'status' calls to compare the
427 446 # contents of the file if the size is the same. This prevents
428 447 # mistakenly treating such files as clean.
429 448 e = dirstatetuple(e[0], e[1], e[2], -1)
430 449 dmap[f] = e
431 450
432 451 if f in copymap:
433 452 f = b"%s\0%s" % (f, copymap[f])
434 453 e = _pack(b">cllll", e[0], e[1], e[2], e[3], len(f))
435 454 write(e)
436 455 write(f)
437 456 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now