annotate hgrunner.cpp @ 109:1721c580c10e

* Add a queueing mechanism for Hg actions, instead of refusing to start an action if something else is already happening. This is essential now that actions can be prompted by asynchronous events (e.g. filesystem watcher). * Make Revert behave sensibly
author Chris Cannam
date Fri, 26 Nov 2010 12:48:29 +0000
parents fdca34c989c0
children 0f039d3cc38e
rev   line source
Chris@57 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@57 2
Chris@57 3 /*
Chris@57 4 EasyMercurial
Chris@57 5
Chris@57 6 Based on HgExplorer by Jari Korhonen
Chris@57 7 Copyright (c) 2010 Jari Korhonen
Chris@57 8 Copyright (c) 2010 Chris Cannam
Chris@57 9 Copyright (c) 2010 Queen Mary, University of London
Chris@57 10
Chris@57 11 This program is free software; you can redistribute it and/or
Chris@57 12 modify it under the terms of the GNU General Public License as
Chris@57 13 published by the Free Software Foundation; either version 2 of the
Chris@57 14 License, or (at your option) any later version. See the file
Chris@57 15 COPYING included with this distribution for more information.
Chris@57 16 */
jtkorhonen@0 17
jtkorhonen@0 18 #include "hgrunner.h"
Chris@62 19 #include "common.h"
Chris@57 20 #include "debug.h"
Chris@50 21
Chris@50 22 #include <QPushButton>
Chris@50 23 #include <QListWidget>
Chris@50 24 #include <QDialog>
Chris@50 25 #include <QLabel>
Chris@50 26 #include <QVBoxLayout>
Chris@62 27 #include <QSettings>
Chris@75 28 #include <QInputDialog>
jtkorhonen@0 29
Chris@43 30 #include <iostream>
jtkorhonen@0 31 #include <unistd.h>
Chris@75 32 #include <errno.h>
Chris@75 33 #include <stdio.h>
jtkorhonen@0 34
Chris@76 35 #ifndef Q_OS_WIN32
Chris@80 36 #ifdef Q_OS_MAC
Chris@80 37 #include <util.h>
Chris@80 38 #else
Chris@76 39 #include <pty.h>
Chris@76 40 #endif
Chris@80 41 #endif
Chris@76 42
jtkorhonen@0 43 HgRunner::HgRunner(QWidget * parent): QProgressBar(parent)
jtkorhonen@0 44 {
Chris@84 45 m_proc = new QProcess(this);
jtkorhonen@0 46
cannam@45 47 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
cannam@45 48 env.insert("LANG", "en_US.utf8");
cannam@45 49 env.insert("LC_ALL", "en_US.utf8");
Chris@104 50 env.insert("HGPLAIN", "1");
Chris@84 51 m_proc->setProcessEnvironment(env);
Chris@84 52
Chris@84 53 m_proc->setProcessChannelMode(QProcess::MergedChannels);
cannam@45 54
jtkorhonen@0 55 setTextVisible(false);
jtkorhonen@0 56 setVisible(false);
Chris@84 57 m_isRunning = false;
jtkorhonen@0 58
Chris@84 59 m_output.clear();
jtkorhonen@0 60
Chris@84 61 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
Chris@84 62 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@62 63 this, SLOT(finished(int, QProcess::ExitStatus)));
Chris@84 64 connect(m_proc, SIGNAL(readyRead()), this, SLOT(dataReady()));
jtkorhonen@0 65 }
jtkorhonen@0 66
jtkorhonen@0 67 HgRunner::~HgRunner()
jtkorhonen@0 68 {
Chris@84 69 if (m_ptySlaveFilename != "") {
Chris@84 70 ::close(m_ptyMasterFd);
Chris@75 71 }
Chris@84 72 delete m_proc;
jtkorhonen@0 73 }
jtkorhonen@0 74
Chris@109 75 void HgRunner::requestAction(HgAction action)
Chris@109 76 {
Chris@109 77 DEBUG << "requestAction " << action.action << endl;
Chris@109 78 bool pushIt = true;
Chris@109 79 if (m_queue.empty()) {
Chris@109 80 if (action == m_currentAction) {
Chris@109 81 // this request is identical to the thing we're executing
Chris@109 82 DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
Chris@109 83 pushIt = false;
Chris@109 84 }
Chris@109 85 } else {
Chris@109 86 HgAction last = m_queue.back();
Chris@109 87 if (action == last) {
Chris@109 88 // this request is identical to the previous thing we
Chris@109 89 // queued which we haven't executed yet
Chris@109 90 DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
Chris@109 91 pushIt = false;
Chris@109 92 }
Chris@109 93 }
Chris@109 94 if (pushIt) m_queue.push_back(action);
Chris@109 95 checkQueue();
Chris@109 96 }
Chris@109 97
Chris@62 98 QString HgRunner::getHgBinaryName()
Chris@62 99 {
Chris@62 100 QSettings settings;
Chris@77 101 QString hg = settings.value("hgbinary", "").toString();
Chris@77 102 if (hg == "") {
Chris@77 103 hg = findExecutable("hg");
Chris@77 104 }
Chris@77 105 if (hg != "hg") {
Chris@77 106 settings.setValue("hgbinary", hg);
Chris@77 107 }
Chris@62 108 return hg;
Chris@62 109 }
Chris@62 110
jtkorhonen@0 111 void HgRunner::started()
jtkorhonen@0 112 {
Chris@104 113 DEBUG << "started" << endl;
Chris@75 114 /*
Chris@104 115 m_proc->write("blah\n");
Chris@104 116 m_proc->write("blah\n");
Chris@104 117 m_proc -> closeWriteChannel();
Chris@75 118 */
jtkorhonen@0 119 }
jtkorhonen@0 120
Chris@84 121 void HgRunner::noteUsername(QString name)
Chris@75 122 {
Chris@84 123 m_userName = name;
Chris@75 124 }
Chris@75 125
Chris@84 126 void HgRunner::noteRealm(QString realm)
Chris@75 127 {
Chris@84 128 m_realm = realm;
Chris@84 129 }
Chris@84 130
Chris@84 131 void HgRunner::getUsername()
Chris@84 132 {
Chris@84 133 if (m_procInput) {
Chris@84 134 bool ok = false;
Chris@84 135 QString prompt = tr("User name:");
Chris@84 136 if (m_realm != "") {
Chris@84 137 prompt = tr("User name for \"%1\":").arg(m_realm);
Chris@84 138 }
Chris@84 139 QString pwd = QInputDialog::getText
Chris@84 140 (qobject_cast<QWidget *>(parent()),
Chris@84 141 tr("Enter user name"), prompt,
Chris@84 142 QLineEdit::Normal, QString(), &ok);
Chris@84 143 if (ok) {
Chris@84 144 m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
Chris@84 145 m_procInput->flush();
Chris@84 146 return;
Chris@84 147 }
Chris@84 148 }
Chris@84 149 // user cancelled or something went wrong
Chris@84 150 killCurrentCommand();
Chris@84 151 }
Chris@84 152
Chris@84 153 void HgRunner::getPassword()
Chris@84 154 {
Chris@84 155 if (m_procInput) {
Chris@84 156 bool ok = false;
Chris@84 157 QString prompt = tr("Password:");
Chris@84 158 if (m_userName != "") {
Chris@84 159 if (m_realm != "") {
Chris@84 160 prompt = tr("Password for \"%1\" at \"%2\":")
Chris@84 161 .arg(m_userName).arg(m_realm);
Chris@75 162 } else {
Chris@84 163 prompt = tr("Password for user \"%1\":")
Chris@84 164 .arg(m_userName);
Chris@75 165 }
Chris@75 166 }
Chris@84 167 QString pwd = QInputDialog::getText
Chris@84 168 (qobject_cast<QWidget *>(parent()),
Chris@84 169 tr("Enter password"), prompt,
Chris@84 170 QLineEdit::Password, QString(), &ok);
Chris@84 171 if (ok) {
Chris@84 172 m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
Chris@84 173 m_procInput->flush();
Chris@84 174 return;
Chris@84 175 }
Chris@75 176 }
Chris@84 177 // user cancelled or something went wrong
Chris@84 178 killCurrentCommand();
Chris@84 179 }
Chris@84 180
Chris@84 181 void HgRunner::checkPrompts(QString chunk)
Chris@84 182 {
Chris@93 183 //DEBUG << "checkPrompts: " << chunk << endl;
Chris@84 184
Chris@84 185 QString text = chunk.trimmed();
Chris@84 186 QString lower = text.toLower();
Chris@84 187 if (lower.endsWith("password:")) {
Chris@84 188 getPassword();
Chris@84 189 return;
Chris@84 190 }
Chris@84 191 if (lower.endsWith("user:")) {
Chris@84 192 getUsername();
Chris@84 193 return;
Chris@84 194 }
Chris@84 195 QRegExp userRe("\\buser:\\s*([^\\s]+)");
Chris@84 196 if (userRe.indexIn(text) >= 0) {
Chris@84 197 noteUsername(userRe.cap(1));
Chris@84 198 }
Chris@84 199 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
Chris@84 200 if (realmRe.indexIn(text) >= 0) {
Chris@84 201 noteRealm(realmRe.cap(1));
Chris@84 202 }
Chris@84 203 }
Chris@84 204
Chris@84 205 void HgRunner::dataReady()
Chris@84 206 {
Chris@84 207 DEBUG << "dataReady" << endl;
Chris@84 208 QString chunk = QString::fromUtf8(m_proc->readAll());
Chris@84 209 m_output += chunk;
Chris@84 210 checkPrompts(chunk);
Chris@75 211 }
Chris@75 212
jtkorhonen@0 213 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
jtkorhonen@0 214 {
Chris@109 215 // Save the current action and reset m_currentAction before we
Chris@109 216 // emit a signal to mark the completion; otherwise we may be
Chris@109 217 // resetting the action after a slot has already tried to set it
Chris@109 218 // to something else to start a new action
Chris@109 219
Chris@109 220 HgAction completedAction = m_currentAction;
Chris@109 221
Chris@84 222 m_isRunning = false;
Chris@109 223 m_currentAction = HgAction();
Chris@84 224
Chris@84 225 closeProcInput();
jtkorhonen@0 226
Chris@109 227 if (completedAction.action == ACT_NONE) {
Chris@109 228 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
Chris@109 229 }
Chris@109 230
Chris@74 231 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
Chris@84 232 DEBUG << "HgRunner::finished: Command completed successfully" << endl;
Chris@109 233 //!!! NB this is all output not stdout as it should be
Chris@109 234 emit commandCompleted(completedAction, m_output);
Chris@62 235 } else {
Chris@84 236 DEBUG << "HgRunner::finished: Command failed" << endl;
Chris@109 237 //!!! NB this is all output not stderr as it should be
Chris@109 238 emit commandFailed(completedAction, m_output);
jtkorhonen@0 239 }
Chris@109 240
Chris@109 241 checkQueue();
jtkorhonen@0 242 }
Chris@109 243 /*
Chris@62 244 bool HgRunner::isCommandRunning()
jtkorhonen@0 245 {
Chris@84 246 return m_isRunning;
jtkorhonen@0 247 }
Chris@109 248 */
jtkorhonen@0 249
Chris@62 250 void HgRunner::killCurrentCommand()
jtkorhonen@0 251 {
Chris@109 252 if (m_isRunning) {
Chris@84 253 m_proc -> kill();
jtkorhonen@0 254 }
jtkorhonen@0 255 }
jtkorhonen@0 256
Chris@109 257 void HgRunner::checkQueue()
Chris@62 258 {
Chris@109 259 if (m_isRunning) {
Chris@109 260 return;
Chris@109 261 }
Chris@109 262 if (m_queue.empty()) {
Chris@109 263 hide();
Chris@109 264 return;
Chris@109 265 }
Chris@109 266 HgAction toRun = m_queue.front();
Chris@109 267 m_queue.pop_front();
Chris@109 268 DEBUG << "checkQueue: have action: running " << toRun.action << endl;
Chris@109 269 startCommand(toRun);
Chris@109 270 }
Chris@109 271
Chris@109 272 void HgRunner::startCommand(HgAction action)
Chris@109 273 {
Chris@109 274 QString executable = action.executable;
Chris@109 275 bool interactive = false;
Chris@109 276 QStringList params = action.params;
Chris@109 277
Chris@109 278 if (executable == "") {
Chris@109 279 // This is a Hg command
Chris@109 280 executable = getHgBinaryName();
Chris@104 281 #ifdef Q_OS_WIN32
Chris@109 282 // This at least means we won't block on the non-working password prompt
Chris@109 283 params.push_front("--noninteractive");
Chris@104 284 #else
Chris@109 285 // password prompt should work here
Chris@109 286 if (action.mayBeInteractive()) {
Chris@109 287 params.push_front("ui.interactive=true");
Chris@109 288 params.push_front("--config");
Chris@109 289 interactive = true;
Chris@109 290 } else {
Chris@109 291 params.push_front("--noninteractive");
Chris@109 292 }
Chris@107 293 }
Chris@104 294 #endif
jtkorhonen@0 295
Chris@84 296 m_isRunning = true;
jtkorhonen@0 297 setRange(0, 0);
Chris@109 298 show();
Chris@84 299 m_output.clear();
Chris@84 300 m_realm = "";
Chris@84 301 m_userName = "";
jtkorhonen@0 302
Chris@109 303 if (!action.workingDir.isEmpty()) {
Chris@109 304 m_proc->setWorkingDirectory(action.workingDir);
jtkorhonen@0 305 }
jtkorhonen@0 306
Chris@84 307 m_procInput = 0;
Chris@107 308 m_ptySlaveFilename = "";
Chris@107 309
Chris@84 310 #ifndef Q_OS_WIN32
Chris@107 311 if (interactive) {
Chris@107 312 char name[1024];
Chris@107 313 if (openpty(&m_ptyMasterFd, &m_ptySlaveFd, name, NULL, NULL)) {
Chris@107 314 perror("openpty failed");
Chris@107 315 } else {
Chris@107 316 DEBUG << "openpty succeeded: master " << m_ptyMasterFd
Chris@107 317 << " slave " << m_ptySlaveFd << " filename " << name << endl;
Chris@107 318 m_procInput = new QFile;
Chris@107 319 m_procInput->open(m_ptyMasterFd, QFile::WriteOnly);
Chris@107 320 m_ptySlaveFilename = name;
Chris@107 321 m_proc->setStandardInputFile(m_ptySlaveFilename);
Chris@107 322 ::close(m_ptySlaveFd);
Chris@107 323 }
Chris@84 324 }
Chris@84 325 #endif
Chris@84 326
Chris@109 327 QString cmdline = executable;
Chris@57 328 foreach (QString param, params) cmdline += " " + param;
Chris@64 329 DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
Chris@109 330 << action.workingDir << endl;
Chris@43 331
Chris@109 332 m_currentAction = action;
Chris@109 333
Chris@109 334 DEBUG << "set current action to " << m_currentAction.action << endl;
Chris@109 335
Chris@109 336 m_proc->start(executable, params);
Chris@84 337 }
Chris@84 338
Chris@84 339 void HgRunner::closeProcInput()
Chris@84 340 {
Chris@84 341 DEBUG << "closeProcInput" << endl;
Chris@84 342
Chris@84 343 m_proc->closeWriteChannel();
Chris@84 344 #ifndef Q_OS_WIN32
Chris@84 345 if (m_ptySlaveFilename != "") {
Chris@84 346 ::close(m_ptyMasterFd);
Chris@84 347 m_ptySlaveFilename = "";
Chris@84 348 }
Chris@84 349 #endif
jtkorhonen@0 350 }
jtkorhonen@0 351