Git: кэш+пагинация истории, diff в потоке, точечный коммит, честное удаление
- История: кэш по vault|rel|headOid, fileHistory(vault,file,limit), в диалоге Показать ещё по 60 с сохранением выбора - Diff считается в QtConcurrent-пуле (parseBlocksWithDiffAsync+blocksReady, stale-guard), спиннер busy; MarkdownView.diffApplied - commitFile точечно вместо всего хранилища (общий commitFromIndex), autoCommit честный; fileExistedAt + willDelete в подтверждении - DocumentManager.readTextFile общий (GitManager.workdirContent через него); fileGitStatus через relativePath - Тесты: пагинация/кэш, existed, commitFile не трогает B, async==sync
This commit is contained in:
+10
-2
@@ -13,6 +13,8 @@ add_test(NAME flora_smoke COMMAND flora_smoke)
|
||||
add_executable(flora_git_test git_test.cpp
|
||||
../src/git/GitManager.cpp
|
||||
../src/git/GitManager.h
|
||||
../src/core/DocumentManager.cpp
|
||||
../src/core/DocumentManager.h
|
||||
)
|
||||
target_include_directories(flora_git_test PRIVATE ../src)
|
||||
target_compile_definitions(flora_git_test PRIVATE CMARK_GFM_STATIC_DEFINE)
|
||||
@@ -27,6 +29,8 @@ add_executable(flora_vault_test vault_test.cpp
|
||||
../src/app/VaultManager.h
|
||||
../src/core/SettingsStore.cpp
|
||||
../src/core/SettingsStore.h
|
||||
../src/core/DocumentManager.cpp
|
||||
../src/core/DocumentManager.h
|
||||
../src/git/GitManager.cpp
|
||||
../src/git/GitManager.h
|
||||
)
|
||||
@@ -68,6 +72,7 @@ target_compile_definitions(flora_mdparser_test PRIVATE
|
||||
target_link_libraries(flora_mdparser_test PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Concurrent
|
||||
cmark::cmark
|
||||
cmark::cmark_extensions
|
||||
)
|
||||
@@ -119,7 +124,7 @@ add_executable(flora_extended_test extended_test.cpp
|
||||
)
|
||||
target_include_directories(flora_extended_test PRIVATE ../src)
|
||||
target_compile_definitions(flora_extended_test PRIVATE CMARK_GFM_STATIC_DEFINE CMARK_GFM_EXTENSIONS_STATIC_DEFINE)
|
||||
target_link_libraries(flora_extended_test PRIVATE Qt6::Core Qt6::Gui cmark::cmark cmark::cmark_extensions Flora_libgit2_static)
|
||||
target_link_libraries(flora_extended_test PRIVATE Qt6::Core Qt6::Gui Qt6::Concurrent cmark::cmark cmark::cmark_extensions Flora_libgit2_static)
|
||||
if(TARGET KF6SyntaxHighlighting)
|
||||
target_link_libraries(flora_extended_test PRIVATE KF6SyntaxHighlighting)
|
||||
endif()
|
||||
@@ -147,7 +152,7 @@ add_executable(flora_integration_test integration_test.cpp
|
||||
)
|
||||
target_include_directories(flora_integration_test PRIVATE ../src)
|
||||
target_compile_definitions(flora_integration_test PRIVATE CMARK_GFM_STATIC_DEFINE CMARK_GFM_EXTENSIONS_STATIC_DEFINE)
|
||||
target_link_libraries(flora_integration_test PRIVATE Qt6::Core Qt6::Gui cmark::cmark cmark::cmark_extensions Flora_libgit2_static)
|
||||
target_link_libraries(flora_integration_test PRIVATE Qt6::Core Qt6::Gui Qt6::Concurrent cmark::cmark cmark::cmark_extensions Flora_libgit2_static)
|
||||
if(TARGET KF6SyntaxHighlighting)
|
||||
target_link_libraries(flora_integration_test PRIVATE KF6SyntaxHighlighting)
|
||||
endif()
|
||||
@@ -157,6 +162,8 @@ add_test(NAME flora_integration_test COMMAND flora_integration_test)
|
||||
add_executable(flora_git_history_test git_history_test.cpp
|
||||
../src/git/GitManager.cpp
|
||||
../src/git/GitManager.h
|
||||
../src/core/DocumentManager.cpp
|
||||
../src/core/DocumentManager.h
|
||||
)
|
||||
target_include_directories(flora_git_history_test PRIVATE ../src)
|
||||
target_link_libraries(flora_git_history_test PRIVATE Qt6::Core Flora_libgit2_static)
|
||||
@@ -166,6 +173,7 @@ add_test(NAME flora_git_history_test COMMAND flora_git_history_test)
|
||||
add_executable(flora_vault_select_test vault_select_test.cpp
|
||||
../src/app/VaultManager.cpp
|
||||
../src/core/SettingsStore.cpp
|
||||
../src/core/DocumentManager.cpp
|
||||
../src/git/GitManager.cpp
|
||||
)
|
||||
target_include_directories(flora_vault_select_test PRIVATE ../src)
|
||||
|
||||
@@ -154,6 +154,62 @@ int main(int argc, char *argv[])
|
||||
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);
|
||||
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).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).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").size();
|
||||
if(!git.commitFile(vault, file, "final B")) return fail("commitFile B", git.lastError());
|
||||
if(git.fileHistory(vault, file).size() != histA + 1) return fail("commitFile history A");
|
||||
if(git.fileHistory(vault, vault+"/other.md").size() != histBBefore) return fail("commitFile затронул B");
|
||||
if(!git.fileHistory(vault, file).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;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QTemporaryDir>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
#include <cstdio>
|
||||
|
||||
#include "markdown/MdParser.h"
|
||||
@@ -201,6 +203,37 @@ int main(int argc, char *argv[])
|
||||
return fail("text add без зелёного");
|
||||
}
|
||||
|
||||
// parseBlocksWithDiffAsync: тот же результат, что sync, id совпадает
|
||||
{
|
||||
const QString oldMd = "# T\n\n```cpp\nint a = 1;\n```\n";
|
||||
const QString newMd = "# T\n\n```cpp\nint a = 2;\n```\n";
|
||||
QVariantList expect = p.parseBlocksWithDiff(oldMd, newMd, vault, fileDir);
|
||||
const int reqId = p.parseBlocksWithDiffAsync(oldMd, newMd, vault, fileDir);
|
||||
QVariantList got;
|
||||
int gotId = -1;
|
||||
QObject::connect(&p, &MdParser::blocksReady,
|
||||
[&](int id, const QVariantList &b) { gotId = id; got = b; });
|
||||
QEventLoop loop;
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
QObject::connect(&p, &MdParser::blocksReady, &loop, &QEventLoop::quit);
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timer.start(10000);
|
||||
loop.exec();
|
||||
if (gotId != reqId) return fail("async id", QString::number(gotId));
|
||||
if (got.size() != expect.size()) return fail("async размер", QString::number(got.size()));
|
||||
for (int i = 0; i < got.size(); ++i) {
|
||||
const QVariantMap g = got[i].toMap(), e = expect[i].toMap();
|
||||
if (g.value("type") != e.value("type") || g.value("change") != e.value("change"))
|
||||
return fail("async блок", QString::number(i));
|
||||
if (g.value("type").toString() == "code") {
|
||||
// копирование работает и после async (id свежие, код — новый)
|
||||
if (!p.getCodeBlock(g.value("codeId").toString()).contains("int a = 2;"))
|
||||
return fail("async copy", p.getCodeBlock(g.value("codeId").toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseBlocksWithDiff: правка внутри кода — ОДИН блок с построчным diff внутри
|
||||
{
|
||||
const QString oldMd = "# T\n\n```cpp\nint a = 1;\nint b = 2;\n```\n";
|
||||
|
||||
Reference in New Issue
Block a user