##// 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()
154 self._docket = docketmod.DirstateDocket.parse(
155 self._readdirstatefile(), self._nodeconstants
156 )
155 try:
156 self._docket = docketmod.DirstateDocket.parse(
157 self._readdirstatefile(), self._nodeconstants
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):
179 old_docket = self.docket
186 try:
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,28 +230,40 b' class _dirstatemapcommon:'
211 230 def parents(self):
212 231 if not self._parents:
213 232 if self._use_dirstate_v2:
214 self._parents = self.docket.parents
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:
239 self._parents = self.docket.parents
215 240 else:
216 read_len = self._nodelen * 2
217 st = self._readdirstatefile(read_len)
218 l = len(st)
219 if l == read_len:
220 self._parents = (
221 st[: self._nodelen],
222 st[self._nodelen : 2 * self._nodelen],
223 )
224 elif l == 0:
225 self._parents = (
226 self._nodeconstants.nullid,
227 self._nodeconstants.nullid,
228 )
229 else:
230 raise error.Abort(
231 _(b'working directory state appears damaged!')
232 )
241 self._v1_parents()
233 242
234 243 return self._parents
235 244
245 def _v1_parents(self, from_v2_exception=None):
246 read_len = self._nodelen * 2
247 st = self._readdirstatefile(read_len)
248 l = len(st)
249 if l == read_len:
250 self._parents = (
251 st[: self._nodelen],
252 st[self._nodelen : 2 * self._nodelen],
253 )
254 elif l == 0:
255 self._parents = (
256 self._nodeconstants.nullid,
257 self._nodeconstants.nullid,
258 )
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")
263 raise error.Abort(
264 _(b'working directory state appears damaged!'), hint
265 )
266
236 267
237 268 class dirstatemap(_dirstatemapcommon):
238 269 """Map encapsulating the dirstate's contents.
@@ -330,11 +361,17 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
334 if not self.docket.uuid:
335 return
336 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
337 st = self._read_v2_data()
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:
371 if not self.docket.uuid:
372 return
373 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
374 st = self._read_v2_data()
338 375 else:
339 376 self._set_identity()
340 377 st = self._readdirstatefile()
@@ -365,10 +402,17 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:
368 p = self.docket.parents
369 meta = self.docket.tree_metadata
370 parse_dirstate = util.nogc(v2.parse_dirstate)
371 parse_dirstate(self._map, self.copymap, st, meta)
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:
412 p = self.docket.parents
413 meta = self.docket.tree_metadata
414 parse_dirstate = util.nogc(v2.parse_dirstate)
415 parse_dirstate(self._map, self.copymap, st, meta)
372 416 else:
373 417 parse_dirstate = util.nogc(parsers.parse_dirstate)
374 418 p = parse_dirstate(self._map, self.copymap, st)
@@ -597,38 +641,37 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
601 inode = (
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()
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)
611 649 else:
612 data = self._read_v2_data()
613 self._map = rustmod.DirstateMap.new_v2(
614 data,
615 self.docket.data_size,
616 self.docket.tree_metadata,
617 self.docket.uuid,
618 inode,
650 parents = self.docket.parents
651 inode = (
652 self.identity.stat.st_ino
653 if self.identity is not None
654 and self.identity.stat is not None
655 else None
656 )
657 testing.wait_on_cfg(
658 self._ui, b'dirstate.post-docket-read-file'
619 659 )
620 parents = self.docket.parents
660 if not self.docket.uuid:
661 data = b''
662 self._map = rustmod.DirstateMap.new_empty()
663 else:
664 data = self._read_v2_data()
665 self._map = rustmod.DirstateMap.new_v2(
666 data,
667 self.docket.data_size,
668 self.docket.tree_metadata,
669 self.docket.uuid,
670 inode,
671 )
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)?;
322 self.dirstate_parents.set(docket.parents());
323 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
332 let docket_res =
333 crate::dirstate_tree::on_disk::read_docket(&dirstate);
334 match docket_res {
335 Ok(docket) => {
336 self.dirstate_parents.set(docket.parents());
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,23 +401,22 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 );
370 let identity = self.dirstate_identity()?;
371 let dirstate_file_contents = self.dirstate_file_contents()?;
372 if dirstate_file_contents.is_empty() {
373 self.dirstate_parents.set(DirstateParents::NULL);
374 Ok(OwningDirstateMap::new_empty(Vec::new()))
375 } else {
376 let (map, parents) = OwningDirstateMap::new_v1(
377 dirstate_file_contents,
378 identity,
379 )?;
380 self.dirstate_parents.set(parents);
381 Ok(map)
382 }
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");
410 let identity = self.dirstate_identity()?;
411 let dirstate_file_contents = self.dirstate_file_contents()?;
412 if dirstate_file_contents.is_empty() {
413 self.dirstate_parents.set(DirstateParents::NULL);
414 Ok(OwningDirstateMap::new_empty(Vec::new()))
415 } else {
416 let (map, parents) =
417 OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
418 self.dirstate_parents.set(parents);
419 Ok(map)
383 420 }
384 421 }
385 422
@@ -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