Merge branch 'main' into prod
All checks were successful
Build Windows EXE / build-windows (push) Successful in 5m43s
All checks were successful
Build Windows EXE / build-windows (push) Successful in 5m43s
This commit is contained in:
BIN
assets/gitree_logo.png
Normal file
BIN
assets/gitree_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 794 KiB |
BIN
assets/gitree_logo_no_bg.png
Normal file
BIN
assets/gitree_logo_no_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <IconsFontAwesome6.h>
|
||||
#include <imgui.h>
|
||||
#include <izo/dialogs.hpp>
|
||||
#include <izo/Dialogs.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -216,7 +216,7 @@ void DiffViewer::draw(RepositoryView& repository, GitManager& manager, float sca
|
||||
if (!path_.empty()) {
|
||||
if (compactButton(ICON_FA_PEN " Edit This File")) {
|
||||
std::string error;
|
||||
if (!izo::open_path(std::filesystem::path(repository.path) / path_, &error)) notice = error;
|
||||
if (!izo::OpenPath(std::filesystem::path(repository.path) / path_, &error)) notice = error;
|
||||
}
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - scaled(116, scale));
|
||||
ImGui::TextDisabled("UTF-8");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <izo/dialogs.hpp>
|
||||
#include <izo/Dialogs.hpp>
|
||||
#include <IconsFontAwesome6.h>
|
||||
#include "ui/gitree_ui.h"
|
||||
#include "managers/avatar_cache.h"
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
@@ -107,15 +108,15 @@ bool open_repository(const char* path) {
|
||||
}
|
||||
|
||||
void pick_and_open_repository() {
|
||||
izo::dialog_options options;
|
||||
izo::DialogOptions options;
|
||||
options.title = "Open Git repository";
|
||||
if (!repo().path.empty()) options.initial_path = repo().path;
|
||||
else options.initial_path = std::filesystem::current_path();
|
||||
if (!repo().path.empty()) options.initialPath = repo().path;
|
||||
else options.initialPath = std::filesystem::current_path();
|
||||
|
||||
const izo::dialog_result result = izo::pick_folder(options);
|
||||
if (result.status == izo::dialog_status::cancelled) return;
|
||||
if (result.status == izo::dialog_status::error) {
|
||||
g_notice = result.error_message.empty() ? "Unable to open the folder picker" : result.error_message;
|
||||
const izo::DialogResult result = izo::PickFolder(options);
|
||||
if (result.status == izo::DialogStatus::Cancelled) return;
|
||||
if (result.status == izo::DialogStatus::Error) {
|
||||
g_notice = result.errorMessage.empty() ? "Unable to open the folder picker" : result.errorMessage;
|
||||
return;
|
||||
}
|
||||
if (result.paths.empty()) {
|
||||
@@ -355,7 +356,7 @@ void sidebar_item_context(const std::string& item, SidebarItemKind kind) {
|
||||
const std::filesystem::path path = kind == SidebarItemKind::worktree
|
||||
? g_git_manager->worktreePath(repo(), item, error)
|
||||
: std::filesystem::path(repo().path) / item;
|
||||
if (path.empty() || !izo::open_path(path, &error)) g_notice = error;
|
||||
if (path.empty() || !izo::OpenPath(path, &error)) g_notice = error;
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy name")) ImGui::SetClipboardText(item.c_str());
|
||||
@@ -594,10 +595,10 @@ void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
const ImVec2 minimum = ImGui::GetItemRectMin();
|
||||
const ImVec2 maximum = ImGui::GetItemRectMax();
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
ImU32 background = GraphRenderer::laneColor(lane, badge.current ? 205 : 135);
|
||||
if (ImGui::IsItemHovered()) background = GraphRenderer::laneColor(lane, badge.current ? 235 : 175);
|
||||
ImU32 background = GraphRenderer::laneColor(lane, badge.current ? 165 : 90);
|
||||
if (ImGui::IsItemHovered()) background = GraphRenderer::laneColor(lane, badge.current ? 195 : 125);
|
||||
draw->AddRectFilled(minimum, maximum, background);
|
||||
draw->AddRect(minimum, maximum, GraphRenderer::laneColor(lane, 220));
|
||||
draw->AddRect(minimum, maximum, GraphRenderer::laneColor(lane, badge.current ? 210 : 145));
|
||||
|
||||
float x = minimum.x + ui(6.0f);
|
||||
const float text_y = minimum.y + (chip_size.y - ImGui::GetFontSize()) * 0.5f;
|
||||
@@ -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,
|
||||
@@ -741,6 +759,12 @@ void draw_commit_table() {
|
||||
float chip_x = reference_origin.x + ui(3.0f);
|
||||
float chip_y = reference_origin.y + ui(0.5f);
|
||||
const float chip_right = reference_origin.x + chip_line_width;
|
||||
if (!commit.refs.empty()) {
|
||||
const float connector_y = reference_origin.y + row_height * 0.5f;
|
||||
ImGui::GetWindowDrawList()->AddLine(
|
||||
{reference_origin.x, connector_y}, {chip_right + ui(6.0f), connector_y},
|
||||
GraphRenderer::laneColor(commit.lane, 120), ui(1.0f));
|
||||
}
|
||||
for (int ref_index = 0; ref_index < static_cast<int>(commit.refs.size()); ++ref_index) {
|
||||
const float badge_width = ref_badge_width(commit.refs[ref_index]);
|
||||
if (chip_x > reference_origin.x + ui(3.0f) && chip_x + badge_width > chip_right) {
|
||||
@@ -752,14 +776,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) {
|
||||
@@ -1210,7 +1237,7 @@ void external_link(const char* label, const char* url) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.31f, 0.76f, 0.92f, 1.0f));
|
||||
if (ImGui::Selectable(label, false, ImGuiSelectableFlags_DontClosePopups)) {
|
||||
std::string error;
|
||||
if (!izo::open_path(url, &error)) g_notice = error.empty() ? "Unable to open link" : error;
|
||||
if (!izo::OpenPath(url, &error)) g_notice = error.empty() ? "Unable to open link" : error;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
@@ -1491,7 +1518,7 @@ void draw_new_tab() {
|
||||
if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Open repository")) open_repository(recent[i].c_str());
|
||||
if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Reveal in file manager")) {
|
||||
std::string error;
|
||||
if (!izo::reveal_in_file_manager(recent[i], &error)) g_notice = error;
|
||||
if (!izo::RevealInFileManager(recent[i], &error)) g_notice = error;
|
||||
}
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy path")) ImGui::SetClipboardText(recent[i].c_str());
|
||||
ImGui::EndPopup();
|
||||
@@ -1571,7 +1598,7 @@ void draw_app() {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (!g_tabs[i]->path.empty() && ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Reveal in file manager")) {
|
||||
std::string error;
|
||||
if (!izo::reveal_in_file_manager(g_tabs[i]->path, &error)) g_notice = error;
|
||||
if (!izo::RevealInFileManager(g_tabs[i]->path, &error)) g_notice = error;
|
||||
}
|
||||
if (!g_tabs[i]->path.empty() && ImGui::MenuItem(ICON_FA_COPY " Copy repository path"))
|
||||
ImGui::SetClipboardText(g_tabs[i]->path.c_str());
|
||||
|
||||
@@ -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)];
|
||||
@@ -38,8 +38,23 @@ void GraphRenderer::drawRow(int row, const CommitInfo& commit,
|
||||
const float lane_spacing = px(18.0f);
|
||||
const float x = origin.x + px(15.0f) + lane_spacing * commit.lane;
|
||||
const float y = origin.y + content_height * 0.5f;
|
||||
const float cell_right = origin.x + ImGui::GetContentRegionAvail().x;
|
||||
const float row_clip_padding = ImGui::GetStyle().CellPadding.y + px(1.0f);
|
||||
|
||||
// GitKraken-style lane ribbon: a quiet tint carries the branch color through
|
||||
// the rest of the graph column while the far edge remains crisply identifiable.
|
||||
draw->AddRectFilled(
|
||||
{x, origin.y - row_clip_padding},
|
||||
{cell_right, origin.y + content_height + row_clip_padding},
|
||||
laneColor(commit.lane, 38));
|
||||
draw->AddLine(
|
||||
{cell_right - px(1.0f), origin.y - row_clip_padding},
|
||||
{cell_right - px(1.0f), origin.y + content_height + row_clip_padding},
|
||||
laneColor(commit.lane, 220), px(2.0f));
|
||||
if (!commit.refs.empty())
|
||||
draw->AddLine({origin.x - ImGui::GetStyle().CellPadding.x, y}, {x, y},
|
||||
laneColor(commit.lane, 150), px(1.0f));
|
||||
|
||||
std::vector<float> row_offsets(row_heights.size() + 1, 0.0f);
|
||||
for (size_t index = 0; index < row_heights.size(); ++index)
|
||||
row_offsets[index + 1] = row_offsets[index] + row_heights[index];
|
||||
@@ -57,23 +72,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;
|
||||
}
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
2
vendor/iZo
vendored
2
vendor/iZo
vendored
Submodule vendor/iZo updated: 88eb3ced5b...80c6bfce90
Reference in New Issue
Block a user