Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e632032f |
@@ -1,6 +1,7 @@
|
||||
#include "branch_selector.h"
|
||||
#include "config_manager.h"
|
||||
#include "task_manager.h"
|
||||
#include <wx/log.h>
|
||||
|
||||
wxBEGIN_EVENT_TABLE(BranchSelector, wxPanel)
|
||||
EVT_COMBOBOX(wxID_ANY, BranchSelector::OnBranchSelected)
|
||||
@@ -10,52 +11,103 @@ BranchSelector::BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository
|
||||
const wxString& label)
|
||||
: wxPanel(parent, wxID_ANY), repo_(repo) {
|
||||
|
||||
wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
|
||||
// 创建界面元素
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// 如果没有提供标签,使用存储库名称
|
||||
// 标签
|
||||
wxString display_label = label.IsEmpty() ? wxString(repo->GetName()) : 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::vector<std::string>, 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<std::string, std::string>& 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();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
#include <wx/wx.h>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include "git_repository.h"
|
||||
|
||||
class BranchSelector : public wxPanel {
|
||||
public:
|
||||
BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository> 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<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(*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<git::Repository> repo_;
|
||||
wxString current_group_;
|
||||
std::atomic<bool> is_busy_{false};
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
134
src/git_process_manager.cpp
Normal file
134
src/git_process_manager.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "git_process_manager.h"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/txtstrm.h>
|
||||
|
||||
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<ProcessInfo>(std::unique_ptr<wxProcess, void(*)(wxProcess*)>(
|
||||
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<ProcessInfo> 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/git_process_manager.h
Normal file
47
src/git_process_manager.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <wx/process.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
namespace git {
|
||||
|
||||
class ProcessManager : public wxEvtHandler {
|
||||
public:
|
||||
using Result = std::expected<std::string, std::string>;
|
||||
using Callback = std::function<void(Result)>;
|
||||
|
||||
ProcessManager();
|
||||
~ProcessManager();
|
||||
|
||||
void ExecuteCommand(const std::string& cmd,
|
||||
const std::filesystem::path& path,
|
||||
Callback callback);
|
||||
|
||||
private:
|
||||
struct ProcessInfo {
|
||||
ProcessInfo(std::unique_ptr<wxProcess, void(*)(wxProcess*)> in_process) : process(std::move(in_process)),
|
||||
timer(nullptr) {
|
||||
}
|
||||
|
||||
std::unique_ptr<wxProcess, void(*)(wxProcess*)> 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<int, std::unique_ptr<ProcessInfo>> active_processes_;
|
||||
std::mutex mutex_;
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
} // namespace git
|
||||
@@ -1,223 +1,268 @@
|
||||
#include "git_repository.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <direct.h>
|
||||
#include <bits/this_thread_sleep.h>
|
||||
#include <wx/process.h>
|
||||
#include <wx/txtstrm.h>
|
||||
|
||||
#include "git_command.h"
|
||||
#include "git_process_manager.h"
|
||||
#include "task_manager.h"
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
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<ProcessManager> Repository::process_manager_ = std::make_shared<ProcessManager>();
|
||||
|
||||
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<void(git::result_t)> 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<void(result_t)>& 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<void(result_t)>& in_callback) {
|
||||
GitCommandTask("reset --hard", path_, [in_callback](result_t result) {
|
||||
in_callback(result);
|
||||
});
|
||||
void Repository::GetSubmodules(std::function<void(Submodules)> 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<std::shared_ptr<Repository>> 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<Repository>(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<void(result_t)>& in_callback) {
|
||||
GitCommandTask("pull", path_, [in_callback](result_t result) {
|
||||
in_callback(result);
|
||||
});
|
||||
void Repository::GetCurrentBranchName(std::function<void(Result)> 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<void(submodules_t)>& in_callback) const {
|
||||
{
|
||||
std::shared_lock s_lock(submodules_mutex_);
|
||||
if (!submodules_.empty()) {
|
||||
in_callback(submodules_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
void Repository::GetAllBranches(std::function<void(Branches)> 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<std::shared_ptr<git::Repository>> 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<Repository>(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::string> {
|
||||
std::vector<std::string> 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<void(result_t)>& 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<std::vector<std::shared_ptr<Repository>>> Repository::GetCachedSubmodules() const {
|
||||
std::shared_lock lock(cache_mutex_);
|
||||
return cached_submodules_;
|
||||
}
|
||||
|
||||
void git::Repository::GetAllBranches(
|
||||
const std::function<void(branches_t)>& 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<std::string> 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<std::string> Repository::GetCachedBranchName() const {
|
||||
std::shared_lock lock(cache_mutex_);
|
||||
return cached_branch_;
|
||||
}
|
||||
|
||||
std::expected<std::vector<std::shared_ptr<git::Repository>>, 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<std::vector<std::string>> Repository::GetCachedBranches() const {
|
||||
std::shared_lock lock(cache_mutex_);
|
||||
return cached_branches_;
|
||||
}
|
||||
|
||||
std::expected<std::string, std::string> 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::vector<std::string>, 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<Repository> OpenRepository(const std::filesystem::path& path) {
|
||||
auto repo = std::make_shared<Repository>(path);
|
||||
if (repo->IsValid()) {
|
||||
return repo;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<git::Repository> git::OpenRepository(const std::filesystem::path& in_path) {
|
||||
auto repo = std::make_shared<Repository>(in_path);
|
||||
if (repo->IsValid())
|
||||
return repo;
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace git
|
||||
|
||||
@@ -6,47 +6,58 @@
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <future>
|
||||
|
||||
namespace git {
|
||||
|
||||
class ProcessManager;
|
||||
class Repository;
|
||||
|
||||
using result_t = std::expected<std::string, std::string>;
|
||||
using submodules_t = std::expected<std::vector<std::shared_ptr<Repository>>, std::string>;
|
||||
using branches_t = std::expected<std::vector<std::string>, std::string>;
|
||||
using Result = std::expected<std::string, std::string>;
|
||||
using Submodules = std::expected<std::vector<std::shared_ptr<Repository>>, std::string>;
|
||||
using Branches = std::expected<std::vector<std::string>, std::string>;
|
||||
using Callback = std::function<void(Result)>;
|
||||
|
||||
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<void(result_t)>& in_callback);
|
||||
void HardReset(const std::function<void(result_t)>& in_callback);
|
||||
void Pull(const std::function<void(result_t)>& in_callback);
|
||||
// 获取信息
|
||||
void GetSubmodules(std::function<void(Submodules)> callback);
|
||||
void GetCurrentBranchName(std::function<void(Result)> callback);
|
||||
void GetAllBranches(std::function<void(Branches)> callback);
|
||||
|
||||
void GetSubmodules(const std::function<void(submodules_t)>& in_callback) const;
|
||||
void GetCurrentBranchName(const std::function<void(result_t)>& in_callback) const;
|
||||
void GetAllBranches(const std::function<void(branches_t)>& in_callback) const;
|
||||
// 同步版本(用于缓存数据)
|
||||
std::optional<std::vector<std::shared_ptr<Repository>>> GetCachedSubmodules() const;
|
||||
std::optional<std::string> GetCachedBranchName() const;
|
||||
std::optional<std::vector<std::string>> GetCachedBranches() const;
|
||||
|
||||
[[nodiscard]] std::expected<std::vector<std::shared_ptr<Repository>>, std::string> GetSubmodules() const;
|
||||
std::string GetName() const { return name_; }
|
||||
bool IsValid() 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;
|
||||
private:
|
||||
void ExecuteCommand(const std::string& cmd, Callback callback);
|
||||
void InvalidateCache();
|
||||
|
||||
std::string name_;
|
||||
std::filesystem::path path_;
|
||||
mutable std::vector<std::shared_ptr<Repository>> submodules_;
|
||||
mutable std::optional<std::string> branch_;
|
||||
mutable std::vector<std::string> 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<std::vector<std::shared_ptr<Repository>>> cached_submodules_;
|
||||
mutable std::optional<std::string> cached_branch_;
|
||||
mutable std::optional<std::vector<std::string>> cached_branches_;
|
||||
|
||||
// 进程管理器
|
||||
static std::shared_ptr<ProcessManager> process_manager_;
|
||||
};
|
||||
|
||||
std::shared_ptr<Repository> OpenRepository(const std::filesystem::path& in_path);
|
||||
}
|
||||
std::shared_ptr<Repository> OpenRepository(const std::filesystem::path& path);
|
||||
|
||||
} // namespace git
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "main_frame.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "branch_selector.h"
|
||||
#include "config_manager.h"
|
||||
#include "task_manager.h"
|
||||
#include "wx/progdlg.h"
|
||||
#include "thread_safe_log_ctrl.h"
|
||||
#include <wx/statline.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <set>
|
||||
|
||||
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<BranchSelector>(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<BranchSelector>(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::vector<std::string>, std::string>& branches) {
|
||||
// 获取所有分支组
|
||||
std::vector<std::string> 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<std::set<std::string>>();
|
||||
auto completed_count = std::make_shared<std::atomic<int>>(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();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <wx/wx.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
#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<BranchSelector*> branch_selectors_;
|
||||
// UI组件
|
||||
std::vector<std::unique_ptr<BranchSelector>> 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<git::Repository> main_repo_;
|
||||
bool is_hard_reset_ = false;
|
||||
|
||||
std::atomic<int> completed{0};
|
||||
std::atomic<bool> has_errors{false};
|
||||
// 异步操作管理
|
||||
std::vector<std::future<void>> pending_operations_;
|
||||
std::atomic<int> active_operations_{0};
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
@@ -1 +1,93 @@
|
||||
#include "task_manager.h"
|
||||
#include <wx/log.h>
|
||||
|
||||
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<std::mutex> 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<void()> task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<std::mutex> lock(queue_mutex_);
|
||||
return tasks_.size();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// task_manager.h
|
||||
#pragma once
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
@@ -8,91 +7,68 @@
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <wx/app.h>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
// 自定义事件用于线程安全的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<typename Func, typename Callback>
|
||||
void PushTask(Func&& task, Callback&& callback) {
|
||||
{
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
tasks_.emplace([task = std::forward<Func>(task),
|
||||
callback = std::forward<Callback>(callback)]() {
|
||||
auto result = task();
|
||||
// 使用 wxCallAfter 确保回调在主线程执行
|
||||
wxTheApp->CallAfter([result, callback]() {
|
||||
callback(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
cv_.notify_one();
|
||||
}
|
||||
// 删除拷贝和移动
|
||||
TaskManager(const TaskManager&) = delete;
|
||||
TaskManager& operator=(const TaskManager&) = delete;
|
||||
|
||||
template<typename Func>
|
||||
auto PushTaskAsync(Func&& task) -> std::future<decltype(task())> {
|
||||
auto promise = std::make_shared<std::promise<decltype(task())>>();
|
||||
auto future = promise->get_future();
|
||||
auto Execute(Func&& task) -> std::future<decltype(task())> {
|
||||
using ReturnType = decltype(task());
|
||||
|
||||
PushTask(std::forward<Func>(task),
|
||||
[promise](auto result) { promise->set_value(result); });
|
||||
auto packaged_task = std::make_shared<std::packaged_task<ReturnType()>>(
|
||||
std::forward<Func>(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<typename Func>
|
||||
void ExecuteOnMainThread(Func&& func) {
|
||||
if (wxIsMainThread()) {
|
||||
func();
|
||||
} else {
|
||||
wxTheApp->CallAfter(std::forward<Func>(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<void()> 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<std::thread> workers_;
|
||||
std::queue<std::function<void()>> tasks_;
|
||||
std::mutex queue_mutex_;
|
||||
mutable std::mutex queue_mutex_;
|
||||
std::condition_variable cv_;
|
||||
std::atomic<bool> shutdown_;
|
||||
std::atomic<bool> shutdown_{false};
|
||||
|
||||
static constexpr size_t DEFAULT_THREAD_COUNT = 4;
|
||||
};
|
||||
|
||||
105
src/thread_safe_log_ctrl.cpp
Normal file
105
src/thread_safe_log_ctrl.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "thread_safe_log_ctrl.h"
|
||||
#include <wx/datetime.h>
|
||||
|
||||
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<std::mutex> 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<LogEntry> logs_to_process;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> 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();
|
||||
}
|
||||
|
||||
36
src/thread_safe_log_ctrl.h
Normal file
36
src/thread_safe_log_ctrl.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "colored_log_ctrl.h"
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <wx/timer.h>
|
||||
|
||||
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<LogEntry> 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();
|
||||
};
|
||||
Reference in New Issue
Block a user