##// END OF EJS Templates
dirstate-item: use an explicit __init__ function instead of the attrs one...
marmoute -
r48464:119b9c8d default
parent child Browse files
Show More
@@ -1,578 +1,584
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 ..thirdparty import attr
18 18 from .. import (
19 19 error,
20 20 pycompat,
21 21 revlogutils,
22 22 util,
23 23 )
24 24
25 25 from ..revlogutils import nodemap as nodemaputil
26 26 from ..revlogutils import constants as revlog_constants
27 27
28 28 stringio = pycompat.bytesio
29 29
30 30
31 31 _pack = struct.pack
32 32 _unpack = struct.unpack
33 33 _compress = zlib.compress
34 34 _decompress = zlib.decompress
35 35
36 36
37 37 # a special value used internally for `size` if the file come from the other parent
38 38 FROM_P2 = -2
39 39
40 40 # a special value used internally for `size` if the file is modified/merged/added
41 41 NONNORMAL = -1
42 42
43 43 # a special value used internally for `time` if the time is ambigeous
44 44 AMBIGUOUS_TIME = -1
45 45
46 46
47 @attr.s(slots=True)
47 @attr.s(slots=True, init=False)
48 48 class DirstateItem(object):
49 49 """represent a dirstate entry
50 50
51 51 It contains:
52 52
53 53 - state (one of 'n', 'a', 'r', 'm')
54 54 - mode,
55 55 - size,
56 56 - mtime,
57 57 """
58 58
59 59 _state = attr.ib()
60 60 _mode = attr.ib()
61 61 _size = attr.ib()
62 62 _mtime = attr.ib()
63 63
64 def __init__(self, state, mode, size, mtime):
65 self._state = state
66 self._mode = mode
67 self._size = size
68 self._mtime = mtime
69
64 70 def __getitem__(self, idx):
65 71 if idx == 0 or idx == -4:
66 72 msg = b"do not use item[x], use item.state"
67 73 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
68 74 return self._state
69 75 elif idx == 1 or idx == -3:
70 76 msg = b"do not use item[x], use item.mode"
71 77 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
72 78 return self._mode
73 79 elif idx == 2 or idx == -2:
74 80 msg = b"do not use item[x], use item.size"
75 81 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
76 82 return self._size
77 83 elif idx == 3 or idx == -1:
78 84 msg = b"do not use item[x], use item.mtime"
79 85 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
80 86 return self._mtime
81 87 else:
82 88 raise IndexError(idx)
83 89
84 90 @property
85 91 def mode(self):
86 92 return self._mode
87 93
88 94 @property
89 95 def size(self):
90 96 return self._size
91 97
92 98 @property
93 99 def mtime(self):
94 100 return self._mtime
95 101
96 102 @property
97 103 def state(self):
98 104 """
99 105 States are:
100 106 n normal
101 107 m needs merging
102 108 r marked for removal
103 109 a marked for addition
104 110
105 111 XXX This "state" is a bit obscure and mostly a direct expression of the
106 112 dirstatev1 format. It would make sense to ultimately deprecate it in
107 113 favor of the more "semantic" attributes.
108 114 """
109 115 return self._state
110 116
111 117 @property
112 118 def tracked(self):
113 119 """True is the file is tracked in the working copy"""
114 120 return self._state in b"nma"
115 121
116 122 @property
117 123 def added(self):
118 124 """True if the file has been added"""
119 125 return self._state == b'a'
120 126
121 127 @property
122 128 def merged(self):
123 129 """True if the file has been merged
124 130
125 131 Should only be set if a merge is in progress in the dirstate
126 132 """
127 133 return self._state == b'm'
128 134
129 135 @property
130 136 def from_p2(self):
131 137 """True if the file have been fetched from p2 during the current merge
132 138
133 139 This is only True is the file is currently tracked.
134 140
135 141 Should only be set if a merge is in progress in the dirstate
136 142 """
137 143 return self._state == b'n' and self._size == FROM_P2
138 144
139 145 @property
140 146 def from_p2_removed(self):
141 147 """True if the file has been removed, but was "from_p2" initially
142 148
143 149 This property seems like an abstraction leakage and should probably be
144 150 dealt in this class (or maybe the dirstatemap) directly.
145 151 """
146 152 return self._state == b'r' and self._size == FROM_P2
147 153
148 154 @property
149 155 def removed(self):
150 156 """True if the file has been removed"""
151 157 return self._state == b'r'
152 158
153 159 @property
154 160 def merged_removed(self):
155 161 """True if the file has been removed, but was "merged" initially
156 162
157 163 This property seems like an abstraction leakage and should probably be
158 164 dealt in this class (or maybe the dirstatemap) directly.
159 165 """
160 166 return self._state == b'r' and self._size == NONNORMAL
161 167
162 168 def v1_state(self):
163 169 """return a "state" suitable for v1 serialization"""
164 170 return self._state
165 171
166 172 def v1_mode(self):
167 173 """return a "mode" suitable for v1 serialization"""
168 174 return self._mode
169 175
170 176 def v1_size(self):
171 177 """return a "size" suitable for v1 serialization"""
172 178 return self._size
173 179
174 180 def v1_mtime(self):
175 181 """return a "mtime" suitable for v1 serialization"""
176 182 return self._mtime
177 183
178 184 def need_delay(self, now):
179 185 """True if the stored mtime would be ambiguous with the current time"""
180 186 return self._state == b'n' and self._mtime == now
181 187
182 188
183 189 def gettype(q):
184 190 return int(q & 0xFFFF)
185 191
186 192
187 193 class BaseIndexObject(object):
188 194 # Can I be passed to an algorithme implemented in Rust ?
189 195 rust_ext_compat = 0
190 196 # Format of an index entry according to Python's `struct` language
191 197 index_format = revlog_constants.INDEX_ENTRY_V1
192 198 # Size of a C unsigned long long int, platform independent
193 199 big_int_size = struct.calcsize(b'>Q')
194 200 # Size of a C long int, platform independent
195 201 int_size = struct.calcsize(b'>i')
196 202 # An empty index entry, used as a default value to be overridden, or nullrev
197 203 null_item = (
198 204 0,
199 205 0,
200 206 0,
201 207 -1,
202 208 -1,
203 209 -1,
204 210 -1,
205 211 sha1nodeconstants.nullid,
206 212 0,
207 213 0,
208 214 revlog_constants.COMP_MODE_INLINE,
209 215 revlog_constants.COMP_MODE_INLINE,
210 216 )
211 217
212 218 @util.propertycache
213 219 def entry_size(self):
214 220 return self.index_format.size
215 221
216 222 @property
217 223 def nodemap(self):
218 224 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
219 225 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
220 226 return self._nodemap
221 227
222 228 @util.propertycache
223 229 def _nodemap(self):
224 230 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
225 231 for r in range(0, len(self)):
226 232 n = self[r][7]
227 233 nodemap[n] = r
228 234 return nodemap
229 235
230 236 def has_node(self, node):
231 237 """return True if the node exist in the index"""
232 238 return node in self._nodemap
233 239
234 240 def rev(self, node):
235 241 """return a revision for a node
236 242
237 243 If the node is unknown, raise a RevlogError"""
238 244 return self._nodemap[node]
239 245
240 246 def get_rev(self, node):
241 247 """return a revision for a node
242 248
243 249 If the node is unknown, return None"""
244 250 return self._nodemap.get(node)
245 251
246 252 def _stripnodes(self, start):
247 253 if '_nodemap' in vars(self):
248 254 for r in range(start, len(self)):
249 255 n = self[r][7]
250 256 del self._nodemap[n]
251 257
252 258 def clearcaches(self):
253 259 self.__dict__.pop('_nodemap', None)
254 260
255 261 def __len__(self):
256 262 return self._lgt + len(self._extra)
257 263
258 264 def append(self, tup):
259 265 if '_nodemap' in vars(self):
260 266 self._nodemap[tup[7]] = len(self)
261 267 data = self._pack_entry(len(self), tup)
262 268 self._extra.append(data)
263 269
264 270 def _pack_entry(self, rev, entry):
265 271 assert entry[8] == 0
266 272 assert entry[9] == 0
267 273 return self.index_format.pack(*entry[:8])
268 274
269 275 def _check_index(self, i):
270 276 if not isinstance(i, int):
271 277 raise TypeError(b"expecting int indexes")
272 278 if i < 0 or i >= len(self):
273 279 raise IndexError
274 280
275 281 def __getitem__(self, i):
276 282 if i == -1:
277 283 return self.null_item
278 284 self._check_index(i)
279 285 if i >= self._lgt:
280 286 data = self._extra[i - self._lgt]
281 287 else:
282 288 index = self._calculate_index(i)
283 289 data = self._data[index : index + self.entry_size]
284 290 r = self._unpack_entry(i, data)
285 291 if self._lgt and i == 0:
286 292 offset = revlogutils.offset_type(0, gettype(r[0]))
287 293 r = (offset,) + r[1:]
288 294 return r
289 295
290 296 def _unpack_entry(self, rev, data):
291 297 r = self.index_format.unpack(data)
292 298 r = r + (
293 299 0,
294 300 0,
295 301 revlog_constants.COMP_MODE_INLINE,
296 302 revlog_constants.COMP_MODE_INLINE,
297 303 )
298 304 return r
299 305
300 306 def pack_header(self, header):
301 307 """pack header information as binary"""
302 308 v_fmt = revlog_constants.INDEX_HEADER
303 309 return v_fmt.pack(header)
304 310
305 311 def entry_binary(self, rev):
306 312 """return the raw binary string representing a revision"""
307 313 entry = self[rev]
308 314 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
309 315 if rev == 0:
310 316 p = p[revlog_constants.INDEX_HEADER.size :]
311 317 return p
312 318
313 319
314 320 class IndexObject(BaseIndexObject):
315 321 def __init__(self, data):
316 322 assert len(data) % self.entry_size == 0, (
317 323 len(data),
318 324 self.entry_size,
319 325 len(data) % self.entry_size,
320 326 )
321 327 self._data = data
322 328 self._lgt = len(data) // self.entry_size
323 329 self._extra = []
324 330
325 331 def _calculate_index(self, i):
326 332 return i * self.entry_size
327 333
328 334 def __delitem__(self, i):
329 335 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
330 336 raise ValueError(b"deleting slices only supports a:-1 with step 1")
331 337 i = i.start
332 338 self._check_index(i)
333 339 self._stripnodes(i)
334 340 if i < self._lgt:
335 341 self._data = self._data[: i * self.entry_size]
336 342 self._lgt = i
337 343 self._extra = []
338 344 else:
339 345 self._extra = self._extra[: i - self._lgt]
340 346
341 347
342 348 class PersistentNodeMapIndexObject(IndexObject):
343 349 """a Debug oriented class to test persistent nodemap
344 350
345 351 We need a simple python object to test API and higher level behavior. See
346 352 the Rust implementation for more serious usage. This should be used only
347 353 through the dedicated `devel.persistent-nodemap` config.
348 354 """
349 355
350 356 def nodemap_data_all(self):
351 357 """Return bytes containing a full serialization of a nodemap
352 358
353 359 The nodemap should be valid for the full set of revisions in the
354 360 index."""
355 361 return nodemaputil.persistent_data(self)
356 362
357 363 def nodemap_data_incremental(self):
358 364 """Return bytes containing a incremental update to persistent nodemap
359 365
360 366 This containst the data for an append-only update of the data provided
361 367 in the last call to `update_nodemap_data`.
362 368 """
363 369 if self._nm_root is None:
364 370 return None
365 371 docket = self._nm_docket
366 372 changed, data = nodemaputil.update_persistent_data(
367 373 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
368 374 )
369 375
370 376 self._nm_root = self._nm_max_idx = self._nm_docket = None
371 377 return docket, changed, data
372 378
373 379 def update_nodemap_data(self, docket, nm_data):
374 380 """provide full block of persisted binary data for a nodemap
375 381
376 382 The data are expected to come from disk. See `nodemap_data_all` for a
377 383 produceur of such data."""
378 384 if nm_data is not None:
379 385 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
380 386 if self._nm_root:
381 387 self._nm_docket = docket
382 388 else:
383 389 self._nm_root = self._nm_max_idx = self._nm_docket = None
384 390
385 391
386 392 class InlinedIndexObject(BaseIndexObject):
387 393 def __init__(self, data, inline=0):
388 394 self._data = data
389 395 self._lgt = self._inline_scan(None)
390 396 self._inline_scan(self._lgt)
391 397 self._extra = []
392 398
393 399 def _inline_scan(self, lgt):
394 400 off = 0
395 401 if lgt is not None:
396 402 self._offsets = [0] * lgt
397 403 count = 0
398 404 while off <= len(self._data) - self.entry_size:
399 405 start = off + self.big_int_size
400 406 (s,) = struct.unpack(
401 407 b'>i',
402 408 self._data[start : start + self.int_size],
403 409 )
404 410 if lgt is not None:
405 411 self._offsets[count] = off
406 412 count += 1
407 413 off += self.entry_size + s
408 414 if off != len(self._data):
409 415 raise ValueError(b"corrupted data")
410 416 return count
411 417
412 418 def __delitem__(self, i):
413 419 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
414 420 raise ValueError(b"deleting slices only supports a:-1 with step 1")
415 421 i = i.start
416 422 self._check_index(i)
417 423 self._stripnodes(i)
418 424 if i < self._lgt:
419 425 self._offsets = self._offsets[:i]
420 426 self._lgt = i
421 427 self._extra = []
422 428 else:
423 429 self._extra = self._extra[: i - self._lgt]
424 430
425 431 def _calculate_index(self, i):
426 432 return self._offsets[i]
427 433
428 434
429 435 def parse_index2(data, inline, revlogv2=False):
430 436 if not inline:
431 437 cls = IndexObject2 if revlogv2 else IndexObject
432 438 return cls(data), None
433 439 cls = InlinedIndexObject
434 440 return cls(data, inline), (0, data)
435 441
436 442
437 443 def parse_index_cl_v2(data):
438 444 return IndexChangelogV2(data), None
439 445
440 446
441 447 class IndexObject2(IndexObject):
442 448 index_format = revlog_constants.INDEX_ENTRY_V2
443 449
444 450 def replace_sidedata_info(
445 451 self,
446 452 rev,
447 453 sidedata_offset,
448 454 sidedata_length,
449 455 offset_flags,
450 456 compression_mode,
451 457 ):
452 458 """
453 459 Replace an existing index entry's sidedata offset and length with new
454 460 ones.
455 461 This cannot be used outside of the context of sidedata rewriting,
456 462 inside the transaction that creates the revision `rev`.
457 463 """
458 464 if rev < 0:
459 465 raise KeyError
460 466 self._check_index(rev)
461 467 if rev < self._lgt:
462 468 msg = b"cannot rewrite entries outside of this transaction"
463 469 raise KeyError(msg)
464 470 else:
465 471 entry = list(self[rev])
466 472 entry[0] = offset_flags
467 473 entry[8] = sidedata_offset
468 474 entry[9] = sidedata_length
469 475 entry[11] = compression_mode
470 476 entry = tuple(entry)
471 477 new = self._pack_entry(rev, entry)
472 478 self._extra[rev - self._lgt] = new
473 479
474 480 def _unpack_entry(self, rev, data):
475 481 data = self.index_format.unpack(data)
476 482 entry = data[:10]
477 483 data_comp = data[10] & 3
478 484 sidedata_comp = (data[10] & (3 << 2)) >> 2
479 485 return entry + (data_comp, sidedata_comp)
480 486
481 487 def _pack_entry(self, rev, entry):
482 488 data = entry[:10]
483 489 data_comp = entry[10] & 3
484 490 sidedata_comp = (entry[11] & 3) << 2
485 491 data += (data_comp | sidedata_comp,)
486 492
487 493 return self.index_format.pack(*data)
488 494
489 495 def entry_binary(self, rev):
490 496 """return the raw binary string representing a revision"""
491 497 entry = self[rev]
492 498 return self._pack_entry(rev, entry)
493 499
494 500 def pack_header(self, header):
495 501 """pack header information as binary"""
496 502 msg = 'version header should go in the docket, not the index: %d'
497 503 msg %= header
498 504 raise error.ProgrammingError(msg)
499 505
500 506
501 507 class IndexChangelogV2(IndexObject2):
502 508 index_format = revlog_constants.INDEX_ENTRY_CL_V2
503 509
504 510 def _unpack_entry(self, rev, data, r=True):
505 511 items = self.index_format.unpack(data)
506 512 entry = items[:3] + (rev, rev) + items[3:8]
507 513 data_comp = items[8] & 3
508 514 sidedata_comp = (items[8] >> 2) & 3
509 515 return entry + (data_comp, sidedata_comp)
510 516
511 517 def _pack_entry(self, rev, entry):
512 518 assert entry[3] == rev, entry[3]
513 519 assert entry[4] == rev, entry[4]
514 520 data = entry[:3] + entry[5:10]
515 521 data_comp = entry[10] & 3
516 522 sidedata_comp = (entry[11] & 3) << 2
517 523 data += (data_comp | sidedata_comp,)
518 524 return self.index_format.pack(*data)
519 525
520 526
521 527 def parse_index_devel_nodemap(data, inline):
522 528 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
523 529 return PersistentNodeMapIndexObject(data), None
524 530
525 531
526 532 def parse_dirstate(dmap, copymap, st):
527 533 parents = [st[:20], st[20:40]]
528 534 # dereference fields so they will be local in loop
529 535 format = b">cllll"
530 536 e_size = struct.calcsize(format)
531 537 pos1 = 40
532 538 l = len(st)
533 539
534 540 # the inner loop
535 541 while pos1 < l:
536 542 pos2 = pos1 + e_size
537 543 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
538 544 pos1 = pos2 + e[4]
539 545 f = st[pos2:pos1]
540 546 if b'\0' in f:
541 547 f, c = f.split(b'\0')
542 548 copymap[f] = c
543 549 dmap[f] = DirstateItem(*e[:4])
544 550 return parents
545 551
546 552
547 553 def pack_dirstate(dmap, copymap, pl, now):
548 554 now = int(now)
549 555 cs = stringio()
550 556 write = cs.write
551 557 write(b"".join(pl))
552 558 for f, e in pycompat.iteritems(dmap):
553 559 if e.need_delay(now):
554 560 # The file was last modified "simultaneously" with the current
555 561 # write to dirstate (i.e. within the same second for file-
556 562 # systems with a granularity of 1 sec). This commonly happens
557 563 # for at least a couple of files on 'update'.
558 564 # The user could change the file without changing its size
559 565 # within the same second. Invalidate the file's mtime in
560 566 # dirstate, forcing future 'status' calls to compare the
561 567 # contents of the file if the size is the same. This prevents
562 568 # mistakenly treating such files as clean.
563 569 e = DirstateItem(e.state, e.mode, e.size, AMBIGUOUS_TIME)
564 570 dmap[f] = e
565 571
566 572 if f in copymap:
567 573 f = b"%s\0%s" % (f, copymap[f])
568 574 e = _pack(
569 575 b">cllll",
570 576 e.v1_state(),
571 577 e.v1_mode(),
572 578 e.v1_size(),
573 579 e.v1_mtime(),
574 580 len(f),
575 581 )
576 582 write(e)
577 583 write(f)
578 584 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now