feat(context): add repository item menus and submodule updates
This commit is contained in:
@@ -212,3 +212,57 @@ bool GitManager::initializeRepository(RepositoryView& repository, const std::str
|
||||
bool GitManager::reload(RepositoryView& repository, std::string& error) {
|
||||
return repository.repo && loadRepositoryData(repository, error);
|
||||
}
|
||||
|
||||
bool GitManager::checkoutBranch(RepositoryView& repository, const std::string& branch, std::string& error) {
|
||||
git_reference* reference = nullptr;
|
||||
if (git_branch_lookup(&reference, repository.repo, branch.c_str(), GIT_BRANCH_LOCAL) != 0) {
|
||||
error = lastError("Unable to find branch");
|
||||
return false;
|
||||
}
|
||||
const char* reference_name = git_reference_name(reference);
|
||||
const int set_head_result = git_repository_set_head(repository.repo, reference_name);
|
||||
git_reference_free(reference);
|
||||
if (set_head_result != 0) {
|
||||
error = lastError("Unable to switch HEAD");
|
||||
return false;
|
||||
}
|
||||
git_checkout_options options{};
|
||||
git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION);
|
||||
options.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
|
||||
if (git_checkout_head(repository.repo, &options) != 0) {
|
||||
error = lastError("Unable to checkout branch");
|
||||
return false;
|
||||
}
|
||||
return loadRepositoryData(repository, error);
|
||||
}
|
||||
|
||||
bool GitManager::updateSubmodule(RepositoryView& repository, const std::string& name, std::string& error) {
|
||||
git_submodule* submodule = nullptr;
|
||||
if (git_submodule_lookup(&submodule, repository.repo, name.c_str()) != 0) {
|
||||
error = lastError("Unable to find submodule");
|
||||
return false;
|
||||
}
|
||||
git_submodule_update_options options{};
|
||||
git_submodule_update_options_init(&options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION);
|
||||
options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
|
||||
const int result = git_submodule_update(submodule, 1, &options);
|
||||
git_submodule_free(submodule);
|
||||
if (result != 0) {
|
||||
error = lastError("Unable to update submodule");
|
||||
return false;
|
||||
}
|
||||
error = "Updated submodule " + name;
|
||||
return loadRepositoryData(repository, error);
|
||||
}
|
||||
|
||||
std::string GitManager::worktreePath(RepositoryView& repository, const std::string& name, std::string& error) {
|
||||
git_worktree* worktree = nullptr;
|
||||
if (git_worktree_lookup(&worktree, repository.repo, name.c_str()) != 0) {
|
||||
error = lastError("Unable to find worktree");
|
||||
return {};
|
||||
}
|
||||
const char* path = git_worktree_path(worktree);
|
||||
std::string result = path ? path : "";
|
||||
git_worktree_free(worktree);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ 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 checkoutBranch(RepositoryView& repository, const std::string& branch, std::string& error);
|
||||
bool updateSubmodule(RepositoryView& repository, const std::string& name, std::string& error);
|
||||
std::string worktreePath(RepositoryView& repository, const std::string& name, std::string& error);
|
||||
|
||||
private:
|
||||
static std::string lastError(const char* fallback);
|
||||
|
||||
@@ -180,12 +180,31 @@ bool sidebar_section_header(const char* label, int count, const char* add_toolti
|
||||
return open;
|
||||
}
|
||||
|
||||
enum class SidebarItemKind { worktree, submodule };
|
||||
|
||||
void sidebar_item_context(const std::string& item, SidebarItemKind kind) {
|
||||
if (!ImGui::BeginPopupContextItem()) return;
|
||||
if (kind == SidebarItemKind::submodule && ImGui::MenuItem(ICON_FA_DOWNLOAD " Pull / Update"))
|
||||
g_git_manager->updateSubmodule(repo(), item, g_notice);
|
||||
if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Open in file manager")) {
|
||||
std::string error;
|
||||
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;
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy name")) ImGui::SetClipboardText(item.c_str());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
void section(const char* label, const std::vector<std::string>& items, const char* item_icon,
|
||||
const char* add_tooltip, const char* add_notice) {
|
||||
const char* add_tooltip, const char* add_notice, SidebarItemKind kind) {
|
||||
const bool open = sidebar_section_header(label, static_cast<int>(items.size()), add_tooltip, add_notice);
|
||||
if (open) {
|
||||
for (const auto& item : items) {
|
||||
ImGui::Selectable((std::string(" ") + item_icon + " " + item).c_str());
|
||||
sidebar_item_context(item, kind);
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
@@ -211,8 +230,18 @@ void add_branch_node(BranchNode& root, const std::string& branch) {
|
||||
node->full_name = branch;
|
||||
}
|
||||
|
||||
void branch_context(const std::string& branch, bool remote) {
|
||||
if (!ImGui::BeginPopupContextItem()) return;
|
||||
if (!remote && ImGui::MenuItem(ICON_FA_CODE_BRANCH " Checkout"))
|
||||
g_git_manager->checkoutBranch(repo(), branch, g_notice);
|
||||
if (remote && ImGui::MenuItem(ICON_FA_DOWNLOAD " Fetch")) g_notice = "Remote fetch is not wired yet";
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy branch name")) ImGui::SetClipboardText(branch.c_str());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
void draw_branch_nodes(const BranchNode& parent, const char* branch_icon, const char* root_group_icon,
|
||||
const std::string& id_path = {}, int depth = 0) {
|
||||
bool remote, const std::string& id_path = {}, int depth = 0) {
|
||||
for (const auto& [name, node] : parent.children) {
|
||||
const std::string id = id_path + "/" + name;
|
||||
if (!node.children.empty()) {
|
||||
@@ -220,18 +249,22 @@ void draw_branch_nodes(const BranchNode& parent, const char* branch_icon, const
|
||||
const bool open = sidebar_collapse_row(id.c_str(), std::string(group_icon) + " " + name, true, 0);
|
||||
if (open) {
|
||||
ImGui::Indent(ui(17.0f));
|
||||
if (node.branch) ImGui::Selectable((std::string(branch_icon) + " " + name + "##branch" + id).c_str());
|
||||
draw_branch_nodes(node, branch_icon, root_group_icon, id, depth + 1);
|
||||
if (node.branch) {
|
||||
ImGui::Selectable((std::string(branch_icon) + " " + name + "##branch" + id).c_str());
|
||||
branch_context(node.full_name, remote);
|
||||
}
|
||||
draw_branch_nodes(node, branch_icon, root_group_icon, remote, id, depth + 1);
|
||||
ImGui::Unindent(ui(17.0f));
|
||||
}
|
||||
} else {
|
||||
ImGui::Selectable((std::string(branch_icon) + " " + name + "##" + id).c_str());
|
||||
branch_context(node.full_name, remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void branch_section(const char* label, const std::vector<std::string>& branches, const char* branch_icon,
|
||||
const char* group_icon, const char* add_tooltip, const char* add_notice) {
|
||||
const char* group_icon, const char* add_tooltip, const char* add_notice, bool remote) {
|
||||
const bool open = sidebar_section_header(label, static_cast<int>(branches.size()), add_tooltip, add_notice);
|
||||
if (open) {
|
||||
BranchNode root;
|
||||
@@ -240,7 +273,7 @@ void branch_section(const char* label, const std::vector<std::string>& branches,
|
||||
add_branch_node(root, branch);
|
||||
}
|
||||
ImGui::Indent(ui(17.0f));
|
||||
draw_branch_nodes(root, branch_icon, group_icon, label);
|
||||
draw_branch_nodes(root, branch_icon, group_icon, remote, label);
|
||||
ImGui::Unindent(ui(17.0f));
|
||||
}
|
||||
ImGui::Separator();
|
||||
@@ -256,13 +289,13 @@ void draw_sidebar(float width) {
|
||||
ImGui::InputTextWithHint("##filter", ICON_FA_MAGNIFYING_GLASS " Search branches...", g_filter.data(), g_filter.size());
|
||||
ImGui::Spacing();
|
||||
branch_section(ICON_FA_HOUSE " LOCAL", repo().local_branches, ICON_FA_CODE_BRANCH, ICON_FA_FOLDER,
|
||||
"Create local branch", "Branch creation is not wired yet");
|
||||
"Create local branch", "Branch creation is not wired yet", false);
|
||||
branch_section(ICON_FA_CLOUD " REMOTE", repo().remote_branches, ICON_FA_CODE_BRANCH, ICON_FA_GLOBE,
|
||||
"Add remote", "Remote creation is not wired yet");
|
||||
"Add remote", "Remote creation is not wired yet", true);
|
||||
section(ICON_FA_DIAGRAM_PROJECT " WORKTREES", repo().worktrees, ICON_FA_COMPUTER,
|
||||
"Add worktree", "Worktree creation is not wired yet");
|
||||
"Add worktree", "Worktree creation is not wired yet", SidebarItemKind::worktree);
|
||||
section(ICON_FA_CUBES " SUBMODULES", repo().submodules, ICON_FA_CUBES,
|
||||
"Add submodule", "Submodule creation is not wired yet");
|
||||
"Add submodule", "Submodule creation is not wired yet", SidebarItemKind::submodule);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -295,6 +328,12 @@ void draw_ref_badge(const RefBadge& badge, int index, int lane) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(6.0f), ui(1.5f)});
|
||||
const std::string id = text + "##ref_badge_" + std::to_string(index);
|
||||
ImGui::Button(id.c_str());
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (badge.kind == RefKind::local && ImGui::MenuItem(ICON_FA_CODE_BRANCH " Checkout"))
|
||||
g_git_manager->checkoutBranch(repo(), badge.name, g_notice);
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy ref name")) ImGui::SetClipboardText(badge.name.c_str());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
@@ -366,6 +405,19 @@ void draw_commit_table() {
|
||||
std::string row_id = "##commit" + std::to_string(i);
|
||||
if (ImGui::Selectable(row_id.c_str(), repo().selected_commit == i,
|
||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) repo().selected_commit = i;
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy commit hash")) {
|
||||
char oid[GIT_OID_SHA1_HEXSIZE + 1]{};
|
||||
git_oid_tostr(oid, sizeof(oid), &commit.oid);
|
||||
ImGui::SetClipboardText(oid);
|
||||
}
|
||||
if (ImGui::MenuItem(ICON_FA_COPY " Copy commit message"))
|
||||
ImGui::SetClipboardText(commit.summary.c_str());
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(ICON_FA_TAG " Create tag here")) g_notice = "Tag creation is not wired yet";
|
||||
if (ImGui::MenuItem(ICON_FA_CODE_BRANCH " Create branch here")) g_notice = "Branch creation is not wired yet";
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine(0, ui(3));
|
||||
for (int ref_index = 0; ref_index < static_cast<int>(commit.refs.size()); ++ref_index) {
|
||||
if (ref_index > 0) ImGui::SameLine(0, ui(4));
|
||||
@@ -554,6 +606,15 @@ void draw_new_tab() {
|
||||
ImGuiSelectableFlags_AllowDoubleClick, {0, ui(42)})) {
|
||||
open_repository(recent[i].c_str());
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
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 (ImGui::MenuItem(ICON_FA_COPY " Copy path")) ImGui::SetClipboardText(recent[i].c_str());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine(ui(260));
|
||||
ImGui::TextDisabled("%s", recent[i].c_str());
|
||||
ImGui::PopID();
|
||||
@@ -598,6 +659,17 @@ void draw_app() {
|
||||
g_active_tab = i;
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
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 (!g_tabs[i]->path.empty() && ImGui::MenuItem(ICON_FA_COPY " Copy repository path"))
|
||||
ImGui::SetClipboardText(g_tabs[i]->path.c_str());
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(ICON_FA_XMARK " Close tab")) tab_to_close = i;
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (!open) tab_to_close = i;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
Reference in New Issue
Block a user