annotate src/mainwindow.cpp @ 537:a4e699d32a9a

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