193 lines
6.0 KiB
C++
193 lines
6.0 KiB
C++
#include "git/GitManager.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
|
|
namespace {
|
|
|
|
QString gitErr()
|
|
{
|
|
const git_error *e = git_error_last();
|
|
return e && e->message ? QString::fromUtf8(e->message) : QStringLiteral("неизвестная ошибка git");
|
|
}
|
|
|
|
struct CommitInfo {
|
|
git_signature *author = nullptr;
|
|
git_index *index = nullptr;
|
|
git_repository *repo = nullptr;
|
|
git_oid treeId{};
|
|
git_oid commitId{};
|
|
~CommitInfo()
|
|
{
|
|
if (author) git_signature_free(author);
|
|
if (index) git_index_free(index);
|
|
if (repo) git_repository_free(repo);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
GitManager::GitManager(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
if (!m_gitInitialized) {
|
|
git_libgit2_init();
|
|
m_gitInitialized = true;
|
|
}
|
|
}
|
|
|
|
QString GitManager::repoExists(const QString &path)
|
|
{
|
|
git_repository *repo = nullptr;
|
|
if (git_repository_open(&repo, path.toUtf8().constData()) < 0)
|
|
return QString();
|
|
git_repository_free(repo);
|
|
return path;
|
|
}
|
|
|
|
void GitManager::setRepoIdentity(git_repository *repo)
|
|
{
|
|
git_config *cfg = nullptr;
|
|
if (git_repository_config(&cfg, repo) < 0)
|
|
return;
|
|
|
|
auto setIfMissing = [cfg](const char *key, const char *value) {
|
|
git_buf buf{};
|
|
if (git_config_get_string_buf(&buf, cfg, key) < 0)
|
|
git_config_set_string(cfg, key, value);
|
|
git_buf_dispose(&buf);
|
|
};
|
|
setIfMissing("user.name", "Flora");
|
|
setIfMissing("user.email", "flora@localhost");
|
|
git_config_free(cfg);
|
|
}
|
|
|
|
void GitManager::setLastError(const QString &msg)
|
|
{
|
|
if (m_lastError == msg)
|
|
return;
|
|
m_lastError = msg;
|
|
emit lastErrorChanged();
|
|
}
|
|
|
|
bool GitManager::initRepo(const QString &path)
|
|
{
|
|
const QByteArray p = path.toUtf8();
|
|
git_repository *repo = nullptr;
|
|
|
|
if (git_repository_open(&repo, p.constData()) == 0) {
|
|
git_repository_free(repo);
|
|
return true; // .git уже есть
|
|
}
|
|
|
|
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_MKDIR;
|
|
opts.initial_head = "main";
|
|
|
|
const int rc = git_repository_init_ext(&repo, p.constData(), &opts);
|
|
if (rc < 0) {
|
|
setLastError(QStringLiteral("Не удалось создать git-репозиторий: %1")
|
|
.arg(gitErr()));
|
|
return false;
|
|
}
|
|
setRepoIdentity(repo);
|
|
git_repository_free(repo);
|
|
setLastError(QString());
|
|
return true;
|
|
}
|
|
|
|
bool GitManager::hasCommits(const QString &path) const
|
|
{
|
|
git_repository *repo = nullptr;
|
|
if (git_repository_open(&repo, path.toUtf8().constData()) < 0)
|
|
return false;
|
|
const bool has = git_repository_head_unborn(repo) == 0;
|
|
git_repository_free(repo);
|
|
return has;
|
|
}
|
|
|
|
bool GitManager::commitAll(const QString &path, const QString &message)
|
|
{
|
|
CommitInfo info;
|
|
if (git_repository_open(&info.repo, path.toUtf8().constData()) < 0) {
|
|
setLastError(QStringLiteral("Хранилище не является git-репозиторием"));
|
|
return false;
|
|
}
|
|
|
|
if (git_repository_index(&info.index, info.repo) < 0) {
|
|
setLastError(QStringLiteral("Не удалось открыть индекс: %1").arg(gitErr()));
|
|
return false;
|
|
}
|
|
|
|
// Добавляем все файлы, включая новые (с учётом .gitignore)
|
|
git_index_add_all(info.index, nullptr, 0, nullptr, nullptr);
|
|
const int wrc = git_index_write(info.index);
|
|
if (wrc < 0) {
|
|
setLastError(QStringLiteral("Не удалось записать индекс: %1").arg(gitErr()));
|
|
return false;
|
|
}
|
|
|
|
if (git_index_entrycount(info.index) == 0) {
|
|
setLastError(QStringLiteral("Нет изменений для коммита"));
|
|
return false;
|
|
}
|
|
|
|
// Создаём дерево
|
|
git_tree *tree = nullptr;
|
|
if (git_index_write_tree(&info.treeId, info.index) < 0
|
|
|| git_tree_lookup(&tree, info.repo, &info.treeId) < 0) {
|
|
setLastError(QStringLiteral("Не удалось создать дерево: %1").arg(gitErr()));
|
|
return false;
|
|
}
|
|
|
|
// Родитель: head, если коммиты уже есть
|
|
git_reference *headRef = nullptr;
|
|
git_commit *parentCommit = nullptr;
|
|
int nParents = 0;
|
|
if (git_repository_head(&headRef, info.repo) == 0) {
|
|
git_object *obj = nullptr;
|
|
if (git_reference_peel(&obj, headRef, GIT_OBJECT_COMMIT) == 0) {
|
|
parentCommit = reinterpret_cast<git_commit *>(obj);
|
|
nParents = 1;
|
|
}
|
|
if (headRef) git_reference_free(headRef);
|
|
}
|
|
|
|
// Если дерево не изменилось относительно родителя — коммитить нечего.
|
|
if (parentCommit) {
|
|
const git_oid *prevTree = git_commit_tree_id(parentCommit);
|
|
if (prevTree && git_oid_equal(prevTree, &info.treeId)) {
|
|
git_commit_free(parentCommit);
|
|
git_tree_free(tree);
|
|
setLastError(QStringLiteral("Нет изменений для коммита"));
|
|
return false;
|
|
}
|
|
}
|
|
const git_commit *parents[1] = {parentCommit};
|
|
|
|
// Подпись из конфига репозитория
|
|
git_signature *author = nullptr;
|
|
if (git_signature_default(&author, info.repo) < 0) {
|
|
git_commit_free(parentCommit);
|
|
git_tree_free(tree);
|
|
setLastError(QStringLiteral("Не настроена git-личность: %1").arg(gitErr()));
|
|
return false;
|
|
}
|
|
info.author = author;
|
|
|
|
const int crc = git_commit_create(&info.commitId, info.repo, "HEAD",
|
|
author, author, "UTF-8",
|
|
message.toUtf8().constData(),
|
|
tree, nParents, parents);
|
|
|
|
if (parentCommit) git_commit_free(parentCommit);
|
|
git_tree_free(tree);
|
|
|
|
if (crc < 0) {
|
|
setLastError(QStringLiteral("Не удалось создать коммит: %1").arg(gitErr()));
|
|
return false;
|
|
}
|
|
|
|
setLastError(QString());
|
|
return true;
|
|
} |