1# dirstateguard.py - class to allow restoring dirstate after failure 2# 3# Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> 4# 5# This software may be used and distributed according to the terms of the 6# GNU General Public License version 2 or any later version. 7 8from __future__ import absolute_import 9 10import os 11from .i18n import _ 12 13from . import ( 14 error, 15 narrowspec, 16 requirements, 17 util, 18) 19 20 21class dirstateguard(util.transactional): 22 """Restore dirstate at unexpected failure. 23 24 At the construction, this class does: 25 26 - write current ``repo.dirstate`` out, and 27 - save ``.hg/dirstate`` into the backup file 28 29 This restores ``.hg/dirstate`` from backup file, if ``release()`` 30 is invoked before ``close()``. 31 32 This just removes the backup file at ``close()`` before ``release()``. 33 """ 34 35 def __init__(self, repo, name): 36 self._repo = repo 37 self._active = False 38 self._closed = False 39 40 def getname(prefix): 41 fd, fname = repo.vfs.mkstemp(prefix=prefix) 42 os.close(fd) 43 return fname 44 45 self._backupname = getname(b'dirstate.backup.%s.' % name) 46 repo.dirstate.savebackup(repo.currenttransaction(), self._backupname) 47 # Don't make this the empty string, things may join it with stuff and 48 # blindly try to unlink it, which could be bad. 49 self._narrowspecbackupname = None 50 if requirements.NARROW_REQUIREMENT in repo.requirements: 51 self._narrowspecbackupname = getname( 52 b'narrowspec.backup.%s.' % name 53 ) 54 narrowspec.savewcbackup(repo, self._narrowspecbackupname) 55 self._active = True 56 57 def __del__(self): 58 if self._active: # still active 59 # this may occur, even if this class is used correctly: 60 # for example, releasing other resources like transaction 61 # may raise exception before ``dirstateguard.release`` in 62 # ``release(tr, ....)``. 63 self._abort() 64 65 def close(self): 66 if not self._active: # already inactivated 67 msg = ( 68 _(b"can't close already inactivated backup: %s") 69 % self._backupname 70 ) 71 raise error.Abort(msg) 72 73 self._repo.dirstate.clearbackup( 74 self._repo.currenttransaction(), self._backupname 75 ) 76 if self._narrowspecbackupname: 77 narrowspec.clearwcbackup(self._repo, self._narrowspecbackupname) 78 self._active = False 79 self._closed = True 80 81 def _abort(self): 82 if self._narrowspecbackupname: 83 narrowspec.restorewcbackup(self._repo, self._narrowspecbackupname) 84 self._repo.dirstate.restorebackup( 85 self._repo.currenttransaction(), self._backupname 86 ) 87 self._active = False 88 89 def release(self): 90 if not self._closed: 91 if not self._active: # already inactivated 92 msg = ( 93 _(b"can't release already inactivated backup: %s") 94 % self._backupname 95 ) 96 raise error.Abort(msg) 97 self._abort() 98