From 0ad8ee0fc273764cc7478fb25976f286cce2964f Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 19:51:47 -0500 Subject: [PATCH] perf(history): stream commit pages on demand --- src/managers/git_manager.cpp | 40 ++++++++++++++++++++++++++++++------ src/managers/git_manager.h | 1 + src/models/repository.h | 4 ++++ src/ui/gitree_ui.cpp | 23 ++++++++++++++++++++- src/ui/graph_renderer.cpp | 14 +++++-------- src/ui/graph_renderer.h | 3 ++- 6 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/managers/git_manager.cpp b/src/managers/git_manager.cpp index 5d44853..9538083 100644 --- a/src/managers/git_manager.cpp +++ b/src/managers/git_manager.cpp @@ -110,6 +110,7 @@ void GitManager::readBranches(RepositoryView& repository, git_branch_t type, } void GitManager::loadRefBadges(RepositoryView& repository) { + for (CommitInfo& commit : repository.commits) commit.refs.clear(); for (const git_branch_t type : {GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE}) { git_branch_iterator* iterator = nullptr; if (git_branch_iterator_new(&iterator, repository.repo, type) != 0) continue; @@ -222,6 +223,9 @@ bool GitManager::loadRepositoryData(RepositoryView& repository, std::string& err repository.local_branches.clear(); repository.remote_branches.clear(); repository.remotes.clear(); repository.worktrees.clear(); repository.worktree_branches.clear(); repository.submodules.clear(); repository.commits.clear(); repository.selected_commit = 0; + if (repository.commit_walk) git_revwalk_free(repository.commit_walk); + repository.commit_walk = nullptr; + repository.history_exhausted = false; loadWorkingTree(repository); const char* workdir = git_repository_workdir(repository.repo); repository.path = workdir ? workdir : git_repository_path(repository.repo); @@ -262,12 +266,36 @@ bool GitManager::loadRepositoryData(RepositoryView& repository, std::string& err } git_submodule_foreach(repository.repo, submoduleCallback, &repository.submodules); - git_revwalk* walk = nullptr; - if (git_revwalk_new(&walk, repository.repo) != 0) { error = lastError("Unable to read history"); return false; } - git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME); - if (git_revwalk_push_head(walk) != 0) { git_revwalk_free(walk); return true; } + if (git_revwalk_new(&repository.commit_walk, repository.repo) != 0) { + error = lastError("Unable to read history"); + return false; + } + git_revwalk_sorting(repository.commit_walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME); + if (git_revwalk_push_head(repository.commit_walk) != 0) { + git_revwalk_free(repository.commit_walk); + repository.commit_walk = nullptr; + repository.history_exhausted = true; + return true; + } + return loadMoreCommits(repository, 500, error); +} + +bool GitManager::loadMoreCommits(RepositoryView& repository, size_t page_size, std::string& error) { + if (!repository.commit_walk || repository.history_exhausted || page_size == 0) return true; + size_t loaded = 0; git_oid oid{}; - while (repository.commits.size() < 250 && git_revwalk_next(&oid, walk) == 0) { + while (loaded < page_size) { + const int walk_result = git_revwalk_next(&oid, repository.commit_walk); + if (walk_result == GIT_ITEROVER) { + repository.history_exhausted = true; + git_revwalk_free(repository.commit_walk); + repository.commit_walk = nullptr; + break; + } + if (walk_result != 0) { + error = lastError("Unable to continue reading history"); + return false; + } git_commit* commit = nullptr; if (git_commit_lookup(&commit, repository.repo, &oid) != 0) continue; CommitInfo item; item.oid = oid; @@ -283,8 +311,8 @@ bool GitManager::loadRepositoryData(RepositoryView& repository, std::string& err if (const git_oid* parent = git_commit_parent_id(commit, static_cast(i))) item.parent_ids.push_back(*parent); repository.commits.push_back(std::move(item)); git_commit_free(commit); + ++loaded; } - git_revwalk_free(walk); computeGraphLanes(repository); loadRefBadges(repository); return true; diff --git a/src/managers/git_manager.h b/src/managers/git_manager.h index 7ed905b..dd9007f 100644 --- a/src/managers/git_manager.h +++ b/src/managers/git_manager.h @@ -12,6 +12,7 @@ public: bool openRepository(RepositoryView& repository, const std::string& path, std::string& error); bool initializeRepository(RepositoryView& repository, const std::string& path, std::string& error); bool reload(RepositoryView& repository, std::string& error); + bool loadMoreCommits(RepositoryView& repository, size_t page_size, std::string& error); bool checkoutBranch(RepositoryView& repository, const std::string& branch, std::string& error); bool fetch(RepositoryView& repository, const std::string& remote, std::string& error); bool pull(RepositoryView& repository, int mode, std::string& error); diff --git a/src/models/repository.h b/src/models/repository.h index 5fc6893..e3426de 100644 --- a/src/models/repository.h +++ b/src/models/repository.h @@ -45,6 +45,8 @@ struct CommitInfo { struct RepositoryView { git_repository* repo = nullptr; + git_revwalk* commit_walk = nullptr; + bool history_exhausted = false; std::string path; std::string name = "New Tab"; std::string branch = "detached"; @@ -64,6 +66,8 @@ struct RepositoryView { RepositoryView& operator=(const RepositoryView&) = delete; void close() { + if (commit_walk) git_revwalk_free(commit_walk); + commit_walk = nullptr; if (repo) git_repository_free(repo); repo = nullptr; } diff --git a/src/ui/gitree_ui.cpp b/src/ui/gitree_ui.cpp index 9397916..bfe8956 100644 --- a/src/ui/gitree_ui.cpp +++ b/src/ui/gitree_ui.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace { @@ -647,6 +648,22 @@ void draw_commit_table() { widest_reference_row + ui(12.0f), ui(145.0f), maximum_reference_width); const float chip_line_width = std::max(ui(40.0f), reference_width - ui(12.0f)); std::vector row_heights(repo().commits.size(), 0.0f); + std::unordered_map commit_rows; + commit_rows.reserve(repo().commits.size()); + for (int index = 0; index < static_cast(repo().commits.size()); ++index) { + char oid[GIT_OID_SHA1_HEXSIZE + 1]{}; + git_oid_tostr(oid, sizeof(oid), &repo().commits[static_cast(index)].oid); + commit_rows.emplace(oid, index); + } + std::vector> parent_rows(repo().commits.size()); + for (size_t index = 0; index < repo().commits.size(); ++index) { + for (const git_oid& parent_oid : repo().commits[index].parent_ids) { + char oid[GIT_OID_SHA1_HEXSIZE + 1]{}; + git_oid_tostr(oid, sizeof(oid), &parent_oid); + const auto parent = commit_rows.find(oid); + if (parent != commit_rows.end()) parent_rows[index].push_back(parent->second); + } + } for (size_t index = 0; index < repo().commits.size(); ++index) { const CommitInfo& commit = repo().commits[index]; if (!commit_visible(commit)) continue; @@ -711,6 +728,7 @@ void draw_commit_table() { const float row_height = row_heights[static_cast(i)]; ImGui::TableNextRow(0, row_height); ImGui::TableSetColumnIndex(0); + if (!ImGui::IsRectVisible({ui(1.0f), row_height})) continue; const ImVec2 reference_origin = ImGui::GetCursorScreenPos(); std::string row_id = "##commit" + std::to_string(i); if (ImGui::Selectable(row_id.c_str(), repo().selected_commit == i, @@ -752,14 +770,17 @@ void draw_commit_table() { chip_x += badge_width + ui(4.0f); } ImGui::TableSetColumnIndex(1); - graph.drawRow(i, commit, repo().commits, row_heights, g_avatar_cache); + graph.drawRow(i, commit, repo().commits, row_heights, parent_rows, g_avatar_cache); ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(commit.summary.c_str()); ImGui::TableSetColumnIndex(3); ImGui::TextDisabled("%s", commit.date.c_str()); } + const bool load_more_history = !repo().history_exhausted && repo().commit_walk && + ImGui::GetScrollMaxY() - ImGui::GetScrollY() < ImGui::GetWindowHeight() * 1.5f; ImGui::EndTable(); ImGui::PopStyleVar(); + if (load_more_history) g_git_manager->loadMoreCommits(repo(), 500, g_notice); } const char* change_icon(FileChangeKind kind) { diff --git a/src/ui/graph_renderer.cpp b/src/ui/graph_renderer.cpp index 35a4e07..6ce89b4 100644 --- a/src/ui/graph_renderer.cpp +++ b/src/ui/graph_renderer.cpp @@ -30,7 +30,7 @@ float GraphRenderer::requiredWidth(const std::vector& commits, float void GraphRenderer::drawRow(int row, const CommitInfo& commit, const std::vector& commits, const std::vector& row_heights, - AvatarCache* avatars) const { + const std::vector>& parent_rows, AvatarCache* avatars) const { ImDrawList* draw = ImGui::GetWindowDrawList(); const ImVec2 origin = ImGui::GetCursorScreenPos(); const float row_height = row_heights[static_cast(row)]; @@ -57,23 +57,19 @@ void GraphRenderer::drawRow(int row, const CommitInfo& commit, for (int child_row = 0; child_row < static_cast(commits.size()); ++child_row) { if (row_heights[static_cast(child_row)] <= 0.0f) continue; const CommitInfo& child = commits[static_cast(child_row)]; - for (const git_oid& parent_id : child.parent_ids) { - const auto parent = std::find_if(commits.begin(), commits.end(), [&parent_id](const CommitInfo& item) { - return git_oid_equal(&item.oid, &parent_id) != 0; - }); - if (parent == commits.end()) continue; - const int parent_row = static_cast(std::distance(commits.begin(), parent)); + for (const int parent_row : parent_rows[static_cast(child_row)]) { if (parent_row <= child_row || row < child_row || row > parent_row || row_heights[static_cast(parent_row)] <= 0.0f) continue; + const CommitInfo& parent = commits[static_cast(parent_row)]; const float child_x = origin.x + px(15.0f) + lane_spacing * child.lane; - const float parent_x = origin.x + px(15.0f) + lane_spacing * parent->lane; + const float parent_x = origin.x + px(15.0f) + lane_spacing * parent.lane; const float child_y = center_y(child_row); const float parent_y = center_y(parent_row); // An edge belongs to the branch it leaves, including the curved transition // into a parent lane, so its color stays consistent with the child node/ref chip. const ImU32 color = laneColor(child.lane, 225); - if (child.lane == parent->lane) { + if (child.lane == parent.lane) { draw->AddLine({child_x, child_y}, {parent_x, parent_y}, color, px(1.8f)); continue; } diff --git a/src/ui/graph_renderer.h b/src/ui/graph_renderer.h index f2d88cc..e0935aa 100644 --- a/src/ui/graph_renderer.h +++ b/src/ui/graph_renderer.h @@ -14,7 +14,8 @@ public: static float requiredWidth(const std::vector& commits, float scale); void drawRow(int row, const CommitInfo& commit, const std::vector& commits, - const std::vector& row_heights, AvatarCache* avatars) const; + const std::vector& row_heights, const std::vector>& parent_rows, + AvatarCache* avatars) const; private: float px(float value) const { return value * scale_; }