Compare commits

1 Commits
master ... next

Author SHA1 Message Date
daiqingshuang
31e632032f 重构 2025-05-27 16:52:47 +08:00
12 changed files with 1257 additions and 491 deletions

View File

@@ -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();
}

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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();
};

View File

@@ -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();
}

View File

@@ -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;
};

View 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();
}

View 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();
};