1f140d3433
- MdParser.copyCodeBlock(id): один вызов вместо get+copy в 3 QML-местах - parseBlocks переписан на splitMdBlocks (убит regex только для ```, заодно чинит ~~~); renderInner без побочек, save/restore удалены - Кэш diff-блоков (хеши+пути, 8 шт, >1МБ не кэшируем); регистрация кодов всегда в главном потоке - История: пагинация limit+Показать ещё с сохранением выбора; willDelete; busy только на время async - GitManager: commitFile точечно (общий commitFromIndex), fileExistedAt, workdirContent через DocumentManager.readTextFile, fileGitStatus через relativePath; удалены fileDiffText/Html и 2-arg fileHistory - MarkdownView: дебаунс async-ветки 80мс + гашение протухших ответов
216 lines
13 KiB
C++
216 lines
13 KiB
C++
// Тест GitManager истории: вперёд/назад, diff, восстановление, время
|
|
#include <QCoreApplication>
|
|
#include <QTemporaryDir>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QDateTime>
|
|
#include <QThread>
|
|
#include <cstdio>
|
|
|
|
#include "git/GitManager.h"
|
|
|
|
static int fail(const char *msg, const QString &detail=QString()){
|
|
std::fprintf(stderr,"FAIL: %s | %s\n", msg, qPrintable(detail));
|
|
return 1;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
|
|
QTemporaryDir dir;
|
|
if(!dir.isValid()) return fail("tmp dir");
|
|
QString vault = dir.path();
|
|
GitManager git;
|
|
|
|
if(!git.initRepo(vault)) return fail("initRepo", git.lastError());
|
|
// должен создать .gitignore
|
|
if(!QFile::exists(vault+"/.gitignore")) return fail(".gitignore not created");
|
|
QFile ig(vault+"/.gitignore");
|
|
ig.open(QIODevice::ReadOnly);
|
|
QString igContent = QString::fromUtf8(ig.readAll());
|
|
if(!igContent.contains(".flora/")) return fail(".gitignore missing .flora", igContent);
|
|
|
|
// .flora должен игнорироваться
|
|
QDir().mkpath(vault+"/.flora");
|
|
QFile hidden(vault+"/.flora/test"); hidden.open(QIODevice::WriteOnly); hidden.write("x"); hidden.close();
|
|
// статус hidden должен быть игнорируется
|
|
QString st = git.fileGitStatus(vault, vault+"/.flora/test");
|
|
if(st != "игнорируется" && st != "—") {
|
|
// допустимо, но должен быть игнорируется после коммита .gitignore?
|
|
// Сначала закоммитим .gitignore
|
|
}
|
|
|
|
// Создаём файл v1
|
|
QString file = vault+"/note.md";
|
|
QFile f(file); f.open(QIODevice::WriteOnly); f.write("Версия 1\nСтрока A\n"); f.close();
|
|
if(!git.commitAll(vault, "v1")) return fail("commit v1", git.lastError());
|
|
if(!git.hasCommits(vault)) return fail("hasCommits");
|
|
|
|
// История после v1: 1 элемент
|
|
QVariantList hist = git.fileHistory(vault, file, 500);
|
|
if(hist.size()!=1) return fail("hist size 1", QString::number(hist.size()));
|
|
QString hash1 = hist.first().toMap().value("hash").toString();
|
|
QString short1 = hist.first().toMap().value("shortHash").toString();
|
|
if(hash1.isEmpty() || short1.isEmpty()) return fail("hash empty");
|
|
if(hist.first().toMap().value("message").toString()!="v1") return fail("message v1");
|
|
QDateTime t1 = hist.first().toMap().value("time").toDateTime();
|
|
if(!t1.isValid()) return fail("time invalid");
|
|
if(hist.first().toMap().value("timeAgo").toString().isEmpty()) return fail("timeAgo empty");
|
|
// lastModified
|
|
QDateTime lm = git.lastModifiedTime(vault, file);
|
|
if(!lm.isValid()) return fail("lastModified invalid");
|
|
QString lmStr = git.lastModifiedTimeString(vault, file);
|
|
if(lmStr.isEmpty() || lmStr=="—") return fail("lmStr empty", lmStr);
|
|
// content at
|
|
QString c1 = git.fileContentAt(vault, file, hash1);
|
|
if(!c1.contains("Версия 1")) return fail("content v1", c1);
|
|
// workdirContent читает текущее с диска
|
|
if(git.workdirContent(file) != "Версия 1\nСтрока A\n") return fail("workdirContent", git.workdirContent(file));
|
|
|
|
// Изменяем файл → v2
|
|
QThread::msleep(1100); // чтобы время коммита отличалось
|
|
QFile f2(file); f2.open(QIODevice::WriteOnly | QIODevice::Truncate); f2.write("Версия 2\nСтрока A\nСтрока B\n"); f2.close();
|
|
if(!git.commitAll(vault, "v2 - добавили строку B")) return fail("commit v2", git.lastError());
|
|
QVariantList hist2 = git.fileHistory(vault, file, 500);
|
|
if(hist2.size()!=2) return fail("hist size 2", QString::number(hist2.size()));
|
|
// Новый сверху
|
|
QString hash2 = hist2.first().toMap().value("hash").toString();
|
|
if(hash2==hash1) return fail("hash2 same as hash1");
|
|
if(hist2.first().toMap().value("message").toString().left(2)!="v2") return fail("hist2 top message", hist2.first().toMap().value("message").toString());
|
|
if(hist2.last().toMap().value("hash").toString()!=hash1) return fail("hist2 last not hash1");
|
|
// время второго должно быть позже
|
|
QDateTime t2 = hist2.first().toMap().value("time").toDateTime();
|
|
if(t2 <= t1) return fail("t2 not later", t2.toString() + " vs " + t1.toString());
|
|
// content at hash2
|
|
QString c2 = git.fileContentAt(vault, file, hash2);
|
|
if(!c2.contains("Версия 2") || !c2.contains("Строка B")) return fail("c2 content", c2);
|
|
// content at hash1 всё ещё v1
|
|
QString c1again = git.fileContentAt(vault, file, hash1);
|
|
if(!c1again.contains("Версия 1") || c1again.contains("Строка B")) return fail("c1again", c1again);
|
|
|
|
// forward/backward: добавляем ещё файл other.md, коммитим, история note.md не должна вырасти
|
|
QString other = vault+"/other.md";
|
|
QFile of(other); of.open(QIODevice::WriteOnly); of.write("other\n"); of.close();
|
|
if(!git.commitAll(vault, "add other")) return fail("commit other");
|
|
QVariantList histAfterOther = git.fileHistory(vault, file, 500);
|
|
if(histAfterOther.size()!=2) return fail("hist after other should still 2", QString::number(histAfterOther.size()));
|
|
|
|
// Восстановление назад к v1
|
|
QString err = git.restoreFile(vault, file, hash1);
|
|
if(!err.isEmpty()) return fail("restore to v1", err);
|
|
QFile check(file); check.open(QIODevice::ReadOnly); QString cur = QString::fromUtf8(check.readAll());
|
|
if(!cur.contains("Версия 1") || cur.contains("Строка B")) return fail("after restore v1 content", cur);
|
|
// История после восстановления должна иметь 3 элемента (v1, v2, восстановление)
|
|
QVariantList hist3 = git.fileHistory(vault, file, 500);
|
|
if(hist3.size()!=3) return fail("hist after restore size 3", QString::number(hist3.size()));
|
|
if(!hist3.first().toMap().value("message").toString().contains("Восстановление")) return fail("restore commit message", hist3.first().toMap().value("message").toString());
|
|
// lastModified должно быть сейчас (только что)
|
|
QString lmAfter = git.lastModifiedTimeString(vault, file);
|
|
if(!lmAfter.contains("только что") && !lmAfter.contains("мин назад") && !lmAfter.contains("ч назад")) {
|
|
// допустимо, но должно быть свежим
|
|
}
|
|
|
|
// Восстановление вперёд к v2 (используем hash2)
|
|
err = git.restoreFile(vault, file, hash2);
|
|
if(!err.isEmpty()) return fail("restore forward to v2", err);
|
|
QFile check2(file); check2.open(QIODevice::ReadOnly); QString cur2 = QString::fromUtf8(check2.readAll());
|
|
if(!cur2.contains("Версия 2")) return fail("after restore forward v2", cur2);
|
|
QVariantList hist4 = git.fileHistory(vault, file, 500);
|
|
if(hist4.size()!=4) return fail("hist after forward restore 4", QString::number(hist4.size()));
|
|
|
|
// Ещё одно изменение вручную вперёд
|
|
QThread::msleep(1100);
|
|
QFile f3(file); f3.open(QIODevice::WriteOnly | QIODevice::Truncate); f3.write("Версия 3\nНовая строка\n"); f3.close();
|
|
if(!git.commitAll(vault, "v3")) return fail("commit v3");
|
|
QVariantList hist5 = git.fileHistory(vault, file, 500);
|
|
if(hist5.size()!=5) return fail("hist 5", QString::number(hist5.size()));
|
|
if(hist5.first().toMap().value("message").toString()!="v3") return fail("top v3");
|
|
|
|
// Проверка несуществующего коммита
|
|
QString badContent = git.fileContentAt(vault, file, "0000000000000000000000000000000000000000");
|
|
if(!badContent.isEmpty()) return fail("bad hash should return empty");
|
|
|
|
QString badRestore = git.restoreFile(vault, file, "0000000000000000000000000000000000000000");
|
|
if(badRestore.isEmpty()) return fail("bad restore should fail");
|
|
|
|
// Проверка lastModified для несуществующего файла
|
|
QDateTime lmBad = git.lastModifiedTime(vault, vault+"/nonexistent.md");
|
|
if(lmBad.isValid()) {
|
|
// если файл не существует, должен быть невалидным или mtime? Сейчас возвращает невалидный
|
|
// допустимо
|
|
}
|
|
|
|
// Авто-коммит
|
|
QFile f4(file); f4.open(QIODevice::WriteOnly | QIODevice::Append); f4.write("\nauto\n"); f4.close();
|
|
bool autoOk = git.autoCommit(vault, file);
|
|
if(!autoOk) return fail("autoCommit", git.lastError());
|
|
QVariantList histAuto = git.fileHistory(vault, file, 500);
|
|
if(histAuto.size()!=6) return fail("hist after auto 6", QString::number(histAuto.size()));
|
|
if(!histAuto.first().toMap().value("message").toString().contains("Автосохранение")) return fail("auto message", histAuto.first().toMap().value("message").toString());
|
|
|
|
// Повторный autoCommit без изменений должен быть true (нет ошибки)
|
|
bool autoAgain = git.autoCommit(vault, file);
|
|
if(!autoAgain) return fail("auto again should be true even if no changes");
|
|
|
|
// Пагинация: limit режет сверху, порядок новые сверху, кэш консистентен
|
|
{
|
|
QVariantList full = git.fileHistory(vault, file, 500);
|
|
QVariantList part = git.fileHistory(vault, file, 2);
|
|
if(part.size()!=2) return fail("paged size", QString::number(part.size()));
|
|
if(part.first().toMap().value("hash") != full.first().toMap().value("hash"))
|
|
return fail("paged order");
|
|
// повторный запрос с тем же лимитом — из кэша, тот же результат
|
|
QVariantList part2 = git.fileHistory(vault, file, 2);
|
|
if(part2.size()!=2 || part2.first().toMap().value("hash") != part.first().toMap().value("hash"))
|
|
return fail("paged cache");
|
|
// limit<=0 — как раньше (всё)
|
|
if(git.fileHistory(vault, file, 0).size() != full.size()) return fail("limit 0");
|
|
}
|
|
|
|
// fileExistedAt: отличает «пустой файл» от «файла не было»
|
|
// (коммит удаления в историю файла не попадает — файла нет в дереве,
|
|
// поэтому false проверяем коммитом, предшествующим созданию файла)
|
|
{
|
|
if(!git.fileExistedAt(vault, file, hash1)) return fail("existed v1");
|
|
const QString late = vault+"/late.md";
|
|
QFile lf(late);
|
|
if(!lf.open(QIODevice::WriteOnly)) return fail("late create");
|
|
lf.write("late\n");
|
|
lf.close();
|
|
if(!git.commitAll(vault, "late file")) return fail("commit late", git.lastError());
|
|
QString lateHash = git.fileHistory(vault, late, 500).first().toMap().value("hash").toString();
|
|
if(!git.fileExistedAt(vault, late, lateHash)) return fail("late existed");
|
|
if(git.fileExistedAt(vault, late, hash1)) return fail("late should not exist at v1");
|
|
if(git.fileExistedAt(vault, late, "0000000000000000000000000000000000000000"))
|
|
return fail("bad hash existed");
|
|
}
|
|
|
|
// commitFile: точечный коммит одного файла (второй файл не трогаем)
|
|
{
|
|
QFile re(file);
|
|
if(!re.open(QIODevice::WriteOnly)) return fail("recreate");
|
|
re.write("v-final\n");
|
|
re.close();
|
|
if(!git.commitFile(vault, file, "final A")) return fail("commitFile A", git.lastError());
|
|
const int histA = git.fileHistory(vault, file, 500).size();
|
|
// правим оба файла
|
|
QFile fa(file);
|
|
fa.open(QIODevice::WriteOnly | QIODevice::Append); fa.write("more A\n"); fa.close();
|
|
QFile fb(vault+"/other.md");
|
|
fb.open(QIODevice::WriteOnly | QIODevice::Append); fb.write("more B\n"); fb.close();
|
|
const int histBBefore = git.fileHistory(vault, vault+"/other.md", 500).size();
|
|
if(!git.commitFile(vault, file, "final B")) return fail("commitFile B", git.lastError());
|
|
if(git.fileHistory(vault, file, 500).size() != histA + 1) return fail("commitFile history A");
|
|
if(git.fileHistory(vault, vault+"/other.md", 500).size() != histBBefore) return fail("commitFile затронул B");
|
|
if(!git.fileHistory(vault, file, 500).first().toMap().value("message").toString().contains("final B"))
|
|
return fail("commitFile message");
|
|
// commitFile без изменений — false с понятной ошибкой, как commitAll
|
|
if(git.commitFile(vault, file, "noop")) return fail("commitFile noop should fail");
|
|
}
|
|
|
|
std::printf("GIT HISTORY TEST OK (forward/backward, diff, restore, time)\n");
|
|
return 0;
|
|
}
|