diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c94340e --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +--- +Language: Cpp +ColumnLimit: 0 +BreakBeforeBraces: Custom +BraceWrapping: + AfterNamespace: true + AfterClass: true + AfterStruct: true + AfterEnum: true + AfterFunction: true + AfterControlStatement: true + BeforeElse: true + BeforeCatch: true + SplitEmptyFunction: false + SplitEmptyRecord: false +NamespaceIndentation: All +AccessModifierOffset: -4 +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +PointerAlignment: Middle +BreakConstructorInitializers: BeforeComma +BreakBeforeTernaryOperators: true +CompactNamespaces: true +FixNamespaceComments: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"git2cpp/' + Priority: 2 + - Regex: '^' + Priority: 4 + - Regex: '.*' + Priority: 1 +... + 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/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4acd4b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/libgit2"] + path = libs/libgit2 + url = https://github.com/libgit2/libgit2.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 20e434c..fa4ba6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,33 +1,63 @@ -project (libgit2cpp) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.6) -include_directories(include) +project(libgit2cpp LANGUAGES C CXX) +set(package git2cpp) -set(CMAKE_CXX_FLAGS -std=c++11) +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) -file(GLOB lib_sources - src/*.cpp - include/git2cpp/*.h +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 () + +file(GLOB_RECURSE LIBGIT2CPP_SOURCES + src/*.cpp + include/git2cpp/*.h +) + +add_library(${package} STATIC ${LIBGIT2CPP_SOURCES}) + +set_target_properties(${package} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + INTERFACE_COMPILE_FEATURES cxx_std_17 ) -add_library(git2cpp ${lib_sources}) -target_link_libraries(git2cpp git2) - -set(examples - add - branch - cat-file - diff - log - rev-list - showindex - status - init - rev-parse - general +if (MSVC AND ($MSVC_VERSION VERSION_GREATER 1900)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") +endif () + +target_include_directories(${package} + PRIVATE src + PUBLIC $ ) -foreach (example ${examples}) - add_executable(${example} examples/${example}.cpp) - target_link_libraries(${example} git2cpp) -endforeach(example) +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 (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 1033be6..f7427a8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,28 @@ libgit2cpp -======= +========== + +[![AppVeryor Build Status](https://ci.appveyor.com/api/projects/status/ps6e0s4nfnw5afh4/branch/master?svg=true)](https://ci.appveyor.com/project/AndreyG/libgit2cpp/branch/master) -C++ wrapper for libgit2 +C++ wrapper for libgit2. It contains libgit2 as submodule (libs/libgit2). Building libgit2cpp - Using CMake -============================== +================================= + + $ mkdir build && cd build + $ cmake .. + $ make + +Supporting CMake options: `USE_BOOST`, `BUNDLE_LIBGIT2`, `BUILD_LIBGIT2CPP_EXAMPLES`. + +Testing +======= + + $ cd build + $ ./test.sh [path_to_libgit2_testrepo] +e.g. + $ mkdir build && cd build $ cmake .. $ make + $ ./test.sh .. ../libs/libgit2/tests/resources/testrepo.git diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..432fc7b --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +libgit2cpp TODO +=============== + +* standardize command-line processing for all examples + * most importantly --git-dir +* unit tests +* embed test repo + * remove dep on libgit2/tests/resources/testrepo.git diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..cf75efc --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,29 @@ +version: '{build}' + +branches: + only: + - master + +environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017" + ARCH: 32 + BOOST: 1_65_1 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + GENERATOR: "Visual Studio 15 2017 Win64" + ARCH: 64 + BOOST: 1_65_1 + +clone_script: +- cmd: git clone --branch=%APPVEYOR_REPO_BRANCH% https://github.com/%APPVEYOR_REPO_NAME%.git %APPVEYOR_BUILD_FOLDER% +- cmd: cd %APPVEYOR_BUILD_FOLDER% +- cmd: git checkout -q %APPVEYOR_REPO_COMMIT% +- cmd: git submodule update --init + +build_script: +- ps: | + mkdir build + cd build + cmake -D BUILD_CLAR=OFF -D BUILD_EXAMPLES=OFF -D USE_BOOST=ON -D BOOST_ROOT="C:\Libraries\boost_$env:BOOST" .. -G"$env:GENERATOR" + cmake --build . --config Debug 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 f491114..d616a5b 100644 --- a/examples/add.cpp +++ b/examples/add.cpp @@ -1,120 +1,126 @@ #include +#include #include #include -#include +#include "git2cpp/initializer.h" #include "git2cpp/repo.h" -#include "git2cpp/threads_initializer.h" -enum print_options { - SKIP = 1, - VERBOSE = 2, - UPDATE = 4, -}; +#include -void init_array(git_strarray *array, int argc, char **argv) +enum print_options { - unsigned int i; - - array->count = argc; - array->strings = reinterpret_cast(malloc(sizeof(char*) * array->count)); - assert(array->strings!=NULL); - - for(i=0; icount; i++) { - array->strings[i]=argv[i]; - } + SKIP = 1, + VERBOSE = 2, + UPDATE = 4, +}; - return; +void init_array(git_strarray & arr, int argc, char ** argv) +{ + arr.count = argc; + arr.strings = reinterpret_cast(malloc(sizeof(char *) * argc)); + std::copy_n(argv, argc, arr.strings); } -int print_matched_cb( const char *path, const char * /*matched_pathspec*/, - git::Repository const & repo, print_options options ) +int print_matched_cb(const char * path, const char * /*matched_pathspec*/, + git::Repository const & repo, print_options options) { - git_status_t status = repo.file_status(path); - - int ret; - if (status & GIT_STATUS_WT_MODIFIED || - status & GIT_STATUS_WT_NEW) { - printf("add '%s'\n", path); - ret = 0; - } else { - ret = 1; - } - - if (options & SKIP) { - ret = 1; - } - - return ret; + git_status_t status = repo.file_status(path); + + int ret; + if (status & GIT_STATUS_WT_MODIFIED || + status & GIT_STATUS_WT_NEW) + { + printf("add '%s'\n", path); + ret = 0; + } + else + { + ret = 1; + } + + if (options & SKIP) + { + ret = 1; + } + + return ret; } void print_usage(void) { - fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n"); - fprintf(stderr, "\t-n, --dry-run dry run\n"); - fprintf(stderr, "\t-v, --verbose be verbose\n"); - fprintf(stderr, "\t-u, --update update tracked files\n"); + fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n"); + fprintf(stderr, "\t-n, --dry-run dry run\n"); + fprintf(stderr, "\t-v, --verbose be verbose\n"); + fprintf(stderr, "\t-u, --update update tracked files\n"); } -int main (int argc, char** argv) +int main(int argc, char ** argv) { using namespace std::placeholders; - git_strarray array = {0}; - - int i = 1, options = 0; - for (; i < argc; ++i) { - if (argv[i][0] != '-') { - break; - } - else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { - options |= VERBOSE; - } - else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { - options |= SKIP; - } - else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { - options |= UPDATE; - } - else if(!strcmp(argv[i], "-h")) { - print_usage(); - break; - } - else if(!strcmp(argv[i], "--")) { - i++; - break; - } - else { - fprintf(stderr, "Unsupported option %s.\n", argv[i]); - print_usage(); - return 1; - } - } - - if (argc<=i) { - print_usage(); - return 1; - } - - init_array(&array, argc-i, argv+i); - - git::ThreadsInitializer threads_initializer; - - git::Repository repo("."); + int i = 1, options = 0; + for (; i < argc; ++i) + { + if (argv[i][0] != '-') + { + break; + } + else if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) + { + options |= VERBOSE; + } + else if (!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) + { + options |= SKIP; + } + else if (!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) + { + options |= UPDATE; + } + else if (!strcmp(argv[i], "-h")) + { + print_usage(); + break; + } + else if (!strcmp(argv[i], "--")) + { + i++; + break; + } + else + { + fprintf(stderr, "Unsupported option %s.\n", argv[i]); + print_usage(); + return 1; + } + } + + if (argc <= i) + { + print_usage(); + return EXIT_FAILURE; + } + + git_strarray arr; + init_array(arr, argc - i, argv + i); + + auto_git_initializer; + + git::Repository repo("."); git::Index::matched_path_callback_t cb; - if (options&VERBOSE || options&SKIP) { - cb = std::bind(&print_matched_cb, _1, _2, std::cref(repo), static_cast(options)); - } + if (options & VERBOSE || options & SKIP) + cb = std::bind(&print_matched_cb, _1, _2, std::cref(repo), static_cast(options)); - git::Index index = repo.index(); - if (options&UPDATE) { - index.update_all(array, cb); - } else { - index.add_all(array, cb); - } + git::Index index = repo.index(); + if (options & UPDATE) + index.update_all(arr, cb); + else + index.add_all(arr, cb); - index.write(); + index.write(); + free(arr.strings); - return 0; + return 0; } 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 0c3f0fa..7d8f291 100644 --- a/examples/branch.cpp +++ b/examples/branch.cpp @@ -1,11 +1,73 @@ +#include "git2cpp/initializer.h" +#include "git2cpp/repo.h" + +#include #include -#include "git2cpp/repo.h" +namespace +{ + using namespace git; -int main() + 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::Repository repo("."); - auto branches = repo.branches(); - for (auto b : branches) - std::cout << b << std::endl; + Initializer threads_initializer; + + 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 723d68f..1b28176 100644 --- a/examples/cat-file.cpp +++ b/examples/cat-file.cpp @@ -1,217 +1,220 @@ -#include +#include "git2cpp/id_to_str.h" +#include "git2cpp/initializer.h" +#include "git2cpp/repo.h" + #include +#include #include #include +#include #include -#include "git2cpp/threads_initializer.h" -#include "git2cpp/repo.h" -#include "git2cpp/id_to_str.h" - -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} +namespace { -static void usage(const char *message, const char *arg) +[[noreturn]] 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: cat-file (-t | -s | -e | -p) [] \n"); - exit(1); + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: cat-file (-t | -s | -e | -p) [] \n"); + exit(1); } -static int check_str_param( - const char *arg, const char *pattern, const char **val) +bool check_str_param(const char * arg, const char * pattern, const char ** val) { - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; + size_t len = strlen(pattern); + if (strncmp(arg, pattern, len)) + return false; + *val = arg + len; + return true; } -static void print_signature(const char *header, const git_signature *sig) +void print_signature(const char * header, const git_signature * sig) { - char sign; - int offset, hours, minutes; - - if (!sig) - return; - - offset = sig->when.offset; - if (offset < 0) { - sign = '-'; - offset = -offset; - } else { - sign = '+'; - } - - hours = offset / 60; - minutes = offset % 60; - - printf("%s %s <%s> %ld %c%02d%02d\n", - header, sig->name, sig->email, (long)sig->when.time, - sign, hours, minutes); + if (!sig) + return; + + int offset = sig->when.offset; + char sign; + if (offset < 0) + { + sign = '-'; + offset = -offset; + } + else + { + sign = '+'; + } + + const int hours = offset / 60; + const int minutes = offset % 60; + + printf("%s %s <%s> %ld %c%02d%02d\n", + header, sig->name, sig->email, (long)sig->when.time, + sign, hours, minutes); } -static void show_blob(const git_blob *blob) +void show_blob(git::Blob const & blob) { - /* ? Does this need crlf filtering? */ - fwrite(git_blob_rawcontent(blob), git_blob_rawsize(blob), 1, stdout); + /* ? Does this need crlf filtering? */ + fwrite(blob.content(), blob.size(), 1, stdout); } -static void show_tree(const git_tree *tree) +void show_tree(git::Tree const & tree) { - size_t i, max_i = (int)git_tree_entrycount(tree); - char oidstr[GIT_OID_HEXSZ + 1]; - const git_tree_entry *te; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; - for (i = 0; i < max_i; ++i) { - te = git_tree_entry_byindex(tree, i); + for (size_t i = 0, n = tree.entrycount(); i < n; ++i) + { + auto te = tree[i]; - git_oid_tostr(oidstr, sizeof(oidstr), git_tree_entry_id(te)); + git_oid_tostr(oidstr, sizeof(oidstr), &te.id()); - printf("%06o %s %s\t%s\n", - git_tree_entry_filemode(te), - git_object_type2string(git_tree_entry_type(te)), - oidstr, git_tree_entry_name(te)); - } + printf("%06o %s %s\t%s\n", + te.filemode(), + git_object_type2string(te.type()), + oidstr, te.name()); + } } -static void show_commit(const git_commit *commit) +void show_commit(git::Commit const & commit) { - unsigned int i, max_i; - char oidstr[GIT_OID_HEXSZ + 1]; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; - git_oid_tostr(oidstr, sizeof(oidstr), git_commit_tree_id(commit)); - printf("tree %s\n", oidstr); + git_oid_tostr(oidstr, sizeof(oidstr), &commit.tree_id()); + printf("tree %s\n", oidstr); - max_i = (unsigned int)git_commit_parentcount(commit); - for (i = 0; i < max_i; ++i) { - git_oid_tostr(oidstr, sizeof(oidstr), git_commit_parent_id(commit, i)); - printf("parent %s\n", oidstr); - } + for (size_t i = 0, n = commit.parents_num(); i != n; ++i) + { + git_oid_tostr(oidstr, sizeof(oidstr), &commit.parent_id(i)); + printf("parent %s\n", oidstr); + } - print_signature("author", git_commit_author(commit)); - print_signature("committer", git_commit_committer(commit)); + print_signature("author", commit.author()); + print_signature("committer", commit.commiter()); - if (git_commit_message(commit)) - printf("\n%s\n", git_commit_message(commit)); + if (auto message = commit.message()) + printf("\n%s\n", message); } -static void show_tag(const git_tag *tag) +void show_tag(git::Tag const & tag) { - char oidstr[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(oidstr, sizeof(oidstr), git_tag_target_id(tag));; - printf("object %s\n", oidstr); - printf("type %s\n", git_object_type2string(git_tag_target_type(tag))); - printf("tag %s\n", git_tag_name(tag)); - print_signature("tagger", git_tag_tagger(tag)); - - if (git_tag_message(tag)) - printf("\n%s\n", git_tag_message(tag)); + std::cout << "object " << git::id_to_str(tag.target_id()) << "\n" + << "\ntype " << git_object_type2string(tag.target_type()) + << "\ntag " << tag.name() + << std::endl; + print_signature("tagger", tag.tagger()); + + if (auto message = tag.message()) + printf("\n%s\n", message); } -enum { - SHOW_TYPE = 1, - SHOW_SIZE = 2, - SHOW_NONE = 3, - SHOW_PRETTY = 4 +enum class Action +{ + NONE = 0, + SHOW_TYPE = 1, + SHOW_SIZE = 2, + SHOW_NONE = 3, + SHOW_PRETTY = 4 }; -int main(int argc, char *argv[]) +} + +int main(int argc, char * argv[]) { - const char *dir = ".", *rev = NULL; - int i, action = 0, verbose = 0; - char oidstr[GIT_OID_HEXSZ + 1]; - - for (i = 1; i < argc; ++i) { - char *a = argv[i]; - - if (a[0] != '-') { - if (rev != NULL) - usage("Only one rev should be provided", NULL); - else - rev = a; - } - else if (!strcmp(a, "-t")) - action = SHOW_TYPE; - else if (!strcmp(a, "-s")) - action = SHOW_SIZE; - else if (!strcmp(a, "-e")) - action = SHOW_NONE; - else if (!strcmp(a, "-p")) - action = SHOW_PRETTY; - else if (!strcmp(a, "-q")) - verbose = 0; - else if (!strcmp(a, "-v")) - verbose = 1; - else if (!strcmp(a, "--help") || !strcmp(a, "-h")) - usage(NULL, NULL); - else if (!check_str_param(a, "--git-dir=", &dir)) - usage("Unknown option", a); - } - - if (!action || !rev) - usage(NULL, NULL); - - git::ThreadsInitializer threads_initializer; + const char *dir = ".", *rev = nullptr; + int i, verbose = 0; + Action action = Action::NONE; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + + for (i = 1; i < argc; ++i) + { + char * a = argv[i]; + + if (a[0] != '-') + { + if (rev) + usage("Only one rev should be provided", nullptr); + else + rev = a; + } + else if (!strcmp(a, "-t")) + action = Action::SHOW_TYPE; + else if (!strcmp(a, "-s")) + action = Action::SHOW_SIZE; + else if (!strcmp(a, "-e")) + action = Action::SHOW_NONE; + else if (!strcmp(a, "-p")) + action = Action::SHOW_PRETTY; + else if (!strcmp(a, "-q")) + verbose = 0; + else if (!strcmp(a, "-v")) + verbose = 1; + else if (!strcmp(a, "--help") || !strcmp(a, "-h")) + usage(nullptr, nullptr); + else if (!check_str_param(a, "--git-dir=", &dir)) + usage("Unknown option", a); + } + + if (action == Action::NONE || !rev) + usage(nullptr, nullptr); + + git::Initializer threads_initializer; git::Repository repo(dir); - git::Object obj = revparse_single(repo, rev); - - if (verbose) { - std::cout - << git_object_type2string(obj.type()) - << " " - << git::id_to_str(obj.id()) - << "\n--\n"; - } - - switch (action) { - case SHOW_TYPE: - printf("%s\n", git_object_type2string(obj.type())); - break; - case SHOW_SIZE: + git::Object obj = revparse_single(repo, rev); + + if (verbose) + { + std::cout + << git_object_type2string(obj.type()) + << " " + << git::id_to_str(obj.id()) + << "\n--\n"; + } + + switch (action) + { + case Action::SHOW_TYPE: + printf("%s\n", git_object_type2string(obj.type())); + break; + case Action::SHOW_SIZE: + { + git::Odb odb = repo.odb(); + git::OdbObject odbobj = odb.read(obj.id()); + + printf("%zu\n", odbobj.size()); + break; + } + case Action::SHOW_NONE: + /* just want return result */ + break; + case Action::SHOW_PRETTY: + + switch (obj.type()) { - git::Odb odb = repo.odb(); - git::OdbObject odbobj = odb.read(obj.id()); - - printf("%ld\n", (long)odbobj.size()); - } - break; - case SHOW_NONE: - /* just want return result */ - break; - case SHOW_PRETTY: - - switch (obj.type()) { - case GIT_OBJ_BLOB: - show_blob(obj.as_blob()); - break; - case GIT_OBJ_COMMIT: - show_commit(obj.as_commit()); - break; - case GIT_OBJ_TREE: - show_tree(obj.as_tree()); - break; - case GIT_OBJ_TAG: - show_tag(obj.as_tag()); - break; - default: - printf("unknown %s\n", oidstr); - break; - } - break; - } - - return 0; + case GIT_OBJ_BLOB: + show_blob(obj.to_blob()); + break; + case GIT_OBJ_COMMIT: + show_commit(obj.to_commit()); + break; + case GIT_OBJ_TREE: + show_tree(obj.to_tree()); + break; + case GIT_OBJ_TAG: + show_tag(obj.to_tag()); + break; + default: + printf("unknown %s\n", oidstr); + break; + } + break; + default: + assert("unexpected action" && static_cast(action)); + } + + return 0; } diff --git a/examples/checkout.cpp b/examples/checkout.cpp new file mode 100644 index 0000000..e6ba273 --- /dev/null +++ b/examples/checkout.cpp @@ -0,0 +1,288 @@ +/* + * libgit2 "checkout" example - shows how to perform checkouts + * + * 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 "git2cpp/annotated_commit.h" + +#include + +#include +#include +#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 + +/** + * The following example demonstrates how to do checkouts with libgit2. + * + * Recognized options are : + * --force: force the checkout to happen. + * --[no-]progress: show checkout progress, on by default. + * --perf: show performance data. + */ + +namespace { + +struct args_info { + int argc; + char **argv; + int pos; +}; + +typedef struct { + int force : 1; + int progress : 1; + int perf : 1; +} checkout_options; + +[[noreturn]] void print_usage() +{ + fprintf(stderr, "usage: checkout [options] \n" + "Options are :\n" + " --git-dir: use the following git repository.\n" + " --force: force the checkout.\n" + " --[no-]progress: show checkout progress.\n" + " --perf: show performance data.\n"); + exit(1); +} + +bool match_bool_arg(int *out, args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + + if (!strcmp(found, opt)) { + *out = 1; + return true; + } + + if (!strncmp(found, "--no-", strlen("--no-")) && + !strcmp(found + strlen("--no-"), opt + 2)) { + *out = 0; + return true; + } + + *out = -1; + return false; +} + +[[noreturn]] void fatal(const char *message, const char *extra) +{ + if (extra) + fprintf(stderr, "%s %s\n", message, extra); + else + fprintf(stderr, "%s\n", message); + + exit(1); +} + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +bool match_str_arg(const char **out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return false; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected value following argument", opt); + args->pos += 1; + *out = args->argv[args->pos]; + return true; + } + + if (found[len] == '=') { + *out = found + len + 1; + return true; + } + + return false; +} + +void parse_options(const char **repo_path, checkout_options *opts, struct args_info *args) +{ + if (args->argc <= 1) + print_usage(); + + memset(opts, 0, sizeof(*opts)); + + /* Default values */ + opts->progress = 1; + + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + int bool_arg; + + if (strcmp(curr, "--") == 0) { + break; + } else if (!strcmp(curr, "--force")) { + opts->force = 1; + } else if (match_bool_arg(&bool_arg, args, "--progress")) { + opts->progress = bool_arg; + } else if (match_bool_arg(&bool_arg, args, "--perf")) { + opts->perf = bool_arg; + } else if (match_str_arg(repo_path, args, "--git-dir")) { + continue; + } else { + break; + } + } +} + +/** + * This function is called to report progression, ie. it's called once with + * a NULL path and the number of total steps, then for each subsequent path, + * the current completed_step value. + */ +void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void */*payload*/) +{ + if (path == NULL) { + printf("checkout started: %" PRIuZ " steps\n", total_steps); + } else { + printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps); + } +} + +/** + * This function is called when the checkout completes, and is used to report the + * number of syscalls performed. + */ +void print_perf_data(const git_checkout_perfdata *perfdata, void* /*payload*/) +{ + printf("perf: stat: %" PRIuZ " mkdir: %" PRIuZ " chmod: %" PRIuZ "\n", + perfdata->stat_calls, perfdata->mkdir_calls, perfdata->chmod_calls); +} + +/** + * This is the main "checkout " function, responsible for performing + * a branch-based checkout. + */ +void perform_checkout_ref(git::Repository & repo, git::AnnotatedCommit const & target, checkout_options const & opts) +{ + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + /** Setup our checkout options from the parsed options */ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + if (opts.force) + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + if (opts.progress) + checkout_opts.progress_cb = print_checkout_progress; + + if (opts.perf) + 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()); + /** + * Perform the checkout so the workdir corresponds to what target_commit + * contains. + * + * 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", git_error_last()->message); + return; + } + + /** + * Now that the checkout has completed, we have to update HEAD. + * + * Depending on the "origin" of target (ie. it's an OID or a branch name), + * we might need to detach HEAD. + */ + int err; + if (auto ref = target.commit_ref()) { + err = repo.set_head(ref); + } else { + err = repo.set_head_detached(target); + } + if (err != 0) { + fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message); + } +} + +git::AnnotatedCommit resolve_refish(git::Repository const & repo, const char *refish) +{ + if (auto ref = repo.dwim(refish)) + return repo.annotated_commit_from_ref(ref); + + auto obj = repo.revparse_single(refish); + return repo.annotated_commit_lookup(obj.single()->id()); +} + +} + +/** That example's entry point */ +int main(int argc, char **argv) +{ + args_info args { argc, argv }; + checkout_options opts; + const char *path = "."; + + /** Parse our command line options */ + parse_options(&path, &opts, &args); + + /** Initialize the library */ + auto_git_initializer; + + git::Repository repo(path); + + /** Make sure we're not about to checkout while something else is going on */ + auto const state = repo.state(); + if (state != GIT_REPOSITORY_STATE_NONE) { + fprintf(stderr, "repository is in unexpected state %d\n", state); + return EXIT_FAILURE; + } + + if (args.pos >= args.argc) { + fprintf(stderr, "unhandled\n"); + return EXIT_FAILURE; + } + + if (strcmp("--", args.argv[args.pos])) + { + /** + * Try to checkout the given path + */ + + fprintf(stderr, "unhandled path-based checkout\n"); + return EXIT_FAILURE; + } + + /** + * Try to resolve a "refish" argument to a target libgit2 can use + */ + auto checkout_target = resolve_refish(repo, args.argv[args.pos]); + perform_checkout_ref(repo, checkout_target, opts); +} 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/commit-graph-generator.cpp b/examples/commit-graph-generator.cpp new file mode 100644 index 0000000..0d07945 --- /dev/null +++ b/examples/commit-graph-generator.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +namespace +{ + struct visitor_t + { + void operator()(git::RevWalker & walker) + { + walker.sort(git::revwalker::sorting::topological); + walker.simplify_first_parent(); + + while (git::Commit commit = walker.next()) + { + output_commit(commit); + + for (size_t i = 0; i != commit.parents_num(); ++i) + { + output_hash(commit.id()) << " -> "; + output_hash(commit.parent_id(i)) << "\n"; + } + + for (size_t i = 1; i < commit.parents_num(); ++i) + { + auto branch_walker = repo_.rev_walker(); + branch_walker.push(commit.parent_id(i)); + branch_walker.hide(commit.merge_base(0, i)); + (*this)(branch_walker); + } + } + } + + visitor_t(git::Repository const & repo, std::ostream & out) + : repo_(repo) + , out_(out) + { + } + + private: + std::ostream & output_hash(git_oid const & id) const + { + out_ << "C" << git::id_to_str(id, 6); + return out_; + } + + void output_commit(git::Commit const & commit) const + { + output_hash(commit.id()) << " [label=\"" << commit.summary() << "\"];" + << "\n"; + } + + private: + git::Repository const & repo_; + std::ostream & out_; + }; + + void visit(git::Repository const & repo, std::ostream & out) + { + visitor_t visitor(repo, out); + + git::RevWalker walker = repo.rev_walker(); + walker.push_head(); + visitor(walker); + } +} + +int main(int argc, char * argv[]) +{ + auto_git_initializer; + git::Repository repo(argc >= 2 ? argv[1] : "."); + std::ofstream out(argc >= 3 ? argv[2] : "commit-graph.dot"); + + out << "digraph {\n"; + visit(repo, out); + out << "}\n"; +} diff --git a/examples/diff.cpp b/examples/diff.cpp index 2cd3119..d49402d 100644 --- a/examples/diff.cpp +++ b/examples/diff.cpp @@ -1,255 +1,432 @@ -#include #include +#include #include #include #include -#include "git2cpp/threads_initializer.h" +#include "git2cpp/diff.h" +#include "git2cpp/initializer.h" #include "git2cpp/repo.h" using namespace git; -Tree resolve_to_tree(Repository const & repo, const char *identifier) +namespace { + +Tree resolve_to_tree(Repository const & repo, const char * identifier) { Object obj = revparse_single(repo, identifier); - switch (obj.type()) + switch (obj.type()) { - case GIT_OBJ_TREE: - return obj.to_tree(); - break; - case GIT_OBJ_COMMIT: - return obj.to_commit().tree(); - break; - } + case GIT_OBJ_TREE: + return obj.to_tree(); + case GIT_OBJ_COMMIT: + return obj.to_commit().tree(); + } std::ostringstream ss; - ss - << "object corresponding to identifier " + ss + << "object corresponding to identifier " << identifier - << " is neither commit or tree"; - + << " is neither commit or tree"; + throw std::logic_error(ss.str()); } -const char *colors[] = { - "\033[m", /* reset */ - "\033[1m", /* bold */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[36m" /* cyan */ +const char * colors[] = { + "\033[m", /* reset */ + "\033[1m", /* bold */ + "\033[31m", /* red */ + "\033[32m", /* green */ + "\033[36m" /* cyan */ }; -static int printer( - const git_diff_delta *, - const git_diff_range *, - char usage, - const char *line, - size_t, - int & last_color) -{ - int color = 0; - - if (last_color >= 0) { - switch (usage) { - case GIT_DIFF_LINE_ADDITION: color = 3; break; - case GIT_DIFF_LINE_DELETION: color = 2; break; - case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; - case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; - case GIT_DIFF_LINE_FILE_HDR: color = 1; break; - case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; - default: color = 0; - } - if (color != last_color) { - if (last_color == 1 || color == 1) - fputs(colors[0], stdout); - fputs(colors[color], stdout); - last_color = color; - } - } - - fputs(line, stdout); - return 0; +[[noreturn]] 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: diff [ []]\n"); + exit(1); } -static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) -{ - size_t len = strlen(pattern); - uint16_t strval; - char *endptr = NULL; - if (strncmp(arg, pattern, len)) - return 0; - if (arg[len] == '\0' && pattern[len - 1] != '=') - return 1; - if (arg[len] == '=') - len++; - strval = strtoul(arg + len, &endptr, 0); - if (endptr == arg) - return 0; - *val = strval; - return 1; +namespace output +{ + typedef tagged_mask_t type; + + const type diff(1 << 0); + const type stat(1 << 1); + const type shortstat(1 << 2); + const type numstat(1 << 3); + const type summary(1 << 4); } -static int check_str_param(const char *arg, const char *pattern, const char **val) +enum class cache_t +{ + normal, + only, + none +}; + +/** The 'opts' struct captures all the various parsed command line options. */ +struct opts_t { - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + int color = -1; + cache_t cache = cache_t::normal; + output::type output; + diff::format format = diff::format::patch; + const char * treeish1 = nullptr; + const char * treeish2 = nullptr; + const char * dir = "."; + + opts_t(int argc, char * argv[]); +}; + +size_t is_prefixed(const char * str, const char * pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; } -static void usage(const char *message, const char *arg) +void fatal(const char * message, const char * extra) { - if (message && arg) - fprintf(stderr, "%s: %s\n", message, arg); - else if (message) - fprintf(stderr, "%s\n", message); - fprintf(stderr, "usage: diff [ []]\n"); - exit(1); + if (extra) + fprintf(stderr, "%s %s\n", message, extra); + else + fprintf(stderr, "%s\n", message); + + exit(1); } -enum { - FORMAT_PATCH = 0, - FORMAT_COMPACT = 1, - FORMAT_RAW = 2 +struct args_info +{ +private: + int argc; + char ** argv; + +public: + int pos; + + args_info(int argc, char ** argv) + : argc(argc) + , argv(argv) + , pos(0) + { + } + + int match_str(const char ** out, const char * opt); + int match_uint16(uint16_t * out, const char * opt); + +private: + const char * match_numeric(const char * opt); }; -/* */ -/* --cached */ -/* */ -/* --cached */ -/* nothing */ +int args_info::match_str(const char ** out, const char * opt) +{ + const char * found = argv[pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) + { + if (pos + 1 == argc) + fatal("expected value following argument", opt); + ++pos; + *out = argv[pos]; + return 1; + } + + if (found[len] == '=') + { + *out = found + len + 1; + return 1; + } + + return 0; +} + +const char * args_info::match_numeric(const char * opt) +{ + const char * found = argv[pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return NULL; + + if (!found[len]) + { + if (pos + 1 == argc) + fatal("expected numeric value following argument", opt); + pos += 1; + found = argv[pos]; + } + else + { + found = found + len; + if (*found == '=') + found++; + } + + return found; +} + +int args_info::match_uint16(uint16_t * out, const char * opt) +{ + const char * found = match_numeric(opt); + uint16_t val; + char * endptr = NULL; + + if (!found) + return 0; + + val = (uint16_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +int match_uint16_arg(uint32_t * out, args_info & args, const char * opt) +{ + uint16_t tmp; + const int res = args.match_uint16(&tmp, opt); + *out = tmp; + return res; +} + +/** Parse arguments as copied from git-diff. */ +opts_t::opts_t(int argc, char * argv[]) +{ + args_info args(argc, argv); + + for (args.pos = 1; args.pos < argc; ++args.pos) + { + const char * a = argv[args.pos]; + + if (a[0] != '-') + { + if (treeish1 == NULL) + treeish1 = a; + else if (treeish2 == NULL) + treeish2 = a; + else + usage("Only one or two tree identifiers can be provided", NULL); + } + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || + !strcmp(a, "--patch")) + { + output |= output::diff; + format = diff::format::patch; + } + else if (!strcmp(a, "--cached")) + cache = cache_t::only; + else if (!strcmp(a, "--nocache")) + cache = cache_t::none; + else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name")) + format = diff::format::name_only; + else if (!strcmp(a, "--name-status") || + !strcmp(a, "--format=name-status")) + format = diff::format::name_status; + else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw")) + format = diff::format::raw; + else if (!strcmp(a, "--format=diff-index")) + { + format = diff::format::raw; + diffopts.id_abbrev = 40; + } + else if (!strcmp(a, "--color")) + color = 0; + else if (!strcmp(a, "--no-color")) + color = -1; + else if (!strcmp(a, "-R")) + diffopts.flags |= GIT_DIFF_REVERSE; + else if (!strcmp(a, "-a") || !strcmp(a, "--text")) + diffopts.flags |= GIT_DIFF_FORCE_TEXT; + else if (!strcmp(a, "--ignore-space-at-eol")) + diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) + diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) + diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + else if (!strcmp(a, "--ignored")) + diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; + else if (!strcmp(a, "--untracked")) + diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + else if (!strcmp(a, "--patience")) + diffopts.flags |= GIT_DIFF_PATIENCE; + else if (!strcmp(a, "--minimal")) + diffopts.flags |= GIT_DIFF_MINIMAL; + else if (!strcmp(a, "--stat")) + output |= output::stat; + else if (!strcmp(a, "--numstat")) + output |= output::numstat; + else if (!strcmp(a, "--shortstat")) + output |= output::shortstat; + else if (!strcmp(a, "--summary")) + output |= output::summary; + else if (args.match_uint16( + &findopts.rename_threshold, "-M") || + args.match_uint16( + &findopts.rename_threshold, "--find-renames")) + findopts.flags |= GIT_DIFF_FIND_RENAMES; + else if (args.match_uint16( + &findopts.copy_threshold, "-C") || + args.match_uint16( + &findopts.copy_threshold, "--find-copies")) + findopts.flags |= GIT_DIFF_FIND_COPIES; + else if (!strcmp(a, "--find-copies-harder")) + findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) + /* TODO: parse thresholds */ + findopts.flags |= GIT_DIFF_FIND_REWRITES; + else if (!match_uint16_arg( + &diffopts.context_lines, args, "-U") && + !match_uint16_arg( + &diffopts.context_lines, args, "--unified") && + !match_uint16_arg( + &diffopts.interhunk_lines, args, "--inter-hunk-context") && + !args.match_uint16( + &diffopts.id_abbrev, "--abbrev") && + !args.match_str(&diffopts.old_prefix, "--src-prefix") && + !args.match_str(&diffopts.new_prefix, "--dst-prefix") && + !args.match_str(&dir, "--git-dir")) + usage("Unknown command line argument", a); + } +} + +Diff find_diff(Repository const & repo, Tree & t1, Tree & t2, cache_t cache, git_diff_options const & opts) +{ + if (t1 && t2) + { + return repo.diff(t1, t2, opts); + } + else if (cache == cache_t::normal) + { + if (t1) + return repo.diff_to_workdir_with_index(t1, opts); + else + return repo.diff_index_to_workdir(opts); + } + else + { + if (!t1) + t1 = resolve_to_tree(repo, "HEAD"); + + if (cache == cache_t::none) + return repo.diff_to_workdir(t1, opts); + else + return repo.diff_to_index(t1, opts); + } +} -DiffList diff(Repository & repo, Tree & t1, Tree & t2, bool cached, git_diff_options const & opts) +int color_printer(git_diff_delta const &, git_diff_hunk const &, git_diff_line const & l, int & last_color) { - auto r_ptr = repo.ptr(); + int color = 0; - if (t1 && t2) - return diff(r_ptr, t1, t2, opts); - else if (t1 && cached) - return diff_to_index(r_ptr, t1, opts); - else if (t1) - return std::move(diff_to_index(r_ptr, t1, opts).merge(diff_index_to_workdir(r_ptr, opts))); - else if (cached) + if (last_color >= 0) { - auto tree = resolve_to_tree(repo, "HEAD"); - return diff_to_index(r_ptr, tree, opts); + switch (l.origin) + { + case GIT_DIFF_LINE_ADDITION: + color = 3; + break; + case GIT_DIFF_LINE_DELETION: + color = 2; + break; + case GIT_DIFF_LINE_ADD_EOFNL: + color = 3; + break; + case GIT_DIFF_LINE_DEL_EOFNL: + color = 2; + break; + case GIT_DIFF_LINE_FILE_HDR: + color = 1; + break; + case GIT_DIFF_LINE_HUNK_HDR: + color = 4; + break; + default: + break; + } + + if (color != last_color) + { + if (last_color == 1 || color == 1) + fputs(colors[0], stdout); + fputs(colors[color], stdout); + last_color = color; + } } - else - return diff_index_to_workdir(r_ptr, opts); + + if (l.origin == GIT_DIFF_LINE_CONTEXT || + l.origin == GIT_DIFF_LINE_ADDITION || + l.origin == GIT_DIFF_LINE_DELETION) + fputc(l.origin, stdout); + + fwrite(l.content, 1, l.content_len, stdout); + + return 0; } -int main(int argc, char *argv[]) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - int i, color = -1, format = FORMAT_PATCH, cached = 0; - char *a, *treeish1 = NULL, *treeish2 = NULL; - const char *dir = "."; - - git::ThreadsInitializer threads_initializer; - - /* parse arguments as copied from git-diff */ - - for (i = 1; i < argc; ++i) { - a = argv[i]; - - if (a[0] != '-') { - if (treeish1 == NULL) - treeish1 = a; - else if (treeish2 == NULL) - treeish2 = a; - else - usage("Only one or two tree identifiers can be provided", NULL); - } - else if (!strcmp(a, "-p") || !strcmp(a, "-u") || - !strcmp(a, "--patch")) - format = FORMAT_PATCH; - else if (!strcmp(a, "--cached")) - cached = 1; - else if (!strcmp(a, "--name-status")) - format = FORMAT_COMPACT; - else if (!strcmp(a, "--raw")) - format = FORMAT_RAW; - else if (!strcmp(a, "--color")) - color = 0; - else if (!strcmp(a, "--no-color")) - color = -1; - else if (!strcmp(a, "-R")) - opts.flags |= GIT_DIFF_REVERSE; - else if (!strcmp(a, "-a") || !strcmp(a, "--text")) - opts.flags |= GIT_DIFF_FORCE_TEXT; - else if (!strcmp(a, "--ignore-space-at-eol")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; - else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; - else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; - else if (!strcmp(a, "--ignored")) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; - else if (!strcmp(a, "--untracked")) - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - else if (check_uint16_param(a, "-M", &findopts.rename_threshold) || - check_uint16_param(a, "--find-renames", - &findopts.rename_threshold)) - findopts.flags |= GIT_DIFF_FIND_RENAMES; - else if (check_uint16_param(a, "-C", &findopts.copy_threshold) || - check_uint16_param(a, "--find-copies", - &findopts.copy_threshold)) - findopts.flags |= GIT_DIFF_FIND_COPIES; - else if (!strcmp(a, "--find-copies-harder")) - findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; - else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) { - /* TODO: parse thresholds */ - findopts.flags |= GIT_DIFF_FIND_REWRITES; - } - else if (!check_uint16_param(a, "-U", &opts.context_lines) && - !check_uint16_param(a, "--unified=", &opts.context_lines) && - !check_uint16_param(a, "--inter-hunk-context=", - &opts.interhunk_lines) && - !check_str_param(a, "--src-prefix=", &opts.old_prefix) && - !check_str_param(a, "--dst-prefix=", &opts.new_prefix) && - !check_str_param(a, "--git-dir=", &dir)) - usage("Unknown arg", a); - } - - git::Repository repo(dir); - - git::Tree t1, t2; - - if (treeish1) - t1 = resolve_to_tree(repo, treeish1); - if (treeish2) - t2 = resolve_to_tree(repo, treeish2); - - DiffList diff_list = diff(repo, t1, t2, cached, opts); - - if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0) - diff_list.find_similar(findopts); - - if (color >= 0) - fputs(colors[0], stdout); - - using namespace std::placeholders; - auto print_cb = std::bind(&printer, _1, _2, _3, _4, _5, std::ref(color)); - - switch (format) { - case FORMAT_PATCH: - diff_list.print_patch(print_cb); - break; - case FORMAT_COMPACT: - diff_list.print_compact(print_cb); - break; - case FORMAT_RAW: - diff_list.print_raw(print_cb); - break; - } - - if (color >= 0) - fputs(colors[0], stdout); - - return 0; +void diff_print_stats(Diff const & diff, output::type output) +{ + namespace fmt = diff::stats::format; + fmt::type format = fmt::none; + if (output & output::stat) + format |= fmt::full; + if (output & output::shortstat) + format |= fmt::_short; + if (output & output::numstat) + format |= fmt::number; + if (output & output::summary) + format |= fmt::include_summary; + + auto stats = diff.stats(); + if (Buffer buf = stats.to_buf(format, 80)) + fputs(buf.ptr(), stdout); } +} + +int main(int argc, char * argv[]) +{ + auto_git_initializer; + opts_t opts(argc, argv); + Repository repo(opts.dir); + Tree t1, t2; + if (opts.treeish1) + t1 = resolve_to_tree(repo, opts.treeish1); + if (opts.treeish2) + t2 = resolve_to_tree(repo, opts.treeish2); + + Diff diff = find_diff(repo, t1, t2, opts.cache, opts.diffopts); + + if (opts.findopts.flags & GIT_DIFF_FIND_ALL) + diff.find_similar(opts.findopts); + + if (!opts.output) + opts.output = output::diff; + + if (opts.output != output::diff) + diff_print_stats(diff, opts.output); + + if (opts.output & output::diff) + { + if (opts.color >= 0) + fputs(colors[0], stdout); + + using namespace std::placeholders; + diff.print(opts.format, std::bind(&color_printer, _1, _2, _3, std::ref(opts.color))); + + if (opts.color >= 0) + fputs(colors[0], stdout); + } +} diff --git a/examples/general.cpp b/examples/general.cpp index 7e14770..249c0e7 100644 --- a/examples/general.cpp +++ b/examples/general.cpp @@ -28,42 +28,46 @@ #include #include -#include #include +#include -#include "git2cpp/repo.h" #include "git2cpp/config.h" +#include "git2cpp/initializer.h" +#include "git2cpp/repo.h" + using namespace git; // Almost all libgit2 functions return 0 on success or negative on error. // This is not production quality error checking, but should be sufficient // as an example. -static void check_error(int error_code, const char *action) +static void check_error(int error_code, const char * action) { - if (!error_code) - return; + if (!error_code) + return; - const git_error *error = giterr_last(); + const git_error * error = giterr_last(); - printf("Error %d %s - %s\n", error_code, action, - (error && error->message) ? error->message : "???"); + printf("Error %d %s - %s\n", error_code, action, + (error && error->message) ? error->message : "???"); - exit(1); + exit(1); } -int main (int argc, char** argv) +int main(int argc, char ** argv) { - // ### Opening the Repository - - // There are a couple of methods for opening a repository, this being the - // simplest. There are also [methods][me] for specifying the index file - // and work tree locations, here we assume they are in the normal places. - // - // (Try running this program against tests-clar/resources/testrepo.git.) - // - // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + // ### Opening the Repository + + // There are a couple of methods for opening a repository, this being the + // simplest. There are also [methods][me] for specifying the index file + // and work tree locations, here we assume they are in the normal places. + // + // (Try running this program against libgit2/tests/resources/testrepo.git/) + // + // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository assert(argc >= 2); - const char *repo_path = argv[1]; + const char * repo_path = argv[1]; + + git::Initializer threads_initializer; try { @@ -89,7 +93,7 @@ int main (int argc, char** argv) printf("\n*Raw to Hex*\n"); // If you have a oid, you can easily get the hex value of the SHA as well. - std::cout << "SHA hex string: " << id_to_str(&oid) << std::endl; + std::cout << "SHA hex string: " << id_to_str(oid) << std::endl; // ### Working with the Object Database @@ -109,7 +113,7 @@ int main (int argc, char** argv) // We can read raw objects directly from the object database if we have // the oid (SHA) of the object. This allows us to access objects without // knowing thier type and inspect the raw bytes unparsed. - OdbObject obj = odb.read(&oid); + OdbObject obj = odb.read(oid); // A raw object only has three properties - the type (commit, blob, tree // or tag), the size of the raw data and the raw, unparsed data itself. @@ -118,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). @@ -137,11 +141,11 @@ 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. - std::cout << "Written Object: " << id_to_str(&oid) << std::endl; + std::cout << "Written Object: " << id_to_str(oid) << std::endl; } // ### Object Parsing @@ -163,7 +167,7 @@ int main (int argc, char** argv) oid = str_to_id("8496071c1b46c854b31185ea97743be6a8774479"); - Commit commit = repo.commit_lookup(&oid); + Commit commit = repo.commit_lookup(oid); // Each of the properties of the commit object are accessible via methods, // including commonly needed variations, such as `git_commit_time` which @@ -184,7 +188,7 @@ int main (int argc, char** argv) // based on) and merge commits will have two or more. Commits can // technically have any number, though it's rare to have more than two. for (size_t p = 0, parents = commit.parents_num(); p != parents; ++p) - std::cout << "Parent: " << id_to_str(commit.parent(p).id()) << std::endl; + std::cout << "Parent: " << id_to_str(commit.parent_id(p)) << std::endl; } { @@ -211,24 +215,23 @@ int main (int argc, char** argv) // parents. Here we're creating oid objects to create the commit with, // but you can also use git_oid tree_id = str_to_id("f60079018b664e4e79329a7ef9559c8d9e0378d1"); - Tree tree = repo.tree_lookup(&tree_id); + Tree tree = repo.tree_lookup(tree_id); git_oid parent_id = str_to_id("5b5b025afb0b4c913b4c338a42934a3863bf3644"); - Commit parent = repo.commit_lookup(&parent_id); + Commit parent = repo.commit_lookup(parent_id); // Here we actually create the commit object with a single call with all // the values we need to create the commit. The SHA key is written to the // `commit_id` variable here. git_oid commit_id = repo.create_commit( - nullptr, /* do not update the HEAD */ - author, - cmtter, - nullptr, /* use default message encoding */ - "example commit", - tree, - parent); + nullptr, /* do not update the HEAD */ + author, + cmtter, + "example commit", + tree, + parent); // Now we can take a look at the commit SHA we've generated. - std::cout << "New Commit: " << id_to_str(&commit_id) << std::endl; + std::cout << "New Commit: " << id_to_str(commit_id) << std::endl; } { @@ -244,16 +247,16 @@ int main (int argc, char** argv) // We create an oid for the tag object if we know the SHA and look it up // the same way that we would a commit (or any other object). oid = str_to_id("b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - Tag tag = repo.tag_lookup(&oid); + Tag tag = repo.tag_lookup(oid); // Now that we have the tag object, we can extract the information it // generally contains: the target (usually a commit object), the type of // the target object (usually 'commit'), the name ('v1.0'), the tagger (a // git_signature - name, email, timestamp), and the tag message. - Object commit = tag.target(); - const char * tname = tag.name(); // "test" - git_otype ttype = tag.target_type(); // GIT_OBJ_COMMIT (otype enum) - const char * tmessage = tag.message();// "tag message\n" + Object commit = tag.target(repo); + const char * tname = tag.name(); // "test" + 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; } @@ -274,20 +277,20 @@ int main (int argc, char** argv) try { - Tree tree = repo.tree_lookup(&oid); + Tree tree = repo.tree_lookup(oid); // Getting the count of entries in the tree so you can iterate over them // if you want to. size_t cnt = tree.entrycount(); // 3 printf("tree entries: %d\n", (int)cnt); - git_tree_entry const * entry = tree[0]; - printf("Entry name: %s\n", git_tree_entry_name(entry)); // "hello.c" + auto entry = tree[0]; + printf("Entry name: %s\n", entry.name()); // "hello.c" // You can also access tree entries by name if you know the name of the // entry you're looking for. entry = tree["README"]; - git_tree_entry_name(entry); // "hello.c" + entry.name(); // "hello.c" // Once you have the entry object, you can access the content or subtree // (or commit, in the case of submodules) that it points to. You can also @@ -316,7 +319,7 @@ int main (int argc, char** argv) printf("\n*Blob Parsing*\n"); git_oid oid = str_to_id("1385f264afb75a56a5bec74243be9b367ba4ca08"); - Blob blob = repo.blob_lookup(&oid); + Blob blob = repo.blob_lookup(oid); // You can access a buffer with the raw contents of the blob directly. // Note that this buffer may not be contain ASCII data for certain blobs @@ -324,7 +327,7 @@ int main (int argc, char** argv) // string, and use the `git_blob_rawsize` attribute to find out its exact // size in bytes printf("Blob Size: %ld\n", (long)blob.size()); // 8 - blob.content(); // "content" + blob.content(); // "content" } { @@ -351,17 +354,17 @@ int main (int argc, char** argv) // branch1..branch2`, you would push the oid of `branch2` and hide the oid // of `branch1`. RevWalker walk = repo.rev_walker(); - walk.sort(GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); - walk.push(&oid); + walk.sort(revwalker::sorting::topological | revwalker::sorting::reverse); + walk.push(oid); // Now that we have the starting point pushed onto the walker, we start // asking for ancestors. It will return them in the sorting order we asked // for as commit oids. We can then lookup and parse the commited pointed // at by the returned OID; note that this operation is specially fast // since the raw contents of the commit object will be cached in memory - while (auto commit = walk.next(repo)) + while (auto commit = walk.next()) { - char const * cmsg = commit.message(); + char const * cmsg = commit.message(); git_signature const * cauth = commit.author(); printf("%s (%s)\n", cmsg, cauth->email); } @@ -391,7 +394,7 @@ int main (int argc, char** argv) // the `git_index_entry` struct for (size_t i = 0, ecount = index.entrycount(); i < ecount; ++i) { - const git_index_entry *e = index[i]; + const git_index_entry * e = index[i]; printf("path: %s\n", e->path); printf("mtime: %d\n", (int)e->mtime.seconds); @@ -480,4 +483,3 @@ int main (int argc, char** argv) return 1; } } - diff --git a/examples/init.cpp b/examples/init.cpp index e0dc2e7..a80a86a 100644 --- a/examples/init.cpp +++ b/examples/init.cpp @@ -20,73 +20,110 @@ #include #include -#include "git2cpp/threads_initializer.h" +#include "git2cpp/initializer.h" #include "git2cpp/repo.h" -static void usage(const char *error, const char *arg) +#ifdef USE_BOOST +#include + +using StringView = boost::string_view; +#else +#include + +using StringView = std::string_view; +#endif + +namespace { + +[[noreturn]] void usage(const char * error, const char * arg) { - fprintf(stderr, "error: %s '%s'\n", error, arg); - fprintf(stderr, "usage: init [-q | --quiet] [--bare] " - "[--template=] [--shared[=perms]] \n"); - exit(1); + fprintf(stderr, "error: %s '%s'\n", error, arg); + fprintf(stderr, "usage: init [-q | --quiet] [--bare] " + "[--template=] [--shared[=perms]] \n"); + exit(1); } /* simple string prefix test used in argument parsing */ -static size_t is_prefixed(const char *arg, const char *pfx) +size_t is_prefixed(StringView arg, StringView pfx) { - size_t len = strlen(pfx); - return !strncmp(arg, pfx, len) ? len : 0; + return arg == pfx ? pfx.size() : 0; } /* parse the tail of the --shared= argument */ -static uint32_t parse_shared(const char *shared) +uint32_t parse_shared(const char * shared) { - if (!strcmp(shared, "false") || !strcmp(shared, "umask")) - return GIT_REPOSITORY_INIT_SHARED_UMASK; - - else if (!strcmp(shared, "true") || !strcmp(shared, "group")) - return GIT_REPOSITORY_INIT_SHARED_GROUP; + if (!strcmp(shared, "false") || !strcmp(shared, "umask")) + return GIT_REPOSITORY_INIT_SHARED_UMASK; + + else if (!strcmp(shared, "true") || !strcmp(shared, "group")) + return GIT_REPOSITORY_INIT_SHARED_GROUP; + + else if (!strcmp(shared, "all") || !strcmp(shared, "world") || + !strcmp(shared, "everybody")) + return GIT_REPOSITORY_INIT_SHARED_ALL; + + else if (shared[0] == '0') + { + char * end = nullptr; + long val = strtol(shared + 1, &end, 8); + if (end == shared + 1 || *end != 0) + usage("invalid octal value for --shared", shared); + return (uint32_t)val; + } - else if (!strcmp(shared, "all") || !strcmp(shared, "world") || - !strcmp(shared, "everybody")) - return GIT_REPOSITORY_INIT_SHARED_ALL; + else + usage("unknown value for --shared", shared); +} - else if (shared[0] == '0') { - long val; - char *end = NULL; - val = strtol(shared + 1, &end, 8); - if (end == shared + 1 || *end != 0) - usage("invalid octal value for --shared", shared); - return (uint32_t)val; - } +using namespace git; - else - usage("unknown value for --shared", shared); +/* Unlike regular "git init", this example shows how to create an initial + * empty commit in the repository. This is the helper function that does + * that. + */ +static void create_initial_commit(Repository & repo) +{ + /* First use the config to initialize a commit signature for the user */ + Signature sig = repo.signature(); - return 0; -} + /* Now let's create an empty tree for this commit */ + Index index = repo.index(); -using namespace git; + /* Outside of this example, you could call git_index_add_bypath() + * here to put actual files into the index. For our purposes, we'll + * leave it empty for now. + */ -/* forward declaration of helper to make an empty parent-less commit */ -static void create_initial_commit(Repository & repo); + git_oid tree_id = index.write_tree(); + Tree tree = repo.tree_lookup(tree_id); + + /* Ready to create the initial commit + * + * Normally creating a commit would involve looking up the current + * HEAD commit and making that be the parent of the initial commit, + * but here this is the first commit so there will be no parent. + */ + repo.create_commit("HEAD", sig, sig, "Initial commit", tree); +} -git_repository_init_options make_opts( bool bare, const char * templ, - uint32_t shared, - const char * gitdir, - const char * dir ) +git_repository_init_options make_opts(bool bare, const char * templ, + uint32_t shared, + const char * gitdir, + const char * dir) { git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; if (bare) opts.flags |= GIT_REPOSITORY_INIT_BARE; - if (templ) { + if (templ) + { opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; opts.template_path = templ; } - if (gitdir) { + if (gitdir) + { /* if you specified a separate git directory, then initialize * the repository at that path and use the second path as the * working directory of the repository (with a git-link file) @@ -101,102 +138,79 @@ git_repository_init_options make_opts( bool bare, const char * templ, return opts; } -int main(int argc, char *argv[]) -{ - int no_options = 1, quiet = 0, bare = 0, initial_commit = 0, i; - uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK; - const char *templ = NULL, *gitdir = NULL, *dir = NULL; - size_t pfxlen; - - ThreadsInitializer threads_initializer; - - /* Process arguments */ - - for (i = 1; i < argc; ++i) { - char *a = argv[i]; - - if (a[0] == '-') - no_options = 0; - - if (a[0] != '-') { - if (dir != NULL) - usage("extra argument", a); - dir = a; - } - else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) - quiet = 1; - else if (!strcmp(a, "--bare")) - bare = 1; - else if ((pfxlen = is_prefixed(a, "--template=")) > 0) - templ = a + pfxlen; - else if (!strcmp(a, "--separate-git-dir")) - gitdir = argv[++i]; - else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0) - gitdir = a + pfxlen; - else if (!strcmp(a, "--shared")) - shared = GIT_REPOSITORY_INIT_SHARED_GROUP; - else if ((pfxlen = is_prefixed(a, "--shared=")) > 0) - shared = parse_shared(a + pfxlen); - else if (!strcmp(a, "--initial-commit")) - initial_commit = 1; - else - usage("unknown option", a); - } - - if (!dir) - usage("must specify directory to init", NULL); - - /* Initialize repository */ - - Repository repo = no_options - ? Repository(dir, Repository::init) - : Repository(dir, Repository::init, make_opts(bare, templ, shared, gitdir, dir)); - - /* Print a message to stdout like "git init" does */ - - if (!quiet) { - printf("Initialized empty Git repository in %s\n", - (bare || gitdir) ? repo.path() : repo.workdir()); - } - - /* As an extension to the basic "git init" command, this example - * gives the option to create an empty initial commit. This is - * mostly to demonstrate what it takes to do that, but also some - * people like to have that empty base commit in their repo. - */ - if (initial_commit) { - create_initial_commit(repo); - printf("Created empty initial commit\n"); - } - - return 0; } -/* Unlike regular "git init", this example shows how to create an initial - * empty commit in the repository. This is the helper function that does - * that. - */ -static void create_initial_commit(Repository & repo) +int main(int argc, char * argv[]) { - /* First use the config to initialize a commit signature for the user */ - Signature sig = repo.signature(); + bool no_options = true, quiet = false, bare = false, initial_commit = false; + uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK; + const char *templ = nullptr, *gitdir = nullptr, *dir = nullptr; + + auto_git_initializer; + + /* Process arguments */ + + for (int i = 1; i < argc; ++i) + { + auto arg = argv[i]; + StringView a = arg; + + if (arg[0] == '-') + no_options = false; + + if (arg[0] != '-') + { + if (dir) + usage("extra argument", arg); + dir = arg; + } + else if (a == "-q" || a == "--quiet") + quiet = true; + else if (a == "--bare") + bare = true; + else if (auto pfxlen = is_prefixed(a, "--template=")) + templ = arg + pfxlen; + else if (a == "--separate-git-dir") + gitdir = argv[++i]; + else if (auto pfxlen = is_prefixed(a, "--separate-git-dir=")) + gitdir = arg + pfxlen; + else if (a == "--shared") + shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if (auto pfxlen = is_prefixed(a, "--shared=")) + shared = parse_shared(arg + pfxlen); + else if (a == "--initial-commit") + initial_commit = true; + else + usage("unknown option", arg); + } - /* Now let's create an empty tree for this commit */ - Index index = repo.index(); + if (!dir) + usage("must specify directory to init", nullptr); - /* Outside of this example, you could call git_index_add_bypath() - * here to put actual files into the index. For our purposes, we'll - * leave it empty for now. - */ + /* Initialize repository */ - git_oid tree_id = index.write_tree(); - Tree tree = repo.tree_lookup(&tree_id); + Repository repo = no_options + ? Repository(dir, Repository::init) + : Repository(dir, Repository::init, make_opts(bare, templ, shared, gitdir, dir)); + + /* Print a message to stdout like "git init" does */ + + if (!quiet) + { + printf("Initialized empty Git repository in %s\n", + (bare || gitdir) ? repo.path() : repo.workdir()); + } - /* Ready to create the initial commit - * - * Normally creating a commit would involve looking up the current - * HEAD commit and making that be the parent of the initial commit, - * but here this is the first commit so there will be no parent. + /* As an extension to the basic "git init" command, this example + * gives the option to create an empty initial commit. This is + * mostly to demonstrate what it takes to do that, but also some + * people like to have that empty base commit in their repo. */ - repo.create_commit("HEAD", sig, sig, NULL, "Initial commit", tree); + if (initial_commit) + { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + return 0; } diff --git a/examples/log.cpp b/examples/log.cpp index 829c828..aa04446 100644 --- a/examples/log.cpp +++ b/examples/log.cpp @@ -3,210 +3,212 @@ #include #include -#include -#include - -#include "git2cpp/repo.h" -#include "git2cpp/pathspec.h" -#include "git2cpp/threads_initializer.h" +#include "git2cpp/diff.h" #include "git2cpp/id_to_str.h" +#include "git2cpp/initializer.h" +#include "git2cpp/pathspec.h" +#include "git2cpp/repo.h" #include "git2cpp/revspec.h" #include "git2cpp/revwalker.h" -static void usage(const char *message, const char *arg) +#include "git2cpp/internal/optional.h" + +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: log []\n"); - exit(1); + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: log []\n"); + exit(1); } -struct log_state +struct log_state { - std::string repodir = "."; - boost::optional repo; - std::unique_ptr walker; + const char * repodir = "."; + git::internal::optional repo; + git::internal::optional walker; int hide = 0; - int sorting = GIT_SORT_TIME; + git::revwalker::sorting::type sorting = git::revwalker::sorting::time; }; -static void set_sorting(struct log_state *s, unsigned int sort_mode) +static void set_sorting(struct log_state * s, git::revwalker::sorting::type sort_mode) { - if (!s->repo) { - s->repo = boost::in_place(s->repodir); - } + if (!s->repo) + { + git::internal::emplace(s->repo, s->repodir); + } - if (!s->walker) - s->walker.reset(new git::RevWalker(s->repo->rev_walker())); + if (!s->walker) + s->walker = s->repo->rev_walker(); - if (sort_mode == GIT_SORT_REVERSE) - s->sorting = s->sorting ^ GIT_SORT_REVERSE; - else - s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + if (sort_mode == git::revwalker::sorting::reverse) + s->sorting = s->sorting ^ git::revwalker::sorting::reverse; + else + s->sorting = sort_mode | (s->sorting & git::revwalker::sorting::reverse); - s->walker->sort(s->sorting); + s->walker->sort(s->sorting); } void push_rev(log_state * s, git::Commit const & commit, int hide) { - hide = s->hide ^ hide; + hide = s->hide ^ hide; - if (!s->walker) { - s->walker.reset(new git::RevWalker(s->repo->rev_walker())); - s->walker->sort(s->sorting); - } + if (!s->walker) + { + s->walker = s->repo->rev_walker(); + s->walker->sort(s->sorting); + } if (!commit) s->walker->push_head(); - else if (hide) - s->walker->hide(commit.id()); - else - s->walker->push(commit.id()); + else if (hide) + s->walker->hide(commit.id()); + else + s->walker->push(commit.id()); } -void push_rev(struct log_state *s, git::Object const & obj, int hide) +void push_rev(struct log_state * s, git::Object const & obj, int hide) { - hide = s->hide ^ hide; - - if (!s->walker) { - s->walker.reset(new git::RevWalker(s->repo->rev_walker())); - s->walker->sort(s->sorting); - } - - if (!obj) - s->walker->push_head(); - else if (hide) - s->walker->hide(obj.id()); - else - s->walker->push(obj.id()); + hide = s->hide ^ hide; + + if (!s->walker) + { + s->walker = s->repo->rev_walker(); + s->walker->sort(s->sorting); + } + + if (!obj) + s->walker->push_head(); + else if (hide) + s->walker->hide(obj.id()); + else + s->walker->push(obj.id()); } -void add_revision(struct log_state *s, const char *revstr) +void add_revision(struct log_state * s, const char * revstr) { - int hide = 0; + int hide = 0; - if (!s->repo) { - s->repo = boost::in_place(s->repodir); - } + if (!s->repo) + { + git::internal::emplace(s->repo, s->repodir); + } - if (!revstr) { - push_rev(s, git::Commit(nullptr), hide); - return; - } + if (!revstr) + { + push_rev(s, git::Commit(), hide); + return; + } if (*revstr == '^') hide = !hide; - git::Revspec revs = (*revstr == '^') - ? s->repo->revparse_single(revstr + 1) - : s->repo->revparse(revstr); + git::Revspec revs = (*revstr == '^') + ? s->repo->revparse_single(revstr + 1) + : s->repo->revparse(revstr); - if (auto obj = revs.single()) + if (auto obj = revs.single()) { - push_rev(s, *obj, hide); + push_rev(s, *obj, hide); } - else + else { git::Revspec::Range const & range = *revs.range(); - push_rev(s, range.to, hide); + push_rev(s, range.to, hide); - if ((revs.flags() & GIT_REVPARSE_MERGE_BASE) != 0) { - git_oid base = s->repo->merge_base(range); - push_rev(s, s->repo->commit_lookup(&base), hide); - } + 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); + } - push_rev(s, range.from, !hide); - } + push_rev(s, range.from, !hide); + } } -static void print_time(const git_time *intime, const char *prefix) +static void print_time(const git_time * intime, const char * prefix) { - char sign, out[32]; - struct tm intm; - int offset, hours, minutes; - time_t t; + char sign, out[32]; - offset = intime->offset; - if (offset < 0) { - sign = '-'; - offset = -offset; - } else { - sign = '+'; - } + int offset = intime->offset; + if (offset < 0) + { + sign = '-'; + offset = -offset; + } + else + { + sign = '+'; + } - hours = offset / 60; - minutes = offset % 60; + int hours = offset / 60; + int minutes = offset % 60; - t = (time_t)intime->time + (intime->offset * 60); + time_t t = static_cast(intime->time) + (intime->offset * 60); - gmtime_r(&t, &intm); - strftime(out, sizeof(out), "%a %b %e %T %Y", &intm); + auto intm = gmtime(&t); + strftime(out, sizeof(out), "%a %b %e %T %Y", intm); - printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); } 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) { - printf("Merge:"); - for (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) { - printf("Author: %s <%s>\n", sig->name, sig->email); - print_time(&sig->when, "Date: "); - } - printf("\n"); - - for (scan = commit.message(); scan && *scan; ) { - for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; - - printf(" %.*s\n", (int)(eol - scan), scan); - scan = *eol ? eol + 1 : NULL; - } - printf("\n"); + printf("commit %s\n", git::id_to_str(commit.id()).c_str()); + + const auto count = commit.parents_num(); + if (count > 1) + { + printf("Merge:"); + for (size_t i = 0; i < count; ++i) + { + printf(" %s", git::id_to_str(commit.parent_id(i), 7).c_str()); + } + printf("\n"); + } + + if (auto sig = commit.author()) + { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (const char* scan = commit.message(); scan && *scan;) + { + const char *eol = scan; + for (; *eol && *eol != '\n'; ++eol) /* find eol */ + ; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : nullptr; + } + printf("\n"); } -int print_diff( - const git_diff_delta *, - const git_diff_range *, - char , - const char *line, - size_t - ) +void print_diff(git_diff_delta const &, git_diff_hunk const &, git_diff_line const & line) { - fputs(line, stdout); - return 0; + fwrite(line.content, 1, line.content_len, stdout); } -static int match_int(int *value, const char *arg, int allow_negative) +static int match_int(int * value, const char * arg, int allow_negative) { - char *found; - *value = (int)strtol(arg, &found, 10); - return (found && *found == '\0' && (allow_negative || *value >= 0)); + char * found; + *value = (int)strtol(arg, &found, 10); + return (found && *found == '\0' && (allow_negative || *value >= 0)); } static int match_int_arg( - int *value, const char *arg, const char *pfx, int allow_negative) + int * value, const char * arg, const char * pfx, int allow_negative) { - size_t pfxlen = strlen(pfx); - if (strncmp(arg, pfx, pfxlen) != 0) - return 0; - if (!match_int(value, arg + pfxlen, allow_negative)) - usage("Invalid value after argument", arg); - return 1; + size_t pfxlen = strlen(pfx); + if (strncmp(arg, pfx, pfxlen) != 0) + return 0; + if (!match_int(value, arg + pfxlen, allow_negative)) + usage("Invalid value after argument", arg); + return 1; } static int match_with_parent(git::Commit const & commit, int i, git_diff_options const & opts) @@ -214,168 +216,173 @@ static int match_with_parent(git::Commit const & commit, int i, git_diff_options auto parent = commit.parent(i); auto c_tree = commit.tree(); auto p_tree = parent.tree(); - auto diff = git::diff(commit.owner(), p_tree, c_tree, opts); + auto diff = commit.repo().diff(p_tree, c_tree, opts); return diff.deltas_num() > 0; } -struct log_options { - int show_diff; - int skip, limit; - int min_parents, max_parents; - git_time_t before; - git_time_t after; - char *author; - char *committer; +struct log_options +{ + int show_diff; + int skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + char * author; + char * committer; }; -int parse_options ( int argc, char *argv[] - , log_state & s - , log_options & opt - , int count - ) +int parse_options(int argc, char * argv[], log_state & s, log_options & opt, int count) { int i = 1; - for (; i < argc; ++i) { - char* a = argv[i]; + for (; i < argc; ++i) + { + char * a = argv[i]; - if (a[0] != '-') { + if (a[0] != '-') + { try { - add_revision(&s, a); - ++count; + add_revision(&s, a); + ++count; } catch (...) /* try failed revision parse as filename */ - { + { break; } - } else if (!strcmp(a, "--")) { - ++i; - break; - } - else if (!strcmp(a, "--date-order")) - set_sorting(&s, GIT_SORT_TIME); - else if (!strcmp(a, "--topo-order")) - set_sorting(&s, GIT_SORT_TOPOLOGICAL); - else if (!strcmp(a, "--reverse")) - set_sorting(&s, GIT_SORT_REVERSE); - else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) - s.repodir = a + strlen("--git-dir="); - else if (match_int_arg(&opt.skip, a, "--skip=", 0)) - /* found valid --skip */; - else if (match_int_arg(&opt.limit, a, "--max-count=", 0)) - /* found valid --max-count */; - else if (a[1] >= '0' && a[1] <= '9') { - if (!match_int(&opt.limit, a + 1, 0)) - usage("Invalid limit on number of commits", a); - } else if (!strcmp(a, "-n")) { - if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0)) - usage("Argument -n not followed by valid count", argv[i + 1]); - else - ++i; - } - else if (!strcmp(a, "--merges")) - opt.min_parents = 2; - else if (!strcmp(a, "--no-merges")) - opt.max_parents = 1; - else if (!strcmp(a, "--no-min-parents")) - opt.min_parents = 0; - else if (!strcmp(a, "--no-max-parents")) - opt.max_parents = -1; - else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1)) - /* found valid --max-parents */; - else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0)) - /* found valid --min_parents */; - else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) - opt.show_diff = 1; - else - usage("Unsupported argument", a); - } + } + else if (!strcmp(a, "--")) + { + ++i; + break; + } + else if (!strcmp(a, "--date-order")) + set_sorting(&s, git::revwalker::sorting::time); + else if (!strcmp(a, "--topo-order")) + set_sorting(&s, git::revwalker::sorting::topological); + else if (!strcmp(a, "--reverse")) + set_sorting(&s, git::revwalker::sorting::reverse); + else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) + s.repodir = a + strlen("--git-dir="); + else if (match_int_arg(&opt.skip, a, "--skip=", 0)) + /* found valid --skip */; + else if (match_int_arg(&opt.limit, a, "--max-count=", 0)) + /* found valid --max-count */; + else if (a[1] >= '0' && a[1] <= '9') + { + if (!match_int(&opt.limit, a + 1, 0)) + usage("Invalid limit on number of commits", a); + } + else if (!strcmp(a, "-n")) + { + if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0)) + usage("Argument -n not followed by valid count", argv[i + 1]); + else + ++i; + } + else if (!strcmp(a, "--merges")) + opt.min_parents = 2; + else if (!strcmp(a, "--no-merges")) + opt.max_parents = 1; + else if (!strcmp(a, "--no-min-parents")) + opt.min_parents = 0; + else if (!strcmp(a, "--no-max-parents")) + opt.max_parents = -1; + else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1)) + /* found valid --max-parents */; + else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0)) + /* found valid --min_parents */; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + opt.show_diff = 1; + else + usage("Unsupported argument", a); + } return i; } -int main(int argc, char *argv[]) +int main(int argc, char * argv[]) { - log_options opt; - memset(&opt, 0, sizeof(opt)); - opt.max_parents = -1; - opt.limit = -1; - + log_options opt; + memset(&opt, 0, sizeof(opt)); + opt.max_parents = -1; + opt.limit = -1; + + git::Initializer threads_initializer; + log_state s; int count = 0; int parsed_options_num = parse_options(argc, argv, s, opt, count); - git::ThreadsInitializer threads_initializer; - - if (!count) - add_revision(&s, NULL); + if (!count) + add_revision(&s, NULL); - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - diffopts.pathspec.strings = &argv[parsed_options_num]; - diffopts.pathspec.count = argc - parsed_options_num; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + diffopts.pathspec.strings = &argv[parsed_options_num]; + diffopts.pathspec.count = argc - parsed_options_num; git::Pathspec ps(diffopts.pathspec); - count = 0; + count = 0; int printed = 0; - while (auto commit = s.walker->next(*s.repo)) + while (git::Commit commit = s.walker->next()) { - int parents = commit.parents_num(); - if (parents < opt.min_parents) - continue; - if (opt.max_parents > 0 && parents > opt.max_parents) - continue; + int parents = commit.parents_num(); + if (parents < opt.min_parents) + continue; + if (opt.max_parents > 0 && parents > opt.max_parents) + continue; - if (diffopts.pathspec.count > 0) + if (diffopts.pathspec.count > 0) { - int unmatched = parents; + int unmatched = parents; - if (parents == 0) + if (parents == 0) { - if (commit.tree().pathspec_match(GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) - unmatched = 1; - } - else if (parents == 1) + if (commit.tree().pathspec_match(GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) + unmatched = 1; + } + else if (parents == 1) { - unmatched = match_with_parent(commit, 0, diffopts) ? 0 : 1; - } - else + unmatched = match_with_parent(commit, 0, diffopts) ? 0 : 1; + } + else { - for (int i = 0; i < parents; ++i) + for (int i = 0; i < parents; ++i) { - if (match_with_parent(commit, i, diffopts)) - unmatched--; - } - } + if (match_with_parent(commit, i, diffopts)) + unmatched--; + } + } - if (unmatched > 0) - continue; - } + if (unmatched > 0) + continue; + } - if (count++ < opt.skip) - continue; - if (opt.limit != -1 && printed++ >= opt.limit) { - break; - } + if (count++ < opt.skip) + continue; + if (opt.limit != -1 && printed++ >= opt.limit) + { + break; + } - print_commit(commit); + print_commit(commit); - if (opt.show_diff) + if (opt.show_diff) { - if (parents > 1) - continue; + if (parents > 1) + continue; git::Tree b = commit.tree(); git::Tree a; - if (parents == 1) + if (parents == 1) { a = commit.parent(0).tree(); - } + } - git::diff(commit.owner(), a, b, diffopts).print_patch(print_diff); - } - } + s.repo->diff(a, b, diffopts).print(git::diff::format::patch, print_diff); + } + } - return 0; + return 0; } 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 new file mode 100644 index 0000000..90bd93d --- /dev/null +++ b/examples/remote.cpp @@ -0,0 +1,234 @@ +/* + * libgit2 "remote" example - shows how to modify remotes for a repo + * + * 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/errors.h" + +#include + +/** + * This is a sample program that is similar to "git remote". See the + * documentation for that (try "git help remote") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to modify remotes of a repository. + */ + +namespace { + +[[noreturn]] void usage(const char *msg, const char *arg = nullptr) +{ + fputs("usage: remote add \n", stderr); + fputs(" remote remove \n", stderr); + fputs(" remote rename \n", stderr); + fputs(" remote set-url [--push] \n", stderr); + fputs(" remote show [-v|--verbose]\n", stderr); + + if (msg && !arg) + fprintf(stderr, "\n%s\n", msg); + else if (msg && arg) + fprintf(stderr, "\n%s: %s\n", msg, arg); + exit(1); +} + +[[noreturn]] void report_error(const char *message) +{ + const git_error *lg2err = git_error_last(); + const char *lg2msg = "", *lg2spacer = ""; + + if (lg2err && lg2err->message) + { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + fprintf(stderr, "%s %s%s\n", message, lg2spacer, lg2msg); + exit(1); +} + +enum class Subcmd { + add, + remove, + rename, + seturl, + show, +}; + +struct Options { + Subcmd cmd; + + /* for command-specific args */ + int argc; + char **argv; + + Options(int argc, char **argv) + : argc(argc - 2) /* executable and subcommand are removed */ + , argv(argv + 2) + { + char *arg = argv[1]; + if (argc < 2) + usage("no command specified"); + + if (!strcmp(arg, "add")) { + cmd = Subcmd::add; + } else if (!strcmp(arg, "remove")) { + cmd = Subcmd::remove; + } else if (!strcmp(arg, "rename")) { + cmd = Subcmd::rename; + } else if (!strcmp(arg, "set-url")) { + cmd = Subcmd::seturl; + } else if (!strcmp(arg, "show")) { + cmd = Subcmd::show; + } else { + usage("command is not valid", arg); + } + } +}; + +using git::Repository; + +void cmd_add(Repository & repo, Options const & o) +{ + if (o.argc != 2) + usage("you need to specify a name and URL"); + + auto name = o.argv[0], url = o.argv[1]; + repo.create_remote(name, url); +} + +void cmd_remove(Repository & repo, Options const & o) +{ + if (o.argc != 1) + usage("you need to specify a name"); + + auto name = o.argv[0]; + repo.delete_remote(name); +} + +void cmd_rename(Repository & repo, Options const & o) +{ + if (o.argc != 2) + usage("you need to specify old and new remote name"); + + auto old_name = o.argv[0]; + auto new_name = o.argv[1]; + if (auto problems = repo.rename_remote(old_name, new_name)) + { + for (size_t i = 0, n = problems->count(); i != n; ++i) + puts((*problems)[i]); + } +} + +void cmd_seturl(Repository & repo, Options const & o) +{ + bool push = false; + char const * name = nullptr, *url = nullptr; + + for (auto i = 0; i != o.argc; ++i) + { + char const * arg = o.argv[i]; + + if (!strcmp(arg, "--push")) { + push = 1; + } else if (arg[0] != '-' && !name) { + name = arg; + } else if (arg[0] != '-' && !url) { + url = arg; + } else { + usage("invalid argument to set-url", arg); + } + } + + if (!name || !url) + usage("you need to specify remote and the new URL"); + + if (push) + repo.set_pushurl(name, url); + else + repo.set_url(name, url); +} + +void cmd_show(Repository const & repo, Options const & o) +{ + bool verbose = false; + + for (int i = 0; i != o.argc; ++i) + { + auto arg = o.argv[i]; + + if (!strcmp(arg, "-v") || + !strcmp(arg, "--verbose")) + { + verbose = true; + } + } + + auto remotes = repo.remotes(); + + for (size_t i = 0, n = remotes.count(); i != n; ++i) + { + auto name = remotes[i]; + if (!verbose) { + puts(name); + continue; + } + + auto remote = repo.remote(name); + + auto fetch = remote.url(); + if (fetch) + printf("%s\t%s (fetch)\n", name, fetch); + auto push = remote.pushurl(); + /* use fetch URL if no distinct push URL has been set */ + push = push ? push : fetch; + if (push) + printf("%s\t%s (push)\n", name, push); + } +} + +} + +int main(int argc, char *argv[]) +{ + Options opt(argc, argv); + + auto_git_initializer; + + auto path_to_repo = git::Repository::discover("."); + if (!path_to_repo) + report_error("Could not find repository"); + + Repository repo(*path_to_repo); + + switch (opt.cmd) + { + case Subcmd::add: + cmd_add(repo, opt); + break; + case Subcmd::remove: + cmd_remove(repo, opt); + break; + case Subcmd::rename: + cmd_rename(repo, opt); + break; + case Subcmd::seturl: + cmd_seturl(repo, opt); + break; + case Subcmd::show: + cmd_show(repo, opt); + break; + } +} diff --git a/examples/rev-list.cpp b/examples/rev-list.cpp index 8d9a599..3adc8c5 100644 --- a/examples/rev-list.cpp +++ b/examples/rev-list.cpp @@ -1,102 +1,116 @@ -#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; -void push_commit(RevWalker const & walk, const git_oid *oid, int hide) +void push_commit(RevWalker const & walk, git_oid const & oid, int hide) { - if (hide) - return walk.hide(oid); - else - return walk.push(oid); + if (hide) + return walk.hide(oid); + else + return walk.push(oid); } -void push_spec(Repository const & repo, RevWalker const & walk, const char *spec, int hide) +void push_spec(Repository const & repo, RevWalker const & walk, const char * spec, int hide) { - push_commit(walk, revparse_single(repo, spec).id(), hide); + push_commit(walk, revparse_single(repo, spec).id(), hide); } -void push_range(Repository const & repo, RevWalker const & walk, const char *range, int hide) +void push_range(Repository const & repo, RevWalker const & walk, const char * range, int hide) { auto revspec = repo.revparse(range); - if (revspec.flags() & GIT_REVPARSE_MERGE_BASE) { - /* TODO: support "..." */ + if (revspec.flags() & GIT_REVSPEC_MERGE_BASE) + { + /* TODO: support "..." */ throw std::runtime_error("unsupported operation"); - } + } auto const & r = *revspec.range(); - push_commit(walk, r.to.id(), !hide); + push_commit(walk, r.to.id(), !hide); push_commit(walk, r.from.id(), hide); } -void revwalk_parseopts(Repository const & repo, RevWalker & walk, int nopts, char **opts) +void revwalk_parseopts(Repository const & repo, RevWalker & walk, int nopts, char ** opts) { - unsigned int sorting = GIT_SORT_NONE; - - int hide = 0; - for (int i = 0; i < nopts; i++) { - if (!strcmp(opts[i], "--topo-order")) { - sorting = GIT_SORT_TOPOLOGICAL | (sorting & GIT_SORT_REVERSE); - walk.sort(sorting); - } else if (!strcmp(opts[i], "--date-order")) { - sorting = GIT_SORT_TIME | (sorting & GIT_SORT_REVERSE); - walk.sort(sorting); - } else if (!strcmp(opts[i], "--reverse")) { - sorting = (sorting & ~GIT_SORT_REVERSE) - | ((sorting & GIT_SORT_REVERSE) ? 0 : GIT_SORT_REVERSE); - walk.sort(sorting); - } else if (!strcmp(opts[i], "--not")) { - hide = !hide; - } else if (opts[i][0] == '^') { - push_spec(repo, walk, opts[i] + 1, !hide); - } else if (strstr(opts[i], "..")) { - push_range(repo, walk, opts[i], hide); - } else { - push_spec(repo, walk, opts[i], hide); - } - } + namespace sort = revwalker::sorting; + auto sorting = sort::none; + + int hide = 0; + for (int i = 0; i < nopts; i++) + { + if (!strcmp(opts[i], "--topo-order")) + { + sorting = sort::topological | (sorting & sort::reverse); + walk.sort(sorting); + } + else if (!strcmp(opts[i], "--date-order")) + { + sorting = sort::time | (sorting & sort::reverse); + walk.sort(sorting); + } + else if (!strcmp(opts[i], "--reverse")) + { + if ((sorting & sort::reverse) != sort::none) + sorting = sorting & ~sort::reverse; + walk.sort(sorting); + } + else if (!strcmp(opts[i], "--not")) + { + hide = !hide; + } + else if (opts[i][0] == '^') + { + push_spec(repo, walk, opts[i] + 1, !hide); + } + else if (strstr(opts[i], "..")) + { + push_range(repo, walk, opts[i], hide); + } + else + { + push_spec(repo, walk, opts[i], hide); + } + } } -int main (int argc, char **argv) +int main(int argc, char ** argv) { + auto_git_initializer; + try { Repository repo("."); auto walker = repo.rev_walker(); - revwalk_parseopts(repo, walker, argc-1, argv+1); + revwalk_parseopts(repo, walker, argc - 1, argv + 1); char buf[41]; - while (walker.next(buf)) + while (walker.next(buf)) { buf[40] = '\0'; printf("%s\n", buf); } - return 0; + return 0; } 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 f3306d4..9c14b1e 100644 --- a/examples/rev-parse.cpp +++ b/examples/rev-parse.cpp @@ -3,69 +3,73 @@ #include #include -#include +#include -#include "git2cpp/threads_initializer.h" +#include "git2cpp/initializer.h" +#include "git2cpp/internal/optional.h" #include "git2cpp/repo.h" using namespace git; -static void usage(const char *message, const char *arg) +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: rev-parse [ --option ] ...\n"); - exit(1); + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: rev-parse [ --option ] ...\n"); + exit(1); } -struct parse_state { - boost::optional repo; - std::string repodir = "."; +struct parse_state +{ + internal::optional repo; + const char * repodir = "."; }; -void parse_revision(parse_state & ps, const char *revstr) +void parse_revision(parse_state & ps, const char * revstr) { - if (!ps.repo) - ps.repo = boost::in_place(ps.repodir); + if (!ps.repo) + internal::emplace(ps.repo, ps.repodir); Revspec rs = ps.repo->revparse(revstr); if (auto commit = rs.single()) { - std::cout << id_to_str(commit->id()) << std::endl; - } - else + std::cout << id_to_str(commit->id()) << std::endl; + } + else { auto const & range = *rs.range(); std::cout << id_to_str(range.to.id()) << std::endl; - if ((rs.flags() & GIT_REVPARSE_MERGE_BASE) != 0) { - git_oid base = ps.repo->merge_base(range); - std::cout << id_to_str(&base) << std::endl; - } + if ((rs.flags() & GIT_REVSPEC_MERGE_BASE) != 0) + { + git_oid base = ps.repo->merge_base(range); + std::cout << id_to_str(base) << std::endl; + } std::cout << "^" << id_to_str(range.from.id()) << std::endl; - } + } } -int main(int argc, char *argv[]) +int main(int argc, char * argv[]) { - parse_state ps; + auto_git_initializer; - ThreadsInitializer threads_initalizer; + parse_state ps; - for (int i = 1; i < argc; ++i) { - const char * a = argv[i]; + for (int i = 1; i < argc; ++i) + { + const char * a = argv[i]; - if (a[0] != '-') - parse_revision(ps, a); + if (a[0] != '-') + parse_revision(ps, a); else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) - ps.repodir = a + strlen("--git-dir="); - else - usage("Cannot handle argument", a); - } + ps.repodir = a + strlen("--git-dir="); + else + usage("Cannot handle argument", a); + } - return 0; + return 0; } diff --git a/examples/showindex.cpp b/examples/showindex.cpp index b259e18..1927ef0 100644 --- a/examples/showindex.cpp +++ b/examples/showindex.cpp @@ -1,58 +1,55 @@ #include #include -extern "C" -{ #include -} -#include "git2cpp/threads_initializer.h" +#include "git2cpp/initializer.h" #include "git2cpp/repo.h" using namespace git; -int main (int argc, char** argv) +int main(int argc, char ** argv) { const char * dir = "."; - ThreadsInitializer threads_initalizer; + Initializer threads_initalizer; - if (argc > 1) - dir = argv[1]; - if (!dir || argc > 2) { - fprintf(stderr, "usage: showindex []\n"); - return 1; - } + if (argc > 1) + dir = argv[1]; + if (!dir || argc > 2) + { + fprintf(stderr, "usage: showindex []\n"); + return 1; + } - size_t dirlen = strlen(dir); + size_t dirlen = strlen(dir); Index index = (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) - ? Index(dir) - : Repository(dir).index(); + ? Index(dir) + : Repository(dir).index(); - size_t ecount = index.entrycount(); - if (!ecount) - printf("Empty index\n"); + size_t ecount = index.entrycount(); + if (!ecount) + printf("Empty index\n"); - for (size_t i = 0; i < ecount; ++i) + for (size_t i = 0; i < ecount; ++i) { - auto e = index[i]; + auto e = index[i]; char out[41]; out[40] = '\0'; - git_oid_fmt(out, &e->oid); - - printf("File Path: %s\n", e->path); - printf(" Stage: %d\n", git_index_entry_stage(e)); - printf(" Blob SHA: %s\n", out); - printf("File Mode: %07o\n", e->mode); - printf("File Size: %d bytes\n", (int)e->file_size); - printf("Dev/Inode: %d/%d\n", (int)e->dev, (int)e->ino); - printf(" UID/GID: %d/%d\n", (int)e->uid, (int)e->gid); - printf(" ctime: %d\n", (int)e->ctime.seconds); - printf(" mtime: %d\n", (int)e->mtime.seconds); - printf("\n"); - } - - return 0; + git_oid_fmt(out, &e->id); + + printf("File Path: %s\n", e->path); + printf(" Stage: %d\n", git_index_entry_stage(e)); + printf(" Blob SHA: %s\n", out); + printf("File Mode: %07o\n", e->mode); + printf("File Size: %d bytes\n", (int)e->file_size); + printf("Dev/Inode: %d/%d\n", (int)e->dev, (int)e->ino); + printf(" UID/GID: %d/%d\n", (int)e->uid, (int)e->gid); + printf(" ctime: %d\n", (int)e->ctime.seconds); + printf(" mtime: %d\n", (int)e->mtime.seconds); + printf("\n"); + } + + return 0; } - diff --git a/examples/status.cpp b/examples/status.cpp index 496aee1..68d9126 100644 --- a/examples/status.cpp +++ b/examples/status.cpp @@ -4,26 +4,41 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include -#include #include #include -extern "C" -{ -#include -} - +#include "git2cpp/initializer.h" #include "git2cpp/repo.h" +#ifdef USE_BOOST +#include +#include + +template +using Optional = boost::optional; + +using StringView = boost::string_view; +#else +#include +#include + +template +using Optional = std::optional; + +using StringView = std::string_view; +#endif + using namespace git; -enum { - FORMAT_DEFAULT = 0, - FORMAT_LONG = 1, - FORMAT_SHORT = 2, - FORMAT_PORCELAIN = 3, +namespace { + +enum class Format +{ + DEFAULT, + LONG, + SHORT, + PORCELAIN, }; /* @@ -44,358 +59,380 @@ enum { * - A sample status formatter that matches the "short" format */ -void show_branch(Repository const & repo, int format) +bool starts_with(StringView str, StringView prefix) +{ + return str.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), str.begin()); +} + +void show_branch(Repository const & repo, Format format) { - const char * branch = nullptr; + Optional branch; try - { - Reference head = repo.head(); - branch = head.name(); - if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) - branch += strlen("refs/heads/"); + { + branch = repo.head().name(); + static const StringView refs_heads = "refs/heads/"; + if (starts_with(*branch, refs_heads)) + branch->remove_prefix(refs_heads.length()); + } + catch (non_existing_branch_error const &) + { + } + catch (missing_head_error const &) + { } - catch (non_existing_branch_error const &) {} - catch (missing_head_error const &) {} catch (std::exception const & e) { throw e; } - if (format == FORMAT_LONG) - printf("# On branch %s\n", - branch ? branch : "Not currently on any branch."); - else - printf("## %s\n", branch ? branch : "HEAD (no branch)"); + if (format == Format::LONG) + std::cout << "# On branch " << branch.value_or("Not currently on any branch."); + else + std::cout << "## " << branch.value_or("HEAD (no branch)"); + std::cout << "\n"; } void print_long(Status const & status) { - size_t maxi = status.entrycount(); - int header = 0, changes_in_index = 0; - int changed_in_workdir = 0, rm_in_workdir = 0; - const char *old_path, *new_path; - - /* print index changes */ - - for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_CURRENT) - continue; - - if (s->status & GIT_STATUS_WT_DELETED) - rm_in_workdir = 1; - - const char *istatus = nullptr; - - if (s->status & GIT_STATUS_INDEX_NEW) - istatus = "new file: "; - if (s->status & GIT_STATUS_INDEX_MODIFIED) - istatus = "modified: "; - if (s->status & GIT_STATUS_INDEX_DELETED) - istatus = "deleted: "; - if (s->status & GIT_STATUS_INDEX_RENAMED) - istatus = "renamed: "; - if (s->status & GIT_STATUS_INDEX_TYPECHANGE) - istatus = "typechange:"; + size_t maxi = status.entrycount(); + int rm_in_workdir = 0; + const char *old_path, *new_path; + + /* print index changes */ + + bool changes_in_index = false; + + for (size_t i = 0; i < maxi; ++i) + { + auto const & s = status[i]; + + if (s.status == GIT_STATUS_CURRENT) + continue; + + if (s.status & GIT_STATUS_WT_DELETED) + rm_in_workdir = 1; - if (istatus == NULL) - continue; - - if (!header) { - printf("# Changes to be committed:\n"); - printf("# (use \"git reset HEAD ...\" to unstage)\n"); - printf("#\n"); - header = 1; - } + const char * istatus = nullptr; - old_path = s->head_to_index->old_file.path; - new_path = s->head_to_index->new_file.path; - - if (old_path && new_path && strcmp(old_path, new_path)) - printf("#\t%s %s -> %s\n", istatus, old_path, new_path); - else - printf("#\t%s %s\n", istatus, old_path ? old_path : new_path); - } - - if (header) { - changes_in_index = 1; - printf("#\n"); - } - header = 0; - - /* print workdir changes to tracked files */ - - for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_CURRENT || !s->index_to_workdir) - continue; - - const char *wstatus = nullptr; - - if (s->status & GIT_STATUS_WT_MODIFIED) - wstatus = "modified: "; - if (s->status & GIT_STATUS_WT_DELETED) - wstatus = "deleted: "; - if (s->status & GIT_STATUS_WT_RENAMED) - wstatus = "renamed: "; - if (s->status & GIT_STATUS_WT_TYPECHANGE) - wstatus = "typechange:"; - - if (wstatus == NULL) - continue; - - if (!header) { - printf("# Changes not staged for commit:\n"); - printf("# (use \"git add%s ...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : ""); - printf("# (use \"git checkout -- ...\" to discard changes in working directory)\n"); - printf("#\n"); - header = 1; - } - - old_path = s->index_to_workdir->old_file.path; - new_path = s->index_to_workdir->new_file.path; - - if (old_path && new_path && strcmp(old_path, new_path)) - printf("#\t%s %s -> %s\n", wstatus, old_path, new_path); - else - printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path); - } - - if (header) { - changed_in_workdir = 1; - printf("#\n"); - } - header = 0; - - /* print untracked files */ - - header = 0; + if (s.status & GIT_STATUS_INDEX_NEW) + istatus = "new file: "; + if (s.status & GIT_STATUS_INDEX_MODIFIED) + istatus = "modified: "; + if (s.status & GIT_STATUS_INDEX_DELETED) + istatus = "deleted: "; + if (s.status & GIT_STATUS_INDEX_RENAMED) + istatus = "renamed: "; + if (s.status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = "typechange:"; - for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_WT_NEW) { - - if (!header) { - printf("# Untracked files:\n"); - printf("# (use \"git add ...\" to include in what will be committed)\n"); - printf("#\n"); - header = 1; - } - - printf("#\t%s\n", s->index_to_workdir->old_file.path); - } - } - - header = 0; - - /* print ignored files */ - - for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_IGNORED) { + if (!istatus) + continue; - if (!header) { - printf("# Ignored files:\n"); - printf("# (use \"git add -f ...\" to include in what will be committed)\n"); - printf("#\n"); - header = 1; - } + if (!changes_in_index) + { + std::cout << "# Changes to be committed:\n" + << "# (use \"git reset HEAD ...\" to unstage)\n" + << "#\n"; + changes_in_index = true; + } + + std::cout << "#\t" << istatus << " "; + + old_path = s.head_to_index->old_file.path; + new_path = s.head_to_index->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + std::cout << old_path << " -> " << new_path; + else if (old_path) + std::cout << old_path; + else + std::cout << new_path; + + std::cout << "\n"; + } + + if (changes_in_index) + std::cout << "#\n"; + + bool changed_in_workdir = false; + + /* print workdir changes to tracked files */ + + for (size_t i = 0; i < maxi; ++i) + { + auto const & s = status[i]; - printf("#\t%s\n", s->index_to_workdir->old_file.path); - } - } + if (s.status == GIT_STATUS_CURRENT || !s.index_to_workdir) + continue; - if (!changes_in_index && changed_in_workdir) - printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); + const char * wstatus = nullptr; + + if (s.status & GIT_STATUS_WT_MODIFIED) + wstatus = "modified: "; + if (s.status & GIT_STATUS_WT_DELETED) + wstatus = "deleted: "; + if (s.status & GIT_STATUS_WT_RENAMED) + wstatus = "renamed: "; + if (s.status & GIT_STATUS_WT_TYPECHANGE) + wstatus = "typechange:"; + + if (!wstatus) + continue; + + if (!changed_in_workdir) + { + std::cout << "# Changes not staged for commit:\n" + << "# (use \"git add" << (rm_in_workdir ? "/rm" : "") << "...\" to update what will be committed)\n" + << "# (use \"git checkout -- ...\" to discard changes in working directory)\n" + << "#\n"; + changed_in_workdir = true; + } + + std::cout << "#\t" << wstatus << " "; + + old_path = s.index_to_workdir->old_file.path; + new_path = s.index_to_workdir->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + std::cout << old_path << " -> " << new_path; + else if (old_path) + std::cout << old_path; + else + std::cout << new_path; + std::cout << "\n"; + } + + if (changed_in_workdir) + std::cout << "#\n"; + + /* print untracked files */ + bool were_untracked_files = false; + for (size_t i = 0; i < maxi; ++i) + { + auto const & s = status[i]; + + if (s.status == GIT_STATUS_WT_NEW) + { + if (!were_untracked_files) + { + std::cout << "# Untracked files:\n" + << "# (use \"git add ...\" to include in what will be committed)\n" + << "#\n"; + were_untracked_files = true; + } + + std::cout << "#\t" << s.index_to_workdir->old_file.path << "\n"; + } + } + + /* print ignored files */ + bool were_ignored_files = false; + + for (size_t i = 0; i < maxi; ++i) + { + auto const & s = status[i]; + + if (s.status == GIT_STATUS_IGNORED) + { + if (!were_ignored_files) + { + std::cout << "# Ignored files:\n" + << "# (use \"git add -f ...\" to include in what will be committed)\n" + << "#\n"; + were_ignored_files = true; + } + + std::cout << "#\t" << s.index_to_workdir->old_file.path << "\n"; + } + } + + if (!changes_in_index && changed_in_workdir) + std::cout << "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"; } void print_short(Repository const & repo, Status const & status) { - size_t maxi = status.entrycount(); - char istatus, wstatus; - const char *extra, *a, *b, *c; + size_t maxi = status.entrycount(); - for (size_t i = 0; i < maxi; ++i) + for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_CURRENT) - continue; - - a = b = c = NULL; - istatus = wstatus = ' '; - extra = ""; - - if (s->status & GIT_STATUS_INDEX_NEW) - istatus = 'A'; - if (s->status & GIT_STATUS_INDEX_MODIFIED) - istatus = 'M'; - if (s->status & GIT_STATUS_INDEX_DELETED) - istatus = 'D'; - if (s->status & GIT_STATUS_INDEX_RENAMED) - istatus = 'R'; - if (s->status & GIT_STATUS_INDEX_TYPECHANGE) - istatus = 'T'; - - if (s->status & GIT_STATUS_WT_NEW) { - if (istatus == ' ') - istatus = '?'; - wstatus = '?'; - } - if (s->status & GIT_STATUS_WT_MODIFIED) - wstatus = 'M'; - if (s->status & GIT_STATUS_WT_DELETED) - wstatus = 'D'; - if (s->status & GIT_STATUS_WT_RENAMED) - wstatus = 'R'; - if (s->status & GIT_STATUS_WT_TYPECHANGE) - wstatus = 'T'; - - if (s->status & GIT_STATUS_IGNORED) { - istatus = '!'; - wstatus = '!'; - } - - if (istatus == '?' && wstatus == '?') - continue; - - if (s->index_to_workdir && - s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) - { - git_submodule *sm = NULL; - unsigned int smstatus = 0; - - if (!repo.submodule_lookup(sm, s->index_to_workdir->new_file.path) && - !git_submodule_status(&smstatus, sm)) - { - if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) - extra = " (new commits)"; - else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) - extra = " (modified content)"; - else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) - extra = " (modified content)"; - else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED) - extra = " (untracked content)"; - } - } - - if (s->head_to_index) { - a = s->head_to_index->old_file.path; - b = s->head_to_index->new_file.path; - } - if (s->index_to_workdir) { - if (!a) - a = s->index_to_workdir->old_file.path; - if (!b) - b = s->index_to_workdir->old_file.path; - c = s->index_to_workdir->new_file.path; - } - - if (istatus == 'R') { - if (wstatus == 'R') - printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra); - else - printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra); - } else { - if (wstatus == 'R') - printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra); - else - printf("%c%c %s%s\n", istatus, wstatus, a, extra); - } - } - - for (size_t i = 0; i < maxi; ++i) { - auto s = status[i]; - - if (s->status == GIT_STATUS_WT_NEW) - printf("?? %s\n", s->index_to_workdir->old_file.path); - } + auto const & s = status[i]; + + if (s.status == GIT_STATUS_CURRENT) + continue; + + const char * a = nullptr; + const char * b = nullptr; + const char * c = nullptr; + char istatus = ' '; + char wstatus = ' '; + const char * extra = ""; + + if (s.status & GIT_STATUS_INDEX_NEW) + istatus = 'A'; + if (s.status & GIT_STATUS_INDEX_MODIFIED) + istatus = 'M'; + if (s.status & GIT_STATUS_INDEX_DELETED) + istatus = 'D'; + if (s.status & GIT_STATUS_INDEX_RENAMED) + istatus = 'R'; + if (s.status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = 'T'; + + if (s.status & GIT_STATUS_WT_NEW) + { + if (istatus == ' ') + istatus = '?'; + wstatus = '?'; + } + if (s.status & GIT_STATUS_WT_MODIFIED) + wstatus = 'M'; + if (s.status & GIT_STATUS_WT_DELETED) + wstatus = 'D'; + if (s.status & GIT_STATUS_WT_RENAMED) + wstatus = 'R'; + if (s.status & GIT_STATUS_WT_TYPECHANGE) + wstatus = 'T'; + + if (s.status & GIT_STATUS_IGNORED) + { + istatus = '!'; + wstatus = '!'; + } + + if (istatus == '?' && wstatus == '?') + continue; + + if (s.index_to_workdir && s.index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) + { + auto sm = repo.submodule_lookup(s.index_to_workdir->new_file.path); + auto smstatus = sm.get_status(); + + typedef Submodule::status status; + + if (contains(smstatus, status::wd_modified)) + extra = " (new commits)"; + else if (contains(smstatus, status::wd_index_modified)) + extra = " (modified content)"; + else if (contains(smstatus, status::wd_wd_modified)) + extra = " (modified content)"; + else if (contains(smstatus, status::wd_untracked)) + extra = " (untracked content)"; + } + + if (s.head_to_index) + { + a = s.head_to_index->old_file.path; + b = s.head_to_index->new_file.path; + } + if (s.index_to_workdir) + { + if (!a) + a = s.index_to_workdir->old_file.path; + if (!b) + b = s.index_to_workdir->old_file.path; + c = s.index_to_workdir->new_file.path; + } + + std::cout << istatus << wstatus << ' ' << a; + if (istatus == 'R') + std::cout << ' ' << b; + if (wstatus == 'R') + std::cout << ' ' << c; + std::cout << extra << "\n"; + } + + for (size_t i = 0; i < maxi; ++i) + { + auto const & s = status[i]; + + if (s.status == GIT_STATUS_WT_NEW) + std::cout << "?? " << s.index_to_workdir->old_file.path << "\n"; + } +} + } -int main(int argc, char *argv[]) +int main(int argc, char * argv[]) { - int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0; - git_status_options opt = GIT_STATUS_OPTIONS_INIT; - const char *repodir = "."; + auto_git_initializer; + + size_t npaths = 0; + Format format = Format::DEFAULT; + bool showbranch = false; + Status::Options opt; + const char * repodir = "."; const size_t MAX_PATHSPEC = 8; char * pathspec[MAX_PATHSPEC]; - opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + opt.include_untracked().renames_head_to_index(); - for (i = 1; i < argc; ++i) { - if (argv[i][0] != '-') { - if (npaths < MAX_PATHSPEC) + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] != '-') + { + if (npaths < MAX_PATHSPEC) { - pathspec[npaths++] = argv[i]; + pathspec[npaths++] = argv[i]; } - else + else { - std::cerr << "Example only supports a limited pathspec\n"; + std::cerr << "Example only supports a limited pathspec\n"; return 1; } - } - else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) - format = FORMAT_SHORT; - else if (!strcmp(argv[i], "--long")) - format = FORMAT_LONG; - else if (!strcmp(argv[i], "--porcelain")) - format = FORMAT_PORCELAIN; - else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) - showbranch = 1; - else if (!strcmp(argv[i], "-z")) { - zterm = 1; - if (format == FORMAT_DEFAULT) - format = FORMAT_PORCELAIN; - } - else if (!strcmp(argv[i], "--ignored")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; - else if (!strcmp(argv[i], "-uno") || - !strcmp(argv[i], "--untracked-files=no")) - opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-unormal") || - !strcmp(argv[i], "--untracked-files=normal")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-uall") || - !strcmp(argv[i], "--untracked-files=all")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - else if (!strcmp(argv[i], "--ignore-submodules=all")) - opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) - repodir = argv[i] + strlen("--git-dir="); - else + } + else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) + format = Format::SHORT; + else if (!strcmp(argv[i], "--long")) + format = Format::LONG; + else if (!strcmp(argv[i], "--porcelain")) + format = Format::PORCELAIN; + else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) + showbranch = 1; + else if (!strcmp(argv[i], "-z")) + { + if (format == Format::DEFAULT) + format = Format::PORCELAIN; + } + else if (!strcmp(argv[i], "--ignored")) + opt.include_ignored(); + else if (!strcmp(argv[i], "-uno") || + !strcmp(argv[i], "--untracked-files=no")) + opt.exclude_untracked(); + else if (!strcmp(argv[i], "-unormal") || + !strcmp(argv[i], "--untracked-files=normal")) + opt.include_untracked(); + else if (!strcmp(argv[i], "-uall") || + !strcmp(argv[i], "--untracked-files=all")) + opt.include_untracked().recurse_untracked_dirs(); + else if (!strcmp(argv[i], "--ignore-submodules=all")) + opt.exclude_submodules(); + else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) + repodir = argv[i] + strlen("--git-dir="); + else { std::cerr << "Unsupported option '" << argv[i] << "'\n"; return 1; } - } + } - if (format == FORMAT_DEFAULT) - format = FORMAT_LONG; - if (format == FORMAT_LONG) - showbranch = 1; - if (npaths > 0) { - opt.pathspec.strings = pathspec; - opt.pathspec.count = npaths; - } + if (format == Format::DEFAULT) + format = Format::LONG; + if (format == Format::LONG) + showbranch = true; + if (npaths > 0) + opt.set_pathspec(pathspec, npaths); - Repository repo(repodir); + Repository repo(repodir); - if (repo.is_bare()) + if (repo.is_bare()) { - std::cerr << "Cannot report status on bare repository\n"; + std::cerr << "Cannot report status on bare repository\n"; return 1; } - /* + /* * Run status on the repository * * Because we want to simluate a full "git status" run and want to @@ -406,16 +443,15 @@ int main(int argc, char *argv[]) * enumerate files that are modified) then you probably don't need the * extended API. */ - Status status = repo.status(opt); + Status status = repo.status(opt); - if (showbranch) - show_branch(repo, format); + if (showbranch) + show_branch(repo, format); - if (format == FORMAT_LONG) - print_long(status); - else - print_short(repo, status); + if (format == Format::LONG) + print_long(status); + else + print_short(repo, status); - return 0; + return 0; } - diff --git a/include/git2cpp/annotated_commit.h b/include/git2cpp/annotated_commit.h new file mode 100644 index 0000000..b266861 --- /dev/null +++ b/include/git2cpp/annotated_commit.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +struct git_annotated_commit; +struct git_oid; + +namespace git +{ + struct AnnotatedCommit + { + AnnotatedCommit(git_annotated_commit * commit) + : commit_(commit) + {} + + git_oid const & commit_id() const; + char const * commit_ref() const; + + private: + friend struct Repository; + git_annotated_commit * ptr() const { return commit_.get(); } + + private: + struct Destroy { void operator() (git_annotated_commit*) const; }; + std::unique_ptr commit_; + }; +} 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/blob.h b/include/git2cpp/blob.h index 374e5c4..87efcf9 100644 --- a/include/git2cpp/blob.h +++ b/include/git2cpp/blob.h @@ -1,30 +1,27 @@ #pragma once -extern "C" -{ -#include -} +#include +#include struct git_blob; -struct git_repository; -struct git_oid; namespace git { struct Blob { - explicit Blob(git_oid const * oid, git_repository * repo); - ~Blob(); + explicit Blob(git_blob *); - git_off_t size() const; - const void * content() const; + std::size_t size() const; + const void * content() const; - Blob& operator = (Blob const &) = delete; - Blob (Blob const &) = delete; + private: + friend struct Repository; - Blob(Blob &&); + git_blob const * ptr() const { return blob_.get(); } private: - git_blob * blob_; + struct Destroy { void operator() (git_blob *) const; }; + + std::unique_ptr blob_; }; } diff --git a/include/git2cpp/buffer.h b/include/git2cpp/buffer.h new file mode 100644 index 0000000..f130e18 --- /dev/null +++ b/include/git2cpp/buffer.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace git +{ + struct Buffer + { + Buffer(Buffer const &) = delete; + Buffer & operator=(Buffer const &) = delete; + + Buffer(Buffer &&) noexcept; + + explicit Buffer(git_buf buf) + : buf_(buf) + {} + + ~Buffer(); + + explicit operator bool() const { return ptr() != nullptr; } + + const char * ptr() const { return buf_.ptr; } + + private: + git_buf buf_; + }; +} diff --git a/include/git2cpp/commit.h b/include/git2cpp/commit.h index 47e9375..01700ad 100644 --- a/include/git2cpp/commit.h +++ b/include/git2cpp/commit.h @@ -1,6 +1,10 @@ #pragma once -#include "tree.h" +#include "repo_fwd.h" + +#include + +#include struct git_commit; struct git_oid; @@ -8,47 +12,43 @@ struct git_repository; namespace git { + struct Tree; + struct Commit { - git_commit const * ptr() const { return commit_; } - size_t parents_num() const; Commit parent(size_t i) const; - git_oid const * parent_id(size_t i) const; + git_oid const & parent_id(size_t i) const; Tree tree() const; - git_repository * owner() const; + explicit operator bool() const { return commit_ != nullptr; } - explicit operator bool () const { return commit_; } + git_oid const & id() const; + git_oid const & tree_id() const; - git_oid const * id() const; + git_signature const * author() const; + git_signature const * commiter() const; - git_signature const * author() const; - git_signature const * commiter() const; + const char * message() const; + const char * summary() const; + git_time_t time() const; - const char * message() const; - git_time_t time() const; + git_oid merge_base(size_t p1, size_t p2) const; - Commit(git_oid const * oid, git_repository * repo); + Repository const & repo() const { return *repo_; } - explicit Commit(git_commit * commit = nullptr) - : commit_(commit) - {} + Commit(git_commit *, Repository const &); + Commit() = default; - Commit(Commit && other) - : commit_(other.commit_) - { - other.commit_ = nullptr; - } - - Commit (Commit const &) = delete; - Commit& operator = (Commit const &) = delete; + private: + friend struct Repository; - ~Commit(); + git_commit const * ptr() const { return commit_.get(); } private: - git_commit * commit_; + struct Destroy { void operator() (git_commit*) const; }; + std::unique_ptr commit_; + Repository const * repo_ = nullptr; }; } - diff --git a/include/git2cpp/config.h b/include/git2cpp/config.h index 59b16bd..51cdb9b 100644 --- a/include/git2cpp/config.h +++ b/include/git2cpp/config.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include struct git_config; @@ -9,7 +11,6 @@ namespace git struct Config { explicit Config(std::string const & filename); - ~Config(); struct no_such_key_error : std::exception { @@ -17,7 +18,7 @@ namespace git : key_(std::move(key)) {} - virtual const char * what() const noexcept override + const char * what() const noexcept override { return "no such key in config"; } @@ -28,14 +29,12 @@ namespace git std::string key_; }; - std::string operator[] (const char * key) const; + std::string operator[](const char * key) const; int get_int(const char * key) const; - Config (Config const &) = delete; - Config& operator = (Config const &) = delete; - private: - git_config * cfg_; + struct Destroy { void operator() (git_config*) const; }; + std::unique_ptr cfg_; }; } diff --git a/include/git2cpp/diff.h b/include/git2cpp/diff.h new file mode 100644 index 0000000..045c80c --- /dev/null +++ b/include/git2cpp/diff.h @@ -0,0 +1,76 @@ +#pragma once + +#include "buffer.h" +#include "tagged_mask.h" + +#include + +#include +#include + +namespace git +{ + namespace diff + { + namespace stats + { + namespace format + { + typedef tagged_mask_t type; + + extern const type none; + extern const type full; + extern const type _short; + extern const type number; + extern const type include_summary; + } + } + + enum class format + { + patch, + patch_header, + raw, + name_only, + name_status + }; + } + + struct Diff + { + struct Stats + { + Buffer to_buf(diff::stats::format::type, size_t width) const; + + private: + friend struct Diff; + + explicit Stats(git_diff_stats * stats) + : stats_(stats) + {} + + private: + struct Destroy { void operator() (git_diff_stats*) const; }; + std::unique_ptr stats_; + }; + + size_t deltas_num() const; + + void find_similar(git_diff_find_options &); + + Stats stats() const; + + typedef std::function + print_callback_t; + + void print(diff::format, print_callback_t print_callback) const; + + explicit Diff(git_diff * diff) + : diff_(diff) + {} + + private: + struct Destroy { void operator() (git_diff *) const; }; + std::unique_ptr diff_; + }; +} diff --git a/include/git2cpp/diff_list.h b/include/git2cpp/diff_list.h deleted file mode 100644 index c4ca783..0000000 --- a/include/git2cpp/diff_list.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -extern "C" -{ -#include -} - -namespace git -{ - struct DiffList - { - size_t deltas_num() const; - - typedef std::function data_callback_t; - - void print_patch (data_callback_t cb) const; - void print_compact (data_callback_t cb) const; - void print_raw (data_callback_t cb) const; - - void find_similar(git_diff_find_options & findopts) - { - git_diff_find_similar(diff_list_, &findopts); - } - - DiffList& merge(DiffList const & other); - - explicit DiffList(git_diff_list * diff_list) - : diff_list_(diff_list) - {} - - ~DiffList() { git_diff_list_free(diff_list_); } - - DiffList (DiffList const &) = delete; - DiffList& operator = (DiffList const &) = delete; - - DiffList(DiffList && other) - : diff_list_(other.diff_list_) - { - other.diff_list_ = nullptr; - } - - private: - git_diff_list * diff_list_; - }; - - struct Tree; - - DiffList diff ( git_repository * repo - , Tree & a, Tree & b - , git_diff_options const & opts - ); - DiffList diff_to_index ( git_repository * repo - , Tree & - , git_diff_options const & opts - ); - - DiffList diff_index_to_workdir(git_repository * repo, git_diff_options const & opts); -} - diff --git a/include/git2cpp/error.h b/include/git2cpp/error.h index 85ae7c6..e62c692 100644 --- a/include/git2cpp/error.h +++ b/include/git2cpp/error.h @@ -1,326 +1,278 @@ #pragma once #include -#include #include "id_to_str.h" +#include "internal/format.h" namespace git { - struct repository_open_error : std::exception + + struct error_t : std::runtime_error { - virtual const char * what() const noexcept override - { - return "Could not open repository"; - } + explicit error_t(std::string const & message) + : std::runtime_error(message) + {} }; - struct repository_init_error : std::exception + struct repository_open_error : error_t { - explicit repository_init_error(std::string const & dir) - : message_("Could not initialize repository on path " + dir) + repository_open_error(std::string const & dir) + : error_t("Could not open repository on path " + dir) {} - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; }; - struct index_open_error : std::exception + struct repository_init_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not open repository index"; - } + explicit repository_init_error(std::string const & dir) + : error_t("Could not initialize repository on path " + dir) + {} }; - struct odb_open_error : std::exception + struct index_open_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not open ODB"; - } + index_open_error() + : error_t("Could not open repository index") + {} }; - struct commit_lookup_error : std::exception + struct odb_open_error : error_t { - explicit commit_lookup_error(git_oid const * id) - : message_("Could not lookup commit " + id_to_str(id)) + odb_open_error() + : error_t("Could not open ODB") {} - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; }; - struct tree_lookup_error : std::exception + struct commit_lookup_error : error_t { - explicit tree_lookup_error(git_oid const * id) - : message_("Could not lookup tree " + id_to_str(id)) + explicit commit_lookup_error(git_oid const & id) + : error_t("Could not lookup commit " + id_to_str(id)) {} + }; - virtual const char * what() const noexcept override - { - return message_.c_str(); - } + struct tree_lookup_error : error_t + { + explicit tree_lookup_error(git_oid const & id) + : error_t("Could not lookup tree " + id_to_str(id)) + {} + }; - private: - std::string message_; + struct tag_lookup_error : error_t + { + explicit tag_lookup_error(git_oid const & id) + : error_t("Could not lookup tag " + id_to_str(id)) + {} }; - struct tag_lookup_error : std::exception + struct blob_lookup_error : error_t { - explicit tag_lookup_error(git_oid const * id) - : message_("Could not lookup tag " + id_to_str(id)) + explicit blob_lookup_error(git_oid const & id) + : error_t("Could not lookup blob " + id_to_str(id)) {} + }; - virtual const char * what() const noexcept override - { - return message_.c_str(); - } + struct submodule_lookup_error : error_t + { + explicit submodule_lookup_error(std::string const & name) + : error_t("Could not lookup submodule " + name) + {} + }; - private: - std::string message_; + struct remote_lookup_error : error_t + { + explicit remote_lookup_error(std::string const & name) + : error_t("Could not lookup remote " + name) + {} }; - struct commit_parent_error : std::exception + struct remote_delete_error : error_t { - explicit commit_parent_error(git_oid const * id) - : message_("Could not get parent for commit " + id_to_str(id)) + explicit remote_delete_error(std::string const & name) + : error_t("Could not delete remote " + name) {} + }; - virtual const char * what() const noexcept override - { - return message_.c_str(); - } + struct remote_create_error : error_t + { + explicit remote_create_error(const char * name, const char * url) + : error_t(internal::format("Could not create remote '%s', url='%s'", name, url)) + {} + }; - private: - std::string message_; + struct remote_set_url_error : error_t + { + explicit remote_set_url_error(const char * name, const char * url) + : error_t(internal::format("Could not set url '%s' for remote '%s'", url, name)) + {} }; - struct commit_tree_error : std::exception + struct remote_set_pushurl_error : error_t { - explicit commit_tree_error(git_oid const * id) - : message_("Could not get tree for commit " + id_to_str(id)) + explicit remote_set_pushurl_error(const char * name, const char * url) + : error_t(internal::format("Could not set pushurl '%s' for remote '%s'", url, name)) {} + }; - virtual const char * what() const noexcept override - { - return message_.c_str(); - } + struct commit_parent_error : error_t + { + explicit commit_parent_error(git_oid const & id) + : error_t("Could not get parent for commit " + id_to_str(id)) + {} + }; - private: - std::string message_; + struct commit_tree_error : error_t + { + explicit commit_tree_error(git_oid const & id) + : error_t("Could not get tree for commit " + id_to_str(id)) + {} }; - struct revparse_error : std::exception + struct revparse_error : error_t { explicit revparse_error(const char * spec) - { - std::ostringstream ss; - ss << "Could not resolve " << spec; - message_ = ss.str(); - } - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; + : error_t(internal::format("Could not resolve %s", spec)) + {} }; - struct odb_read_error : std::exception + struct odb_read_error : error_t { - explicit odb_read_error(git_oid const * id) - : message_("Could not find obj " + id_to_str(id)) + explicit odb_read_error(git_oid const & id) + : error_t("Could not find obj " + id_to_str(id)) {} - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; }; - struct odb_write_error : std::exception + struct odb_write_error : error_t { - virtual const char * what() const noexcept override - { - return "odb write error"; - } + odb_write_error() + : error_t("odb write error") + {} }; - struct file_not_found_error : std::exception + struct file_not_found_error : error_t { explicit file_not_found_error(const char * filepath) - { - std::ostringstream ss; - ss << "file path \"" << filepath << "\" not found"; - message_ = ss.str(); - } - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; + : error_t(internal::format("file path \"%s\" not found", filepath)) + {} }; - struct ambiguous_path_error : std::exception + struct ambiguous_path_error : error_t { explicit ambiguous_path_error(const char * filepath) - { - std::ostringstream ss; - ss << "file path \"" << filepath << "\" is ambiguous"; - message_ = ss.str(); - } - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; + : error_t(internal::format("file path \"%s\" is ambiguous", filepath)) + {} }; - struct unknown_file_status_error : std::exception + struct unknown_file_status_error : error_t { explicit unknown_file_status_error(const char * filepath) - { - std::ostringstream ss; - ss << "unknown error during getting status for file \"" << filepath; - message_ = ss.str(); - } - - virtual const char * what() const noexcept override - { - return message_.c_str(); - } - - private: - std::string message_; + : error_t(internal::format("unknown error during getting status for file \"%s\"", filepath)) + {} }; - struct pathspec_new_error : std::exception + struct pathspec_new_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not build pathspec"; - } + pathspec_new_error() + : error_t("Could not build pathspec") + {} }; - struct revwalk_new_error : std::exception + struct revwalk_new_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not create revision walker"; - } + revwalk_new_error() + : error_t("Could not create revision walker") + {} }; - struct invalid_head_error : std::exception + struct invalid_head_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not find repository HEAD"; - } + invalid_head_error() + : error_t("Could not find repository HEAD") + {} }; - struct non_commit_object_error : std::exception + struct non_commit_object_error : error_t { - explicit non_commit_object_error(git_oid const * id) - { - std::ostringstream ss; - ss << "object " << id_to_str(id) << " is not a commit"; - message_ = ss.str(); - } + explicit non_commit_object_error(git_oid const & id) + : error_t("object " + id_to_str(id) + " is not a commit") + {} + }; - virtual const char * what() const noexcept override - { - return message_.c_str(); - } + struct unknown_get_current_branch_error : error_t + { + unknown_get_current_branch_error() + : error_t("failed to get current branch") + {} + }; - private: - std::string message_; + struct get_status_error : error_t + { + get_status_error() + : error_t("Could not get status") + {} }; - - struct unknown_get_current_branch_error : std::exception + + struct signature_create_error : error_t { - virtual const char * what() const noexcept override - { - return "failed to get current branch"; - } + signature_create_error() + : error_t("Unable to create a commit signature." + " Perhaps 'user.name' and 'user.email' are not set") + {} }; - struct get_status_error : std::exception + struct index_write_tree_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not get status"; - } + index_write_tree_error() + : error_t("Unable to write tree from index") + {} }; - struct signature_create_error : std::exception + struct index_write_error : error_t { - virtual const char * what() const noexcept override - { - return "Unable to create a commit signature." - " Perhaps 'user.name' and 'user.email' are not set"; - } + index_write_error() + : error_t("Unable to write index") + {} }; - struct index_write_tree_error : std::exception + struct commit_create_error : error_t { - virtual const char * what() const noexcept override - { - return "Unable to write tree from index"; - } + commit_create_error() + : error_t("Could not create commit") + {} }; - struct index_write_error : std::exception + struct merge_base_error : error_t { - virtual const char * what() const noexcept override - { - return "Unable to write index"; - } + merge_base_error(git_oid const & c1, git_oid const & c2) + : error_t("Could not find merge base for commits " + id_to_str(c1, 8) + " and " + id_to_str(c2, 8)) + {} }; - struct commit_create_error : std::exception + struct config_open_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not create commit"; - } + config_open_error() + : error_t("Could not open config") + {} }; - struct merge_base_error : std::exception + struct branch_create_error : error_t { - virtual const char * what() const noexcept override + enum reason_t { - return "Could not find merge base"; - } + already_exists, + invalid_spec, + unknown + } reason; + + branch_create_error(reason_t r) + : error_t("Could not create branch") + , reason(r) + {} }; - struct config_open_error : std::exception + struct blame_file_error : error_t { - virtual const char * what() const noexcept override - { - return "Could not open config"; - } + explicit blame_file_error(std::string const & path) + : error_t("Could not blame file " + path) + {} }; } - diff --git a/include/git2cpp/id_to_str.h b/include/git2cpp/id_to_str.h index cde6d25..0866bfd 100644 --- a/include/git2cpp/id_to_str.h +++ b/include/git2cpp/id_to_str.h @@ -2,16 +2,13 @@ #include -extern "C" -{ #include -} namespace git { - std::string id_to_str(git_oid const * oid); + std::string id_to_str(git_oid const & oid); - std::string id_to_str(git_oid const * oid, size_t digits_num); + std::string id_to_str(git_oid const & oid, size_t digits_num); git_oid str_to_id(const char * str); } diff --git a/include/git2cpp/index.h b/include/git2cpp/index.h index 361ebe8..2d899ad 100644 --- a/include/git2cpp/index.h +++ b/include/git2cpp/index.h @@ -1,6 +1,10 @@ #pragma once +#include + #include +#include +#include struct git_index; struct git_repository; @@ -14,26 +18,29 @@ namespace git explicit Index(git_repository * repo); explicit Index(const char * dir); - ~Index(); - size_t entrycount() const; - git_index_entry const * operator[] (size_t i) const; + git_index_entry const * operator[](size_t i) const; - typedef std::function matched_path_callback_t; - - void update_all (git_strarray const & pathspec, matched_path_callback_t cb); - void add_all (git_strarray const & pathspec, matched_path_callback_t cb, unsigned int flags = 0); + git_index_entry const * get_by_path(const char *path, int stage) const; - git_oid write_tree() const; + typedef std::function matched_path_callback_t; - void write() const; + void update_all(git_strarray const & pathspec, matched_path_callback_t cb); + void add_all(git_strarray const & pathspec, matched_path_callback_t cb, unsigned int flags = 0); + void clear(); - Index (Index const &) = delete; - Index& operator = (Index const &) = delete; + void add_path(const char *); + void add_path(std::string const &); - Index(Index && other); + void remove_path(const char *); + void remove_path(std::string const &); + + git_oid write_tree() const; + + void write() const; private: - git_index * index_; + struct Destroy { void operator() (git_index*) const; }; + std::unique_ptr index_; }; } diff --git a/include/git2cpp/initializer.h b/include/git2cpp/initializer.h new file mode 100644 index 0000000..86a9f2f --- /dev/null +++ b/include/git2cpp/initializer.h @@ -0,0 +1,17 @@ +#pragma once + +namespace git +{ + struct Initializer + { + Initializer(); + ~Initializer(); + + Initializer(Initializer const &) = delete; + Initializer & operator=(Initializer const &) = delete; + }; +} + +#define auto_git_initializer \ + ::git::Initializer git_initializer; \ + (void)git_initializer diff --git a/include/git2cpp/internal/format.h b/include/git2cpp/internal/format.h new file mode 100644 index 0000000..a680dc8 --- /dev/null +++ b/include/git2cpp/internal/format.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#ifdef USE_BOOST +#include +#endif + +namespace git { +namespace internal +{ + +#ifndef USE_BOOST + template + std::string format(const char * fmt, Args&& ... args) + { + auto size = std::snprintf(nullptr, 0, fmt, args...); + std::string result(size, 0); + std::snprintf(&result[0], size + 1, fmt, args...); + return result; + } +#else + inline boost::format & format_apply(boost::format & fmt) + { + return fmt; + } + + template + boost::format & format_apply(boost::format & fmt, T && data, Args&& ... args) + { + return format_apply(fmt % data, std::forward(args)...); + } + + template + std::string format(const char * fmt_str, Args&& ... args) + { + boost::format fmt(fmt_str); + return str(format_apply(fmt, std::forward(args)...)); + } +#endif + +}} diff --git a/include/git2cpp/internal/optional.h b/include/git2cpp/internal/optional.h new file mode 100644 index 0000000..5413a0f --- /dev/null +++ b/include/git2cpp/internal/optional.h @@ -0,0 +1,214 @@ +#pragma once + +#ifdef USE_BOOST + #include + #include + #define GIT_USE_BOOST_OPTIONAL + +#elif defined(__has_include) && __has_include() + #include + #define GIT_USE_STD_OPTIONAL + +#elif defined(__has_include) && __has_include() + #include + #define GIT_USE_STD_EXPERIMENTAL_OPTIONAL + +#else + #include + #include +#endif + +namespace git { +namespace internal +{ +#if defined(GIT_USE_BOOST_OPTIONAL) + #undef GIT_USE_BOOST_OPTIONAL + using boost::optional; + using boost::none_t; + using boost::none; + + template + optional & emplace(optional & opt, Args&&... args) + { + opt = boost::in_place(std::forward(args)...); + return opt; + } + + template + bool has_value(optional const & opt) + { + return opt.is_initialized(); + } + +#elif defined(GIT_USE_STD_OPTIONAL) + #undef GIT_USE_STD_OPTIONAL + using std::optional; + using none_t = std::nullopt_t; + const none_t none = std::nullopt; + + template + optional & emplace(optional & opt, Args&&... args) + { + opt.emplace(std::forward(args)...); + return opt; + } + + template + bool has_value(optional const & opt) + { + return opt.has_value(); + } + +#elif defined(GIT_USE_STD_EXPERIMENTAL_OPTIONAL) + #undef GIT_USE_STD_EXPERIMENTAL_OPTIONAL + using std::experimental::optional; + using none_t = std::experimental::nullopt_t; + const none_t none = std::experimental::nullopt; + + template + optional & emplace(optional & opt, Args&&... args) + { + opt.emplace(std::forward(args)...); + return opt; + } + + template + bool has_value(optional const & opt) + { + return static_cast(opt); + } + +#else + typedef std::nullptr_t none_t; + const none_t none = nullptr; + + //Simple (and incomplete) optional implementations + template + class optional + { + private: //type alias + typedef typename std::aligned_storage::type StorageT; + + public: //type traits + enum non_movable_t{}; + enum non_copyable_t{}; + + static const bool movable = std::is_move_constructible::value && std::is_move_assignable::value; + static const bool copyable = std::is_copy_constructible::value && std::is_copy_assignable::value; + typedef typename std::conditional::type MovableT; + typedef typename std::conditional::type CopyableT; + + public: //initializing and attribution + constexpr optional(none_t t = none) + : initialized(false) + {} + + optional(T const & v) { assign(v);} + optional(T && v) { assign(std::move(v));} + + optional(optional const & other) { assign(other);} + optional(optional && other) { assign(std::move(other));} + + optional & operator=(none_t none) { reset(); return *this;} + optional & operator=(T && v) { return assign(std::move(v));} + optional & operator=(const T & v) { return assign(v);} + + optional & operator=(optional const & other){ return assign(other);} + optional & operator=(optional && other){ return assign(std::move(other));} + + ~optional() { reset(); } + + public: //getting value + + T const & value() const { return reinterpret_cast(storage); } + T & value() { return reinterpret_cast(storage); } + + T const & operator* () const { return value(); } + T & operator * () { return value(); } + + T const * operator -> () const { return &value(); } + T * operator -> () { return &value(); } + + public: //querying state + + explicit operator bool() const { return initialized; } + + public: //change state + void reset() + { + if (initialized) + { + value().~T(); + initialized = false; + } + } + + template + void emplace(Args&&... args){ + if (initialized) + reset(); + init(std::forward(args)...); + } + + private: //assign + optional & assign(MovableT && value) { + return set_from_value(std::move(value)); + } + optional & assign(CopyableT const & value) { + return set_from_value(value); + } + optional & assign(optional const & other) { + return set_or_reset(other.value(), other.initialized); + } + optional & assign(optional && other) { + return set_or_reset(std::move(other.value()), other.initialized); + } + + protected: + template + optional & set_or_reset(U&& u, bool isValid) + { + if (isValid) + set_from_value(std::forward(u)); + else + reset(); + + return *this; + } + + template + optional & set_from_value(U&& u) + { + if (initialized) + reinterpret_cast(storage) = std::forward(u); + else + init(std::forward(u)); + + return *this; + } + + template + void init(Args&& ... args){ + new (&storage) T(std::forward(args)...); + initialized = true; + } + + protected: + StorageT storage; + bool initialized; + }; + + template + optional & emplace(optional & opt, Args&&... args){ + opt.emplace(std::forward(args)...); + return opt; + } + + template + bool has_value(optional const & opt) + { + return static_cast(opt); + } +#endif + +}}//namespace git diff --git a/include/git2cpp/object.h b/include/git2cpp/object.h index f928733..6e00c57 100644 --- a/include/git2cpp/object.h +++ b/include/git2cpp/object.h @@ -1,42 +1,46 @@ #pragma once -extern "C" -{ #include -} - -#include "commit.h" +#include struct git_object; struct git_oid; +struct git_tree; +struct git_tag; namespace git { - struct Object - { - explicit Object(git_object * obj); - ~Object(); + struct Repository; - explicit operator bool() const { return obj_; } + struct Blob; + struct Commit; + struct Tree; + struct Tag; - git_otype type() const; - git_oid const * id() const; + struct Object + { + Object() = default; + Object(git_object * obj, Repository 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; + explicit operator bool() const { return obj_.operator bool(); } - Commit to_commit(); - Tree to_tree(); + git_object_t type() const; + git_oid const & id() const; - Object (Object const &) = delete; - Object& operator = (Object const &) = delete; + Commit to_commit() /*&&*/; + Tree to_tree() /*&&*/; + Blob to_blob() /*&&*/; + Tag to_tag() /*&&*/; - Object(Object && other); + private: + git_blob * as_blob(); + git_commit * as_commit(); + git_tree * as_tree(); + git_tag * as_tag(); private: - git_object * obj_; + struct Destroy { void operator() (git_object*) const; }; + std::unique_ptr obj_; + Repository const * repo_ = nullptr; }; } - diff --git a/include/git2cpp/odb.h b/include/git2cpp/odb.h index 4d93fb3..f34c1a7 100644 --- a/include/git2cpp/odb.h +++ b/include/git2cpp/odb.h @@ -2,10 +2,7 @@ #include "odb_object.h" -extern "C" -{ #include -} struct git_odb; @@ -13,19 +10,15 @@ namespace git { struct Odb { - explicit Odb(git_repository * repo); - ~Odb(); - - OdbObject read(git_oid const * oid) const; - git_oid write(const void * data, size_t len, git_otype type); + OdbObject read(git_oid const & oid) const; + git_oid write(const void * data, size_t len, git_object_t type); - Odb (Odb const &) = delete; - Odb& operator = (Odb const &) = delete; - - Odb(Odb && other); + private: + friend struct Repository; + explicit Odb(git_repository * repo); private: - git_odb * odb_; + struct Destroy { void operator() (git_odb* odb) const; }; + std::unique_ptr odb_; }; } - diff --git a/include/git2cpp/odb_object.h b/include/git2cpp/odb_object.h index 1e45abf..b3b5b21 100644 --- a/include/git2cpp/odb_object.h +++ b/include/git2cpp/odb_object.h @@ -1,11 +1,7 @@ #pragma once -#include - -extern "C" -{ #include -} +#include namespace git { @@ -15,19 +11,12 @@ namespace git : obj_(obj) {} - ~OdbObject(); - - git_otype type() const; - unsigned char const * data() const; - size_t size() const; - - OdbObject (OdbObject const &) = delete; - OdbObject& operator = (OdbObject const &) = delete; - - OdbObject(OdbObject && other); + git_object_t type() const; + unsigned char const * data() const; + size_t size() const; private: - git_odb_object * obj_; + struct Destroy { void operator() (git_odb_object *) const; }; + std::unique_ptr obj_; }; } - diff --git a/include/git2cpp/pathspec.h b/include/git2cpp/pathspec.h index 0bc22d3..56a5336 100644 --- a/include/git2cpp/pathspec.h +++ b/include/git2cpp/pathspec.h @@ -2,10 +2,7 @@ #include "error.h" -extern "C" -{ - #include -} +#include namespace git { @@ -24,10 +21,10 @@ namespace git git_pathspec_free(ps_); } - Pathspec (Pathspec const &) = delete; - Pathspec& operator = (Pathspec const &) = delete; + Pathspec(Pathspec const &) = delete; + Pathspec & operator=(Pathspec const &) = delete; private: - git_pathspec * ps_; + git_pathspec * ps_; }; } diff --git a/include/git2cpp/reference.h b/include/git2cpp/reference.h index 0cb2628..d9dc98b 100644 --- a/include/git2cpp/reference.h +++ b/include/git2cpp/reference.h @@ -1,9 +1,7 @@ #pragma once -extern "C" -{ #include -} +#include struct git_reference; struct git_oid; @@ -12,21 +10,27 @@ namespace git { struct Reference { + Reference() + : ref_(nullptr) + {} + explicit Reference(git_reference * ref); - ~Reference(); - const char * name() const; - git_ref_t type() const; - git_oid const * target() const; - const char * symbolic_target() const; + explicit operator bool() const { return ref_ != nullptr; } - Reference& operator = (Reference const &) = delete; - Reference (Reference const &) = delete; + const char * name() const; + git_reference_t type() const; + git_oid const & target() const; + const char * symbolic_target() const; - Reference(Reference &&); + bool has_same_target_as(Reference const & other) const; private: - git_reference * ref_; + friend struct Repository; + git_reference * ptr() const { return ref_.get(); } + + private: + struct Destroy { void operator() (git_reference*) const; }; + std::unique_ptr ref_; }; } - diff --git a/include/git2cpp/remote.h b/include/git2cpp/remote.h new file mode 100644 index 0000000..917e315 --- /dev/null +++ b/include/git2cpp/remote.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +struct git_remote; +struct git_oid; +struct git_indexer_progress; +struct git_credential; + +namespace git +{ + struct Remote + { + 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) + {} + + std::unique_ptr remote_; + }; +} diff --git a/include/git2cpp/repo.h b/include/git2cpp/repo.h index 70516fd..9b4ab3e 100644 --- a/include/git2cpp/repo.h +++ b/include/git2cpp/repo.h @@ -1,105 +1,184 @@ #pragma once -#include -#include - -extern "C" -{ -#include -} +#include "repo_fwd.h" +#include "blame.h" +#include "blob.h" #include "commit.h" +#include "diff.h" #include "index.h" #include "odb.h" -#include "revspec.h" -#include "status.h" #include "reference.h" -#include "signature.h" +#include "remote.h" +#include "revspec.h" #include "revwalker.h" -#include "tag.h" -#include "blob.h" +#include "signature.h" +#include "status.h" #include "str_array.h" +#include "submodule.h" +#include "tag.h" +#include "tree.h" + +#include "internal/optional.h" + +#include +#include +#include + +struct git_blame_options; namespace git { - struct non_existing_branch_error {}; - struct missing_head_error {}; + struct non_existing_branch_error + {}; + struct missing_head_error + {}; - struct Repository + struct repository_clone_error { - git_repository * ptr() { return repo_; } + struct detailed_info + { + char const* message; + int klass; + }; + std::variant data; + }; - Commit commit_lookup(git_oid const * oid) const; - Tree tree_lookup (git_oid const * oid) const; - Tag tag_lookup (git_oid const * oid) const; - Blob blob_lookup (git_oid const * oid) const; + enum class branch_type + { + LOCAL, + REMOTE, + ALL + }; + + struct FileDiffHandler + { + virtual void line(git_diff_line const &) = 0; + + protected: + ~FileDiffHandler() = default; + }; + + struct AnnotatedCommit; + + struct Repository + { + Commit commit_lookup(git_oid const & oid) const; + Tree tree_lookup(git_oid const & oid) const; + Tag tag_lookup(git_oid const & oid) const; + Blob blob_lookup(git_oid const & oid) const; git_oid merge_base(Revspec::Range const & range) const; + git_oid merge_base(git_oid const &, git_oid const &) const; - Revspec revparse (const char * spec) const; - Revspec revparse_single (const char * spec) const; + Revspec revparse(const char * spec) const; + Revspec revparse_single(const char * spec) const; RevWalker rev_walker() const; git_status_t file_status(const char * filepath) const; - Object entry_to_object(git_tree_entry const * entry) const; + Object entry_to_object(Tree::OwnedEntry) const; + Object entry_to_object(Tree::BorrowedEntry) const; + + Index index() const; + Odb odb() const; - Index index() const; - Odb odb() const; + Diff diff(Tree &, Tree &, git_diff_options const &) const; + Diff diff_to_index(Tree &, git_diff_options const &) const; + Diff diff_to_workdir(Tree &, git_diff_options const &) const; + Diff diff_to_workdir_with_index(Tree &, git_diff_options const &) const; + Diff diff_index_to_workdir(git_diff_options const &) const; Signature signature() const; - Status status(git_status_options const &) const; + Status status(Status::Options const &) const; + + git_repository_state_t state() const; StrArray reference_list() const; - std::vector branches() 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; + + AnnotatedCommit annotated_commit_from_ref(Reference const &) const; + AnnotatedCommit annotated_commit_lookup(git_oid const &) const; bool is_bare() const; - Reference head() const; - Reference ref(const char * name) const; + Reference head() const; + Reference ref(const char * name) const; - int submodule_lookup(git_submodule *&, const char * name) const; - - const char * path() const; - const char * workdir() const; + Submodule submodule_lookup(const char * name) const; + + const char * path() const; + const char * workdir() const; git_oid create_commit(const char * update_ref, Signature const & author, Signature const & commiter, - const char * message_encoding, const char * message, - Tree const & tree); + Tree const & tree, + const char * message_encoding = nullptr); git_oid create_commit(const char * update_ref, Signature const & author, Signature const & commiter, - const char * message_encoding, const char * message, Tree const & tree, - Commit const & parent); + 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, + std::string const & new_path, git_oid const & new_id, FileDiffHandler &) const; + + StrArray remotes() const; + Remote remote(const char * name) const; + Remote create_remote(const char * name, const char * url); + void delete_remote(const char * name); + internal::optional rename_remote(const char * old_name, const char * new_name); + void set_url (const char * name, const char * url); + void set_pushurl(const char * name, const char * url); + /// @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); - struct init_tag {}; - static init_tag init; + struct init_tag + {}; + static const init_tag init; + Repository(const char * dir, init_tag); + Repository(const char * dir, init_tag, git_repository_init_options opts); Repository(std::string const & dir, init_tag); - Repository(std::string const & dir, init_tag, - git_repository_init_options opts); - Repository (Repository const &) = delete; - Repository& operator = (Repository const &) = delete; + static Repository clone(const char * url, const char* path, git_checkout_options const &, Remote::FetchCallbacks &); - Repository(Repository &&); - - ~Repository(); + static internal::optional discover(const char * start_path); private: - git_repository * repo_; + 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/repo_fwd.h b/include/git2cpp/repo_fwd.h new file mode 100644 index 0000000..47b942a --- /dev/null +++ b/include/git2cpp/repo_fwd.h @@ -0,0 +1,6 @@ +#pragma once + +namespace git +{ + struct Repository; +} diff --git a/include/git2cpp/revspec.h b/include/git2cpp/revspec.h index 4245130..0fbfd81 100644 --- a/include/git2cpp/revspec.h +++ b/include/git2cpp/revspec.h @@ -2,39 +2,39 @@ #include "object.h" -extern "C" -{ +extern "C" { #include } namespace git { + struct Repository; + struct Revspec { struct Range { Object from, to; - explicit Range(git_object * single) - : from (single) - , to (nullptr) + Range(git_object * single, Repository const & repo) + : from(single, repo) {} - explicit Range(git_revspec const & revspec) - : from (revspec.from) - , to (revspec.to) + explicit Range(git_revspec const & revspec, Repository const & repo) + : from(revspec.from, repo) + , to(revspec.to, repo) {} }; - Object const * single() const; - Range const * range() const; + Object const * single() const; + Range const * range() const; Object * single(); unsigned int flags() const; - Revspec(git_object * single); - Revspec(git_revspec const &); + Revspec(git_object * single, Repository const &); + Revspec(git_revspec const &, Repository const &); private: unsigned int flags_ = 0; diff --git a/include/git2cpp/revwalker.h b/include/git2cpp/revwalker.h index 6b05674..ac426eb 100644 --- a/include/git2cpp/revwalker.h +++ b/include/git2cpp/revwalker.h @@ -1,38 +1,46 @@ #pragma once #include "commit.h" - -struct git_repository; -struct git_revwalk; +#include "tagged_mask.h" +#include namespace git { struct Repository; - struct RevWalker + namespace revwalker { - explicit RevWalker(git_repository * repo); - ~RevWalker(); + namespace sorting + { + typedef tagged_mask_t type; - void sort(int sorting); + extern const type none; + extern const type topological; + extern const type time; + extern const type reverse; + } + } - void push_head() const; - void hide(git_oid const * obj) const; - void push(git_oid const * obj) const; + struct RevWalker + { + RevWalker(git_revwalk * walker, Repository const & repo) + : walker_(walker) + , repo_(&repo) + {} - Commit next(Repository const & repo) const; - bool next(char * id_buffer) const; + void sort(revwalker::sorting::type); + void simplify_first_parent(); - RevWalker (RevWalker const &) = delete; - RevWalker& operator = (RevWalker const &) = delete; + void push_head() const; + void hide(git_oid const &) const; + void push(git_oid const &) const; - RevWalker(RevWalker && other) - : walker_(other.walker_) - { - other.walker_ = nullptr; - } + Commit next() const; + bool next(char * id_buffer) const; private: - git_revwalk * walker_; + struct Destroy { void operator() (git_revwalk*) const; }; + std::unique_ptr walker_; + Repository const * repo_; }; } diff --git a/include/git2cpp/signature.h b/include/git2cpp/signature.h index 743322d..75756d4 100644 --- a/include/git2cpp/signature.h +++ b/include/git2cpp/signature.h @@ -1,5 +1,9 @@ #pragma once +#include + +#include + struct git_signature; struct git_repository; @@ -7,15 +11,16 @@ namespace git { struct Signature { - git_signature const * ptr() const { return sig_; } + Signature(const char * name, const char * email, git_time_t time, int offset); + private: + friend struct Repository; explicit Signature(git_repository * repo); - Signature(const char * name, const char * email, git_time_t time, int offset); - ~Signature(); + git_signature const * ptr() const { return sig_.get(); } private: - git_signature * sig_; + struct Destroy { void operator() (git_signature*) const; }; + std::unique_ptr sig_; }; } - diff --git a/include/git2cpp/status.h b/include/git2cpp/status.h index 9092c47..7372da9 100644 --- a/include/git2cpp/status.h +++ b/include/git2cpp/status.h @@ -1,31 +1,54 @@ #pragma once -extern "C" -{ -#include -#include #include -} -struct git_status_list; +#include namespace git { struct Status { - Status(git_repository * repo, git_status_options const & opts); - ~Status(); + struct Options + { + enum class Show + { + IndexOnly, + WorkdirOnly, + IndexAndWorkdir + }; + + enum class Sort + { + CaseSensitively, + CaseInsensitively, + }; + + Options(Show = Show::IndexAndWorkdir, Sort = Sort::CaseSensitively); + + Options & include_untracked(); + Options & exclude_untracked(); + Options & renames_head_to_index(); + Options & include_ignored(); + Options & recurse_untracked_dirs(); + Options & exclude_submodules(); + + void set_pathspec(char ** ptr, size_t size); + + git_status_options const * raw() const { return &opts_; } + + private: + git_status_options opts_; + }; size_t entrycount() const; - git_status_entry const * operator[] (size_t i) const; - - Status (Status const &) = delete; - Status& operator = (Status const &) = delete; + git_status_entry const & operator[](size_t i) const; - Status(Status &&); + private: + friend struct Repository; + Status(git_repository * repo, Options const & opts); private: - git_status_list * status_; + struct Destroy { void operator() (git_status_list*) const; }; + std::unique_ptr status_; }; } - diff --git a/include/git2cpp/str_array.h b/include/git2cpp/str_array.h index 069ef03..6c02abc 100644 --- a/include/git2cpp/str_array.h +++ b/include/git2cpp/str_array.h @@ -1,9 +1,8 @@ #pragma once -extern "C" -{ #include -} + +#include namespace git { @@ -13,16 +12,31 @@ namespace git : str_array_(str_array) {} + StrArray(StrArray const &) = delete; + StrArray& operator =(StrArray const &) = delete; + + StrArray(StrArray && other) noexcept + : str_array_(other.str_array_) + { + std::memset(&other.str_array_, 0, sizeof(git_strarray)); + } + + StrArray& operator =(StrArray && other) noexcept + { + std::swap(str_array_, other.str_array_); + return *this; + } + size_t count() const { return str_array_.count; } - const char * operator [] (size_t i) const + const char * operator[](size_t i) const { return str_array_.strings[i]; } ~StrArray() { - git_strarray_free(&str_array_); + git_strarray_dispose(&str_array_); } private: diff --git a/include/git2cpp/submodule.h b/include/git2cpp/submodule.h new file mode 100644 index 0000000..6877538 --- /dev/null +++ b/include/git2cpp/submodule.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +struct git_submodule; +struct git_repository; + +namespace git +{ + struct Submodule + { + enum class status : unsigned int + { + in_head = GIT_SUBMODULE_STATUS_IN_HEAD, + in_index = GIT_SUBMODULE_STATUS_IN_INDEX, + in_config = GIT_SUBMODULE_STATUS_IN_CONFIG, + in_wd = GIT_SUBMODULE_STATUS_IN_WD, + index_added = GIT_SUBMODULE_STATUS_INDEX_ADDED, + index_deleted = GIT_SUBMODULE_STATUS_INDEX_DELETED, + index_modified = GIT_SUBMODULE_STATUS_INDEX_MODIFIED, + wd_uninitialized = GIT_SUBMODULE_STATUS_WD_UNINITIALIZED, + wd_added = GIT_SUBMODULE_STATUS_WD_ADDED, + wd_deleted = GIT_SUBMODULE_STATUS_WD_DELETED, + wd_modified = GIT_SUBMODULE_STATUS_WD_MODIFIED, + wd_index_modified = GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED, + wd_wd_modified = GIT_SUBMODULE_STATUS_WD_WD_MODIFIED, + wd_untracked = GIT_SUBMODULE_STATUS_WD_UNTRACKED, + }; + + friend bool contains(status set, status element); + + status get_status() const; + + private: + friend struct Repository; + Submodule(git_submodule *, git_repository *); + + private: + struct Destroy { void operator() (git_submodule*) const; }; + std::unique_ptr sm_; + git_repository * repo_; + }; +} diff --git a/include/git2cpp/tag.h b/include/git2cpp/tag.h index 7464a8e..bb57c9b 100644 --- a/include/git2cpp/tag.h +++ b/include/git2cpp/tag.h @@ -1,27 +1,31 @@ #pragma once -#include "object.h" +#include +#include struct git_tag; +struct git_oid; namespace git { + struct Repository; + struct Object; + struct Tag { - Tag(git_oid const * oid, git_repository * repo); - ~Tag(); + explicit Tag(git_tag *); - Tag& operator = (Tag const &) = delete; - Tag (Tag const &) = delete; + Object target(Repository const &) const; - Tag(Tag &&); + git_oid const & target_id() const; + git_object_t target_type() const; - Object target() const; - git_otype target_type() const; - const char * name() const; - const char * message() const; + const char * name() const; + const char * message() const; + git_signature const * tagger() const; private: - git_tag * tag_; + struct Destroy { void operator() (git_tag*) const; }; + std::unique_ptr tag_; }; } diff --git a/include/git2cpp/tagged_mask.h b/include/git2cpp/tagged_mask.h new file mode 100644 index 0000000..80082ca --- /dev/null +++ b/include/git2cpp/tagged_mask.h @@ -0,0 +1,36 @@ +#pragma once + +namespace git +{ + template + struct tagged_mask_t + { + typedef tagged_mask_t self_t; + + explicit tagged_mask_t(RawType value = RawType()) + : value_(value) + {} + + RawType value() const { return value_; } + + explicit operator bool() const { return value_; } + + self_t & operator|=(self_t other) + { + value_ |= other.value_; + return *this; + } + + friend self_t operator~(self_t x) { return self_t(~x.value_); } + + friend self_t operator|(self_t x, self_t y) { return self_t(x.value_ | y.value_); } + friend self_t operator&(self_t x, self_t y) { return self_t(x.value_ & y.value_); } + friend self_t operator^(self_t x, self_t y) { return self_t(x.value_ ^ y.value_); } + + friend bool operator==(self_t x, self_t y) { return x.value_ == y.value_; } + friend bool operator!=(self_t x, self_t y) { return x.value_ != y.value_; } + + private: + RawType value_; + }; +} diff --git a/include/git2cpp/threads_initializer.h b/include/git2cpp/threads_initializer.h deleted file mode 100644 index b97c4e1..0000000 --- a/include/git2cpp/threads_initializer.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -namespace git -{ - struct ThreadsInitializer - { - ThreadsInitializer(); - ~ThreadsInitializer(); - - ThreadsInitializer (ThreadsInitializer const &) = delete; - ThreadsInitializer& operator = (ThreadsInitializer const &) = delete; - }; -} diff --git a/include/git2cpp/tree.h b/include/git2cpp/tree.h index 3c801f7..9ca5821 100644 --- a/include/git2cpp/tree.h +++ b/include/git2cpp/tree.h @@ -1,65 +1,76 @@ #pragma once -#include "diff_list.h" #include "pathspec.h" +#include + namespace git { + struct Repository; + struct Tree { - git_tree const * ptr() const { return tree_; } - git_tree * ptr() { return tree_; } - - int pathspec_match(uint32_t flags, Pathspec const & ps) + struct BorrowedEntry { - return git_pathspec_match_tree(NULL, tree_, flags, ps.ptr()); - } + const char * name() const; + git_oid const & id() const; + git_object_t type() const; + git_filemode_t filemode() const; - size_t entrycount() const - { - return git_tree_entrycount(tree_); - } + private: + friend struct Tree; + friend struct Repository; - git_tree_entry const * operator[] (size_t i) const - { - return git_tree_entry_byindex(tree_, i); - } + explicit BorrowedEntry(git_tree_entry const * entry) + : entry_(entry) + {} - git_tree_entry const * operator[] (std::string const & filename) const + git_tree_entry const * ptr() const { return entry_; } + + private: + git_tree_entry const * entry_; + }; + + struct OwnedEntry { - return git_tree_entry_byname(tree_, filename.c_str()); - } + Tree to_tree() /*&&*/; - Tree(git_oid const * oid, git_repository * repo); + private: + friend struct Tree; + friend struct Repository; - explicit Tree(git_tree * tree = nullptr) - : tree_(tree) - {} + OwnedEntry(git_tree_entry * entry, Repository const & repo); - Tree(Tree && other) - : tree_(other.tree_) - { - other.tree_ = nullptr; - } + git_tree_entry const * ptr() const { return entry_.get(); } - Tree& operator =(Tree && other) - { - tree_ = other.tree_; - other.tree_ = nullptr; - } + private: + struct Destroy { void operator() (git_tree_entry*) const; }; + std::unique_ptr entry_; + Repository const * repo_; + }; - Tree (Tree const &) = delete; - Tree& operator = (Tree const &) = delete; + git_tree const * ptr() const { return tree_.get(); } + git_tree * ptr() { return tree_.get(); } - ~Tree() - { - git_tree_free(tree_); - } + int pathspec_match(uint32_t flags, Pathspec const & ps); + + size_t entrycount() const; + + BorrowedEntry operator[](size_t) const; + + BorrowedEntry operator[](std::string const & filename) const; - explicit operator bool() const { return tree_; } + OwnedEntry find(const char * path) const; + + Tree(git_tree *, Repository const &); + + Tree() = default; + + explicit operator bool() const { return tree_ != nullptr; } private: - git_tree * tree_; + struct Destroy { void operator() (git_tree*) const; }; + std::unique_ptr tree_; + Repository const * repo_ = nullptr; }; } - diff --git a/libs/libgit2 b/libs/libgit2 new file mode 160000 index 0000000..e632535 --- /dev/null +++ b/libs/libgit2 @@ -0,0 +1 @@ +Subproject commit e6325351ceee58cf56f58bdce61b38907805544f diff --git a/src/annotated_commit.cpp b/src/annotated_commit.cpp new file mode 100644 index 0000000..8eecef3 --- /dev/null +++ b/src/annotated_commit.cpp @@ -0,0 +1,21 @@ +#include "git2cpp/annotated_commit.h" + +#include + +namespace git +{ + git_oid const& AnnotatedCommit::commit_id() const + { + return *git_annotated_commit_id(ptr()); + } + + char const* AnnotatedCommit::commit_ref() const + { + return git_annotated_commit_ref(ptr()); + } + + void AnnotatedCommit::Destroy::operator()(git_annotated_commit* commit) const + { + git_annotated_commit_free(commit); + } +} 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/blob.cpp b/src/blob.cpp index 2a75625..4b31df7 100644 --- a/src/blob.cpp +++ b/src/blob.cpp @@ -1,29 +1,26 @@ #include "git2cpp/blob.h" -extern "C" -{ #include -} namespace git { - Blob::Blob(const git_oid *oid, git_repository *repo) + Blob::Blob(git_blob * blob) + : blob_(blob) { - git_blob_lookup(&blob_, repo, oid); } - Blob::~Blob() + std::size_t Blob::size() const { - git_blob_free(blob_); + return static_cast(git_blob_rawsize(ptr())); } - git_off_t Blob::size() const + const void * Blob::content() const { - return git_blob_rawsize(blob_); + return git_blob_rawcontent(ptr()); } - const void * Blob::content() const + void Blob::Destroy::operator()(git_blob * blob) const { - return git_blob_rawcontent(blob_); + git_blob_free(blob); } } diff --git a/src/buffer.cpp b/src/buffer.cpp new file mode 100644 index 0000000..8d15189 --- /dev/null +++ b/src/buffer.cpp @@ -0,0 +1,9 @@ +#include "git2cpp/buffer.h" + +namespace git +{ + Buffer::~Buffer() + { + git_buf_dispose(&buf_); + } +} diff --git a/src/commit.cpp b/src/commit.cpp index 0ef8a11..760d8eb 100644 --- a/src/commit.cpp +++ b/src/commit.cpp @@ -1,73 +1,85 @@ #include "git2cpp/commit.h" +#include "git2cpp/error.h" +#include "git2cpp/repo.h" +#include "git2cpp/tree.h" -extern "C" -{ #include -} namespace git { + Commit::Commit(git_commit * commit, Repository const & repo) + : commit_(commit) + , repo_(&repo) + { + } + + void Commit::Destroy::operator()(git_commit* commit) const { + git_commit_free(commit); + } + Commit Commit::parent(size_t i) const { git_commit * parent; - if (git_commit_parent(&parent, commit_, i)); + if (git_commit_parent(&parent, ptr(), static_cast(i))) throw commit_parent_error(id()); - return Commit(parent); + return Commit(parent, *repo_); } Tree Commit::tree() const { git_tree * tree; - if (git_commit_tree(&tree, commit_)) + if (git_commit_tree(&tree, ptr())) throw commit_tree_error(id()); - return Tree(tree); + return Tree(tree, *repo_); } - git_repository * Commit::owner() const + size_t Commit::parents_num() const { - return git_commit_owner(commit_); + return git_commit_parentcount(ptr()); } - size_t Commit::parents_num() const + git_oid const & Commit::id() const { - return git_commit_parentcount(commit_); + return *git_commit_id(ptr()); } - git_oid const * Commit::id() const + git_oid const & Commit::tree_id() const { - return git_commit_id(commit_); + return *git_commit_tree_id(ptr()); } - git_oid const * Commit::parent_id(size_t i) const + git_oid const & Commit::parent_id(size_t i) const { - return git_commit_parent_id(commit_, i); + return *git_commit_parent_id(ptr(), static_cast(i)); } git_signature const * Commit::author() const { - return git_commit_author(commit_); + return git_commit_author(ptr()); } git_signature const * Commit::commiter() const { - return git_commit_committer(commit_); + return git_commit_committer(ptr()); } const char * Commit::message() const { - return git_commit_message(commit_); + return git_commit_message(ptr()); } - git_time_t Commit::time() const + const char * Commit::summary() const { - return git_commit_time(commit_); + return git_commit_summary(commit_.get()); } - Commit::Commit(git_oid const * oid, git_repository * repo) + git_time_t Commit::time() const { - if (git_commit_lookup(&commit_, repo, oid)) - throw commit_lookup_error(oid); + return git_commit_time(ptr()); } - Commit::~Commit() { git_commit_free(commit_); } + git_oid Commit::merge_base(size_t p1, size_t p2) const + { + return repo_->merge_base(parent_id(p1), parent_id(p2)); + } } diff --git a/src/config.cpp b/src/config.cpp index 562a8cb..cdae46e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,28 +1,27 @@ #include "git2cpp/config.h" #include "git2cpp/error.h" -extern "C" -{ #include -} namespace git { Config::Config(std::string const & filename) { - if (git_config_open_ondisk(&cfg_, filename.c_str())) + git_config * cfg; + if (git_config_open_ondisk(&cfg, filename.c_str())) throw config_open_error(); + cfg_.reset(cfg); } - Config::~Config() + void Config::Destroy::operator()(git_config* cfg) const { - git_config_free(cfg_); + git_config_free(cfg); } - std::string Config::operator [] (const char * key) const + std::string Config::operator[](const char * key) const { const char * res; - if (git_config_get_string(&res, cfg_, key)) + if (git_config_get_string(&res, cfg_.get(), key)) throw no_such_key_error(key); return res; } @@ -30,7 +29,7 @@ namespace git int Config::get_int(const char * key) const { int res; - if (git_config_get_int32(&res, cfg_, key)) + if (git_config_get_int32(&res, cfg_.get(), key)) throw no_such_key_error(key); return res; } diff --git a/src/diff.cpp b/src/diff.cpp new file mode 100644 index 0000000..5c8ab85 --- /dev/null +++ b/src/diff.cpp @@ -0,0 +1,109 @@ +#include "git2cpp/diff.h" +#include "git2cpp/error.h" + +#include + +#ifdef USE_BOOST +#include +#include +#else +#include +#endif + +namespace git +{ + namespace diff + { + namespace stats + { + namespace format + { + const type none(GIT_DIFF_STATS_NONE); + const type full(GIT_DIFF_STATS_FULL); + const type _short(GIT_DIFF_STATS_SHORT); + const type number(GIT_DIFF_STATS_NUMBER); + const type include_summary(GIT_DIFF_STATS_INCLUDE_SUMMARY); + } + } + +#ifdef USE_BOOST + template + using map_container = boost::container::flat_map; +#else + struct EnumHash + { + template + std::size_t operator()(T t) const + { + return static_cast(t); + } + }; + + template + using map_container = std::unordered_map; +#endif + + git_diff_format_t convert(format f) + { + static const map_container converter = { + {format::patch, GIT_DIFF_FORMAT_PATCH}, + {format::patch_header, GIT_DIFF_FORMAT_PATCH_HEADER}, + {format::raw, GIT_DIFF_FORMAT_RAW}, + {format::name_only, GIT_DIFF_FORMAT_NAME_ONLY}, + {format::name_status, GIT_DIFF_FORMAT_NAME_STATUS}}; + return converter.at(f); + } + } + + namespace + { + int apply_callback(git_diff_delta const * delta, git_diff_hunk const * hunk, git_diff_line const * line, void * payload) + { + assert(delta); + assert(line); + auto cb = reinterpret_cast(payload); + (*cb)(*delta, *hunk, *line); + return 0; + } + } + + void Diff::Destroy::operator() (git_diff* diff) const { git_diff_free(diff); } + + void Diff::find_similar(git_diff_find_options & findopts) + { + git_diff_find_similar(diff_.get(), &findopts); + } + + size_t Diff::deltas_num() const + { + return git_diff_num_deltas(diff_.get()); + } + + void Diff::print(diff::format f, print_callback_t print_callback) const + { + git_diff_print(diff_.get(), convert(f), &apply_callback, &print_callback); + } + + Diff::Stats Diff::stats() const + { + git_diff_stats * stats; + if (git_diff_get_stats(&stats, diff_.get())) + throw error_t("git_diff_get_stats fail"); + else + return Stats(stats); + } + + void Diff::Stats::Destroy::operator()(git_diff_stats* stats) const + { + git_diff_stats_free(stats); + } + + Buffer Diff::Stats::to_buf(diff::stats::format::type format, size_t width) const + { + 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 + return Buffer(buf); + } +} diff --git a/src/diff_list.cpp b/src/diff_list.cpp deleted file mode 100644 index 9d608b3..0000000 --- a/src/diff_list.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include - -#include "git2cpp/diff_list.h" -#include "git2cpp/tree.h" - -namespace git -{ - DiffList diff (git_repository * repo - , Tree & a, Tree & b - , git_diff_options const & opts - ) - { - git_diff_list * diff_list; - auto op_res = git_diff_tree_to_tree(&diff_list, repo, a.ptr(), b.ptr(), &opts); - assert(op_res == 0); - return DiffList(diff_list); - } - - DiffList diff_to_index ( git_repository * repo - , Tree & t - , git_diff_options const & opts - ) - { - git_diff_list * diff_list; - auto op_res = git_diff_tree_to_index(&diff_list, repo, t.ptr(), nullptr, &opts); - assert(op_res == 0); - return DiffList(diff_list); - } - - DiffList diff_index_to_workdir(git_repository * repo, git_diff_options const & opts) - { - git_diff_list * diff_list; - auto op_res = git_diff_index_to_workdir(&diff_list, repo, nullptr, &opts); - assert(op_res == 0); - return DiffList(diff_list); - } - - namespace - { - int apply_callback ( git_diff_delta const * delta - , git_diff_range const * range - , char usage - , const char * line - , size_t line_len - , void * payload ) - { - auto cb = reinterpret_cast(payload); - return (*cb)(delta, range, usage, line, line_len); - } - } - - void DiffList::print_patch(data_callback_t cb) const - { - git_diff_print_patch(diff_list_, &apply_callback, &cb); - } - - void DiffList::print_compact(data_callback_t cb) const - { - git_diff_print_compact(diff_list_, &apply_callback, &cb); - } - - void DiffList::print_raw(data_callback_t cb) const - { - git_diff_print_raw(diff_list_, &apply_callback, &cb); - } - - DiffList& DiffList::merge(DiffList const & other) - { - git_diff_merge(diff_list_, other.diff_list_); - return *this; - } - - size_t DiffList::deltas_num() const - { - return git_diff_num_deltas(diff_list_); - } -} - diff --git a/src/id_to_str.cpp b/src/id_to_str.cpp index 8e36ffb..b58dfb5 100644 --- a/src/id_to_str.cpp +++ b/src/id_to_str.cpp @@ -1,25 +1,22 @@ #include "git2cpp/id_to_str.h" -extern "C" -{ #include -} namespace git { - std::string id_to_str(git_oid const * oid) + 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) + std::string id_to_str(git_oid const & oid, size_t digits_num) { - char buf[digits_num + 1]; - git_oid_tostr(buf, sizeof(buf), oid); - return std::string(buf); + char buf[GIT_OID_SHA1_HEXSIZE + 1]; + git_oid_tostr(buf, sizeof(buf), &oid); + return std::string(buf, buf + digits_num); } - git_oid str_to_id(const char *str) + git_oid str_to_id(const char * str) { git_oid res; git_oid_fromstr(&res, str); diff --git a/src/index.cpp b/src/index.cpp index c31dae9..b1d83c3 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -1,49 +1,48 @@ #include -extern "C" -{ -#include #include -} +#include -#include "git2cpp/index.h" #include "git2cpp/error.h" +#include "git2cpp/index.h" namespace git { Index::Index(git_repository * repo) { - if (git_repository_index(&index_, repo) < 0) + git_index * index; + if (git_repository_index(&index, repo) < 0) throw index_open_error(); + index_.reset(index); } Index::Index(const char * dir) { - if (git_index_open(&index_, dir)) + git_index * index; + if (git_index_open(&index, dir)) throw index_open_error(); - git_index_read(index_); + index_.reset(index); + git_index_read(index, true); } - Index::~Index() + void Index::Destroy::operator()(git_index* index) const { - if (index_) - git_index_free(index_); + git_index_free(index); } - Index::Index(Index && other) - : index_(other.index_) + size_t Index::entrycount() const { - other.index_ = nullptr; + return git_index_entrycount(index_.get()); } - size_t Index::entrycount() const + git_index_entry const * Index::operator[](size_t i) const { - return git_index_entrycount(index_); + return git_index_get_byindex(index_.get(), i); } - git_index_entry const * Index::operator[] (size_t i) const + git_index_entry const* Index::get_by_path(const char* path, int stage) const { - return git_index_get_byindex(index_, i); + return git_index_get_bypath(index_.get(), path, stage); } namespace @@ -57,30 +56,58 @@ namespace git void Index::update_all(git_strarray const & pathspec, matched_path_callback_t cb) { - int res = cb - ? git_index_update_all(index_, &pathspec, &apply_callback, &cb) - : git_index_update_all(index_, &pathspec, nullptr, nullptr); + int res = cb + ? git_index_update_all(index_.get(), &pathspec, &apply_callback, &cb) + : git_index_update_all(index_.get(), &pathspec, nullptr, nullptr); assert(res == 0); } void Index::add_all(git_strarray const & pathspec, matched_path_callback_t cb, unsigned int flags) { - int res = cb - ? git_index_add_all(index_, &pathspec, flags, &apply_callback, &cb) - : git_index_add_all(index_, &pathspec, flags, nullptr, nullptr); + int res = cb + ? git_index_add_all(index_.get(), &pathspec, flags, &apply_callback, &cb) + : git_index_add_all(index_.get(), &pathspec, flags, nullptr, nullptr); + assert(res == 0); + } + + void Index::clear() + { + int res = git_index_clear(index_.get()); + assert(res == 0); + } + + void Index::add_path(const char * path) + { + int res = git_index_add_bypath(index_.get(), path); assert(res == 0); } + void Index::add_path(std::string const & path) + { + add_path(path.c_str()); + } + + void Index::remove_path(const char * path) + { + int res = git_index_remove_bypath(index_.get(), path); + assert(res == 0); + } + + void Index::remove_path(std::string const & path) + { + remove_path(path.c_str()); + } + void Index::write() const { - if (git_index_write(index_)) + if (git_index_write(index_.get())) throw index_write_error(); } git_oid Index::write_tree() const { git_oid res; - if (git_index_write_tree(&res, index_) < 0) + if (git_index_write_tree(&res, index_.get()) < 0) throw index_write_tree_error(); return res; } diff --git a/src/initializer.cpp b/src/initializer.cpp new file mode 100644 index 0000000..e9728e8 --- /dev/null +++ b/src/initializer.cpp @@ -0,0 +1,16 @@ +#include "git2cpp/initializer.h" + +#include + +namespace git +{ + Initializer::Initializer() + { + git_libgit2_init(); + } + + Initializer::~Initializer() + { + git_libgit2_shutdown(); + } +} diff --git a/src/object.cpp b/src/object.cpp index c30bf79..9acef11 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1,67 +1,75 @@ #include "git2cpp/object.h" +#include "git2cpp/blob.h" +#include "git2cpp/commit.h" +#include "git2cpp/tree.h" +#include "git2cpp/tag.h" #include -extern "C" -{ #include -} namespace git { - Object::Object(git_object * obj) + Object::Object(git_object * obj, Repository const & repo) : obj_(obj) - {} - - Object::~Object() + , repo_(&repo) { - git_object_free(obj_); } - Object::Object(Object && other) - : obj_(other.obj_) + void Object::Destroy::operator()(git_object* obj) const { - other.obj_ = nullptr; + git_object_free(obj); } - git_otype Object::type() const + git_object_t Object::type() const { - return git_object_type(obj_); + return git_object_type(obj_.get()); } - git_oid const * Object::id() const + git_oid const & Object::id() const { - return git_object_id(obj_); + return *git_object_id(obj_.get()); } -#define DEFINE_METHOD_AS(type_name, enum_element) \ - git_##type_name const * Object::as_##type_name() const \ - { \ - assert(type() == GIT_OBJ_##enum_element); \ - return reinterpret_cast(obj_); \ - } \ +#define DEFINE_METHOD_AS(type_name, enum_element) \ + git_##type_name * Object::as_##type_name() \ + { \ + assert(type() == GIT_OBJECT_##enum_element); \ + return reinterpret_cast(obj_.get()); \ + } - DEFINE_METHOD_AS(blob, BLOB) - DEFINE_METHOD_AS(commit, COMMIT) - DEFINE_METHOD_AS(tree, TREE) - DEFINE_METHOD_AS(tag, TAG) + DEFINE_METHOD_AS(blob, BLOB) + DEFINE_METHOD_AS(commit, COMMIT) + DEFINE_METHOD_AS(tree, TREE) + DEFINE_METHOD_AS(tag, TAG) #undef DEFINE_METHOD_AS - Tree Object::to_tree() + Tree Object::to_tree() /*&&*/ { - assert(type() == GIT_OBJ_TREE); - Tree res(reinterpret_cast(obj_)); + Tree res(as_tree(), *repo_); obj_ = nullptr; return res; } - Commit Object::to_commit() + Commit Object::to_commit() /*&&*/ { - assert(type() == GIT_OBJ_COMMIT); - Commit res(reinterpret_cast(obj_)); + Commit res(as_commit(), *repo_); obj_ = nullptr; return res; } -} + Blob Object::to_blob() /*&&*/ + { + Blob res(as_blob()); + obj_ = nullptr; + return res; + } + + Tag Object::to_tag() /*&&*/ + { + Tag res(as_tag()); + obj_ = nullptr; + return res; + } +} diff --git a/src/odb.cpp b/src/odb.cpp index 03da994..5db95fd 100644 --- a/src/odb.cpp +++ b/src/odb.cpp @@ -1,40 +1,37 @@ -extern "C" -{ -#include #include -} +#include -#include "git2cpp/odb.h" #include "git2cpp/error.h" +#include "git2cpp/odb.h" namespace git { Odb::Odb(git_repository * repo) { - if (git_repository_odb(&odb_, repo)) + git_odb * odb; + if (git_repository_odb(&odb, repo)) throw odb_open_error(); + odb_.reset(odb); } - Odb::~Odb() + void Odb::Destroy::operator()(git_odb* odb) const { - if (odb_) - git_odb_free(odb_); + git_odb_free(odb); } - OdbObject Odb::read(git_oid const * oid) const + OdbObject Odb::read(git_oid const & oid) const { git_odb_object * obj; - if (git_odb_read(&obj, odb_, oid)) + if (git_odb_read(&obj, odb_.get(), &oid)) throw odb_read_error(oid); return OdbObject(obj); } - git_oid Odb::write(const void *data, size_t len, git_otype type) + git_oid Odb::write(const void * data, size_t len, git_object_t type) { git_oid res; - if (git_odb_write(&res, odb_, data, len, type)) + if (git_odb_write(&res, odb_.get(), data, len, type)) throw odb_write_error(); return res; } } - diff --git a/src/odb_object.cpp b/src/odb_object.cpp index 2e99ade..b3c2f5a 100644 --- a/src/odb_object.cpp +++ b/src/odb_object.cpp @@ -1,31 +1,27 @@ #include "git2cpp/odb_object.h" -extern "C" -{ #include -} namespace git { - OdbObject::~OdbObject() + void OdbObject::Destroy::operator()(git_odb_object* obj) const { - if (obj_) - git_odb_object_free(obj_); + git_odb_object_free(obj); } - git_otype OdbObject::type() const + git_object_t OdbObject::type() const { - return git_odb_object_type(obj_); + return git_odb_object_type(obj_.get()); } unsigned char const * OdbObject::data() const { - return reinterpret_cast(git_odb_object_data(obj_)); + return reinterpret_cast(git_odb_object_data(obj_.get())); } size_t OdbObject::size() const { - return git_odb_object_size(obj_); + return git_odb_object_size(obj_.get()); } -} +} diff --git a/src/reference.cpp b/src/reference.cpp index 42df965..617819a 100644 --- a/src/reference.cpp +++ b/src/reference.cpp @@ -1,38 +1,45 @@ #include "git2cpp/reference.h" -extern "C" -{ - #include -} +#include + +#include +#include namespace git { - Reference::~Reference() + void Reference::Destroy::operator()(git_reference* ref) const { - git_reference_free(ref_); + git_reference_free(ref); } Reference::Reference(git_reference * ref) : ref_(ref) - {} + { + } const char * Reference::name() const { - return git_reference_name(ref_); + return git_reference_name(ptr()); } - git_ref_t Reference::type() const + git_reference_t Reference::type() const { - return git_reference_type(ref_); + return git_reference_type(ptr()); } - git_oid const * Reference::target() const + git_oid const & Reference::target() const { - return git_reference_target(ref_); + assert(type() == GIT_REFERENCE_DIRECT); + return *git_reference_target(ptr()); } const char * Reference::symbolic_target() const { - return git_reference_symbolic_target(ref_); + 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 new file mode 100644 index 0000000..a024701 --- /dev/null +++ b/src/remote.cpp @@ -0,0 +1,64 @@ +#include "git2cpp/remote.h" + +#include + +namespace git +{ + const char * Remote::url() const + { + return git_remote_url(remote_.get()); + } + + const char * Remote::pushurl() const + { + 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 bd76b71..bb59846 100644 --- a/src/repo.cpp +++ b/src/repo.cpp @@ -1,112 +1,161 @@ -extern "C" -{ -#include +#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 +#include +#include #include +#include +#include #include -#include -} +#include +#include -#include "git2cpp/repo.h" -#include "git2cpp/error.h" +#include namespace git { - namespace + const Repository::init_tag Repository::init; + + Repository::Repository(git_repository * repo) + : repo_(repo) { - int write_branch_name(const char * name, git_branch_t, void * payload) - { - std::vector * out = reinterpret_cast *>(payload); - out->emplace_back(name); - return 0; - } + } + + Repository::Repository(const char * dir) + { + git_repository * repo; + if (git_repository_open_ext(&repo, dir, 0, nullptr)) + throw repository_open_error(dir); + repo_.reset(repo); } Repository::Repository(std::string const & dir) + : Repository(dir.c_str()) { - if (git_repository_open_ext(&repo_, dir.c_str(), 0, NULL)) - throw repository_open_error(); } - Repository::Repository(std::string const & dir, init_tag) + Repository::Repository(const char * dir, init_tag) { - if (git_repository_init(&repo_, dir.c_str(), 0) < 0) + git_repository * repo; + if (git_repository_init(&repo, dir, false) < 0) throw repository_init_error(dir); + repo_.reset(repo); } - Repository::Repository(std::string const & dir, init_tag, - git_repository_init_options opts) + Repository::Repository(std::string const & dir, init_tag tag) + : Repository(dir.c_str(), tag) + {} + + Repository::Repository(const char * dir, init_tag, git_repository_init_options opts) { - if (git_repository_init_ext(&repo_, dir.c_str(), &opts) < 0) + git_repository * repo; + if (git_repository_init_ext(&repo, dir, &opts) < 0) throw repository_init_error(dir); + repo_.reset(repo); } - Repository::~Repository() + 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) { - git_repository_free(repo_); + 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_); + return git_repository_is_bare(repo_.get()) != 0; } Signature Repository::signature() const { - return Signature(repo_); + return Signature(repo_.get()); } - Commit Repository::commit_lookup(git_oid const * oid) const + Commit Repository::commit_lookup(git_oid const & oid) const { - return Commit(oid, repo_); + git_commit * commit; + if (git_commit_lookup(&commit, repo_.get(), &oid)) + throw commit_lookup_error(oid); + else + return {commit, *this}; } - Tree Repository::tree_lookup(git_oid const * oid) const + Tree Repository::tree_lookup(git_oid const & oid) const { - return Tree(oid, repo_); + git_tree * tree; + if (git_tree_lookup(&tree, repo_.get(), &oid)) + throw tree_lookup_error(oid); + else + return {tree, *this}; } - Tag Repository::tag_lookup(git_oid const * oid) const + Tag Repository::tag_lookup(git_oid const & oid) const { - return Tag(oid, repo_); + git_tag * tag; + if (git_tag_lookup(&tag, repo_.get(), &oid)) + throw tag_lookup_error(oid); + else + return Tag(tag); } - Blob Repository::blob_lookup(git_oid const * oid) const + Blob Repository::blob_lookup(git_oid const & oid) const { - return Blob(oid, repo_); + git_blob * blob; + if (git_blob_lookup(&blob, repo_.get(), &oid)) + throw blob_lookup_error(oid); + else + return Blob(blob); } Revspec Repository::revparse(const char * spec) const { git_revspec revspec; - if (git_revparse(&revspec, repo_, spec)) + if (git_revparse(&revspec, repo_.get(), spec)) throw revparse_error(spec); - return Revspec(revspec); + else + return {revspec, *this}; } Revspec Repository::revparse_single(const char * spec) const { git_object * obj; - if (git_revparse_single(&obj, repo_, spec) < 0) + if (git_revparse_single(&obj, repo_.get(), spec) < 0) throw revparse_error(spec); - return Revspec(obj); + return Revspec(obj, *this); } Index Repository::index() const { - return Index(repo_); + return Index(repo_.get()); } Odb Repository::odb() const { - return Odb(repo_); + return Odb(repo_.get()); } git_status_t Repository::file_status(const char * filepath) const { + static_assert(sizeof(git_status_t) == sizeof(unsigned int), "sizeof(git_status_t) != sizeof(unsigned int)"); git_status_t res; - switch (git_status_file(reinterpret_cast(&res), repo_, filepath)) + switch (git_status_file(reinterpret_cast(&res), repo_.get(), filepath)) { case GIT_ENOTFOUND: throw file_not_found_error(filepath); @@ -119,87 +168,216 @@ namespace git return res; } - std::vector Repository::branches() const + struct branch_iterator { - std::vector res; - git_branch_foreach ( repo_ - , GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE - , write_branch_name - , &res - ); + branch_iterator(git_repository * repo, branch_type type) + : type_(convert(type)) + { + git_branch_iterator_new(&base_, repo, type_); + ++(*this); + } + + ~branch_iterator() + { + git_branch_iterator_free(base_); + } + + explicit operator bool() const { return internal::has_value(ref_); } + + void operator++() + { + git_branch_t type; + git_reference * ref; + switch (git_branch_next(&ref, &type, base_)) + { + case GIT_OK: + assert(type & type_); + internal::emplace(ref_, ref); + break; + case GIT_ITEROVER: + ref_ = internal::none; + break; + default: + ref_ = internal::none; + throw std::logic_error("unknown git_branch_next error"); + } + } + + Reference & operator*() + { + return *ref_; + } + + Reference * operator->() + { + return &*ref_; + } + + private: + static git_branch_t convert(branch_type t) + { + switch (t) + { + case branch_type::LOCAL: + return GIT_BRANCH_LOCAL; + case branch_type::REMOTE: + return GIT_BRANCH_REMOTE; + case branch_type::ALL: + return GIT_BRANCH_ALL; + } + throw std::logic_error("invalid branch type"); + } + + private: + git_branch_t type_; + git_branch_iterator * base_; + internal::optional ref_; + }; + + 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) + { + 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; + if (git_reference_dwim(&ref, repo_.get(), shorthand) == GIT_OK) + return Reference(ref); + else + return Reference(); + } + + AnnotatedCommit Repository::annotated_commit_from_ref(Reference const& ref) const + { + git_annotated_commit * commit; + const auto err = git_annotated_commit_from_ref(&commit, repo_.get(), ref.ptr()); + assert(err == GIT_OK); + return AnnotatedCommit(commit); + } + + AnnotatedCommit Repository::annotated_commit_lookup(git_oid const& id) const + { + git_annotated_commit * commit; + const auto err = git_annotated_commit_lookup(&commit, repo_.get(), &id); + assert(err == GIT_OK); + return AnnotatedCommit(commit); + } + RevWalker Repository::rev_walker() const { - return RevWalker(repo_); + git_revwalk * walker; + if (git_revwalk_new(&walker, repo_.get())) + throw revwalk_new_error(); + else + return {walker, *this}; } - git_oid Repository::merge_base(Revspec::Range const & range) const + git_oid Repository::merge_base(git_oid const & a, git_oid const & b) const { git_oid res; - if (git_merge_base(&res, repo_, range.from.id(), range.to.id())) - throw merge_base_error(); + if (git_merge_base(&res, repo_.get(), &a, &b)) + throw merge_base_error(a, b); return res; } + git_oid Repository::merge_base(Revspec::Range const & range) const + { + return merge_base(range.from.id(), range.to.id()); + } + Reference Repository::head() const { git_reference * head; - auto error = git_repository_head(&head, repo_); - if (error == GIT_EUNBORNBRANCH) + switch (git_repository_head(&head, repo_.get())) + { + case GIT_OK: + return Reference(head); + case GIT_EUNBORNBRANCH: throw non_existing_branch_error(); - else if (error == GIT_ENOTFOUND) + case GIT_ENOTFOUND: throw missing_head_error(); - else if (error) + default: throw unknown_get_current_branch_error(); - else - return Reference(head); + } } Reference Repository::ref(const char * name) const { git_reference * ref; - git_reference_lookup(&ref, repo_, name); + git_reference_lookup(&ref, repo_.get(), name); return Reference(ref); } - Status Repository::status(git_status_options const & opts) const + Status Repository::status(Status::Options const & opts) const + { + return Status(repo_.get(), opts); + } + + git_repository_state_t Repository::state() const { - return Status(repo_, opts); + return static_cast(git_repository_state(repo_.get())); } StrArray Repository::reference_list() const { git_strarray str_array; - git_reference_list(&str_array, repo_); + git_reference_list(&str_array, repo_.get()); return StrArray(str_array); } - int Repository::submodule_lookup(git_submodule *& sm, const char * name) const + Submodule Repository::submodule_lookup(const char * name) const { - return git_submodule_lookup(&sm, repo_, name); + git_submodule * sm; + if (git_submodule_lookup(&sm, repo_.get(), name)) + throw submodule_lookup_error(name); + else + return {sm, repo_.get()}; } const char * Repository::path() const { - return git_repository_path(repo_); + return git_repository_path(repo_.get()); } const char * Repository::workdir() const { - return git_repository_workdir(repo_); + return git_repository_workdir(repo_.get()); } git_oid Repository::create_commit(const char * update_ref, Signature const & author, Signature const & commiter, - const char * message_encoding, const char * message, - Tree const & tree) + Tree const & tree, + const char * message_encoding) { git_oid res; - if (git_commit_create_v(&res, repo_, update_ref, + if (git_commit_create_v(&res, repo_.get(), update_ref, author.ptr(), commiter.ptr(), message_encoding, message, tree.ptr(), 0)) @@ -212,13 +390,13 @@ namespace git git_oid Repository::create_commit(const char * update_ref, Signature const & author, Signature const & commiter, - const char * message_encoding, const char * message, Tree const & tree, - Commit const & parent) + Commit const & parent, + const char * message_encoding) { git_oid res; - if (git_commit_create_v(&res, repo_, update_ref, + if (git_commit_create_v(&res, repo_.get(), update_ref, author.ptr(), commiter.ptr(), message_encoding, message, tree.ptr(), 1, parent.ptr())) @@ -228,15 +406,202 @@ namespace git return res; } - Object Repository::entry_to_object(git_tree_entry const * entry) const + git_oid Repository::amend_commit(Commit const & commit_to_amend, const char * update_ref, const char * message, Tree const & tree) { - git_object * obj; - git_tree_entry_to_object(&obj, repo_, entry); - return Object(obj); + 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) + { + git_object * obj; + git_tree_entry_to_object(&obj, repo_ptr, entry); + return Object(obj, repo); + } + } + + Object Repository::entry_to_object(Tree::BorrowedEntry entry) const + { + return tree_entry_to_object(entry.ptr(), *this, repo_.get()); + } + + Object Repository::entry_to_object(Tree::OwnedEntry entry) const + { + return tree_entry_to_object(entry.ptr(), *this, repo_.get()); } Object revparse_single(Repository const & repo, const char * spec) { return std::move(*repo.revparse_single(spec).single()); } + + Diff Repository::diff(Tree & a, Tree & b, git_diff_options const & opts) const + { + git_diff * diff; + auto op_res = git_diff_tree_to_tree(&diff, repo_.get(), a.ptr(), b.ptr(), &opts); + assert(op_res == 0); + return Diff(diff); + } + + Diff Repository::diff_to_index(Tree & t, git_diff_options const & opts) const + { + git_diff * diff; + auto op_res = git_diff_tree_to_index(&diff, repo_.get(), t.ptr(), nullptr, &opts); + assert(op_res == 0); + return Diff(diff); + } + + Diff Repository::diff_to_workdir(Tree & t, git_diff_options const & opts) const + { + git_diff * diff; + auto op_res = git_diff_tree_to_workdir(&diff, repo_.get(), t.ptr(), &opts); + assert(op_res == 0); + return Diff(diff); + } + + Diff Repository::diff_to_workdir_with_index(Tree & t, git_diff_options const & opts) const + { + git_diff * diff; + auto op_res = git_diff_tree_to_workdir_with_index(&diff, repo_.get(), t.ptr(), &opts); + assert(op_res == 0); + return Diff(diff); + } + + Diff Repository::diff_index_to_workdir(git_diff_options const & opts) const + { + git_diff * diff; + auto op_res = git_diff_index_to_workdir(&diff, repo_.get(), nullptr, &opts); + assert(op_res == 0); + return Diff(diff); + } + + void Repository::reset_default(Commit const & commit, git_strarray const & pathspecs) + { + auto op_res = git_reset_default(repo_.get(), reinterpret_cast(const_cast(commit.ptr())), const_cast(&pathspecs)); + assert(op_res == GIT_OK); + } + + void Repository::file_diff(std::string const & old_path, git_oid const & old_id, + std::string const & new_path, git_oid const & new_id, + FileDiffHandler & diff_handler) const + { + auto old_file = blob_lookup(old_id); + auto new_file = blob_lookup(new_id); + git_diff_options options = GIT_DIFF_OPTIONS_INIT; + auto data_callback = [](git_diff_delta const *, git_diff_hunk const *, git_diff_line const * line, void * payload) { + auto handler = reinterpret_cast(payload); + handler->line(*line); + return 0; + }; + auto op_res = git_diff_blobs(old_file.ptr(), old_path.c_str(), new_file.ptr(), new_path.c_str(), + &options, nullptr, nullptr, nullptr, data_callback, &diff_handler); + assert(op_res == GIT_OK); + } + + StrArray Repository::remotes() const + { + git_strarray result; + auto op_res = git_remote_list(&result, repo_.get()); + assert(op_res == GIT_OK); + return StrArray(result); + } + + Remote Repository::remote(const char * name) const + { + git_remote * remote; + if (git_remote_lookup(&remote, repo_.get(), name)) + throw remote_lookup_error(name); + else + return Remote(remote); + } + + Remote Repository::create_remote(const char * name, const char * url) + { + git_remote * remote; + if (git_remote_create(&remote, repo_.get(), name, url)) + throw remote_create_error(name, url); + else + return Remote(remote); + } + + void Repository::delete_remote(const char * name) + { + if (git_remote_delete(repo_.get(), name)) + throw remote_delete_error(name); + } + + internal::optional Repository::rename_remote(const char * old_name, const char * new_name) + { + git_strarray problems {}; + if (git_remote_rename(&problems, repo_.get(), old_name, new_name)) + return StrArray(problems); + else + return internal::none; + } + + void Repository::set_url(const char * name, const char * url) + { + if (git_remote_set_url(repo_.get(), name, url)) + throw remote_set_url_error(name, url); + } + + void Repository::set_pushurl(const char * name, const char * url) + { + if (git_remote_set_pushurl(repo_.get(), name, url)) + throw remote_set_pushurl_error(name, url); + } + + int Repository::checkout_tree(Commit const& commit, git_checkout_options const& options) + { + 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; + if (git_repository_discover(&buf, start_path, 0, nullptr)) + return internal::none; + return std::string(buf.ptr, buf.size); + } + + void Repository::Destroy::operator() (git_repository* repo) const + { + git_repository_free(repo); + } } diff --git a/src/revspec.cpp b/src/revspec.cpp index 15b0808..ba3b9cd 100644 --- a/src/revspec.cpp +++ b/src/revspec.cpp @@ -2,21 +2,23 @@ namespace git { - Revspec::Revspec(git_object * single) - : flags_(GIT_REVPARSE_SINGLE) - , revspec_(single) - {} + Revspec::Revspec(git_object * single, Repository const & repo) + : flags_(GIT_REVSPEC_SINGLE) + , revspec_(single, repo) + { + } - Revspec::Revspec(git_revspec const & revspec) + Revspec::Revspec(git_revspec const & revspec, Repository const & repo) : flags_(revspec.flags) - , revspec_((revspec.flags & GIT_REVPARSE_SINGLE) - ? Range(revspec.from) - : Range(revspec)) - {} + , revspec_((revspec.flags & GIT_REVSPEC_SINGLE) + ? Range(revspec.from, repo) + : Range(revspec, repo)) + { + } Object * Revspec::single() { - if (flags_ & GIT_REVPARSE_SINGLE) + if (flags_ & GIT_REVSPEC_SINGLE) return &revspec_.from; else return nullptr; @@ -24,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_; @@ -35,4 +37,3 @@ namespace git return flags_; } } - diff --git a/src/revwalker.cpp b/src/revwalker.cpp index fa584ee..692666b 100644 --- a/src/revwalker.cpp +++ b/src/revwalker.cpp @@ -1,53 +1,60 @@ -extern "C" -{ -#include -} - -#include "git2cpp/error.h" #include "git2cpp/revwalker.h" +#include "git2cpp/error.h" #include "git2cpp/repo.h" +#include + namespace git { - RevWalker::RevWalker(git_repository * repo) + namespace revwalker + { + namespace sorting + { + const type none(GIT_SORT_NONE); + const type topological(GIT_SORT_TOPOLOGICAL); + const type time(GIT_SORT_TIME); + const type reverse(GIT_SORT_REVERSE); + } + } + + void RevWalker::Destroy::operator()(git_revwalk* walker) const { - if (git_revwalk_new(&walker_, repo)) - throw revwalk_new_error(); + git_revwalk_free(walker); } - RevWalker::~RevWalker() + void RevWalker::sort(revwalker::sorting::type s) { - git_revwalk_free(walker_); + git_revwalk_sorting(walker_.get(), s.value()); } - void RevWalker::sort(int sorting) + void RevWalker::simplify_first_parent() { - git_revwalk_sorting(walker_, sorting); + git_revwalk_simplify_first_parent(walker_.get()); } void RevWalker::push_head() const { - if (git_revwalk_push_head(walker_)) + if (git_revwalk_push_head(walker_.get())) throw invalid_head_error(); } - void RevWalker::hide(git_oid const * obj) const + void RevWalker::hide(git_oid const & obj) const { - if (git_revwalk_hide(walker_, obj)) + if (git_revwalk_hide(walker_.get(), &obj)) throw non_commit_object_error(obj); } - void RevWalker::push(git_oid const * obj) const + void RevWalker::push(git_oid const & obj) const { - if (git_revwalk_push(walker_, obj)) + if (git_revwalk_push(walker_.get(), &obj)) throw non_commit_object_error(obj); } - Commit RevWalker::next(Repository const & repo) const + Commit RevWalker::next() const { git_oid oid; - if (git_revwalk_next(&oid, walker_) == 0) - return repo.commit_lookup(&oid); + if (git_revwalk_next(&oid, walker_.get()) == 0) + return repo_->commit_lookup(oid); else return Commit(); } @@ -55,10 +62,9 @@ namespace git bool RevWalker::next(char * id_buffer) const { git_oid oid; - bool valid = (git_revwalk_next(&oid, walker_) == 0); + bool valid = (git_revwalk_next(&oid, walker_.get()) == 0); if (valid) git_oid_fmt(id_buffer, &oid); return valid; } } - diff --git a/src/signature.cpp b/src/signature.cpp index b72daac..8677a95 100644 --- a/src/signature.cpp +++ b/src/signature.cpp @@ -1,26 +1,30 @@ -extern "C" -{ #include -} -#include "git2cpp/signature.h" #include "git2cpp/error.h" +#include "git2cpp/signature.h" + +#include namespace git { Signature::Signature(git_repository * repo) { - if (git_signature_default(&sig_, repo) < 0) + git_signature * sig; + if (git_signature_default(&sig, repo) != GIT_OK) throw signature_create_error(); + sig_.reset(sig); } Signature::Signature(const char * name, const char * email, git_time_t time, int offset) { - git_signature_new(&sig_, name, email, time, offset); + git_signature * sig; + if (git_signature_new(&sig, name, email, time, offset) != GIT_OK) + throw signature_create_error(); + sig_.reset(sig); } - Signature::~Signature() + void Signature::Destroy::operator()(git_signature* sig) const { - git_signature_free(sig_); + git_signature_free(sig); } } diff --git a/src/status.cpp b/src/status.cpp index 9dcee88..0f76bcc 100644 --- a/src/status.cpp +++ b/src/status.cpp @@ -5,23 +5,101 @@ namespace git { size_t Status::entrycount() const { - return git_status_list_entrycount(status_); - } + return git_status_list_entrycount(status_.get()); + } + + git_status_entry const & Status::operator[](size_t i) const + { + if (auto res = git_status_byindex(status_.get(), i)) + return *res; + else + throw error_t("status entry index out of bounds: " + std::to_string(i)); + } + + namespace + { + git_status_show_t convert(Status::Options::Show show) + { + switch (show) + { + case Status::Options::Show::IndexOnly: + return GIT_STATUS_SHOW_INDEX_ONLY; + case Status::Options::Show::WorkdirOnly: + return GIT_STATUS_SHOW_WORKDIR_ONLY; + default: + return GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + } + } + } + + Status::Options::Options(Show show, Sort sort) + : opts_(GIT_STATUS_OPTIONS_INIT) + { + opts_.show = convert(show); + + switch (sort) + { + case Sort::CaseSensitively: + opts_.flags |= GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + break; + case Sort::CaseInsensitively: + opts_.flags |= GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + break; + } + } + + void Status::Options::set_pathspec(char ** ptr, size_t size) + { + opts_.pathspec.strings = ptr; + opts_.pathspec.count = size; + } + + Status::Options & Status::Options::include_untracked() + { + opts_.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + return *this; + } + + Status::Options & Status::Options::exclude_untracked() + { + opts_.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; + return *this; + } + + Status::Options & Status::Options::recurse_untracked_dirs() + { + opts_.flags |= GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + return *this; + } - git_status_entry const * Status::operator[] (size_t i) const + Status::Options & Status::Options::exclude_submodules() { - return git_status_byindex(status_, i); + opts_.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + return *this; } - Status::Status(git_repository * repo, git_status_options const & opts) + Status::Options & Status::Options::include_ignored() { - if (git_status_list_new(&status_, repo, &opts)) + opts_.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; + return *this; + } + + Status::Options & Status::Options::renames_head_to_index() + { + opts_.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + return *this; + } + + Status::Status(git_repository * repo, Options const & opts) + { + git_status_list * status; + if (git_status_list_new(&status, repo, opts.raw())) throw get_status_error(); + status_.reset(status); } - Status::~Status() + void Status::Destroy::operator()(git_status_list* status) const { - git_status_list_free(status_); + git_status_list_free(status); } } - diff --git a/src/submodule.cpp b/src/submodule.cpp new file mode 100644 index 0000000..9136ac8 --- /dev/null +++ b/src/submodule.cpp @@ -0,0 +1,41 @@ +#include "git2cpp/submodule.h" + +#include + +#include "git2cpp/error.h" + +namespace git +{ + Submodule::Submodule(git_submodule * sm, git_repository * repo) + : sm_(sm) + , repo_(repo) + { + } + + void Submodule::Destroy::operator()(git_submodule* sm) const + { + git_submodule_free(sm); + } + + Submodule::status Submodule::get_status() const + { + unsigned int res; + if (auto err = git_submodule_status(&res, repo_, git_submodule_name(sm_.get()), git_submodule_ignore(sm_.get()))) + throw error_t("git_submodule_status failed with error code " + std::to_string(err)); + else + return static_cast(res); + } + + namespace + { + unsigned int to_raw(Submodule::status s) + { + return static_cast(s); + } + } + + bool contains(Submodule::status set, Submodule::status element) + { + return to_raw(set) & to_raw(element); + } +} diff --git a/src/tag.cpp b/src/tag.cpp index 3e6b0f5..dd5b4fc 100644 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -1,40 +1,52 @@ +#include "git2cpp/tag.h" +#include "git2cpp/object.h" + #include -#include "git2cpp/tag.h" -#include "git2cpp/error.h" +#include namespace git { - Tag::Tag(git_oid const * oid, git_repository * repo) + Tag::Tag(git_tag * tag) + : tag_(tag) { - if (git_tag_lookup(&tag_, repo, oid)) - throw tag_lookup_error(oid); } - Tag::~Tag() + void Tag::Destroy::operator()(git_tag* tag) const { - git_tag_free(tag_); + git_tag_free(tag); } - Object Tag::target() const + Object Tag::target(Repository const & repo) const { git_object * obj; - git_tag_target(&obj, tag_); - return Object(obj); + git_tag_target(&obj, tag_.get()); + return Object(obj, repo); + } + + git_oid const & Tag::target_id() const + { + 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_); + return git_tag_target_type(tag_.get()); } const char * Tag::name() const { - return git_tag_name(tag_); + return git_tag_name(tag_.get()); } const char * Tag::message() const { - return git_tag_message(tag_); + return git_tag_message(tag_.get()); } + + git_signature const * Tag::tagger() const + { + return git_tag_tagger(tag_.get()); + } + } diff --git a/src/threads_initializer.cpp b/src/threads_initializer.cpp deleted file mode 100644 index a670b0d..0000000 --- a/src/threads_initializer.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "git2cpp/threads_initializer.h" - -extern "C" -{ -#include -} - -namespace git -{ - ThreadsInitializer::ThreadsInitializer() - { - git_threads_init(); - } - - ThreadsInitializer::~ThreadsInitializer() - { - git_threads_shutdown(); - } -} - diff --git a/src/tree.cpp b/src/tree.cpp index 2ec2f36..c315d21 100644 --- a/src/tree.cpp +++ b/src/tree.cpp @@ -1,11 +1,94 @@ #include "git2cpp/tree.h" #include "git2cpp/error.h" +#include "git2cpp/repo.h" + +#include namespace git { - Tree::Tree(git_oid const * oid, git_repository *repo) + Tree::Tree(git_tree * tree, Repository const & repo) + : tree_(tree) + , repo_(&repo) + { + } + + void Tree::Destroy::operator()(git_tree* tree) const + { + git_tree_free(tree); + } + + int Tree::pathspec_match(uint32_t flags, Pathspec const & ps) + { + return git_pathspec_match_tree(nullptr, ptr(), flags, ps.ptr()); + } + + size_t Tree::entrycount() const + { + return git_tree_entrycount(ptr()); + } + + Tree::BorrowedEntry Tree::operator[](size_t i) const + { + return BorrowedEntry(git_tree_entry_byindex(ptr(), i)); + } + + Tree::BorrowedEntry Tree::operator[](std::string const & filename) const + { + if (auto entry = git_tree_entry_byname(ptr(), filename.c_str())) + return BorrowedEntry(entry); + else + throw file_not_found_error(filename.c_str()); + } + + Tree::OwnedEntry Tree::find(const char * path) const + { + git_tree_entry * res; + const auto status = git_tree_entry_bypath(&res, ptr(), path); + switch (status) + { + case GIT_OK: + return OwnedEntry(res, *repo_); + case GIT_ENOTFOUND: + throw file_not_found_error(path); + default: + throw error_t(internal::format("unknown error inside function: 'git_tree_entry_bypath': %d", status)); + } + } + + Tree::OwnedEntry::OwnedEntry(git_tree_entry * entry, Repository const & repo) + : entry_(entry) + , repo_(&repo) + { + } + + void Tree::OwnedEntry::Destroy::operator()(git_tree_entry* entry) const + { + git_tree_entry_free(entry); + } + + Tree Tree::OwnedEntry::to_tree() /* && */ + { + auto const & repo = *repo_; + return repo.entry_to_object(std::move(*this)).to_tree(); + } + + const char * Tree::BorrowedEntry::name() const + { + return git_tree_entry_name(entry_); + } + + git_oid const & Tree::BorrowedEntry::id() const + { + return *git_tree_entry_id(entry_); + } + + git_object_t Tree::BorrowedEntry::type() const + { + return git_tree_entry_type(entry_); + } + + git_filemode_t Tree::BorrowedEntry::filemode() const { - if (git_tree_lookup(&tree_, repo, oid)) - throw tree_lookup_error(oid); + return git_tree_entry_filemode(entry_); } } diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..a32334a --- /dev/null +++ b/test.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +#trace +# set -x +#verbose outputs each input line as is read +# set -v + +if [ $# -eq 0 ] ; then + echo "usage: test.sh read_only_repo [writable_repo]" 1>&2 + return 1 +fi + +REPO=$1 + +if [ ! -d $REPO ]; then + echo "repo $REPO not a directory" 1>&2 + exit 1 +fi + +CWD=${PWD} + +function test() +{ + local test_name="$1" + echo -e "**** test $test_name *********************************\n\n" + local bin="$CWD/examples/$test_name" + + shift + + $bin "$@" + + local exit_status + + exit_status=$? + + echo -e "\nexit status $exit_status \n\n" +} + +# read-only tests (repo shouldn't be bare) +pushd $REPO + +test branch-cpp +test diff-cpp +test commit-graph-generator-cpp . "$CWD/commit-graph.dot" +test log-cpp +test rev-list-cpp --topo-order HEAD +test rev-parse-cpp HEAD +test showindex-cpp +test status-cpp + +popd + +# write test (use libgit2/tests/resources/testrepo.git) + +RW_REPO=$2 + +if [ -z $RW_REPO ]; then + echo "no writable repo specified" + exit 0 +fi + +pushd $RW_REPO + +test cat-file-cpp -t a8233120f6ad708f843d861ce2b7228ec4e3dec6 +test cat-file-cpp -p a8233120f6ad708f843d861ce2b7228ec4e3dec6 +test general-cpp . + +popd