使用git.exe
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16...3.31)
|
||||||
project(branch_switcher)
|
project(branch_switcher)
|
||||||
|
|
||||||
include(cmake/project_cpp_standard.cmake)
|
include(cmake/project_cpp_standard.cmake)
|
||||||
set_cpp_standard(20)
|
set_cpp_standard(20)
|
||||||
|
|
||||||
# 如果需要,手动指定 wxWidgets 路径
|
# 如果需要,手动指定 wxWidgets 路径
|
||||||
set(wxWidgets_ROOT_DIR "D:/Software/msys2/mingw64")
|
# set(wxWidgets_ROOT_DIR "D:/Projects/vcpkg/installed/x64-mingw-dynamic/lib")
|
||||||
|
|
||||||
# 添加 MSYS2 特定设置
|
# 添加 MSYS2 特定设置
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
@@ -14,9 +14,8 @@ if(MINGW)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 寻找所需的包
|
# 寻找所需的包
|
||||||
find_package(wxWidgets REQUIRED COMPONENTS core base adv html net)
|
# 引入vcpkg
|
||||||
find_package(unofficial-libgit2 REQUIRED)
|
find_package(wxWidgets COMPONENTS core base REQUIRED)
|
||||||
find_package(Boost REQUIRED COMPONENTS process)
|
|
||||||
|
|
||||||
# 输出 wxWidgets 信息,用于调试
|
# 输出 wxWidgets 信息,用于调试
|
||||||
message(STATUS "wxWidgets_FOUND: ${wxWidgets_FOUND}")
|
message(STATUS "wxWidgets_FOUND: ${wxWidgets_FOUND}")
|
||||||
@@ -26,6 +25,8 @@ message(STATUS "wxWidgets_LIBRARIES: ${wxWidgets_LIBRARIES}")
|
|||||||
# 包含 wxWidgets 设置
|
# 包含 wxWidgets 设置
|
||||||
include(${wxWidgets_USE_FILE})
|
include(${wxWidgets_USE_FILE})
|
||||||
include(cmake/retrieve_files.cmake)
|
include(cmake/retrieve_files.cmake)
|
||||||
|
include(cmake/mirage_utils.cmake)
|
||||||
|
configure_project_defaults()
|
||||||
|
|
||||||
# 获取源文件
|
# 获取源文件
|
||||||
set(SRC_FILES "")
|
set(SRC_FILES "")
|
||||||
@@ -41,6 +42,4 @@ target_include_directories(${PROJECT_NAME} PUBLIC
|
|||||||
# 链接库
|
# 链接库
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
${wxWidgets_LIBRARIES}
|
${wxWidgets_LIBRARIES}
|
||||||
unofficial::libgit2::libgit2
|
|
||||||
Boost::process
|
|
||||||
)
|
)
|
||||||
|
|||||||
44
cmake/mirage_utils.cmake
Normal file
44
cmake/mirage_utils.cmake
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
# 定义一个函数来配置项目的默认设置
|
||||||
|
# 这包括设置输出目录和项目根目录变量
|
||||||
|
function(configure_project_defaults)
|
||||||
|
# 检查是否在顶层 CMakeLists.txt 中调用 (可选但推荐)
|
||||||
|
# 确保 CMAKE_SOURCE_DIR 和 CMAKE_CURRENT_SOURCE_DIR 相同
|
||||||
|
if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||||
|
message(WARNING "configure_project_defaults() 应该在项目的根 CMakeLists.txt 中调用。")
|
||||||
|
# 如果您确实需要在子目录中设置不同的根目录,请调整此逻辑
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- 配置输出目录 ---
|
||||||
|
# 使用 CMAKE_BINARY_DIR 作为基础构建目录
|
||||||
|
# ${CMAKE_BINARY_DIR} 指向您配置 CMake 时指定的构建目录
|
||||||
|
# 例如,在 CLion 中通常是 cmake-build-debug 或 cmake-build-release
|
||||||
|
# 如果手动运行 cmake ..,它就是您运行 cmake 命令的目录
|
||||||
|
|
||||||
|
# **设置可执行文件输出路径**:
|
||||||
|
# 对于单配置生成器 (如 Makefiles, Ninja), 可执行文件将位于 <build>/bin/
|
||||||
|
# 对于多配置生成器 (如 Visual Studio, Xcode), CMake 通常会自动在此路径下附加配置名称
|
||||||
|
# (例如 <build>/bin/Debug/, <build>/bin/Release/)
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Directory for runtime executables")
|
||||||
|
message(STATUS "运行时输出目录设置为: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
|
||||||
|
|
||||||
|
# **设置库文件输出路径 (共享库和静态库)**:
|
||||||
|
# 规则同上,库文件将位于 <build>/lib/ 或 <build>/lib/<Config>/
|
||||||
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for shared libraries")
|
||||||
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for static libraries")
|
||||||
|
message(STATUS "库输出目录设置为: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
|
||||||
|
message(STATUS "存档输出目录设置为: ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}")
|
||||||
|
|
||||||
|
# --- 提示 ---
|
||||||
|
# 这种全局设置输出目录的方法对于中小型项目是常见的。
|
||||||
|
# 对于更复杂的项目或需要更细粒度控制的情况,可以考虑为每个目标(target)单独设置输出目录属性:
|
||||||
|
# 例如:
|
||||||
|
# add_executable(my_app main.cpp)
|
||||||
|
# set_target_properties(my_app PROPERTIES
|
||||||
|
# RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/executables"
|
||||||
|
# )
|
||||||
|
# add_library(my_lib STATIC my_lib.cpp)
|
||||||
|
# set_target_properties(my_lib PROPERTIES
|
||||||
|
# ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_libs"
|
||||||
|
# )
|
||||||
|
endfunction()
|
||||||
@@ -1,54 +1,85 @@
|
|||||||
|
# 函数:设置 C++ 标准及相关编译选项
|
||||||
|
# 参数 standard: 需要设置的 C++ 标准版本 (例如 11, 14, 17, 20, 23)
|
||||||
function(set_cpp_standard standard)
|
function(set_cpp_standard standard)
|
||||||
# 参数验证
|
# --- 参数验证 ---
|
||||||
set(VALID_STANDARDS 11 14 17 20 23)
|
set(VALID_STANDARDS 11 14 17 20 23 26) # 定义支持的 C++ 标准列表
|
||||||
if(NOT ${standard} IN_LIST VALID_STANDARDS)
|
list(FIND VALID_STANDARDS ${standard} _standard_index) # 查找 standard 是否在列表中
|
||||||
message(WARNING "非标准 C++ 版本: ${standard},支持的版本有: ${VALID_STANDARDS}")
|
if(_standard_index EQUAL -1) # 如果未找到
|
||||||
|
message(WARNING "**非标准 C++ 版本**: ${standard}。支持的版本有: ${VALID_STANDARDS}")
|
||||||
|
# 可选:可以设置为一个默认值或停止配置
|
||||||
|
# message(FATAL_ERROR "不支持的 C++ 标准: ${standard}")
|
||||||
|
# set(standard 17) # 或者设置为默认值,例如 C++17
|
||||||
|
# message(WARNING "已将 C++ 标准设置为默认值: ${standard}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 指定需要的 C++ 标准,设置到父作用域
|
# --- 设置 C++ 标准 ---
|
||||||
|
# 指定需要的 C++ 标准,设置到父作用域,使其对调用者定义的 target 生效
|
||||||
set(CMAKE_CXX_STANDARD ${standard} PARENT_SCOPE)
|
set(CMAKE_CXX_STANDARD ${standard} PARENT_SCOPE)
|
||||||
# 强制要求此标准,如果编译器不支持则配置时报错
|
# **强制要求此标准**,如果编译器不支持则配置时报错
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE)
|
||||||
# 可选:禁用编译器特定的扩展,使用纯粹的标准
|
# **禁用编译器特定的扩展**,使用更纯粹的标准 C++
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF PARENT_SCOPE)
|
set(CMAKE_CXX_EXTENSIONS OFF PARENT_SCOPE)
|
||||||
|
|
||||||
if(MSVC)
|
# --- 平台特定设置 ---
|
||||||
# 设置utf-8编码
|
if(WIN32 OR CYGWIN)
|
||||||
add_compile_options(/utf-8)
|
# 为 Windows 定义 UNICODE 宏
|
||||||
# 强制 MSVC 正确设置 __cplusplus 宏
|
|
||||||
add_compile_options(/Zc:__cplusplus)
|
|
||||||
# 可选:增加 MSVC 警告级别
|
|
||||||
add_compile_options(/W4)
|
|
||||||
# 可选: 启用 UNICODE 支持
|
|
||||||
add_definitions(-DUNICODE -D_UNICODE)
|
add_definitions(-DUNICODE -D_UNICODE)
|
||||||
|
# 可选:添加 WIN32_LEAN_AND_MEAN 以减少 Windows 头文件包含,加快编译速度
|
||||||
|
# add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||||
|
message(STATUS "为 Windows 添加 UNICODE 定义")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
# --- 编译器特定设置 ---
|
||||||
# 可选:添加 WIN32_LEAN_AND_MEAN 以减少 Windows 头文件包含
|
if(MSVC)
|
||||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
# **设置源代码和执行字符集为 UTF-8**
|
||||||
|
add_compile_options(/utf-8)
|
||||||
|
# **强制 MSVC 正确设置 __cplusplus 宏**,以便代码能准确判断 C++ 标准
|
||||||
|
add_compile_options(/Zc:__cplusplus)
|
||||||
|
# **设置警告级别为 W4** (较高警告级别)
|
||||||
|
add_compile_options(/W4)
|
||||||
|
# **禁用特定警告:C4100 未使用的形参** (有时用于接口兼容性)
|
||||||
|
add_compile_options(/wd4100)
|
||||||
|
# 禁用特定警告:C4996 使用了被标记为否决的函数或变量 (例如一些旧的 CRT 函数)
|
||||||
|
add_compile_options(/wd4996)
|
||||||
|
message(STATUS "为 MSVC 添加特定编译选项: /utf-8 /Zc:__cplusplus /W4 /wd4100 /wd4996")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# GCC/Clang 特定选项
|
# GCC/Clang 特定选项
|
||||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
# **启用常用警告**
|
||||||
add_compile_options(-Wall -Wextra)
|
add_compile_options(-Wall -Wextra)
|
||||||
# 禁用未使用参数的警告
|
# **禁用未使用参数的警告** (与 MSVC 的 /wd4100 对应)
|
||||||
add_compile_options(-Wno-unused-parameter)
|
add_compile_options(-Wno-unused-parameter)
|
||||||
# 根据 C++ 标准添加特定选项
|
# **设置输入和执行字符集为 UTF-8** (对应 MSVC 的 /utf-8)
|
||||||
if(${standard} GREATER 14) # 更兼容的写法,避免使用 GREATER_EQUAL
|
# 这有助于处理源代码中的 UTF-8 字符,并影响运行时字符编码,但控制台本身的显示需要环境配合
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -finput-charset=UTF-8 -fexec-charset=UTF-8")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finput-charset=UTF-8 -fexec-charset=UTF-8")
|
||||||
|
|
||||||
|
# 根据 C++ 标准添加特定警告 (C++17 及以上)
|
||||||
|
if(${standard} GREATER 14)
|
||||||
|
# **启用阴影变量警告和非虚析构函数警告**
|
||||||
add_compile_options(-Wshadow -Wnon-virtual-dtor)
|
add_compile_options(-Wshadow -Wnon-virtual-dtor)
|
||||||
|
message(STATUS "为 GCC/Clang (C++${CMAKE_CXX_STANDARD}) 添加额外警告: -Wshadow -Wnon-virtual-dtor")
|
||||||
endif()
|
endif()
|
||||||
|
message(STATUS "为 GCC/Clang 添加特定编译选项: -Wall -Wextra -Wno-unused-parameter -finput-charset=UTF-8 -fexec-charset=UTF-8")
|
||||||
|
# 注意: 控制台/终端的 UTF-8 输出显示通常还需要操作系统环境的配合 (例如 Linux/macOS 终端本身设置,Windows下 chcp 65001)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 如果是MinGW, 并且使用了C++17或更高版本, 则添加libstdc++exp库
|
# MinGW 特定设置 (通常在 Windows 上使用 GCC 工具链)
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
message(STATUS "检测到MinGW编译器")
|
message(STATUS "检测到 MinGW 编译器")
|
||||||
# 更兼容的版本比较
|
# 如果使用了 C++17 或更高版本, 可能需要链接 libstdc++exp 以支持 <filesystem> 等特性
|
||||||
if(${standard} GREATER 14) # C++17及以上
|
if(${standard} GREATER 14)
|
||||||
message(STATUS "为C++${standard}添加libstdc++exp库支持")
|
message(STATUS "**为 MinGW C++${CMAKE_CXX_STANDARD} 添加 libstdc++fs 库依赖** (用于 <filesystem>)")
|
||||||
|
# 注意:较新版本的 MinGW 可能不再需要显式链接,或者需要链接的是 -lstdc++fs
|
||||||
|
# link_libraries() 会影响后续所有 target,更推荐使用 target_link_libraries()
|
||||||
|
# 这里暂时保留 link_libraries(),但建议后续根据具体 target 调整
|
||||||
|
# 尝试链接 stdc++fs,这在较新的 MinGW 中更常见用于 <filesystem>
|
||||||
|
# link_libraries(-lstdc++fs)
|
||||||
|
# 如果链接 -lstdc++fs 失败,可以尝试回退到 -lstdc++exp
|
||||||
link_libraries(-lstdc++exp)
|
link_libraries(-lstdc++exp)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "已设置C++${standard}标准")
|
message(STATUS "**C++ 标准已设置为: c++${standard}**")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function(is_current_platform platform is_match)
|
|||||||
set(matches FALSE)
|
set(matches FALSE)
|
||||||
|
|
||||||
if(platform STREQUAL "windows")
|
if(platform STREQUAL "windows")
|
||||||
if(WIN32)
|
if(WIN32 OR CYGWIN)
|
||||||
set(matches TRUE)
|
set(matches TRUE)
|
||||||
endif()
|
endif()
|
||||||
elseif(platform STREQUAL "linux")
|
elseif(platform STREQUAL "linux")
|
||||||
@@ -314,4 +314,3 @@ function(add_resource_file)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|||||||
15
src/git_command.h
Normal file
15
src/git_command.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Created by 46944 on 25-5-22.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// 检查目录是否为Git仓库
|
||||||
|
inline bool IsGitRepository(const std::string& directory = ".") {
|
||||||
|
#ifdef _WIN32
|
||||||
|
int exitCode = system(("cd /d \"" + directory + "\" && git rev-parse --is-inside-work-tree > nul 2>&1").c_str());
|
||||||
|
#else
|
||||||
|
int exitCode = system(("cd \"" + directory + "\" && git rev-parse --is-inside-work-tree > /dev/null 2>&1").c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
@@ -1,552 +1,112 @@
|
|||||||
#include "git_repository.h"
|
#include "git_repository.h"
|
||||||
#include <iostream>
|
#include "git_command.h"
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
/**
|
std::string ExecGitCommand(const std::string& cmd) {
|
||||||
* @brief 检查 libgit2 函数调用的错误代码。
|
std::string fullCmd = "git " + cmd;
|
||||||
*
|
std::string result;
|
||||||
* 如果错误代码指示失败(通常小于 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();
|
|
||||||
|
|
||||||
// 构建详细的错误消息
|
#ifdef _WIN32
|
||||||
std::string error_message = action_description + " 失败: ";
|
FILE* pipe = _popen(fullCmd.c_str(), "r");
|
||||||
|
#else
|
||||||
|
FILE* pipe = popen(fullCmd.c_str(), "r");
|
||||||
|
#endif
|
||||||
|
|
||||||
// 检查 last_error 是否有效以及是否有错误消息文本
|
if (!pipe) {
|
||||||
if (last_error && last_error->message) {
|
return "执行命令失败";
|
||||||
error_message += last_error->message;
|
}
|
||||||
} else {
|
|
||||||
// 如果 libgit2 没有提供具体的错误信息
|
|
||||||
error_message += "未知错误 (libgit2 未提供详细信息)";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 可以选择性地附加错误代码以供调试
|
char buffer[128];
|
||||||
error_message += " (错误码: " + std::to_string(error_code) + ")";
|
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||||
|
result += buffer;
|
||||||
|
}
|
||||||
|
|
||||||
// 抛出标准运行时异常
|
#ifdef _WIN32
|
||||||
throw std::runtime_error(error_message);
|
_pclose(pipe);
|
||||||
}
|
#else
|
||||||
// 如果 error_code >= 0,则表示操作成功,函数不执行任何操作
|
pclose(pipe);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace git {
|
git::Repository::Repository(const std::filesystem::path& in_path) {
|
||||||
// --- 假设 RAII 封装类已存在 ---
|
path_ = in_path;
|
||||||
// GitRemote 示例:
|
}
|
||||||
class GitRemote : public GitResource {
|
|
||||||
public:
|
bool git::Repository::SwitchBranch(const std::string& in_branch, bool in_hard_reset) {
|
||||||
explicit GitRemote(git_remote* remote) : remote_(remote) {}
|
std::string cmd = "checkout " + in_branch;
|
||||||
~GitRemote() override { if (remote_) git_remote_free(remote_); }
|
if (in_hard_reset) {
|
||||||
operator git_remote*() const { return remote_; }
|
cmd += " --hard";
|
||||||
private:
|
}
|
||||||
git_remote* remote_ = nullptr;
|
auto result = ExecGitCommand(cmd);
|
||||||
};
|
if (result.find("error") != std::string::npos) {
|
||||||
|
return false;
|
||||||
// GitAnnotatedCommit 示例:
|
}
|
||||||
class GitAnnotatedCommit : public GitResource {
|
return true;
|
||||||
public:
|
}
|
||||||
explicit GitAnnotatedCommit(git_annotated_commit* commit) : commit_(commit) {}
|
|
||||||
~GitAnnotatedCommit() override { if (commit_) git_annotated_commit_free(commit_); }
|
bool git::Repository::Pull() {
|
||||||
operator git_annotated_commit*() const { return commit_; }
|
std::string cmd = "pull";
|
||||||
const git_oid* Id() const { return commit_ ? git_annotated_commit_id(commit_) : nullptr; }
|
auto result = ExecGitCommand(cmd);
|
||||||
private:
|
if (result.find("error") != std::string::npos) {
|
||||||
git_annotated_commit* commit_ = nullptr;
|
return false;
|
||||||
};
|
}
|
||||||
|
return true;
|
||||||
// GitIndex 示例
|
}
|
||||||
class GitIndex : public GitResource {
|
|
||||||
public:
|
std::vector<std::shared_ptr<git::Repository>> git::Repository::GetSubmodules() const {
|
||||||
explicit GitIndex(git_index *index) : index_(index) {}
|
std::vector<std::shared_ptr<git::Repository>> submodules;
|
||||||
~GitIndex() override { if (index_) git_index_free(index_); }
|
std::string cmd = "submodule status";
|
||||||
operator git_index*() const { return index_; }
|
auto result = ExecGitCommand(cmd);
|
||||||
bool HasConflicts() const { return index_ && git_index_has_conflicts(index_); }
|
if (result.find("error") != std::string::npos) {
|
||||||
int Write() { return index_ ? git_index_write(index_) : -1; }
|
return submodules;
|
||||||
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:
|
std::istringstream iss(result);
|
||||||
git_index* index_ = nullptr;
|
std::string line;
|
||||||
};
|
while (std::getline(iss, line)) {
|
||||||
|
std::istringstream iss_line(line);
|
||||||
// GitCommit 示例
|
std::string path;
|
||||||
class GitCommit : public GitResource {
|
iss_line >> path;
|
||||||
public:
|
submodules.push_back(std::make_shared<Repository>(path));
|
||||||
explicit GitCommit(git_commit *commit) : commit_(commit) {}
|
}
|
||||||
~GitCommit() override { if (commit_) git_commit_free(commit_); }
|
return submodules;
|
||||||
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; }
|
std::string git::Repository::GetCurrentBranchName() const {
|
||||||
const git_signature* Author() const { return commit_ ? git_commit_author(commit_) : nullptr; }
|
std::string cmd = "rev-parse --abbrev-ref HEAD";
|
||||||
const char* Message() const { return commit_ ? git_commit_message(commit_) : ""; }
|
auto result = ExecGitCommand(cmd);
|
||||||
git_tree* Tree() const {
|
if (result.find("error") != std::string::npos) {
|
||||||
git_tree* tree = nullptr;
|
return "";
|
||||||
if (commit_ && git_commit_tree(&tree, commit_) == 0) {
|
}
|
||||||
return tree; // 调用者必须管理此 tree 的生命周期或将其包装
|
return result;
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
}
|
std::vector<std::string> git::Repository::GetAllBranches() const {
|
||||||
private:
|
std::vector<std::string> branches;
|
||||||
git_commit* commit_ = nullptr;
|
std::string cmd = "branch";
|
||||||
};
|
auto result = ExecGitCommand(cmd);
|
||||||
|
if (result.find("error") != std::string::npos) {
|
||||||
// GitTree 示例
|
return branches;
|
||||||
class GitTree : public GitResource {
|
}
|
||||||
public:
|
|
||||||
explicit GitTree(git_tree *tree) : tree_(tree) {}
|
std::istringstream iss(result);
|
||||||
~GitTree() override { if (tree_) git_tree_free(tree_); }
|
std::string line;
|
||||||
operator git_tree*() const { return tree_; }
|
while (std::getline(iss, line)) {
|
||||||
const git_oid* Id() const { return tree_ ? git_tree_id(tree_) : nullptr; }
|
if (!line.empty()) {
|
||||||
private:
|
branches.push_back(line.substr(2)); // Remove the leading "* " or " "
|
||||||
git_tree* tree_ = nullptr;
|
}
|
||||||
};
|
}
|
||||||
|
return branches;
|
||||||
// GitSignature 示例
|
}
|
||||||
class GitSignature : public GitResource {
|
|
||||||
public:
|
bool git::Repository::IsValid() const {
|
||||||
explicit GitSignature(git_signature *sig) : sig_(sig) {}
|
return IsGitRepository(path_.string());
|
||||||
~GitSignature() override { if (sig_) git_signature_free(sig_); }
|
}
|
||||||
operator git_signature*() const { return sig_; }
|
|
||||||
private:
|
std::shared_ptr<git::Repository> git::OpenRepository(const std::filesystem::path& in_path) {
|
||||||
git_signature* sig_ = nullptr;
|
auto repo = std::make_shared<Repository>(in_path);
|
||||||
};
|
if (repo->IsValid())
|
||||||
// --- RAII 封装类结束 ---
|
return repo;
|
||||||
|
return nullptr;
|
||||||
// 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
|
|
||||||
|
|||||||
@@ -1,81 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <git2.h>
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace git {
|
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 {
|
class Repository {
|
||||||
public:
|
public:
|
||||||
explicit Repository(git_repository* repo);
|
explicit Repository(const std::filesystem::path& in_path);
|
||||||
~Repository();
|
|
||||||
|
|
||||||
Repository(const Repository&) = delete;
|
bool SwitchBranch(const std::string& in_branch, bool in_hard_reset);
|
||||||
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();
|
bool Pull();
|
||||||
|
|
||||||
// 子模块操作
|
[[nodiscard]] std::vector<std::shared_ptr<Repository>> GetSubmodules() const;
|
||||||
std::vector<std::shared_ptr<Repository>> GetSubmodules() const;
|
[[nodiscard]] std::string GetCurrentBranchName() const;
|
||||||
|
[[nodiscard]] std::vector<std::string> GetAllBranches() const;
|
||||||
// 转换操作符
|
[[nodiscard]] auto GetName() const { return name_; }
|
||||||
operator git_repository*() const { return repo_; }
|
|
||||||
operator bool() const { return repo_ != nullptr; }
|
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsValid() const;
|
||||||
private:
|
private:
|
||||||
git_repository* repo_ = nullptr;
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string path_;
|
std::filesystem::path path_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化和清理
|
std::shared_ptr<Repository> OpenRepository(const std::filesystem::path& in_path);
|
||||||
bool InitLibGit();
|
}
|
||||||
void CleanupLibGit();
|
|
||||||
|
|
||||||
// 辅助函数
|
|
||||||
bool IsValidRepository(const std::string& path);
|
|
||||||
std::shared_ptr<Repository> OpenRepository(const std::string& path);
|
|
||||||
|
|
||||||
} // namespace git
|
|
||||||
|
|||||||
12
src/main.cpp
12
src/main.cpp
@@ -2,16 +2,11 @@
|
|||||||
#include "git_repository.h"
|
#include "git_repository.h"
|
||||||
#include "config_manager.h"
|
#include "config_manager.h"
|
||||||
#include "main_frame.h"
|
#include "main_frame.h"
|
||||||
|
#include "git_command.h"
|
||||||
|
|
||||||
class GitBranchApp : public wxApp {
|
class GitBranchApp : public wxApp {
|
||||||
public:
|
public:
|
||||||
bool OnInit() override {
|
bool OnInit() override {
|
||||||
// 初始化Git库
|
|
||||||
if (!git::InitLibGit()) {
|
|
||||||
wxMessageBox("Failed to initialize libgit2", "Error", wxICON_ERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载配置
|
// 加载配置
|
||||||
auto& config = ConfigManager::Instance();
|
auto& config = ConfigManager::Instance();
|
||||||
if (!config.Load()) {
|
if (!config.Load()) {
|
||||||
@@ -40,9 +35,6 @@ public:
|
|||||||
// 保存配置
|
// 保存配置
|
||||||
ConfigManager::Instance().Save();
|
ConfigManager::Instance().Save();
|
||||||
|
|
||||||
// 清理Git库
|
|
||||||
git::CleanupLibGit();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +49,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string path = dialog.GetPath().ToStdString();
|
std::string path = dialog.GetPath().ToStdString();
|
||||||
if (!git::IsValidRepository(path)) {
|
if (!IsGitRepository(path)) {
|
||||||
wxMessageBox("Please select a valid Git repository", "Error", wxICON_ERROR);
|
wxMessageBox("Please select a valid Git repository", "Error", wxICON_ERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user