# HG changeset patch # User Pierre-Yves David # Date 1661920662 -7200 # Node ID 0705afae62532f5803ac3fbfe85b57db55866727 # Parent 486b8a3831006b51b38534bf1ead0d0b18e6c0d4 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. diff -r 486b8a383100 -r 0705afae6253 mercurial/dirstate.py --- 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""" diff -r 486b8a383100 -r 0705afae6253 mercurial/localrepo.py --- 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()] diff -r 486b8a383100 -r 0705afae6253 tests/test-dirstate.t --- 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