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 d2d8860..fa4ba6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,83 +1,63 @@ -project (libgit2cpp) -cmake_minimum_required(VERSION 2.8) +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) - -set_target_properties(git2cpp PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED YES - INTERFACE_COMPILE_FEATURES cxx_std_17 -) +if (MSVC AND ($MSVC_VERSION VERSION_GREATER 1900)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") +endif () -set(examples - add - branch - cat-file - diff - log - rev-list - showindex - status - init - rev-parse - general - remote - checkout +target_include_directories(${package} + PRIVATE src + PUBLIC $ ) -foreach (example ${examples}) - add_executable("${example}-cpp" examples/${example}.cpp) - target_link_libraries("${example}-cpp" git2cpp) -endforeach(example) +if (USE_BOOST) + target_include_directories(${package} PUBLIC ${Boost_INCLUDE_DIRS}) +endif () -add_executable(commit-graph-generator examples/commit-graph-generator.cpp) -target_link_libraries(commit-graph-generator git2cpp) +if (BUILD_LIBGIT2CPP_EXAMPLES) + add_subdirectory(examples) + file(COPY test.sh DESTINATION . FILE_PERMISSIONS ${EXE_PERM}) +endif () -file(COPY test.sh DESTINATION . FILE_PERMISSIONS ${EXE_PERM}) +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/README.md b/README.md index 74e4613..f7427a8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Building libgit2cpp - Using CMake $ cmake .. $ make -Supporting CMake options: `USE_BOOST`, `BUNDLE_LIBGIT2`. +Supporting CMake options: `USE_BOOST`, `BUNDLE_LIBGIT2`, `BUILD_LIBGIT2CPP_EXAMPLES`. Testing ======= 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/blame.cpp b/examples/blame.cpp new file mode 100644 index 0000000..dc589e3 --- /dev/null +++ b/examples/blame.cpp @@ -0,0 +1,196 @@ +/* + * libgit2 "blame" example - shows how to use the blame API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "git2cpp/repo.h" +#include "git2cpp/initializer.h" +#include "git2/blame.h" + +#ifdef _MSC_VER +#define snprintf sprintf_s +#define strcasecmp strcmpi +#endif + +/** + * This example demonstrates how to invoke the libgit2 blame API to roughly + * simulate the output of `git blame` and a few of its command line arguments. + */ + +struct opts { + const char * path; + const char * commitspec = nullptr; + int start_line; + int end_line; + bool C = false; + bool M = false; + bool F = false; + + opts(int argc, char *argv[]); +}; + +int main(int argc, char *argv[]) +{ + opts o(argc, argv); + git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT; + + if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT; + + auto_git_initializer; + + git::Repository repo("."); + /** + * The commit range comes in "commitish" form. Use the rev-parse API to + * nail down the end points. + */ + if (o.commitspec) + { + auto revspec = repo.revparse(o.commitspec); + + if (revspec.flags() & GIT_REVSPEC_SINGLE) + { + blameopts.newest_commit = revspec.single()->id(); + } + else + { + auto const & range = *revspec.range(); + blameopts.oldest_commit = range.from.id(); + blameopts.newest_commit = range.to.id(); + } + } + + /** Run the blame. */ + auto blame = repo.blame_file(o.path, blameopts); + + /** + * Get the raw data inside the blob for output. We use the + * `commitish:path/to/file.txt` format to find it. + */ + std::string spec = git_oid_is_zero(&blameopts.newest_commit) + ? "HEAD" + : git::id_to_str(blameopts.newest_commit); + spec += ":"; + spec += o.path; + + auto blob = repo.blob_lookup(repo.revparse_single(spec.c_str()).single()->id()); + + char const * rawdata = reinterpret_cast(blob.content()); + size_t rawsize = blob.size(); + + /** Produce the output. */ + size_t line = 1; + bool break_on_null_hunk = false; + for (size_t i = 0; i < rawsize; ++line) { + const git_blame_hunk *hunk = blame.hunk_byline(line); + + if (break_on_null_hunk && !hunk) + break; + + char const * eol = reinterpret_cast(memchr(rawdata + i, '\n', rawsize - i)); + if (hunk) { + break_on_null_hunk = true; + + char oid[10] = {0}; + git_oid_tostr(oid, 10, &hunk->final_commit_id); + char sig[128] = {0}; + snprintf(sig, 127, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email); + + printf("%s ( %-30s %3d) %.*s\n", + oid, + sig, + (int)line, + (int)(eol - rawdata - i), + rawdata + i); + } + + i = eol - rawdata + 1; + } + + return 0; +} + +/** Tell the user how to make this thing work. */ +static void usage(const char *msg) +{ + if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "usage: blame [options] [] \n"); + fprintf(stderr, "\n"); + fprintf(stderr, " example: `HEAD~10..HEAD`, or `1234abcd`\n"); + fprintf(stderr, " -L process only line range n-m, counting from 1\n"); + fprintf(stderr, " -M find line moves within and across files\n"); + fprintf(stderr, " -C find line copies within and across files\n"); + fprintf(stderr, " -F follow only the first parent commits\n"); + fprintf(stderr, "\n"); + exit(1); +} + +/** Parse the arguments. */ +opts::opts(int argc, char *argv[]) +{ + int i; + const char * bare_args[3] = {}; + + if (argc < 2) usage(nullptr); + + for (i=1; i= 3) + usage("Invalid argument set"); + bare_args[i] = a; + } + else if (!strcmp(a, "--")) + continue; + else if (!strcasecmp(a, "-M")) + M = true; + else if (!strcasecmp(a, "-C")) + C = true; + else if (!strcasecmp(a, "-F")) + F = true; + else if (!strcasecmp(a, "-L")) { + i++; a = argv[i]; + if (i >= argc) throw std::runtime_error("Not enough arguments to -L"); + if (sscanf(a, "%d,%d", &start_line, &end_line) != 2) + { + fprintf(stderr, "-L format error\n"); + std::abort(); + } + } + else { + /* commit range */ + if (commitspec) throw std::runtime_error("Only one commit spec allowed"); + commitspec = a; + } + } + + /* Handle the bare arguments */ + if (!bare_args[0]) usage("Please specify a path"); + path = bare_args[0]; + if (bare_args[1]) { + /* */ + path = bare_args[1]; + commitspec = bare_args[0]; + } + if (bare_args[2]) { + /* */ + char spec[128] = {0}; + path = bare_args[2]; + sprintf(spec, "%s..%s", bare_args[0], bare_args[1]); + commitspec = spec; + } +} diff --git a/examples/branch.cpp b/examples/branch.cpp index 9df894c..7d8f291 100644 --- a/examples/branch.cpp +++ b/examples/branch.cpp @@ -1,14 +1,73 @@ -#include - #include "git2cpp/initializer.h" #include "git2cpp/repo.h" -int main() +#include +#include + +namespace +{ + using namespace git; + + int create_branch(Repository & repo, const char * name, bool force) + { + try + { + auto head = repo.head(); + auto branch = repo.create_branch(name, repo.commit_lookup(head.target()), force); + assert(branch); + assert(std::strcmp(branch.name(), name)); + std::cout << "branch " << name << " has been created" << std::endl; + return EXIT_SUCCESS; + } + catch (branch_create_error const & e) + { + switch (e.reason) + { + case branch_create_error::already_exists:; + std::cerr << "a branch named '" << name << "' already exists" << std::endl; + break; + case branch_create_error::invalid_spec: + std::cerr << "'" << name << "' is not a valid branch name" << std::endl; + break; + case branch_create_error::unknown: + break; + } + return EXIT_FAILURE; + } + } + + void print_help() + { + std::cerr << "invalid command line arguments, expected are:" << std::endl + << "[[-f] branch_name]" << std::endl; + } +} + +int main(int argc, char* argv[]) { - git::Initializer threads_initializer; + Initializer threads_initializer; - git::Repository repo("."); - auto branches = repo.branches(git::branch_type::ALL); - for (auto const & b : branches) - std::cout << b.name() << std::endl; + Repository repo("."); + switch (argc) + { + case 1: + { + auto branches = repo.branches(git::branch_type::ALL); + for (auto const & b : branches) + std::cout << b.name() << std::endl; + return EXIT_SUCCESS; + } + case 2: + return create_branch(repo, argv[1], false); + case 3: + if (std::strcmp(argv[1], "-f") != 0) + { + print_help(); + return EXIT_FAILURE; + } + return create_branch(repo, argv[2], true); + default: + print_help(); + return EXIT_FAILURE; + } } 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/checkout.cpp b/examples/checkout.cpp index acd8b0b..e6ba273 100644 --- a/examples/checkout.cpp +++ b/examples/checkout.cpp @@ -201,7 +201,7 @@ void perform_checkout_ref(git::Repository & repo, git::AnnotatedCommit const & t checkout_opts.perfdata_cb = print_perf_data; /** Grab the commit we're interested to move to */ - auto target_commit = repo.commit_lookup(target.commit_id()); + auto target_commit = repo.commit_lookup(target.commit_id()); /** * Perform the checkout so the workdir corresponds to what target_commit * contains. @@ -209,10 +209,9 @@ void perform_checkout_ref(git::Repository & repo, git::AnnotatedCommit const & t * Note that it's okay to pass a git_commit here, because it will be * peeled to a tree. */ - ; if (repo.checkout_tree(target_commit, checkout_opts)) - { - fprintf(stderr, "failed to checkout tree: %s\n", giterr_last()->message); + { + fprintf(stderr, "failed to checkout tree: %s\n", git_error_last()->message); return; } @@ -229,7 +228,7 @@ void perform_checkout_ref(git::Repository & repo, git::AnnotatedCommit const & t err = repo.set_head_detached(target); } if (err != 0) { - fprintf(stderr, "failed to update HEAD reference: %s\n", giterr_last()->message); + fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message); } } 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/examples/general.cpp b/examples/general.cpp index 2a32565..249c0e7 100644 --- a/examples/general.cpp +++ b/examples/general.cpp @@ -122,7 +122,7 @@ int main(int argc, char ** argv) // binary data. For a tree it is a special binary format, so it's unlikely // to be hugely helpful as a raw object. const unsigned char * data = obj.data(); - git_otype otype = obj.type(); + git_object_t otype = obj.type(); // We provide methods to convert from the object type which is an enum, to // a string representation of that value (and vice-versa). @@ -141,7 +141,7 @@ int main(int argc, char ** argv) // it gives you direct access to the key/value properties of Git. Here // we'll write a new blob object that just contains a simple string. // Notice that we have to specify the object type as the `git_otype` enum. - git_oid oid = odb.write("test data", sizeof("test data") - 1, GIT_OBJ_BLOB); + git_oid oid = odb.write("test data", sizeof("test data") - 1, GIT_OBJECT_BLOB); // Now that we've written the object, we can check out what SHA1 was // generated when the object was written to our database. @@ -255,7 +255,7 @@ int main(int argc, char ** argv) // git_signature - name, email, timestamp), and the tag message. Object commit = tag.target(repo); const char * tname = tag.name(); // "test" - git_otype ttype = tag.target_type(); // GIT_OBJ_COMMIT (otype enum) + git_object_t ttype = tag.target_type(); // GIT_OBJ_COMMIT (otype enum) const char * tmessage = tag.message(); // "tag message\n" std::cout << "Tag Message: " << tmessage << std::endl; } diff --git a/examples/log.cpp b/examples/log.cpp index 50dd947..aa04446 100644 --- a/examples/log.cpp +++ b/examples/log.cpp @@ -118,7 +118,7 @@ void add_revision(struct log_state * s, const char * revstr) git::Revspec::Range const & range = *revs.range(); push_rev(s, range.to, hide); - if ((revs.flags() & GIT_REVPARSE_MERGE_BASE) != 0) + if ((revs.flags() & GIT_REVSPEC_MERGE_BASE) != 0) { git_oid base = s->repo->merge_base(range); push_rev(s, s->repo->commit_lookup(base), hide); @@ -156,36 +156,34 @@ static void print_time(const git_time * intime, const char * prefix) static void print_commit(git::Commit const & commit) { - int i, count; - const git_signature * sig; - const char *scan, *eol; - printf("commit %s\n", git::id_to_str(commit.id()).c_str()); - if ((count = (int)commit.parents_num()) > 1) + const auto count = commit.parents_num(); + if (count > 1) { printf("Merge:"); - for (i = 0; i < count; ++i) + for (size_t i = 0; i < count; ++i) { printf(" %s", git::id_to_str(commit.parent_id(i), 7).c_str()); } printf("\n"); } - if ((sig = commit.author()) != NULL) + if (auto sig = commit.author()) { printf("Author: %s <%s>\n", sig->name, sig->email); print_time(&sig->when, "Date: "); } printf("\n"); - for (scan = commit.message(); scan && *scan;) + for (const char* scan = commit.message(); scan && *scan;) { - for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */ + const char *eol = scan; + for (; *eol && *eol != '\n'; ++eol) /* find eol */ ; printf(" %.*s\n", (int)(eol - scan), scan); - scan = *eol ? eol + 1 : NULL; + scan = *eol ? eol + 1 : nullptr; } printf("\n"); } diff --git a/examples/ls-files.cpp b/examples/ls-files.cpp new file mode 100644 index 0000000..5e42ed4 --- /dev/null +++ b/examples/ls-files.cpp @@ -0,0 +1,128 @@ +/* + * libgit2 "ls-files" example - shows how to view all files currently in the index + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +/** + * This example demonstrates the libgit2 index APIs to roughly + * simulate the output of `git ls-files`. + * `git ls-files` has many options and this currently does not show them. + * + * `git ls-files` base command shows all paths in the index at that time. + * This includes staged and committed files, but unstaged files will not display. + * + * This currently supports the default behavior and the `--error-unmatch` option. + */ + +#include +#include +#include + +#include "git2/index.h" + +typedef struct { + int error_unmatch; + char *files[1024]; + size_t file_count; +} ls_options; + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: ls-files [--error-unmatch] [--] [...]\n"); + exit(1); +} + +static int parse_options(ls_options *opts, int argc, char *argv[]) +{ + int parsing_files = 0; + int i; + + memset(opts, 0, sizeof(ls_options)); + + if (argc < 2) + return 0; + + for (i = 1; i < argc; ++i) { + char *a = argv[i]; + + /* if it doesn't start with a '-' or is after the '--' then it is a file */ + if (a[0] != '-' || parsing_files) { + parsing_files = 1; + + /* watch for overflows (just in case) */ + if (opts->file_count == 1024) { + fprintf(stderr, "ls-files can only support 1024 files at this time.\n"); + return -1; + } + + opts->files[opts->file_count++] = a; + } else if (!strcmp(a, "--")) { + parsing_files = 1; + } else if (!strcmp(a, "--error-unmatch")) { + opts->error_unmatch = 1; + } else { + usage("Unsupported argument", a); + return -1; + } + } + + return 0; +} + +static int print_paths(ls_options *opts, git::Index & index) +{ + /* if there are no files explicitly listed by the user print all entries in the index */ + if (opts->file_count == 0) { + size_t const entry_count = index.entrycount(); + + for (size_t i = 0; i < entry_count; i++) { + auto entry = index[i]; + puts(entry->path); + } + return 0; + } + + /* loop through the files found in the args and print them if they exist */ + for (size_t i = 0; i < opts->file_count; ++i) { + const char *path = opts->files[i]; + + if (index.get_by_path(path, GIT_INDEX_STAGE_NORMAL)) { + puts(path); + } else if (opts->error_unmatch) { + fprintf(stderr, "error: pathspec '%s' did not match any file(s) known to git.\n", path); + fprintf(stderr, "Did you forget to 'git add'?\n"); + return -1; + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + ls_options opts; + + auto error = parse_options(&opts, argc, argv); + if (error < 0) + return error; + + auto_git_initializer; + + git::Repository repo("."); + auto index = repo.index(); + + return print_paths(&opts, index); +} diff --git a/examples/remote.cpp b/examples/remote.cpp index 580401d..90bd93d 100644 --- a/examples/remote.cpp +++ b/examples/remote.cpp @@ -46,7 +46,7 @@ namespace { [[noreturn]] void report_error(const char *message) { - const git_error *lg2err = giterr_last(); + const git_error *lg2err = git_error_last(); const char *lg2msg = "", *lg2spacer = ""; if (lg2err && lg2err->message) diff --git a/examples/rev-list.cpp b/examples/rev-list.cpp index 6b2fbfb..3adc8c5 100644 --- a/examples/rev-list.cpp +++ b/examples/rev-list.cpp @@ -1,16 +1,13 @@ -#include -#include - -#include - #include "git2cpp/initializer.h" #include "git2cpp/repo.h" #include "git2cpp/revwalker.h" -extern "C" { #include -#include -} + +#include + +#include +#include using namespace git; @@ -31,7 +28,7 @@ void push_range(Repository const & repo, RevWalker const & walk, const char * ra { auto revspec = repo.revparse(range); - if (revspec.flags() & GIT_REVPARSE_MERGE_BASE) + if (revspec.flags() & GIT_REVSPEC_MERGE_BASE) { /* TODO: support "..." */ throw std::runtime_error("unsupported operation"); @@ -88,7 +85,7 @@ void revwalk_parseopts(Repository const & repo, RevWalker & walk, int nopts, cha int main(int argc, char ** argv) { - git::Initializer threads_initializer; + auto_git_initializer; try { @@ -110,7 +107,7 @@ int main(int argc, char ** argv) catch (std::exception const & e) { std::cerr << e.what() << std::endl; - if (auto err = giterr_last()) + if (auto err = git_error_last()) { if (err->message) std::cerr << "libgit2 last error: " << err->message << std::endl; diff --git a/examples/rev-parse.cpp b/examples/rev-parse.cpp index 37bf368..9c14b1e 100644 --- a/examples/rev-parse.cpp +++ b/examples/rev-parse.cpp @@ -43,7 +43,7 @@ void parse_revision(parse_state & ps, const char * revstr) auto const & range = *rs.range(); std::cout << id_to_str(range.to.id()) << std::endl; - if ((rs.flags() & GIT_REVPARSE_MERGE_BASE) != 0) + if ((rs.flags() & GIT_REVSPEC_MERGE_BASE) != 0) { git_oid base = ps.repo->merge_base(range); std::cout << id_to_str(base) << std::endl; diff --git a/include/git2cpp/blame.h b/include/git2cpp/blame.h new file mode 100644 index 0000000..2ad639c --- /dev/null +++ b/include/git2cpp/blame.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +struct git_blame; +struct git_blame_hunk; + +namespace git +{ + struct Blame + { + uint32_t hunk_count() const; + + const git_blame_hunk * hunk_byindex(uint32_t index) const; + + const git_blame_hunk * hunk_byline(size_t lineno) const; + + explicit Blame(git_blame * blame) + : blame_(blame) + {} + + private: + struct Destroy { void operator() (git_blame *) const; }; + std::unique_ptr blame_; + }; +} diff --git a/include/git2cpp/error.h b/include/git2cpp/error.h index 410b73e..e62c692 100644 --- a/include/git2cpp/error.h +++ b/include/git2cpp/error.h @@ -253,4 +253,26 @@ namespace git : error_t("Could not open config") {} }; + + struct branch_create_error : error_t + { + enum reason_t + { + already_exists, + invalid_spec, + unknown + } reason; + + branch_create_error(reason_t r) + : error_t("Could not create branch") + , reason(r) + {} + }; + + struct blame_file_error : error_t + { + explicit blame_file_error(std::string const & path) + : error_t("Could not blame file " + path) + {} + }; } diff --git a/include/git2cpp/index.h b/include/git2cpp/index.h index b23c6bc..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; @@ -20,6 +21,8 @@ namespace git size_t entrycount() const; git_index_entry const * operator[](size_t i) const; + git_index_entry const * get_by_path(const char *path, int stage) const; + typedef std::function matched_path_callback_t; void update_all(git_strarray const & pathspec, matched_path_callback_t cb); diff --git a/include/git2cpp/object.h b/include/git2cpp/object.h index 7ca7785..6e00c57 100644 --- a/include/git2cpp/object.h +++ b/include/git2cpp/object.h @@ -24,19 +24,20 @@ namespace git explicit operator bool() const { return obj_.operator bool(); } - git_otype type() const; + git_object_t type() const; git_oid const & id() const; - git_blob const * as_blob() const; - git_commit const * as_commit() const; - git_tree const * as_tree() const; - git_tag const * as_tag() const; - Commit to_commit() /*&&*/; Tree to_tree() /*&&*/; Blob to_blob() /*&&*/; Tag to_tag() /*&&*/; + private: + git_blob * as_blob(); + git_commit * as_commit(); + git_tree * as_tree(); + git_tag * as_tag(); + private: struct Destroy { void operator() (git_object*) const; }; std::unique_ptr obj_; diff --git a/include/git2cpp/reference.h b/include/git2cpp/reference.h index d9cbb2b..d9dc98b 100644 --- a/include/git2cpp/reference.h +++ b/include/git2cpp/reference.h @@ -19,10 +19,12 @@ namespace git explicit operator bool() const { return ref_ != nullptr; } const char * name() const; - git_ref_t type() const; + git_reference_t type() const; git_oid const & target() const; const char * symbolic_target() const; + bool has_same_target_as(Reference const & other) const; + private: friend struct Repository; git_reference * ptr() const { return ref_.get(); } diff --git a/include/git2cpp/remote.h b/include/git2cpp/remote.h index 9bee445..917e315 100644 --- a/include/git2cpp/remote.h +++ b/include/git2cpp/remote.h @@ -1,6 +1,11 @@ #pragma once +#include + struct git_remote; +struct git_oid; +struct git_indexer_progress; +struct git_credential; namespace git { @@ -9,13 +14,30 @@ namespace git const char * url() const; const char * pushurl() const; + 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 &) {} + + virtual git_credential* acquire_cred(const char * url, const char * username_from_url, unsigned int allowed_types) = 0; + }; + + void fetch(FetchCallbacks &, char const * reflog_message = nullptr); + private: friend struct Repository; + struct Destroy { void operator() (git_remote*) const; }; + explicit Remote(git_remote * remote) : remote_(remote) {} - git_remote * remote_; + std::unique_ptr remote_; }; } diff --git a/include/git2cpp/repo.h b/include/git2cpp/repo.h index d873525..9b4ab3e 100644 --- a/include/git2cpp/repo.h +++ b/include/git2cpp/repo.h @@ -2,6 +2,7 @@ #include "repo_fwd.h" +#include "blame.h" #include "blob.h" #include "commit.h" #include "diff.h" @@ -21,8 +22,11 @@ #include "internal/optional.h" #include +#include #include +struct git_blame_options; + namespace git { struct non_existing_branch_error @@ -30,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, @@ -84,7 +98,9 @@ namespace git StrArray reference_list() const; - std::vector branches(branch_type) const; + std::vector branches(branch_type, git_reference_t ref_kind = GIT_REFERENCE_ALL) const; + + Reference create_branch(const char * name, Commit const & target, bool force); /// @return can be empty Reference dwim(const char * shorthand) const; @@ -117,6 +133,8 @@ namespace git Commit const & parent, const char * message_encoding = nullptr); + git_oid amend_commit(Commit const & commit_to_amend, const char * update_ref, const char * message, Tree const & tree); + void reset_default(Commit const &, git_strarray const & pathspecs); void file_diff(std::string const & old_path, git_oid const & old_id, @@ -132,11 +150,15 @@ namespace git /// @return raw error code int checkout_tree(Commit const &, git_checkout_options const &); + int checkout_head(git_checkout_options const &); /// @return raw error code int set_head(char const* ref); + int set_head_detached(git_oid const&); int set_head_detached(AnnotatedCommit const&); + Blame blame_file(const char * path, git_blame_options const &); + explicit Repository(const char * dir); explicit Repository(std::string const & dir); @@ -147,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/include/git2cpp/str_array.h b/include/git2cpp/str_array.h index 5345de5..6c02abc 100644 --- a/include/git2cpp/str_array.h +++ b/include/git2cpp/str_array.h @@ -36,7 +36,7 @@ namespace git ~StrArray() { - git_strarray_free(&str_array_); + git_strarray_dispose(&str_array_); } private: diff --git a/include/git2cpp/tag.h b/include/git2cpp/tag.h index bf6aab1..bb57c9b 100644 --- a/include/git2cpp/tag.h +++ b/include/git2cpp/tag.h @@ -18,7 +18,7 @@ namespace git Object target(Repository const &) const; git_oid const & target_id() const; - git_otype target_type() const; + git_object_t target_type() const; const char * name() const; const char * message() const; diff --git a/include/git2cpp/tree.h b/include/git2cpp/tree.h index da2763f..9ca5821 100644 --- a/include/git2cpp/tree.h +++ b/include/git2cpp/tree.h @@ -14,7 +14,7 @@ namespace git { const char * name() const; git_oid const & id() const; - git_otype type() const; + git_object_t type() const; git_filemode_t filemode() const; private: diff --git a/libs/libgit2 b/libs/libgit2 index 99afd41..e632535 160000 --- a/libs/libgit2 +++ b/libs/libgit2 @@ -1 +1 @@ -Subproject commit 99afd41f1c43c856d39e3b9572d7a2103875a771 +Subproject commit e6325351ceee58cf56f58bdce61b38907805544f diff --git a/src/blame.cpp b/src/blame.cpp new file mode 100644 index 0000000..53262ad --- /dev/null +++ b/src/blame.cpp @@ -0,0 +1,25 @@ +#include "git2cpp/blame.h" +#include "git2/blame.h" + +namespace git +{ + uint32_t Blame::hunk_count() const + { + return git_blame_get_hunk_count(blame_.get()); + } + + const git_blame_hunk* Blame::hunk_byindex(uint32_t index) const + { + return git_blame_get_hunk_byindex(blame_.get(), index); + } + + const git_blame_hunk* Blame::hunk_byline(size_t lineno) const + { + return git_blame_get_hunk_byline(blame_.get(), lineno); + } + + void Blame::Destroy::operator() (git_blame * blame) const + { + git_blame_free(blame); + } +} 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/index.cpp b/src/index.cpp index 0fd6197..b1d83c3 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -40,6 +40,11 @@ namespace git return git_index_get_byindex(index_.get(), i); } + git_index_entry const* Index::get_by_path(const char* path, int stage) const + { + return git_index_get_bypath(index_.get(), path, stage); + } + namespace { int apply_callback(const char * path, const char * matched_pathspec, void * payload) diff --git a/src/object.cpp b/src/object.cpp index d2981e8..9acef11 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -21,7 +21,7 @@ namespace git git_object_free(obj); } - git_otype Object::type() const + git_object_t Object::type() const { return git_object_type(obj_.get()); } @@ -32,10 +32,10 @@ namespace git } #define DEFINE_METHOD_AS(type_name, enum_element) \ - git_##type_name const * Object::as_##type_name() const \ + git_##type_name * Object::as_##type_name() \ { \ - assert(type() == GIT_OBJ_##enum_element); \ - return reinterpret_cast(obj_.get()); \ + assert(type() == GIT_OBJECT_##enum_element); \ + return reinterpret_cast(obj_.get()); \ } DEFINE_METHOD_AS(blob, BLOB) @@ -47,32 +47,28 @@ namespace git Tree Object::to_tree() /*&&*/ { - assert(type() == GIT_OBJ_TREE); - Tree res(reinterpret_cast(obj_.get()), *repo_); + Tree res(as_tree(), *repo_); obj_ = nullptr; return res; } Commit Object::to_commit() /*&&*/ { - assert(type() == GIT_OBJ_COMMIT); - Commit res(reinterpret_cast(obj_.get()), *repo_); + Commit res(as_commit(), *repo_); obj_ = nullptr; return res; } Blob Object::to_blob() /*&&*/ { - assert(type() == GIT_OBJ_BLOB); - Blob res(reinterpret_cast(obj_.get())); + Blob res(as_blob()); obj_ = nullptr; return res; } Tag Object::to_tag() /*&&*/ { - assert(type() == GIT_OBJ_TAG); - Tag res(reinterpret_cast(obj_.get())); + Tag res(as_tag()); obj_ = nullptr; return res; } diff --git a/src/odb_object.cpp b/src/odb_object.cpp index ffe86e3..b3c2f5a 100644 --- a/src/odb_object.cpp +++ b/src/odb_object.cpp @@ -9,7 +9,7 @@ namespace git git_odb_object_free(obj); } - git_otype OdbObject::type() const + git_object_t OdbObject::type() const { return git_odb_object_type(obj_.get()); } diff --git a/src/reference.cpp b/src/reference.cpp index 0466e67..617819a 100644 --- a/src/reference.cpp +++ b/src/reference.cpp @@ -22,14 +22,14 @@ namespace git return git_reference_name(ptr()); } - git_ref_t Reference::type() const + git_reference_t Reference::type() const { return git_reference_type(ptr()); } git_oid const & Reference::target() const { - assert(type() != GIT_REF_SYMBOLIC); + assert(type() == GIT_REFERENCE_DIRECT); return *git_reference_target(ptr()); } @@ -37,4 +37,9 @@ namespace git { return git_reference_symbolic_target(ptr()); } + + bool Reference::has_same_target_as(Reference const & other) const + { + return git_oid_equal(&target(), &other.target()); + } } diff --git a/src/remote.cpp b/src/remote.cpp index 040660b..a024701 100644 --- a/src/remote.cpp +++ b/src/remote.cpp @@ -6,11 +6,59 @@ namespace git { const char * Remote::url() const { - return git_remote_url(remote_); + return git_remote_url(remote_.get()); } const char * Remote::pushurl() const { - return git_remote_pushurl(remote_); + return git_remote_pushurl(remote_.get()); + } + + 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; + + opts.callbacks.update_tips = [] (char const * refname, git_oid const * a, git_oid const * b, void * data) + { + auto callbacks = static_cast(data); + callbacks->update_tips(refname, *a, *b); + return 0; + }; + opts.callbacks.sideband_progress = [] (char const * str, int len, void * data) + { + auto callbacks = static_cast(data); + callbacks->sideband_progress(str, len); + return 0; + }; + opts.callbacks.transfer_progress = [] (git_indexer_progress const * stats, void * data) + { + auto callbacks = static_cast(data); + callbacks->transfer_progress(*stats); + return 0; + }; + opts.callbacks.credentials = [] (git_credential ** out, char const *url, char const * user_from_url, unsigned int allowed_types, void * data) + { + auto callbacks = static_cast(data); + auto cred = callbacks->acquire_cred(url, user_from_url, allowed_types); + if (!cred) + return -1; + *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); + } + + void Remote::Destroy::operator()(git_remote * remote) const + { + git_remote_free(remote); } } diff --git a/src/repo.cpp b/src/repo.cpp index 7b359e7..bb59846 100644 --- a/src/repo.cpp +++ b/src/repo.cpp @@ -1,9 +1,12 @@ #include "git2cpp/repo.h" +#include "git2/clone.h" + #include "git2cpp/annotated_commit.h" #include "git2cpp/error.h" #include "git2cpp/internal/optional.h" +#include #include #include #include @@ -21,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; @@ -54,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; @@ -184,6 +208,11 @@ namespace git return *ref_; } + Reference * operator->() + { + return &*ref_; + } + private: static git_branch_t convert(branch_type t) { @@ -205,14 +234,35 @@ namespace git internal::optional ref_; }; - std::vector Repository::branches(branch_type type) const + std::vector Repository::branches(branch_type type, git_reference_t ref_kind) const { std::vector res; for (branch_iterator it(repo_.get(), type); it; ++it) - res.emplace_back(std::move(*it)); + { + if (ref_kind == GIT_REFERENCE_ALL || it->type() == ref_kind) + res.emplace_back(std::move(*it)); + } return res; } + Reference Repository::create_branch(const char * name, Commit const & target, bool force) + { + git_reference * ref; + const auto err = git_branch_create(&ref, repo_.get(), name, target.ptr(), force); + switch (err) + { + case GIT_OK: + return Reference(ref); + case GIT_EEXISTS: + assert(!force); + throw branch_create_error(branch_create_error::already_exists); + case GIT_EINVALIDSPEC: + throw branch_create_error(branch_create_error::invalid_spec); + default: + throw branch_create_error(branch_create_error::unknown); + } + } + Reference Repository::dwim(const char* shorthand) const { git_reference * ref; @@ -356,6 +406,14 @@ namespace git return res; } + git_oid Repository::amend_commit(Commit const & commit_to_amend, const char * update_ref, const char * message, Tree const & tree) + { + git_oid res; + auto op_res = git_commit_amend(&res, commit_to_amend.ptr(), update_ref, nullptr, nullptr, nullptr, message, tree.ptr()); + assert(op_res == GIT_OK); + return res; + } + namespace { Object tree_entry_to_object(git_tree_entry const * entry, Repository const & repo, git_repository * repo_ptr) @@ -502,19 +560,41 @@ namespace git return git_checkout_tree(repo_.get(), reinterpret_cast(commit.ptr()), &options); } + int Repository::checkout_head(git_checkout_options const & options) + { + return git_checkout_head(repo_.get(), &options); + } + int Repository::set_head(char const* ref) { return git_repository_set_head(repo_.get(), ref); } + int Repository::set_head_detached(git_oid const & commit) + { + return git_repository_set_head_detached(repo_.get(), &commit); + } + int Repository::set_head_detached(AnnotatedCommit const& commit) { return git_repository_set_head_detached_from_annotated(repo_.get(), commit.ptr()); } + Blame Repository::blame_file(const char* path, git_blame_options const& options) + { + git_blame * blame; + const auto err = git_blame_file( + &blame, repo_.get(), path, + /*IMO `options` can be const, git_blame_file doesn't change it*/ const_cast(&options) + ); + if (err != GIT_OK) + throw blame_file_error(path); + return Blame(blame); + } + 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/src/revspec.cpp b/src/revspec.cpp index 0de9d5a..ba3b9cd 100644 --- a/src/revspec.cpp +++ b/src/revspec.cpp @@ -3,14 +3,14 @@ namespace git { Revspec::Revspec(git_object * single, Repository const & repo) - : flags_(GIT_REVPARSE_SINGLE) + : flags_(GIT_REVSPEC_SINGLE) , revspec_(single, repo) { } Revspec::Revspec(git_revspec const & revspec, Repository const & repo) : flags_(revspec.flags) - , revspec_((revspec.flags & GIT_REVPARSE_SINGLE) + , revspec_((revspec.flags & GIT_REVSPEC_SINGLE) ? Range(revspec.from, repo) : Range(revspec, repo)) { @@ -18,7 +18,7 @@ namespace git Object * Revspec::single() { - if (flags_ & GIT_REVPARSE_SINGLE) + if (flags_ & GIT_REVSPEC_SINGLE) return &revspec_.from; else return nullptr; @@ -26,7 +26,7 @@ namespace git Revspec::Range const * Revspec::range() const { - if (flags_ & GIT_REVPARSE_SINGLE) + if (flags_ & GIT_REVSPEC_SINGLE) return nullptr; else return &revspec_; diff --git a/src/status.cpp b/src/status.cpp index 3fe3af0..0f76bcc 100644 --- a/src/status.cpp +++ b/src/status.cpp @@ -25,7 +25,7 @@ namespace git case Status::Options::Show::IndexOnly: return GIT_STATUS_SHOW_INDEX_ONLY; case Status::Options::Show::WorkdirOnly: - return GIT_STATUS_SHOW_INDEX_ONLY; + return GIT_STATUS_SHOW_WORKDIR_ONLY; default: return GIT_STATUS_SHOW_INDEX_AND_WORKDIR; } diff --git a/src/tag.cpp b/src/tag.cpp index e58ac14..dd5b4fc 100644 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -29,7 +29,7 @@ namespace git return *git_tag_target_id(tag_.get()); } - git_otype Tag::target_type() const + git_object_t Tag::target_type() const { return git_tag_target_type(tag_.get()); } diff --git a/src/tree.cpp b/src/tree.cpp index d9405fa..c315d21 100644 --- a/src/tree.cpp +++ b/src/tree.cpp @@ -82,7 +82,7 @@ namespace git return *git_tree_entry_id(entry_); } - git_otype Tree::BorrowedEntry::type() const + git_object_t Tree::BorrowedEntry::type() const { return git_tree_entry_type(entry_); } 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