TODO 异步和错误处理

This commit is contained in:
daiqingshuang
2025-05-22 18:36:55 +08:00
parent 0737703d6f
commit bf97b2cbe0
10 changed files with 155 additions and 93 deletions

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16...3.31)
project(branch_switcher)
include(cmake/project_cpp_standard.cmake)
set_cpp_standard(20)
set_cpp_standard(23)
# 如果需要,手动指定 wxWidgets 路径
# set(wxWidgets_ROOT_DIR "D:/Projects/vcpkg/installed/x64-mingw-dynamic/lib")

View File

@@ -1,5 +1,6 @@
#include "branch_selector.h"
#include "config_manager.h"
#include "task_manager.h"
wxBEGIN_EVENT_TABLE(BranchSelector, wxPanel)
EVT_COMBOBOX(wxID_ANY, BranchSelector::OnBranchSelected)
@@ -13,19 +14,24 @@ BranchSelector::BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository
// 如果没有提供标签,使用存储库名称
wxString display_label = label.IsEmpty() ? wxString(repo->GetName()) : label;
wxStaticText* label_ctrl = new wxStaticText(this, wxID_ANY, display_label);
label_ctrl = new wxStaticText(this, wxID_ANY, display_label);
// 创建下拉列表
branch_combo_ = new wxComboBox(this, wxID_ANY);
// 填充分支列表
const auto& branches = repo->GetAllBranches();
for (const auto& branch : branches) {
for (const auto& branch : branches.value()) {
branch_combo_->Append(branch);
}
auto result = repo->GetCurrentBranchName();
// 选择当前分支
branch_combo_->SetStringSelection(repo->GetCurrentBranchName());
if (!result.has_value()) {
ProcessResult(result);
return;
}
branch_combo_->SetStringSelection(result.value());
// 布局
hbox->Add(label_ctrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
@@ -77,13 +83,18 @@ bool BranchSelector::ApplyBranchChange() {
}
// 切换分支
bool success = repo_->SwitchBranch(selected_branch);
if (!success) {
auto result = repo_->SwitchBranch(selected_branch);
if (!result.has_value()) {
return false;
}
// 拉取更新
return repo_->Pull();
TaskManager::Get().PushGitTask([this]{ return repo_->Pull(); },
[this](auto in_result){
ProcessResult(in_result);
});
repo_->Pull();
return true;
}
void BranchSelector::OnBranchSelected(wxCommandEvent& event) {

View File

@@ -13,13 +13,25 @@ public:
std::string GetSelectedBranchName() const;
bool ApplyBranchChange();
bool HardReset() const { return repo_->HardReset(); }
bool HardReset() const { return repo_->HardReset().has_value(); }
std::shared_ptr<git::Repository> GetRepo() const { return repo_; }
template<typename Value, typename ErrMsg>
void ProcessResult(const std::expected<Value, ErrMsg>& in_expected) {
if (!in_expected.has_value()) {
label_ctrl->SetForegroundColour(*wxRED);
label_ctrl->SetToolTip(in_expected.error());
} else {
label_ctrl->SetForegroundColour(*wxBLACK);
label_ctrl->SetToolTip(wxEmptyString);
}
}
private:
void OnBranchSelected(wxCommandEvent& event);
wxComboBox* branch_combo_ = nullptr;
wxStaticText* label_ctrl = nullptr;
std::shared_ptr<git::Repository> repo_;
wxString current_group_;

View File

@@ -4,7 +4,7 @@
#include "git_command.h"
std::string ExecGitCommand(const std::string& cmd, const std::filesystem::path& path) {
std::expected<std::string, std::string> ExecGitCommand(const std::string& cmd, const std::filesystem::path& path) {
std::string fullCmd = "git " + cmd;
std::string result;
@@ -19,7 +19,7 @@ std::string ExecGitCommand(const std::string& cmd, const std::filesystem::path&
#endif
if (!pipe) {
return "执行命令失败";
return std::unexpected("执行命令失败");
}
char buffer[128];
@@ -34,6 +34,10 @@ std::string ExecGitCommand(const std::string& cmd, const std::filesystem::path&
#endif
_chdir(currentPath.string().c_str());
if (result != "Already up to date.\n")
return std::unexpected(result);
if (result.find("error") != std::string::npos)
return std::unexpected(result);
return result;
}
@@ -42,40 +46,27 @@ git::Repository::Repository(const std::filesystem::path& in_path) {
name_ = path_.filename().string();
}
bool git::Repository::SwitchBranch(const std::string& in_branch) {
std::string cmd = "checkout " + in_branch;
auto result = ExecGitCommand(cmd, path_);
if (result.find("error") != std::string::npos) {
return false;
}
return true;
std::expected<std::string, std::string> git::Repository::SwitchBranch(const std::string& in_branch) {
const std::string cmd = "checkout " + in_branch;
return ExecGitCommand(cmd, path_);
}
bool git::Repository::HardReset() {
auto result = ExecGitCommand("reset --hard", path_);
return result.find("error") == std::string::npos;
std::expected<std::string, std::string> git::Repository::HardReset() {
return ExecGitCommand("reset --hard", path_);
}
bool git::Repository::Pull() {
std::string cmd = "pull";
auto result = ExecGitCommand(cmd, path_);
if (result != "Already up to date.\n")
return false;
if (result.find("error") != std::string::npos) {
return false;
}
return true;
std::expected<std::string, std::string> git::Repository::Pull() {
return ExecGitCommand("pull", path_);
}
std::vector<std::shared_ptr<git::Repository>> git::Repository::GetSubmodules() const {
std::expected<std::vector<std::shared_ptr<git::Repository>>, std::string> git::Repository::GetSubmodules() const {
std::vector<std::shared_ptr<git::Repository>> submodules;
std::string cmd = "submodule";
auto result = ExecGitCommand(cmd, path_);
if (result.find("error") != std::string::npos) {
return submodules;
}
if (!result.has_value())
return std::unexpected(result.error());
std::istringstream iss(result);
std::istringstream iss(result.value());
std::string line;
while (std::getline(iss, line)) {
// 截取第一个空格到第二个空格之间的内容
@@ -91,25 +82,24 @@ std::vector<std::shared_ptr<git::Repository>> git::Repository::GetSubmodules() c
return submodules;
}
std::string git::Repository::GetCurrentBranchName() const {
std::string cmd = "rev-parse --abbrev-ref HEAD";
std::expected<std::string, std::string> git::Repository::GetCurrentBranchName() const {
const std::string cmd = "rev-parse --abbrev-ref HEAD";
auto result = ExecGitCommand(cmd, path_);
if (result.find("error") != std::string::npos) {
return "";
}
result.replace(result.find('\n'), 1, ""); // Remove trailing newline
if (!result.has_value())
return result;
auto& str = result.value();
str.replace(str.find('\n'), 1, ""); // Remove trailing newline
return result;
}
std::vector<std::string> git::Repository::GetAllBranches() const {
std::expected<std::vector<std::string>, std::string> git::Repository::GetAllBranches() const {
std::vector<std::string> branches;
std::string cmd = "branch";
const std::string cmd = "branch";
auto result = ExecGitCommand(cmd, path_);
if (result.find("error") != std::string::npos) {
return branches;
}
if (!result.has_value())
return std::unexpected(result.error());
std::istringstream iss(result);
std::istringstream iss(result.value());
std::string line;
while (std::getline(iss, line)) {
if (!line.empty()) {

View File

@@ -3,19 +3,20 @@
#include <memory>
#include <string>
#include <vector>
#include <expected>
namespace git {
class Repository {
public:
explicit Repository(const std::filesystem::path& in_path);
bool SwitchBranch(const std::string& in_branch);
bool HardReset();
bool Pull();
std::expected<std::string, std::string> SwitchBranch(const std::string& in_branch);
std::expected<std::string, std::string> HardReset();
std::expected<std::string, std::string> Pull();
[[nodiscard]] std::vector<std::shared_ptr<Repository>> GetSubmodules() const;
[[nodiscard]] std::string GetCurrentBranchName() const;
[[nodiscard]] std::vector<std::string> GetAllBranches() const;
[[nodiscard]] std::expected<std::vector<std::shared_ptr<Repository>>, std::string> GetSubmodules() const;
[[nodiscard]] std::expected<std::string, std::string> GetCurrentBranchName() const;
[[nodiscard]] std::expected<std::vector<std::string>, std::string> GetAllBranches() const;
[[nodiscard]] auto GetName() const { return name_; }
[[nodiscard]] bool IsValid() const;

View File

@@ -3,6 +3,7 @@
#include "config_manager.h"
#include "main_frame.h"
#include "git_command.h"
#include "task_manager.h"
class GitBranchApp : public wxApp {
public:
@@ -31,6 +32,10 @@ public:
return true;
}
virtual void OnEventLoopEnter(wxEventLoopBase*) override {
TaskManager::Get().ProcessComplete();
}
int OnExit() override {
// 保存配置
ConfigManager::Instance().Save();

View File

@@ -47,7 +47,7 @@ void MainFrame::InitializeUI() {
// 创建子模块分支选择器
auto submodules = main_repo_->GetSubmodules();
for (const auto& submodule : submodules) {
for (const auto& submodule : submodules.value()) {
branch_selectors_.push_back(new BranchSelector(this, submodule));
}
@@ -58,9 +58,6 @@ void MainFrame::InitializeUI() {
// 创建应用按钮
wxButton* apply_button = new wxButton(this, wxID_APPLY, "Apply");
// 创建日志控件
log_ctrl_ = new ColoredLogCtrl(this);
// 添加到主布局
main_sizer->Add(group_combo_, 0, wxEXPAND | wxALL, 5);
@@ -70,7 +67,6 @@ void MainFrame::InitializeUI() {
main_sizer->Add(hard_reset_checkbox_, 0, wxALL, 5);
main_sizer->Add(apply_button, 0, wxALL, 5);
main_sizer->Add(log_ctrl_, 1, wxEXPAND | wxALL, 5);
SetSizerAndFit(main_sizer);
SetMinSize(wxSize(600, 400));
@@ -80,22 +76,24 @@ void MainFrame::PopulateBranchGroups() {
// 获取所有分支组
std::vector<std::string> branch_groups;
auto result = main_repo_->GetAllBranches();
// 获取主仓库的所有分支
for (const auto& branch : main_repo_->GetAllBranches()) {
for (const auto& branch : result.value()) {
branch_groups.push_back(branch);
}
// 获取子模块的所有分支
auto submodules = main_repo_->GetSubmodules();
for (const auto& submodule : submodules) {
for (const auto& branch : submodule->GetAllBranches()) {
for (const auto& submodule : submodules.value()) {
auto r = submodule->GetAllBranches();
for (const auto& branch : r.value()) {
branch_groups.push_back(branch);
}
}
// 去重
std::sort(branch_groups.begin(), branch_groups.end());
branch_groups.erase(std::unique(branch_groups.begin(), branch_groups.end()), branch_groups.end());
std::ranges::sort(branch_groups);
branch_groups.erase(std::ranges::unique(branch_groups).begin(), branch_groups.end());
// 创建分支组选择器
group_combo_ = new wxComboBox(this, wxID_ANY);
@@ -107,46 +105,42 @@ void MainFrame::PopulateBranchGroups() {
}
void MainFrame::OnApply(wxCommandEvent& event) {
log_ctrl_->LogInfo("Applying branch changes...");
bool any_failures = false;
for (auto* selector : branch_selectors_) {
std::string repo_name = selector->GetRepo()->GetName();
std::string branch_name = selector->GetSelectedBranchName();
log_ctrl_->LogInfo(wxString::Format("Switching %s to branch: %s", repo_name, branch_name));
// log_ctrl_->LogInfo(wxString::Format("Switching %s to branch: %s", repo_name, branch_name));
if (is_hard_reset_) {
if (selector->HardReset()) {
log_ctrl_->LogSuccess(wxString::Format("Successfully updated %s to %s", repo_name, branch_name));
} else {
log_ctrl_->LogError(wxString::Format("Failed to update %s to %s", repo_name, branch_name));
}
// if (is_hard_reset_) {
// if (selector->HardReset()) {
// log_ctrl_->LogSuccess(wxString::Format("Successfully updated %s to %s", repo_name, branch_name));
// } else {
// log_ctrl_->LogError(wxString::Format("Failed to update %s to %s", repo_name, branch_name));
// }
// }
//
// if (selector->ApplyBranchChange()) {
// log_ctrl_->LogSuccess(wxString::Format("Successfully updated %s to %s", repo_name, branch_name));
// } else {
// log_ctrl_->LogError(wxString::Format("Failed to update %s to %s", repo_name, branch_name));
// any_failures = true;
// }
}
if (selector->ApplyBranchChange()) {
log_ctrl_->LogSuccess(wxString::Format("Successfully updated %s to %s", repo_name, branch_name));
} else {
log_ctrl_->LogError(wxString::Format("Failed to update %s to %s", repo_name, branch_name));
any_failures = true;
}
}
if (any_failures) {
log_ctrl_->LogWarning("Some operations failed. See errors above.");
} else {
log_ctrl_->LogSuccess("All operations completed successfully!");
}
log_ctrl_->LogInfo("==============完成==============");
// if (any_failures) {
// log_ctrl_->LogWarning("Some operations failed. See errors above.");
// } else {
// log_ctrl_->LogSuccess("All operations completed successfully!");
// }
}
void MainFrame::OnGroupSelect(wxCommandEvent& event) {
wxString selected_group = group_combo_->GetStringSelection();
if (selected_group.IsEmpty()) return;
log_ctrl_->LogInfo(wxString::Format("Selecting branch group: %s", selected_group));
// log_ctrl_->LogInfo(wxString::Format("Selecting branch group: %s", selected_group));
for (auto* selector : branch_selectors_) {
selector->SelectGroupBranch(selected_group);
@@ -160,9 +154,9 @@ void MainFrame::OnHardResetChanged(wxCommandEvent& event) {
is_hard_reset_ = event.IsChecked();
ConfigManager::Instance().SetHardReset(is_hard_reset_);
if (is_hard_reset_) {
log_ctrl_->LogWarning("Hard reset mode enabled. This will discard local changes!");
} else {
log_ctrl_->LogInfo("Hard reset mode disabled.");
}
// if (is_hard_reset_) {
// log_ctrl_->LogWarning("Hard reset mode enabled. This will discard local changes!");
// } else {
// log_ctrl_->LogInfo("Hard reset mode disabled.");
// }
}

View File

@@ -24,7 +24,6 @@ private:
std::vector<BranchSelector*> branch_selectors_;
wxComboBox* group_combo_ = nullptr;
wxCheckBox* hard_reset_checkbox_ = nullptr;
ColoredLogCtrl* log_ctrl_ = nullptr;
std::shared_ptr<git::Repository> main_repo_;
bool is_hard_reset_ = false;

1
src/task_manager.cpp Normal file
View File

@@ -0,0 +1 @@
#include "task_manager.h"

49
src/task_manager.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <functional>
#include <thread>
#include <vector>
class TaskManager {
public:
static auto& Get() {
static TaskManager instance;
return instance;
}
void PushGitTask(auto&& task, auto&& on_complete) {
tasks_.emplace_back(std::thread([this, task, on_complete] {
const auto& result = task();
std::lock_guard lock(mutex_);
on_complete_.push_back([result, on_complete] {
on_complete(result);
});
}));
}
void PushGitTask(auto&& task) {
tasks_.emplace_back(std::thread([this, task] {
task();
}));
}
void ProcessComplete() {
std::lock_guard lock(mutex_);
for (auto& task : tasks_) {
if (task.joinable()) {
task.join();
}
}
for (const auto& complete : on_complete_) {
if (complete) {
complete();
}
}
on_complete_.clear();
}
private:
TaskManager() = default;
std::vector<std::thread> tasks_;
std::vector<std::function<void()>> on_complete_;
std::mutex mutex_;
};