#include "git/GitManager.h" #include #include 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(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; }