Commit 36069625 authored by Jonathan Verner's avatar Jonathan Verner Committed by Milian Wolff
Browse files

Add a function to the git plugin to get info about stashes.

Plus make gitStash return a VcsJob* (instead of a DVcsJob*), so that it
can be used in places which only include the vcsjob header.
parent 7a8e9b3e
......@@ -215,7 +215,11 @@ bool emptyOutput(DVcsJob* job)
bool GitPlugin::hasStashes(const QDir& repository)
{
return !emptyOutput(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent));
if (auto *job = qobject_cast<DVcsJob*>(gitStash(repository, QStringList(QStringLiteral("list")), KDevelop::OutputJob::Silent))) {
return !emptyOutput(job);
}
Q_ASSERT(false); // gitStash should always return a DVcsJob !
return false;
}
bool GitPlugin::hasModifications(const QDir& d)
......@@ -767,13 +771,73 @@ DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args,
return job;
}
DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity)
VcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity)
{
auto* job = new GitJob(repository, this, verbosity);
*job << "git" << "stash" << args;
return job;
}
VcsJob* GitPlugin::stashList(const QDir& repository,
KDevelop::OutputJob::OutputJobVerbosity verbosity)
{
/* The format returns 4 fields separated by a 0-byte character (%x00):
*
* %gd ... shortened reflog selector
* %p ... abbreviated parent hashes (separated by a space, the first is the commit
* on which the stash was made)
* %s ... subject (the stash message)
* %ct ... committer timestamp
*
* see man git-log, PRETTY FORMATS section and man git-stash for details.
*/
auto* job=qobject_cast<DVcsJob*>(gitStash(repository, QStringList({
QStringLiteral("list"),
QStringLiteral("--format=format:%gd%x00%P%x00%s%x00%ct"),
}), verbosity));
connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStashList);
return job;
}
void GitPlugin::parseGitStashList(KDevelop::VcsJob* _job)
{
auto* job = qobject_cast<DVcsJob*>(_job);
const QList<QByteArray> output = job->rawOutput().split('\n');
QList<StashItem> results;
for (const QByteArray& line : output) {
if (line.isEmpty()) continue;
const QList<QByteArray> fields = line.split('\x00');
/* Extract the fields */
Q_ASSERT(fields.length() >= 4);
const auto message = QString::fromUtf8(fields[2]);
const auto parentHash = QString::fromUtf8(fields[1].split(' ')[0]);
const auto creationTime = QDateTime::fromSecsSinceEpoch(fields[3].toInt());
const auto shortRef = QString::fromUtf8(fields[0]);
const auto stackDepth = fields[0].mid(7, fields[0].indexOf('}')-7).toInt();
QStringRef branch {};
QStringRef parentCommitDesc {};
if (message.startsWith(QStringLiteral("WIP on "))) {
const int colPos = message.indexOf(QLatin1Char(':'), 7);
branch = message.midRef(7, colPos-7);
parentCommitDesc = message.midRef(colPos+2);
}
results << StashItem {
stackDepth,
shortRef,
parentHash,
parentCommitDesc.toString(),
branch.toString(),
message,
creationTime,
};
}
job->setResults(QVariant::fromValue(results));
}
VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName)
{
auto* job = new GitJob(urlDir(repository), this);
......@@ -792,7 +856,7 @@ VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch)
KGuiItem(i18nc("@action:button", "Stash"), QStringLiteral("vcs-stash")),
KGuiItem(i18nc("@action:button", "Keep"), QStringLiteral("dialog-cancel")));
if (answer == KMessageBox::Yes) {
QScopedPointer<DVcsJob> stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose));
QScopedPointer<VcsJob> stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose));
stash->exec();
} else if (answer == KMessageBox::Cancel) {
return nullptr;
......
......@@ -15,6 +15,8 @@
#include <outputview/outputjob.h>
#include <vcs/vcsjob.h>
#include <QDateTime>
class KDirWatch;
class QDir;
......@@ -215,7 +217,43 @@ public:
void additionalMenuEntries(QMenu* menu, const QList<QUrl>& urls) override;
KDevelop::DVcsJob* gitStash(const QDir& repository, const QStringList& args, KDevelop::OutputJob::OutputJobVerbosity verbosity);
// Stash Management
/**
* Structure to hold information about an item on the stash stack
*/
struct StashItem {
int stackDepth = -1; /* Position on the stack */
QString shortRef; /* The reflog selector (e.g. stash@{0}) */
QString parentSHA; /* The short SHA of the commit on which the stash was made */
QString parentDescription; /* A short description of the commit on which the stash was made */
QString branch; /* The branch on which the stash was made */
QString message; /* The message with which the stash was made */
QDateTime creationTime; /* The date-time the stash item was committed */
};
/**
* Returns a job to run `git stash` in the repository @p repository with
* additional arguments @p args.
*
* The @p verbosity parameter will determine whether the job output will
* be shown in the VCS Output ToolView.
*
* For example, a job to silently apply the top-most stashed item to the current
* tree would be created as follows:
*
* gitStash(repoDir, {QStringLiteral("apply")}, KDevelop::OutputJob::Silent)
*
*/
KDevelop::VcsJob* gitStash(const QDir& repository, const QStringList& args, KDevelop::OutputJob::OutputJobVerbosity verbosity);
/**
* The result (job->fetchResults()) will be a @ref QList of @ref StashItem s
*
* @p repository is the repository to work on
* @p verbosity determines whether the job output will be shown in the VCS Output ToolView
*/
KDevelop::VcsJob* stashList(const QDir& repository, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Silent);
bool hasStashes(const QDir& repository);
bool hasModifications(const QDir& repository);
......@@ -265,6 +303,7 @@ private Q_SLOTS:
void parseGitVersionOutput(KDevelop::DVcsJob* job);
void parseGitBranchOutput(KDevelop::DVcsJob* job);
void parseGitCurrentBranch(KDevelop::DVcsJob* job);
void parseGitStashList(KDevelop::VcsJob* job);
void ctxRebase();
void ctxPushStash();
......@@ -320,6 +359,8 @@ private:
CommitToolViewFactory* m_commitToolViewFactory;
};
Q_DECLARE_METATYPE(GitPlugin::StashItem)
QVariant runSynchronously(KDevelop::VcsJob* job);
#endif
......@@ -25,7 +25,7 @@ StashPatchSource::StashPatchSource(const QString& stashName, GitPlugin* plugin,
tempFile.open();
m_patchFile = QUrl::fromLocalFile(tempFile.fileName());
KDevelop::DVcsJob * job = m_plugin->gitStash(m_baseDir, QStringList{QStringLiteral("show"), QStringLiteral("-u"), m_stashName}, KDevelop::OutputJob::Silent);
auto job = qobject_cast<KDevelop::DVcsJob*>(m_plugin->gitStash(m_baseDir, QStringList{QStringLiteral("show"), QStringLiteral("-u"), m_stashName}, KDevelop::OutputJob::Silent));
connect(job, &DVcsJob::resultsReady, this, &StashPatchSource::updatePatchFile);
KDevelop::ICore::self()->runController()->registerJob(job);
......
......@@ -566,6 +566,95 @@ void GitInitTest::testDiff()
QVERIFY(QDir().exists(path+"/.git"));
}
void GitInitTest::testStash()
{
repoInit();
addFiles();
commitFiles();
const QVector<KDevelop::DVcsEvent> commits = m_plugin->allCommits(gitTest_BaseDir());
QVERIFY(!commits.isEmpty());
QStringList logMessages;
for (auto& commit : commits)
logMessages << commit.log();
auto repo_url = QUrl::fromLocalFile(gitTest_BaseDir());
// No stash
QVERIFY(!m_plugin->hasStashes(gitTest_BaseDir()));
VcsJob* j = m_plugin->stashList(gitTest_BaseDir());
VERIFYJOB(j);
QVERIFY(j->fetchResults().value<QList<GitPlugin::StashItem>>().isEmpty());
// Do some changes
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("NEW CONTENT TO STASH")));
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName2(), QStringLiteral("No, REALLY NEW FOOO()!")));
// Verify stashing works
j = m_plugin->gitStash(gitTest_BaseDir(), {}, OutputJob::Silent);
VERIFYJOB(j);
QVERIFY(m_plugin->hasStashes(gitTest_BaseDir()));
// Verify stash list gives us the single stash
j = m_plugin->stashList(gitTest_BaseDir());
VERIFYJOB(j);
auto items = j->fetchResults().value<QList<GitPlugin::StashItem>>();
QVERIFY(items.length() == 1);
QVERIFY(items[0].stackDepth == 0);
QVERIFY(items[0].branch == runSynchronously(m_plugin->currentBranch(repo_url)).toString());
QVERIFY(commits[0].commit() == items[0].parentSHA);
// Verify stash on new branch gives us correct parent branch
//
// 1. switch to a new branch
auto rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head);
j = m_plugin->branch(repo_url, rev, QStringLiteral("my-new-branch"));
VERIFYJOB(j);
j = m_plugin->switchBranch(repo_url, QStringLiteral("my-new-branch"));
VERIFYJOB(j);
// 2. Do more changes & stash them
QString testFileContent = QStringLiteral("2. NEW CONTENT TO STASH");
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), testFileContent));
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName2(), QStringLiteral("2. No, REALLY NEW FOOO()!")));
j = m_plugin->gitStash(gitTest_BaseDir(), {}, OutputJob::Silent);
VERIFYJOB(j);
QFile testFile(gitTest_BaseDir() + gitTest_FileName());
testFile.open(QIODevice::ReadOnly);
auto testFileContentAfterStash = QString::fromUtf8(testFile.readAll());
testFile.close();
QVERIFY(testFileContent != testFileContentAfterStash);
// 3. Verify stash list gives us a new stash
j = m_plugin->stashList(gitTest_BaseDir());
VERIFYJOB(j);
items = j->fetchResults().value<QList<GitPlugin::StashItem>>();
QVERIFY(items.length() == 2);
// 4. Find the newest stash
bool found = false;
for(const auto& item : items) {
if (item.stackDepth != 0) continue;
// 5. Verify that the new branch is parent of the new stash
QVERIFY(item.branch == QStringLiteral("my-new-branch"));
found = true;
}
QVERIFY(found);
// Verify that stash pop recreates the contents and drops the stash item
j = m_plugin->gitStash(gitTest_BaseDir(), {QStringLiteral("pop")}, OutputJob::Silent);
VERIFYJOB(j);
testFile.open(QIODevice::ReadOnly);
auto testFileContentAfterPop = QString::fromUtf8(testFile.readAll());
QVERIFY(testFileContentAfterPop == testFileContent);
j = m_plugin->stashList(gitTest_BaseDir());
VERIFYJOB(j);
items = j->fetchResults().value<QList<GitPlugin::StashItem>>();
QVERIFY(items.length() == 1);
}
QTEST_MAIN(GitInitTest)
// #include "gittest.moc"
......@@ -49,6 +49,7 @@ private Q_SLOTS:
void testRemoveUnindexedFile();
void testRemoveFolderContainingUnversionedFiles();
void testDiff();
void testStash();
private:
GitPlugin* m_plugin;
......
Supports Markdown
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