Фаза 1: мастер первого запуска — SettingsStore, GitManager (auto-init+коммиты), SyncManager (проверка соединений+ErrorCatalog), WelcomeWizard/ProviderSetupGuide; тесты smoke/git/vault (12/12)

This commit is contained in:
Flora (opencode)
2026-08-17 00:43:33 +03:00
parent 980fbcedcb
commit ed633ae036
18 changed files with 1412 additions and 29 deletions
+193
View File
@@ -0,0 +1,193 @@
#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;
}