Prod: полный аудит и стабилизация — исправлены критические баги, расширено покрытие тестами, подготовлен бандл
- FileStore: защита от выхода за пределы хранилища (isInsideVault), валидация имён (запрет скрытых, зарезервированных, управляющих символов), проверка destDir, лимит watcher, dailyNote валидация, openVault проверка isDir - DocumentManager: лимит 10МБ, кламп автосейва 0.5-120с, защита от OOM, бинарный детект, проверка путей - VaultIndex: исправлен парсинг тегов (заголовки, инлайн-код), case-insensitive поиск, дедупликация рёбер, защита от traversal, лимит глубины, проверка vaultExists - MdParser: блокировка всех on* событий, javascript:/data:, esc кавычек, MIME fallback, normalizeLang расширен (c/cpp/py/js и кириллица), wiki глобальный поиск, image лимит 3МБ, защита clipboard - GitManager: commitAll с GIT_INDEX_ADD_DEFAULT и поддержкой пустых коммитов/удалений, fileGitStatus с cleanPath и проверкой префикса, repoExists/hasCommits с cleanPath, initRepo валидация - SettingsStore: encodeKey/encodeRel для QSettings Ini, flat хранение viewMode, sync после записи, кламп ширин панелей, миграция старых ключей - SyncManager: счётчик busy, обработка httpStatus при network error, валидация токена/провайдера/URL, GitHub Enterprise URL, таймаут - VaultManager: валидация путей (корень, файл vs папка, права), проверка вложенности - WindowController: per-window FileStore/DocumentManager/VaultIndex, cleanPath, закрытие окна через QQuickWindow::closing, обработка ошибок компонента - QML: TableEditor пустая строка, WelcomeWizard QUrl парсинг, FileTree фильтр, GraphView лимит 600 узлов, RightPanel debounce rebuild, Theme singleton, MonthCalendar и др. - Тесты: добавлены flora_settings_test, flora_sync_test, flora_extended_test, flora_integration_test — покрытие всех модулей, 20/20 зелёных - Сборка: обновлён Flora.app (4.6М бинарь -> 173М бандл, ad-hoc подпись, проверен offscreen запуск, версия 0.0.1 + cmark 0.29 + libgit2 1.9) - .gitignore: добавлены Testing/, coverage, gcda/gcov, __pycache__, .tmp, test-vault и др.
This commit is contained in:
+92
-13
@@ -27,22 +27,25 @@ struct CommitInfo {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GitManager::s_gitInitialized = false;
|
||||
|
||||
GitManager::GitManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
if (!m_gitInitialized) {
|
||||
if (!s_gitInitialized) {
|
||||
git_libgit2_init();
|
||||
m_gitInitialized = true;
|
||||
s_gitInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
QString GitManager::repoExists(const QString &path)
|
||||
{
|
||||
const QString clean = QDir::cleanPath(path);
|
||||
git_repository *repo = nullptr;
|
||||
if (git_repository_open(&repo, path.toUtf8().constData()) < 0)
|
||||
if (git_repository_open(&repo, clean.toUtf8().constData()) < 0)
|
||||
return QString();
|
||||
git_repository_free(repo);
|
||||
return path;
|
||||
return clean;
|
||||
}
|
||||
|
||||
void GitManager::setRepoIdentity(git_repository *repo)
|
||||
@@ -72,11 +75,18 @@ void GitManager::setLastError(const QString &msg)
|
||||
|
||||
bool GitManager::initRepo(const QString &path)
|
||||
{
|
||||
const QByteArray p = path.toUtf8();
|
||||
const QString clean = QDir::cleanPath(path.trimmed());
|
||||
if (clean.isEmpty()) {
|
||||
setLastError(QStringLiteral("Путь к хранилищу пустой"));
|
||||
return false;
|
||||
}
|
||||
const QByteArray p = clean.toUtf8();
|
||||
git_repository *repo = nullptr;
|
||||
|
||||
if (git_repository_open(&repo, p.constData()) == 0) {
|
||||
setRepoIdentity(repo);
|
||||
git_repository_free(repo);
|
||||
setLastError(QString());
|
||||
return true; // .git уже есть
|
||||
}
|
||||
|
||||
@@ -98,18 +108,89 @@ bool GitManager::initRepo(const QString &path)
|
||||
|
||||
bool GitManager::hasCommits(const QString &path) const
|
||||
{
|
||||
const QString clean = QDir::cleanPath(path);
|
||||
git_repository *repo = nullptr;
|
||||
if (git_repository_open(&repo, path.toUtf8().constData()) < 0)
|
||||
if (git_repository_open(&repo, clean.toUtf8().constData()) < 0)
|
||||
return false;
|
||||
const bool has = git_repository_head_unborn(repo) == 0;
|
||||
git_repository_free(repo);
|
||||
return has;
|
||||
}
|
||||
|
||||
QString GitManager::fileGitStatus(const QString &vaultPath, const QString &filePath) const
|
||||
{
|
||||
if (vaultPath.isEmpty() || filePath.isEmpty())
|
||||
return QStringLiteral("—");
|
||||
const QString vault = QDir::cleanPath(vaultPath);
|
||||
const QString file = QDir::cleanPath(filePath);
|
||||
if (!file.startsWith(vault + '/') && file != vault)
|
||||
return QStringLiteral("—");
|
||||
git_repository *repo = nullptr;
|
||||
if (git_repository_open(&repo, vault.toUtf8().constData()) < 0)
|
||||
return QStringLiteral("—");
|
||||
|
||||
// относительный путь внутри репозитория
|
||||
QString rel = file;
|
||||
if (rel == vault) {
|
||||
git_repository_free(repo);
|
||||
return QStringLiteral("—");
|
||||
}
|
||||
if (rel.startsWith(vault)) {
|
||||
rel = rel.mid(vault.length());
|
||||
while (rel.startsWith("/") || rel.startsWith("\\"))
|
||||
rel.remove(0, 1);
|
||||
}
|
||||
if (rel.isEmpty()) {
|
||||
git_repository_free(repo);
|
||||
return QStringLiteral("—");
|
||||
}
|
||||
|
||||
unsigned int status = 0;
|
||||
const int rc = git_status_file(&status, repo, rel.toUtf8().constData());
|
||||
git_repository_free(repo);
|
||||
|
||||
if (rc != 0) {
|
||||
// файл не найден в индексе или ошибка — считаем неотслеживаемым если файл существует
|
||||
if (QFile::exists(filePath))
|
||||
return QStringLiteral("неотслеживается");
|
||||
return QStringLiteral("—");
|
||||
}
|
||||
|
||||
if (status == GIT_STATUS_CURRENT)
|
||||
return QStringLiteral("чисто");
|
||||
if (status & GIT_STATUS_CONFLICTED)
|
||||
return QStringLiteral("конфликт");
|
||||
if (status & GIT_STATUS_WT_NEW)
|
||||
return QStringLiteral("неотслеживается");
|
||||
if (status & GIT_STATUS_INDEX_NEW)
|
||||
return QStringLiteral("в индексе (новый)");
|
||||
if (status & GIT_STATUS_INDEX_MODIFIED)
|
||||
return QStringLiteral("в индексе");
|
||||
if (status & GIT_STATUS_WT_MODIFIED)
|
||||
return QStringLiteral("изменён");
|
||||
if (status & GIT_STATUS_WT_DELETED)
|
||||
return QStringLiteral("удалён");
|
||||
if (status & GIT_STATUS_IGNORED)
|
||||
return QStringLiteral("игнорируется");
|
||||
return QStringLiteral("чисто");
|
||||
}
|
||||
|
||||
QString GitManager::fileGitStatusShort(const QString &vaultPath, const QString &filePath) const
|
||||
{
|
||||
const QString s = fileGitStatus(vaultPath, filePath);
|
||||
if (s == "чисто") return QStringLiteral("чисто");
|
||||
if (s == "изменён") return QStringLiteral("изменён");
|
||||
if (s == "неотслеживается") return QStringLiteral("новый");
|
||||
if (s.contains("конфликт")) return QStringLiteral("конфликт");
|
||||
if (s == "—") return QStringLiteral("—");
|
||||
return s;
|
||||
}
|
||||
|
||||
bool GitManager::commitAll(const QString &path, const QString &message)
|
||||
{
|
||||
const QString cleanPath = QDir::cleanPath(path);
|
||||
CommitInfo info;
|
||||
if (git_repository_open(&info.repo, path.toUtf8().constData()) < 0) {
|
||||
if (git_repository_open(&info.repo, cleanPath.toUtf8().constData()) < 0) {
|
||||
setLastError(QStringLiteral("Хранилище не является git-репозиторием"));
|
||||
return false;
|
||||
}
|
||||
@@ -119,18 +200,16 @@ bool GitManager::commitAll(const QString &path, const QString &message)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Добавляем все файлы, включая новые (с учётом .gitignore)
|
||||
git_index_add_all(info.index, nullptr, 0, nullptr, nullptr);
|
||||
// Добавляем все файлы, включая новые и удаления (с учётом .gitignore)
|
||||
git_index_add_all(info.index, nullptr, GIT_INDEX_ADD_DEFAULT, 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;
|
||||
}
|
||||
// Не проверяем entrycount==0 отдельно — пустой индекс может означать удаление всех файлов,
|
||||
// что тоже нужно коммитить. Проверяем равенство дерева ниже.
|
||||
|
||||
// Создаём дерево
|
||||
git_tree *tree = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user