// Тест GitManager истории: вперёд/назад, diff, восстановление, время #include #include #include #include #include #include #include #include #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; }