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 |
|
|
|
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 |
|
|
|
335 | return | |
|
336 | testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') | |
|
337 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
613 | self._map = rustmod.DirstateMap.new_v2( | |
|
614 |
|
|
|
615 |
self. |
|
|
616 |
self.d |
|
|
617 |
|
|
|
618 |
|
|
|
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 |
|
|
|
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._ |
|
|
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) |
|
|
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 |
_ => |
|
|
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 |
|
|
|
377 | dirstate_file_contents, | |
|
378 | identity, | |
|
379 | )?; | |
|
380 |
|
|
|
381 |
|
|
|
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) |
|
|
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