706 lines
19 KiB
C++
706 lines
19 KiB
C++
#ifdef ENABLE_VCS
|
|
#include "gitmanager.h"
|
|
#endif
|
|
#include "../utils.h"
|
|
#include "../settings.h"
|
|
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
|
|
GitManager::GitManager(QObject *parent) : QObject(parent)
|
|
{
|
|
}
|
|
|
|
void GitManager::createRepository(const QString &folder)
|
|
{
|
|
QString currentBranch;
|
|
if (hasRepository(folder,currentBranch))
|
|
throw GitError(tr("Folder \"%1\" already has a repository!"));
|
|
QStringList args;
|
|
args.append("init");
|
|
runGit(folder,args);
|
|
|
|
QStringList contents;
|
|
contents.append(".git");
|
|
contents.append("*.o");
|
|
contents.append("*.exe");
|
|
contents.append("*.layout");
|
|
#ifdef Q_OS_LINUX
|
|
contents.append("*.");
|
|
#endif
|
|
|
|
QDir dir(folder);
|
|
stringsToFile(contents,dir.filePath(".gitignore"));
|
|
QString output;
|
|
add(folder,".gitignore",output);
|
|
}
|
|
|
|
bool GitManager::hasRepository(const QString &folder, QString& currentBranch)
|
|
{
|
|
if (folder.isEmpty())
|
|
return false;
|
|
QStringList args;
|
|
args.append("status");
|
|
args.append("-b");
|
|
args.append("-u");
|
|
args.append("no");
|
|
args.append("--ignored=no");
|
|
QString output = runGit(folder,args);
|
|
bool result = output.startsWith("On branch");
|
|
if (result) {
|
|
int pos = QString("On branch").length();
|
|
while (pos<output.length() && output[pos].isSpace())
|
|
pos++;
|
|
int endPos = pos;
|
|
while (endPos<output.length() && !output[endPos].isSpace())
|
|
endPos++;
|
|
currentBranch = output.mid(pos,endPos-pos);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString GitManager::rootFolder(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("rev-parse");
|
|
args.append("--show-toplevel");
|
|
return runGit(folder,args).trimmed();
|
|
}
|
|
|
|
bool GitManager::isFileInRepository(const QFileInfo& fileInfo)
|
|
{
|
|
QStringList args;
|
|
args.append("ls-files");
|
|
args.append(fileInfo.fileName());
|
|
QString output = runGit(fileInfo.absolutePath(),args);
|
|
return output.trimmed() == fileInfo.fileName();
|
|
}
|
|
|
|
bool GitManager::isFileStaged(const QFileInfo &fileInfo)
|
|
{
|
|
QStringList args;
|
|
args.append("diff");
|
|
args.append("--staged");
|
|
args.append("--name-only");
|
|
args.append(fileInfo.fileName());
|
|
QString output = runGit(fileInfo.absolutePath(),args);
|
|
return output.trimmed() == fileInfo.fileName();
|
|
}
|
|
|
|
bool GitManager::isFileChanged(const QFileInfo &fileInfo)
|
|
{
|
|
QStringList args;
|
|
args.append("diff");
|
|
args.append("--name-only");
|
|
args.append(fileInfo.fileName());
|
|
QString output = runGit(fileInfo.absolutePath(),args);
|
|
return output.trimmed() == fileInfo.fileName();
|
|
}
|
|
|
|
bool GitManager::add(const QString &folder, const QString &path, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("add");
|
|
args.append(path);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::remove(const QString &folder, const QString &path, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("rm");
|
|
args.append(path);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::rename(const QString &folder, const QString &oldName,
|
|
const QString &newName, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("mv");
|
|
args.append(oldName);
|
|
args.append(newName);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::restore(const QString &folder, const QString &path, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("restore");
|
|
if (path.isEmpty())
|
|
args.append(".");
|
|
else
|
|
args.append(path);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
int GitManager::logCounts(const QString &folder, const QString &branch)
|
|
{
|
|
QStringList args;
|
|
args.append("rev-list");
|
|
args.append("--count");
|
|
if (branch.isEmpty())
|
|
args.append("HEAD");
|
|
else
|
|
args.append(branch);
|
|
QString s = runGit(folder,args).trimmed();
|
|
bool ok;
|
|
int result = s.toInt(&ok);
|
|
if (!ok)
|
|
result = 0;
|
|
return result;
|
|
}
|
|
|
|
QList<PGitCommitInfo> GitManager::log(const QString &folder, int start, int count, const QString &branch)
|
|
{
|
|
QStringList args;
|
|
args.append("log");
|
|
args.append("--skip");
|
|
args.append(QString("%1").arg(start));
|
|
args.append("-n");
|
|
args.append(QString("%1").arg(count));
|
|
args.append("--format=medium");
|
|
args.append("--date=iso-strict");
|
|
if (branch.isEmpty())
|
|
args.append("HEAD");
|
|
else
|
|
args.append(branch);
|
|
QString output = runGit(folder,args);
|
|
QStringList lines = textToLines(output);
|
|
QList<PGitCommitInfo> result;
|
|
int pos = 0;
|
|
PGitCommitInfo commitInfo;
|
|
while (pos<lines.length()) {
|
|
if (lines[pos].startsWith("commit ")) {
|
|
commitInfo = std::make_shared<GitCommitInfo>();
|
|
commitInfo->commitHash=lines[pos].mid(QString("commit ").length()).trimmed();
|
|
result.append(commitInfo);
|
|
} else if(!commitInfo) {
|
|
break;
|
|
} else if (lines[pos].startsWith("Author:")) {
|
|
commitInfo->author=lines[pos].mid(QString("Author:").length()).trimmed();
|
|
} else if (lines[pos].startsWith("Date:")) {
|
|
commitInfo->authorDate=QDateTime::fromString(lines[pos].mid(QString("Date:").length()).trimmed(),Qt::ISODate);
|
|
} else if (!lines[pos].trimmed().isEmpty()) {
|
|
if (commitInfo->title.isEmpty()) {
|
|
commitInfo->title = lines[pos].trimmed();
|
|
} else {
|
|
commitInfo->fullCommitMessage.append(lines[pos].trimmed()+"\n");
|
|
}
|
|
}
|
|
pos++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QStringList GitManager::listFiles(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("ls-files");
|
|
return textToLines(runGit(folder,args));
|
|
}
|
|
|
|
QStringList GitManager::listStagedFiles(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("diff");
|
|
args.append("--staged");
|
|
args.append("--name-only");
|
|
return textToLines(runGit(folder,args));
|
|
}
|
|
|
|
QStringList GitManager::listChangedFiles(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("diff");
|
|
args.append("--name-only");
|
|
return textToLines(runGit(folder,args));
|
|
}
|
|
|
|
QStringList GitManager::listConflicts(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("diff");
|
|
args.append("--name-only");
|
|
args.append("--diff-filter=U");
|
|
return textToLines(runGit(folder,args));
|
|
}
|
|
|
|
QStringList GitManager::listRemotes(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
return textToLines(runGit(folder,args));
|
|
}
|
|
|
|
bool GitManager::removeRemote(const QString &folder, const QString &remoteName, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
args.append("remove");
|
|
args.append(remoteName);
|
|
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::renameRemote(const QString &folder, const QString &oldName, const QString &newName, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
args.append("rename");
|
|
args.append(oldName);
|
|
args.append(newName);
|
|
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::addRemote(const QString &folder, const QString &name, const QString &url, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
args.append("add");
|
|
args.append(name);
|
|
args.append(url);
|
|
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::setRemoteURL(const QString &folder, const QString &name, const QString &newURL, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
args.append("set-url");
|
|
args.append(name);
|
|
args.append(newURL);
|
|
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
QString GitManager::getRemoteURL(const QString &folder, const QString &name)
|
|
{
|
|
QStringList args;
|
|
args.append("remote");
|
|
args.append("get-url");
|
|
args.append(name);
|
|
return runGit(folder,args).trimmed();
|
|
}
|
|
|
|
QString GitManager::getBranchRemote(const QString &folder, const QString &branch)
|
|
{
|
|
QStringList args;
|
|
args.append("config");
|
|
args.append("--get");
|
|
args.append(QString("branch.%1.remote").arg(branch));
|
|
return runGit(folder,args).trimmed();
|
|
}
|
|
|
|
QString GitManager::getBranchMerge(const QString &folder, const QString &branch)
|
|
{
|
|
QStringList args;
|
|
args.append("config");
|
|
args.append("--get");
|
|
args.append(QString("branch.%1.merge").arg(branch));
|
|
return runGit(folder,args).trimmed();
|
|
}
|
|
|
|
bool GitManager::setBranchUpstream(
|
|
const QString &folder,
|
|
const QString &branch,
|
|
const QString &remoteName,
|
|
QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("branch");
|
|
args.append(QString("--set-upstream-to=%1/%2").arg(remoteName,branch));
|
|
args.append(branch);
|
|
output = runGit(folder,args).trimmed();
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::fetch(const QString &folder, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("fetch");
|
|
output = runGit(folder,args).trimmed();
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::pull(const QString &folder, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("pull");
|
|
output = runGit(folder,args).trimmed();
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::push(const QString &folder, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("push");
|
|
output = runGit(folder,args).trimmed();
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::push(const QString &folder, const QString &remoteName, const QString &branch, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("push");
|
|
args.append("--set-upstream");
|
|
args.append(remoteName);
|
|
args.append(branch);
|
|
output = runGit(folder,args).trimmed();
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::removeConfig(const QString &folder, const QString &name, QString &output)
|
|
{
|
|
QStringList args;
|
|
args.append("config");
|
|
args.append("--unset-all");
|
|
args.append(name);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::setConfig(const QString &folder, const QString &name, const QString &value, QString &output)
|
|
{
|
|
removeConfig(folder,name,output);
|
|
QStringList args;
|
|
args.append("config");
|
|
args.append("--add");
|
|
args.append(name);
|
|
args.append(value);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::setUserName(const QString &folder, const QString &userName, QString &output)
|
|
{
|
|
return setConfig(folder,"user.name",userName,output);
|
|
}
|
|
|
|
bool GitManager::setUserEmail(const QString &folder, const QString &userEmail, QString &output)
|
|
{
|
|
return setConfig(folder,"user.email",userEmail,output);
|
|
}
|
|
|
|
QString GitManager::getConfig(const QString& folder, const QString &name)
|
|
{
|
|
QStringList args;
|
|
args.append("config");
|
|
args.append("--get");
|
|
args.append(name);
|
|
return runGit(folder,args).trimmed();
|
|
}
|
|
|
|
QString GitManager::getUserName(const QString& folder)
|
|
{
|
|
return getConfig(folder, "user.name");
|
|
}
|
|
|
|
QString GitManager::getUserEmail(const QString& folder)
|
|
{
|
|
return getConfig(folder, "user.email");
|
|
}
|
|
|
|
QStringList GitManager::listBranches(const QString &folder, int ¤t)
|
|
{
|
|
QStringList args;
|
|
args.append("branch");
|
|
args.append("-a");
|
|
args.append("-l");
|
|
QStringList temp = textToLines(runGit(folder,args));
|
|
current = -1;
|
|
for (int i=0;i<temp.length();i++) {
|
|
QString s = temp[i];
|
|
if (s.startsWith('*')) {
|
|
current = i;
|
|
temp[i] = s.mid(1).trimmed();
|
|
} else if (s.startsWith('+')) {
|
|
temp[i] = s.mid(1).trimmed();
|
|
} else {
|
|
temp[i] = s.trimmed();
|
|
}
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
bool GitManager::switchToBranch(const QString &folder, const QString &branch,
|
|
bool create, bool force, bool merge, bool track,
|
|
bool noTrack, bool forceCreation, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("switch");
|
|
if (forceCreation)
|
|
args.append("-C");
|
|
else if (create)
|
|
args.append("-c");
|
|
if (merge)
|
|
args.append("-m");
|
|
if (force)
|
|
args.append("-f");
|
|
if (track)
|
|
args.append("--track");
|
|
else if (noTrack)
|
|
args.append("--no-track");
|
|
args.append(branch);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::merge(const QString &folder, const QString &commit, bool squash,
|
|
bool fastForwardOnly, bool noFastForward, bool noCommit,
|
|
QString& output,
|
|
const QString& commitMessage)
|
|
{
|
|
QStringList args;
|
|
args.append("merge");
|
|
if (squash)
|
|
args.append("--squash");
|
|
if (fastForwardOnly)
|
|
args.append("--ff-only");
|
|
else if (noFastForward)
|
|
args.append("--no-ff");
|
|
if (noCommit)
|
|
args.append("--no-commit");
|
|
if (!commitMessage.isEmpty()
|
|
&& commitMessage != QObject::tr("<Auto Generated by Git>")){
|
|
args.append("-m");
|
|
args.append(commitMessage);
|
|
}
|
|
args.append(commit);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::continueMerge(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("merge");
|
|
args.append("--continue");
|
|
QString output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
|
|
}
|
|
|
|
void GitManager::abortMerge(const QString &folder)
|
|
{
|
|
QStringList args;
|
|
args.append("merge");
|
|
args.append("--abort");
|
|
runGit(folder,args);
|
|
}
|
|
|
|
bool GitManager::isSuccess(const QString &output)
|
|
{
|
|
QStringList lst = textToLines(output);
|
|
if (!lst.isEmpty()) {
|
|
foreach (const QString& s, lst) {
|
|
QString last= s.trimmed();
|
|
if (last.startsWith("error:") || last.startsWith("fatal:"))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GitManager::clone(const QString &folder, const QString &url, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("clone");
|
|
args.append(url);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::commit(const QString &folder, const QString &message, bool autoStage, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("commit");
|
|
if (autoStage)
|
|
args.append("-a");
|
|
args.append("-m");
|
|
args.append(message);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::revert(const QString &folder, QString& output)
|
|
{
|
|
QStringList args;
|
|
args.append("revert");
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::reset(const QString &folder, const QString &commit,
|
|
GitResetStrategy strategy,
|
|
QString& output)
|
|
{
|
|
//todo reset type
|
|
QStringList args;
|
|
args.append("reset");
|
|
switch(strategy) {
|
|
case GitResetStrategy::Soft:
|
|
args.append("--soft");
|
|
break;
|
|
case GitResetStrategy::Hard:
|
|
args.append("--hard");
|
|
break;
|
|
case GitResetStrategy::Mixed:
|
|
args.append("--mixed");
|
|
break;
|
|
case GitResetStrategy::Merge:
|
|
args.append("--merge");
|
|
break;
|
|
case GitResetStrategy::Keep:
|
|
args.append("--keep");
|
|
break;
|
|
}
|
|
args.append(commit);
|
|
output = runGit(folder,args);
|
|
return isSuccess(output);
|
|
}
|
|
|
|
bool GitManager::isValid()
|
|
{
|
|
return pSettings->vcs().gitOk();
|
|
}
|
|
|
|
QString GitManager::runGit(const QString& workingFolder, const QStringList &args)
|
|
{
|
|
if (!isValid())
|
|
return "";
|
|
QFileInfo fileInfo(pSettings->vcs().gitPath());
|
|
if (!fileInfo.exists())
|
|
return "fatal: git doesn't exist";
|
|
emit gitCmdRunning(QString("Running in \"%1\": \n \"%2\" \"%3\"")
|
|
.arg(workingFolder,
|
|
pSettings->vcs().gitPath(),
|
|
args.join("\" \"")));
|
|
// qDebug()<<"---------";
|
|
// qDebug()<<args;
|
|
QProcessEnvironment env;
|
|
#ifdef Q_OS_WIN
|
|
env.insert("PATH",pSettings->dirs().appDir());
|
|
env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appDir())+"redpanda-win-git-askpass.exe");
|
|
#elif defined(Q_OS_LINUX)
|
|
env.insert(QProcessEnvironment::systemEnvironment());
|
|
env.insert("LANG","en");
|
|
env.insert("LANGUAGE","en");
|
|
env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"redpanda-git-askpass");
|
|
#endif
|
|
QString output = runAndGetOutput(
|
|
fileInfo.absoluteFilePath(),
|
|
workingFolder,
|
|
args,
|
|
"",
|
|
false,
|
|
env);
|
|
output = escapeUTF8String(output.toUtf8());
|
|
// qDebug()<<output;
|
|
emit gitCmdFinished(output);
|
|
// if (output.startsWith("fatal:"))
|
|
// throw GitError(output);
|
|
return output;
|
|
}
|
|
|
|
QString GitManager::escapeUTF8String(const QByteArray &rawString)
|
|
{
|
|
QByteArray stringValue;
|
|
int p = 0;
|
|
while (p<rawString.length()) {
|
|
char ch = rawString[p];
|
|
if (ch =='\\' && p+1 < rawString.length()) {
|
|
p++;
|
|
ch = rawString[p];
|
|
switch (ch) {
|
|
case '\'':
|
|
stringValue+=0x27;
|
|
p++;
|
|
break;
|
|
case '"':
|
|
stringValue+=0x22;
|
|
p++;
|
|
break;
|
|
case '?':
|
|
stringValue+=0x3f;
|
|
p++;
|
|
break;
|
|
case '\\':
|
|
stringValue+=0x5c;
|
|
p++;
|
|
break;
|
|
case 'a':
|
|
stringValue+=0x07;
|
|
p++;
|
|
break;
|
|
case 'b':
|
|
stringValue+=0x08;
|
|
p++;
|
|
break;
|
|
case 'f':
|
|
stringValue+=0x0c;
|
|
p++;
|
|
break;
|
|
case 'n':
|
|
stringValue+=0x0a;
|
|
p++;
|
|
break;
|
|
case 'r':
|
|
stringValue+=0x0d;
|
|
p++;
|
|
break;
|
|
case 't':
|
|
stringValue+=0x09;
|
|
p++;
|
|
break;
|
|
case 'v':
|
|
stringValue+=0x0b;
|
|
p++;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
{
|
|
int i=0;
|
|
for (i=0;i<3;i++) {
|
|
if (p+i>=rawString.length() ||
|
|
rawString[p+i]<'0' || rawString[p+i]>'7')
|
|
break;
|
|
}
|
|
bool ok;
|
|
unsigned char ch = rawString.mid(p,i).toInt(&ok,8);
|
|
stringValue+=ch;
|
|
p+=i;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (ch!='\"')
|
|
stringValue+=ch;
|
|
p++;
|
|
}
|
|
}
|
|
return QString::fromUtf8(stringValue);
|
|
}
|
|
|
|
GitError::GitError(const QString &reason):BaseError(reason)
|
|
{
|
|
|
|
}
|