Commit 1e237f9c authored by Sven Brauch's avatar Sven Brauch
Browse files

Ask git if it is safe to reload a document

When a file is changed on disk, we can look in the git object repository
and see whether the contents currently in the editor are stored somewhere
there. In that case, there is no risk of data loss when we just silently
reload the document; the user can always retrieve the old version from
git.

REVIEW:113584
parent 798ef4ee
......@@ -32,6 +32,7 @@
#include <QDateTime>
#include <QFileSystemWatcher>
#include <QTimer>
#include <QTextCodec>
#include <interfaces/icore.h>
#include <interfaces/iprojectcontroller.h>
......@@ -54,6 +55,7 @@
#include <KStandardDirs>
#include <KTextEdit>
#include <KDirWatch>
#include <KTextEditor/Document>
#include "gitjob.h"
#include "gitmessagehighlighter.h"
......@@ -1399,3 +1401,77 @@ void GitPlugin::delayedBranchChanged()
{
emit repositoryBranchChanged(m_branchesChange.takeFirst());
}
CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document)
{
CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path());
job->start();
return job;
}
GitPluginCheckInRepositoryJob::GitPluginCheckInRepositoryJob(KTextEditor::Document* document,
const QString& rootDirectory)
: CheckInRepositoryJob(document)
, m_hashjob(0)
, m_findjob(0)
, m_rootDirectory(rootDirectory)
{
}
void GitPluginCheckInRepositoryJob::start()
{
const QTextCodec* codec = QTextCodec::codecForName(document()->encoding().toAscii());
const QDir workingDirectory(m_rootDirectory);
if ( ! workingDirectory.exists() ) {
emit finished(false);
return;
}
m_findjob = new QProcess(this);
m_findjob->setWorkingDirectory(m_rootDirectory);
m_hashjob = new QProcess(this);
m_hashjob->setWorkingDirectory(m_rootDirectory);
m_hashjob->setStandardOutputProcess(m_findjob);
connect(m_findjob, SIGNAL(finished(int)), SLOT(repositoryQueryFinished(int)));
connect(m_hashjob, SIGNAL(error(QProcess::ProcessError)), SLOT(processFailed(QProcess::ProcessError)));
connect(m_findjob, SIGNAL(error(QProcess::ProcessError)), SLOT(processFailed(QProcess::ProcessError)));
m_hashjob->start("git", QStringList() << "hash-object" << "--stdin");
m_findjob->start("git", QStringList() << "cat-file" << "--batch-check");
for ( int i = 0; i < document()->lines(); i++ ) {
m_hashjob->write(codec->fromUnicode(document()->line(i)));
if ( i != document()->lines() - 1 ) {
m_hashjob->write("\n");
}
}
m_hashjob->closeWriteChannel();
}
GitPluginCheckInRepositoryJob::~GitPluginCheckInRepositoryJob()
{
if ( m_findjob && m_findjob->state() == QProcess::Running ) {
m_findjob->kill();
}
if ( m_hashjob && m_hashjob->state() == QProcess::Running ) {
m_hashjob->kill();
}
}
void GitPluginCheckInRepositoryJob::processFailed(QProcess::ProcessError err)
{
kDebug() << "calling git failed with error:" << err;
emit finished(false);
}
void GitPluginCheckInRepositoryJob::repositoryQueryFinished(int)
{
const QByteArray output = m_findjob->readAllStandardOutput();
bool requestSucceeded = output.contains(" blob ");
emit finished(requestSucceeded);
}
......@@ -23,8 +23,10 @@
#define KDEVPLATFORM_PLUGIN_GIT_PLUGIN_H
#include <vcs/interfaces/idistributedversioncontrol.h>
#include <vcs/interfaces/icontentawareversioncontrol.h>
#include <vcs/dvcs/dvcsplugin.h>
#include <QObject>
#include <QProcess>
#include <vcs/vcsstatusinfo.h>
#include <outputview/outputjob.h>
#include <vcs/vcsjob.h>
......@@ -38,6 +40,24 @@ namespace KDevelop
class VcsRevision;
}
class GitPluginCheckInRepositoryJob : public KDevelop::CheckInRepositoryJob
{
Q_OBJECT
public:
GitPluginCheckInRepositoryJob(KTextEditor::Document* document, const QString& rootDirectory);
virtual ~GitPluginCheckInRepositoryJob();
virtual void start();
private slots:
void repositoryQueryFinished(int);
void processFailed(QProcess::ProcessError);
private:
QProcess* m_hashjob;
QProcess* m_findjob;
QString m_rootDirectory;
};
class StandardJob : public KDevelop::VcsJob
{
Q_OBJECT
......@@ -64,10 +84,10 @@ class StandardJob : public KDevelop::VcsJob
* It implements the DVCS dependent things not implemented in KDevelop::DistributedVersionControlPlugin
* @author Evgeniy Ivanov <powerfox@kde.ru>
*/
class GitPlugin: public KDevelop::DistributedVersionControlPlugin
class GitPlugin: public KDevelop::DistributedVersionControlPlugin, public KDevelop::IContentAwareVersionControl
{
Q_OBJECT
Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::IDistributedVersionControl)
Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::IDistributedVersionControl KDevelop::IContentAwareVersionControl)
friend class GitInitTest;
public:
GitPlugin(QObject *parent, const QVariantList & args = QVariantList() );
......@@ -139,6 +159,8 @@ public:
virtual bool hasError() const;
virtual QString errorDescription() const;
virtual void registerRepositoryForCurrentBranchChanges(const KUrl& repository);
KDevelop::CheckInRepositoryJob* isInRepository(KTextEditor::Document* document);
protected:
KUrl repositoryRoot(const KUrl& path);
......
......@@ -20,6 +20,7 @@
#include <QFile>
#include <QPointer>
#include <QTextCodec>
#include <QMenu>
#include <QAction>
#include <QVBoxLayout>
......@@ -49,6 +50,8 @@
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iproject.h>
#include <vcs/interfaces/icontentawareversioncontrol.h>
#include <language/interfaces/editorcontext.h>
#include <project/projectutils.h>
......@@ -162,9 +165,63 @@ struct TextDocumentPrivate {
break;
}
// In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e.
// not retrieveable from the VCS. If that is not the case, then the document can safely be
// reloaded without displaying a dialog asking the user.
if ( dirty ) {
queryCanRecreateFromVcs(document);
}
setStatus(document, dirty);
}
// Determines whether the current contents of this document in the editor
// could be retrieved from the VCS if they were dismissed.
virtual void queryCanRecreateFromVcs(KTextEditor::Document* document) const {
IProject* project = Core::self()->projectController()->findProjectForUrl( document->url() );
if (!project) {
return;
}
IContentAwareVersionControl* iface;
iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin());
if (!iface) {
return;
}
if ( !qobject_cast<KTextEditor::ModificationInterface*>( document ) ) {
return;
}
CheckInRepositoryJob* req = iface->isInRepository( document );
if ( !req ) {
return;
}
QObject::connect(req, SIGNAL(finished(bool)),
m_textDocument, SLOT(repositoryCheckFinished(bool)));
// Abort the request when the user edits the document
QObject::connect(m_textDocument->textDocument(), SIGNAL(textChanged(KTextEditor::Document*)),
req, SLOT(abort()));
}
void repositoryCheckFinished(bool canRecreate) {
if ( state != IDocument::Dirty && state != IDocument::DirtyAndModified ) {
// document is not dirty for whatever reason, nothing to do.
return;
}
if ( ! canRecreate ) {
return;
}
KTextEditor::ModificationInterface* modIface = qobject_cast<KTextEditor::ModificationInterface*>( document );
Q_ASSERT(modIface);
// Ok, all safe, we can clean up the document. Close it if the file is gone,
// and reload if it's still there.
setStatus(document, false);
modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified);
if ( QFile::exists(document->url().path()) ) {
m_textDocument->reload();
} else {
m_textDocument->close(KDevelop::IDocument::Discard);
}
}
void setStatus(KTextEditor::Document* document, bool dirty)
{
QIcon statusIcon;
......
......@@ -84,6 +84,7 @@ private:
Q_PRIVATE_SLOT(d, void slotDocumentLoaded())
Q_PRIVATE_SLOT(d, void documentSaved(KTextEditor::Document*,bool))
Q_PRIVATE_SLOT(d, void saveSessionConfig());
Q_PRIVATE_SLOT(d, void repositoryCheckFinished(bool));
struct TextDocumentPrivate * const d;
friend struct TextDocumentPrivate;
......
......@@ -42,6 +42,7 @@ set(kdevplatformvcs_LIB_SRCS
dvcs/ui/revhistory/commitView.cpp
dvcs/ui/revhistory/commitlogmodel.cpp
interfaces/ibasicversioncontrol.cpp
interfaces/icontentawareversioncontrol.cpp
)
kde4_add_ui_files(kdevplatformvcs_LIB_SRCS ${kdevplatformvcs_UIS})
......
/* This file is part of KDevelop
*
* Copyright 2013 Sven Brauch <svenbrauch@gmail.com>
*
* 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 "icontentawareversioncontrol.h"
#include <QDebug>
namespace KDevelop {
struct CheckInRepositoryJobPrivate
{
CheckInRepositoryJobPrivate(KTextEditor::Document* document)
: document(document) { };
friend class CheckInRepositoryJob;
KTextEditor::Document* document;
};
CheckInRepositoryJob::CheckInRepositoryJob(KTextEditor::Document* document)
: KJob()
, d(new CheckInRepositoryJobPrivate(document))
{
connect(this, SIGNAL(finished(bool)), SLOT(deleteLater()));
setCapabilities(Killable);
};
KTextEditor::Document* CheckInRepositoryJob::document() const
{
return d->document;
}
CheckInRepositoryJob::~CheckInRepositoryJob()
{
delete d;
}
void CheckInRepositoryJob::abort()
{
kill();
}
} // namespace KDevelop
#include "icontentawareversioncontrol.moc"
/* This file is part of KDevelop
*
* Copyright 2013 Sven Brauch <svenbrauch@gmail.com>
*
* 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 KDEVPLATFORM_ICONTENTAWAREVERSIONCONTROL_H
#define KDEVPLATFORM_ICONTENTAWAREVERSIONCONTROL_H
#include <KUrl>
#include <KJob>
#include <QSharedPointer>
#include "../vcsexport.h"
namespace KTextEditor {
class Document;
}
namespace KDevelop {
class KDEVPLATFORMVCS_EXPORT CheckInRepositoryJob : public KJob
{
Q_OBJECT
public:
CheckInRepositoryJob(KTextEditor::Document* document);
virtual ~CheckInRepositoryJob();
KTextEditor::Document* document() const;
public slots:
/// Abort this request.
void abort();
signals:
void finished(bool canRecreate);
protected:
struct CheckInRepositoryJobPrivate* d;
};
/**
* This interface is used by version control systems which can tell whether a given
* blob of data is stored in the repository or not, such as git.
* This information is used to reload files automatically if that involves no data loss.
*/
class IContentAwareVersionControl
{
public:
virtual ~IContentAwareVersionControl() {};
/**
* @brief Determines whether the given data is stored in the VCS' repository.
*
* @param document Document to search for in the repository
* @returns CheckInRepositoryJob request object to track get notified when this finishes.
* The request object deletes itself after finished() was emitted.
*/
virtual CheckInRepositoryJob* isInRepository(KTextEditor::Document* document) = 0;
};
}
Q_DECLARE_INTERFACE( KDevelop::IContentAwareVersionControl, "org.kdevelop.IContentAwareVersionControl" )
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment