Mercurial > hg > easyhg
changeset 548:dca5bd5b2a06
Merge from branch "fswatcher"
author | Chris Cannam |
---|---|
date | Tue, 14 Feb 2012 17:55:39 +0000 |
parents | a4e699d32a9a (current diff) 9f91d1b2ed51 (diff) |
children | 06f7ae09015f |
files | |
diffstat | 9 files changed, 537 insertions(+), 202 deletions(-) [+] |
line wrap: on
line diff
--- 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
--- 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;
--- 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
--- 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("<qt><b>%1</b></qt>") - .arg(m_simpleLabels[s]); } - settings.endGroup(); + return QString("<qt><b>%1</b></qt>") + .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;
--- 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();
--- /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 <QMutexLocker> +#include <QDir> + +#include <deque> + +#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<QString> alreadyWatched = + QSet<QString>::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<QString> alreadyWatched = + QSet<QString>::fromList(m_watcher.directories()); + + std::deque<QString> 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<QString> +FsWatcher::getChangedPaths(int token) +{ + QMutexLocker locker(&m_mutex); + size_t lastUpdatedAt = m_tokenMap[token]; + QSet<QString> changed; + for (QHash<QString, size_t>::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<QString> 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<QString> +FsWatcher::scanDirectory(QString path) +{ + QSet<QString> 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 +}
--- /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 <QObject> +#include <QMutex> +#include <QString> +#include <QSet> +#include <QHash> +#include <QMap> +#include <QStringList> +#include <QFileSystemWatcher> + +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<QString> 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<QString> 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<QString, size_t> 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<int, size_t> 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<QString, QSet<QString> > 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
--- 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<QString> alreadyWatched; - QStringList dl(m_fsWatcher->directories()); - foreach (QString d, dl) alreadyWatched.insert(d); - - std::deque<QString> 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.<br><br>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.<br>This may indicate an installation problem.<br><br>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.<p>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.<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."), - 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.<br><br>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(); }
--- 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 <QMainWindow> #include <QListWidget> -#include <QFileSystemWatcher> 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;