Commit 589f74f3 authored by Evgeniy Ivanov's avatar Evgeniy Ivanov
Browse files

Squashed commit of the following:

    Basic Revision history implementation, including DVCScommit class and interface implementation for Git.
It works correctly with simple revision history (actually it works fine with all things could be done with DVCS in KDevelop). Simple history with merges is ok too.
    Some API documentation (essential).
    Branching tests for GitExecutor (but can be used in all executors).
    Few Easter Eggs for translators :)
    Fixes for Git to allow CommitManager to work correctly with "no commits" repos.
    Some minor fixes/changes.
parent 86477b86
......@@ -61,11 +61,11 @@ bool GitExecutor::isValidDirectory(const KUrl & dirPath)
job->exec();
if (job->status() == KDevelop::VcsJob::JobSucceeded)
{
kDebug(9500) << "Dir:" << dirPath << " is is inside work tree of git" ;
kDebug(9500) << "Dir:" << dirPath << " is inside work tree of git" ;
return true;
}
}
kDebug(9500) << "Dir:" << dirPath.path() << " is is not inside work tree of git" ;
kDebug(9500) << "Dir:" << dirPath.path() << " is not inside work tree of git" ;
return false;
}
......@@ -343,7 +343,6 @@ QList<VcsStatusInfo> GitExecutor::getModifiedFiles(const QString &directory)
QList<VcsStatusInfo> GitExecutor::getCachedFiles(const QString &directory)
{
// gitRevParse(dirPath.path(), QString("--is-inside-work-tree")
DVCSjob* job = gitRevParse(directory, QStringList(QString("--branches")));
job->exec();
QStringList shaArg;
......@@ -351,6 +350,8 @@ QList<VcsStatusInfo> GitExecutor::getCachedFiles(const QString &directory)
{
//there is no branches, which means there is no commit yet
//let's create an empty tree to use with git-diff-index
//TODO: in newer version of git (AFAIK 1.5.5) we can do:
//"git diff-index $(git rev-parse -q --verify HEAD  || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"
DVCSjob* job = new DVCSjob(vcsplugin);
if (prepareJob(job, directory) )
{
......@@ -393,7 +394,228 @@ QList<VcsStatusInfo> GitExecutor::getCachedFiles(const QString &directory)
return cachedFiles;
}
//Actually we can just copy the outpuc without parsing. So it's a kind of draft for future
/* Few words about how this hardcore works:
1. get all commits (with --paretns)
2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3)
3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS,
MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count)
another setType(INITIAL) is used for "bottom/root/first" commits of branches
4. find and set merges, HEADS. It's an ittaration through all commits.
- first we check if parent is from the same branch, if no then we go through all commits searching parent's index
and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met)
- then we check branchesShas[i][0] to mark heads
4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)!
It's a very dirty implementation.
FIXME:
1. HEAD which is head has extra line to connect it with further commit
2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be
extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3).
3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly
*/
QList<DVCScommit> GitExecutor::getAllCommits(const QString &repo)
{
static bool hasHash = false;
if (!hasHash)
{
initBranchHash(repo);
hasHash = true;
}
QStringList args;
args << "--all" << "--pretty" << "--parents";
DVCSjob* job = gitRevList(repo, args);
if (job)
job->exec();
QStringList commits = job->output().split('\n');
static QRegExp rx_com("commit \\w{40,40}");
QList<DVCScommit>commitList;
DVCScommit item;
//used to keep where we have empty/cross/branch entry
//true if it's an active branch (then cross or branch) and false if not
QVector<bool> additionalFlags(branchesShas.count());
foreach(int flag, additionalFlags)
flag = false;
//parse output
for(int i = 0; i < commits.count(); ++i)
{
if (commits[i].contains(rx_com))
{
kDebug(9500) << "commit found in " << commits[i];
item.setCommit(commits[i].section(' ', 1, 1).trimmed());
// kDebug(9500) << "commit is: " << commits[i].section(' ', 1);
QStringList parents;
QString parent = commits[i].section(' ', 2);
int section = 2;
while (!parent.isEmpty())
{
/* kDebug() << "Parent is: " << parent;*/
parents.append(parent.trimmed());
section++;
parent = commits[i].section(' ', section);
}
item.setParents(parents);
//Avoid Merge string
while (!commits[i].contains("Author: "))
++i;
item.setAuthor(commits[i].section("Author: ", 1).trimmed());
// kDebug(9500) << "author is: " << commits[i].section("Author: ", 1);
item.setDate(commits[++i].section("Date: ", 1).trimmed());
// kDebug(9500) << "date is: " << commits[i].section("Date: ", 1);
QString log;
i++; //next line!
while (i < commits.count() && !commits[i].contains(rx_com))
log += commits[i++];
--i; //while took commit line
item.setLog(log.trimmed());
// kDebug(9500) << "log is: " << log;
//mask is used in CommitViewDelegate to understand what we should draw for each branch
QList<int> mask;
//set mask (properties for each graph column in row)
for(int i = 0; i < branchesShas.count(); ++i)
{
kDebug()<<"commit: " << item.getCommit();
if (branchesShas[i].contains(item.getCommit()))
{
mask.append(item.getType()); //we set type in setParents
//check if parent from the same branch, if not then we have found a root of the branch
//and will use empty column for all futher (from top to bottom) revisions
//FIXME: we should set CROSS between parent and child (and do it when find merge point)
additionalFlags[i] = false;
foreach(const QString &sha, item.getParents())
{
if (branchesShas[i].contains(sha))
additionalFlags[i] = true;
}
if (additionalFlags[i] == false)
item.setType(DVCScommit::INITIAL); //hasn't parents from the same branch, used in drawing
}
else
{
if (additionalFlags[i] == false)
mask.append(DVCScommit::EMPTY);
else
mask.append(DVCScommit::CROSS);
}
kDebug() << "mask " << i << "is " << mask[i];
}
item.setProperties(mask);
commitList.append(item);
}
}
//find and set merges, HEADS, require refactoring!
for(QList<DVCScommit>::iterator iter = commitList.begin();
iter != commitList.end(); ++iter)
{
QStringList parents = iter->getParents();
//we need only only child branches
if (parents.count() != 1)
break;
QString parent = parents[0];
QString commit = iter->getCommit();
bool parent_checked = false;
int heads_checked = 0;
for(int i = 0; i < branchesShas.count(); ++i)
{
//check parent
if (branchesShas[i].contains(commit))
{
if (!branchesShas[i].contains(parent))
{
//parent and child are not in same branch
//since it is list, than parent has i+1 index
//set CROSS and HCROSS
for(QList<DVCScommit>::iterator f_iter = iter;
f_iter != commitList.end(); ++f_iter)
{
if (parent == f_iter->getCommit())
{
for(int j = 0; j < i; ++j)
{
if(branchesShas[j].contains(parent))
f_iter->setPropetry(j, DVCScommit::MERGE);
else
f_iter->setPropetry(j, DVCScommit::HCROSS);
}
f_iter->setType(DVCScommit::MERGE);
f_iter->setPropetry(i, DVCScommit::MERGE_RIGHT);
kDebug() << parent << " is parent of " << commit;
kDebug() << f_iter->getCommit() << " is merge";
parent_checked = true;
break;
}
else
f_iter->setPropetry(i, DVCScommit::CROSS);
}
}
}
//mark HEADs
if (commit == branchesShas[i][0])
{
iter->setType(DVCScommit::HEAD);
iter->setPropetry(i, DVCScommit::HEAD);
heads_checked++;
kDebug() << "HEAD found";
}
//some optimization
if (heads_checked == branchesShas.count() && parent_checked)
break;
}
}
return commitList;
}
void GitExecutor::initBranchHash(const QString &repo)
{
QStringList branches = GitExecutor::branches(repo);
kDebug() << "BRANCHES: " << branches;
//Now root branch is the current branch. In future it should be the longest branch
//other commitLists are got with git-rev-lits branch ^br1 ^ br2
QString root = GitExecutor::curBranch(repo);
DVCSjob* job = gitRevList(repo, QStringList(root));
if (job)
job->exec();
QStringList commits = job->output().split('\n');
// kDebug() << "\n\n\n commits" << commits << "\n\n\n";
branchesShas.append(commits);
foreach(const QString &branch, branches)
{
if (branch == root)
continue;
QStringList args(branch);
foreach(const QString &branch_arg, branches)
{
if (branch_arg != branch)
//man gitRevList for '^'
args<<'^' + branch_arg;
}
DVCSjob* job = gitRevList(repo, args);
if (job)
job->exec();
QStringList commits = job->output().split('\n');
// kDebug() << "\n\n\n commits" << commits << "\n\n\n";
branchesShas.append(commits);
}
}
//Actually we can just copy the output without parsing. So it's a kind of draft for future
void GitExecutor::parseOutput(const QString& jobOutput, QList<DVCScommit>& commits) const
{
// static QRegExp rx_sep( "[-=]+" );
......@@ -404,6 +626,7 @@ void GitExecutor::parseOutput(const QString& jobOutput, QList<DVCScommit>& commi
QStringList lines = jobOutput.split("\n");
DVCScommit item;
QString commitLog;
for (int i=0; i<lines.count(); ++i) {
QString s = lines[i];
......@@ -411,16 +634,18 @@ void GitExecutor::parseOutput(const QString& jobOutput, QList<DVCScommit>& commi
if (rx_com.exactMatch(s)) {
kDebug(9500) << "MATCH COMMIT";
item.commit = s;
item.setCommit(s);
s = lines[++i];
item.author = s;
item.setAuthor(s);
s = lines[++i];
item.date = s;
item.setDate(s);
item.setLog(commitLog);
commits.append(item);
}
else
{
item.log += s+'\n';
//FIXME: add this in a loop to the if, like in getAllCommits()
commitLog += s +'\n';
}
}
}
......@@ -461,6 +686,19 @@ DVCSjob* GitExecutor::gitRevParse(const QString &repository, const QStringList &
return NULL;
}
DVCSjob* GitExecutor::gitRevList(const QString &repository, const QStringList &args)
{
DVCSjob* job = new DVCSjob(vcsplugin);
if (prepareJob(job, repository) ) {
*job << "git-rev-list";
foreach(const QString &arg, args)
*job << arg;
return job;
}
if (job) delete job;
return NULL;
}
KDevelop::VcsStatusInfo::State GitExecutor::charToState(const char ch)
{
switch (ch)
......
......@@ -95,7 +95,7 @@ private:
//it can be public, but it isn't required now
DVCSjob* lsFiles(const QString &repository, const QStringList &args);
DVCSjob* gitRevParse(const QString &repository, const QStringList &args);
DVCSjob* gitRevList(const QString &repository, const QStringList &args);
public:
//parsers for branch:
QString curBranch(const QString &repository);
......@@ -106,13 +106,20 @@ public:
QList<KDevelop::VcsStatusInfo> getCachedFiles(const QString &directory);
QStringList getOtherFiles(const QString &directory);
//graph helpers
QList<DVCScommit> getAllCommits(const QString &repo);
//used in log
void parseOutput(const QString& jobOutput, QList<DVCScommit>& commits) const;
private:
//commit dialog "main" helper
QStringList getLsFiles(const QString &directory, const QStringList &args);
void initBranchHash(const QString &repo);
KDevelop::VcsStatusInfo::State charToState(const char ch);
QList<QStringList> branchesShas;
KDevelop::IPlugin* vcsplugin;
};
......
......@@ -46,6 +46,9 @@ GitPlugin::GitPlugin( QObject *parent, const QVariantList & )
core()->uiController()->addToolView(i18n("Git"), DistributedVersionControlPlugin::d->m_factory);
QString EasterEgg = i18n("Thanks for the translation! Have a nice day, mr. translator!");
Q_UNUSED(EasterEgg)
setXMLFile("kdevgit.rc");
setupActions();
......
......@@ -84,6 +84,7 @@ void GitInitTest::repoInit()
// try to start the job
QVERIFY( j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
//check if the CVSROOT directory in the new local repository exists now
QVERIFY( QFileInfo(QString(GIT_REPO)).exists() );
......@@ -118,23 +119,20 @@ void GitInitTest::addFiles()
//add always should use relative path to the any directory of the repository, let's check:
DVCSjob* j = m_proxy->add(QString(GITTEST_BASEDIR), KUrl::List(QStringList(QString(GITTEST_DIR1))));
QVERIFY( j );
if (j)
QVERIFY(j->exec() );
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
// /tmp/kdevGit_testdir/ and testfile
j = m_proxy->add(QString(GITTEST_BASEDIR), KUrl::List(QStringList(QString(GIT_TESTFILE_NAME))));
QVERIFY( j );
if (j)
QVERIFY(j->exec() );
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
//repository path without trailing slash
j = m_proxy->add(QString(GITTEST_BASEDIR_NO_TR_SLASH), KUrl::List(QStringList(QString(GIT_TESTFILE_NAME))));
QVERIFY( j );
if (j)
QVERIFY(j->exec() );
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
f.setFileName(GIT_SRC_DIR""GIT_TESTFILE_NAME3);
if(f.open(QIODevice::WriteOnly)) {
......@@ -148,14 +146,14 @@ void GitInitTest::addFiles()
// /tmp/repo and /tmp/repo/src/bar
j = m_proxy->add(QString(GITTEST_BASEDIR_NO_TR_SLASH), KUrl::List(QStringList(QString(GIT_SRC_DIR""GIT_TESTFILE_NAME3))));
QVERIFY( j );
if (j)
QVERIFY(j->exec() );
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
//let's use absolute path, because it's used in ContextMenus
j = m_proxy->add(QString(GITTEST_BASEDIR), KUrl::List(QStringList(QString(GITTEST_BASEDIR""GIT_TESTFILE_NAME2))));
if (j)
QVERIFY(j->exec() );
QVERIFY(j);
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
//Now let's create several files and try "git add file1 file2 file3"
f.setFileName(GITTEST_BASEDIR"file1");
......@@ -176,8 +174,9 @@ void GitInitTest::addFiles()
multipleFiles<<"file1";
multipleFiles<<"file2";
j = m_proxy->add(QString(GITTEST_BASEDIR), KUrl::List(multipleFiles));
if (j)
QVERIFY(j->exec() );
QVERIFY(j);
QVERIFY(j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
}
void GitInitTest::commitFiles()
......@@ -185,6 +184,7 @@ void GitInitTest::commitFiles()
kDebug() << "\nListing variables with KProcess\n";
DVCSjob* j_var = m_proxy->var(QString(GITTEST_BASEDIR));
QVERIFY(j_var->exec() );
QVERIFY(j_var->status() == KDevelop::VcsJob::JobSucceeded);
kDebug() << "Committing...";
//we start it after addFiles, so we just have to commit
......@@ -194,6 +194,7 @@ void GitInitTest::commitFiles()
// try to start the job
QVERIFY( j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
//since we commited the file to the "pure" repository, .git/refs/heads/master should exist
//TODO: maybe other method should be used
......@@ -207,6 +208,7 @@ void GitInitTest::commitFiles()
*jobLs<<"git-ls-tree"<<"--name-only"<<"-r"<<"HEAD";
if (jobLs) {
QVERIFY(jobLs->exec() );
QVERIFY(jobLs->status() == KDevelop::VcsJob::JobSucceeded);
QStringList files = jobLs->output().split("\n");
QVERIFY(files.contains(QString(GIT_TESTFILE_NAME)));
......@@ -240,12 +242,14 @@ void GitInitTest::commitFiles()
// try to start the job
QVERIFY( j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
j = m_proxy->commit(QString(GITTEST_BASEDIR), QString("KDevelop's Test commit2"));
QVERIFY( j );
// try to start the job
QVERIFY( j->exec() );
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
QString secondCommit;
if(headRef.open(QIODevice::ReadOnly)) {
......@@ -259,19 +263,19 @@ void GitInitTest::commitFiles()
QVERIFY(firstCommit != secondCommit);
}
void GitInitTest::cloneRepository()
{
kDebug() << "Do not clone people, clone Git repos!";
// make job that clones the local repository, created in the previous test
DVCSjob* j = m_proxy->clone(KUrl(GITTEST_BASEDIR), KUrl(GITTEST_BASEDIR2));
QVERIFY( j );
// try to start the job
QVERIFY( j->exec() );
//check if the .git directory in the new local repository exists now
QVERIFY( QFileInfo(QString(GITTEST_BASEDIR2"kdevGit_testdir/.git/")).exists() );
}
// void GitInitTest::cloneRepository()
// {
// kDebug() << "Do not clone people, clone Git repos!";
// // make job that clones the local repository, created in the previous test
// DVCSjob* j = m_proxy->clone(KUrl(GITTEST_BASEDIR), KUrl(GITTEST_BASEDIR2));
// QVERIFY( j );
//
// // try to start the job
// QVERIFY( j->exec() );
//
// //check if the .git directory in the new local repository exists now
// QVERIFY( QFileInfo(QString(GITTEST_BASEDIR2"kdevGit_testdir/.git/")).exists() );
// }
void GitInitTest::testInit()
{
......@@ -288,7 +292,53 @@ void GitInitTest::testCommit()
commitFiles();
}
QTEST_KDEMAIN(GitInitTest, GUI)
void GitInitTest::testBranching()
{
DVCSjob* j = m_proxy->branch(GITTEST_BASEDIR);
QVERIFY(j);
QVERIFY(j->exec());
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
QString curBranch = m_proxy->curBranch(GITTEST_BASEDIR);
QCOMPARE(curBranch, QString("master"));
QString newBranch("new");
j = m_proxy->branch(GITTEST_BASEDIR, QString("master"), newBranch);
QVERIFY(j);
QVERIFY(j->exec());
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
QVERIFY(m_proxy->branches(GITTEST_BASEDIR).contains(newBranch));
j = m_proxy->checkout(GITTEST_BASEDIR, newBranch);
QVERIFY(j);
QVERIFY(j->exec());
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
QCOMPARE(m_proxy->curBranch(GITTEST_BASEDIR), newBranch);
j = m_proxy->branch(GITTEST_BASEDIR, QString("master"), QString(), QStringList("-D"));
QVERIFY(j);
QVERIFY(j->exec());
QVERIFY(j->status() == KDevelop::VcsJob::JobSucceeded);
QVERIFY(!m_proxy->branches(GITTEST_BASEDIR).contains(QString("master")));
}
void GitInitTest::revHistory()
{
QList<DVCScommit> commits = m_proxy->getAllCommits(GITTEST_BASEDIR);
QVERIFY(!commits.isEmpty());
QStringList logMessages;
for(int i = 0; i < commits.count(); ++i)
logMessages << commits[i].getLog();
QCOMPARE(commits.count(), 2);
QCOMPARE(logMessages[0], QString("KDevelop's Test commit2") ); //0 is later than 1!
QCOMPARE(logMessages[1], QString("Test commit"));
QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1!
QVERIFY(!commits[0].getParents().isEmpty()); //initial commit is on the top
QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$")));
QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$")));
QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$")));
}
QTEST_KDEMAIN(GitInitTest, GUI)
// #include "gittest.moc"
......@@ -37,7 +37,7 @@ class GitInitTest: public QObject
void repoInit();
void addFiles();
void commitFiles();
void cloneRepository();
// void cloneRepository();
// void importTestData();
// void checkoutTestData();
......@@ -46,6 +46,8 @@ class GitInitTest: public QObject
void testInit();
void testAdd();
void testCommit();
void testBranching();
void revHistory();
void cleanupTestCase();
private:
......
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