Mercurial > hg
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" |