28 DirstateItem = parsers.DirstateItem |
29 DirstateItem = parsers.DirstateItem |
29 else: |
30 else: |
30 DirstateItem = rustmod.DirstateItem |
31 DirstateItem = rustmod.DirstateItem |
31 |
32 |
32 rangemask = 0x7FFFFFFF |
33 rangemask = 0x7FFFFFFF |
|
34 |
|
35 WRITE_MODE_AUTO = 0 |
|
36 WRITE_MODE_FORCE_NEW = 1 |
|
37 WRITE_MODE_FORCE_APPEND = 2 |
|
38 |
|
39 |
|
40 V2_MAX_READ_ATTEMPTS = 5 |
33 |
41 |
34 |
42 |
35 class _dirstatemapcommon: |
43 class _dirstatemapcommon: |
36 """ |
44 """ |
37 Methods that are identical for both implementations of the dirstatemap |
45 Methods that are identical for both implementations of the dirstatemap |
52 self._filename = b'dirstate' |
60 self._filename = b'dirstate' |
53 self._nodelen = 20 # Also update Rust code when changing this! |
61 self._nodelen = 20 # Also update Rust code when changing this! |
54 self._parents = None |
62 self._parents = None |
55 self._dirtyparents = False |
63 self._dirtyparents = False |
56 self._docket = None |
64 self._docket = None |
|
65 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode") |
|
66 if write_mode == b"auto": |
|
67 self._write_mode = WRITE_MODE_AUTO |
|
68 elif write_mode == b"force-append": |
|
69 self._write_mode = WRITE_MODE_FORCE_APPEND |
|
70 elif write_mode == b"force-new": |
|
71 self._write_mode = WRITE_MODE_FORCE_NEW |
|
72 else: |
|
73 # unknown value, fallback to default |
|
74 self._write_mode = WRITE_MODE_AUTO |
57 |
75 |
58 # for consistent view between _pl() and _read() invocations |
76 # for consistent view between _pl() and _read() invocations |
59 self._pendingmode = None |
77 self._pendingmode = None |
60 |
78 |
61 def _set_identity(self): |
79 def _set_identity(self): |
130 if not self._docket: |
148 if not self._docket: |
131 if not self._use_dirstate_v2: |
149 if not self._use_dirstate_v2: |
132 raise error.ProgrammingError( |
150 raise error.ProgrammingError( |
133 b'dirstate only has a docket in v2 format' |
151 b'dirstate only has a docket in v2 format' |
134 ) |
152 ) |
|
153 self._set_identity() |
135 self._docket = docketmod.DirstateDocket.parse( |
154 self._docket = docketmod.DirstateDocket.parse( |
136 self._readdirstatefile(), self._nodeconstants |
155 self._readdirstatefile(), self._nodeconstants |
137 ) |
156 ) |
138 return self._docket |
157 return self._docket |
|
158 |
|
159 def _read_v2_data(self): |
|
160 data = None |
|
161 attempts = 0 |
|
162 while attempts < V2_MAX_READ_ATTEMPTS: |
|
163 attempts += 1 |
|
164 try: |
|
165 # TODO: use mmap when possible |
|
166 data = self._opener.read(self.docket.data_filename()) |
|
167 except FileNotFoundError: |
|
168 # read race detected between docket and data file |
|
169 # reload the docket and retry |
|
170 self._docket = None |
|
171 if data is None: |
|
172 assert attempts >= V2_MAX_READ_ATTEMPTS |
|
173 msg = b"dirstate read race happened %d times in a row" |
|
174 msg %= attempts |
|
175 raise error.Abort(msg) |
|
176 return self._opener.read(self.docket.data_filename()) |
139 |
177 |
140 def write_v2_no_append(self, tr, st, meta, packed): |
178 def write_v2_no_append(self, tr, st, meta, packed): |
141 old_docket = self.docket |
179 old_docket = self.docket |
142 new_docket = docketmod.DirstateDocket.with_new_uuid( |
180 new_docket = docketmod.DirstateDocket.with_new_uuid( |
143 self.parents(), len(packed), meta |
181 self.parents(), len(packed), meta |
288 return copies |
326 return copies |
289 |
327 |
290 ### disk interaction |
328 ### disk interaction |
291 |
329 |
292 def read(self): |
330 def read(self): |
293 # ignore HG_PENDING because identity is used only for writing |
331 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') |
294 self._set_identity() |
|
295 |
|
296 if self._use_dirstate_v2: |
332 if self._use_dirstate_v2: |
|
333 |
297 if not self.docket.uuid: |
334 if not self.docket.uuid: |
298 return |
335 return |
299 st = self._opener.read(self.docket.data_filename()) |
336 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') |
|
337 st = self._read_v2_data() |
300 else: |
338 else: |
|
339 self._set_identity() |
301 st = self._readdirstatefile() |
340 st = self._readdirstatefile() |
302 |
341 |
303 if not st: |
342 if not st: |
304 return |
343 return |
305 |
344 |
554 Fills the Dirstatemap when called. |
593 Fills the Dirstatemap when called. |
555 """ |
594 """ |
556 # ignore HG_PENDING because identity is used only for writing |
595 # ignore HG_PENDING because identity is used only for writing |
557 self._set_identity() |
596 self._set_identity() |
558 |
597 |
|
598 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') |
559 if self._use_dirstate_v2: |
599 if self._use_dirstate_v2: |
560 if self.docket.uuid: |
600 self.docket # load the data if needed |
561 # TODO: use mmap when possible |
601 inode = ( |
562 data = self._opener.read(self.docket.data_filename()) |
602 self.identity.stat.st_ino |
|
603 if self.identity is not None |
|
604 and self.identity.stat is not None |
|
605 else None |
|
606 ) |
|
607 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') |
|
608 if not self.docket.uuid: |
|
609 data = b'' |
|
610 self._map = rustmod.DirstateMap.new_empty() |
563 else: |
611 else: |
564 data = b'' |
612 data = self._read_v2_data() |
565 self._map = rustmod.DirstateMap.new_v2( |
613 self._map = rustmod.DirstateMap.new_v2( |
566 data, self.docket.data_size, self.docket.tree_metadata |
614 data, |
567 ) |
615 self.docket.data_size, |
|
616 self.docket.tree_metadata, |
|
617 self.docket.uuid, |
|
618 inode, |
|
619 ) |
568 parents = self.docket.parents |
620 parents = self.docket.parents |
569 else: |
621 else: |
|
622 self._set_identity() |
|
623 inode = ( |
|
624 self.identity.stat.st_ino |
|
625 if self.identity is not None |
|
626 and self.identity.stat is not None |
|
627 else None |
|
628 ) |
570 self._map, parents = rustmod.DirstateMap.new_v1( |
629 self._map, parents = rustmod.DirstateMap.new_v1( |
571 self._readdirstatefile() |
630 self._readdirstatefile(), inode |
572 ) |
631 ) |
573 |
632 |
574 if parents and not self._dirtyparents: |
633 if parents and not self._dirtyparents: |
575 self.setparents(*parents) |
634 self.setparents(*parents) |
576 |
635 |
636 st.close() |
695 st.close() |
637 self._dirtyparents = False |
696 self._dirtyparents = False |
638 return |
697 return |
639 |
698 |
640 # We can only append to an existing data file if there is one |
699 # We can only append to an existing data file if there is one |
641 can_append = self.docket.uuid is not None |
700 write_mode = self._write_mode |
642 packed, meta, append = self._map.write_v2(can_append) |
701 if self.docket.uuid is None: |
|
702 write_mode = WRITE_MODE_FORCE_NEW |
|
703 packed, meta, append = self._map.write_v2(write_mode) |
643 if append: |
704 if append: |
644 docket = self.docket |
705 docket = self.docket |
645 data_filename = docket.data_filename() |
706 data_filename = docket.data_filename() |
646 # We mark it for backup to make sure a future `hg rollback` (or |
707 # We mark it for backup to make sure a future `hg rollback` (or |
647 # `hg recover`?) call find the data it needs to restore a |
708 # `hg recover`?) call find the data it needs to restore a |