annotate src/hgrunner.cpp @ 633:db62a0cb3037

* Added setting to specify that diff command should be run once for each selected file, rather than passing all file names to diff command (p4merge doesn't like being given many files)
author Sam Izzo <sam@humbug.net>
date Mon, 27 Aug 2012 01:26:57 +1000
parents ab92f695f776
children ae67ea0af696
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@605 154 DEBUG << "requestAction " << action.action << ": " << m_queue.size() << " thing(s) in queue, current action is " << m_currentAction.action << endl;
Chris@109 155 bool pushIt = true;
Chris@605 156
Chris@605 157 action = expandEnvironment(action);
Chris@605 158
Chris@109 159 if (m_queue.empty()) {
Chris@109 160 if (action == m_currentAction) {
Chris@109 161 // this request is identical to the thing we're executing
Chris@109 162 DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
Chris@109 163 pushIt = false;
Chris@109 164 }
Chris@109 165 } else {
Chris@109 166 HgAction last = m_queue.back();
Chris@109 167 if (action == last) {
Chris@109 168 // this request is identical to the previous thing we
Chris@109 169 // queued which we haven't executed yet
Chris@109 170 DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
Chris@109 171 pushIt = false;
Chris@109 172 }
Chris@109 173 }
Chris@605 174 if (pushIt) {
Chris@605 175 m_queue.push_back(action);
Chris@605 176 }
Chris@109 177 checkQueue();
Chris@109 178 }
Chris@109 179
Chris@605 180 HgAction HgRunner::expandEnvironment(HgAction action)
Chris@605 181 {
Chris@605 182 // Adjust the executable and params for action to match our actual
Chris@605 183 // environment. We do this when the action is received, rather
Chris@605 184 // than when we execute it, so that we can compare
Chris@605 185 // (post-expansion) commands to see e.g. whether the one just
Chris@605 186 // received is the same as the one we're currently executing
Chris@605 187
Chris@605 188 QString executable = action.executable;
Chris@605 189 QStringList params = action.params;
Chris@605 190
Chris@605 191 if (executable == "") {
Chris@605 192 // This is a Hg command
Chris@605 193 executable = getHgBinaryName();
Chris@605 194 if (executable == "") executable = "hg";
Chris@605 195
Chris@605 196 QString ssh = getSshBinaryName();
Chris@605 197 if (ssh != "") {
Chris@605 198 params.push_front(QString("ui.ssh=\"%1\"").arg(ssh));
Chris@605 199 params.push_front("--config");
Chris@605 200 }
Chris@605 201
Chris@605 202 if (action.mayBeInteractive()) {
Chris@605 203 params.push_front("ui.interactive=true");
Chris@605 204 params.push_front("--config");
Chris@605 205 QSettings settings;
Chris@605 206 if (settings.value("useextension", true).toBool()) {
Chris@605 207 params = addExtensionOptions(params);
Chris@605 208 }
Chris@605 209 }
Chris@605 210 }
Chris@605 211
Chris@605 212 action.executable = executable;
Chris@605 213 action.params = params;
Chris@605 214
Chris@605 215 return action;
Chris@605 216 }
Chris@605 217
Chris@239 218 QString HgRunner::getHgBinaryName()
Chris@62 219 {
Chris@62 220 QSettings settings;
Chris@175 221 settings.beginGroup("Locations");
Chris@239 222 return settings.value("hgbinary", "").toString();
Chris@62 223 }
Chris@62 224
chris@406 225 QString HgRunner::getSshBinaryName()
chris@406 226 {
chris@406 227 QSettings settings;
chris@406 228 settings.beginGroup("Locations");
chris@406 229 return settings.value("sshbinary", "").toString();
chris@406 230 }
chris@406 231
Chris@239 232 QString HgRunner::getExtensionLocation()
Chris@239 233 {
Chris@239 234 QSettings settings;
Chris@239 235 settings.beginGroup("Locations");
Chris@239 236 QString extpath = settings.value("extensionpath", "").toString();
Chris@239 237 if (extpath != "" && QFile(extpath).exists()) return extpath;
Chris@239 238 return "";
Chris@239 239 }
Chris@239 240
jtkorhonen@0 241 void HgRunner::started()
jtkorhonen@0 242 {
Chris@104 243 DEBUG << "started" << endl;
Chris@75 244 /*
Chris@104 245 m_proc->write("blah\n");
Chris@104 246 m_proc->write("blah\n");
Chris@104 247 m_proc -> closeWriteChannel();
Chris@75 248 */
jtkorhonen@0 249 }
jtkorhonen@0 250
Chris@84 251 void HgRunner::noteUsername(QString name)
Chris@75 252 {
Chris@84 253 m_userName = name;
Chris@75 254 }
Chris@75 255
Chris@84 256 void HgRunner::noteRealm(QString realm)
Chris@75 257 {
Chris@84 258 m_realm = realm;
Chris@84 259 }
Chris@84 260
Chris@84 261 void HgRunner::getUsername()
Chris@84 262 {
Chris@113 263 if (m_ptyFile) {
Chris@84 264 bool ok = false;
Chris@84 265 QString prompt = tr("User name:");
Chris@84 266 if (m_realm != "") {
Chris@84 267 prompt = tr("User name for \"%1\":").arg(m_realm);
Chris@84 268 }
Chris@84 269 QString pwd = QInputDialog::getText
Chris@84 270 (qobject_cast<QWidget *>(parent()),
Chris@84 271 tr("Enter user name"), prompt,
Chris@84 272 QLineEdit::Normal, QString(), &ok);
Chris@84 273 if (ok) {
Chris@113 274 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
Chris@113 275 m_ptyFile->flush();
Chris@84 276 return;
Chris@111 277 } else {
Chris@111 278 DEBUG << "HgRunner::getUsername: user cancelled" << endl;
Chris@111 279 killCurrentCommand();
Chris@111 280 return;
Chris@84 281 }
Chris@84 282 }
Chris@84 283 // user cancelled or something went wrong
Chris@111 284 DEBUG << "HgRunner::getUsername: something went wrong" << endl;
Chris@84 285 killCurrentCommand();
Chris@84 286 }
Chris@84 287
Chris@84 288 void HgRunner::getPassword()
Chris@84 289 {
Chris@113 290 if (m_ptyFile) {
Chris@84 291 bool ok = false;
Chris@84 292 QString prompt = tr("Password:");
Chris@84 293 if (m_userName != "") {
Chris@84 294 if (m_realm != "") {
Chris@84 295 prompt = tr("Password for \"%1\" at \"%2\":")
Chris@84 296 .arg(m_userName).arg(m_realm);
Chris@75 297 } else {
Chris@84 298 prompt = tr("Password for user \"%1\":")
Chris@84 299 .arg(m_userName);
Chris@75 300 }
Chris@75 301 }
Chris@84 302 QString pwd = QInputDialog::getText
Chris@84 303 (qobject_cast<QWidget *>(parent()),
Chris@84 304 tr("Enter password"), prompt,
Chris@84 305 QLineEdit::Password, QString(), &ok);
Chris@84 306 if (ok) {
Chris@113 307 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
Chris@113 308 m_ptyFile->flush();
Chris@84 309 return;
Chris@111 310 } else {
Chris@111 311 DEBUG << "HgRunner::getPassword: user cancelled" << endl;
Chris@111 312 killCurrentCommand();
Chris@111 313 return;
Chris@84 314 }
Chris@75 315 }
Chris@84 316 // user cancelled or something went wrong
Chris@111 317 DEBUG << "HgRunner::getPassword: something went wrong" << endl;
Chris@84 318 killCurrentCommand();
Chris@84 319 }
Chris@84 320
Chris@113 321 bool HgRunner::checkPrompts(QString chunk)
Chris@84 322 {
Chris@93 323 //DEBUG << "checkPrompts: " << chunk << endl;
Chris@84 324
Chris@128 325 if (!m_currentAction.mayBeInteractive()) return false;
Chris@128 326
Chris@84 327 QString text = chunk.trimmed();
Chris@84 328 QString lower = text.toLower();
Chris@84 329 if (lower.endsWith("password:")) {
Chris@84 330 getPassword();
Chris@113 331 return true;
Chris@84 332 }
Chris@128 333 if (lower.endsWith("user:") || lower.endsWith("username:")) {
Chris@84 334 getUsername();
Chris@113 335 return true;
Chris@84 336 }
Chris@128 337 QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)");
Chris@84 338 if (userRe.indexIn(text) >= 0) {
Chris@128 339 noteUsername(userRe.cap(2));
Chris@84 340 }
Chris@84 341 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
Chris@84 342 if (realmRe.indexIn(text) >= 0) {
Chris@84 343 noteRealm(realmRe.cap(1));
Chris@84 344 }
Chris@113 345 return false;
Chris@84 346 }
Chris@84 347
Chris@110 348 void HgRunner::dataReadyStdout()
Chris@84 349 {
Chris@110 350 DEBUG << "dataReadyStdout" << endl;
Chris@408 351 if (!m_proc) return;
Chris@110 352 QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
Chris@113 353 if (!checkPrompts(chunk)) {
Chris@113 354 m_stdout += chunk;
Chris@113 355 }
Chris@110 356 }
Chris@110 357
Chris@110 358 void HgRunner::dataReadyStderr()
Chris@110 359 {
Chris@110 360 DEBUG << "dataReadyStderr" << endl;
Chris@408 361 if (!m_proc) return;
Chris@110 362 QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
Chris@113 363 DEBUG << chunk;
Chris@113 364 if (!checkPrompts(chunk)) {
Chris@113 365 m_stderr += chunk;
Chris@113 366 }
Chris@113 367 }
Chris@113 368
Chris@113 369 void HgRunner::dataReadyPty()
Chris@113 370 {
Chris@113 371 DEBUG << "dataReadyPty" << endl;
Chris@113 372 QString chunk = QString::fromUtf8(m_ptyFile->readAll());
Chris@113 373 DEBUG << "chunk of " << chunk.length() << " chars" << endl;
Chris@113 374 if (!checkPrompts(chunk)) {
Chris@113 375 m_stdout += chunk;
Chris@113 376 }
Chris@75 377 }
Chris@75 378
Chris@189 379 void HgRunner::error(QProcess::ProcessError)
Chris@189 380 {
Chris@189 381 finished(-1, QProcess::CrashExit);
Chris@189 382 }
Chris@189 383
jtkorhonen@0 384 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
jtkorhonen@0 385 {
Chris@564 386 if (!m_proc) return;
chris@385 387
Chris@564 388 // Save the current action and reset m_currentAction before we
Chris@109 389 // emit a signal to mark the completion; otherwise we may be
Chris@109 390 // resetting the action after a slot has already tried to set it
Chris@109 391 // to something else to start a new action
Chris@109 392
Chris@109 393 HgAction completedAction = m_currentAction;
Chris@109 394
Chris@605 395 DEBUG << "HgRunner::finished: completed " << completedAction.action << endl;
Chris@605 396
Chris@84 397 m_isRunning = false;
Chris@109 398 m_currentAction = HgAction();
Chris@84 399
Chris@195 400 //closeProcInput();
Chris@189 401 m_proc->deleteLater();
Chris@113 402 m_proc = 0;
jtkorhonen@0 403
Chris@109 404 if (completedAction.action == ACT_NONE) {
Chris@109 405 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
Chris@62 406 } else {
Chris@113 407 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
Chris@113 408 DEBUG << "HgRunner::finished: Command completed successfully"
Chris@113 409 << endl;
Chris@124 410 // DEBUG << "stdout is " << m_stdout << endl;
Chris@113 411 emit commandCompleted(completedAction, m_stdout);
Chris@113 412 } else {
Chris@113 413 DEBUG << "HgRunner::finished: Command failed, exit code "
Chris@113 414 << procExitCode << ", exit status " << procExitStatus
Chris@113 415 << ", stderr follows" << endl;
Chris@113 416 DEBUG << m_stderr << endl;
Chris@537 417 emit commandFailed(completedAction, m_stderr, m_stdout);
Chris@113 418 }
jtkorhonen@0 419 }
Chris@109 420
Chris@109 421 checkQueue();
jtkorhonen@0 422 }
jtkorhonen@0 423
Chris@182 424 void HgRunner::killCurrentActions()
Chris@182 425 {
Chris@564 426 HgAction current = m_currentAction;
Chris@182 427 m_queue.clear();
Chris@182 428 killCurrentCommand();
Chris@564 429 emit commandCancelled(current);
Chris@182 430 }
Chris@182 431
Chris@62 432 void HgRunner::killCurrentCommand()
jtkorhonen@0 433 {
Chris@109 434 if (m_isRunning) {
Chris@113 435 m_currentAction.action = ACT_NONE; // so that we don't bother to notify
chris@385 436 if (m_proc) m_proc->kill();
jtkorhonen@0 437 }
jtkorhonen@0 438 }
jtkorhonen@0 439
Chris@109 440 void HgRunner::checkQueue()
Chris@62 441 {
Chris@109 442 if (m_isRunning) {
Chris@109 443 return;
Chris@109 444 }
Chris@109 445 if (m_queue.empty()) {
Chris@109 446 hide();
Chris@109 447 return;
Chris@109 448 }
Chris@109 449 HgAction toRun = m_queue.front();
Chris@109 450 m_queue.pop_front();
Chris@109 451 DEBUG << "checkQueue: have action: running " << toRun.action << endl;
Chris@109 452 startCommand(toRun);
Chris@109 453 }
Chris@109 454
Chris@455 455 void HgRunner::pruneOldAuthFiles()
Chris@455 456 {
Chris@455 457 QString path = QDesktopServices::storageLocation
Chris@455 458 (QDesktopServices::CacheLocation);
Chris@455 459 QDir d(path);
Chris@455 460 if (!d.exists()) return;
Chris@455 461 QStringList filters;
Chris@455 462 filters << "easyhg.*.dat";
Chris@455 463 QStringList fl = d.entryList(filters);
Chris@455 464 foreach (QString f, fl) {
Chris@455 465 QStringList parts = f.split('.');
Chris@455 466 if (parts.size() > 1) {
Chris@455 467 int pid = parts[1].toInt();
Chris@455 468 DEBUG << "Checking pid " << pid << " for cache file " << f << endl;
Chris@455 469
Chris@455 470 ProcessStatus ps = GetProcessStatus(pid);
Chris@455 471 if (ps == ProcessNotRunning) {
Chris@455 472 DEBUG << "Removing stale cache file " << f << endl;
Chris@455 473 QDir(d).remove(f);
Chris@455 474 }
Chris@455 475 }
Chris@455 476 }
Chris@455 477 }
Chris@455 478
Chris@455 479 QString HgRunner::getAuthFilePath()
Chris@455 480 {
Chris@455 481 if (m_authFilePath == "") {
Chris@455 482
Chris@455 483 pruneOldAuthFiles();
Chris@455 484
Chris@455 485 QByteArray fileExt = randomKey();
Chris@455 486 if (fileExt == QByteArray()) {
Chris@455 487 DEBUG << "HgRunner::addExtensionOptions: Failed to get proper auth file ext" << endl;
Chris@455 488 return "";
Chris@455 489 }
Chris@455 490 QString fileExt16 = QString::fromLocal8Bit(fileExt.toBase64()).left(16)
Chris@455 491 .replace('+', '-').replace('/', '_');
Chris@455 492 QString path = QDesktopServices::storageLocation
Chris@455 493 (QDesktopServices::CacheLocation);
Chris@455 494 QDir().mkpath(path);
Chris@455 495 if (path == "") {
Chris@455 496 DEBUG << "HgRunner::addExtensionOptions: Failed to get cache location" << endl;
Chris@455 497 return "";
Chris@455 498 }
Chris@455 499
Chris@455 500 m_authFilePath = QString("%1/easyhg.%2.%3.dat").arg(path)
Chris@455 501 .arg(getpid()).arg(fileExt16);
Chris@455 502 }
Chris@455 503
Chris@455 504 return m_authFilePath;
Chris@455 505 }
Chris@455 506
Chris@455 507 QString HgRunner::getAuthKey()
Chris@455 508 {
Chris@455 509 if (m_authKey == "") {
Chris@455 510 QByteArray key = randomKey();
Chris@455 511 if (key == QByteArray()) {
Chris@455 512 DEBUG << "HgRunner::addExtensionOptions: Failed to get proper auth key" << endl;
Chris@455 513 return "";
Chris@455 514 }
Chris@455 515 QString key16 = QString::fromLocal8Bit(key.toBase64()).left(16);
Chris@455 516 m_authKey = key16;
Chris@455 517 }
Chris@455 518
Chris@455 519 return m_authKey;
Chris@455 520 }
Chris@455 521
Chris@444 522 QStringList HgRunner::addExtensionOptions(QStringList params)
Chris@444 523 {
Chris@444 524 QString extpath = getExtensionLocation();
Chris@444 525 if (extpath == "") {
Chris@444 526 DEBUG << "HgRunner::addExtensionOptions: Failed to get extension location" << endl;
Chris@444 527 return params;
Chris@444 528 }
Chris@444 529
Chris@455 530 QString afp = getAuthFilePath();
Chris@455 531 QString afk = getAuthKey();
Chris@444 532
Chris@455 533 if (afp != "" && afk != "") {
Chris@455 534 params.push_front(QString("easyhg.authkey=%1").arg(m_authKey));
Chris@455 535 params.push_front("--config");
Chris@455 536 params.push_front(QString("easyhg.authfile=%1").arg(m_authFilePath));
Chris@455 537 params.push_front("--config");
Chris@444 538 }
Chris@444 539
Chris@444 540 // Looks like this one must be without quotes, even though the SSH
Chris@444 541 // one above only works on Windows if it has quotes (at least where
Chris@444 542 // there is a space in the path). Odd
Chris@444 543 params.push_front(QString("extensions.easyhg=%1").arg(extpath));
Chris@444 544 params.push_front("--config");
Chris@444 545
Chris@444 546 return params;
Chris@444 547 }
Chris@444 548
Chris@109 549 void HgRunner::startCommand(HgAction action)
Chris@109 550 {
Chris@340 551 if (action.workingDir.isEmpty()) {
Chris@340 552 // We require a working directory, never just operate in pwd
Chris@537 553 emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one", "");
Chris@340 554 return;
Chris@340 555 }
Chris@340 556
Chris@84 557 m_isRunning = true;
Chris@561 558 m_progress->setRange(0, 0);
Chris@571 559 if (!action.shouldBeFast()) {
Chris@571 560 show();
Chris@571 561 m_cancel->setVisible(action.makesSenseToCancel());
Chris@571 562 }
Chris@110 563 m_stdout.clear();
Chris@110 564 m_stderr.clear();
Chris@84 565 m_realm = "";
Chris@84 566 m_userName = "";
jtkorhonen@0 567
Chris@113 568 m_proc = new QProcess;
Chris@113 569
Chris@177 570 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
Chris@177 571
Chris@177 572 #ifdef Q_OS_WIN32
Chris@257 573 // On Win32 we like to bundle Hg and other executables with EasyHg
Chris@177 574 if (m_myDirPath != "") {
Chris@177 575 env.insert("PATH", m_myDirPath + ";" + env.value("PATH"));
Chris@172 576 }
Chris@172 577 #endif
Chris@172 578
Chris@257 579 #ifdef Q_OS_MAC
Chris@490 580 if (QSettings().value("python32", false).toBool()) {
Chris@261 581 env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1");
Chris@257 582 }
Chris@257 583 #endif
Chris@257 584
Chris@113 585 env.insert("LANG", "en_US.utf8");
Chris@113 586 env.insert("LC_ALL", "en_US.utf8");
Chris@408 587 env.insert("HGENCODING", "utf8");
Chris@113 588 env.insert("HGPLAIN", "1");
Chris@113 589 m_proc->setProcessEnvironment(env);
Chris@113 590
Chris@113 591 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
Chris@189 592 connect(m_proc, SIGNAL(error(QProcess::ProcessError)),
Chris@189 593 this, SLOT(error(QProcess::ProcessError)));
Chris@113 594 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@113 595 this, SLOT(finished(int, QProcess::ExitStatus)));
Chris@113 596 connect(m_proc, SIGNAL(readyReadStandardOutput()),
Chris@113 597 this, SLOT(dataReadyStdout()));
Chris@113 598 connect(m_proc, SIGNAL(readyReadStandardError()),
Chris@113 599 this, SLOT(dataReadyStderr()));
Chris@113 600
Chris@340 601 m_proc->setWorkingDirectory(action.workingDir);
jtkorhonen@0 602
Chris@605 603 if (action.mayBeInteractive()) {
Chris@111 604 openTerminal();
Chris@111 605 if (m_ptySlaveFilename != "") {
Chris@113 606 DEBUG << "HgRunner: connecting to pseudoterminal" << endl;
Chris@107 607 m_proc->setStandardInputFile(m_ptySlaveFilename);
Chris@114 608 // m_proc->setStandardOutputFile(m_ptySlaveFilename);
Chris@113 609 // m_proc->setStandardErrorFile(m_ptySlaveFilename);
Chris@107 610 }
Chris@84 611 }
Chris@84 612
Chris@605 613 QString cmdline = action.executable;
Chris@605 614 foreach (QString param, action.params) cmdline += " " + param;
Chris@64 615 DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
Chris@109 616 << action.workingDir << endl;
Chris@43 617
Chris@109 618 m_currentAction = action;
Chris@109 619
Chris@109 620 DEBUG << "set current action to " << m_currentAction.action << endl;
Chris@109 621
Chris@238 622 emit commandStarting(action);
Chris@238 623
Chris@605 624 m_proc->start(action.executable, action.params);
Chris@84 625 }
Chris@84 626
Chris@84 627 void HgRunner::closeProcInput()
Chris@84 628 {
Chris@84 629 DEBUG << "closeProcInput" << endl;
Chris@84 630
chris@385 631 if (m_proc) m_proc->closeWriteChannel();
Chris@111 632 }
Chris@111 633
Chris@111 634 void HgRunner::openTerminal()
Chris@111 635 {
Chris@111 636 #ifndef Q_OS_WIN32
Chris@111 637 if (m_ptySlaveFilename != "") return; // already open
Chris@111 638 DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl;
Chris@111 639 int master = posix_openpt(O_RDWR | O_NOCTTY);
Chris@111 640 if (master < 0) {
Chris@111 641 DEBUG << "openpt failed" << endl;
Chris@111 642 perror("openpt failed");
Chris@111 643 return;
Chris@111 644 }
Chris@113 645 struct termios t;
Chris@113 646 if (tcgetattr(master, &t)) {
Chris@113 647 DEBUG << "tcgetattr failed" << endl;
Chris@113 648 perror("tcgetattr failed");
Chris@113 649 }
Chris@113 650 cfmakeraw(&t);
Chris@113 651 if (tcsetattr(master, TCSANOW, &t)) {
Chris@113 652 DEBUG << "tcsetattr failed" << endl;
Chris@113 653 perror("tcsetattr failed");
Chris@113 654 }
Chris@111 655 if (grantpt(master)) {
Chris@111 656 perror("grantpt failed");
Chris@111 657 }
Chris@111 658 if (unlockpt(master)) {
Chris@111 659 perror("unlockpt failed");
Chris@111 660 }
Chris@111 661 char *slave = ptsname(master);
Chris@111 662 if (!slave) {
Chris@111 663 perror("ptsname failed");
Chris@111 664 ::close(master);
Chris@111 665 return;
Chris@111 666 }
Chris@111 667 m_ptyMasterFd = master;
Chris@113 668 m_ptyFile = new QFile();
Chris@113 669 connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty()));
Chris@113 670 if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) {
Chris@113 671 DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl;
Chris@113 672 }
Chris@111 673 m_ptySlaveFilename = slave;
Chris@111 674 DEBUG << "HgRunner::openTerminal: succeeded, slave is "
Chris@111 675 << m_ptySlaveFilename << endl;
Chris@111 676 #endif
Chris@111 677 }
Chris@111 678
Chris@111 679 void HgRunner::closeTerminal()
Chris@111 680 {
Chris@84 681 #ifndef Q_OS_WIN32
Chris@84 682 if (m_ptySlaveFilename != "") {
Chris@113 683 delete m_ptyFile;
Chris@113 684 m_ptyFile = 0;
Chris@84 685 ::close(m_ptyMasterFd);
Chris@84 686 m_ptySlaveFilename = "";
Chris@84 687 }
Chris@84 688 #endif
jtkorhonen@0 689 }