annotate src/mainwindow.cpp @ 633:db62a0cb3037

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