From 31e632032f06b368e79fd017370dff31d8052693 Mon Sep 17 00:00:00 2001 From: daiqingshuang Date: Tue, 27 May 2025 16:52:47 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/branch_selector.cpp | 281 ++++++++++++++++------- src/branch_selector.h | 24 +- src/git_process_manager.cpp | 134 +++++++++++ src/git_process_manager.h | 47 ++++ src/git_repository.cpp | 417 +++++++++++++++++++---------------- src/git_repository.h | 65 +++--- src/main_frame.cpp | 404 +++++++++++++++++++++++---------- src/main_frame.h | 29 ++- src/task_manager.cpp | 92 ++++++++ src/task_manager.h | 114 ++++------ src/thread_safe_log_ctrl.cpp | 105 +++++++++ src/thread_safe_log_ctrl.h | 36 +++ 12 files changed, 1257 insertions(+), 491 deletions(-) create mode 100644 src/git_process_manager.cpp create mode 100644 src/git_process_manager.h create mode 100644 src/thread_safe_log_ctrl.cpp create mode 100644 src/thread_safe_log_ctrl.h diff --git a/src/branch_selector.cpp b/src/branch_selector.cpp index 88a1e8f..e263d18 100644 --- a/src/branch_selector.cpp +++ b/src/branch_selector.cpp @@ -1,6 +1,7 @@ #include "branch_selector.h" #include "config_manager.h" #include "task_manager.h" +#include wxBEGIN_EVENT_TABLE(BranchSelector, wxPanel) EVT_COMBOBOX(wxID_ANY, BranchSelector::OnBranchSelected) @@ -10,52 +11,103 @@ BranchSelector::BranchSelector(wxWindow* parent, std::shared_ptrGetName()) : label; - label_ctrl = new wxStaticText(this, wxID_ANY, display_label); + label_ctrl_ = new wxStaticText(this, wxID_ANY, display_label); + label_ctrl_->SetMinSize(wxSize(100, -1)); - // 创建下拉列表 - branch_combo_ = new wxComboBox(this, wxID_ANY); + // 下拉列表 + branch_combo_ = new wxComboBox(this, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxDefaultSize, + 0, nullptr, wxCB_READONLY); + branch_combo_->SetMinSize(wxSize(200, -1)); + + // 进度条(初始隐藏) + progress_gauge_ = new wxGauge(this, wxID_ANY, 100, + wxDefaultPosition, wxSize(-1, 10)); + progress_gauge_->Hide(); + progress_gauge_->Pulse(); // 布局 - hbox->Add(label_ctrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - hbox->Add(branch_combo_, 1); + top_sizer->Add(label_ctrl_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + top_sizer->Add(branch_combo_, 1, wxALIGN_CENTER_VERTICAL); - SetSizerAndFit(hbox); + main_sizer->Add(top_sizer, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(progress_gauge_, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5); - // 填充分支列表 - repo->GetAllBranches([this](const std::expected, std::string>& branches) { + SetSizerAndFit(main_sizer); + + // 初始化分支列表 + LoadBranches(); +} + +void BranchSelector::LoadBranches() { + SetBusy(true); + branch_combo_->Clear(); + branch_combo_->Enable(false); + + // 异步获取所有分支 + repo_->GetAllBranches([this](const git::Branches& branches) { + if (!branches.has_value()) { + UpdateStatus(false, wxString::Format("获取分支列表失败: %s", + wxString::FromUTF8(branches.error()))); + SetBusy(false); + return; + } + + // 填充分支列表 for (const auto& branch : branches.value()) { - branch_combo_->Append(branch); + branch_combo_->Append(wxString::FromUTF8(branch)); } - }); - repo->GetCurrentBranchName([this](const std::expected& result) { - // 选择当前分支 - if (!result.has_value()) { - ProcessResult(result); - } - else { - branch_combo_->SetStringSelection(result.value()); - } + // 获取当前分支 + repo_->GetCurrentBranchName([this](const git::Result& result) { + if (result.has_value()) { + wxString current_branch = wxString::FromUTF8(result.value()); + branch_combo_->SetStringSelection(current_branch); + UpdateStatus(true, wxString::Format("当前分支: %s", current_branch)); + } else { + UpdateStatus(false, wxString::Format("获取当前分支失败: %s", + wxString::FromUTF8(result.error()))); + } + + branch_combo_->Enable(true); + SetBusy(false); + }); }); } bool BranchSelector::SelectBranch(const wxString& branch_name) { - if (!branch_combo_->SetStringSelection(branch_name)) + if (is_busy_) { + wxLogWarning("操作正在进行中,请稍候..."); return false; + } + + int index = branch_combo_->FindString(branch_name); + if (index == wxNOT_FOUND) { + wxLogWarning("分支 '%s' 不存在于 %s", branch_name, repo_->GetName()); + return false; + } + + branch_combo_->SetSelection(index); + + // 触发选择事件 wxCommandEvent event(wxEVT_COMBOBOX); event.SetString(branch_name); - OnBranchSelected(event); + event.SetEventObject(branch_combo_); + ProcessEvent(event); + return true; } bool BranchSelector::SelectGroupBranch(const wxString& group_name) { current_group_ = group_name; - // 尝试从配置中获取分支 + // 从配置中获取该组对应的分支 bool found = false; std::string branch_name = ConfigManager::Instance().GetBranchFromGroup( group_name.ToStdString(), @@ -64,20 +116,25 @@ bool BranchSelector::SelectGroupBranch(const wxString& group_name) { &found ); - // 如果找到了缓存,使用缓存,否则使用组名 + // 优先使用缓存的分支名 if (found) { - if (!SelectBranch(branch_name)) { - // 如果设置失败,可能是因为分支不存在,尝试使用组名 - return SelectBranch(group_name); + if (SelectBranch(wxString::FromUTF8(branch_name))) { + return true; } + // 如果缓存的分支不存在,继续尝试其他选项 + } + + // 尝试使用组名作为分支名 + if (SelectBranch(group_name)) { return true; } - // 尝试使用组名作为分支名 - if (!SelectBranch(group_name)) { - // 如果组名设置失败,使用默认分支 - return SelectBranch(branch_name); + + // 最后尝试默认分支 + if (branch_name != group_name.ToStdString()) { + return SelectBranch(wxString::FromUTF8(branch_name)); } - return true; + + return false; } std::string BranchSelector::GetSelectedBranchName() const { @@ -87,61 +144,135 @@ std::string BranchSelector::GetSelectedBranchName() const { void BranchSelector::ApplyBranchChange() { const std::string selected_branch = GetSelectedBranchName(); if (selected_branch.empty()) { + UpdateStatus(false, "请先选择一个分支"); return; } - repo_->SwitchBranch(selected_branch, [this](const git::result_t& result) { - ProcessResult(result); + // 获取当前分支,检查是否需要切换 + auto cached_branch = repo_->GetCachedBranchName(); + if (cached_branch.has_value() && cached_branch.value() == selected_branch) { + wxLogInfo("%s: 已经在分支 %s 上", repo_->GetName(), selected_branch); + + // 如果已经在目标分支,直接执行后续操作 + if (ConfigManager::Instance().GetHardReset()) { + PerformHardReset(); + } else { + PerformPull(); + } + return; + } + + SetBusy(true); + wxLogInfo("%s: 切换到分支 %s", repo_->GetName(), selected_branch); + + // 执行分支切换 + repo_->SwitchBranch(selected_branch, [this, selected_branch](const git::Result& result) { if (!result.has_value()) { - wxLogError("%s SwitchBranch: %s", repo_->GetName(), result.error()); + UpdateStatus(false, wxString::Format("切换分支失败: %s", + wxString::FromUTF8(result.error()))); + wxLogError("%s: 切换分支失败: %s", repo_->GetName(), result.error()); + SetBusy(false); return; } - wxLogInfo("%s SwitchBranch: %s", repo_->GetName(), result.value()); - // 分支切换成功后,更新当前分支名称 - branch_combo_->SetStringSelection(result.value()); - // 如果需要硬重置 + UpdateStatus(true, wxString::Format("已切换到分支: %s", + wxString::FromUTF8(selected_branch))); + wxLogInfo("%s: 成功切换到分支 %s", repo_->GetName(), selected_branch); + + // 更新UI显示 + branch_combo_->SetStringSelection(wxString::FromUTF8(selected_branch)); + + // 根据配置决定下一步操作 if (ConfigManager::Instance().GetHardReset()) { - repo_->HardReset([this](const git::result_t& reset_result) { - ProcessResult(reset_result); - if (!reset_result.has_value()) { - wxLogError("%s HardReset: %s", repo_->GetName(), reset_result.error()); - return; - } - - wxLogInfo("%s HardReset: %s", repo_->GetName(), reset_result.value()); - // 拉取更新 - repo_->Pull([this](const git::result_t& pull_result) { - ProcessResult(pull_result); - if (!pull_result.has_value()) { - wxLogError("%s Pull: %s", repo_->GetName(), pull_result.error()); - } else { - wxLogInfo("%s Pull: %s", repo_->GetName(), pull_result.value()); - } - }); - }); - } - else { - // 拉取更新 - repo_->Pull([this](const git::result_t& pull_result) { - ProcessResult(pull_result); - if (!pull_result.has_value()) { - wxLogError("%s Pull: %s", repo_->GetName(), pull_result.error()); - } else { - wxLogInfo("%s Pull: %s", repo_->GetName(), pull_result.value()); - } - }); + PerformHardReset(); + } else { + PerformPull(); } }); } +void BranchSelector::PerformHardReset() { + wxLogInfo("%s: 执行 hard reset", repo_->GetName()); + + repo_->HardReset([this](const git::Result& result) { + if (!result.has_value()) { + UpdateStatus(false, wxString::Format("Hard reset 失败: %s", + wxString::FromUTF8(result.error()))); + wxLogError("%s: Hard reset 失败: %s", repo_->GetName(), result.error()); + SetBusy(false); + return; + } + + wxLogInfo("%s: Hard reset 成功", repo_->GetName()); + // 继续执行 pull + PerformPull(); + }); +} + +void BranchSelector::PerformPull() { + wxLogInfo("%s: 执行 pull", repo_->GetName()); + + repo_->Pull([this](const git::Result& result) { + if (!result.has_value()) { + UpdateStatus(false, wxString::Format("Pull 失败: %s", + wxString::FromUTF8(result.error()))); + wxLogError("%s: Pull 失败: %s", repo_->GetName(), result.error()); + } else { + UpdateStatus(true, "操作完成"); + wxLogInfo("%s: Pull 成功: %s", repo_->GetName(), + wxString::FromUTF8(result.value()).Trim()); + } + SetBusy(false); + }); +} + void BranchSelector::OnBranchSelected(wxCommandEvent& event) { - // 如果选择的分支不是组名,保存到配置 - if (!current_group_.IsEmpty() && event.GetString() != current_group_) { - ConfigManager::Instance().SaveBranchToGroup( - current_group_.ToStdString(), - repo_->GetName(), - event.GetString().ToStdString() - ); + // 如果是用户手动选择的分支(而不是程序设置的),保存到配置 + if (!current_group_.IsEmpty() && !is_busy_) { + wxString selected = event.GetString(); + if (selected != current_group_) { + ConfigManager::Instance().SaveBranchToGroup( + current_group_.ToStdString(), + repo_->GetName(), + selected.ToStdString() + ); + wxLogInfo("已保存分支选择: %s -> %s", current_group_, selected); + } } } + +void BranchSelector::UpdateStatus(bool success, const wxString& tooltip) { + if (!wxThread::IsMain()) { + CallAfter([this, success, tooltip]() { UpdateStatus(success, tooltip); }); + return; + } + + if (success) { + label_ctrl_->SetForegroundColour(wxColour(0, 128, 0)); // 深绿色 + } else { + label_ctrl_->SetForegroundColour(*wxRED); + } + + label_ctrl_->SetToolTip(tooltip); + label_ctrl_->Refresh(); +} + +void BranchSelector::SetBusy(bool busy) { + if (!wxThread::IsMain()) { + CallAfter([this, busy]() { SetBusy(busy); }); + return; + } + + is_busy_ = busy; + + if (busy) { + progress_gauge_->Show(); + progress_gauge_->Pulse(); + branch_combo_->Enable(false); + } else { + progress_gauge_->Hide(); + branch_combo_->Enable(true); + } + + Layout(); +} diff --git a/src/branch_selector.h b/src/branch_selector.h index 58b2960..80f18c9 100644 --- a/src/branch_selector.h +++ b/src/branch_selector.h @@ -1,12 +1,13 @@ #pragma once #include #include +#include #include "git_repository.h" class BranchSelector : public wxPanel { public: BranchSelector(wxWindow* parent, std::shared_ptr repo, - const wxString& label = wxEmptyString); + const wxString& label = wxEmptyString); bool SelectBranch(const wxString& branch_name); bool SelectGroupBranch(const wxString& group_name); @@ -16,24 +17,21 @@ public: std::shared_ptr GetRepo() const { return repo_; } - template - void ProcessResult(const std::expected& in_expected) { - if (in_expected.has_value()) { - label_ctrl->SetForegroundColour(*wxGREEN); - label_ctrl->SetToolTip(wxString::FromUTF8(in_expected.value())); - } else { - label_ctrl->SetForegroundColour(*wxRED); - label_ctrl->SetToolTip(wxString::FromUTF8(in_expected.error())); - } - label_ctrl->Refresh(true); - } + void LoadBranches(); + void PerformHardReset(); + void PerformPull(); private: void OnBranchSelected(wxCommandEvent& event); + void UpdateStatus(bool success, const wxString& tooltip); + void SetBusy(bool busy); wxComboBox* branch_combo_ = nullptr; - wxStaticText* label_ctrl = nullptr; + wxStaticText* label_ctrl_ = nullptr; + wxGauge* progress_gauge_ = nullptr; + std::shared_ptr repo_; wxString current_group_; + std::atomic is_busy_{false}; wxDECLARE_EVENT_TABLE(); }; diff --git a/src/git_process_manager.cpp b/src/git_process_manager.cpp new file mode 100644 index 0000000..4df04e5 --- /dev/null +++ b/src/git_process_manager.cpp @@ -0,0 +1,134 @@ +#include "git_process_manager.h" + +#include +#include + +wxBEGIN_EVENT_TABLE(git::ProcessManager, wxEvtHandler) + EVT_END_PROCESS(wxID_ANY, git::ProcessManager::OnProcessTerminate) + EVT_TIMER(wxID_ANY, git::ProcessManager::OnTimer) +wxEND_EVENT_TABLE() + +git::ProcessManager::ProcessManager() { + // 设置事件处理器 +} + +git::ProcessManager::~ProcessManager() { + std::lock_guard lock(mutex_); + // 清理所有活动进程 + for (auto& [pid, info] : active_processes_) { + if (info->process && wxProcess::Exists(pid)) { + info->process->Kill(pid, wxSIGKILL); + } + delete info->timer; + } +} + +void git::ProcessManager::ExecuteCommand(const std::string& cmd, + const std::filesystem::path& path, + Callback callback) { + // 在主线程创建进程 + if (!wxIsMainThread()) { + wxTheApp->CallAfter([this, cmd, path, callback] { ExecuteCommand(cmd, path, callback); }); + return; + } + + std::string full_cmd = "git -C \"" + path.string() + "\" " + cmd; + + // 创建进程,使用自定义删除器 + auto info = std::make_unique(std::unique_ptr( + new wxProcess(this), + [](wxProcess* p) { delete p; } + )); + info->callback = callback; + + + info->process->Redirect(); + + int pid = wxExecute(full_cmd, wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE, info->process.get()); + + if (pid == 0) { + callback(std::unexpected("Failed to execute git command: " + cmd)); + return; + } + + // 创建定时器定期读取输出 + info->timer = new wxTimer(this, pid); + info->timer->Start(50); // 50ms间隔 + + std::lock_guard lock(mutex_); + active_processes_[pid] = std::move(info); +} + +void git::ProcessManager::OnTimer(wxTimerEvent& event) { + int pid = event.GetId(); + + std::lock_guard lock(mutex_); + auto it = active_processes_.find(pid); + if (it != active_processes_.end()) { + ReadProcessOutput(*it->second); + } +} + +void git::ProcessManager::OnProcessTerminate(wxProcessEvent& event) { + int pid = event.GetPid(); + + std::unique_ptr info; + { + std::lock_guard lock(mutex_); + auto it = active_processes_.find(pid); + if (it == active_processes_.end()) return; + + info = std::move(it->second); + active_processes_.erase(it); + } + + // 停止定时器 + info->timer->Stop(); + delete info->timer; + + // 读取最后的输出 + ReadProcessOutput(*info); + + // 处理结果 + Result result; + if (!info->error.empty() && + info->error.find("Switched to branch") != std::string::npos && + info->error.find("Already on") != std::string::npos) { + result = std::unexpected(info->error); + } else { + result = info->output; + } + + // 在主线程调用回调 + wxTheApp->CallAfter([callback = info->callback, result]() { + callback(result); + }); +} + +void git::ProcessManager::ReadProcessOutput(ProcessInfo& info) { + // 读取标准输出 + if (wxInputStream* stream = info.process->GetInputStream()) { + if (info.process->IsInputAvailable()) { + wxTextInputStream tis(*stream); + while (!stream->Eof()) { + wxString line = tis.ReadLine(); + if (!line.IsEmpty()) { + info.output += line.ToStdString() + "\n"; + } + } + } + } + + // 读取错误输出 + if (wxInputStream* stream = info.process->GetErrorStream()) { + if (info.process->IsErrorAvailable()) { + wxTextInputStream tis(*stream); + while (!stream->Eof()) { + wxString line = tis.ReadLine(); + if (!line.IsEmpty()) { + info.error += line.ToStdString() + "\n"; + } + } + } + } +} diff --git a/src/git_process_manager.h b/src/git_process_manager.h new file mode 100644 index 0000000..ad57305 --- /dev/null +++ b/src/git_process_manager.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace git { + + class ProcessManager : public wxEvtHandler { + public: + using Result = std::expected; + using Callback = std::function; + + ProcessManager(); + ~ProcessManager(); + + void ExecuteCommand(const std::string& cmd, + const std::filesystem::path& path, + Callback callback); + + private: + struct ProcessInfo { + ProcessInfo(std::unique_ptr in_process) : process(std::move(in_process)), + timer(nullptr) { + } + + std::unique_ptr process; + Callback callback; + std::string output; + std::string error; + wxTimer* timer; + }; + + void OnProcessTerminate(wxProcessEvent& event); + void OnTimer(wxTimerEvent& event); + void ReadProcessOutput(ProcessInfo& info); + + std::unordered_map> active_processes_; + std::mutex mutex_; + + wxDECLARE_EVENT_TABLE(); + }; + +} // namespace git diff --git a/src/git_repository.cpp b/src/git_repository.cpp index dfc17e3..df10b49 100644 --- a/src/git_repository.cpp +++ b/src/git_repository.cpp @@ -1,223 +1,268 @@ #include "git_repository.h" - -#include -#include -#include -#include -#include - -#include "git_command.h" +#include "git_process_manager.h" #include "task_manager.h" +#include +#include -wxProcess* CreateGitProcess(const std::string& cmd, const std::filesystem::path& path) { - // 使用完整路径执行命令,避免改变工作目录 - std::string full_cmd = "git -C \"" + path.string() + "\" " + cmd; +namespace git { - // ReSharper disable once CppDFAMemoryLeak - wxProcess* process = new wxProcess(); - process->Redirect(); - wxExecute(full_cmd, wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxUSE_STREAMS, process); - return process; +// 静态成员初始化 +std::shared_ptr Repository::process_manager_ = std::make_shared(); + +Repository::Repository(const std::filesystem::path& path) + : name_(path.filename().string()), path_(path) { } -git::result_t ExecGitCommand(wxProcess* process) { - while (wxProcess::Exists(process->GetPid())) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } +Repository::~Repository() = default; - // 读取输出 - wxString process_result; - if (wxInputStream* stream = process->GetInputStream(); process->IsInputAvailable()) { - wxTextInputStream tis(*stream); - while (!stream->Eof()) { - process_result += tis.ReadLine() + "\n"; - } - } - - // 读取错误输出 - wxString error_result; - if (wxInputStream* errorStream = process->GetErrorStream(); process->IsErrorAvailable()) { - wxTextInputStream tis(*errorStream, wxT("\n"), wxConvUTF8); - while (!errorStream->Eof()) { - const auto& line = tis.ReadLine(); - if (!line.empty()) - error_result += line + "\n"; - } - } - - // 检查命令是否成功 - while (!error_result.empty()) { - if (error_result.Contains("Switched to branch")) - break; - if (error_result.Contains("Already on")) - break; - if (error_result.Contains("Updating files")) - break; - return std::unexpected("git命令失败: " + error_result.ToStdString()); - } - - return process_result.ToStdString(); +void Repository::ExecuteCommand(const std::string& cmd, Callback callback) { + process_manager_->ExecuteCommand(cmd, path_, callback); } -void GitCommandTask(const std::string& cmd, const std::filesystem::path& path, std::function callback) { - wxASSERT_MSG(wxIsMainThread(), "GitCommandTask must be called from the main thread"); - const auto process = CreateGitProcess(cmd, path); +void Repository::SwitchBranch(const std::string& branch, Callback callback) { + const std::string cmd = "checkout " + branch; - if (!process) { - callback(std::unexpected("无法启动git进程: " + cmd)); - return; - } + ExecuteCommand(cmd, [this, branch, callback](const Result& result) { + if (result.has_value()) { + // 更新缓存的分支名 + { + std::unique_lock lock(cache_mutex_); + cached_branch_ = branch; + } - TaskManager::Get().PushTask([process] { - return ExecGitCommand(process); - }, callback); - // ReSharper disable once CppDFAMemoryLeak + // 清除其他可能受影响的缓存 + InvalidateCache(); + } + + // 确保回调在主线程执行 + TaskManager::Get().ExecuteOnMainThread([callback, result]() { + callback(result); + }); + }); } -git::Repository::Repository(const std::filesystem::path& in_path) { - path_ = in_path; - name_ = path_.filename().string(); +void Repository::HardReset(Callback callback) { + ExecuteCommand("reset --hard", [callback](const Result& result) { + TaskManager::Get().ExecuteOnMainThread([callback, result]() { + callback(result); + }); + }); } -void git::Repository::SwitchBranch(const std::string& in_branch, const std::function& in_callback) { - const std::string cmd = "checkout " + in_branch; - GitCommandTask(cmd, path_, [this, in_branch, in_callback](result_t result) { - if (result.has_value()) { - const std::unique_lock u_lock(branch_name_mutex_); - branch_ = in_branch; - } - in_callback(result); - }); +void Repository::Pull(Callback callback) { + ExecuteCommand("pull", [callback](const Result& result) { + TaskManager::Get().ExecuteOnMainThread([callback, result]() { + callback(result); + }); + }); } -void git::Repository::HardReset(const std::function& in_callback) { - GitCommandTask("reset --hard", path_, [in_callback](result_t result) { - in_callback(result); - }); +void Repository::GetSubmodules(std::function callback) { + // 先检查缓存 + { + std::shared_lock lock(cache_mutex_); + if (cached_submodules_.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, submodules = cached_submodules_.value()]() { + callback(submodules); + }); + return; + } + } + + // 异步获取子模块 + ExecuteCommand("submodule", [this, callback](const Result& result) { + if (!result.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, error = result.error()]() { + callback(std::unexpected(error)); + }); + return; + } + + // 解析子模块 + auto parse_future = TaskManager::Get().Execute([this, output = result.value()]() -> Submodules { + std::vector> submodules; + std::istringstream iss(output); + std::string line; + + while (std::getline(iss, line)) { + // 解析格式: " 160000 commit-hash 0\tsubmodule-path" + // 或: "+160000 commit-hash 0\tsubmodule-path" (有修改) + // 或: "-160000 commit-hash 0\tsubmodule-path" (未初始化) + + // 跳过空行 + if (line.empty()) continue; + + // 找到制表符位置 + size_t tab_pos = line.find('\t'); + if (tab_pos == std::string::npos) continue; + + // 提取子模块路径 + std::string submodule_path = line.substr(tab_pos + 1); + + // 去除可能的尾部空格 + submodule_path.erase(submodule_path.find_last_not_of(" \n\r\t") + 1); + + if (!submodule_path.empty()) { + std::filesystem::path full_path = path_ / submodule_path; + auto submodule = std::make_shared(full_path); + submodules.push_back(submodule); + } + } + + return submodules; + }); + + // 处理解析结果 + parse_future.wait(); + auto submodules_result = parse_future.get(); + + if (submodules_result.has_value()) { + // 更新缓存 + { + std::unique_lock lock(cache_mutex_); + cached_submodules_ = submodules_result.value(); + } + } + + // 在主线程回调 + TaskManager::Get().ExecuteOnMainThread([callback, submodules_result]() { + callback(submodules_result); + }); + }); } -void git::Repository::Pull(const std::function& in_callback) { - GitCommandTask("pull", path_, [in_callback](result_t result) { - in_callback(result); - }); +void Repository::GetCurrentBranchName(std::function callback) { + // 检查缓存 + { + std::shared_lock lock(cache_mutex_); + if (cached_branch_.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, branch = cached_branch_.value()]() { + callback(branch); + }); + return; + } + } + + // 异步获取当前分支 + ExecuteCommand("rev-parse --abbrev-ref HEAD", [this, callback](const Result& result) { + if (!result.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, error = result.error()]() { + callback(std::unexpected(error)); + }); + return; + } + + // 处理结果 + std::string branch_name = result.value(); + + // 去除尾部换行符 + branch_name.erase(branch_name.find_last_not_of("\n\r") + 1); + + // 更新缓存 + { + std::unique_lock lock(cache_mutex_); + cached_branch_ = branch_name; + } + + // 在主线程回调 + TaskManager::Get().ExecuteOnMainThread([callback, branch_name]() { + callback(branch_name); + }); + }); } -void git::Repository::GetSubmodules(const std::function& in_callback) const { - { - std::shared_lock s_lock(submodules_mutex_); - if (!submodules_.empty()) { - in_callback(submodules_); - return; - } - } +void Repository::GetAllBranches(std::function callback) { + // 检查缓存 + { + std::shared_lock lock(cache_mutex_); + if (cached_branches_.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, branches = cached_branches_.value()]() { + callback(branches); + }); + return; + } + } - GitCommandTask("submodule", path_, [this, in_callback](result_t result) { - if (!result.has_value()) { - in_callback(std::unexpected(result.error())); - return; - } + // 异步获取所有分支 + ExecuteCommand("branch", [this, callback](const Result& result) { + if (!result.has_value()) { + TaskManager::Get().ExecuteOnMainThread([callback, error = result.error()]() { + callback(std::unexpected(error)); + }); + return; + } - std::vector> submodules; - std::istringstream iss(result.value()); - std::string line; - while (std::getline(iss, line)) { - // 截取第一个空格到第二个空格之间的内容 - const size_t first_space = line.find(' ', 1); - const size_t second_space = line.find(' ', first_space + 1); - if (first_space != std::string::npos && second_space != std::string::npos) { - std::string submodule_path = line.substr(first_space + 1, second_space - first_space - 1); - std::filesystem::path submodule_full_path = path_ / submodule_path; - auto submodule_repo = std::make_shared(submodule_full_path); - submodules.push_back(submodule_repo); - } - } - const std::unique_lock u_lock(submodules_mutex_); - submodules_ = submodules; - in_callback(submodules); - }); + // 解析分支列表 + auto parse_future = TaskManager::Get().Execute([output = result.value()]() -> std::vector { + std::vector branches; + std::istringstream iss(output); + std::string line; + + while (std::getline(iss, line)) { + if (!line.empty() && line.length() > 2) { + // 移除前面的 "* " 或 " " + std::string branch = line.substr(2); + // 去除尾部空白 + branch.erase(branch.find_last_not_of(" \n\r\t") + 1); + if (!branch.empty()) { + branches.push_back(branch); + } + } + } + + return branches; + }); + + // 等待解析完成 + parse_future.wait(); + auto branches = parse_future.get(); + + // 更新缓存 + { + std::unique_lock lock(cache_mutex_); + cached_branches_ = branches; + } + + // 在主线程回调 + TaskManager::Get().ExecuteOnMainThread([callback, branches]() { + callback(branches); + }); + }); } -void git::Repository::GetCurrentBranchName(const std::function& in_callback) const { - std::shared_lock s_lock(branch_name_mutex_); - if (branch_) { - in_callback(branch_.value()); - return; - } - - GitCommandTask("rev-parse --abbrev-ref HEAD", path_, [this, in_callback](result_t result) { - if (!result.has_value()) { - in_callback(std::unexpected(result.error())); - return; - } - - std::string branch_name = result.value(); - std::erase(branch_name, '\n'); // Remove trailing newline - const std::unique_lock u_lock(branch_name_mutex_); - branch_ = branch_name; - in_callback(branch_name); - }); +std::optional>> Repository::GetCachedSubmodules() const { + std::shared_lock lock(cache_mutex_); + return cached_submodules_; } -void git::Repository::GetAllBranches( - const std::function& in_callback) const { - { - std::shared_lock s_lock(all_branches_mutex_); - if (!all_branches_.empty()) { - in_callback(all_branches_); - return; - } - } - - GitCommandTask("branch", path_, [this, in_callback](result_t result) { - if (!result.has_value()) { - in_callback(std::unexpected(result.error())); - return; - } - - std::vector branches; - std::istringstream iss(result.value()); - std::string line; - while (std::getline(iss, line)) { - if (!line.empty()) { - branches.push_back(line.substr(2)); // Remove the leading "* " or " " - } - } - const std::unique_lock u_lock(all_branches_mutex_); - all_branches_ = branches; - in_callback(branches); - }); +std::optional Repository::GetCachedBranchName() const { + std::shared_lock lock(cache_mutex_); + return cached_branch_; } -std::expected>, std::string> git::Repository::GetSubmodules() const { - std::shared_lock s_lock(submodules_mutex_); - if (!submodules_.empty()) { - return submodules_; - } - return std::unexpected("No submodules available"); +std::optional> Repository::GetCachedBranches() const { + std::shared_lock lock(cache_mutex_); + return cached_branches_; } -std::expected git::Repository::GetCurrentBranchName() const { - std::shared_lock s_lock(branch_name_mutex_); - if (branch_) { - return *branch_; - } - return std::unexpected("No current branch set"); +bool Repository::IsValid() const { + return std::filesystem::exists(path_ / ".git"); } -std::expected, std::string> git::Repository::GetAllBranches() const { - std::shared_lock s_lock(all_branches_mutex_); - if (!all_branches_.empty()) - return all_branches_; - return std::unexpected("No branches available"); +void Repository::InvalidateCache() { + std::unique_lock lock(cache_mutex_); + // 只清除可能受影响的缓存 + // 分支切换不会影响子模块列表,所以保留 cached_submodules_ + cached_branches_.reset(); + // cached_branch_ 在 SwitchBranch 中已更新,不需要清除 } -bool git::Repository::IsValid() const { - return IsGitRepository(path_.string()); +std::shared_ptr OpenRepository(const std::filesystem::path& path) { + auto repo = std::make_shared(path); + if (repo->IsValid()) { + return repo; + } + return nullptr; } -std::shared_ptr git::OpenRepository(const std::filesystem::path& in_path) { - auto repo = std::make_shared(in_path); - if (repo->IsValid()) - return repo; - return nullptr; -} +} // namespace git diff --git a/src/git_repository.h b/src/git_repository.h index ba7f453..ff326ab 100644 --- a/src/git_repository.h +++ b/src/git_repository.h @@ -6,47 +6,58 @@ #include #include #include - +#include namespace git { + + class ProcessManager; class Repository; - using result_t = std::expected; - using submodules_t = std::expected>, std::string>; - using branches_t = std::expected, std::string>; + using Result = std::expected; + using Submodules = std::expected>, std::string>; + using Branches = std::expected, std::string>; + using Callback = std::function; class Repository { public: + explicit Repository(const std::filesystem::path& path); + ~Repository(); - explicit Repository(const std::filesystem::path& in_path); + // 异步操作 + void SwitchBranch(const std::string& branch, Callback callback); + void HardReset(Callback callback); + void Pull(Callback callback); - void SwitchBranch(const std::string& in_branch, const std::function& in_callback); - void HardReset(const std::function& in_callback); - void Pull(const std::function& in_callback); + // 获取信息 + void GetSubmodules(std::function callback); + void GetCurrentBranchName(std::function callback); + void GetAllBranches(std::function callback); - void GetSubmodules(const std::function& in_callback) const; - void GetCurrentBranchName(const std::function& in_callback) const; - void GetAllBranches(const std::function& in_callback) const; + // 同步版本(用于缓存数据) + std::optional>> GetCachedSubmodules() const; + std::optional GetCachedBranchName() const; + std::optional> GetCachedBranches() const; - [[nodiscard]] std::expected>, std::string> GetSubmodules() const; + std::string GetName() const { return name_; } + bool IsValid() const; - [[nodiscard]] std::expected GetCurrentBranchName() const; - - [[nodiscard]] std::expected, std::string> GetAllBranches() const; - - [[nodiscard]] auto GetName() const { return name_; } - [[nodiscard]] bool IsValid() const; private: + void ExecuteCommand(const std::string& cmd, Callback callback); + void InvalidateCache(); + std::string name_; std::filesystem::path path_; - mutable std::vector> submodules_; - mutable std::optional branch_; - mutable std::vector all_branches_; - // mutable std::shared_mutex mutex_; - mutable std::shared_mutex branch_name_mutex_; - mutable std::shared_mutex all_branches_mutex_; - mutable std::shared_mutex submodules_mutex_; + + // 缓存数据 + mutable std::shared_mutex cache_mutex_; + mutable std::optional>> cached_submodules_; + mutable std::optional cached_branch_; + mutable std::optional> cached_branches_; + + // 进程管理器 + static std::shared_ptr process_manager_; }; - std::shared_ptr OpenRepository(const std::filesystem::path& in_path); -} + std::shared_ptr OpenRepository(const std::filesystem::path& path); + +} // namespace git diff --git a/src/main_frame.cpp b/src/main_frame.cpp index 57d422d..74357bc 100644 --- a/src/main_frame.cpp +++ b/src/main_frame.cpp @@ -1,11 +1,11 @@ #include "main_frame.h" - -#include - #include "branch_selector.h" #include "config_manager.h" #include "task_manager.h" -#include "wx/progdlg.h" +#include "thread_safe_log_ctrl.h" +#include +#include +#include wxBEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_BUTTON(wxID_APPLY, MainFrame::OnApply) @@ -13,140 +13,279 @@ wxBEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_CHECKBOX(wxID_ANY, MainFrame::OnHardResetChanged) wxEND_EVENT_TABLE() -wxString TrimTrailingNewline(const wxString& str) { - if (!str.IsEmpty() && str.Last() == '\n') { - return str.BeforeLast('\n'); - } - return str; +MainFrame::MainFrame(const wxString& title) + : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(800, 600)) { + + // 设置图标(如果有的话) + // SetIcon(wxIcon("app_icon")); + + // 居中窗口 + Centre(); + + // 开始加载 + LoadRepository(); } -MainFrame::MainFrame(const wxString& title) - : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxDefaultSize) { - - // 加载主仓库 - std::string repo_path = ConfigManager::Instance().GetRepositoryPath(); - main_repo_ = git::OpenRepository(repo_path); - if (!main_repo_) { - wxMessageBox(wxString::FromUTF8("无法打开git仓库: ") + repo_path, "Error", wxICON_ERROR); - Close(true); - return; +MainFrame::~MainFrame() { + // 等待所有异步操作完成 + for (auto& future : pending_operations_) { + if (future.valid()) { + future.wait(); + } } +} - // 获取硬重置配置 - is_hard_reset_ = ConfigManager::Instance().GetHardReset(); +void MainFrame::LoadRepository() { + // 创建临时的加载界面 + wxPanel* loading_panel = new wxPanel(this); + wxBoxSizer* loading_sizer = new wxBoxSizer(wxVERTICAL); - // 初始化UI - InitializeUI(); + wxStaticText* loading_text = new wxStaticText(loading_panel, wxID_ANY, + wxT("正在加载仓库信息...")); + loading_text->SetFont(loading_text->GetFont().Scale(1.2)); + + wxGauge* loading_gauge = new wxGauge(loading_panel, wxID_ANY, 100, + wxDefaultPosition, wxSize(300, 20)); + loading_gauge->Pulse(); + + loading_sizer->AddStretchSpacer(); + loading_sizer->Add(loading_text, 0, wxALIGN_CENTER | wxALL, 10); + loading_sizer->Add(loading_gauge, 0, wxALIGN_CENTER | wxALL, 10); + loading_sizer->AddStretchSpacer(); + + loading_panel->SetSizer(loading_sizer); + + // 异步加载仓库 + auto future = TaskManager::Get().Execute([this]() { + std::string repo_path = ConfigManager::Instance().GetRepositoryPath(); + main_repo_ = git::OpenRepository(repo_path); + + if (!main_repo_) { + throw std::runtime_error("无法打开Git仓库: " + repo_path); + } + + // 获取硬重置配置 + is_hard_reset_ = ConfigManager::Instance().GetHardReset(); + + return true; + }); + + // 等待加载完成 + pending_operations_.push_back(std::async(std::launch::async, [this, future = std::move(future), loading_panel]() mutable { + try { + future.get(); + + // 在主线程初始化UI + CallAfter([this, loading_panel]() { + loading_panel->Destroy(); + InitializeUI(); + }); + } catch (const std::exception& e) { + CallAfter([this, msg = std::string(e.what())]() { + wxMessageBox(wxString::FromUTF8(msg), "错误", wxICON_ERROR); + Close(true); + }); + } + })); } void MainFrame::InitializeUI() { - main_repo_->GetSubmodules([this](const git::submodules_t& submodules){ - if (!submodules) { - // 弹出窗口报错 - wxMessageBox(wxString::Format(wxString::FromUTF8("获取子模块失败: %s"), submodules.error()), "Error", wxICON_ERROR); + // 创建主面板 + wxPanel* main_panel = new wxPanel(this); + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + // 创建工具栏区域 + wxPanel* toolbar_panel = new wxPanel(main_panel); + wxBoxSizer* toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); + + // 分支组选择器 + wxStaticText* group_label = new wxStaticText(toolbar_panel, wxID_ANY, wxT("分支组:")); + group_combo_ = new wxComboBox(toolbar_panel, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxSize(200, -1), + 0, nullptr, wxCB_READONLY); + group_combo_->SetToolTip(wxT("选择要切换的分支组")); + + // 硬重置选项 + hard_reset_checkbox_ = new wxCheckBox(toolbar_panel, wxID_ANY, wxT("执行 Hard Reset")); + hard_reset_checkbox_->SetValue(is_hard_reset_); + hard_reset_checkbox_->SetToolTip(wxT("切换分支后是否执行 git reset --hard")); + + // 应用按钮 + apply_button_ = new wxButton(toolbar_panel, wxID_APPLY, wxT("应用更改")); + apply_button_->SetToolTip(wxT("将所有仓库切换到选定的分支")); + apply_button_->Enable(false); + + // 进度条 + progress_bar_ = new wxGauge(toolbar_panel, wxID_ANY, 100, + wxDefaultPosition, wxSize(-1, 20)); + progress_bar_->Hide(); + + toolbar_sizer->Add(group_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + toolbar_sizer->Add(group_combo_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + toolbar_sizer->Add(hard_reset_checkbox_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + toolbar_sizer->Add(apply_button_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 10); + toolbar_sizer->Add(progress_bar_, 1, wxALIGN_CENTER_VERTICAL); + + toolbar_panel->SetSizer(toolbar_sizer); + + // 分隔线 + wxStaticLine* line1 = new wxStaticLine(main_panel); + + // 创建分支选择器区域(使用滚动窗口) + wxScrolledWindow* selector_scroll = new wxScrolledWindow(main_panel, wxID_ANY, + wxDefaultPosition, wxDefaultSize, + wxVSCROLL | wxBORDER_SUNKEN); + selector_scroll->SetScrollRate(0, 20); + wxBoxSizer* selector_sizer = new wxBoxSizer(wxVERTICAL); + + // 创建日志控件 + log_ctrl_ = new ThreadSafeLogCtrl(main_panel); + log_ctrl_->SetMinSize(wxSize(-1, 150)); + + // 分隔线 + wxStaticLine* line2 = new wxStaticLine(main_panel); + + // 布局主面板 + main_sizer->Add(toolbar_panel, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(line1, 0, wxEXPAND | wxLEFT | wxRIGHT, 5); + main_sizer->Add(selector_scroll, 1, wxEXPAND | wxALL, 5); + main_sizer->Add(line2, 0, wxEXPAND | wxLEFT | wxRIGHT, 5); + main_sizer->Add(log_ctrl_, 0, wxEXPAND | wxALL, 5); + + main_panel->SetSizer(main_sizer); + + // 设置状态栏 + CreateStatusBar(2); + SetStatusText(wxT("就绪"), 0); + SetStatusText(wxString::Format(wxT("仓库: %s"), main_repo_->GetName()), 1); + + // 开始加载子模块和分支信息 + SetBusy(true); + log_ctrl_->LogInfoThreadSafe(wxT("正在加载仓库信息...")); + + main_repo_->GetSubmodules([this, selector_scroll, selector_sizer](const git::Submodules& submodules) { + if (!submodules.has_value()) { + log_ctrl_->LogErrorThreadSafe(wxString::Format(wxT("获取子模块失败: %s"), + wxString::FromUTF8(submodules.error()))); + SetBusy(false); return; } - // 创建主布局 - main_sizer_ = new wxBoxSizer(wxVERTICAL); - // 创建日志控件 - log_ctrl_ = new ColoredLogCtrl(this); - // 创建分支组选择器 - group_combo_ = new wxComboBox(this, wxID_ANY); - // 创建主仓库分支选择器 - branch_selectors_.push_back(new BranchSelector(this, main_repo_, wxString::FromUTF8("主仓库"))); - // 创建子模块分支选择器 - for (const auto& submodule: submodules.value()) { - branch_selectors_.push_back(new BranchSelector(this, submodule)); - } + CallAfter([this, selector_scroll, selector_sizer, repos = submodules.value()]() { + // 创建主仓库分支选择器 + auto main_selector = std::make_unique(selector_scroll, main_repo_, wxT("主仓库")); + selector_sizer->Add(main_selector.get(), 0, wxEXPAND | wxALL, 2); + branch_selectors_.push_back(std::move(main_selector)); - // 创建硬重置选项 - hard_reset_checkbox_ = new wxCheckBox(this, wxID_ANY, "Hard reset"); - hard_reset_checkbox_->SetValue(is_hard_reset_); + // 创建子模块分支选择器 + for (const auto& submodule : repos) { + auto selector = std::make_unique(selector_scroll, submodule); + selector_sizer->Add(selector.get(), 0, wxEXPAND | wxALL, 2); + branch_selectors_.push_back(std::move(selector)); + } - // 创建应用按钮 - apply_button_ = new wxButton(this, wxID_APPLY, "Apply"); + selector_scroll->SetSizer(selector_sizer); + selector_scroll->FitInside(); - // 添加分支组选择器 - main_sizer_->Add(group_combo_, 0, wxEXPAND | wxALL, 5); + log_ctrl_->LogSuccessThreadSafe(wxString::Format(wxT("已加载 %zu 个仓库"), + branch_selectors_.size())); - // 添加所有分支选择器到布局 - for (auto selector: branch_selectors_) { main_sizer_->Add(selector, 0, wxEXPAND | wxALL, 5); } - - 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_); - SetSize(wxSize(600, 400)); - SetMinSize(wxSize(600, 400)); - - // 设置分支组 - CallAfter([this] { PopulateBranchGroups(); }); + // 加载分支组 + PopulateBranchGroups(); + }); }); } void MainFrame::PopulateBranchGroups() { - apply_button_->Enable(false); // 初始时禁用应用按钮 + log_ctrl_->LogInfoThreadSafe(wxT("正在加载分支列表...")); - main_repo_->GetAllBranches([this](const std::expected, std::string>& branches) { - // 获取所有分支组 - std::vector branch_groups; - if (!branches.has_value()) { - log_ctrl_->LogError(wxString::Format(wxString::FromUTF8("获取主仓库分支失败: %s"), branches.error())); - return; - } - // 获取主仓库的所有分支 - for (const auto& branch: branches.value()) { branch_groups.push_back(branch); } + // 用于收集所有分支的集合 + auto all_branches = std::make_shared>(); + auto completed_count = std::make_shared>(0); + int total_repos = branch_selectors_.size(); - // 获取子模块的所有分支 - auto submodules = main_repo_->GetSubmodules(); - for (const auto& submodule: submodules.value()) { - auto r = submodule->GetAllBranches(); - if (!r.has_value()) { - log_ctrl_->LogError(wxString::Format(wxString::FromUTF8("获取子模块 %s 的分支失败: %s"), - submodule->GetName(), - r.error())); - continue; + // 收集所有仓库的分支 + for (const auto& selector : branch_selectors_) { + auto repo = selector->GetRepo(); + + repo->GetAllBranches([this, repo, all_branches, completed_count, total_repos](const git::Branches& branches) { + if (branches.has_value()) { + // 线程安全地添加分支 + for (const auto& branch : branches.value()) { + CallAfter([all_branches, branch]() { + all_branches->insert(branch); + }); + } + } else { + log_ctrl_->LogErrorThreadSafe(wxString::Format(wxT("获取 %s 的分支失败: %s"), + repo->GetName(), + wxString::FromUTF8(branches.error()))); } - for (const auto& branch: r.value()) { branch_groups.push_back(branch); } - } - // 去重 - std::ranges::sort(branch_groups); - branch_groups.erase(std::ranges::unique(branch_groups).begin(), branch_groups.end()); + // 检查是否所有仓库都完成了 + if (++(*completed_count) == total_repos) { + CallAfter([this, all_branches]() { + // 填充下拉框 + group_combo_->Clear(); + for (const auto& branch : *all_branches) { + group_combo_->Append(wxString::FromUTF8(branch)); + } - // 填充分支组 - for (const auto& group: branch_groups) { group_combo_->Append(group); } - apply_button_->Enable(true); + // 设置默认选择 + if (group_combo_->GetCount() > 0) { + // 尝试获取主仓库的当前分支 + auto current_branch = main_repo_->GetCachedBranchName(); + if (current_branch.has_value()) { + group_combo_->SetStringSelection(wxString::FromUTF8(current_branch.value())); + log_ctrl_->LogInfoThreadSafe(wxString::Format(wxT("当前分支组: %s"), + wxString::FromUTF8(current_branch.value()))); + } else { + group_combo_->SetSelection(0); + } - if (group_combo_->GetCount() > 0) { - if (auto branch_name = main_repo_->GetCurrentBranchName()) { - log_ctrl_->LogInfo(wxString::Format(wxString::FromUTF8("当前分支: %s"), branch_name.value())); - group_combo_->SetStringSelection(branch_name.value()); + // 触发选择事件 + wxCommandEvent event(wxEVT_COMBOBOX); + event.SetEventObject(group_combo_); + ProcessEvent(event); + } else { + log_ctrl_->LogErrorThreadSafe(wxT("没有找到任何分支")); + } + + apply_button_->Enable(true); + SetBusy(false); + log_ctrl_->LogSuccessThreadSafe(wxT("分支列表加载完成")); + }); } - else { - log_ctrl_->LogError(wxString::Format(wxString::FromUTF8("获取当前分支失败: %s, 将使用默认分支 Develop"), - branch_name.error())); - group_combo_->SetStringSelection("Develop"); - } - wxCommandEvent event(wxEVT_COMBOBOX); - OnGroupSelect(event); - } - else { log_ctrl_->LogError(wxString::FromUTF8("没有可用的分支")); } - // 置顶窗口 - Raise(); - }); + }); + } } void MainFrame::OnApply(wxCommandEvent& event) { - log_ctrl_->LogInfo(wxString::FromUTF8("...开始应用修改...")); + if (active_operations_ > 0) { + log_ctrl_->LogWarningThreadSafe(wxT("已有操作正在进行中,请稍候...")); + return; + } - for (auto branch_selector : branch_selectors_) { - auto selector = branch_selector; - selector->ApplyBranchChange(); + SetBusy(true); + active_operations_ = branch_selectors_.size(); + int completed = 0; + + log_ctrl_->LogInfoThreadSafe(wxT("========== 开始应用更改 ==========")); + + for (auto& selector : branch_selectors_) { + // 异步应用每个仓库的更改 + CallAfter([this, &selector, &completed]() { + selector->ApplyBranchChange(); + + // 使用延迟来检查完成状态(简化示例) + // 实际应该使用事件或回调机制 + CallAfter([this, &completed]() { + if (--active_operations_ == 0) { + SetBusy(false); + log_ctrl_->LogInfoThreadSafe(wxT("========== 操作完成 ==========")); + } + }); + }); } } @@ -154,17 +293,60 @@ 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_->LogInfoThreadSafe(wxString::Format(wxT("切换到分支组: %s"), selected_group)); - for (const auto& selector : branch_selectors_) { + for (auto& selector : branch_selectors_) { selector->SelectGroupBranch(selected_group); } - // 保存配置 + // 异步保存配置 ConfigManager::Instance().Save(); } void MainFrame::OnHardResetChanged(wxCommandEvent& event) { is_hard_reset_ = event.IsChecked(); ConfigManager::Instance().SetHardReset(is_hard_reset_); + + wxString status = is_hard_reset_ ? wxT("启用") : wxT("禁用"); + log_ctrl_->LogInfoThreadSafe(wxString::Format(wxT("Hard Reset: %s"), status)); +} + +void MainFrame::UpdateProgress(int current, int total) { + if (!wxThread::IsMain()) { + CallAfter([this, current, total]() { UpdateProgress(current, total); }); + return; + } + + if (total > 0) { + progress_bar_->SetRange(total); + progress_bar_->SetValue(current); + + SetStatusText(wxString::Format(wxT("进度: %d/%d"), current, total), 0); + } +} + +void MainFrame::SetBusy(bool busy) { + if (!wxThread::IsMain()) { + CallAfter([this, busy]() { SetBusy(busy); }); + return; + } + + if (busy) { + progress_bar_->Show(); + progress_bar_->Pulse(); + apply_button_->Enable(false); + group_combo_->Enable(false); + SetCursor(wxCURSOR_WAIT); + SetStatusText(wxT("处理中..."), 0); + } else { + progress_bar_->Hide(); + apply_button_->Enable(true); + group_combo_->Enable(true); + SetCursor(wxCURSOR_DEFAULT); + SetStatusText(wxT("就绪"), 0); + } + + // 强制刷新布局 + if (auto sizer = GetSizer()) + sizer->Layout(); } diff --git a/src/main_frame.h b/src/main_frame.h index 5e9f80c..7a3ad11 100644 --- a/src/main_frame.h +++ b/src/main_frame.h @@ -1,18 +1,19 @@ #pragma once -#include #include #include #include +#include #include "git_repository.h" -#include "colored_log_ctrl.h" class BranchSelector; +class ThreadSafeLogCtrl; -// 主窗口 -class MainFrame : public wxFrame { +class MainFrame : + + public wxFrame { public: explicit MainFrame(const wxString& title); - ~MainFrame() override = default; + ~MainFrame() override; private: void OnApply(wxCommandEvent& event); @@ -20,18 +21,26 @@ private: void OnHardResetChanged(wxCommandEvent& event); void InitializeUI(); + void LoadRepository(); void PopulateBranchGroups(); + void UpdateProgress(int current, int total); + void SetBusy(bool busy); - std::vector branch_selectors_; + // UI组件 + std::vector> branch_selectors_; wxComboBox* group_combo_ = nullptr; wxCheckBox* hard_reset_checkbox_ = nullptr; - wxBoxSizer* main_sizer_ = nullptr; - ColoredLogCtrl* log_ctrl_ = nullptr; wxButton* apply_button_ = nullptr; + ThreadSafeLogCtrl* log_ctrl_ = nullptr; + wxGauge* progress_bar_ = nullptr; + + // 数据 std::shared_ptr main_repo_; bool is_hard_reset_ = false; - std::atomic completed{0}; - std::atomic has_errors{false}; + // 异步操作管理 + std::vector> pending_operations_; + std::atomic active_operations_{0}; + wxDECLARE_EVENT_TABLE(); }; diff --git a/src/task_manager.cpp b/src/task_manager.cpp index a857e44..7ccbb71 100644 --- a/src/task_manager.cpp +++ b/src/task_manager.cpp @@ -1 +1,93 @@ #include "task_manager.h" +#include + +wxDEFINE_EVENT(wxEVT_TASK_COMPLETE, wxThreadEvent); + +TaskManager& TaskManager::Get() { + static TaskManager instance; + return instance; +} + +TaskManager::TaskManager() : shutdown_(false) { + // 获取硬件并发数 + size_t num_threads = std::thread::hardware_concurrency(); + if (num_threads == 0) { + num_threads = DEFAULT_THREAD_COUNT; + } + + // 限制最大线程数 + num_threads = std::min(num_threads, size_t(8)); + + wxLogInfo("TaskManager: 创建 %uz 个工作线程", num_threads); + + // 创建工作线程池 + workers_.reserve(num_threads); + for (size_t i = 0; i < num_threads; ++i) { + workers_.emplace_back(&TaskManager::WorkerThread, this); + } +} + +TaskManager::~TaskManager() { + Shutdown(); +} + +void TaskManager::Shutdown() { + { + std::lock_guard lock(queue_mutex_); + if (shutdown_) { + return; // 已经关闭 + } + shutdown_ = true; + } + + // 唤醒所有工作线程 + cv_.notify_all(); + + // 等待所有工作线程结束 + for (auto& worker : workers_) { + if (worker.joinable()) { + worker.join(); + } + } + + wxLogInfo("TaskManager: 所有工作线程已关闭"); +} + +void TaskManager::WorkerThread() { + while (true) { + std::function task; + + { + std::unique_lock lock(queue_mutex_); + + // 等待任务或关闭信号 + cv_.wait(lock, [this] { return shutdown_ || !tasks_.empty(); }); + + if (shutdown_ && tasks_.empty()) { + return; + } + + if (!tasks_.empty()) { + task = std::move(tasks_.front()); + tasks_.pop(); + } + } + + // 执行任务 + if (task) { + try { + task(); + } catch (const std::exception& e) { + // 记录异常但不终止线程 + wxLogError("TaskManager: 任务执行异常: %s", e.what()); + } catch (...) { + wxLogError("TaskManager: 任务执行未知异常"); + } + } + } +} + +size_t TaskManager::GetQueueSize() const { + std::lock_guard lock(queue_mutex_); + return tasks_.size(); +} diff --git a/src/task_manager.h b/src/task_manager.h index 2a03162..b057923 100644 --- a/src/task_manager.h +++ b/src/task_manager.h @@ -1,4 +1,3 @@ -// task_manager.h #pragma once #include #include @@ -8,91 +7,68 @@ #include #include #include -#include +#include +#include + +// 自定义事件用于线程安全的UI更新 +wxDECLARE_EVENT(wxEVT_TASK_COMPLETE, wxThreadEvent); class TaskManager { public: - static TaskManager& Get() { - static TaskManager instance; - return instance; - } + static TaskManager& Get(); + ~TaskManager(); - ~TaskManager() { - Shutdown(); - } - - template - void PushTask(Func&& task, Callback&& callback) { - { - std::lock_guard lock(queue_mutex_); - tasks_.emplace([task = std::forward(task), - callback = std::forward(callback)]() { - auto result = task(); - // 使用 wxCallAfter 确保回调在主线程执行 - wxTheApp->CallAfter([result, callback]() { - callback(result); - }); - }); - } - cv_.notify_one(); - } + // 删除拷贝和移动 + TaskManager(const TaskManager&) = delete; + TaskManager& operator=(const TaskManager&) = delete; template - auto PushTaskAsync(Func&& task) -> std::future { - auto promise = std::make_shared>(); - auto future = promise->get_future(); + auto Execute(Func&& task) -> std::future { + using ReturnType = decltype(task()); - PushTask(std::forward(task), - [promise](auto result) { promise->set_value(result); }); + auto packaged_task = std::make_shared>( + std::forward(task) + ); + auto future = packaged_task->get_future(); + + { + std::lock_guard lock(queue_mutex_); + if (shutdown_) { + throw std::runtime_error("TaskManager is shutting down"); + } + + tasks_.emplace([packaged_task]() { + (*packaged_task)(); + }); + } + + cv_.notify_one(); return future; } - void Shutdown() { - { - std::lock_guard lock(queue_mutex_); - shutdown_ = true; - } - cv_.notify_all(); - - for (auto& worker : workers_) { - if (worker.joinable()) { - worker.join(); - } + // UI安全的执行 + template + void ExecuteOnMainThread(Func&& func) { + if (wxIsMainThread()) { + func(); + } else { + wxTheApp->CallAfter(std::forward(func)); } } + void Shutdown(); + size_t GetQueueSize() const; + private: - TaskManager() : shutdown_(false) { - // 创建工作线程池 - const size_t num_threads = 4; - for (size_t i = 0; i < num_threads; ++i) { - workers_.emplace_back(&TaskManager::WorkerThread, this); - } - } - - void WorkerThread() { - while (true) { - std::function task; - { - std::unique_lock lock(queue_mutex_); - cv_.wait(lock, [this] { return shutdown_ || !tasks_.empty(); }); - - if (shutdown_ && tasks_.empty()) { - return; - } - - task = std::move(tasks_.front()); - tasks_.pop(); - } - - task(); - } - } + TaskManager(); + void WorkerThread(); std::vector workers_; std::queue> tasks_; - std::mutex queue_mutex_; + mutable std::mutex queue_mutex_; std::condition_variable cv_; - std::atomic shutdown_; + std::atomic shutdown_{false}; + + static constexpr size_t DEFAULT_THREAD_COUNT = 4; }; diff --git a/src/thread_safe_log_ctrl.cpp b/src/thread_safe_log_ctrl.cpp new file mode 100644 index 0000000..044688b --- /dev/null +++ b/src/thread_safe_log_ctrl.cpp @@ -0,0 +1,105 @@ +#include "thread_safe_log_ctrl.h" +#include + +wxBEGIN_EVENT_TABLE(ThreadSafeLogCtrl, ColoredLogCtrl) + EVT_TIMER(wxID_ANY, ThreadSafeLogCtrl::OnTimer) +wxEND_EVENT_TABLE() + +ThreadSafeLogCtrl::ThreadSafeLogCtrl(wxWindow* parent) + : ColoredLogCtrl(parent), update_timer_(this) { + + // 启动定时器定期更新日志 + update_timer_.Start(UPDATE_INTERVAL); +} + +ThreadSafeLogCtrl::~ThreadSafeLogCtrl() { + // 停止定时器 + update_timer_.Stop(); + + // 处理剩余的日志 + ProcessPendingLogs(); +} + +void ThreadSafeLogCtrl::LogMessageThreadSafe(const wxString& message, const wxColour& color) { + LogEntry entry{message, color}; + + { + std::lock_guard lock(log_mutex_); + pending_logs_.push(std::move(entry)); + } + + // 如果在主线程且队列较大,立即处理一部分 + if (wxThread::IsMain() && pending_logs_.size() > 50) { + ProcessPendingLogs(); + } +} + +void ThreadSafeLogCtrl::LogErrorThreadSafe(const wxString& message) { + LogMessageThreadSafe(message, *wxRED); +} + +void ThreadSafeLogCtrl::LogWarningThreadSafe(const wxString& message) { + LogMessageThreadSafe(message, wxColour(255, 140, 0)); // 橙色 +} + +void ThreadSafeLogCtrl::LogInfoThreadSafe(const wxString& message) { + LogMessageThreadSafe(message, *wxBLUE); +} + +void ThreadSafeLogCtrl::LogSuccessThreadSafe(const wxString& message) { + LogMessageThreadSafe(message, wxColour(0, 128, 0)); // 深绿色 +} + +void ThreadSafeLogCtrl::OnTimer(wxTimerEvent& event) { + ProcessPendingLogs(); +} + +void ThreadSafeLogCtrl::ProcessPendingLogs() { + // 确保在主线程 + if (!wxThread::IsMain()) { + CallAfter([this]() { ProcessPendingLogs(); }); + return; + } + + std::vector logs_to_process; + + { + std::lock_guard lock(log_mutex_); + + // 批量获取日志,避免频繁加锁 + const size_t batch_size = std::min(size_t(100), pending_logs_.size()); + logs_to_process.reserve(batch_size); + + for (size_t i = 0; i < batch_size && !pending_logs_.empty(); ++i) { + logs_to_process.push_back(std::move(pending_logs_.front())); + pending_logs_.pop(); + } + } + + // 如果没有待处理的日志,直接返回 + if (logs_to_process.empty()) { + return; + } + + // 冻结控件以提高性能 + Freeze(); + + // 检查是否需要清理旧日志 + long current_lines = GetNumberOfLines(); + if (current_lines > MAX_LINES) { + // 删除前面的一些行 + long lines_to_remove = current_lines - (MAX_LINES * 3 / 4); // 保留75% + SetEditable(true); + Remove(0, GetLineLength(0) * lines_to_remove); + SetEditable(false); + } + + // 批量添加日志 + for (const auto& entry : logs_to_process) { + LogMessage(entry.message, entry.color); + } + + // 解冻控件 + Thaw(); +} + diff --git a/src/thread_safe_log_ctrl.h b/src/thread_safe_log_ctrl.h new file mode 100644 index 0000000..50d26ed --- /dev/null +++ b/src/thread_safe_log_ctrl.h @@ -0,0 +1,36 @@ +#pragma once +#include "colored_log_ctrl.h" +#include +#include +#include + +class ThreadSafeLogCtrl : public ColoredLogCtrl { +public: + ThreadSafeLogCtrl(wxWindow* parent); + ~ThreadSafeLogCtrl(); + + // 线程安全的日志方法 + void LogMessageThreadSafe(const wxString& message, const wxColour& color = *wxBLACK); + void LogErrorThreadSafe(const wxString& message); + void LogWarningThreadSafe(const wxString& message); + void LogInfoThreadSafe(const wxString& message); + void LogSuccessThreadSafe(const wxString& message); + +private: + struct LogEntry { + wxString message; + wxColour color; + }; + + void OnTimer(wxTimerEvent& event); + void ProcessPendingLogs(); + + std::queue pending_logs_; + mutable std::mutex log_mutex_; + wxTimer update_timer_; + + static constexpr int UPDATE_INTERVAL = 100; // 100ms + static constexpr size_t MAX_LINES = 10000; // 最大行数 + + wxDECLARE_EVENT_TABLE(); +};