diff --git a/bazaar/fileviewbazaarplugin.cpp b/bazaar/fileviewbazaarplugin.cpp index f2ca0f8..cd79f7a 100644 --- a/bazaar/fileviewbazaarplugin.cpp +++ b/bazaar/fileviewbazaarplugin.cpp @@ -1,505 +1,505 @@ /*************************************************************************** * Copyright (C) 2009-2010 by Peter Penz * * Copyright (C) 2011 Canonical Ltd. * * By Jonathan Riddell * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "fileviewbazaarplugin.h" #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(FileViewBazaarPluginFactory, registerPlugin();) FileViewBazaarPlugin::FileViewBazaarPlugin(QObject* parent, const QList& args) : KVersionControlPlugin(parent), m_pendingOperation(false), m_versionInfoHash(), m_updateAction(0), m_pullAction(0), m_pushAction(0), m_showLocalChangesAction(0), m_commitAction(0), m_addAction(0), m_removeAction(0), m_logAction(0), m_command(), m_arguments(), m_errorMsg(), m_operationCompletedMsg(), m_contextDir(), m_contextItems(), m_process(), m_tempFile() { Q_UNUSED(args); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme("go-down")); m_updateAction->setText(i18nc("@item:inmenu", "Bazaar Update")); connect(m_updateAction, SIGNAL(triggered()), this, SLOT(updateFiles())); m_pullAction = new QAction(this); m_pullAction->setIcon(QIcon::fromTheme("go-bottom")); m_pullAction->setText(i18nc("@item:inmenu", "Bazaar Pull")); connect(m_pullAction, SIGNAL(triggered()), this, SLOT(pullFiles())); m_pushAction = new QAction(this); m_pushAction->setIcon(QIcon::fromTheme("go-top")); m_pushAction->setText(i18nc("@item:inmenu", "Bazaar Push")); connect(m_pushAction, SIGNAL(triggered()), this, SLOT(pushFiles())); m_showLocalChangesAction = new QAction(this); m_showLocalChangesAction->setIcon(QIcon::fromTheme("view-split-left-right")); m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local Bazaar Changes")); connect(m_showLocalChangesAction, SIGNAL(triggered()), this, SLOT(showLocalChanges())); m_commitAction = new QAction(this); m_commitAction->setIcon(QIcon::fromTheme("svn-commit")); m_commitAction->setText(i18nc("@item:inmenu", "Bazaar Commit...")); connect(m_commitAction, SIGNAL(triggered()), this, SLOT(commitFiles())); m_addAction = new QAction(this); m_addAction->setIcon(QIcon::fromTheme("list-add")); m_addAction->setText(i18nc("@item:inmenu", "Bazaar Add...")); connect(m_addAction, SIGNAL(triggered()), this, SLOT(addFiles())); m_removeAction = new QAction(this); m_removeAction->setIcon(QIcon::fromTheme("list-remove")); m_removeAction->setText(i18nc("@item:inmenu", "Bazaar Delete")); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeFiles())); m_logAction = new QAction(this); m_logAction->setIcon(QIcon::fromTheme("format-list-ordered")); m_logAction->setText(i18nc("@item:inmenu", "Bazaar Log")); connect(m_logAction, SIGNAL(triggered()), this, SLOT(log())); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus))); - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError())); + connect(&m_process, &QProcess::errorOccurred, + this, &FileViewBazaarPlugin::slotOperationError); } FileViewBazaarPlugin::~FileViewBazaarPlugin() { } QString FileViewBazaarPlugin::fileName() const { return QLatin1String(".bzr"); } bool FileViewBazaarPlugin::beginRetrieval(const QString& directory) { Q_ASSERT(directory.endsWith(QLatin1Char('/'))); QString baseDir; QProcess process1; process1.setWorkingDirectory(directory); process1.start(QLatin1String("bzr root")); while (process1.waitForReadyRead()) { char buffer[512]; while (process1.readLine(buffer, sizeof(buffer)) > 0) { baseDir = QString(buffer).trimmed(); } } // if bzr is not installed if (baseDir == "") { return false; } // Clear all entries for this directory including the entries // for sub directories QMutableHashIterator it(m_versionInfoHash); while (it.hasNext()) { it.next(); if (it.key().startsWith(directory) || !it.key().startsWith(baseDir)) { it.remove(); } } QProcess process2; process2.setWorkingDirectory(directory); process2.start(QLatin1String("bzr ignored")); while (process2.waitForReadyRead()) { char buffer[512]; while (process2.readLine(buffer, sizeof(buffer)) > 0) { QString line = QString(buffer).trimmed(); QStringList list = line.split(" "); QString file = baseDir + "/" + list[0]; m_versionInfoHash.insert(file, UnversionedVersion); } } QStringList arguments; arguments << QLatin1String("status") << QLatin1String("-S"); arguments << baseDir; QProcess process; process.start(QLatin1String("bzr"), arguments); while (process.waitForReadyRead()) { char buffer[1024]; while (process.readLine(buffer, sizeof(buffer)) > 0) { ItemVersion state = NormalVersion; QString filePath = QString::fromUtf8(buffer); // This could probably do with being more consistent switch (buffer[0]) { case '?': state = UnversionedVersion; break; case ' ': if (buffer[1] == 'M') {state = LocallyModifiedVersion;} break; case '+': state = AddedVersion; break; case '-': state = RemovedVersion; break; case 'C': state = ConflictingVersion; break; default: if (filePath.contains('*')) { state = UpdateRequiredVersion; } break; } // Only values with a different state as 'NormalVersion' // are added to the hash table. If a value is not in the // hash table, it is automatically defined as 'NormalVersion' // (see FileViewBazaarPlugin::versionState()). if (state != NormalVersion) { int pos = 4; const int length = filePath.length() - pos - 1; //conflicts annoyingly have a human readable text before the filename //TODO cover other conflict types if (filePath.startsWith("C Text conflict")) { filePath = filePath.mid(17, length); } filePath = baseDir + "/" + filePath.mid(pos, length); //remove type symbols from directories, links and executables if (filePath.endsWith("/") || filePath.endsWith("@") || filePath.endsWith("*")) { filePath = filePath.left(filePath.length() - 1); } if (!filePath.isEmpty()) { m_versionInfoHash.insert(filePath, state); } } } } if ((process.exitCode() != 0 || process.exitStatus() != QProcess::NormalExit)) { return false; } return true; } void FileViewBazaarPlugin::endRetrieval() { } KVersionControlPlugin::ItemVersion FileViewBazaarPlugin::itemVersion(const KFileItem& item) const { const QString itemUrl = item.localPath(); if (m_versionInfoHash.contains(itemUrl)) { return m_versionInfoHash.value(itemUrl); } if (!item.isDir()) { // files that have not been listed by 'bzr status' or 'bzr ignored' (= m_versionInfoHash) // are under version control per definition return NormalVersion; } // The item is a directory. Check whether an item listed by 'bzr status' (= m_versionInfoHash) // is part of this directory. In this case a local modification should be indicated in the // directory already. const QString itemDir = itemUrl + QDir::separator(); QHash::const_iterator it = m_versionInfoHash.constBegin(); while (it != m_versionInfoHash.constEnd()) { if (it.key().startsWith(itemDir)) { const ItemVersion state = m_versionInfoHash.value(it.key()); if (state == LocallyModifiedVersion) { return LocallyModifiedVersion; } } ++it; } return NormalVersion; } QList FileViewBazaarPlugin::actions(const KFileItemList &items) const { if (items.count() == 1 && items.first().isDir()) { QString directory = items.first().localPath(); if (!directory.endsWith(QLatin1Char('/'))) { directory += QLatin1Char('/'); } if (directory == m_contextDir) { return contextMenuDirectoryActions(directory); } else { return contextMenuFilesActions(items); } } else { return contextMenuFilesActions(items); } } QList FileViewBazaarPlugin::contextMenuFilesActions(const KFileItemList& items) const { Q_ASSERT(!items.isEmpty()); foreach (const KFileItem& item, items) { m_contextItems.append(item); } m_contextDir.clear(); const bool noPendingOperation = !m_pendingOperation; if (noPendingOperation) { // iterate all items and check the version state to know which // actions can be enabled const int itemsCount = items.count(); int versionedCount = 0; int editingCount = 0; foreach (const KFileItem& item, items) { const ItemVersion state = itemVersion(item); if (state != UnversionedVersion) { ++versionedCount; } switch (state) { case LocallyModifiedVersion: case ConflictingVersion: ++editingCount; break; default: break; } } m_commitAction->setEnabled(editingCount > 0); m_addAction->setEnabled(versionedCount == 0); m_removeAction->setEnabled(versionedCount == itemsCount); } else { m_commitAction->setEnabled(false); m_addAction->setEnabled(false); m_removeAction->setEnabled(false); } m_updateAction->setEnabled(noPendingOperation); m_pullAction->setEnabled(noPendingOperation); m_pushAction->setEnabled(noPendingOperation); m_showLocalChangesAction->setEnabled(noPendingOperation); m_logAction->setEnabled(noPendingOperation); QList actions; actions.append(m_updateAction); actions.append(m_pullAction); actions.append(m_pushAction); actions.append(m_commitAction); actions.append(m_addAction); actions.append(m_removeAction); actions.append(m_showLocalChangesAction); actions.append(m_logAction); return actions; } QList FileViewBazaarPlugin::contextMenuDirectoryActions(const QString& directory) const { m_contextDir = directory; m_contextItems.clear(); // Only enable the actions if no commands are // executed currently (see slotOperationCompleted() and // startBazaarCommandProcess()). const bool enabled = !m_pendingOperation; m_updateAction->setEnabled(enabled); m_pullAction->setEnabled(enabled); m_pushAction->setEnabled(enabled); m_commitAction->setEnabled(enabled); m_addAction->setEnabled(enabled); m_showLocalChangesAction->setEnabled(enabled); m_logAction->setEnabled(enabled); QList actions; actions.append(m_updateAction); actions.append(m_pullAction); actions.append(m_pushAction); actions.append(m_commitAction); actions.append(m_addAction); actions.append(m_showLocalChangesAction); actions.append(m_logAction); return actions; } void FileViewBazaarPlugin::updateFiles() { execBazaarCommand("qupdate", QStringList(), i18nc("@info:status", "Updating Bazaar repository..."), i18nc("@info:status", "Update of Bazaar repository failed."), i18nc("@info:status", "Updated Bazaar repository.")); } void FileViewBazaarPlugin::pullFiles() { QStringList arguments = QStringList(); arguments << "-d"; execBazaarCommand("qpull", arguments, i18nc("@info:status", "Pulling Bazaar repository..."), i18nc("@info:status", "Pull of Bazaar repository failed."), i18nc("@info:status", "Pulled Bazaar repository.")); } void FileViewBazaarPlugin::pushFiles() { QStringList arguments = QStringList(); arguments << "-d"; execBazaarCommand("qpush", arguments, i18nc("@info:status", "Pushing Bazaar repository..."), i18nc("@info:status", "Push of Bazaar repository failed."), i18nc("@info:status", "Pushed Bazaar repository.")); } void FileViewBazaarPlugin::showLocalChanges() { execBazaarCommand("qdiff", QStringList(), i18nc("@info:status", "Reviewing Changes..."), i18nc("@info:status", "Review Changes failed."), i18nc("@info:status", "Reviewed Changes.")); } void FileViewBazaarPlugin::commitFiles() { execBazaarCommand("qcommit", QStringList(), i18nc("@info:status", "Committing Bazaar changes..."), i18nc("@info:status", "Commit of Bazaar changes failed."), i18nc("@info:status", "Committed Bazaar changes.")); } void FileViewBazaarPlugin::addFiles() { execBazaarCommand(QLatin1String("qadd"), QStringList(), i18nc("@info:status", "Adding files to Bazaar repository..."), i18nc("@info:status", "Adding of files to Bazaar repository failed."), i18nc("@info:status", "Added files to Bazaar repository.")); } void FileViewBazaarPlugin::removeFiles() { execBazaarCommand(QLatin1String("remove"), QStringList(), i18nc("@info:status", "Removing files from Bazaar repository..."), i18nc("@info:status", "Removing of files from Bazaar repository failed."), i18nc("@info:status", "Removed files from Bazaar repository.")); } void FileViewBazaarPlugin::log() { execBazaarCommand(QLatin1String("qlog"), QStringList(), i18nc("@info:status", "Running Bazaar Log..."), i18nc("@info:status", "Running Bazaar Log failed."), i18nc("@info:status", "Bazaar Log closed.")); } void FileViewBazaarPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { m_pendingOperation = false; if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { emit errorMessage(m_errorMsg); } else if (m_contextItems.isEmpty()) { emit operationCompletedMessage(m_operationCompletedMsg); emit itemVersionsChanged(); } else { startBazaarCommandProcess(); } } void FileViewBazaarPlugin::slotOperationError() { // don't do any operation on other items anymore m_contextItems.clear(); m_pendingOperation = false; emit errorMessage(m_errorMsg); } void FileViewBazaarPlugin::execBazaarCommand(const QString& command, const QStringList& arguments, const QString& infoMsg, const QString& errorMsg, const QString& operationCompletedMsg) { emit infoMessage(infoMsg); QProcess process; process.start(QLatin1String("bzr plugins")); bool foundQbzr = false; while (process.waitForReadyRead()) { char buffer[512]; while (process.readLine(buffer, sizeof(buffer)) > 0) { QString output = QString(buffer).trimmed(); if (output.startsWith("qbzr")) { foundQbzr = true; break; } } } if (!foundQbzr) { emit infoMessage("Please Install QBzr"); return; } m_command = command; m_arguments = arguments; m_errorMsg = errorMsg; m_operationCompletedMsg = operationCompletedMsg; startBazaarCommandProcess(); } void FileViewBazaarPlugin::startBazaarCommandProcess() { Q_ASSERT(m_process.state() == QProcess::NotRunning); m_pendingOperation = true; const QString program(QLatin1String("bzr")); QStringList arguments; arguments << m_command << m_arguments; if (!m_contextDir.isEmpty()) { arguments << m_contextDir; m_contextDir.clear(); } else { const KFileItem item = m_contextItems.takeLast(); arguments << item.localPath(); // the remaining items of m_contextItems will be executed // after the process has finished (see slotOperationFinished()) } m_process.start(program, arguments); } #include "fileviewbazaarplugin.moc" diff --git a/git/fileviewgitplugin.cpp b/git/fileviewgitplugin.cpp index b95349c..fde86dc 100644 --- a/git/fileviewgitplugin.cpp +++ b/git/fileviewgitplugin.cpp @@ -1,771 +1,771 @@ /****************************************************************************** * Copyright (C) 2010 by Peter Penz * * Copyright (C) 2010 by Sebastian Doerner * * Copyright (C) 2010 by Johannes Steffen * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ******************************************************************************/ #include "fileviewgitplugin.h" #include "checkoutdialog.h" #include "commitdialog.h" #include "tagdialog.h" #include "pushdialog.h" #include "gitwrapper.h" #include "pulldialog.h" #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(FileViewGitPluginFactory, registerPlugin();) FileViewGitPlugin::FileViewGitPlugin(QObject* parent, const QList& args) : KVersionControlPlugin(parent), m_pendingOperation(false), m_addAction(0), m_removeAction(0), m_checkoutAction(0), m_commitAction(0), m_tagAction(0), m_pushAction(0), m_pullAction(0) { Q_UNUSED(args); m_revertAction = new QAction(this); m_revertAction->setIcon(QIcon::fromTheme("document-revert")); m_revertAction->setText(xi18nd("@action:inmenu", "Git Revert")); connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertFiles())); m_addAction = new QAction(this); m_addAction->setIcon(QIcon::fromTheme("list-add")); m_addAction->setText(xi18nd("@action:inmenu", "Git Add")); connect(m_addAction, SIGNAL(triggered()), this, SLOT(addFiles())); m_showLocalChangesAction = new QAction(this); m_showLocalChangesAction->setIcon(QIcon::fromTheme("view-split-left-right")); m_showLocalChangesAction->setText(xi18nd("@item:inmenu", "Show Local Git Changes")); connect(m_showLocalChangesAction, SIGNAL(triggered()), this, SLOT(showLocalChanges())); m_removeAction = new QAction(this); m_removeAction->setIcon(QIcon::fromTheme("list-remove")); m_removeAction->setText(xi18nd("@action:inmenu", "Git Remove")); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeFiles())); m_checkoutAction = new QAction(this); // m_checkoutAction->setIcon(QIcon::fromTheme("svn_switch")); does not exist in normal kde SC m_checkoutAction->setText(xi18nd("@action:inmenu", "Git Checkout...")); connect(m_checkoutAction, SIGNAL(triggered()), this, SLOT(checkout())); m_commitAction = new QAction(this); m_commitAction->setIcon(QIcon::fromTheme("svn-commit")); m_commitAction->setText(xi18nd("@action:inmenu", "Git Commit...")); connect(m_commitAction, SIGNAL(triggered()), this, SLOT(commit())); m_tagAction = new QAction(this); // m_tagAction->setIcon(QIcon::fromTheme("svn-commit")); m_tagAction->setText(xi18nd("@action:inmenu", "Git Create Tag...")); connect(m_tagAction, SIGNAL(triggered()), this, SLOT(createTag())); m_pushAction = new QAction(this); // m_pushAction->setIcon(QIcon::fromTheme("svn-commit")); m_pushAction->setText(xi18nd("@action:inmenu", "Git Push...")); connect(m_pushAction, SIGNAL(triggered()), this, SLOT(push())); m_pullAction = new QAction(this); m_pullAction->setText(xi18nd("@action:inmenu", "Git Pull...")); connect(m_pullAction, SIGNAL(triggered()), this, SLOT(pull())); m_mergeAction = new QAction(this); m_mergeAction->setIcon(QIcon::fromTheme("merge")); m_mergeAction->setText(xi18nd("@action:inmenu", "Git Merge...")); connect(m_mergeAction, &QAction::triggered, this, &FileViewGitPlugin::merge); m_logAction = new QAction(this); m_logAction->setText(xi18nd("@action:inmenu", "Git Log...")); connect(m_logAction, &QAction::triggered, this, &FileViewGitPlugin::log); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus))); - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError())); + connect(&m_process, &QProcess::errorOccurred, + this, &FileViewGitPlugin::slotOperationError); } FileViewGitPlugin::~FileViewGitPlugin() { GitWrapper::freeInstance(); } QString FileViewGitPlugin::fileName() const { return QLatin1String(".git"); } int FileViewGitPlugin::readUntilZeroChar(QIODevice* device, char* buffer, const int maxChars) { if (buffer == 0) { // discard until next \0 char c; while (device->getChar(&c) && c != '\0') ; return 0; } int index = -1; while (++index < maxChars) { if (!device->getChar(&buffer[index])) { buffer[index] = '\0'; return index == 0 ? 0 : index + 1; } if (buffer[index] == '\0') { // line end or we put it there (see above) return index + 1; } } return maxChars; } bool FileViewGitPlugin::beginRetrieval(const QString& directory) { Q_ASSERT(directory.endsWith('/')); GitWrapper::instance()->setWorkingDirectory(directory); m_currentDir = directory; // ----- find path below git base dir ----- QProcess process; process.setWorkingDirectory(directory); process.start(QLatin1String("git rev-parse --show-prefix")); QString dirBelowBaseDir = ""; while (process.waitForReadyRead()) { char buffer[512]; while (process.readLine(buffer, sizeof(buffer)) > 0) { dirBelowBaseDir = QString(buffer).trimmed(); // ends in "/" or is empty } } m_versionInfoHash.clear(); // ----- find files with special status ----- process.start(QLatin1String("git status --porcelain -z -u --ignored")); while (process.waitForReadyRead()) { char buffer[1024]; while (readUntilZeroChar(&process, buffer, sizeof(buffer)) > 0 ) { QString line = QTextCodec::codecForLocale()->toUnicode(buffer); // ----- recognize file status ----- char X = line[0].toLatin1(); // X and Y from the table in `man git-status` char Y = line[1].toLatin1(); const QString fileName= line.mid(3); ItemVersion state = NormalVersion; switch (X) { case '!': state = IgnoredVersion; break; case '?': state = UnversionedVersion; break; case 'C': // handle copied as added version case 'A': state = AddedVersion; break; case 'D': state = RemovedVersion; break; case 'M': state = LocallyModifiedVersion; break; case 'R': state = LocallyModifiedVersion; // Renames list the old file name directly afterwards, separated by \0. readUntilZeroChar(&process, 0, 0); // discard old file name break; } // overwrite status depending on the working tree switch (Y) { case 'D': // handle "deleted in working tree" as "modified in working tree" case 'M': state = LocallyModifiedUnstagedVersion; break; } // overwrite status in case of conflicts (lower part of the table in `man git-status`) if (X == 'U' || Y == 'U' || (X == 'A' && Y == 'A') || (X == 'D' && Y == 'D')) { state = ConflictingVersion; } // ----- decide what to record about that file ----- if (state == NormalVersion || !fileName.startsWith(dirBelowBaseDir)) { continue; } /// File name relative to the current working directory. const QString relativeFileName = fileName.mid(dirBelowBaseDir.length()); //if file is part of a sub-directory, record the directory if (relativeFileName.contains('/')) { if (state == IgnoredVersion) continue; if (state == AddedVersion || state == RemovedVersion) { state = LocallyModifiedVersion; } const QString absoluteDirName = directory + relativeFileName.left(relativeFileName.indexOf('/')); if (m_versionInfoHash.contains(absoluteDirName)) { ItemVersion oldState = m_versionInfoHash.value(absoluteDirName); //only keep the most important state for a directory if (oldState == ConflictingVersion) continue; if (oldState == LocallyModifiedUnstagedVersion && state != ConflictingVersion) continue; if (oldState == LocallyModifiedVersion && state != LocallyModifiedUnstagedVersion && state != ConflictingVersion) continue; m_versionInfoHash.insert(absoluteDirName, state); } else { m_versionInfoHash.insert(absoluteDirName, state); } } else { //normal file, no directory m_versionInfoHash.insert(directory + relativeFileName, state); } } } return true; } void FileViewGitPlugin::endRetrieval() { } KVersionControlPlugin::ItemVersion FileViewGitPlugin::itemVersion(const KFileItem& item) const { const QString itemUrl = item.localPath(); if (m_versionInfoHash.contains(itemUrl)) { return m_versionInfoHash.value(itemUrl); } else { // files that are not in our map are normal, tracked files by definition return NormalVersion; } } QList FileViewGitPlugin::actions(const KFileItemList &items) const { if (items.count() == 1 && items.first().isDir()) { QString directory = items.first().localPath(); if (!directory.endsWith(QLatin1Char('/'))) { directory += QLatin1Char('/'); } if (directory == m_currentDir) { return contextMenuDirectoryActions(directory); } else { return contextMenuFilesActions(items); } } else { return contextMenuFilesActions(items); } } QList FileViewGitPlugin::contextMenuFilesActions(const KFileItemList& items) const { Q_ASSERT(!items.isEmpty()); if (!m_pendingOperation){ m_contextDir = QFileInfo(items.first().localPath()).canonicalPath(); m_contextItems.clear(); foreach(const KFileItem& item, items){ m_contextItems.append(item); } //see which actions should be enabled int versionedCount = 0; int addableCount = 0; int revertCount = 0; foreach(const KFileItem& item, items){ const ItemVersion state = itemVersion(item); if (state != UnversionedVersion && state != RemovedVersion && state != IgnoredVersion) { ++versionedCount; } if (state == UnversionedVersion || state == LocallyModifiedUnstagedVersion || state == IgnoredVersion) { ++addableCount; } if (state == LocallyModifiedVersion || state == LocallyModifiedUnstagedVersion || state == ConflictingVersion) { ++revertCount; } } m_addAction->setEnabled(addableCount == items.count()); m_revertAction->setEnabled(revertCount == items.count()); m_removeAction->setEnabled(versionedCount == items.count()); } else{ m_addAction->setEnabled(false); m_revertAction->setEnabled(false); m_removeAction->setEnabled(false); } QList actions; actions.append(m_addAction); actions.append(m_removeAction); actions.append(m_revertAction); return actions; } QList FileViewGitPlugin::contextMenuDirectoryActions(const QString& directory) const { QList actions; if (!m_pendingOperation){ m_contextDir = directory; } m_checkoutAction->setEnabled(!m_pendingOperation); actions.append(m_checkoutAction); bool canCommit = false; bool showChanges = false; bool shouldMerge = false; QHash::const_iterator it = m_versionInfoHash.constBegin(); while (it != m_versionInfoHash.constEnd()) { const ItemVersion state = it.value(); if (state == LocallyModifiedVersion || state == AddedVersion || state == RemovedVersion) { canCommit = true; } if (state == LocallyModifiedUnstagedVersion || state == LocallyModifiedVersion) { showChanges = true; } if (state == ConflictingVersion) { canCommit = false; showChanges = true; shouldMerge = true; break; } ++it; } m_logAction->setEnabled(!m_pendingOperation); actions.append(m_logAction); m_showLocalChangesAction->setEnabled(!m_pendingOperation && showChanges); actions.append(m_showLocalChangesAction); if (!shouldMerge) { m_commitAction->setEnabled(!m_pendingOperation && canCommit); actions.append(m_commitAction); } else { m_mergeAction->setEnabled(!m_pendingOperation); actions.append(m_mergeAction); } m_tagAction->setEnabled(!m_pendingOperation); actions.append(m_tagAction); m_pushAction->setEnabled(!m_pendingOperation); actions.append(m_pushAction); m_pullAction->setEnabled(!m_pendingOperation); actions.append(m_pullAction); return actions; } void FileViewGitPlugin::addFiles() { execGitCommand(QLatin1String("add"), QStringList(), xi18nd("@info:status", "Adding files to Git repository..."), xi18nd("@info:status", "Adding files to Git repository failed."), xi18nd("@info:status", "Added files to Git repository.")); } void FileViewGitPlugin::removeFiles() { QStringList arguments; arguments << "-r"; //recurse through directories arguments << "--force"; //also remove files that have not been committed yet execGitCommand(QLatin1String("rm"), arguments, xi18nd("@info:status", "Removing files from Git repository..."), xi18nd("@info:status", "Removing files from Git repository failed."), xi18nd("@info:status", "Removed files from Git repository.")); } void FileViewGitPlugin::revertFiles() { execGitCommand(QLatin1String("checkout"), { "--" }, xi18nd("@info:status", "Reverting files from Git repository..."), xi18nd("@info:status", "Reverting files from Git repository failed."), xi18nd("@info:status", "Reverted files from Git repository.")); } void FileViewGitPlugin::showLocalChanges() { Q_ASSERT(!m_contextDir.isEmpty()); KRun::runCommand(QLatin1String("git difftool --dir-diff ."), nullptr, m_contextDir); } void FileViewGitPlugin::showDiff(const QUrl &link) { if (link.scheme() != QLatin1String("rev")) { return; } KRun::runCommand(QStringLiteral("git difftool --dir-diff %1^ %1").arg(link.path()), nullptr, m_contextDir); } void FileViewGitPlugin::log() { QProcess process; process.setWorkingDirectory(m_contextDir); process.start( QLatin1String("git"), QStringList { QStringLiteral("log"), QStringLiteral("--relative=."), QStringLiteral("--date=format:%d-%m-%Y"), QStringLiteral("-n 100"), QStringLiteral("--pretty=format: %h %ad %s %an ") } ); if (!process.waitForFinished() || process.exitCode() != 0) { emit errorMessage(xi18nd("@info:status", "Git Log failed.")); return; } const QString gitOutput = process.readAllStandardOutput(); QPalette palette; const QString styleSheet = QStringLiteral( "body { background: %1; color: %2; }" \ "table.logtable td { padding: 9px 8px 9px; }" \ "a { color: %3; }" \ "a:visited { color: %4; } " ).arg(palette.background().color().name(), palette.text().color().name(), palette.link().color().name(), palette.linkVisited().color().name()); auto view = new QTextBrowser(); view->setAttribute(Qt::WA_DeleteOnClose); view->setWindowTitle(xi18nd("@title:window", "Git Log")); view->setOpenLinks(false); view->setOpenExternalLinks(false); connect(view, &QTextBrowser::anchorClicked, this, &FileViewGitPlugin::showDiff); view->setHtml(QStringLiteral( "" \ "" \ "" \ "" \ "" \ "" \ "%7" \ "
%3 %4 %5

%6
" \ "" ).arg(styleSheet, palette.highlight().color().name(), i18nc("Git commit hash", "Commit"), i18nc("Git commit date", "Date"), i18nc("Git commit message", "Message"), i18nc("Git commit author", "Author"), gitOutput)); view->resize(QSize(720, 560)); view->show(); } void FileViewGitPlugin::merge() { Q_ASSERT(!m_contextDir.isEmpty()); KRun::runCommand(QStringLiteral("git mergetool"), nullptr, m_contextDir); } void FileViewGitPlugin::checkout() { CheckoutDialog dialog; if (dialog.exec() == QDialog::Accepted){ QProcess process; process.setWorkingDirectory(m_contextDir); QStringList arguments; arguments << "checkout"; if (dialog.force()) { arguments << "-f"; } const QString newBranchName = dialog.newBranchName(); if (!newBranchName.isEmpty()) { arguments << "-b"; arguments << newBranchName; } const QString checkoutIdentifier = dialog.checkoutIdentifier(); if (!checkoutIdentifier.isEmpty()) { arguments << checkoutIdentifier; } //to appear in messages const QString currentBranchName = newBranchName.isEmpty() ? checkoutIdentifier : newBranchName; process.start(QLatin1String("git"), arguments); process.setReadChannel(QProcess::StandardError); //git writes info messages to stderr as well QString completedMessage; while (process.waitForReadyRead()) { char buffer[512]; while (process.readLine(buffer, sizeof(buffer)) > 0){ const QString currentLine(buffer); if (currentLine.startsWith(QLatin1String("Switched to branch"))) { completedMessage = xi18nd("@info:status", "Switched to branch '%1'", currentBranchName); } if (currentLine.startsWith(QLatin1String("HEAD is now at"))) { const QString headIdentifier = currentLine. mid(QString("HEAD is now at ").length()).trimmed(); completedMessage = xi18nd("@info:status Git HEAD pointer, parameter includes " "short SHA-1 & commit message ", "HEAD is now at %1", headIdentifier); } //special output for checkout -b if (currentLine.startsWith(QLatin1String("Switched to a new branch"))) { completedMessage = xi18nd("@info:status", "Switched to a new branch '%1'", currentBranchName); } } } if (process.exitCode() == 0 && process.exitStatus() == QProcess::NormalExit) { if (!completedMessage.isEmpty()) { emit operationCompletedMessage(completedMessage); emit itemVersionsChanged(); } } else { emit errorMessage(xi18nd("@info:status", "Git Checkout failed." " Maybe your working directory is dirty.")); } } } void FileViewGitPlugin::commit() { CommitDialog dialog; if (dialog.exec() == QDialog::Accepted) { QTemporaryFile tmpCommitMessageFile; tmpCommitMessageFile.open(); tmpCommitMessageFile.write(dialog.commitMessage()); tmpCommitMessageFile.close(); QProcess process; process.setWorkingDirectory(m_contextDir); process.start(QString("git commit") + (dialog.amend() ? " --amend" : "")+ " -F " + tmpCommitMessageFile.fileName()); QString completedMessage; while (process.waitForReadyRead()){ char buffer[512]; while (process.readLine(buffer, sizeof(buffer)) > 0) { if (strlen(buffer) > 0 && buffer[0] == '[') { completedMessage = QTextCodec::codecForLocale()->toUnicode(buffer).trimmed(); break; } } } if (!completedMessage.isEmpty()) { emit operationCompletedMessage(completedMessage); emit itemVersionsChanged(); } } } void FileViewGitPlugin::createTag() { TagDialog dialog; if (dialog.exec() == QDialog::Accepted) { QTemporaryFile tempTagMessageFile; tempTagMessageFile.open(); tempTagMessageFile.write(dialog.tagMessage()); tempTagMessageFile.close(); QProcess process; process.setWorkingDirectory(m_contextDir); process.setReadChannel(QProcess::StandardError); process.start(QString("git tag -a -F %1 %2 %3").arg(tempTagMessageFile.fileName()). arg(dialog.tagName()).arg(dialog.baseBranch())); QString completedMessage; bool gotTagAlreadyExistsMessage = false; while (process.waitForReadyRead()) { char buffer[512]; while (process.readLine(buffer, sizeof(buffer)) > 0) { QString line(buffer); if (line.contains("already exists")) { gotTagAlreadyExistsMessage = true; } } } if (process.exitCode() == 0 && process.exitStatus() == QProcess::NormalExit) { completedMessage = xi18nd("@info:status","Successfully created tag '%1'", dialog.tagName()); emit operationCompletedMessage(completedMessage); } else { //I don't know any other error, but in case one occurs, the user doesn't get FALSE error messages emit errorMessage(gotTagAlreadyExistsMessage ? xi18nd("@info:status", "Git tag creation failed." " A tag with the name '%1' already exists.", dialog.tagName()) : xi18nd("@info:status", "Git tag creation failed.") ); } } } void FileViewGitPlugin::push() { PushDialog dialog; if (dialog.exec() == QDialog::Accepted) { m_process.setWorkingDirectory(m_contextDir); m_errorMsg = xi18nd("@info:status", "Pushing branch %1 to %2:%3 failed.", dialog.localBranch(), dialog.destination(), dialog.remoteBranch()); m_operationCompletedMsg = xi18nd("@info:status", "Pushed branch %1 to %2:%3.", dialog.localBranch(), dialog.destination(), dialog.remoteBranch()); emit infoMessage(xi18nd("@info:status", "Pushing branch %1 to %2:%3...", dialog.localBranch(), dialog.destination(), dialog.remoteBranch())); m_command = "push"; m_pendingOperation = true; m_process.start(QString("git push%4 %1 %2:%3").arg(dialog.destination()). arg(dialog.localBranch()).arg(dialog.remoteBranch()). arg(dialog.force() ? QLatin1String(" --force") : QLatin1String(""))); } } void FileViewGitPlugin::pull() { PullDialog dialog; if (dialog.exec() == QDialog::Accepted) { m_process.setWorkingDirectory(m_contextDir); m_errorMsg = xi18nd("@info:status", "Pulling branch %1 from %2 failed.", dialog.remoteBranch(), dialog.source()); m_operationCompletedMsg = xi18nd("@info:status", "Pulled branch %1 from %2 successfully.", dialog.remoteBranch(), dialog.source()); emit infoMessage(xi18nd("@info:status", "Pulling branch %1 from %2...", dialog.remoteBranch(), dialog.source())); m_command = "pull"; m_pendingOperation = true; m_process.start(QString("git pull %1 %2").arg(dialog.source()).arg(dialog.remoteBranch())); } } void FileViewGitPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { m_pendingOperation = false; QString message; if (m_command == QLatin1String("push")) { //output parsing for push message = parsePushOutput(); m_command = ""; } if (m_command == QLatin1String("pull")) { message = parsePullOutput(); m_command = ""; } if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { emit errorMessage(message.isNull() ? m_errorMsg : message); } else if (m_contextItems.isEmpty()) { emit operationCompletedMessage(message.isNull() ? m_operationCompletedMsg : message); emit itemVersionsChanged(); } else { startGitCommandProcess(); } } void FileViewGitPlugin::slotOperationError() { // don't do any operation on other items anymore m_contextItems.clear(); m_pendingOperation = false; emit errorMessage(m_errorMsg); } QString FileViewGitPlugin::parsePushOutput() { m_process.setReadChannel(QProcess::StandardError); QString message; char buffer[256]; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { const QString line(buffer); if (line.contains("->") || (line.contains("fatal") && message.isNull())) { message = line.trimmed(); } if (line.contains("Everything up-to-date") && message.isNull()) { message = xi18nd("@info:status", "Branch is already up-to-date."); } } return message; } QString FileViewGitPlugin::parsePullOutput() { char buffer[256]; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { const QString line(buffer); if (line.contains("Already up-to-date")) { return xi18nd("@info:status", "Branch is already up-to-date."); } if (line.contains("CONFLICT")) { emit itemVersionsChanged(); return xi18nd("@info:status", "Merge conflicts occurred. Fix them and commit the result."); } } return QString(); } void FileViewGitPlugin::execGitCommand(const QString& gitCommand, const QStringList& arguments, const QString& infoMsg, const QString& errorMsg, const QString& operationCompletedMsg) { emit infoMessage(infoMsg); m_command = gitCommand; m_arguments = arguments; m_errorMsg = errorMsg; m_operationCompletedMsg = operationCompletedMsg; startGitCommandProcess(); } void FileViewGitPlugin::startGitCommandProcess() { Q_ASSERT(!m_contextItems.isEmpty()); Q_ASSERT(m_process.state() == QProcess::NotRunning); m_pendingOperation = true; const KFileItem item = m_contextItems.takeLast(); m_process.setWorkingDirectory(m_contextDir); QStringList arguments; arguments << m_command; arguments << m_arguments; //force explicitly selected files but no files in selected directories if (m_command == "add" && !item.isDir()){ arguments<< QLatin1String("-f"); } arguments << item.url().fileName(); m_process.start(QLatin1String("git"), arguments); // the remaining items of m_contextItems will be executed // after the process has finished (see slotOperationFinished()) } #include "fileviewgitplugin.moc" diff --git a/hg/fileviewhgplugin.cpp b/hg/fileviewhgplugin.cpp index 430bc7a..099e2e2 100644 --- a/hg/fileviewhgplugin.cpp +++ b/hg/fileviewhgplugin.cpp @@ -1,845 +1,845 @@ /*************************************************************************** * Copyright (C) 2011 by Vishesh Yadav * * Copyright (C) 2015 by Tomasz Bojczuk * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "fileviewhgplugin.h" #include "hgconfig.h" #include "configdialog.h" #include "renamedialog.h" #include "commitdialog.h" #include "branchdialog.h" #include "tagdialog.h" #include "updatedialog.h" #include "clonedialog.h" #include "createdialog.h" #include "pushdialog.h" #include "pulldialog.h" #include "mergedialog.h" #include "bundledialog.h" #include "exportdialog.h" #include "importdialog.h" #include "servedialog.h" #include "backoutdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(FileViewHgPluginFactory, registerPlugin();) //TODO: Build a proper status signal system to sync HgWrapper/Dialgs with this //TODO: Show error messages and set their message approproately(hg output) //TODO: Use xi18nc rather thn i18c throughout plugin FileViewHgPlugin::FileViewHgPlugin(QObject *parent, const QList &args): KVersionControlPlugin(parent), m_mainContextMenu(0), m_addAction(0), m_removeAction(0), m_renameAction(0), m_commitAction(0), m_branchAction(0), m_tagAction(0), m_updateAction(0), m_cloneAction(0), m_createAction(0), m_configAction(0), m_globalConfigAction(0), m_repoConfigAction(0), m_pushAction(0), m_pullAction(0), m_revertAction(0), m_revertAllAction(0), m_rollbackAction(0), m_mergeAction(0), m_bundleAction(0), m_exportAction(0), m_unbundleAction(0), m_importAction(0), m_diffAction(0), m_serveAction(0), m_backoutAction(0), m_isCommitable(false), m_hgWrapper(0), m_retrievalHgw(0) { Q_UNUSED(args); qRegisterMetaType("QProcess::ProcessError"); qRegisterMetaType("QProcess::ExitStatus"); qRegisterMetaType("QProcess::ProcessState"); m_addAction = new QAction(this); m_addAction->setIcon(QIcon::fromTheme("list-add")); m_addAction->setText(xi18nc("@action:inmenu", "Hg Add")); connect(m_addAction, SIGNAL(triggered()), this, SLOT(addFiles())); m_removeAction = new QAction(this); m_removeAction->setIcon(QIcon::fromTheme("list-remove")); m_removeAction->setText(xi18nc("@action:inmenu", "Hg Remove")); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeFiles())); m_renameAction = new QAction(this); m_renameAction->setIcon(QIcon::fromTheme("list-rename")); m_renameAction->setText(xi18nc("@action:inmenu", "Hg Rename")); connect(m_renameAction, SIGNAL(triggered()), this, SLOT(renameFile())); m_commitAction = new QAction(this); m_commitAction->setIcon(QIcon::fromTheme("svn-commit")); m_commitAction->setText(xi18nc("@action:inmenu", "Hg Commit")); connect(m_commitAction, SIGNAL(triggered()), this, SLOT(commit())); m_tagAction = new QAction(this); m_tagAction->setIcon(QIcon::fromTheme("svn-tag")); m_tagAction->setText(xi18nc("@action:inmenu", "Hg Tag")); connect(m_tagAction, SIGNAL(triggered()), this, SLOT(tag())); m_branchAction = new QAction(this); m_branchAction->setIcon(QIcon::fromTheme("svn-branch")); m_branchAction->setText(xi18nc("@action:inmenu", "Hg Branch")); connect(m_branchAction, SIGNAL(triggered()), this, SLOT(branch())); m_cloneAction = new QAction(this); m_cloneAction->setIcon(QIcon::fromTheme("hg-clone")); m_cloneAction->setText(xi18nc("@action:inmenu", "Hg Clone")); connect(m_cloneAction, SIGNAL(triggered()), this, SLOT(clone())); m_createAction = new QAction(this); m_createAction->setIcon(QIcon::fromTheme("hg-create")); m_createAction->setText(xi18nc("@action:inmenu", "Hg Init")); connect(m_createAction, SIGNAL(triggered()), this, SLOT(create())); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme("svn-update")); m_updateAction->setText(xi18nc("@action:inmenu", "Hg Update")); connect(m_updateAction, SIGNAL(triggered()), this, SLOT(update())); m_globalConfigAction = new QAction(this); m_globalConfigAction->setIcon(QIcon::fromTheme("hg-config")); m_globalConfigAction->setText(xi18nc("@action:inmenu", "Hg Global Config")); connect(m_globalConfigAction, SIGNAL(triggered()), this, SLOT(global_config())); m_repoConfigAction = new QAction(this); m_repoConfigAction->setIcon(QIcon::fromTheme("hg-config")); m_repoConfigAction->setText(xi18nc("@action:inmenu", "Hg Repository Config")); connect(m_repoConfigAction, SIGNAL(triggered()), this, SLOT(repo_config())); m_pushAction = new QAction(this); m_pushAction->setIcon(QIcon::fromTheme("git-push")); m_pushAction->setText(xi18nc("@action:inmenu", "Hg Push")); connect(m_pushAction, SIGNAL(triggered()), this, SLOT(push())); m_pullAction = new QAction(this); m_pullAction->setIcon(QIcon::fromTheme("git-pull")); m_pullAction->setText(xi18nc("@action:inmenu", "Hg Pull")); connect(m_pullAction, SIGNAL(triggered()), this, SLOT(pull())); m_revertAction = new QAction(this); m_revertAction->setIcon(QIcon::fromTheme("hg-revert")); m_revertAction->setText(xi18nc("@action:inmenu", "Hg Revert")); connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revert())); m_revertAllAction = new QAction(this); m_revertAllAction->setIcon(QIcon::fromTheme("hg-revert")); m_revertAllAction->setText(xi18nc("@action:inmenu", "Hg Revert All")); connect(m_revertAllAction, SIGNAL(triggered()), this, SLOT(revertAll())); m_rollbackAction = new QAction(this); m_rollbackAction->setIcon(QIcon::fromTheme("hg-rollback")); m_rollbackAction->setText(xi18nc("@action:inmenu", "Hg Rollback")); connect(m_rollbackAction, SIGNAL(triggered()), this, SLOT(rollback())); m_mergeAction = new QAction(this); m_mergeAction->setIcon(QIcon::fromTheme("hg-merge")); m_mergeAction->setText(xi18nc("@action:inmenu", "Hg Merge")); connect(m_mergeAction, SIGNAL(triggered()), this, SLOT(merge())); m_bundleAction = new QAction(this); m_bundleAction->setIcon(QIcon::fromTheme("hg-bundle")); m_bundleAction->setText(xi18nc("@action:inmenu", "Hg Bundle")); connect(m_bundleAction, SIGNAL(triggered()), this, SLOT(bundle())); m_exportAction = new QAction(this); m_exportAction->setIcon(QIcon::fromTheme("hg-export")); m_exportAction->setText(xi18nc("@action:inmenu", "Hg Export")); connect(m_exportAction, SIGNAL(triggered()), this, SLOT(exportChangesets())); m_importAction = new QAction(this); m_importAction->setIcon(QIcon::fromTheme("hg-import")); m_importAction->setText(xi18nc("@action:inmenu", "Hg Import")); connect(m_importAction, SIGNAL(triggered()), this, SLOT(importChangesets())); m_unbundleAction = new QAction(this); m_unbundleAction->setIcon(QIcon::fromTheme("hg-unbundle")); m_unbundleAction->setText(xi18nc("@action:inmenu", "Hg Unbundle")); connect(m_unbundleAction, SIGNAL(triggered()), this, SLOT(unbundle())); m_serveAction = new QAction(this); m_serveAction->setIcon(QIcon::fromTheme("hg-serve")); m_serveAction->setText(xi18nc("@action:inmenu", "Hg Serve")); connect(m_serveAction, SIGNAL(triggered()), this, SLOT(serve())); m_backoutAction = new QAction(this); m_backoutAction->setIcon(QIcon::fromTheme("hg-backout")); m_backoutAction->setText(xi18nc("@action:inmenu", "Hg Backout")); connect(m_backoutAction, SIGNAL(triggered()), this, SLOT(backout())); m_diffAction = new QAction(this); m_diffAction->setIcon(QIcon::fromTheme("hg-diff")); m_diffAction->setText(xi18nc("@action:inmenu", "Hg Diff")); connect(m_diffAction, SIGNAL(triggered()), this, SLOT(diff())); /* Submenu to make the main menu less cluttered */ m_mainContextMenu = new QMenu; m_mainContextMenu->addAction(m_updateAction); m_mainContextMenu->addAction(m_branchAction); m_mainContextMenu->addAction(m_tagAction); m_mainContextMenu->addAction(m_mergeAction); m_mainContextMenu->addAction(m_revertAllAction); m_mainContextMenu->addAction(m_rollbackAction); m_mainContextMenu->addAction(m_backoutAction); m_mainContextMenu->addAction(m_bundleAction); m_mainContextMenu->addAction(m_unbundleAction); m_mainContextMenu->addAction(m_exportAction); m_mainContextMenu->addAction(m_importAction); m_mainContextMenu->addAction(m_serveAction); m_mainContextMenu->addAction(m_globalConfigAction); m_mainContextMenu->addAction(m_repoConfigAction); m_menuAction = new QAction(this); m_menuAction->setIcon(QIcon::fromTheme("hg-main")); m_menuAction->setText(xi18nc("@action:inmenu", "Mercurial")); m_menuAction->setMenu(m_mainContextMenu); } FileViewHgPlugin::~FileViewHgPlugin() { } void FileViewHgPlugin::createHgWrapper() const { static bool created = false; if (created && m_hgWrapper != 0) { return; } created = true; m_hgWrapper = HgWrapper::instance(); connect(m_hgWrapper, SIGNAL(primaryOperationFinished(int, QProcess::ExitStatus)), this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus))); - connect(m_hgWrapper, SIGNAL(primaryOperationError(QProcess::ProcessError)), - this, SLOT(slotOperationError())); + connect(m_hgWrapper, &HgWrapper::primaryOperationError, + this, &FileViewHgPlugin::slotOperationError); } QString FileViewHgPlugin::fileName() const { return QLatin1String(".hg"); } bool FileViewHgPlugin::beginRetrieval(const QString &directory) { clearMessages(); m_currentDir = directory; m_versionInfoHash.clear(); //createHgWrapper(); //m_hgWrapper->setCurrentDir(directory); //m_hgWrapper->getItemVersions(m_versionInfoHash); if (m_retrievalHgw == 0) { m_retrievalHgw = new HgWrapper; } m_retrievalHgw->setCurrentDir(directory); m_retrievalHgw->getItemVersions(m_versionInfoHash); return true; } void FileViewHgPlugin::endRetrieval() { } KVersionControlPlugin::ItemVersion FileViewHgPlugin::itemVersion(const KFileItem &item) const { //FIXME: When folder is empty or all files within untracked. const QString itemUrl = item.localPath(); if (item.isDir()) { QHash::const_iterator it = m_versionInfoHash.constBegin(); while (it != m_versionInfoHash.constEnd()) { if (it.key().startsWith(itemUrl)) { const ItemVersion state = m_versionInfoHash.value(it.key()); if (state == LocallyModifiedVersion || state == AddedVersion || state == RemovedVersion) { return LocallyModifiedVersion; } } ++it; } // Making folders with all files within untracked 'Unversioned' // will disable the context menu there. Will enable recursive // add however. QDir dir(item.localPath()); QStringList filesInside = dir.entryList(); foreach (const QString &fileName, filesInside) { if (fileName == "." || fileName == ".." ) { continue; } QUrl tempUrl(dir.absoluteFilePath(fileName)); KFileItem tempFileItem(tempUrl); if (itemVersion(tempFileItem) == NormalVersion) { return NormalVersion; } } return UnversionedVersion; } if (m_versionInfoHash.contains(itemUrl)) { return m_versionInfoHash.value(itemUrl); } return NormalVersion; } QList FileViewHgPlugin::actions(const KFileItemList &items) const { //TODO: Make it work with universal context menu when imlpemented // in dolphin qDebug() << items.count(); if (items.count() == 1 && items.first().isDir()) { return directoryContextMenu(m_currentDir); } else { return itemContextMenu(items); } return QList(); } QList FileViewHgPlugin::universalContextMenuActions(const QString &directory) const { QList result; m_universalCurrentDirectory = directory; result.append(m_createAction); result.append(m_cloneAction); return result; } QList FileViewHgPlugin::itemContextMenu(const KFileItemList &items) const { Q_ASSERT(!items.isEmpty()); clearMessages(); createHgWrapper(); m_hgWrapper->setCurrentDir(m_currentDir); if (!m_hgWrapper->isBusy()) { m_contextItems.clear(); foreach (const KFileItem &item, items) { m_contextItems.append(item); } //see which actions should be enabled int versionedCount = 0; int addableCount = 0; int revertableCount = 0; foreach (const KFileItem &item, items) { const ItemVersion state = itemVersion(item); if (state != UnversionedVersion && state != RemovedVersion) { ++versionedCount; } if (state == UnversionedVersion || state == LocallyModifiedUnstagedVersion) { ++addableCount; } if (state == LocallyModifiedVersion || state == AddedVersion || state == RemovedVersion) { ++revertableCount; } } m_addAction->setEnabled(addableCount == items.count()); m_removeAction->setEnabled(versionedCount == items.count()); m_revertAction->setEnabled(revertableCount == items.count()); m_diffAction->setEnabled(revertableCount == items.count() && items.size() == 1); m_renameAction->setEnabled(items.size() == 1 && itemVersion(items.first()) != UnversionedVersion); } else { m_addAction->setEnabled(false); m_removeAction->setEnabled(false); m_renameAction->setEnabled(false); m_revertAction->setEnabled(false); m_diffAction->setEnabled(false); } QList actions; actions.append(m_addAction); actions.append(m_removeAction); actions.append(m_renameAction); actions.append(m_revertAction); actions.append(m_diffAction); return actions; } QList FileViewHgPlugin::directoryContextMenu(const QString &directory) const { QList actions; clearMessages(); createHgWrapper(); m_hgWrapper->setCurrentDir(directory); if (!m_hgWrapper->isBusy()) { actions.append(m_commitAction); } actions.append(m_pushAction); actions.append(m_pullAction); actions.append(m_diffAction); actions.append(m_menuAction); return actions; } void FileViewHgPlugin::addFiles() { Q_ASSERT(!m_contextItems.isEmpty()); QString infoMsg = xi18nc("@info:status", "Adding files to Hg repository..."); m_errorMsg = xi18nc("@info:status", "Adding files to Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Added files to Hg repository."); emit infoMessage(infoMsg); m_hgWrapper->addFiles(m_contextItems); emit itemVersionsChanged(); } void FileViewHgPlugin::removeFiles() { Q_ASSERT(!m_contextItems.isEmpty()); int answer = KMessageBox::questionYesNo(0, xi18nc("@message:yesorno", "Would you like to remove selected files " "from the repository?")); if (answer == KMessageBox::No) { return; } QString infoMsg = xi18nc("@info:status", "Removing files from Hg repository..."); m_errorMsg = xi18nc("@info:status", "Removing files from Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Removed files from Hg repository."); emit infoMessage(infoMsg); m_hgWrapper->removeFiles(m_contextItems); } void FileViewHgPlugin::renameFile() { Q_ASSERT(m_contextItems.size() == 1); m_errorMsg = xi18nc("@info:status", "Renaming of file in Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Renamed file in Hg repository successfully."); emit infoMessage(xi18nc("@info:status", "Renaming file in Hg repository.")); HgRenameDialog dialog(m_contextItems.first()); dialog.exec(); m_contextItems.clear(); } void FileViewHgPlugin::commit() { if (m_hgWrapper->isWorkingDirectoryClean()) { KMessageBox::information(0, xi18nc("@message", "No changes for commit!")); return; } //FIXME: Disable emitting of status messages when executing sub tasks. m_errorMsg = xi18nc("@info:status", "Commit to Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Committed to Hg repository."); emit infoMessage(xi18nc("@info:status", "Commit Hg repository.")); HgCommitDialog dialog; if (dialog.exec() == QDialog::Accepted) { emit itemVersionsChanged(); }; } void FileViewHgPlugin::tag() { m_errorMsg = xi18nc("@info:status", "Tag operation in Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Tagging operation in Hg repository is successful."); emit infoMessage(xi18nc("@info:status", "Tagging operation in Hg repository.")); HgTagDialog dialog; dialog.exec(); } void FileViewHgPlugin::update() { m_errorMsg = xi18nc("@info:status", "Update of Hg working directory failed."); m_operationCompletedMsg = xi18nc("@info:status", "Update of Hg working directory is successful."); emit infoMessage(xi18nc("@info:status", "Updating Hg working directory.")); HgUpdateDialog dialog; dialog.exec(); } void FileViewHgPlugin::branch() { m_errorMsg = xi18nc("@info:status", "Branch operation on Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Branch operation on Hg repository completed successfully."); emit infoMessage(xi18nc("@info:status", "Branch operation on Hg repository.")); HgBranchDialog dialog; dialog.exec(); } void FileViewHgPlugin::clone() { clearMessages(); HgCloneDialog dialog(m_universalCurrentDirectory); dialog.exec(); } void FileViewHgPlugin::create() { clearMessages(); HgCreateDialog dialog(m_universalCurrentDirectory); dialog.exec(); } void FileViewHgPlugin::global_config() { clearMessages(); HgConfigDialog diag(HgConfig::GlobalConfig); diag.exec(); } void FileViewHgPlugin::repo_config() { clearMessages(); HgConfigDialog diag(HgConfig::RepoConfig); diag.exec(); } void FileViewHgPlugin::push() { clearMessages(); HgPushDialog diag; diag.exec(); } void FileViewHgPlugin::pull() { clearMessages(); HgPullDialog diag; diag.exec(); } void FileViewHgPlugin::merge() { clearMessages(); HgMergeDialog diag; diag.exec(); } void FileViewHgPlugin::bundle() { clearMessages(); HgBundleDialog diag; diag.exec(); } void FileViewHgPlugin::unbundle() { clearMessages(); QString bundle = QFileDialog::getOpenFileName(); if (bundle.isEmpty()) { return; } QStringList args; args << bundle; if (m_hgWrapper->executeCommandTillFinished(QLatin1String("unbundle"), args)) { } else { KMessageBox::error(0, m_hgWrapper->readAllStandardError()); } } void FileViewHgPlugin::importChangesets() { clearMessages(); HgImportDialog diag; diag.exec(); } void FileViewHgPlugin::exportChangesets() { clearMessages(); HgExportDialog diag; diag.exec(); } void FileViewHgPlugin::revert() { clearMessages(); int answer = KMessageBox::questionYesNo(0, xi18nc("@message:yesorno", "Would you like to revert changes " "made to selected files?")); if (answer == KMessageBox::No) { return; } QString infoMsg = xi18nc("@info:status", "Reverting files in Hg repository..."); m_errorMsg = xi18nc("@info:status", "Reverting files in Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Reverting files in Hg repository completed successfully."); emit infoMessage(infoMsg); m_hgWrapper->revert(m_contextItems); } void FileViewHgPlugin::revertAll() { int answer = KMessageBox::questionYesNo(0, xi18nc("@message:yesorno", "Would you like to revert all changes " "made to current working directory?")); if (answer == KMessageBox::No) { return; } QString infoMsg = xi18nc("@info:status", "Reverting files in Hg repository..."); m_errorMsg = xi18nc("@info:status", "Reverting files in Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Reverting files in Hg repository completed successfully."); emit infoMessage(infoMsg); m_hgWrapper->revertAll(); } void FileViewHgPlugin::diff() { QString infoMsg = xi18nc("@info:status", "Generating diff for Hg repository..."); m_errorMsg = xi18nc("@info:status", "Could not get Hg repository diff."); m_operationCompletedMsg = xi18nc("@info:status", "Generated Hg diff successfully."); emit infoMessage(infoMsg); QStringList args; args << QLatin1String("--config"); args << QLatin1String("extensions.hgext.extdiff="); args << QLatin1String("-p"); args << this->visualDiffExecPath(); if (m_contextItems.length() == 1) { args << m_contextItems.takeFirst().localPath(); } m_hgWrapper->executeCommand(QLatin1String("extdiff"), args); } void FileViewHgPlugin::serve() { clearMessages(); HgServeDialog diag; diag.exec(); } void FileViewHgPlugin::backout() { clearMessages(); m_hgWrapper = HgWrapper::instance(); if (!m_hgWrapper->isWorkingDirectoryClean()) { KMessageBox::error(0, xi18nc("@message:error", "abort: Uncommitted changes in working directory!")); return; } HgBackoutDialog diag; diag.exec(); } void FileViewHgPlugin::rollback() { // execute a dry run rollback first to see if there is anything to // be rolled back, or check what will be rolled back if (!m_hgWrapper->rollback(true)) { KMessageBox::error(0, xi18nc("@info:message", "No rollback " "information available!")); return; } // get what will be rolled back QString lastTransaction = m_hgWrapper->readAllStandardOutput(); int cutOfFrom = lastTransaction.indexOf(QRegExp("\\d")); lastTransaction = lastTransaction.mid(cutOfFrom); // ask int answer = KMessageBox::questionYesNo(0, xi18nc("@message:yesorno", "Would you like to rollback last transaction?") + "\nrevision: " + lastTransaction); if (answer == KMessageBox::No) { return; } QString infoMsg = xi18nc("@info:status", "Executing Rollback Hg repository..."); m_errorMsg = xi18nc("@info:status", "Rollback of Hg repository failed."); m_operationCompletedMsg = xi18nc("@info:status", "Rollback of Hg repository completed successfully."); emit infoMessage(infoMsg); m_hgWrapper->rollback(); KMessageBox::information(0, m_hgWrapper->readAllStandardOutput()); emit itemVersionsChanged(); } void FileViewHgPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { emit errorMessage(m_errorMsg); } else { m_contextItems.clear(); emit operationCompletedMessage(m_operationCompletedMsg); emit itemVersionsChanged(); } } void FileViewHgPlugin::slotOperationError() { m_contextItems.clear(); emit errorMessage(m_errorMsg); } void FileViewHgPlugin::clearMessages() const { m_operationCompletedMsg.clear(); m_errorMsg.clear(); } QString FileViewHgPlugin::visualDiffExecPath() { KConfig config("dolphin-hg", KConfig::SimpleConfig, QStandardPaths::GenericConfigLocation); KConfigGroup group(&config, QLatin1String("diff")); QString result = group.readEntry(QLatin1String("exec"), QString()).trimmed(); if (result.length() > 0) { return result; } KService::List services = KMimeTypeTrader::self()->query("text/x-diff"); return services.first()->exec().split(' ').takeFirst(); } #include "fileviewhgplugin.moc" diff --git a/hg/hgwrapper.cpp b/hg/hgwrapper.cpp index 0ac3edc..e08a822 100644 --- a/hg/hgwrapper.cpp +++ b/hg/hgwrapper.cpp @@ -1,410 +1,410 @@ /*************************************************************************** * Copyright (C) 2011 by Vishesh Yadav * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "hgwrapper.h" #include #include #include #include //TODO: Replace start() with executeCommand functions wherever possible. //FIXME: Add/Remove/Revert argument length limit. Divide the list. //FIXME: Cannot create thread for parent that is in different thread. HgWrapper *HgWrapper::m_instance = 0; HgWrapper::HgWrapper(QObject *parent) : QObject(parent) { m_localCodec = QTextCodec::codecForLocale(); // re-emit QProcess signals - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SIGNAL(error(QProcess::ProcessError))); + connect(&m_process, &QProcess::errorOccurred, + this, &HgWrapper::errorOccurred); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SIGNAL(finished(int, QProcess::ExitStatus))), connect(&m_process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SIGNAL(stateChanged(QProcess::ProcessState))); connect(&m_process, SIGNAL(started()), this, SIGNAL(started())); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus))); - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError(QProcess::ProcessError))); + connect(&m_process, &QProcess::errorOccurred, + this, &HgWrapper::slotOperationError); } HgWrapper *HgWrapper::instance() { if (!m_instance) { m_instance = new HgWrapper; } return m_instance; } void HgWrapper::freeInstance() { delete m_instance; m_instance = 0; } void HgWrapper::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "'hg' Exit Code: " << exitCode << " Exit Status: " << exitStatus; if (m_primaryOperation) { emit primaryOperationFinished(exitCode, exitStatus); } } void HgWrapper::slotOperationError(QProcess::ProcessError error) { qDebug() << "Error occurred while executing 'hg' with arguments "; if (m_primaryOperation) { emit primaryOperationError(error); } } bool HgWrapper::executeCommand(const QString &hgCommand, const QStringList &arguments, QString &output, bool primaryOperation) { Q_ASSERT(m_process.state() == QProcess::NotRunning); executeCommand(hgCommand, arguments, primaryOperation); m_process.waitForFinished(); output = QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardOutput()); return (m_process.exitStatus() == QProcess::NormalExit && m_process.exitCode() == 0); } void HgWrapper::executeCommand(const QString &hgCommand, const QStringList &arguments, bool primaryOperation) { Q_ASSERT(m_process.state() == QProcess::NotRunning); m_primaryOperation = primaryOperation; if (m_primaryOperation) { qDebug() << "Primary operation"; } QStringList args; args << hgCommand; args << arguments; m_process.setWorkingDirectory(m_currentDir); m_process.start(QLatin1String("hg"), args); } bool HgWrapper::executeCommandTillFinished(const QString &hgCommand, const QStringList &arguments, bool primaryOperation) { Q_ASSERT(m_process.state() == QProcess::NotRunning); m_primaryOperation = primaryOperation; QStringList args; args << hgCommand; args << arguments; m_process.setWorkingDirectory(m_currentDir); m_process.start(QLatin1String("hg"), args); m_process.waitForFinished(); return (m_process.exitStatus() == QProcess::NormalExit && m_process.exitCode() == 0); } QString HgWrapper::getBaseDir() const { return m_hgBaseDir; } QString HgWrapper::getCurrentDir() const { return m_currentDir; } void HgWrapper::updateBaseDir() { m_process.setWorkingDirectory(m_currentDir); m_process.start(QLatin1String("hg root")); m_process.waitForFinished(); m_hgBaseDir = QString(m_process.readAllStandardOutput()).trimmed(); } void HgWrapper::setCurrentDir(const QString &directory) { m_currentDir = directory; updateBaseDir(); //now get root directory of repository } void HgWrapper::setBaseAsWorkingDir() { m_process.setWorkingDirectory(getBaseDir()); } void HgWrapper::addFiles(const KFileItemList &fileList) { Q_ASSERT(m_process.state() == QProcess::NotRunning); QStringList args; args << QLatin1String("add"); foreach (const KFileItem &item, fileList) { args << item.localPath(); } m_process.start(QLatin1String("hg"), args); } bool HgWrapper::renameFile(const QString &source, const QString &destination) { Q_ASSERT(m_process.state() == QProcess::NotRunning); QStringList args; args << source << destination; executeCommand(QLatin1String("rename"), args, true); m_process.waitForFinished(); return (m_process.exitStatus() == QProcess::NormalExit && m_process.exitCode() == 0); } void HgWrapper::removeFiles(const KFileItemList &fileList) { Q_ASSERT(m_process.state() == QProcess::NotRunning); QStringList args; args << QLatin1String("remove"); args << QLatin1String("--force"); foreach (const KFileItem &item, fileList) { args << item.localPath(); } m_process.start(QLatin1String("hg"), args); } bool HgWrapper::commit(const QString &message, const QStringList &files, bool closeCurrentBranch) { QStringList args; args << files; args << QLatin1String("-m") << message; if (closeCurrentBranch) { args << "--close-branch"; } executeCommand(QLatin1String("commit"), args, true); m_process.waitForFinished(); return (m_process.exitCode() == 0 && m_process.exitStatus() == QProcess::NormalExit); } bool HgWrapper::createBranch(const QString &name) { QStringList args; args << name; executeCommand(QLatin1String("branch"), args, true); m_process.waitForFinished(); return (m_process.exitCode() == 0 && m_process.exitStatus() == QProcess::NormalExit); } bool HgWrapper::switchBranch(const QString &name) { QStringList args; args << QLatin1String("-c") << name; executeCommand(QLatin1String("update"), args, true); m_process.waitForFinished(); return (m_process.exitCode() == 0 && m_process.exitStatus() == QProcess::NormalExit); } bool HgWrapper::createTag(const QString &name) { QStringList args; args << name; executeCommand(QLatin1String("tag"), args, true); m_process.waitForFinished(); return (m_process.exitCode() == 0 && m_process.exitStatus() == QProcess::NormalExit); } bool HgWrapper::revertAll() { QStringList args; args << "--all"; return executeCommandTillFinished(QLatin1String("revert"), args, true); } bool HgWrapper::revert(const KFileItemList &fileList) { QStringList arguments; foreach (const KFileItem &item, fileList) { arguments << item.localPath(); } return executeCommandTillFinished(QLatin1String("revert"), arguments, true); } bool HgWrapper::rollback(bool dryRun) { QStringList args; if (dryRun) { args << QLatin1String("-n"); } return executeCommandTillFinished(QLatin1String("rollback"), args, true); } bool HgWrapper::switchTag(const QString &name) { QStringList args; args << QLatin1String("-c") << name; executeCommand(QLatin1String("update"), args, true); m_process.waitForFinished(); return (m_process.exitCode() == 0 && m_process.exitStatus() == QProcess::NormalExit); } //TODO: Make it return QStringList. QString HgWrapper::getParentsOfHead() { Q_ASSERT(m_process.state() == QProcess::NotRunning); QString output; QStringList args; args << QLatin1String("--template"); args << QLatin1String("{rev}:{node|short} "); executeCommand(QLatin1String("parents"), args, output); return output; } QStringList HgWrapper::getTags() { QStringList result; executeCommand(QLatin1String("tags")); while (m_process.waitForReadyRead()) { char buffer[1048]; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { result << QString(buffer).split(QRegExp("\\s+"), QString::SkipEmptyParts).first(); } } return result; } QStringList HgWrapper::getBranches() { QStringList result; executeCommand(QLatin1String("branches")); while (m_process.waitForReadyRead()) { char buffer[1048]; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { // 'hg branches' command lists the branches in following format // [(inactive)] // Extract just the branchname result << QString(buffer).remove(QRegExp("[\\s]+[\\d:a-zA-Z\\(\\)]*")); } } return result; } void HgWrapper::getItemVersions(QHash &result) { /*int nTrimOutLeft = m_hgBaseDir.length(); QString relativePrefix = m_currentDir.right(m_currentDir.length() - nTrimOutLeft - 1); qDebug() << m_hgBaseDir << " " << relativePrefix;*/ // Get status of files QStringList args; args << QLatin1String("status"); args << QLatin1String("--modified"); args << QLatin1String("--added"); args << QLatin1String("--removed"); args << QLatin1String("--deleted"); args << QLatin1String("--unknown"); args << QLatin1String("--ignored"); m_process.setWorkingDirectory(m_currentDir); m_process.start(QLatin1String("hg"), args); while (m_process.waitForReadyRead()) { char buffer[1024]; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { const QString currentLine(QTextCodec::codecForLocale()->toUnicode(buffer).trimmed()); char currentStatus = buffer[0]; QString currentFile = currentLine.mid(2); KVersionControlPlugin::ItemVersion vs = KVersionControlPlugin::NormalVersion; switch (currentStatus) { case 'A': vs = KVersionControlPlugin::AddedVersion; break; case 'M': vs = KVersionControlPlugin::LocallyModifiedVersion; break; case '?': vs = KVersionControlPlugin::UnversionedVersion; break; case 'R': vs = KVersionControlPlugin::RemovedVersion; break; case 'I': vs = KVersionControlPlugin::IgnoredVersion; break; case 'C': vs = KVersionControlPlugin::NormalVersion; break; case '!': vs = KVersionControlPlugin::MissingVersion; break; } if (vs != KVersionControlPlugin::NormalVersion) { // Get full path to file and insert it to result QUrl url = QUrl::fromLocalFile(m_hgBaseDir); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + "/" + currentFile); QString filePath = url.path(); result.insert(filePath, vs); } } } } void HgWrapper::terminateCurrentProcess() { qDebug() << "terminating"; m_process.terminate(); } bool HgWrapper::isWorkingDirectoryClean() { QStringList args; args << QLatin1String("--modified"); args << QLatin1String("--added"); args << QLatin1String("--removed"); args << QLatin1String("--deleted"); QString output; executeCommand(QLatin1String("status"), args, output); return output.trimmed().isEmpty(); } diff --git a/hg/hgwrapper.h b/hg/hgwrapper.h index 7be5fda..b035a6e 100644 --- a/hg/hgwrapper.h +++ b/hg/hgwrapper.h @@ -1,279 +1,279 @@ /*************************************************************************** * Copyright (C) 2011 by Vishesh Yadav * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef HGWRAPPER_H #define HGWRAPPER_H #include #include #include #include #include #include class QTextCodec; //TODO: Create signals for infoMessage and errorMessage which will be // caught by main plugin interface. /** * A singleton class providing implementation of many Mercurial commands */ class HgWrapper : public QObject { Q_OBJECT public: HgWrapper(QObject *parent = 0); static HgWrapper *instance(); static void freeInstance(); /** * Start a mercurial command with given arguments. * * @param hgCommand Command to be executed. eg. diff, status * @param arguments Arguments for the given command * @param primaryOperation Will emit primaryOperationFinished, * primaryOperationError signals. */ void executeCommand(const QString &hgCommand, const QStringList &arguments = QStringList(), bool primaryOperation=false); /** * Start a mercurial command with given arguments and return until * process completes. * * @param hgCommand Command to be executed. eg. diff, status * @param arguments Arguments for the given command * @param primaryOperation Will emit primaryOperationCompleted, * primaryOperationError signals. * @return true if operations completed successfully, otherwise false */ bool executeCommandTillFinished(const QString &hgCommand, const QStringList &arguments = QStringList(), bool primaryOperation=false); /** * Start a mercurial command with given arguments, write standard output * to output parameter and return till finished. * * @param hgCommand Command to be executed. eg. diff, status * @param arguments Arguments for the given command * @param output Append standard output of process to this string * @param primaryOperation Will emit primaryOperationCompleted, * primaryOperationError signals. * @return true if operations completed successfully, otherwise false */ bool executeCommand(const QString &hgCommand, const QStringList &arguments, QString &output, bool primaryOperation=false); /** * Get the root directory of Mercurial repository. Using 'hg root' * * @return String containing path of the root directory. */ QString getBaseDir() const; /** * Sets the current directory being browsed with plugin enabled. * Updates base directory of repository accordingly */ void setCurrentDir(const QString &directory); /** * Get the directory path that is currently used as the working directory * to execute the commands by the HgWrapper. */ QString getCurrentDir() const; /** * Set the root directory of repository as working directory. */ void setBaseAsWorkingDir(); /** * Get FileName-ItemVersion pairs of the repository returned by * * $hg status --modified --added --removed --deleted --unknown --ignored * * Hence returns files with ItemVersion * - LocallyModifiedVersion * - AddedVersion * - RemovedVersion * - RemovedVersion * - UnversionedVersion * - IgnoredVersion * - MissingVersion * * @param result A hashmap containing FileName-ItemVersion pairs * */ void getItemVersions(QHash &result); void addFiles(const KFileItemList &fileList); void removeFiles(const KFileItemList &fileList); bool renameFile(const QString &source, const QString &destination); /** * Commits changes made to the working directory. * @param message Commit message. Should not be empty. * @param files List of files to be committed. Files changed but not * listed here will be ignored during commit. * If the list is empty, all modified files will be * committed, the deault behovior. * @param closeCurrentBranch Closes the current branch after commit. * @return true if successful, otherwise false */ bool commit(const QString &message, const QStringList &files = QStringList(), bool closeCurrentBranch = false); /** * Create a new branch * @param name Name of new branch to be createdialog * @return true if successfully created, otherwise false */ bool createBranch(const QString &name); /** * Update current working directory to another branch * @param name Name of the branch to which working directory * has to be updated. * @return true if successful, otherwise false */ bool switchBranch(const QString &name); /** * Create tag for current changeset(the changeset of working directory) * @param name Name of the new tag to be createdialog * @return true if successful, otherwise false */ bool createTag(const QString &name); /** * Update working directory to a changeset named by given tag * @param name Tag of the changeset to which working directory * has to be updated. * @return true if successful, otherwise false */ bool switchTag(const QString &name); /** * Reverts all local changes made to working directory. Will update to * last changeset of current branch, ie state just after last commit. */ bool revertAll(); /** * Reverts local changes made to selected files. All changes made to * these files after last commit will be lost. */ bool revert(const KFileItemList &fileList); /** * Undo's last transaction. Like commit, pull, push(to this repo). * Does not alter working directory. * * Use with care. Rollback cant be undone. See Mercurial man page FOR * more info. * * @param dryRun Do not actually perform action, but just print output * Used to check if Rollback can be done, and if yes then * what will be rolled back. * @return true if successful, otherwise false */ bool rollback(bool dryRun=false); /** * Checks if the working directory is clean, ie there are no * uncommitted changes present. * * @return true if clean otherwise false */ bool isWorkingDirectoryClean(); QString getParentsOfHead(); /** * Returns list of all branch names. */ QStringList getBranches(); /** * Returns list of all tags */ QStringList getTags(); inline QString readAllStandardOutput() { return QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardOutput()); } inline QString readAllStandardError() { return QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardError()); } /** * Check if some Mercurial operation is currently being executed or * about to be started. */ inline bool isBusy() { return (m_process.state() == QProcess::Running || m_process.state() == QProcess::Starting); } public slots: /** * Try to terminate the currently running operation. */ void terminateCurrentProcess(); signals: ///equivalent to the signals of QProcess void finished(int exitCode, QProcess::ExitStatus exitStatus); - void error(QProcess::ProcessError error); + void errorOccurred(QProcess::ProcessError error); void started(); void stateChanged(QProcess::ProcessState state); void primaryOperationFinished(int exitCode, QProcess::ExitStatus exitStatus); void primaryOperationError(QProcess::ProcessError error); private: ///Get and update m_hgBaseDir void updateBaseDir(); private slots: void slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus); void slotOperationError(QProcess::ProcessError error); private: static HgWrapper *m_instance; QProcess m_process; QTextCodec *m_localCodec; QString m_hgBaseDir; QString m_currentDir; bool m_primaryOperation; // to differentiate intermediate process }; #endif // HGWRAPPER_H diff --git a/hg/syncdialogbase.cpp b/hg/syncdialogbase.cpp index 3d228e7..921bb03 100644 --- a/hg/syncdialogbase.cpp +++ b/hg/syncdialogbase.cpp @@ -1,349 +1,349 @@ /*************************************************************************** * Copyright (C) 2011 by Vishesh Yadav * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "syncdialogbase.h" #include "hgconfig.h" #include "pathselector.h" #include "fileviewhgpluginsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include HgSyncBaseDialog::HgSyncBaseDialog(DialogType dialogType, QWidget *parent): DialogBase(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, parent), m_haveChanges(false), m_terminated(false), m_dialogType(dialogType) { m_hgw = HgWrapper::instance(); } void HgSyncBaseDialog::setup() { createChangesGroup(); readBigSize(); setupUI(); connect(m_changesButton, SIGNAL(clicked()), this, SLOT(slotGetChanges())); connect(&m_process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(slotUpdateBusy(QProcess::ProcessState))); connect(&m_main_process, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(slotUpdateBusy(QProcess::ProcessState))); connect(&m_main_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotOperationComplete(int, QProcess::ExitStatus))); - connect(&m_main_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError())); - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotChangesProcessError())); + connect(&m_main_process, &QProcess::errorOccurred, + this, &HgSyncBaseDialog::slotOperationError); + connect(&m_process, &QProcess::errorOccurred, + this, &HgSyncBaseDialog::slotOperationError); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotChangesProcessComplete(int, QProcess::ExitStatus))); connect(this, SIGNAL(finished(int)), this, SLOT(slotWriteBigSize())); } void HgSyncBaseDialog::createOptionGroup() { setOptions(); QVBoxLayout *layout = new QVBoxLayout; foreach (QCheckBox *cb, m_options) { layout->addWidget(cb); } m_optionGroup = new QGroupBox(this); m_optionGroup->setLayout(layout); m_optionGroup->setVisible(false); } void HgSyncBaseDialog::setupUI() { // top url bar m_pathSelector = new HgPathSelector; // changes button //FIXME not very good idea. Bad HACK if (m_dialogType == PullDialog) { m_changesButton = new QPushButton(i18nc("@label:button", "Show Incoming Changes")); } else { m_changesButton = new QPushButton(i18nc("@label:button", "Show Outgoing Changes")); } m_changesButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_changesButton->setCheckable(true); // dialog's main widget QWidget *widget = new QWidget(this); QVBoxLayout *lay = new QVBoxLayout; lay->addWidget(m_pathSelector); // changes m_changesGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); lay->addWidget(m_changesGroup); // bottom bar QHBoxLayout *bottomLayout = new QHBoxLayout; m_statusProg = new QProgressBar; m_statusProg->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); bottomLayout->addWidget(m_changesButton, Qt::AlignLeft); bottomLayout->addStretch(); bottomLayout->addWidget(m_statusProg); // lay->addLayout(bottomLayout); widget->setLayout(lay); createOptionGroup(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(widget); mainLayout->addWidget(m_optionGroup); // bottom button box with OK/Cancel and Options buttons okButton()->setText(xi18nc("@action:button", m_dialogType == PullDialog ? "Pull" : "Push")); okButton()->setIcon(QIcon::fromTheme( m_dialogType == PullDialog ? "git-pull" : "git-push")); m_optionsButton = new QPushButton(buttonBox()); m_optionsButton->setIcon(QIcon::fromTheme("help-about")); switchOptionsButton(true); buttonBox()->addButton(m_optionsButton, QDialogButtonBox::ResetRole); layout()->insertLayout(0, mainLayout); connect(m_optionsButton, SIGNAL(clicked()), this, SLOT(slotOptionsButtonClick())); } void HgSyncBaseDialog::slotGetChanges() { if (m_haveChanges) { m_changesGroup->setVisible(!m_changesGroup->isVisible()); m_changesButton->setChecked(m_changesGroup->isVisible()); if (m_changesGroup->isVisible()) { loadBigSize(); } else { loadSmallSize(); } return; } if (m_process.state() == QProcess::Running) { return; } QStringList args; getHgChangesArguments(args); m_process.setWorkingDirectory(m_hgw->getBaseDir()); m_process.start(QLatin1String("hg"), args); } void HgSyncBaseDialog::slotChangesProcessComplete(int exitCode, QProcess::ExitStatus status) { if (exitCode != 0 || status != QProcess::NormalExit) { QString message = QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardError()); if (message.isEmpty()) { message = i18nc("@message", "No changes found!"); } KMessageBox::error(this, message); return; } char buffer[512]; /** * unwantedRead boolean to ensure that unwanted information messages * by mercurial are filtered out. * eg. Comparing with /path/to/repository * Searching for changes */ bool unwantedRead = false; /** * hasChanges boolean checks whether there are any changes to be sent * or received and invoke noChangesMessage() if its false */ bool hasChanges = false; while (m_process.readLine(buffer, sizeof(buffer)) > 0) { QString line(QTextCodec::codecForLocale()->toUnicode(buffer)); if (unwantedRead ) { line.remove(QLatin1String("Commit: ")); parseUpdateChanges(line.trimmed()); hasChanges = true;; } else if (line.startsWith(QLatin1String("Commit: "))) { unwantedRead = true; line.remove(QLatin1String("Commit: ")); parseUpdateChanges(line.trimmed()); hasChanges = true; } } if (!hasChanges) { noChangesMessage(); } m_changesGroup->setVisible(true); m_changesButton->setChecked(true); loadBigSize(); m_haveChanges = true; emit changeListAvailable(); } void HgSyncBaseDialog::slotChangesProcessError() { qDebug() << "Cant get changes"; KMessageBox::error(this, i18n("Error!")); } void HgSyncBaseDialog::loadSmallSize() { m_bigSize = size(); resize(m_smallSize); adjustSize(); updateGeometry(); } void HgSyncBaseDialog::loadBigSize() { m_smallSize = size(); resize(m_bigSize); } void HgSyncBaseDialog::slotWriteBigSize() { if (m_changesGroup->isVisible()) { m_bigSize = size(); } writeBigSize(); } void HgSyncBaseDialog::done(int r) { if (r == QDialog::Accepted) { if (m_main_process.state() == QProcess::Running || m_main_process.state() == QProcess::Starting) { qDebug() << "HgWrapper already busy"; return; } QStringList args; QString command = (m_dialogType==PullDialog)?"pull":"push"; args << command; args << m_pathSelector->remote(); appendOptionArguments(args); m_terminated = false; m_main_process.setWorkingDirectory(m_hgw->getBaseDir()); m_main_process.start(QLatin1String("hg"), args); } else { if (m_process.state() == QProcess::Running || m_process.state() == QProcess::Starting || m_main_process.state() == QProcess::Running || m_main_process.state() == QProcess::Starting) { if (m_process.state() == QProcess::Running || m_process.state() == QProcess::Starting) { m_process.terminate(); } if (m_main_process.state() == QProcess::Running || m_main_process.state() == QProcess::Starting) { qDebug() << "terminating pull/push process"; m_terminated = true; m_main_process.terminate(); } } else { QDialog::done(r); } } } void HgSyncBaseDialog::slotOperationComplete(int exitCode, QProcess::ExitStatus status) { if (exitCode == 0 && status == QProcess::NormalExit) { QDialog::done(QDialog::Accepted); } else { if (!m_terminated) { KMessageBox::error(this, i18n("Error!")); } } } void HgSyncBaseDialog::slotOperationError() { KMessageBox::error(this, i18n("Error!")); } void HgSyncBaseDialog::slotUpdateBusy(QProcess::ProcessState state) { if (state == QProcess::Running || state == QProcess::Starting) { m_statusProg->setRange(0, 0); m_changesButton->setEnabled(false); m_changesButton->setChecked(true); okButton()->setDisabled(true); } else { m_statusProg->setRange(0, 100); m_changesButton->setEnabled(true); okButton()->setDisabled(false); } m_statusProg->repaint(); QApplication::processEvents(); } void HgSyncBaseDialog::slotOptionsButtonClick() { if (m_optionsButton->text().contains(">>")) { switchOptionsButton(false); m_optionGroup->setVisible(true); } else { switchOptionsButton(true); m_optionGroup->setVisible(false); } } void HgSyncBaseDialog::switchOptionsButton(bool switchOn) { m_optionsButton->setText(xi18nc("@action:button", "Options") + (switchOn ? " >>" : " <<")); } diff --git a/svn/fileviewsvnplugin.cpp b/svn/fileviewsvnplugin.cpp index e20c3f1..fd221e2 100644 --- a/svn/fileviewsvnplugin.cpp +++ b/svn/fileviewsvnplugin.cpp @@ -1,483 +1,483 @@ /*************************************************************************** * Copyright (C) 2009-2011 by Peter Penz * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "fileviewsvnplugin.h" #include "fileviewsvnpluginsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(FileViewSvnPluginFactory, registerPlugin();) FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList& args) : KVersionControlPlugin(parent), m_pendingOperation(false), m_versionInfoHash(), m_updateAction(0), m_showLocalChangesAction(0), m_commitAction(0), m_addAction(0), m_removeAction(0), m_showUpdatesAction(0), m_command(), m_arguments(), m_errorMsg(), m_operationCompletedMsg(), m_contextDir(), m_contextItems(), m_process(), m_tempFile() { Q_UNUSED(args); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme("view-refresh")); m_updateAction->setText(i18nc("@item:inmenu", "SVN Update")); connect(m_updateAction, SIGNAL(triggered()), this, SLOT(updateFiles())); m_showLocalChangesAction = new QAction(this); m_showLocalChangesAction->setIcon(QIcon::fromTheme("view-split-left-right")); m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes")); connect(m_showLocalChangesAction, SIGNAL(triggered()), this, SLOT(showLocalChanges())); m_commitAction = new QAction(this); m_commitAction->setIcon(QIcon::fromTheme("svn-commit")); m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit...")); connect(m_commitAction, SIGNAL(triggered()), this, SLOT(commitFiles())); m_addAction = new QAction(this); m_addAction->setIcon(QIcon::fromTheme("list-add")); m_addAction->setText(i18nc("@item:inmenu", "SVN Add")); connect(m_addAction, SIGNAL(triggered()), this, SLOT(addFiles())); m_removeAction = new QAction(this); m_removeAction->setIcon(QIcon::fromTheme("list-remove")); m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete")); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(removeFiles())); m_revertAction = new QAction(this); m_revertAction->setIcon(QIcon::fromTheme("document-revert")); m_revertAction->setText(i18nc("@item:inmenu", "SVN Revert")); connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertFiles())); m_showUpdatesAction = new QAction(this); m_showUpdatesAction->setCheckable(true); m_showUpdatesAction->setText(i18nc("@item:inmenu", "Show SVN Updates")); m_showUpdatesAction->setChecked(FileViewSvnPluginSettings::showUpdates()); connect(m_showUpdatesAction, SIGNAL(toggled(bool)), this, SLOT(slotShowUpdatesToggled(bool))); connect(this, SIGNAL(setShowUpdatesChecked(bool)), m_showUpdatesAction, SLOT(setChecked(bool))); connect(&m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus))); - connect(&m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(slotOperationError())); + connect(&m_process, &QProcess::errorOccurred, + this, &FileViewSvnPlugin::slotOperationError); } FileViewSvnPlugin::~FileViewSvnPlugin() { } QString FileViewSvnPlugin::fileName() const { return QLatin1String(".svn"); } bool FileViewSvnPlugin::beginRetrieval(const QString& directory) { Q_ASSERT(directory.endsWith(QLatin1Char('/'))); // Clear all entries for this directory including the entries // for sub directories QMutableHashIterator it(m_versionInfoHash); while (it.hasNext()) { it.next(); if (it.key().startsWith(directory)) { it.remove(); } } QStringList arguments; arguments << QLatin1String("status"); if (FileViewSvnPluginSettings::showUpdates()) { arguments << QLatin1String("--show-updates"); } arguments << QLatin1String("--no-ignore") << directory; QProcess process; process.start(QLatin1String("svn"), arguments); while (process.waitForReadyRead()) { char buffer[1024]; while (process.readLine(buffer, sizeof(buffer)) > 0) { ItemVersion version = NormalVersion; QString filePath(buffer); switch (buffer[0]) { case 'I': case '?': version = UnversionedVersion; break; case 'M': version = LocallyModifiedVersion; break; case 'A': version = AddedVersion; break; case 'D': version = RemovedVersion; break; case 'C': version = ConflictingVersion; break; default: if (filePath.contains('*')) { version = UpdateRequiredVersion; } else if (filePath.contains("W155010")) { version = UnversionedVersion; } break; } // Only values with a different version as 'NormalVersion' // are added to the hash table. If a value is not in the // hash table, it is automatically defined as 'NormalVersion' // (see FileViewSvnPlugin::itemVersion()). if (version != NormalVersion) { int pos = filePath.indexOf('/'); const int length = filePath.length() - pos - 1; filePath = filePath.mid(pos, length); if (!filePath.isEmpty()) { m_versionInfoHash.insert(filePath, version); } } } } if ((process.exitCode() != 0 || process.exitStatus() != QProcess::NormalExit)) { if (FileViewSvnPluginSettings::showUpdates()) { // Network update failed. Unset ShowUpdates option, which triggers a refresh emit infoMessage(i18nc("@info:status", "SVN status update failed. Disabling Option " "\"Show SVN Updates\".")); emit setShowUpdatesChecked(false); // this is no fail, we just try again differently // furthermore returning false shows an error message that would override our info return true; } else { return false; } } return true; } void FileViewSvnPlugin::endRetrieval() { } KVersionControlPlugin::ItemVersion FileViewSvnPlugin::itemVersion(const KFileItem& item) const { const QString itemUrl = item.localPath(); if (m_versionInfoHash.contains(itemUrl)) { return m_versionInfoHash.value(itemUrl); } if (!item.isDir()) { // files that have not been listed by 'svn status' (= m_versionInfoHash) // are under version control per definition // NOTE: svn status does not report files in unversioned paths const QString path = QFileInfo(itemUrl).path(); return m_versionInfoHash.value(path, NormalVersion); } // The item is a directory. Check whether an item listed by 'svn status' (= m_versionInfoHash) // is part of this directory. In this case a local modification should be indicated in the // directory already. const QString itemDir = itemUrl + QDir::separator(); QHash::const_iterator it = m_versionInfoHash.constBegin(); while (it != m_versionInfoHash.constEnd()) { if (it.key().startsWith(itemDir)) { const ItemVersion version = m_versionInfoHash.value(it.key()); if (version == LocallyModifiedVersion) { return LocallyModifiedVersion; } } ++it; } return NormalVersion; } QList FileViewSvnPlugin::actions(const KFileItemList& items) const { if (items.count() == 1 && items.first().isDir()) { return directoryActions(items.first()); } foreach (const KFileItem& item, items) { m_contextItems.append(item); } m_contextDir.clear(); const bool noPendingOperation = !m_pendingOperation; if (noPendingOperation) { // iterate all items and check the version version to know which // actions can be enabled const int itemsCount = items.count(); int versionedCount = 0; int editingCount = 0; foreach (const KFileItem& item, items) { const ItemVersion version = itemVersion(item); if (version != UnversionedVersion) { ++versionedCount; } switch (version) { case LocallyModifiedVersion: case ConflictingVersion: ++editingCount; break; default: break; } } m_commitAction->setEnabled(editingCount > 0); m_addAction->setEnabled(versionedCount == 0); m_revertAction->setEnabled(editingCount == itemsCount); m_removeAction->setEnabled(versionedCount == itemsCount); } else { m_commitAction->setEnabled(false); m_addAction->setEnabled(false); m_revertAction->setEnabled(false); m_removeAction->setEnabled(false); } m_updateAction->setEnabled(noPendingOperation); QList actions; actions.append(m_updateAction); actions.append(m_commitAction); actions.append(m_addAction); actions.append(m_removeAction); actions.append(m_revertAction); actions.append(m_showUpdatesAction); return actions; } void FileViewSvnPlugin::updateFiles() { execSvnCommand("update", QStringList(), i18nc("@info:status", "Updating SVN repository..."), i18nc("@info:status", "Update of SVN repository failed."), i18nc("@info:status", "Updated SVN repository.")); } void FileViewSvnPlugin::showLocalChanges() { Q_ASSERT(!m_contextDir.isEmpty()); Q_ASSERT(m_contextItems.isEmpty()); const QString command = QLatin1String("mkfifo /tmp/fifo; svn diff ") + KShell::quoteArg(m_contextDir) + QLatin1String(" > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo"); KRun::runCommand(command, 0); } void FileViewSvnPlugin::commitFiles() { QDialog dialog(0, Qt::Dialog); QWidget* boxWidget = new QWidget(&dialog); QVBoxLayout* boxLayout = new QVBoxLayout(boxWidget); boxLayout->addWidget(new QLabel(i18nc("@label", "Description:"), boxWidget)); QPlainTextEdit* editor = new QPlainTextEdit(boxWidget); boxLayout->addWidget(editor); dialog.setWindowTitle(i18nc("@title:window", "SVN Commit")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setText(i18nc("@action:button", "Commit")); KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"), "SvnCommitDialog"); KWindowConfig::restoreWindowSize(dialog.windowHandle(), dialogConfig); if (dialog.exec() == QDialog::Accepted) { // Write the commit description into a temporary file, so // that it can be read by the command "svn commit -F". The temporary // file must stay alive until slotOperationCompleted() is invoked and will // be destroyed when the version plugin is destructed. if (!m_tempFile.open()) { emit errorMessage(i18nc("@info:status", "Commit of SVN changes failed.")); return; } QTextStream out(&m_tempFile); const QString fileName = m_tempFile.fileName(); out << editor->toPlainText(); m_tempFile.close(); QStringList arguments; arguments << "-F" << fileName; execSvnCommand("commit", arguments, i18nc("@info:status", "Committing SVN changes..."), i18nc("@info:status", "Commit of SVN changes failed."), i18nc("@info:status", "Committed SVN changes.")); } KWindowConfig::saveWindowSize(dialog.windowHandle(), dialogConfig, KConfigBase::Persistent); } void FileViewSvnPlugin::addFiles() { execSvnCommand(QLatin1String("add"), QStringList(), i18nc("@info:status", "Adding files to SVN repository..."), i18nc("@info:status", "Adding of files to SVN repository failed."), i18nc("@info:status", "Added files to SVN repository.")); } void FileViewSvnPlugin::removeFiles() { execSvnCommand(QLatin1String("remove"), QStringList(), i18nc("@info:status", "Removing files from SVN repository..."), i18nc("@info:status", "Removing of files from SVN repository failed."), i18nc("@info:status", "Removed files from SVN repository.")); } void FileViewSvnPlugin::revertFiles() { execSvnCommand(QStringLiteral("revert"), QStringList(), i18nc("@info:status", "Reverting files from SVN repository..."), i18nc("@info:status", "Reverting of files from SVN repository failed."), i18nc("@info:status", "Reverted files from SVN repository.")); } void FileViewSvnPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { m_pendingOperation = false; if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { emit errorMessage(m_errorMsg); } else if (m_contextItems.isEmpty()) { emit operationCompletedMessage(m_operationCompletedMsg); emit itemVersionsChanged(); } else { startSvnCommandProcess(); } } void FileViewSvnPlugin::slotOperationError() { // don't do any operation on other items anymore m_contextItems.clear(); m_pendingOperation = false; emit errorMessage(m_errorMsg); } void FileViewSvnPlugin::slotShowUpdatesToggled(bool checked) { FileViewSvnPluginSettings* settings = FileViewSvnPluginSettings::self(); Q_ASSERT(settings != 0); settings->setShowUpdates(checked); settings->writeConfig(); emit itemVersionsChanged(); } void FileViewSvnPlugin::execSvnCommand(const QString& svnCommand, const QStringList& arguments, const QString& infoMsg, const QString& errorMsg, const QString& operationCompletedMsg) { emit infoMessage(infoMsg); m_command = svnCommand; m_arguments = arguments; m_errorMsg = errorMsg; m_operationCompletedMsg = operationCompletedMsg; startSvnCommandProcess(); } void FileViewSvnPlugin::startSvnCommandProcess() { Q_ASSERT(m_process.state() == QProcess::NotRunning); m_pendingOperation = true; const QString program(QLatin1String("svn")); QStringList arguments; arguments << m_command << m_arguments; if (!m_contextDir.isEmpty()) { arguments << m_contextDir; m_contextDir.clear(); } else { const KFileItem item = m_contextItems.takeLast(); arguments << item.localPath(); // the remaining items of m_contextItems will be executed // after the process has finished (see slotOperationFinished()) } m_process.start(program, arguments); } QList FileViewSvnPlugin::directoryActions(const KFileItem& directory) const { m_contextDir = directory.localPath(); if (!m_contextDir.endsWith(QLatin1Char('/'))) { m_contextDir += QLatin1Char('/'); } m_contextItems.clear(); // Only enable the SVN actions if no SVN commands are // executed currently (see slotOperationCompleted() and // startSvnCommandProcess()). const bool enabled = !m_pendingOperation; m_updateAction->setEnabled(enabled); const ItemVersion version = itemVersion(directory); m_showLocalChangesAction->setEnabled(enabled && (version != NormalVersion)); m_commitAction->setEnabled(enabled && (version == LocallyModifiedVersion)); QList actions; actions.append(m_updateAction); actions.append(m_showLocalChangesAction); actions.append(m_commitAction); actions.append(m_showUpdatesAction); return actions; } #include "fileviewsvnplugin.moc"