annotate src/mainwindow.cpp @ 672:88fa1544b407

Merge from branch qt5. There's much more to be done before we can make another release, but clearly it's going to be done using qt5
author Chris Cannam
date Wed, 05 Dec 2018 09:44:10 +0000
parents ce29dc775650
children 0329bbd4b57c
rev   line source
Chris@585 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
jtkorhonen@0 2
Chris@57 3 /*
Chris@57 4 EasyMercurial
Chris@57 5
Chris@98 6 Based on hgExplorer by Jari Korhonen
Chris@57 7 Copyright (c) 2010 Jari Korhonen
Chris@644 8 Copyright (c) 2013 Chris Cannam
Chris@644 9 Copyright (c) 2013 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 */
Chris@50 17
jtkorhonen@0 18 #include <QStringList>
jtkorhonen@0 19 #include <QDir>
jtkorhonen@24 20 #include <QNetworkInterface>
jtkorhonen@17 21 #include <QHostAddress>
jtkorhonen@17 22 #include <QHostInfo>
jtkorhonen@34 23 #include <QDesktopServices>
Chris@50 24 #include <QStatusBar>
Chris@50 25 #include <QMessageBox>
Chris@50 26 #include <QMenuBar>
Chris@50 27 #include <QApplication>
Chris@50 28 #include <QToolBar>
Chris@61 29 #include <QToolButton>
Chris@50 30 #include <QSettings>
Chris@90 31 #include <QInputDialog>
Chris@359 32 #include <QWidgetAction>
Chris@125 33 #include <QRegExp>
Chris@199 34 #include <QShortcut>
Chris@237 35 #include <QUrl>
Chris@476 36 #include <QDialogButtonBox>
Chris@238 37 #include <QTimer>
Chris@494 38 #include <QTextBrowser>
jtkorhonen@0 39
Chris@53 40 #include "mainwindow.h"
Chris@69 41 #include "multichoicedialog.h"
Chris@64 42 #include "startupdialog.h"
Chris@53 43 #include "colourset.h"
Chris@62 44 #include "debug.h"
Chris@74 45 #include "logparser.h"
Chris@103 46 #include "confirmcommentdialog.h"
Chris@125 47 #include "incomingdialog.h"
Chris@175 48 #include "settingsdialog.h"
Chris@275 49 #include "moreinformationdialog.h"
Chris@331 50 #include "annotatedialog.h"
Chris@229 51 #include "version.h"
Chris@287 52 #include "workstatuswidget.h"
Chris@414 53 #include "hgignoredialog.h"
Chris@491 54 #include "versiontester.h"
Chris@540 55 #include "fswatcher.h"
Chris@53 56
jtkorhonen@0 57
Chris@172 58 MainWindow::MainWindow(QString myDirPath) :
Chris@238 59 m_myDirPath(myDirPath),
Chris@540 60 m_helpDialog(0)
jtkorhonen@0 61 {
Chris@197 62 setWindowIcon(QIcon(":images/easyhg-icon.png"));
Chris@197 63
jtkorhonen@0 64 QString wndTitle;
jtkorhonen@0 65
Chris@284 66 m_showAllFiles = false;
Chris@273 67
Chris@540 68 m_fsWatcher = new FsWatcher();
Chris@540 69 m_fsWatcherToken = m_fsWatcher->getNewToken();
Chris@540 70 m_commandSequenceInProgress = false;
Chris@541 71 connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(checkFilesystem()));
Chris@540 72
Chris@284 73 m_commitsSincePush = 0;
Chris@284 74 m_shouldHgStat = true;
Chris@90 75
jtkorhonen@0 76 createActions();
jtkorhonen@0 77 createMenus();
jtkorhonen@0 78 createToolBars();
jtkorhonen@0 79 createStatusBar();
jtkorhonen@0 80
Chris@284 81 m_runner = new HgRunner(m_myDirPath, this);
Chris@284 82 connect(m_runner, SIGNAL(commandStarting(HgAction)),
Chris@241 83 this, SLOT(commandStarting(HgAction)));
Chris@284 84 connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)),
Chris@109 85 this, SLOT(commandCompleted(HgAction, QString)));
Chris@537 86 connect(m_runner, SIGNAL(commandFailed(HgAction, QString, QString)),
Chris@537 87 this, SLOT(commandFailed(HgAction, QString, QString)));
Chris@564 88 connect(m_runner, SIGNAL(commandCancelled(HgAction)),
Chris@564 89 this, SLOT(commandCancelled(HgAction)));
Chris@284 90 statusBar()->addPermanentWidget(m_runner);
jtkorhonen@0 91
Chris@61 92 setWindowTitle(tr("EasyMercurial"));
jtkorhonen@0 93
Chris@284 94 m_remoteRepoPath = "";
Chris@284 95 m_workFolderPath = "";
jtkorhonen@0 96
jtkorhonen@0 97 readSettings();
jtkorhonen@0 98
Chris@284 99 m_justMerged = false;
Chris@210 100
Chris@210 101 QWidget *central = new QWidget(this);
Chris@210 102 setCentralWidget(central);
Chris@210 103
Chris@210 104 QGridLayout *cl = new QGridLayout(central);
Chris@287 105 int row = 0;
Chris@210 106
Chris@210 107 #ifndef Q_OS_MAC
Chris@210 108 cl->setMargin(0);
Chris@210 109 #endif
jtkorhonen@0 110
Chris@287 111 m_workStatus = new WorkStatusWidget(this);
Chris@558 112 cl->addWidget(m_workStatus, row++, 0);
Chris@287 113
Chris@287 114 m_hgTabs = new HgTabWidget(central, m_workFolderPath);
Chris@287 115 connectTabsSignals();
Chris@287 116
Chris@554 117 cl->addWidget(m_hgTabs, row++, 0, 1, 2);
Chris@287 118
Chris@284 119 connect(m_hgTabs, SIGNAL(selectionChanged()),
Chris@95 120 this, SLOT(enableDisableActions()));
Chris@484 121 connect(m_hgTabs, SIGNAL(showAllChanged()),
Chris@484 122 this, SLOT(showAllChanged()));
Chris@95 123
jtkorhonen@0 124 connectActions();
Chris@120 125 clearState();
jtkorhonen@0 126 enableDisableActions();
jtkorhonen@0 127
Chris@284 128 if (m_firstStart) {
Chris@64 129 startupDialog();
jtkorhonen@0 130 }
jtkorhonen@0 131
Chris@239 132 SettingsDialog::findDefaultLocations(m_myDirPath);
Chris@112 133
Chris@64 134 ColourSet *cs = ColourSet::instance();
Chris@64 135 cs->clearDefaultNames();
Chris@64 136 cs->addDefaultName("");
Chris@153 137 cs->addDefaultName("default");
Chris@64 138 cs->addDefaultName(getUserInfo());
Chris@62 139
Chris@491 140 VersionTester *vt = new VersionTester
Chris@507 141 ("easyhg.org", "/latest-version.txt", EASYHG_VERSION);
Chris@491 142 connect(vt, SIGNAL(newerVersionAvailable(QString)),
Chris@491 143 this, SLOT(newerVersionAvailable(QString)));
Chris@491 144
Chris@175 145 hgTest();
Chris@359 146 updateRecentMenu();
jtkorhonen@0 147 }
jtkorhonen@0 148
jtkorhonen@0 149
jtkorhonen@0 150 void MainWindow::closeEvent(QCloseEvent *)
jtkorhonen@0 151 {
jtkorhonen@0 152 writeSettings();
Chris@284 153 delete m_fsWatcher;
jtkorhonen@0 154 }
jtkorhonen@0 155
jtkorhonen@0 156
Chris@554 157 void MainWindow::resizeEvent(QResizeEvent *)
Chris@554 158 {
Chris@554 159 }
Chris@554 160
Chris@554 161
Chris@64 162 QString MainWindow::getUserInfo() const
Chris@64 163 {
Chris@64 164 QSettings settings;
Chris@64 165 settings.beginGroup("User Information");
Chris@64 166 QString name = settings.value("name", getUserRealName()).toString();
Chris@64 167 QString email = settings.value("email", "").toString();
Chris@64 168
Chris@64 169 QString identifier;
Chris@64 170
Chris@64 171 if (email != "") {
Chris@64 172 identifier = QString("%1 <%2>").arg(name).arg(email);
Chris@64 173 } else {
Chris@64 174 identifier = name;
Chris@64 175 }
Chris@64 176
Chris@64 177 return identifier;
Chris@64 178 }
Chris@64 179
jtkorhonen@0 180 void MainWindow::about()
jtkorhonen@0 181 {
Chris@97 182 QMessageBox::about(this, tr("About EasyMercurial"),
Chris@229 183 tr("<qt><h2>EasyMercurial v%1</h2>"
Chris@228 184 #ifdef Q_OS_MAC
Chris@228 185 "<font size=-1>"
Chris@228 186 #endif
Chris@97 187 "<p>EasyMercurial is a simple user interface for the "
Chris@186 188 "Mercurial</a> version control system.</p>"
Chris@186 189 "<h4>Credits and Copyright</h4>"
Chris@186 190 "<p>Development carried out by Chris Cannam for "
Chris@186 191 "SoundSoftware.ac.uk at the Centre for Digital Music, "
Chris@186 192 "Queen Mary, University of London.</p>"
Chris@186 193 "<p>EasyMercurial is based on HgExplorer by "
Chris@186 194 "Jari Korhonen, with thanks.</p>"
Chris@186 195 "<p style=\"margin-left: 2em;\">"
Chris@644 196 "Copyright &copy; 2013 Queen Mary, University of London.<br>"
Chris@186 197 "Copyright &copy; 2010 Jari Korhonen.<br>"
Chris@644 198 "Copyright &copy; 2013 Chris Cannam."
Chris@186 199 "</p>"
Chris@186 200 "<p style=\"margin-left: 2em;\">"
Chris@186 201 "This program requires Mercurial, by Matt Mackall and others.<br>"
Chris@186 202 "This program uses Qt by Nokia.<br>"
Chris@186 203 "This program uses Nuvola icons by David Vignoni.<br>"
Chris@186 204 "This program may use KDiff3 by Joachim Eibl.<br>"
Chris@186 205 "This program may use PyQt by River Bank Computing.<br>"
Chris@186 206 "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others."
Chris@186 207 "</p>"
Chris@186 208 "<h4>License</h4>"
Chris@186 209 "<p>This program is free software; you can redistribute it and/or "
Chris@97 210 "modify it under the terms of the GNU General Public License as "
Chris@97 211 "published by the Free Software Foundation; either version 2 of the "
Chris@97 212 "License, or (at your option) any later version. See the file "
Chris@223 213 "COPYING included with this distribution for more information.</p>"
Chris@228 214 #ifdef Q_OS_MAC
Chris@228 215 "</font>"
Chris@228 216 #endif
Chris@229 217 ).arg(EASYHG_VERSION));
jtkorhonen@0 218 }
jtkorhonen@0 219
Chris@94 220 void MainWindow::clearSelections()
Chris@94 221 {
Chris@284 222 m_hgTabs->clearSelections();
Chris@94 223 }
jtkorhonen@0 224
Chris@484 225 void MainWindow::showAllChanged()
Chris@199 226 {
Chris@199 227 hgQueryPaths();
Chris@199 228 }
Chris@199 229
Chris@120 230 void MainWindow::hgRefresh()
Chris@120 231 {
Chris@120 232 clearState();
Chris@120 233 hgQueryPaths();
Chris@120 234 }
Chris@120 235
Chris@175 236 void MainWindow::hgTest()
Chris@175 237 {
Chris@175 238 QStringList params;
Chris@175 239 params << "--version";
Chris@382 240 // The path is not necessarily set here, but we need something for
Chris@382 241 // this test or else HgRunner will (cautiously) refuse to run
Chris@382 242 QString path = m_myDirPath;
Chris@382 243 if (path == "") path = QDir::homePath();
Chris@382 244 m_runner->requestAction(HgAction(ACT_TEST_HG, path, params));
Chris@175 245 }
Chris@175 246
Chris@200 247 void MainWindow::hgTestExtension()
Chris@200 248 {
Chris@200 249 QStringList params;
Chris@200 250 params << "--version";
Chris@382 251 // The path is not necessarily set here, but we need something for
Chris@382 252 // this test or else HgRunner will (cautiously) refuse to run
Chris@382 253 QString path = m_myDirPath;
Chris@382 254 if (path == "") path = QDir::homePath();
Chris@382 255 m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, path, params));
Chris@200 256 }
Chris@200 257
jtkorhonen@0 258 void MainWindow::hgStat()
jtkorhonen@0 259 {
Chris@109 260 QStringList params;
Chris@199 261
Chris@542 262 // We always stat all files, regardless of whether we're showing
Chris@542 263 // them all, because we need them for the filesystem monitor
Chris@542 264 params << "stat" << "-A";
Chris@153 265
Chris@284 266 m_lastStatOutput = "";
Chris@273 267
Chris@541 268 // We're about to do a stat, so we can silently bring ourselves
Chris@541 269 // up-to-date on any file changes to this point
Chris@541 270 (void)m_fsWatcher->getChangedPaths(m_fsWatcherToken);
Chris@541 271
Chris@284 272 m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params));
jtkorhonen@0 273 }
jtkorhonen@0 274
Chris@109 275 void MainWindow::hgQueryPaths()
Chris@74 276 {
Chris@484 277 m_showAllFiles = m_hgTabs->shouldShowAll();
Chris@484 278
Chris@210 279 // Quickest is to just read the file
Chris@198 280
Chris@284 281 QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
Chris@198 282
Chris@210 283 QString path;
Chris@210 284
Chris@198 285 if (hgrc.exists()) {
Chris@198 286 QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
Chris@198 287 s.beginGroup("paths");
Chris@210 288 path = s.value("default").toString();
Chris@210 289 }
Chris@198 290
Chris@565 291 // std::cerr << "hgQueryPaths: setting m_remoteRepoPath to " << m_remoteRepoPath << " from file " << hgrc.absoluteFilePath() << std::endl;
Chris@553 292
Chris@284 293 m_remoteRepoPath = path;
Chris@198 294
Chris@210 295 // We have to do this here, because commandCompleted won't be called
Chris@284 296 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 297 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@287 298 updateWorkFolderAndRepoNames();
Chris@210 299
Chris@210 300 hgQueryBranch();
Chris@210 301 return;
Chris@210 302
Chris@210 303 /* The classic method!
Chris@198 304
Chris@109 305 QStringList params;
Chris@109 306 params << "paths";
Chris@284 307 m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params));
Chris@210 308 */
Chris@74 309 }
Chris@74 310
Chris@109 311 void MainWindow::hgQueryBranch()
Chris@106 312 {
Chris@210 313 // Quickest is to just read the file
Chris@198 314
Chris@284 315 QFile hgbr(m_workFolderPath + "/.hg/branch");
Chris@198 316
Chris@210 317 QString br = "default";
Chris@210 318
Chris@198 319 if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) {
Chris@210 320 QByteArray ba = hgbr.readLine();
Chris@210 321 br = QString::fromUtf8(ba).trimmed();
Chris@210 322 }
Chris@210 323
Chris@284 324 m_currentBranch = br;
Chris@210 325
Chris@210 326 // We have to do this here, because commandCompleted won't be called
Chris@210 327 hgStat();
Chris@210 328 return;
Chris@198 329
Chris@210 330 /* The classic method!
Chris@198 331
Chris@109 332 QStringList params;
Chris@109 333 params << "branch";
Chris@284 334 m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params));
Chris@210 335 */
Chris@106 336 }
Chris@106 337
Chris@506 338 void MainWindow::hgQueryHeadsActive()
Chris@506 339 {
Chris@506 340 QStringList params;
Chris@514 341 params << "heads";
Chris@506 342 m_runner->requestAction(HgAction(ACT_QUERY_HEADS_ACTIVE, m_workFolderPath, params));
Chris@506 343 }
Chris@506 344
Chris@109 345 void MainWindow::hgQueryHeads()
jtkorhonen@0 346 {
Chris@109 347 QStringList params;
Chris@137 348 // On empty repos, "hg heads" will fail -- we don't care about
Chris@137 349 // that. Use --closed option so as to include closed branches;
Chris@137 350 // otherwise we'll be stuck if the user updates into one, and our
Chris@137 351 // incremental log will end up with spurious stuff in it because
Chris@137 352 // we won't be pruning at the ends of closed branches
Chris@137 353 params << "heads" << "--closed";
Chris@284 354 m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params));
jtkorhonen@0 355 }
jtkorhonen@0 356
jtkorhonen@0 357 void MainWindow::hgLog()
jtkorhonen@0 358 {
mikel@617 359 QSettings settings;
mikel@617 360 settings.beginGroup("Presentation");
mikel@617 361
Chris@109 362 QStringList params;
Chris@109 363 params << "log";
mikel@617 364 params << "--date";
mikel@617 365 params << settings.value("datefrom", QDate(2000, 1, 1)).toDate().toString("yyyy-MM-dd") + " to " + QDate::currentDate().toString("yyyy-MM-dd");
Chris@109 366 params << "--template";
Chris@125 367 params << Changeset::getLogTemplate();
Chris@109 368
Chris@284 369 m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params));
Chris@109 370 }
Chris@109 371
Chris@150 372 void MainWindow::hgLogIncremental(QStringList prune)
Chris@120 373 {
Chris@305 374 // Sometimes we can be called with prune empty -- it represents
Chris@305 375 // the current heads, but if we have none already and for some
Chris@305 376 // reason are being prompted for an incremental update, we may run
Chris@305 377 // into trouble. In that case, make this a full log instead
Chris@305 378
Chris@305 379 if (prune.empty()) {
Chris@305 380 hgLog();
Chris@305 381 return;
Chris@305 382 }
Chris@305 383
Chris@120 384 QStringList params;
Chris@120 385 params << "log";
Chris@120 386
Chris@150 387 foreach (QString p, prune) {
Chris@153 388 params << "--prune" << Changeset::hashOf(p);
Chris@120 389 }
Chris@120 390
Chris@120 391 params << "--template";
Chris@125 392 params << Changeset::getLogTemplate();
Chris@120 393
Chris@284 394 m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params));
Chris@120 395 }
Chris@109 396
Chris@109 397 void MainWindow::hgQueryParents()
Chris@109 398 {
Chris@109 399 QStringList params;
Chris@109 400 params << "parents";
Chris@284 401 m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params));
Chris@109 402 }
Chris@109 403
Chris@326 404 void MainWindow::hgAnnotateFiles(QStringList files)
Chris@326 405 {
Chris@326 406 QStringList params;
Chris@326 407
Chris@326 408 if (!files.isEmpty()) {
Chris@332 409 params << "annotate" << "-udqc" << "--" << files;
Chris@326 410 m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params));
Chris@326 411 }
Chris@326 412 }
Chris@326 413
Chris@109 414 void MainWindow::hgResolveList()
Chris@109 415 {
Chris@109 416 QStringList params;
jtkorhonen@0 417
Chris@109 418 params << "resolve" << "--list";
Chris@284 419 m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params));
Chris@109 420 }
Chris@109 421
Chris@109 422 void MainWindow::hgAdd()
jtkorhonen@0 423 {
Chris@109 424 // hgExplorer permitted adding "all" files -- I'm not sure
Chris@109 425 // that one is a good idea, let's require the user to select
jtkorhonen@0 426
Chris@326 427 hgAddFiles(m_hgTabs->getSelectedAddableFiles());
Chris@326 428 }
Chris@326 429
Chris@326 430 void MainWindow::hgAddFiles(QStringList files)
Chris@326 431 {
Chris@326 432 QStringList params;
Chris@109 433
Chris@109 434 if (!files.empty()) {
Chris@109 435 params << "add" << "--" << files;
Chris@284 436 m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params));
jtkorhonen@0 437 }
jtkorhonen@0 438 }
jtkorhonen@0 439
Chris@98 440 void MainWindow::hgRemove()
Chris@98 441 {
Chris@326 442 hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles());
Chris@326 443 }
Chris@326 444
Chris@326 445 void MainWindow::hgRemoveFiles(QStringList files)
Chris@326 446 {
Chris@109 447 QStringList params;
Chris@98 448
Chris@109 449 if (!files.empty()) {
Chris@109 450 params << "remove" << "--after" << "--force" << "--" << files;
Chris@284 451 m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params));
Chris@109 452 }
jtkorhonen@0 453 }
jtkorhonen@0 454
jtkorhonen@0 455 void MainWindow::hgCommit()
jtkorhonen@0 456 {
Chris@326 457 hgCommitFiles(QStringList());
Chris@326 458 }
Chris@326 459
Chris@326 460 void MainWindow::hgCommitFiles(QStringList files)
Chris@326 461 {
Chris@109 462 QStringList params;
Chris@109 463 QString comment;
Chris@94 464
Chris@284 465 if (m_justMerged) {
Chris@284 466 comment = m_mergeCommitComment;
Chris@157 467 }
Chris@157 468
Chris@284 469 QStringList allFiles = m_hgTabs->getAllCommittableFiles();
Chris@127 470 QStringList reportFiles = files;
Chris@237 471 if (reportFiles.empty()) {
Chris@237 472 reportFiles = allFiles;
Chris@237 473 }
Chris@103 474
Chris@237 475 QString subsetNote;
Chris@237 476 if (reportFiles != allFiles) {
Chris@237 477 subsetNote = tr("<p><b>Note:</b> you are committing only the files you have selected, not all of the files that have been changed!");
Chris@237 478 }
Chris@237 479
Chris@155 480 QString cf(tr("Commit files"));
Chris@155 481
Chris@311 482 QString branchText;
Chris@311 483 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@311 484 branchText = tr("the default branch");
Chris@311 485 } else {
Chris@311 486 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@311 487 }
Chris@311 488
Chris@109 489 if (ConfirmCommentDialog::confirmAndGetLongComment
Chris@109 490 (this,
Chris@155 491 cf,
Chris@237 492 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@473 493 .arg(tr("You are about to commit changes to the following files in %1:").arg(branchText))
Chris@237 494 .arg(subsetNote),
Chris@237 495 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@473 496 .arg(tr("You are about to commit changes to %n file(s) in %1.", "", reportFiles.size()).arg(branchText))
Chris@237 497 .arg(subsetNote),
Chris@127 498 reportFiles,
Chris@193 499 comment,
Chris@365 500 tr("Co&mmit"))) {
Chris@103 501
Chris@284 502 if (!m_justMerged && !files.empty()) {
Chris@157 503 // User wants to commit selected file(s) (and this is not
Chris@157 504 // merge commit, which would fail if we selected files)
Chris@157 505 params << "commit" << "--message" << comment
Chris@157 506 << "--user" << getUserInfo() << "--" << files;
Chris@109 507 } else {
Chris@109 508 // Commit all changes
Chris@157 509 params << "commit" << "--message" << comment
Chris@157 510 << "--user" << getUserInfo();
jtkorhonen@0 511 }
Chris@109 512
Chris@284 513 m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params));
Chris@284 514 m_mergeCommitComment = "";
jtkorhonen@0 515 }
jtkorhonen@0 516 }
jtkorhonen@0 517
jtkorhonen@34 518 QString MainWindow::filterTag(QString tag)
jtkorhonen@34 519 {
Chris@278 520 for(int i = 0; i < tag.size(); i++) {
Chris@278 521 if (tag[i].isLower() || tag[i].isUpper() ||
Chris@278 522 tag[i].isDigit() || (tag[i] == QChar('.'))) {
jtkorhonen@34 523 //ok
Chris@278 524 } else {
jtkorhonen@34 525 tag[i] = QChar('_');
jtkorhonen@34 526 }
jtkorhonen@34 527 }
jtkorhonen@34 528 return tag;
jtkorhonen@34 529 }
jtkorhonen@34 530
jtkorhonen@34 531
Chris@311 532 void MainWindow::hgNewBranch()
Chris@278 533 {
Chris@278 534 QStringList params;
Chris@278 535 QString branch;
Chris@278 536
Chris@278 537 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@278 538 (this,
Chris@278 539 tr("New Branch"),
Chris@278 540 tr("Enter new branch name:"),
Chris@278 541 branch,
Chris@365 542 tr("Start &Branch"))) {
Chris@278 543 if (!branch.isEmpty()) {//!!! do something better if it is empty
Chris@278 544
Chris@278 545 params << "branch" << filterTag(branch);
Chris@307 546 m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
Chris@278 547 }
Chris@278 548 }
Chris@278 549 }
Chris@278 550
Chris@311 551 void MainWindow::hgNoBranch()
Chris@311 552 {
Chris@311 553 if (m_currentParents.empty()) return;
Chris@311 554
Chris@311 555 QString parentBranch = m_currentParents[0]->branch();
Chris@311 556 if (parentBranch == "") parentBranch = "default";
Chris@311 557
Chris@311 558 QStringList params;
Chris@311 559 params << "branch" << parentBranch;
Chris@311 560 m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
Chris@311 561 }
Chris@311 562
Chris@514 563 void MainWindow::hgCloseBranch()
Chris@514 564 {
Chris@514 565 QStringList params;
Chris@514 566
Chris@514 567 //!!! how to ensure this doesn't happen when uncommitted changes present?
Chris@514 568
Chris@514 569 QString cf(tr("Close branch"));
Chris@514 570 QString comment;
Chris@514 571
Chris@514 572 QString defaultWarning;
Chris@514 573
Chris@648 574 bool haveSprouts = (m_hgMergeAct->isEnabled());
Chris@648 575
Chris@514 576 QString branchText;
Chris@514 577 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@514 578 branchText = tr("the default branch");
Chris@648 579 if (!haveSprouts) {
Chris@648 580 defaultWarning = tr("<p><b>Warning:</b> you are asking to close the default branch. This is not usually a good idea!</p>");
Chris@648 581 }
Chris@514 582 } else {
Chris@514 583 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@514 584 }
Chris@514 585
Chris@648 586 if (haveSprouts) {
Chris@648 587 branchText = tr("a sub-branch of %1").arg(branchText);
Chris@648 588 }
Chris@648 589
Chris@514 590 if (ConfirmCommentDialog::confirmAndGetLongComment
Chris@514 591 (this,
Chris@514 592 cf,
Chris@514 593 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@518 594 .arg(tr("You are about to close %1.<p>This branch will be marked as closed and hidden from the history view.<p>You will still be able to see if it you select \"Show closed branches\" in the history view, and it will be reopened if you commit to it.<p>Please enter your comment for the commit log:").arg(branchText))
Chris@514 595 .arg(defaultWarning),
Chris@514 596 comment,
Chris@514 597 tr("C&lose branch"))) {
Chris@514 598
Chris@514 599 params << "commit" << "--message" << comment
Chris@514 600 << "--user" << getUserInfo() << "--close-branch";
Chris@514 601
Chris@514 602 m_runner->requestAction(HgAction(ACT_CLOSE_BRANCH, m_workFolderPath, params));
Chris@514 603 }
Chris@514 604 }
Chris@514 605
Chris@164 606 void MainWindow::hgTag(QString id)
jtkorhonen@34 607 {
Chris@109 608 QStringList params;
Chris@109 609 QString tag;
jtkorhonen@34 610
Chris@109 611 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@109 612 (this,
Chris@109 613 tr("Tag"),
Chris@109 614 tr("Enter tag:"),
Chris@193 615 tag,
Chris@365 616 tr("Add &Tag"))) {
Chris@164 617 if (!tag.isEmpty()) {//!!! do something better if it is empty
Chris@164 618
Chris@164 619 params << "tag" << "--user" << getUserInfo();
Chris@164 620 params << "--rev" << Changeset::hashOf(id) << filterTag(tag);
Chris@109 621
Chris@284 622 m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params));
jtkorhonen@34 623 }
jtkorhonen@34 624 }
jtkorhonen@34 625 }
jtkorhonen@34 626
Chris@417 627 void MainWindow::initHgIgnore()
jtkorhonen@34 628 {
Chris@284 629 if (!QDir(m_workFolderPath).exists()) return;
Chris@417 630 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@417 631
Chris@249 632 QFile f(hgIgnorePath);
Chris@249 633 if (!f.exists()) {
Chris@249 634 f.open(QFile::WriteOnly);
Chris@249 635 QTextStream *ts = new QTextStream(&f);
Chris@249 636 *ts << "syntax: glob\n";
Chris@249 637 delete ts;
Chris@249 638 f.close();
Chris@249 639 }
Chris@417 640 }
Chris@417 641
Chris@425 642 void MainWindow::hgIgnore()
Chris@425 643 {
Chris@425 644 // hgExplorer permitted adding "all" files -- I'm not sure
Chris@425 645 // that one is a good idea, let's require the user to select
Chris@425 646
Chris@425 647 hgIgnoreFiles(m_hgTabs->getSelectedAddableFiles());
Chris@425 648 }
Chris@425 649
Chris@417 650 void MainWindow::hgEditIgnore()
Chris@417 651 {
Chris@417 652 if (!QDir(m_workFolderPath).exists()) return;
Chris@417 653
Chris@417 654 initHgIgnore();
Chris@417 655
Chris@417 656 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@476 657
Chris@476 658 QFile f(hgIgnorePath);
Chris@476 659 if (!f.exists()) return; // shouldn't happen (after initHgIgnore called)
Chris@476 660
Chris@476 661 if (!f.open(QFile::ReadOnly)) return;
Chris@476 662 QTextStream sin(&f);
Chris@476 663 QString all = sin.readAll();
Chris@476 664 f.close();
Chris@476 665
Chris@476 666 QDialog d;
Chris@476 667 QGridLayout layout;
Chris@476 668 d.setLayout(&layout);
Chris@476 669
Chris@476 670 int row = 0;
Chris@476 671 layout.addWidget(new QLabel(tr("<qt><h3>Ignored File Patterns</h3></qt>")), row++, 0);//!!! todo: link to Hg docs?
Chris@476 672
Chris@476 673 QTextEdit ed;
Chris@476 674 ed.setAcceptRichText(false);
Chris@476 675 ed.setLineWrapMode(QTextEdit::NoWrap);
Chris@476 676 layout.setRowStretch(row, 10);
Chris@476 677 layout.addWidget(&ed, row++, 0);
Chris@179 678
Chris@476 679 QDialogButtonBox bb(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
Chris@476 680 connect(bb.button(QDialogButtonBox::Save), SIGNAL(clicked()),
Chris@476 681 &d, SLOT(accept()));
Chris@476 682 connect(bb.button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
Chris@476 683 &d, SLOT(reject()));
Chris@476 684 layout.addWidget(&bb, row++, 0);
Chris@476 685
Chris@476 686 ed.document()->setPlainText(all);
Chris@476 687
Chris@476 688 d.resize(QSize(300, 400));
Chris@476 689
Chris@476 690 if (d.exec() == QDialog::Accepted) {
Chris@476 691 if (!f.open(QFile::WriteOnly | QFile::Truncate)) {
Chris@476 692 QMessageBox::critical(this, tr("Write failed"),
Chris@476 693 tr("Failed to open file %1 for writing")
Chris@476 694 .arg(f.fileName()));
Chris@476 695 return;
Chris@476 696 }
Chris@476 697 QTextStream sout(&f);
Chris@476 698 sout << ed.document()->toPlainText();
Chris@476 699 f.close();
Chris@179 700 }
Chris@179 701 }
Chris@179 702
Chris@421 703 static QString regexEscape(QString filename)
Chris@421 704 {
Chris@421 705 return filename
Chris@421 706 .replace(".", "\\.")
Chris@421 707 .replace("[", "\\[")
Chris@421 708 .replace("]", "\\]")
Chris@421 709 .replace("(", "\\(")
Chris@421 710 .replace(")", "\\)")
Chris@421 711 .replace("?", "\\?");
Chris@421 712 }
Chris@421 713
Chris@326 714 void MainWindow::hgIgnoreFiles(QStringList files)
Chris@326 715 {
Chris@417 716 if (!QDir(m_workFolderPath).exists() || files.empty()) return;
Chris@413 717
Chris@413 718 // we should:
Chris@413 719 //
Chris@413 720 // * show the user the list of file names selected
Chris@413 721 //
Chris@413 722 // * offer a choice (depending on the files selected?)
Chris@413 723 //
Chris@413 724 // - ignore only these files
Chris@413 725 //
Chris@413 726 // - ignore files with these names, in any subdirectories?
Chris@413 727 //
Chris@413 728 // - ignore all files with this extension (if they have a common
Chris@413 729 // extension)?
Chris@413 730 //
Chris@413 731 // - ignore all files with these extensions (if they have any
Chris@413 732 // extensions?)
Chris@413 733
Chris@414 734 DEBUG << "MainWindow::hgIgnoreFiles: File names are:" << endl;
Chris@414 735 foreach (QString file, files) DEBUG << file << endl;
Chris@414 736
Chris@414 737 QSet<QString> suffixes;
Chris@414 738 foreach (QString file, files) {
Chris@414 739 QString s = QFileInfo(file).suffix();
Chris@414 740 if (s != "") suffixes.insert(s);
Chris@414 741 }
Chris@414 742
Chris@419 743 QString directory;
Chris@429 744 int dirCount = 0;
Chris@419 745 foreach (QString file, files) {
Chris@419 746 QString d = QFileInfo(file).path();
Chris@419 747 if (d != directory) {
Chris@419 748 ++dirCount;
Chris@419 749 directory = d;
Chris@419 750 }
Chris@419 751 }
Chris@421 752 if (dirCount != 1 || directory == ".") directory = "";
Chris@419 753
Chris@414 754 HgIgnoreDialog::IgnoreType itype =
Chris@415 755 HgIgnoreDialog::confirmIgnore
Chris@419 756 (this, files, QStringList::fromSet(suffixes), directory);
Chris@414 757
Chris@416 758 DEBUG << "hgIgnoreFiles: Ignore type is " << itype << endl;
Chris@417 759
Chris@421 760 if (itype == HgIgnoreDialog::IgnoreNothing) return;
Chris@421 761
Chris@417 762 // Now, .hgignore can be switched from regex to glob syntax
Chris@417 763 // part-way through -- and glob is much simpler for us, so we
Chris@417 764 // should do that if it's in regex mode at the end of the file.
Chris@417 765
Chris@417 766 initHgIgnore();
Chris@417 767
Chris@417 768 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@417 769
Chris@417 770 // hgignore file should now exist (initHgIgnore should have
Chris@417 771 // created it if it didn't). Check for glob status first
Chris@417 772
Chris@417 773 QFile f(hgIgnorePath);
Chris@417 774 if (!f.exists()) {
Chris@417 775 std::cerr << "MainWindow::ignoreFiles: Internal error: .hgignore file not found (even though we were supposed to have created it)" << std::endl;
Chris@417 776 return;
Chris@417 777 }
Chris@417 778
Chris@417 779 f.open(QFile::ReadOnly);
Chris@417 780 bool glob = false;
Chris@606 781 bool cr = false; // whether the last line examined ended with a CR
Chris@417 782 while (!f.atEnd()) {
Chris@417 783 QByteArray ba = f.readLine();
Chris@606 784 QString s = QString::fromLocal8Bit(ba);
Chris@606 785 cr = (s.endsWith('\n') || s.endsWith('\r'));
Chris@606 786 s = s.trimmed();
Chris@417 787 if (s.startsWith("syntax:")) {
Chris@417 788 if (s.endsWith("glob")) {
Chris@417 789 glob = true;
Chris@417 790 } else {
Chris@417 791 glob = false;
Chris@417 792 }
Chris@417 793 }
Chris@417 794 }
Chris@417 795 f.close();
Chris@417 796
Chris@417 797 f.open(QFile::Append);
Chris@417 798 QTextStream out(&f);
Chris@417 799
Chris@606 800 if (!cr) {
Chris@606 801 out << endl;
Chris@606 802 }
Chris@606 803
Chris@417 804 if (!glob) {
Chris@417 805 out << "syntax: glob" << endl;
Chris@417 806 }
Chris@417 807
Chris@421 808 QString info = "<qt><h3>" + tr("Ignored files") + "</h3><p>";
Chris@421 809 info += tr("The following lines have been added to the .hgignore file for this working copy:");
Chris@421 810 info += "</p><code>";
Chris@421 811
Chris@421 812 QStringList args;
Chris@417 813 if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
Chris@421 814 args = QStringList::fromSet(suffixes);
Chris@421 815 } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
Chris@421 816 args = files;
Chris@421 817 } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
Chris@421 818 QSet<QString> names;
Chris@421 819 foreach (QString f, files) {
Chris@421 820 names << QFileInfo(f).fileName();
Chris@417 821 }
Chris@421 822 args = QStringList::fromSet(names);
Chris@421 823 } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
Chris@421 824 args << directory;
Chris@421 825 }
Chris@421 826
Chris@421 827 bool first = true;
Chris@421 828
Chris@421 829 foreach (QString a, args) {
Chris@421 830 QString line;
Chris@421 831 if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
Chris@421 832 line = "*." + a;
Chris@421 833 } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
Chris@421 834 // Doesn't seem to be possible to do this with a glob,
Chris@421 835 // because the glob is always unanchored and there is no
Chris@421 836 // equivalent of ^ to anchor it
chris@423 837 line = "re:^" + regexEscape(a) + "$";
Chris@421 838 } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
Chris@421 839 line = a;
Chris@421 840 } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
Chris@421 841 line = "re:^" + regexEscape(a) + "/";
Chris@418 842 }
Chris@421 843 if (line != "") {
Chris@421 844 out << line << endl;
Chris@421 845 if (!first) info += "<br>";
Chris@421 846 first = false;
Chris@421 847 info += xmlEncode(line);
Chris@417 848 }
Chris@417 849 }
Chris@417 850
Chris@419 851 f.close();
Chris@419 852
Chris@421 853 info += "</code></qt>";
Chris@421 854
Chris@421 855 QMessageBox::information(this, tr("Ignored files"),
Chris@421 856 info);
Chris@421 857
Chris@417 858 hgRefresh();
Chris@326 859 }
Chris@326 860
Chris@671 861 void MainWindow::hgUnIgnoreFiles(QStringList)
Chris@326 862 {
Chris@421 863 // Not implemented: edit the .hgignore instead
Chris@421 864 hgEditIgnore();
Chris@326 865 }
Chris@326 866
sam@624 867 void MainWindow::hgShowIn(QStringList files)
sam@624 868 {
sam@624 869 foreach (QString file, files)
sam@624 870 {
sam@624 871 QStringList args;
Chris@638 872 #if defined Q_OS_WIN32
sam@624 873 // Although the Win32 API is quite happy to have
sam@624 874 // forward slashes as directory separators, Windows
sam@624 875 // Explorer is not
sam@635 876 int last = m_workFolderPath.length() - 1;
sam@635 877 char c = m_workFolderPath[last].toAscii();
sam@635 878 if (c == '\\' || c == '/') {
sam@635 879 m_workFolderPath.chop(1);
sam@635 880 }
sam@635 881 file = m_workFolderPath + "\\" + file;
sam@624 882 file = file.replace('/', '\\');
sam@624 883 args << "/select," << file;
sam@624 884 // FIXME: This shouldn't be using a hardcoded path.
sam@624 885 QProcess::execute("c:/windows/explorer.exe", args);
Chris@638 886 #elif defined(Q_OS_MAC)
sam@637 887 file = m_workFolderPath + "/" + file;
sam@637 888 args << "--reveal" << file;
sam@624 889 QProcess::execute("/usr/bin/open", args);
Chris@638 890 #endif
sam@624 891 }
sam@624 892 }
sam@624 893
Chris@239 894 QString MainWindow::getDiffBinaryName()
Chris@179 895 {
Chris@179 896 QSettings settings;
Chris@179 897 settings.beginGroup("Locations");
Chris@239 898 return settings.value("extdiffbinary", "").toString();
Chris@179 899 }
Chris@179 900
Chris@239 901 QString MainWindow::getMergeBinaryName()
Chris@179 902 {
Chris@179 903 QSettings settings;
Chris@179 904 settings.beginGroup("Locations");
Chris@239 905 return settings.value("mergebinary", "").toString();
Chris@179 906 }
Chris@179 907
Chris@168 908 void MainWindow::hgShowSummary()
Chris@168 909 {
Chris@168 910 QStringList params;
Chris@168 911
Chris@168 912 params << "diff" << "--stat";
Chris@168 913
Chris@288 914 m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params));
Chris@168 915 }
Chris@168 916
jtkorhonen@0 917 void MainWindow::hgFolderDiff()
jtkorhonen@0 918 {
Chris@326 919 hgDiffFiles(QStringList());
Chris@326 920 }
Chris@326 921
Chris@326 922 void MainWindow::hgDiffFiles(QStringList files)
Chris@326 923 {
Chris@239 924 QString diff = getDiffBinaryName();
Chris@179 925 if (diff == "") return;
Chris@112 926
Chris@109 927 QStringList params;
jtkorhonen@0 928
Chris@112 929 // Diff parent against working folder (folder diff)
Chris@112 930
Chris@148 931 params << "--config" << "extensions.extdiff=" << "extdiff";
sam@633 932 params << "--program" << diff << "--";
sam@633 933
sam@633 934 QSettings settings;
sam@633 935 if (settings.value("multipleDiffInstances", false).toBool()) {
sam@633 936 foreach (QString file, files) {
sam@633 937 QStringList p = params;
sam@633 938 p << file;
sam@633 939 m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, p));
sam@633 940 }
sam@633 941 }
sam@633 942 else {
sam@633 943 params << files; // may be none: whole dir
sam@633 944 m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
sam@633 945 }
jtkorhonen@0 946 }
jtkorhonen@0 947
Chris@148 948 void MainWindow::hgDiffToCurrent(QString id)
jtkorhonen@0 949 {
Chris@239 950 QString diff = getDiffBinaryName();
Chris@179 951 if (diff == "") return;
Chris@163 952
Chris@148 953 QStringList params;
jtkorhonen@0 954
Chris@148 955 // Diff given revision against working folder
jtkorhonen@0 956
Chris@148 957 params << "--config" << "extensions.extdiff=" << "extdiff";
Chris@179 958 params << "--program" << diff;
Chris@153 959 params << "--rev" << Changeset::hashOf(id);
Chris@148 960
Chris@284 961 m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
jtkorhonen@0 962 }
jtkorhonen@0 963
Chris@148 964 void MainWindow::hgDiffToParent(QString child, QString parent)
Chris@148 965 {
Chris@239 966 QString diff = getDiffBinaryName();
Chris@179 967 if (diff == "") return;
Chris@163 968
Chris@148 969 QStringList params;
Chris@148 970
Chris@281 971 // Diff given revision against parent revision
Chris@148 972
Chris@148 973 params << "--config" << "extensions.extdiff=" << "extdiff";
Chris@179 974 params << "--program" << diff;
Chris@153 975 params << "--rev" << Changeset::hashOf(parent)
Chris@153 976 << "--rev" << Changeset::hashOf(child);
Chris@148 977
Chris@284 978 m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params));
Chris@273 979 }
Chris@326 980
Chris@273 981
Chris@289 982 void MainWindow::hgShowSummaryFor(Changeset *cs)
Chris@288 983 {
Chris@288 984 QStringList params;
Chris@288 985
Chris@289 986 // This will pick a default parent if there is more than one
Chris@289 987 // (whereas with diff we need to supply one). But it does need a
Chris@289 988 // bit more parsing
Chris@289 989 params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id());
Chris@289 990
Chris@289 991 m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath,
Chris@289 992 params, cs));
Chris@148 993 }
Chris@148 994
jtkorhonen@0 995
jtkorhonen@0 996 void MainWindow::hgUpdate()
jtkorhonen@0 997 {
Chris@109 998 QStringList params;
jtkorhonen@0 999
Chris@109 1000 params << "update";
Chris@109 1001
Chris@284 1002 m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
jtkorhonen@0 1003 }
jtkorhonen@0 1004
jtkorhonen@0 1005
Chris@148 1006 void MainWindow::hgUpdateToRev(QString id)
jtkorhonen@0 1007 {
Chris@148 1008 QStringList params;
jtkorhonen@0 1009
Chris@153 1010 params << "update" << "--rev" << Changeset::hashOf(id) << "--check";
jtkorhonen@0 1011
Chris@284 1012 m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
jtkorhonen@0 1013 }
jtkorhonen@0 1014
jtkorhonen@0 1015
jtkorhonen@0 1016 void MainWindow::hgRevert()
jtkorhonen@0 1017 {
Chris@326 1018 hgRevertFiles(QStringList());
Chris@326 1019 }
Chris@326 1020
Chris@326 1021 void MainWindow::hgRevertFiles(QStringList files)
Chris@326 1022 {
Chris@109 1023 QStringList params;
Chris@109 1024 QString comment;
Chris@237 1025 bool all = false;
Chris@98 1026
Chris@284 1027 QStringList allFiles = m_hgTabs->getAllRevertableFiles();
Chris@237 1028 if (files.empty() || files == allFiles) {
Chris@237 1029 files = allFiles;
Chris@237 1030 all = true;
Chris@237 1031 }
Chris@237 1032
Chris@237 1033 QString subsetNote;
Chris@237 1034 if (!all) {
Chris@237 1035 subsetNote = tr("<p><b>Note:</b> you are reverting only the files you have selected, not all of the files that have been changed!");
Chris@237 1036 }
Chris@155 1037
Chris@155 1038 QString rf(tr("Revert files"));
Chris@237 1039
Chris@237 1040 // Set up params before asking for confirmation, because there is
Chris@237 1041 // a failure case here that we would need to report on early
Chris@237 1042
Chris@284 1043 DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl;
Chris@273 1044
Chris@284 1045 if (m_justMerged) {
Chris@237 1046
Chris@237 1047 // This is a little fiddly. The proper way to "revert" the
Chris@237 1048 // whole of an uncommitted merge is with "hg update --clean ."
Chris@237 1049 // But if the user has selected only some files, we're sort of
Chris@237 1050 // promising to revert only those, which means we need to
Chris@237 1051 // specify which parent to revert to. We can only do that if
Chris@237 1052 // we have a record of it, which we do if you just did the
Chris@237 1053 // merge from within easyhg but don't if you've exited and
Chris@237 1054 // restarted, or changed repository, since then. Hmmm.
Chris@237 1055
Chris@237 1056 if (all) {
Chris@237 1057 params << "update" << "--clean" << ".";
Chris@237 1058 } else {
Chris@284 1059 if (m_mergeTargetRevision != "") {
Chris@237 1060 params << "revert" << "--rev"
Chris@284 1061 << Changeset::hashOf(m_mergeTargetRevision)
Chris@237 1062 << "--" << files;
Chris@237 1063 } else {
Chris@237 1064 QMessageBox::information
Chris@237 1065 (this, tr("Unable to revert"),
Chris@237 1066 tr("<qt><b>Sorry, unable to revert these files</b><br><br>EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.<br><br>This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>"));
Chris@237 1067 return;
Chris@237 1068 }
Chris@237 1069 }
Chris@237 1070 } else {
Chris@237 1071 params << "revert" << "--" << files;
Chris@237 1072 }
Chris@237 1073
Chris@109 1074 if (ConfirmCommentDialog::confirmDangerousFilesAction
Chris@109 1075 (this,
Chris@155 1076 rf,
Chris@237 1077 tr("<h3>%1</h3><p>%2%3").arg(rf)
Chris@237 1078 .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed."))
Chris@237 1079 .arg(subsetNote),
Chris@237 1080 tr("<h3>%1</h3><p>%2%3").arg(rf)
Chris@237 1081 .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.", "", files.size()))
Chris@237 1082 .arg(subsetNote),
Chris@193 1083 files,
Chris@365 1084 tr("Re&vert"))) {
Chris@163 1085
Chris@284 1086 m_lastRevertedFiles = files;
Chris@109 1087
Chris@284 1088 m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params));
jtkorhonen@0 1089 }
jtkorhonen@0 1090 }
jtkorhonen@0 1091
Chris@163 1092
Chris@361 1093 void MainWindow::hgRenameFiles(QStringList files)
Chris@361 1094 {
Chris@361 1095 QString renameTo;
Chris@361 1096
Chris@361 1097 QString file;
Chris@361 1098 if (files.empty()) return;
Chris@361 1099 file = files[0];
Chris@361 1100
Chris@361 1101 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@361 1102 (this,
Chris@361 1103 tr("Rename"),
Chris@361 1104 tr("Rename <code>%1</code> to:").arg(xmlEncode(file)),
Chris@361 1105 renameTo,
Chris@365 1106 tr("Re&name"))) {
Chris@361 1107
Chris@361 1108 if (renameTo != "" && renameTo != file) {
Chris@361 1109
Chris@361 1110 QStringList params;
Chris@361 1111
Chris@361 1112 params << "rename" << "--" << file << renameTo;
Chris@361 1113
Chris@361 1114 m_runner->requestAction(HgAction(ACT_RENAME_FILE, m_workFolderPath, params));
Chris@361 1115 }
Chris@361 1116 }
Chris@361 1117 }
Chris@361 1118
Chris@361 1119
Chris@361 1120 void MainWindow::hgCopyFiles(QStringList files)
Chris@361 1121 {
Chris@361 1122 QString copyTo;
Chris@361 1123
Chris@361 1124 QString file;
Chris@361 1125 if (files.empty()) return;
Chris@361 1126 file = files[0];
Chris@361 1127
Chris@361 1128 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@361 1129 (this,
Chris@361 1130 tr("Copy"),
Chris@361 1131 tr("Copy <code>%1</code> to:").arg(xmlEncode(file)),
Chris@361 1132 copyTo,
Chris@365 1133 tr("Co&py"))) {
Chris@361 1134
Chris@361 1135 if (copyTo != "" && copyTo != file) {
Chris@361 1136
Chris@361 1137 QStringList params;
Chris@361 1138
Chris@361 1139 params << "copy" << "--" << file << copyTo;
Chris@361 1140
Chris@361 1141 m_runner->requestAction(HgAction(ACT_COPY_FILE, m_workFolderPath, params));
Chris@361 1142 }
Chris@361 1143 }
Chris@361 1144 }
Chris@361 1145
Chris@361 1146
Chris@326 1147 void MainWindow::hgMarkFilesResolved(QStringList files)
Chris@163 1148 {
Chris@163 1149 QStringList params;
Chris@163 1150
Chris@163 1151 params << "resolve" << "--mark";
Chris@163 1152
Chris@163 1153 if (files.empty()) {
Chris@163 1154 params << "--all";
Chris@163 1155 } else {
Chris@164 1156 params << "--" << files;
Chris@163 1157 }
Chris@163 1158
Chris@284 1159 m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params));
Chris@163 1160 }
Chris@163 1161
Chris@163 1162
Chris@326 1163 void MainWindow::hgRedoMerge()
Chris@326 1164 {
Chris@326 1165 hgRedoFileMerges(QStringList());
Chris@326 1166 }
Chris@326 1167
Chris@326 1168
Chris@326 1169 void MainWindow::hgRedoFileMerges(QStringList files)
jtkorhonen@33 1170 {
Chris@109 1171 QStringList params;
jtkorhonen@33 1172
Chris@163 1173 params << "resolve";
Chris@163 1174
Chris@239 1175 QString merge = getMergeBinaryName();
Chris@179 1176 if (merge != "") {
Chris@179 1177 params << "--tool" << merge;
Chris@163 1178 }
Chris@163 1179
Chris@163 1180 if (files.empty()) {
Chris@163 1181 params << "--all";
Chris@163 1182 } else {
Chris@164 1183 params << "--" << files;
Chris@163 1184 }
Chris@163 1185
Chris@284 1186 if (m_currentParents.size() == 1) {
Chris@284 1187 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@237 1188 }
Chris@237 1189
Chris@284 1190 m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params));
Chris@273 1191
Chris@284 1192 m_mergeCommitComment = tr("Merge");
jtkorhonen@33 1193 }
Chris@326 1194
jtkorhonen@33 1195
jtkorhonen@0 1196 void MainWindow::hgMerge()
jtkorhonen@0 1197 {
Chris@284 1198 if (m_hgTabs->canResolve()) {
Chris@326 1199 hgRedoMerge();
Chris@163 1200 return;
Chris@163 1201 }
Chris@163 1202
Chris@109 1203 QStringList params;
jtkorhonen@0 1204
Chris@109 1205 params << "merge";
Chris@163 1206
Chris@239 1207 QString merge = getMergeBinaryName();
Chris@179 1208 if (merge != "") {
Chris@179 1209 params << "--tool" << merge;
Chris@163 1210 }
Chris@163 1211
Chris@284 1212 if (m_currentParents.size() == 1) {
Chris@284 1213 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@163 1214 }
Chris@163 1215
Chris@284 1216 m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
Chris@273 1217
Chris@284 1218 m_mergeCommitComment = tr("Merge");
jtkorhonen@0 1219 }
jtkorhonen@0 1220
jtkorhonen@0 1221
Chris@148 1222 void MainWindow::hgMergeFrom(QString id)
Chris@148 1223 {
Chris@148 1224 QStringList params;
Chris@148 1225
Chris@148 1226 params << "merge";
Chris@153 1227 params << "--rev" << Changeset::hashOf(id);
Chris@163 1228
Chris@239 1229 QString merge = getMergeBinaryName();
Chris@179 1230 if (merge != "") {
Chris@179 1231 params << "--tool" << merge;
Chris@163 1232 }
Chris@148 1233
Chris@284 1234 if (m_currentParents.size() == 1) {
Chris@284 1235 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@237 1236 }
Chris@237 1237
Chris@284 1238 m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
Chris@273 1239
Chris@284 1240 m_mergeCommitComment = "";
Chris@273 1241
Chris@284 1242 foreach (Changeset *cs, m_currentHeads) {
Chris@284 1243 if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) {
Chris@157 1244 if (cs->branch() == "" || cs->branch() == "default") {
Chris@284 1245 m_mergeCommitComment = tr("Merge from the default branch");
Chris@157 1246 } else {
Chris@284 1247 m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch());
Chris@157 1248 }
Chris@157 1249 }
Chris@157 1250 }
Chris@157 1251
Chris@284 1252 if (m_mergeCommitComment == "") {
Chris@284 1253 m_mergeCommitComment = tr("Merge from %1").arg(id);
Chris@157 1254 }
Chris@148 1255 }
Chris@148 1256
Chris@148 1257
jtkorhonen@0 1258 void MainWindow::hgCloneFromRemote()
jtkorhonen@0 1259 {
Chris@109 1260 QStringList params;
jtkorhonen@0 1261
Chris@284 1262 if (!QDir(m_workFolderPath).exists()) {
Chris@284 1263 if (!QDir().mkpath(m_workFolderPath)) {
Chris@109 1264 DEBUG << "hgCloneFromRemote: Failed to create target path "
Chris@284 1265 << m_workFolderPath << endl;
Chris@607 1266 QMessageBox::critical
Chris@607 1267 (this, tr("Could not create target folder"),
Chris@607 1268 tr("<qt><b>Could not create target folder</b><br><br>The local target folder \"%1\" does not exist<br>and could not be created.</qt>").arg(xmlEncode(m_workFolderPath)));
Chris@608 1269 m_workFolderPath = "";
Chris@109 1270 return;
Chris@104 1271 }
Chris@109 1272 }
Chris@104 1273
Chris@284 1274 params << "clone" << m_remoteRepoPath << m_workFolderPath;
Chris@109 1275
Chris@287 1276 updateWorkFolderAndRepoNames();
Chris@284 1277 m_hgTabs->updateWorkFolderFileList("");
Chris@608 1278 m_hgTabs->clearAll();
Chris@273 1279
Chris@284 1280 m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params));
jtkorhonen@0 1281 }
jtkorhonen@0 1282
jtkorhonen@0 1283 void MainWindow::hgInit()
jtkorhonen@0 1284 {
Chris@109 1285 QStringList params;
jtkorhonen@0 1286
Chris@109 1287 params << "init";
Chris@284 1288 params << m_workFolderPath;
Chris@273 1289
Chris@284 1290 m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params));
jtkorhonen@0 1291 }
jtkorhonen@0 1292
jtkorhonen@0 1293 void MainWindow::hgIncoming()
jtkorhonen@0 1294 {
Chris@109 1295 QStringList params;
jtkorhonen@0 1296
Chris@284 1297 params << "incoming" << "--newest-first" << m_remoteRepoPath;
Chris@125 1298 params << "--template" << Changeset::getLogTemplate();
jtkorhonen@0 1299
Chris@284 1300 m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params));
jtkorhonen@0 1301 }
jtkorhonen@0 1302
jtkorhonen@0 1303 void MainWindow::hgPull()
jtkorhonen@0 1304 {
Chris@193 1305 if (ConfirmCommentDialog::confirm
Chris@126 1306 (this, tr("Confirm pull"),
Chris@299 1307 tr("<qt><h3>Pull from remote repository?</h3></qt>"),
Chris@299 1308 tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)),
Chris@365 1309 tr("&Pull"))) {
jtkorhonen@0 1310
Chris@126 1311 QStringList params;
Chris@284 1312 params << "pull" << m_remoteRepoPath;
Chris@284 1313 m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params));
Chris@126 1314 }
jtkorhonen@0 1315 }
jtkorhonen@0 1316
jtkorhonen@0 1317 void MainWindow::hgPush()
jtkorhonen@0 1318 {
Chris@363 1319 if (m_remoteRepoPath.isEmpty()) {
Chris@363 1320 changeRemoteRepo(true);
Chris@363 1321 if (m_remoteRepoPath.isEmpty()) return;
Chris@363 1322 }
Chris@363 1323
Chris@356 1324 QString uncommittedNote;
Chris@356 1325 if (m_hgTabs->canCommit()) {
Chris@356 1326 uncommittedNote = tr("<p><b>Note:</b> You have uncommitted changes. If you want to push these changes to the remote repository, you need to commit them first.");
Chris@356 1327 }
Chris@356 1328
Chris@193 1329 if (ConfirmCommentDialog::confirm
Chris@126 1330 (this, tr("Confirm push"),
Chris@299 1331 tr("<qt><h3>Push to remote repository?</h3></qt>"),
Chris@356 1332 tr("<qt><p>You are about to push your commits to the remote repository at <code>%1</code>.</p>%2</qt>").arg(xmlEncode(m_remoteRepoPath)).arg(uncommittedNote),
Chris@365 1333 tr("&Push"))) {
jtkorhonen@0 1334
Chris@126 1335 QStringList params;
Chris@284 1336 params << "push" << "--new-branch" << m_remoteRepoPath;
Chris@284 1337 m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params));
Chris@126 1338 }
jtkorhonen@0 1339 }
jtkorhonen@0 1340
Chris@182 1341 QStringList MainWindow::listAllUpIpV4Addresses()
jtkorhonen@26 1342 {
Chris@182 1343 QStringList ret;
jtkorhonen@26 1344 QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
jtkorhonen@26 1345
Chris@182 1346 for (int i = 0; i < ifaces.count(); i++) {
jtkorhonen@26 1347 QNetworkInterface iface = ifaces.at(i);
Chris@182 1348 if (iface.flags().testFlag(QNetworkInterface::IsUp)
Chris@182 1349 && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
Chris@182 1350 for (int j=0; j<iface.addressEntries().count(); j++) {
jtkorhonen@28 1351 QHostAddress tmp = iface.addressEntries().at(j).ip();
Chris@182 1352 if (QAbstractSocket::IPv4Protocol == tmp.protocol()) {
Chris@182 1353 ret.push_back(tmp.toString());
jtkorhonen@24 1354 }
jtkorhonen@24 1355 }
jtkorhonen@22 1356 }
jtkorhonen@17 1357 }
jtkorhonen@28 1358 return ret;
jtkorhonen@28 1359 }
jtkorhonen@17 1360
Chris@120 1361 void MainWindow::clearState()
Chris@120 1362 {
Chris@305 1363 DEBUG << "MainWindow::clearState" << endl;
Chris@284 1364 foreach (Changeset *cs, m_currentParents) delete cs;
Chris@284 1365 m_currentParents.clear();
Chris@284 1366 foreach (Changeset *cs, m_currentHeads) delete cs;
Chris@284 1367 m_currentHeads.clear();
Chris@506 1368 m_closedHeadIds.clear();
Chris@284 1369 m_currentBranch = "";
Chris@284 1370 m_lastStatOutput = "";
Chris@284 1371 m_lastRevertedFiles.clear();
Chris@284 1372 m_mergeTargetRevision = "";
Chris@284 1373 m_mergeCommitComment = "";
Chris@284 1374 m_stateUnknown = true;
Chris@284 1375 m_needNewLog = true;
Chris@120 1376 }
jtkorhonen@17 1377
jtkorhonen@11 1378 void MainWindow::hgServe()
jtkorhonen@11 1379 {
Chris@109 1380 QStringList params;
Chris@109 1381 QString msg;
jtkorhonen@11 1382
Chris@182 1383 QStringList addrs = listAllUpIpV4Addresses();
Chris@182 1384
Chris@182 1385 if (addrs.empty()) {
Chris@182 1386 QMessageBox::critical
Chris@182 1387 (this, tr("Serve"), tr("Failed to identify an active IPv4 address"));
Chris@182 1388 return;
Chris@182 1389 }
Chris@182 1390
Chris@526 1391 // See #202. We really want to display the port that the server
Chris@526 1392 // ends up using -- but we don't get that information until after
Chris@526 1393 // it has exited! However, we can improve the likelihood of
Chris@526 1394 // showing the right port by at least checking whether a port is
Chris@526 1395 // defined in the hgrc file.
Chris@526 1396
Chris@526 1397 QFileInfo hgrc(QDir::homePath() + "/.hgrc");
Chris@526 1398 QString path;
Chris@526 1399 int port = 8000; // the default
Chris@526 1400 if (hgrc.exists()) {
Chris@526 1401 QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
Chris@526 1402 s.beginGroup("web");
Chris@527 1403 int p = s.value("port").toInt();
Chris@527 1404 if (p) port = p;
Chris@526 1405 }
Chris@182 1406
Chris@182 1407 QTextStream ts(&msg);
Chris@429 1408 ts << QString("<qt><h3>%1</h3><p>%2</p>")
Chris@425 1409 .arg(tr("Sharing Repository"))
Chris@429 1410 .arg(tr("Your local repository is now being made temporarily available via HTTP for workgroup access."));
Chris@429 1411 if (addrs.size() > 1) {
Chris@429 1412 ts << QString("<p>%3</p>")
Chris@429 1413 .arg(tr("Users who have network access to your computer can now clone your repository, by using one of the following URLs as a remote location:"));
Chris@429 1414 } else {
Chris@429 1415 ts << QString("<p>%3</p>")
Chris@429 1416 .arg(tr("Users who have network access to your computer can now clone your repository, by using the following URL as a remote location:"));
Chris@429 1417 }
Chris@182 1418 foreach (QString addr, addrs) {
Chris@526 1419 ts << QString("<pre>&nbsp;&nbsp;http://%1:%2</pre>").arg(xmlEncode(addr)).arg(port);
Chris@182 1420 }
Chris@425 1421 ts << tr("<p>Press Close to terminate this server, end remote access, and return.</p>");
Chris@182 1422 ts.flush();
Chris@182 1423
Chris@109 1424 params << "serve";
jtkorhonen@11 1425
Chris@284 1426 m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params));
Chris@109 1427
Chris@425 1428 QMessageBox::information(this, tr("Share Repository"), msg, QMessageBox::Close);
Chris@182 1429
Chris@284 1430 m_runner->killCurrentActions();
jtkorhonen@11 1431 }
jtkorhonen@11 1432
Chris@64 1433 void MainWindow::startupDialog()
Chris@64 1434 {
Chris@64 1435 StartupDialog *dlg = new StartupDialog(this);
Chris@284 1436 if (dlg->exec()) m_firstStart = false;
Chris@344 1437 else exit(0);
Chris@64 1438 }
jtkorhonen@11 1439
Chris@69 1440 void MainWindow::open()
Chris@69 1441 {
Chris@86 1442 bool done = false;
Chris@69 1443
Chris@86 1444 while (!done) {
Chris@69 1445
Chris@86 1446 MultiChoiceDialog *d = new MultiChoiceDialog
Chris@86 1447 (tr("Open Repository"),
Chris@86 1448 tr("<qt><big>What would you like to open?</big></qt>"),
Chris@86 1449 this);
Chris@69 1450
Chris@345 1451 d->addChoice("remote",
Chris@345 1452 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
Chris@345 1453 tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."),
Chris@345 1454 MultiChoiceDialog::UrlToDirectoryArg);
Chris@345 1455
Chris@339 1456 d->addChoice("local",
Chris@339 1457 tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"),
Chris@339 1458 tr("Open an existing local Mercurial repository."),
Chris@339 1459 MultiChoiceDialog::DirectoryArg);
Chris@339 1460
Chris@339 1461 d->addChoice("init",
Chris@339 1462 tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"),
Chris@339 1463 tr("Open a local folder, by creating a Mercurial repository in it."),
Chris@339 1464 MultiChoiceDialog::DirectoryArg);
Chris@339 1465
Chris@248 1466 QSettings settings;
Chris@484 1467 settings.beginGroup("");
Chris@359 1468 QString lastChoice = settings.value("lastopentype", "remote").toString();
Chris@248 1469 if (lastChoice != "local" &&
Chris@248 1470 lastChoice != "remote" &&
Chris@248 1471 lastChoice != "init") {
Chris@359 1472 lastChoice = "remote";
Chris@248 1473 }
Chris@248 1474
Chris@248 1475 d->setCurrentChoice(lastChoice);
Chris@86 1476
Chris@86 1477 if (d->exec() == QDialog::Accepted) {
Chris@86 1478
Chris@86 1479 QString choice = d->getCurrentChoice();
Chris@248 1480 settings.setValue("lastopentype", choice);
Chris@248 1481
Chris@86 1482 QString arg = d->getArgument().trimmed();
Chris@86 1483
Chris@86 1484 bool result = false;
Chris@86 1485
Chris@86 1486 if (choice == "local") {
Chris@86 1487 result = openLocal(arg);
Chris@86 1488 } else if (choice == "remote") {
Chris@86 1489 result = openRemote(arg, d->getAdditionalArgument().trimmed());
Chris@86 1490 } else if (choice == "init") {
Chris@86 1491 result = openInit(arg);
Chris@86 1492 }
Chris@86 1493
Chris@86 1494 if (result) {
Chris@86 1495 enableDisableActions();
Chris@120 1496 clearState();
Chris@109 1497 hgQueryPaths();
Chris@91 1498 done = true;
Chris@91 1499 }
Chris@86 1500
Chris@86 1501 } else {
Chris@86 1502
Chris@86 1503 // cancelled
Chris@86 1504 done = true;
Chris@69 1505 }
Chris@79 1506
Chris@86 1507 delete d;
Chris@69 1508 }
Chris@69 1509 }
Chris@69 1510
Chris@359 1511 void MainWindow::recentMenuActivated()
Chris@359 1512 {
Chris@359 1513 QAction *a = qobject_cast<QAction *>(sender());
Chris@359 1514 if (!a) return;
Chris@359 1515 QString local = a->text();
Chris@359 1516 open(local);
Chris@359 1517 }
Chris@359 1518
Chris@182 1519 void MainWindow::changeRemoteRepo()
Chris@182 1520 {
Chris@363 1521 changeRemoteRepo(false);
Chris@363 1522 }
Chris@363 1523
Chris@363 1524 void MainWindow::changeRemoteRepo(bool initial)
Chris@363 1525 {
Chris@183 1526 // This will involve rewriting the local .hgrc
Chris@183 1527
Chris@284 1528 QDir hgDir(m_workFolderPath + "/.hg");
Chris@184 1529 if (!hgDir.exists()) {
Chris@184 1530 //!!! visible error!
Chris@184 1531 return;
Chris@184 1532 }
Chris@184 1533
Chris@284 1534 QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
Chris@184 1535 if (hgrc.exists() && !hgrc.isWritable()) {
Chris@183 1536 //!!! visible error!
Chris@183 1537 return;
Chris@183 1538 }
Chris@183 1539
Chris@183 1540 MultiChoiceDialog *d = new MultiChoiceDialog
Chris@363 1541 (tr("Set Remote Location"),
Chris@363 1542 tr("<qt><big>Set the remote location</big></qt>"),
Chris@183 1543 this);
Chris@183 1544
Chris@363 1545 QString explanation;
Chris@363 1546 if (initial) {
Chris@609 1547 explanation = tr("Provide a remote URL to use when pushing from, or pulling to, the local<br>repository <code>%1</code>.<br>This will be the default for subsequent pushes and pulls.<br>You can change it using &ldquo;Set Remote Location&rdquo; on the File menu.").arg(m_workFolderPath);
Chris@363 1548 } else {
Chris@609 1549 explanation = tr("Provide a new remote URL to use when pushing from, or pulling to, the local<br>repository <code>%1</code>.").arg(m_workFolderPath);
Chris@363 1550 }
Chris@363 1551
Chris@183 1552 d->addChoice("remote",
Chris@183 1553 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
Chris@363 1554 explanation,
Chris@553 1555 MultiChoiceDialog::UrlArg,
Chris@553 1556 true); // default empty
Chris@183 1557
Chris@183 1558 if (d->exec() == QDialog::Accepted) {
Chris@210 1559
Chris@210 1560 // New block to ensure QSettings is deleted before
Chris@210 1561 // hgQueryPaths called. NB use of absoluteFilePath instead of
Chris@210 1562 // canonicalFilePath, which would fail if the file did not yet
Chris@210 1563 // exist
Chris@210 1564
Chris@210 1565 {
Chris@210 1566 QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat);
Chris@210 1567 s.beginGroup("paths");
Chris@210 1568 s.setValue("default", d->getArgument());
Chris@210 1569 }
Chris@210 1570
Chris@284 1571 m_stateUnknown = true;
Chris@183 1572 hgQueryPaths();
Chris@183 1573 }
Chris@183 1574
Chris@183 1575 delete d;
Chris@182 1576 }
Chris@182 1577
Chris@145 1578 void MainWindow::open(QString local)
Chris@145 1579 {
Chris@145 1580 if (openLocal(local)) {
Chris@145 1581 enableDisableActions();
Chris@145 1582 clearState();
Chris@145 1583 hgQueryPaths();
Chris@145 1584 }
Chris@145 1585 }
Chris@145 1586
Chris@79 1587 bool MainWindow::complainAboutFilePath(QString arg)
Chris@79 1588 {
Chris@79 1589 QMessageBox::critical
Chris@79 1590 (this, tr("File chosen"),
Chris@84 1591 tr("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg)));
Chris@79 1592 return false;
Chris@79 1593 }
Chris@79 1594
Chris@248 1595 bool MainWindow::askAboutUnknownFolder(QString arg)
Chris@248 1596 {
Chris@248 1597 bool result = (QMessageBox::question
Chris@248 1598 (this, tr("Path does not exist"),
Chris@248 1599 tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").arg(xmlEncode(arg)),
Chris@248 1600 QMessageBox::Ok | QMessageBox::Cancel,
Chris@248 1601 QMessageBox::Cancel)
Chris@248 1602 == QMessageBox::Ok);
Chris@248 1603 if (result) {
Chris@248 1604 QDir dir(arg);
Chris@248 1605 dir.cdUp();
Chris@248 1606 if (!dir.mkpath(dir.absolutePath())) {
Chris@248 1607 QMessageBox::critical
Chris@248 1608 (this, tr("Failed to create folder"),
Chris@248 1609 tr("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath()));
Chris@248 1610 return false;
Chris@248 1611 }
Chris@248 1612 return true;
Chris@248 1613 }
Chris@248 1614 return false;
Chris@248 1615 }
Chris@248 1616
Chris@79 1617 bool MainWindow::complainAboutUnknownFolder(QString arg)
Chris@79 1618 {
Chris@79 1619 QMessageBox::critical
Chris@79 1620 (this, tr("Folder does not exist"),
Chris@84 1621 tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg)));
Chris@84 1622 return false;
Chris@84 1623 }
Chris@84 1624
Chris@84 1625 bool MainWindow::complainAboutInitInRepo(QString arg)
Chris@84 1626 {
Chris@84 1627 QMessageBox::critical
Chris@84 1628 (this, tr("Path is in existing repository"),
Chris@84 1629 tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg)));
Chris@84 1630 return false;
Chris@84 1631 }
Chris@84 1632
Chris@84 1633 bool MainWindow::complainAboutInitFile(QString arg)
Chris@84 1634 {
Chris@84 1635 QMessageBox::critical
Chris@84 1636 (this, tr("Path is a file"),
Chris@84 1637 tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg)));
Chris@84 1638 return false;
Chris@84 1639 }
Chris@84 1640
Chris@84 1641 bool MainWindow::complainAboutCloneToExisting(QString arg)
Chris@84 1642 {
Chris@84 1643 QMessageBox::critical
Chris@84 1644 (this, tr("Path is in existing repository"),
Chris@237 1645 tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
Chris@84 1646 return false;
Chris@84 1647 }
Chris@84 1648
Chris@84 1649 bool MainWindow::complainAboutCloneToFile(QString arg)
Chris@84 1650 {
Chris@84 1651 QMessageBox::critical
Chris@84 1652 (this, tr("Path is a file"),
Chris@84 1653 tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").arg(xmlEncode(arg)));
Chris@84 1654 return false;
Chris@84 1655 }
Chris@84 1656
Chris@237 1657 QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote)
Chris@84 1658 {
Chris@348 1659 // If the directory "arg" exists but is empty, then we accept it.
Chris@348 1660
Chris@348 1661 // If the directory "arg" exists and is non-empty, but "arg" plus
Chris@348 1662 // the last path component of "remote" does not exist, then offer
Chris@348 1663 // the latter as an alternative path.
Chris@237 1664
Chris@237 1665 QString offer;
Chris@237 1666
Chris@237 1667 QDir d(arg);
Chris@348 1668
Chris@237 1669 if (d.exists()) {
Chris@348 1670
Chris@348 1671 if (d.entryList(QDir::Dirs | QDir::Files |
Chris@348 1672 QDir::NoDotAndDotDot |
Chris@348 1673 QDir::Hidden | QDir::System).empty()) {
Chris@348 1674 // directory is empty; accept it
Chris@348 1675 return arg;
Chris@348 1676 }
Chris@348 1677
Chris@237 1678 if (QRegExp("^\\w+://").indexIn(remote) >= 0) {
Chris@237 1679 QString rpath = QUrl(remote).path();
Chris@237 1680 if (rpath != "") {
Chris@237 1681 rpath = QDir(rpath).dirName();
Chris@237 1682 if (rpath != "" && !d.exists(rpath)) {
Chris@237 1683 offer = d.filePath(rpath);
Chris@237 1684 }
Chris@237 1685 }
Chris@237 1686 }
Chris@237 1687 }
Chris@237 1688
Chris@237 1689 if (offer != "") {
Chris@237 1690 bool result = (QMessageBox::question
Chris@237 1691 (this, tr("Folder exists"),
Chris@237 1692 tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>")
Chris@237 1693 .arg(xmlEncode(arg)).arg(xmlEncode(offer)),
Chris@237 1694 QMessageBox::Ok | QMessageBox::Cancel,
Chris@237 1695 QMessageBox::Cancel)
Chris@237 1696 == QMessageBox::Ok);
Chris@237 1697 if (result) return offer;
Chris@237 1698 else return "";
Chris@237 1699 }
Chris@237 1700
Chris@84 1701 QMessageBox::critical
Chris@84 1702 (this, tr("Folder exists"),
Chris@237 1703 tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
Chris@237 1704 return "";
Chris@79 1705 }
Chris@79 1706
Chris@79 1707 bool MainWindow::askToOpenParentRepo(QString arg, QString parent)
Chris@79 1708 {
Chris@79 1709 return (QMessageBox::question
Chris@84 1710 (this, tr("Path is inside a repository"),
Chris@86 1711 tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>")
Chris@79 1712 .arg(xmlEncode(arg)).arg(xmlEncode(parent)),
Chris@79 1713 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1714 QMessageBox::Ok)
Chris@79 1715 == QMessageBox::Ok);
Chris@79 1716 }
Chris@79 1717
Chris@79 1718 bool MainWindow::askToInitExisting(QString arg)
Chris@79 1719 {
Chris@79 1720 return (QMessageBox::question
Chris@84 1721 (this, tr("Folder has no repository"),
Chris@359 1722 tr("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder is not a Mercurial working copy.<br><br>Would you like to initialise a repository here?</qt>")
Chris@79 1723 .arg(xmlEncode(arg)),
Chris@79 1724 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1725 QMessageBox::Ok)
Chris@79 1726 == QMessageBox::Ok);
Chris@79 1727 }
Chris@79 1728
Chris@79 1729 bool MainWindow::askToInitNew(QString arg)
Chris@79 1730 {
Chris@79 1731 return (QMessageBox::question
Chris@84 1732 (this, tr("Folder does not exist"),
Chris@84 1733 tr("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>")
Chris@84 1734 .arg(xmlEncode(arg)),
Chris@84 1735 QMessageBox::Ok | QMessageBox::Cancel,
Chris@84 1736 QMessageBox::Ok)
Chris@84 1737 == QMessageBox::Ok);
Chris@84 1738 }
Chris@84 1739
Chris@84 1740 bool MainWindow::askToOpenInsteadOfInit(QString arg)
Chris@84 1741 {
Chris@84 1742 return (QMessageBox::question
Chris@84 1743 (this, tr("Repository exists"),
Chris@84 1744 tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository. Would you like to open it?</qt>")
Chris@79 1745 .arg(xmlEncode(arg)),
Chris@79 1746 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1747 QMessageBox::Ok)
Chris@79 1748 == QMessageBox::Ok);
Chris@79 1749 }
Chris@79 1750
Chris@79 1751 bool MainWindow::openLocal(QString local)
Chris@79 1752 {
Chris@79 1753 DEBUG << "open " << local << endl;
Chris@79 1754
Chris@79 1755 FolderStatus status = getFolderStatus(local);
Chris@79 1756 QString containing = getContainingRepoFolder(local);
Chris@79 1757
Chris@79 1758 switch (status) {
Chris@79 1759
Chris@79 1760 case FolderHasRepo:
Chris@79 1761 // fine
Chris@79 1762 break;
Chris@79 1763
Chris@79 1764 case FolderExists:
Chris@79 1765 if (containing != "") {
Chris@79 1766 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1767 local = containing;
Chris@79 1768 } else {
Chris@86 1769 //!!! No -- this is likely to happen far more by accident
Chris@86 1770 // than because the user actually wanted to init something.
Chris@86 1771 // Don't ask, just politely reject.
Chris@79 1772 if (!askToInitExisting(local)) return false;
Chris@79 1773 return openInit(local);
Chris@79 1774 }
Chris@79 1775 break;
Chris@79 1776
Chris@79 1777 case FolderParentExists:
Chris@79 1778 if (containing != "") {
Chris@79 1779 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1780 local = containing;
Chris@79 1781 } else {
Chris@79 1782 if (!askToInitNew(local)) return false;
Chris@79 1783 return openInit(local);
Chris@79 1784 }
Chris@79 1785 break;
Chris@79 1786
Chris@79 1787 case FolderUnknown:
Chris@84 1788 if (containing != "") {
Chris@84 1789 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1790 local = containing;
Chris@84 1791 } else {
Chris@84 1792 return complainAboutUnknownFolder(local);
Chris@84 1793 }
Chris@84 1794 break;
Chris@79 1795
Chris@79 1796 case FolderIsFile:
Chris@79 1797 return complainAboutFilePath(local);
Chris@79 1798 }
Chris@79 1799
Chris@284 1800 m_workFolderPath = local;
Chris@284 1801 m_remoteRepoPath = "";
Chris@79 1802 return true;
Chris@79 1803 }
Chris@79 1804
Chris@79 1805 bool MainWindow::openRemote(QString remote, QString local)
Chris@79 1806 {
Chris@79 1807 DEBUG << "clone " << remote << " to " << local << endl;
Chris@84 1808
Chris@84 1809 FolderStatus status = getFolderStatus(local);
Chris@84 1810 QString containing = getContainingRepoFolder(local);
Chris@84 1811
Chris@84 1812 DEBUG << "status = " << status << ", containing = " << containing << endl;
Chris@84 1813
Chris@84 1814 if (status == FolderHasRepo || containing != "") {
Chris@84 1815 return complainAboutCloneToExisting(local);
Chris@84 1816 }
Chris@84 1817
Chris@84 1818 if (status == FolderIsFile) {
Chris@84 1819 return complainAboutCloneToFile(local);
Chris@84 1820 }
Chris@84 1821
Chris@84 1822 if (status == FolderUnknown) {
Chris@248 1823 if (!askAboutUnknownFolder(local)) {
Chris@248 1824 return false;
Chris@248 1825 }
Chris@84 1826 }
Chris@84 1827
Chris@84 1828 if (status == FolderExists) {
Chris@237 1829 local = complainAboutCloneToExistingFolder(local, remote);
Chris@237 1830 if (local == "") return false;
Chris@84 1831 }
Chris@84 1832
Chris@284 1833 m_workFolderPath = local;
Chris@284 1834 m_remoteRepoPath = remote;
Chris@84 1835 hgCloneFromRemote();
Chris@84 1836
Chris@79 1837 return true;
Chris@79 1838 }
Chris@79 1839
Chris@84 1840 bool MainWindow::openInit(QString local)
Chris@79 1841 {
Chris@84 1842 DEBUG << "openInit " << local << endl;
Chris@84 1843
Chris@84 1844 FolderStatus status = getFolderStatus(local);
Chris@84 1845 QString containing = getContainingRepoFolder(local);
Chris@84 1846
Chris@84 1847 DEBUG << "status = " << status << ", containing = " << containing << endl;
Chris@84 1848
Chris@84 1849 if (status == FolderHasRepo) {
Chris@84 1850 if (!askToOpenInsteadOfInit(local)) return false;
Chris@552 1851 return openLocal(local);
Chris@84 1852 }
Chris@84 1853
Chris@84 1854 if (containing != "") {
Chris@84 1855 return complainAboutInitInRepo(local);
Chris@84 1856 }
Chris@84 1857
Chris@84 1858 if (status == FolderIsFile) {
Chris@84 1859 return complainAboutInitFile(local);
Chris@84 1860 }
Chris@84 1861
Chris@84 1862 if (status == FolderUnknown) {
Chris@84 1863 return complainAboutUnknownFolder(local);
Chris@84 1864 }
Chris@84 1865
Chris@284 1866 m_workFolderPath = local;
Chris@284 1867 m_remoteRepoPath = "";
Chris@84 1868 hgInit();
Chris@79 1869 return true;
Chris@79 1870 }
Chris@79 1871
jtkorhonen@0 1872 void MainWindow::settings()
jtkorhonen@0 1873 {
Chris@472 1874 settings(SettingsDialog::PersonalDetailsTab);
Chris@472 1875 }
Chris@472 1876
Chris@472 1877 void MainWindow::settings(SettingsDialog::Tab tab)
Chris@472 1878 {
jtkorhonen@0 1879 SettingsDialog *settingsDlg = new SettingsDialog(this);
Chris@472 1880 settingsDlg->setCurrentTab(tab);
jtkorhonen@0 1881 settingsDlg->exec();
Chris@230 1882
Chris@230 1883 if (settingsDlg->presentationChanged()) {
Chris@284 1884 m_hgTabs->updateFileStates();
Chris@230 1885 updateToolBarStyle();
Chris@273 1886 hgRefresh();
Chris@230 1887 }
jtkorhonen@0 1888 }
jtkorhonen@0 1889
Chris@541 1890 void MainWindow::updateFsWatcher()
Chris@541 1891 {
Chris@541 1892 m_fsWatcher->setWorkDirPath(m_workFolderPath);
Chris@541 1893 m_fsWatcher->setTrackedFilePaths(m_hgTabs->getFileStates().trackedFiles());
Chris@541 1894 }
Chris@541 1895
Chris@238 1896 void MainWindow::checkFilesystem()
Chris@238 1897 {
Chris@238 1898 DEBUG << "MainWindow::checkFilesystem" << endl;
Chris@541 1899 if (!m_commandSequenceInProgress) {
Chris@541 1900 if (!m_fsWatcher->getChangedPaths(m_fsWatcherToken).empty()) {
Chris@541 1901 hgRefresh();
Chris@541 1902 return;
Chris@541 1903 }
Chris@549 1904 updateFsWatcher();
Chris@541 1905 }
Chris@90 1906 }
Chris@90 1907
Chris@275 1908 QString MainWindow::format1(QString head)
Chris@275 1909 {
Chris@275 1910 return QString("<qt><h3>%1</h3></qt>").arg(head);
Chris@275 1911 }
Chris@275 1912
Chris@125 1913 QString MainWindow::format3(QString head, QString intro, QString code)
Chris@125 1914 {
Chris@196 1915 code = xmlEncode(code).replace("\n", "<br>")
Chris@196 1916 #ifndef Q_OS_WIN32
Chris@196 1917 // The hard hyphen comes out funny on Windows
Chris@196 1918 .replace("-", "&#8209;")
Chris@196 1919 #endif
Chris@196 1920 .replace(" ", "&nbsp;");
Chris@125 1921 if (intro == "") {
Chris@168 1922 return QString("<qt><h3>%1</h3><p><code>%2</code></p>")
Chris@168 1923 .arg(head).arg(code);
Chris@126 1924 } else if (code == "") {
Chris@126 1925 return QString("<qt><h3>%1</h3><p>%2</p>")
Chris@126 1926 .arg(head).arg(intro);
Chris@125 1927 } else {
Chris@168 1928 return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>")
Chris@168 1929 .arg(head).arg(intro).arg(code);
Chris@125 1930 }
Chris@125 1931 }
Chris@125 1932
Chris@120 1933 void MainWindow::showIncoming(QString output)
Chris@120 1934 {
Chris@284 1935 m_runner->hide();
Chris@125 1936 IncomingDialog *d = new IncomingDialog(this, output);
Chris@125 1937 d->exec();
Chris@125 1938 delete d;
Chris@125 1939 }
Chris@125 1940
Chris@125 1941 int MainWindow::extractChangeCount(QString text)
Chris@125 1942 {
Chris@125 1943 QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+");
Chris@125 1944 if (re.indexIn(text) >= 0) {
Chris@125 1945 return re.cap(1).toInt();
Chris@125 1946 } else if (text.contains("no changes")) {
Chris@125 1947 return 0;
Chris@125 1948 } else {
Chris@125 1949 return -1; // unknown
Chris@125 1950 }
Chris@120 1951 }
Chris@120 1952
Chris@120 1953 void MainWindow::showPushResult(QString output)
Chris@120 1954 {
Chris@291 1955 QString head;
Chris@125 1956 QString report;
Chris@125 1957 int n = extractChangeCount(output);
Chris@125 1958 if (n > 0) {
Chris@291 1959 head = tr("Pushed %n changeset(s)", "", n);
Chris@300 1960 report = tr("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").arg(xmlEncode(m_remoteRepoPath));
Chris@125 1961 } else if (n == 0) {
Chris@291 1962 head = tr("No changes to push");
Chris@291 1963 report = tr("The remote repository already contains all changes that have been committed locally.");
Chris@294 1964 if (m_hgTabs->canCommit()) {
Chris@291 1965 report = tr("%1<p>You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report);
Chris@291 1966 }
Chris@125 1967 } else {
Chris@291 1968 head = tr("Push complete");
Chris@125 1969 }
Chris@284 1970 m_runner->hide();
Chris@291 1971
Chris@291 1972 MoreInformationDialog::information(this, tr("Push complete"),
Chris@291 1973 head, report, output);
Chris@120 1974 }
Chris@120 1975
Chris@120 1976 void MainWindow::showPullResult(QString output)
Chris@120 1977 {
Chris@291 1978 QString head;
Chris@125 1979 QString report;
Chris@125 1980 int n = extractChangeCount(output);
Chris@125 1981 if (n > 0) {
Chris@291 1982 head = tr("Pulled %n changeset(s)", "", n);
Chris@322 1983 report = tr("New changes will be highlighted in yellow in the history.");
Chris@125 1984 } else if (n == 0) {
Chris@291 1985 head = tr("No changes to pull");
Chris@291 1986 report = tr("Your local repository already contains all changes found in the remote repository.");
Chris@125 1987 } else {
Chris@291 1988 head = tr("Pull complete");
Chris@125 1989 }
Chris@284 1990 m_runner->hide();
Chris@275 1991
Chris@275 1992 MoreInformationDialog::information(this, tr("Pull complete"),
Chris@291 1993 head, report, output);
Chris@120 1994 }
Chris@120 1995
Chris@174 1996 void MainWindow::reportNewRemoteHeads(QString output)
Chris@174 1997 {
Chris@174 1998 bool headsAreLocal = false;
Chris@174 1999
Chris@284 2000 if (m_currentParents.size() == 1) {
Chris@506 2001 int currentBranchActiveHeads = 0;
Chris@174 2002 bool parentIsHead = false;
Chris@284 2003 Changeset *parent = m_currentParents[0];
Chris@506 2004 foreach (Changeset *head, m_activeHeads) {
Chris@284 2005 if (head->isOnBranch(m_currentBranch)) {
Chris@506 2006 ++currentBranchActiveHeads;
Chris@174 2007 }
Chris@174 2008 if (parent->id() == head->id()) {
Chris@174 2009 parentIsHead = true;
Chris@174 2010 }
Chris@174 2011 }
Chris@506 2012 if (currentBranchActiveHeads == 2 && parentIsHead) {
Chris@174 2013 headsAreLocal = true;
Chris@174 2014 }
Chris@174 2015 }
Chris@174 2016
Chris@174 2017 if (headsAreLocal) {
Chris@291 2018 MoreInformationDialog::warning
Chris@291 2019 (this,
Chris@291 2020 tr("Push failed"),
Chris@291 2021 tr("Push failed"),
Chris@291 2022 tr("Your local repository could not be pushed to the remote repository.<br><br>You may need to merge the changes locally first."),
Chris@291 2023 output);
Chris@318 2024 } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) {
Chris@318 2025 MoreInformationDialog::warning
Chris@318 2026 (this,
Chris@318 2027 tr("Push failed"),
Chris@318 2028 tr("Push failed"),
Chris@318 2029 tr("Your local repository could not be pushed to the remote repository.<br><br>You have an uncommitted merge in your local folder. You probably need to commit it before you push."),
Chris@318 2030 output);
Chris@174 2031 } else {
Chris@291 2032 MoreInformationDialog::warning
Chris@291 2033 (this,
Chris@291 2034 tr("Push failed"),
Chris@291 2035 tr("Push failed"),
Chris@291 2036 tr("Your local repository could not be pushed to the remote repository.<br><br>The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."),
Chris@291 2037 output);
Chris@174 2038 }
Chris@174 2039 }
Chris@174 2040
Chris@353 2041 void MainWindow::reportAuthFailed(QString output)
Chris@353 2042 {
Chris@353 2043 MoreInformationDialog::warning
Chris@353 2044 (this,
Chris@353 2045 tr("Authorization failed"),
Chris@353 2046 tr("Authorization failed"),
Chris@353 2047 tr("You may have entered an incorrect user name or password, or the remote URL may be wrong.<br><br>Or you may lack the necessary permissions on the remote repository.<br><br>Check with the administrator of your remote repository if necessary."),
Chris@353 2048 output);
Chris@353 2049 }
Chris@353 2050
Chris@671 2051 void MainWindow::commandStarting(HgAction)
Chris@238 2052 {
Chris@540 2053 m_commandSequenceInProgress = true;
Chris@238 2054 }
Chris@238 2055
Chris@546 2056 void MainWindow::commandFailed(HgAction action, QString stdErr, QString stdOut)
Chris@62 2057 {
Chris@62 2058 DEBUG << "MainWindow::commandFailed" << endl;
Chris@540 2059
Chris@540 2060 m_commandSequenceInProgress = false;
Chris@74 2061
Chris@210 2062 QString setstr;
Chris@210 2063 #ifdef Q_OS_MAC
Chris@210 2064 setstr = tr("Preferences");
Chris@210 2065 #else
Chris@210 2066 setstr = tr("Settings");
Chris@210 2067 #endif
Chris@210 2068
Chris@353 2069 // Some commands we just have to ignore bad return values from,
Chris@353 2070 // and some output gets special treatment.
Chris@353 2071
Chris@353 2072 // Note our fallback case should always be to report a
Chris@353 2073 // non-specific error and show the text -- in case output scraping
Chris@353 2074 // fails (as it surely will). Note also that we must force the
Chris@353 2075 // locale in order to ensure the output is scrapable; this happens
Chris@353 2076 // in HgRunner and may break some system encodings.
Chris@113 2077
Chris@113 2078 switch(action.action) {
Chris@113 2079 case ACT_NONE:
Chris@113 2080 // uh huh
Chris@113 2081 return;
Chris@175 2082 case ACT_TEST_HG:
Chris@291 2083 MoreInformationDialog::warning
Chris@291 2084 (this,
Chris@291 2085 tr("Failed to run Mercurial"),
Chris@291 2086 tr("Failed to run Mercurial"),
Chris@483 2087 tr("The Mercurial program either could not be found or failed to run.<br><br>Check that the Mercurial program path is correct in %1.").arg(setstr),
Chris@545 2088 stdErr);
Chris@472 2089 settings(SettingsDialog::PathsTab);
Chris@200 2090 return;
Chris@200 2091 case ACT_TEST_HG_EXT:
Chris@309 2092 MoreInformationDialog::warning
Chris@291 2093 (this,
Chris@291 2094 tr("Failed to run Mercurial"),
Chris@291 2095 tr("Failed to run Mercurial with extension enabled"),
Chris@310 2096 tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.<br>This may indicate an installation problem.<br><br>You may be able to continue working if you switch off &ldquo;Use EasyHg Mercurial Extension&rdquo; in %1. Note that remote repositories that require authentication might not work if you do this.").arg(setstr),
Chris@545 2097 stdErr);
Chris@642 2098 settings(SettingsDialog::ExtensionsTab);
Chris@175 2099 return;
Chris@252 2100 case ACT_CLONEFROMREMOTE:
Chris@252 2101 // if clone fails, we have no repo
Chris@284 2102 m_workFolderPath = "";
Chris@252 2103 enableDisableActions();
Chris@330 2104 break; // go on to default report
Chris@113 2105 case ACT_INCOMING:
Chris@545 2106 if (stdErr.contains("authorization failed")) {
Chris@545 2107 reportAuthFailed(stdErr);
Chris@353 2108 return;
Chris@545 2109 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2110 // ignore this, user cancelled username or password dialog
Chris@358 2111 return;
Chris@353 2112 } else {
Chris@545 2113 // Incoming returns non-zero code and no stdErr if the
Chris@353 2114 // check was successful but there are no changes
Chris@353 2115 // pending. This is the only case where we need to remove
Chris@353 2116 // warning messages, because it's the only case where a
Chris@353 2117 // non-zero code can be returned even though the command
Chris@353 2118 // has for our purposes succeeded
Chris@661 2119 QStringList lines = stdErr.split(QRegExp("[\\r\\n]+"));
Chris@661 2120 QString replaced;
Chris@661 2121 foreach (QString line, lines) {
Chris@661 2122 line.replace(QRegExp("^.*warning: [^\\n]*"), "");
Chris@661 2123 if (line != "") {
Chris@661 2124 replaced += line + "\n";
Chris@661 2125 }
Chris@352 2126 }
Chris@352 2127 if (replaced == "") {
Chris@352 2128 showIncoming("");
Chris@352 2129 return;
Chris@352 2130 }
Chris@211 2131 }
Chris@330 2132 break; // go on to default report
Chris@353 2133 case ACT_PULL:
Chris@545 2134 if (stdErr.contains("authorization failed")) {
Chris@545 2135 reportAuthFailed(stdErr);
Chris@353 2136 return;
Chris@545 2137 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2138 // ignore this, user cancelled username or password dialog
Chris@358 2139 return;
Chris@546 2140 } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
Chris@537 2141 // success: hg 2.1 starts returning failure code for empty pull/push
Chris@540 2142 m_commandSequenceInProgress = true; // there may be further commands
Chris@546 2143 commandCompleted(action, stdOut);
Chris@537 2144 return;
Chris@353 2145 }
Chris@353 2146 break; // go on to default report
Chris@353 2147 case ACT_PUSH:
Chris@545 2148 if (stdErr.contains("creates new remote head")) {
Chris@545 2149 reportNewRemoteHeads(stdErr);
Chris@353 2150 return;
Chris@545 2151 } else if (stdErr.contains("authorization failed")) {
Chris@545 2152 reportAuthFailed(stdErr);
Chris@353 2153 return;
Chris@545 2154 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2155 // ignore this, user cancelled username or password dialog
Chris@358 2156 return;
Chris@546 2157 } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
Chris@537 2158 // success: hg 2.1 starts returning failure code for empty pull/push
Chris@540 2159 m_commandSequenceInProgress = true; // there may be further commands
Chris@546 2160 commandCompleted(action, stdOut);
Chris@537 2161 return;
Chris@353 2162 }
Chris@353 2163 break; // go on to default report
Chris@506 2164 case ACT_QUERY_HEADS_ACTIVE:
Chris@162 2165 case ACT_QUERY_HEADS:
Chris@162 2166 // fails if repo is empty; we don't care (if there's a genuine
Chris@202 2167 // problem, something else will fail too). Pretend it
Chris@202 2168 // succeeded, so that any further actions that are contingent
Chris@202 2169 // on the success of the heads query get carried out properly.
Chris@540 2170 m_commandSequenceInProgress = true; // there may be further commands
Chris@202 2171 commandCompleted(action, "");
Chris@162 2172 return;
Chris@113 2173 case ACT_FOLDERDIFF:
Chris@113 2174 case ACT_CHGSETDIFF:
Chris@545 2175 // external program, unlikely to be anything useful in stdErr
Chris@113 2176 // and some return with failure codes when something as basic
Chris@113 2177 // as the user closing the window via the wm happens
Chris@113 2178 return;
Chris@328 2179 case ACT_MERGE:
Chris@545 2180 if (stdErr.contains("working directory ancestor")) {
Chris@530 2181 // arguably we should prevent this upfront, but that's
Chris@530 2182 // trickier!
Chris@530 2183 MoreInformationDialog::information
Chris@530 2184 (this, tr("Merge"), tr("Merge has no effect"),
Chris@530 2185 tr("You asked to merge a revision with one of its ancestors.<p>This has no effect, because the ancestor's changes already exist in both revisions."),
Chris@545 2186 stdErr);
Chris@530 2187 return;
Chris@530 2188 }
Chris@530 2189 // else fall through
Chris@328 2190 case ACT_RETRY_MERGE:
Chris@328 2191 MoreInformationDialog::information
Chris@328 2192 (this, tr("Merge"), tr("Merge failed"),
Chris@328 2193 tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."),
Chris@545 2194 stdErr);
Chris@378 2195 m_mergeCommitComment = "";
Chris@603 2196 hgQueryPaths();
Chris@328 2197 return;
Chris@199 2198 case ACT_STAT:
Chris@330 2199 break; // go on to default report
Chris@113 2200 default:
Chris@114 2201 break;
Chris@113 2202 }
Chris@113 2203
Chris@113 2204 QString command = action.executable;
Chris@113 2205 if (command == "") command = "hg";
Chris@113 2206 foreach (QString arg, action.params) {
Chris@113 2207 command += " " + arg;
Chris@113 2208 }
Chris@113 2209
Chris@309 2210 MoreInformationDialog::warning
Chris@309 2211 (this,
Chris@309 2212 tr("Command failed"),
Chris@309 2213 tr("Command failed"),
Chris@545 2214 (stdErr == "" ?
Chris@483 2215 tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.") :
Chris@483 2216 tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See &ldquo;More Details&rdquo; for the command output.")),
Chris@545 2217 stdErr);
Chris@62 2218 }
Chris@62 2219
Chris@109 2220 void MainWindow::commandCompleted(HgAction completedAction, QString output)
jtkorhonen@0 2221 {
Chris@521 2222 // std::cerr << "commandCompleted: " << completedAction.action << std::endl;
Chris@519 2223
Chris@109 2224 HGACTIONS action = completedAction.action;
Chris@109 2225
Chris@109 2226 if (action == ACT_NONE) return;
Chris@109 2227
Chris@519 2228 output.replace("\r\n", "\n");
Chris@519 2229
Chris@150 2230 bool headsChanged = false;
Chris@150 2231 QStringList oldHeadIds;
Chris@150 2232
Chris@150 2233 switch (action) {
Chris@109 2234
Chris@175 2235 case ACT_TEST_HG:
Chris@377 2236 {
Chris@377 2237 QRegExp versionRE("^Mercurial.*version ([\\d+])\\.([\\d+])");
Chris@377 2238 int pos = versionRE.indexIn(output);
Chris@377 2239 if (pos >= 0) {
Chris@377 2240 int major = versionRE.cap(1).toInt();
Chris@377 2241 int minor = versionRE.cap(2).toInt();
Chris@377 2242 // We need v1.7 or newer
Chris@377 2243 if (major < 1 || (major == 1 && minor < 7)) {
Chris@377 2244 MoreInformationDialog::warning
Chris@377 2245 (this,
Chris@377 2246 tr("Newer Mercurial version required"),
Chris@377 2247 tr("Newer Mercurial version required"),
Chris@377 2248 tr("To use EasyMercurial, you should have at least Mercurial v1.7 installed.<br><br>The version found on this system (v%1.%2) does not support all of the features required by EasyMercurial.").arg(major).arg(minor),
Chris@377 2249 output);
Chris@377 2250 }
Chris@377 2251 }
Chris@175 2252 break;
Chris@377 2253 }
Chris@175 2254
Chris@200 2255 case ACT_TEST_HG_EXT:
Chris@200 2256 break;
Chris@200 2257
Chris@109 2258 case ACT_QUERY_PATHS:
jtkorhonen@0 2259 {
Chris@109 2260 DEBUG << "stdout is " << output << endl;
Chris@109 2261 LogParser lp(output, "=");
Chris@109 2262 LogList ll = lp.parse();
Chris@109 2263 DEBUG << ll.size() << " results" << endl;
Chris@109 2264 if (!ll.empty()) {
Chris@284 2265 m_remoteRepoPath = lp.parse()[0]["default"].trimmed();
Chris@284 2266 DEBUG << "Set remote path to " << m_remoteRepoPath << endl;
Chris@210 2267 } else {
Chris@284 2268 m_remoteRepoPath = "";
Chris@109 2269 }
Chris@284 2270 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 2271 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@287 2272 updateWorkFolderAndRepoNames();
Chris@109 2273 break;
Chris@109 2274 }
jtkorhonen@0 2275
Chris@109 2276 case ACT_QUERY_BRANCH:
Chris@284 2277 m_currentBranch = output.trimmed();
Chris@109 2278 break;
jtkorhonen@0 2279
Chris@109 2280 case ACT_STAT:
Chris@284 2281 m_lastStatOutput = output;
Chris@109 2282 break;
Chris@163 2283
Chris@163 2284 case ACT_RESOLVE_LIST:
Chris@505 2285 // This happens on every update, after the stat (above)
Chris@163 2286 if (output != "") {
Chris@163 2287 // Remove lines beginning with R (they are resolved,
Chris@163 2288 // and the file stat parser treats R as removed)
Chris@163 2289 QStringList outList = output.split('\n');
Chris@163 2290 QStringList winnowed;
Chris@163 2291 foreach (QString line, outList) {
Chris@163 2292 if (!line.startsWith("R ")) winnowed.push_back(line);
Chris@163 2293 }
Chris@163 2294 output = winnowed.join("\n");
Chris@163 2295 }
Chris@284 2296 DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl;
Chris@199 2297 DEBUG << "resolve output = " << output << endl;
Chris@284 2298 m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output);
Chris@163 2299 break;
Chris@163 2300
Chris@163 2301 case ACT_RESOLVE_MARK:
Chris@284 2302 m_shouldHgStat = true;
Chris@163 2303 break;
Chris@109 2304
Chris@109 2305 case ACT_INCOMING:
Chris@120 2306 showIncoming(output);
Chris@120 2307 break;
Chris@120 2308
Chris@109 2309 case ACT_ANNOTATE:
Chris@331 2310 {
Chris@331 2311 AnnotateDialog dialog(this, output);
Chris@331 2312 dialog.exec();
Chris@284 2313 m_shouldHgStat = true;
Chris@109 2314 break;
Chris@331 2315 }
Chris@109 2316
Chris@109 2317 case ACT_PULL:
Chris@120 2318 showPullResult(output);
Chris@284 2319 m_shouldHgStat = true;
Chris@109 2320 break;
Chris@109 2321
Chris@109 2322 case ACT_PUSH:
Chris@120 2323 showPushResult(output);
Chris@109 2324 break;
Chris@109 2325
Chris@109 2326 case ACT_INIT:
Chris@284 2327 MultiChoiceDialog::addRecentArgument("init", m_workFolderPath);
Chris@284 2328 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@109 2329 enableDisableActions();
Chris@284 2330 m_shouldHgStat = true;
Chris@109 2331 break;
Chris@109 2332
Chris@109 2333 case ACT_CLONEFROMREMOTE:
Chris@284 2334 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 2335 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@284 2336 MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true);
Chris@291 2337 MoreInformationDialog::information
Chris@291 2338 (this,
Chris@291 2339 tr("Clone"),
Chris@384 2340 tr("Open successful"),
Chris@295 2341 tr("The remote repository was successfully cloned to the local folder <code>%1</code>.").arg(xmlEncode(m_workFolderPath)),
Chris@291 2342 output);
Chris@109 2343 enableDisableActions();
Chris@284 2344 m_shouldHgStat = true;
Chris@109 2345 break;
Chris@109 2346
Chris@109 2347 case ACT_LOG:
Chris@284 2348 m_hgTabs->setNewLog(output);
Chris@284 2349 m_needNewLog = false;
Chris@120 2350 break;
Chris@120 2351
Chris@120 2352 case ACT_LOG_INCREMENTAL:
Chris@284 2353 m_hgTabs->addIncrementalLog(output);
Chris@109 2354 break;
Chris@109 2355
Chris@109 2356 case ACT_QUERY_PARENTS:
Chris@152 2357 {
Chris@284 2358 foreach (Changeset *cs, m_currentParents) delete cs;
Chris@284 2359 m_currentParents = Changeset::parseChangesets(output);
Chris@284 2360 QStringList parentIds = Changeset::getIds(m_currentParents);
Chris@284 2361 m_hgTabs->setCurrent(parentIds, m_currentBranch);
Chris@152 2362 }
Chris@109 2363 break;
Chris@109 2364
Chris@506 2365 case ACT_QUERY_HEADS_ACTIVE:
Chris@506 2366 foreach (Changeset *cs, m_activeHeads) delete cs;
Chris@506 2367 m_activeHeads = Changeset::parseChangesets(output);
Chris@506 2368 break;
Chris@506 2369
Chris@109 2370 case ACT_QUERY_HEADS:
Chris@150 2371 {
Chris@284 2372 oldHeadIds = Changeset::getIds(m_currentHeads);
Chris@150 2373 Changesets newHeads = Changeset::parseChangesets(output);
Chris@150 2374 QStringList newHeadIds = Changeset::getIds(newHeads);
Chris@150 2375 if (oldHeadIds != newHeadIds) {
Chris@150 2376 DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl;
Chris@305 2377 DEBUG << "Old heads: " << oldHeadIds.join(",") << endl;
Chris@305 2378 DEBUG << "New heads: " << newHeadIds.join(",") << endl;
Chris@150 2379 headsChanged = true;
Chris@284 2380 foreach (Changeset *cs, m_currentHeads) delete cs;
Chris@284 2381 m_currentHeads = newHeads;
Chris@506 2382 updateClosedHeads();
Chris@150 2383 }
Chris@150 2384 }
Chris@109 2385 break;
Chris@130 2386
Chris@130 2387 case ACT_COMMIT:
Chris@347 2388 if (m_currentParents.empty()) {
Chris@347 2389 // first commit to empty repo
Chris@347 2390 m_needNewLog = true;
Chris@347 2391 }
Chris@284 2392 m_hgTabs->clearSelections();
Chris@284 2393 m_justMerged = false;
Chris@284 2394 m_shouldHgStat = true;
Chris@130 2395 break;
Chris@163 2396
Chris@514 2397 case ACT_CLOSE_BRANCH:
Chris@514 2398 m_hgTabs->clearSelections();
Chris@514 2399 m_justMerged = false;
Chris@514 2400 m_shouldHgStat = true;
Chris@514 2401 break;
Chris@514 2402
Chris@163 2403 case ACT_REVERT:
Chris@326 2404 hgMarkFilesResolved(m_lastRevertedFiles);
Chris@284 2405 m_justMerged = false;
Chris@163 2406 break;
Chris@109 2407
Chris@109 2408 case ACT_REMOVE:
Chris@109 2409 case ACT_ADD:
Chris@284 2410 m_hgTabs->clearSelections();
Chris@284 2411 m_shouldHgStat = true;
Chris@116 2412 break;
Chris@116 2413
Chris@164 2414 case ACT_TAG:
Chris@284 2415 m_needNewLog = true;
Chris@284 2416 m_shouldHgStat = true;
Chris@164 2417 break;
Chris@164 2418
Chris@278 2419 case ACT_NEW_BRANCH:
Chris@307 2420 m_shouldHgStat = true;
Chris@278 2421 break;
Chris@278 2422
Chris@288 2423 case ACT_UNCOMMITTED_SUMMARY:
Chris@168 2424 QMessageBox::information(this, tr("Change summary"),
Chris@168 2425 format3(tr("Summary of uncommitted changes"),
Chris@168 2426 "",
Chris@168 2427 output));
Chris@168 2428 break;
Chris@168 2429
Chris@288 2430 case ACT_DIFF_SUMMARY:
Chris@289 2431 {
Chris@289 2432 // Output has log info first, diff following after a blank line
Chris@289 2433 QStringList olist = output.split("\n\n", QString::SkipEmptyParts);
Chris@289 2434 if (olist.size() > 1) output = olist[1];
Chris@289 2435
Chris@289 2436 Changeset *cs = (Changeset *)completedAction.extraData;
Chris@289 2437 if (cs) {
Chris@289 2438 QMessageBox::information
Chris@289 2439 (this, tr("Change summary"),
Chris@289 2440 format3(tr("Summary of changes"),
Chris@289 2441 cs->formatHtml(),
Chris@289 2442 output));
Chris@289 2443 } else if (output == "") {
Chris@289 2444 // Can happen, for a merge commit (depending on parent)
Chris@288 2445 QMessageBox::information(this, tr("Change summary"),
Chris@288 2446 format3(tr("Summary of changes"),
Chris@288 2447 tr("No changes"),
Chris@288 2448 output));
Chris@288 2449 } else {
Chris@288 2450 QMessageBox::information(this, tr("Change summary"),
Chris@288 2451 format3(tr("Summary of changes"),
Chris@288 2452 "",
Chris@288 2453 output));
Chris@288 2454 }
Chris@288 2455 break;
Chris@289 2456 }
Chris@288 2457
Chris@109 2458 case ACT_FOLDERDIFF:
Chris@109 2459 case ACT_CHGSETDIFF:
Chris@109 2460 case ACT_SERVE:
Chris@109 2461 case ACT_HG_IGNORE:
Chris@284 2462 m_shouldHgStat = true;
Chris@109 2463 break;
Chris@109 2464
Chris@109 2465 case ACT_UPDATE:
Chris@162 2466 QMessageBox::information(this, tr("Update"), tr("<qt><h3>Update successful</h3><p>%1</p>").arg(xmlEncode(output)));
Chris@284 2467 m_shouldHgStat = true;
Chris@109 2468 break;
Chris@109 2469
Chris@109 2470 case ACT_MERGE:
Chris@294 2471 MoreInformationDialog::information
Chris@294 2472 (this, tr("Merge"), tr("Merge successful"),
Chris@302 2473 tr("Remember to test and commit the result before making any further changes."),
Chris@294 2474 output);
Chris@284 2475 m_shouldHgStat = true;
Chris@284 2476 m_justMerged = true;
Chris@109 2477 break;
Chris@109 2478
Chris@109 2479 case ACT_RETRY_MERGE:
Chris@163 2480 QMessageBox::information(this, tr("Resolved"),
Chris@302 2481 tr("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>"));
Chris@284 2482 m_shouldHgStat = true;
Chris@284 2483 m_justMerged = true;
Chris@109 2484 break;
Chris@109 2485
Chris@109 2486 default:
Chris@109 2487 break;
Chris@109 2488 }
Chris@108 2489
Chris@121 2490 // Sequence when no full log required:
Chris@520 2491 // paths -> branch -> stat -> resolve-list -> heads ->
Chris@150 2492 // incremental-log (only if heads changed) -> parents
Chris@150 2493 //
Chris@121 2494 // Sequence when full log required:
Chris@520 2495 // paths -> branch -> stat -> resolve-list -> heads ->
Chris@519 2496 // parents -> log
Chris@150 2497 //
Chris@150 2498 // Note we want to call enableDisableActions only once, at the end
Chris@150 2499 // of whichever sequence is in use.
Chris@150 2500
Chris@156 2501 bool noMore = false;
Chris@156 2502
Chris@150 2503 switch (action) {
Chris@175 2504
Chris@175 2505 case ACT_TEST_HG:
Chris@248 2506 {
Chris@248 2507 QSettings settings;
Chris@248 2508 if (settings.value("useextension", true).toBool()) {
Chris@248 2509 hgTestExtension();
Chris@284 2510 } else if (m_workFolderPath == "") {
Chris@248 2511 open();
Chris@248 2512 } else {
Chris@248 2513 hgQueryPaths();
Chris@248 2514 }
Chris@200 2515 break;
Chris@248 2516 }
Chris@200 2517
Chris@200 2518 case ACT_TEST_HG_EXT:
Chris@284 2519 if (m_workFolderPath == "") {
Chris@248 2520 open();
Chris@248 2521 } else{
Chris@248 2522 hgQueryPaths();
Chris@248 2523 }
Chris@175 2524 break;
Chris@150 2525
Chris@150 2526 case ACT_QUERY_PATHS:
Chris@519 2527 // NB this call is duplicated in hgQueryPaths
Chris@109 2528 hgQueryBranch();
Chris@150 2529 break;
Chris@150 2530
Chris@150 2531 case ACT_QUERY_BRANCH:
Chris@519 2532 // NB this call is duplicated in hgQueryBranch
Chris@109 2533 hgStat();
Chris@150 2534 break;
Chris@150 2535
Chris@150 2536 case ACT_STAT:
Chris@163 2537 hgResolveList();
Chris@163 2538 break;
Chris@519 2539
Chris@163 2540 case ACT_RESOLVE_LIST:
Chris@506 2541 hgQueryHeadsActive();
Chris@506 2542 break;
Chris@506 2543
Chris@506 2544 case ACT_QUERY_HEADS_ACTIVE:
Chris@150 2545 hgQueryHeads();
Chris@150 2546 break;
Chris@150 2547
Chris@150 2548 case ACT_QUERY_HEADS:
Chris@284 2549 if (headsChanged && !m_needNewLog) {
Chris@150 2550 hgLogIncremental(oldHeadIds);
Chris@121 2551 } else {
Chris@150 2552 hgQueryParents();
Chris@121 2553 }
Chris@150 2554 break;
Chris@150 2555
Chris@150 2556 case ACT_LOG_INCREMENTAL:
Chris@109 2557 hgQueryParents();
Chris@150 2558 break;
Chris@150 2559
Chris@150 2560 case ACT_QUERY_PARENTS:
Chris@284 2561 if (m_needNewLog) {
Chris@120 2562 hgLog();
Chris@150 2563 } else {
Chris@150 2564 // we're done
Chris@156 2565 noMore = true;
Chris@120 2566 }
Chris@150 2567 break;
Chris@150 2568
Chris@150 2569 case ACT_LOG:
Chris@150 2570 // we're done
Chris@156 2571 noMore = true;
Chris@198 2572 break;
Chris@150 2573
Chris@150 2574 default:
Chris@284 2575 if (m_shouldHgStat) {
Chris@284 2576 m_shouldHgStat = false;
Chris@109 2577 hgQueryPaths();
Chris@150 2578 } else {
Chris@156 2579 noMore = true;
jtkorhonen@0 2580 }
Chris@150 2581 break;
Chris@150 2582 }
Chris@156 2583
Chris@156 2584 if (noMore) {
Chris@540 2585 m_commandSequenceInProgress = false;
Chris@284 2586 m_stateUnknown = false;
Chris@156 2587 enableDisableActions();
Chris@284 2588 m_hgTabs->updateHistory();
Chris@359 2589 updateRecentMenu();
Chris@541 2590 checkFilesystem();
Chris@156 2591 }
jtkorhonen@0 2592 }
jtkorhonen@0 2593
Chris@671 2594 void MainWindow::commandCancelled(HgAction)
Chris@564 2595 {
Chris@564 2596 // Originally I had this checking whether the cancelled action was
Chris@564 2597 // a network one and, if so, calling hgQueryPaths to update the
Chris@564 2598 // local view in case it had changed anything. But that doesn't
Chris@564 2599 // work properly -- because at this point, although the command
Chris@564 2600 // has been cancelled and a kill signal sent, it hasn't actually
Chris@564 2601 // exited yet. If we request another command now, it will go on
Chris@564 2602 // the stack and be associated with the failed exit forthcoming
Chris@564 2603 // from the cancelled command -- giving the user a disturbing
Chris@564 2604 // command-failed dialog
Chris@564 2605 }
Chris@564 2606
jtkorhonen@0 2607 void MainWindow::connectActions()
jtkorhonen@0 2608 {
Chris@284 2609 connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close()));
Chris@284 2610 connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about()));
Chris@494 2611 connect(m_helpAct, SIGNAL(triggered()), this, SLOT(help()));
Chris@273 2612
Chris@284 2613 connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh()));
Chris@284 2614 connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
Chris@284 2615 connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd()));
Chris@284 2616 connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit()));
Chris@425 2617 connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore()));
Chris@425 2618 connect(m_hgEditIgnoreAct, SIGNAL(triggered()), this, SLOT(hgEditIgnore()));
Chris@284 2619 connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff()));
Chris@284 2620 connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate()));
Chris@284 2621 connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert()));
Chris@284 2622 connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge()));
Chris@273 2623
Chris@284 2624 connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings()));
Chris@284 2625 connect(m_openAct, SIGNAL(triggered()), this, SLOT(open()));
Chris@284 2626 connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo()));
Chris@273 2627
Chris@284 2628 connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming()));
Chris@284 2629 connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull()));
Chris@284 2630 connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush()));
Chris@273 2631
Chris@284 2632 connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
jtkorhonen@0 2633 }
Chris@141 2634
Chris@141 2635 void MainWindow::connectTabsSignals()
Chris@141 2636 {
Chris@327 2637 connect(m_hgTabs, SIGNAL(currentChanged(int)),
Chris@327 2638 this, SLOT(enableDisableActions()));
Chris@327 2639
Chris@284 2640 connect(m_hgTabs, SIGNAL(commit()),
Chris@141 2641 this, SLOT(hgCommit()));
Chris@141 2642
Chris@284 2643 connect(m_hgTabs, SIGNAL(revert()),
Chris@141 2644 this, SLOT(hgRevert()));
Chris@141 2645
Chris@284 2646 connect(m_hgTabs, SIGNAL(diffWorkingFolder()),
Chris@141 2647 this, SLOT(hgFolderDiff()));
Chris@168 2648
Chris@284 2649 connect(m_hgTabs, SIGNAL(showSummary()),
Chris@168 2650 this, SLOT(hgShowSummary()));
Chris@311 2651
Chris@311 2652 connect(m_hgTabs, SIGNAL(newBranch()),
Chris@311 2653 this, SLOT(hgNewBranch()));
Chris@311 2654
Chris@311 2655 connect(m_hgTabs, SIGNAL(noBranch()),
Chris@311 2656 this, SLOT(hgNoBranch()));
Chris@148 2657
Chris@284 2658 connect(m_hgTabs, SIGNAL(updateTo(QString)),
Chris@148 2659 this, SLOT(hgUpdateToRev(QString)));
Chris@141 2660
Chris@284 2661 connect(m_hgTabs, SIGNAL(diffToCurrent(QString)),
Chris@148 2662 this, SLOT(hgDiffToCurrent(QString)));
Chris@141 2663
Chris@284 2664 connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)),
Chris@148 2665 this, SLOT(hgDiffToParent(QString, QString)));
Chris@141 2666
Chris@289 2667 connect(m_hgTabs, SIGNAL(showSummary(Changeset *)),
Chris@289 2668 this, SLOT(hgShowSummaryFor(Changeset *)));
Chris@288 2669
Chris@284 2670 connect(m_hgTabs, SIGNAL(mergeFrom(QString)),
Chris@148 2671 this, SLOT(hgMergeFrom(QString)));
Chris@164 2672
Chris@307 2673 connect(m_hgTabs, SIGNAL(newBranch(QString)),
Chris@311 2674 this, SLOT(hgNewBranch()));
Chris@278 2675
Chris@514 2676 connect(m_hgTabs, SIGNAL(closeBranch(QString)),
Chris@514 2677 this, SLOT(hgCloseBranch()));
Chris@514 2678
Chris@284 2679 connect(m_hgTabs, SIGNAL(tag(QString)),
Chris@148 2680 this, SLOT(hgTag(QString)));
Chris@326 2681
Chris@326 2682 connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)),
Chris@326 2683 this, SLOT(hgAnnotateFiles(QStringList)));
Chris@326 2684
Chris@326 2685 connect(m_hgTabs, SIGNAL(diffFiles(QStringList)),
Chris@326 2686 this, SLOT(hgDiffFiles(QStringList)));
Chris@326 2687
Chris@326 2688 connect(m_hgTabs, SIGNAL(commitFiles(QStringList)),
Chris@326 2689 this, SLOT(hgCommitFiles(QStringList)));
Chris@326 2690
Chris@326 2691 connect(m_hgTabs, SIGNAL(revertFiles(QStringList)),
Chris@326 2692 this, SLOT(hgRevertFiles(QStringList)));
Chris@326 2693
Chris@361 2694 connect(m_hgTabs, SIGNAL(renameFiles(QStringList)),
Chris@361 2695 this, SLOT(hgRenameFiles(QStringList)));
Chris@361 2696
Chris@361 2697 connect(m_hgTabs, SIGNAL(copyFiles(QStringList)),
Chris@361 2698 this, SLOT(hgCopyFiles(QStringList)));
Chris@361 2699
Chris@326 2700 connect(m_hgTabs, SIGNAL(addFiles(QStringList)),
Chris@326 2701 this, SLOT(hgAddFiles(QStringList)));
Chris@326 2702
Chris@326 2703 connect(m_hgTabs, SIGNAL(removeFiles(QStringList)),
Chris@326 2704 this, SLOT(hgRemoveFiles(QStringList)));
Chris@326 2705
Chris@326 2706 connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)),
Chris@326 2707 this, SLOT(hgRedoFileMerges(QStringList)));
Chris@326 2708
Chris@326 2709 connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)),
Chris@326 2710 this, SLOT(hgMarkFilesResolved(QStringList)));
Chris@326 2711
Chris@326 2712 connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)),
Chris@326 2713 this, SLOT(hgIgnoreFiles(QStringList)));
Chris@326 2714
Chris@326 2715 connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)),
Chris@326 2716 this, SLOT(hgUnIgnoreFiles(QStringList)));
sam@624 2717
sam@624 2718 connect(m_hgTabs, SIGNAL(showIn(QStringList)),
sam@624 2719 this, SLOT(hgShowIn(QStringList)));
Chris@141 2720 }
Chris@141 2721
jtkorhonen@0 2722 void MainWindow::enableDisableActions()
jtkorhonen@0 2723 {
Chris@90 2724 DEBUG << "MainWindow::enableDisableActions" << endl;
Chris@90 2725
Chris@284 2726 QString dirname = QDir(m_workFolderPath).dirName();
Chris@340 2727
Chris@340 2728 if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead
Chris@202 2729 setWindowTitle(tr("EasyMercurial: %1").arg(dirname));
Chris@202 2730 } else {
Chris@202 2731 setWindowTitle(tr("EasyMercurial"));
Chris@202 2732 }
Chris@202 2733
Chris@115 2734 //!!! should also do things like set the status texts for the
Chris@115 2735 //!!! actions appropriately by context
Chris@115 2736
jtkorhonen@0 2737 QDir localRepoDir;
jtkorhonen@0 2738 QDir workFolderDir;
jtkorhonen@0 2739
Chris@284 2740 m_remoteRepoActionsEnabled = true;
Chris@284 2741 if (m_remoteRepoPath.isEmpty()) {
Chris@284 2742 m_remoteRepoActionsEnabled = false;
jtkorhonen@0 2743 }
jtkorhonen@0 2744
Chris@284 2745 m_localRepoActionsEnabled = true;
Chris@284 2746 if (m_workFolderPath.isEmpty()) {
Chris@284 2747 m_localRepoActionsEnabled = false;
jtkorhonen@0 2748 }
jtkorhonen@0 2749
Chris@284 2750 if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) {
Chris@284 2751 m_localRepoActionsEnabled = false;
jtkorhonen@0 2752 }
jtkorhonen@0 2753
Chris@284 2754 if (!localRepoDir.exists(m_workFolderPath + "/.hg")) {
Chris@284 2755 m_localRepoActionsEnabled = false;
jtkorhonen@0 2756 }
jtkorhonen@0 2757
Chris@179 2758 bool haveDiff = false;
Chris@179 2759 QSettings settings;
Chris@179 2760 settings.beginGroup("Locations");
Chris@179 2761 if (settings.value("extdiffbinary", "").toString() != "") {
Chris@179 2762 haveDiff = true;
Chris@179 2763 }
Chris@179 2764 settings.endGroup();
Chris@112 2765
Chris@505 2766 m_hgTabs->setHaveMerge(m_currentParents.size() == 2);
Chris@505 2767
Chris@365 2768 m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2769 m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff);
Chris@365 2770 m_hgRevertAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2771 m_hgAddAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2772 m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled);
Chris@425 2773 m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2774 m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2775 m_hgCommitAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2776 m_hgMergeAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2777 m_hgServeAct->setEnabled(m_localRepoActionsEnabled);
Chris@413 2778 m_hgEditIgnoreAct->setEnabled(m_localRepoActionsEnabled);
Chris@273 2779
Chris@284 2780 DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl;
Chris@284 2781 DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl;
Chris@273 2782
Chris@284 2783 m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd());
Chris@284 2784 m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove());
Chris@284 2785 m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit());
Chris@284 2786 m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert());
Chris@284 2787 m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff());
Chris@425 2788 m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canIgnore());
Chris@90 2789
Chris@108 2790 // A default merge makes sense if:
Chris@108 2791 // * there is only one parent (if there are two, we have an uncommitted merge) and
Chris@108 2792 // * there are exactly two heads that have the same branch as the current branch and
Chris@108 2793 // * our parent is one of those heads
Chris@108 2794 //
Chris@108 2795 // A default update makes sense if:
Chris@108 2796 // * there is only one parent and
Chris@108 2797 // * the parent is not one of the current heads
Chris@156 2798
Chris@108 2799 bool canMerge = false;
Chris@108 2800 bool canUpdate = false;
Chris@156 2801 bool haveMerge = false;
Chris@162 2802 bool emptyRepo = false;
Chris@225 2803 bool noWorkingCopy = false;
Chris@235 2804 bool newBranch = false;
Chris@506 2805 bool closedBranch = false;
Chris@506 2806 int currentBranchActiveHeads = 0;
Chris@273 2807
Chris@284 2808 if (m_currentParents.size() == 1) {
Chris@156 2809 bool parentIsHead = false;
Chris@506 2810 bool parentIsActiveHead = false;
Chris@284 2811 Changeset *parent = m_currentParents[0];
Chris@506 2812 foreach (Changeset *head, m_activeHeads) {
Chris@284 2813 if (head->isOnBranch(m_currentBranch)) {
Chris@506 2814 ++currentBranchActiveHeads;
Chris@235 2815 }
Chris@235 2816 if (parent->id() == head->id()) {
Chris@506 2817 parentIsActiveHead = parentIsHead = true;
Chris@108 2818 }
Chris@108 2819 }
Chris@506 2820 if (!parentIsActiveHead) {
Chris@506 2821 foreach (Changeset *head, m_currentHeads) {
Chris@506 2822 if (parent->id() == head->id()) {
Chris@506 2823 parentIsHead = true;
Chris@506 2824 }
Chris@506 2825 }
Chris@506 2826 }
Chris@506 2827 if (currentBranchActiveHeads == 2 && parentIsActiveHead) {
Chris@108 2828 canMerge = true;
Chris@108 2829 }
Chris@506 2830 if (currentBranchActiveHeads == 0 && parentIsActiveHead) {
Chris@235 2831 // Just created a new branch
Chris@235 2832 newBranch = true;
Chris@235 2833 }
Chris@108 2834 if (!parentIsHead) {
Chris@108 2835 canUpdate = true;
Chris@108 2836 DEBUG << "parent id = " << parent->id() << endl;
Chris@108 2837 DEBUG << " head ids "<<endl;
Chris@284 2838 foreach (Changeset *h, m_currentHeads) {
Chris@108 2839 DEBUG << "head id = " << h->id() << endl;
Chris@108 2840 }
Chris@506 2841 } else if (!parentIsActiveHead) {
Chris@506 2842 closedBranch = true;
Chris@108 2843 }
Chris@284 2844 m_justMerged = false;
Chris@284 2845 } else if (m_currentParents.size() == 0) {
Chris@284 2846 if (m_currentHeads.size() == 0) {
Chris@225 2847 // No heads -> empty repo
Chris@225 2848 emptyRepo = true;
Chris@225 2849 } else {
Chris@225 2850 // Heads, but no parents -> no working copy, e.g. we have
Chris@225 2851 // just converted this repo but haven't updated in it yet.
Chris@225 2852 // Uncommon but confusing; probably merits a special case
Chris@225 2853 noWorkingCopy = true;
Chris@225 2854 canUpdate = true;
Chris@225 2855 }
Chris@284 2856 m_justMerged = false;
Chris@156 2857 } else {
Chris@156 2858 haveMerge = true;
Chris@284 2859 m_justMerged = true;
Chris@108 2860 }
Chris@505 2861
Chris@363 2862 m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled);
Chris@363 2863 m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled);
Chris@363 2864 // permit push even if no remote yet; we'll ask for one
Chris@363 2865 m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo);
Chris@363 2866
Chris@284 2867 m_hgMergeAct->setEnabled(m_localRepoActionsEnabled &&
Chris@506 2868 (canMerge || m_hgTabs->canResolve()));
Chris@284 2869 m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled &&
Chris@506 2870 (canUpdate && !m_hgTabs->haveChangesToCommit()));
Chris@115 2871
Chris@115 2872 // Set the state field on the file status widget
Chris@115 2873
Chris@115 2874 QString branchText;
Chris@284 2875 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@115 2876 branchText = tr("the default branch");
Chris@115 2877 } else {
Chris@284 2878 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@115 2879 }
Chris@156 2880
Chris@284 2881 if (m_stateUnknown) {
Chris@284 2882 if (m_workFolderPath == "") {
Chris@287 2883 m_workStatus->setState(tr("No repository open"));
Chris@248 2884 } else {
Chris@287 2885 m_workStatus->setState(tr("(Examining repository)"));
Chris@248 2886 }
Chris@173 2887 } else if (emptyRepo) {
Chris@287 2888 m_workStatus->setState(tr("Nothing committed to this repository yet"));
Chris@225 2889 } else if (noWorkingCopy) {
Chris@287 2890 m_workStatus->setState(tr("No working copy yet: consider updating"));
Chris@162 2891 } else if (canMerge) {
Chris@287 2892 m_workStatus->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText));
Chris@284 2893 } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) {
Chris@287 2894 m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText));
Chris@156 2895 } else if (haveMerge) {
Chris@287 2896 m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText));
Chris@235 2897 } else if (newBranch) {
Chris@287 2898 m_workStatus->setState(tr("On %1. New branch: has not yet been committed").arg(branchText));
Chris@506 2899 } else if (closedBranch) {
Chris@506 2900 if (canUpdate) {
Chris@506 2901 m_workStatus->setState(tr("On a closed branch. Not at the head of the branch"));
Chris@506 2902 } else {
Chris@506 2903 m_workStatus->setState(tr("At the head of a closed branch"));
Chris@506 2904 }
Chris@156 2905 } else if (canUpdate) {
Chris@284 2906 if (m_hgTabs->haveChangesToCommit()) {
Chris@163 2907 // have uncommitted changes
Chris@287 2908 m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText));
Chris@163 2909 } else {
Chris@163 2910 // no uncommitted changes
Chris@287 2911 m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
Chris@163 2912 }
Chris@506 2913 } else if (currentBranchActiveHeads > 1) {
Chris@506 2914 m_workStatus->setState(tr("At one of %n heads of %1", "", currentBranchActiveHeads).arg(branchText));
Chris@115 2915 } else {
Chris@287 2916 m_workStatus->setState(tr("At the head of %1").arg(branchText));
Chris@115 2917 }
jtkorhonen@0 2918 }
jtkorhonen@0 2919
Chris@359 2920
Chris@506 2921 void MainWindow::updateClosedHeads()
Chris@506 2922 {
Chris@506 2923 m_closedHeadIds.clear();
Chris@506 2924 QSet<QString> activeIds;
Chris@506 2925 foreach (Changeset *cs, m_activeHeads) {
Chris@506 2926 activeIds.insert(cs->id());
Chris@506 2927 }
Chris@506 2928 foreach (Changeset *cs, m_currentHeads) {
Chris@506 2929 if (!activeIds.contains(cs->id())) {
Chris@506 2930 m_closedHeadIds.insert(cs->id());
Chris@506 2931 }
Chris@506 2932 }
Chris@506 2933 m_hgTabs->setClosedHeadIds(m_closedHeadIds);
Chris@506 2934 }
Chris@506 2935
Chris@359 2936 void MainWindow::updateRecentMenu()
Chris@359 2937 {
Chris@359 2938 m_recentMenu->clear();
Chris@359 2939 RecentFiles rf("Recent-local");
Chris@359 2940 QStringList recent = rf.getRecent();
Chris@359 2941 if (recent.empty()) {
Chris@359 2942 QLabel *label = new QLabel(tr("No recent local repositories"));
Chris@359 2943 QWidgetAction *wa = new QWidgetAction(m_recentMenu);
Chris@359 2944 wa->setDefaultWidget(label);
Chris@359 2945 return;
Chris@359 2946 }
Chris@359 2947 foreach (QString r, recent) {
Chris@359 2948 QAction *a = m_recentMenu->addAction(r);
Chris@611 2949 connect(a, SIGNAL(triggered()), this, SLOT(recentMenuActivated()));
Chris@359 2950 }
Chris@359 2951 }
Chris@359 2952
jtkorhonen@0 2953 void MainWindow::createActions()
jtkorhonen@0 2954 {
jtkorhonen@0 2955 //File actions
Chris@365 2956 m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("&Open..."), this);
Chris@609 2957 m_openAct->setStatusTip(tr("Open a remote repository or an existing local folder"));
Chris@365 2958 m_openAct->setShortcut(tr("Ctrl+O"));
Chris@365 2959
Chris@609 2960 m_changeRemoteRepoAct = new QAction(tr("Set Push and Pull &Location..."), this);
Chris@609 2961 m_changeRemoteRepoAct->setStatusTip(tr("Set or change the default URL for pull and push actions from this repository"));
Chris@273 2962
Chris@365 2963 m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("&Settings..."), this);
Chris@365 2964 m_settingsAct->setStatusTip(tr("View and change application settings"));
Chris@273 2965
Chris@343 2966 #ifdef Q_OS_WIN32
Chris@365 2967 m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("E&xit"), this);
Chris@343 2968 #else
Chris@365 2969 m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("&Quit"), this);
Chris@343 2970 #endif
Chris@284 2971 m_exitAct->setShortcuts(QKeySequence::Quit);
Chris@365 2972 m_exitAct->setStatusTip(tr("Exit EasyMercurial"));
jtkorhonen@0 2973
jtkorhonen@0 2974 //Repository actions
Chris@544 2975 m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Re-Read Working Folder"), this);
Chris@365 2976 m_hgRefreshAct->setShortcut(tr("Ctrl+R"));
Chris@284 2977 m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder"));
Chris@273 2978
Chris@365 2979 m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Pre&view Incoming Changes"), this);
Chris@365 2980 m_hgIncomingAct->setIconText(tr("Preview"));
Chris@365 2981 m_hgIncomingAct->setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled"));
Chris@365 2982
Chris@365 2983 m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pu&ll from Remote Repository"), this);
Chris@365 2984 m_hgPullAct->setIconText(tr("Pull"));
Chris@365 2985 m_hgPullAct->setShortcut(tr("Ctrl+L"));
Chris@365 2986 m_hgPullAct->setStatusTip(tr("Pull changes from the remote repository to the local repository"));
Chris@365 2987
Chris@365 2988 m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Pus&h to Remote Repository"), this);
Chris@365 2989 m_hgPushAct->setIconText(tr("Push"));
Chris@365 2990 m_hgPushAct->setShortcut(tr("Ctrl+H"));
Chris@284 2991 m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository"));
jtkorhonen@0 2992
jtkorhonen@0 2993 //Workfolder actions
Chris@365 2994 m_hgFolderDiffAct = new QAction(QIcon(":/images/folderdiff.png"), tr("&Diff"), this);
Chris@365 2995 m_hgFolderDiffAct->setIconText(tr("Diff"));
Chris@365 2996 m_hgFolderDiffAct->setShortcut(tr("Ctrl+D"));
Chris@284 2997 m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state"));
Chris@273 2998
Chris@365 2999 m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Re&vert"), this);
Chris@284 3000 m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state"));
Chris@273 3001
Chris@365 3002 m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("&Add Files"), this);
Chris@365 3003 m_hgAddAct->setIconText(tr("Add"));
Chris@365 3004 m_hgAddAct->setShortcut(tr("+"));
Chris@365 3005 m_hgAddAct->setStatusTip(tr("Mark the selected files to be added on the next commit"));
Chris@365 3006
Chris@365 3007 m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("&Remove Files"), this);
Chris@365 3008 m_hgRemoveAct->setIconText(tr("Remove"));
Chris@365 3009 m_hgRemoveAct->setShortcut(tr("Del"));
Chris@365 3010 m_hgRemoveAct->setStatusTip(tr("Mark the selected files to be removed from version control on the next commit"));
Chris@365 3011
Chris@425 3012 m_hgIgnoreAct = new QAction(tr("&Ignore Files..."), this);
Chris@425 3013 m_hgIgnoreAct->setStatusTip(tr("Add the selected filenames to the ignored list, of files that should never be tracked in this repository"));
Chris@425 3014
Chris@425 3015 m_hgEditIgnoreAct = new QAction(tr("Edit Ignored List"), this);
Chris@425 3016 m_hgEditIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial"));
Chris@425 3017
Chris@365 3018 m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("&Update to Branch Head"), this);
Chris@365 3019 m_hgUpdateAct->setIconText(tr("Update"));
Chris@365 3020 m_hgUpdateAct->setShortcut(tr("Ctrl+U"));
Chris@284 3021 m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch"));
jtkorhonen@0 3022
Chris@365 3023 m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("&Commit..."), this);
Chris@365 3024 m_hgCommitAct->setShortcut(tr("Ctrl+Return"));
Chris@284 3025 m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository"));
Chris@273 3026
Chris@365 3027 m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("&Merge"), this);
Chris@365 3028 m_hgMergeAct->setShortcut(tr("Ctrl+M"));
Chris@284 3029 m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder"));
jtkorhonen@0 3030
Chris@425 3031 m_hgServeAct = new QAction(tr("Share Repository"), this);
Chris@425 3032 m_hgServeAct->setStatusTip(tr("Serve local repository temporarily via HTTP for workgroup access"));
jtkorhonen@11 3033
jtkorhonen@0 3034 //Help actions
Chris@494 3035 #ifdef Q_OS_MAC
Chris@494 3036 m_helpAct = new QAction(tr("EasyMercurial Help"), this);
Chris@494 3037 #else
Chris@494 3038 m_helpAct = new QAction(tr("Help Topics"), this);
Chris@494 3039 #endif
Chris@494 3040 m_helpAct->setShortcuts(QKeySequence::HelpContents);
Chris@284 3041 m_aboutAct = new QAction(tr("About EasyMercurial"), this);
Chris@94 3042
Chris@94 3043 // Miscellaneous
Chris@199 3044 QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this);
Chris@199 3045 connect(clearSelectionsShortcut, SIGNAL(activated()),
Chris@199 3046 this, SLOT(clearSelections()));
jtkorhonen@0 3047 }
jtkorhonen@0 3048
jtkorhonen@0 3049 void MainWindow::createMenus()
jtkorhonen@0 3050 {
Chris@664 3051 #ifdef Q_OS_LINUX
Chris@664 3052 // In Ubuntu 14.04 the window's menu bar goes missing entirely if
Chris@664 3053 // the user is running any desktop environment other than Unity
Chris@664 3054 // (in which the faux single-menubar appears). The user has a
Chris@664 3055 // workaround, to remove the appmenu-qt5 package, but that is
Chris@664 3056 // awkward and the problem is so severe that it merits disabling
Chris@664 3057 // the system menubar integration altogether. Like this:
Chris@664 3058 menuBar()->setNativeMenuBar(false);
Chris@664 3059 #endif
Chris@664 3060
Chris@365 3061 m_fileMenu = menuBar()->addMenu(tr("&File"));
Chris@365 3062
Chris@365 3063 m_fileMenu->addAction(m_openAct);
Chris@365 3064 m_recentMenu = m_fileMenu->addMenu(tr("Open Re&cent"));
Chris@365 3065 m_fileMenu->addAction(m_hgRefreshAct);
Chris@365 3066 m_fileMenu->addSeparator();
Chris@425 3067 m_fileMenu->addAction(m_hgServeAct);
Chris@425 3068 m_fileMenu->addSeparator();
Chris@365 3069 m_fileMenu->addAction(m_settingsAct);
Chris@365 3070 m_fileMenu->addSeparator();
Chris@365 3071 m_fileMenu->addAction(m_exitAct);
Chris@365 3072
Chris@365 3073 QMenu *workMenu;
Chris@365 3074 workMenu = menuBar()->addMenu(tr("&Work"));
Chris@365 3075 workMenu->addAction(m_hgFolderDiffAct);
Chris@365 3076 workMenu->addSeparator();
Chris@365 3077 workMenu->addAction(m_hgUpdateAct);
Chris@365 3078 workMenu->addAction(m_hgCommitAct);
Chris@365 3079 workMenu->addAction(m_hgMergeAct);
Chris@365 3080 workMenu->addSeparator();
Chris@365 3081 workMenu->addAction(m_hgAddAct);
Chris@365 3082 workMenu->addAction(m_hgRemoveAct);
Chris@365 3083 workMenu->addSeparator();
Chris@425 3084 workMenu->addAction(m_hgIgnoreAct);
Chris@425 3085 workMenu->addAction(m_hgEditIgnoreAct);
Chris@425 3086 workMenu->addSeparator();
Chris@365 3087 workMenu->addAction(m_hgRevertAct);
Chris@365 3088
Chris@365 3089 QMenu *remoteMenu;
Chris@365 3090 remoteMenu = menuBar()->addMenu(tr("&Remote"));
Chris@365 3091 remoteMenu->addAction(m_hgIncomingAct);
Chris@365 3092 remoteMenu->addSeparator();
Chris@609 3093 remoteMenu->addAction(m_changeRemoteRepoAct);
Chris@365 3094 remoteMenu->addAction(m_hgPullAct);
Chris@365 3095 remoteMenu->addAction(m_hgPushAct);
Chris@273 3096
Chris@378 3097 m_helpMenu = menuBar()->addMenu(tr("&Help"));
Chris@494 3098 m_helpMenu->addAction(m_helpAct);
Chris@284 3099 m_helpMenu->addAction(m_aboutAct);
jtkorhonen@0 3100 }
jtkorhonen@0 3101
jtkorhonen@0 3102 void MainWindow::createToolBars()
jtkorhonen@0 3103 {
Chris@442 3104 int sz = 32;
Chris@442 3105
Chris@645 3106 QString spacerBefore, spacerAfter;
Chris@645 3107
Chris@645 3108 spacerBefore = spacerAfter = " ";
Chris@645 3109
Chris@645 3110 #ifdef Q_OS_MAC
Chris@645 3111 spacerAfter = "";
Chris@645 3112 #endif
Chris@645 3113
Chris@645 3114 #ifdef Q_OS_WIN32
chris@651 3115 spacerBefore = spacerAfter = " ";
Chris@645 3116 #endif
Chris@645 3117
Chris@645 3118 m_repoToolBar = addToolBar(tr("Remote"));
Chris@645 3119 m_repoToolBar->setIconSize(QSize(sz, sz));
Chris@645 3120 if (spacerBefore != "") {
Chris@645 3121 m_repoToolBar->addWidget(new QLabel(spacerBefore));
Chris@645 3122 }
Chris@645 3123 m_repoToolBar->addAction(m_openAct);
Chris@645 3124 if (spacerAfter != "") {
Chris@645 3125 m_repoToolBar->addWidget(new QLabel(spacerAfter));
Chris@645 3126 }
Chris@645 3127 m_repoToolBar->addSeparator();
Chris@645 3128 m_repoToolBar->addAction(m_hgIncomingAct);
Chris@645 3129 m_repoToolBar->addAction(m_hgPullAct);
Chris@645 3130 m_repoToolBar->addAction(m_hgPushAct);
Chris@645 3131 m_repoToolBar->setMovable(false);
Chris@273 3132
Chris@594 3133 m_workFolderToolBar = new QToolBar(tr("Work"));
Chris@284 3134 addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar);
Chris@442 3135 m_workFolderToolBar->setIconSize(QSize(sz, sz));
Chris@645 3136
Chris@645 3137 QWidget *w = new QWidget;
Chris@645 3138 w->setFixedHeight(6);
Chris@645 3139 m_workFolderToolBar->addWidget(w);
Chris@645 3140
Chris@284 3141 m_workFolderToolBar->addAction(m_hgFolderDiffAct);
Chris@284 3142 m_workFolderToolBar->addSeparator();
Chris@284 3143 m_workFolderToolBar->addAction(m_hgRevertAct);
Chris@284 3144 m_workFolderToolBar->addAction(m_hgUpdateAct);
Chris@284 3145 m_workFolderToolBar->addAction(m_hgCommitAct);
Chris@284 3146 m_workFolderToolBar->addAction(m_hgMergeAct);
Chris@284 3147 m_workFolderToolBar->addSeparator();
Chris@284 3148 m_workFolderToolBar->addAction(m_hgAddAct);
Chris@284 3149 m_workFolderToolBar->addAction(m_hgRemoveAct);
Chris@365 3150 m_workFolderToolBar->setMovable(false);
Chris@61 3151
Chris@230 3152 updateToolBarStyle();
jtkorhonen@0 3153 }
jtkorhonen@0 3154
Chris@230 3155 void MainWindow::updateToolBarStyle()
Chris@230 3156 {
Chris@230 3157 QSettings settings;
Chris@230 3158 settings.beginGroup("Presentation");
Chris@230 3159 bool showText = settings.value("showiconlabels", true).toBool();
Chris@230 3160 settings.endGroup();
Chris@230 3161
Chris@230 3162 foreach (QToolButton *tb, findChildren<QToolButton *>()) {
Chris@230 3163 tb->setToolButtonStyle(showText ?
Chris@230 3164 Qt::ToolButtonTextUnderIcon :
Chris@230 3165 Qt::ToolButtonIconOnly);
Chris@230 3166 }
Chris@230 3167 }
jtkorhonen@0 3168
Chris@287 3169 void MainWindow::updateWorkFolderAndRepoNames()
Chris@287 3170 {
Chris@287 3171 m_hgTabs->setLocalPath(m_workFolderPath);
Chris@287 3172
Chris@287 3173 m_workStatus->setLocalPath(m_workFolderPath);
Chris@287 3174 m_workStatus->setRemoteURL(m_remoteRepoPath);
Chris@287 3175 }
Chris@287 3176
jtkorhonen@0 3177 void MainWindow::createStatusBar()
jtkorhonen@0 3178 {
jtkorhonen@0 3179 statusBar()->showMessage(tr("Ready"));
jtkorhonen@0 3180 }
jtkorhonen@0 3181
jtkorhonen@0 3182 void MainWindow::readSettings()
jtkorhonen@0 3183 {
jtkorhonen@0 3184 QDir workFolder;
jtkorhonen@0 3185
Chris@61 3186 QSettings settings;
jtkorhonen@0 3187
Chris@284 3188 m_workFolderPath = settings.value("workfolderpath", "").toString();
Chris@553 3189 if (!workFolder.exists(m_workFolderPath)) {
Chris@284 3190 m_workFolderPath = "";
jtkorhonen@0 3191 }
jtkorhonen@0 3192
jtkorhonen@0 3193 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
Chris@340 3194 QSize size = settings.value("size", QSize(550, 550)).toSize();
Chris@284 3195 m_firstStart = settings.value("firststart", QVariant(true)).toBool();
jtkorhonen@0 3196
jtkorhonen@0 3197 resize(size);
jtkorhonen@0 3198 move(pos);
jtkorhonen@0 3199 }
jtkorhonen@0 3200
jtkorhonen@0 3201 void MainWindow::writeSettings()
jtkorhonen@0 3202 {
Chris@61 3203 QSettings settings;
jtkorhonen@0 3204 settings.setValue("pos", pos());
jtkorhonen@0 3205 settings.setValue("size", size());
Chris@284 3206 settings.setValue("remoterepopath", m_remoteRepoPath);
Chris@284 3207 settings.setValue("workfolderpath", m_workFolderPath);
Chris@284 3208 settings.setValue("firststart", m_firstStart);
jtkorhonen@0 3209 }
jtkorhonen@0 3210
Chris@491 3211 void MainWindow::newerVersionAvailable(QString version)
Chris@491 3212 {
Chris@491 3213 QSettings settings;
Chris@491 3214 settings.beginGroup("NewerVersionWarning");
Chris@491 3215 QString tag = QString("version-%1-available-show").arg(version);
Chris@491 3216 if (settings.value(tag, true).toBool()) {
Chris@491 3217 QString title(tr("Newer version available"));
Chris@618 3218 QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of EasyMercurial, but version %3 is now available.</p><p>Please see the <a href=\"http://easyhg.org/\">EasyMercurial website</a> for more information.</p>").arg(EASYHG_VERSION).arg(version));
Chris@491 3219 QMessageBox::information(this, title, text);
Chris@491 3220 settings.setValue(tag, false);
Chris@491 3221 }
Chris@491 3222 settings.endGroup();
Chris@491 3223 }
Chris@491 3224
Chris@494 3225 void MainWindow::help()
Chris@494 3226 {
Chris@494 3227 if (!m_helpDialog) {
Chris@494 3228 m_helpDialog = new QDialog;
Chris@494 3229 QGridLayout *layout = new QGridLayout;
Chris@494 3230 m_helpDialog->setLayout(layout);
Chris@498 3231 QPushButton *home = new QPushButton;
Chris@498 3232 home->setIcon(QIcon(":images/home.png"));
Chris@498 3233 layout->addWidget(home, 0, 0);
Chris@498 3234 QPushButton *back = new QPushButton;
Chris@498 3235 back->setIcon(QIcon(":images/back.png"));
Chris@498 3236 layout->addWidget(back, 0, 1);
Chris@498 3237 QPushButton *fwd = new QPushButton;
Chris@498 3238 fwd->setIcon(QIcon(":images/forward.png"));
Chris@498 3239 layout->addWidget(fwd, 0, 2);
Chris@494 3240 QTextBrowser *text = new QTextBrowser;
Chris@494 3241 text->setOpenExternalLinks(true);
Chris@498 3242 layout->addWidget(text, 1, 0, 1, 4);
Chris@494 3243 text->setSource(QUrl("qrc:help/topics.html"));
Chris@494 3244 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
Chris@494 3245 connect(bb, SIGNAL(rejected()), m_helpDialog, SLOT(hide()));
Chris@498 3246 connect(text, SIGNAL(backwardAvailable(bool)),
Chris@498 3247 back, SLOT(setEnabled(bool)));
Chris@498 3248 connect(text, SIGNAL(forwardAvailable(bool)),
Chris@498 3249 fwd, SLOT(setEnabled(bool)));
Chris@498 3250 connect(home, SIGNAL(clicked()), text, SLOT(home()));
Chris@498 3251 connect(back, SIGNAL(clicked()), text, SLOT(backward()));
Chris@498 3252 connect(fwd, SIGNAL(clicked()), text, SLOT(forward()));
Chris@498 3253 back->setEnabled(false);
Chris@498 3254 fwd->setEnabled(false);
Chris@498 3255 layout->addWidget(bb, 2, 0, 1, 4);
Chris@498 3256 layout->setColumnStretch(3, 20);
Chris@494 3257 m_helpDialog->resize(450, 500);
Chris@494 3258 }
Chris@494 3259 QTextBrowser *tb = m_helpDialog->findChild<QTextBrowser *>();
Chris@494 3260 if (tb) tb->home();
Chris@494 3261 m_helpDialog->show();
Chris@494 3262 m_helpDialog->raise();
Chris@494 3263 }
Chris@494 3264