annotate src/hgrunner.cpp @ 537:a4e699d32a9a

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