Mercurial > hg
changeset 49467:0705afae6253 stable
dirstate-v2: backup the data file during the transaction (issue6730)
If we backup of the docket, without doing a backup of the data file, we
highly risk restoring a docket pointing to a missing file in the future.
So we now backup the data-file alongside the docket.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 31 Aug 2022 06:37:42 +0200 |
parents | 486b8a383100 |
children | 2905b78fc52e |
files | mercurial/dirstate.py mercurial/localrepo.py tests/test-dirstate.t |
diffstat | 3 files changed, 59 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/dirstate.py Wed Aug 31 05:48:32 2022 +0200 +++ b/mercurial/dirstate.py Wed Aug 31 06:37:42 2022 +0200 @@ -31,6 +31,7 @@ ) from .dirstateutils import ( + docket as docketmod, timestamp, ) @@ -1433,6 +1434,27 @@ else: return self._filename + def data_backup_filename(self, backupname): + if not self._use_dirstate_v2: + return None + return backupname + b'.v2-data' + + def _new_backup_data_filename(self, backupname): + """return a filename to backup a data-file or None""" + if not self._use_dirstate_v2: + return None + data_filename = self._map.docket.data_filename() + return data_filename, self.data_backup_filename(backupname) + + def backup_data_file(self, backupname): + if not self._use_dirstate_v2: + return None + docket = docketmod.DirstateDocket.parse( + self._opener.read(backupname), + self._nodeconstants, + ) + return self.data_backup_filename(backupname), docket.data_filename() + def savebackup(self, tr, backupname): '''Save current dirstate into backup file''' filename = self._actualfilename(tr) @@ -1472,6 +1494,19 @@ self._opener.join(backupname), hardlink=True, ) + data_pair = self._new_backup_data_filename(backupname) + if data_pair is not None: + data_filename, bck_data_filename = data_pair + util.copyfile( + self._opener.join(data_filename), + self._opener.join(bck_data_filename), + hardlink=True, + ) + if tr is not None: + # ensure that pending file written above is unlinked at + # failure, even if tr.writepending isn't invoked until the + # end of this transaction + tr.registertmp(bck_data_filename, location=b'plain') def restorebackup(self, tr, backupname): '''Restore dirstate by backup file''' @@ -1480,14 +1515,29 @@ self.invalidate() filename = self._actualfilename(tr) o = self._opener + data_pair = self.backup_data_file(backupname) if util.samefile(o.join(backupname), o.join(filename)): o.unlink(backupname) else: o.rename(backupname, filename, checkambig=True) + if data_pair is not None: + data_backup, target = data_pair + if o.exists(target) and util.samefile( + o.join(data_backup), o.join(target) + ): + o.unlink(data_backup) + else: + o.rename(data_backup, target, checkambig=True) + def clearbackup(self, tr, backupname): '''Clear backup file''' - self._opener.unlink(backupname) + o = self._opener + data_backup = self.backup_data_file(backupname) + o.unlink(backupname) + + if data_backup is not None: + o.unlink(data_backup[0]) def verify(self, m1, m2): """check the dirstate content again the parent manifest and yield errors"""
--- a/mercurial/localrepo.py Wed Aug 31 05:48:32 2022 +0200 +++ b/mercurial/localrepo.py Wed Aug 31 06:37:42 2022 +0200 @@ -2618,16 +2618,23 @@ return tr def _journalfiles(self): - return ( + first = ( (self.svfs, b'journal'), (self.svfs, b'journal.narrowspec'), (self.vfs, b'journal.narrowspec.dirstate'), (self.vfs, b'journal.dirstate'), + ) + middle = [] + dirstate_data = self.dirstate.data_backup_filename(b'journal.dirstate') + if dirstate_data is not None: + middle.append((self.vfs, dirstate_data)) + end = ( (self.vfs, b'journal.branch'), (self.vfs, b'journal.desc'), (bookmarks.bookmarksvfs(self), b'journal.bookmarks'), (self.svfs, b'journal.phaseroots'), ) + return first + tuple(middle) + end def undofiles(self): return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
--- a/tests/test-dirstate.t Wed Aug 31 05:48:32 2022 +0200 +++ b/tests/test-dirstate.t Wed Aug 31 06:37:42 2022 +0200 @@ -243,11 +243,5 @@ repository tip rolled back to revision 1 (undo commit) working directory now based on revision 1 -#if dirstate-v1 $ hg status A foo -#else - $ hg status - abort: $ENOENT$: '*/.hg/dirstate.*' (glob) (known-bad-output !) - [255] -#endif