##// END OF EJS Templates
rust-dirstate: fall back to v1 if reading v2 failed...
Raphaël Gomès -
r51553:bf16ef96 stable
parent child Browse files
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 read_len = self._nodelen * 2
217 247 st = self._readdirstatefile(read_len)
218 248 l = len(st)
@@ -227,12 +257,13 b' class _dirstatemapcommon:'
227 257 self._nodeconstants.nullid,
228 258 )
229 259 else:
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 raise error.Abort(
231 _(b'working directory state appears damaged!')
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(self._ui, b'dirstate.post-docket-read-file')
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._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 )
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 should be
242 /// able to understand the repo *if* it uses the requirement, but not that
243 /// the requirement is actually used.
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.parents()
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 _ => return Err(e),
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 let identity = self.dirstate_identity()?;
371 411 let dirstate_file_contents = self.dirstate_file_contents()?;
372 412 if dirstate_file_contents.is_empty() {
373 413 self.dirstate_parents.set(DirstateParents::NULL);
374 414 Ok(OwningDirstateMap::new_empty(Vec::new()))
375 415 } else {
376 let (map, parents) = OwningDirstateMap::new_v1(
377 dirstate_file_contents,
378 identity,
379 )?;
416 let (map, parents) =
417 OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
380 418 self.dirstate_parents.set(parents);
381 419 Ok(map)
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) (missing-correct-output !)
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