diff --git a/.gitignore b/.gitignore index a027f93..ee42b85 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.swo build *.user +cmake-build*/ +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt index e9712f4..fa4ba6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,89 +1,63 @@ -project (libgit2cpp) -cmake_minimum_required(VERSION 3.5.1) +cmake_minimum_required(VERSION 3.6) -# Build options -OPTION(USE_BOOST "Enable use of boost header libraries" OFF) +project(libgit2cpp LANGUAGES C CXX) +set(package git2cpp) -IF(USE_BOOST) - add_definitions(-DUSE_BOOST=1) - find_package(Boost REQUIRED) - include_directories(${BOOST_INCLUDEDIR}) -ENDIF() +option(USE_BOOST "Enable use of Boost header libraries" OFF) +option(BUNDLE_LIBGIT2 "Use bundled libgit2" ${MSVC}) +option(BUILD_LIBGIT2CPP_EXAMPLES "Build libgit2cpp examples" ON) -if (MSVC) - OPTION(BUNDLE_LIBGIT2 "use bundled libgit2" ON) -else() - OPTION(BUNDLE_LIBGIT2 "use bundled libgit2" OFF) -endif() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +include(Version) + +if (USE_BOOST) + find_package(Boost REQUIRED) + add_definitions(-DUSE_BOOST=1) + include_directories(${BOOST_INCLUDEDIR}) +endif () if (BUNDLE_LIBGIT2) - add_subdirectory(libs/libgit2) -endif() + add_subdirectory(libs/libgit2) +endif () -file(GLOB_RECURSE lib_sources - src/*.cpp - include/git2cpp/*.h +file(GLOB_RECURSE LIBGIT2CPP_SOURCES + src/*.cpp + include/git2cpp/*.h ) -if (MSVC AND ($MSVC_VERSION VERSION_GREATER 1900)) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") -endif() - -add_library(git2cpp STATIC ${lib_sources}) +add_library(${package} STATIC ${LIBGIT2CPP_SOURCES}) -target_include_directories(git2cpp - PRIVATE - src - PUBLIC - $ +set_target_properties(${package} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + INTERFACE_COMPILE_FEATURES cxx_std_17 ) -if (USE_BOOST) - target_include_directories(git2cpp PUBLIC ${Boost_INCLUDE_DIRS}) -endif() - -if (BUNDLE_LIBGIT2) - target_include_directories(git2cpp - PUBLIC libs/libgit2/include - ) -endif() - -target_link_libraries(git2cpp git2) +if (MSVC AND ($MSVC_VERSION VERSION_GREATER 1900)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") +endif () -set_target_properties(git2cpp PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED YES - INTERFACE_COMPILE_FEATURES cxx_std_17 +target_include_directories(${package} + PRIVATE src + PUBLIC $ ) -option(BUILD_LIBGIT2CPP_EXAMPLES ON) +if (USE_BOOST) + target_include_directories(${package} PUBLIC ${Boost_INCLUDE_DIRS}) +endif () + +if (BUILD_LIBGIT2CPP_EXAMPLES) + add_subdirectory(examples) + file(COPY test.sh DESTINATION . FILE_PERMISSIONS ${EXE_PERM}) +endif () -if(BUILD_LIBGIT2CPP_EXAMPLES) - set(examples - add - branch - cat-file - diff - log - rev-list - showindex - status - init - rev-parse - general - remote - checkout - ls-files - blame - ) - - foreach (example ${examples}) - add_executable("${example}-cpp" examples/${example}.cpp) - target_link_libraries("${example}-cpp" git2cpp) - endforeach(example) - - add_executable(commit-graph-generator examples/commit-graph-generator.cpp) - target_link_libraries(commit-graph-generator git2cpp) - - file(COPY test.sh DESTINATION . FILE_PERMISSIONS ${EXE_PERM}) -endif() +if (BUNDLE_LIBGIT2) + target_include_directories(${package} PUBLIC libs/libgit2/include) + target_link_libraries(${package} libgit2package) +else () + find_package(PkgConfig REQUIRED) + pkg_search_module(LibGit2 REQUIRED IMPORTED_TARGET libgit2) + target_link_libraries(${package} PkgConfig::LibGit2) +endif () + +include(InstallRules) diff --git a/appveyor.yml b/appveyor.yml index 4baf481..cf75efc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,18 +6,10 @@ branches: environment: matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "Visual Studio 14 2015" - ARCH: 32 - BOOST: 1_63_0 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 GENERATOR: "Visual Studio 15 2017" ARCH: 32 BOOST: 1_65_1 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - GENERATOR: "Visual Studio 14 2015 Win64" - ARCH: 64 - BOOST: 1_63_0 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 GENERATOR: "Visual Studio 15 2017 Win64" ARCH: 64 diff --git a/cmake/InstallConfig.cmake b/cmake/InstallConfig.cmake new file mode 100644 index 0000000..d9ce767 --- /dev/null +++ b/cmake/InstallConfig.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/git2cppTargets.cmake") diff --git a/cmake/InstallRules.cmake b/cmake/InstallRules.cmake new file mode 100644 index 0000000..bf387bd --- /dev/null +++ b/cmake/InstallRules.cmake @@ -0,0 +1,58 @@ +set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "") + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +install( + DIRECTORY include/ + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT git2cpp_Development +) + +install( + TARGETS ${package} + EXPORT git2cppTargets + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +if (NOT DEFINED VERSION) + set(VERSION "1.0.0") +endif () + +write_basic_package_version_file( + "${package}ConfigVersion.cmake" + VERSION ${VERSION} + COMPATIBILITY SameMajorVersion + ARCH_INDEPENDENT +) + +set( + git2cpp_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" + CACHE PATH "CMake package config location relative to the install prefix" +) + +mark_as_advanced(git2cpp_INSTALL_CMAKEDIR) + +install( + FILES cmake/InstallConfig.cmake + DESTINATION "${git2cpp_INSTALL_CMAKEDIR}" + RENAME "${package}Config.cmake" + COMPONENT git2cpp_Development +) + +install( + FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" + DESTINATION "${git2cpp_INSTALL_CMAKEDIR}" + COMPONENT git2cpp_Development +) + +install( + EXPORT git2cppTargets + NAMESPACE git2cpp:: + DESTINATION "${git2cpp_INSTALL_CMAKEDIR}" + COMPONENT git2cpp_Development +) + +if (PROJECT_IS_TOP_LEVEL) + include(CPack) +endif () diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..d61a5e5 --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,33 @@ +set(VERSION "0.0.0" CACHE STRING "libgit2cpp Version") +set(COMMIT_HASH "") +set(COMMIT_COUNT 0) + +find_package(Git) + +if (Git_FOUND AND VERSION STREQUAL "0.0.0") + message(STATUS "No version defined, fetching one from git") + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE COMMIT_COUNT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + set(VERSION "r${COMMIT_COUNT}.${COMMIT_HASH}") +endif () + +message(STATUS "Version: ${VERSION}") +string(REGEX REPLACE "([0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9])?)(.*)" "\\1" VERSION_FOR_CMAKE "${VERSION}") +message(STATUS "Version for CMake: ${VERSION_FOR_CMAKE}") + +#configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..d840b21 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE LIBGIT2CPP_EXAMPLE_SOURCES *.cpp) +foreach(EXAMPLE_SOURCE ${LIBGIT2CPP_EXAMPLE_SOURCES}) + get_filename_component(EXAMPLE_NAME ${EXAMPLE_SOURCE} NAME_WE) + add_executable(${EXAMPLE_NAME}-cpp ${EXAMPLE_SOURCE}) + target_link_libraries(${EXAMPLE_NAME}-cpp ${package}) +endforeach() diff --git a/examples/add.cpp b/examples/add.cpp index c24721e..d616a5b 100644 --- a/examples/add.cpp +++ b/examples/add.cpp @@ -6,6 +6,8 @@ #include "git2cpp/initializer.h" #include "git2cpp/repo.h" +#include + enum print_options { SKIP = 1, diff --git a/examples/cat-file.cpp b/examples/cat-file.cpp index c27b80c..1b28176 100644 --- a/examples/cat-file.cpp +++ b/examples/cat-file.cpp @@ -64,7 +64,7 @@ void show_blob(git::Blob const & blob) void show_tree(git::Tree const & tree) { - char oidstr[GIT_OID_HEXSZ + 1]; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; for (size_t i = 0, n = tree.entrycount(); i < n; ++i) { @@ -81,7 +81,7 @@ void show_tree(git::Tree const & tree) void show_commit(git::Commit const & commit) { - char oidstr[GIT_OID_HEXSZ + 1]; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; git_oid_tostr(oidstr, sizeof(oidstr), &commit.tree_id()); printf("tree %s\n", oidstr); @@ -127,7 +127,7 @@ int main(int argc, char * argv[]) const char *dir = ".", *rev = nullptr; int i, verbose = 0; Action action = Action::NONE; - char oidstr[GIT_OID_HEXSZ + 1]; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; for (i = 1; i < argc; ++i) { diff --git a/examples/clone.cpp b/examples/clone.cpp new file mode 100644 index 0000000..ee5d266 --- /dev/null +++ b/examples/clone.cpp @@ -0,0 +1,130 @@ +#include "git2cpp/initializer.h" +#include "git2cpp/remote.h" +#include "git2cpp/repo.h" + +#include + +#include + +/* Define the printf format specifer to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Iu" +# define PRIxZ "Ix" +# define PRIdZ "Id" +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIdZ "zd" +#endif + +namespace +{ + struct progress_data + { + git_indexer_progress fetch_progress; + size_t completed_steps; + size_t total_steps; + const char * path; + + void print() + { + int network_percent = fetch_progress.total_objects > 0 ? (100 * fetch_progress.received_objects) / fetch_progress.total_objects : 0; + int index_percent = fetch_progress.total_objects > 0 ? (100 * fetch_progress.indexed_objects) / fetch_progress.total_objects : 0; + + int checkout_percent = total_steps > 0 + ? (int)((100 * completed_steps) / total_steps) + : 0; + size_t kbytes = fetch_progress.received_bytes / 1024; + + if (fetch_progress.total_objects && + fetch_progress.received_objects == fetch_progress.total_objects) + { + printf("Resolving deltas %u/%u\r", + fetch_progress.indexed_deltas, + fetch_progress.total_deltas); + } + else + { + printf("net %3d%% (%4" PRIuZ " kb, %5u/%5u) / idx %3d%% (%5u/%5u) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ")%s\n", + network_percent, kbytes, + fetch_progress.received_objects, fetch_progress.total_objects, + index_percent, fetch_progress.indexed_objects, fetch_progress.total_objects, + checkout_percent, + completed_steps, total_steps, + path); + } + } + }; + + void checkout_progress(const char * path, size_t cur, size_t tot, void * payload) + { + progress_data * pd = static_cast(payload); + pd->completed_steps = cur; + pd->total_steps = tot; + pd->path = path; + pd->print(); + } + + struct FetchCallbacks final : git::Remote::FetchCallbacks + { + FetchCallbacks(progress_data & pd) + : pd_(pd) + { + } + + void sideband_progress(char const * str, int len) override + { + printf("remote: %.*s", len, str); + fflush(stdout); + } + + void transfer_progress(git_indexer_progress const & progress) override + { + pd_.fetch_progress = progress; + pd_.print(); + } + + git_credential * acquire_cred(const char * url, const char * username_from_url, unsigned allowed_types) override + { + return nullptr; + } + + private: + progress_data & pd_; + }; +} + +int main(int argc, char ** argv) +{ + /* Validate args */ + if (argc != 3) + { + printf("USAGE: %s \n", argv[0]); + return EXIT_FAILURE; + } + + auto_git_initializer; + + progress_data pd = {{0}}; + FetchCallbacks fetch_callbacks(pd); + git_checkout_options checkout_opts = {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE}; + /* Set up options */ + checkout_opts.progress_cb = checkout_progress; + checkout_opts.progress_payload = &pd; + const char * url = argv[1]; + const char * path = argv[2]; + /* Do the clone */ + try + { + git::Repository::clone(url, path, checkout_opts, fetch_callbacks); + } + catch (git::repository_clone_error err) + { + printf("\n"); + if (auto detailed_info = std::get_if(&err.data)) + printf("ERROR %d: %s\n", detailed_info->klass, detailed_info->message); + else + printf("ERROR %d: no detailed info\n", std::get(err.data)); + return EXIT_FAILURE; + } +} diff --git a/include/git2cpp/index.h b/include/git2cpp/index.h index 28e0bc5..2d899ad 100644 --- a/include/git2cpp/index.h +++ b/include/git2cpp/index.h @@ -4,6 +4,7 @@ #include #include +#include struct git_index; struct git_repository; diff --git a/include/git2cpp/remote.h b/include/git2cpp/remote.h index 1908623..917e315 100644 --- a/include/git2cpp/remote.h +++ b/include/git2cpp/remote.h @@ -16,6 +16,10 @@ namespace git struct FetchCallbacks { + protected: + ~FetchCallbacks() = default; + + public: virtual void update_tips(char const * refname, git_oid const & a, git_oid const & b) {} virtual void sideband_progress(char const * str, int len) {} virtual void transfer_progress(git_indexer_progress const &) {} diff --git a/include/git2cpp/repo.h b/include/git2cpp/repo.h index b58b114..9b4ab3e 100644 --- a/include/git2cpp/repo.h +++ b/include/git2cpp/repo.h @@ -22,6 +22,7 @@ #include "internal/optional.h" #include +#include #include struct git_blame_options; @@ -33,6 +34,16 @@ namespace git struct missing_head_error {}; + struct repository_clone_error + { + struct detailed_info + { + char const* message; + int klass; + }; + std::variant data; + }; + enum class branch_type { LOCAL, @@ -158,11 +169,15 @@ namespace git Repository(const char * dir, init_tag, git_repository_init_options opts); Repository(std::string const & dir, init_tag); + static Repository clone(const char * url, const char* path, git_checkout_options const &, Remote::FetchCallbacks &); + static internal::optional discover(const char * start_path); private: struct Destroy { void operator() (git_repository *) const; }; std::unique_ptr repo_; + + explicit Repository(git_repository*); }; Object revparse_single(Repository const & repo, const char * spec); diff --git a/libs/libgit2 b/libs/libgit2 index b7bad55..e632535 160000 --- a/libs/libgit2 +++ b/libs/libgit2 @@ -1 +1 @@ -Subproject commit b7bad55e4bb0a285b073ba5e02b01d3f522fc95d +Subproject commit e6325351ceee58cf56f58bdce61b38907805544f diff --git a/src/diff.cpp b/src/diff.cpp index 0ff4464..5c8ab85 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -100,7 +100,7 @@ namespace git Buffer Diff::Stats::to_buf(diff::stats::format::type format, size_t width) const { - git_buf buf = GIT_BUF_INIT_CONST(nullptr, 0); + git_buf buf = GIT_BUF_INIT; if (git_diff_stats_to_buf(&buf, stats_.get(), git_diff_stats_format_t(format.value()), width)) throw error_t("git_diff_stats_to_buf fail"); else diff --git a/src/id_to_str.cpp b/src/id_to_str.cpp index 48c48c9..b58dfb5 100644 --- a/src/id_to_str.cpp +++ b/src/id_to_str.cpp @@ -6,12 +6,12 @@ namespace git { std::string id_to_str(git_oid const & oid) { - return id_to_str(oid, GIT_OID_HEXSZ); + return id_to_str(oid, GIT_OID_SHA1_HEXSIZE); } std::string id_to_str(git_oid const & oid, size_t digits_num) { - char buf[GIT_OID_HEXSZ + 1]; + char buf[GIT_OID_SHA1_HEXSIZE + 1]; git_oid_tostr(buf, sizeof(buf), &oid); return std::string(buf, buf + digits_num); } diff --git a/src/remote.cpp b/src/remote.cpp index 04543b9..a024701 100644 --- a/src/remote.cpp +++ b/src/remote.cpp @@ -14,8 +14,10 @@ namespace git return git_remote_pushurl(remote_.get()); } - void Remote::fetch(FetchCallbacks & callbacks, char const * reflog_message) + git_fetch_options fetch_options_from_callbacks(Remote::FetchCallbacks & callbacks) { + using FetchCallbacks = Remote::FetchCallbacks; + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; opts.callbacks.payload = &callbacks; @@ -46,6 +48,12 @@ namespace git *out = cred; return 0; }; + return opts; + } + + void Remote::fetch(FetchCallbacks & callbacks, char const * reflog_message) + { + const auto opts = fetch_options_from_callbacks(callbacks); git_remote_fetch(remote_.get(), nullptr, &opts, reflog_message); } diff --git a/src/repo.cpp b/src/repo.cpp index 514852a..bb59846 100644 --- a/src/repo.cpp +++ b/src/repo.cpp @@ -1,5 +1,7 @@ #include "git2cpp/repo.h" +#include "git2/clone.h" + #include "git2cpp/annotated_commit.h" #include "git2cpp/error.h" #include "git2cpp/internal/optional.h" @@ -22,6 +24,11 @@ namespace git { const Repository::init_tag Repository::init; + Repository::Repository(git_repository * repo) + : repo_(repo) + { + } + Repository::Repository(const char * dir) { git_repository * repo; @@ -55,6 +62,22 @@ namespace git repo_.reset(repo); } + git_fetch_options fetch_options_from_callbacks(Remote::FetchCallbacks &); + + Repository Repository::clone(const char * url, const char* path, git_checkout_options const & checkout_opts, Remote::FetchCallbacks & fetch_callbacks) + { + const git_clone_options clone_opts = { GIT_CLONE_OPTIONS_VERSION, checkout_opts, fetch_options_from_callbacks(fetch_callbacks) }; + git_repository *cloned_repo = nullptr; + if (auto error = git_clone(&cloned_repo, url, path, &clone_opts)) + { + if (auto err = git_error_last()) + throw repository_clone_error{ repository_clone_error::detailed_info{ err->message, err->klass } }; + else + throw repository_clone_error{ error }; + } + return Repository(cloned_repo); + } + bool Repository::is_bare() const { return git_repository_is_bare(repo_.get()) != 0; @@ -571,7 +594,7 @@ namespace git internal::optional Repository::discover(const char * start_path) { - git_buf buf = GIT_BUF_INIT_CONST(nullptr, 0); + git_buf buf = GIT_BUF_INIT; if (git_repository_discover(&buf, start_path, 0, nullptr)) return internal::none; return std::string(buf.ptr, buf.size); diff --git a/test.sh b/test.sh index 3f1f276..a32334a 100755 --- a/test.sh +++ b/test.sh @@ -23,7 +23,7 @@ function test() { local test_name="$1" echo -e "**** test $test_name *********************************\n\n" - local bin="$CWD/$test_name" + local bin="$CWD/examples/$test_name" shift @@ -41,7 +41,7 @@ pushd $REPO test branch-cpp test diff-cpp -test commit-graph-generator . "$CWD/commit-graph.dot" +test commit-graph-generator-cpp . "$CWD/commit-graph.dot" test log-cpp test rev-list-cpp --topo-order HEAD test rev-parse-cpp HEAD