##// 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 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6
6
7 import struct
7 from .i18n import _
8 from .i18n import _
8
9
9 from . import (
10 from . import (
@@ -151,9 +152,15 b' class _dirstatemapcommon:'
151 b'dirstate only has a docket in v2 format'
152 b'dirstate only has a docket in v2 format'
152 )
153 )
153 self._set_identity()
154 self._set_identity()
154 self._docket = docketmod.DirstateDocket.parse(
155 try:
155 self._readdirstatefile(), self._nodeconstants
156 self._docket = docketmod.DirstateDocket.parse(
156 )
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 return self._docket
164 return self._docket
158
165
159 def _read_v2_data(self):
166 def _read_v2_data(self):
@@ -176,11 +183,23 b' class _dirstatemapcommon:'
176 return self._opener.read(self.docket.data_filename())
183 return self._opener.read(self.docket.data_filename())
177
184
178 def write_v2_no_append(self, tr, st, meta, packed):
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 new_docket = docketmod.DirstateDocket.with_new_uuid(
199 new_docket = docketmod.DirstateDocket.with_new_uuid(
181 self.parents(), len(packed), meta
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 raise error.ProgrammingError(b'dirstate docket name collision')
203 raise error.ProgrammingError(b'dirstate docket name collision')
185 data_filename = new_docket.data_filename()
204 data_filename = new_docket.data_filename()
186 self._opener.write(data_filename, packed)
205 self._opener.write(data_filename, packed)
@@ -194,7 +213,7 b' class _dirstatemapcommon:'
194 st.close()
213 st.close()
195 # Remove the old data file after the new docket pointing to
214 # Remove the old data file after the new docket pointing to
196 # the new data file was written.
215 # the new data file was written.
197 if old_docket.uuid:
216 if old_docket is not None and old_docket.uuid:
198 data_filename = old_docket.data_filename()
217 data_filename = old_docket.data_filename()
199 if tr is not None:
218 if tr is not None:
200 tr.addbackup(data_filename, location=b'plain')
219 tr.addbackup(data_filename, location=b'plain')
@@ -211,28 +230,40 b' class _dirstatemapcommon:'
211 def parents(self):
230 def parents(self):
212 if not self._parents:
231 if not self._parents:
213 if self._use_dirstate_v2:
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 else:
240 else:
216 read_len = self._nodelen * 2
241 self._v1_parents()
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 )
233
242
234 return self._parents
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 class dirstatemap(_dirstatemapcommon):
268 class dirstatemap(_dirstatemapcommon):
238 """Map encapsulating the dirstate's contents.
269 """Map encapsulating the dirstate's contents.
@@ -330,11 +361,17 b' class dirstatemap(_dirstatemapcommon):'
330 def read(self):
361 def read(self):
331 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
362 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
332 if self._use_dirstate_v2:
363 if self._use_dirstate_v2:
333
364 try:
334 if not self.docket.uuid:
365 self.docket
335 return
366 except error.CorruptedDirstate:
336 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
367 # fall back to dirstate-v1 if we fail to read v2
337 st = self._read_v2_data()
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 else:
375 else:
339 self._set_identity()
376 self._set_identity()
340 st = self._readdirstatefile()
377 st = self._readdirstatefile()
@@ -365,10 +402,17 b' class dirstatemap(_dirstatemapcommon):'
365 #
402 #
366 # (we cannot decorate the function directly since it is in a C module)
403 # (we cannot decorate the function directly since it is in a C module)
367 if self._use_dirstate_v2:
404 if self._use_dirstate_v2:
368 p = self.docket.parents
405 try:
369 meta = self.docket.tree_metadata
406 self.docket
370 parse_dirstate = util.nogc(v2.parse_dirstate)
407 except error.CorruptedDirstate:
371 parse_dirstate(self._map, self.copymap, st, meta)
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 else:
416 else:
373 parse_dirstate = util.nogc(parsers.parse_dirstate)
417 parse_dirstate = util.nogc(parsers.parse_dirstate)
374 p = parse_dirstate(self._map, self.copymap, st)
418 p = parse_dirstate(self._map, self.copymap, st)
@@ -597,38 +641,37 b' if rustmod is not None:'
597
641
598 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
642 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
599 if self._use_dirstate_v2:
643 if self._use_dirstate_v2:
600 self.docket # load the data if needed
644 try:
601 inode = (
645 self.docket
602 self.identity.stat.st_ino
646 except error.CorruptedDirstate as e:
603 if self.identity is not None
647 # fall back to dirstate-v1 if we fail to read v2
604 and self.identity.stat is not None
648 parents = self._v1_map(e)
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()
611 else:
649 else:
612 data = self._read_v2_data()
650 parents = self.docket.parents
613 self._map = rustmod.DirstateMap.new_v2(
651 inode = (
614 data,
652 self.identity.stat.st_ino
615 self.docket.data_size,
653 if self.identity is not None
616 self.docket.tree_metadata,
654 and self.identity.stat is not None
617 self.docket.uuid,
655 else None
618 inode,
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 else:
673 else:
622 self._set_identity()
674 parents = self._v1_map()
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 )
632
675
633 if parents and not self._dirtyparents:
676 if parents and not self._dirtyparents:
634 self.setparents(*parents)
677 self.setparents(*parents)
@@ -638,6 +681,23 b' if rustmod is not None:'
638 self.get = self._map.get
681 self.get = self._map.get
639 return self._map
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 @property
701 @property
642 def copymap(self):
702 def copymap(self):
643 return self._map.copymap()
703 return self._map.copymap()
@@ -696,9 +756,15 b' if rustmod is not None:'
696 self._dirtyparents = False
756 self._dirtyparents = False
697 return
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 # We can only append to an existing data file if there is one
766 # We can only append to an existing data file if there is one
700 write_mode = self._write_mode
767 if docket is None or docket.uuid is None:
701 if self.docket.uuid is None:
702 write_mode = WRITE_MODE_FORCE_NEW
768 write_mode = WRITE_MODE_FORCE_NEW
703 packed, meta, append = self._map.write_v2(write_mode)
769 packed, meta, append = self._map.write_v2(write_mode)
704 if append:
770 if append:
@@ -650,6 +650,13 b' class CorruptedState(Exception):'
650 __bytes__ = _tobytes
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 class PeerTransportError(Abort):
660 class PeerTransportError(Abort):
654 """Transport-level I/O error when communicating with a peer repo."""
661 """Transport-level I/O error when communicating with a peer repo."""
655
662
@@ -238,9 +238,10 b' impl Repo {'
238 /// a dirstate-v2 file will indeed be found, but in rare cases (like the
238 /// a dirstate-v2 file will indeed be found, but in rare cases (like the
239 /// upgrade mechanism being cut short), the on-disk version will be a
239 /// upgrade mechanism being cut short), the on-disk version will be a
240 /// v1 file.
240 /// v1 file.
241 /// Semantically, having a requirement only means that a client should be
241 /// Semantically, having a requirement only means that a client cannot
242 /// able to understand the repo *if* it uses the requirement, but not that
242 /// properly understand or properly update the repo if it lacks the support
243 /// the requirement is actually used.
243 /// for the required feature, but not that that feature is actually used
244 /// in all occasions.
244 pub fn use_dirstate_v2(&self) -> bool {
245 pub fn use_dirstate_v2(&self) -> bool {
245 self.requirements
246 self.requirements
246 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
247 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
@@ -287,9 +288,20 b' impl Repo {'
287 let parents = if dirstate.is_empty() {
288 let parents = if dirstate.is_empty() {
288 DirstateParents::NULL
289 DirstateParents::NULL
289 } else if self.use_dirstate_v2() {
290 } else if self.use_dirstate_v2() {
290 let docket =
291 let docket_res =
291 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
292 crate::dirstate_tree::on_disk::read_docket(&dirstate);
292 docket.parents()
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 } else {
305 } else {
294 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
306 *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
295 };
307 };
@@ -317,10 +329,30 b' impl Repo {'
317 self.dirstate_parents.set(DirstateParents::NULL);
329 self.dirstate_parents.set(DirstateParents::NULL);
318 Ok((identity, None, 0))
330 Ok((identity, None, 0))
319 } else {
331 } else {
320 let docket =
332 let docket_res =
321 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
333 crate::dirstate_tree::on_disk::read_docket(&dirstate);
322 self.dirstate_parents.set(docket.parents());
334 match docket_res {
323 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
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 continue;
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 Err(DirstateError::Common(error))
402 Err(DirstateError::Common(error))
365 } else {
403 } else {
366 debug_wait_for_file_or_print(
404 self.new_dirstate_map_v1()
367 self.config(),
405 }
368 "dirstate.pre-read-file",
406 }
369 );
407
370 let identity = self.dirstate_identity()?;
408 fn new_dirstate_map_v1(&self) -> Result<OwningDirstateMap, DirstateError> {
371 let dirstate_file_contents = self.dirstate_file_contents()?;
409 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
372 if dirstate_file_contents.is_empty() {
410 let identity = self.dirstate_identity()?;
373 self.dirstate_parents.set(DirstateParents::NULL);
411 let dirstate_file_contents = self.dirstate_file_contents()?;
374 Ok(OwningDirstateMap::new_empty(Vec::new()))
412 if dirstate_file_contents.is_empty() {
375 } else {
413 self.dirstate_parents.set(DirstateParents::NULL);
376 let (map, parents) = OwningDirstateMap::new_v1(
414 Ok(OwningDirstateMap::new_empty(Vec::new()))
377 dirstate_file_contents,
415 } else {
378 identity,
416 let (map, parents) =
379 )?;
417 OwningDirstateMap::new_v1(dirstate_file_contents, identity)?;
380 self.dirstate_parents.set(parents);
418 self.dirstate_parents.set(parents);
381 Ok(map)
419 Ok(map)
382 }
383 }
420 }
384 }
421 }
385
422
@@ -20,7 +20,7 b' Copy v1 dirstate'
20
20
21 Upgrade it to v2
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 added: dirstate-v2
24 added: dirstate-v2
25 $ hg debugrequires | grep dirstate
25 $ hg debugrequires | grep dirstate
26 dirstate-v2
26 dirstate-v2
@@ -35,9 +35,15 b' Manually reset to dirstate v1 to simulat'
35
35
36 There should be no errors, but a v2 dirstate should be written back to disk
36 There should be no errors, but a v2 dirstate should be written back to disk
37 $ hg st
37 $ hg st
38 abort: dirstate-v2 parse error: when reading docket, Expected at least * bytes, got * (glob) (known-bad-output !)
39 [255]
40 $ ls -1 .hg/dirstate*
38 $ ls -1 .hg/dirstate*
41 .hg/dirstate
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