1# wctxcleaner.py - check and clean dirty working directory
2#
3# Copyright 2011 Steve Borho <steve@borho.org>
4#
5# This software may be used and distributed according to the terms of the
6# GNU General Public License version 2, incorporated herein by reference.
7
8from __future__ import absolute_import
9
10from .qtcore import (
11    QObject,
12    QThread,
13    pyqtSignal,
14    pyqtSlot,
15)
16from .qtgui import (
17    QMessageBox,
18    QWidget,
19)
20
21from mercurial import (
22    cmdutil,
23    error,
24    hg,
25)
26
27from ..util.i18n import _
28from . import (
29    cmdcore,
30    cmdui,
31    qtlib,
32    thgrepo,
33)
34
35def _checkchanged(repo):
36    try:
37        cmdutil.bailifchanged(repo)
38        return False
39    except error.Abort:
40        return True
41
42
43class CheckThread(QThread):
44    def __init__(self, repo, parent):
45        QThread.__init__(self, parent)
46        self.repo = hg.repository(repo.ui, repo.root)
47        self.results = (False, 1)
48        self.canceled = False
49
50    def run(self):
51        self.repo.invalidate()
52        self.repo.invalidatedirstate()
53        unresolved = False
54        for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
55            if self.canceled:
56                return
57            if status == b'u':
58                unresolved = True
59                break
60        wctx = self.repo[None]
61        try:
62            dirty = _checkchanged(self.repo) or unresolved
63            self.results = (dirty, len(wctx.parents()))
64        except EnvironmentError:
65            self.results = (True, len(wctx.parents()))
66
67    def cancel(self):
68        self.canceled = True
69
70
71class WctxCleaner(QObject):
72
73    checkStarted = pyqtSignal()
74    checkFinished = pyqtSignal(bool, int)  # clean, parents
75
76    def __init__(self, repoagent, parent=None):
77        super(WctxCleaner, self).__init__(parent)
78        assert parent is None or isinstance(parent, QWidget), parent
79        self._repoagent = repoagent
80        self._cmdsession = cmdcore.nullCmdSession()
81        self._checkth = CheckThread(repoagent.rawRepo(), self)
82        self._checkth.started.connect(self.checkStarted)
83        self._checkth.finished.connect(self._onCheckFinished)
84        self._clean = False
85
86    @pyqtSlot()
87    def check(self):
88        """Check states of working directory asynchronously"""
89        if self._checkth.isRunning():
90            return
91        self._checkth.start()
92
93    def cancelCheck(self):
94        self._checkth.cancel()
95        self._checkth.wait()
96
97    def isChecking(self):
98        return self._checkth.isRunning()
99
100    def isCheckCanceled(self):
101        return self._checkth.canceled
102
103    def isClean(self):
104        return self._clean
105
106    @pyqtSlot()
107    def _onCheckFinished(self):
108        dirty, parents = self._checkth.results
109        self._clean = not dirty
110        self.checkFinished.emit(not dirty, parents)
111
112    @pyqtSlot(str)
113    def runCleaner(self, cmd):
114        """Clean working directory by the specified action"""
115        cmd = str(cmd)
116        if cmd == 'commit':
117            self.launchCommitDialog()
118        elif cmd == 'shelve':
119            self.launchShelveDialog()
120        elif cmd.startswith('discard'):
121            confirm = cmd != 'discard:noconfirm'
122            self.discardChanges(confirm)
123        else:
124            raise ValueError('unknown command: %s' % cmd)
125
126    def launchCommitDialog(self):
127        from tortoisehg.hgqt import commit
128        dlg = commit.CommitDialog(self._repoagent, [], {}, self.parent())
129        dlg.finished.connect(dlg.deleteLater)
130        dlg.exec_()
131        self.check()
132
133    def launchShelveDialog(self):
134        from tortoisehg.hgqt import shelve
135        dlg = shelve.ShelveDialog(self._repoagent, self.parent())
136        dlg.finished.connect(dlg.deleteLater)
137        dlg.exec_()
138        self.check()
139
140    def discardChanges(self, confirm=True):
141        if confirm:
142            labels = [(QMessageBox.Yes, _('&Discard')),
143                      (QMessageBox.No, _('Cancel'))]
144            if not qtlib.QuestionMsgBox(_('Confirm Discard'),
145                     _('Discard outstanding changes to working directory?'),
146                     labels=labels, parent=self.parent()):
147                return
148
149        cmdline = ['update', '--clean', '--rev', '.']
150        self._cmdsession = sess = self._repoagent.runCommand(cmdline, self)
151        sess.commandFinished.connect(self._onCommandFinished)
152
153    @pyqtSlot(int)
154    def _onCommandFinished(self, ret):
155        if ret == 0:
156            self.check()
157        else:
158            cmdui.errorMessageBox(self._cmdsession, self.parent())
159