This commit is contained in:
daiqingshuang 2025-04-10 18:29:49 +08:00
commit 6eb14af6e1
16 changed files with 1630 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
/cmake-build-debug
/cmake-build-release
/.idea
/scripts/shader_paths.txt
/cache/shader_loader.h
#>fips
# this area is managed by fips, do not edit
.fips-*
fips-files/build/
fips-files/deploy/
*.pyc
.vscode/
.idea/
CMakeUserPresets.json
#<fips
/tools/shader_path.txt
/cache

46
CMakeLists.txt Normal file
View File

@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.16)
project(branch_switcher)
include(cmake/project_cpp_standard.cmake)
set_cpp_standard(20)
# wxWidgets
set(wxWidgets_ROOT_DIR "D:/Software/msys2/mingw64")
# MSYS2
if(MINGW)
add_definitions(-D__WXMSW__)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition")
endif()
#
find_package(wxWidgets REQUIRED COMPONENTS core base adv html net)
find_package(unofficial-libgit2 REQUIRED)
find_package(Boost REQUIRED COMPONENTS process)
# wxWidgets
message(STATUS "wxWidgets_FOUND: ${wxWidgets_FOUND}")
message(STATUS "wxWidgets_INCLUDE_DIRS: ${wxWidgets_INCLUDE_DIRS}")
message(STATUS "wxWidgets_LIBRARIES: ${wxWidgets_LIBRARIES}")
# wxWidgets
include(${wxWidgets_USE_FILE})
include(cmake/retrieve_files.cmake)
#
set(SRC_FILES "")
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(${PROJECT_NAME} ${SRC_FILES})
#
target_include_directories(${PROJECT_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${wxWidgets_INCLUDE_DIRS}
)
#
target_link_libraries(${PROJECT_NAME} PRIVATE
${wxWidgets_LIBRARIES}
unofficial::libgit2::libgit2
Boost::process
)

View File

@ -0,0 +1,54 @@
function(set_cpp_standard standard)
#
set(VALID_STANDARDS 11 14 17 20 23)
if(NOT ${standard} IN_LIST VALID_STANDARDS)
message(WARNING "非标准 C++ 版本: ${standard},支持的版本有: ${VALID_STANDARDS}")
endif()
# C++
set(CMAKE_CXX_STANDARD ${standard} PARENT_SCOPE)
#
set(CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE)
# 使
set(CMAKE_CXX_EXTENSIONS OFF PARENT_SCOPE)
if(MSVC)
# utf-8
add_compile_options(/utf-8)
# MSVC __cplusplus
add_compile_options(/Zc:__cplusplus)
# MSVC
add_compile_options(/W4)
# : UNICODE
add_definitions(-DUNICODE -D_UNICODE)
endif()
if(WIN32)
# WIN32_LEAN_AND_MEAN Windows
add_definitions(-DWIN32_LEAN_AND_MEAN)
endif()
# GCC/Clang
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra)
# 使
add_compile_options(-Wno-unused-parameter)
# C++
if(${standard} GREATER 14) # 使 GREATER_EQUAL
add_compile_options(-Wshadow -Wnon-virtual-dtor)
endif()
endif()
# MinGW, 使C++17, libstdc++exp
if(MINGW)
message(STATUS "检测到MinGW编译器")
#
if(${standard} GREATER 14) # C++17
message(STATUS "为C++${standard}添加libstdc++exp库支持")
link_libraries(-lstdc++exp)
endif()
endif()
message(STATUS "已设置C++${standard}标准")
endfunction()

317
cmake/retrieve_files.cmake Normal file
View File

@ -0,0 +1,317 @@
#[=======================================================================[
:
platform: 平台标识符 (windows|linux|mac|mobile|desktop)
is_match:
#]=======================================================================]
function(is_current_platform platform is_match)
# TRUE
set(matches FALSE)
if(platform STREQUAL "windows")
if(WIN32)
set(matches TRUE)
endif()
elseif(platform STREQUAL "linux")
if(UNIX AND NOT APPLE)
set(matches TRUE)
endif()
elseif(platform STREQUAL "mac")
if(APPLE AND NOT IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "ios")
if(IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "android")
if(ANDROID)
set(matches TRUE)
endif()
# unix
elseif(platform STREQUAL "unix")
if(UNIX)
set(matches TRUE)
endif()
elseif(platform STREQUAL "mobile")
if(ANDROID OR IOS)
set(matches TRUE)
endif()
elseif(platform STREQUAL "desktop")
if(WIN32 OR (UNIX AND NOT APPLE) OR (APPLE AND NOT IOS))
set(matches TRUE)
endif()
else()
#
set(matches TRUE)
endif()
set(${is_match} ${matches} PARENT_SCOPE)
endfunction()
#[=======================================================================[
:
path:
extension:
out_files:
#]=======================================================================]
function(retrieve_files_custom path extension out_files)
# 1.
if(NOT IS_DIRECTORY "${path}")
message(WARNING "错误:目录 '${path}' 不存在")
return()
endif()
message(STATUS "正在检索目录: ${path}")
# 2.
set(file_patterns "")
foreach(ext IN LISTS extension)
list(APPEND file_patterns "${path}/*.${ext}")
endforeach()
# 3.
file(GLOB_RECURSE found_files
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
CONFIGURE_DEPENDS ${file_patterns}
)
# 4.
set(filtered_files "")
foreach(current_file IN LISTS found_files)
# 4.1
get_filename_component(file_dir "${current_file}" DIRECTORY)
string(REPLACE "/" ";" dir_components "${file_dir}")
# 4.2
set(should_skip_file FALSE)
set(found_platform_dir FALSE)
foreach(dir_name IN LISTS dir_components)
#
if(dir_name MATCHES "^(windows|linux|mac|ios|android|unix|mobile|desktop)$")
set(found_platform_dir TRUE)
is_current_platform(${dir_name} platform_matches)
if(NOT platform_matches)
set(should_skip_file TRUE)
break()
endif()
endif()
endforeach()
#
if(should_skip_file)
continue()
endif()
# 4.3
list(APPEND filtered_files "${current_file}")
# 4.4 IDE
#
get_filename_component(root_abs_path "${path}" ABSOLUTE)
get_filename_component(file_dir_abs_path "${file_dir}" ABSOLUTE)
file(RELATIVE_PATH group_path "${root_abs_path}" "${file_dir_abs_path}")
#
if(group_path STREQUAL ".")
set(group_name "")
else()
string(REPLACE "/" "\\" group_name "${group_path}")
endif()
# IDE
source_group("${group_name}" FILES "${current_file}")
endforeach()
# 5.
set(${out_files} ${filtered_files} PARENT_SCOPE)
endfunction()
#[=======================================================================[
便
#]=======================================================================]
function(retrieve_files path out_files)
#
set(file_extensions
"h" #
"hpp" # C++
"ini" #
"cpp" # C++
"c" # C
"ixx" # C++20
)
# Mac
if(APPLE)
list(APPEND file_extensions "mm") # Objective-C++
endif()
#
set(temp_files "")
retrieve_files_custom(${path} "${file_extensions}" temp_files)
#
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
endfunction()
#[=======================================================================[
#
# CMAKE_RUNTIME_OUTPUT_DIRECTORY EXECUTABLE_OUTPUT_PATH
#
#
# :
# TARGET_NAME: () - 关联的目标 (库或可执行文件)
# TARGET_NAME
# RESOURCE_FILES: () - 一个或多个要复制的资源文件的路径列表 (相对或绝对)
# OUTPUT_SUBDIR: () - 相对于可执行文件输出目录的子目录路径 (例如 "assets")
#
# :
# # 确保设置了可执行文件输出目录 (通常在顶层 CMakeLists.txt)
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#
# #
# add_library(my_lib STATIC src/my_lib.cpp)
#
# # my_lib 'config'
# add_resource_file(
# TARGET_NAME my_lib
# RESOURCE_FILES config/settings.json config/defaults.ini
# OUTPUT_SUBDIR config
# )
#
# #
# add_executable(my_app main.cpp)
# target_link_libraries(my_app PRIVATE my_lib)
#
# # my_app
# add_resource_file(
# TARGET_NAME my_app
# RESOURCE_FILES assets/icon.png
# )
#]=======================================================================]
function(add_resource_file)
#
set(options "") #
set(oneValueArgs TARGET_NAME OUTPUT_SUBDIR)
set(multiValueArgs RESOURCE_FILES)
#
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# --- ---
if(NOT ARG_TARGET_NAME)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **TARGET_NAME**.")
endif()
if(NOT ARG_RESOURCE_FILES)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **RESOURCE_FILES**.")
endif()
if(NOT TARGET ${ARG_TARGET_NAME})
message(WARNING "**add_resource_file**: 目标 '${ARG_TARGET_NAME}' (尚)不存在。请确保在调用 add_executable/add_library('${ARG_TARGET_NAME}') 之后调用此函数。")
# 使CMake
endif()
# --- ---
set(DESTINATION_BASE "")
if(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(DESTINATION_BASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
elseif(DEFINED EXECUTABLE_OUTPUT_PATH AND EXECUTABLE_OUTPUT_PATH)
# EXECUTABLE_OUTPUT_PATH
set(DESTINATION_BASE "${EXECUTABLE_OUTPUT_PATH}")
else()
# Visual Studio, Xcode
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
#
# 使 $<OUTPUT_DIRECTORY> 使
# CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
# CMAKE_RUNTIME_OUTPUT_DIRECTORY
message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。对于多配置生成器,可能需要设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> 变量。")
else()
# Makefiles, Ninja CMAKE_BINARY_DIR
set(DESTINATION_BASE "${CMAKE_BINARY_DIR}")
message(WARNING "**add_resource_file**: **未设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY**。默认将资源复制到 CMAKE_BINARY_DIR ('${CMAKE_BINARY_DIR}')。强烈建议设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY 以获得可预测的行为。")
endif()
# message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。")
endif()
#
set(DESTINATION_DIR "${DESTINATION_BASE}") #
if(ARG_OUTPUT_SUBDIR)
#
string(STRIP "${ARG_OUTPUT_SUBDIR}" _subdir)
if(IS_ABSOLUTE "${_subdir}")
message(FATAL_ERROR "**add_resource_file**: **OUTPUT_SUBDIR** ('${ARG_OUTPUT_SUBDIR}') **必须是相对路径**。")
else()
# /便
string(REGEX REPLACE "^[/\\\\]+" "" _subdir "${_subdir}")
string(REGEX REPLACE "[/\\\\]+$" "" _subdir "${_subdir}")
if(_subdir) #
set(DESTINATION_DIR "${DESTINATION_BASE}/${_subdir}")
endif()
endif()
endif()
# --- ---
set(ABS_RESOURCE_FILES "")
foreach(RESOURCE_FILE ${ARG_RESOURCE_FILES})
get_filename_component(RESOURCE_FILE_REALPATH "${RESOURCE_FILE}" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
# if(IS_ABSOLUTE "${RESOURCE_FILE}")
# # 使
# list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE}")
# else()
# #
# list(APPEND ABS_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE}")
# endif()
list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE_REALPATH}")
# 检查文件是否存在 (在配置时发出警告,有助于早期发现错误)
# list(GET ABS_RESOURCE_FILES -1 _current_abs_file) #
if(NOT EXISTS "${RESOURCE_FILE_REALPATH}")
message(WARNING "**add_resource_file**: **资源文件** '${RESOURCE_FILE}' (解析为 '${RESOURCE_FILE_REALPATH}') **在配置时不存在**。")
endif()
endforeach()
# --- ---
# 使 add_custom_command
if(ABS_RESOURCE_FILES) #
# DESTINATION_DIR CMAKE_RUNTIME_OUTPUT_DIRECTORY
# ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin
# add_custom_command COMMAND /
add_custom_command(
TARGET ${ARG_TARGET_NAME}
POST_BUILD #
# 1: 确保目标目录存在 (copy_if_different 不会创建目录)
COMMAND ${CMAKE_COMMAND} -E make_directory "${DESTINATION_DIR}"
# 2:
COMMAND ${CMAKE_COMMAND} -E copy_if_different # 使CMake
${ABS_RESOURCE_FILES} #
"${DESTINATION_DIR}" # 最终可执行文件所在的目标目录 (带引号以处理空格)
COMMENT " ${ARG_TARGET_NAME} : ${DESTINATION_DIR}..." #
VERBATIM #
)
else()
message(WARNING "**add_resource_file**: 没有有效的资源文件提供给目标 '${ARG_TARGET_NAME}'。")
endif()
# --- : IDE ---
if(ABS_RESOURCE_FILES)
set(_source_group_name "Resource Files") #
if(ARG_OUTPUT_SUBDIR)
# 使
string(STRIP "${ARG_OUTPUT_SUBDIR}" _clean_subdir)
string(REPLACE "\\" "/" _clean_subdir "${_clean_subdir}") # 使
string(REGEX REPLACE "^[/]+" "" _clean_subdir "${_clean_subdir}")
string(REGEX REPLACE "[/]+$" "" _clean_subdir "${_clean_subdir}")
if(_clean_subdir)
set(_source_group_name "Resource Files/${_clean_subdir}")
endif()
endif()
# 使 source_group IDE
source_group(${_source_group_name} FILES ${ABS_RESOURCE_FILES})
endif()
endfunction()

99
src/branch_selector.cpp Normal file
View File

@ -0,0 +1,99 @@
#include "branch_selector.h"
#include "config_manager.h"
wxBEGIN_EVENT_TABLE(BranchSelector, wxPanel)
EVT_COMBOBOX(wxID_ANY, BranchSelector::OnBranchSelected)
wxEND_EVENT_TABLE()
BranchSelector::BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository> repo,
const wxString& label)
: wxPanel(parent, wxID_ANY), repo_(repo) {
wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
// 如果没有提供标签,使用存储库名称
wxString display_label = label.IsEmpty() ? wxString(repo->GetName()) : label;
wxStaticText* label_ctrl = new wxStaticText(this, wxID_ANY, display_label);
// 创建下拉列表
branch_combo_ = new wxComboBox(this, wxID_ANY);
// 填充分支列表
const auto& branches = repo->GetAllBranches();
for (const auto& branch : branches) {
branch_combo_->Append(branch);
}
// 选择当前分支
branch_combo_->SetStringSelection(repo->GetCurrentBranchName());
// 布局
hbox->Add(label_ctrl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
hbox->Add(branch_combo_, 1);
SetSizerAndFit(hbox);
}
bool BranchSelector::SelectBranch(const wxString& branch_name) {
return branch_combo_->SetStringSelection(branch_name);
}
bool BranchSelector::SelectGroupBranch(const wxString& group_name) {
current_group_ = group_name;
// 尝试从配置中获取分支
bool found = false;
std::string branch_name = ConfigManager::Instance().GetBranchFromGroup(
group_name.ToStdString(),
repo_->GetName(),
"Develop",
&found
);
// 如果找到了缓存,使用缓存,否则使用组名
if (found) {
if (!branch_combo_->SetStringSelection(branch_name)) {
// 如果设置失败,可能是因为分支不存在,尝试使用组名
return branch_combo_->SetStringSelection(group_name);
}
return true;
} else {
// 尝试使用组名作为分支名
if (!branch_combo_->SetStringSelection(group_name)) {
// 如果组名设置失败,使用默认分支
return branch_combo_->SetStringSelection(branch_name);
}
return true;
}
}
std::string BranchSelector::GetSelectedBranchName() const {
return branch_combo_->GetStringSelection().ToStdString();
}
bool BranchSelector::ApplyBranchChange(bool hard_reset) {
std::string selected_branch = GetSelectedBranchName();
if (selected_branch.empty()) {
return false;
}
// 切换分支
bool success = repo_->SwitchBranch(selected_branch, hard_reset);
if (!success) {
return false;
}
// 拉取更新
return repo_->Pull();
}
void BranchSelector::OnBranchSelected(wxCommandEvent& event) {
// 如果选择的分支不是组名,保存到配置
if (!current_group_.IsEmpty() && event.GetString() != current_group_) {
ConfigManager::Instance().SaveBranchToGroup(
current_group_.ToStdString(),
repo_->GetName(),
event.GetString().ToStdString()
);
}
}

26
src/branch_selector.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <wx/wx.h>
#include <memory>
#include "git_repository.h"
class BranchSelector : public wxPanel {
public:
BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository> repo,
const wxString& label = wxEmptyString);
bool SelectBranch(const wxString& branch_name);
bool SelectGroupBranch(const wxString& group_name);
std::string GetSelectedBranchName() const;
bool ApplyBranchChange(bool hard_reset);
std::shared_ptr<git::Repository> GetRepo() const { return repo_; }
private:
void OnBranchSelected(wxCommandEvent& event);
wxComboBox* branch_combo_ = nullptr;
std::shared_ptr<git::Repository> repo_;
wxString current_group_;
wxDECLARE_EVENT_TABLE();
};

39
src/colored_log_ctrl.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "colored_log_ctrl.h"
ColoredLogCtrl::ColoredLogCtrl(wxWindow* parent)
: wxTextCtrl(parent, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2) {
SetFont(wxFont(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
}
void ColoredLogCtrl::LogMessage(const wxString& message, const wxColour& color) {
// 获取当前文本长度
long start = GetLastPosition();
// 添加时间戳和消息
wxString timeStamp = wxDateTime::Now().Format("%Y-%m-%d %H:%M:%S: ");
AppendText(timeStamp + message + "\n");
// 设置颜色
SetStyle(start, GetLastPosition(), wxTextAttr(color));
// 滚动到底部
ShowPosition(GetLastPosition());
}
void ColoredLogCtrl::LogError(const wxString& message) {
LogMessage(message, *wxRED);
}
void ColoredLogCtrl::LogWarning(const wxString& message) {
LogMessage(message, wxColour(255, 140, 0)); // 橙色
}
void ColoredLogCtrl::LogInfo(const wxString& message) {
LogMessage(message, *wxBLUE);
}
void ColoredLogCtrl::LogSuccess(const wxString& message) {
LogMessage(message, wxColour(0, 128, 0)); // 绿色
}

13
src/colored_log_ctrl.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <wx/wx.h>
class ColoredLogCtrl : public wxTextCtrl {
public:
ColoredLogCtrl(wxWindow* parent);
void LogMessage(const wxString& message, const wxColour& color = *wxBLACK);
void LogError(const wxString& message);
void LogWarning(const wxString& message);
void LogInfo(const wxString& message);
void LogSuccess(const wxString& message);
};

68
src/config_manager.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "config_manager.h"
#include <fstream>
#include <iostream>
ConfigManager& ConfigManager::Instance() {
static ConfigManager instance;
return instance;
}
bool ConfigManager::Load(const std::string& file_path) {
// 检查文件是否存在
std::ifstream file(file_path);
if (!file.good()) {
// 文件不存在或无法访问
InitializeDefaultConfig();
return false;
}
file.close();
// 加载配置文件
SI_Error result = ini_.LoadFile(file_path.c_str());
if (result < 0) {
// 加载失败
InitializeDefaultConfig();
return false;
}
return true;
}
bool ConfigManager::Save(const std::string& file_path) {
SI_Error result = ini_.SaveFile(file_path.c_str());
return result >= 0;
}
void ConfigManager::InitializeDefaultConfig() {
// 设置默认值
ini_.SetValue("settings", "repo_path", "");
ini_.SetBoolValue("settings", "hard_reset", false);
}
std::string ConfigManager::GetRepositoryPath() const {
return ini_.GetValue("settings", "repo_path", "");
}
void ConfigManager::SetRepositoryPath(const std::string& path) {
ini_.SetValue("settings", "repo_path", path.c_str());
}
bool ConfigManager::GetHardReset() const {
return ini_.GetBoolValue("settings", "hard_reset", false);
}
void ConfigManager::SetHardReset(bool value) {
ini_.SetBoolValue("settings", "hard_reset", value);
}
void ConfigManager::SaveBranchToGroup(const std::string& group_name, const std::string& repo_name, const std::string& branch_name) {
ini_.SetValue(group_name.c_str(), repo_name.c_str(), branch_name.c_str());
}
std::string ConfigManager::GetBranchFromGroup(const std::string& group_name, const std::string& repo_name, const std::string& default_branch, bool* found) {
const char* value = ini_.GetValue(group_name.c_str(), repo_name.c_str(), default_branch.c_str());
if (found) {
*found = (value != default_branch.c_str()); // 检查是否使用了默认值
}
return value;
}

34
src/config_manager.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <memory>
#include "SimpleIni.h"
class ConfigManager {
public:
static ConfigManager& Instance();
bool Load(const std::string& file_path = "repo.ini");
bool Save(const std::string& file_path = "repo.ini");
std::string GetRepositoryPath() const;
void SetRepositoryPath(const std::string& path);
bool GetHardReset() const;
void SetHardReset(bool value);
// 分支组操作
void SaveBranchToGroup(const std::string& group_name, const std::string& repo_name, const std::string& branch_name);
std::string GetBranchFromGroup(const std::string& group_name, const std::string& repo_name, const std::string& default_branch = "Develop", bool* found = nullptr);
private:
ConfigManager() = default;
~ConfigManager() = default;
void InitializeDefaultConfig();
CSimpleIniA ini_;
// 禁止复制
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
};

552
src/git_repository.cpp Normal file
View File

@ -0,0 +1,552 @@
#include "git_repository.h"
#include <iostream>
#include <sstream>
/**
* @brief libgit2
*
* 0 libgit2
* std::runtime_error
*
* @param error_code libgit2
* @param action_description
* @throws std::runtime_error error_code
*/
void CheckGitError(int error_code, const std::string& action_description) {
// libgit2 通常在出错时返回负值
if (error_code < 0) {
// 获取 libgit2 线程本地存储中的最后一个错误信息
const git_error* last_error = git_error_last();
// 构建详细的错误消息
std::string error_message = action_description + " 失败: ";
// 检查 last_error 是否有效以及是否有错误消息文本
if (last_error && last_error->message) {
error_message += last_error->message;
} else {
// 如果 libgit2 没有提供具体的错误信息
error_message += "未知错误 (libgit2 未提供详细信息)";
}
// 可以选择性地附加错误代码以供调试
error_message += " (错误码: " + std::to_string(error_code) + ")";
// 抛出标准运行时异常
throw std::runtime_error(error_message);
}
// 如果 error_code >= 0则表示操作成功函数不执行任何操作
}
namespace git {
// --- 假设 RAII 封装类已存在 ---
// GitRemote 示例:
class GitRemote : public GitResource {
public:
explicit GitRemote(git_remote* remote) : remote_(remote) {}
~GitRemote() override { if (remote_) git_remote_free(remote_); }
operator git_remote*() const { return remote_; }
private:
git_remote* remote_ = nullptr;
};
// GitAnnotatedCommit 示例:
class GitAnnotatedCommit : public GitResource {
public:
explicit GitAnnotatedCommit(git_annotated_commit* commit) : commit_(commit) {}
~GitAnnotatedCommit() override { if (commit_) git_annotated_commit_free(commit_); }
operator git_annotated_commit*() const { return commit_; }
const git_oid* Id() const { return commit_ ? git_annotated_commit_id(commit_) : nullptr; }
private:
git_annotated_commit* commit_ = nullptr;
};
// GitIndex 示例
class GitIndex : public GitResource {
public:
explicit GitIndex(git_index *index) : index_(index) {}
~GitIndex() override { if (index_) git_index_free(index_); }
operator git_index*() const { return index_; }
bool HasConflicts() const { return index_ && git_index_has_conflicts(index_); }
int Write() { return index_ ? git_index_write(index_) : -1; }
int ReadTree(const git_tree* tree) { return index_ ? git_index_read_tree(index_, tree) : -1; }
int WriteTree(git_oid* out_oid) { return index_ ? git_index_write_tree(out_oid, index_) : -1; }
private:
git_index* index_ = nullptr;
};
// GitCommit 示例
class GitCommit : public GitResource {
public:
explicit GitCommit(git_commit *commit) : commit_(commit) {}
~GitCommit() override { if (commit_) git_commit_free(commit_); }
operator git_commit*() const { return commit_; }
const git_oid* Id() const { return commit_ ? git_commit_id(commit_) : nullptr; }
const git_signature* Committer() const { return commit_ ? git_commit_committer(commit_) : nullptr; }
const git_signature* Author() const { return commit_ ? git_commit_author(commit_) : nullptr; }
const char* Message() const { return commit_ ? git_commit_message(commit_) : ""; }
git_tree* Tree() const {
git_tree* tree = nullptr;
if (commit_ && git_commit_tree(&tree, commit_) == 0) {
return tree; // 调用者必须管理此 tree 的生命周期或将其包装
}
return nullptr;
}
private:
git_commit* commit_ = nullptr;
};
// GitTree 示例
class GitTree : public GitResource {
public:
explicit GitTree(git_tree *tree) : tree_(tree) {}
~GitTree() override { if (tree_) git_tree_free(tree_); }
operator git_tree*() const { return tree_; }
const git_oid* Id() const { return tree_ ? git_tree_id(tree_) : nullptr; }
private:
git_tree* tree_ = nullptr;
};
// GitSignature 示例
class GitSignature : public GitResource {
public:
explicit GitSignature(git_signature *sig) : sig_(sig) {}
~GitSignature() override { if (sig_) git_signature_free(sig_); }
operator git_signature*() const { return sig_; }
private:
git_signature* sig_ = nullptr;
};
// --- RAII 封装类结束 ---
// GitReference 实现
std::string GitReference::GetShortName() const {
if (!ref_) return "";
return git_reference_shorthand(ref_);
}
// Repository 实现
Repository::Repository(git_repository* repo) : repo_(repo) {
if (repo_) {
path_ = git_repository_path(repo_);
// 默认仓库名为目录名
size_t last_slash = path_.find_last_of("/\\");
if (last_slash != std::string::npos) {
name_ = path_.substr(last_slash + 1);
// 如果路径以 .git 结尾,去掉它
size_t git_suffix = name_.find(".git");
if (git_suffix != std::string::npos && git_suffix == name_.size() - 4) {
name_ = name_.substr(0, git_suffix);
}
} else {
name_ = path_;
}
}
}
Repository::~Repository() {
if (repo_) {
git_repository_free(repo_);
repo_ = nullptr;
}
}
std::string Repository::GetCurrentBranchName() const {
if (!repo_) return "";
git_reference* head = nullptr;
int error = git_repository_head(&head, repo_);
if (error != 0) {
const git_error* e = git_error_last();
std::cerr << "Error getting HEAD: " << (e ? e->message : "unknown error") << std::endl;
return "";
}
GitReference ref(head);
return ref.GetShortName();
}
void ForEachBranch(git_repository* repo, git_branch_t flags,
const std::function<void(const GitReference&)>& callback) {
git_branch_iterator* iter = nullptr;
if (git_branch_iterator_new(&iter, repo, flags) != 0) {
return;
}
git_reference* ref = nullptr;
git_branch_t type;
while (git_branch_next(&ref, &type, iter) == 0) {
GitReference git_ref(ref);
callback(git_ref);
}
git_branch_iterator_free(iter);
}
std::vector<std::string> Repository::GetAllBranches() const {
std::vector<std::string> branches;
if (!repo_) return branches;
ForEachBranch(repo_, GIT_BRANCH_ALL, [&branches](const GitReference& ref) {
const char* name;
if (git_branch_name(&name, ref) == 0) {
branches.push_back(name);
}
});
return branches;
}
bool Repository::SwitchBranch(const std::string& branch_name, bool hard_reset) {
if (!repo_) return false;
try {
// 尝试查找本地分支
git_reference* target_ref = nullptr;
int error = git_branch_lookup(&target_ref, repo_, branch_name.c_str(), GIT_BRANCH_LOCAL);
if (error != 0) {
// 尝试查找远程分支
error = git_branch_lookup(&target_ref, repo_, branch_name.c_str(), GIT_BRANCH_REMOTE);
if (error != 0) {
std::cerr << "Branch not found: " << branch_name << std::endl;
return false;
}
}
GitReference ref(target_ref);
// 获取目标commit
git_object* target_commit = nullptr;
error = git_reference_peel(&target_commit, ref, GIT_OBJECT_COMMIT);
if (error != 0) {
std::cerr << "Failed to peel reference to commit" << std::endl;
return false;
}
// 创建检出选项
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
// 检出分支
error = git_checkout_tree(repo_, target_commit, &checkout_opts);
if (error != 0) {
git_object_free(target_commit);
std::cerr << "Failed to checkout tree" << std::endl;
return false;
}
git_object_free(target_commit);
// 更新HEAD引用
error = git_repository_set_head(repo_, git_reference_name(ref));
if (error != 0) {
std::cerr << "Failed to update HEAD reference" << std::endl;
return false;
}
// 如果需要硬重置
if (hard_reset) {
git_reference* remote_ref = nullptr;
std::string remote_branch_name = "origin/" + branch_name;
error = git_branch_lookup(&remote_ref, repo_, remote_branch_name.c_str(), GIT_BRANCH_REMOTE);
if (error == 0) {
GitReference remote(remote_ref);
git_object* remote_commit = nullptr;
error = git_reference_peel(&remote_commit, remote, GIT_OBJECT_COMMIT);
if (error == 0) {
// 重置到远程分支
error = git_reset(repo_, remote_commit, GIT_RESET_HARD, nullptr);
git_object_free(remote_commit);
if (error != 0) {
std::cerr << "Failed to hard reset" << std::endl;
return false;
}
}
}
}
return true;
}
catch (const std::exception& e) {
std::cerr << "Exception during branch switch: " << e.what() << std::endl;
return false;
}
}
bool Repository::Pull() {
if (!repo_) {
std::cerr << "Pull 错误:仓库未打开。" << std::endl;
return false;
}
// 初始化原始指针,稍后由 RAII 接管
git_reference* head_ref_ptr = nullptr;
git_annotated_commit* remote_head_annotated_ptr = nullptr;
git_remote* remote_ptr = nullptr;
try {
// 1. 获取当前的 HEAD 引用
CheckGitError(git_repository_head(&head_ref_ptr, repo_), "获取 HEAD 引用");
GitReference head_ref(head_ref_ptr); // RAII 封装
if (!git_reference_is_branch(head_ref)) {
std::cerr << "Pull 警告HEAD 处于分离状态。无法执行 pull。" << std::endl;
// 这不完全是错误,但 pull 在此无意义
return true; // 或根据期望行为返回 false
}
std::cout << "当前分支: " << git_reference_shorthand(head_ref) << std::endl;
// 2. 获取当前 HEAD 的上游分支引用
git_reference* upstream_ref_ptr = nullptr;
int upstream_error = git_branch_upstream(&upstream_ref_ptr, head_ref);
if (upstream_error == GIT_ENOTFOUND) {
std::cerr << "Pull 错误:分支 '" << git_reference_shorthand(head_ref)
<< "' 未配置上游分支。" << std::endl;
return false;
}
CheckGitError(upstream_error, "获取上游分支");
GitReference upstream_ref(upstream_ref_ptr); // RAII 封装
const char* upstream_name = git_reference_name(upstream_ref); // e.g., "refs/remotes/origin/main"
if (!upstream_name) {
throw std::runtime_error("无法获取上游引用名称。");
}
std::cout << "上游分支引用: " << upstream_name << std::endl;
// 3. 从上游引用中获取远程仓库名称和远程分支名称
char remote_name_buffer[256]; // 假设远程名称不会太长
git_buf remote_name_buf = { remote_name_buffer, 0, sizeof(remote_name_buffer), };
CheckGitError(git_branch_remote_name(&remote_name_buf, repo_, upstream_name), "获取远程仓库名称");
std::string remote_name_str(remote_name_buffer); // 例如 "origin"
if (remote_name_str.empty()) {
throw std::runtime_error("无法从上游引用确定远程仓库名称。");
}
std::cout << "远程仓库名称: " << remote_name_str << std::endl;
// 提取远程分支名称 (例如从 "refs/remotes/origin/main" 提取 "main")
std::string remote_branch_name_str;
const char* shorthand = git_reference_shorthand(upstream_ref); // 例如 "origin/main"
if (shorthand) {
std::string shorthand_str(shorthand);
size_t slash_pos = shorthand_str.find('/');
if (slash_pos != std::string::npos) {
remote_branch_name_str = shorthand_str.substr(slash_pos + 1);
}
}
if (remote_branch_name_str.empty()) {
throw std::runtime_error("无法从上游引用确定远程分支名称。");
}
std::cout << "远程分支名称: " << remote_branch_name_str << std::endl;
// 4. 查找远程仓库
CheckGitError(git_remote_lookup(&remote_ptr, repo_, remote_name_str.c_str()), "查找远程仓库 '" + remote_name_str + "'");
GitRemote remote(remote_ptr); // RAII 封装
// 5. 从远程仓库 Fetch
std::cout << "正在从 " << remote_name_str << " 拉取..." << std::endl;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
// 您可能需要配置 fetch_opts例如设置凭据回调
// fetch_opts.callbacks.credentials = ...;
CheckGitError(git_remote_fetch(remote, nullptr, &fetch_opts, "fetch: Pull 过程中自动 fetch"), "从远程仓库 Fetch");
std::cout << "Fetch 完成。" << std::endl;
// 6. 获取 Fetch 后的远程 HEAD 的 Annotated Commit
// upstream_ref 现在可能指向 *新* Fetch 下来的 commit ID
CheckGitError(git_annotated_commit_from_ref(&remote_head_annotated_ptr, repo_, upstream_ref), "获取远程 HEAD 的 Annotated Commit");
GitAnnotatedCommit remote_head_annotated(remote_head_annotated_ptr); // RAII 封装
// 7. 执行合并分析 (Merge Analysis)
git_merge_analysis_t analysis_out;
git_merge_preference_t preference_out;
const git_annotated_commit* merge_heads[] = {remote_head_annotated};
CheckGitError(git_merge_analysis(&analysis_out, &preference_out, repo_, merge_heads, 1), "执行合并分析");
// 8. 处理不同的合并场景
if (analysis_out & GIT_MERGE_ANALYSIS_UP_TO_DATE) {
std::cout << "分支 '" << git_reference_shorthand(head_ref)
<< "' 已经是最新的。" << std::endl;
return true;
} else {
if (analysis_out & GIT_MERGE_ANALYSIS_FASTFORWARD) {
std::cout << "执行快进 (Fast-forward) 合并..." << std::endl;
// 从 annotated commit 获取目标 commit OID
const git_oid* target_oid = remote_head_annotated.Id();
if (!target_oid) throw std::runtime_error("无法从远程 annotated commit 获取 OID。");
// 获取本地分支 (HEAD) 的引用对象
git_reference* local_branch_ref_ptr = nullptr;
CheckGitError(git_reference_lookup(&local_branch_ref_ptr, repo_, git_reference_name(head_ref)), "查找本地分支引用以进行快进合并");
GitReference local_branch_ref(local_branch_ref_ptr); // RAII
// 更新本地分支引用,使其直接指向远程 commit
git_reference* new_direct_ref_ptr = nullptr;
CheckGitError(git_reference_set_target(&new_direct_ref_ptr, local_branch_ref, target_oid, "pull: 快进更新。"), "快进合并期间设置本地分支目标");
// 如果需要管理 new_direct_ref_ptr 的生命周期,也用 RAII 包装
if (new_direct_ref_ptr) git_reference_free(new_direct_ref_ptr);
// Checkout HEAD 以更新工作目录和索引
git_checkout_options ff_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
ff_checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; // 使用 FORCE 确保工作目录匹配
CheckGitError(git_checkout_head(repo_, &ff_checkout_opts), "快进合并后检出 HEAD");
std::cout << "快进合并成功完成。" << std::endl;
return true;
}
if (analysis_out & GIT_MERGE_ANALYSIS_NORMAL) {
std::cout << "执行普通合并..." << std::endl;
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
// 倾向于创建包含冲突的文件,而不是立即中止
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING | GIT_CHECKOUT_ALLOW_CONFLICTS;
CheckGitError(git_merge(repo_, merge_heads, 1, &merge_opts, &checkout_opts),
"执行合并操作");
// 检查是否存在冲突
git_index* index_ptr = nullptr;
CheckGitError(git_repository_index(&index_ptr, repo_), "合并后获取仓库索引");
GitIndex index(index_ptr); // RAII 封装
if (index.HasConflicts()) {
std::cerr << "Pull 错误:检测到合并冲突。请手动解决冲突并提交。" << std::endl;
// 将仓库留在合并状态供用户解决
// 如果需要,可以在此处保存冲突细节
CheckGitError(index.Write(), "写入包含冲突的索引"); // 保存包含冲突标记的索引状态
return false; // 因冲突而指示失败
} else {
std::cout << "合并完成,无冲突。正在创建合并提交..." << std::endl;
// 创建合并提交 (Merge Commit)
git_oid tree_oid;
CheckGitError(index.WriteTree(&tree_oid), "写入合并后的树"); // 将索引更改写入树对象
git_tree* tree_ptr = nullptr;
CheckGitError(git_tree_lookup(&tree_ptr, repo_, &tree_oid), "查找合并后的树");
GitTree merged_tree(tree_ptr); // RAII 封装
// 创建签名 (Signature)
git_signature* signature_ptr = nullptr;
// !! 重要:替换为实际的用户名和邮箱,最好从 Git 配置读取
CheckGitError(git_signature_default(&signature_ptr, repo_), "获取默认签名");
// 如果 git_signature_default 失败或未配置,需要手动创建或提供默认值
if(!signature_ptr) {
CheckGitError(git_signature_now(&signature_ptr, "默认用户名", "user@example.com"), "创建签名");
}
GitSignature signature(signature_ptr); // RAII 封装
// 获取本地 HEAD commit 作为父提交
git_commit* local_head_commit_ptr = nullptr;
git_oid local_head_oid;
CheckGitError(git_reference_name_to_id(&local_head_oid, repo_, git_reference_name(head_ref)),
"获取本地 HEAD OID 用于合并提交");
CheckGitError(git_commit_lookup(&local_head_commit_ptr, repo_, &local_head_oid),
"查找本地 HEAD commit 用于合并提交");
GitCommit local_head_commit(local_head_commit_ptr); // RAII
// 获取远程 commit 作为另一个父提交
git_commit* remote_commit_ptr = nullptr;
CheckGitError(git_commit_lookup(&remote_commit_ptr, repo_, remote_head_annotated.Id()),
"查找远程 commit 用于合并提交");
GitCommit remote_commit(remote_commit_ptr); // RAII
git_commit* parents[] = {local_head_commit, remote_commit};
// 生成合并提交消息
std::string commit_message = "Merge remote-tracking branch '" + remote_name_str + "/" + remote_branch_name_str + "'";
// 创建合并提交
git_oid new_commit_oid;
CheckGitError(git_commit_create(&new_commit_oid, repo_,
git_reference_name(head_ref), // 更新哪个引用 (HEAD)
signature, signature, // 作者和提交者签名
nullptr, // 编码nullptr 表示 UTF-8
commit_message.c_str(), // 提交信息
merged_tree, // 合并后的树
2, parents), // 父提交数量和数组
"创建合并提交");
// 清理仓库状态(例如,移除 MERGE_HEAD 文件)
CheckGitError(git_repository_state_cleanup(repo_), "合并后清理仓库状态");
std::cout << "合并提交成功创建。" << std::endl;
return true;
}
} else if (analysis_out & GIT_MERGE_ANALYSIS_UNBORN) {
std::cerr << "Pull 错误:本地分支 '" << git_reference_shorthand(head_ref)
<< "' 尚未出生 (unborn)。无法合并。" << std::endl;
return false;
} else {
// 其他未知的分析结果
std::cerr << "Pull 错误:未知的合并分析结果 (" << analysis_out << ")。" << std::endl;
return false;
}
}
} catch (const std::exception& e) {
std::cerr << "Pull 操作失败: " << e.what() << std::endl;
// 清理在部分失败情况下可能未被 RAII 覆盖的资源
// (尽管如果 RAII 实现正确,大多数情况应已处理)
return false;
}
// 当 RAII 包装器离开作用域时,它们会自动释放资源(如 head_ref, upstream_ref, remote 等)
// 即使发生异常也是如此。
}
int OpenSubmodule(git_submodule* submodule, const char* name, void* payload) {
auto submodules = static_cast<std::vector<std::shared_ptr<Repository>>*>(payload);
git_repository* subrepo = nullptr;
if (git_submodule_open(&subrepo, submodule) == 0) {
auto repo = std::make_shared<Repository>(subrepo);
repo->SetName(name);
submodules->push_back(repo);
}
return 0;
}
std::vector<std::shared_ptr<Repository>> Repository::GetSubmodules() const {
std::vector<std::shared_ptr<Repository>> submodules;
if (!repo_) return submodules;
git_submodule_foreach(repo_, OpenSubmodule, &submodules);
return submodules;
}
bool InitLibGit() {
return git_libgit2_init() >= 0;
}
void CleanupLibGit() {
git_libgit2_shutdown();
}
bool IsValidRepository(const std::string& path) {
git_repository* repo = nullptr;
int error = git_repository_open(&repo, path.c_str());
if (error == 0) {
git_repository_free(repo);
return true;
}
return false;
}
std::shared_ptr<Repository> OpenRepository(const std::string& path) {
git_repository* repo = nullptr;
int error = git_repository_open(&repo, path.c_str());
if (error != 0) {
return nullptr;
}
return std::make_shared<Repository>(repo);
}
} // namespace git

81
src/git_repository.h Normal file
View File

@ -0,0 +1,81 @@
#pragma once
#include <git2.h>
#include <string>
#include <vector>
#include <memory>
#include <functional>
namespace git {
// 为libgit2对象提供RAII包装的类
class GitResource {
public:
virtual ~GitResource() = default;
};
// Git引用的RAII包装
class GitReference : public GitResource {
public:
explicit GitReference(git_reference* ref) : ref_(ref) {}
~GitReference() override { if (ref_) git_reference_free(ref_); }
operator git_reference*() const { return ref_; }
std::string GetShortName() const;
private:
git_reference* ref_ = nullptr;
};
// Git分支操作类
class Branch {
public:
explicit Branch(const std::string& name) : name_(name) {}
const std::string& GetName() const { return name_; }
private:
std::string name_;
};
// Git仓库操作类
class Repository {
public:
explicit Repository(git_repository* repo);
~Repository();
Repository(const Repository&) = delete;
Repository& operator=(const Repository&) = delete;
// 基本属性
const std::string& GetName() const { return name_; }
const std::string& GetPath() const { return path_; }
void SetName(const std::string& name) { name_ = name; }
// 分支操作
std::string GetCurrentBranchName() const;
std::vector<std::string> GetAllBranches() const;
bool SwitchBranch(const std::string& branch_name, bool hard_reset = false);
bool Pull();
// 子模块操作
std::vector<std::shared_ptr<Repository>> GetSubmodules() const;
// 转换操作符
operator git_repository*() const { return repo_; }
operator bool() const { return repo_ != nullptr; }
private:
git_repository* repo_ = nullptr;
std::string name_;
std::string path_;
};
// 初始化和清理
bool InitLibGit();
void CleanupLibGit();
// 辅助函数
bool IsValidRepository(const std::string& path);
std::shared_ptr<Repository> OpenRepository(const std::string& path);
} // namespace git

73
src/main.cpp Normal file
View File

@ -0,0 +1,73 @@
#include <wx/wx.h>
#include "git_repository.h"
#include "config_manager.h"
#include "main_frame.h"
class GitBranchApp : public wxApp {
public:
bool OnInit() override {
// 初始化Git库
if (!git::InitLibGit()) {
wxMessageBox("Failed to initialize libgit2", "Error", wxICON_ERROR);
return false;
}
// 加载配置
auto& config = ConfigManager::Instance();
if (!config.Load()) {
// 配置不存在或无效,初始化新配置
if (!InitializeConfig()) {
return false;
}
}
// 打开主仓库
std::string repo_path = config.GetRepositoryPath();
auto repo = git::OpenRepository(repo_path);
if (!repo) {
wxMessageBox("Failed to open repository: " + repo_path, "Error", wxICON_ERROR);
return false;
}
// 创建并显示主窗口
auto* frame = new MainFrame("Git Branch Manager");
frame->Show(true);
return true;
}
int OnExit() override {
// 保存配置
ConfigManager::Instance().Save();
// 清理Git库
git::CleanupLibGit();
return 0;
}
private:
bool InitializeConfig() {
// 显示目录选择对话框
wxDirDialog dialog(nullptr, "Select Git Repository", "",
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK) {
return false;
}
std::string path = dialog.GetPath().ToStdString();
if (!git::IsValidRepository(path)) {
wxMessageBox("Please select a valid Git repository", "Error", wxICON_ERROR);
return false;
}
auto& config = ConfigManager::Instance();
config.SetRepositoryPath(path);
config.Save();
return true;
}
};
wxIMPLEMENT_APP(GitBranchApp);

158
src/main_frame.cpp Normal file
View File

@ -0,0 +1,158 @@
#include "main_frame.h"
#include "config_manager.h"
wxBEGIN_EVENT_TABLE(MainFrame, wxFrame)
EVT_BUTTON(wxID_APPLY, MainFrame::OnApply)
EVT_COMBOBOX(wxID_ANY, MainFrame::OnGroupSelect)
EVT_CHECKBOX(wxID_ANY, MainFrame::OnHardResetChanged)
wxEND_EVENT_TABLE()
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("Failed to open repository: " + repo_path, "Error", wxICON_ERROR);
Close(true);
return;
}
// 获取硬重置配置
is_hard_reset_ = ConfigManager::Instance().GetHardReset();
// 初始化UI
InitializeUI();
// 默认选择Develop分支组
if (group_combo_->GetCount() > 0) {
group_combo_->SetStringSelection("Develop");
wxCommandEvent event(wxEVT_COMBOBOX);
OnGroupSelect(event);
}
}
void MainFrame::InitializeUI() {
// 创建主布局
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// 创建分支组选择器
PopulateBranchGroups();
// 创建主仓库分支选择器
branch_selectors_.push_back(new BranchSelector(this, main_repo_, "Main Repository"));
// 创建子模块分支选择器
auto submodules = main_repo_->GetSubmodules();
for (const auto& submodule : submodules) {
branch_selectors_.push_back(new BranchSelector(this, submodule));
}
// 创建硬重置选项
hard_reset_checkbox_ = new wxCheckBox(this, wxID_ANY, "Hard reset");
hard_reset_checkbox_->SetValue(is_hard_reset_);
// 创建应用按钮
wxButton* apply_button = new wxButton(this, wxID_APPLY, "Apply");
// 创建日志控件
log_ctrl_ = new ColoredLogCtrl(this);
// 添加到主布局
main_sizer->Add(group_combo_, 0, wxEXPAND | wxALL, 5);
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);
SetMinSize(wxSize(600, 400));
}
void MainFrame::PopulateBranchGroups() {
// 获取所有分支组
std::vector<std::string> branch_groups;
// 获取主仓库的所有分支
for (const auto& branch : main_repo_->GetAllBranches()) {
branch_groups.push_back(branch);
}
// 获取子模块的所有分支
auto submodules = main_repo_->GetSubmodules();
for (const auto& submodule : submodules) {
for (const auto& branch : submodule->GetAllBranches()) {
branch_groups.push_back(branch);
}
}
// 去重
std::sort(branch_groups.begin(), branch_groups.end());
branch_groups.erase(std::unique(branch_groups.begin(), branch_groups.end()), branch_groups.end());
// 创建分支组选择器
group_combo_ = new wxComboBox(this, wxID_ANY);
// 填充分支组
for (const auto& group : branch_groups) {
group_combo_->Append(group);
}
}
void MainFrame::OnApply(wxCommandEvent& event) {
log_ctrl_->LogInfo("Applying branch changes...");
bool any_failures = false;
for (auto* selector : branch_selectors_) {
std::string repo_name = selector->GetRepo()->GetName();
std::string branch_name = selector->GetSelectedBranchName();
log_ctrl_->LogInfo(wxString::Format("Switching %s to branch: %s", repo_name, branch_name));
if (selector->ApplyBranchChange(is_hard_reset_)) {
log_ctrl_->LogSuccess(wxString::Format("Successfully updated %s to %s", repo_name, branch_name));
} else {
log_ctrl_->LogError(wxString::Format("Failed to update %s to %s", repo_name, branch_name));
any_failures = true;
}
}
if (any_failures) {
log_ctrl_->LogWarning("Some operations failed. See errors above.");
} else {
log_ctrl_->LogSuccess("All operations completed successfully!");
}
log_ctrl_->LogInfo("==============完成==============");
}
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));
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_);
if (is_hard_reset_) {
log_ctrl_->LogWarning("Hard reset mode enabled. This will discard local changes!");
} else {
log_ctrl_->LogInfo("Hard reset mode disabled.");
}
}

52
src/main_frame.h Normal file
View File

@ -0,0 +1,52 @@
#pragma once
#include <wx/wx.h>
#include <memory>
#include <vector>
#include "git_repository.h"
#include "colored_log_ctrl.h"
// 分支选择器组件
class BranchSelector : public wxPanel {
public:
BranchSelector(wxWindow* parent, std::shared_ptr<git::Repository> repo, const wxString& label = wxEmptyString);
bool SelectBranch(const wxString& branch_name);
bool SelectGroupBranch(const wxString& group_name);
std::string GetSelectedBranchName() const;
bool ApplyBranchChange(bool hard_reset);
auto GetRepo() const { return repo_; }
private:
void OnBranchSelected(wxCommandEvent& event);
wxComboBox* branch_combo_ = nullptr;
std::shared_ptr<git::Repository> repo_;
wxString current_group_;
wxDECLARE_EVENT_TABLE();
};
// 主窗口
class MainFrame : public wxFrame {
public:
explicit MainFrame(const wxString& title);
~MainFrame() override = default;
private:
void OnApply(wxCommandEvent& event);
void OnGroupSelect(wxCommandEvent& event);
void OnHardResetChanged(wxCommandEvent& event);
void InitializeUI();
void PopulateBranchGroups();
std::vector<BranchSelector*> branch_selectors_;
wxComboBox* group_combo_ = nullptr;
wxCheckBox* hard_reset_checkbox_ = nullptr;
ColoredLogCtrl* log_ctrl_ = nullptr;
std::shared_ptr<git::Repository> main_repo_;
bool is_hard_reset_ = false;
wxDECLARE_EVENT_TABLE();
};

1
third_party/wxWidgets vendored Submodule

@ -0,0 +1 @@
Subproject commit 0aa14d33b5e78e60e8280444a0afe8b7d9355317