Mercurial > public > mercurial-scm > hg-stable
diff mercurial/dirstatemap.py @ 50694:a41eeb877d07
branching: merge with stable
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Sun, 18 Jun 2023 00:09:39 +0200 |
parents | bf16ef96defe |
children | bfbd84c57bda |
line wrap: on
line diff
--- a/mercurial/dirstatemap.py Tue Jun 20 02:36:52 2023 +0200 +++ b/mercurial/dirstatemap.py Sun Jun 18 00:09:39 2023 +0200 @@ -4,6 +4,7 @@ # GNU General Public License version 2 or any later version. +import struct from .i18n import _ from . import ( @@ -151,9 +152,15 @@ b'dirstate only has a docket in v2 format' ) self._set_identity() - self._docket = docketmod.DirstateDocket.parse( - self._readdirstatefile(), self._nodeconstants - ) + try: + self._docket = docketmod.DirstateDocket.parse( + self._readdirstatefile(), self._nodeconstants + ) + except struct.error: + self._ui.debug(b"failed to read dirstate-v2 data") + raise error.CorruptedDirstate( + b"failed to read dirstate-v2 data" + ) return self._docket def _read_v2_data(self): @@ -176,11 +183,23 @@ return self._opener.read(self.docket.data_filename()) def write_v2_no_append(self, tr, st, meta, packed): - old_docket = self.docket + try: + old_docket = self.docket + except error.CorruptedDirstate: + # This means we've identified a dirstate-v1 file on-disk when we + # were expecting a dirstate-v2 docket. We've managed to recover + # from that unexpected situation, and now we want to write back a + # dirstate-v2 file to make the on-disk situation right again. + # + # This shouldn't be triggered since `self.docket` is cached and + # we would have called parents() or read() first, but it's here + # just in case. + old_docket = None + new_docket = docketmod.DirstateDocket.with_new_uuid( self.parents(), len(packed), meta ) - if old_docket.uuid == new_docket.uuid: + if old_docket is not None and old_docket.uuid == new_docket.uuid: raise error.ProgrammingError(b'dirstate docket name collision') data_filename = new_docket.data_filename() self._opener.write(data_filename, packed) @@ -194,7 +213,7 @@ st.close() # Remove the old data file after the new docket pointing to # the new data file was written. - if old_docket.uuid: + if old_docket is not None and old_docket.uuid: data_filename = old_docket.data_filename() if tr is not None: tr.addbackup(data_filename, location=b'plain') @@ -211,28 +230,40 @@ def parents(self): if not self._parents: if self._use_dirstate_v2: - self._parents = self.docket.parents + try: + self.docket + except error.CorruptedDirstate as e: + # fall back to dirstate-v1 if we fail to read v2 + self._v1_parents(e) + else: + self._parents = self.docket.parents else: - read_len = self._nodelen * 2 - st = self._readdirstatefile(read_len) - l = len(st) - if l == read_len: - self._parents = ( - st[: self._nodelen], - st[self._nodelen : 2 * self._nodelen], - ) - elif l == 0: - self._parents = ( - self._nodeconstants.nullid, - self._nodeconstants.nullid, - ) - else: - raise error.Abort( - _(b'working directory state appears damaged!') - ) + self._v1_parents() return self._parents + def _v1_parents(self, from_v2_exception=None): + read_len = self._nodelen * 2 + st = self._readdirstatefile(read_len) + l = len(st) + if l == read_len: + self._parents = ( + st[: self._nodelen], + st[self._nodelen : 2 * self._nodelen], + ) + elif l == 0: + self._parents = ( + self._nodeconstants.nullid, + self._nodeconstants.nullid, + ) + else: + hint = None + if from_v2_exception is not None: + hint = _(b"falling back to dirstate-v1 from v2 also failed") + raise error.Abort( + _(b'working directory state appears damaged!'), hint + ) + class dirstatemap(_dirstatemapcommon): """Map encapsulating the dirstate's contents. @@ -330,11 +361,17 @@ def read(self): testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') if self._use_dirstate_v2: - - if not self.docket.uuid: - return - testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') - st = self._read_v2_data() + try: + self.docket + except error.CorruptedDirstate: + # fall back to dirstate-v1 if we fail to read v2 + self._set_identity() + st = self._readdirstatefile() + else: + if not self.docket.uuid: + return + testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') + st = self._read_v2_data() else: self._set_identity() st = self._readdirstatefile() @@ -365,10 +402,17 @@ # # (we cannot decorate the function directly since it is in a C module) if self._use_dirstate_v2: - p = self.docket.parents - meta = self.docket.tree_metadata - parse_dirstate = util.nogc(v2.parse_dirstate) - parse_dirstate(self._map, self.copymap, st, meta) + try: + self.docket + except error.CorruptedDirstate: + # fall back to dirstate-v1 if we fail to parse v2 + parse_dirstate = util.nogc(parsers.parse_dirstate) + p = parse_dirstate(self._map, self.copymap, st) + else: + p = self.docket.parents + meta = self.docket.tree_metadata + parse_dirstate = util.nogc(v2.parse_dirstate) + parse_dirstate(self._map, self.copymap, st, meta) else: parse_dirstate = util.nogc(parsers.parse_dirstate) p = parse_dirstate(self._map, self.copymap, st) @@ -597,38 +641,37 @@ testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file') if self._use_dirstate_v2: - self.docket # load the data if needed - inode = ( - self.identity.stat.st_ino - if self.identity is not None - and self.identity.stat is not None - else None - ) - testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') - if not self.docket.uuid: - data = b'' - self._map = rustmod.DirstateMap.new_empty() + try: + self.docket + except error.CorruptedDirstate as e: + # fall back to dirstate-v1 if we fail to read v2 + parents = self._v1_map(e) else: - data = self._read_v2_data() - self._map = rustmod.DirstateMap.new_v2( - data, - self.docket.data_size, - self.docket.tree_metadata, - self.docket.uuid, - inode, + parents = self.docket.parents + inode = ( + self.identity.stat.st_ino + if self.identity is not None + and self.identity.stat is not None + else None + ) + testing.wait_on_cfg( + self._ui, b'dirstate.post-docket-read-file' ) - parents = self.docket.parents + if not self.docket.uuid: + data = b'' + self._map = rustmod.DirstateMap.new_empty() + else: + data = self._read_v2_data() + self._map = rustmod.DirstateMap.new_v2( + data, + self.docket.data_size, + self.docket.tree_metadata, + self.docket.uuid, + inode, + ) + parents = self.docket.parents else: - self._set_identity() - inode = ( - self.identity.stat.st_ino - if self.identity is not None - and self.identity.stat is not None - else None - ) - self._map, parents = rustmod.DirstateMap.new_v1( - self._readdirstatefile(), inode - ) + parents = self._v1_map() if parents and not self._dirtyparents: self.setparents(*parents) @@ -638,6 +681,23 @@ self.get = self._map.get return self._map + def _v1_map(self, from_v2_exception=None): + self._set_identity() + inode = ( + self.identity.stat.st_ino + if self.identity is not None and self.identity.stat is not None + else None + ) + try: + self._map, parents = rustmod.DirstateMap.new_v1( + self._readdirstatefile(), inode + ) + except OSError as e: + if from_v2_exception is not None: + raise e from from_v2_exception + raise + return parents + @property def copymap(self): return self._map.copymap() @@ -696,9 +756,15 @@ self._dirtyparents = False return + write_mode = self._write_mode + try: + docket = self.docket + except error.CorruptedDirstate: + # fall back to dirstate-v1 if we fail to parse v2 + docket = None + # We can only append to an existing data file if there is one - write_mode = self._write_mode - if self.docket.uuid is None: + if docket is None or docket.uuid is None: write_mode = WRITE_MODE_FORCE_NEW packed, meta, append = self._map.write_v2(write_mode) if append: