comparison mercurial/dirstate.py @ 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 c2092612c424
children 6193e846cb65
comparison
equal deleted inserted replaced
49466:486b8a383100 49467:0705afae6253
29 scmutil, 29 scmutil,
30 util, 30 util,
31 ) 31 )
32 32
33 from .dirstateutils import ( 33 from .dirstateutils import (
34 docket as docketmod,
34 timestamp, 35 timestamp,
35 ) 36 )
36 37
37 from .interfaces import ( 38 from .interfaces import (
38 dirstate as intdirstate, 39 dirstate as intdirstate,
1431 if tr: 1432 if tr:
1432 return self._pendingfilename 1433 return self._pendingfilename
1433 else: 1434 else:
1434 return self._filename 1435 return self._filename
1435 1436
1437 def data_backup_filename(self, backupname):
1438 if not self._use_dirstate_v2:
1439 return None
1440 return backupname + b'.v2-data'
1441
1442 def _new_backup_data_filename(self, backupname):
1443 """return a filename to backup a data-file or None"""
1444 if not self._use_dirstate_v2:
1445 return None
1446 data_filename = self._map.docket.data_filename()
1447 return data_filename, self.data_backup_filename(backupname)
1448
1449 def backup_data_file(self, backupname):
1450 if not self._use_dirstate_v2:
1451 return None
1452 docket = docketmod.DirstateDocket.parse(
1453 self._opener.read(backupname),
1454 self._nodeconstants,
1455 )
1456 return self.data_backup_filename(backupname), docket.data_filename()
1457
1436 def savebackup(self, tr, backupname): 1458 def savebackup(self, tr, backupname):
1437 '''Save current dirstate into backup file''' 1459 '''Save current dirstate into backup file'''
1438 filename = self._actualfilename(tr) 1460 filename = self._actualfilename(tr)
1439 assert backupname != filename 1461 assert backupname != filename
1440 1462
1470 util.copyfile( 1492 util.copyfile(
1471 self._opener.join(filename), 1493 self._opener.join(filename),
1472 self._opener.join(backupname), 1494 self._opener.join(backupname),
1473 hardlink=True, 1495 hardlink=True,
1474 ) 1496 )
1497 data_pair = self._new_backup_data_filename(backupname)
1498 if data_pair is not None:
1499 data_filename, bck_data_filename = data_pair
1500 util.copyfile(
1501 self._opener.join(data_filename),
1502 self._opener.join(bck_data_filename),
1503 hardlink=True,
1504 )
1505 if tr is not None:
1506 # ensure that pending file written above is unlinked at
1507 # failure, even if tr.writepending isn't invoked until the
1508 # end of this transaction
1509 tr.registertmp(bck_data_filename, location=b'plain')
1475 1510
1476 def restorebackup(self, tr, backupname): 1511 def restorebackup(self, tr, backupname):
1477 '''Restore dirstate by backup file''' 1512 '''Restore dirstate by backup file'''
1478 # this "invalidate()" prevents "wlock.release()" from writing 1513 # this "invalidate()" prevents "wlock.release()" from writing
1479 # changes of dirstate out after restoring from backup file 1514 # changes of dirstate out after restoring from backup file
1480 self.invalidate() 1515 self.invalidate()
1481 filename = self._actualfilename(tr) 1516 filename = self._actualfilename(tr)
1482 o = self._opener 1517 o = self._opener
1518 data_pair = self.backup_data_file(backupname)
1483 if util.samefile(o.join(backupname), o.join(filename)): 1519 if util.samefile(o.join(backupname), o.join(filename)):
1484 o.unlink(backupname) 1520 o.unlink(backupname)
1485 else: 1521 else:
1486 o.rename(backupname, filename, checkambig=True) 1522 o.rename(backupname, filename, checkambig=True)
1487 1523
1524 if data_pair is not None:
1525 data_backup, target = data_pair
1526 if o.exists(target) and util.samefile(
1527 o.join(data_backup), o.join(target)
1528 ):
1529 o.unlink(data_backup)
1530 else:
1531 o.rename(data_backup, target, checkambig=True)
1532
1488 def clearbackup(self, tr, backupname): 1533 def clearbackup(self, tr, backupname):
1489 '''Clear backup file''' 1534 '''Clear backup file'''
1490 self._opener.unlink(backupname) 1535 o = self._opener
1536 data_backup = self.backup_data_file(backupname)
1537 o.unlink(backupname)
1538
1539 if data_backup is not None:
1540 o.unlink(data_backup[0])
1491 1541
1492 def verify(self, m1, m2): 1542 def verify(self, m1, m2):
1493 """check the dirstate content again the parent manifest and yield errors""" 1543 """check the dirstate content again the parent manifest and yield errors"""
1494 missing_from_p1 = b"%s in state %s, but not in manifest1\n" 1544 missing_from_p1 = b"%s in state %s, but not in manifest1\n"
1495 unexpected_in_p1 = b"%s in state %s, but also in manifest1\n" 1545 unexpected_in_p1 = b"%s in state %s, but also in manifest1\n"