perf(history): stream commit pages on demand

This commit is contained in:
2026-06-18 19:51:47 -05:00
parent cb50b61e75
commit 0ad8ee0fc2
6 changed files with 68 additions and 17 deletions

View File

@@ -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<unsigned int>(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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -25,6 +25,7 @@
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
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<float> row_heights(repo().commits.size(), 0.0f);
std::unordered_map<std::string, int> commit_rows;
commit_rows.reserve(repo().commits.size());
for (int index = 0; index < static_cast<int>(repo().commits.size()); ++index) {
char oid[GIT_OID_SHA1_HEXSIZE + 1]{};
git_oid_tostr(oid, sizeof(oid), &repo().commits[static_cast<size_t>(index)].oid);
commit_rows.emplace(oid, index);
}
std::vector<std::vector<int>> 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<size_t>(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) {

View File

@@ -30,7 +30,7 @@ float GraphRenderer::requiredWidth(const std::vector<CommitInfo>& commits, float
void GraphRenderer::drawRow(int row, const CommitInfo& commit,
const std::vector<CommitInfo>& commits, const std::vector<float>& row_heights,
AvatarCache* avatars) const {
const std::vector<std::vector<int>>& parent_rows, AvatarCache* avatars) const {
ImDrawList* draw = ImGui::GetWindowDrawList();
const ImVec2 origin = ImGui::GetCursorScreenPos();
const float row_height = row_heights[static_cast<size_t>(row)];
@@ -57,23 +57,19 @@ void GraphRenderer::drawRow(int row, const CommitInfo& commit,
for (int child_row = 0; child_row < static_cast<int>(commits.size()); ++child_row) {
if (row_heights[static_cast<size_t>(child_row)] <= 0.0f) continue;
const CommitInfo& child = commits[static_cast<size_t>(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<int>(std::distance(commits.begin(), parent));
for (const int parent_row : parent_rows[static_cast<size_t>(child_row)]) {
if (parent_row <= child_row || row < child_row || row > parent_row ||
row_heights[static_cast<size_t>(parent_row)] <= 0.0f) continue;
const CommitInfo& parent = commits[static_cast<size_t>(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;
}

View File

@@ -14,7 +14,8 @@ public:
static float requiredWidth(const std::vector<CommitInfo>& commits, float scale);
void drawRow(int row, const CommitInfo& commit, const std::vector<CommitInfo>& commits,
const std::vector<float>& row_heights, AvatarCache* avatars) const;
const std::vector<float>& row_heights, const std::vector<std::vector<int>>& parent_rows,
AvatarCache* avatars) const;
private:
float px(float value) const { return value * scale_; }