##// END OF EJS Templates
dirstate-item: add missing bit of docstring...
Raphaël Gomès -
r50010:4c75f00b default
parent child Browse files
Show More
@@ -1,974 +1,974 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
9 9 import io
10 10 import stat
11 11 import struct
12 12 import zlib
13 13
14 14 from ..node import (
15 15 nullrev,
16 16 sha1nodeconstants,
17 17 )
18 18 from ..thirdparty import attr
19 19 from .. import (
20 20 error,
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 = io.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 # Bits of the `flags` byte inside a node in the file format
47 47 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
48 48 DIRSTATE_V2_P1_TRACKED = 1 << 1
49 49 DIRSTATE_V2_P2_INFO = 1 << 2
50 50 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 3
51 51 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 4
52 52 DIRSTATE_V2_HAS_FALLBACK_EXEC = 1 << 5
53 53 DIRSTATE_V2_FALLBACK_EXEC = 1 << 6
54 54 DIRSTATE_V2_HAS_FALLBACK_SYMLINK = 1 << 7
55 55 DIRSTATE_V2_FALLBACK_SYMLINK = 1 << 8
56 56 DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 9
57 57 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 10
58 58 DIRSTATE_V2_HAS_MTIME = 1 << 11
59 59 DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS = 1 << 12
60 60 DIRSTATE_V2_DIRECTORY = 1 << 13
61 61 DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 14
62 62 DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 15
63 63
64 64
65 65 @attr.s(slots=True, init=False)
66 66 class DirstateItem:
67 67 """represent a dirstate entry
68 68
69 69 It hold multiple attributes
70 70
71 71 # about file tracking
72 72 - wc_tracked: is the file tracked by the working copy
73 73 - p1_tracked: is the file tracked in working copy first parent
74 74 - p2_info: the file has been involved in some merge operation. Either
75 75 because it was actually merged, or because the p2 version was
76 76 ahead, or because some rename moved it there. In either case
77 77 `hg status` will want it displayed as modified.
78 78
79 79 # about the file state expected from p1 manifest:
80 80 - mode: the file mode in p1
81 81 - size: the file size in p1
82 82
83 83 These value can be set to None, which mean we don't have a meaningful value
84 84 to compare with. Either because we don't really care about them as there
85 85 `status` is known without having to look at the disk or because we don't
86 86 know these right now and a full comparison will be needed to find out if
87 87 the file is clean.
88 88
89 89 # about the file state on disk last time we saw it:
90 90 - mtime: the last known clean mtime for the file.
91 91
92 92 This value can be set to None if no cachable state exist. Either because we
93 93 do not care (see previous section) or because we could not cache something
94 94 yet.
95 95 """
96 96
97 97 _wc_tracked = attr.ib()
98 98 _p1_tracked = attr.ib()
99 99 _p2_info = attr.ib()
100 100 _mode = attr.ib()
101 101 _size = attr.ib()
102 102 _mtime_s = attr.ib()
103 103 _mtime_ns = attr.ib()
104 104 _fallback_exec = attr.ib()
105 105 _fallback_symlink = attr.ib()
106 106 _mtime_second_ambiguous = attr.ib()
107 107
108 108 def __init__(
109 109 self,
110 110 wc_tracked=False,
111 111 p1_tracked=False,
112 112 p2_info=False,
113 113 has_meaningful_data=True,
114 114 has_meaningful_mtime=True,
115 115 parentfiledata=None,
116 116 fallback_exec=None,
117 117 fallback_symlink=None,
118 118 ):
119 119 self._wc_tracked = wc_tracked
120 120 self._p1_tracked = p1_tracked
121 121 self._p2_info = p2_info
122 122
123 123 self._fallback_exec = fallback_exec
124 124 self._fallback_symlink = fallback_symlink
125 125
126 126 self._mode = None
127 127 self._size = None
128 128 self._mtime_s = None
129 129 self._mtime_ns = None
130 130 self._mtime_second_ambiguous = False
131 131 if parentfiledata is None:
132 132 has_meaningful_mtime = False
133 133 has_meaningful_data = False
134 134 elif parentfiledata[2] is None:
135 135 has_meaningful_mtime = False
136 136 if has_meaningful_data:
137 137 self._mode = parentfiledata[0]
138 138 self._size = parentfiledata[1]
139 139 if has_meaningful_mtime:
140 140 (
141 141 self._mtime_s,
142 142 self._mtime_ns,
143 143 self._mtime_second_ambiguous,
144 144 ) = parentfiledata[2]
145 145
146 146 @classmethod
147 147 def from_v2_data(cls, flags, size, mtime_s, mtime_ns):
148 148 """Build a new DirstateItem object from V2 data"""
149 149 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
150 150 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME)
151 151 mode = None
152 152
153 153 if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED:
154 154 # we do not have support for this flag in the code yet,
155 155 # force a lookup for this file.
156 156 has_mode_size = False
157 157 has_meaningful_mtime = False
158 158
159 159 fallback_exec = None
160 160 if flags & DIRSTATE_V2_HAS_FALLBACK_EXEC:
161 161 fallback_exec = flags & DIRSTATE_V2_FALLBACK_EXEC
162 162
163 163 fallback_symlink = None
164 164 if flags & DIRSTATE_V2_HAS_FALLBACK_SYMLINK:
165 165 fallback_symlink = flags & DIRSTATE_V2_FALLBACK_SYMLINK
166 166
167 167 if has_mode_size:
168 168 assert stat.S_IXUSR == 0o100
169 169 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
170 170 mode = 0o755
171 171 else:
172 172 mode = 0o644
173 173 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
174 174 mode |= stat.S_IFLNK
175 175 else:
176 176 mode |= stat.S_IFREG
177 177
178 178 second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
179 179 return cls(
180 180 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
181 181 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
182 182 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
183 183 has_meaningful_data=has_mode_size,
184 184 has_meaningful_mtime=has_meaningful_mtime,
185 185 parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)),
186 186 fallback_exec=fallback_exec,
187 187 fallback_symlink=fallback_symlink,
188 188 )
189 189
190 190 @classmethod
191 191 def from_v1_data(cls, state, mode, size, mtime):
192 192 """Build a new DirstateItem object from V1 data
193 193
194 194 Since the dirstate-v1 format is frozen, the signature of this function
195 195 is not expected to change, unlike the __init__ one.
196 196 """
197 197 if state == b'm':
198 198 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
199 199 elif state == b'a':
200 200 return cls(wc_tracked=True)
201 201 elif state == b'r':
202 202 if size == NONNORMAL:
203 203 p1_tracked = True
204 204 p2_info = True
205 205 elif size == FROM_P2:
206 206 p1_tracked = False
207 207 p2_info = True
208 208 else:
209 209 p1_tracked = True
210 210 p2_info = False
211 211 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
212 212 elif state == b'n':
213 213 if size == FROM_P2:
214 214 return cls(wc_tracked=True, p2_info=True)
215 215 elif size == NONNORMAL:
216 216 return cls(wc_tracked=True, p1_tracked=True)
217 217 elif mtime == AMBIGUOUS_TIME:
218 218 return cls(
219 219 wc_tracked=True,
220 220 p1_tracked=True,
221 221 has_meaningful_mtime=False,
222 222 parentfiledata=(mode, size, (42, 0, False)),
223 223 )
224 224 else:
225 225 return cls(
226 226 wc_tracked=True,
227 227 p1_tracked=True,
228 228 parentfiledata=(mode, size, (mtime, 0, False)),
229 229 )
230 230 else:
231 231 raise RuntimeError(b'unknown state: %s' % state)
232 232
233 233 def set_possibly_dirty(self):
234 234 """Mark a file as "possibly dirty"
235 235
236 236 This means the next status call will have to actually check its content
237 237 to make sure it is correct.
238 238 """
239 239 self._mtime_s = None
240 240 self._mtime_ns = None
241 241
242 242 def set_clean(self, mode, size, mtime):
243 243 """mark a file as "clean" cancelling potential "possibly dirty call"
244 244
245 245 Note: this function is a descendant of `dirstate.normal` and is
246 246 currently expected to be call on "normal" entry only. There are not
247 247 reason for this to not change in the future as long as the ccode is
248 248 updated to preserve the proper state of the non-normal files.
249 249 """
250 250 self._wc_tracked = True
251 251 self._p1_tracked = True
252 252 self._mode = mode
253 253 self._size = size
254 254 self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime
255 255
256 256 def set_tracked(self):
257 257 """mark a file as tracked in the working copy
258 258
259 259 This will ultimately be called by command like `hg add`.
260 260 """
261 261 self._wc_tracked = True
262 262 # `set_tracked` is replacing various `normallookup` call. So we mark
263 263 # the files as needing lookup
264 264 #
265 265 # Consider dropping this in the future in favor of something less broad.
266 266 self._mtime_s = None
267 267 self._mtime_ns = None
268 268
269 269 def set_untracked(self):
270 270 """mark a file as untracked in the working copy
271 271
272 272 This will ultimately be called by command like `hg remove`.
273 273 """
274 274 self._wc_tracked = False
275 275 self._mode = None
276 276 self._size = None
277 277 self._mtime_s = None
278 278 self._mtime_ns = None
279 279
280 280 def drop_merge_data(self):
281 """remove all "merge-only" from a DirstateItem
281 """remove all "merge-only" information from a DirstateItem
282 282
283 283 This is to be call by the dirstatemap code when the second parent is dropped
284 284 """
285 285 if self._p2_info:
286 286 self._p2_info = False
287 287 self._mode = None
288 288 self._size = None
289 289 self._mtime_s = None
290 290 self._mtime_ns = None
291 291
292 292 @property
293 293 def mode(self):
294 294 return self._v1_mode()
295 295
296 296 @property
297 297 def size(self):
298 298 return self._v1_size()
299 299
300 300 @property
301 301 def mtime(self):
302 302 return self._v1_mtime()
303 303
304 304 def mtime_likely_equal_to(self, other_mtime):
305 305 self_sec = self._mtime_s
306 306 if self_sec is None:
307 307 return False
308 308 self_ns = self._mtime_ns
309 309 other_sec, other_ns, second_ambiguous = other_mtime
310 310 if self_sec != other_sec:
311 311 # seconds are different theses mtime are definitly not equal
312 312 return False
313 313 elif other_ns == 0 or self_ns == 0:
314 314 # at least one side as no nano-seconds information
315 315
316 316 if self._mtime_second_ambiguous:
317 317 # We cannot trust the mtime in this case
318 318 return False
319 319 else:
320 320 # the "seconds" value was reliable on its own. We are good to go.
321 321 return True
322 322 else:
323 323 # We have nano second information, let us use them !
324 324 return self_ns == other_ns
325 325
326 326 @property
327 327 def state(self):
328 328 """
329 329 States are:
330 330 n normal
331 331 m needs merging
332 332 r marked for removal
333 333 a marked for addition
334 334
335 335 XXX This "state" is a bit obscure and mostly a direct expression of the
336 336 dirstatev1 format. It would make sense to ultimately deprecate it in
337 337 favor of the more "semantic" attributes.
338 338 """
339 339 if not self.any_tracked:
340 340 return b'?'
341 341 return self._v1_state()
342 342
343 343 @property
344 344 def has_fallback_exec(self):
345 345 """True if "fallback" information are available for the "exec" bit
346 346
347 347 Fallback information can be stored in the dirstate to keep track of
348 348 filesystem attribute tracked by Mercurial when the underlying file
349 349 system or operating system does not support that property, (e.g.
350 350 Windows).
351 351
352 352 Not all version of the dirstate on-disk storage support preserving this
353 353 information.
354 354 """
355 355 return self._fallback_exec is not None
356 356
357 357 @property
358 358 def fallback_exec(self):
359 359 """ "fallback" information for the executable bit
360 360
361 361 True if the file should be considered executable when we cannot get
362 362 this information from the files system. False if it should be
363 363 considered non-executable.
364 364
365 365 See has_fallback_exec for details."""
366 366 return self._fallback_exec
367 367
368 368 @fallback_exec.setter
369 369 def set_fallback_exec(self, value):
370 370 """control "fallback" executable bit
371 371
372 372 Set to:
373 373 - True if the file should be considered executable,
374 374 - False if the file should be considered non-executable,
375 375 - None if we do not have valid fallback data.
376 376
377 377 See has_fallback_exec for details."""
378 378 if value is None:
379 379 self._fallback_exec = None
380 380 else:
381 381 self._fallback_exec = bool(value)
382 382
383 383 @property
384 384 def has_fallback_symlink(self):
385 385 """True if "fallback" information are available for symlink status
386 386
387 387 Fallback information can be stored in the dirstate to keep track of
388 388 filesystem attribute tracked by Mercurial when the underlying file
389 389 system or operating system does not support that property, (e.g.
390 390 Windows).
391 391
392 392 Not all version of the dirstate on-disk storage support preserving this
393 393 information."""
394 394 return self._fallback_symlink is not None
395 395
396 396 @property
397 397 def fallback_symlink(self):
398 398 """ "fallback" information for symlink status
399 399
400 400 True if the file should be considered executable when we cannot get
401 401 this information from the files system. False if it should be
402 402 considered non-executable.
403 403
404 404 See has_fallback_exec for details."""
405 405 return self._fallback_symlink
406 406
407 407 @fallback_symlink.setter
408 408 def set_fallback_symlink(self, value):
409 409 """control "fallback" symlink status
410 410
411 411 Set to:
412 412 - True if the file should be considered a symlink,
413 413 - False if the file should be considered not a symlink,
414 414 - None if we do not have valid fallback data.
415 415
416 416 See has_fallback_symlink for details."""
417 417 if value is None:
418 418 self._fallback_symlink = None
419 419 else:
420 420 self._fallback_symlink = bool(value)
421 421
422 422 @property
423 423 def tracked(self):
424 424 """True is the file is tracked in the working copy"""
425 425 return self._wc_tracked
426 426
427 427 @property
428 428 def any_tracked(self):
429 429 """True is the file is tracked anywhere (wc or parents)"""
430 430 return self._wc_tracked or self._p1_tracked or self._p2_info
431 431
432 432 @property
433 433 def added(self):
434 434 """True if the file has been added"""
435 435 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
436 436
437 437 @property
438 438 def maybe_clean(self):
439 439 """True if the file has a chance to be in the "clean" state"""
440 440 if not self._wc_tracked:
441 441 return False
442 442 elif not self._p1_tracked:
443 443 return False
444 444 elif self._p2_info:
445 445 return False
446 446 return True
447 447
448 448 @property
449 449 def p1_tracked(self):
450 450 """True if the file is tracked in the first parent manifest"""
451 451 return self._p1_tracked
452 452
453 453 @property
454 454 def p2_info(self):
455 455 """True if the file needed to merge or apply any input from p2
456 456
457 457 See the class documentation for details.
458 458 """
459 459 return self._wc_tracked and self._p2_info
460 460
461 461 @property
462 462 def removed(self):
463 463 """True if the file has been removed"""
464 464 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
465 465
466 466 def v2_data(self):
467 467 """Returns (flags, mode, size, mtime) for v2 serialization"""
468 468 flags = 0
469 469 if self._wc_tracked:
470 470 flags |= DIRSTATE_V2_WDIR_TRACKED
471 471 if self._p1_tracked:
472 472 flags |= DIRSTATE_V2_P1_TRACKED
473 473 if self._p2_info:
474 474 flags |= DIRSTATE_V2_P2_INFO
475 475 if self._mode is not None and self._size is not None:
476 476 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
477 477 if self.mode & stat.S_IXUSR:
478 478 flags |= DIRSTATE_V2_MODE_EXEC_PERM
479 479 if stat.S_ISLNK(self.mode):
480 480 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
481 481 if self._mtime_s is not None:
482 482 flags |= DIRSTATE_V2_HAS_MTIME
483 483 if self._mtime_second_ambiguous:
484 484 flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
485 485
486 486 if self._fallback_exec is not None:
487 487 flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC
488 488 if self._fallback_exec:
489 489 flags |= DIRSTATE_V2_FALLBACK_EXEC
490 490
491 491 if self._fallback_symlink is not None:
492 492 flags |= DIRSTATE_V2_HAS_FALLBACK_SYMLINK
493 493 if self._fallback_symlink:
494 494 flags |= DIRSTATE_V2_FALLBACK_SYMLINK
495 495
496 496 # Note: we do not need to do anything regarding
497 497 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
498 498 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
499 499 return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0)
500 500
501 501 def _v1_state(self):
502 502 """return a "state" suitable for v1 serialization"""
503 503 if not self.any_tracked:
504 504 # the object has no state to record, this is -currently-
505 505 # unsupported
506 506 raise RuntimeError('untracked item')
507 507 elif self.removed:
508 508 return b'r'
509 509 elif self._p1_tracked and self._p2_info:
510 510 return b'm'
511 511 elif self.added:
512 512 return b'a'
513 513 else:
514 514 return b'n'
515 515
516 516 def _v1_mode(self):
517 517 """return a "mode" suitable for v1 serialization"""
518 518 return self._mode if self._mode is not None else 0
519 519
520 520 def _v1_size(self):
521 521 """return a "size" suitable for v1 serialization"""
522 522 if not self.any_tracked:
523 523 # the object has no state to record, this is -currently-
524 524 # unsupported
525 525 raise RuntimeError('untracked item')
526 526 elif self.removed and self._p1_tracked and self._p2_info:
527 527 return NONNORMAL
528 528 elif self._p2_info:
529 529 return FROM_P2
530 530 elif self.removed:
531 531 return 0
532 532 elif self.added:
533 533 return NONNORMAL
534 534 elif self._size is None:
535 535 return NONNORMAL
536 536 else:
537 537 return self._size
538 538
539 539 def _v1_mtime(self):
540 540 """return a "mtime" suitable for v1 serialization"""
541 541 if not self.any_tracked:
542 542 # the object has no state to record, this is -currently-
543 543 # unsupported
544 544 raise RuntimeError('untracked item')
545 545 elif self.removed:
546 546 return 0
547 547 elif self._mtime_s is None:
548 548 return AMBIGUOUS_TIME
549 549 elif self._p2_info:
550 550 return AMBIGUOUS_TIME
551 551 elif not self._p1_tracked:
552 552 return AMBIGUOUS_TIME
553 553 elif self._mtime_second_ambiguous:
554 554 return AMBIGUOUS_TIME
555 555 else:
556 556 return self._mtime_s
557 557
558 558
559 559 def gettype(q):
560 560 return int(q & 0xFFFF)
561 561
562 562
563 563 class BaseIndexObject:
564 564 # Can I be passed to an algorithme implemented in Rust ?
565 565 rust_ext_compat = 0
566 566 # Format of an index entry according to Python's `struct` language
567 567 index_format = revlog_constants.INDEX_ENTRY_V1
568 568 # Size of a C unsigned long long int, platform independent
569 569 big_int_size = struct.calcsize(b'>Q')
570 570 # Size of a C long int, platform independent
571 571 int_size = struct.calcsize(b'>i')
572 572 # An empty index entry, used as a default value to be overridden, or nullrev
573 573 null_item = (
574 574 0,
575 575 0,
576 576 0,
577 577 -1,
578 578 -1,
579 579 -1,
580 580 -1,
581 581 sha1nodeconstants.nullid,
582 582 0,
583 583 0,
584 584 revlog_constants.COMP_MODE_INLINE,
585 585 revlog_constants.COMP_MODE_INLINE,
586 586 revlog_constants.RANK_UNKNOWN,
587 587 )
588 588
589 589 @util.propertycache
590 590 def entry_size(self):
591 591 return self.index_format.size
592 592
593 593 @util.propertycache
594 594 def _nodemap(self):
595 595 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
596 596 for r in range(0, len(self)):
597 597 n = self[r][7]
598 598 nodemap[n] = r
599 599 return nodemap
600 600
601 601 def has_node(self, node):
602 602 """return True if the node exist in the index"""
603 603 return node in self._nodemap
604 604
605 605 def rev(self, node):
606 606 """return a revision for a node
607 607
608 608 If the node is unknown, raise a RevlogError"""
609 609 return self._nodemap[node]
610 610
611 611 def get_rev(self, node):
612 612 """return a revision for a node
613 613
614 614 If the node is unknown, return None"""
615 615 return self._nodemap.get(node)
616 616
617 617 def _stripnodes(self, start):
618 618 if '_nodemap' in vars(self):
619 619 for r in range(start, len(self)):
620 620 n = self[r][7]
621 621 del self._nodemap[n]
622 622
623 623 def clearcaches(self):
624 624 self.__dict__.pop('_nodemap', None)
625 625
626 626 def __len__(self):
627 627 return self._lgt + len(self._extra)
628 628
629 629 def append(self, tup):
630 630 if '_nodemap' in vars(self):
631 631 self._nodemap[tup[7]] = len(self)
632 632 data = self._pack_entry(len(self), tup)
633 633 self._extra.append(data)
634 634
635 635 def _pack_entry(self, rev, entry):
636 636 assert entry[8] == 0
637 637 assert entry[9] == 0
638 638 return self.index_format.pack(*entry[:8])
639 639
640 640 def _check_index(self, i):
641 641 if not isinstance(i, int):
642 642 raise TypeError(b"expecting int indexes")
643 643 if i < 0 or i >= len(self):
644 644 raise IndexError(i)
645 645
646 646 def __getitem__(self, i):
647 647 if i == -1:
648 648 return self.null_item
649 649 self._check_index(i)
650 650 if i >= self._lgt:
651 651 data = self._extra[i - self._lgt]
652 652 else:
653 653 index = self._calculate_index(i)
654 654 data = self._data[index : index + self.entry_size]
655 655 r = self._unpack_entry(i, data)
656 656 if self._lgt and i == 0:
657 657 offset = revlogutils.offset_type(0, gettype(r[0]))
658 658 r = (offset,) + r[1:]
659 659 return r
660 660
661 661 def _unpack_entry(self, rev, data):
662 662 r = self.index_format.unpack(data)
663 663 r = r + (
664 664 0,
665 665 0,
666 666 revlog_constants.COMP_MODE_INLINE,
667 667 revlog_constants.COMP_MODE_INLINE,
668 668 revlog_constants.RANK_UNKNOWN,
669 669 )
670 670 return r
671 671
672 672 def pack_header(self, header):
673 673 """pack header information as binary"""
674 674 v_fmt = revlog_constants.INDEX_HEADER
675 675 return v_fmt.pack(header)
676 676
677 677 def entry_binary(self, rev):
678 678 """return the raw binary string representing a revision"""
679 679 entry = self[rev]
680 680 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
681 681 if rev == 0:
682 682 p = p[revlog_constants.INDEX_HEADER.size :]
683 683 return p
684 684
685 685
686 686 class IndexObject(BaseIndexObject):
687 687 def __init__(self, data):
688 688 assert len(data) % self.entry_size == 0, (
689 689 len(data),
690 690 self.entry_size,
691 691 len(data) % self.entry_size,
692 692 )
693 693 self._data = data
694 694 self._lgt = len(data) // self.entry_size
695 695 self._extra = []
696 696
697 697 def _calculate_index(self, i):
698 698 return i * self.entry_size
699 699
700 700 def __delitem__(self, i):
701 701 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
702 702 raise ValueError(b"deleting slices only supports a:-1 with step 1")
703 703 i = i.start
704 704 self._check_index(i)
705 705 self._stripnodes(i)
706 706 if i < self._lgt:
707 707 self._data = self._data[: i * self.entry_size]
708 708 self._lgt = i
709 709 self._extra = []
710 710 else:
711 711 self._extra = self._extra[: i - self._lgt]
712 712
713 713
714 714 class PersistentNodeMapIndexObject(IndexObject):
715 715 """a Debug oriented class to test persistent nodemap
716 716
717 717 We need a simple python object to test API and higher level behavior. See
718 718 the Rust implementation for more serious usage. This should be used only
719 719 through the dedicated `devel.persistent-nodemap` config.
720 720 """
721 721
722 722 def nodemap_data_all(self):
723 723 """Return bytes containing a full serialization of a nodemap
724 724
725 725 The nodemap should be valid for the full set of revisions in the
726 726 index."""
727 727 return nodemaputil.persistent_data(self)
728 728
729 729 def nodemap_data_incremental(self):
730 730 """Return bytes containing a incremental update to persistent nodemap
731 731
732 732 This containst the data for an append-only update of the data provided
733 733 in the last call to `update_nodemap_data`.
734 734 """
735 735 if self._nm_root is None:
736 736 return None
737 737 docket = self._nm_docket
738 738 changed, data = nodemaputil.update_persistent_data(
739 739 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
740 740 )
741 741
742 742 self._nm_root = self._nm_max_idx = self._nm_docket = None
743 743 return docket, changed, data
744 744
745 745 def update_nodemap_data(self, docket, nm_data):
746 746 """provide full block of persisted binary data for a nodemap
747 747
748 748 The data are expected to come from disk. See `nodemap_data_all` for a
749 749 produceur of such data."""
750 750 if nm_data is not None:
751 751 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
752 752 if self._nm_root:
753 753 self._nm_docket = docket
754 754 else:
755 755 self._nm_root = self._nm_max_idx = self._nm_docket = None
756 756
757 757
758 758 class InlinedIndexObject(BaseIndexObject):
759 759 def __init__(self, data, inline=0):
760 760 self._data = data
761 761 self._lgt = self._inline_scan(None)
762 762 self._inline_scan(self._lgt)
763 763 self._extra = []
764 764
765 765 def _inline_scan(self, lgt):
766 766 off = 0
767 767 if lgt is not None:
768 768 self._offsets = [0] * lgt
769 769 count = 0
770 770 while off <= len(self._data) - self.entry_size:
771 771 start = off + self.big_int_size
772 772 (s,) = struct.unpack(
773 773 b'>i',
774 774 self._data[start : start + self.int_size],
775 775 )
776 776 if lgt is not None:
777 777 self._offsets[count] = off
778 778 count += 1
779 779 off += self.entry_size + s
780 780 if off != len(self._data):
781 781 raise ValueError(b"corrupted data")
782 782 return count
783 783
784 784 def __delitem__(self, i):
785 785 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
786 786 raise ValueError(b"deleting slices only supports a:-1 with step 1")
787 787 i = i.start
788 788 self._check_index(i)
789 789 self._stripnodes(i)
790 790 if i < self._lgt:
791 791 self._offsets = self._offsets[:i]
792 792 self._lgt = i
793 793 self._extra = []
794 794 else:
795 795 self._extra = self._extra[: i - self._lgt]
796 796
797 797 def _calculate_index(self, i):
798 798 return self._offsets[i]
799 799
800 800
801 801 def parse_index2(data, inline, format=revlog_constants.REVLOGV1):
802 802 if format == revlog_constants.CHANGELOGV2:
803 803 return parse_index_cl_v2(data)
804 804 if not inline:
805 805 if format == revlog_constants.REVLOGV2:
806 806 cls = IndexObject2
807 807 else:
808 808 cls = IndexObject
809 809 return cls(data), None
810 810 cls = InlinedIndexObject
811 811 return cls(data, inline), (0, data)
812 812
813 813
814 814 def parse_index_cl_v2(data):
815 815 return IndexChangelogV2(data), None
816 816
817 817
818 818 class IndexObject2(IndexObject):
819 819 index_format = revlog_constants.INDEX_ENTRY_V2
820 820
821 821 def replace_sidedata_info(
822 822 self,
823 823 rev,
824 824 sidedata_offset,
825 825 sidedata_length,
826 826 offset_flags,
827 827 compression_mode,
828 828 ):
829 829 """
830 830 Replace an existing index entry's sidedata offset and length with new
831 831 ones.
832 832 This cannot be used outside of the context of sidedata rewriting,
833 833 inside the transaction that creates the revision `rev`.
834 834 """
835 835 if rev < 0:
836 836 raise KeyError
837 837 self._check_index(rev)
838 838 if rev < self._lgt:
839 839 msg = b"cannot rewrite entries outside of this transaction"
840 840 raise KeyError(msg)
841 841 else:
842 842 entry = list(self[rev])
843 843 entry[0] = offset_flags
844 844 entry[8] = sidedata_offset
845 845 entry[9] = sidedata_length
846 846 entry[11] = compression_mode
847 847 entry = tuple(entry)
848 848 new = self._pack_entry(rev, entry)
849 849 self._extra[rev - self._lgt] = new
850 850
851 851 def _unpack_entry(self, rev, data):
852 852 data = self.index_format.unpack(data)
853 853 entry = data[:10]
854 854 data_comp = data[10] & 3
855 855 sidedata_comp = (data[10] & (3 << 2)) >> 2
856 856 return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN)
857 857
858 858 def _pack_entry(self, rev, entry):
859 859 data = entry[:10]
860 860 data_comp = entry[10] & 3
861 861 sidedata_comp = (entry[11] & 3) << 2
862 862 data += (data_comp | sidedata_comp,)
863 863
864 864 return self.index_format.pack(*data)
865 865
866 866 def entry_binary(self, rev):
867 867 """return the raw binary string representing a revision"""
868 868 entry = self[rev]
869 869 return self._pack_entry(rev, entry)
870 870
871 871 def pack_header(self, header):
872 872 """pack header information as binary"""
873 873 msg = 'version header should go in the docket, not the index: %d'
874 874 msg %= header
875 875 raise error.ProgrammingError(msg)
876 876
877 877
878 878 class IndexChangelogV2(IndexObject2):
879 879 index_format = revlog_constants.INDEX_ENTRY_CL_V2
880 880
881 881 null_item = (
882 882 IndexObject2.null_item[: revlog_constants.ENTRY_RANK]
883 883 + (0,) # rank of null is 0
884 884 + IndexObject2.null_item[revlog_constants.ENTRY_RANK :]
885 885 )
886 886
887 887 def _unpack_entry(self, rev, data, r=True):
888 888 items = self.index_format.unpack(data)
889 889 return (
890 890 items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET],
891 891 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH],
892 892 items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH],
893 893 rev,
894 894 rev,
895 895 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1],
896 896 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2],
897 897 items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID],
898 898 items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET],
899 899 items[
900 900 revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH
901 901 ],
902 902 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3,
903 903 (items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2)
904 904 & 3,
905 905 items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK],
906 906 )
907 907
908 908 def _pack_entry(self, rev, entry):
909 909
910 910 base = entry[revlog_constants.ENTRY_DELTA_BASE]
911 911 link_rev = entry[revlog_constants.ENTRY_LINK_REV]
912 912 assert base == rev, (base, rev)
913 913 assert link_rev == rev, (link_rev, rev)
914 914 data = (
915 915 entry[revlog_constants.ENTRY_DATA_OFFSET],
916 916 entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH],
917 917 entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH],
918 918 entry[revlog_constants.ENTRY_PARENT_1],
919 919 entry[revlog_constants.ENTRY_PARENT_2],
920 920 entry[revlog_constants.ENTRY_NODE_ID],
921 921 entry[revlog_constants.ENTRY_SIDEDATA_OFFSET],
922 922 entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH],
923 923 entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3
924 924 | (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3)
925 925 << 2,
926 926 entry[revlog_constants.ENTRY_RANK],
927 927 )
928 928 return self.index_format.pack(*data)
929 929
930 930
931 931 def parse_index_devel_nodemap(data, inline):
932 932 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
933 933 return PersistentNodeMapIndexObject(data), None
934 934
935 935
936 936 def parse_dirstate(dmap, copymap, st):
937 937 parents = [st[:20], st[20:40]]
938 938 # dereference fields so they will be local in loop
939 939 format = b">cllll"
940 940 e_size = struct.calcsize(format)
941 941 pos1 = 40
942 942 l = len(st)
943 943
944 944 # the inner loop
945 945 while pos1 < l:
946 946 pos2 = pos1 + e_size
947 947 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
948 948 pos1 = pos2 + e[4]
949 949 f = st[pos2:pos1]
950 950 if b'\0' in f:
951 951 f, c = f.split(b'\0')
952 952 copymap[f] = c
953 953 dmap[f] = DirstateItem.from_v1_data(*e[:4])
954 954 return parents
955 955
956 956
957 957 def pack_dirstate(dmap, copymap, pl):
958 958 cs = stringio()
959 959 write = cs.write
960 960 write(b"".join(pl))
961 961 for f, e in dmap.items():
962 962 if f in copymap:
963 963 f = b"%s\0%s" % (f, copymap[f])
964 964 e = _pack(
965 965 b">cllll",
966 966 e._v1_state(),
967 967 e._v1_mode(),
968 968 e._v1_size(),
969 969 e._v1_mtime(),
970 970 len(f),
971 971 )
972 972 write(e)
973 973 write(f)
974 974 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now