# HG changeset patch # User Chris Cannam # Date 1329242139 0 # Node ID dca5bd5b2a065c9b52c1fa627dda8121f5b217c4 # Parent a4e699d32a9a5c39fabdd58b53a49931517b1b34# Parent 9f91d1b2ed51857c565bae86fa198301f9d3399a Merge from branch "fswatcher" diff -r a4e699d32a9a -r dca5bd5b2a06 easyhg.pro --- a/easyhg.pro Fri Feb 10 13:08:07 2012 +0000 +++ b/easyhg.pro Tue Feb 14 17:55:39 2012 +0000 @@ -1,5 +1,5 @@ -CONFIG += debug +CONFIG += release TEMPLATE = app TARGET = EasyMercurial @@ -64,7 +64,8 @@ src/annotatedialog.h \ src/hgignoredialog.h \ src/versiontester.h \ - src/squeezedlabel.h + src/squeezedlabel.h \ + src/fswatcher.h SOURCES = \ src/main.cpp \ src/mainwindow.cpp \ @@ -101,7 +102,8 @@ src/annotatedialog.cpp \ src/hgignoredialog.cpp \ src/versiontester.cpp \ - src/squeezedlabel.cpp + src/squeezedlabel.cpp \ + src/fswatcher.cpp macx-* { SOURCES += src/common_osx.mm diff -r a4e699d32a9a -r dca5bd5b2a06 src/filestates.cpp --- a/src/filestates.cpp Fri Feb 10 13:08:07 2012 +0000 +++ b/src/filestates.cpp Tue Feb 14 17:55:39 2012 +0000 @@ -142,6 +142,18 @@ return (m_stateMap.contains(file)); } +QStringList FileStates::trackedFiles() const +{ + QStringList all; + all << filesInState(Modified); + all << filesInState(Added); + all << filesInState(Removed); + all << filesInState(InConflict); + all << filesInState(Missing); + all << filesInState(Clean); + return all; +} + FileStates::Activities FileStates::activitiesSupportedBy(State s) { Activities a; diff -r a4e699d32a9a -r dca5bd5b2a06 src/filestates.h --- a/src/filestates.h Fri Feb 10 13:08:07 2012 +0000 +++ b/src/filestates.h Tue Feb 14 17:55:39 2012 +0000 @@ -52,6 +52,8 @@ State stateOf(QString file) const; bool isKnown(QString file) const; + QStringList trackedFiles() const; + enum Activity { // These are in the order in which they want to be listed in diff -r a4e699d32a9a -r dca5bd5b2a06 src/filestatuswidget.cpp --- a/src/filestatuswidget.cpp Fri Feb 10 13:08:07 2012 +0000 +++ b/src/filestatuswidget.cpp Tue Feb 14 17:55:39 2012 +0000 @@ -178,6 +178,13 @@ return m_showAllFiles->isChecked(); } +bool FileStatusWidget::shouldShow(FileStates::State s) const +{ + if (shouldShowAll()) return true; + else return (s != FileStates::Clean && + s != FileStates::Ignored); +} + QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation) { QSettings settings; @@ -193,11 +200,9 @@ .arg(m_simpleLabels[s]) .arg(m_descriptions[s]); } - } else { - return QString("%1") - .arg(m_simpleLabels[s]); } - settings.endGroup(); + return QString("%1") + .arg(m_simpleLabels[s]); } void FileStatusWidget::setNoModificationsLabelText() @@ -425,6 +430,12 @@ QListWidget *w = m_stateListMap[s]; w->clear(); + + if (!shouldShow(s)) { + w->parentWidget()->hide(); + continue; + } + QStringList files = m_fileStates.filesInState(s); QStringList highPriority, lowPriority; diff -r a4e699d32a9a -r dca5bd5b2a06 src/filestatuswidget.h --- a/src/filestatuswidget.h Fri Feb 10 13:08:07 2012 +0000 +++ b/src/filestatuswidget.h Tue Feb 14 17:55:39 2012 +0000 @@ -55,6 +55,7 @@ QStringList getSelectedRemovableFiles() const; bool shouldShowAll() const; + bool shouldShow(FileStates::State) const; signals: void selectionChanged(); diff -r a4e699d32a9a -r dca5bd5b2a06 src/fswatcher.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fswatcher.cpp Tue Feb 14 17:55:39 2012 +0000 @@ -0,0 +1,271 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "fswatcher.h" +#include "debug.h" + +#include +#include + +#include + +#define DEBUG_FSWATCHER 1 + +/* + * Watching the filesystem is trickier than it seems at first glance. + * + * We ideally should watch every directory, and every file that is + * tracked by Hg. If a new file is created in a directory, then we + * need to respond in order to show it as a potential candidate to be + * added. + * + * Complicating matters though is that Hg itself might modify the + * filesystem. This can happen even in "read-only" operations: for + * example, hg stat creates files called hg-checklink and hg-checkexec + * to test properties of the filesystem. So we need to know to ignore + * those files; unfortunately, when watching a directory (which is how + * we find out about the creation of new files) we are notified only + * that the directory has changed -- we aren't told what changed. + * + * This means that, when a directory changes, we need to rescan the + * directory to learn whether the set of files in it _excluding_ files + * matching our ignore patterns differs from the previous scan, and + * ignore the change if it doesn't. + */ + +FsWatcher::FsWatcher() : + m_lastToken(0), + m_lastCounter(0) +{ + connect(&m_watcher, SIGNAL(directoryChanged(QString)), + this, SLOT(fsDirectoryChanged(QString))); + connect(&m_watcher, SIGNAL(fileChanged(QString)), + this, SLOT(fsFileChanged(QString))); +} + +FsWatcher::~FsWatcher() +{ +} + +void +FsWatcher::setWorkDirPath(QString path) +{ + QMutexLocker locker(&m_mutex); + if (m_workDirPath == path) return; + m_watcher.removePaths(m_watcher.directories()); + m_watcher.removePaths(m_watcher.files()); + m_workDirPath = path; + addWorkDirectory(path); + debugPrint(); +} + +void +FsWatcher::setTrackedFilePaths(QStringList paths) +{ + QMutexLocker locker(&m_mutex); + + QSet alreadyWatched = + QSet::fromList(m_watcher.files()); + + foreach (QString path, paths) { + path = m_workDirPath + QDir::separator() + path; + if (!alreadyWatched.contains(path)) { + m_watcher.addPath(path); + } else { + alreadyWatched.remove(path); + } + } + + // Remove the remaining paths, those that were being watched + // before but that are not in the list we were given + foreach (QString path, alreadyWatched) { + m_watcher.removePath(path); + } + + debugPrint(); +} + +void +FsWatcher::addWorkDirectory(QString path) +{ + // QFileSystemWatcher will refuse to add a file or directory to + // its watch list that it is already watching -- fine -- but it + // prints a warning when this happens, which we wouldn't want. So + // we'll check for duplicates ourselves. + QSet alreadyWatched = + QSet::fromList(m_watcher.directories()); + + std::deque pending; + pending.push_back(path); + + while (!pending.empty()) { + + QString path = pending.front(); + pending.pop_front(); + if (!alreadyWatched.contains(path)) { + m_watcher.addPath(path); + m_dirContents[path] = scanDirectory(path); + } + + QDir d(path); + if (d.exists()) { + d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | + QDir::Readable | QDir::NoSymLinks); + foreach (QString entry, d.entryList()) { + if (entry.startsWith('.')) continue; + QString entryPath = d.absoluteFilePath(entry); + pending.push_back(entryPath); + } + } + } +} + +void +FsWatcher::setIgnoredFilePrefixes(QStringList prefixes) +{ + QMutexLocker locker(&m_mutex); + m_ignoredPrefixes = prefixes; +} + +void +FsWatcher::setIgnoredFileSuffixes(QStringList suffixes) +{ + QMutexLocker locker(&m_mutex); + m_ignoredSuffixes = suffixes; +} + +int +FsWatcher::getNewToken() +{ + QMutexLocker locker(&m_mutex); + int token = ++m_lastToken; + m_tokenMap[token] = m_lastCounter; + return token; +} + +QSet +FsWatcher::getChangedPaths(int token) +{ + QMutexLocker locker(&m_mutex); + size_t lastUpdatedAt = m_tokenMap[token]; + QSet changed; + for (QHash::const_iterator i = m_changes.begin(); + i != m_changes.end(); ++i) { + if (i.value() > lastUpdatedAt) { + changed.insert(i.key()); + } + } + m_tokenMap[token] = m_lastCounter; + return changed; +} + +void +FsWatcher::fsDirectoryChanged(QString path) +{ + { + QMutexLocker locker(&m_mutex); + + if (shouldIgnore(path)) return; + + QSet files = scanDirectory(path); + if (files == m_dirContents[path]) { +#ifdef DEBUG_FSWATCHER + std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring" << std::endl; +#endif + return; + } else { +#ifdef DEBUG_FSWATCHER + std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl; +#endif + m_dirContents[path] = files; + } + + size_t counter = ++m_lastCounter; + m_changes[path] = counter; + } + + emit changed(); +} + +void +FsWatcher::fsFileChanged(QString path) +{ + { + QMutexLocker locker(&m_mutex); + + // We don't check whether the file matches an ignore pattern, + // because we are only notified for file changes if we are + // watching the file explicitly, i.e. the file is in the + // tracked file paths list. So we never want to ignore them + + std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl; + + size_t counter = ++m_lastCounter; + m_changes[path] = counter; + } + + emit changed(); +} + +bool +FsWatcher::shouldIgnore(QString path) +{ + QFileInfo fi(path); + QString fn(fi.fileName()); + foreach (QString pfx, m_ignoredPrefixes) { + if (fn.startsWith(pfx)) { + std::cerr << "(ignoring: " << path << ")" << std::endl; + return true; + } + } + foreach (QString sfx, m_ignoredSuffixes) { + if (fn.endsWith(sfx)) { + std::cerr << "(ignoring: " << path << ")" << std::endl; + return true; + } + } + return false; +} + +QSet +FsWatcher::scanDirectory(QString path) +{ + QSet files; + QDir d(path); + if (d.exists()) { + d.setFilter(QDir::Files | QDir::NoDotAndDotDot | + QDir::Readable | QDir::NoSymLinks); + foreach (QString entry, d.entryList()) { + if (entry.startsWith('.')) continue; + if (shouldIgnore(entry)) continue; + files.insert(entry); + } + } +// std::cerr << "scanDirectory:" << std::endl; +// foreach (QString f, files) std::cerr << f << std::endl; + return files; +} + +void +FsWatcher::debugPrint() +{ +#ifdef DEBUG_FSWATCHER + std::cerr << "FsWatcher: Now watching " << m_watcher.directories().size() + << " directories and " << m_watcher.files().size() + << " files" << std::endl; +#endif +} diff -r a4e699d32a9a -r dca5bd5b2a06 src/fswatcher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fswatcher.h Tue Feb 14 17:55:39 2012 +0000 @@ -0,0 +1,148 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef FSWATCHER_H +#define FSWATCHER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class FsWatcher : public QObject +{ + Q_OBJECT + +public: + FsWatcher(); + virtual ~FsWatcher(); + + /** + * Set the root path of the work directory to be monitored. This + * directory and all its subdirectories (recursively) will be + * monitored for changes. + * + * If this path differs from the currently set work dir path, then + * the tracked file paths will also be cleared. Call + * setTrackedFilePaths afterwards to ensure non-directory files + * are monitored. + */ + void setWorkDirPath(QString path); + + /** + * Provide a set of paths for files which should be tracked. These + * will be the only non-directory files monitored for changes. The + * paths should be relative to the work directory. + */ + void setTrackedFilePaths(QStringList paths); + + /** + * Provide a set of prefixes to ignore. Files whose names start + * with a prefix in this set will be ignored when they change. + */ + void setIgnoredFilePrefixes(QStringList prefixes); + + /** + * Provide a set of suffixes to ignore. Files whose names end + * with a suffix in this set will be ignored when they change. + */ + void setIgnoredFileSuffixes(QStringList suffixes); + + /** + * Return a token to identify the current caller in subsequent + * calls to getChangedPaths(). Only changes that occur after this + * has been called can be detected by the caller. + */ + int getNewToken(); + + /** + * Return a list of all non-ignored file paths that have been + * observed to have changed since the last call to getChangedPaths + * with the same token. + */ + QSet getChangedPaths(int token); + +signals: + /** + * Emitted when something has changed. Use the asynchronous + * interface to find out what. + */ + void changed(); + +private slots: + void fsDirectoryChanged(QString); + void fsFileChanged(QString); + +private: + // call with lock already held + void addWorkDirectory(QString path); + + // call with lock already held + bool shouldIgnore(QString path); + + // call with lock already held. Returns set of non-ignored files in dir + QSet scanDirectory(QString path); + + // call with lock already held + void debugPrint(); + +private: + /** + * A change associates a filename with a counter (the + * size_t). Each time a file is changed, its counter is assigned + * from m_lastCounter and m_lastCounter is incremented. + * + * This is not especially efficient -- we often want to find "all + * files whose counter is greater than X" which involves a + * traversal. Maybe something better later. + */ + QHash m_changes; + + /** + * Associates a token (the client identifier) with a counter. The + * counter represents the value of m_lastCounter at the moment + * getChangedPaths last completed for that token. Any files in + * m_changes whose counters are larger must have been modified. + */ + QMap m_tokenMap; + + /** + * Associates a directory path with a list of all the files in it + * that do not match our ignore patterns. When a directory is + * signalled as having changed, then we need to rescan it and + * compare against this list before we can determine whether to + * notify about the change or not. + */ + QHash > m_dirContents; + + QStringList m_ignoredPrefixes; + QStringList m_ignoredSuffixes; + + /// Everything in this class is synchronised. + QMutex m_mutex; + + QString m_workDirPath; + int m_lastToken; + size_t m_lastCounter; + QFileSystemWatcher m_watcher; +}; + +#endif diff -r a4e699d32a9a -r dca5bd5b2a06 src/mainwindow.cpp --- a/src/mainwindow.cpp Fri Feb 10 13:08:07 2012 +0000 +++ b/src/mainwindow.cpp Tue Feb 14 17:55:39 2012 +0000 @@ -52,14 +52,12 @@ #include "workstatuswidget.h" #include "hgignoredialog.h" #include "versiontester.h" +#include "fswatcher.h" MainWindow::MainWindow(QString myDirPath) : m_myDirPath(myDirPath), - m_helpDialog(0), - m_fsWatcherGeneralTimer(0), - m_fsWatcherRestoreTimer(0), - m_fsWatcherSuspended(false) + m_helpDialog(0) { setWindowIcon(QIcon(":images/easyhg-icon.png")); @@ -67,7 +65,11 @@ m_showAllFiles = false; - m_fsWatcher = 0; + m_fsWatcher = new FsWatcher(); + m_fsWatcherToken = m_fsWatcher->getNewToken(); + m_commandSequenceInProgress = false; + connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(checkFilesystem())); + m_commitsSincePush = 0; m_shouldHgStat = true; @@ -251,14 +253,16 @@ { QStringList params; - if (m_showAllFiles) { - params << "stat" << "-A"; - } else { - params << "stat" << "-ardum"; - } + // We always stat all files, regardless of whether we're showing + // them all, because we need them for the filesystem monitor + params << "stat" << "-A"; m_lastStatOutput = ""; + // We're about to do a stat, so we can silently bring ourselves + // up-to-date on any file changes to this point + (void)m_fsWatcher->getChangedPaths(m_fsWatcherToken); + m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params)); } @@ -1301,14 +1305,6 @@ m_mergeCommitComment = ""; m_stateUnknown = true; m_needNewLog = true; - if (m_fsWatcher) { - delete m_fsWatcherGeneralTimer; - m_fsWatcherGeneralTimer = 0; - delete m_fsWatcherRestoreTimer; - m_fsWatcherRestoreTimer = 0; - delete m_fsWatcher; - m_fsWatcher = 0; - } } void MainWindow::hgServe() @@ -1821,126 +1817,22 @@ } } -void MainWindow::updateFileSystemWatcher() +void MainWindow::updateFsWatcher() { - bool justCreated = false; - if (!m_fsWatcher) { - m_fsWatcher = new QFileSystemWatcher(); - justCreated = true; - } - - // QFileSystemWatcher will refuse to add a file or directory to - // its watch list that it is already watching -- fine, that's what - // we want -- but it prints a warning when this happens, which is - // annoying because it would be the normal case for us. So we'll - // check for duplicates ourselves. - QSet alreadyWatched; - QStringList dl(m_fsWatcher->directories()); - foreach (QString d, dl) alreadyWatched.insert(d); - - std::deque pending; - pending.push_back(m_workFolderPath); - - while (!pending.empty()) { - - QString path = pending.front(); - pending.pop_front(); - if (!alreadyWatched.contains(path)) { - m_fsWatcher->addPath(path); - DEBUG << "Added to file system watcher: " << path << endl; - } - - QDir d(path); - if (d.exists()) { - d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | - QDir::Readable | QDir::NoSymLinks); - foreach (QString entry, d.entryList()) { - if (entry.startsWith('.')) continue; - QString entryPath = d.absoluteFilePath(entry); - pending.push_back(entryPath); - } - } - } - - // The general timer isn't really related to the fs watcher - // object, it just does something similar -- every now and then we - // do a refresh just to update the history dates etc - - m_fsWatcherGeneralTimer = new QTimer(this); - connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()), - this, SLOT(checkFilesystem())); - m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour - m_fsWatcherGeneralTimer->start(); - - if (justCreated) { - connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), - this, SLOT(fsDirectoryChanged(QString))); - connect(m_fsWatcher, SIGNAL(fileChanged(QString)), - this, SLOT(fsFileChanged(QString))); - } -} - -void MainWindow::suspendFileSystemWatcher() -{ - DEBUG << "MainWindow::suspendFileSystemWatcher" << endl; - if (m_fsWatcher) { - m_fsWatcherSuspended = true; - if (m_fsWatcherRestoreTimer) { - delete m_fsWatcherRestoreTimer; - m_fsWatcherRestoreTimer = 0; - } - m_fsWatcherGeneralTimer->stop(); - } -} - -void MainWindow::restoreFileSystemWatcher() -{ - DEBUG << "MainWindow::restoreFileSystemWatcher" << endl; - if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer; - - // The restore timer is used to leave a polite interval between - // being asked to restore the watcher and actually doing so. It's - // a single shot timer each time it's used, but we don't use - // QTimer::singleShot because we want to stop the previous one if - // it's running (via deleting it) - - m_fsWatcherRestoreTimer = new QTimer(this); - connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()), - this, SLOT(actuallyRestoreFileSystemWatcher())); - m_fsWatcherRestoreTimer->setInterval(1000); - m_fsWatcherRestoreTimer->setSingleShot(true); - m_fsWatcherRestoreTimer->start(); -} - -void MainWindow::actuallyRestoreFileSystemWatcher() -{ - DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl; - if (m_fsWatcher) { - m_fsWatcherSuspended = false; - m_fsWatcherGeneralTimer->start(); - } + m_fsWatcher->setWorkDirPath(m_workFolderPath); + m_fsWatcher->setTrackedFilePaths(m_hgTabs->getFileStates().trackedFiles()); } void MainWindow::checkFilesystem() { DEBUG << "MainWindow::checkFilesystem" << endl; - hgRefresh(); -} - -void MainWindow::fsDirectoryChanged(QString d) -{ - DEBUG << "MainWindow::fsDirectoryChanged " << d << endl; - if (!m_fsWatcherSuspended) { - hgStat(); + if (!m_commandSequenceInProgress) { + if (!m_fsWatcher->getChangedPaths(m_fsWatcherToken).empty()) { + hgRefresh(); + return; + } } -} - -void MainWindow::fsFileChanged(QString f) -{ - DEBUG << "MainWindow::fsFileChanged " << f << endl; - if (!m_fsWatcherSuspended) { - hgStat(); - } + updateFsWatcher(); } QString MainWindow::format1(QString head) @@ -2088,21 +1980,14 @@ void MainWindow::commandStarting(HgAction action) { - // Annoyingly, hg stat actually modifies the working directory -- - // it creates files called hg-checklink and hg-checkexec to test - // properties of the filesystem. For safety's sake, suspend the - // fs watcher while running commands, and restore it shortly after - // a command has finished. - - if (action.action == ACT_STAT) { - suspendFileSystemWatcher(); - } + m_commandSequenceInProgress = true; } -void MainWindow::commandFailed(HgAction action, QString stderr, QString stdout) +void MainWindow::commandFailed(HgAction action, QString stdErr, QString stdOut) { DEBUG << "MainWindow::commandFailed" << endl; - restoreFileSystemWatcher(); + + m_commandSequenceInProgress = false; QString setstr; #ifdef Q_OS_MAC @@ -2130,7 +2015,7 @@ tr("Failed to run Mercurial"), tr("Failed to run Mercurial"), tr("The Mercurial program either could not be found or failed to run.

Check that the Mercurial program path is correct in %1.").arg(setstr), - stderr); + stdErr); settings(SettingsDialog::PathsTab); return; case ACT_TEST_HG_EXT: @@ -2139,7 +2024,7 @@ tr("Failed to run Mercurial"), tr("Failed to run Mercurial with extension enabled"), tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.
This may indicate an installation problem.

You may be able to continue working if you switch off “Use EasyHg Mercurial Extension” in %1. Note that remote repositories that require authentication might not work if you do this.").arg(setstr), - stderr); + stdErr); settings(SettingsDialog::PathsTab); return; case ACT_CLONEFROMREMOTE: @@ -2148,20 +2033,20 @@ enableDisableActions(); break; // go on to default report case ACT_INCOMING: - if (stderr.contains("authorization failed")) { - reportAuthFailed(stderr); + if (stdErr.contains("authorization failed")) { + reportAuthFailed(stdErr); return; - } else if (stderr.contains("entry cancelled")) { + } else if (stdErr.contains("entry cancelled")) { // ignore this, user cancelled username or password dialog return; } else { - // Incoming returns non-zero code and no stderr if the + // Incoming returns non-zero code and no stdErr if the // check was successful but there are no changes // pending. This is the only case where we need to remove // warning messages, because it's the only case where a // non-zero code can be returned even though the command // has for our purposes succeeded - QString replaced = stderr; + QString replaced = stdErr; while (1) { QString r1 = replaced; r1.replace(QRegExp("warning: [^\\n]*"), ""); @@ -2175,31 +2060,33 @@ } break; // go on to default report case ACT_PULL: - if (stderr.contains("authorization failed")) { - reportAuthFailed(stderr); + if (stdErr.contains("authorization failed")) { + reportAuthFailed(stdErr); return; - } else if (stderr.contains("entry cancelled")) { + } else if (stdErr.contains("entry cancelled")) { // ignore this, user cancelled username or password dialog return; - } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) { + } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) { // success: hg 2.1 starts returning failure code for empty pull/push - commandCompleted(action, stdout); + m_commandSequenceInProgress = true; // there may be further commands + commandCompleted(action, stdOut); return; } break; // go on to default report case ACT_PUSH: - if (stderr.contains("creates new remote head")) { - reportNewRemoteHeads(stderr); + if (stdErr.contains("creates new remote head")) { + reportNewRemoteHeads(stdErr); return; - } else if (stderr.contains("authorization failed")) { - reportAuthFailed(stderr); + } else if (stdErr.contains("authorization failed")) { + reportAuthFailed(stdErr); return; - } else if (stderr.contains("entry cancelled")) { + } else if (stdErr.contains("entry cancelled")) { // ignore this, user cancelled username or password dialog return; - } else if (stderr.contains("no changes found") || stdout.contains("no changes found")) { + } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) { // success: hg 2.1 starts returning failure code for empty pull/push - commandCompleted(action, stdout); + m_commandSequenceInProgress = true; // there may be further commands + commandCompleted(action, stdOut); return; } break; // go on to default report @@ -2209,22 +2096,23 @@ // problem, something else will fail too). Pretend it // succeeded, so that any further actions that are contingent // on the success of the heads query get carried out properly. + m_commandSequenceInProgress = true; // there may be further commands commandCompleted(action, ""); return; case ACT_FOLDERDIFF: case ACT_CHGSETDIFF: - // external program, unlikely to be anything useful in stderr + // external program, unlikely to be anything useful in stdErr // and some return with failure codes when something as basic // as the user closing the window via the wm happens return; case ACT_MERGE: - if (stderr.contains("working directory ancestor")) { + if (stdErr.contains("working directory ancestor")) { // arguably we should prevent this upfront, but that's // trickier! MoreInformationDialog::information (this, tr("Merge"), tr("Merge has no effect"), tr("You asked to merge a revision with one of its ancestors.

This has no effect, because the ancestor's changes already exist in both revisions."), - stderr); + stdErr); return; } // else fall through @@ -2232,7 +2120,7 @@ MoreInformationDialog::information (this, tr("Merge"), tr("Merge failed"), tr("Some files were not merged successfully.

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."), - stderr); + stdErr); m_mergeCommitComment = ""; return; case ACT_STAT: @@ -2251,17 +2139,16 @@ (this, tr("Command failed"), tr("Command failed"), - (stderr == "" ? + (stdErr == "" ? tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.") : tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.

See “More Details” for the command output.")), - stderr); + stdErr); } void MainWindow::commandCompleted(HgAction completedAction, QString output) { // std::cerr << "commandCompleted: " << completedAction.action << std::endl; - restoreFileSystemWatcher(); HGACTIONS action = completedAction.action; if (action == ACT_NONE) return; @@ -2320,7 +2207,6 @@ case ACT_STAT: m_lastStatOutput = output; - updateFileSystemWatcher(); break; case ACT_RESOLVE_LIST: @@ -2624,10 +2510,12 @@ } if (noMore) { + m_commandSequenceInProgress = false; m_stateUnknown = false; enableDisableActions(); m_hgTabs->updateHistory(); updateRecentMenu(); + checkFilesystem(); } } @@ -2996,7 +2884,7 @@ m_exitAct->setStatusTip(tr("Exit EasyMercurial")); //Repository actions - m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Refresh"), this); + m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Re-Read Working Folder"), this); m_hgRefreshAct->setShortcut(tr("Ctrl+R")); m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder")); @@ -3118,22 +3006,19 @@ { int sz = 32; - m_fileToolBar = addToolBar(tr("File")); - m_fileToolBar->setIconSize(QSize(sz, sz)); - m_fileToolBar->addAction(m_openAct); - m_fileToolBar->addAction(m_hgRefreshAct); - m_fileToolBar->setMovable(false); - - m_repoToolBar = addToolBar(tr("Remote")); - m_repoToolBar->setIconSize(QSize(sz, sz)); - m_repoToolBar->addAction(m_hgIncomingAct); - m_repoToolBar->addAction(m_hgPullAct); - m_repoToolBar->addAction(m_hgPushAct); - m_repoToolBar->setMovable(false); + bool spacingReqd = false; +#ifndef Q_OS_MAC + spacingReqd = true; +#endif m_workFolderToolBar = addToolBar(tr("Work")); addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar); m_workFolderToolBar->setIconSize(QSize(sz, sz)); + if (spacingReqd) { + QWidget *w = new QWidget; + w->setFixedHeight(6); + m_workFolderToolBar->addWidget(w); + } m_workFolderToolBar->addAction(m_hgFolderDiffAct); m_workFolderToolBar->addSeparator(); m_workFolderToolBar->addAction(m_hgRevertAct); @@ -3145,6 +3030,17 @@ m_workFolderToolBar->addAction(m_hgRemoveAct); m_workFolderToolBar->setMovable(false); + m_repoToolBar = addToolBar(tr("Remote")); + m_repoToolBar->setIconSize(QSize(sz, sz)); + if (spacingReqd) m_repoToolBar->addWidget(new QLabel(" ")); + m_repoToolBar->addAction(m_openAct); + if (spacingReqd) m_repoToolBar->addWidget(new QLabel(" ")); + m_repoToolBar->addSeparator(); + m_repoToolBar->addAction(m_hgIncomingAct); + m_repoToolBar->addAction(m_hgPullAct); + m_repoToolBar->addAction(m_hgPushAct); + m_repoToolBar->setMovable(false); + updateToolBarStyle(); } diff -r a4e699d32a9a -r dca5bd5b2a06 src/mainwindow.h --- a/src/mainwindow.h Fri Feb 10 13:08:07 2012 +0000 +++ b/src/mainwindow.h Tue Feb 14 17:55:39 2012 +0000 @@ -27,7 +27,6 @@ #include #include -#include QT_BEGIN_NAMESPACE class QAction; @@ -36,6 +35,7 @@ QT_END_NAMESPACE class WorkStatusWidget; +class FsWatcher; class MainWindow : public QMainWindow { @@ -113,10 +113,8 @@ void hgIgnoreFiles(QStringList); void hgUnIgnoreFiles(QStringList); - void fsDirectoryChanged(QString); - void fsFileChanged(QString); + void updateFsWatcher(); void checkFilesystem(); - void actuallyRestoreFileSystemWatcher(); void newerVersionAvailable(QString); @@ -176,10 +174,6 @@ void clearState(); - void updateFileSystemWatcher(); - void suspendFileSystemWatcher(); - void restoreFileSystemWatcher(); - void updateClosedHeads(); void updateWorkFolderAndRepoNames(); @@ -243,7 +237,6 @@ QAction *m_aboutAct; QAction *m_helpAct; - QToolBar *m_fileToolBar; QToolBar *m_repoToolBar; QToolBar *m_workFolderToolBar; @@ -257,10 +250,9 @@ QString getMergeBinaryName(); QString getEditorBinaryName(); - QFileSystemWatcher *m_fsWatcher; - QTimer *m_fsWatcherGeneralTimer; - QTimer *m_fsWatcherRestoreTimer; - bool m_fsWatcherSuspended; + FsWatcher *m_fsWatcher; + int m_fsWatcherToken; + bool m_commandSequenceInProgress; QString m_lastStatOutput; QStringList m_lastRevertedFiles;