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