init
This commit is contained in:
commit
6eb14af6e1
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal 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
46
CMakeLists.txt
Normal 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
|
||||
)
|
54
cmake/project_cpp_standard.cmake
Normal file
54
cmake/project_cpp_standard.cmake
Normal 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
317
cmake/retrieve_files.cmake
Normal 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
99
src/branch_selector.cpp
Normal 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
26
src/branch_selector.h
Normal 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
39
src/colored_log_ctrl.cpp
Normal 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
13
src/colored_log_ctrl.h
Normal 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
68
src/config_manager.cpp
Normal 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
34
src/config_manager.h
Normal 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
552
src/git_repository.cpp
Normal 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
81
src/git_repository.h
Normal 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
73
src/main.cpp
Normal 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
158
src/main_frame.cpp
Normal 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
52
src/main_frame.h
Normal 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
1
third_party/wxWidgets
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 0aa14d33b5e78e60e8280444a0afe8b7d9355317
|
Loading…
x
Reference in New Issue
Block a user