annotate src/mainwindow.cpp @ 571:012ba1b83328

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