Show More
@@ -4,6 +4,7 b'' | |||
|
4 | 4 | # GNU General Public License version 2 or any later version. |
|
5 | 5 | |
|
6 | 6 | |
|
7 | import struct | |
|
7 | 8 | from .i18n import _ |
|
8 | 9 | |
|
9 | 10 | from . import ( |
@@ -151,9 +152,15 b' class _dirstatemapcommon:' | |||
|
151 | 152 | b'dirstate only has a docket in v2 format' |
|
152 | 153 | ) |
|
153 | 154 | self._set_identity() |
|
155 | try: | |
|
154 | 156 | self._docket = docketmod.DirstateDocket.parse( |
|
155 | 157 | self._readdirstatefile(), self._nodeconstants |
|
156 | 158 | ) |
|
159 | except struct.error: | |
|
160 | self._ui.debug(b"failed to read dirstate-v2 data") | |
|
161 | raise error.CorruptedDirstate( | |
|
162 | b"failed to read dirstate-v2 data" | |
|
163 | ) | |
|
157 | 164 | return self._docket |
|
158 | 165 | |
|
159 | 166 | def _read_v2_data(self): |
@@ -176,11 +183,23 b' class _dirstatemapcommon:' | |||
|
176 | 183 | return self._opener.read(self.docket.data_filename()) |
|
177 | 184 | |
|
178 | 185 | def write_v2_no_append(self, tr, st, meta, packed): |
|
186 | try: | |
|
179 | 187 | old_docket = self.docket |
|
188 | except error.CorruptedDirstate: | |
|
189 | # This means we've identified a dirstate-v1 file on-disk when we | |
|
190 | # were expecting a dirstate-v2 docket. We've managed to recover | |
|
191 | # from that unexpected situation, and now we want to write back a | |
|
192 | # dirstate-v2 file to make the on-disk situation right again. | |
|
193 | # | |
|
194 | # This shouldn't be triggered since `self.docket` is cached and | |
|
195 | # we would have called parents() or read() first, but it's here | |
|
196 | # just in case. | |
|
197 | old_docket = None | |
|
198 | ||
|
180 | 199 | new_docket = docketmod.DirstateDocket.with_new_uuid( |
|
181 | 200 | self.parents(), len(packed), meta |
|
182 | 201 | ) |
|
183 | if old_docket.uuid == new_docket.uuid: | |
|
202 | if old_docket is not None and old_docket.uuid == new_docket.uuid: | |
|
184 | 203 | raise error.ProgrammingError(b'dirstate docket name collision') |
|
185 | 204 | data_filename = new_docket.data_filename() |
|
186 | 205 | self._opener.write(data_filename, packed) |
@@ -194,7 +213,7 b' class _dirstatemapcommon:' | |||
|
194 | 213 | st.close() |
|
195 | 214 | # Remove the old data file after the new docket pointing to |
|
196 | 215 | # the new data file was written. |
|
197 | if old_docket.uuid: | |
|
216 | if old_docket is not None and old_docket.uuid: | |
|
198 | 217 | data_filename = old_docket.data_filename() |
|
199 | 218 | if tr is not None: |
|
200 | 219 | tr.addbackup(data_filename, location=b'plain') |
@@ -211,8 +230,19 b' class _dirstatemapcommon:' | |||
|
211 | 230 | def parents(self): |
|
212 | 231 | if not self._parents: |
|
213 | 232 | if self._use_dirstate_v2: |
|
233 | try: | |
|
234 | self.docket | |
|
235 | except error.CorruptedDirstate as e: | |
|
236 | # fall back to dirstate-v1 if we fail to read v2 | |
|
237 | self._v1_parents(e) | |
|
238 | else: | |
|
214 | 239 | self._parents = self.docket.parents |
|
215 | 240 | else: |
|
241 | self._v1_parents() | |
|
242 | ||
|
243 | return self._parents | |
|
244 | ||
|
245 | def _v1_parents(self, from_v2_exception=None): | |
|
216 | 246 |
|
|
217 | 247 |
|
|
218 | 248 |
|
@@ -227,12 +257,13 b' class _dirstatemapcommon:' | |||
|
227 | 257 |
|
|
228 | 258 |
|
|
229 | 259 |
|
|
260 | hint = None | |
|
261 | if from_v2_exception is not None: | |
|
262 | hint = _(b"falling back to dirstate-v1 from v2 also failed") | |
|
230 | 263 |
|
|
231 |
|
|
|
264 | _(b'working directory state appears damaged!'), hint | |
|
232 | 265 |
|
|
233 | 266 | |
|
234 | return self._parents | |
|
235 | ||
|
236 | 267 | |
|
237 | 268 | class dirstatemap(_dirstatemapcommon): |
|
238 | 269 | """Map encapsulating the dirstate's contents. |
@@ -330,7 +361,13 b' class dirstatemap(_dirstatemapcommon):' | |||
|
330 | 361 | def read(self): |
|
331 | 362 | testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') |
|
332 | 363 | if self._use_dirstate_v2: |
|
333 | ||
|
364 | try: | |
|
365 | self.docket | |
|
366 | except error.CorruptedDirstate: | |
|
367 | # fall back to dirstate-v1 if we fail to read v2 | |
|
368 | self._set_identity() | |
|
369 | st = self._readdirstatefile() | |
|
370 | else: | |
|
334 | 371 | if not self.docket.uuid: |
|
335 | 372 | return |
|
336 | 373 | testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') |
@@ -365,6 +402,13 b' class dirstatemap(_dirstatemapcommon):' | |||
|
365 | 402 | # |
|
366 | 403 | # (we cannot decorate the function directly since it is in a C module) |
|
367 | 404 | if self._use_dirstate_v2: |
|
405 | try: | |
|
406 | self.docket | |
|
407 | except error.CorruptedDirstate: | |
|
408 | # fall back to dirstate-v1 if we fail to parse v2 | |
|
409 | parse_dirstate = util.nogc(parsers.parse_dirstate) | |
|
410 | p = parse_dirstate(self._map, self.copymap, st) | |
|
411 | else: | |
|
368 | 412 | p = self.docket.parents |
|
369 | 413 | meta = self.docket.tree_metadata |
|
370 | 414 | parse_dirstate = util.nogc(v2.parse_dirstate) |
@@ -597,14 +641,22 b' if rustmod is not None:' | |||
|
597 | 641 | |
|
598 | 642 | testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') |
|
599 | 643 | if self._use_dirstate_v2: |
|
600 | self.docket # load the data if needed | |
|
644 | try: | |
|
645 | self.docket | |
|
646 | except error.CorruptedDirstate as e: | |
|
647 | # fall back to dirstate-v1 if we fail to read v2 | |
|
648 | parents = self._v1_map(e) | |
|
649 | else: | |
|
650 | parents = self.docket.parents | |
|
601 | 651 | inode = ( |
|
602 | 652 | self.identity.stat.st_ino |
|
603 | 653 | if self.identity is not None |
|
604 | 654 | and self.identity.stat is not None |
|
605 | 655 | else None |
|
606 | 656 | ) |
|
607 |
testing.wait_on_cfg( |
|
|
657 | testing.wait_on_cfg( | |
|
658 | self._ui, b'dirstate.post-docket-read-file' | |
|
659 | ) | |
|
608 | 660 | if not self.docket.uuid: |
|
609 | 661 | data = b'' |
|
610 | 662 | self._map = rustmod.DirstateMap.new_empty() |
@@ -619,16 +671,7 b' if rustmod is not None:' | |||
|
619 | 671 | ) |
|
620 | 672 | parents = self.docket.parents |
|
621 | 673 | else: |
|
622 |
self._ |
|
|
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 | ) | |
|
629 | self._map, parents = rustmod.DirstateMap.new_v1( | |
|
630 | self._readdirstatefile(), inode | |
|
631 | ) | |
|
674 | parents = self._v1_map() | |
|
632 | 675 | |
|
633 | 676 | if parents and not self._dirtyparents: |
|
634 | 677 | self.setparents(*parents) |
@@ -638,6 +681,23 b' if rustmod is not None:' | |||
|
638 | 681 | self.get = self._map.get |
|
639 | 682 | return self._map |
|
640 | 683 | |
|
684 | def _v1_map(self, from_v2_exception=None): | |
|
685 | self._set_identity() | |
|
686 | inode = ( | |
|
687 | self.identity.stat.st_ino | |
|
688 | if self.identity is not None and self.identity.stat is not None | |
|
689 | else None | |
|
690 | ) | |
|
691 | try: | |
|
692 | self._map, parents = rustmod.DirstateMap.new_v1( | |
|
693 | self._readdirstatefile(), inode | |
|
694 | ) | |
|
695 | except OSError as e: | |
|
696 | if from_v2_exception is not None: | |
|
697 | raise e from from_v2_exception | |
|
698 | raise | |
|
699 | return parents | |
|
700 | ||
|
641 | 701 | @property |
|
642 | 702 | def copymap(self): |
|
643 | 703 | return self._map.copymap() |
@@ -696,9 +756,15 b' if rustmod is not None:' | |||
|
696 | 756 | self._dirtyparents = False |
|
697 | 757 | return |
|
698 | 758 | |
|
759 | write_mode = self._write_mode | |
|
760 | try: | |
|
761 | docket = self.docket | |
|
762 | except error.CorruptedDirstate: | |
|
763 | # fall back to dirstate-v1 if we fail to parse v2 | |
|
764 | docket = None | |
|
765 | ||
|
699 | 766 | # We can only append to an existing data file if there is one |
|
700 | write_mode = self._write_mode | |
|
701 | if self.docket.uuid is None: | |
|
767 | if docket is None or docket.uuid is None: | |
|
702 | 768 | write_mode = WRITE_MODE_FORCE_NEW |
|
703 | 769 | packed, meta, append = self._map.write_v2(write_mode) |
|
704 | 770 | if append: |
@@ -650,6 +650,13 b' class CorruptedState(Exception):' | |||
|
650 | 650 | __bytes__ = _tobytes |
|
651 | 651 | |
|
652 | 652 | |
|
653 | class CorruptedDirstate(Exception): | |
|
654 | """error raised the dirstate appears corrupted on-disk. It may be due to | |
|
655 | a dirstate version mismatch (i.e. expecting v2 and finding v1 on disk).""" | |
|
656 | ||
|
657 | __bytes__ = _tobytes | |
|
658 | ||
|
659 | ||
|
653 | 660 | class PeerTransportError(Abort): |
|
654 | 661 | """Transport-level I/O error when communicating with a peer repo.""" |
|
655 | 662 |
@@ -238,9 +238,10 b' impl Repo {' | |||
|
238 | 238 | /// a dirstate-v2 file will indeed be found, but in rare cases (like the |
|
239 | 239 | /// upgrade mechanism being cut short), the on-disk version will be a |
|
240 | 240 | /// v1 file. |
|
241 |
/// Semantically, having a requirement only means that a client |
|
|
242 | /// able to understand the repo *if* it uses the requirement, but not that | |
|
243 |
/// the require |
|
|
241 | /// Semantically, having a requirement only means that a client cannot | |
|
242 | /// properly understand or properly update the repo if it lacks the support | |
|
243 | /// for the required feature, but not that that feature is actually used | |
|
244 | /// in all occasions. | |
|
244 | 245 | pub fn use_dirstate_v2(&self) -> bool { |
|
245 | 246 | self.requirements |
|
246 | 247 | .contains(requirements::DIRSTATE_V2_REQUIREMENT) |
@@ -287,9 +288,20 b' impl Repo {' | |||
|
287 | 288 | let parents = if dirstate.is_empty() { |
|
288 | 289 | DirstateParents::NULL |
|
289 | 290 | } else if self.use_dirstate_v2() { |
|
290 | let docket = | |
|
291 |
crate::dirstate_tree::on_disk::read_docket(&dirstate) |
|
|
292 |
docket |
|
|
291 | let docket_res = | |
|
292 | crate::dirstate_tree::on_disk::read_docket(&dirstate); | |
|
293 | match docket_res { | |
|
294 | Ok(docket) => docket.parents(), | |
|
295 | Err(_) => { | |
|
296 | log::info!( | |
|
297 | "Parsing dirstate docket failed, \ | |
|
298 | falling back to dirstate-v1" | |
|
299 | ); | |
|
300 | *crate::dirstate::parsers::parse_dirstate_parents( | |
|
301 | &dirstate, | |
|
302 | )? | |
|
303 | } | |
|
304 | } | |
|
293 | 305 | } else { |
|
294 | 306 | *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)? |
|
295 | 307 | }; |
@@ -317,10 +329,30 b' impl Repo {' | |||
|
317 | 329 | self.dirstate_parents.set(DirstateParents::NULL); |
|
318 | 330 | Ok((identity, None, 0)) |
|
319 | 331 | } else { |
|
320 | let docket = | |
|
321 |
crate::dirstate_tree::on_disk::read_docket(&dirstate) |
|
|
332 | let docket_res = | |
|
333 | crate::dirstate_tree::on_disk::read_docket(&dirstate); | |
|
334 | match docket_res { | |
|
335 | Ok(docket) => { | |
|
322 | 336 | self.dirstate_parents.set(docket.parents()); |
|
323 | Ok((identity, Some(docket.uuid.to_owned()), docket.data_size())) | |
|
337 | Ok(( | |
|
338 | identity, | |
|
339 | Some(docket.uuid.to_owned()), | |
|
340 | docket.data_size(), | |
|
341 | )) | |
|
342 | } | |
|
343 | Err(_) => { | |
|
344 | log::info!( | |
|
345 | "Parsing dirstate docket failed, \ | |
|
346 | falling back to dirstate-v1" | |
|
347 | ); | |
|
348 | let parents = | |
|
349 | *crate::dirstate::parsers::parse_dirstate_parents( | |
|
350 | &dirstate, | |
|
351 | )?; | |
|
352 | self.dirstate_parents.set(parents); | |
|
353 | Ok((identity, None, 0)) | |
|
354 | } | |
|
355 | } | |
|
324 | 356 | } |
|
325 | 357 | } |
|
326 | 358 | |
@@ -352,7 +384,13 b' impl Repo {' | |||
|
352 | 384 | ); |
|
353 | 385 | continue; |
|
354 | 386 | } |
|
355 |
_ => |
|
|
387 | _ => { | |
|
388 | log::info!( | |
|
389 | "Reading dirstate v2 failed, \ | |
|
390 | falling back to v1" | |
|
391 | ); | |
|
392 | return self.new_dirstate_map_v1(); | |
|
393 | } | |
|
356 | 394 | }, |
|
357 | 395 | } |
|
358 | 396 | } |
@@ -363,25 +401,24 b' impl Repo {' | |||
|
363 | 401 | ); |
|
364 | 402 | Err(DirstateError::Common(error)) |
|
365 | 403 | } else { |
|
366 | debug_wait_for_file_or_print( | |
|
367 | self.config(), | |
|
368 | "dirstate.pre-read-file", | |
|
369 | ); | |
|
404 | self.new_dirstate_map_v1() | |
|
405 | } | |
|
406 | } | |
|
407 | ||
|
408 | fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> { | |
|
409 | debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file"); | |
|
370 | 410 |
|
|
371 | 411 |
|
|
372 | 412 |
|
|
373 | 413 |
|
|
374 | 414 |
|
|
375 | 415 |
|
|
376 |
|
|
|
377 |
|
|
|
378 | identity, | |
|
379 | )?; | |
|
416 | let (map, parents) = | |
|
417 | OwningDirstateMap::new_v1(dirstate_file_contents, identity)?; | |
|
380 | 418 |
|
|
381 | 419 |
|
|
382 | 420 |
|
|
383 | 421 |
|
|
384 | } | |
|
385 | 422 | |
|
386 | 423 | fn read_docket_and_data_file( |
|
387 | 424 | &self, |
@@ -20,7 +20,7 b' Copy v1 dirstate' | |||
|
20 | 20 | |
|
21 | 21 | Upgrade it to v2 |
|
22 | 22 | |
|
23 | $ hg debugupgraderepo -q --config format.use-dirstate-v2=1 --run | grep added | |
|
23 | $ hg debugupgraderepo -q --config format.use-dirstate-v2=1 --run | egrep 'added:|removed:' | |
|
24 | 24 | added: dirstate-v2 |
|
25 | 25 | $ hg debugrequires | grep dirstate |
|
26 | 26 | dirstate-v2 |
@@ -35,9 +35,15 b' Manually reset to dirstate v1 to simulat' | |||
|
35 | 35 | |
|
36 | 36 | There should be no errors, but a v2 dirstate should be written back to disk |
|
37 | 37 | $ hg st |
|
38 | abort: dirstate-v2 parse error: when reading docket, Expected at least * bytes, got * (glob) (known-bad-output !) | |
|
39 | [255] | |
|
40 | 38 | $ ls -1 .hg/dirstate* |
|
41 | 39 | .hg/dirstate |
|
42 |
.hg/dirstate.* (glob) |
|
|
40 | .hg/dirstate.* (glob) | |
|
41 | ||
|
42 | Corrupt the dirstate to see how the errors show up to the user | |
|
43 | $ echo "I ate your data" > .hg/dirstate | |
|
43 | 44 | |
|
45 | $ hg st | |
|
46 | abort: working directory state appears damaged! (no-rhg !) | |
|
47 | (falling back to dirstate-v1 from v2 also failed) (no-rhg !) | |
|
48 | abort: Too little data for dirstate. (rhg !) | |
|
49 | [255] |
General Comments 0
You need to be logged in to leave comments.
Login now