annotate src/hgrunner.cpp @ 571:012ba1b83328

Show cancel button with progress bar only when running an operation that it makes sense to cancel (we don't really want people cancelling e.g. initial folder scan because it would leave things in an inconsistent state)
author Chris Cannam
date Thu, 01 Mar 2012 22:53:54 +0000
parents 39cac58b4f92
children ab92f695f776
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@560 8 Copyright (c) 2012 Chris Cannam
Chris@560 9 Copyright (c) 2012 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@240 21 #include "settingsdialog.h"
Chris@50 22
Chris@62 23 #include <QSettings>
Chris@75 24 #include <QInputDialog>
Chris@444 25 #include <QDesktopServices>
Chris@188 26 #include <QTemporaryFile>
Chris@161 27 #include <QDir>
Chris@561 28 #include <QProgressBar>
Chris@571 29 #include <QPushButton>
Chris@561 30 #include <QGridLayout>
jtkorhonen@0 31
Chris@43 32 #include <iostream>
Chris@75 33 #include <errno.h>
Chris@75 34 #include <stdio.h>
Chris@111 35 #include <stdlib.h>
jtkorhonen@0 36
Chris@76 37 #ifndef Q_OS_WIN32
Chris@111 38 #include <unistd.h>
Chris@113 39 #include <termios.h>
Chris@111 40 #include <fcntl.h>
chris@479 41 #else
chris@479 42 #include <process.h>
Chris@80 43 #endif
Chris@76 44
Chris@561 45 HgRunner::HgRunner(QString myDirPath, QWidget *parent) :
Chris@561 46 QWidget(parent),
Chris@172 47 m_myDirPath(myDirPath)
jtkorhonen@0 48 {
Chris@561 49 QGridLayout *layout = new QGridLayout(this);
Chris@561 50 layout->setMargin(0);
Chris@561 51
Chris@561 52 m_progress = new QProgressBar;
Chris@561 53 layout->addWidget(m_progress, 0, 0);
Chris@561 54
Chris@571 55 m_cancel = new QPushButton;
Chris@561 56 m_cancel->setIcon(QIcon(":images/cancel-small.png"));
Chris@571 57 m_cancel->setFlat(true);
Chris@571 58 m_cancel->setFixedHeight(m_progress->sizeHint().height());
Chris@571 59 m_cancel->setFixedWidth(m_progress->sizeHint().height());
Chris@561 60 connect(m_cancel, SIGNAL(clicked()), this, SLOT(killCurrentActions()));
Chris@561 61 layout->addWidget(m_cancel, 0, 1);
Chris@561 62
Chris@113 63 m_proc = 0;
Chris@84 64
Chris@239 65 // Always unbundle the extension: even if it already exists (in
Chris@239 66 // case we're upgrading) and even if we're not going to use it (so
Chris@239 67 // that it's available in case someone wants to use it later,
Chris@239 68 // e.g. to fix a malfunctioning setup). But the path we actually
Chris@239 69 // prefer is the one in the settings first, if it exists; then the
Chris@239 70 // unbundled one; then anything in the path if for some reason
Chris@239 71 // unbundling failed
Chris@239 72 unbundleExtension();
Chris@239 73
Chris@561 74 m_progress->setTextVisible(false);
Chris@561 75 hide();
Chris@84 76 m_isRunning = false;
jtkorhonen@0 77 }
jtkorhonen@0 78
jtkorhonen@0 79 HgRunner::~HgRunner()
jtkorhonen@0 80 {
Chris@111 81 closeTerminal();
Chris@120 82 if (m_proc) {
Chris@120 83 m_proc->kill();
Chris@122 84 m_proc->deleteLater();
Chris@120 85 }
Chris@444 86 if (m_authFilePath != "") {
Chris@444 87 QFile(m_authFilePath).remove();
Chris@444 88 }
Chris@444 89 //!!! and remove any other misc auth file paths...
jtkorhonen@0 90 }
jtkorhonen@0 91
Chris@188 92 QString HgRunner::getUnbundledFileName()
Chris@188 93 {
Chris@240 94 return SettingsDialog::getUnbundledExtensionFileName();
Chris@188 95 }
Chris@188 96
Chris@180 97 QString HgRunner::unbundleExtension()
Chris@161 98 {
Chris@188 99 // Pull out the bundled Python file into a temporary file, and
Chris@188 100 // copy it to our known extension location, replacing the magic
Chris@188 101 // text NO_EASYHG_IMPORT_PATH with our installation location
Chris@188 102
Chris@161 103 QString bundled = ":easyhg.py";
Chris@188 104 QString unbundled = getUnbundledFileName();
Chris@188 105
Chris@188 106 QString target = QFileInfo(unbundled).path();
Chris@161 107 if (!QDir().mkpath(target)) {
Chris@161 108 DEBUG << "Failed to make unbundle path " << target << endl;
Chris@188 109 std::cerr << "Failed to make unbundle path " << target << std::endl;
Chris@180 110 return "";
Chris@161 111 }
Chris@188 112
Chris@161 113 QFile bf(bundled);
Chris@188 114 DEBUG << "unbundle: bundled file will be " << bundled << endl;
Chris@188 115 if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) {
Chris@180 116 DEBUG << "Bundled extension is missing!" << endl;
Chris@180 117 return "";
Chris@180 118 }
Chris@188 119
Chris@188 120 QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target));
Chris@188 121 tmpfile.setAutoRemove(false);
Chris@188 122 DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl;
Chris@188 123 if (!tmpfile.open()) {
Chris@188 124 DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl;
Chris@188 125 std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl;
Chris@180 126 return "";
Chris@161 127 }
Chris@188 128
Chris@188 129 QString all = QString::fromUtf8(bf.readAll());
Chris@188 130 all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath);
Chris@188 131 tmpfile.write(all.toUtf8());
Chris@188 132 DEBUG << "unbundle: wrote " << all.length() << " characters" << endl;
Chris@188 133
Chris@188 134 tmpfile.close();
Chris@188 135
Chris@188 136 QFile ef(unbundled);
Chris@188 137 if (ef.exists()) {
Chris@188 138 DEBUG << "unbundle: removing old file " << unbundled << endl;
Chris@188 139 ef.remove();
Chris@188 140 }
Chris@188 141 DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl;
Chris@188 142 if (!tmpfile.rename(unbundled)) {
Chris@188 143 DEBUG << "Failed to move temporary file to target file " << unbundled << endl;
Chris@188 144 std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl;
Chris@188 145 return "";
Chris@188 146 }
Chris@188 147
Chris@188 148 DEBUG << "Unbundled extension to " << unbundled << endl;
Chris@188 149 return unbundled;
Chris@161 150 }
Chris@161 151
Chris@109 152 void HgRunner::requestAction(HgAction action)
Chris@109 153 {
Chris@109 154 DEBUG << "requestAction " << action.action << endl;
Chris@109 155 bool pushIt = true;
Chris@109 156 if (m_queue.empty()) {
Chris@109 157 if (action == m_currentAction) {
Chris@109 158 // this request is identical to the thing we're executing
Chris@109 159 DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
Chris@109 160 pushIt = false;
Chris@109 161 }
Chris@109 162 } else {
Chris@109 163 HgAction last = m_queue.back();
Chris@109 164 if (action == last) {
Chris@109 165 // this request is identical to the previous thing we
Chris@109 166 // queued which we haven't executed yet
Chris@109 167 DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
Chris@109 168 pushIt = false;
Chris@109 169 }
Chris@109 170 }
Chris@109 171 if (pushIt) m_queue.push_back(action);
Chris@109 172 checkQueue();
Chris@109 173 }
Chris@109 174
Chris@239 175 QString HgRunner::getHgBinaryName()
Chris@62 176 {
Chris@62 177 QSettings settings;
Chris@175 178 settings.beginGroup("Locations");
Chris@239 179 return settings.value("hgbinary", "").toString();
Chris@62 180 }
Chris@62 181
chris@406 182 QString HgRunner::getSshBinaryName()
chris@406 183 {
chris@406 184 QSettings settings;
chris@406 185 settings.beginGroup("Locations");
chris@406 186 return settings.value("sshbinary", "").toString();
chris@406 187 }
chris@406 188
Chris@239 189 QString HgRunner::getExtensionLocation()
Chris@239 190 {
Chris@239 191 QSettings settings;
Chris@239 192 settings.beginGroup("Locations");
Chris@239 193 QString extpath = settings.value("extensionpath", "").toString();
Chris@239 194 if (extpath != "" && QFile(extpath).exists()) return extpath;
Chris@239 195 return "";
Chris@239 196 }
Chris@239 197
jtkorhonen@0 198 void HgRunner::started()
jtkorhonen@0 199 {
Chris@104 200 DEBUG << "started" << endl;
Chris@75 201 /*
Chris@104 202 m_proc->write("blah\n");
Chris@104 203 m_proc->write("blah\n");
Chris@104 204 m_proc -> closeWriteChannel();
Chris@75 205 */
jtkorhonen@0 206 }
jtkorhonen@0 207
Chris@84 208 void HgRunner::noteUsername(QString name)
Chris@75 209 {
Chris@84 210 m_userName = name;
Chris@75 211 }
Chris@75 212
Chris@84 213 void HgRunner::noteRealm(QString realm)
Chris@75 214 {
Chris@84 215 m_realm = realm;
Chris@84 216 }
Chris@84 217
Chris@84 218 void HgRunner::getUsername()
Chris@84 219 {
Chris@113 220 if (m_ptyFile) {
Chris@84 221 bool ok = false;
Chris@84 222 QString prompt = tr("User name:");
Chris@84 223 if (m_realm != "") {
Chris@84 224 prompt = tr("User name for \"%1\":").arg(m_realm);
Chris@84 225 }
Chris@84 226 QString pwd = QInputDialog::getText
Chris@84 227 (qobject_cast<QWidget *>(parent()),
Chris@84 228 tr("Enter user name"), prompt,
Chris@84 229 QLineEdit::Normal, QString(), &ok);
Chris@84 230 if (ok) {
Chris@113 231 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
Chris@113 232 m_ptyFile->flush();
Chris@84 233 return;
Chris@111 234 } else {
Chris@111 235 DEBUG << "HgRunner::getUsername: user cancelled" << endl;
Chris@111 236 killCurrentCommand();
Chris@111 237 return;
Chris@84 238 }
Chris@84 239 }
Chris@84 240 // user cancelled or something went wrong
Chris@111 241 DEBUG << "HgRunner::getUsername: something went wrong" << endl;
Chris@84 242 killCurrentCommand();
Chris@84 243 }
Chris@84 244
Chris@84 245 void HgRunner::getPassword()
Chris@84 246 {
Chris@113 247 if (m_ptyFile) {
Chris@84 248 bool ok = false;
Chris@84 249 QString prompt = tr("Password:");
Chris@84 250 if (m_userName != "") {
Chris@84 251 if (m_realm != "") {
Chris@84 252 prompt = tr("Password for \"%1\" at \"%2\":")
Chris@84 253 .arg(m_userName).arg(m_realm);
Chris@75 254 } else {
Chris@84 255 prompt = tr("Password for user \"%1\":")
Chris@84 256 .arg(m_userName);
Chris@75 257 }
Chris@75 258 }
Chris@84 259 QString pwd = QInputDialog::getText
Chris@84 260 (qobject_cast<QWidget *>(parent()),
Chris@84 261 tr("Enter password"), prompt,
Chris@84 262 QLineEdit::Password, QString(), &ok);
Chris@84 263 if (ok) {
Chris@113 264 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
Chris@113 265 m_ptyFile->flush();
Chris@84 266 return;
Chris@111 267 } else {
Chris@111 268 DEBUG << "HgRunner::getPassword: user cancelled" << endl;
Chris@111 269 killCurrentCommand();
Chris@111 270 return;
Chris@84 271 }
Chris@75 272 }
Chris@84 273 // user cancelled or something went wrong
Chris@111 274 DEBUG << "HgRunner::getPassword: something went wrong" << endl;
Chris@84 275 killCurrentCommand();
Chris@84 276 }
Chris@84 277
Chris@113 278 bool HgRunner::checkPrompts(QString chunk)
Chris@84 279 {
Chris@93 280 //DEBUG << "checkPrompts: " << chunk << endl;
Chris@84 281
Chris@128 282 if (!m_currentAction.mayBeInteractive()) return false;
Chris@128 283
Chris@84 284 QString text = chunk.trimmed();
Chris@84 285 QString lower = text.toLower();
Chris@84 286 if (lower.endsWith("password:")) {
Chris@84 287 getPassword();
Chris@113 288 return true;
Chris@84 289 }
Chris@128 290 if (lower.endsWith("user:") || lower.endsWith("username:")) {
Chris@84 291 getUsername();
Chris@113 292 return true;
Chris@84 293 }
Chris@128 294 QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)");
Chris@84 295 if (userRe.indexIn(text) >= 0) {
Chris@128 296 noteUsername(userRe.cap(2));
Chris@84 297 }
Chris@84 298 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
Chris@84 299 if (realmRe.indexIn(text) >= 0) {
Chris@84 300 noteRealm(realmRe.cap(1));
Chris@84 301 }
Chris@113 302 return false;
Chris@84 303 }
Chris@84 304
Chris@110 305 void HgRunner::dataReadyStdout()
Chris@84 306 {
Chris@110 307 DEBUG << "dataReadyStdout" << endl;
Chris@408 308 if (!m_proc) return;
Chris@110 309 QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
Chris@113 310 if (!checkPrompts(chunk)) {
Chris@113 311 m_stdout += chunk;
Chris@113 312 }
Chris@110 313 }
Chris@110 314
Chris@110 315 void HgRunner::dataReadyStderr()
Chris@110 316 {
Chris@110 317 DEBUG << "dataReadyStderr" << endl;
Chris@408 318 if (!m_proc) return;
Chris@110 319 QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
Chris@113 320 DEBUG << chunk;
Chris@113 321 if (!checkPrompts(chunk)) {
Chris@113 322 m_stderr += chunk;
Chris@113 323 }
Chris@113 324 }
Chris@113 325
Chris@113 326 void HgRunner::dataReadyPty()
Chris@113 327 {
Chris@113 328 DEBUG << "dataReadyPty" << endl;
Chris@113 329 QString chunk = QString::fromUtf8(m_ptyFile->readAll());
Chris@113 330 DEBUG << "chunk of " << chunk.length() << " chars" << endl;
Chris@113 331 if (!checkPrompts(chunk)) {
Chris@113 332 m_stdout += chunk;
Chris@113 333 }
Chris@75 334 }
Chris@75 335
Chris@189 336 void HgRunner::error(QProcess::ProcessError)
Chris@189 337 {
Chris@189 338 finished(-1, QProcess::CrashExit);
Chris@189 339 }
Chris@189 340
jtkorhonen@0 341 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
jtkorhonen@0 342 {
Chris@564 343 if (!m_proc) return;
chris@385 344
Chris@564 345 // Save the current action and reset m_currentAction before we
Chris@109 346 // emit a signal to mark the completion; otherwise we may be
Chris@109 347 // resetting the action after a slot has already tried to set it
Chris@109 348 // to something else to start a new action
Chris@109 349
Chris@109 350 HgAction completedAction = m_currentAction;
Chris@109 351
Chris@84 352 m_isRunning = false;
Chris@109 353 m_currentAction = HgAction();
Chris@84 354
Chris@195 355 //closeProcInput();
Chris@189 356 m_proc->deleteLater();
Chris@113 357 m_proc = 0;
jtkorhonen@0 358
Chris@109 359 if (completedAction.action == ACT_NONE) {
Chris@109 360 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
Chris@62 361 } else {
Chris@113 362 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
Chris@113 363 DEBUG << "HgRunner::finished: Command completed successfully"
Chris@113 364 << endl;
Chris@124 365 // DEBUG << "stdout is " << m_stdout << endl;
Chris@113 366 emit commandCompleted(completedAction, m_stdout);
Chris@113 367 } else {
Chris@113 368 DEBUG << "HgRunner::finished: Command failed, exit code "
Chris@113 369 << procExitCode << ", exit status " << procExitStatus
Chris@113 370 << ", stderr follows" << endl;
Chris@113 371 DEBUG << m_stderr << endl;
Chris@537 372 emit commandFailed(completedAction, m_stderr, m_stdout);
Chris@113 373 }
jtkorhonen@0 374 }
Chris@109 375
Chris@109 376 checkQueue();
jtkorhonen@0 377 }
jtkorhonen@0 378
Chris@182 379 void HgRunner::killCurrentActions()
Chris@182 380 {
Chris@564 381 HgAction current = m_currentAction;
Chris@182 382 m_queue.clear();
Chris@182 383 killCurrentCommand();
Chris@564 384 emit commandCancelled(current);
Chris@182 385 }
Chris@182 386
Chris@62 387 void HgRunner::killCurrentCommand()
jtkorhonen@0 388 {
Chris@109 389 if (m_isRunning) {
Chris@113 390 m_currentAction.action = ACT_NONE; // so that we don't bother to notify
chris@385 391 if (m_proc) m_proc->kill();
jtkorhonen@0 392 }
jtkorhonen@0 393 }
jtkorhonen@0 394
Chris@109 395 void HgRunner::checkQueue()
Chris@62 396 {
Chris@109 397 if (m_isRunning) {
Chris@109 398 return;
Chris@109 399 }
Chris@109 400 if (m_queue.empty()) {
Chris@109 401 hide();
Chris@109 402 return;
Chris@109 403 }
Chris@109 404 HgAction toRun = m_queue.front();
Chris@109 405 m_queue.pop_front();
Chris@109 406 DEBUG << "checkQueue: have action: running " << toRun.action << endl;
Chris@109 407 startCommand(toRun);
Chris@109 408 }
Chris@109 409
Chris@455 410 void HgRunner::pruneOldAuthFiles()
Chris@455 411 {
Chris@455 412 QString path = QDesktopServices::storageLocation
Chris@455 413 (QDesktopServices::CacheLocation);
Chris@455 414 QDir d(path);
Chris@455 415 if (!d.exists()) return;
Chris@455 416 QStringList filters;
Chris@455 417 filters << "easyhg.*.dat";
Chris@455 418 QStringList fl = d.entryList(filters);
Chris@455 419 foreach (QString f, fl) {
Chris@455 420 QStringList parts = f.split('.');
Chris@455 421 if (parts.size() > 1) {
Chris@455 422 int pid = parts[1].toInt();
Chris@455 423 DEBUG << "Checking pid " << pid << " for cache file " << f << endl;
Chris@455 424
Chris@455 425 ProcessStatus ps = GetProcessStatus(pid);
Chris@455 426 if (ps == ProcessNotRunning) {
Chris@455 427 DEBUG << "Removing stale cache file " << f << endl;
Chris@455 428 QDir(d).remove(f);
Chris@455 429 }
Chris@455 430 }
Chris@455 431 }
Chris@455 432 }
Chris@455 433
Chris@455 434 QString HgRunner::getAuthFilePath()
Chris@455 435 {
Chris@455 436 if (m_authFilePath == "") {
Chris@455 437
Chris@455 438 pruneOldAuthFiles();
Chris@455 439
Chris@455 440 QByteArray fileExt = randomKey();
Chris@455 441 if (fileExt == QByteArray()) {
Chris@455 442 DEBUG << "HgRunner::addExtensionOptions: Failed to get proper auth file ext" << endl;
Chris@455 443 return "";
Chris@455 444 }
Chris@455 445 QString fileExt16 = QString::fromLocal8Bit(fileExt.toBase64()).left(16)
Chris@455 446 .replace('+', '-').replace('/', '_');
Chris@455 447 QString path = QDesktopServices::storageLocation
Chris@455 448 (QDesktopServices::CacheLocation);
Chris@455 449 QDir().mkpath(path);
Chris@455 450 if (path == "") {
Chris@455 451 DEBUG << "HgRunner::addExtensionOptions: Failed to get cache location" << endl;
Chris@455 452 return "";
Chris@455 453 }
Chris@455 454
Chris@455 455 m_authFilePath = QString("%1/easyhg.%2.%3.dat").arg(path)
Chris@455 456 .arg(getpid()).arg(fileExt16);
Chris@455 457 }
Chris@455 458
Chris@455 459 return m_authFilePath;
Chris@455 460 }
Chris@455 461
Chris@455 462 QString HgRunner::getAuthKey()
Chris@455 463 {
Chris@455 464 if (m_authKey == "") {
Chris@455 465 QByteArray key = randomKey();
Chris@455 466 if (key == QByteArray()) {
Chris@455 467 DEBUG << "HgRunner::addExtensionOptions: Failed to get proper auth key" << endl;
Chris@455 468 return "";
Chris@455 469 }
Chris@455 470 QString key16 = QString::fromLocal8Bit(key.toBase64()).left(16);
Chris@455 471 m_authKey = key16;
Chris@455 472 }
Chris@455 473
Chris@455 474 return m_authKey;
Chris@455 475 }
Chris@455 476
Chris@444 477 QStringList HgRunner::addExtensionOptions(QStringList params)
Chris@444 478 {
Chris@444 479 QString extpath = getExtensionLocation();
Chris@444 480 if (extpath == "") {
Chris@444 481 DEBUG << "HgRunner::addExtensionOptions: Failed to get extension location" << endl;
Chris@444 482 return params;
Chris@444 483 }
Chris@444 484
Chris@455 485 QString afp = getAuthFilePath();
Chris@455 486 QString afk = getAuthKey();
Chris@444 487
Chris@455 488 if (afp != "" && afk != "") {
Chris@455 489 params.push_front(QString("easyhg.authkey=%1").arg(m_authKey));
Chris@455 490 params.push_front("--config");
Chris@455 491 params.push_front(QString("easyhg.authfile=%1").arg(m_authFilePath));
Chris@455 492 params.push_front("--config");
Chris@444 493 }
Chris@444 494
Chris@444 495 // Looks like this one must be without quotes, even though the SSH
Chris@444 496 // one above only works on Windows if it has quotes (at least where
Chris@444 497 // there is a space in the path). Odd
Chris@444 498 params.push_front(QString("extensions.easyhg=%1").arg(extpath));
Chris@444 499 params.push_front("--config");
Chris@444 500
Chris@444 501 return params;
Chris@444 502 }
Chris@444 503
Chris@109 504 void HgRunner::startCommand(HgAction action)
Chris@109 505 {
Chris@109 506 QString executable = action.executable;
Chris@109 507 bool interactive = false;
Chris@109 508 QStringList params = action.params;
Chris@109 509
Chris@340 510 if (action.workingDir.isEmpty()) {
Chris@340 511 // We require a working directory, never just operate in pwd
Chris@537 512 emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one", "");
Chris@340 513 return;
Chris@340 514 }
Chris@340 515
Chris@109 516 if (executable == "") {
Chris@109 517 // This is a Hg command
Chris@239 518 executable = getHgBinaryName();
chris@406 519 if (executable == "") executable = "hg";
chris@406 520
chris@406 521 QString ssh = getSshBinaryName();
chris@406 522 if (ssh != "") {
chris@406 523 params.push_front(QString("ui.ssh=\"%1\"").arg(ssh));
chris@406 524 params.push_front("--config");
chris@406 525 }
Chris@161 526
Chris@161 527 if (action.mayBeInteractive()) {
Chris@161 528 params.push_front("ui.interactive=true");
Chris@161 529 params.push_front("--config");
Chris@484 530 QSettings settings;
Chris@176 531 if (settings.value("useextension", true).toBool()) {
Chris@444 532 params = addExtensionOptions(params);
Chris@176 533 }
Chris@161 534 interactive = true;
Chris@161 535 }
Chris@161 536
Chris@161 537 //!!! want an option to use the mercurial_keyring extension as well
Chris@107 538 }
jtkorhonen@0 539
Chris@84 540 m_isRunning = true;
Chris@561 541 m_progress->setRange(0, 0);
Chris@571 542 if (!action.shouldBeFast()) {
Chris@571 543 show();
Chris@571 544 m_cancel->setVisible(action.makesSenseToCancel());
Chris@571 545 }
Chris@110 546 m_stdout.clear();
Chris@110 547 m_stderr.clear();
Chris@84 548 m_realm = "";
Chris@84 549 m_userName = "";
jtkorhonen@0 550
Chris@113 551 m_proc = new QProcess;
Chris@113 552
Chris@177 553 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
Chris@177 554
Chris@177 555 #ifdef Q_OS_WIN32
Chris@257 556 // On Win32 we like to bundle Hg and other executables with EasyHg
Chris@177 557 if (m_myDirPath != "") {
Chris@177 558 env.insert("PATH", m_myDirPath + ";" + env.value("PATH"));
Chris@172 559 }
Chris@172 560 #endif
Chris@172 561
Chris@257 562 #ifdef Q_OS_MAC
Chris@490 563 if (QSettings().value("python32", false).toBool()) {
Chris@261 564 env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1");
Chris@257 565 }
Chris@257 566 #endif
Chris@257 567
Chris@113 568 env.insert("LANG", "en_US.utf8");
Chris@113 569 env.insert("LC_ALL", "en_US.utf8");
Chris@408 570 env.insert("HGENCODING", "utf8");
Chris@113 571 env.insert("HGPLAIN", "1");
Chris@113 572 m_proc->setProcessEnvironment(env);
Chris@113 573
Chris@113 574 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
Chris@189 575 connect(m_proc, SIGNAL(error(QProcess::ProcessError)),
Chris@189 576 this, SLOT(error(QProcess::ProcessError)));
Chris@113 577 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@113 578 this, SLOT(finished(int, QProcess::ExitStatus)));
Chris@113 579 connect(m_proc, SIGNAL(readyReadStandardOutput()),
Chris@113 580 this, SLOT(dataReadyStdout()));
Chris@113 581 connect(m_proc, SIGNAL(readyReadStandardError()),
Chris@113 582 this, SLOT(dataReadyStderr()));
Chris@113 583
Chris@340 584 m_proc->setWorkingDirectory(action.workingDir);
jtkorhonen@0 585
Chris@107 586 if (interactive) {
Chris@111 587 openTerminal();
Chris@111 588 if (m_ptySlaveFilename != "") {
Chris@113 589 DEBUG << "HgRunner: connecting to pseudoterminal" << endl;
Chris@107 590 m_proc->setStandardInputFile(m_ptySlaveFilename);
Chris@114 591 // m_proc->setStandardOutputFile(m_ptySlaveFilename);
Chris@113 592 // m_proc->setStandardErrorFile(m_ptySlaveFilename);
Chris@107 593 }
Chris@84 594 }
Chris@84 595
Chris@109 596 QString cmdline = executable;
Chris@57 597 foreach (QString param, params) cmdline += " " + param;
Chris@64 598 DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
Chris@109 599 << action.workingDir << endl;
Chris@43 600
Chris@109 601 m_currentAction = action;
Chris@109 602
Chris@113 603 // fill these out with what we actually ran
Chris@113 604 m_currentAction.executable = executable;
Chris@113 605 m_currentAction.params = params;
Chris@113 606
Chris@109 607 DEBUG << "set current action to " << m_currentAction.action << endl;
Chris@109 608
Chris@238 609 emit commandStarting(action);
Chris@238 610
Chris@109 611 m_proc->start(executable, params);
Chris@84 612 }
Chris@84 613
Chris@84 614 void HgRunner::closeProcInput()
Chris@84 615 {
Chris@84 616 DEBUG << "closeProcInput" << endl;
Chris@84 617
chris@385 618 if (m_proc) m_proc->closeWriteChannel();
Chris@111 619 }
Chris@111 620
Chris@111 621 void HgRunner::openTerminal()
Chris@111 622 {
Chris@111 623 #ifndef Q_OS_WIN32
Chris@111 624 if (m_ptySlaveFilename != "") return; // already open
Chris@111 625 DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl;
Chris@111 626 int master = posix_openpt(O_RDWR | O_NOCTTY);
Chris@111 627 if (master < 0) {
Chris@111 628 DEBUG << "openpt failed" << endl;
Chris@111 629 perror("openpt failed");
Chris@111 630 return;
Chris@111 631 }
Chris@113 632 struct termios t;
Chris@113 633 if (tcgetattr(master, &t)) {
Chris@113 634 DEBUG << "tcgetattr failed" << endl;
Chris@113 635 perror("tcgetattr failed");
Chris@113 636 }
Chris@113 637 cfmakeraw(&t);
Chris@113 638 if (tcsetattr(master, TCSANOW, &t)) {
Chris@113 639 DEBUG << "tcsetattr failed" << endl;
Chris@113 640 perror("tcsetattr failed");
Chris@113 641 }
Chris@111 642 if (grantpt(master)) {
Chris@111 643 perror("grantpt failed");
Chris@111 644 }
Chris@111 645 if (unlockpt(master)) {
Chris@111 646 perror("unlockpt failed");
Chris@111 647 }
Chris@111 648 char *slave = ptsname(master);
Chris@111 649 if (!slave) {
Chris@111 650 perror("ptsname failed");
Chris@111 651 ::close(master);
Chris@111 652 return;
Chris@111 653 }
Chris@111 654 m_ptyMasterFd = master;
Chris@113 655 m_ptyFile = new QFile();
Chris@113 656 connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty()));
Chris@113 657 if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) {
Chris@113 658 DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl;
Chris@113 659 }
Chris@111 660 m_ptySlaveFilename = slave;
Chris@111 661 DEBUG << "HgRunner::openTerminal: succeeded, slave is "
Chris@111 662 << m_ptySlaveFilename << endl;
Chris@111 663 #endif
Chris@111 664 }
Chris@111 665
Chris@111 666 void HgRunner::closeTerminal()
Chris@111 667 {
Chris@84 668 #ifndef Q_OS_WIN32
Chris@84 669 if (m_ptySlaveFilename != "") {
Chris@113 670 delete m_ptyFile;
Chris@113 671 m_ptyFile = 0;
Chris@84 672 ::close(m_ptyMasterFd);
Chris@84 673 m_ptySlaveFilename = "";
Chris@84 674 }
Chris@84 675 #endif
jtkorhonen@0 676 }